just-bash-util 0.1.5 → 0.1.6

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 CHANGED
@@ -63,7 +63,7 @@ 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
@@ -81,6 +81,16 @@ options: {
81
81
  }
82
82
  ```
83
83
 
84
+ Flags support counting mode via `.count()`. Repeated occurrences produce a number instead of a boolean — useful for verbosity levels (`-v`, `-vv`, `-vvv`):
85
+
86
+ ```ts
87
+ options: {
88
+ verbose: f().alias("v").count().describe("Verbosity level"),
89
+ }
90
+ // -v → 1, -vv → 2, -vvv → 3, absent → 0
91
+ // handler receives args.verbose as number
92
+ ```
93
+
84
94
  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
95
 
86
96
  ```ts
@@ -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
- result[entry.key] = true;
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
- result[entry.key] = true;
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 {
@@ -640,7 +656,9 @@ var Command = class _Command {
640
656
  const value = input[key];
641
657
  const kebab = camelToKebab(key);
642
658
  if (def._kind === "flag") {
643
- if (value === true) {
659
+ if (def.counted && typeof value === "number" && value > 0) {
660
+ for (let n = 0; n < value; n++) tokens.push(`--${kebab}`);
661
+ } else if (value === true) {
644
662
  tokens.push(`--${kebab}`);
645
663
  } else if (value === false && def.default === true) {
646
664
  tokens.push(`--no-${kebab}`);
@@ -690,7 +708,7 @@ var Command = class _Command {
690
708
  for (const [key, def] of Object.entries(allOpts)) {
691
709
  if (resolved[key] === void 0) {
692
710
  if (def._kind === "flag") {
693
- resolved[key] = def.default ?? false;
711
+ resolved[key] = def.default ?? (def.counted ? 0 : false);
694
712
  } else if (def._kind === "option") {
695
713
  if (def.default !== void 0) {
696
714
  resolved[key] = def.default;
@@ -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> = {
@@ -8,7 +8,7 @@ import {
8
8
  generateHelp,
9
9
  o,
10
10
  parseArgs
11
- } from "../chunk-RNQNKFXA.js";
11
+ } from "../chunk-4J5EECVQ.js";
12
12
  export {
13
13
  Command,
14
14
  a,
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  generateHelp,
9
9
  o,
10
10
  parseArgs
11
- } from "./chunk-RNQNKFXA.js";
11
+ } from "./chunk-4J5EECVQ.js";
12
12
  import {
13
13
  findUp,
14
14
  loadConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "just-bash-util",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "CLI command framework, config file discovery, and path utilities for just-bash",
5
5
  "type": "module",
6
6
  "license": "MIT",