/deno-dzx

🦕 Deno shell tools inspired by zx.

Primary LanguageTypeScriptMIT LicenseMIT

dzx

Latest Build Coverage Issues License deno.land/x

Deno shell tools inspired by zx

Example

Warning This project is in a very experimental state. Many things are subject to change.

import {
  $,
  async,
  cd,
  fs,
  io,
  path,
} from "https://deno.land/x/dzx@0.4.0/mod.ts";

// Output stdout & stderr. Can be: true, false, 0, 1 or 2. Default is: 1
$.verbose = 2;
$.shell = "/usr/local/bin/zsh";

console.log(`Hello from ${$.blue.bold("dzx")}!`);

const branch = await $`git branch --show-current`;
await $`dep deploy --branch=${branch}`;

await Promise.all([
  $`deno lint`,
  $`deno fmt --check`,
  $`deno test --allow-all`,
]);

const name = "foo bar";
await $`mkdir ./tmp/${name}`; // Params will be quoted if required: /tmp/'foo bar'.

cd("tmp/foo bar");
console.log(Deno.cwd()); // ./tmp/foo bar

cd("tmp");
console.log(Deno.cwd()); // ./tmp

await async.delay(1000);
const basename = path.basename(import.meta.url);
const stdin = await io.readAll(Deno.stdin);
await fs.ensureDir("./tmp");

Content

Usage

dzx has different entry points.

  • ./mod.ts: Exports the shell and all std modules.

    import { $, cd, path } from "https://deno.land/x/dzx@0.4.0/mod.ts";
    // or
    import $ from "https://deno.land/x/dzx@0.4.0/mod.ts";
  • ./shell.ts: Exports only the shell.

    import { $ } from "https://deno.land/x/dzx@0.4.0/shell.ts";
    // or
    import $ from "https://deno.land/x/dzx@0.4.0/shell.ts";

Shell

The $ symbol (shell) is the main context of dzx.

Variables

  • $.shell: Set the shell that is used by $`command`. Default: /bin/bash
  • $.prefix: Command prefix. Default: set -euo pipefail;.
  • $.mainModule: The executed dzx script.
  • $.verbose: Enable debugging output (log shell commands and execution time).
  • $.stdout: Change stdout mode of $`command`. Can be "inherit", "piped", "null" or number. Default: "piped"
  • $.stderr: Change stderr mode of $`command`. Can be "inherit", "piped", "null" or number. Default: "piped"
  • $.args: Equivalent to Deno.args, but without the script name as first argument.
  • $.startTime: The execution start time in ms.
  • $.time: The time left since execution start (now() - $.startTime).
  • $.quote: Parser method that is used to safely quote strings. Used by: $`command`

Methods

  • $`command`: Executes a shell command and returns a Process instance. The Process class has several chainable methods.

    const count = parseInt(await $`ls -1 | wc -l`);
    console.log(`Files count: ${count}`);

    If the executed program was successful, an instance of ProcessOutput will be return.

    class ProcessOutput {
      readonly stdout: string;
      readonly stderr: string;
      readonly combined: string;
      readonly status: Deno.ProcessStatus;
      toString(): string;
    }

    If the executed program returns a non-zero exit code, a ProcessError will be thrown.

    The ProcessError class extends from the Error class and implements all properties and methods from ProcessOutput.

    try {
      await $`exit 1`;
    } catch (error) {
      console.log(`Exit code: ${error.status.code}`);
      console.log(`Error: ${error.stderr}`);
    }
  • $o`command`: Executes a shell command and only returns its trimmed stdout (without throwing an error)

    const stdout = await $o`pwd`);
    console.log(stdout); // -> '/home/code/project

    If the executed program returns a non-zero exit code, no error will be thrown. Either the failed processes stdout will be the return value, or an empty string will be returned by default

    const stdout = await $o`echo 'hello' >&2; exit 1;`);
    console.log(stdout); // -> ""
  • $e`command`: Executes a shell command and only returns its trimmed stderr (without throwing an error)

    const stderr = await $e`pwd`);
    console.log(stderr); // -> ""

    If the executed program returns a non-zero exit code, no error will be thrown. Either the failed processes stderr will be the return value, or an empty string will be returned by default

    const stderr = await $e`echo 'hello' >&2; exit 1;`);
    console.log(stderr); // -> "hello"
  • cd(): Change the current working directory. If path does not exist, an error is thrown. The path is always relative to the original cwd, unless the path is an absolute path or starts with ~ which indecates the home directory.

    console.log(Deno.cwd()); // --> /example/directory
    cd("foo/bar");
    console.log(Deno.cwd()); // --> /example/directory/foo/bar
    cd("foo/baz");
    console.log(Deno.cwd()); // --> /example/directory/foo/baz
    cd("/tmp");
    console.log(Deno.cwd()); // --> /tmp
    cd("~/Documents");
    console.log(Deno.cwd()); // --> [HOME]/Documents
  • quote`string`: The quote methods quotes safly a string. by default the shq package is used. Can be overidden with $.quote.

