{"id":13494591,"url":"https://github.com/dsherret/dax","last_synced_at":"2026-02-02T05:06:27.151Z","repository":{"id":44422666,"uuid":"512290351","full_name":"dsherret/dax","owner":"dsherret","description":"Cross-platform shell tools for Deno and Node.js inspired by zx.","archived":false,"fork":false,"pushed_at":"2024-09-09T22:46:36.000Z","size":1676,"stargazers_count":1051,"open_issues_count":28,"forks_count":35,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-10-29T15:48:34.768Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dsherret.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-09T21:37:27.000Z","updated_at":"2024-10-29T12:31:45.000Z","dependencies_parsed_at":"2023-11-15T00:28:40.124Z","dependency_job_id":"149ec173-aa6e-46c6-98dc-4b24e4e25f96","html_url":"https://github.com/dsherret/dax","commit_stats":{"total_commits":250,"total_committers":21,"mean_commits":"11.904761904761905","dds":"0.43200000000000005","last_synced_commit":"52584e553381813f341e75c2e6bfc1a8a895b652"},"previous_names":[],"tags_count":55,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsherret%2Fdax","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsherret%2Fdax/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsherret%2Fdax/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsherret%2Fdax/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dsherret","download_url":"https://codeload.github.com/dsherret/dax/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247202542,"owners_count":20900798,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T19:01:26.346Z","updated_at":"2026-02-02T05:06:27.145Z","avatar_url":"https://github.com/dsherret.png","language":"TypeScript","funding_links":[],"categories":["others","TypeScript"],"sub_categories":[],"readme":"# dax\n\n[![JSR](https://jsr.io/badges/@david/dax)](https://jsr.io/@david/dax)\n[![npm Version](https://img.shields.io/npm/v/dax.svg?style=flat)](http://www.npmjs.com/package/dax)\n\n\u003cimg src=\"src/assets/logo.svg\" height=\"150px\" alt=\"dax logo\"\u003e\n\nCross-platform shell tools for Deno and Node.js inspired by [zx](https://github.com/google/zx).\n\n## Differences with zx\n\n1. Cross-platform shell.\n   - Makes more code work on Windows.\n   - Allows exporting the shell's environment to the current process.\n   - Uses [deno_task_shell](https://github.com/denoland/deno_task_shell)'s parser.\n   - Has common commands built-in for better Windows support.\n1. Minimal globals or global configuration.\n   - Only a default instance of `$`, but it's not mandatory to use this.\n1. No custom CLI.\n1. Good for application code in addition to use as a shell script replacement.\n1. Named after my cat.\n\n## Install\n\nDeno:\n\n```sh\n# or skip and import directly from `jsr:@david/dax@\u003cversion\u003e`\ndeno add jsr:@david/dax\n```\n\nNode:\n\n```sh\nnpm install dax\n```\n\n## Executing commands\n\n```ts\n#!/usr/bin/env -S deno run --allow-all\nimport $ from \"@david/dax\"; // \"dax\" in Node\n\n// run a command\nawait $`echo 5`; // outputs: 5\n\n// outputting to stdout and running a sub process\nawait $`echo 1 \u0026\u0026 deno run main.ts`;\n\n// parallel\nawait Promise.all([\n  $`sleep 1 ; echo 1`,\n  $`sleep 2 ; echo 2`,\n  $`sleep 3 ; echo 3`,\n]);\n```\n\n### Getting output\n\nGet the stdout of a command (makes stdout \"quiet\"):\n\n```ts\nconst result = await $`echo 1`.text();\nconsole.log(result); // 1\n```\n\nGet the result of stdout as json (makes stdout \"quiet\"):\n\n```ts\nconst result = await $`echo '{ \"prop\": 5 }'`.json();\nconsole.log(result.prop); // 5\n```\n\nGet the result of stdout as bytes (makes stdout \"quiet\"):\n\n```ts\nconst bytes = await $`gzip \u003c file.txt`.bytes();\nconsole.log(bytes);\n```\n\nGet the result of stdout as a list of lines (makes stdout \"quiet\"):\n\n```ts\nconst result = await $`echo 1 \u0026\u0026 echo 2`.lines();\nconsole.log(result); // [\"1\", \"2\"]\n```\n\nGet stderr's text:\n\n```ts\nconst result = await $`deno eval \"console.error(1)\"`.text(\"stderr\");\nconsole.log(result); // 1\n```\n\nWorking with a lower level result that provides more details:\n\n```ts\nconst result = await $`deno eval 'console.log(1); console.error(2);'`\n  .stdout(\"piped\")\n  .stderr(\"piped\");\nconsole.log(result.code); // 0\nconsole.log(result.stdoutBytes); // Uint8Array(2) [ 49, 10 ]\nconsole.log(result.stdout); // 1\\n\nconsole.log(result.stderr); // 2\\n\nconst output = await $`echo '{ \"test\": 5 }'`.stdout(\"piped\");\nconsole.log(output.stdoutJson);\n```\n\nGetting the combined output:\n\n```ts\nconst text = await $`deno eval 'console.log(1); console.error(2); console.log(3);'`\n  .text(\"combined\");\n\nconsole.log(text); // 1\\n2\\n3\\n\n```\n\n### Exit codes\n\nBy default, commands will throw an error on non-zero exit code:\n\n```ts\nawait $`exit 123`;\n// Uncaught Error: Exited with code: 123\n//    at CommandChild.pipedStdoutBuffer (...)\n```\n\nIf you want to disable this behaviour, run a command with `.noThrow()`:\n\n```ts\nconst result = await $`exit 123`.noThrow();\nconsole.log(result.code); // 123\n// or only for certain exit codes\nawait $`exit 123`.noThrow(123);\n```\n\nOr handle the error case within the shell:\n\n```ts\nawait $`failing_command || echo 'Errored!'`;\n```\n\nNote: if you want it to not throw by default, you can build a custom `$` (see below).\n\n#### Exit code helper\n\nIf you just want to get the exit code, you can use the `.code()` helper:\n\n```ts\nconst code = await $`git diff --quiet`.code();\n```\n\n### Piping\n\nPiping stdout or stderr to a `Deno.WriterSync`:\n\n```ts\nawait $`echo 1`.stdout(Deno.stderr);\nawait $`deno eval 'console.error(2);`.stderr(Deno.stdout);\n```\n\nPiping to a `WritableStream`:\n\n```ts\nawait $`echo 1`.stdout(Deno.stderr.writable, { preventClose: true });\n// or with a redirect\nawait $`echo 1 \u003e ${someWritableStream}`;\n```\n\nTo a file path:\n\n```ts\nawait $`echo 1`.stdout($.path(\"data.txt\"));\n// or\nawait $`echo 1 \u003e data.txt`;\n// or\nawait $`echo 1 \u003e ${$.path(\"data.txt\")}`;\n```\n\nTo a file:\n\n```ts\nusing file = $.path(\"data.txt\").openSync({ write: true, create: true });\nawait $`echo 1`.stdout(file);\n// or\nawait $`echo 1 \u003e ${file}`;\n```\n\nFrom one command to another:\n\n```ts\nconst output = await $`echo foo \u0026\u0026 echo bar`\n  .pipe($`grep foo`)\n  .text();\n\n// or using a pipe sequence\nconst output = await $`(echo foo \u0026\u0026 echo bar) | grep foo`\n  .text();\n```\n\n### Providing arguments to a command\n\nUse an expression in a template literal to provide a single argument to a command:\n\n```ts\nconst dirName = \"some_dir\";\nawait $`mkdir ${dirName}`; // executes as: mkdir some_dir\n```\n\nArguments are escaped so strings with spaces get escaped and remain as a single argument:\n\n```ts\nconst dirName = \"Dir with spaces\";\nawait $`mkdir ${dirName}`; // executes as: mkdir 'Dir with spaces'\n```\n\nAlternatively, provide an array for multiple arguments:\n\n```ts\nconst dirNames = [\"some_dir\", \"other dir\"];\nawait $`mkdir ${dirNames}`; // executes as: mkdir some_dir 'other dir'\n```\n\nIf you do not want to escape an argument in a template literal, you can opt out by using `$.rawArg` starting in 0.43.0:\n\n```ts\nconst args = \"arg1   arg2\";\nawait $`echo ${$.rawArg(args)} ${args}`; // executes as: echo arg1 arg2 arg1   arg2\n```\n\nAlternatively, you can opt out completely by using `$.raw`:\n\n```ts\nconst args = \"arg1   arg2\";\nawait $.raw`echo ${args}`; // executes as: echo arg1 arg2\n\n// or escape a specific argument while using $.raw\nawait $.raw`echo ${$.escapeArg(args)} ${args}`; // executes as: echo \"arg1  arg2\" arg1 arg2\n```\n\nProviding stdout of one command to another is possible as follows:\n\n```ts\n// Note: This will read trim the last newline of the other command's stdout\nconst result = await $`echo 1`.stdout(\"piped\"); // need to set stdout as piped for this to work\nconst finalText = await $`echo ${result}`.text();\nconsole.log(finalText); // 1\n```\n\n...though it's probably more straightforward to just collect the output text of a command and provide that:\n\n```ts\nconst result = await $`echo 1`.text();\nconst finalText = await $`echo ${result}`.text();\nconsole.log(finalText); // 1\n```\n\n#### JavaScript objects to redirects\n\nYou can provide JavaScript objects to shell output redirects:\n\n```ts\nconst buffer = new Uint8Array(2);\nawait $`echo 1 \u0026\u0026 (echo 2 \u003e ${buffer}) \u0026\u0026 echo 3`; // 1\\n3\\n\nconsole.log(buffer); // Uint8Array(2) [ 50, 10 ] (2\\n)\n```\n\nSupported objects: `Uint8Array`, `Path`, `WritableStream`, any function that returns a `WritableStream`, any object that implements `[$.symbols.writable](): WritableStream`\n\nOr input redirects:\n\n```ts\n// strings\nconst data = \"my data in a string\";\nconst bytes = await $`gzip \u003c ${data}`;\n\n// paths\nconst path = $.path(\"file.txt\");\nconst bytes = await $`gzip \u003c ${path}`;\n\n// requests (this example does not make the request until after 5 seconds)\nconst request = $.request(\"https://plugins.dprint.dev/info.json\")\n  .showProgress(); // show a progress bar while downloading\nconst bytes = await $`sleep 5 \u0026\u0026 gzip \u003c ${request}`.bytes();\n```\n\nSupported objects: `string`, `Uint8Array`, `Path`, `RequestBuilder`, `ReadableStream`, any function that returns a `ReadableStream`, any object that implements `[$.symbols.readable](): ReadableStream`\n\n### Providing stdin\n\n```ts\nawait $`command`.stdin(\"inherit\"); // default\nawait $`command`.stdin(\"null\");\nawait $`command`.stdin(new Uint8Array[1, 2, 3, 4]());\nawait $`command`.stdin(someReaderOrReadableStream);\nawait $`command`.stdin($.path(\"data.json\"));\nawait $`command`.stdin($.request(\"https://plugins.dprint.dev/info.json\"));\nawait $`command`.stdinText(\"some value\");\n```\n\nOr using a redirect:\n\n```ts\nawait $`command \u003c ${$.path(\"data.json\")}`;\n```\n\n### Streaming API\n\nAwaiting 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.\n\nFor example, the following will output 1, wait 2 seconds, then output 2 to the current process' stderr:\n\n```ts\nconst child = $`echo 1 \u0026\u0026 sleep 1 \u0026\u0026 echo 2`.stdout(\"piped\").spawn();\nawait $`deno eval 'await Deno.stdin.readable.pipeTo(Deno.stderr.writable);'`\n  .stdin(child.stdout());\n```\n\n### Setting environment variables\n\nDone via the `.env(...)` method:\n\n```ts\n// outputs: 1 2 3 4\nawait $`echo $var1 $var2 $var3 $var4`\n  .env(\"var1\", \"1\")\n  .env(\"var2\", \"2\")\n  // or use object syntax\n  .env({\n    var3: \"3\",\n    var4: \"4\",\n  });\n```\n\n### Setting cwd for command\n\nUse `.cwd(\"new_cwd_goes_here\")`:\n\n```ts\n// outputs that it's in the someDir directory\nawait $`deno eval 'console.log(Deno.cwd());'`.cwd(\"./someDir\");\n```\n\n### Silencing a command\n\nMakes a command not output anything to stdout and stderr.\n\n```ts\nawait $`echo 5`.quiet();\nawait $`echo 5`.quiet(\"stdout\"); // or just stdout\nawait $`echo 5`.quiet(\"stderr\"); // or just stderr\n```\n\n### Output a command before executing it\n\nThe following code:\n\n```ts\nconst text = \"example\";\nawait $`echo ${text}`.printCommand();\n```\n\nOutputs the following (with the command text in blue):\n\n```ts\n\u003e echo example\nexample\n```\n\n#### Enabling on a `$`\n\nLike with any default in Dax, you can build a new `$` turning on this option so this will occur with all commands (see [Custom `$`](#custom-)). Alternatively, you can enable this globally by calling `$.setPrintCommand(true);`.\n\n```ts\n$.setPrintCommand(true);\n\nconst text = \"example\";\nawait $`echo ${text}`; // will output `\u003e echo example` before running the command\n```\n\n### Timeout a command\n\nThis will exit with code 124 after 1 second.\n\n```ts\n// timeout a command after a specified time\nawait $`echo 1 \u0026\u0026 sleep 100 \u0026\u0026 echo 2`.timeout(\"1s\");\n```\n\n### Aborting a command\n\nInstead of awaiting the template literal, you can get a command child by calling the `.spawn()` method:\n\n```ts\nconst child = $`echo 1 \u0026\u0026 sleep 100 \u0026\u0026 echo 2`.spawn();\n\nawait doSomeOtherWork();\nchild.kill(); // defaults to \"SIGTERM\"\nawait child; // Error: Aborted with exit code: 124\n```\n\n#### `KillController`\n\nIn some cases you might want to send signals to many commands at the same time. This is possible via a `KillController`.\n\n```ts\nimport $, { KillController } from \"...\";\n\nconst controller = new KillController();\nconst signal = controller.signal;\n\nconst promise = Promise.all([\n  $`sleep 1000s`.signal(signal),\n  $`sleep 2000s`.signal(signal),\n  $`sleep 3000s`.signal(signal),\n]);\n\n$.sleep(\"1s\").then(() =\u003e controller.kill()); // defaults to \"SIGTERM\"\n\nawait promise; // throws after 1 second\n```\n\nCombining this with the `CommandBuilder` API and building your own `$` as shown later in the documentation, can be extremely useful for sending a `Deno.Signal` to all commands you've spawned.\n\n### Exporting the environment of the shell to JavaScript\n\nWhen executing commands in the shell, the environment will be contained to the shell and not exported to the current process. For example:\n\n```ts\nawait $`cd src \u0026\u0026 export MY_VALUE=5`;\n// will output nothing\nawait $`echo $MY_VALUE`;\n// will both NOT output it's in the src dir\nawait $`echo $PWD`;\nconsole.log(Deno.cwd());\n```\n\nYou can change that by using `exportEnv()` on the command:\n\n```ts\nawait $`cd src \u0026\u0026 export MY_VALUE=5`.exportEnv();\n// will output \"5\"\nawait $`echo $MY_VALUE`;\n// will both output it's in the src dir\nawait $`echo $PWD`;\nconsole.log(Deno.cwd());\n```\n\n## Logging\n\nDax comes with some helper functions for logging:\n\n```ts\n// logs with potential indentation\n// Note: everything is logged over stderr by default\n$.log(\"Hello!\");\n// log with the first word as bold green\n$.logStep(\"Fetching data from server...\");\n// or force multiple words to be green by using two arguments\n$.logStep(\"Setting up\", \"local directory...\");\n// similar to $.logStep, but with red\n$.logError(\"Error Some error message.\");\n// similar to $.logStep, but with yellow\n$.logWarn(\"Warning Some warning message.\");\n// logs out text in gray\n$.logLight(\"Some unimportant message.\");\n```\n\nYou may wish to indent some text when logging, use `$.logGroup` to do so:\n\n```ts\n// log indented within (handles de-indenting when an error is thrown)\nawait $.logGroup(async () =\u003e {\n  $.log(\"This will be indented.\");\n  await $.logGroup(async () =\u003e {\n    $.log(\"This will indented even more.\");\n    // do maybe async stuff here\n  });\n});\n\n// or use $.logGroup with $.logGroupEnd\n$.logGroup();\n$.log(\"Indented 1\");\n$.logGroup(\"Level 2\");\n$.log(\"Indented 2\");\n$.logGroupEnd();\n$.logGroupEnd();\n```\n\nAs mentioned previously, Dax logs to stderr for everything by default. This may not be desired, so you can change the current behaviour of a `$` object by setting a logger for either \"info\", \"warn\", or \"error\".\n\n```ts\n// Set the loggers. For example, log everything\n// on stdout instead of stderr\n$.setInfoLogger(console.log);\n$.setWarnLogger(console.log);\n$.setErrorLogger(console.log);\n\n// or a more advanced scenario\n$.setInfoLogger((...args: any[]) =\u003e {\n  console.error(...args);\n  // write args to a file here...\n};)\n```\n\n## Selections / Prompts\n\nThere are a few selections/prompts that can be used.\n\nBy default, all prompts will exit the process if the user cancelled their selection via ctrl+c. If you don't want this behaviour, then use the `maybe` variant functions.\n\n### `$.prompt` / `$.maybePrompt`\n\nGets a string value from the user:\n\n```ts\nconst name = await $.prompt(\"What's your name?\");\n\n// or provide an object, which has some more options\nconst name = await $.prompt({\n  message: \"What's your name?\",\n  default: \"Dax\", // prefilled value\n  noClear: true, // don't clear the text on result\n});\n\n// or hybrid\nconst name = await $.prompt(\"What's your name?\", {\n  default: \"Dax\",\n});\n\n// with a character mask (for password / secret input)\nconst password = await $.prompt(\"What's your password?\", {\n  mask: true,\n});\n```\n\nAgain, you can use `$.maybePrompt(\"What's your name?\")` to get a nullable return value for when the user presses `ctrl+c`.\n\n### `$.confirm` / `$.maybeConfirm`\n\nGets the answer to a yes or no question:\n\n```ts\nconst result = await $.confirm(\"Would you like to continue?\");\n\n// or with more options\nconst result = await $.confirm({\n  message: \"Would you like to continue?\",\n  default: true,\n});\n\n// or hybrid\nconst result = await $.confirm(\"Would you like to continue?\", {\n  default: false,\n  noClear: true,\n});\n```\n\n### `$.select` / `$.maybeSelect`\n\nGets a single value:\n\n```ts\nconst index = await $.select({\n  message: \"What's your favourite colour?\",\n  options: [\n    \"Red\",\n    \"Green\",\n    \"Blue\",\n  ],\n});\n```\n\n### `$.multiSelect` / `$.maybeMultiSelect`\n\nGets multiple or no values:\n\n```ts\nconst indexes = await $.multiSelect({\n  message: \"Which of the following are days of the week?\",\n  options: [\n    \"Monday\",\n    {\n      text: \"Wednesday\",\n      selected: true, // defaults to false\n    },\n    \"Blue\",\n  ],\n});\n```\n\n## Progress indicator\n\nYou may wish to indicate that some progress is occurring.\n\n### Indeterminate\n\n```ts\nconst pb = $.progress(\"Updating Database\");\n\nawait pb.with(async () =\u003e {\n  // do some work here\n});\n```\n\nThe `.with(async () =\u003e { ... })` API will hide the progress bar when the action completes including hiding it when an error is thrown. If you don't want to bother with this though you can just call `pb.finish()` instead.\n\n```ts\nconst pb = $.progress(\"Updating Database\");\n\ntry {\n  // do some work here\n} finally {\n  pb.finish();\n}\n```\n\n### Determinate\n\nSet a length to be determinate, which will display a progress bar:\n\n```ts\nconst items = [/*...*/];\nconst pb = $.progress(\"Processing Items\", {\n  length: items.length,\n});\n\nawait pb.with(async () =\u003e {\n  for (const item of items) {\n    await doWork(item);\n    pb.increment(); // or use pb.position(val)\n  }\n});\n```\n\n#### Synchronous work\n\nThe progress bars are updated on an interval (via `setInterval`) to prevent rendering more than necessary. If you are doing a lot of synchronous work the progress bars won't update. Due to this, you can force a render where you think it would be appropriate by using the `.forceRender()` method:\n\n```ts\nconst pb = $.progress(\"Processing Items\", {\n  length: items.length,\n});\n\npb.with(() =\u003e {\n  for (const item of items) {\n    doWork(item);\n    pb.increment();\n    pb.forceRender();\n  }\n});\n```\n\n## Path API\n\nThe path API offers an immutable [`Path`](https://jsr.io/@david/path/doc/~/Path) class via [`jsr:@david/path`](https://jsr.io/@david/path), which is a similar concept to Rust's `PathBuf` struct.\n\n```ts\n// create a `Path`\nlet srcDir = $.path(\"src\");\n// get information about the path\nsrcDir.isDirSync(); // false\n// do actions on it\nawait srcDir.mkdir();\nsrcDir.isDirSync(); // true\n\nsrcDir.isRelative(); // true\nsrcDir = srcDir.resolve(); // resolve the path to be absolute\nsrcDir.isRelative(); // false\nsrcDir.isAbsolute(); // true\n\n// join to get other paths and do actions on them\nconst textFile = srcDir.join(\"subDir\").join(\"file.txt\");\ntextFile.writeTextSync(\"some text\");\nconsole.log(textFile.readTextSync()); // \"some text\"\n\nconst jsonFile = srcDir.join(\"otherDir\", \"file.json\");\nconsole.log(jsonFile.parentOrThrow()); // path for otherDir\njsonFile.writeJsonSync({\n  someValue: 5,\n});\nconsole.log(jsonFile.readJsonSync().someValue); // 5\n```\n\nIt also works to provide these paths to commands:\n\n```ts\nconst srcDir = $.path(\"src\").resolve();\n\nawait $`echo ${srcDir}`;\n```\n\n`Path`s can be created in the following ways:\n\n```ts\nconst pathRelative = $.path(\"./relative\");\nconst pathAbsolute = $.path(\"/tmp\");\nconst pathFileUrl = $.path(new URL(\"file:///tmp\")); // converts to /tmp\nconst pathStringFileUrl = $.path(\"file:///tmp\"); // converts to /tmp\nconst pathImportMeta = $.path(import.meta); // the path for the current module\n```\n\nThere are a lot of helper methods here, so check the [documentation on Path](https://jsr.io/@david/path/doc/~/Path) for more details.\n\n## Helper functions\n\nSleeping asynchronously for a specified amount of time:\n\n```ts\nawait $.sleep(100); // ms\nawait $.sleep(\"1.5s\");\nawait $.sleep(\"1m30s\");\n```\n\nGetting path to an executable based on a command name:\n\n```ts\nconsole.log(await $.which(\"deno\")); // outputs the path to deno executable\n```\n\nCheck if a command exists:\n\n```ts\nconsole.log(await $.commandExists(\"deno\"));\nconsole.log($.commandExistsSync(\"deno\"));\n```\n\nAttempting to do an action until it succeeds or hits the maximum number of retries:\n\n```ts\nawait $.withRetries({\n  count: 5,\n  // you may also specify an iterator here which is useful for exponential backoff\n  delay: \"5s\",\n  action: async () =\u003e {\n    await $`cargo publish`;\n  },\n});\n```\n\n\"Dedent\" or remove leading whitespace from a string:\n\n```ts\nconsole.log($.dedent`\n    This line will appear without any indentation.\n      * This list will appear with 2 spaces more than previous line.\n      * As will this line.\n\n    Empty lines (like the one above) will not affect the common indentation.\n  `);\n```\n\n```\nThis line will appear without any indentation.\n  * This list will appear with 2 spaces more than previous line.\n  * As will this line.\n\nEmpty lines (like the one above) will not affect the common indentation.\n```\n\nRemove ansi escape sequences from a string:\n\n```ts\n$.stripAnsi(\"\\u001B[4mHello World\\u001B[0m\");\n//=\u003e 'Hello World'\n```\n\n## Making requests\n\nDax ships with a slightly less verbose wrapper around `fetch` that will throw by default on non-`2xx` status codes (this is configurable per status code).\n\nDownload a file as JSON:\n\n```ts\nconst data = await $.request(\"https://plugins.dprint.dev/info.json\").json();\nconsole.log(data.plugins);\n```\n\nOr as text:\n\n```ts\nconst text = await $.request(\"https://example.com\").text();\n```\n\nOr get the long form:\n\n```ts\nconst response = await $.request(\"https://plugins.dprint.dev/info.json\");\nconsole.log(response.code);\nconsole.log(await response.json());\n```\n\nRequests can be piped to commands:\n\n```ts\nconst request = $.request(\"https://plugins.dprint.dev/info.json\");\nawait $`deno run main.ts`.stdin(request);\n\n// or as a redirect... this sleeps 5 seconds, then makes\n// request and redirects the output to the command\nawait $`sleep 5 \u0026\u0026 deno run main.ts \u003c ${request}`;\n```\n\nSee the [documentation on `RequestBuilder`](https://jsr.io/@david/dax/doc/~/RequestBuilder) for more details. It should be as flexible as `fetch`, but uses a builder API (ex. set headers via `.header(...)`).\n\n### Showing progress\n\nYou can have downloads show a progress bar by using the `.showProgress()` builder method:\n\n```ts\nconst url = \"https://dl.deno.land/release/v1.29.1/deno-x86_64-unknown-linux-gnu.zip\";\nconst downloadPath = await $.request(url)\n  .showProgress()\n  .pipeToPath();\n```\n\n## Shell\n\nThe shell is cross-platform and uses the parser from [deno_task_shell](https://github.com/denoland/deno_task_shell).\n\nSequential lists:\n\n```ts\n// result will contain the directory in someDir\nconst result = await $`cd someDir ; deno eval 'console.log(Deno.cwd())'`;\n```\n\nBoolean lists:\n\n```ts\n// outputs to stdout with 1\\n\\2n\nawait $`echo 1 \u0026\u0026 echo 2`;\n// outputs to stdout with 1\\n\nawait $`echo 1 || echo 2`;\n```\n\nPipe sequences:\n\n```ts\nawait $`echo 1 | deno run main.ts`;\n```\n\nRedirects:\n\n```ts\nawait $`echo 1 \u003e output.txt`;\nconst gzippedBytes = await $`gzip \u003c input.txt`.bytes();\n```\n\nSub shells:\n\n```ts\nawait $`(echo 1 \u0026\u0026 echo 2) \u003e output.txt`;\n```\n\nSetting env var for command in the shell (generally you can just use `.env(...)` though):\n\n```ts\n// result will contain the directory in someDir\nconst result = await $`test=123 deno eval 'console.log(Deno.env.get('test'))'`;\nconsole.log(result.stdout); // 123\n```\n\nShell variables (these aren't exported):\n\n```ts\n// the 'test' variable WON'T be exported to the sub processes, so\n// that will print a blank line, but it will be used in the final echo command\nawait $`test=123 \u0026\u0026 deno eval 'console.log(Deno.env.get('test'))' \u0026\u0026 echo $test`;\n```\n\nEnv variables (these are exported):\n\n```ts\n// the 'test' variable WILL be exported to the sub processes and\n// it will be used in the final echo command\nawait $`export test=123 \u0026\u0026 deno eval 'console.log(Deno.env.get('test'))' \u0026\u0026 echo $test`;\n```\n\nVariable substitution:\n\n```ts\nconst result = await $`echo $TEST`.env(\"TEST\", \"123\").text();\nconsole.log(result); // 123\n```\n\n### Custom cross-platform shell commands\n\nCurrently implemented (though not every option is supported):\n\n- [`cd`](https://man7.org/linux/man-pages/man1/cd.1p.html) - Change directory command.\n  - Note that shells don't export their environment by default.\n- [`echo`](https://man7.org/linux/man-pages/man1/echo.1.html) - Echo command.\n- [`exit`](https://man7.org/linux/man-pages/man1/exit.1p.html) - Exit command.\n- [`cp`](https://man7.org/linux/man-pages/man1/cp.1.html) - Copies files.\n- [`mv`](https://man7.org/linux/man-pages/man1/mv.1.html) - Moves files.\n- [`rm`](https://man7.org/linux/man-pages/man1/rm.1.html) - Remove files or directories command.\n- [`mkdir`](https://man7.org/linux/man-pages/man1/mkdir.1.html) - Makes\n  directories.\n  - Ex. `mkdir -p DIRECTORY...` - Commonly used to make a directory and all its\n    parents with no error if it exists.\n- [`pwd`](https://man7.org/linux/man-pages/man1/pwd.1.html) - Prints the current/working directory.\n- [`set`](https://man7.org/linux/man-pages/man1/set.1p.html) - Only supports `pipefail`\n- [`shopt`](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html) - Only supports `nullglob`, `failglob`, `globstar` (defaults to `globstar`)\n- [`sleep`](https://man7.org/linux/man-pages/man1/sleep.1.html) - Sleep command.\n- [`test`](https://man7.org/linux/man-pages/man1/test.1.html) - Test command.\n- [`touch`](https://man7.org/linux/man-pages/man1/touch.1.html) - Creates a file (note: flags have not been implemented yet).\n- [`unset`](https://man7.org/linux/man-pages/man1/unset.1p.html) - Unsets an environment variable.\n- [`cat`](https://man7.org/linux/man-pages/man1/cat.1.html) - Concatenate files and print on the standard output\n- [`printenv`](https://man7.org/linux/man-pages/man1/printenv.1.html) - Print all or part of environment\n- `which` - Resolves the path to an executable (`-a` flag is not supported at this time)\n- [`true`](https://man7.org/linux/man-pages/man1/true.1.html) - True command.\n- [`false`](https://man7.org/linux/man-pages/man1/false.1.html) - False command.\n- More to come. Will try to get a similar list as https://deno.land/manual/tools/task_runner#built-in-commands\n\nYou can also register your own commands with the shell parser (see below).\n\nNote that these cross-platform commands can be bypassed by running them through `sh`: `sh -c \u003ccommand\u003e` (ex. `sh -c cp source destination`). Obviously doing this won't work on Windows though.\n\n### Shell Options\n\n- `pipefail` (`set -o`/`+o`) - When enabled, a pipeline's exit code is the rightmost non-zero exit code. Default: **off**\n- `nullglob` (`shopt -s`/`-u`) - When enabled, a glob pattern matching nothing expands to nothing. Default: **off**\n- `failglob` (`shopt -s`/`-u`) - When enabled, a glob pattern matching nothing causes an error. Default: **off**\n- `globstar` (`shopt -s`/`-u`) - When enabled, `**` matches recursively across directories. Default: **on**\n- `questionGlob` (builder only) - When enabled, `?` matches any single character in glob patterns. When disabled, `?` is treated literally. Default: **off**\n\nThese can be configured via builder methods or when building a custom `$`:\n\n```ts\nconst $ = build$({\n  commandBuilder: (builder) =\u003e\n    builder\n      .pipefail()\n      .nullglob()\n      .failglob()\n      .globstar(false)\n      .questionGlob(),\n});\n```\n\n### Cross-platform shebang support\n\nUsers on unix-based platforms often write a script like so:\n\n```ts\n#!/usr/bin/env -S deno run\nconsole.log(\"Hello there!\");\n```\n\n...which can be executed on the command line by running `./file.ts`. This doesn't work on the command line in Windows, but it does on all platforms in dax:\n\n```ts\nawait $`./file.ts`;\n```\n\n## Builder APIs\n\nThe builder APIs are what the library uses internally and they're useful for scenarios where you want to re-use some setup state. They're immutable so every function call returns a new object (which is the same thing that happens with the objects returned from `$` and `$.request`).\n\n### `CommandBuilder`\n\n`CommandBuilder` can be used for building up commands similar to what the tagged template `$` does:\n\n```ts\nimport { CommandBuilder } from \"@david/dax\";\n\nconst commandBuilder = new CommandBuilder()\n  .cwd(\"./subDir\")\n  .stdout(\"inheritPiped\") // output to stdout and pipe to a buffer\n  .noThrow();\n\nconst otherBuilder = commandBuilder\n  .stderr(\"null\");\n\nconst result = await commandBuilder\n  // won't have a null stderr\n  .command(\"deno run my_script.ts\")\n  .spawn();\n\nconst result2 = await otherBuilder\n  // will have a null stderr\n  .command(\"deno run my_script.ts\")\n  .spawn();\n```\n\nYou can also register your own custom commands using the `registerCommand` or `registerCommands` methods:\n\n```ts\nconst commandBuilder = new CommandBuilder()\n  .registerCommand(\n    \"true\",\n    () =\u003e Promise.resolve({ code: 0 }),\n  );\n\nconst result = await commandBuilder\n  // now includes the 'true' command\n  .command(\"true \u0026\u0026 echo yay\")\n  .spawn();\n```\n\n#### Default Commands\n\nThe `CommandBuilder` will always have the default cross-platform commands registered. You can unregister them by using the `unregisterCommand` function:\n\n```ts\nconst commandBuilder = new CommandBuilder()\n  .unregisterCommand(\"printenv\"); // will use what's on the system now\n```\n\n### `RequestBuilder`\n\n`RequestBuilder` can be used for building up requests similar to `$.request`:\n\n```ts\nimport { RequestBuilder } from \"@david/dax\";\n\nconst requestBuilder = new RequestBuilder()\n  .header(\"SOME_VALUE\", \"some value to send in a header\");\n\nconst result = await requestBuilder\n  .url(\"https://example.com\")\n  .timeout(\"10s\")\n  .text();\n```\n\n### Custom `$`\n\nYou may wish to create your own `$` function that has a certain setup context (for example, custom commands or functions on `$`, a defined environment variable or cwd). You may do this by using the exported `build$` with `CommandBuilder` and/or `RequestBuilder`, which is essentially what the main default exported `$` uses internally to build itself. In addition, you may also add your own functions to `$`:\n\n```ts\nimport { build$, createExecutableCommand } from \"@david/dax\";\n\n// creates a $ object with the provided starting environment\nconst $ = build$({\n  commandBuilder: (builder) =\u003e\n    builder\n      .registerCommand(\"deno\", createExecutableCommand(Deno.execPath()))\n      .cwd(\"./subDir\")\n      .env(\"HTTPS_PROXY\", \"some_value\"),\n  requestBuilder: (builder) =\u003e\n    builder\n      .header(\"SOME_NAME\", \"some value\"),\n  extras: {\n    add(a: number, b: number) {\n      return a + b;\n    },\n  },\n});\n\n// this command will use the env described above, but the main\n// process won't have its environment changed\nawait $`deno run my_script.ts`;\n\nconsole.log(await $.request(\"https://plugins.dprint.dev/info.json\").json());\n\n// use your custom function\nconsole.log($.add(1, 2));\n```\n\nThis may be useful also if you want to change the default configuration. Another example:\n\n```ts\nconst $ = build$({\n  commandBuilder: (builder) =\u003e\n    builder.exportEnv()\n      .noThrow(),\n});\n\n// since exportEnv() was set, this will now actually change\n// the directory of the executing process\nawait $`cd test \u0026\u0026 export MY_VALUE=5`;\n// will output \"5\"\nawait $`echo $MY_VALUE`;\n// will output it's in the test dir\nawait $`echo $PWD`;\n// won't throw even though this command fails (because of `.noThrow()`)\nawait $`deno eval 'Deno.exit(1);'`;\n```\n\n#### Building `$` from another `$`\n\nYou can build a `$` from another `$` by calling `$.build$({ /* options go here */ })`.\n\nThis might be useful in scenarios where you want to use a `$` with a custom logger.\n\n```ts\nconst local$ = $.build$();\nlocal$.setInfoLogger((...args: any[]) =\u003e {\n  // a more real example might be logging to a file\n  console.log(\"Logging...\");\n  console.log(...args);\n});\nlocal$.log(\"Hello!\");\n```\n\nOutputs:\n\n```\nLogging...\nHello!\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdsherret%2Fdax","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdsherret%2Fdax","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdsherret%2Fdax/lists"}