just-bash-util 0.1.5 → 0.1.7
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/README.md +33 -1
- package/dist/{chunk-RNQNKFXA.js → chunk-B6JJTTPV.js} +33 -10
- package/dist/command/index.d.ts +19 -10
- package/dist/command/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,10 +63,11 @@ await serve.invoke({ port: 8080, entry: "app.ts" }, ctx);
|
|
|
63
63
|
- Subcommand nesting with automatic option inheritance
|
|
64
64
|
- `omitInherited` to exclude parent options from specific subcommands
|
|
65
65
|
- `--help` / `-h` auto-generated at every level
|
|
66
|
-
- `--no-<flag>` negation, `-abc` combined short flags, `--key=value` syntax
|
|
66
|
+
- `--no-<flag>` negation, `-abc` combined short flags, `--key=value` syntax, counted flags (`-vvv` → 3)
|
|
67
67
|
- `--` end-of-options separator (remaining tokens become positional args and are available via `meta.passthrough`)
|
|
68
68
|
- Environment variable fallbacks for options
|
|
69
69
|
- Levenshtein-based "did you mean?" suggestions for typos
|
|
70
|
+
- `transformArgs` callback to rewrite tokens before parsing — support non-standard shorthand syntax like `-5` → `-n 5`
|
|
70
71
|
- Automatic error handling — thrown errors in handlers are caught and returned as clean `ExecResult` with `exitCode: 1`
|
|
71
72
|
|
|
72
73
|
#### Options and flags
|
|
@@ -81,6 +82,16 @@ options: {
|
|
|
81
82
|
}
|
|
82
83
|
```
|
|
83
84
|
|
|
85
|
+
Flags support counting mode via `.count()`. Repeated occurrences produce a number instead of a boolean — useful for verbosity levels (`-v`, `-vv`, `-vvv`):
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
options: {
|
|
89
|
+
verbose: f().alias("v").count().describe("Verbosity level"),
|
|
90
|
+
}
|
|
91
|
+
// -v → 1, -vv → 2, -vvv → 3, absent → 0
|
|
92
|
+
// handler receives args.verbose as number
|
|
93
|
+
```
|
|
94
|
+
|
|
84
95
|
Short flags (single-dash, single-character) require `.alias()`. A single-character key like `b: f()` creates the long flag `--b`, **not** the short flag `-b`. To get `-b`, use a descriptive key with an alias:
|
|
85
96
|
|
|
86
97
|
```ts
|
|
@@ -113,6 +124,27 @@ handler: (args, ctx, meta) => {
|
|
|
113
124
|
}
|
|
114
125
|
```
|
|
115
126
|
|
|
127
|
+
#### Token rewriting with `transformArgs`
|
|
128
|
+
|
|
129
|
+
Commands can define a `transformArgs` callback to rewrite the raw token array before it reaches the parser. This is useful for supporting non-standard shorthand syntax that the parser wouldn't otherwise understand:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
cli.command("log", {
|
|
133
|
+
description: "Show commit log",
|
|
134
|
+
transformArgs: (tokens) =>
|
|
135
|
+
tokens.map((t) => (/^-(\d+)$/.test(t) ? `-n${t.slice(1)}` : t)),
|
|
136
|
+
options: {
|
|
137
|
+
maxCount: o.number().alias("n").describe("Limit output to n commits"),
|
|
138
|
+
},
|
|
139
|
+
handler: (args) => {
|
|
140
|
+
// git log -5 → tokens rewritten to -n5 → args.maxCount === 5
|
|
141
|
+
return { stdout: `showing ${args.maxCount} commits`, stderr: "", exitCode: 0 };
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The callback receives a mutable copy of the tokens and returns the rewritten array. It runs only in `execute()` (token-based invocation) — `invoke()` takes typed args directly, so there's nothing to transform.
|
|
147
|
+
|
|
116
148
|
### `just-bash-util/config` — Config file discovery
|
|
117
149
|
|
|
118
150
|
Cosmiconfig-style config search that walks up the directory tree, trying conventional filenames at each level. Comments and trailing commas are supported out of the box.
|
|
@@ -64,6 +64,13 @@ var FlagBuilder = class _FlagBuilder {
|
|
|
64
64
|
default(value) {
|
|
65
65
|
return new _FlagBuilder({ ...this._def, default: value });
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Enable counting mode. Repeated occurrences (-v -v or -vv) produce a
|
|
69
|
+
* number instead of a boolean: 0 (absent), 1, 2, 3, etc.
|
|
70
|
+
*/
|
|
71
|
+
count() {
|
|
72
|
+
return new _FlagBuilder({ ...this._def, counted: true });
|
|
73
|
+
}
|
|
67
74
|
};
|
|
68
75
|
|
|
69
76
|
// src/command/builders/arg.ts
|
|
@@ -231,7 +238,7 @@ function parseArgs(options, argDefs, tokens, env) {
|
|
|
231
238
|
if (longName.startsWith("no-")) {
|
|
232
239
|
const positiveEntry = longMap.get(longName.slice(3));
|
|
233
240
|
if (positiveEntry && positiveEntry.def._kind === "flag") {
|
|
234
|
-
result[positiveEntry.key] = false;
|
|
241
|
+
result[positiveEntry.key] = positiveEntry.def.counted ? 0 : false;
|
|
235
242
|
i++;
|
|
236
243
|
continue;
|
|
237
244
|
}
|
|
@@ -246,7 +253,11 @@ function parseArgs(options, argDefs, tokens, env) {
|
|
|
246
253
|
continue;
|
|
247
254
|
}
|
|
248
255
|
if (entry.def._kind === "flag") {
|
|
249
|
-
|
|
256
|
+
if (entry.def.counted) {
|
|
257
|
+
result[entry.key] = (result[entry.key] || 0) + 1;
|
|
258
|
+
} else {
|
|
259
|
+
result[entry.key] = true;
|
|
260
|
+
}
|
|
250
261
|
i++;
|
|
251
262
|
continue;
|
|
252
263
|
}
|
|
@@ -281,7 +292,11 @@ function parseArgs(options, argDefs, tokens, env) {
|
|
|
281
292
|
continue;
|
|
282
293
|
}
|
|
283
294
|
if (entry.def._kind === "flag") {
|
|
284
|
-
|
|
295
|
+
if (entry.def.counted) {
|
|
296
|
+
result[entry.key] = (result[entry.key] || 0) + 1;
|
|
297
|
+
} else {
|
|
298
|
+
result[entry.key] = true;
|
|
299
|
+
}
|
|
285
300
|
continue;
|
|
286
301
|
}
|
|
287
302
|
const restOfString = chars.slice(j + 1);
|
|
@@ -342,7 +357,7 @@ function parseArgs(options, argDefs, tokens, env) {
|
|
|
342
357
|
for (const [key, def] of Object.entries(options)) {
|
|
343
358
|
if (result[key] === void 0) {
|
|
344
359
|
if (def._kind === "flag") {
|
|
345
|
-
result[key] = def.default ?? false;
|
|
360
|
+
result[key] = def.default ?? (def.counted ? 0 : false);
|
|
346
361
|
} else if (def._kind === "option") {
|
|
347
362
|
const opt = def;
|
|
348
363
|
if (opt.env && env?.[opt.env] !== void 0) {
|
|
@@ -486,6 +501,7 @@ function formatOptionsTable(schema, header) {
|
|
|
486
501
|
parts.push(`--${longName}`);
|
|
487
502
|
const descParts = [];
|
|
488
503
|
if (flag.description) descParts.push(flag.description);
|
|
504
|
+
if (flag.counted) descParts.push("(counted)");
|
|
489
505
|
if (flag.default !== void 0) descParts.push(`(default: ${flag.default})`);
|
|
490
506
|
rows.push([parts.join(" "), descParts.join(" ")]);
|
|
491
507
|
} else {
|
|
@@ -534,10 +550,11 @@ var Command = class _Command {
|
|
|
534
550
|
examples;
|
|
535
551
|
omitInherited;
|
|
536
552
|
handler;
|
|
553
|
+
transformArgs;
|
|
537
554
|
children = /* @__PURE__ */ new Map();
|
|
538
555
|
parent;
|
|
539
556
|
/** @internal */
|
|
540
|
-
constructor(name, description, options, args, examples, omitInherited, handler) {
|
|
557
|
+
constructor(name, description, options, args, examples, omitInherited, handler, transformArgs) {
|
|
541
558
|
this.name = name;
|
|
542
559
|
this.description = description;
|
|
543
560
|
this.options = options;
|
|
@@ -545,6 +562,7 @@ var Command = class _Command {
|
|
|
545
562
|
this.examples = examples;
|
|
546
563
|
this.omitInherited = omitInherited;
|
|
547
564
|
this.handler = handler;
|
|
565
|
+
this.transformArgs = transformArgs;
|
|
548
566
|
}
|
|
549
567
|
// --------------------------------------------------------------------------
|
|
550
568
|
// Tree building
|
|
@@ -559,7 +577,8 @@ var Command = class _Command {
|
|
|
559
577
|
resolveArgsInput(config.args),
|
|
560
578
|
config.examples ?? [],
|
|
561
579
|
omitSet,
|
|
562
|
-
config.handler
|
|
580
|
+
config.handler,
|
|
581
|
+
config.transformArgs
|
|
563
582
|
);
|
|
564
583
|
child.parent = this;
|
|
565
584
|
this.children.set(name, child);
|
|
@@ -640,7 +659,9 @@ var Command = class _Command {
|
|
|
640
659
|
const value = input[key];
|
|
641
660
|
const kebab = camelToKebab(key);
|
|
642
661
|
if (def._kind === "flag") {
|
|
643
|
-
if (value ===
|
|
662
|
+
if (def.counted && typeof value === "number" && value > 0) {
|
|
663
|
+
for (let n = 0; n < value; n++) tokens.push(`--${kebab}`);
|
|
664
|
+
} else if (value === true) {
|
|
644
665
|
tokens.push(`--${kebab}`);
|
|
645
666
|
} else if (value === false && def.default === true) {
|
|
646
667
|
tokens.push(`--no-${kebab}`);
|
|
@@ -690,7 +711,7 @@ var Command = class _Command {
|
|
|
690
711
|
for (const [key, def] of Object.entries(allOpts)) {
|
|
691
712
|
if (resolved[key] === void 0) {
|
|
692
713
|
if (def._kind === "flag") {
|
|
693
|
-
resolved[key] = def.default ?? false;
|
|
714
|
+
resolved[key] = def.default ?? (def.counted ? 0 : false);
|
|
694
715
|
} else if (def._kind === "option") {
|
|
695
716
|
if (def.default !== void 0) {
|
|
696
717
|
resolved[key] = def.default;
|
|
@@ -745,7 +766,8 @@ var Command = class _Command {
|
|
|
745
766
|
return { stdout: generateHelp(this), stderr: "", exitCode: 0 };
|
|
746
767
|
}
|
|
747
768
|
if (this.handler) {
|
|
748
|
-
const
|
|
769
|
+
const effective = this.transformArgs ? this.transformArgs([...tokens]) : [...tokens];
|
|
770
|
+
const parsed = parseArgs(this.allOptions, this.args, effective, env);
|
|
749
771
|
if (!parsed.ok) {
|
|
750
772
|
return { stdout: "", stderr: formatErrors(parsed.errors), exitCode: 1 };
|
|
751
773
|
}
|
|
@@ -779,7 +801,8 @@ function command(name, config) {
|
|
|
779
801
|
resolveArgsInput(config.args),
|
|
780
802
|
config.examples ?? [],
|
|
781
803
|
/* @__PURE__ */ new Set(),
|
|
782
|
-
config.handler
|
|
804
|
+
config.handler,
|
|
805
|
+
config.transformArgs
|
|
783
806
|
);
|
|
784
807
|
}
|
|
785
808
|
function hasHelpFlag(tokens) {
|
package/dist/command/index.d.ts
CHANGED
|
@@ -21,7 +21,8 @@ interface FlagDef {
|
|
|
21
21
|
readonly _kind: "flag";
|
|
22
22
|
readonly description?: string;
|
|
23
23
|
readonly short?: string;
|
|
24
|
-
readonly default?: boolean;
|
|
24
|
+
readonly default?: boolean | number;
|
|
25
|
+
readonly counted?: boolean;
|
|
25
26
|
}
|
|
26
27
|
interface ArgDef<TOut = unknown> {
|
|
27
28
|
readonly _kind: "arg";
|
|
@@ -88,16 +89,21 @@ declare function string$1(): OptionBuilder<string | undefined>;
|
|
|
88
89
|
/** Create a number option (optional by default) */
|
|
89
90
|
declare function number$1(): OptionBuilder<number | undefined>;
|
|
90
91
|
|
|
91
|
-
declare class FlagBuilder {
|
|
92
|
+
declare class FlagBuilder<TCounted extends boolean = false> {
|
|
92
93
|
/** @internal */
|
|
93
94
|
readonly _def: FlagDef;
|
|
94
95
|
constructor(def?: FlagDef);
|
|
95
96
|
/** Add a description */
|
|
96
|
-
describe(text: string): FlagBuilder
|
|
97
|
+
describe(text: string): FlagBuilder<TCounted>;
|
|
97
98
|
/** Set a short alias (single character, e.g. "v" for -v) */
|
|
98
|
-
alias(short: string): FlagBuilder
|
|
99
|
+
alias(short: string): FlagBuilder<TCounted>;
|
|
99
100
|
/** Set a default value */
|
|
100
|
-
default(value: boolean): FlagBuilder
|
|
101
|
+
default(value: TCounted extends true ? number : boolean): FlagBuilder<TCounted>;
|
|
102
|
+
/**
|
|
103
|
+
* Enable counting mode. Repeated occurrences (-v -v or -vv) produce a
|
|
104
|
+
* number instead of a boolean: 0 (absent), 1, 2, 3, etc.
|
|
105
|
+
*/
|
|
106
|
+
count(): FlagBuilder<true>;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
declare class ArgBuilder<TOut, TName extends string = never, THasDefault extends boolean = false> {
|
|
@@ -140,12 +146,12 @@ type Prettify<T> = {
|
|
|
140
146
|
[K in keyof T as string extends K ? never : K]: T[K];
|
|
141
147
|
} & {};
|
|
142
148
|
/** Builder input types — what the user passes in config */
|
|
143
|
-
type OptionInput = OptionBuilder<any, any> | FlagBuilder
|
|
149
|
+
type OptionInput = OptionBuilder<any, any> | FlagBuilder<any>;
|
|
144
150
|
type OptionsInput = Record<string, OptionInput>;
|
|
145
151
|
type ArgsInput = readonly ArgBuilder<any, any, any>[];
|
|
146
152
|
/** Infer the value types from option builder instances (handler signature) */
|
|
147
153
|
type InferOptionsFromInput<T extends OptionsInput> = {
|
|
148
|
-
[K in keyof T]: T[K] extends OptionBuilder<infer V, any> ? V : T[K] extends FlagBuilder ? boolean : never;
|
|
154
|
+
[K in keyof T]: T[K] extends OptionBuilder<infer V, any> ? V : T[K] extends FlagBuilder<true> ? number : T[K] extends FlagBuilder ? boolean : never;
|
|
149
155
|
};
|
|
150
156
|
/** Infer positional arg types from arg builder instances (handler signature) */
|
|
151
157
|
type InferArgsFromInput<T extends ArgsInput> = {
|
|
@@ -153,9 +159,9 @@ type InferArgsFromInput<T extends ArgsInput> = {
|
|
|
153
159
|
};
|
|
154
160
|
/** Infer invoke() options: required options mandatory, defaulted/optional/flags optional */
|
|
155
161
|
type InferInvokeOptions<T extends OptionsInput> = {
|
|
156
|
-
[K in keyof T as T[K] extends FlagBuilder ? never : T[K] extends OptionBuilder<infer V, infer D> ? [D] extends [true] ? never : undefined extends V ? never : K : never]: T[K] extends OptionBuilder<infer V, any> ? V : never;
|
|
162
|
+
[K in keyof T as T[K] extends FlagBuilder<any> ? never : T[K] extends OptionBuilder<infer V, infer D> ? [D] extends [true] ? never : undefined extends V ? never : K : never]: T[K] extends OptionBuilder<infer V, any> ? V : never;
|
|
157
163
|
} & {
|
|
158
|
-
[K in keyof T as T[K] extends FlagBuilder ? K : T[K] extends OptionBuilder<infer V, infer D> ? [D] extends [true] ? K : undefined extends V ? K : never : never]?: T[K] extends OptionBuilder<infer V, any> ? V : T[K] extends FlagBuilder ? boolean : never;
|
|
164
|
+
[K in keyof T as T[K] extends FlagBuilder<any> ? K : T[K] extends OptionBuilder<infer V, infer D> ? [D] extends [true] ? K : undefined extends V ? K : never : never]?: T[K] extends OptionBuilder<infer V, any> ? V : T[K] extends FlagBuilder<true> ? number : T[K] extends FlagBuilder ? boolean : never;
|
|
159
165
|
};
|
|
160
166
|
/** Infer invoke() args: required args mandatory, defaulted/optional args optional */
|
|
161
167
|
type InferInvokeArgs<T extends ArgsInput> = {
|
|
@@ -171,6 +177,7 @@ declare class Command<THandlerArgs extends object = {}, TInvokeArgs extends obje
|
|
|
171
177
|
readonly examples: readonly string[];
|
|
172
178
|
readonly omitInherited: ReadonlySet<string>;
|
|
173
179
|
readonly handler?: Handler<any>;
|
|
180
|
+
readonly transformArgs?: (tokens: string[]) => string[];
|
|
174
181
|
readonly children: Map<string, Command<any, any>>;
|
|
175
182
|
parent?: Command<any, any>;
|
|
176
183
|
/** @internal — phantom type carrying the resolved handler args */
|
|
@@ -178,7 +185,7 @@ declare class Command<THandlerArgs extends object = {}, TInvokeArgs extends obje
|
|
|
178
185
|
/** @internal — phantom type carrying the resolved invoke args */
|
|
179
186
|
readonly _invokeArgs: TInvokeArgs;
|
|
180
187
|
/** @internal */
|
|
181
|
-
constructor(name: string, description: string, options: OptionsSchema, args: ArgsSchema, examples: readonly string[], omitInherited: ReadonlySet<string>, handler: Handler<any> | undefined);
|
|
188
|
+
constructor(name: string, description: string, options: OptionsSchema, args: ArgsSchema, examples: readonly string[], omitInherited: ReadonlySet<string>, handler: Handler<any> | undefined, transformArgs?: (tokens: string[]) => string[]);
|
|
182
189
|
/** Add a subcommand. Returns the child command for further nesting. */
|
|
183
190
|
command<TOpts extends OptionsInput = {}, const TArgs extends ArgsInput = [], const TOmit extends string[] = []>(name: string, config: {
|
|
184
191
|
readonly description: string;
|
|
@@ -186,6 +193,7 @@ declare class Command<THandlerArgs extends object = {}, TInvokeArgs extends obje
|
|
|
186
193
|
readonly args?: TArgs;
|
|
187
194
|
readonly examples?: readonly string[];
|
|
188
195
|
readonly omitInherited?: TOmit;
|
|
196
|
+
readonly transformArgs?: (tokens: string[]) => string[];
|
|
189
197
|
readonly handler?: Handler<Prettify<Omit<THandlerArgs, TOmit[number]> & InferOptionsFromInput<TOpts> & InferArgsFromInput<TArgs>>>;
|
|
190
198
|
}): Command<Prettify<Omit<THandlerArgs, TOmit[number]> & InferOptionsFromInput<TOpts> & InferArgsFromInput<TArgs>>, Prettify<Omit<TInvokeArgs, TOmit[number]> & InferInvokeOptions<TOpts> & InferInvokeArgs<TArgs>>>;
|
|
191
199
|
/** Full path from root (e.g. "mycli db migrate") */
|
|
@@ -252,6 +260,7 @@ declare function command<TOpts extends OptionsInput = {}, const TArgs extends Ar
|
|
|
252
260
|
readonly options?: TOpts;
|
|
253
261
|
readonly args?: TArgs;
|
|
254
262
|
readonly examples?: readonly string[];
|
|
263
|
+
readonly transformArgs?: (tokens: string[]) => string[];
|
|
255
264
|
readonly handler?: Handler<Prettify<InferOptionsFromInput<TOpts> & InferArgsFromInput<TArgs>>>;
|
|
256
265
|
}): Command<Prettify<InferOptionsFromInput<TOpts> & InferArgsFromInput<TArgs>>, Prettify<InferInvokeOptions<TOpts> & InferInvokeArgs<TArgs>>>;
|
|
257
266
|
/**
|
package/dist/command/index.js
CHANGED
package/dist/index.js
CHANGED