@tasteee/sheezy
v1.0.0
Published
A tiny, friendly shell runner with {{template}} interpolation, cwd tracking, and show / hide / intercept output. Every call returns a [result, error] tuple.
Maintainers
Readme
🐚 sheezy
A tiny, friendly shell runner for Node.
npm install @tasteee/sheezyYou want to run shell commands from a script. You want to drop variables into
those commands without gluing strings together. You want cd to actually
stick. And sometimes you want the output printed, sometimes hidden, sometimes
handed to you.
Hi. I'm that library, it's me.
import { sheezy } from "@tasteee/sheezy";
const shell = sheezy.create();
await shell.run("npm run build");
shell.data.repoUrl = "https://github.com/tasteee/sheezy";
await shell.run("git clone {{repoUrl}}");Just a run method that does the obvious thing.
Create a shell
A shell is a small object that remembers your working directory and your template data between calls.
const shell = sheezy.create();You can hand it a starting directory, some data, and a few defaults:
const shell = sheezy.create({
cwd: "/Users/you/projects",
data: { branch: "main" },
isSilent: false, // default is false
});Everything is optional. With no arguments you get a shell with process.cwd() as the cwd and live
output.
Run a command
const [result, runError] = await shell.run("ls -la");run always returns a [result, error] tuple. No try/catch required. On
success you get the result and a null error; on failure you get a null
result and the error.
const [result, runError] = await shell.run("npm test");
if (runError) {
console.error("tests failed", runError);
return;
}
console.log(result.exitCode); // 0The result is a plain object, the same shape every time:
type RunResultT = {
command: string; // the command that ran (after interpolation)
cwd: string; // the directory it ran in
exitCode: number | undefined;
stdout: string; // captured when hidden or intercepted
stderr: string;
};Interpolate from shell.data
Put {{placeholders}} in your command and sheezy fills them from shell.data
before running. Set values however you like — up front, or as you go.
const shell = sheezy.create({ data: { folderName: "sheezy" } });
shell.data.repoUrl = "https://github.com/tasteee/sheezy";
await shell.run("git clone {{repoUrl}} {{folderName}}");
await shell.run("cd {{folderName}}");Missing data quietly becomes an empty string. If you'd rather fail loudly when a placeholder has no value, just say so.
const shell = sheezy.create({ throwMissingData: true })
await shell.run("git clone {{repoUrl}}");
// throws: sheezy: missing template data for repoUrlcd does the thing
cd is special. Instead of spawning a throwaway process that forgets where it
went, sheezy updates shell.data.cwd and every later command runs from there.
const shell = sheezy.create();
await shell.run("cd packages/core");
await shell.run("npm install"); // runs inside packages/coreChain it with && and the cd still applies to everything after it — the
commands run one after another, left to right:
await shell.run("cd packages/core && npm install && npm run build");Need an absolute path from the current directory? resolve tracks cwd too:
const buildDir = shell.resolve("dist"); // <cwd>/distShow it, hide it, or intercept it
By default, output streams straight to your terminal as it happens — exactly like you'd typed the command yourself.
await shell.run("npm run build"); // printed livePass isSilent to keep it quiet. Nothing prints, but it's still captured on
the result so you can inspect it:
const [result] = await shell.run("npm run build", { isSilent: true });
console.log(result.stdout); // you have it, the terminal didn'tOr take the wheel completely with onOutput. You get every chunk of stdout and
stderr as it arrives — log it, parse it, stream it to a UI, whatever:
await shell.run("npm run build", {
onOutput: (output) => {
progressBar.append(output);
},
});Run options, all together
await shell.run("deploy {{service}}", {
isSilent: false, // hide output (default: false)
onOutput: undefined, // intercept output chunk by chunk
throwMissingData: false, // throw on missing {{placeholders}}
shell: false, // hand the raw command to the system shell (see "Good to know")
env: { NODE_ENV: "production" }, // extra environment variables
});Any of these can also be set on sheezy.create(...) as defaults, and a per-run
option always wins over the shell-level default.
Good to know
sheezy keeps command parsing simple and predictable. A command is split on spaces — quotes are not parsed. So this won't do what you might expect, the quotes are passed through as literal characters:
await shell.run('git commit -m "two words"'); // ⚠️ args: ["two, words]When you need quotes, spaces inside an argument, pipes, globs, or redirects,
turn on shell and let your system shell do the parsing:
await shell.run('git commit -m "two words"', { shell: true });A few more things worth knowing:
&&is a simple split. sheezy splits on&&itself socdcan take effect between steps. It's textual, so avoid a literal&&inside a quoted argument or an interpolated value.&(parallel) isn't supported. Commands always run one at a time, left to right.cddoesn't check the path. It updatesdata.cwdoptimistically; a bad path surfaces as an error on the next command that runs there.
TypeScript
sheezy is written in TypeScript and ships its own types. The handy ones are exported if you want them:
import { sheezy } from "@tasteee/sheezy";
import type {
ShellT,
ShellDataT,
ShellOptionsT,
RunOptionsT,
RunResultT,
RunReturnT,
} from "@tasteee/sheezy";Notes
- ESM only. sheezy is published as an ES module.
import, notrequire. - Node 18.19+ (it builds on execa 9).
&&runs commands sequentially and stops at the first failure.
License
MIT © tasteee
