just-bash-util 0.1.3 → 0.1.5

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
@@ -28,9 +28,9 @@ const cli = command("mycli", {
28
28
  const serve = cli.command("serve", {
29
29
  description: "Start the dev server",
30
30
  options: {
31
- port: o.number().default(3000).short("p").describe("Port to listen on"),
31
+ port: o.number().default(3000).alias("p").describe("Port to listen on"),
32
32
  host: o.string().describe("Host to bind to"),
33
- open: f().short("o").describe("Open browser"),
33
+ open: f().alias("o").describe("Open browser"),
34
34
  },
35
35
  args: [a.string().name("entry").describe("Entry file")],
36
36
  handler: (args, ctx) => {
@@ -64,11 +64,55 @@ await serve.invoke({ port: 8080, entry: "app.ts" }, ctx);
64
64
  - `omitInherited` to exclude parent options from specific subcommands
65
65
  - `--help` / `-h` auto-generated at every level
66
66
  - `--no-<flag>` negation, `-abc` combined short flags, `--key=value` syntax
67
- - `--` passthrough separator
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
70
  - Automatic error handling — thrown errors in handlers are caught and returned as clean `ExecResult` with `exitCode: 1`
71
71
 
72
+ #### Options and flags
73
+
74
+ Option keys are written in camelCase and automatically converted to kebab-case for the CLI:
75
+
76
+ ```ts
77
+ options: {
78
+ allowEmpty: f(), // CLI: --allow-empty handler: args.allowEmpty
79
+ dryRun: f().alias("n"), // CLI: --dry-run / -n handler: args.dryRun
80
+ message: o.string().alias("m"), // CLI: --message / -m handler: args.message
81
+ }
82
+ ```
83
+
84
+ 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
+
86
+ ```ts
87
+ // ✗ b: f() → creates --b (long flag), not -b
88
+ // ✓ branch: f().alias("b") → creates --branch and -b
89
+ ```
90
+
91
+ #### Positional args
92
+
93
+ Args are required by default. Use `.optional()` for optional args, and `.variadic()` to collect remaining positionals into an array. Chain `.optional().variadic()` for zero-or-more:
94
+
95
+ ```ts
96
+ args: [
97
+ a.string().name("entry"), // required single arg
98
+ a.string().name("file").optional(), // optional single arg
99
+ a.string().name("files").variadic(), // required: one or more
100
+ a.string().name("paths").optional().variadic(), // optional: zero or more → string[]
101
+ ]
102
+ ```
103
+
104
+ #### The `--` separator
105
+
106
+ The `--` token signals end-of-options. Tokens after `--` are treated as positional arguments (not parsed as flags) and are also available in `meta.passthrough`:
107
+
108
+ ```ts
109
+ // mycli checkout -- README.md
110
+ handler: (args, ctx, meta) => {
111
+ args.target; // "README.md" (assigned to positional arg)
112
+ meta.passthrough; // ["README.md"] (raw tokens after --)
113
+ }
114
+ ```
115
+
72
116
  ### `just-bash-util/config` — Config file discovery
73
117
 
74
118
  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.
@@ -142,6 +186,15 @@ parsePackageSpecifier("@vue/shared/dist"); // { name: "@vue/shared", subpath: ".
142
186
  parsePackageSpecifier("lodash/merge"); // { name: "lodash", subpath: "./merge" }
143
187
  ```
144
188
 
189
+ **`join` vs `resolve`** — `join` concatenates segments and normalizes; an absolute second argument is kept as-is (appended, not replacing). `resolve` processes right-to-left and stops at the first absolute path, like Node's `path.resolve` (but without prepending `cwd` when no absolute segment exists):
190
+
191
+ ```ts
192
+ join("/repo", "/file.txt"); // "/repo/file.txt" — concatenates with /
193
+ resolve("/repo", "/file.txt"); // "/file.txt" — absolute segment wins
194
+ resolve("/repo", "file.txt"); // "/repo/file.txt"
195
+ resolve("a", "b"); // "a/b" — stays relative (no cwd)
196
+ ```
197
+
145
198
  ## Peer dependencies
146
199
 
147
200
  Requires [`just-bash`](https://www.npmjs.com/package/just-bash) ^2.9.6 — provides the `CommandContext` and `ExecResult` types used throughout.
@@ -9,9 +9,9 @@ var OptionBuilder = class _OptionBuilder {
9
9
  describe(text) {
10
10
  return new _OptionBuilder({ ...this._def, description: text });
11
11
  }
12
- /** Set a short alias (single character) */
13
- short(alias) {
14
- return new _OptionBuilder({ ...this._def, short: alias });
12
+ /** Set a short alias (single character, e.g. "p" for -p) */
13
+ alias(short) {
14
+ return new _OptionBuilder({ ...this._def, short });
15
15
  }
16
16
  /** Set an environment variable fallback */
17
17
  env(name) {
@@ -56,9 +56,9 @@ var FlagBuilder = class _FlagBuilder {
56
56
  describe(text) {
57
57
  return new _FlagBuilder({ ...this._def, description: text });
58
58
  }
59
- /** Set a short alias (single character) */
60
- short(alias) {
61
- return new _FlagBuilder({ ...this._def, short: alias });
59
+ /** Set a short alias (single character, e.g. "v" for -v) */
60
+ alias(short) {
61
+ return new _FlagBuilder({ ...this._def, short });
62
62
  }
63
63
  /** Set a default value */
64
64
  default(value) {
@@ -91,7 +91,9 @@ var ArgBuilder = class _ArgBuilder {
91
91
  required: false
92
92
  });
93
93
  }
94
- /** Mark as variadic — collects all remaining positionals into an array */
94
+ /** Mark as variadic — collects all remaining positionals into an array.
95
+ * If the arg is already optional, element-level undefined is stripped
96
+ * (the optionality means "zero or more", not "elements can be undefined"). */
95
97
  variadic() {
96
98
  return new _ArgBuilder({
97
99
  ...this._def,
@@ -208,6 +210,7 @@ function parseArgs(options, argDefs, tokens, env) {
208
210
  if (token === "--") {
209
211
  i++;
210
212
  while (i < tokens.length) {
213
+ positionals.push(tokens[i]);
211
214
  passthrough.push(tokens[i]);
212
215
  i++;
213
216
  }
@@ -266,10 +269,14 @@ function parseArgs(options, argDefs, tokens, env) {
266
269
  const ch = chars[j];
267
270
  const entry = shortMap.get(ch);
268
271
  if (!entry) {
272
+ const suggestions = [];
273
+ if (longMap.has(ch)) {
274
+ suggestions.push(`--${ch}`);
275
+ }
269
276
  errors.push({
270
277
  type: "unknown_option",
271
278
  name: `-${ch}`,
272
- suggestions: []
279
+ suggestions
273
280
  });
274
281
  continue;
275
282
  }
@@ -307,6 +314,8 @@ function parseArgs(options, argDefs, tokens, env) {
307
314
  errors.push({ type: "missing_required", name: argName, kind: "arg" });
308
315
  } else if (argDef.default !== void 0) {
309
316
  result[argName] = argDef.default;
317
+ } else {
318
+ result[argName] = [];
310
319
  }
311
320
  posIdx = positionals.length;
312
321
  } else {
@@ -527,12 +536,8 @@ var Command = class _Command {
527
536
  handler;
528
537
  children = /* @__PURE__ */ new Map();
529
538
  parent;
530
- /** @internal — accumulated builder types for generic inference */
531
- _accOpts;
532
- /** @internal — args builder types for generic inference */
533
- _accArgs;
534
539
  /** @internal */
535
- constructor(name, description, options, args, examples, omitInherited, handler, accOpts, accArgs) {
540
+ constructor(name, description, options, args, examples, omitInherited, handler) {
536
541
  this.name = name;
537
542
  this.description = description;
538
543
  this.options = options;
@@ -540,8 +545,6 @@ var Command = class _Command {
540
545
  this.examples = examples;
541
546
  this.omitInherited = omitInherited;
542
547
  this.handler = handler;
543
- this._accOpts = accOpts;
544
- this._accArgs = accArgs;
545
548
  }
546
549
  // --------------------------------------------------------------------------
547
550
  // Tree building
@@ -549,9 +552,6 @@ var Command = class _Command {
549
552
  /** Add a subcommand. Returns the child command for further nesting. */
550
553
  command(name, config) {
551
554
  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
555
  const child = new _Command(
556
556
  name,
557
557
  config.description,
@@ -559,9 +559,7 @@ var Command = class _Command {
559
559
  resolveArgsInput(config.args),
560
560
  config.examples ?? [],
561
561
  omitSet,
562
- config.handler,
563
- accOpts,
564
- config.args ?? []
562
+ config.handler
565
563
  );
566
564
  child.parent = this;
567
565
  this.children.set(name, child);
@@ -781,9 +779,7 @@ function command(name, config) {
781
779
  resolveArgsInput(config.args),
782
780
  config.examples ?? [],
783
781
  /* @__PURE__ */ new Set(),
784
- config.handler,
785
- config.options ?? {},
786
- config.args ?? []
782
+ config.handler
787
783
  );
788
784
  }
789
785
  function hasHelpFlag(tokens) {
@@ -74,8 +74,8 @@ declare class OptionBuilder<TOut, THasDefault extends boolean = false> {
74
74
  constructor(def: OptionDef<TOut>);
75
75
  /** Add a description */
76
76
  describe(text: string): OptionBuilder<TOut, THasDefault>;
77
- /** Set a short alias (single character) */
78
- short(alias: string): OptionBuilder<TOut, THasDefault>;
77
+ /** Set a short alias (single character, e.g. "p" for -p) */
78
+ alias(short: string): OptionBuilder<TOut, THasDefault>;
79
79
  /** Set an environment variable fallback */
80
80
  env(name: string): OptionBuilder<TOut, THasDefault>;
81
81
  /** Mark as required — removes undefined from TOut */
@@ -94,8 +94,8 @@ declare class FlagBuilder {
94
94
  constructor(def?: FlagDef);
95
95
  /** Add a description */
96
96
  describe(text: string): FlagBuilder;
97
- /** Set a short alias (single character) */
98
- short(alias: string): FlagBuilder;
97
+ /** Set a short alias (single character, e.g. "v" for -v) */
98
+ alias(short: string): FlagBuilder;
99
99
  /** Set a default value */
100
100
  default(value: boolean): FlagBuilder;
101
101
  }
@@ -110,8 +110,10 @@ declare class ArgBuilder<TOut, TName extends string = never, THasDefault extends
110
110
  describe(text: string): ArgBuilder<TOut, TName, THasDefault>;
111
111
  /** Mark as optional — adds undefined to TOut */
112
112
  optional(): ArgBuilder<TOut | undefined, TName, THasDefault>;
113
- /** Mark as variadic — collects all remaining positionals into an array */
114
- variadic(): ArgBuilder<TOut[], TName, THasDefault>;
113
+ /** Mark as variadic — collects all remaining positionals into an array.
114
+ * If the arg is already optional, element-level undefined is stripped
115
+ * (the optionality means "zero or more", not "elements can be undefined"). */
116
+ variadic(): ArgBuilder<NonNullable<TOut>[], TName, THasDefault>;
115
117
  /** Set a default value (also makes the arg optional at parse time) */
116
118
  default(value: TOut): ArgBuilder<TOut, TName, true>;
117
119
  }
@@ -161,7 +163,7 @@ type InferInvokeArgs<T extends ArgsInput> = {
161
163
  } & {
162
164
  [I in keyof T & `${number}` as T[I] extends ArgBuilder<infer _V, infer N extends string, infer D> ? [D] extends [true] ? N : T[I] extends ArgBuilder<infer V, any, any> ? undefined extends V ? N : never : never : never]?: T[I] extends ArgBuilder<infer V, any, any> ? V : never;
163
165
  };
164
- declare class Command<TAccOpts extends OptionsInput = {}, TAccArgs extends ArgsInput = []> {
166
+ declare class Command<THandlerArgs extends object = {}, TInvokeArgs extends object = {}> {
165
167
  readonly name: string;
166
168
  readonly description: string;
167
169
  readonly options: OptionsSchema;
@@ -171,12 +173,12 @@ declare class Command<TAccOpts extends OptionsInput = {}, TAccArgs extends ArgsI
171
173
  readonly handler?: Handler<any>;
172
174
  readonly children: Map<string, Command<any, any>>;
173
175
  parent?: Command<any, any>;
174
- /** @internal — accumulated builder types for generic inference */
175
- readonly _accOpts: TAccOpts;
176
- /** @internal — args builder types for generic inference */
177
- readonly _accArgs: TAccArgs;
176
+ /** @internal — phantom type carrying the resolved handler args */
177
+ readonly _handlerArgs: THandlerArgs;
178
+ /** @internal — phantom type carrying the resolved invoke args */
179
+ readonly _invokeArgs: TInvokeArgs;
178
180
  /** @internal */
179
- constructor(name: string, description: string, options: OptionsSchema, args: ArgsSchema, examples: readonly string[], omitInherited: ReadonlySet<string>, handler: Handler<any> | undefined, accOpts: TAccOpts, accArgs: TAccArgs);
181
+ constructor(name: string, description: string, options: OptionsSchema, args: ArgsSchema, examples: readonly string[], omitInherited: ReadonlySet<string>, handler: Handler<any> | undefined);
180
182
  /** Add a subcommand. Returns the child command for further nesting. */
181
183
  command<TOpts extends OptionsInput = {}, const TArgs extends ArgsInput = [], const TOmit extends string[] = []>(name: string, config: {
182
184
  readonly description: string;
@@ -184,8 +186,8 @@ declare class Command<TAccOpts extends OptionsInput = {}, TAccArgs extends ArgsI
184
186
  readonly args?: TArgs;
185
187
  readonly examples?: readonly string[];
186
188
  readonly omitInherited?: TOmit;
187
- readonly handler?: Handler<Prettify<Omit<InferOptionsFromInput<TAccOpts>, TOmit[number]> & InferOptionsFromInput<TOpts> & InferArgsFromInput<TArgs>>>;
188
- }): Command<Omit<TAccOpts, TOmit[number]> & TOpts, TArgs>;
189
+ readonly handler?: Handler<Prettify<Omit<THandlerArgs, TOmit[number]> & InferOptionsFromInput<TOpts> & InferArgsFromInput<TArgs>>>;
190
+ }): Command<Prettify<Omit<THandlerArgs, TOmit[number]> & InferOptionsFromInput<TOpts> & InferArgsFromInput<TArgs>>, Prettify<Omit<TInvokeArgs, TOmit[number]> & InferInvokeOptions<TOpts> & InferInvokeArgs<TArgs>>>;
189
191
  /** Full path from root (e.g. "mycli db migrate") */
190
192
  get fullPath(): string;
191
193
  /**
@@ -221,7 +223,7 @@ declare class Command<TAccOpts extends OptionsInput = {}, TAccArgs extends ArgsI
221
223
  * await cli.execute(["serve", ...tokens], ctx);
222
224
  * ```
223
225
  */
224
- toTokens(args: Partial<Prettify<InferOptionsFromInput<TAccOpts> & InferArgsFromInput<TAccArgs>>>): string[];
226
+ toTokens(args: Partial<THandlerArgs>): string[];
225
227
  /**
226
228
  * Call this command's handler directly with typed args.
227
229
  *
@@ -234,7 +236,7 @@ declare class Command<TAccOpts extends OptionsInput = {}, TAccArgs extends ArgsI
234
236
  * const result = await serve.invoke({ port: 8080, entry: "app.ts" }, ctx);
235
237
  * ```
236
238
  */
237
- invoke(args: Prettify<InferInvokeOptions<TAccOpts> & InferInvokeArgs<TAccArgs>>, ctx: CommandContext): Promise<ExecResult>;
239
+ invoke(args: TInvokeArgs, ctx: CommandContext): Promise<ExecResult>;
238
240
  /**
239
241
  * Execute this command tree with the given tokens.
240
242
  *
@@ -251,7 +253,7 @@ declare function command<TOpts extends OptionsInput = {}, const TArgs extends Ar
251
253
  readonly args?: TArgs;
252
254
  readonly examples?: readonly string[];
253
255
  readonly handler?: Handler<Prettify<InferOptionsFromInput<TOpts> & InferArgsFromInput<TArgs>>>;
254
- }): Command<TOpts, TArgs>;
256
+ }): Command<Prettify<InferOptionsFromInput<TOpts> & InferArgsFromInput<TArgs>>, Prettify<InferInvokeOptions<TOpts> & InferInvokeArgs<TArgs>>>;
255
257
  /**
256
258
  * Infer the handler args type from a Command instance.
257
259
  *
@@ -270,7 +272,7 @@ declare function command<TOpts extends OptionsInput = {}, const TArgs extends Ar
270
272
  * // ^? { port: number; entry: string }
271
273
  * ```
272
274
  */
273
- type Infer<T extends Command<any, any>> = Prettify<InferOptionsFromInput<T["_accOpts"]> & InferArgsFromInput<T["_accArgs"]>>;
275
+ type Infer<T extends Command<any, any>> = T["_handlerArgs"];
274
276
 
275
277
  type ParseArgsResult = {
276
278
  ok: true;
@@ -8,7 +8,7 @@ import {
8
8
  generateHelp,
9
9
  o,
10
10
  parseArgs
11
- } from "../chunk-QKL3AOEA.js";
11
+ } from "../chunk-RNQNKFXA.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-QKL3AOEA.js";
11
+ } from "./chunk-RNQNKFXA.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.3",
3
+ "version": "0.1.5",
4
4
  "description": "CLI command framework, config file discovery, and path utilities for just-bash",
5
5
  "type": "module",
6
6
  "license": "MIT",