@rune-cli/rune 0.0.9 → 0.0.11

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.
@@ -117,21 +117,21 @@ interface SchemaFieldBase<TName extends string, TSchema extends StandardSchemaV1
117
117
  readonly default?: never;
118
118
  }
119
119
  interface PrimitiveArgField<TName extends string = string, TType extends PrimitiveFieldType = PrimitiveFieldType> extends PrimitiveFieldBase<TName, TType> {
120
- readonly alias?: never;
120
+ readonly short?: never;
121
121
  readonly flag?: never;
122
122
  }
123
123
  interface SchemaArgField<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends SchemaFieldBase<TName, TSchema> {
124
- readonly alias?: never;
124
+ readonly short?: never;
125
125
  readonly flag?: never;
126
126
  }
127
127
  interface PrimitiveOptionField<TName extends string = string, TType extends PrimitiveFieldType = PrimitiveFieldType> extends PrimitiveFieldBase<TName, TType> {
128
128
  /** Single-character shorthand (e.g. `"v"` for `--verbose` → `-v`). */
129
- readonly alias?: string | undefined;
129
+ readonly short?: string | undefined;
130
130
  readonly flag?: never;
131
131
  }
132
132
  interface SchemaOptionField<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends SchemaFieldBase<TName, TSchema> {
133
133
  /** Single-character shorthand (e.g. `"v"` for `--verbose` → `-v`). */
134
- readonly alias?: string | undefined;
134
+ readonly short?: string | undefined;
135
135
  /**
136
136
  * When `true`, the option is parsed as a boolean flag (no value expected).
137
137
  * The schema receives `true` when the flag is present, `undefined` when absent.
@@ -201,6 +201,12 @@ interface CommandContext<TOptions, TArgs> {
201
201
  interface DefineCommandInput<TArgsFields extends readonly CommandArgField[] | undefined = undefined, TOptionsFields extends readonly CommandOptionField[] | undefined = undefined> {
202
202
  /** One-line summary shown in `--help` output. */
203
203
  readonly description?: string | undefined;
204
+ /**
205
+ * Alternative names for this command. Each alias is an additional path
206
+ * segment that routes to this command. Aliases must follow kebab-case
207
+ * rules (lowercase letters, digits, and internal hyphens).
208
+ */
209
+ readonly aliases?: readonly string[] | undefined;
204
210
  /**
205
211
  * Positional arguments declared in the order they appear on the command line.
206
212
  * Required arguments must come before optional ones.
@@ -211,7 +217,7 @@ interface DefineCommandInput<TArgsFields extends readonly CommandArgField[] | un
211
217
  */
212
218
  readonly args?: TArgsFields;
213
219
  /**
214
- * Options declared as `--name` flags, with optional single-character aliases.
220
+ * Options declared as `--name` flags, with optional single-character short forms.
215
221
  * Option names must be unique within the command, start with a letter, and
216
222
  * contain only letters, numbers, and internal hyphens.
217
223
  *
@@ -227,6 +233,7 @@ interface DefineCommandInput<TArgsFields extends readonly CommandArgField[] | un
227
233
  }
228
234
  interface DefinedCommand<TArgsFields extends readonly CommandArgField[] = readonly [], TOptionsFields extends readonly CommandOptionField[] = readonly []> {
229
235
  readonly description?: string | undefined;
236
+ readonly aliases: readonly string[];
230
237
  readonly args: TArgsFields;
231
238
  readonly options: TOptionsFields;
232
239
  readonly run: (ctx: CommandContext<InferNamedFields<TOptionsFields, true>, InferNamedFields<TArgsFields>>) => void | Promise<void>;
@@ -246,7 +253,7 @@ interface DefinedCommand<TArgsFields extends readonly CommandArgField[] = readon
246
253
  * { name: "name", type: "string", required: true },
247
254
  * ],
248
255
  * options: [
249
- * { name: "loud", type: "boolean", alias: "l" },
256
+ * { name: "loud", type: "boolean", short: "l" },
250
257
  * ],
251
258
  * run(ctx) {
252
259
  * const greeting = `Hello, ${ctx.args.name}!`;
@@ -285,6 +292,40 @@ interface DefinedCommand<TArgsFields extends readonly CommandArgField[] = readon
285
292
  */
286
293
  declare function defineCommand<const TArgsFields extends readonly CommandArgField[] | undefined = undefined, const TOptionsFields extends readonly CommandOptionField[] | undefined = undefined>(input: DefineCommandInput<TArgsFields, TOptionsFields> & ValidateArgOrder<TArgsFields>): DefinedCommand<NormalizeFields<TArgsFields, CommandArgField>, NormalizeFields<TOptionsFields, CommandOptionField>>;
287
294
  //#endregion
295
+ //#region src/define-group.d.ts
296
+ /** The group definition object accepted by {@link defineGroup}. */
297
+ interface DefineGroupInput {
298
+ /** One-line summary shown in `--help` output. */
299
+ readonly description: string;
300
+ /**
301
+ * Alternative names for this command group. Each alias is an additional path
302
+ * segment that routes to this group. Aliases must follow kebab-case rules
303
+ * (lowercase letters, digits, and internal hyphens).
304
+ */
305
+ readonly aliases?: readonly string[] | undefined;
306
+ }
307
+ /** The normalized group object returned by `defineGroup`. */
308
+ interface DefinedGroup {
309
+ readonly description: string;
310
+ readonly aliases: readonly string[];
311
+ }
312
+ /**
313
+ * Defines metadata for a command group (a directory that only groups
314
+ * subcommands without being executable itself).
315
+ *
316
+ * Place the default export of this function in a `_group.ts` file inside a
317
+ * command directory.
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * // src/commands/project/_group.ts
322
+ * export default defineGroup({
323
+ * description: "Manage projects",
324
+ * });
325
+ * ```
326
+ */
327
+ declare function defineGroup(input: DefineGroupInput): DefinedGroup;
328
+ //#endregion
288
329
  //#region src/execute-command.d.ts
289
330
  interface ExecuteCommandInput<TOptions, TArgs> {
290
331
  readonly options?: TOptions | undefined;
@@ -293,4 +334,4 @@ interface ExecuteCommandInput<TOptions, TArgs> {
293
334
  readonly rawArgs?: readonly string[] | undefined;
294
335
  }
295
336
  //#endregion
296
- export { ExecuteCommandInput as a, PrimitiveFieldType as c, SchemaOptionField as d, defineCommand as f, DefinedCommand as i, PrimitiveOptionField as l, CommandContext as n, InferExecutionFields as o, CommandOptionField as r, PrimitiveArgField as s, CommandArgField as t, SchemaArgField as u };
337
+ export { DefinedCommand as a, InferExecutionFields as c, PrimitiveOptionField as d, SchemaArgField as f, defineGroup as h, DefineGroupInput as i, PrimitiveArgField as l, defineCommand as m, CommandContext as n, DefinedGroup as o, SchemaOptionField as p, CommandOptionField as r, ExecuteCommandInput as s, CommandArgField as t, PrimitiveFieldType as u };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ExecuteCommandInput, c as PrimitiveFieldType, d as SchemaOptionField, f as defineCommand, i as DefinedCommand, l as PrimitiveOptionField, n as CommandContext, o as InferExecutionFields, r as CommandOptionField, s as PrimitiveArgField, t as CommandArgField, u as SchemaArgField } from "./index-BWxfSwrT.mjs";
2
- export { type CommandArgField, type CommandContext, type CommandOptionField, type DefinedCommand, type ExecuteCommandInput, type InferExecutionFields, type PrimitiveArgField, type PrimitiveFieldType, type PrimitiveOptionField, type SchemaArgField, type SchemaOptionField, defineCommand };
1
+ import { a as DefinedCommand, c as InferExecutionFields, d as PrimitiveOptionField, f as SchemaArgField, h as defineGroup, i as DefineGroupInput, l as PrimitiveArgField, m as defineCommand, n as CommandContext, o as DefinedGroup, p as SchemaOptionField, r as CommandOptionField, s as ExecuteCommandInput, t as CommandArgField, u as PrimitiveFieldType } from "./index-C179V2IJ.mjs";
2
+ export { type CommandArgField, type CommandContext, type CommandOptionField, type DefineGroupInput, type DefinedCommand, type DefinedGroup, type ExecuteCommandInput, type InferExecutionFields, type PrimitiveArgField, type PrimitiveFieldType, type PrimitiveOptionField, type SchemaArgField, type SchemaOptionField, defineCommand, defineGroup };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as defineCommand } from "./dist-DuisScgY.mjs";
2
- export { defineCommand };
1
+ import { n as defineGroup, t as defineCommand } from "./dist-Bpf2xVvb.mjs";
2
+ export { defineCommand, defineGroup };
@@ -1,4 +1,4 @@
1
- import { a as isSchemaField, i as isDefinedCommand, o as parseCommand, r as executeCommand } from "./dist-DuisScgY.mjs";
1
+ import { a as isSchemaField, i as isDefinedCommand, o as parseCommandArgs, r as executeCommand } from "./dist-Bpf2xVvb.mjs";
2
2
  import { pathToFileURL } from "node:url";
