@rune-cli/rune 0.0.1 → 0.0.3

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/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as successResult, n as runManifestCommand, r as failureResult, t as writeCommandExecutionResult } from "./write-result-C0wgFsjj.mjs";
2
+ import { i as successResult, n as runManifestCommand, r as failureResult, t as writeCommandExecutionResult } from "./write-result-qylmqXvG.mjs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { build } from "esbuild";
5
5
  import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
@@ -313,6 +313,21 @@ async function writeBuiltRuntimeFiles(distDirectory, manifest) {
313
313
  await mkdir(distDirectory, { recursive: true });
314
314
  await writeFile(path.join(distDirectory, BUILD_MANIFEST_FILENAME), serializeCommandManifest(manifest));
315
315
  }
316
+ function renderRuneBuildHelp() {
317
+ return `\
318
+ Build a Rune project into a distributable CLI.
319
+
320
+ Usage: rune build [options]
321
+
322
+ Options:
323
+ --project <path> Path to the Rune project root (default: current directory)
324
+ -h, --help Show this help message
325
+
326
+ Examples:
327
+ rune build
328
+ rune build --project ./my-app
329
+ `;
330
+ }
316
331
  async function runBuildCommand(options) {
317
332
  let projectRoot = "";
318
333
  try {
@@ -340,14 +355,6 @@ async function runBuildCommand(options) {
340
355
  return failureResult(error instanceof Error ? error.message : "Failed to run rune build");
341
356
  }
342
357
  }
343
- function renderRuneBuildHelp() {
344
- return [
345
- "Usage: rune build [--project <path>]",
346
- "",
347
- "Build a Rune project into a distributable CLI.",
348
- ""
349
- ].join("\n");
350
- }
351
358
  //#endregion
352
359
  //#region src/cli/dev-command.ts
353
360
  const DEV_MANIFEST_DIRECTORY_PATH = ".rune";
@@ -358,6 +365,21 @@ async function writeDevManifest(projectRoot, manifestContents) {
358
365
  await mkdir(manifestDirectory, { recursive: true });
359
366
  await writeFile(manifestPath, manifestContents);
360
367
  }
368
+ function renderRuneDevHelp() {
369
+ return `\
370
+ Run a Rune project in development mode.
371
+
372
+ Usage: rune dev [options] [command...]
373
+
374
+ Options:
375
+ --project <path> Path to the Rune project root (default: current directory)
376
+ -h, --help Show this help message
377
+
378
+ Examples:
379
+ rune dev hello
380
+ rune dev --project ./my-app hello
381
+ `;
382
+ }
361
383
  async function runDevCommand(options) {
362
384
  try {
363
385
  const projectRoot = resolveProjectPath(options);
@@ -375,24 +397,6 @@ async function runDevCommand(options) {
375
397
  return failureResult(error instanceof Error ? error.message : "Failed to run rune dev");
376
398
  }
377
399
  }
378
- function renderRuneDevHelp() {
379
- return [
380
- "Usage: rune dev [--project <path>] [--] [command...]",
381
- "",
382
- "Run a Rune project in development mode.",
383
- ""
384
- ].join("\n");
385
- }
386
- function renderRuneCliHelp() {
387
- return [
388
- "Usage: rune <command>",
389
- "",
390
- "Commands:",
391
- " build Build a Rune project into a distributable CLI",
392
- " dev Run a Rune project in development mode",
393
- ""
394
- ].join("\n");
395
- }
396
400
  //#endregion
397
401
  //#region src/cli/rune-cli.ts
398
402
  function tryParseProjectOption(argv, index) {
@@ -460,6 +464,18 @@ function parseBuildArgs(argv) {
460
464
  }
461
465
  return { projectPath };
462
466
  }
467
+ function renderRuneCliHelp() {
468
+ return `\
469
+ Usage: rune <command>
470
+
471
+ Commands:
472
+ build Build a Rune project into a distributable CLI
473
+ dev Run a Rune project in development mode
474
+
475
+ Options:
476
+ -h, --help Show this help message
477
+ `;
478
+ }
463
479
  async function runRuneCli(options) {
464
480
  const [subcommand, ...restArgs] = options.argv;
465
481
  if (!subcommand || isHelpFlag(subcommand)) return successResult(renderRuneCliHelp());
@@ -0,0 +1,400 @@
1
+ import { format, parseArgs } from "node:util";
2
+ //#region ../core/dist/index.mjs
3
+ function isSchemaField(field) {
4
+ return "schema" in field && field.schema !== void 0;
5
+ }
6
+ const EMPTY_FIELDS = [];
7
+ function isOptionalArg(field) {
8
+ if (isSchemaField(field)) return;
9
+ return field.required !== true || field.default !== void 0;
10
+ }
11
+ function validateArgOrdering(args) {
12
+ let firstOptionalName;
13
+ for (const field of args) {
14
+ const optional = isOptionalArg(field);
15
+ if (optional === void 0) continue;
16
+ if (optional) firstOptionalName ??= field.name;
17
+ else if (firstOptionalName !== void 0) throw new Error(`Required argument "${field.name}" cannot follow optional argument "${firstOptionalName}"`);
18
+ }
19
+ }
20
+ /**
21
+ * Defines a CLI command with a description, positional arguments, options,
22
+ * and a function to execute when the command is invoked.
23
+ *
24
+ * The command module's default export should be the return value of this function.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * export default defineCommand({
29
+ * description: "Greet someone",
30
+ * args: [
31
+ * { name: "name", type: "string", required: true },
32
+ * ],
33
+ * options: [
34
+ * { name: "loud", type: "boolean", alias: "l" },
35
+ * ],
36
+ * run(ctx) {
37
+ * const greeting = `Hello, ${ctx.args.name}!`;
38
+ * console.log(ctx.options.loud ? greeting.toUpperCase() : greeting);
39
+ * },
40
+ * });
41
+ * ```
42
+ *
43
+ * Required positional arguments must precede optional ones. This ordering is
44
+ * enforced at the type level for concrete schema types and at runtime for
45
+ * primitive fields:
46
+ *
47
+ * ```ts
48
+ * // Type error — required arg after optional arg
49
+ * defineCommand({
50
+ * args: [
51
+ * { name: "source", type: "string" },
52
+ * { name: "target", type: "string", required: true },
53
+ * ],
54
+ * run() {},
55
+ * });
56
+ *
57
+ * // Type error — required primitive arg after optional schema arg
58
+ * defineCommand({
59
+ * args: [
60
+ * { name: "mode", schema: z.string().optional() },
61
+ * { name: "target", type: "string", required: true },
62
+ * ],
63
+ * run() {},
64
+ * });
65
+ * ```
66
+ *
67
+ * When a schema type is widened to plain `StandardSchemaV1` (e.g. stored in
68
+ * a variable without a concrete type), optionality information is lost and
69
+ * the ordering check is skipped for that field.
70
+ */
71
+ function defineCommand(input) {
72
+ if (input.args) validateArgOrdering(input.args);
73
+ return {
74
+ description: input.description,
75
+ args: input.args ?? EMPTY_FIELDS,
76
+ options: input.options ?? EMPTY_FIELDS,
77
+ run: input.run
78
+ };
79
+ }
80
+ function formatExecutionError(error) {
81
+ if (error instanceof Error) return error.message === "" ? "" : error.message || error.name || "Unknown error";
82
+ if (typeof error === "string") return error;
83
+ return "Unknown error";
84
+ }
85
+ async function captureProcessOutput(action) {
86
+ const stdoutChunks = [];
87
+ const stderrChunks = [];
88
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
89
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
90
+ const originalConsoleMethods = {
91
+ log: console.log,
92
+ info: console.info,
93
+ debug: console.debug,
94
+ warn: console.warn,
95
+ error: console.error
96
+ };
97
+ const captureChunk = (chunks, chunk, encoding) => {
98
+ if (typeof chunk === "string") {
99
+ chunks.push(chunk);
100
+ return;
101
+ }
102
+ chunks.push(Buffer.from(chunk).toString(encoding));
103
+ };
104
+ const captureConsole = (chunks, args) => {
105
+ chunks.push(`${format(...args)}\n`);
106
+ };
107
+ const createWriteCapture = (chunks) => ((chunk, encoding, cb) => {
108
+ captureChunk(chunks, chunk, typeof encoding === "string" ? encoding : void 0);
109
+ if (typeof encoding === "function") encoding(null);
110
+ else cb?.(null);
111
+ return true;
112
+ });
113
+ process.stdout.write = createWriteCapture(stdoutChunks);
114
+ process.stderr.write = createWriteCapture(stderrChunks);
115
+ for (const method of [
116
+ "log",
117
+ "info",
118
+ "debug"
119
+ ]) console[method] = (...args) => captureConsole(stdoutChunks, args);
120
+ for (const method of ["warn", "error"]) console[method] = (...args) => captureConsole(stderrChunks, args);
121
+ try {
122
+ const value = await action();
123
+ return {
124
+ stdout: stdoutChunks.join(""),
125
+ stderr: stderrChunks.join(""),
126
+ value
127
+ };
128
+ } catch (error) {
129
+ return {
130
+ stdout: stdoutChunks.join(""),
131
+ stderr: stderrChunks.join(""),
132
+ error
133
+ };
134
+ } finally {
135
+ process.stdout.write = originalStdoutWrite;
136
+ process.stderr.write = originalStderrWrite;
137
+ Object.assign(console, originalConsoleMethods);
138
+ }
139
+ }
140
+ const EMPTY_ARGS = [];
141
+ async function executeCommand(command, input = {}) {
142
+ const result = await captureProcessOutput(async () => {
143
+ await command.run({
144
+ options: input.options ?? {},
145
+ args: input.args ?? {},
146
+ cwd: input.cwd ?? process.cwd(),
147
+ rawArgs: input.rawArgs ?? EMPTY_ARGS
148
+ });
149
+ });
150
+ if (result.error === void 0) return {
151
+ exitCode: 0,
152
+ stdout: result.stdout,
153
+ stderr: result.stderr
154
+ };
155
+ const message = formatExecutionError(result.error);
156
+ return {
157
+ exitCode: 1,
158
+ stdout: result.stdout,
159
+ stderr: `${result.stderr}${message ? `${message}\n` : ""}`
160
+ };
161
+ }
162
+ function formatFieldTypeHint(field) {
163
+ if (isSchemaField(field)) return "";
164
+ return ` <${field.type}>`;
165
+ }
166
+ function formatOptionLabel(field) {
167
+ return `--${field.name}${formatFieldTypeHint(field)}`;
168
+ }
169
+ function formatArgumentLabel(field) {
170
+ return field.name;
171
+ }
172
+ function missingRequiredOption(field) {
173
+ return {
174
+ ok: false,
175
+ error: { message: `Missing required option:\n\n ${formatOptionLabel(field)}` }
176
+ };
177
+ }
178
+ function missingRequiredArgument(field) {
179
+ return {
180
+ ok: false,
181
+ error: { message: `Missing required argument:\n\n ${formatArgumentLabel(field)}` }
182
+ };
183
+ }
184
+ function invalidOptionValue(field, messages) {
185
+ return {
186
+ ok: false,
187
+ error: { message: `Invalid value for option ${formatOptionLabel(field)}:\n\n ${messages.join("\n ")}` }
188
+ };
189
+ }
190
+ function invalidArgumentValue(field, messages) {
191
+ return {
192
+ ok: false,
193
+ error: { message: `Invalid value for argument ${formatArgumentLabel(field)}:\n\n ${messages.join("\n ")}` }
194
+ };
195
+ }
196
+ function unknownOption(token) {
197
+ return {
198
+ ok: false,
199
+ error: { message: `Unknown option "${token}"` }
200
+ };
201
+ }
202
+ function duplicateOption(field) {
203
+ return {
204
+ ok: false,
205
+ error: { message: `Duplicate option "${formatOptionLabel(field)}" is not supported` }
206
+ };
207
+ }
208
+ function unexpectedArgument(token) {
209
+ return {
210
+ ok: false,
211
+ error: { message: `Unexpected argument "${token}"` }
212
+ };
213
+ }
214
+ function normalizeParseArgsError(error) {
215
+ if (!(error instanceof Error)) return {
216
+ ok: false,
217
+ error: { message: "Argument parsing failed" }
218
+ };
219
+ const unknownMatch = error.message.match(/Unknown option '([^']+)'/);
220
+ if (unknownMatch) return unknownOption(unknownMatch[1]);
221
+ return {
222
+ ok: false,
223
+ error: { message: error.message }
224
+ };
225
+ }
226
+ function parsePrimitiveValue(field, rawValue) {
227
+ if (isSchemaField(field)) throw new Error("Schema fields must be handled separately");
228
+ switch (field.type) {
229
+ case "string": return {
230
+ ok: true,
231
+ value: String(rawValue)
232
+ };
233
+ case "number": {
234
+ if (typeof rawValue !== "string") return {
235
+ ok: false,
236
+ error: { message: `Expected number, received ${JSON.stringify(rawValue)}` }
237
+ };
238
+ const value = Number(rawValue);
239
+ if (!Number.isFinite(value)) return {
240
+ ok: false,
241
+ error: { message: `Expected number, received ${JSON.stringify(rawValue)}` }
242
+ };
243
+ return {
244
+ ok: true,
245
+ value
246
+ };
247
+ }
248
+ case "boolean":
249
+ if (typeof rawValue === "boolean") return {
250
+ ok: true,
251
+ value: rawValue
252
+ };
253
+ if (rawValue === "true") return {
254
+ ok: true,
255
+ value: true
256
+ };
257
+ if (rawValue === "false") return {
258
+ ok: true,
259
+ value: false
260
+ };
261
+ return {
262
+ ok: false,
263
+ error: { message: `Expected boolean, received ${JSON.stringify(rawValue)}` }
264
+ };
265
+ }
266
+ }
267
+ async function validateSchemaValue(schema, rawValue) {
268
+ const result = await schema["~standard"].validate(rawValue);
269
+ if ("value" in result) return {
270
+ ok: true,
271
+ value: result.value
272
+ };
273
+ if (result.issues?.length) return {
274
+ ok: false,
275
+ error: { message: result.issues.map((issue) => issue.message).join("\n") }
276
+ };
277
+ return {
278
+ ok: false,
279
+ error: { message: "Schema validation failed" }
280
+ };
281
+ }
282
+ async function parseProvidedField(field, rawValue) {
283
+ if (isSchemaField(field)) return validateSchemaValue(field.schema, rawValue);
284
+ return parsePrimitiveValue(field, rawValue);
285
+ }
286
+ async function resolveMissingField(field, missingRequired) {
287
+ if (!isSchemaField(field)) {
288
+ if (field.default !== void 0) return {
289
+ ok: true,
290
+ present: true,
291
+ value: field.default
292
+ };
293
+ if (field.required) return missingRequired();
294
+ return {
295
+ ok: true,
296
+ present: false
297
+ };
298
+ }
299
+ const omittedResult = await validateSchemaValue(field.schema, void 0);
300
+ if (!omittedResult.ok) return missingRequired();
301
+ if (omittedResult.value === void 0) return {
302
+ ok: true,
303
+ present: false
304
+ };
305
+ return {
306
+ ok: true,
307
+ present: true,
308
+ value: omittedResult.value
309
+ };
310
+ }
311
+ async function parseArgumentField(field, rawValue) {
312
+ if (rawValue === void 0) return resolveMissingField(field, () => missingRequiredArgument(field));
313
+ const result = await parseProvidedField(field, rawValue);
314
+ if (!result.ok) return invalidArgumentValue(field, result.error.message.split("\n"));
315
+ return {
316
+ ok: true,
317
+ present: true,
318
+ value: result.value
319
+ };
320
+ }
321
+ async function parseOptionField(field, rawValue) {
322
+ const result = await parseProvidedField(field, rawValue);
323
+ if (!result.ok) return invalidOptionValue(field, result.error.message.split("\n"));
324
+ return {
325
+ ok: true,
326
+ present: true,
327
+ value: result.value
328
+ };
329
+ }
330
+ function getOptionParseType(field) {
331
+ if (isSchemaField(field)) return field.flag ? "boolean" : "string";
332
+ return field.type === "boolean" ? "boolean" : "string";
333
+ }
334
+ function buildParseArgsOptions(options) {
335
+ const config = {};
336
+ for (const field of options) config[field.name] = field.alias ? {
337
+ type: getOptionParseType(field),
338
+ short: field.alias
339
+ } : { type: getOptionParseType(field) };
340
+ return config;
341
+ }
342
+ function detectDuplicateOption(options, tokens) {
343
+ const counts = /* @__PURE__ */ new Map();
344
+ for (const token of tokens) {
345
+ if (token.kind !== "option" || !token.name) continue;
346
+ const nextCount = (counts.get(token.name) ?? 0) + 1;
347
+ counts.set(token.name, nextCount);
348
+ if (nextCount > 1) {
349
+ const field = options.find((option) => option.name === token.name);
350
+ if (field) return duplicateOption(field);
351
+ }
352
+ }
353
+ }
354
+ async function parseCommand(command, rawArgs) {
355
+ let parsed;
356
+ try {
357
+ parsed = parseArgs({
358
+ args: [...rawArgs],
359
+ allowPositionals: true,
360
+ strict: true,
361
+ tokens: true,
362
+ options: buildParseArgsOptions(command.options)
363
+ });
364
+ } catch (error) {
365
+ return normalizeParseArgsError(error);
366
+ }
367
+ const duplicateError = detectDuplicateOption(command.options, parsed.tokens);
368
+ if (duplicateError) return duplicateError;
369
+ const parsedArgs = {};
370
+ const parsedOptions = {};
371
+ for (let index = 0; index < command.args.length; index += 1) {
372
+ const field = command.args[index];
373
+ const result = await parseArgumentField(field, parsed.positionals[index]);
374
+ if (!result.ok) return result;
375
+ if (result.present) parsedArgs[field.name] = result.value;
376
+ }
377
+ if (parsed.positionals.length > command.args.length) return unexpectedArgument(parsed.positionals[command.args.length]);
378
+ for (const field of command.options) {
379
+ const rawValue = parsed.values[field.name];
380
+ if (rawValue !== void 0) {
381
+ const result = await parseOptionField(field, rawValue);
382
+ if (!result.ok) return result;
383
+ if (result.present) parsedOptions[field.name] = result.value;
384
+ continue;
385
+ }
386
+ const result = await resolveMissingField(field, () => missingRequiredOption(field));
387
+ if (!result.ok) return result;
388
+ if (result.present) parsedOptions[field.name] = result.value;
389
+ }
390
+ return {
391
+ ok: true,
392
+ value: {
393
+ options: parsedOptions,
394
+ args: parsedArgs,
395
+ rawArgs
396
+ }
397
+ };
398
+ }
399
+ //#endregion
400
+ export { parseCommand as a, isSchemaField as i, executeCommand as n, formatFieldTypeHint as r, defineCommand as t };
@@ -0,0 +1,286 @@
1
+ //#region ../core/dist/index.d.mts
2
+ //#region ../../node_modules/.pnpm/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts
3
+ /** The Standard Typed interface. This is a base type extended by other specs. */
4
+ interface StandardTypedV1<Input = unknown, Output = Input> {
5
+ /** The Standard properties. */
6
+ readonly "~standard": StandardTypedV1.Props<Input, Output>;
7
+ }
8
+ declare namespace StandardTypedV1 {
9
+ /** The Standard Typed properties interface. */
10
+ interface Props<Input = unknown, Output = Input> {
11
+ /** The version number of the standard. */
12
+ readonly version: 1;
13
+ /** The vendor name of the schema library. */
14
+ readonly vendor: string;
15
+ /** Inferred types associated with the schema. */
16
+ readonly types?: Types<Input, Output> | undefined;
17
+ }
18
+ /** The Standard Typed types interface. */
19
+ interface Types<Input = unknown, Output = Input> {
20
+ /** The input type of the schema. */
21
+ readonly input: Input;
22
+ /** The output type of the schema. */
23
+ readonly output: Output;
24
+ }
25
+ /** Infers the input type of a Standard Typed. */
26
+ type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
27
+ /** Infers the output type of a Standard Typed. */
28
+ type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
29
+ }
30
+ /** The Standard Schema interface. */
31
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
32
+ /** The Standard Schema properties. */
33
+ readonly "~standard": StandardSchemaV1.Props<Input, Output>;
34
+ }
35
+ declare namespace StandardSchemaV1 {
36
+ /** The Standard Schema properties interface. */
37
+ interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
38
+ /** Validates unknown input values. */
39
+ readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
40
+ }
41
+ /** The result interface of the validate function. */
42
+ type Result<Output> = SuccessResult<Output> | FailureResult;
43
+ /** The result interface if validation succeeds. */
44
+ interface SuccessResult<Output> {
45
+ /** The typed output value. */
46
+ readonly value: Output;
47
+ /** A falsy value for `issues` indicates success. */
48
+ readonly issues?: undefined;
49
+ }
50
+ interface Options {
51
+ /** Explicit support for additional vendor-specific parameters, if needed. */
52
+ readonly libraryOptions?: Record<string, unknown> | undefined;
53
+ }
54
+ /** The result interface if validation fails. */
55
+ interface FailureResult {
56
+ /** The issues of failed validation. */
57
+ readonly issues: ReadonlyArray<Issue>;
58
+ }
59
+ /** The issue interface of the failure output. */
60
+ interface Issue {
61
+ /** The error message of the issue. */
62
+ readonly message: string;
63
+ /** The path of the issue, if any. */
64
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
65
+ }
66
+ /** The path segment interface of the issue. */
67
+ interface PathSegment {
68
+ /** The key representing a path segment. */
69
+ readonly key: PropertyKey;
70
+ }
71
+ /** The Standard types interface. */
72
+ interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
73
+ /** Infers the input type of a Standard. */
74
+ type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
75
+ /** Infers the output type of a Standard. */
76
+ type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
77
+ }
78
+ /** The Standard JSON Schema interface. */
79
+ //#endregion
80
+ //#region src/command-types.d.ts
81
+ type PrimitiveFieldType = "string" | "number" | "boolean";
82
+ type PrimitiveFieldValue<TType extends PrimitiveFieldType> = TType extends "string" ? string : TType extends "number" ? number : boolean;
83
+ interface NamedField<TName extends string = string> {
84
+ /** Identifier used as the key in `ctx.args` / `ctx.options` and as the CLI flag name for options. */
85
+ readonly name: TName;
86
+ /** One-line help text shown in `--help` output. */
87
+ readonly description?: string | undefined;
88
+ }
89
+ interface PrimitiveFieldBase<TName extends string, TType extends PrimitiveFieldType> extends NamedField<TName> {
90
+ /** Primitive type that Rune parses the raw CLI token into (`"string"`, `"number"`, or `"boolean"`). */
91
+ readonly type: TType;
92
+ /**
93
+ * When `true`, the field must be provided by the user.
94
+ * Omitted or `false` makes the field optional (absent fields are `undefined` in `ctx`).
95
+ */
96
+ readonly required?: boolean | undefined;
97
+ /** Value used when the user does not provide this field. Makes the field always present in `ctx`. */
98
+ readonly default?: PrimitiveFieldValue<TType> | undefined;
99
+ readonly schema?: never;
100
+ }
101
+ interface SchemaFieldBase<TName extends string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends NamedField<TName> {
102
+ /**
103
+ * A Standard Schema object (e.g. `z.string()`, `v.number()`) used to
104
+ * validate and transform the raw CLI token. Required/optional and default
105
+ * semantics are derived from the schema itself.
106
+ */
107
+ readonly schema: TSchema;
108
+ readonly type?: never;
109
+ readonly required?: never;
110
+ readonly default?: never;
111
+ }
112
+ interface PrimitiveArgField<TName extends string = string, TType extends PrimitiveFieldType = PrimitiveFieldType> extends PrimitiveFieldBase<TName, TType> {
113
+ readonly alias?: never;
114
+ readonly flag?: never;
115
+ }
116
+ interface SchemaArgField<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends SchemaFieldBase<TName, TSchema> {
117
+ readonly alias?: never;
118
+ readonly flag?: never;
119
+ }
120
+ interface PrimitiveOptionField<TName extends string = string, TType extends PrimitiveFieldType = PrimitiveFieldType> extends PrimitiveFieldBase<TName, TType> {
121
+ /** Single-character shorthand (e.g. `"v"` for `--verbose` → `-v`). */
122
+ readonly alias?: string | undefined;
123
+ readonly flag?: never;
124
+ }
125
+ interface SchemaOptionField<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> extends SchemaFieldBase<TName, TSchema> {
126
+ /** Single-character shorthand (e.g. `"v"` for `--verbose` → `-v`). */
127
+ readonly alias?: string | undefined;
128
+ /**
129
+ * When `true`, the option is parsed as a boolean flag (no value expected).
130
+ * The schema receives `true` when the flag is present, `undefined` when absent.
131
+ */
132
+ readonly flag?: true | undefined;
133
+ }
134
+ type CommandArgField = PrimitiveArgField | SchemaArgField;
135
+ type CommandOptionField = PrimitiveOptionField | SchemaOptionField;
136
+ type NormalizeFields<TFields extends readonly TField[] | undefined, TField> = TFields extends readonly TField[] ? TFields : readonly [];
137
+ type FieldName<TField> = TField extends {
138
+ readonly name: infer TName extends string;
139
+ } ? TName : never;
140
+ type InferSchemaOutput<TSchema> = TSchema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TSchema> : never;
141
+ type InferSchemaInput<TSchema> = TSchema extends StandardSchemaV1 ? StandardSchemaV1.InferInput<TSchema> : never;
142
+ type IsOptionalSchemaOutput<TValue> = undefined extends TValue ? true : false;
143
+ type FieldValue<TField> = TField extends {
144
+ readonly schema: infer TSchema;
145
+ } ? Exclude<InferSchemaOutput<TSchema>, undefined> : TField extends {
146
+ readonly type: infer TType extends PrimitiveFieldType;
147
+ } ? PrimitiveFieldValue<TType> : never;
148
+ type FieldInputValue<TField> = TField extends {
149
+ readonly schema: infer TSchema;
150
+ } ? InferSchemaInput<TSchema> : TField extends {
151
+ readonly type: infer TType extends PrimitiveFieldType;
152
+ } ? PrimitiveFieldValue<TType> : never;
153
+ type HasDefaultValue<TField> = TField extends {
154
+ readonly default: infer TDefault;
155
+ } ? [TDefault] extends [undefined] ? false : true : false;
156
+ type IsRequiredField<TField> = TField extends {
157
+ readonly schema: infer TSchema;
158
+ } ? IsOptionalSchemaOutput<InferSchemaOutput<TSchema>> extends true ? false : true : HasDefaultValue<TField> extends true ? true : TField extends {
159
+ readonly required: true;
160
+ } ? true : false;
161
+ type IsArgOptional<TField> = TField extends {
162
+ readonly schema: infer TSchema;
163
+ } ? unknown extends InferSchemaInput<TSchema> ? false : undefined extends InferSchemaInput<TSchema> ? true : false : TField extends {
164
+ readonly type: PrimitiveFieldType;
165
+ } ? HasDefaultValue<TField> extends true ? true : TField extends {
166
+ readonly required: true;
167
+ } ? false : true : false;
168
+ type IsValidArgOrder<TArgs extends readonly any[], TSeenOptional extends boolean = false> = TArgs extends readonly [infer THead, ...infer TTail] ? IsArgOptional<THead> extends true ? IsValidArgOrder<TTail, true> : TSeenOptional extends true ? false : IsValidArgOrder<TTail, false> : true;
169
+ type ValidateArgOrder<TArgs> = TArgs extends readonly CommandArgField[] ? IsValidArgOrder<TArgs> extends false ? {
170
+ readonly args: never;
171
+ } : unknown : unknown;
172
+ type Simplify<TValue> = { [TKey in keyof TValue]: TValue[TKey] };
173
+ type InferNamedFields<TFields extends readonly NamedField[]> = Simplify<{ [TField in TFields[number] as IsRequiredField<TField> extends true ? FieldName<TField> : never]: FieldValue<TField> } & { [TField in TFields[number] as IsRequiredField<TField> extends true ? never : FieldName<TField>]?: FieldValue<TField> }>;
174
+ type InferExecutionFields<TFields extends readonly NamedField[]> = Simplify<{ [TField in TFields[number] as FieldName<TField>]?: FieldInputValue<TField> }>;
175
+ /** Runtime data passed into a command's `run` function. */
176
+ interface CommandContext<TOptions, TArgs> {
177
+ /** Parsed and validated positional argument values keyed by field name. */
178
+ readonly args: TArgs;
179
+ /** Parsed and validated option values keyed by field name. */
180
+ readonly options: TOptions;
181
+ /** Working directory the CLI was invoked from. */
182
+ readonly cwd: string;
183
+ /**
184
+ * Unparsed argv tokens passed to this command, before Rune splits them
185
+ * into `args` and `options`. Useful for forwarding to child processes.
186
+ */
187
+ readonly rawArgs: readonly string[];
188
+ }
189
+ /** The command definition object accepted by {@link defineCommand}. */
190
+ interface DefineCommandInput<TArgsFields extends readonly CommandArgField[] | undefined = undefined, TOptionsFields extends readonly CommandOptionField[] | undefined = undefined> {
191
+ /** One-line summary shown in `--help` output. */
192
+ readonly description?: string | undefined;
193
+ /**
194
+ * Positional arguments declared in the order they appear on the command line.
195
+ * Required arguments must come before optional ones.
196
+ *
197
+ * Each entry is either a primitive field (`{ name, type }`) or a schema
198
+ * field (`{ name, schema }`).
199
+ */
200
+ readonly args?: TArgsFields;
201
+ /**
202
+ * Options declared as `--name` flags, with optional single-character aliases.
203
+ *
204
+ * Each entry is either a primitive field (`{ name, type }`) or a schema
205
+ * field (`{ name, schema }`).
206
+ */
207
+ readonly options?: TOptionsFields;
208
+ /**
209
+ * The function executed when this command is invoked.
210
+ * Receives a {@link CommandContext} with fully parsed `args` and `options`.
211
+ */
212
+ readonly run: (ctx: CommandContext<InferNamedFields<NormalizeFields<TOptionsFields, CommandOptionField>>, InferNamedFields<NormalizeFields<TArgsFields, CommandArgField>>>) => void | Promise<void>;
213
+ }
214
+ interface DefinedCommand<TArgsFields extends readonly CommandArgField[] = readonly [], TOptionsFields extends readonly CommandOptionField[] = readonly []> {
215
+ readonly description?: string | undefined;
216
+ readonly args: TArgsFields;
217
+ readonly options: TOptionsFields;
218
+ readonly run: (ctx: CommandContext<InferNamedFields<TOptionsFields>, InferNamedFields<TArgsFields>>) => void | Promise<void>;
219
+ } //#endregion
220
+ //#region src/define-command.d.ts
221
+ /**
222
+ * Defines a CLI command with a description, positional arguments, options,
223
+ * and a function to execute when the command is invoked.
224
+ *
225
+ * The command module's default export should be the return value of this function.
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * export default defineCommand({
230
+ * description: "Greet someone",
231
+ * args: [
232
+ * { name: "name", type: "string", required: true },
233
+ * ],
234
+ * options: [
235
+ * { name: "loud", type: "boolean", alias: "l" },
236
+ * ],
237
+ * run(ctx) {
238
+ * const greeting = `Hello, ${ctx.args.name}!`;
239
+ * console.log(ctx.options.loud ? greeting.toUpperCase() : greeting);
240
+ * },
241
+ * });
242
+ * ```
243
+ *
244
+ * Required positional arguments must precede optional ones. This ordering is
245
+ * enforced at the type level for concrete schema types and at runtime for
246
+ * primitive fields:
247
+ *
248
+ * ```ts
249
+ * // Type error — required arg after optional arg
250
+ * defineCommand({
251
+ * args: [
252
+ * { name: "source", type: "string" },
253
+ * { name: "target", type: "string", required: true },
254
+ * ],
255
+ * run() {},
256
+ * });
257
+ *
258
+ * // Type error — required primitive arg after optional schema arg
259
+ * defineCommand({
260
+ * args: [
261
+ * { name: "mode", schema: z.string().optional() },
262
+ * { name: "target", type: "string", required: true },
263
+ * ],
264
+ * run() {},
265
+ * });
266
+ * ```
267
+ *
268
+ * When a schema type is widened to plain `StandardSchemaV1` (e.g. stored in
269
+ * a variable without a concrete type), optionality information is lost and
270
+ * the ordering check is skipped for that field.
271
+ */
272
+ 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>>; //#endregion
273
+ //#region src/execute-command.d.ts
274
+ interface ExecuteCommandInput<TOptions, TArgs> {
275
+ readonly options?: TOptions | undefined;
276
+ readonly args?: TArgs | undefined;
277
+ readonly cwd?: string | undefined;
278
+ readonly rawArgs?: readonly string[] | undefined;
279
+ }
280
+ interface CommandExecutionResult {
281
+ readonly exitCode: number;
282
+ readonly stdout: string;
283
+ readonly stderr: string;
284
+ }
285
+ //#endregion
286
+ export { DefinedCommand as a, PrimitiveArgField as c, SchemaArgField as d, SchemaOptionField as f, CommandOptionField as i, PrimitiveFieldType as l, CommandContext as n, ExecuteCommandInput as o, defineCommand as p, CommandExecutionResult as r, InferExecutionFields as s, CommandArgField as t, PrimitiveOptionField as u };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { defineCommand } from "@rune-cli/core";
2
- export { defineCommand };
1
+ import { a as DefinedCommand, c as PrimitiveArgField, d as SchemaArgField, f as SchemaOptionField, i as CommandOptionField, l as PrimitiveFieldType, n as CommandContext, o as ExecuteCommandInput, p as defineCommand, r as CommandExecutionResult, s as InferExecutionFields, t as CommandArgField, u as PrimitiveOptionField } from "./index-BF5_G9J2.mjs";
2
+ export { type CommandArgField, type CommandContext, type CommandExecutionResult, type CommandOptionField, type DefinedCommand, type ExecuteCommandInput, type InferExecutionFields, type PrimitiveArgField, type PrimitiveFieldType, type PrimitiveOptionField, type SchemaArgField, type SchemaOptionField, defineCommand };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { defineCommand } from "@rune-cli/core";
1
+ import { t as defineCommand } from "./dist-Bcn0FpHi.mjs";
2
2
  export { defineCommand };
