strapkit

Running processes

Run shell commands inside Strapkit and react to their output.

Strapkit gives you two ways to run things:

  • sk.exec(script) — run a script, get back { stdout, stderr, exitCode }. Use this for builds, tests, or any one-shot command where you want the output as a value.
  • sk.shellExec(command) — type a command into an xterm.js- attached shell. Use this for dev servers, TUIs, or anything you want the user to see in a terminal.

For full method signatures see the Processes API reference.

Run a script and use its output

const { stdout, exitCode } = await sk.exec('ls /app');
if (exitCode === 0) {
  console.log(stdout);
}

Multi-line scripts work the same way. Pipes, redirects, conditionals — all the normal shell tricks:

const result = await sk.exec('cd /app && npm install --silent 2>&1 && npm run build');
console.log(result.stdout);
console.log('exited', result.exitCode);

Start a dev server and preview it

Servers don't exit, so exec would block forever waiting for them. Use shellExec to start them, and the onPortOpen callback to know when they're ready:

sk.onPortOpen((port) => {
  if (port === 3000) sk.showPreview(port, 'my-preview');
});

sk.shellExec('cd /app && npm run dev');

onPortOpen fires the moment the server binds the port — no log scraping, no guessing.

Pass input via stdin

const { stdout } = await sk.exec('grep TODO', {
  stdin: 'a\nTODO: do the thing\nb\n',
});
// stdout: "TODO: do the thing\n"

For larger inputs, write to a file first and cat it — that's the same pattern you'd use in any shell.

Run a command without shell parsing

When arguments come from user input or some other data source, building a shell string yourself is risky. Use the args option:

const search = 'TODO';
const { stdout } = await sk.exec('grep', {
  args: ['-r', search, '/app/src'],
});
console.log(stdout);

Each entry in args is shell-quoted automatically — spaces, quotes, and metacharacters in search won't be re-interpreted by the shell.

Capture output while it's running

exec returns its output all at once when the script finishes. If you need to stream output to the user as it happens (a log panel during a long build), run the command via shellExec and read the output through the terminal that's already attached.

If you only need the final result, exec is the cleaner choice.

Run a few things in sequence

exec calls are queued — a later one waits for the earlier ones — so sequences just chain naturally:

await sk.exec('mkdir -p /app/dist');
const build = await sk.exec('cd /app && npm run build');
if (build.exitCode !== 0) throw new Error(build.stderr);
await sk.exec('cp /app/dist/index.html /var/www/');

If you want to fail fast inside a single script, use set -e:

await sk.exec(`
  set -e
  cd /app
  npm run build
  cp dist/index.html /var/www/
`);

Detect failures

exec resolves on every exit. Check exitCode (and stderr) to decide:

const { exitCode, stderr } = await sk.exec('npm test');
if (exitCode !== 0) {
  console.error(stderr);
}

exec only rejects if Strapkit itself can't run the script (the shell isn't running, or the WASM build doesn't include the runner). A non-zero exit code is a normal completion.

When to use shellExec instead

shellExec is right when:

  • You want the command running visibly in an xterm.js terminal.
  • The command is interactive (REPL, TUI, prompts).
  • The command is a long-running server you'll stop later.

For one-shot commands where you want the output as a return value, exec is simpler.

On this page