@fuzdev/fuz_util 0.47.0 → 0.48.1

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/dist/args.d.ts ADDED
@@ -0,0 +1,138 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * CLI arguments container.
4
+ * Positional arguments stored in `_`, named flags/options as string keys.
5
+ * Produced by `argv_parse` or external parsers (mri, minimist, etc.).
6
+ */
7
+ export interface Args {
8
+ _?: Array<string>;
9
+ [key: string]: ArgValue;
10
+ }
11
+ /**
12
+ * Parsed CLI arguments with guaranteed positionals array.
13
+ * Returned by `argv_parse` which always initializes `_`.
14
+ */
15
+ export interface ParsedArgs extends Args {
16
+ _: Array<string>;
17
+ }
18
+ /**
19
+ * Value types supported in CLI arguments.
20
+ */
21
+ export type ArgValue = string | number | boolean | undefined | Array<string | number | boolean>;
22
+ /**
23
+ * Schema description for help text generation.
24
+ * Not used by args_parse/args_serialize directly - provided for consumers
25
+ * building CLI help output.
26
+ */
27
+ export interface ArgSchema {
28
+ type: string;
29
+ default: ArgValue;
30
+ description: string;
31
+ }
32
+ /**
33
+ * Result of alias extraction from a schema.
34
+ * Includes canonical keys for downstream conflict detection.
35
+ */
36
+ export interface ArgsAliasesResult {
37
+ aliases: Map<string, string>;
38
+ canonical_keys: Set<string>;
39
+ }
40
+ /**
41
+ * Validates a zod schema for CLI arg usage.
42
+ *
43
+ * Checks for:
44
+ * - Alias conflicts with canonical keys
45
+ * - Duplicate aliases across different keys
46
+ *
47
+ * Results are cached per schema (WeakMap). Safe to call multiple times.
48
+ *
49
+ * @param schema Zod object schema with optional alias metadata
50
+ * @returns Validation result with success flag and optional error
51
+ */
52
+ export declare const args_validate_schema: (schema: z.ZodType) => {
53
+ success: true;
54
+ } | {
55
+ success: false;
56
+ error: z.ZodError;
57
+ };
58
+ /**
59
+ * Validates parsed CLI args against a zod schema.
60
+ *
61
+ * Handles CLI-specific concerns before validation:
62
+ * 1. Validates schema (cached) - returns error if alias conflicts exist
63
+ * 2. Expands aliases defined in schema `.meta({aliases: ['v']})`
64
+ * 3. Strips alias keys (required for strictObject schemas)
65
+ * 4. Validates with zod
66
+ * 5. After validation, syncs `no-` prefixed boolean flags with their base keys
67
+ *
68
+ * Schema analysis is cached per schema (WeakMap) for performance.
69
+ *
70
+ * @param unparsed_args Args object from CLI parser (mri, minimist, etc.)
71
+ * @param schema Zod object schema with optional alias metadata
72
+ * @returns Zod SafeParseResult with expanded/synced data on success
73
+ */
74
+ export declare const args_parse: <TOutput extends Record<string, ArgValue> = Args>(unparsed_args: Args, schema: z.ZodType<TOutput>) => z.ZodSafeParseResult<TOutput>;
75
+ /**
76
+ * Serializes Args to CLI string array for subprocess forwarding.
77
+ *
78
+ * Handles CLI conventions:
79
+ * - Positionals first, then flags
80
+ * - Single-char keys get single dash, multi-char get double dash
81
+ * - Boolean `true` becomes bare flag, `false` is skipped
82
+ * - `undefined` values are skipped
83
+ * - `no-` prefixed keys skipped when base key is truthy (avoid contradiction)
84
+ * - When schema provided, extracts aliases and prefers shortest form
85
+ *
86
+ * Schema analysis is cached per schema (WeakMap) for performance.
87
+ *
88
+ * @param args Args object to serialize
89
+ * @param schema Optional zod schema to extract aliases for short form preference
90
+ * @returns Array of CLI argument strings
91
+ */
92
+ export declare const args_serialize: (args: Args, schema?: z.ZodType) => Array<string>;
93
+ /**
94
+ * Extracts alias mappings and canonical keys from a zod schema's metadata.
95
+ *
96
+ * Useful for consumers building custom tooling (help generators, conflict detection, etc.).
97
+ * Results are cached per schema (WeakMap).
98
+ *
99
+ * Note: Returns copies of the cached data to prevent mutation of internal cache.
100
+ *
101
+ * @param schema Zod object schema with optional `.meta({aliases})` on fields
102
+ * @returns Object with aliases map and canonical_keys set
103
+ */
104
+ export declare const args_extract_aliases: (schema: z.ZodType) => ArgsAliasesResult;
105
+ /**
106
+ * Parses raw CLI argv array into an Args object.
107
+ *
108
+ * A lightweight, dependency-free alternative to mri/minimist with compatible behavior.
109
+ *
110
+ * Features:
111
+ * - `--flag` → `{flag: true}`
112
+ * - `--flag value` → `{flag: 'value'}` or `{flag: 123}` (numeric coercion)
113
+ * - `--flag=value` → equals syntax
114
+ * - `--flag=` → `{flag: ''}` (empty string, differs from mri which returns true)
115
+ * - `-f` → `{f: true}` (short flag)
116
+ * - `-f value` → `{f: 'value'}`
117
+ * - `-abc` → `{a: true, b: true, c: true}` (combined short flags)
118
+ * - `-abc value` → `{a: true, b: true, c: 'value'}` (last flag gets value)
119
+ * - `--no-flag` → `{flag: false}` (negation prefix)
120
+ * - `--` → stops flag parsing, rest become positionals
121
+ * - Positionals collected in `_` array
122
+ * - Repeated flags become arrays
123
+ *
124
+ * Intentional differences from mri:
125
+ * - `--flag=` returns `''` (mri returns `true`)
126
+ * - `--flag= next` returns `{flag: '', _: ['next']}` (mri takes `next` as the value)
127
+ * - `---flag` returns `{'-flag': true}` (mri strips all dashes)
128
+ * - `['--flag', '']` preserves `''` (mri coerces to `0`)
129
+ * - `--__proto__` works as a normal key (mri silently fails)
130
+ *
131
+ * The returned object uses `Object.create(null)` to prevent prototype pollution
132
+ * and allow any key name including `__proto__` and `constructor`.
133
+ *
134
+ * @param argv Raw argument array (typically process.argv.slice(2))
135
+ * @returns Parsed Args object with guaranteed `_` array (null prototype)
136
+ */
137
+ export declare const argv_parse: (argv: Array<string>) => ParsedArgs;
138
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB;;;;GAIG;AACH,MAAM,WAAW,IAAI;IACpB,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAW,SAAQ,IAAI;IACvC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEhG;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,QAAQ,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAsID;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,QAAQ,CAAC,CAAC,OAAO,KACf;IAAC,OAAO,EAAE,IAAI,CAAA;CAAC,GAAG;IAAC,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAA;CAMtD,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,EACzE,eAAe,IAAI,EACnB,QAAQ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,KACxB,CAAC,CAAC,kBAAkB,CAAC,OAAO,CA6C9B,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,IAAI,EAAE,SAAS,CAAC,CAAC,OAAO,KAAG,KAAK,CAAC,MAAM,CA2D3E,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,iBAMxD,CAAC;AAgCF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,KAAK,CAAC,MAAM,CAAC,KAAG,UAyHhD,CAAC"}
package/dist/args.js ADDED
@@ -0,0 +1,447 @@
1
+ import { z } from 'zod';
2
+ // WeakMap cache for schema analysis - avoids redundant reflection
3
+ const schema_cache = new WeakMap();
4
+ // Internal: Unwrap nested schema types (Optional, Default, Transform, Pipe)
5
+ const unwrap_schema = (def) => {
6
+ if ('innerType' in def)
7
+ return def.innerType; // Optional, Nullable
8
+ if ('in' in def)
9
+ return def.in; // Pipe
10
+ if ('schema' in def)
11
+ return def.schema; // Default, Transform
12
+ return undefined;
13
+ };
14
+ // Internal: Check if schema type is boolean (recursing through wrappers)
15
+ const is_boolean_field = (schema) => {
16
+ const def = schema._zod.def;
17
+ if (def.type === 'boolean')
18
+ return true;
19
+ const inner = unwrap_schema(def);
20
+ if (inner)
21
+ return is_boolean_field(inner);
22
+ return false;
23
+ };
24
+ // Internal: Analyze schema for aliases, canonical keys, boolean keys, and conflicts
25
+ const analyze_schema = (schema) => {
26
+ const aliases = new Map();
27
+ const canonical_keys = new Set();
28
+ const boolean_keys = new Set();
29
+ const errors = [];
30
+ const def = schema._zod.def;
31
+ // Unwrap to get object def (handle wrapped types like optional, default, etc.)
32
+ let obj_def = def;
33
+ while (!('shape' in obj_def)) {
34
+ const inner = unwrap_schema(obj_def);
35
+ if (!inner)
36
+ return { aliases, canonical_keys, boolean_keys, errors };
37
+ obj_def = inner._zod.def;
38
+ }
39
+ const shape = obj_def.shape;
40
+ // First pass: collect all canonical keys
41
+ for (const key of Object.keys(shape)) {
42
+ canonical_keys.add(key);
43
+ }
44
+ // Second pass: process fields for aliases and booleans
45
+ for (const [key, field] of Object.entries(shape)) {
46
+ const field_schema = field;
47
+ // Track boolean fields for no- prefix sync
48
+ if (is_boolean_field(field_schema)) {
49
+ boolean_keys.add(key);
50
+ }
51
+ const meta = field_schema.meta();
52
+ if (meta?.aliases) {
53
+ for (const alias of meta.aliases) {
54
+ // Check for alias-canonical conflict
55
+ if (canonical_keys.has(alias)) {
56
+ errors.push({
57
+ type: 'alias_canonical_conflict',
58
+ alias,
59
+ canonical: key,
60
+ conflict_with: alias,
61
+ });
62
+ }
63
+ // Check for duplicate alias
64
+ else if (aliases.has(alias)) {
65
+ errors.push({
66
+ type: 'duplicate_alias',
67
+ alias,
68
+ canonical: key,
69
+ conflict_with: aliases.get(alias),
70
+ });
71
+ }
72
+ else {
73
+ aliases.set(alias, key);
74
+ }
75
+ }
76
+ }
77
+ }
78
+ return { aliases, canonical_keys, boolean_keys, errors };
79
+ };
80
+ // Internal: Convert analysis errors to ZodError for consistent API
81
+ const to_conflict_error = (errors) => {
82
+ return new z.ZodError(errors.map((err) => ({
83
+ code: 'custom',
84
+ path: [err.alias],
85
+ message: err.type === 'alias_canonical_conflict'
86
+ ? `Alias '${err.alias}' for '${err.canonical}' conflicts with canonical key '${err.conflict_with}'`
87
+ : `Alias '${err.alias}' is used by both '${err.canonical}' and '${err.conflict_with}'`,
88
+ })));
89
+ };
90
+ // Internal: Get or create cache entry for schema
91
+ const get_schema_cache = (schema) => {
92
+ let entry = schema_cache.get(schema);
93
+ if (!entry) {
94
+ const analysis = analyze_schema(schema);
95
+ entry = {
96
+ aliases: analysis.aliases,
97
+ canonical_keys: analysis.canonical_keys,
98
+ boolean_keys: analysis.boolean_keys,
99
+ conflict_error: analysis.errors.length > 0 ? to_conflict_error(analysis.errors) : null,
100
+ };
101
+ schema_cache.set(schema, entry);
102
+ }
103
+ return entry;
104
+ };
105
+ /**
106
+ * Validates a zod schema for CLI arg usage.
107
+ *
108
+ * Checks for:
109
+ * - Alias conflicts with canonical keys
110
+ * - Duplicate aliases across different keys
111
+ *
112
+ * Results are cached per schema (WeakMap). Safe to call multiple times.
113
+ *
114
+ * @param schema Zod object schema with optional alias metadata
115
+ * @returns Validation result with success flag and optional error
116
+ */
117
+ export const args_validate_schema = (schema) => {
118
+ const cache = get_schema_cache(schema);
119
+ if (cache.conflict_error) {
120
+ return { success: false, error: cache.conflict_error };
121
+ }
122
+ return { success: true };
123
+ };
124
+ /**
125
+ * Validates parsed CLI args against a zod schema.
126
+ *
127
+ * Handles CLI-specific concerns before validation:
128
+ * 1. Validates schema (cached) - returns error if alias conflicts exist
129
+ * 2. Expands aliases defined in schema `.meta({aliases: ['v']})`
130
+ * 3. Strips alias keys (required for strictObject schemas)
131
+ * 4. Validates with zod
132
+ * 5. After validation, syncs `no-` prefixed boolean flags with their base keys
133
+ *
134
+ * Schema analysis is cached per schema (WeakMap) for performance.
135
+ *
136
+ * @param unparsed_args Args object from CLI parser (mri, minimist, etc.)
137
+ * @param schema Zod object schema with optional alias metadata
138
+ * @returns Zod SafeParseResult with expanded/synced data on success
139
+ */
140
+ export const args_parse = (unparsed_args, schema) => {
141
+ const cache = get_schema_cache(schema);
142
+ // Return conflict error if schema has issues
143
+ if (cache.conflict_error) {
144
+ return { success: false, error: cache.conflict_error };
145
+ }
146
+ // Build expanded args - copy canonical, expand aliases, strip alias keys
147
+ const expanded = {};
148
+ for (const [key, value] of Object.entries(unparsed_args)) {
149
+ if (cache.aliases.has(key)) {
150
+ const canonical = cache.aliases.get(key);
151
+ // Only expand if canonical not already present (canonical takes precedence)
152
+ if (!(canonical in expanded) && !(canonical in unparsed_args)) {
153
+ expanded[canonical] = value;
154
+ }
155
+ // Don't copy alias key (strip it)
156
+ }
157
+ else {
158
+ expanded[key] = value;
159
+ }
160
+ }
161
+ // Validate with zod
162
+ const parsed = schema.safeParse(expanded);
163
+ if (parsed.success) {
164
+ // Mutate data with the correct source of truth for no- prefixed args
165
+ const data = parsed.data;
166
+ for (const key in data) {
167
+ if (key.startsWith('no-')) {
168
+ const base_key = key.substring(3);
169
+ // Only sync if both keys are booleans in the schema
170
+ if (cache.boolean_keys.has(key) && cache.boolean_keys.has(base_key)) {
171
+ if (!(key in unparsed_args) && !(key in expanded)) {
172
+ data[key] = !data[base_key];
173
+ }
174
+ else if (!(base_key in unparsed_args) && !(base_key in expanded)) {
175
+ data[base_key] = !data[key];
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ return parsed;
182
+ };
183
+ /**
184
+ * Serializes Args to CLI string array for subprocess forwarding.
185
+ *
186
+ * Handles CLI conventions:
187
+ * - Positionals first, then flags
188
+ * - Single-char keys get single dash, multi-char get double dash
189
+ * - Boolean `true` becomes bare flag, `false` is skipped
190
+ * - `undefined` values are skipped
191
+ * - `no-` prefixed keys skipped when base key is truthy (avoid contradiction)
192
+ * - When schema provided, extracts aliases and prefers shortest form
193
+ *
194
+ * Schema analysis is cached per schema (WeakMap) for performance.
195
+ *
196
+ * @param args Args object to serialize
197
+ * @param schema Optional zod schema to extract aliases for short form preference
198
+ * @returns Array of CLI argument strings
199
+ */
200
+ export const args_serialize = (args, schema) => {
201
+ const result = [];
202
+ // Build reverse map (canonical → shortest alias) if schema provided
203
+ let shortest_names;
204
+ if (schema) {
205
+ const cache = get_schema_cache(schema);
206
+ shortest_names = new Map();
207
+ // Group aliases by canonical key
208
+ const aliases_by_canonical = new Map();
209
+ for (const [alias, canonical] of cache.aliases) {
210
+ if (!aliases_by_canonical.has(canonical)) {
211
+ aliases_by_canonical.set(canonical, []);
212
+ }
213
+ aliases_by_canonical.get(canonical).push(alias);
214
+ }
215
+ // Find shortest for each canonical
216
+ for (const [canonical, aliases] of aliases_by_canonical) {
217
+ const all_names = [canonical, ...aliases];
218
+ const shortest = all_names.reduce((a, b) => (a.length <= b.length ? a : b));
219
+ shortest_names.set(canonical, shortest);
220
+ }
221
+ }
222
+ const add_value = (name, value) => {
223
+ if (value === undefined)
224
+ return;
225
+ if (value === false)
226
+ return; // Can't represent false as bare flag
227
+ result.push(name);
228
+ if (typeof value !== 'boolean') {
229
+ result.push(value + '');
230
+ }
231
+ };
232
+ let positionals = null;
233
+ for (const [key, value] of Object.entries(args)) {
234
+ if (key === '_') {
235
+ positionals = value ? value.map((v) => v + '') : [];
236
+ }
237
+ else {
238
+ // Skip no-X if X exists and is truthy
239
+ if (key.startsWith('no-')) {
240
+ const base = key.substring(3);
241
+ if (base in args && args[base]) {
242
+ continue; // Skip redundant no- flag
243
+ }
244
+ }
245
+ // Determine the name to use (prefer shortest if schema provided)
246
+ const use_key = shortest_names?.get(key) ?? key;
247
+ const name = `${use_key.length === 1 ? '-' : '--'}${use_key}`;
248
+ if (Array.isArray(value)) {
249
+ for (const v of value)
250
+ add_value(name, v);
251
+ }
252
+ else {
253
+ add_value(name, value);
254
+ }
255
+ }
256
+ }
257
+ return positionals ? [...positionals, ...result] : result;
258
+ };
259
+ /**
260
+ * Extracts alias mappings and canonical keys from a zod schema's metadata.
261
+ *
262
+ * Useful for consumers building custom tooling (help generators, conflict detection, etc.).
263
+ * Results are cached per schema (WeakMap).
264
+ *
265
+ * Note: Returns copies of the cached data to prevent mutation of internal cache.
266
+ *
267
+ * @param schema Zod object schema with optional `.meta({aliases})` on fields
268
+ * @returns Object with aliases map and canonical_keys set
269
+ */
270
+ export const args_extract_aliases = (schema) => {
271
+ const cache = get_schema_cache(schema);
272
+ return {
273
+ aliases: new Map(cache.aliases),
274
+ canonical_keys: new Set(cache.canonical_keys),
275
+ };
276
+ };
277
+ // Internal: Try to coerce a string value to number if it looks numeric
278
+ const coerce_value = (value) => {
279
+ // Handle empty string
280
+ if (value === '')
281
+ return value;
282
+ // Try to parse as number
283
+ const num = Number(value);
284
+ // Return number if valid and finite, otherwise keep as string
285
+ // This matches mri behavior: "123" -> 123, "12.5" -> 12.5, "1e5" -> 100000
286
+ // But "123abc" -> "123abc", "NaN" -> "NaN", "Infinity" -> "Infinity"
287
+ if (!Number.isNaN(num) && Number.isFinite(num) && value.trim() !== '') {
288
+ return num;
289
+ }
290
+ return value;
291
+ };
292
+ // Internal: Set a value on args, handling arrays for repeated flags
293
+ const set_arg = (args, key, value) => {
294
+ if (key in args) {
295
+ // Convert to array or push to existing array
296
+ const existing = args[key];
297
+ if (Array.isArray(existing)) {
298
+ existing.push(value);
299
+ }
300
+ else {
301
+ args[key] = [existing, value];
302
+ }
303
+ }
304
+ else {
305
+ args[key] = value;
306
+ }
307
+ };
308
+ /**
309
+ * Parses raw CLI argv array into an Args object.
310
+ *
311
+ * A lightweight, dependency-free alternative to mri/minimist with compatible behavior.
312
+ *
313
+ * Features:
314
+ * - `--flag` → `{flag: true}`
315
+ * - `--flag value` → `{flag: 'value'}` or `{flag: 123}` (numeric coercion)
316
+ * - `--flag=value` → equals syntax
317
+ * - `--flag=` → `{flag: ''}` (empty string, differs from mri which returns true)
318
+ * - `-f` → `{f: true}` (short flag)
319
+ * - `-f value` → `{f: 'value'}`
320
+ * - `-abc` → `{a: true, b: true, c: true}` (combined short flags)
321
+ * - `-abc value` → `{a: true, b: true, c: 'value'}` (last flag gets value)
322
+ * - `--no-flag` → `{flag: false}` (negation prefix)
323
+ * - `--` → stops flag parsing, rest become positionals
324
+ * - Positionals collected in `_` array
325
+ * - Repeated flags become arrays
326
+ *
327
+ * Intentional differences from mri:
328
+ * - `--flag=` returns `''` (mri returns `true`)
329
+ * - `--flag= next` returns `{flag: '', _: ['next']}` (mri takes `next` as the value)
330
+ * - `---flag` returns `{'-flag': true}` (mri strips all dashes)
331
+ * - `['--flag', '']` preserves `''` (mri coerces to `0`)
332
+ * - `--__proto__` works as a normal key (mri silently fails)
333
+ *
334
+ * The returned object uses `Object.create(null)` to prevent prototype pollution
335
+ * and allow any key name including `__proto__` and `constructor`.
336
+ *
337
+ * @param argv Raw argument array (typically process.argv.slice(2))
338
+ * @returns Parsed Args object with guaranteed `_` array (null prototype)
339
+ */
340
+ export const argv_parse = (argv) => {
341
+ // Use Object.create(null) to allow __proto__ as a normal key
342
+ // This prevents prototype pollution and makes all key names work
343
+ const args = Object.create(null);
344
+ args._ = [];
345
+ const positionals = args._;
346
+ let i = 0;
347
+ let flags_done = false; // Set to true after seeing --
348
+ while (i < argv.length) {
349
+ const arg = argv[i];
350
+ // After --, everything is a positional
351
+ if (flags_done) {
352
+ positionals.push(arg);
353
+ i++;
354
+ continue;
355
+ }
356
+ // -- stops flag parsing
357
+ if (arg === '--') {
358
+ flags_done = true;
359
+ i++;
360
+ continue;
361
+ }
362
+ // Long flag: --flag or --flag=value or --no-flag
363
+ if (arg.startsWith('--')) {
364
+ const rest = arg.slice(2);
365
+ // Handle --flag=value
366
+ const equals_index = rest.indexOf('=');
367
+ if (equals_index !== -1) {
368
+ const key = rest.slice(0, equals_index);
369
+ const value = rest.slice(equals_index + 1);
370
+ // Empty value after = becomes empty string (explicit value assignment)
371
+ // This differs from mri which treats it as boolean true
372
+ set_arg(args, key, coerce_value(value));
373
+ i++;
374
+ continue;
375
+ }
376
+ // Handle --no-flag (negation) - includes --no- which sets '' to false
377
+ if (rest.startsWith('no-')) {
378
+ const key = rest.slice(3); // May be empty string for --no-
379
+ args[key] = false;
380
+ i++;
381
+ continue;
382
+ }
383
+ // Handle --flag or --flag value
384
+ const key = rest;
385
+ const next = argv[i + 1];
386
+ // If next arg exists and doesn't look like a flag, it's the value
387
+ if (next !== undefined && !next.startsWith('-')) {
388
+ set_arg(args, key, coerce_value(next));
389
+ i += 2;
390
+ }
391
+ else {
392
+ // Boolean flag
393
+ args[key] = true;
394
+ i++;
395
+ }
396
+ continue;
397
+ }
398
+ // Single dash is ignored (matches mri)
399
+ if (arg === '-') {
400
+ i++;
401
+ continue;
402
+ }
403
+ // Short flag(s): -f or -abc or -f value
404
+ if (arg.startsWith('-') && arg.length > 1) {
405
+ const chars = arg.slice(1);
406
+ // Handle -f=value (short flag with equals)
407
+ const equals_index = chars.indexOf('=');
408
+ if (equals_index !== -1) {
409
+ const key = chars.slice(0, equals_index);
410
+ const value = chars.slice(equals_index + 1);
411
+ // For -abc=value, set a and b to true, c gets value
412
+ for (let j = 0; j < key.length - 1; j++) {
413
+ args[key[j]] = true;
414
+ }
415
+ if (key.length > 0) {
416
+ set_arg(args, key[key.length - 1], coerce_value(value));
417
+ }
418
+ i++;
419
+ continue;
420
+ }
421
+ // Handle combined flags: -abc means -a -b -c
422
+ // Last flag can take a value if next arg isn't a flag
423
+ const next = argv[i + 1];
424
+ const has_value = next !== undefined && !next.startsWith('-');
425
+ for (let j = 0; j < chars.length; j++) {
426
+ const char = chars[j];
427
+ const is_last = j === chars.length - 1;
428
+ if (is_last && has_value) {
429
+ // Last char gets the value
430
+ set_arg(args, char, coerce_value(next));
431
+ i += 2;
432
+ }
433
+ else {
434
+ // Boolean flag
435
+ args[char] = true;
436
+ if (is_last)
437
+ i++;
438
+ }
439
+ }
440
+ continue;
441
+ }
442
+ // Positional argument
443
+ positionals.push(arg);
444
+ i++;
445
+ }
446
+ return args;
447
+ };
package/dist/process.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type SpawnOptions, type ChildProcess } from 'node:child_process';
1
+ import { spawn as node_spawn_child_process, type SpawnOptions, type ChildProcess } from 'node:child_process';
2
2
  /**
3
3
  * Spawn failed before the process could run.
4
4
  *
@@ -64,6 +64,10 @@ export interface SpawnProcessOptions extends SpawnOptions {
64
64
  * Sends SIGTERM when exceeded. A value of 0 triggers immediate SIGTERM.
65
65
  */
66
66
  timeout_ms?: number;
67
+ /**
68
+ * Custom spawn function for testing. Defaults to `node:child_process` spawn.
69
+ */
70
+ spawn_child_process?: typeof node_spawn_child_process;
67
71
  }
68
72
  /**
69
73
  * Options for killing processes.
@@ -1 +1 @@
1
- {"version":3,"file":"process.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/process.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/process.ts"],"names":[],"mappings":"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"}
package/dist/process.js CHANGED
@@ -1,4 +1,4 @@
1
- import { spawn as spawn_child_process, } from 'node:child_process';
1
+ import { spawn as node_spawn_child_process, } from 'node:child_process';
2
2
  import { styleText as st } from 'node:util';
3
3
  import { Logger } from './log.js';
4
4
  import { print_error, print_key_value } from './print.js';
@@ -117,7 +117,7 @@ export class ProcessRegistry {
117
117
  * @returns Handle with `child` process and `closed` promise
118
118
  */
119
119
  spawn(command, args = [], options) {
120
- const { signal, timeout_ms, ...spawn_options } = options ?? {};
120
+ const { signal, timeout_ms, spawn_child_process = node_spawn_child_process, ...spawn_options } = options ?? {};
121
121
  validate_timeout_ms(timeout_ms);
122
122
  const child = spawn_child_process(command, args, { stdio: 'inherit', ...spawn_options });
123
123
  this.processes.add(child);
@@ -15,11 +15,11 @@ import { z } from 'zod';
15
15
  */
16
16
  export declare const DeclarationKind: z.ZodEnum<{
17
17
  function: "function";
18
- json: "json";
19
18
  type: "type";
19
+ constructor: "constructor";
20
+ json: "json";
20
21
  variable: "variable";
21
22
  class: "class";
22
- constructor: "constructor";
23
23
  component: "component";
24
24
  css: "css";
25
25
  }>;
@@ -79,11 +79,11 @@ export declare const DeclarationJson: z.ZodObject<{
79
79
  name: z.ZodString;
80
80
  kind: z.ZodEnum<{
81
81
  function: "function";
82
- json: "json";
83
82
  type: "type";
83
+ constructor: "constructor";
84
+ json: "json";
84
85
  variable: "variable";
85
86
  class: "class";
86
- constructor: "constructor";
87
87
  component: "component";
88
88
  css: "css";
89
89
  }>;
@@ -171,11 +171,11 @@ export declare const ModuleJson: z.ZodObject<{
171
171
  name: z.ZodString;
172
172
  kind: z.ZodEnum<{
173
173
  function: "function";
174
- json: "json";
175
174
  type: "type";
175
+ constructor: "constructor";
176
+ json: "json";
176
177
  variable: "variable";
177
178
  class: "class";
178
- constructor: "constructor";
179
179
  component: "component";
180
180
  css: "css";
181
181
  }>;
@@ -278,11 +278,11 @@ export declare const SourceJson: z.ZodObject<{
278
278
  name: z.ZodString;
279
279
  kind: z.ZodEnum<{
280
280
  function: "function";
281
- json: "json";
282
281
  type: "type";
282
+ constructor: "constructor";
283
+ json: "json";
283
284
  variable: "variable";
284
285
  class: "class";
285
- constructor: "constructor";
286
286
  component: "component";
287
287
  css: "css";
288
288
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_util",
3
- "version": "0.47.0",
3
+ "version": "0.48.1",
4
4
  "description": "utility belt for JS",
5
5
  "glyph": "🦕",
6
6
  "logo": "logo.svg",
@@ -65,7 +65,7 @@
65
65
  "@fuzdev/fuz_css": "^0.44.1",
66
66
  "@fuzdev/fuz_ui": "^0.179.0",
67
67
  "@ryanatkn/eslint-config": "^0.9.0",
68
- "@ryanatkn/gro": "^0.187.0",
68
+ "@ryanatkn/gro": "^0.189.0",
69
69
  "@sveltejs/adapter-static": "^3.0.10",
70
70
  "@sveltejs/kit": "^2.50.1",
71
71
  "@sveltejs/package": "^2.5.7",
@@ -0,0 +1,546 @@
1
+ import {z} from 'zod';
2
+
3
+ /**
4
+ * CLI arguments container.
5
+ * Positional arguments stored in `_`, named flags/options as string keys.
6
+ * Produced by `argv_parse` or external parsers (mri, minimist, etc.).
7
+ */
8
+ export interface Args {
9
+ _?: Array<string>;
10
+ [key: string]: ArgValue;
11
+ }
12
+
13
+ /**
14
+ * Parsed CLI arguments with guaranteed positionals array.
15
+ * Returned by `argv_parse` which always initializes `_`.
16
+ */
17
+ export interface ParsedArgs extends Args {
18
+ _: Array<string>;
19
+ }
20
+
21
+ /**
22
+ * Value types supported in CLI arguments.
23
+ */
24
+ export type ArgValue = string | number | boolean | undefined | Array<string | number | boolean>;
25
+
26
+ /**
27
+ * Schema description for help text generation.
28
+ * Not used by args_parse/args_serialize directly - provided for consumers
29
+ * building CLI help output.
30
+ */
31
+ export interface ArgSchema {
32
+ type: string;
33
+ default: ArgValue;
34
+ description: string;
35
+ }
36
+
37
+ /**
38
+ * Result of alias extraction from a schema.
39
+ * Includes canonical keys for downstream conflict detection.
40
+ */
41
+ export interface ArgsAliasesResult {
42
+ aliases: Map<string, string>; // alias → canonical
43
+ canonical_keys: Set<string>; // all canonical key names
44
+ }
45
+
46
+ // Internal cache entry structure
47
+ interface SchemaCacheEntry {
48
+ aliases: Map<string, string>; // alias → canonical
49
+ canonical_keys: Set<string>; // all canonical key names
50
+ boolean_keys: Set<string>; // keys with boolean type (for no- sync)
51
+ conflict_error: z.ZodError | null; // null if schema is valid
52
+ }
53
+
54
+ // WeakMap cache for schema analysis - avoids redundant reflection
55
+ const schema_cache: WeakMap<z.ZodType, SchemaCacheEntry> = new WeakMap();
56
+
57
+ // Internal: Unwrap nested schema types (Optional, Default, Transform, Pipe)
58
+ const unwrap_schema = (def: z.core.$ZodTypeDef): z.ZodType | undefined => {
59
+ if ('innerType' in def) return def.innerType as z.ZodType; // Optional, Nullable
60
+ if ('in' in def) return def.in as z.ZodType; // Pipe
61
+ if ('schema' in def) return def.schema as z.ZodType; // Default, Transform
62
+ return undefined;
63
+ };
64
+
65
+ // Internal: Check if schema type is boolean (recursing through wrappers)
66
+ const is_boolean_field = (schema: z.ZodType): boolean => {
67
+ const def = schema._zod.def;
68
+ if (def.type === 'boolean') return true;
69
+ const inner = unwrap_schema(def);
70
+ if (inner) return is_boolean_field(inner);
71
+ return false;
72
+ };
73
+
74
+ // Internal: Schema analysis result
75
+ interface SchemaAnalysisResult {
76
+ aliases: Map<string, string>;
77
+ canonical_keys: Set<string>;
78
+ boolean_keys: Set<string>;
79
+ errors: Array<{
80
+ type: 'alias_canonical_conflict' | 'duplicate_alias';
81
+ alias: string;
82
+ canonical: string;
83
+ conflict_with: string;
84
+ }>;
85
+ }
86
+
87
+ // Internal: Analyze schema for aliases, canonical keys, boolean keys, and conflicts
88
+ const analyze_schema = (schema: z.ZodType): SchemaAnalysisResult => {
89
+ const aliases: Map<string, string> = new Map();
90
+ const canonical_keys: Set<string> = new Set();
91
+ const boolean_keys: Set<string> = new Set();
92
+ const errors: SchemaAnalysisResult['errors'] = [];
93
+ const def = schema._zod.def;
94
+
95
+ // Unwrap to get object def (handle wrapped types like optional, default, etc.)
96
+ let obj_def = def;
97
+ while (!('shape' in obj_def)) {
98
+ const inner = unwrap_schema(obj_def);
99
+ if (!inner) return {aliases, canonical_keys, boolean_keys, errors};
100
+ obj_def = inner._zod.def;
101
+ }
102
+
103
+ const shape = (obj_def as z.core.$ZodObjectDef).shape;
104
+
105
+ // First pass: collect all canonical keys
106
+ for (const key of Object.keys(shape)) {
107
+ canonical_keys.add(key);
108
+ }
109
+
110
+ // Second pass: process fields for aliases and booleans
111
+ for (const [key, field] of Object.entries(shape)) {
112
+ const field_schema = field as z.ZodType;
113
+
114
+ // Track boolean fields for no- prefix sync
115
+ if (is_boolean_field(field_schema)) {
116
+ boolean_keys.add(key);
117
+ }
118
+
119
+ const meta = field_schema.meta();
120
+ if (meta?.aliases) {
121
+ for (const alias of meta.aliases as Array<string>) {
122
+ // Check for alias-canonical conflict
123
+ if (canonical_keys.has(alias)) {
124
+ errors.push({
125
+ type: 'alias_canonical_conflict',
126
+ alias,
127
+ canonical: key,
128
+ conflict_with: alias,
129
+ });
130
+ }
131
+ // Check for duplicate alias
132
+ else if (aliases.has(alias)) {
133
+ errors.push({
134
+ type: 'duplicate_alias',
135
+ alias,
136
+ canonical: key,
137
+ conflict_with: aliases.get(alias)!,
138
+ });
139
+ } else {
140
+ aliases.set(alias, key);
141
+ }
142
+ }
143
+ }
144
+ }
145
+ return {aliases, canonical_keys, boolean_keys, errors};
146
+ };
147
+
148
+ // Internal: Convert analysis errors to ZodError for consistent API
149
+ const to_conflict_error = (errors: SchemaAnalysisResult['errors']): z.ZodError => {
150
+ return new z.ZodError(
151
+ errors.map((err) => ({
152
+ code: 'custom' as const,
153
+ path: [err.alias],
154
+ message:
155
+ err.type === 'alias_canonical_conflict'
156
+ ? `Alias '${err.alias}' for '${err.canonical}' conflicts with canonical key '${err.conflict_with}'`
157
+ : `Alias '${err.alias}' is used by both '${err.canonical}' and '${err.conflict_with}'`,
158
+ })),
159
+ );
160
+ };
161
+
162
+ // Internal: Get or create cache entry for schema
163
+ const get_schema_cache = (schema: z.ZodType): SchemaCacheEntry => {
164
+ let entry = schema_cache.get(schema);
165
+ if (!entry) {
166
+ const analysis = analyze_schema(schema);
167
+ entry = {
168
+ aliases: analysis.aliases,
169
+ canonical_keys: analysis.canonical_keys,
170
+ boolean_keys: analysis.boolean_keys,
171
+ conflict_error: analysis.errors.length > 0 ? to_conflict_error(analysis.errors) : null,
172
+ };
173
+ schema_cache.set(schema, entry);
174
+ }
175
+ return entry;
176
+ };
177
+
178
+ /**
179
+ * Validates a zod schema for CLI arg usage.
180
+ *
181
+ * Checks for:
182
+ * - Alias conflicts with canonical keys
183
+ * - Duplicate aliases across different keys
184
+ *
185
+ * Results are cached per schema (WeakMap). Safe to call multiple times.
186
+ *
187
+ * @param schema Zod object schema with optional alias metadata
188
+ * @returns Validation result with success flag and optional error
189
+ */
190
+ export const args_validate_schema = (
191
+ schema: z.ZodType,
192
+ ): {success: true} | {success: false; error: z.ZodError} => {
193
+ const cache = get_schema_cache(schema);
194
+ if (cache.conflict_error) {
195
+ return {success: false, error: cache.conflict_error};
196
+ }
197
+ return {success: true};
198
+ };
199
+
200
+ /**
201
+ * Validates parsed CLI args against a zod schema.
202
+ *
203
+ * Handles CLI-specific concerns before validation:
204
+ * 1. Validates schema (cached) - returns error if alias conflicts exist
205
+ * 2. Expands aliases defined in schema `.meta({aliases: ['v']})`
206
+ * 3. Strips alias keys (required for strictObject schemas)
207
+ * 4. Validates with zod
208
+ * 5. After validation, syncs `no-` prefixed boolean flags with their base keys
209
+ *
210
+ * Schema analysis is cached per schema (WeakMap) for performance.
211
+ *
212
+ * @param unparsed_args Args object from CLI parser (mri, minimist, etc.)
213
+ * @param schema Zod object schema with optional alias metadata
214
+ * @returns Zod SafeParseResult with expanded/synced data on success
215
+ */
216
+ export const args_parse = <TOutput extends Record<string, ArgValue> = Args>(
217
+ unparsed_args: Args,
218
+ schema: z.ZodType<TOutput>,
219
+ ): z.ZodSafeParseResult<TOutput> => {
220
+ const cache = get_schema_cache(schema);
221
+
222
+ // Return conflict error if schema has issues
223
+ if (cache.conflict_error) {
224
+ return {success: false, error: cache.conflict_error as z.ZodError<TOutput>};
225
+ }
226
+
227
+ // Build expanded args - copy canonical, expand aliases, strip alias keys
228
+ const expanded: Record<string, ArgValue> = {};
229
+ for (const [key, value] of Object.entries(unparsed_args)) {
230
+ if (cache.aliases.has(key)) {
231
+ const canonical = cache.aliases.get(key)!;
232
+ // Only expand if canonical not already present (canonical takes precedence)
233
+ if (!(canonical in expanded) && !(canonical in unparsed_args)) {
234
+ expanded[canonical] = value;
235
+ }
236
+ // Don't copy alias key (strip it)
237
+ } else {
238
+ expanded[key] = value;
239
+ }
240
+ }
241
+
242
+ // Validate with zod
243
+ const parsed = schema.safeParse(expanded);
244
+
245
+ if (parsed.success) {
246
+ // Mutate data with the correct source of truth for no- prefixed args
247
+ const data = parsed.data as Record<string, ArgValue>;
248
+ for (const key in data) {
249
+ if (key.startsWith('no-')) {
250
+ const base_key = key.substring(3);
251
+ // Only sync if both keys are booleans in the schema
252
+ if (cache.boolean_keys.has(key) && cache.boolean_keys.has(base_key)) {
253
+ if (!(key in unparsed_args) && !(key in expanded)) {
254
+ data[key] = !data[base_key];
255
+ } else if (!(base_key in unparsed_args) && !(base_key in expanded)) {
256
+ data[base_key] = !data[key];
257
+ }
258
+ }
259
+ }
260
+ }
261
+ }
262
+
263
+ return parsed;
264
+ };
265
+
266
+ /**
267
+ * Serializes Args to CLI string array for subprocess forwarding.
268
+ *
269
+ * Handles CLI conventions:
270
+ * - Positionals first, then flags
271
+ * - Single-char keys get single dash, multi-char get double dash
272
+ * - Boolean `true` becomes bare flag, `false` is skipped
273
+ * - `undefined` values are skipped
274
+ * - `no-` prefixed keys skipped when base key is truthy (avoid contradiction)
275
+ * - When schema provided, extracts aliases and prefers shortest form
276
+ *
277
+ * Schema analysis is cached per schema (WeakMap) for performance.
278
+ *
279
+ * @param args Args object to serialize
280
+ * @param schema Optional zod schema to extract aliases for short form preference
281
+ * @returns Array of CLI argument strings
282
+ */
283
+ export const args_serialize = (args: Args, schema?: z.ZodType): Array<string> => {
284
+ const result: Array<string> = [];
285
+
286
+ // Build reverse map (canonical → shortest alias) if schema provided
287
+ let shortest_names: Map<string, string> | undefined;
288
+ if (schema) {
289
+ const cache = get_schema_cache(schema);
290
+ shortest_names = new Map();
291
+ // Group aliases by canonical key
292
+ const aliases_by_canonical: Map<string, Array<string>> = new Map();
293
+ for (const [alias, canonical] of cache.aliases) {
294
+ if (!aliases_by_canonical.has(canonical)) {
295
+ aliases_by_canonical.set(canonical, []);
296
+ }
297
+ aliases_by_canonical.get(canonical)!.push(alias);
298
+ }
299
+ // Find shortest for each canonical
300
+ for (const [canonical, aliases] of aliases_by_canonical) {
301
+ const all_names = [canonical, ...aliases];
302
+ const shortest = all_names.reduce((a, b) => (a.length <= b.length ? a : b));
303
+ shortest_names.set(canonical, shortest);
304
+ }
305
+ }
306
+
307
+ const add_value = (name: string, value: string | number | boolean | undefined): void => {
308
+ if (value === undefined) return;
309
+ if (value === false) return; // Can't represent false as bare flag
310
+ result.push(name);
311
+ if (typeof value !== 'boolean') {
312
+ result.push(value + '');
313
+ }
314
+ };
315
+
316
+ let positionals: Array<string> | null = null;
317
+ for (const [key, value] of Object.entries(args)) {
318
+ if (key === '_') {
319
+ positionals = value ? (value as Array<string | number | boolean>).map((v) => v + '') : [];
320
+ } else {
321
+ // Skip no-X if X exists and is truthy
322
+ if (key.startsWith('no-')) {
323
+ const base = key.substring(3);
324
+ if (base in args && args[base]) {
325
+ continue; // Skip redundant no- flag
326
+ }
327
+ }
328
+
329
+ // Determine the name to use (prefer shortest if schema provided)
330
+ const use_key = shortest_names?.get(key) ?? key;
331
+ const name = `${use_key.length === 1 ? '-' : '--'}${use_key}`;
332
+
333
+ if (Array.isArray(value)) {
334
+ for (const v of value) add_value(name, v);
335
+ } else {
336
+ add_value(name, value);
337
+ }
338
+ }
339
+ }
340
+
341
+ return positionals ? [...positionals, ...result] : result;
342
+ };
343
+
344
+ /**
345
+ * Extracts alias mappings and canonical keys from a zod schema's metadata.
346
+ *
347
+ * Useful for consumers building custom tooling (help generators, conflict detection, etc.).
348
+ * Results are cached per schema (WeakMap).
349
+ *
350
+ * Note: Returns copies of the cached data to prevent mutation of internal cache.
351
+ *
352
+ * @param schema Zod object schema with optional `.meta({aliases})` on fields
353
+ * @returns Object with aliases map and canonical_keys set
354
+ */
355
+ export const args_extract_aliases = (schema: z.ZodType): ArgsAliasesResult => {
356
+ const cache = get_schema_cache(schema);
357
+ return {
358
+ aliases: new Map(cache.aliases),
359
+ canonical_keys: new Set(cache.canonical_keys),
360
+ };
361
+ };
362
+
363
+ // Internal: Try to coerce a string value to number if it looks numeric
364
+ const coerce_value = (value: string): string | number => {
365
+ // Handle empty string
366
+ if (value === '') return value;
367
+ // Try to parse as number
368
+ const num = Number(value);
369
+ // Return number if valid and finite, otherwise keep as string
370
+ // This matches mri behavior: "123" -> 123, "12.5" -> 12.5, "1e5" -> 100000
371
+ // But "123abc" -> "123abc", "NaN" -> "NaN", "Infinity" -> "Infinity"
372
+ if (!Number.isNaN(num) && Number.isFinite(num) && value.trim() !== '') {
373
+ return num;
374
+ }
375
+ return value;
376
+ };
377
+
378
+ // Internal: Set a value on args, handling arrays for repeated flags
379
+ const set_arg = (args: Args, key: string, value: string | number | boolean): void => {
380
+ if (key in args) {
381
+ // Convert to array or push to existing array
382
+ const existing = args[key];
383
+ if (Array.isArray(existing)) {
384
+ existing.push(value);
385
+ } else {
386
+ args[key] = [existing!, value];
387
+ }
388
+ } else {
389
+ args[key] = value;
390
+ }
391
+ };
392
+
393
+ /**
394
+ * Parses raw CLI argv array into an Args object.
395
+ *
396
+ * A lightweight, dependency-free alternative to mri/minimist with compatible behavior.
397
+ *
398
+ * Features:
399
+ * - `--flag` → `{flag: true}`
400
+ * - `--flag value` → `{flag: 'value'}` or `{flag: 123}` (numeric coercion)
401
+ * - `--flag=value` → equals syntax
402
+ * - `--flag=` → `{flag: ''}` (empty string, differs from mri which returns true)
403
+ * - `-f` → `{f: true}` (short flag)
404
+ * - `-f value` → `{f: 'value'}`
405
+ * - `-abc` → `{a: true, b: true, c: true}` (combined short flags)
406
+ * - `-abc value` → `{a: true, b: true, c: 'value'}` (last flag gets value)
407
+ * - `--no-flag` → `{flag: false}` (negation prefix)
408
+ * - `--` → stops flag parsing, rest become positionals
409
+ * - Positionals collected in `_` array
410
+ * - Repeated flags become arrays
411
+ *
412
+ * Intentional differences from mri:
413
+ * - `--flag=` returns `''` (mri returns `true`)
414
+ * - `--flag= next` returns `{flag: '', _: ['next']}` (mri takes `next` as the value)
415
+ * - `---flag` returns `{'-flag': true}` (mri strips all dashes)
416
+ * - `['--flag', '']` preserves `''` (mri coerces to `0`)
417
+ * - `--__proto__` works as a normal key (mri silently fails)
418
+ *
419
+ * The returned object uses `Object.create(null)` to prevent prototype pollution
420
+ * and allow any key name including `__proto__` and `constructor`.
421
+ *
422
+ * @param argv Raw argument array (typically process.argv.slice(2))
423
+ * @returns Parsed Args object with guaranteed `_` array (null prototype)
424
+ */
425
+ export const argv_parse = (argv: Array<string>): ParsedArgs => {
426
+ // Use Object.create(null) to allow __proto__ as a normal key
427
+ // This prevents prototype pollution and makes all key names work
428
+ const args = Object.create(null) as ParsedArgs;
429
+ args._ = [];
430
+ const positionals = args._;
431
+
432
+ let i = 0;
433
+ let flags_done = false; // Set to true after seeing --
434
+
435
+ while (i < argv.length) {
436
+ const arg = argv[i]!;
437
+
438
+ // After --, everything is a positional
439
+ if (flags_done) {
440
+ positionals.push(arg);
441
+ i++;
442
+ continue;
443
+ }
444
+
445
+ // -- stops flag parsing
446
+ if (arg === '--') {
447
+ flags_done = true;
448
+ i++;
449
+ continue;
450
+ }
451
+
452
+ // Long flag: --flag or --flag=value or --no-flag
453
+ if (arg.startsWith('--')) {
454
+ const rest = arg.slice(2);
455
+
456
+ // Handle --flag=value
457
+ const equals_index = rest.indexOf('=');
458
+ if (equals_index !== -1) {
459
+ const key = rest.slice(0, equals_index);
460
+ const value = rest.slice(equals_index + 1);
461
+ // Empty value after = becomes empty string (explicit value assignment)
462
+ // This differs from mri which treats it as boolean true
463
+ set_arg(args, key, coerce_value(value));
464
+ i++;
465
+ continue;
466
+ }
467
+
468
+ // Handle --no-flag (negation) - includes --no- which sets '' to false
469
+ if (rest.startsWith('no-')) {
470
+ const key = rest.slice(3); // May be empty string for --no-
471
+ args[key] = false;
472
+ i++;
473
+ continue;
474
+ }
475
+
476
+ // Handle --flag or --flag value
477
+ const key = rest;
478
+ const next = argv[i + 1];
479
+
480
+ // If next arg exists and doesn't look like a flag, it's the value
481
+ if (next !== undefined && !next.startsWith('-')) {
482
+ set_arg(args, key, coerce_value(next));
483
+ i += 2;
484
+ } else {
485
+ // Boolean flag
486
+ args[key] = true;
487
+ i++;
488
+ }
489
+ continue;
490
+ }
491
+
492
+ // Single dash is ignored (matches mri)
493
+ if (arg === '-') {
494
+ i++;
495
+ continue;
496
+ }
497
+
498
+ // Short flag(s): -f or -abc or -f value
499
+ if (arg.startsWith('-') && arg.length > 1) {
500
+ const chars = arg.slice(1);
501
+
502
+ // Handle -f=value (short flag with equals)
503
+ const equals_index = chars.indexOf('=');
504
+ if (equals_index !== -1) {
505
+ const key = chars.slice(0, equals_index);
506
+ const value = chars.slice(equals_index + 1);
507
+ // For -abc=value, set a and b to true, c gets value
508
+ for (let j = 0; j < key.length - 1; j++) {
509
+ args[key[j]!] = true;
510
+ }
511
+ if (key.length > 0) {
512
+ set_arg(args, key[key.length - 1]!, coerce_value(value));
513
+ }
514
+ i++;
515
+ continue;
516
+ }
517
+
518
+ // Handle combined flags: -abc means -a -b -c
519
+ // Last flag can take a value if next arg isn't a flag
520
+ const next = argv[i + 1];
521
+ const has_value = next !== undefined && !next.startsWith('-');
522
+
523
+ for (let j = 0; j < chars.length; j++) {
524
+ const char = chars[j]!;
525
+ const is_last = j === chars.length - 1;
526
+
527
+ if (is_last && has_value) {
528
+ // Last char gets the value
529
+ set_arg(args, char, coerce_value(next));
530
+ i += 2;
531
+ } else {
532
+ // Boolean flag
533
+ args[char] = true;
534
+ if (is_last) i++;
535
+ }
536
+ }
537
+ continue;
538
+ }
539
+
540
+ // Positional argument
541
+ positionals.push(arg);
542
+ i++;
543
+ }
544
+
545
+ return args;
546
+ };
@@ -1,5 +1,5 @@
1
1
  import {
2
- spawn as spawn_child_process,
2
+ spawn as node_spawn_child_process,
3
3
  type SpawnOptions,
4
4
  type ChildProcess,
5
5
  } from 'node:child_process';
@@ -98,6 +98,10 @@ export interface SpawnProcessOptions extends SpawnOptions {
98
98
  * Sends SIGTERM when exceeded. A value of 0 triggers immediate SIGTERM.
99
99
  */
100
100
  timeout_ms?: number;
101
+ /**
102
+ * Custom spawn function for testing. Defaults to `node:child_process` spawn.
103
+ */
104
+ spawn_child_process?: typeof node_spawn_child_process;
101
105
  }
102
106
 
103
107
  /**
@@ -251,7 +255,12 @@ export class ProcessRegistry {
251
255
  args: ReadonlyArray<string> = [],
252
256
  options?: SpawnProcessOptions,
253
257
  ): SpawnedProcess {
254
- const {signal, timeout_ms, ...spawn_options} = options ?? {};
258
+ const {
259
+ signal,
260
+ timeout_ms,
261
+ spawn_child_process = node_spawn_child_process,
262
+ ...spawn_options
263
+ } = options ?? {};
255
264
  validate_timeout_ms(timeout_ms);
256
265
  const child = spawn_child_process(command, args, {stdio: 'inherit', ...spawn_options});
257
266