@blamejs/core 0.8.43 → 0.8.50

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.
Files changed (222) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +10 -10
  3. package/index.js +52 -0
  4. package/lib/a2a.js +159 -34
  5. package/lib/acme.js +762 -0
  6. package/lib/ai-pref.js +166 -43
  7. package/lib/api-key.js +108 -47
  8. package/lib/api-snapshot.js +157 -40
  9. package/lib/app-shutdown.js +113 -77
  10. package/lib/archive.js +337 -40
  11. package/lib/arg-parser.js +697 -0
  12. package/lib/asyncapi.js +99 -55
  13. package/lib/atomic-file.js +465 -104
  14. package/lib/audit-chain.js +123 -34
  15. package/lib/audit-daily-review.js +389 -0
  16. package/lib/audit-sign.js +302 -56
  17. package/lib/audit-tools.js +412 -63
  18. package/lib/audit.js +656 -35
  19. package/lib/auth/jwt-external.js +17 -0
  20. package/lib/auth/oauth.js +7 -0
  21. package/lib/auth-bot-challenge.js +505 -0
  22. package/lib/auth-header.js +92 -25
  23. package/lib/backup/bundle.js +26 -0
  24. package/lib/backup/index.js +512 -89
  25. package/lib/backup/manifest.js +168 -7
  26. package/lib/break-glass.js +415 -39
  27. package/lib/budr.js +103 -30
  28. package/lib/bundler.js +86 -66
  29. package/lib/cache.js +192 -72
  30. package/lib/chain-writer.js +65 -40
  31. package/lib/circuit-breaker.js +56 -33
  32. package/lib/cli-helpers.js +106 -75
  33. package/lib/cli.js +6 -30
  34. package/lib/cloud-events.js +99 -32
  35. package/lib/cluster-storage.js +162 -37
  36. package/lib/cluster.js +340 -49
  37. package/lib/codepoint-class.js +66 -0
  38. package/lib/compliance.js +424 -24
  39. package/lib/config-drift.js +111 -46
  40. package/lib/config.js +94 -40
  41. package/lib/consent.js +165 -18
  42. package/lib/constants.js +1 -0
  43. package/lib/content-credentials.js +153 -48
  44. package/lib/cookies.js +154 -62
  45. package/lib/credential-hash.js +133 -61
  46. package/lib/crypto-field.js +702 -18
  47. package/lib/crypto-hpke.js +256 -0
  48. package/lib/crypto.js +744 -22
  49. package/lib/csv.js +178 -35
  50. package/lib/daemon.js +456 -0
  51. package/lib/dark-patterns.js +186 -55
  52. package/lib/db-query.js +79 -2
  53. package/lib/db.js +1431 -60
  54. package/lib/ddl-change-control.js +523 -0
  55. package/lib/deprecate.js +195 -40
  56. package/lib/dev.js +82 -39
  57. package/lib/dora.js +67 -48
  58. package/lib/dr-runbook.js +368 -0
  59. package/lib/dsr.js +142 -11
  60. package/lib/dual-control.js +91 -56
  61. package/lib/events.js +120 -41
  62. package/lib/external-db-migrate.js +192 -2
  63. package/lib/external-db.js +795 -50
  64. package/lib/fapi2.js +122 -1
  65. package/lib/fda-21cfr11.js +395 -0
  66. package/lib/fdx.js +132 -2
  67. package/lib/file-type.js +87 -0
  68. package/lib/file-upload.js +93 -0
  69. package/lib/flag.js +82 -20
  70. package/lib/forms.js +132 -29
  71. package/lib/framework-error.js +169 -0
  72. package/lib/framework-schema.js +163 -35
  73. package/lib/gate-contract.js +849 -175
  74. package/lib/graphql-federation.js +68 -7
  75. package/lib/guard-all.js +172 -55
  76. package/lib/guard-archive.js +286 -124
  77. package/lib/guard-auth.js +194 -21
  78. package/lib/guard-cidr.js +190 -28
  79. package/lib/guard-csv.js +397 -51
  80. package/lib/guard-domain.js +213 -91
  81. package/lib/guard-email.js +236 -29
  82. package/lib/guard-filename.js +307 -75
  83. package/lib/guard-graphql.js +263 -30
  84. package/lib/guard-html.js +310 -116
  85. package/lib/guard-image.js +243 -30
  86. package/lib/guard-json.js +260 -54
  87. package/lib/guard-jsonpath.js +235 -23
  88. package/lib/guard-jwt.js +284 -30
  89. package/lib/guard-markdown.js +204 -22
  90. package/lib/guard-mime.js +190 -26
  91. package/lib/guard-oauth.js +277 -28
  92. package/lib/guard-pdf.js +251 -27
  93. package/lib/guard-regex.js +226 -18
  94. package/lib/guard-shell.js +229 -26
  95. package/lib/guard-svg.js +177 -10
  96. package/lib/guard-template.js +232 -21
  97. package/lib/guard-time.js +195 -29
  98. package/lib/guard-uuid.js +189 -30
  99. package/lib/guard-xml.js +259 -36
  100. package/lib/guard-yaml.js +241 -44
  101. package/lib/honeytoken.js +63 -27
  102. package/lib/html-balance.js +83 -0
  103. package/lib/http-client.js +486 -59
  104. package/lib/http-message-signature.js +582 -0
  105. package/lib/i18n.js +102 -49
  106. package/lib/iab-mspa.js +112 -32
  107. package/lib/iab-tcf.js +107 -2
  108. package/lib/inbox.js +90 -52
  109. package/lib/keychain.js +865 -0
  110. package/lib/legal-hold.js +374 -0
  111. package/lib/local-db-thin.js +320 -0
  112. package/lib/log-stream.js +281 -51
  113. package/lib/log.js +184 -86
  114. package/lib/mail-bounce.js +107 -62
  115. package/lib/mail.js +295 -58
  116. package/lib/mcp.js +108 -27
  117. package/lib/metrics.js +98 -89
  118. package/lib/middleware/age-gate.js +36 -0
  119. package/lib/middleware/ai-act-disclosure.js +37 -0
  120. package/lib/middleware/api-encrypt.js +45 -0
  121. package/lib/middleware/assetlinks.js +40 -0
  122. package/lib/middleware/asyncapi-serve.js +35 -0
  123. package/lib/middleware/attach-user.js +40 -0
  124. package/lib/middleware/bearer-auth.js +40 -0
  125. package/lib/middleware/body-parser.js +230 -0
  126. package/lib/middleware/bot-disclose.js +34 -0
  127. package/lib/middleware/bot-guard.js +39 -0
  128. package/lib/middleware/compression.js +37 -0
  129. package/lib/middleware/cookies.js +32 -0
  130. package/lib/middleware/cors.js +40 -0
  131. package/lib/middleware/csp-nonce.js +40 -0
  132. package/lib/middleware/csp-report.js +34 -0
  133. package/lib/middleware/csrf-protect.js +43 -0
  134. package/lib/middleware/daily-byte-quota.js +53 -85
  135. package/lib/middleware/db-role-for.js +40 -0
  136. package/lib/middleware/dpop.js +40 -0
  137. package/lib/middleware/error-handler.js +37 -14
  138. package/lib/middleware/fetch-metadata.js +39 -0
  139. package/lib/middleware/flag-context.js +34 -0
  140. package/lib/middleware/gpc.js +33 -0
  141. package/lib/middleware/headers.js +35 -0
  142. package/lib/middleware/health.js +46 -0
  143. package/lib/middleware/host-allowlist.js +30 -0
  144. package/lib/middleware/network-allowlist.js +38 -0
  145. package/lib/middleware/openapi-serve.js +34 -0
  146. package/lib/middleware/rate-limit.js +160 -18
  147. package/lib/middleware/request-id.js +36 -18
  148. package/lib/middleware/request-log.js +37 -0
  149. package/lib/middleware/require-aal.js +29 -0
  150. package/lib/middleware/require-auth.js +32 -0
  151. package/lib/middleware/require-bound-key.js +41 -0
  152. package/lib/middleware/require-content-type.js +32 -0
  153. package/lib/middleware/require-methods.js +27 -0
  154. package/lib/middleware/require-mtls.js +33 -0
  155. package/lib/middleware/require-step-up.js +37 -0
  156. package/lib/middleware/security-headers.js +44 -0
  157. package/lib/middleware/security-txt.js +38 -0
  158. package/lib/middleware/span-http-server.js +37 -0
  159. package/lib/middleware/sse.js +36 -0
  160. package/lib/middleware/trace-log-correlation.js +33 -0
  161. package/lib/middleware/trace-propagate.js +32 -0
  162. package/lib/middleware/tus-upload.js +90 -0
  163. package/lib/middleware/web-app-manifest.js +53 -0
  164. package/lib/mtls-ca.js +100 -70
  165. package/lib/network-byte-quota.js +308 -0
  166. package/lib/network-heartbeat.js +135 -0
  167. package/lib/network-tls.js +534 -4
  168. package/lib/network.js +103 -0
  169. package/lib/notify.js +114 -43
  170. package/lib/ntp-check.js +192 -51
  171. package/lib/observability.js +145 -47
  172. package/lib/openapi.js +90 -44
  173. package/lib/outbox.js +99 -1
  174. package/lib/pagination.js +168 -86
  175. package/lib/parsers/index.js +16 -5
  176. package/lib/permissions.js +93 -40
  177. package/lib/pqc-agent.js +84 -8
  178. package/lib/pqc-software.js +94 -60
  179. package/lib/process-spawn.js +95 -21
  180. package/lib/pubsub.js +96 -66
  181. package/lib/queue.js +375 -54
  182. package/lib/redact.js +793 -21
  183. package/lib/render.js +139 -47
  184. package/lib/request-helpers.js +485 -121
  185. package/lib/restore-bundle.js +142 -39
  186. package/lib/restore-rollback.js +136 -45
  187. package/lib/retention.js +178 -50
  188. package/lib/retry.js +116 -33
  189. package/lib/router.js +475 -23
  190. package/lib/safe-async.js +543 -94
  191. package/lib/safe-buffer.js +337 -41
  192. package/lib/safe-json.js +467 -62
  193. package/lib/safe-jsonpath.js +285 -0
  194. package/lib/safe-schema.js +631 -87
  195. package/lib/safe-sql.js +221 -59
  196. package/lib/safe-url.js +278 -46
  197. package/lib/sandbox-worker.js +135 -0
  198. package/lib/sandbox.js +358 -0
  199. package/lib/scheduler.js +135 -70
  200. package/lib/self-update.js +647 -0
  201. package/lib/session-device-binding.js +431 -0
  202. package/lib/session.js +259 -49
  203. package/lib/slug.js +138 -26
  204. package/lib/ssrf-guard.js +316 -56
  205. package/lib/storage.js +433 -70
  206. package/lib/subject.js +405 -23
  207. package/lib/template.js +148 -8
  208. package/lib/tenant-quota.js +545 -0
  209. package/lib/testing.js +440 -53
  210. package/lib/time.js +291 -23
  211. package/lib/tls-exporter.js +239 -0
  212. package/lib/tracing.js +90 -74
  213. package/lib/uuid.js +97 -22
  214. package/lib/vault/index.js +284 -22
  215. package/lib/vault/seal-pem-file.js +66 -0
  216. package/lib/watcher.js +368 -0
  217. package/lib/webhook.js +196 -63
  218. package/lib/websocket.js +393 -68
  219. package/lib/wiki-concepts.js +338 -0
  220. package/lib/worker-pool.js +464 -0
  221. package/package.json +3 -3
  222. package/sbom.cyclonedx.json +7 -7
