SkillAgentSearch skills...

Dax

Cross-platform shell tools for Deno and Node.js inspired by zx.

Install / Use

/learn @dsherret/Dax
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

dax

JSR npm Version

<img src="src/assets/logo.svg" height="150px" alt="dax logo">

Cross-platform shell tools for Deno and Node.js inspired by zx.

Differences with zx

  1. Cross-platform shell.
    • Makes more code work on Windows.
    • Allows exporting the shell's environment to the current process.
    • Uses deno_task_shell's parser.
    • Has common commands built-in for better Windows support.
  2. Minimal globals or global configuration.
    • Only a default instance of $, but it's not mandatory to use this.
  3. No custom CLI.
  4. Good for application code in addition to use as a shell script replacement.
  5. Named after my cat.

Install

Deno:

# or skip and import directly from `jsr:@david/dax@<version>`
deno add jsr:@david/dax

Node:

npm install dax

Executing commands

#!/usr/bin/env -S deno run --allow-all
import $ from "@david/dax"; // "dax" in Node

// run a command
await $`echo 5`; // outputs: 5

// outputting to stdout and running a sub process
await $`echo 1 && deno run main.ts`;

// parallel
await Promise.all([
  $`sleep 1 ; echo 1`,
  $`sleep 2 ; echo 2`,
  $`sleep 3 ; echo 3`,
]);

Getting output

Get the stdout of a command (makes stdout "quiet"):

const result = await $`echo 1`.text();
console.log(result); // 1

Get the result of stdout as json (makes stdout "quiet"):

const result = await $`echo '{ "prop": 5 }'`.json();
console.log(result.prop); // 5

Get the result of stdout as bytes (makes stdout "quiet"):

const bytes = await $`gzip < file.txt`.bytes();
console.log(bytes);

Get the result of stdout as a list of lines (makes stdout "quiet"):

const result = await $`echo 1 && echo 2`.lines();
console.log(result); // ["1", "2"]

Get stderr's text:

const result = await $`deno eval "console.error(1)"`.text("stderr");
console.log(result); // 1

Working with a lower level result that provides more details:

const result = await $`deno eval 'console.log(1); console.error(2);'`
  .stdout("piped")
  .stderr("piped");
console.log(result.code); // 0
console.log(result.stdoutBytes); // Uint8Array(2) [ 49, 10 ]
console.log(result.stdout); // 1\n
console.log(result.stderr); // 2\n
const output = await $`echo '{ "test": 5 }'`.stdout("piped");
console.log(output.stdoutJson);

Getting the combined output:

const text = await $`deno eval 'console.log(1); console.error(2); console.log(3);'`
  .text("combined");

console.log(text); // 1\n2\n3\n

Exit codes

By default, commands will throw an error on non-zero exit code:

await $`exit 123`;
// Uncaught Error: Exited with code: 123
//    at CommandChild.pipedStdoutBuffer (...)

If you want to disable this behaviour, run a command with .noThrow():

const result = await $`exit 123`.noThrow();
console.log(result.code); // 123
// or only for certain exit codes
await $`exit 123`.noThrow(123);

Or handle the error case within the shell:

await $`failing_command || echo 'Errored!'`;

Note: if you want it to not throw by default, you can build a custom $ (see below).

Exit code helper

If you just want to get the exit code, you can use the .code() helper:

const code = await $`git diff --quiet`.code();

Piping

Piping stdout or stderr to a Deno.WriterSync:

await $`echo 1`.stdout(Deno.stderr);
await $`deno eval 'console.error(2);`.stderr(Deno.stdout);

Piping to a WritableStream:

await $`echo 1`.stdout(Deno.stderr.writable, { preventClose: true });
// or with a redirect
await $`echo 1 > ${someWritableStream}`;

To a file path:

await $`echo 1`.stdout($.path("data.txt"));
// or
await $`echo 1 > data.txt`;
// or
await $`echo 1 > ${$.path("data.txt")}`;

To a file:

using file = $.path("data.txt").openSync({ write: true, create: true });
await $`echo 1`.stdout(file);
// or
await $`echo 1 > ${file}`;

From one command to another:

const output = await $`echo foo && echo bar`
  .pipe($`grep foo`)
  .text();

// or using a pipe sequence
const output = await $`(echo foo && echo bar) | grep foo`
  .text();

Providing arguments to a command

Use an expression in a template literal to provide a single argument to a command:

const dirName = "some_dir";
await $`mkdir ${dirName}`; // executes as: mkdir some_dir

Arguments are escaped so strings with spaces get escaped and remain as a single argument:

const dirName = "Dir with spaces";
await $`mkdir ${dirName}`; // executes as: mkdir 'Dir with spaces'

Alternatively, provide an array for multiple arguments:

const dirNames = ["some_dir", "other dir"];
await $`mkdir ${dirNames}`; // executes as: mkdir some_dir 'other dir'

