Cross-platform shell tools for Deno and Node.js inspired by zx.
- 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.
- Minimal globals or global configuration.
- Only a default instance of
$
, but it's not mandatory to use this.
- Only a default instance of
- No custom CLI.
- Good for application code in addition to use as a shell script replacement.
- Named after my cat.
Deno:
# or skip and import directly from `jsr:@david/dax@<version>`
deno add jsr:@david/dax
Node:
npm install dax-sh
#!/usr/bin/env -S deno run --allow-all
import $ from "@david/dax"; // "dax-sh" 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`,
]);
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
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();
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 arguments in a template literal, you can opt out completely, by using $.raw
:
const args = "arg1 arg2 arg3";
await $.raw`echo ${args}`; // executes as: echo arg1 arg2 arg3
// or escape a specific argument while using $.raw
const args2 = "arg1 arg2";
await $.raw`echo ${$.escape(args2)} ${args2}`; // 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
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
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")}`;
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());
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",
});
Use .cwd("new_cwd_goes_here")
:
// outputs that it's in the someDir directory
await $`deno eval 'console.log(Deno.cwd());'`.cwd("./someDir");
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
The following code:
const text = "example";
await $`echo ${text}`.printCommand();
Outputs the following (with the command text in blue):
> echo example
example
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
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");
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 doSomeOtherWork();
child.kill(); // defaults to "SIGTERM"
await child; // Error: Aborted with exit code: 124
In some cases you might want to send signals to many commands at the same time. This is possible via a KillSignalController
.
import $, { KillSignalController } from "...";
const controller = new KillSignalController();
const signal = controller.signal;
const promise = Promise.all([
$`sleep 1000s`.signal(signal),
$`sleep 2000s`.signal(signal),
$`sleep 3000s`.signal(signal),
]);
$.sleep("1s").then(() => controller.kill()); // defaults to "SIGTERM"
await promise; // throws after 1 second
Combining 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.
When executing commands in the shell, the environment will be contained to the shell and not exported to the current process. For example:
await $`cd src && export MY_VALUE=5`;
// will output nothing
await $`echo $MY_VALUE`;
// will both NOT output it's in the src dir
await $`echo $PWD`;
console.log(Deno.cwd());
You can change that by using exportEnv()
on the command:
await $`cd src && export MY_VALUE=5`.exportEnv();
// will output "5"
await $`echo $MY_VALUE`;
// will both output it's in the src dir
await $`echo $PWD`;
console.log(Deno.cwd());
Dax comes with some helper functions for logging:
// logs with potential indentation
// Note: everything is logged over stderr by default
$.log("Hello!");
// log with the first word as bold green
$.logStep("Fetching data from server...");
// or force multiple words to be green by using two arguments
$.logStep("Setting up", "local directory...");
// similar to $.logStep, but with red
$.logError("Error Some error message.");
// similar to $.logStep, but with yellow
$.logWarn("Warning Some warning message.");
// logs out text in gray
$.logLight("Some unimportant message.");
You may wish to indent some text when logging, use $.logGroup
to do so:
// log indented within (handles de-indenting when an error is thrown)
await $.logGroup(async () => {
$.log("This will be indented.");
await $.logGroup(async () => {
$.log("This will indented even more.");
// do maybe async stuff here
});
});
// or use $.logGroup with $.logGroupEnd
$.logGroup();
$.log("Indented 1");
$.logGroup("Level 2");
$.log("Indented 2");
$.logGroupEnd();
$.logGroupEnd();
As 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".
// Set the loggers. For example, log everything
// on stdout instead of stderr
$.setInfoLogger(console.log);
$.setWarnLogger(console.log);
$.setErrorLogger(console.log);
// or a more advanced scenario
$.setInfoLogger((...args: any[]) => {
console.error(...args);
// write args to a file here...
};)
There are a few selections/prompts that can be used.
By 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.
Gets a string value from the user:
const name = await $.prompt("What's your name?");
// or provide an object, which has some more options
const name = await $.prompt({
message: "What's your name?",
default: "Dax", // prefilled value
noClear: true, // don't clear the text on result
});
// or hybrid
const name = await $.prompt("What's your name?", {
default: "Dax",
});
// with a character mask (for password / secret input)
const password = await $.prompt("What's your password?", {
mask: true,
});
Again, you can use $.maybePrompt("What's your name?")
to get a nullable return value for when the user presses ctrl+c
.
Gets the answer to a yes or no question:
const result = await $.confirm("Would you like to continue?");
// or with more options
const result = await $.confirm({
message: "Would you like to continue?",
default: true,
});
// or hybrid
const result = await $.confirm("Would you like to continue?", {
default: false,
noClear: true,
});
Gets a single value:
const index = await $.select({
message: "What's your favourite colour?",
options: [
"Red",
"Green",
"Blue",
],
});
Gets multiple or no values:
const indexes = await $.multiSelect({
message: "Which of the following are days of the week?",
options: [
"Monday",
{
text: "Wednesday",
selected: true, // defaults to false
},
"Blue",
],
});
You may wish to indicate that some progress is occurring.
const pb = $.progress("Updating Database");
await pb.with(async () => {
// do some work here
});
The .with(async () => { ... })
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.
const pb = $.progress("Updating Database");
try {
// do some work here
} finally {
pb.finish();
}
Set a length to be determinate, which will display a progress bar:
const items = [/*...*/];
const pb = $.progress("Processing Items", {
length: items.length,
});
await pb.with(async () => {
for (const item of items) {
await doWork(item);
pb.increment(); // or use pb.position(val)
}
});
The 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:
const pb = $.progress("Processing Items", {
length: items.length,
});
pb.with(() => {
for (const item of items) {
doWork(item);
pb.increment();
pb.forceRender();
}
});
The path API offers an immutable Path
class via jsr:@david/path
, which is a similar concept to Rust's PathBuf
struct.
// create a `Path`
let srcDir = $.path("src");
// get information about the path
srcDir.isDirSync(); // false
// do actions on it
await srcDir.mkdir();
srcDir.isDirSync(); // true
srcDir.isRelative(); // true
srcDir = srcDir.resolve(); // resolve the path to be absolute
srcDir.isRelative(); // false
srcDir.isAbsolute(); // true
// join to get other paths and do actions on them
const textFile = srcDir.join("subDir").join("file.txt");
textFile.writeTextSync("some text");
console.log(textFile.readTextSync()); // "some text"
const jsonFile = srcDir.join("otherDir", "file.json");
console.log(jsonFile.parentOrThrow()); // path for otherDir
jsonFile.writeJsonSync({
someValue: 5,
});
console.log(jsonFile.readJsonSync().someValue); // 5
It also works to provide these paths to commands:
const srcDir = $.path("src").resolve();
await $`echo ${srcDir}`;
Path
s can be created in the following ways:
const pathRelative = $.path("./relative");
const pathAbsolute = $.path("/tmp");
const pathFileUrl = $.path(new URL("file:///tmp")); // converts to /tmp
const pathStringFileUrl = $.path("file:///tmp"); // converts to /tmp
const pathImportMeta = $.path(import.meta); // the path for the current module
There are a lot of helper methods here, so check the documentation on Path for more details.
Changing the current working directory of the current process:
$.cd("someDir");
console.log(Deno.cwd()); // will be in someDir directory
// or change the directory of the process to be in
// the directory of the current script
$.cd(import.meta);
Sleeping asynchronously for a specified amount of time:
await $.sleep(100); // ms
await $.sleep("1.5s");
await $.sleep("1m30s");
Getting path to an executable based on a command name:
console.log(await $.which("deno")); // outputs the path to deno executable
Check if a command exists:
console.log(await $.commandExists("deno"));
console.log($.commandExistsSync("deno"));
Attempting to do an action until it succeeds or hits the maximum number of retries:
await $.withRetries({
count: 5,
// you may also specify an iterator here which is useful for exponential backoff
delay: "5s",
action: async () => {
await $`cargo publish`;
},
});
"Dedent" or remove leading whitespace from a string:
console.log($.dedent`
This line will appear without any indentation.
* This list will appear with 2 spaces more than previous line.
* As will this line.
Empty lines (like the one above) will not affect the common indentation.
`);
This line will appear without any indentation.
* This list will appear with 2 spaces more than previous line.
* As will this line.
Empty lines (like the one above) will not affect the common indentation.
Remove ansi escape sequences from a string:
$.stripAnsi("\u001B[4mHello World\u001B[0m");
//=> 'Hello World'
Dax 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).
Download a file as JSON:
const data = await $.request("https://plugins.dprint.dev/info.json").json();
console.log(data.plugins);
Or as text:
const text = await $.request("https://example.com").text();
Or get the long form:
const response = await $.request("https://plugins.dprint.dev/info.json");
console.log(response.code);
console.log(await response.json());
Requests can be piped to commands:
const request = $.request("https://plugins.dprint.dev/info.json");
await $`deno run main.ts`.stdin(request);
// or as a redirect... this sleeps 5 seconds, then makes
// request and redirects the output to the command
await $`sleep 5 && deno run main.ts < ${request}`;
See the documentation on RequestBuilder
for more details. It should be as flexible as fetch
, but uses a builder API (ex. set headers via .header(...)
).
You can have downloads show a progress bar by using the .showProgress()
builder method:
const url = "https://dl.deno.land/release/v1.29.1/deno-x86_64-unknown-linux-gnu.zip";
const downloadPath = await $.request(url)
.showProgress()
.pipeToPath();
The shell is cross-platform and uses the parser from deno_task_shell.
Sequential lists:
// result will contain the directory in someDir
const result = await $`cd someDir ; deno eval 'console.log(Deno.cwd())'`;
Boolean lists:
// outputs to stdout with 1\n\2n
await $`echo 1 && echo 2`;
// outputs to stdout with 1\n
await $`echo 1 || echo 2`;
Pipe sequences:
await $`echo 1 | deno run main.ts`;
Redirects:
await $`echo 1 > output.txt`;
const gzippedBytes = await $`gzip < input.txt`.bytes();
Sub shells:
await $`(echo 1 && echo 2) > output.txt`;
Setting env var for command in the shell (generally you can just use .env(...)
though):
// result will contain the directory in someDir
const result = await $`test=123 deno eval 'console.log(Deno.env.get('test'))'`;
console.log(result.stdout); // 123
Shell variables (these aren't exported):
// the 'test' variable WON'T be exported to the sub processes, so
// that will print a blank line, but it will be used in the final echo command
await $`test=123 && deno eval 'console.log(Deno.env.get('test'))' && echo $test`;
Env variables (these are exported):
// the 'test' variable WILL be exported to the sub processes and
// it will be used in the final echo command
await $`export test=123 && deno eval 'console.log(Deno.env.get('test'))' && echo $test`;
Variable substitution:
const result = await $`echo $TEST`.env("TEST", "123").text();
console.log(result); // 123
Currently implemented (though not every option is supported):
cd
- Change directory command.- Note that shells don't export their environment by default.
echo
- Echo command.exit
- Exit command.cp
- Copies files.mv
- Moves files.rm
- Remove files or directories command.mkdir
- Makes directories.- Ex.
mkdir -p DIRECTORY...
- Commonly used to make a directory and all its parents with no error if it exists.
- Ex.
pwd
- Prints the current/working directory.sleep
- Sleep command.test
- Test command.touch
- Creates a file (note: flags have not been implemented yet).unset
- Unsets an environment variable.cat
- Concatenate files and print on the standard outputprintenv
- Print all or part of environmentwhich
- Resolves the path to an executable (-a
flag is not supported at this time)- More to come. Will try to get a similar list as https://deno.land/manual/tools/task_runner#built-in-commands
You can also register your own commands with the shell parser (see below).
Note that these cross-platform commands can be bypassed by running them through sh
: sh -c <command>
(ex. sh -c cp source destination
). Obviously doing this won't work on Windows though.
Users on unix-based platforms often write a script like so:
#!/usr/bin/env -S deno run
console.log("Hello there!");
...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:
await $`./file.ts`;
The 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
).
CommandBuilder
can be used for building up commands similar to what the tagged template $
does:
import { CommandBuilder } from "@david/dax";
const commandBuilder = new CommandBuilder()
.cwd("./subDir")
.stdout("inheritPiped") // output to stdout and pipe to a buffer
.noThrow();
const otherBuilder = commandBuilder
.stderr("null");
const result = await commandBuilder
// won't have a null stderr
.command("deno run my_script.ts")
.spawn();
const result2 = await otherBuilder
// will have a null stderr
.command("deno run my_script.ts")
.spawn();
You can also register your own custom commands using the registerCommand
or registerCommands
methods:
const commandBuilder = new CommandBuilder()
.registerCommand(
"true",
() => Promise.resolve({ code: 0 }),
);
const result = await commandBuilder
// now includes the 'true' command
.command("true && echo yay")
.spawn();
RequestBuilder
can be used for building up requests similar to $.request
:
import { RequestBuilder } from "@david/dax";
const requestBuilder = new RequestBuilder()
.header("SOME_VALUE", "some value to send in a header");
const result = await requestBuilder
.url("https://example.com")
.timeout("10s")
.text();
You 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 $
:
import { build$, CommandBuilder, RequestBuilder } from "@david/dax";
// creates a $ object with the provided starting environment
const $ = build$({
commandBuilder: new CommandBuilder()
.cwd("./subDir")
.env("HTTPS_PROXY", "some_value"),
requestBuilder: new RequestBuilder()
.header("SOME_NAME", "some value"),
extras: {
add(a: number, b: number) {
return a + b;
},
},
});
// this command will use the env described above, but the main
// process won't have its environment changed
await $`deno run my_script.ts`;
console.log(await $.request("https://plugins.dprint.dev/info.json").json());
// use your custom function
console.log($.add(1, 2));
This may be useful also if you want to change the default configuration. Another example:
const commandBuilder = new CommandBuilder()
.exportEnv()
.noThrow();
const $ = build$({ commandBuilder });
// since exportEnv() was set, this will now actually change
// the directory of the executing process
await $`cd test && export MY_VALUE=5`;
// will output "5"
await $`echo $MY_VALUE`;
// will output it's in the test dir
await $`echo $PWD`;
// won't throw even though this command fails (because of `.noThrow()`)
await $`deno eval 'Deno.exit(1);'`;
You can build a $
from another $
by calling $.build$({ /* options go here */ })
.
This might be useful in scenarios where you want to use a $
with a custom logger.
const local$ = $.build$();
local$.setInfoLogger((...args: any[]) => {
// a more real example might be logging to a file
console.log("Logging...");
console.log(...args);
});
local$.log("Hello!");
Outputs:
Logging...
Hello!
dax's People
Forkers
aaronhuggins pocketken curtislarson hashrock sigmasd roykeane403 imcgaunn pkedy sorikairox bartlomieju vegerot chainkemists lilnasy impactaky nfnitloop mashizora nettybun leighmcculloch pomdtr rivy-t ryooooooga yohe-am matklad yssource devdoshi cseidr zephraph lichwala jeff-hykin hajime-san metatypedev iuioiua guest271314 bpollack novusnota-forks scarf005 nakasyoudax's Issues
Add better inspect for `PathRef`
It's not good right now:
> const p = $.path(".").resolve()
undefined
> p
PathRef {}
Should probably be PathRef("..path goes here...")
or something like that.
Can the existing API support the <() expression in the shell
Is there any way to make the following code execute successfully
Example 1
import $ from "https://deno.land/x/dax/mod.ts";
const a = "12345"
const b = "23456"
await $`diff <(echo ${a}) <(echo ${b})`
Output
error: Uncaught "Unexpected character.\n <(echo 12345) <(echo 23456)\n ~"
Example 2
import $ from "https://deno.land/x/dax/mod.ts";
const a = "12345"
const b = "23456"
const tempFileA = await Deno.makeTempFile();
await Deno.writeTextFile(tempFileA, a);
const tempFileB = await Deno.makeTempFile();
await Deno.writeTextFile(tempFileB, b);
await $`diff ${tempFileA} ${tempFileB}`
Output
1c1
< 12345
\ No newline at end of file
---
> 23456
\ No newline at end of file
error: Uncaught Error: Exited with code: 1
throw new Error(`Exited with code: ${code}`);
^
at CommandChild.pipedStdoutBuffer (https://deno.land/x/[email protected]/src/command.ts:587:17)
at eventLoopTick (ext:core/01_core.js:181:11)
Env
[email protected]
deno 1.33.2
Make dax extension friendly
which it seems already is, you can check here we were discussion ideas on how to extend it
impactaky/dax_extras#2 (comment)
import $ from "https://deno.land/x/[email protected]/mod.ts";
import { addExtras } from "./vendor/raw.githubusercontent.com/impactaky/dax_extras/1.0.0/mod.ts";
addExtras($); // this will mutate it in place
the function idea seems to work, but we want to known the dax version so we can make a semver check on it,
do you think we can add a version prop to $ , so something like $.version = "0.32.0" / or current version
stack trace tranucated
Don't know if its a dax or a deno issue, or if its the way things works
But It would be great if this can be fixed
code:
import { $ } from "https://deno.land/x/[email protected]/mod.ts";
await $`donotexistbinary`;
result
Uncaught Error: Command not found: donotexistbinary
at resolveCommand (https://deno.land/x/[email protected]/src/shell.ts:773:11)
at eventLoopTick (ext:core/01_core.js:182:11)
at async executeCommandArgs (https://deno.land/x/[email protected]/src/shell.ts:5
71:27)
at async executeSimpleCommand (https://deno.land/x/[email protected]/src/shell.ts
:560:10)
at async executeSequentialList (https://deno.land/x/[email protected]/src/shell.t
s:386:20)
at async spawn (https://deno.land/x/[email protected]/src/shell.ts:375:18)
at async CommandChild.pipedStdoutBuffer (https://deno.land/x/[email protected]/sr
c/command.ts:572:20)
The stack doesn't reach to user code
request.pipeToPath: add an option to override file
Currently it will override any existing file, maybe having an option for that is better
Feature request: Pluggable commands
It would be really handy if we could extend the shell command parser out with support for additional, user-defined commands. For my current use case, for example, it'd be great to be able to provide Deno-based drop-ins for things like gzip
, tar
and so on, but you probably don't want every possible command under the sun within dax itself...?
I know our vet has complained about my cats getting a little too fat, anyway...
Collaboration opportunity with c4spar/deno-dzx
First, thank you for your work on Deno! I really enjoy the quality of what you and the team build 😄
I would normally have made something like this a Discussion
topic instead of an issue, but I wonder if you've had thoughts on collaborating on https://github.com/c4spar/deno-dzx to add your ideas to that project? It is the most established Deno version of zx
that I've seen (and contributed to) and I think it might be valuable to cross-pollinate ideas in that more established project!
Just wanted to put it on your radar in case you haven't already seen it - cheers!
Functionality for checking if external changes have occurred since the last time some code was run
It might be neat to have an API that can be used to tell if some external changes have occurred since the last time a command was run. This would be useful for not doing an action if unnecessary to do so.
const tracker = $.changeTracker(import.meta, "data files"); // cache keyed on this current file and a string
tracker.addMtime("data/file1.json"); // hashes based on the file path's last modified time
tracker.addPath("data/file2.json"); // hashes based on the file path's content
tracker.addMtime("some_dir"); // hashes based on the descendants mtime
tracker.addPath("some_dir"); // hashes based on the descendants contents
tracker.addValue(123456); // hashes a specific value, which could have a source anywhere else
// multiple paths or values
tracker.addPaths(paths);
tracker.addMTimes(otherPaths);
tracker.addValues(values);
// will always run if the output path doesn't exist
tracker.addOutputPath("output/data.json");
// run if changed
if (tracker.hasChangedSync()) {
await doStuff();
tracker.commitSync();
}
// or as a single call
await tracker.runIfChanged(async () => {
await doStuff();
});
// builder pattern
await $.changeTracker(import.meta, "data files")
.addPaths(paths)
.addOutputPath("output/data.json")
.runIfChanged(async () => {
await createOutputDataJsonFile();
});
The hash could be saved in local storage.
.abort() not working properly when running deno task <task-name>
Ran into a problem on a production pup instance running a web server using command deno task prod
. Once started, the server cannot be killed programatically. Pup uses dax internally.
The problem is solved by starting the server using command deno run instead of deno task.
When aborting deno task prod
using CTRL+C at the command line, everything works as it should.
Complete repro at https://github.com/Hexagon/dax-issue-146
Repro steps
Running deno run -A main.ts directly using dax works fine
# First start - all good,
> deno run -A works.ts
Listening...
... aborted with exit code 124
# Second start - all good
> deno run -A works.ts
Listening...
... aborted with exit code 124
Running deno task prod using dax seem to leave the actual deno process hanging
# First start - all good, running deno task prod using dax
> deno run -A failure.ts
Listening...
... aborted with exit code 124
# Second start - the server from the first start is still listening on the port, even though everything see fine?
> deno run -A failure.ts
AddrInUse: Address already in use
... aborted with exit code 124
Code for repro
deno.json
{
"tasks": {
"prod": "deno run -A main.ts"
}
}
main.ts
const server = Deno.listen({ port: 8083 });
console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
/* ... the rest of the example server imlementation */
works.ts
import $ from "https://deno.land/x/dax/mod.ts";
const child = $`deno run -A main.ts`.spawn();
// abort the child after 3s
await $.sleep("1s");
child.abort();
await child; // Error: Aborted with exit code: 124
failure.ts
import $ from "https://deno.land/x/dax/mod.ts";
const child = $`deno task prod`.spawn();
// abort the child after 3s
await $.sleep("1s");
child.abort();
await child; // Error: Aborted with exit code: 124
Add tests for `$.request`
It's getting more complicated. It could use some tests that works with a local server.
Edit: Started to add this in #55
Add parallelism example to readme
Just an example that uses Promise.all
for people who don't know about that in JavaScript.
`registerCommand(s)` string literal completion
Hello and thanks for the super useful project!
I was fiddling around with the typings for the CommandBuilder
and registerCommand
/registerCommands
in order to support auto completion for custom commands that have been added and was wondering if you would be interested in adding this feature in. It does add a bit of complexity to the CommandBuilder
types but I think it can be pretty useful in the long run.
Here is a quick demo of how it works:
Screen.Recording.2023-01-03.at.8.21.48.AM.mov
And here is the commit on my fork (still needs type tests and I'm sure I need to add the LiteralUnion
in a few more spots): curtislarson@cd24df6
Let me know your thoughts!
Ability to pipe stdout/stderr of another command to stdin of another
It should be possible to do something like:
const child = $`echo 1 && sleep 10 && echo 2`.stdout("piped").spawn();
await $`deno eval 'await Deno.stdin.readable.pipeTo(Deno.stdout.writable);'`.stdin(child.stdout());
I think probably the return value of spawn()
should instead be a special kind of Promise that has methods like .stdout()
and .stderr()
on it.
I think it's ok that this is a little verbose since this isn't a common scenario.
Bug with `stdin`?
In my current project, we have a few instances where we are using pipes in order to feed data to certain commands, e.g. echo something | kubectl apply -f -
for applying kubernetes objects. As PipeSequence
is currently unsupported, I have tried converting them over to use the .stdin()
method on the $
helper. However I have noticed when doing so that my processes seem to hang -- I can see the subprocess fire up, but it never exits.
In further debugging the issue I was able to determine that the stdin
stream was being written out OK, however, it seems that the command consuming the stream (kubectl
in my case) was waiting for some sort of flush operation or EOF. e.g.:
const someYaml = 'pretend this is real';
console.log({ someYaml });
const result = await $`kubectl apply -f -`.stdin(someYaml).text();
console.log(result);
results in:
{ someYaml: "pretend this is real" }
and kubectl
will just sit there. However, if I quickly hack at executeCommandArgs
and move the stdin.close()
for the subprocess from the finally
block into the actual writeStdin
function, so that the stream is closed once the content has been written, kubectl
completes successfully:
diff --git a/src/shell.ts b/src/shell.ts
index 6e4794b..1af3a02 100644
--- a/src/shell.ts
+++ b/src/shell.ts
@@ -560,7 +560,6 @@ async function executeCommandArgs(commandArgs: string[], context: Context) {
completeController.abort();
context.signal.removeEventListener("abort", abortListener);
p.close();
- p.stdin?.close();
p.stdout?.close();
p.stderr?.close();
}
@@ -571,6 +570,7 @@ async function executeCommandArgs(commandArgs: string[], context: Context) {
return;
}
await pipeReaderToWriter(stdin, p.stdin!, signal);
+ p.stdin?.close();
}
async function readStdOutOrErr(reader: Deno.Reader | null, writer: ShellPipeWriter) {
results in:
{ someYaml: "pretend this is real" }
error: error validating "STDIN": error validating data: invalid object to validate; if you
choose to ignore these errors, turn validation off with --validate=false
{ result: "" }
Which is more in line with what I would expect to see -- kubectl
wonking via stderr
in this case, or successfully completing if I were feeding it real junk.
I can submit a PR for the above change easily enough (tests will pass with the change), but I wanted to double check first to make sure I wasn't missing something obvious with how to use this. Its been a long week...
Thanks!
Ability to easily show progress bars on `$.request`
Maybe something like:
const data = await $.request("https://plugins.dprint.dev/info.json")
.showProgress()
.json()
Somewhat requires #47 in order to display the download sizes nicely.
Get writable stream of file at `PathRef`
It should be easy to get a writable stream of a file from a PathRef
in a non-error prone way.
Additions to `PathRef`
components(): Iterator<string>
startsWith(text: string | PathRef | URL): boolean;
endsWith(text: string): boolean;
equals(path: string | URL | PathRef): boolean;
linkTo / linkToSync
methods (copysymlinkTo
)
Update `$.raw` js docs to talk about `$.escapeArg`
Allow providing an array of arguments via template literal substitution
Right now there’s no easy way to provide an array of arguments.
This should work:
const args = [“arg1”, “arg 2”];
await $`command ${args}`;
Path API
It would be nice if instead of writing stuff like this:
try {
return JSON.parse(Deno.readTextFileSync(this.#filePath)) as { ip: string };
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
return undefined;
} else {
throw err;
}
}
...if I could instead do:
const maybeData = $.path(this.#filePath).maybeJsonSync<{ name: string }>();
const data = $.path(this.#filePath).jsonSync<{ name: string }>();
I will work on this probably this week.
Provide a progress API?
I have noticed in long running processes, I would like to give a "progress" message... both a determinate and indeterminate variety:
- Progress bar, where the percent complete is known
- Activity/progress/spinner, where the completion endpoint is known, but would like to provide a spinner to indicate activity.
Investigate caching wasm file
IIRC, I think right now it downloads on every run? Not good. (maybe this just requires updating wasmbuild)
Group versus Indent?
I have found the .logIndent()
API to be odd/confusing. Having to wrap everything in some sort of async handler makes it very difficult to write straight forward imperative code. I personally like the console.group()
model, where there is a set state which gets indented and outdented in the order of execution.
Permissions documentation
import $ from "https://deno.land/x/[email protected]/mod.ts";
//
// run a command
await $`echo 5`; // outputs: 5
Requires
✅ Granted network access to "deno.land".
✅ Granted env access to all.
✅ Granted read access to <CWD>.
Are these really needed ? and if so maybe we can document them
Also do you happen to have a good way to figure out what's asking for permission, I think I'll try to deno run --inspect-brk later
Add `noThrow(exit_code, ...)` to `CommandBuilder`
Similar to $.request
: https://deno.land/x/[email protected]/src/request.ts?source#L135
Rename symlink creation methods
The methods names should be left to right least specific to most specific and prefer absolute symlinks:
createAbsoluteSymlinkTo
->createSymlinkTo
createRelativeSymlinkAt
->createSymlinkRelativeTo
-- I'm not sure why this one is opposite with "At"`. I'll probably remember when I go to look at this issue, but I think it was just misnamed.
Consider being able to spawn the executable or script at a `PathRef`
It would be nice to be able to spawn an executable or script with shebang at a PathRef
like so:
const ciScript = $.path("./.github/workflows/ci.generate.ts");
await ciScript.run();
Investigate and improve permission prompting
I think the permission prompting could be a little better and explanatory.
Edit: investigated all permission prompts...
- Once deno supports Wasm modules then we can get rid of needing to save and read the cache directory.
- Calling Deno.cwd() is necessary when the shell is created and cannot be lazily evaluated since it could change while the shell is executing, which would lead to very unexpected behaviour. This can be bypassed by providing an explicit
cwd
option. - Getting env access to all when the shell is initialized is necessary in order to get the environment state when the shell is spawned (can't be lazily evaluated, which is the same issue as the last point). This could be mitigated by supporting
clearEnv()
in the future.
Basic argument parsing
Generally when writing scripts, you don't need complex argument parsing and want something that can easily be figured out by looking at the code. You also don't need help text. For that reason, I think an approach like the following might be more appropriate than something more featureful like deno_std's argument parser.
$.args.on("prepare", () => {
console.log("Preparing...");
});
$.args.on("build", () => {
console.log("Building...");
});
> deno run -A script.ts prepare
Preparing...
> deno run -A script.ts build
Building
> deno run -A script.ts other
Unknown command: other
Possibilities:
- prepare
- build
More complex example:
const args = $.args(); // shorthand for `const args = $.args(Deno.args);`
await args.on("build", async (args) => {
await args.on("data", (args) => {
console.log(1);
if (args.hasFlag("force") || args.hasFlag("f") || hasChanged()) {
await buildData();
console.log(2);
}
});
// can be sync or async
args.on("info", (args) => {
console.log(3);
});
});
args.on("version", (args) => {
console.log(4);
});
// no match here causes a non-zero exit code that shows all the found options
> deno run -A script.ts build data
1
2
> deno run -A script.ts build data
1
> deno run -A script.ts build data --force
1
2
> deno run -A script.ts build data -f
1
2
> deno run -A script.ts build info
3
> deno run -A script.ts build other
Unknown command: build other
Possibilities:
- build data
- build info
> deno run -A script.ts version
4
> deno run -A script.ts invalid
Unknown command: invalid
Possibilities:
- build
- version
- People could call a function on
args
to prevent the "unknown command" error (maybeargs.markHandled()
or something). $.args.hasFlag("force")
for--force
and$.args.hasFlag("f")
for-f
$.args.get("time"): Arg | undefined
-- always requires an equals sign$.args.getOrThrow("time"): Arg | undefined
$.args.getOrThrow("time").number()
$.args.throwUncapturedFlags()
- Throw for any flags that haven't been captured (maybe)
progress test fail
I tested already the progress api and it works as expected
I tried to checkout the repo, but from some reason this test fails https://github.com/dsherret/dax/blob/main/mod.test.ts#L881
progress => ./mod.test.ts:881:6
error: AssertionError: Values are not equal:
[Diff] Actual / Expected
[
+ "Downloading Test",
]
throw new AssertionError(message);
^
at assertEquals (https://deno.land/[email protected]/testing/asserts.ts:190:9)
at file:///home/mrcool/dev/deno/others/dax/mod.test.ts:886:3
I followed with a debugger and it seems for some reason the instantiation seems to fail here https://github.com/dsherret/dax/blob/main/src/console/utils.ts#L166 , so its weird I get the assertion error then, how did it reach there , maybe assertEquals has some internal try catch
I made sure I regenerated the wasm file with deno task wasmbuild, but same test error
Option to echo command before it's run
For example:
const example = "example";
await $`echo ${example}`.printCommand();
Outputs:
> echo example
example
Not sure about the name though. This is useful for debugging purposes especially. Probably the >
should be white and the command text light grey.
Text written to piped stdout/stderr is printed out of order
When using the default stdout
/stderr
streams for a $
command, text written to the different streams in a particular order can appear out of order when printed.
Example
if I run deno run -A main.ts
on the following file:
// main.ts
import $ from "https://deno.land/x/[email protected]/mod.ts";
await $`deno eval 'console.log("1: out"); console.error("2: err"); console.log("3: out"); console.log("4: out"); console.error("5: err");'`
the output printed to the terminal is:
1: out
3: out
2: err
5: err
4: out
By contrast, if I inherit the streams from the parent process as:
// main.ts
import $ from "https://deno.land/x/[email protected]/mod.ts";
await $`deno eval 'console.log("1: out"); console.error("2: err"); console.log("3: out"); console.log("4: out"); console.error("5: err");'`
.stdout("inherit").stderr("inherit");
then the output to the terminal is:
1: out
2: err
3: out
4: out
5: err
Is this "out of order" output because the streams are buffered when not inherited?
Selection API
It would be nice to have a selection and multi-selection api. I worked on it previously and have a branch locally, but it looks like getting arrow key presses is completely broken in Deno on Windows (denoland/deno#5945), so that will need to be fixed first.
Replace `fs` and `path` exports with `PathRef` functionality
We should remove the fs
and path
exports and replace it with functionality in PathRef
.
How about adding pipe function to command
Hello,
Thanks for a great tool.
I wanted to write neatly by chaining pipe() to command call.
I wrote the following code. This met my requirements.
What do you think?
import { CommandBuilder } from "https://deno.land/x/[email protected]/mod.ts";
CommandBuilder.prototype.pipe = function (next: CommandBuilder) {
const p = this.stdout("piped").spawn();
return next.stdin(p.stdout());
};
const ret = await $`echo "foo\nbar"`
.pipe($`grep foo`)
.text();
console.log(ret);
Ability to format value as bytes in progress bars
The ${completed}/${total}
should have a way to be formatted as MiB
, GiB
, etc. Ideally the API would be very simple and opinionated (so no custom formatting)... maybe something like:
const pb = $.progress(`Downloading ${url}`)
.kind("bytes") // this
.length(byteCount);
Attempt to still resolve commands when there’s no PATH env var
Someone may be using this and not want to grant env var permissions (even though --allow-run is effectively --allow-all). It should still attempt to resolve commands in that scenario.
Docker-Build Style Partial Window Scrolling
dax
should implement a new feature that enables partial window scrolling in the terminal, similar to the behavior seen in the Docker build process.
The current behavior of dax when running scripts or applications that produce a large amount of output in the terminal makes it difficult to navigate through the output as it scrolls off the visible portion of the terminal window. The introduction of this feature would greatly enhance the developer experience by allowing users to easily scroll through and review the output without losing context.
This would require capturing output of a subcommand, limiting it to N lines, and performing a partial scroll of the terminal window when those N lines overflow.
An example, limited to 5 lines, might look like the below. Previous lines (output before the deno_napi
line) would be hidden from view.
X running `cargo build`
Compiling deno_napi v0.37.0 (/Users/matt/Documents/github/deno/deno/ext/napi)
Compiling deno_url v0.107.0 (/Users/matt/Documents/github/deno/deno/ext/url)
Compiling deno_ffi v0.94.0 (/Users/matt/Documents/github/deno/deno/ext/ffi)
Compiling deno_net v0.99.0 (/Users/matt/Documents/github/deno/deno/ext/net)
Compiling deno_fs v0.17.0 (/Users/matt/Documents/github/deno/deno/ext/fs)
CommandBuilder#clearEnv()
This would just not consult the current environment (so no permission prompt) when creating the command context’s env vars.
Add `$.setPrintCommand(true)` to mutate configuration of a `$`
There are certain configuration settings that are useful to be able to mutate on an existing $
rather than constructing a new one. This mutable configuration should never be anything that could potentially break other code, but for logging purposes being able to modify if a command is printed to the console seems useful.
So instead of:
import { build$, CommandBuilder } from "...";
const $ = build$({
commandBuilder: new CommandBuilder().printCommand(),
});
Users could do:
import { $ } from "...";
$.setPrintCommand(true);
Add an api to download files to disk
Maybe it could be another override to request
the advantage it would skip loading the file into memory while keeping the cool progress bar
Quoting doesn't always work correctly
import { $ } from "./mod.ts";
const a = "a/b";
await $`mkdir ${a}/c`.printCommand();
> mkdir 'a/b'/c
error: Uncaught (in promise) "Unexpected character.\n /c\n ~"
Implement pwd command
Seems we're missing this really easy command to implement.
pwd
- Prints the name of
the current/working directory.
Wasm cache versionning
I didn't run into a problem yet, but just reading the code, it seems like wasm caching just checks if the file exists locally
So if the wasm file gets new apis, it wont be re-downloaded again because it exists already
Maybe the wasm caching need to be aware of dax version
Ability to build a `$` with additional properties
For example:
import { build$, CommandBuilder, RequestBuilder } from "https://deno.land/x/dax/mod.ts";
const commandBuilder = new CommandBuilder()
.cwd("./subDir")
.env("HTTPS_PROXY", "some_value");
const requestBuilder = new RequestBuilder()
.header("SOME_NAME", "some value");
const $ = build$({
commandBuilder,
requestBuilder,
// this part is new
extras: {
sayHi() {
console.log("Hi!");
},
},
});
$.sayHi(); // outputs "Hi!"
const new$ = $.build$({
// this part is new
extras: {
sayBye() {
console.log("Bye!");
},
},
});
new$.sayHi(); // outputs "Hi!"
new$.sayBye(); // outputs "Bye!"
Add sync command api
Hello, thanks for this project
I mostly use this in repl so I end up writing a lot of await $c1 await $c2, I feel like those awaits are really unneeded and verbose. It would be nice if ax supported a sync api using Deno.spawnSync
Don't throw on timeout if `RequestBuilder.noThrow` is set
In the following example:
import { $ } from "https://deno.land/x/[email protected]/mod.ts";
await $.request("https://example.com").noThrow().timeout(10).fetch();
unless example.com
responds alarmingly fast, this code will throw an AbortError
when the request is aborted thanks to the timeout(10)
.
This is the expected behavior of the abort controller, but in my opinion, is unexpected when the method you chain onto the RequestBuilder
is called "noThrow
".
In my opinion, either of the following would help things be more intuitive when noThrow
is set and a timeout
is triggered:
(a) rename noThrow
to something else (ignoreHTTPStatus
?) - this is a breaking change, but maybe with the module at v <1.0.0
, that's ok?
(b) chain a catch
onto the RequestBuilder.fetch
method which simply returns undefined
This is just a suggestion, so as always feel free to close as won't fix
if you feel that the API already makes sense for the majority of users, but wanted to surface the idea just in case!
Cheers, and thanks for considering :)
`resolveCommand` does not always resolve correctly (error in `deno_which`)
It appears that resolveCommand
fails to correctly resolve the location of commands that are available on the system path. This causes scripts to fail with the error thrown here.
The following simple example will crash:
import $ from "https://deno.land/x/[email protected]/mod.ts"
const winget = await $`winget --version`.text();
// console.log(await $.which("winget"));
// console.log(await $.commandExists("winget"));
The issue lies in deno_which.
Here is a failing test case for deno_which
:
import {
assertEquals,
assertRejects,
} from "https://deno.land/[email protected]/testing/asserts.ts";
import { Environment, which, whichSync } from "./mod.ts";
const expectedWingetLocation = await getLocation("winget");
Deno.test("should get path", async () => {
await runTest(async (which) => {
const result = await which("winget");
checkMatches(result, expectedWingetLocation);
});
});
Deno.test("should get path when using exe on windows", {
ignore: Deno.build.os !== "windows",
}, async () => {
await runTest(async (which) => {
const result = await which("winget");
checkMatches(result, expectedWingetLocation);
});
});
async function runTest(
action: (
whichFunction: (
cmd: string,
environment?: Environment,
) => Promise<string | undefined>,
) => Promise<void>,
) {
await action(which);
await action((cmd, environment) => {
try {
return Promise.resolve(whichSync(cmd, environment));
} catch (err) {
return Promise.reject(err);
}
});
}
function checkMatches(a: string | undefined, b: string | undefined) {
if (Deno.build.os === "windows") {
if (a != null) {
a = a.toLowerCase();
}
if (b != null) {
b = b.toLowerCase();
}
}
assertEquals(a, b);
}
async function getLocation(command: string) {
const cmd = Deno.build.os === "windows"
? ["cmd", "/c", "where", command]
: ["which", command];
const p = await Deno.run({
cmd,
stdout: "piped",
});
try {
return new TextDecoder().decode(await p.output()).split(/\r?\n/)[0];
} finally {
p.close();
}
}
Add a default timeout to `$.request`
I think that a case can be made in either direction for whether or not $.request
should have a default timeout (as of now, it does not have one). Since the principle use case of dax
is as a scripting tool, presumably it will frequently have uses in:
- CI / CD pipelines
- Background processing tasks
and other places where it can be reasonably expected that a human is not sitting there watching a script proceed (and able to notice that a request is hung due to any number of potential issues).
Due to the fact that the current implementation makes no distinction between the various parts of making a request (and I don't know if it even reasonably can while remaining a simple fetch
under the hood) such as the time it takes to connect, to lookup DNS, to actually download the data, etc, I think it makes sense to have this default timeout be "generous" within the context of calling an API or downloading a webpage. I would suggest something like 60_000
(one minute) as the default.
I looked around at default http clients from different ecosystems (ruby, php, go, etc) and found that it's pretty common not to have a default timeout, so the precedent is definitely there to keep it as is, but in my opinion it makes sense to have a default for the role that dax
fills.
Thanks for considering!
Interleaved stdout and stderr
Thank you for your work on this project!
It would be excellent if there was a way to get the interleaved stdout
and stderr
as it occurred in realtime in the shell sub-process. This functionality is present in zx
for example in the ProcessOutput#toString() method. It's also present in the execa Node module via the all
property of the result object.
I use zx
a lot for operational/orchestration shell scripting, but would love to switch over to dax
since I love Deno and the principles of your project align better with my preferences, and having this capability would help make that transition smoother.
Cheers!
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.