@@ -0,0 +1,697 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.argParser
4
+ * @nav Production
5
+ * @title Arg Parser
6
+ *
7
+ * @intro
8
+ * Reusable command-line argument parser for tools sitting on top of
9
+ * the framework. Operators declare a parser declaratively (top-level
10
+ * flags + named commands + per-command flags) and call `.parse(argv)`
11
+ * to get a typed result, or `.help()` to render usage text.
12
+ *
13
+ * Throws ArgParserError on invalid input (unknown flag, missing
14
+ * required, type-coercion failure, prototype-pollution attempt) so a
15
+ * bad CLI invocation fails at parse-time with a clear message instead
16
+ * of running a subcommand with garbage. `--` ends flag parsing;
17
+ * `--help` / `-h` is reserved and renders top-level or per-command
18
+ * usage. Flag names `__proto__` / `constructor` / `prototype` are
19
+ * refused as a prototype-pollution defense, even though parsed flags
20
+ * live in an Object.create(null) bag.
21
+ *
22
+ * Type coercion: `string` (as-is), `number` (Number(), rejects NaN),
23
+ * `boolean` (presence + accepts "true"/"false"/"1"/"0"/"yes"/"no"),
24
+ * `list` (repeated flags or single comma-separated value). Defaults
25
+ * apply before required-checks, so a flag with both `required: true`
26
+ * and a `default` is always satisfied. Aliases are single ASCII
27
+ * letters (`-d ./app.db` ≡ `--db ./app.db`); multi-char aliases are
28
+ * flag names, not aliases.
29
+ *
30
+ * `parseRaw(argv)` is the framework-internal minimal splitter used by
31
+ * `lib/cli.js` subcommand handlers — same prototype-pollution defense
32
+ * and `--` terminator semantics, but no command/flag schema.
33
+ *
34
+ * @card
35
+ * Reusable command-line argument parser for tools sitting on top of the framework.
36
+ */
37
+
38
+ var { defineClass } = require("./framework-error");
39
+
40
+ var ArgParserError = defineClass("ArgParserError", { alwaysPermanent: true });
41
+
42
+ var SUPPORTED_TYPES = ["string", "number", "boolean", "list"];
43
+
44
+ // Names operators can never use for a flag. Prototype-pollution defense:
45
+ // even though the parser internally uses Object.create(null), downstream
46
+ // callers spreading parsed.flags into a normal object would otherwise
47
+ // overwrite the prototype.
48
+ var FORBIDDEN_NAMES = ["__proto__", "constructor", "prototype"];
49
+
50
+ function _isPlainNonEmpty(s) {
51
+ return typeof s === "string" && s.length > 0;
52
+ }
53
+
54
+ function _validateFlagName(name) {
55
+ if (!_isPlainNonEmpty(name)) {
56
+ throw new ArgParserError("argParser/flag-name-invalid",
57
+ "flag name must be a non-empty string");
58
+ }
59
+ if (FORBIDDEN_NAMES.indexOf(name) !== -1) {
60
+ throw new ArgParserError("argParser/flag-name-forbidden",
61
+ "flag name '" + name + "' is reserved");
62
+ }
63
+ // ASCII-only kebab-/snake-case identifier. Keeps every flag name safe
64
+ // to embed in help text and prevents `--foo$bar=baz` style oddities.
65
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
66
+ throw new ArgParserError("argParser/flag-name-shape",
67
+ "flag name '" + name + "' must match [a-zA-Z][a-zA-Z0-9_-]*");
68
+ }
69
+ }
70
+
71
+ function _validateAlias(alias, ownerName) {
72
+ if (alias === undefined || alias === null) return;
73
+ if (typeof alias !== "string" || alias.length !== 1 || !/^[a-zA-Z]$/.test(alias)) {
74
+ throw new ArgParserError("argParser/alias-shape",
75
+ "flag '" + ownerName + "' alias must be a single letter [a-zA-Z]");
76
+ }
77
+ }
78
+
79
+ function _validateFlagSpec(spec, ownerLabel) {
80
+ if (!spec || typeof spec !== "object") {
81
+ throw new ArgParserError("argParser/flag-spec-invalid",
82
+ ownerLabel + ": flag spec must be an object");
83
+ }
84
+ _validateFlagName(spec.name);
85
+ _validateAlias(spec.alias, spec.name);
86
+ var type = spec.type || "string";
87
+ if (SUPPORTED_TYPES.indexOf(type) === -1) {
88
+ throw new ArgParserError("argParser/flag-type-unsupported",
89
+ ownerLabel + ": flag '" + spec.name +
90
+ "' type '" + type + "' must be one of " + SUPPORTED_TYPES.join(", "));
91
+ }
92
+ if (spec.description !== undefined && typeof spec.description !== "string") {
93
+ throw new ArgParserError("argParser/flag-description-invalid",
94
+ ownerLabel + ": flag '" + spec.name + "' description must be a string");
95
+ }
96
+ }
97
+
98
+ function _coerceValue(spec, raw, label) {
99
+ var type = spec.type || "string";
100
+ if (type === "string") return String(raw);
101
+ if (type === "number") {
102
+ var n = Number(raw);
103
+ if (!isFinite(n)) {
104
+ throw new ArgParserError("argParser/value-not-number",
105
+ label + " '" + spec.name + "' expected a number, got " + JSON.stringify(raw));
106
+ }
107
+ return n;
108
+ }
109
+ if (type === "boolean") {
110
+ if (raw === true) return true;
111
+ if (raw === false) return false;
112
+ var s = String(raw).toLowerCase();
113
+ if (s === "true" || s === "1" || s === "yes") return true;
114
+ if (s === "false" || s === "0" || s === "no") return false;
115
+ throw new ArgParserError("argParser/value-not-boolean",
116
+ label + " '" + spec.name + "' expected boolean, got " + JSON.stringify(raw));
117
+ }
118
+ if (type === "list") {
119
+ // Caller may pass a single comma-separated string; split here.
120
+ var s2 = String(raw);
121
+ return s2.indexOf(",") === -1 ? [s2] : s2.split(",");
122
+ }
123
+ // Unreachable: validated at create-time.
124
+ throw new ArgParserError("argParser/type-unreachable",
125
+ "unsupported flag type '" + type + "'");
126
+ }
127
+
128
+ function _buildFlagIndex(flagSpecs, ownerLabel) {
129
+ var byName = Object.create(null);
130
+ var byAlias = Object.create(null);
131
+ for (var i = 0; i < flagSpecs.length; i++) {
132
+ var spec = flagSpecs[i];
133
+ _validateFlagSpec(spec, ownerLabel);
134
+ if (byName[spec.name]) {
135
+ throw new ArgParserError("argParser/flag-duplicate",
136
+ ownerLabel + ": flag '" + spec.name + "' declared twice");
137
+ }
138
+ byName[spec.name] = spec;
139
+ if (spec.alias) {
140
+ if (byAlias[spec.alias]) {
141
+ throw new ArgParserError("argParser/alias-duplicate",
142
+ ownerLabel + ": alias '-" + spec.alias + "' declared twice");
143
+ }
144
+ byAlias[spec.alias] = spec;
145
+ }
146
+ }
147
+ return { byName: byName, byAlias: byAlias, list: flagSpecs.slice() };
148
+ }
149
+
150
+ function _renderFlagsBlock(specs, indent) {
151
+ if (!specs || specs.length === 0) return "";
152
+ var pad = "";
153
+ for (var k = 0; k < indent; k++) pad += " ";
154
+ var lines = [];
155
+ // Compute the column where descriptions align.
156
+ var labels = specs.map(function (s) {
157
+ var head = "--" + s.name;
158
+ if (s.alias) head = "-" + s.alias + ", " + head;
159
+ if (s.type && s.type !== "boolean") head += " <" + s.type + ">";
160
+ return head;
161
+ });
162
+ var maxLabel = 0;
163
+ labels.forEach(function (l) { if (l.length > maxLabel) maxLabel = l.length; });
164
+ for (var i = 0; i < specs.length; i++) {
165
+ var spec = specs[i];
166
+ var label = labels[i];
167
+ var trail = "";
168
+ while (label.length + trail.length < maxLabel + 2) trail += " ";
169
+ var desc = spec.description || "";
170
+ var meta = [];
171
+ if (spec.required) meta.push("required");
172
+ if (spec.default !== undefined) meta.push("default " + JSON.stringify(spec.default));
173
+ if (meta.length > 0) desc = (desc ? desc + " " : "") + "(" + meta.join(", ") + ")";
174
+ lines.push(pad + label + trail + desc);
175
+ }
176
+ return lines.join("\n");
177
+ }
178
+
179
+ function _renderHelp(parser) {
180
+ var lines = [];
181
+ var prog = parser.programName || "program";
182
+ if (parser.description) lines.push(parser.description);
183
+ if (parser.commands.list.length > 0) {
184
+ lines.push("Usage: " + prog + " <command> [flags]");
185
+ } else {
186
+ lines.push("Usage: " + prog + " [flags] [args]");
187
+ }
188
+ if (parser.flags.list.length > 0) {
189
+ lines.push("");
190
+ lines.push("Flags:");
191
+ lines.push(_renderFlagsBlock(parser.flags.list, 2));
192
+ }
193
+ if (parser.commands.list.length > 0) {
194
+ lines.push("");
195
+ lines.push("Commands:");
196
+ var nameWidth = 0;
197
+ parser.commands.list.forEach(function (c) {
198
+ if (c.name.length > nameWidth) nameWidth = c.name.length;
199
+ });
200
+ parser.commands.list.forEach(function (c) {
201
+ var pad = "";
202
+ while (c.name.length + pad.length < nameWidth + 2) pad += " ";
203
+ lines.push(" " + c.name + pad + (c.description || ""));
204
+ });
205
+ }
206
+ return lines.join("\n");
207
+ }
208
+
209
+ function _renderCommandHelp(parser, command) {
210
+ var lines = [];
211
+ var prog = parser.programName || "program";
212
+ lines.push("Usage: " + prog + " " + command.name + " [flags]");
213
+ if (command.description) {
214
+ lines.push("");
215
+ lines.push(command.description);
216
+ }
217
+ // Merged: command-level flags override / extend top-level flags.
218
+ var merged = {};
219
+ parser.flags.list.forEach(function (s) { merged[s.name] = s; });
220
+ command.flags.list.forEach(function (s) { merged[s.name] = s; });
221
+ var allSpecs = Object.keys(merged).map(function (n) { return merged[n]; });
222
+ if (allSpecs.length > 0) {
223
+ lines.push("");
224
+ lines.push("Flags:");
225
+ lines.push(_renderFlagsBlock(allSpecs, 2));
226
+ }
227
+ return lines.join("\n");
228
+ }
229
+
230
+ // Low-level: walk argv and split into { command, tokens, terminatorReached }.
231
+ // Tokens are passed to _consumeFlags below. This step only identifies the
232
+ // command-name (the first non-flag token, when commands are configured).
233
+ function _splitArgv(parser, argv) {
234
+ if (!Array.isArray(argv)) {
235
+ throw new ArgParserError("argParser/argv-not-array",
236
+ "argv must be an array of strings");
237
+ }
238
+ for (var n = 0; n < argv.length; n++) {
239
+ if (typeof argv[n] !== "string") {
240
+ throw new ArgParserError("argParser/argv-element-not-string",
241
+ "argv[" + n + "] must be a string");
242
+ }
243
+ }
244
+ if (parser.commands.list.length === 0) {
245
+ return { command: null, pre: argv.slice(), rest: [] };
246
+ }
247
+ // Walk argv looking for the first non-flag token. Anything before it
248
+ // is a top-level flag (consumed against parser.flags). The token itself
249
+ // becomes the command, and everything after is consumed against the
250
+ // command's flags.
251
+ for (var i = 0; i < argv.length; i++) {
252
+ var t = argv[i];
253
+ if (t === "--") return { command: null, pre: argv.slice(0, i), rest: argv.slice(i) };
254
+ if (t.indexOf("-") !== 0) {
255
+ var name = t;
256
+ if (!parser.commands.byName[name]) {
257
+ // Surface help / version as built-ins even when no command matches.
258
+ if (name === "help") return { command: "__help__", pre: argv.slice(0, i), rest: argv.slice(i + 1) };
259
+ throw new ArgParserError("argParser/unknown-command",
260
+ "unknown command '" + name + "'");
261
+ }
262
+ return { command: name, pre: argv.slice(0, i), rest: argv.slice(i + 1) };
263
+ }
264
+ }
265
+ return { command: null, pre: argv.slice(), rest: [] };
266
+ }
267
+
268
+ // Consume a token-list against a flag index, returning { flags, positionals,
269
+ // helpRequested }. Operates on the merged (top-level + command) flag index.
270
+ function _consumeFlags(index, tokens) {
271
+ var flags = Object.create(null);
272
+ var positionals = [];
273
+ var helpRequested = false;
274
+ var listAccum = Object.create(null);
275
+
276
+ function _set(spec, raw) {
277
+ if (spec.type === "list") {
278
+ if (!listAccum[spec.name]) listAccum[spec.name] = [];
279
+ var v = _coerceValue(spec, raw, "flag");
280
+ // _coerceValue("list", x) returns an array; concat each element.
281
+ for (var j = 0; j < v.length; j++) listAccum[spec.name].push(v[j]);
282
+ } else {
283
+ flags[spec.name] = _coerceValue(spec, raw, "flag");
284
+ }
285
+ }
286
+
287
+ for (var i = 0; i < tokens.length; i++) {
288
+ var tok = tokens[i];
289
+ if (tok === "--") {
290
+ for (var j = i + 1; j < tokens.length; j++) positionals.push(tokens[j]);
291
+ break;
292
+ }
293
+ if (tok === "--help" || tok === "-h") { helpRequested = true; continue; }
294
+
295
+ var spec = null;
296
+ var rawValue = null;
297
+ var hasInlineValue = false;
298
+
299
+ if (tok.indexOf("--") === 0) {
300
+ var rest = tok.slice(2);
301
+ if (FORBIDDEN_NAMES.indexOf(rest.split("=")[0]) !== -1) {
302
+ throw new ArgParserError("argParser/argv-forbidden-name",
303
+ "flag '--" + rest.split("=")[0] + "' is reserved");
304
+ }
305
+ var eq = rest.indexOf("=");
306
+ var nm;
307
+ if (eq !== -1) {
308
+ nm = rest.slice(0, eq);
309
+ rawValue = rest.slice(eq + 1);
310
+ hasInlineValue = true;
311
+ } else {
312
+ nm = rest;
313
+ }
314
+ spec = index.byName[nm];
315
+ if (!spec) {
316
+ throw new ArgParserError("argParser/unknown-flag",
317
+ "unknown flag '--" + nm + "'");
318
+ }
319
+ } else if (tok.indexOf("-") === 0 && tok.length >= 2) {
320
+ var alias = tok.slice(1);
321
+ // Forms supported: -v, -v=value, -v value (next token).
322
+ var aeq = alias.indexOf("=");
323
+ var ach;
324
+ if (aeq !== -1) {
325
+ ach = alias.slice(0, aeq);
326
+ rawValue = alias.slice(aeq + 1);
327
+ hasInlineValue = true;
328
+ } else {
329
+ ach = alias;
330
+ }
331
+ if (ach.length !== 1) {
332
+ throw new ArgParserError("argParser/short-flag-shape",
333
+ "short flag '" + tok + "' must be a single letter (use --long-form for multi-char names)");
334
+ }
335
+ spec = index.byAlias[ach];
336
+ if (!spec) {
337
+ throw new ArgParserError("argParser/unknown-alias",
338
+ "unknown short flag '-" + ach + "'");
339
+ }
340
+ } else {
341
+ positionals.push(tok);
342
+ continue;
343
+ }
344
+
345
+ if (spec.type === "boolean" && !hasInlineValue) {
346
+ // Bare boolean flag — no inline value, do NOT consume the next
347
+ // token (it might be a positional that happens to look word-y).
348
+ _set(spec, true);
349
+ } else {
350
+ if (!hasInlineValue) {
351
+ if (i + 1 >= tokens.length) {
352
+ throw new ArgParserError("argParser/value-missing",
353
+ "flag '--" + spec.name + "' requires a value");
354
+ }
355
+ rawValue = tokens[++i];
356
+ }
357
+ _set(spec, rawValue);
358
+ }
359
+ }
360
+
361
+ // Roll list accumulators into the flags object.
362
+ Object.keys(listAccum).forEach(function (k) { flags[k] = listAccum[k]; });
363
+
364
+ return { flags: flags, positionals: positionals, helpRequested: helpRequested };
365
+ }
366
+
367
+ function _applyDefaultsAndRequired(specs, flags, ownerLabel) {
368
+ // Defaults first — required check sees defaulted values.
369
+ for (var i = 0; i < specs.length; i++) {
370
+ var spec = specs[i];
371
+ if (flags[spec.name] === undefined && spec.default !== undefined) {
372
+ // Defaults are taken as-supplied. Operator-supplied defaults are
373
+ // already in the target type's shape (this is config-time data).
374
+ flags[spec.name] = spec.default;
375
+ }
376
+ }
377
+ for (var k = 0; k < specs.length; k++) {
378
+ var s = specs[k];
379
+ if (s.required && flags[s.name] === undefined) {
380
+ throw new ArgParserError("argParser/missing-required",
381
+ ownerLabel + ": flag '--" + s.name + "' is required");
382
+ }
383
+ }
384
+ }
385
+
386
+ function _validateCommandSpec(cmd) {
387
+ if (!cmd || typeof cmd !== "object") {
388
+ throw new ArgParserError("argParser/command-spec-invalid",
389
+ "command spec must be an object");
390
+ }
391
+ if (!_isPlainNonEmpty(cmd.name)) {
392
+ throw new ArgParserError("argParser/command-name-invalid",
393
+ "command name must be a non-empty string");
394
+ }
395
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(cmd.name)) {
396
+ throw new ArgParserError("argParser/command-name-shape",
397
+ "command name '" + cmd.name + "' must match [a-zA-Z][a-zA-Z0-9_-]*");
398
+ }
399
+ if (cmd.handler !== undefined && typeof cmd.handler !== "function") {
400
+ throw new ArgParserError("argParser/command-handler-not-function",
401
+ "command '" + cmd.name + "' handler must be a function");
402
+ }
403
+ if (cmd.description !== undefined && typeof cmd.description !== "string") {
404
+ throw new ArgParserError("argParser/command-description-invalid",
405
+ "command '" + cmd.name + "' description must be a string");
406
+ }
407
+ }
408
+
409
+ /**
410
+ * @primitive b.argParser.create
411
+ * @signature b.argParser.create(opts)
412
+ * @since 0.8.48
413
+ * @status stable
414
+ * @related b.argParser.parseRaw
415
+ *
416
+ * Build a CLI parser from a declarative spec — top-level flags plus
417
+ * named commands with their own per-command flag lists. Returns
418
+ * `{ parse, help }`. Validates the spec at construction time so
419
+ * misconfigured aliases / duplicate names / unsupported types throw
420
+ * before any argv is seen.
421
+ *
422
+ * @opts
423
+ * programName: string, // optional; rendered in usage text
424
+ * description: string, // optional; one-line program summary
425
+ * flags: Array, // top-level flag specs
426
+ * commands: Array, // command specs (each carries its own flags)
427
+ *
428
+ * // Each flag spec: { name, alias?, type?, required?, default?, description? }
429
+ * // type ∈ { "string", "number", "boolean", "list" }; default "string"
430
+ * // alias is a single ASCII letter
431
+ * // forbidden names: __proto__, constructor, prototype
432
+ * //
433
+ * // Each command spec: { name, description?, flags?, handler? }
434
+ *
435
+ * @example
436
+ * var ap = b.argParser.create({
437
+ * programName: "blamejs",
438
+ * description: "Server-side framework CLI",
439
+ * flags: [
440
+ * { name: "verbose", alias: "v", type: "boolean",
441
+ * description: "Verbose output" },
442
+ * ],
443
+ * commands: [
444
+ * {
445
+ * name: "migrate",
446
+ * description: "Run database migrations",
447
+ * flags: [
448
+ * { name: "db", type: "string", required: true,
449
+ * description: "Path to sqlite file" },
450
+ * { name: "dir", type: "string", default: "./migrations" },
451
+ * ],
452
+ * },
453
+ * ],
454
+ * });
455
+ *
456
+ * var parsed = ap.parse(["migrate", "--db", "./app.db", "-v"]);
457
+ * parsed.command; // → "migrate"
458
+ * parsed.flags.db; // → "./app.db"
459
+ * parsed.flags.verbose; // → true
460
+ * parsed.flags.dir; // → "./migrations" (default)
461
+ * parsed.positionals; // → []
462
+ *
463
+ * ap.help(); // → top-level usage string
464
+ * ap.help("migrate"); // → "Usage: blamejs migrate [flags] ..."
465
+ */
466
+ function create(opts) {
467
+ if (!opts || typeof opts !== "object") {
468
+ throw new ArgParserError("argParser/opts-required",
469
+ "argParser.create requires an opts object");
470
+ }
471
+ var programName = opts.programName;
472
+ if (programName !== undefined && typeof programName !== "string") {
473
+ throw new ArgParserError("argParser/program-name-invalid",
474
+ "programName must be a string");
475
+ }
476
+ var description = opts.description;
477
+ if (description !== undefined && typeof description !== "string") {
478
+ throw new ArgParserError("argParser/description-invalid",
479
+ "description must be a string");
480
+ }
481
+ var topFlagsSpec = opts.flags || [];
482
+ if (!Array.isArray(topFlagsSpec)) {
483
+ throw new ArgParserError("argParser/flags-not-array",
484
+ "flags must be an array");
485
+ }
486
+ var topFlags = _buildFlagIndex(topFlagsSpec, "top-level");
487
+
488
+ var commandsSpec = opts.commands || [];
489
+ if (!Array.isArray(commandsSpec)) {
490
+ throw new ArgParserError("argParser/commands-not-array",
491
+ "commands must be an array");
492
+ }
493
+ var commandsByName = Object.create(null);
494
+ var commandsList = [];
495
+ for (var i = 0; i < commandsSpec.length; i++) {
496
+ var c = commandsSpec[i];
497
+ _validateCommandSpec(c);
498
+ if (commandsByName[c.name]) {
499
+ throw new ArgParserError("argParser/command-duplicate",
500
+ "command '" + c.name + "' declared twice");
501
+ }
502
+ var cmdFlagsSpec = c.flags || [];
503
+ if (!Array.isArray(cmdFlagsSpec)) {
504
+ throw new ArgParserError("argParser/command-flags-not-array",
505
+ "command '" + c.name + "' flags must be an array");
506
+ }
507
+ var cmdFlags = _buildFlagIndex(cmdFlagsSpec, "command '" + c.name + "'");
508
+ var entry = {
509
+ name: c.name,
510
+ description: c.description || "",
511
+ handler: c.handler || null,
512
+ flags: cmdFlags,
513
+ };
514
+ commandsByName[c.name] = entry;
515
+ commandsList.push(entry);
516
+ }
517
+
518
+ var parser = {
519
+ programName: programName || "",
520
+ description: description || "",
521
+ flags: topFlags,
522
+ commands: { byName: commandsByName, list: commandsList },
523
+ };
524
+
525
+ function help(commandName) {
526
+ if (commandName) {
527
+ var cmd = commandsByName[commandName];
528
+ if (!cmd) {
529
+ throw new ArgParserError("argParser/help-unknown-command",
530
+ "no command named '" + commandName + "'");
531
+ }
532
+ return _renderCommandHelp(parser, cmd);
533
+ }
534
+ return _renderHelp(parser);
535
+ }
536
+
537
+ function parse(argv, parseOpts) {
538
+ parseOpts = parseOpts || {};
539
+ var exitOnHelp = parseOpts.exit === true;
540
+ var stdout = parseOpts.stdout || process.stdout;
541
+ var split = _splitArgv(parser, argv);
542
+
543
+ // Top-level flags consumed before the command name (split.pre) plus
544
+ // any flags up to the command terminator.
545
+ var pre = split.pre || [];
546
+ var preParsed = _consumeFlags(parser.flags, pre);
547
+
548
+ if (split.command === "__help__") {
549
+ // `<prog> help [<cmd>]`
550
+ var topic = (split.rest && split.rest.length > 0) ? split.rest[0] : null;
551
+ var msg = topic && commandsByName[topic] ? help(topic) : help();
552
+ if (exitOnHelp) { stdout.write(msg + "\n"); process.exit(0); } // allow:process-exit — explicit { exit: true } from a bin/ shim
553
+ return { command: null, flags: {}, positionals: [], helpRequested: true, helpText: msg };
554
+ }
555
+
556
+ if (split.command) {
557
+ var cmdEntry = commandsByName[split.command];
558
+ // Build a merged index for this command (top-level + command flags;
559
+ // command flags shadow same-named top-level flags).
560
+ var mergedByName = Object.create(null);
561
+ var mergedByAlias = Object.create(null);
562
+ var mergedList = [];
563
+ function _add(spec) {
564
+ if (mergedByName[spec.name]) {
565
+ // Overwrite top-level with the command-level definition.
566
+ // Drop the prior alias mapping when it pointed at the top spec.
567
+ var prior = mergedByName[spec.name];
568
+ if (prior.alias && mergedByAlias[prior.alias] === prior) {
569
+ delete mergedByAlias[prior.alias];
570
+ }
571
+ mergedList = mergedList.filter(function (s) { return s.name !== spec.name; });
572
+ }
573
+ mergedByName[spec.name] = spec;
574
+ if (spec.alias) mergedByAlias[spec.alias] = spec;
575
+ mergedList.push(spec);
576
+ }
577
+ parser.flags.list.forEach(_add);
578
+ cmdEntry.flags.list.forEach(_add);
579
+ var mergedIndex = { byName: mergedByName, byAlias: mergedByAlias, list: mergedList };
580
+ var cmdParsed = _consumeFlags(mergedIndex, split.rest);
581
+
582
+ if (cmdParsed.helpRequested || preParsed.helpRequested) {
583
+ var cmsg = _renderCommandHelp(parser, cmdEntry);
584
+ if (exitOnHelp) { stdout.write(cmsg + "\n"); process.exit(0); } // allow:process-exit — explicit { exit: true } from a bin/ shim
585
+ return { command: cmdEntry.name, flags: {}, positionals: [], helpRequested: true, helpText: cmsg };
586
+ }
587
+
588
+ // Carry top-level flag values forward into the per-command flags
589
+ // object so handlers see one unified bag (operator ergonomics).
590
+ Object.keys(preParsed.flags).forEach(function (k) {
591
+ if (cmdParsed.flags[k] === undefined) cmdParsed.flags[k] = preParsed.flags[k];
592
+ });
593
+ _applyDefaultsAndRequired(mergedList, cmdParsed.flags, "command '" + cmdEntry.name + "'");
594
+
595
+ return {
596
+ command: cmdEntry.name,
597
+ flags: cmdParsed.flags,
598
+ positionals: cmdParsed.positionals,
599
+ helpRequested: false,
600
+ handler: cmdEntry.handler,
601
+ };
602
+ }
603
+
604
+ // No commands configured (or argv contained no command-name) — treat
605
+ // as a flag-only parser. Honor `--` and aggregate everything else.
606
+ if (preParsed.helpRequested) {
607
+ var hmsg = _renderHelp(parser);
608
+ if (exitOnHelp) { stdout.write(hmsg + "\n"); process.exit(0); } // allow:process-exit — explicit { exit: true } from a bin/ shim
609
+ return { command: null, flags: {}, positionals: [], helpRequested: true, helpText: hmsg };
610
+ }
611
+ _applyDefaultsAndRequired(parser.flags.list, preParsed.flags, "top-level");
612
+ return {
613
+ command: null,
614
+ flags: preParsed.flags,
615
+ positionals: preParsed.positionals,
616
+ helpRequested: false,
617
+ };
618
+ }
619
+
620
+ return { parse: parse, help: help };
621
+ }
622
+
623
+ /**
624
+ * @primitive b.argParser.parseRaw
625
+ * @signature b.argParser.parseRaw(argv)
626
+ * @since 0.8.48
627
+ * @status stable
628
+ * @related b.argParser.create
629
+ *
630
+ * Minimal positional + flag splitter used by `lib/cli.js` subcommand
631
+ * handlers. Returns `{ pos, flags }` where `flags` is an
632
+ * Object.create(null) bag — no schema validation, no command dispatch.
633
+ * Refuses prototype-pollution flag names (`__proto__`, `constructor`,
634
+ * `prototype`). Treats `-x` as a boolean shortcut; supports `--key
635
+ * value`, `--key=value`, and bare `--bool`. `--` terminates flag
636
+ * parsing.
637
+ *
638
+ * @example
639
+ * var r = b.argParser.parseRaw(
640
+ * ["build", "--target=node", "-v", "--out", "dist", "--", "extra"]);
641
+ * r.pos; // → ["build", "extra"]
642
+ * r.flags.target; // → "node"
643
+ * r.flags.v; // → true
644
+ * r.flags.out; // → "dist"
645
+ */
646
+ function parseRaw(argv) {
647
+ if (!Array.isArray(argv)) {
648
+ throw new ArgParserError("argParser/argv-not-array",
649
+ "argv must be an array of strings");
650
+ }
651
+ var pos = [];
652
+ var flags = Object.create(null);
653
+ for (var i = 0; i < argv.length; i++) {
654
+ var tok = argv[i];
655
+ if (typeof tok !== "string") {
656
+ throw new ArgParserError("argParser/argv-element-not-string",
657
+ "argv[" + i + "] must be a string");
658
+ }
659
+ if (tok === "--") {
660
+ for (var j = i + 1; j < argv.length; j++) pos.push(argv[j]);
661
+ break;
662
+ }
663
+ if (tok.indexOf("--") === 0) {
664
+ var name = tok.slice(2);
665
+ var eq = name.indexOf("=");
666
+ var val;
667
+ if (eq !== -1) {
668
+ val = name.slice(eq + 1);
669
+ name = name.slice(0, eq);
670
+ } else if (i + 1 < argv.length && argv[i + 1].indexOf("--") !== 0) {
671
+ val = argv[++i];
672
+ } else {
673
+ val = true;
674
+ }
675
+ if (FORBIDDEN_NAMES.indexOf(name) !== -1) {
676
+ throw new ArgParserError("argParser/argv-forbidden-name",
677
+ "flag '--" + name + "' is reserved");
678
+ }
679
+ flags[name] = val;
680
+ } else if (tok.indexOf("-") === 0 && tok.length === 2) {
681
+ var s = tok.slice(1);
682
+ if (FORBIDDEN_NAMES.indexOf(s) !== -1) {
683
+ throw new ArgParserError("argParser/argv-forbidden-name",
684
+ "flag '-" + s + "' is reserved");
685
+ }
686
+ flags[s] = true;
687
+ } else {
688
+ pos.push(tok);
689
+ }
690
+ }
691
+ return { pos: pos, flags: flags };
692
+ }
693
+
694
+ module.exports = {
695
+ create: create,
696
+ parseRaw: parseRaw,
697
+ };