@blamejs/core 0.7.1 → 0.7.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +423 -389
- package/README.md +150 -149
- package/bin/blamejs.js +0 -0
- package/index.js +308 -282
- package/lib/api-key.js +660 -672
- package/lib/api-snapshot.js +338 -338
- package/lib/app-shutdown.js +385 -385
- package/lib/app.js +365 -365
- package/lib/archive.js +250 -250
- package/lib/atomic-file.js +544 -544
- package/lib/audit-chain.js +177 -177
- package/lib/audit-sign.js +344 -344
- package/lib/audit-tools.js +677 -677
- package/lib/audit.js +766 -766
- package/lib/auth/jwt.js +311 -311
- package/lib/auth/lockout.js +436 -436
- package/lib/auth/oauth.js +721 -721
- package/lib/auth/passkey.js +181 -181
- package/lib/auth/password.js +594 -594
- package/lib/backup/bundle.js +217 -217
- package/lib/backup/crypto.js +176 -176
- package/lib/backup/index.js +515 -515
- package/lib/backup/manifest.js +282 -282
- package/lib/break-glass.js +1338 -1338
- package/lib/bundler.js +441 -441
- package/lib/cache-redis.js +256 -266
- package/lib/cache.js +1206 -1211
- package/lib/canonical-json.js +115 -115
- package/lib/chain-writer.js +234 -234
- package/lib/cli-helpers.js +206 -206
- package/lib/cli.js +2334 -2334
- package/lib/cluster-provider-db.js +317 -317
- package/lib/cluster-storage.js +226 -226
- package/lib/cluster.js +703 -703
- package/lib/codepoint-class.js +196 -0
- package/lib/config-drift.js +301 -301
- package/lib/consent.js +222 -222
- package/lib/constants.js +191 -191
- package/lib/cookies.js +315 -315
- package/lib/credential-hash.js +322 -322
- package/lib/crypto.js +266 -266
- package/lib/csv.js +275 -286
- package/lib/db-declare-row-policy.js +267 -267
- package/lib/db-declare-view.js +420 -421
- package/lib/db-query.js +406 -406
- package/lib/db-schema.js +319 -319
- package/lib/db.js +1288 -1288
- package/lib/deprecate.js +222 -222
- package/lib/dev.js +335 -335
- package/lib/dual-control.js +473 -473
- package/lib/error-page.js +420 -420
- package/lib/external-db-migrate.js +441 -441
- package/lib/external-db.js +1061 -1061
- package/lib/file-type.js +273 -273
- package/lib/file-upload.js +1136 -0
- package/lib/forms.js +422 -422
- package/lib/framework-error.js +293 -202
- package/lib/framework-schema.js +717 -717
- package/lib/gate-contract.js +971 -0
- package/lib/guard-all.js +405 -0
- package/lib/guard-archive.js +739 -0
- package/lib/guard-csv.js +816 -0
- package/lib/guard-email.js +744 -0
- package/lib/guard-filename.js +724 -0
- package/lib/guard-html.js +976 -0
- package/lib/guard-json.js +729 -0
- package/lib/guard-markdown.js +586 -0
- package/lib/guard-svg.js +976 -0
- package/lib/guard-xml.js +405 -0
- package/lib/guard-yaml.js +529 -0
- package/lib/handlers.js +350 -350
- package/lib/http-client-cookie-jar.js +508 -508
- package/lib/http-client.js +1195 -1195
- package/lib/i18n.js +878 -878
- package/lib/jobs.js +185 -185
- package/lib/log-stream-cloudwatch.js +369 -369
- package/lib/log-stream-local.js +146 -146
- package/lib/log-stream-otlp-grpc.js +410 -410
- package/lib/log-stream-otlp.js +286 -286
- package/lib/log-stream-syslog.js +302 -302
- package/lib/log-stream-webhook.js +199 -199
- package/lib/log-stream.js +330 -330
- package/lib/log.js +500 -500
- package/lib/mail-bounce.js +528 -528
- package/lib/mail-dkim.js +369 -362
- package/lib/mail.js +981 -962
- package/lib/metrics.js +683 -683
- package/lib/middleware/api-encrypt.js +936 -573
- package/lib/middleware/attach-user.js +157 -157
- package/lib/middleware/body-parser.js +1170 -1091
- package/lib/middleware/bot-guard.js +178 -178
- package/lib/middleware/compression.js +452 -452
- package/lib/middleware/cors.js +314 -314
- package/lib/middleware/csp-nonce.js +348 -348
- package/lib/middleware/csrf-protect.js +316 -316
- package/lib/middleware/db-role-for.js +264 -269
- package/lib/middleware/health.js +392 -392
- package/lib/middleware/index.js +79 -79
- package/lib/middleware/rate-limit.js +358 -358
- package/lib/middleware/request-id.js +61 -61
- package/lib/middleware/request-log.js +168 -168
- package/lib/middleware/require-auth.js +104 -104
- package/lib/middleware/security-headers.js +116 -116
- package/lib/middleware/sse.js +166 -166
- package/lib/migrations.js +383 -383
- package/lib/mtls-ca.js +518 -518
- package/lib/mtls-engine-default.js +481 -481
- package/lib/network-dns.js +632 -632
- package/lib/network-heartbeat.js +290 -290
- package/lib/network-nts.js +574 -574
- package/lib/network-proxy.js +265 -265
- package/lib/network-tls.js +328 -328
- package/lib/network.js +233 -233
- package/lib/notify.js +612 -614
- package/lib/ntp-check.js +229 -229
- package/lib/numeric-bounds.js +111 -91
- package/lib/object-store/azure-blob-bucket-ops.js +349 -349
- package/lib/object-store/azure-blob.js +488 -451
- package/lib/object-store/gcs-bucket-ops.js +351 -351
- package/lib/object-store/gcs.js +519 -479
- package/lib/object-store/http-put.js +153 -153
- package/lib/object-store/index.js +197 -197
- package/lib/object-store/sigv4-bucket-ops.js +1092 -1092
- package/lib/object-store/sigv4.js +903 -855
- package/lib/observability.js +151 -151
- package/lib/otel-export.js +269 -269
- package/lib/pagination.js +464 -464
- package/lib/parsers/index.js +80 -80
- package/lib/parsers/safe-env.js +642 -642
- package/lib/parsers/safe-ini.js +292 -292
- package/lib/parsers/safe-toml.js +784 -784
- package/lib/parsers/safe-xml.js +390 -390
- package/lib/parsers/safe-yaml.js +1015 -1015
- package/lib/permissions.js +708 -708
- package/lib/pqc-agent.js +87 -87
- package/lib/pqc-gate.js +279 -279
- package/lib/protobuf-encoder.js +190 -190
- package/lib/protocol-dispatcher.js +161 -161
- package/lib/pubsub-redis.js +167 -177
- package/lib/pubsub.js +429 -429
- package/lib/queue-local.js +476 -476
- package/lib/queue-redis.js +745 -752
- package/lib/queue-sqs.js +319 -319
- package/lib/queue.js +695 -695
- package/lib/redis-client.js +519 -489
- package/lib/request-helpers.js +340 -336
- package/lib/restore-bundle.js +237 -237
- package/lib/restore-rollback.js +259 -259
- package/lib/restore.js +409 -409
- package/lib/retry.js +376 -376
- package/lib/router.js +748 -748
- package/lib/safe-async.js +735 -735
- package/lib/safe-buffer.js +237 -237
- package/lib/safe-json.js +541 -541
- package/lib/safe-schema.js +1266 -1266
- package/lib/safe-url.js +159 -159
- package/lib/scheduler.js +706 -706
- package/lib/security-assert.js +373 -373
- package/lib/seeders.js +618 -630
- package/lib/session.js +478 -478
- package/lib/slug.js +269 -269
- package/lib/ssrf-guard.js +401 -401
- package/lib/static.js +879 -114
- package/lib/storage.js +471 -471
- package/lib/subject.js +281 -281
- package/lib/template.js +791 -791
- package/lib/testing.js +798 -798
- package/lib/time.js +310 -310
- package/lib/totp.js +302 -302
- package/lib/tracing.js +494 -494
- package/lib/uuid.js +132 -132
- package/lib/validate-opts.js +340 -270
- package/lib/vault/index.js +308 -308
- package/lib/vault/rotate.js +784 -784
- package/lib/vault/wrap.js +296 -296
- package/lib/vendor/noble-ciphers.cjs +9 -9
- package/lib/webhook.js +595 -598
- package/lib/websocket.js +1048 -1048
- package/package.json +77 -77
- package/sbom.cyclonedx.json +7 -7
package/lib/cli-helpers.js
CHANGED
|
@@ -1,206 +1,206 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* cli-helpers — shared shape for blamejs CLI subcommands AND for
|
|
4
|
-
* operators writing their own one-shot CLI scripts on top of the
|
|
5
|
-
* framework.
|
|
6
|
-
*
|
|
7
|
-
* Three patterns recur across every CLI command (`migrate`, `seed`,
|
|
8
|
-
* `audit`, `vault`, `backup`, `api-key`):
|
|
9
|
-
*
|
|
10
|
-
* 1. Bootstrap a headless `b.createApp` instance from `--data-dir`,
|
|
11
|
-
* operate against vault + DB + audit chain, shut down cleanly.
|
|
12
|
-
* 2. Report success / error / usage with a consistent
|
|
13
|
-
* `blamejs <verb> <sub>: <message>` prefix on stderr + canonical
|
|
14
|
-
* exit codes (0 ok, 1 runtime failure, 2 arg error).
|
|
15
|
-
* 3. Resolve a passphrase from `--<flag>` or an env var, encode to
|
|
16
|
-
* the Buffer the underlying crypto primitive needs.
|
|
17
|
-
*
|
|
18
|
-
* var cli = b.cliHelpers;
|
|
19
|
-
*
|
|
20
|
-
* var report = cli.makeReporter(ctx, "blamejs my-tool issue");
|
|
21
|
-
* if (!args.flags["data-dir"]) return report.usage(MY_USAGE);
|
|
22
|
-
*
|
|
23
|
-
* var pp = cli.resolvePassphrase(args, ctx, {
|
|
24
|
-
* flag: "passphrase",
|
|
25
|
-
* envVar: "MY_TOOL_PASSPHRASE",
|
|
26
|
-
* });
|
|
27
|
-
* if (!pp) return report.error("--passphrase or MY_TOOL_PASSPHRASE is required", 2);
|
|
28
|
-
*
|
|
29
|
-
* var booted;
|
|
30
|
-
* try {
|
|
31
|
-
* booted = await cli.bootApp({
|
|
32
|
-
* dataDir: args.flags["data-dir"],
|
|
33
|
-
* vaultMode: "wrapped",
|
|
34
|
-
* env: ctx.env,
|
|
35
|
-
* });
|
|
36
|
-
* // do the op against booted.b.X / booted.app.db / etc.
|
|
37
|
-
* return report.ok("done");
|
|
38
|
-
* } catch (e) {
|
|
39
|
-
* return report.error(e.message);
|
|
40
|
-
* } finally {
|
|
41
|
-
* if (booted) await booted.app.shutdown();
|
|
42
|
-
* }
|
|
43
|
-
*
|
|
44
|
-
* The reporter writes through whichever stream `ctx.stdout` / `ctx.stderr`
|
|
45
|
-
* point at — `process.stdout` / `process.stderr` in production, captured
|
|
46
|
-
* stream stubs in tests — so the same handler is testable without
|
|
47
|
-
* spawning a child process.
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
var lazyRequire = require("./lazy-require");
|
|
51
|
-
var validateOpts = require("./validate-opts");
|
|
52
|
-
|
|
53
|
-
// lazyRequire the framework root so cli-helpers stays loadable in
|
|
54
|
-
// contexts that aren't the framework root (tests, operator-side
|
|
55
|
-
// scripts importing only cli-helpers). The first bootApp() call
|
|
56
|
-
// resolves the framework module; subsequent calls reuse the cached
|
|
57
|
-
// load.
|
|
58
|
-
var blamejs = lazyRequire(function () { return require("../"); });
|
|
59
|
-
|
|
60
|
-
// ---- Streams + exit-code-shaped reporting --------------------------------
|
|
61
|
-
|
|
62
|
-
function _writeLine(stream, line) {
|
|
63
|
-
if (!stream || typeof stream.write !== "function") return;
|
|
64
|
-
if (line == null) return;
|
|
65
|
-
stream.write(String(line) + "\n");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Make a reporter bound to a CLI context + a verb prefix. Every
|
|
70
|
-
* stderr message produced by .error / .usage gets the prefix.
|
|
71
|
-
*
|
|
72
|
-
* var report = cliHelpers.makeReporter(ctx, "blamejs vault seal");
|
|
73
|
-
* return report.ok("sealed: " + path); // stdout, returns 0
|
|
74
|
-
* return report.error("decrypt failed"); // stderr "blamejs vault seal: decrypt failed", returns 1
|
|
75
|
-
* return report.error("missing arg", 2); // returns 2 (arg error vs runtime)
|
|
76
|
-
* return report.usage(VAULT_USAGE); // stderr USAGE, returns 2
|
|
77
|
-
* return report.helpStdout(VAULT_USAGE); // stdout USAGE, returns 0 (for `help <verb>`)
|
|
78
|
-
*/
|
|
79
|
-
function makeReporter(ctx, prefix) {
|
|
80
|
-
if (!ctx || typeof ctx !== "object") {
|
|
81
|
-
throw new Error("cliHelpers.makeReporter: ctx is required");
|
|
82
|
-
}
|
|
83
|
-
if (typeof prefix !== "string" || prefix.length === 0) {
|
|
84
|
-
throw new Error("cliHelpers.makeReporter: prefix is required (non-empty string)");
|
|
85
|
-
}
|
|
86
|
-
var stdout = ctx.stdout || (typeof process !== "undefined" ? process.stdout : null);
|
|
87
|
-
var stderr = ctx.stderr || (typeof process !== "undefined" ? process.stderr : null);
|
|
88
|
-
return {
|
|
89
|
-
ok: function (message) {
|
|
90
|
-
if (message != null) _writeLine(stdout, message);
|
|
91
|
-
return 0;
|
|
92
|
-
},
|
|
93
|
-
error: function (message, exitCode) {
|
|
94
|
-
_writeLine(stderr, prefix + ": " + message);
|
|
95
|
-
return typeof exitCode === "number" ? exitCode : 1;
|
|
96
|
-
},
|
|
97
|
-
usage: function (usageText) {
|
|
98
|
-
_writeLine(stderr, usageText);
|
|
99
|
-
return 2;
|
|
100
|
-
},
|
|
101
|
-
helpStdout: function (usageText) {
|
|
102
|
-
_writeLine(stdout, usageText);
|
|
103
|
-
return 0;
|
|
104
|
-
},
|
|
105
|
-
// Direct access for handlers that have multi-line output.
|
|
106
|
-
write: function (line) { _writeLine(stdout, line); },
|
|
107
|
-
writeErr: function (line) { _writeLine(stderr, line); },
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ---- Passphrase resolution -----------------------------------------------
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Resolve a passphrase from a flag or env var into a UTF-8 Buffer
|
|
115
|
-
* (the shape the underlying crypto primitives accept).
|
|
116
|
-
*
|
|
117
|
-
* cli.resolvePassphrase(args, ctx, { flag: "passphrase", envVar: "BLAMEJS_VAULT_PASSPHRASE" })
|
|
118
|
-
* → Buffer | null
|
|
119
|
-
*
|
|
120
|
-
* Returns `null` when neither source produced a non-empty string. The
|
|
121
|
-
* caller decides whether absence is a hard error (vault seal) or a
|
|
122
|
-
* soft default (operator chose plaintext-mode).
|
|
123
|
-
*/
|
|
124
|
-
function resolvePassphrase(args, ctx, opts) {
|
|
125
|
-
opts = opts || {};
|
|
126
|
-
validateOpts(opts, ["flag", "envVar"], "cliHelpers.resolvePassphrase");
|
|
127
|
-
if (typeof opts.flag !== "string" || opts.flag.length === 0) {
|
|
128
|
-
throw new Error("cliHelpers.resolvePassphrase: opts.flag is required");
|
|
129
|
-
}
|
|
130
|
-
var raw = null;
|
|
131
|
-
if (args && args.flags && typeof args.flags[opts.flag] === "string" &&
|
|
132
|
-
args.flags[opts.flag].length > 0) {
|
|
133
|
-
raw = args.flags[opts.flag];
|
|
134
|
-
} else if (opts.envVar && ctx && ctx.env &&
|
|
135
|
-
typeof ctx.env[opts.envVar] === "string" &&
|
|
136
|
-
ctx.env[opts.envVar].length > 0) {
|
|
137
|
-
raw = ctx.env[opts.envVar];
|
|
138
|
-
}
|
|
139
|
-
if (raw == null) return null;
|
|
140
|
-
return Buffer.from(raw, "utf8");
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ---- Headless app bootstrap ----------------------------------------------
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Boot a serverless `b.createApp` instance from a data dir so a CLI
|
|
147
|
-
* script (the framework's own subcommands or operator-written tools)
|
|
148
|
-
* can operate against the same vault + DB + audit chain the live app
|
|
149
|
-
* uses, without standing up an HTTP listener.
|
|
150
|
-
*
|
|
151
|
-
* var booted = await cli.bootApp({
|
|
152
|
-
* dataDir: "./data",
|
|
153
|
-
* vaultMode: "wrapped", // or "plaintext"; default "wrapped"
|
|
154
|
-
* env: process.env, // BLAMEJS_VAULT_PASSPHRASE read from here
|
|
155
|
-
* });
|
|
156
|
-
* // booted.b — the framework module
|
|
157
|
-
* // booted.app — the headless app instance (call .shutdown() to clean up)
|
|
158
|
-
*
|
|
159
|
-
* Caller MUST call `await booted.app.shutdown()` in a `finally` so the
|
|
160
|
-
* SQLite file handles + cluster lease release. The default DB at-rest
|
|
161
|
-
* mode is `plain` because CLI runs are short-lived ops that never
|
|
162
|
-
* serve requests; the encrypted-at-rest mode needs a tmpfs handle that
|
|
163
|
-
* wouldn't survive the CLI exit anyway. Operators running against a
|
|
164
|
-
* production data dir whose DB is encrypted-at-rest set
|
|
165
|
-
* `dbAtRest: "encrypted"` and ensure `BLAMEJS_TMPDIR` is set.
|
|
166
|
-
*/
|
|
167
|
-
async function bootApp(opts) {
|
|
168
|
-
opts = opts || {};
|
|
169
|
-
validateOpts(opts, ["dataDir", "vaultMode", "dbAtRest", "env"], "cliHelpers.bootApp");
|
|
170
|
-
if (typeof opts.dataDir !== "string" || opts.dataDir.length === 0) {
|
|
171
|
-
throw new Error("cliHelpers.bootApp: opts.dataDir is required");
|
|
172
|
-
}
|
|
173
|
-
var vaultMode = opts.vaultMode || "wrapped";
|
|
174
|
-
if (vaultMode !== "wrapped" && vaultMode !== "plaintext") {
|
|
175
|
-
throw new Error("cliHelpers.bootApp: opts.vaultMode must be 'wrapped' or 'plaintext'");
|
|
176
|
-
}
|
|
177
|
-
var env = opts.env || (typeof process !== "undefined" ? process.env : {});
|
|
178
|
-
|
|
179
|
-
var vaultPassphrase = null;
|
|
180
|
-
if (vaultMode === "wrapped") {
|
|
181
|
-
var raw = env && env.BLAMEJS_VAULT_PASSPHRASE;
|
|
182
|
-
if (typeof raw !== "string" || raw.length === 0) {
|
|
183
|
-
throw new Error("cliHelpers.bootApp: BLAMEJS_VAULT_PASSPHRASE is required " +
|
|
184
|
-
"for vault mode 'wrapped' (pass vaultMode: 'plaintext' for a dev data dir)");
|
|
185
|
-
}
|
|
186
|
-
vaultPassphrase = Buffer.from(raw, "utf8");
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
var b = blamejs();
|
|
190
|
-
var app = await b.createApp({
|
|
191
|
-
dataDir: opts.dataDir,
|
|
192
|
-
routes: function () {},
|
|
193
|
-
vault: { mode: vaultMode, passphrase: vaultPassphrase },
|
|
194
|
-
db: {
|
|
195
|
-
atRest: opts.dbAtRest || "plain",
|
|
196
|
-
auditSigning: { mode: "plaintext" },
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
return { b: b, app: app };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
module.exports = {
|
|
203
|
-
makeReporter: makeReporter,
|
|
204
|
-
resolvePassphrase: resolvePassphrase,
|
|
205
|
-
bootApp: bootApp,
|
|
206
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* cli-helpers — shared shape for blamejs CLI subcommands AND for
|
|
4
|
+
* operators writing their own one-shot CLI scripts on top of the
|
|
5
|
+
* framework.
|
|
6
|
+
*
|
|
7
|
+
* Three patterns recur across every CLI command (`migrate`, `seed`,
|
|
8
|
+
* `audit`, `vault`, `backup`, `api-key`):
|
|
9
|
+
*
|
|
10
|
+
* 1. Bootstrap a headless `b.createApp` instance from `--data-dir`,
|
|
11
|
+
* operate against vault + DB + audit chain, shut down cleanly.
|
|
12
|
+
* 2. Report success / error / usage with a consistent
|
|
13
|
+
* `blamejs <verb> <sub>: <message>` prefix on stderr + canonical
|
|
14
|
+
* exit codes (0 ok, 1 runtime failure, 2 arg error).
|
|
15
|
+
* 3. Resolve a passphrase from `--<flag>` or an env var, encode to
|
|
16
|
+
* the Buffer the underlying crypto primitive needs.
|
|
17
|
+
*
|
|
18
|
+
* var cli = b.cliHelpers;
|
|
19
|
+
*
|
|
20
|
+
* var report = cli.makeReporter(ctx, "blamejs my-tool issue");
|
|
21
|
+
* if (!args.flags["data-dir"]) return report.usage(MY_USAGE);
|
|
22
|
+
*
|
|
23
|
+
* var pp = cli.resolvePassphrase(args, ctx, {
|
|
24
|
+
* flag: "passphrase",
|
|
25
|
+
* envVar: "MY_TOOL_PASSPHRASE",
|
|
26
|
+
* });
|
|
27
|
+
* if (!pp) return report.error("--passphrase or MY_TOOL_PASSPHRASE is required", 2);
|
|
28
|
+
*
|
|
29
|
+
* var booted;
|
|
30
|
+
* try {
|
|
31
|
+
* booted = await cli.bootApp({
|
|
32
|
+
* dataDir: args.flags["data-dir"],
|
|
33
|
+
* vaultMode: "wrapped",
|
|
34
|
+
* env: ctx.env,
|
|
35
|
+
* });
|
|
36
|
+
* // do the op against booted.b.X / booted.app.db / etc.
|
|
37
|
+
* return report.ok("done");
|
|
38
|
+
* } catch (e) {
|
|
39
|
+
* return report.error(e.message);
|
|
40
|
+
* } finally {
|
|
41
|
+
* if (booted) await booted.app.shutdown();
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* The reporter writes through whichever stream `ctx.stdout` / `ctx.stderr`
|
|
45
|
+
* point at — `process.stdout` / `process.stderr` in production, captured
|
|
46
|
+
* stream stubs in tests — so the same handler is testable without
|
|
47
|
+
* spawning a child process.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
var lazyRequire = require("./lazy-require");
|
|
51
|
+
var validateOpts = require("./validate-opts");
|
|
52
|
+
|
|
53
|
+
// lazyRequire the framework root so cli-helpers stays loadable in
|
|
54
|
+
// contexts that aren't the framework root (tests, operator-side
|
|
55
|
+
// scripts importing only cli-helpers). The first bootApp() call
|
|
56
|
+
// resolves the framework module; subsequent calls reuse the cached
|
|
57
|
+
// load.
|
|
58
|
+
var blamejs = lazyRequire(function () { return require("../"); });
|
|
59
|
+
|
|
60
|
+
// ---- Streams + exit-code-shaped reporting --------------------------------
|
|
61
|
+
|
|
62
|
+
function _writeLine(stream, line) {
|
|
63
|
+
if (!stream || typeof stream.write !== "function") return;
|
|
64
|
+
if (line == null) return;
|
|
65
|
+
stream.write(String(line) + "\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Make a reporter bound to a CLI context + a verb prefix. Every
|
|
70
|
+
* stderr message produced by .error / .usage gets the prefix.
|
|
71
|
+
*
|
|
72
|
+
* var report = cliHelpers.makeReporter(ctx, "blamejs vault seal");
|
|
73
|
+
* return report.ok("sealed: " + path); // stdout, returns 0
|
|
74
|
+
* return report.error("decrypt failed"); // stderr "blamejs vault seal: decrypt failed", returns 1
|
|
75
|
+
* return report.error("missing arg", 2); // returns 2 (arg error vs runtime)
|
|
76
|
+
* return report.usage(VAULT_USAGE); // stderr USAGE, returns 2
|
|
77
|
+
* return report.helpStdout(VAULT_USAGE); // stdout USAGE, returns 0 (for `help <verb>`)
|
|
78
|
+
*/
|
|
79
|
+
function makeReporter(ctx, prefix) {
|
|
80
|
+
if (!ctx || typeof ctx !== "object") {
|
|
81
|
+
throw new Error("cliHelpers.makeReporter: ctx is required");
|
|
82
|
+
}
|
|
83
|
+
if (typeof prefix !== "string" || prefix.length === 0) {
|
|
84
|
+
throw new Error("cliHelpers.makeReporter: prefix is required (non-empty string)");
|
|
85
|
+
}
|
|
86
|
+
var stdout = ctx.stdout || (typeof process !== "undefined" ? process.stdout : null);
|
|
87
|
+
var stderr = ctx.stderr || (typeof process !== "undefined" ? process.stderr : null);
|
|
88
|
+
return {
|
|
89
|
+
ok: function (message) {
|
|
90
|
+
if (message != null) _writeLine(stdout, message);
|
|
91
|
+
return 0;
|
|
92
|
+
},
|
|
93
|
+
error: function (message, exitCode) {
|
|
94
|
+
_writeLine(stderr, prefix + ": " + message);
|
|
95
|
+
return typeof exitCode === "number" ? exitCode : 1;
|
|
96
|
+
},
|
|
97
|
+
usage: function (usageText) {
|
|
98
|
+
_writeLine(stderr, usageText);
|
|
99
|
+
return 2;
|
|
100
|
+
},
|
|
101
|
+
helpStdout: function (usageText) {
|
|
102
|
+
_writeLine(stdout, usageText);
|
|
103
|
+
return 0;
|
|
104
|
+
},
|
|
105
|
+
// Direct access for handlers that have multi-line output.
|
|
106
|
+
write: function (line) { _writeLine(stdout, line); },
|
|
107
|
+
writeErr: function (line) { _writeLine(stderr, line); },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---- Passphrase resolution -----------------------------------------------
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Resolve a passphrase from a flag or env var into a UTF-8 Buffer
|
|
115
|
+
* (the shape the underlying crypto primitives accept).
|
|
116
|
+
*
|
|
117
|
+
* cli.resolvePassphrase(args, ctx, { flag: "passphrase", envVar: "BLAMEJS_VAULT_PASSPHRASE" })
|
|
118
|
+
* → Buffer | null
|
|
119
|
+
*
|
|
120
|
+
* Returns `null` when neither source produced a non-empty string. The
|
|
121
|
+
* caller decides whether absence is a hard error (vault seal) or a
|
|
122
|
+
* soft default (operator chose plaintext-mode).
|
|
123
|
+
*/
|
|
124
|
+
function resolvePassphrase(args, ctx, opts) {
|
|
125
|
+
opts = opts || {};
|
|
126
|
+
validateOpts(opts, ["flag", "envVar"], "cliHelpers.resolvePassphrase");
|
|
127
|
+
if (typeof opts.flag !== "string" || opts.flag.length === 0) {
|
|
128
|
+
throw new Error("cliHelpers.resolvePassphrase: opts.flag is required");
|
|
129
|
+
}
|
|
130
|
+
var raw = null;
|
|
131
|
+
if (args && args.flags && typeof args.flags[opts.flag] === "string" &&
|
|
132
|
+
args.flags[opts.flag].length > 0) {
|
|
133
|
+
raw = args.flags[opts.flag];
|
|
134
|
+
} else if (opts.envVar && ctx && ctx.env &&
|
|
135
|
+
typeof ctx.env[opts.envVar] === "string" &&
|
|
136
|
+
ctx.env[opts.envVar].length > 0) {
|
|
137
|
+
raw = ctx.env[opts.envVar];
|
|
138
|
+
}
|
|
139
|
+
if (raw == null) return null;
|
|
140
|
+
return Buffer.from(raw, "utf8");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---- Headless app bootstrap ----------------------------------------------
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Boot a serverless `b.createApp` instance from a data dir so a CLI
|
|
147
|
+
* script (the framework's own subcommands or operator-written tools)
|
|
148
|
+
* can operate against the same vault + DB + audit chain the live app
|
|
149
|
+
* uses, without standing up an HTTP listener.
|
|
150
|
+
*
|
|
151
|
+
* var booted = await cli.bootApp({
|
|
152
|
+
* dataDir: "./data",
|
|
153
|
+
* vaultMode: "wrapped", // or "plaintext"; default "wrapped"
|
|
154
|
+
* env: process.env, // BLAMEJS_VAULT_PASSPHRASE read from here
|
|
155
|
+
* });
|
|
156
|
+
* // booted.b — the framework module
|
|
157
|
+
* // booted.app — the headless app instance (call .shutdown() to clean up)
|
|
158
|
+
*
|
|
159
|
+
* Caller MUST call `await booted.app.shutdown()` in a `finally` so the
|
|
160
|
+
* SQLite file handles + cluster lease release. The default DB at-rest
|
|
161
|
+
* mode is `plain` because CLI runs are short-lived ops that never
|
|
162
|
+
* serve requests; the encrypted-at-rest mode needs a tmpfs handle that
|
|
163
|
+
* wouldn't survive the CLI exit anyway. Operators running against a
|
|
164
|
+
* production data dir whose DB is encrypted-at-rest set
|
|
165
|
+
* `dbAtRest: "encrypted"` and ensure `BLAMEJS_TMPDIR` is set.
|
|
166
|
+
*/
|
|
167
|
+
async function bootApp(opts) {
|
|
168
|
+
opts = opts || {};
|
|
169
|
+
validateOpts(opts, ["dataDir", "vaultMode", "dbAtRest", "env"], "cliHelpers.bootApp");
|
|
170
|
+
if (typeof opts.dataDir !== "string" || opts.dataDir.length === 0) {
|
|
171
|
+
throw new Error("cliHelpers.bootApp: opts.dataDir is required");
|
|
172
|
+
}
|
|
173
|
+
var vaultMode = opts.vaultMode || "wrapped";
|
|
174
|
+
if (vaultMode !== "wrapped" && vaultMode !== "plaintext") {
|
|
175
|
+
throw new Error("cliHelpers.bootApp: opts.vaultMode must be 'wrapped' or 'plaintext'");
|
|
176
|
+
}
|
|
177
|
+
var env = opts.env || (typeof process !== "undefined" ? process.env : {});
|
|
178
|
+
|
|
179
|
+
var vaultPassphrase = null;
|
|
180
|
+
if (vaultMode === "wrapped") {
|
|
181
|
+
var raw = env && env.BLAMEJS_VAULT_PASSPHRASE;
|
|
182
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
183
|
+
throw new Error("cliHelpers.bootApp: BLAMEJS_VAULT_PASSPHRASE is required " +
|
|
184
|
+
"for vault mode 'wrapped' (pass vaultMode: 'plaintext' for a dev data dir)");
|
|
185
|
+
}
|
|
186
|
+
vaultPassphrase = Buffer.from(raw, "utf8");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
var b = blamejs();
|
|
190
|
+
var app = await b.createApp({
|
|
191
|
+
dataDir: opts.dataDir,
|
|
192
|
+
routes: function () {},
|
|
193
|
+
vault: { mode: vaultMode, passphrase: vaultPassphrase },
|
|
194
|
+
db: {
|
|
195
|
+
atRest: opts.dbAtRest || "plain",
|
|
196
|
+
auditSigning: { mode: "plaintext" },
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
return { b: b, app: app };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = {
|
|
203
|
+
makeReporter: makeReporter,
|
|
204
|
+
resolvePassphrase: resolvePassphrase,
|
|
205
|
+
bootApp: bootApp,
|
|
206
|
+
};
|