CLI Helpers
Shared shape for blamejs CLI subcommands AND for operators writing their own one-shot CLI scripts on top of the framework. Three patterns recur across every CLI command (migrate, seed, audit, vault, backup, api-key): bootstrap a headless b.createApp instance from --data-dir and shut it down cleanly; report success / error / usage with a consistent blamejs prefix on stderr plus canonical exit codes (0 ok, 1 runtime failure, 2 arg error); resolve a passphrase from a -- or env var into the Buffer shape the underlying crypto primitives accept.
The reporter writes through whichever stream ctx.stdout / ctx.stderr point at — process.stdout / process.stderr in production, captured stream stubs in tests — so the same handler is testable without spawning a child process.
b.cliHelpers.makeReporter(ctx, prefix) #
Build a reporter bound to a CLI context (ctx.stdout / ctx.stderr) and a verb prefix. Every stderr message produced by .error / .usage gets the prefix. Methods return canonical Unix exit codes — 0 for ok / helpStdout, 1 for error (override via second arg), 2 for usage (argument-error convention).
var ctx = { stdout: process.stdout, stderr: process.stderr };
var report = b.cliHelpers.makeReporter(ctx, "blamejs vault seal");
var ok = report.ok("sealed: /data/vault"); // → 0
var fail = report.error("decrypt failed"); // → 1
var arg = report.error("missing --data-dir", 2); // → 2
var help = report.usage("Usage: blamejs vault ..."); // → 2
b.cliHelpers.resolvePassphrase(args, ctx, opts) #
{
flag: string (CLI flag name, e.g. "passphrase" reads args.flags.passphrase),
envVar: string (env var fallback, e.g. "BLAMEJS_VAULT_PASSPHRASE"),
}
Resolve a passphrase from a CLI flag (preferred) or an env var (fallback) into a UTF-8 Buffer — the shape vault / crypto primitives accept. Returns null when neither source produced a non-empty string; the caller decides whether absence is a hard error (vault seal) or a soft default (plaintext-mode dev data dir).
var args = { flags: { passphrase: "hunter2" } };
var ctx = { env: { BLAMEJS_VAULT_PASSPHRASE: "envval" } };
var pp = b.cliHelpers.resolvePassphrase(args, ctx, {
flag: "passphrase",
envVar: "BLAMEJS_VAULT_PASSPHRASE",
});
pp.toString("utf8"); // → "hunter2" (flag wins over env)
var none = b.cliHelpers.resolvePassphrase({ flags: {} }, { env: {} }, {
flag: "passphrase", envVar: "BLAMEJS_VAULT_PASSPHRASE",
});
none; // → null
b.cliHelpers.bootApp(opts) #
{
dataDir: string (filesystem path to the data dir; required),
vaultMode: "wrapped" | "plaintext" (default "wrapped" — wrapped
reads BLAMEJS_VAULT_PASSPHRASE from `opts.env`),
dbAtRest: "plain" | "encrypted" (default "plain"),
env: object (env-var bag; default process.env),
}
Boot a headless b.createApp instance from a data dir so a CLI script (framework subcommand or operator-written tool) can operate against the same vault + DB + audit chain the live app uses, with no HTTP listener attached. Returns { b, app } where b is the framework module and app is the headless instance — caller MUST await booted.app.shutdown() in a finally so SQLite file handles and the cluster lease release.
The default DB at-rest mode is plain because CLI runs are short-lived ops that never serve requests; encrypted-at-rest needs a tmpfs handle that wouldn't survive CLI exit anyway. Operators running against a production data dir whose DB is encrypted-at-rest pass dbAtRest: "encrypted" and ensure BLAMEJS_TMPDIR is set.
async function run() {
var booted;
try {
booted = await b.cliHelpers.bootApp({
dataDir: "./data",
vaultMode: "plaintext",
env: process.env,
});
var rows = await booted.app.db.all("SELECT count(*) AS n FROM _blamejs_audit_log");
return rows[0].n;
} finally {
if (booted) await booted.app.shutdown();
}
}
Last updated 2026-08-08T16:39:49.652Z by seeder.