If you do not want to escape an argument in a template literal, you can opt out by using $.rawArg starting in 0.43.0:

const args = "arg1   arg2";
await $`echo ${$.rawArg(args)} ${args}`; // executes as: echo arg1 arg2 arg1   arg2

Alternatively, you can opt out completely by using $.raw:

const args = "arg1   arg2";
await $.raw`echo ${args}`; // executes as: echo arg1 arg2

// or escape a specific argument while using $.raw
await $.raw`echo ${$.escapeArg(args)} ${args}`; // executes as: echo "arg1  arg2" arg1 arg2

Providing stdout of one command to another is possible as follows:

// Note: This will read trim the last newline of the other command's stdout
const result = await $`echo 1`.stdout("piped"); // need to set stdout as piped for this to work
const finalText = await $`echo ${result}`.text();
console.log(finalText); // 1

...though it's probably more straightforward to just collect the output text of a command and provide that:

const result = await $`echo 1`.text();
const finalText = await $`echo ${result}`.text();
console.log(finalText); // 1

JavaScript objects to redirects

You can provide JavaScript objects to shell output redirects:

const buffer = new Uint8Array(2);
await $`echo 1 && (echo 2 > ${buffer}) && echo 3`; // 1\n3\n
console.log(buffer); // Uint8Array(2) [ 50, 10 ] (2\n)

Supported objects: Uint8Array, Path, WritableStream, any function that returns a WritableStream, any object that implements [$.symbols.writable](): WritableStream

Or input redirects:

// strings
const data = "my data in a string";
const bytes = await $`gzip < ${data}`;

// paths
const path = $.path("file.txt");
const bytes = await $`gzip < ${path}`;

// requests (this example does not make the request until after 5 seconds)
const request = $.request("https://plugins.dprint.dev/info.json")
  .showProgress(); // show a progress bar while downloading
const bytes = await $`sleep 5 && gzip < ${request}`.bytes();

Supported objects: string, Uint8Array, Path, RequestBuilder, ReadableStream, any function that returns a ReadableStream, any object that implements [$.symbols.readable](): ReadableStream

Providing stdin

await $`command`.stdin("inherit"); // default
await $`command`.stdin("null");
await $`command`.stdin(new Uint8Array[1, 2, 3, 4]());
await $`command`.stdin(someReaderOrReadableStream);
await $`command`.stdin($.path("data.json"));
await $`command`.stdin($.request("https://plugins.dprint.dev/info.json"));
await $`command`.stdinText("some value");

Or using a redirect:

await $`command < ${$.path("data.json")}`;

Streaming API

Awaiting a command will get the CommandResult, but calling .spawn() on a command without await will return a CommandChild. This has some methods on it to get web streams of stdout and stderr of the executing command if the corresponding pipe is set to "piped". These can then be sent wherever you'd like, such as to the body of a $.request or another command's stdin.

For example, the following will output 1, wait 2 seconds, then output 2 to the current process' stderr:

const child = $`echo 1 && sleep 1 && echo 2`.stdout("piped").spawn();
await $`deno eval 'await Deno.stdin.readable.pipeTo(Deno.stderr.writable);'`
  .stdin(child.stdout());

Setting environment variables

Done via the .env(...) method:

// outputs: 1 2 3 4
await $`echo $var1 $var2 $var3 $var4`
  .env("var1", "1")
  .env("var2", "2")
  // or use object syntax
  .env({
    var3: "3",
    var4: "4",
  });

Setting cwd for command

Use .cwd("new_cwd_goes_here"):

// outputs that it's in the someDir directory
await $`deno eval 'console.log(Deno.cwd());'`.cwd("./someDir");

Silencing a command

Makes a command not output anything to stdout and stderr.

await $`echo 5`.quiet();
await $`echo 5`.quiet("stdout"); // or just stdout
await $`echo 5`.quiet("stderr"); // or just stderr

Output a command before executing it

The following code:

const text = "example";
await $`echo ${text}`.printCommand();

Outputs the following (with the command text in blue):

> echo example
example

Enabling on a $

Like with any default in Dax, you can build a new $ turning on this option so this will occur with all commands (see Custom $). Alternatively, you can enable this globally by calling $.setPrintCommand(true);.

$.setPrintCommand(true);

const text = "example";
await $`echo ${text}`; // will output `> echo example` before running the command

Timeout a command

This will exit with code 124 after 1 second.

// timeout a command after a specified time
await $`echo 1 && sleep 100 && echo 2`.timeout("1s");

Aborting a command

Instead of awaiting the template literal, you can get a command child by calling the .spawn() method:

const child = $`echo 1 && sleep 100 && echo 2`.spawn();

await doSomeOt
View on GitHub
GitHub Stars1.4k
CategoryDevelopment
Updated5d ago
Forks38

Languages

TypeScript

Security Score

95/100

Audited on Mar 31, 2026

No findings