-
Notifications
You must be signed in to change notification settings - Fork 0
Scraps
Shell scripts are everywhere. If you need something done right now, you fire
up vim
, pound out a quick script, and run it. Problem solved.
But shell scripts are dangerous. Error handling is tricky, and there is
edge-case behaviour everywhere. Moreover, you are often running these things as
root
, right? What is to stop you from accidentally running rm -rf /
(written
as rm -rf "$SAFE_TO_DELETE_DIR"
in your script) and wiping your server?
Enter Deno. It is scriptish (roots in JavaScript), but not as much as bash
.
Unlike bash
, it is type checked and has reasonable and predicatable error
handling.
The most compelling feature to me is the sandbox. Even if I don't make my code 100% hack-proof, the sandbox lets me define bounds around what my program can or can't do. For the times when I can't test your code, this is very comforting.
Starting with something simple yet useful, this is an example of running a
bash
script using runner
.
const pg = group();
try {
console.log(
await runner(emptyInput(), stringOutput())(pg).run({
cmd: [
"/bin/bash",
"--login",
"-c",
"echo 'Hello, Deno.'",
],
}),
);
} finally {
pg.close();
}
Raw stdin
, stdout
, and stderr
from a process are streamed byte data. This
is a simple definition, but it is not very useful. We need to be able to
interpret data differently in different circumstances.
Streamed byte data is the fastest, so if we are just piping bytes from one
process to another, we would use BytesIterableOutput()
for stdout
of process
#1 and BytesIterableInput()
for stdin
of process #2.
If you have a small amount of data (it can be kept in memory),
proc.stringInput()
and proc.stringOutput()
let you work with string
data.
For text data that is too big to fit in memory, or if you just want to work with
real-time streamed text data, use proc.stringIterableInput()
and
proc.stringIterableOutput()
. There is some overhead associated with processing
streamed bytes into text lines, but this is how you will interact with process
input and output much of the time.
My goal in writing proc
was to put Deno process handling on par with bash
.
Simple bash
scripts are wonderful, but they tend to grow unwieldy over time as
things are added. I'd like to be able to replace some of my old bash
scripts
with something more robust, and Deno is the first scripting language I've found
that feels like it could work for this.
First, there is Deno's "Secure by default." This is huge when I am writing admin scripts, where if I make a mistake, I can wipe out a server. The ability to define security boundaries from the command-line is a game changer to me. Second, there is Deno's approach to package management, which means I can just import what I want and use it without required project infrastructure. I can just write a script and run it. Third, there is tight coupling with Typescript, which means I get strongly typed dynamic programming. I want someone to catch my mistakes, but I also want to code as fast as possible.
But there is still the nagging problem of the process API in Deno. It feels a little bit like I am dropping down into a poorly abstracted C library. It is hard to use processes correctly in Deno with this API. I find that I often end up leaking resources or - sometimes - leaving orphaned processes hanging around. However, when I use the Deno process API correctly, it is very reliable, has predictable behavior, and it is fast.
proc
provides a reasonable solution to the leaky resource problem and - at the
same time - redefines the API to be more in line with modern JavaScript. I hope
you find it useful and enjoyable!