@@ -1,4 +1,4 @@
1
- import { CommandArgField, CommandExecutionResult, CommandOptionField, DefinedCommand } from "@rune-cli/core";
1
+ import { a as DefinedCommand, i as CommandOptionField, r as CommandExecutionResult, t as CommandArgField } from "./index-BF5_G9J2.mjs";
2
2
 
3
3
  //#region src/manifest/manifest-types.d.ts
4
4
  type CommandManifestPath = readonly string[];
package/dist/runtime.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as runManifestCommand, t as writeCommandExecutionResult } from "./write-result-C0wgFsjj.mjs";
1
+ import { n as runManifestCommand, t as writeCommandExecutionResult } from "./write-result-qylmqXvG.mjs";
2
2
  export { runManifestCommand, writeCommandExecutionResult };
package/dist/test.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { CommandArgField, CommandExecutionResult, CommandOptionField, DefinedCommand, ExecuteCommandInput, InferExecutionFields } from "@rune-cli/core";
1
+ import { a as DefinedCommand, i as CommandOptionField, o as ExecuteCommandInput, r as CommandExecutionResult, s as InferExecutionFields, t as CommandArgField } from "./index-BF5_G9J2.mjs";
2
2
 
3
3
  //#region src/test.d.ts
4
4
  type RunCommandOptions<TOptions, TArgs> = ExecuteCommandInput<TOptions, TArgs>;
