proc 0.22.1
When I started this project, Deno was still young. My goal was to create a
better way to run child processes. I realized the Deno had the potential to be a
better version of Bash scripting. In its simplest form, a Deno script can run
standalone, without external configuration or compilation. A big selling point
is safety: decent type support and security-by-default. The complete lack of any
guardrails in Bash scripts, or really for any scripting languages commonly
available to system admins, is a lurking problem. These scripts often run with
root
privileges, and you can't always test them before you run them for the
first time on a production server. Ouch.
However, the young Deno lacked a lightweight, fluent way to run child processes - the one thing that Bash is exceedingly good at.
Fast forward a few years and a few rewrites. The library has become a way to
work with streaming data (files, IO, etc.) using JavaScript's standard
AsyncIterable
in place of streams. You can use map
, filter
, find
, and a whole bunch of
other methods - just like you would on an Array
- but they are streamed and
lazy. Errors work the way you expect them to. You can quickly process through
terrabytes of information using very little memory.
It also lets you run
child processes. Yeah, that part turned out really good.
It's easy. It's almost trivial. You can run processes concurrently. There is a
little more boilerplate than Bash, you know, because it uses Typescript syntax -
but it is really minimal and easy to read. Deno has improved their process
runner since the old days, but this is still better.
This project has been a labor of love. I use this every single day, so I know it works. I am now actively working toward the 1.0 release. Deno 2.0 has arrived, and the legacy version of the code has been removed. The last version that supports Deno 1.46.3 is 0.21.x. Version 0.22.0 forward is tested on Deno 2.0, though it will probably work for late versions of Deno 1.
If you happen to come across this project and wonder if it might be useful for you, know that I have been working on this for a couple of years now, and development and active support are ongoing. No warranties or promises, of course.
Usage
import { run } from "https://deno.land/x/proc@0.22.1/mod.ts";
A Simple Example
Run ls -la
as a child process. Decode stdout
as lines of text. Print to
console.
await run("ls", "-la").toStdout();
A Better Example
Don't worry about understanding everything in this example yet. This shows a
little of what is possible using proc
.
Given the text for War and Peace:
- Read the file into an
AsyncIterable
ofUint8Array
. - Uncompress it (the file is GZ'd).
- Convert to lowercase using JavaScript, because the JavaScript conversion is
more correct than the one in
tr
. grep
out all the words on word boundaries.tee
this into two streams (AsyncIterable
ofUint8Array
) of words.- Count the total number of words.
- Use
sort
withuniq
to count the unique words.
const [words1, words2] = read(
fromFileUrl(import.meta.resolve("./warandpeace.txt.gz")),
)
.transform(gunzip)
.lines
.map((line) => line.toLocaleLowerCase())
.run("grep", "-oE", "(\\w|')+") // grep out the words to individual lines
.tee();
const [uniqueWords, totalWords] = await Promise.all([
words1.run("sort").run("uniq").lines.count(),
words2.lines.count(),
]);
console.log(`Total: ${totalWords.toLocaleString()}`);
console.log(`Unique: ${uniqueWords.toLocaleString()}`);
Up to the point where we run Promise.all
, this is asynchronous, streaming,
lazily evaluated code. It is trivially running three child processes (grep
,
sort
, and uniq
), a DecompressionStream
transform, and in-process logic to
normalize to lower-case. This is all happening concurrently, mostly in parallel,
one buffer, one line, or one word at a time.