Process

Methods and properties of the Process class which implements Promise<ProcessOutput> and is returned by the $ method.

  • .pid: Returns the process id of the executed command.

    // Get the process id.
    const child = $`echo foo`;
    console.log("pid:", child.pid);
    await child;
  • .noThrow: If invoked the command doesn't throw an error if the command returns a none-zero exit code.

    // Don't throw an error for none-zero exit codes.
    const { status } = await $`exit 1`.noThrow;
  • .statusCode: Returns the status code of the executed command and calls .noThrow internally to catch the error and return the status code.

    // Get only the status code.
    const statusCode: number = await $`exit 1`.statusCode;
  • .stdout Returns Promise<string> and resolves with the stdout output.

    // Get only stdout.
    const foo: string = await $`echo foo; echo bar >&2`.stdout;
  • .stderr Returns Promise<string> and resolves with the stderr output.

    // Get only stderr.
    const bar: string = await $`echo foo; echo bar >&2`.stderr;
  • .retry(retries: number | RetryCallback) Retry the command n times if it fails.

    // Retry the command 3 times.
    await $`exit 1`.retry(3);
    
    // Retry the command 3 times using a callback handler.
    await $`exit 1`.retry(({ retries }: ProcessError) => retries < 3);
  • .delay(delay: number) Number of milliseconds to delay the retry of a failed command. Default: 500

    // Retry the command 3 times but wait 1sec before executing it again.
    await $`exit 1`.retry(3).delay(1000);
  • .timeout(timeout: number) Throws an error if the command takes longer than the provided timeout in milliseconds.

    // Kill the command if it takes longer than one second.
    await $`sleep 10`.timeout(1000);
  • .cwd(path: string) Set the cwd for the current process.

    // Change process cwd.
    await $`pwd`.cwd("examples");
  • .env(name: string, value: string | number | boolean | Record<string, unknown>) Defines an environment variable which is only set for the current process. If the value is an object, JSON.stringify(value) will be used as value for the environment variable.

    // Pass environment variable to process.
    await $`echo $FOO_BAR`.env("FOO_BAR", 123);
  • .kill(signal: Deno.Signal) Kills the running process.

    // Manually kill the command.
    const child = $`sleep 10`;
    setTimout(() => child.kill("SIGINT"), 100);
    await child;

Std modules

Note The ./mod.ts module exports several deno/std modules. If you don't need these modules you can just import the $ symbol from the ./shell.ts module.

  • $.[style]: Cliffy's color module. A chainable wrapper for Deno's std/fmt/colors module. Available on the global $ symbol.

    console.log($.blue.bold("Hello world!"));
  • async: Deno's std/async module.

    await async.delay(1000);
  • path: Deno's std/path module.

    const basename = path.basename(import.meta.url);
    const options: path.GlobToRegExpOptions = { os: "linux" };
    const regex: RegExp = path.globToRegExp("*.ts", options);
  • io: Deno's std/io module.

    const fileReader = await Deno.open("./example.txt");
    for await (let line of io.readLines(fileReader)) {
      console.log(line);
    }
  • streams: Deno's std/streams module.

    const data = await streams.readAll(Deno.stdin);
  • fs: Deno's std/fs module.

    fs.ensureDir("./tmp");
  • log: Deno's std/log module.

    log.info("Some info!");
  • flags: Deno's std/flags module.

    const args: flags.Args = flags.parse($.args);