package/dist/test.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { executeCommand } from "@rune-cli/core";
1
+ import { n as executeCommand } from "./dist-Bcn0FpHi.mjs";
2
2
  //#region src/test.ts
3
3
  async function runCommand(command, options = {}) {
4
4
  return executeCommand(command, options);
@@ -1,4 +1,4 @@
1
- import { executeCommand, formatFieldTypeHint, isSchemaField, parseCommand } from "@rune-cli/core";
1
+ import { a as parseCommand, i as isSchemaField, n as executeCommand, r as formatFieldTypeHint } from "./dist-Bcn0FpHi.mjs";
2
2
  import { pathToFileURL } from "node:url";
3
3
  //#region src/cli/result.ts
4
4
  function successResult(stdout) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rune-cli/rune",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Rune is a CLI framework built around the concept of file-based command routing.",
5
5
  "homepage": "https://github.com/morinokami/rune#readme",
6
6
  "bugs": {
@@ -39,15 +39,14 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "esbuild": "0.27.4",
43
- "@rune-cli/core": "0.0.0"
42
+ "esbuild": "0.27.4"
44
43
  },
45
44
  "devDependencies": {
46
45
  "@types/node": "24.12.0",
47
46
  "@typescript/native-preview": "7.0.0-dev.20260317.1",
48
- "bumpp": "11.0.1",
49
47
  "typescript": "5.9.3",
50
- "vite-plus": "v0.1.13"
48
+ "vite-plus": "v0.1.13",
49
+ "@rune-cli/core": "0.0.0"
51
50
  },
52
51
  "peerDependencies": {
53
52
  "typescript": ">=5.0.0"