just-bash-util 0.1.0

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.
@@ -0,0 +1,793 @@
1
+ // src/command/builders/option.ts
2
+ var OptionBuilder = class _OptionBuilder {
3
+ /** @internal */
4
+ _def;
5
+ constructor(def) {
6
+ this._def = def;
7
+ }
8
+ /** Add a description */
9
+ describe(text) {
10
+ return new _OptionBuilder({ ...this._def, description: text });
11
+ }
12
+ /** Set a short alias (single character) */
13
+ short(alias) {
14
+ return new _OptionBuilder({ ...this._def, short: alias });
15
+ }
16
+ /** Set an environment variable fallback */
17
+ env(name) {
18
+ return new _OptionBuilder({ ...this._def, env: name });
19
+ }
20
+ /** Mark as required — removes undefined from TOut */
21
+ required() {
22
+ return new _OptionBuilder({
23
+ ...this._def,
24
+ required: true
25
+ });
26
+ }
27
+ /** Set a default value — removes undefined from TOut */
28
+ default(value) {
29
+ return new _OptionBuilder({
30
+ ...this._def,
31
+ default: value
32
+ });
33
+ }
34
+ };
35
+ function createOption(type) {
36
+ return new OptionBuilder({
37
+ _kind: "option",
38
+ type
39
+ });
40
+ }
41
+ function string() {
42
+ return createOption("string");
43
+ }
44
+ function number() {
45
+ return createOption("number");
46
+ }
47
+
48
+ // src/command/builders/flag.ts
49
+ var FlagBuilder = class _FlagBuilder {
50
+ /** @internal */
51
+ _def;
52
+ constructor(def = { _kind: "flag" }) {
53
+ this._def = def;
54
+ }
55
+ /** Add a description */
56
+ describe(text) {
57
+ return new _FlagBuilder({ ...this._def, description: text });
58
+ }
59
+ /** Set a short alias (single character) */
60
+ short(alias) {
61
+ return new _FlagBuilder({ ...this._def, short: alias });
62
+ }
63
+ /** Set a default value */
64
+ default(value) {
65
+ return new _FlagBuilder({ ...this._def, default: value });
66
+ }
67
+ };
68
+
69
+ // src/command/builders/arg.ts
70
+ var ArgBuilder = class _ArgBuilder {
71
+ /** @internal */
72
+ _def;
73
+ constructor(def) {
74
+ this._def = def;
75
+ }
76
+ /** Set the positional arg name (used for named access in the handler) */
77
+ name(name) {
78
+ return new _ArgBuilder({
79
+ ...this._def,
80
+ name
81
+ });
82
+ }
83
+ /** Add a description */
84
+ describe(text) {
85
+ return new _ArgBuilder({ ...this._def, description: text });
86
+ }
87
+ /** Mark as optional — adds undefined to TOut */
88
+ optional() {
89
+ return new _ArgBuilder({
90
+ ...this._def,
91
+ required: false
92
+ });
93
+ }
94
+ /** Mark as variadic — collects all remaining positionals into an array */
95
+ variadic() {
96
+ return new _ArgBuilder({
97
+ ...this._def,
98
+ variadic: true
99
+ });
100
+ }
101
+ /** Set a default value (also makes the arg optional at parse time) */
102
+ default(value) {
103
+ return new _ArgBuilder({
104
+ ...this._def,
105
+ required: false,
106
+ default: value
107
+ });
108
+ }
109
+ };
110
+ function createArg(type) {
111
+ return new ArgBuilder({
112
+ _kind: "arg",
113
+ type,
114
+ required: true
115
+ });
116
+ }
117
+ function string2() {
118
+ return createArg("string");
119
+ }
120
+ function number2() {
121
+ return createArg("number");
122
+ }
123
+
124
+ // src/command/builders/index.ts
125
+ var o = {
126
+ string,
127
+ number
128
+ };
129
+ function f() {
130
+ return new FlagBuilder();
131
+ }
132
+ var a = {
133
+ string: string2,
134
+ number: number2
135
+ };
136
+
137
+ // src/command/errors.ts
138
+ function formatError(error) {
139
+ switch (error.type) {
140
+ case "unknown_option": {
141
+ let msg = `Unknown option "${error.name}".`;
142
+ if (error.suggestions.length > 0) {
143
+ msg += ` Did you mean ${error.suggestions.map((s) => `"${s}"`).join(" or ")}?`;
144
+ }
145
+ return msg;
146
+ }
147
+ case "invalid_type":
148
+ return `Invalid value for "${error.name}": expected ${error.expected}, got "${error.received}".`;
149
+ case "missing_required":
150
+ return error.kind === "option" ? `Missing required option "--${error.name}".` : `Missing required argument <${error.name}>.`;
151
+ case "unexpected_positional":
152
+ return error.maxPositionals === 0 ? `Unexpected argument "${error.value}". This command takes no positional arguments.` : `Unexpected argument "${error.value}". Expected at most ${error.maxPositionals} positional argument${error.maxPositionals === 1 ? "" : "s"}.`;
153
+ case "missing_value":
154
+ return `Option "--${error.name}" requires a value.`;
155
+ case "unknown_command": {
156
+ let msg = `Unknown command "${error.path}".`;
157
+ if (error.suggestions.length > 0) {
158
+ msg += ` Did you mean ${error.suggestions.map((s) => `"${s}"`).join(" or ")}?`;
159
+ }
160
+ return msg;
161
+ }
162
+ }
163
+ }
164
+ function formatErrors(errors) {
165
+ return errors.map(formatError).join("\n");
166
+ }
167
+ function levenshtein(a2, b) {
168
+ const m = a2.length;
169
+ const n = b.length;
170
+ const dp = new Array((m + 1) * (n + 1));
171
+ for (let i = 0; i <= m; i++) dp[i * (n + 1)] = i;
172
+ for (let j = 0; j <= n; j++) dp[j] = j;
173
+ for (let i = 1; i <= m; i++) {
174
+ for (let j = 1; j <= n; j++) {
175
+ const cost = a2[i - 1] === b[j - 1] ? 0 : 1;
176
+ dp[i * (n + 1) + j] = Math.min(
177
+ dp[(i - 1) * (n + 1) + j] + 1,
178
+ dp[i * (n + 1) + (j - 1)] + 1,
179
+ dp[(i - 1) * (n + 1) + (j - 1)] + cost
180
+ );
181
+ }
182
+ }
183
+ return dp[m * (n + 1) + n];
184
+ }
185
+ function findSuggestions(input, candidates, maxDistance = 3) {
186
+ const scored = candidates.map((c) => ({ candidate: c, distance: levenshtein(input, c) })).filter((x) => x.distance <= maxDistance && x.distance > 0).sort((a2, b) => a2.distance - b.distance);
187
+ return scored.slice(0, 2).map((x) => x.candidate);
188
+ }
189
+
190
+ // src/command/parser.ts
191
+ function parseArgs(options, argDefs, tokens, env) {
192
+ const errors = [];
193
+ const longMap = /* @__PURE__ */ new Map();
194
+ const shortMap = /* @__PURE__ */ new Map();
195
+ for (const [key, def] of Object.entries(options)) {
196
+ const longName = camelToKebab(key);
197
+ longMap.set(longName, { key, def });
198
+ if (def.short) {
199
+ shortMap.set(def.short, { key, def });
200
+ }
201
+ }
202
+ const result = {};
203
+ const positionals = [];
204
+ const passthrough = [];
205
+ let i = 0;
206
+ while (i < tokens.length) {
207
+ const token = tokens[i];
208
+ if (token === "--") {
209
+ i++;
210
+ while (i < tokens.length) {
211
+ passthrough.push(tokens[i]);
212
+ i++;
213
+ }
214
+ break;
215
+ }
216
+ if (token.startsWith("--")) {
217
+ const eqIdx = token.indexOf("=");
218
+ let longName;
219
+ let inlineValue;
220
+ if (eqIdx !== -1) {
221
+ longName = token.slice(2, eqIdx);
222
+ inlineValue = token.slice(eqIdx + 1);
223
+ } else {
224
+ longName = token.slice(2);
225
+ }
226
+ const entry = longMap.get(longName);
227
+ if (!entry) {
228
+ if (longName.startsWith("no-")) {
229
+ const positiveEntry = longMap.get(longName.slice(3));
230
+ if (positiveEntry && positiveEntry.def._kind === "flag") {
231
+ result[positiveEntry.key] = false;
232
+ i++;
233
+ continue;
234
+ }
235
+ }
236
+ const allLongNames = [...longMap.keys()];
237
+ errors.push({
238
+ type: "unknown_option",
239
+ name: `--${longName}`,
240
+ suggestions: findSuggestions(longName, allLongNames).map((s) => `--${s}`)
241
+ });
242
+ i++;
243
+ continue;
244
+ }
245
+ if (entry.def._kind === "flag") {
246
+ result[entry.key] = true;
247
+ i++;
248
+ continue;
249
+ }
250
+ const rawValue = inlineValue ?? tokens[++i];
251
+ if (rawValue === void 0) {
252
+ errors.push({ type: "missing_value", name: entry.key });
253
+ i++;
254
+ continue;
255
+ }
256
+ const parsed = coerce(rawValue, entry.def.type, entry.key, errors);
257
+ if (parsed !== void 0) {
258
+ result[entry.key] = parsed;
259
+ }
260
+ i++;
261
+ continue;
262
+ }
263
+ if (token.startsWith("-") && token.length > 1) {
264
+ const chars = token.slice(1);
265
+ for (let j = 0; j < chars.length; j++) {
266
+ const ch = chars[j];
267
+ const entry = shortMap.get(ch);
268
+ if (!entry) {
269
+ errors.push({
270
+ type: "unknown_option",
271
+ name: `-${ch}`,
272
+ suggestions: []
273
+ });
274
+ continue;
275
+ }
276
+ if (entry.def._kind === "flag") {
277
+ result[entry.key] = true;
278
+ continue;
279
+ }
280
+ const restOfString = chars.slice(j + 1);
281
+ const rawValue = restOfString.length > 0 ? restOfString : tokens[++i];
282
+ if (rawValue === void 0) {
283
+ errors.push({ type: "missing_value", name: entry.key });
284
+ break;
285
+ }
286
+ const parsed = coerce(rawValue, entry.def.type, entry.key, errors);
287
+ if (parsed !== void 0) {
288
+ result[entry.key] = parsed;
289
+ }
290
+ break;
291
+ }
292
+ i++;
293
+ continue;
294
+ }
295
+ positionals.push(token);
296
+ i++;
297
+ }
298
+ let posIdx = 0;
299
+ for (let idx = 0; idx < argDefs.length; idx++) {
300
+ const argDef = argDefs[idx];
301
+ const argName = argDef.name ?? `arg${idx}`;
302
+ if (argDef.variadic) {
303
+ const values = positionals.slice(posIdx);
304
+ if (values.length > 0) {
305
+ result[argName] = values.map((v) => coerce(v, argDef.type, argName, errors));
306
+ } else if (argDef.required) {
307
+ errors.push({ type: "missing_required", name: argName, kind: "arg" });
308
+ } else if (argDef.default !== void 0) {
309
+ result[argName] = argDef.default;
310
+ }
311
+ posIdx = positionals.length;
312
+ } else {
313
+ const value = positionals[posIdx];
314
+ if (value !== void 0) {
315
+ result[argName] = coerce(value, argDef.type, argName, errors);
316
+ posIdx++;
317
+ } else if (argDef.required) {
318
+ errors.push({ type: "missing_required", name: argName, kind: "arg" });
319
+ } else if (argDef.default !== void 0) {
320
+ result[argName] = argDef.default;
321
+ }
322
+ }
323
+ }
324
+ if (posIdx < positionals.length) {
325
+ for (let j = posIdx; j < positionals.length; j++) {
326
+ errors.push({
327
+ type: "unexpected_positional",
328
+ value: positionals[j],
329
+ maxPositionals: argDefs.length
330
+ });
331
+ }
332
+ }
333
+ for (const [key, def] of Object.entries(options)) {
334
+ if (result[key] === void 0) {
335
+ if (def._kind === "flag") {
336
+ result[key] = def.default ?? false;
337
+ } else if (def._kind === "option") {
338
+ const opt = def;
339
+ if (opt.env && env?.[opt.env] !== void 0) {
340
+ const parsed = coerce(env[opt.env], opt.type, key, errors);
341
+ if (parsed !== void 0) {
342
+ result[key] = parsed;
343
+ }
344
+ }
345
+ if (result[key] === void 0) {
346
+ if (opt.required && opt.default === void 0) {
347
+ errors.push({ type: "missing_required", name: key, kind: "option" });
348
+ } else if (opt.default !== void 0) {
349
+ result[key] = opt.default;
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+ if (errors.length > 0) {
356
+ return { ok: false, errors };
357
+ }
358
+ return { ok: true, args: result, passthrough };
359
+ }
360
+ function coerce(raw, type, key, errors) {
361
+ switch (type) {
362
+ case "string":
363
+ return raw;
364
+ case "number": {
365
+ const n = Number(raw);
366
+ if (isNaN(n)) {
367
+ errors.push({ type: "invalid_type", name: key, expected: "number", received: raw });
368
+ return void 0;
369
+ }
370
+ return n;
371
+ }
372
+ case "boolean": {
373
+ if (raw === "true" || raw === "1") return true;
374
+ if (raw === "false" || raw === "0") return false;
375
+ errors.push({ type: "invalid_type", name: key, expected: "boolean", received: raw });
376
+ return void 0;
377
+ }
378
+ default:
379
+ return raw;
380
+ }
381
+ }
382
+ function camelToKebab(str) {
383
+ return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
384
+ }
385
+
386
+ // src/command/help.ts
387
+ function generateHelp(cmd) {
388
+ const lines = [];
389
+ const hasSubcommands = cmd.children.size > 0;
390
+ if (cmd.description) {
391
+ lines.push(`${cmd.fullPath} - ${cmd.description}`);
392
+ } else {
393
+ lines.push(cmd.fullPath);
394
+ }
395
+ lines.push("");
396
+ const usageParts = [cmd.fullPath];
397
+ if (hasSubcommands) {
398
+ usageParts.push("<command>");
399
+ }
400
+ const allOptions = { ...cmd.inheritedOptions, ...cmd.options };
401
+ if (Object.keys(allOptions).length > 0) {
402
+ usageParts.push("[options]");
403
+ }
404
+ const argDefs = cmd.args;
405
+ for (const argDef of argDefs) {
406
+ const argName = argDef.name ?? "arg";
407
+ const label = argDef.variadic ? `${argName}...` : argName;
408
+ usageParts.push(argDef.required ? `<${label}>` : `[${label}]`);
409
+ }
410
+ lines.push("Usage:");
411
+ lines.push(` ${usageParts.join(" ")}`);
412
+ lines.push("");
413
+ if (hasSubcommands) {
414
+ lines.push("Commands:");
415
+ const entries = [];
416
+ for (const [name, child] of cmd.children) {
417
+ entries.push([name, child.description || ""]);
418
+ }
419
+ const maxNameLen = Math.max(...entries.map(([name]) => name.length));
420
+ for (const [name, desc] of entries) {
421
+ const padding = " ".repeat(maxNameLen - name.length + 2);
422
+ lines.push(` ${name}${padding}${desc}`);
423
+ }
424
+ lines.push("");
425
+ }
426
+ if (argDefs.length > 0) {
427
+ lines.push("Arguments:");
428
+ const argRows = [];
429
+ for (const argDef of argDefs) {
430
+ const rawName = argDef.name ?? "arg";
431
+ const label = argDef.variadic ? `${rawName}...` : rawName;
432
+ const parts = [];
433
+ if (argDef.description) parts.push(argDef.description);
434
+ if (argDef.required) parts.push("(required)");
435
+ if (argDef.default !== void 0) {
436
+ parts.push(`(default: ${JSON.stringify(argDef.default)})`);
437
+ }
438
+ argRows.push([label, parts.join(" ")]);
439
+ }
440
+ const maxLen = Math.max(...argRows.map(([label]) => label.length));
441
+ for (const [label, desc] of argRows) {
442
+ const padding = " ".repeat(maxLen - label.length + 2);
443
+ lines.push(` ${label}${padding}${desc}`);
444
+ }
445
+ lines.push("");
446
+ }
447
+ const ownOptLines = formatOptionsTable(cmd.options);
448
+ if (ownOptLines.length > 0) {
449
+ lines.push("Options:");
450
+ lines.push(...ownOptLines);
451
+ lines.push("");
452
+ }
453
+ const inheritedOptLines = formatOptionsTable(cmd.inheritedOptions, "Inherited Options:");
454
+ if (inheritedOptLines.length > 0) {
455
+ lines.push(...inheritedOptLines);
456
+ lines.push("");
457
+ }
458
+ if (cmd.examples.length > 0) {
459
+ lines.push("Examples:");
460
+ for (const ex of cmd.examples) {
461
+ lines.push(` ${ex}`);
462
+ }
463
+ lines.push("");
464
+ }
465
+ return lines.join("\n");
466
+ }
467
+ function formatOptionsTable(schema, header) {
468
+ const entries = Object.entries(schema);
469
+ if (entries.length === 0) return [];
470
+ const rows = [];
471
+ for (const [key, def] of entries) {
472
+ const longName = camelToKebab(key);
473
+ if (def._kind === "flag") {
474
+ const flag = def;
475
+ const parts = [];
476
+ if (flag.short) parts.push(`-${flag.short},`);
477
+ parts.push(`--${longName}`);
478
+ const descParts = [];
479
+ if (flag.description) descParts.push(flag.description);
480
+ if (flag.default !== void 0) descParts.push(`(default: ${flag.default})`);
481
+ rows.push([parts.join(" "), descParts.join(" ")]);
482
+ } else {
483
+ const opt = def;
484
+ const parts = [];
485
+ if (opt.short) parts.push(`-${opt.short},`);
486
+ parts.push(`--${longName} <${opt.type}>`);
487
+ const descParts = [];
488
+ if (opt.description) descParts.push(opt.description);
489
+ if (opt.required) descParts.push("(required)");
490
+ if (opt.default !== void 0) descParts.push(`(default: ${JSON.stringify(opt.default)})`);
491
+ if (opt.env) descParts.push(`[env: ${opt.env}]`);
492
+ rows.push([parts.join(" "), descParts.join(" ")]);
493
+ }
494
+ }
495
+ const maxFlagLen = Math.max(...rows.map(([flag]) => flag.length));
496
+ const lines = [];
497
+ if (header) {
498
+ lines.push(header);
499
+ }
500
+ for (const [flag, desc] of rows) {
501
+ const padding = " ".repeat(maxFlagLen - flag.length + 2);
502
+ lines.push(` ${flag}${padding}${desc}`);
503
+ }
504
+ return lines;
505
+ }
506
+
507
+ // src/command/command.ts
508
+ function resolveOptionsInput(input) {
509
+ if (!input) return {};
510
+ const result = {};
511
+ for (const [key, builder] of Object.entries(input)) {
512
+ result[key] = builder._def;
513
+ }
514
+ return result;
515
+ }
516
+ function resolveArgsInput(input) {
517
+ if (!input) return [];
518
+ return input.map((builder) => builder._def);
519
+ }
520
+ var Command = class _Command {
521
+ name;
522
+ description;
523
+ options;
524
+ args;
525
+ examples;
526
+ omitInherited;
527
+ handler;
528
+ children = /* @__PURE__ */ new Map();
529
+ parent;
530
+ /** @internal — accumulated builder types for generic inference */
531
+ _accOpts;
532
+ /** @internal — args builder types for generic inference */
533
+ _accArgs;
534
+ /** @internal */
535
+ constructor(name, description, options, args, examples, omitInherited, handler, accOpts, accArgs) {
536
+ this.name = name;
537
+ this.description = description;
538
+ this.options = options;
539
+ this.args = args;
540
+ this.examples = examples;
541
+ this.omitInherited = omitInherited;
542
+ this.handler = handler;
543
+ this._accOpts = accOpts;
544
+ this._accArgs = accArgs;
545
+ }
546
+ // --------------------------------------------------------------------------
547
+ // Tree building
548
+ // --------------------------------------------------------------------------
549
+ /** Add a subcommand. Returns the child command for further nesting. */
550
+ command(name, config) {
551
+ const omitSet = new Set(config.omitInherited ?? []);
552
+ const parentAcc = { ...this._accOpts };
553
+ for (const key of omitSet) delete parentAcc[key];
554
+ const accOpts = { ...parentAcc, ...config.options ?? {} };
555
+ const child = new _Command(
556
+ name,
557
+ config.description,
558
+ resolveOptionsInput(config.options),
559
+ resolveArgsInput(config.args),
560
+ config.examples ?? [],
561
+ omitSet,
562
+ config.handler,
563
+ accOpts,
564
+ config.args ?? []
565
+ );
566
+ child.parent = this;
567
+ this.children.set(name, child);
568
+ return child;
569
+ }
570
+ // --------------------------------------------------------------------------
571
+ // Computed properties
572
+ // --------------------------------------------------------------------------
573
+ /** Full path from root (e.g. "mycli db migrate") */
574
+ get fullPath() {
575
+ const segments = [];
576
+ let current = this;
577
+ while (current) {
578
+ segments.unshift(current.name);
579
+ current = current.parent;
580
+ }
581
+ return segments.join(" ");
582
+ }
583
+ /**
584
+ * Return a plain `{ name, execute }` object compatible with just-bash's
585
+ * `CustomCommand` interface, with `execute` pre-bound to this command tree.
586
+ *
587
+ * @example
588
+ * ```ts
589
+ * const bash = new Bash({ customCommands: [mycli.toCommand()] });
590
+ * ```
591
+ */
592
+ toCommand() {
593
+ return { name: this.name, execute: this.execute.bind(this) };
594
+ }
595
+ /** Options inherited from ancestor commands */
596
+ get inheritedOptions() {
597
+ const inherited = {};
598
+ const chain = [];
599
+ let current = this;
600
+ while (current) {
601
+ chain.unshift(current);
602
+ current = current.parent;
603
+ }
604
+ for (const cmd of chain) {
605
+ for (const key of cmd.omitInherited) {
606
+ delete inherited[key];
607
+ }
608
+ if (cmd !== this) {
609
+ Object.assign(inherited, cmd.options);
610
+ }
611
+ }
612
+ return inherited;
613
+ }
614
+ /** All options available to this command (inherited + own) */
615
+ get allOptions() {
616
+ return { ...this.inheritedOptions, ...this.options };
617
+ }
618
+ // --------------------------------------------------------------------------
619
+ // Programmatic invocation
620
+ // --------------------------------------------------------------------------
621
+ /**
622
+ * Serialize a typed args object into CLI tokens.
623
+ *
624
+ * Produces tokens that, when parsed, reproduce the given args.
625
+ * Useful for building commands to pass to `execute()` or composing
626
+ * with `fullPath` for string-based execution.
627
+ *
628
+ * Only explicitly-provided values are serialized — omit a key to let
629
+ * the parser apply its default or env fallback as usual.
630
+ *
631
+ * @example
632
+ * ```ts
633
+ * const tokens = serve.toTokens({ port: 8080, entry: "app.ts" });
634
+ * await cli.execute(["serve", ...tokens], ctx);
635
+ * ```
636
+ */
637
+ toTokens(args) {
638
+ const tokens = [];
639
+ const allOpts = this.allOptions;
640
+ const input = args;
641
+ for (const [key, def] of Object.entries(allOpts)) {
642
+ const value = input[key];
643
+ const kebab = camelToKebab(key);
644
+ if (def._kind === "flag") {
645
+ if (value === true) {
646
+ tokens.push(`--${kebab}`);
647
+ } else if (value === false && def.default === true) {
648
+ tokens.push(`--no-${kebab}`);
649
+ }
650
+ } else if (def._kind === "option") {
651
+ if (value !== void 0) {
652
+ tokens.push(`--${kebab}`, String(value));
653
+ }
654
+ }
655
+ }
656
+ for (const argDef of this.args) {
657
+ const argName = argDef.name ?? "arg";
658
+ const value = input[argName];
659
+ if (value === void 0) continue;
660
+ if (argDef.variadic && Array.isArray(value)) {
661
+ for (const v of value) {
662
+ tokens.push(String(v));
663
+ }
664
+ } else {
665
+ tokens.push(String(value));
666
+ }
667
+ }
668
+ return tokens;
669
+ }
670
+ /**
671
+ * Call this command's handler directly with typed args.
672
+ *
673
+ * Required options (no default) and required positional args must be
674
+ * provided. Options with defaults, flags, and optional args can be
675
+ * omitted — invoke applies their defaults automatically.
676
+ *
677
+ * @example
678
+ * ```ts
679
+ * const result = await serve.invoke({ port: 8080, entry: "app.ts" }, ctx);
680
+ * ```
681
+ */
682
+ async invoke(args, ctx) {
683
+ if (!this.handler) {
684
+ return {
685
+ stdout: "",
686
+ stderr: `Command "${this.fullPath}" has no handler`,
687
+ exitCode: 1
688
+ };
689
+ }
690
+ const resolved = { ...args };
691
+ const allOpts = this.allOptions;
692
+ for (const [key, def] of Object.entries(allOpts)) {
693
+ if (resolved[key] === void 0) {
694
+ if (def._kind === "flag") {
695
+ resolved[key] = def.default ?? false;
696
+ } else if (def._kind === "option") {
697
+ if (def.default !== void 0) {
698
+ resolved[key] = def.default;
699
+ } else if (def.required) {
700
+ return {
701
+ stdout: "",
702
+ stderr: `Missing required option "${key}"`,
703
+ exitCode: 1
704
+ };
705
+ }
706
+ }
707
+ }
708
+ }
709
+ for (const argDef of this.args) {
710
+ const argName = argDef.name ?? "arg";
711
+ if (resolved[argName] === void 0) {
712
+ if (argDef.default !== void 0) {
713
+ resolved[argName] = argDef.default;
714
+ } else if (argDef.required) {
715
+ return {
716
+ stdout: "",
717
+ stderr: `Missing required arg "${argName}"`,
718
+ exitCode: 1
719
+ };
720
+ }
721
+ }
722
+ }
723
+ return this.handler(resolved, ctx, { passthrough: [] });
724
+ }
725
+ // --------------------------------------------------------------------------
726
+ // Execution
727
+ // --------------------------------------------------------------------------
728
+ /**
729
+ * Execute this command tree with the given tokens.
730
+ *
731
+ * Tokens flow through the tree: each level consumes the subcommand name
732
+ * and passes the rest deeper. When no subcommand matches, the current
733
+ * node either parses and runs its handler, or returns help/error.
734
+ */
735
+ async execute(tokens, ctx) {
736
+ const env = ctx?.env ? Object.fromEntries(ctx.env) : {};
737
+ const firstToken = tokens[0];
738
+ if (firstToken && !firstToken.startsWith("-") && this.children.has(firstToken)) {
739
+ return this.children.get(firstToken).execute(tokens.slice(1), ctx);
740
+ }
741
+ if (hasHelpFlag(tokens)) {
742
+ return { stdout: generateHelp(this), stderr: "", exitCode: 0 };
743
+ }
744
+ if (this.handler) {
745
+ const parsed = parseArgs(this.allOptions, this.args, [...tokens], env);
746
+ if (!parsed.ok) {
747
+ return { stdout: "", stderr: formatErrors(parsed.errors), exitCode: 1 };
748
+ }
749
+ return this.handler(parsed.args, ctx, { passthrough: parsed.passthrough });
750
+ }
751
+ if (firstToken && !firstToken.startsWith("-")) {
752
+ const suggestions = findSuggestions(firstToken, [...this.children.keys()]);
753
+ return {
754
+ stdout: "",
755
+ stderr: formatErrors([{
756
+ type: "unknown_command",
757
+ path: `${this.fullPath} ${firstToken}`,
758
+ suggestions
759
+ }]),
760
+ exitCode: 1
761
+ };
762
+ }
763
+ return { stdout: generateHelp(this), stderr: "", exitCode: 0 };
764
+ }
765
+ };
766
+ function command(name, config) {
767
+ return new Command(
768
+ name,
769
+ config.description,
770
+ resolveOptionsInput(config.options),
771
+ resolveArgsInput(config.args),
772
+ config.examples ?? [],
773
+ /* @__PURE__ */ new Set(),
774
+ config.handler,
775
+ config.options ?? {},
776
+ config.args ?? []
777
+ );
778
+ }
779
+ function hasHelpFlag(tokens) {
780
+ return tokens.some((t) => t === "--help" || t === "-h");
781
+ }
782
+
783
+ export {
784
+ o,
785
+ f,
786
+ a,
787
+ formatError,
788
+ formatErrors,
789
+ parseArgs,
790
+ generateHelp,
791
+ Command,
792
+ command
793
+ };