Globals

Note Globals are mostly used by the cli. In most cases you don't need globals and you can just import all members from ./mod.ts or ./shell.ts.

When impoting ./globals.ts, all members exported by ./mod.ts are globally available.

import "https://deno.land/x/dzx@0.4.0/globals.ts";

cd("foo/bar");

await $`ls | wc -l`;

CLI

The CLI is a tool-chain and playground for dzx. It can execute scripts and provides some sub-commands like repl and eval where dzx is globally available.

Install

deno install --allow-all -f https://deno.land/x/dzx@0.4.0/dzx.ts

Commands

  • dzx [script] [...args]: Run a local or remote dzx script (optional in a web worker). If invoked without argument the repl will be started.

    dzx --worker ./example.ts
  • dzx bundle [script]: Bundle a dzx script to a standalone deno script. Can also read from stdin.

    dzx bundle ./example.ts > bundle.js
    deno run --allow-read --allow-env --allow-run bundle.js
  • dzx compile [...permissions] [script] [...script-arguments]: Compile an dzx script to a standalone binary. Can also read from stdin.

    dzx compile --allow-read --allow-env --allow-run ./example.ts --port 8080

    Note that when reading from stdin, you must separate "deno compile" flags (i.e. your permissions) from your "script args" with a double dash (--), i.e.

    cat ./example.ts | dzx compile --allow-read -- --port 8080

    It is also recommended when reading from stdin that you pass the --output flag with your permissions to specify the output file name, otherwise the compiled file will be emitted with a random value as its name.

  • dzx eval: Evaluate a dzx script from command line.

    dzx eval "console.log($.shell)"

    Eval can also read from stdin:

    echo "console.log($.shell)" | dzx eval
  • dzx repl: Start a dzx repl (Read eval print loop).

    The repl command starts a deno repl bootstrapped with the dzx runtime code.

  • dzx upgrade: Upgrade the dzx executable to latest or given version.

    Upgrade to latest version:

    dzx upgrade

    Upgrade to specific version:

    dzx upgrade --version 3.0.0

    List all available versions:

    dzx upgrade --list-versions

Execute scripts via cli

You can run a script with the cli by simply calling dzx followed by the filename dzx script.ts.

When a dzx script is executed via CLI, you don't need to import anything. All exports are automatically globally available. This applies also to commands like dzx eval "console.log($)".

To create an executable script, add next shebang at the beginning of your script:

#!/usr/bin/env dzx

After making your script executable,

chmod +x ./script.js

you can simply run it with:

./script.js

To enable typescript support in your IDE, you can optionally add a tripple slash reference to the top of the file.

#!/usr/bin/env dzx
/// <reference path="https://deno.land/x/dzx@0.4.0/globals.ts" />

Permissions

You can use dzx without installation by using next shebang. This also allows you to explicitly set the permissions for your script.

#!/usr/bin/env deno run --allow-run --allow-read --allow-env https://deno.land/x/dzx@0.4.0/dzx.ts
/// <reference path="https://deno.land/x/dzx@0.4.0/globals.ts" />

console.log(`Hello ${$.blue.bold("world")}!`);

Markdown

With dzx you can run the js/ts code blocks from a Markdown file as if they were a regular script. This is very convenient when you want to blend some nicely formatted documentation in with the actual steps of execution.

Give it a try by running:

dzx ./examples/markdown.md

Note See the markdown example for further documentation and notes.

Experimental

worker

Warning This is an exerminental feature. Permission flags doesn't support values currently. Read permissions are required by default.

If dzx is called with -w or --worker, the script is executed inside an isolated web worker. If enabled, you can pass explicit permissions directly to the dzx cli.

#!/usr/bin/env dzx --worker --allow-read
/// <reference path="https://deno.land/x/dzx@0.4.0/globals.ts" />

console.log(`Hello from ${$.blue.bold("worker")}!`);

Contributing

Any kind of contribution is very welcome!

License

MIT