3
3
  //#region src/cli/flags.ts
4
4
  function isHelpFlag(token) {
@@ -8,7 +8,7 @@ function isVersionFlag(token) {
8
8
  return token === "--version" || token === "-V";
9
9
  }
10
10
  //#endregion
11
- //#region src/manifest/command-loader.ts
11
+ //#region src/manifest/runtime/command-loader.ts
12
12
  async function loadCommandFromModule(sourceFilePath) {
13
13
  const loadedModule = await import(pathToFileURL(sourceFilePath).href);
14
14
  if (loadedModule.default === void 0) throw new Error(`Command module did not export a default command: ${sourceFilePath}`);
@@ -29,7 +29,23 @@ function describeCommandModuleExport(value) {
29
29
  }
30
30
  const defaultLoadCommand = (node) => loadCommandFromModule(node.sourceFilePath);
31
31
  //#endregion
32
- //#region src/manifest/damerau-levenshtein.ts
32
+ //#region src/manifest/manifest-map.ts
33
+ function commandManifestPathToKey(pathSegments) {
34
+ return pathSegments.join(" ");
35
+ }
36
+ function createCommandManifestNodeMap(manifest) {
37
+ const entries = [];
38
+ for (const node of manifest.nodes) {
39
+ entries.push([commandManifestPathToKey(node.pathSegments), node]);
40
+ if (node.aliases.length > 0 && node.pathSegments.length > 0) {
41
+ const parentSegments = node.pathSegments.slice(0, -1);
42
+ for (const alias of node.aliases) entries.push([commandManifestPathToKey([...parentSegments, alias]), node]);
43
+ }
44
+ }
45
+ return Object.fromEntries(entries);
46
+ }
47
+ //#endregion
48
+ //#region src/manifest/runtime/damerau-levenshtein.ts
33
49
  function damerauLevenshteinDistance(left, right) {
34
50
  const rows = left.length + 1;
35
51
  const cols = right.length + 1;
@@ -44,15 +60,7 @@ function damerauLevenshteinDistance(left, right) {
44
60
  return matrix[left.length][right.length];
45
61
  }
46
62
  //#endregion
47
- //#region src/manifest/manifest-map.ts
48
- function commandManifestPathToKey(pathSegments) {
49
- return pathSegments.join(" ");
50
- }
51
- function createCommandManifestNodeMap(manifest) {
52
- return Object.fromEntries(manifest.nodes.map((node) => [commandManifestPathToKey(node.pathSegments), node]));
53
- }
54
- //#endregion
55
- //#region src/manifest/resolve-command-path.ts
63
+ //#region src/manifest/runtime/resolve-command-route.ts
56
64
  function isOptionLikeToken(token) {
57
65
  return token === "--" || token.startsWith("-");
58
66
  }
@@ -62,13 +70,35 @@ function getHelpRequested(args) {
62
70
  function getSuggestionThreshold(candidate) {
63
71
  return Math.max(2, Math.floor(candidate.length / 3));
64
72
  }
65
- function getSuggestedChildNames(unknownSegment, childNames) {
66
- return [...childNames].map((childName) => ({
67
- childName,
68
- distance: damerauLevenshteinDistance(unknownSegment, childName)
69
- })).filter(({ childName, distance }) => distance <= getSuggestionThreshold(childName)).sort((left, right) => left.distance - right.distance || left.childName.localeCompare(right.childName)).slice(0, 3).map(({ childName }) => childName);
73
+ function getSuggestedChildNames(unknownSegment, candidates) {
74
+ const scored = candidates.map((candidate) => ({
75
+ canonicalName: candidate.canonicalName,
76
+ distance: damerauLevenshteinDistance(unknownSegment, candidate.matchName),
77
+ threshold: getSuggestionThreshold(candidate.matchName)
78
+ })).filter(({ distance, threshold }) => distance <= threshold);
79
+ const bestByCanonical = /* @__PURE__ */ new Map();
80
+ for (const entry of scored) {
81
+ const existing = bestByCanonical.get(entry.canonicalName);
82
+ if (existing === void 0 || entry.distance < existing) bestByCanonical.set(entry.canonicalName, entry.distance);
83
+ }
84
+ return [...bestByCanonical.entries()].sort(([nameA, distA], [nameB, distB]) => distA - distB || nameA.localeCompare(nameB)).slice(0, 3).map(([name]) => name);
85
+ }
86
+ function collectSiblingCandidates(currentNode, nodeMap) {
87
+ const candidates = [];
88
+ for (const childName of currentNode.childNames) {
89
+ candidates.push({
90
+ canonicalName: childName,
91
+ matchName: childName
92
+ });
93
+ const childNode = nodeMap[commandManifestPathToKey([...currentNode.pathSegments, childName])];
94
+ if (childNode) for (const alias of childNode.aliases) candidates.push({
95
+ canonicalName: childName,
96
+ matchName: alias
97
+ });
98
+ }
99
+ return candidates;
70
100
  }
71
- function resolveCommandPath(manifest, rawArgs) {
101
+ function resolveCommandRoute(manifest, rawArgs) {
72
102
  const nodeMap = createCommandManifestNodeMap(manifest);
73
103
  const rootNode = nodeMap[""];
74
104
  if (rootNode === void 0) throw new Error("Manifest root node is missing");
@@ -79,7 +109,7 @@ function resolveCommandPath(manifest, rawArgs) {
79
109
  if (isOptionLikeToken(token)) break;
80
110
  const childNode = nodeMap[commandManifestPathToKey([...currentNode.pathSegments, token])];
81
111
  if (childNode === void 0) {
82
- const suggestions = getSuggestedChildNames(token, currentNode.childNames);
112
+ const suggestions = getSuggestedChildNames(token, collectSiblingCandidates(currentNode, nodeMap));
83
113
  if (currentNode.kind === "group" || suggestions.length > 0) return {
84
114
  kind: "unknown",
85
115
  attemptedPath: [...currentNode.pathSegments, token],
@@ -111,7 +141,7 @@ function resolveCommandPath(manifest, rawArgs) {
111
141
  };
112
142
  }
113
143
  //#endregion
114
- //#region src/manifest/render-help.ts
144
+ //#region src/manifest/runtime/render-help.ts
115
145
  function formatCommandName(cliName, pathSegments) {
116
146
  return pathSegments.length === 0 ? cliName : `${cliName} ${pathSegments.join(" ")}`;
117
147
  }
@@ -126,8 +156,8 @@ function formatArgumentLabel(field) {
126
156
  }
127
157
  function formatOptionLabel(field) {
128
158
  const longOptionLabel = `--${field.name}${formatTypeHint(field)}`;
129
- if (!field.alias) return longOptionLabel;
130
- return `-${field.alias}, ${longOptionLabel}`;
159
+ if (!field.short) return longOptionLabel;
160
+ return `-${field.short}, ${longOptionLabel}`;
131
161
  }
132
162
  async function isFieldRequired(field) {
133
163
  if (!isSchemaField(field)) return field.required === true && field.default === void 0;
@@ -148,12 +178,16 @@ function renderGroupHelp(options) {
148
178
  const { manifest, node, cliName, version } = options;
149
179
  const nodeMap = createCommandManifestNodeMap(manifest);
150
180
  const entries = node.childNames.map((childName) => {
181
+ const childNode = nodeMap[commandManifestPathToKey([...node.pathSegments, childName])];
151
182
  return {
152
- label: childName,
153
- description: nodeMap[commandManifestPathToKey([...node.pathSegments, childName])]?.description
183
+ label: `${childName}${childNode && childNode.aliases.length > 0 ? ` (${childNode.aliases.join(", ")})` : ""}`,
184
+ description: childNode?.description
154
185
  };
155
186
  });
156
- const parts = [`Usage: ${formatCommandName(cliName, node.pathSegments)} <command>`];
187
+ const commandName = formatCommandName(cliName, node.pathSegments);
188
+ const parts = [];
189
+ if (node.description) parts.push(node.description);
190
+ parts.push(`Usage: ${commandName} <command>`);
157
191
  if (entries.length > 0) parts.push(`Subcommands:\n${formatSectionEntries(entries)}`);
158
192
  const optionEntries = [{
159
193
  label: "-h, --help",
@@ -194,7 +228,7 @@ function renderUnknownCommandMessage(route, cliName) {
194
228
  return `${parts.join("\n\n")}\n`;
195
229
  }
196
230
  //#endregion
197
- //#region src/manifest/resolve-help.ts
231
+ //#region src/manifest/runtime/resolve-help.ts
198
232
  async function renderResolvedHelp(options) {
199
233
  if (options.route.kind === "unknown") return renderUnknownCommandMessage(options.route, options.cliName);
200
234
  if (options.route.kind === "group") return renderGroupHelp({
@@ -206,7 +240,7 @@ async function renderResolvedHelp(options) {
206
240
  return renderCommandHelp(await (options.loadCommand ?? defaultLoadCommand)(options.route.node), options.route.matchedPath, options.cliName);
207
241
  }
208
242
  //#endregion
209
- //#region src/manifest/run-manifest-command.ts
243
+ //#region src/manifest/runtime/run-manifest-command.ts
210
244
  function ensureTrailingNewline(text) {
211
245
  return text.endsWith("\n") ? text : `${text}\n`;
212
246
  }
@@ -221,7 +255,7 @@ async function runManifestCommand(options) {
221
255
  process.stdout.write(`${options.cliName} v${options.version}\n`);
222
256
  return 0;
223
257
  }
224
- const route = resolveCommandPath(options.manifest, options.rawArgs);
258
+ const route = resolveCommandRoute(options.manifest, options.rawArgs);
225
259
  if (route.kind === "unknown" || route.kind === "group" || route.helpRequested) {
226
260
  const output = await renderResolvedHelp({
227
261
  manifest: options.manifest,
@@ -238,16 +272,16 @@ async function runManifestCommand(options) {
238
272
  return 0;
239
273
  }
240
274
  const command = await (options.loadCommand ?? defaultLoadCommand)(route.node);
241
- const parsed = await parseCommand(command, route.remainingArgs);
242
- if (!parsed.ok) {
243
- process.stderr.write(ensureTrailingNewline(parsed.error.message));
275
+ const commandInput = await parseCommandArgs(command, route.remainingArgs);
276
+ if (!commandInput.ok) {
277
+ process.stderr.write(ensureTrailingNewline(commandInput.error.message));
244
278
  return 1;
245
279
  }
246
280
  const result = await executeCommand(command, {
247
- options: parsed.value.options,
248
- args: parsed.value.args,
281
+ options: commandInput.value.options,
282
+ args: commandInput.value.args,
249
283
  cwd: options.cwd,
250
- rawArgs: parsed.value.rawArgs
284
+ rawArgs: commandInput.value.rawArgs
251
285
  });
252
286
  if (result.errorMessage) process.stderr.write(ensureTrailingNewline(result.errorMessage));
253
287
  return result.exitCode;
@@ -1,4 +1,4 @@
1
- import { i as DefinedCommand, r as CommandOptionField, t as CommandArgField } from "./index-BWxfSwrT.mjs";
1
+ import { a as DefinedCommand, r as CommandOptionField, t as CommandArgField } from "./index-C179V2IJ.mjs";
2
2
 
3
3
  //#region src/manifest/manifest-types.d.ts
4
4
  type CommandManifestPath = readonly string[];
@@ -7,6 +7,7 @@ interface CommandManifestNodeBase {
7
7
  readonly pathSegments: CommandManifestPath;
8
8
  readonly kind: CommandManifestNodeKind;
9
9
  readonly childNames: readonly string[];
10
+ readonly aliases: readonly string[];
10
11
  readonly description?: string | undefined;
11
12
  }
12
13
  interface CommandManifestCommandNode extends CommandManifestNodeBase {
@@ -22,10 +23,10 @@ interface CommandManifest {
22
23
  readonly nodes: readonly CommandManifestNode[];
23
24
  }
24
25
  //#endregion
25
- //#region src/manifest/command-loader.d.ts
26
+ //#region src/manifest/runtime/command-loader.d.ts
26
27
  type LoadCommandFn = (node: CommandManifestCommandNode) => Promise<DefinedCommand<readonly CommandArgField[], readonly CommandOptionField[]>>;
27
28
  //#endregion
28
- //#region src/manifest/run-manifest-command.d.ts
29
+ //#region src/manifest/runtime/run-manifest-command.d.ts
29
30
  interface RunManifestCommandOptions {
30
31
  readonly manifest: CommandManifest;
31
32
  readonly rawArgs: readonly string[];
package/dist/runtime.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import "./dist-DuisScgY.mjs";
2
- import { t as runManifestCommand } from "./run-manifest-command-BphalAwU.mjs";
1
+ import "./dist-Bpf2xVvb.mjs";
2
+ import { t as runManifestCommand } from "./run-manifest-command-DepwxrFI.mjs";
3
3
  export { runManifestCommand };
package/dist/test.d.mts CHANGED
@@ -1,6 +1,210 @@
1
- import { a as ExecuteCommandInput, i as DefinedCommand, o as InferExecutionFields, r as CommandOptionField, t as CommandArgField } from "./index-BWxfSwrT.mjs";
2
-
3
- //#region src/test.d.ts
1
+ //#region ../test-utils/dist/index.d.mts
2
+ //#endregion
3
+ //#region ../core/dist/index.d.mts
4
+ //#region ../../node_modules/.pnpm/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts
5
+ /** The Standard Typed interface. This is a base type extended by other specs. */
6
+ interface StandardTypedV1<Input = unknown, Output = Input> {
7
+ /** The Standard properties. */
8
+ readonly "~standard": StandardTypedV1.Props<Input, Output>;
9
+ }
10
+ declare namespace StandardTypedV1 {
11
+ /** The Standard Typed properties interface. */
12
+ interface Props<Input = unknown, Output = Input> {
13
+ /** The version number of the standard. */
14
+ readonly version: 1;
15
+ /** The vendor name of the schema library. */
16
+ readonly vendor: string;
17
+ /** Inferred types associated with the schema. */
18
+ readonly types?: Types<Input, Output> | undefined;
19
+ }
20
+ /** The Standard Typed types interface. */
21
+ interface Types<Input = unknown, Output = Input> {
22
+ /** The input type of the schema. */
23
+ readonly input: Input;
24
+ /** The output type of the schema. */
25
+ readonly output: Output;
26
+ }
27
+ /** Infers the input type of a Standard Typed. */
28
+ type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
29
+ /** Infers the output type of a Standard Typed. */
30
+ type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
31
+ }
32
+ /** The Standard Schema interface. */
33
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
34
+ /** The Standard Schema properties. */
35
+ readonly "~standard": StandardSchemaV1.Props<Input, Output>;
36
+ }
37
+ declare namespace StandardSchemaV1 {
38
+ /** The Standard Schema properties interface. */
39
+ interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
40
+ /** Validates unknown input values. */
41
+ readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
42
+ }
43
+ /** The result interface of the validate function. */
44
+ type Result<Output> = SuccessResult<Output> | FailureResult;
45
+ /** The result interface if validation succeeds. */
46
+ interface SuccessResult<Output> {
47
+ /** The typed output value. */
48
+ readonly value: Output;
49
+ /** A falsy value for `issues` indicates success. */
50
+ readonly issues?: undefined;
51
+ }
52
+ interface Options {
53
+ /** Explicit support for additional vendor-specific parameters, if needed. */
54
+ readonly libraryOptions?: Record<string, unknown> | undefined;
55
+ }
56
+ /** The result interface if validation fails. */
57
+ interface FailureResult {
58
+ /** The issues of failed validation. */
59
+ readonly issues: ReadonlyArray<Issue>;
60
+ }
61
+ /** The issue interface of the failure output. */
62
+ interface Issue {
63
+ /** The error message of the issue. */
64
+ readonly message: string;
65
+ /** The path of the issue, if any. */
66
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
67
+ }
68
+ /** The path segment interface of the issue. */
69
+ interface PathSegment {
70
+ /** The key representing a path segment. */
71
+ readonly key: PropertyKey;
72
+ }
73
+ /** The Standard types interface. */
74
+ interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
75
+ /** Infers the input type of a Standard. */
76
+ type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
77
+ /** Infers the output type of a Standard. */
78
+ type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
79
+ }
80
+ /** The Standard JSON Schema interface. */
81
+ //#endregion
82
+ //#region src/command-types.d.ts
83
+ type PrimitiveFieldType = "string" | "number" | "boolean";
84
+ type PrimitiveFieldValue<TType extends PrimitiveFieldType> = TType extends "string" ? string : TType extends "number" ? number : boolean;
85
+ interface NamedField<TName extends string = string> {
86
+ /**
87
+ * Identifier used as the key in `ctx.args` / `ctx.options`.
88
+ *
89
+ * For args, any non-empty name is allowed.
90
+ * For options, names must start with a letter and may contain only letters,
91
+ * numbers, and internal hyphens (for example: `dry-run`, `dryRun`, `v2`).
92
+ */
93
+ readonly name: TName;
94
+ /** One-line help text shown in `--help` output. */
95
+ readonly description?: string | undefined;
96
+ }
97
+ interface PrimitiveFieldBase<TName extends string, TType extends PrimitiveFieldType> extends NamedField<TName> {
98
+ /** Primitive type that Rune parses the raw CLI token into (`"string"`, `"number"`, or `"boolean"`). */
99
+ readonly type: TType;
100
+ /**
101
+ * When `true`, the field must be provided by the user.
102
+ * Omitted or `false` makes the field optional. Absent fields are `undefined`
103
+ * in `ctx`, except primitive boolean options, which default to `false`.
104
+ */
105
+ readonly required?: boolean | undefined;
106
+ /** Value used when the user does not provide this field. Makes the field always present in `ctx`. */
107
+ readonly default?: PrimitiveFieldValue<TType> | undefined;
108
+ readonly schema?: never;
109
+ }
110
+ interface SchemaFieldBase<TName extends string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends NamedField<TName> {
111
+ /**
112
+ * A Standard Schema object (e.g. `z.string()`, `v.number()`) used to
113
+ * validate and transform the raw CLI token. Required/optional and default
114
+ * semantics are derived from the schema itself.
115
+ */
116
+ readonly schema: TSchema;
117
+ readonly type?: never;
118
+ readonly required?: never;
119
+ readonly default?: never;
120
+ }
121
+ interface PrimitiveArgField<TName extends string = string, TType extends PrimitiveFieldType = PrimitiveFieldType> extends PrimitiveFieldBase<TName, TType> {
122
+ readonly short?: never;
123
+ readonly flag?: never;
124
+ }
125
+ interface SchemaArgField<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends SchemaFieldBase<TName, TSchema> {
126
+ readonly short?: never;
127
+ readonly flag?: never;
128
+ }
129
+ interface PrimitiveOptionField<TName extends string = string, TType extends PrimitiveFieldType = PrimitiveFieldType> extends PrimitiveFieldBase<TName, TType> {
130
+ /** Single-character shorthand (e.g. `"v"` for `--verbose` → `-v`). */
131
+ readonly short?: string | undefined;
132
+ readonly flag?: never;
133
+ }
134
+ interface SchemaOptionField<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends SchemaFieldBase<TName, TSchema> {
135
+ /** Single-character shorthand (e.g. `"v"` for `--verbose` → `-v`). */
136
+ readonly short?: string | undefined;
137
+ /**
138
+ * When `true`, the option is parsed as a boolean flag (no value expected).
139
+ * The schema receives `true` when the flag is present, `undefined` when absent.
140
+ */
141
+ readonly flag?: true | undefined;
142
+ }
143
+ type CommandArgField = PrimitiveArgField | SchemaArgField;
144
+ type CommandOptionField = PrimitiveOptionField | SchemaOptionField;
145
+ type FieldName<TField> = TField extends {
146
+ readonly name: infer TName extends string;
147
+ } ? TName : never;
148
+ type InferSchemaOutput<TSchema> = TSchema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TSchema> : never;
149
+ type InferSchemaInput<TSchema> = TSchema extends StandardSchemaV1 ? StandardSchemaV1.InferInput<TSchema> : never;
150
+ type IsOptionalSchemaOutput<TValue> = undefined extends TValue ? true : false;
151
+ type FieldValue<TField> = TField extends {
152
+ readonly schema: infer TSchema;
153
+ } ? Exclude<InferSchemaOutput<TSchema>, undefined> : TField extends {
154
+ readonly type: infer TType extends PrimitiveFieldType;
155
+ } ? PrimitiveFieldValue<TType> : never;
156
+ type FieldInputValue<TField> = TField extends {
157
+ readonly schema: infer TSchema;
158
+ } ? InferSchemaInput<TSchema> : TField extends {
159
+ readonly type: infer TType extends PrimitiveFieldType;
160
+ } ? PrimitiveFieldValue<TType> : never;
161
+ type HasDefaultValue<TField> = TField extends {
162
+ readonly default: infer TDefault;
163
+ } ? [TDefault] extends [undefined] ? false : true : false;
164
+ type IsRequiredField<TField, TBooleanAlwaysPresent extends boolean = false> = TField extends {
165
+ readonly schema: infer TSchema;
166
+ } ? IsOptionalSchemaOutput<InferSchemaOutput<TSchema>> extends true ? false : true : HasDefaultValue<TField> extends true ? true : TBooleanAlwaysPresent extends true ? TField extends {
167
+ readonly type: "boolean";
168
+ } ? true : TField extends {
169
+ readonly required: true;
170
+ } ? true : false : TField extends {
171
+ readonly required: true;
172
+ } ? true : false;
173
+ type Simplify<TValue> = { [TKey in keyof TValue]: TValue[TKey] };
174
+ type InferNamedFields<TFields extends readonly NamedField[], TBooleanAlwaysPresent extends boolean = false> = Simplify<{ [TField in TFields[number] as IsRequiredField<TField, TBooleanAlwaysPresent> extends true ? FieldName<TField> : never]: FieldValue<TField> } & { [TField in TFields[number] as IsRequiredField<TField, TBooleanAlwaysPresent> extends true ? never : FieldName<TField>]?: FieldValue<TField> }>;
175
+ type InferExecutionFields<TFields extends readonly NamedField[]> = Simplify<{ [TField in TFields[number] as FieldName<TField>]?: FieldInputValue<TField> }>;
176
+ /** Runtime data passed into a command's `run` function. */
177
+ interface CommandContext<TOptions, TArgs> {
178
+ /** Parsed and validated positional argument values keyed by field name. */
179
+ readonly args: TArgs;
180
+ /** Parsed and validated option values keyed by field name. */
181
+ readonly options: TOptions;
182
+ /** Working directory the CLI was invoked from. */
183
+ readonly cwd: string;
184
+ /**
185
+ * Unparsed argv tokens passed to this command, before Rune splits them
186
+ * into `args` and `options`. Useful for forwarding to child processes.
187
+ */
188
+ readonly rawArgs: readonly string[];
189
+ }
190
+ /** The command definition object accepted by {@link defineCommand}. */
191
+ interface DefinedCommand<TArgsFields extends readonly CommandArgField[] = readonly [], TOptionsFields extends readonly CommandOptionField[] = readonly []> {
192
+ readonly description?: string | undefined;
193
+ readonly aliases: readonly string[];
194
+ readonly args: TArgsFields;
195
+ readonly options: TOptionsFields;
196
+ readonly run: (ctx: CommandContext<InferNamedFields<TOptionsFields, true>, InferNamedFields<TArgsFields>>) => void | Promise<void>;
197
+ } //#endregion
198
+ //#region src/define-command.d.ts
199
+ //#endregion
200
+ //#region src/execute-command.d.ts
201
+ interface ExecuteCommandInput<TOptions, TArgs> {
202
+ readonly options?: TOptions | undefined;
203
+ readonly args?: TArgs | undefined;
204
+ readonly cwd?: string | undefined;
205
+ readonly rawArgs?: readonly string[] | undefined;
206
+ } //#endregion
207
+ //#region src/run-command.d.ts
4
208
  type RunCommandOptions<TOptions, TArgs> = ExecuteCommandInput<TOptions, TArgs>;
5
209
  interface CommandExecutionResult {
6
210
  readonly exitCode: number;
@@ -8,43 +212,6 @@ interface CommandExecutionResult {
8
212
  readonly stderr: string;
9
213
  readonly errorMessage?: string | undefined;
10
214
  }
11
- /**
12
- * Runs a command definition directly in-process for testing.
13
- *
14
- * This helper bypasses Rune's CLI parser and validation layers. Callers
15
- * provide already-normalized `options` and `args` values, and the command's
16
- * `run` function is executed with those values injected into the context.
17
- *
18
- * All output written to `process.stdout`, `process.stderr`, and `console` is
19
- * captured and returned as strings so tests can assert on them.
20
- *
21
- * @param command - A command created with {@link defineCommand}.
22
- * @param options - Pre-validated options, args, cwd, and rawArgs to inject.
23
- * @returns The exit code, captured stdout/stderr, and an optional error message.
24
- *
25
- * @example
26
- * ```ts
27
- * import { defineCommand } from "rune";
28
- * import { runCommand } from "rune/test";
29
- * import { expect, test } from "vitest";
30
- *
31
- * const hello = defineCommand({
32
- * options: [{ name: "name", type: "string", required: true }],
33
- * run(ctx) {
34
- * console.log(`Hello, ${ctx.options.name}!`);
35
- * },
36
- * });
37
- *
38
- * test("hello command", async () => {
39
- * const result = await runCommand(hello, {
40
- * options: { name: "Rune" },
41
- * });
42
- *
43
- * expect(result.exitCode).toBe(0);
44
- * expect(result.stdout).toBe("Hello, Rune!\n");
45
- * });
46
- * ```
47
- */
48
- declare function runCommand<TArgsFields extends readonly CommandArgField[], TOptionsFields extends readonly CommandOptionField[]>(command: DefinedCommand<TArgsFields, TOptionsFields>, options?: RunCommandOptions<InferExecutionFields<TOptionsFields>, InferExecutionFields<TArgsFields>>): Promise<CommandExecutionResult>;
215
+ declare function runCommand<TArgsFields extends readonly CommandArgField[], TOptionsFields extends readonly CommandOptionField[]>(command: DefinedCommand<TArgsFields, TOptionsFields>, options?: RunCommandOptions<InferExecutionFields<TOptionsFields>, InferExecutionFields<TArgsFields>>): Promise<CommandExecutionResult>; //#endregion
49
216
  //#endregion
50
- export { CommandExecutionResult, RunCommandOptions, runCommand };
217
+ export { type CommandExecutionResult, type RunCommandOptions, runCommand };