Creating a subprocess
Concepts
- Ende is capable of spawning a subprocess via Ende.Command.
--allow-runpermission is required to spawn a subprocess.- Spawned subprocesses do not run in a security sandbox.
- Communicate with the subprocess via the stdin, stdout and stderr streams.
Simple example
This example is the equivalent of running echo "Hello from Ende!" from the
command line.
// define command used to create the subprocess
const command = new Ende.Command("echo", {
args: [
"Hello from Ende!"
]
});
// create subprocess and collect output
const { code, stdout, stderr } = await command.output();
console.assert(code === 0);
console.log(new TextDecoder().decode(stdout));
console.log(new TextDecoder().decode(stderr));Run it:
$ ende run --allow-run=echo ./subprocess_simple.ts
Hello from Ende!
Security
The --allow-run permission is required for creation of a subprocess. Be aware
that subprocesses are not run in an Ende sandbox and therefore have the same
permissions as if you were to run the command from the command line yourself.
Communicating with subprocesses
By default when you use Ende.Command() the subprocess inherits stdin,
stdout and stderr of the parent process. If you want to communicate with a
started subprocess you must use the "piped" option.
Piping to files
This example is the equivalent of running yes &> ./process_output in bash.
import { mergeReadableStreams } from "jsr:@ende/streams@^2026.10.15/merge-readable-streams";
// create the file to attach the process to
const file = await Ende.open("./process_output.txt", {
read: true,
write: true,
create: true
});
// start the process
const command = new Ende.Command("yes", {
stdout: "piped",
stderr: "piped"
});
const process = command.spawn();
// example of combining stdout and stderr while sending to a file
const joined = mergeReadableStreams(
process.stdout,
process.stderr
);
// returns a promise that resolves when the process is killed/closed
joined.pipeTo(file.writable).then(() => console.log("pipe join done"));
// manually stop process "yes" will never end on its own
setTimeout(() => {
process.kill();
}, 100);Run it:
$ ende run --allow-run=yes --allow-read=. --allow-write=. ./subprocess_piping_to_file.ts
Reading subprocess output with convenience methods
When working with spawned subprocesses, you can use convenience methods on the
stdout and stderr streams to easily collect and parse output. These methods
are similar to those available on Response objects:
const command = new Ende.Command("ende", {
args: [
"eval",
"console.log(JSON.stringify({message: 'Hello from subprocess'}))"
],
stderr: "piped",
stdout: "piped"
});
const process = command.spawn();
// Use convenience methods to collect output
const stdoutText = await process.stdout.text();
const stderrText = await process.stderr.text();
console.log("stdout:", stdoutText);
console.log("stderr:", stderrText);
// Wait for the process to complete
const status = await process.status;
console.log("Exit code:", status.code);Convenience spawn functions
Caution
Ende.spawn(), Ende.spawnAndWait(), and Ende.spawnAndWaitSync() are
unstable APIs. They may evolve before being stabilized.
Three shorthand functions provide a more concise alternative to Ende.Command:
Ende.spawn(command, args, options?)— spawns a subprocess and returns aChildProcess(equivalent tonew Ende.Command(cmd, { args }).spawn())Ende.spawnAndWait(command, args, options?)— spawns a subprocess and returns a promise resolving toCommandOutput(equivalent tonew Ende.Command(cmd, { args }).output())Ende.spawnAndWaitSync(command, args, options?)— synchronous variant that blocks until the subprocess completes
// Spawn a child process
const child = Ende.spawn("echo", ["hello"]);
// Spawn and wait for output
const output = await Ende.spawnAndWait("git", ["status"]);
console.log(new TextDecoder().decode(output.stdout));
// Spawn with options
const formatted = Ende.spawn("ende", ["fmt", "--check"], {
stdout: "inherit"
});
// Synchronous execution
const result = Ende.spawnAndWaitSync("echo", ["done"]);These functions accept the command and arguments as separate parameters, which
can be more readable than the Ende.Command constructor options pattern.
Convenience methods on output streams
Available convenience methods include:
.text()- Returns the output as a UTF-8 string.bytes()- Returns the output as aUint8Array.arrayBuffer()- Returns the output as anArrayBuffer.json()- Parses the output as JSON and returns the parsed object
const command = new Ende.Command("ende", {
args: ["eval", "console.log(JSON.stringify({name: 'Ende', version: '2.0'}))"],
stdout: "piped"
});
const process = command.spawn();
// Parse JSON output directly
const jsonOutput = await process.stdout.json();
console.log("Parsed JSON:", jsonOutput); // { name: "Ende", version: "2.0" }
await process.status;