@rune-cli/rune 0.0.7 → 0.0.8

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,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import "./dist-FWLEc-op.mjs";
3
- import { n as isHelpFlag, r as isVersionFlag, t as runManifestCommand } from "./run-manifest-command-BmeVwPA5.mjs";
2
+ import "./dist-uz53Uv1e.mjs";
3
+ import { n as isHelpFlag, r as isVersionFlag, t as runManifestCommand } from "./run-manifest-command-Dq_lBv-H.mjs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { build } from "esbuild";
6
6
  import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  import ts from "typescript";
9
9
  //#region package.json
10
- var version = "0.0.7";
10
+ var version = "0.0.8";
11
11
  //#endregion
12
12
  //#region src/manifest/generate-manifest.ts
13
13
  const COMMAND_ENTRY_FILE = "index.ts";
@@ -4,6 +4,34 @@ function isSchemaField(field) {
4
4
  return "schema" in field && field.schema !== void 0;
5
5
  }
6
6
  const DEFINED_COMMAND_BRAND = Symbol.for("@rune-cli/defined-command");
7
+ const OPTION_NAME_RE = /^[A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*$/;
8
+ const ALIAS_RE = /^[a-zA-Z]$/;
9
+ function validateFieldShape(fields, kind) {
10
+ for (const field of fields) {
11
+ const raw = field;
12
+ if (raw.schema === void 0 && raw.type === void 0) throw new Error(`${kind === "argument" ? "Argument" : "Option"} "${raw.name}" must have either a "type" or "schema" property.`);
13
+ }
14
+ }
15
+ function validateUniqueFieldNames(fields, kind) {
16
+ const seen = /* @__PURE__ */ new Set();
17
+ for (const field of fields) {
18
+ if (field.name.length === 0) throw new Error(`Invalid ${kind} name "${field.name}". Names must be non-empty.`);
19
+ if (seen.has(field.name)) throw new Error(`Duplicate ${kind} name "${field.name}".`);
20
+ seen.add(field.name);
21
+ }
22
+ }
23
+ function validateOptionNames(options) {
24
+ for (const field of options) if (!OPTION_NAME_RE.test(field.name)) throw new Error(`Invalid option name "${field.name}". Option names must start with a letter and contain only letters, numbers, and internal hyphens.`);
25
+ }
26
+ function validateOptionAliases(options) {
27
+ const seen = /* @__PURE__ */ new Set();
28
+ for (const field of options) {
29
+ if (field.alias === void 0) continue;
30
+ if (!ALIAS_RE.test(field.alias)) throw new Error(`Invalid alias "${field.alias}" for option "${field.name}". Alias must be a single letter.`);
31
+ if (seen.has(field.alias)) throw new Error(`Duplicate alias "${field.alias}" for option "${field.name}".`);
32
+ seen.add(field.alias);
33
+ }
34
+ }
7
35
  function isOptionalArg(field) {
8
36
  if (isSchemaField(field)) return;
9
37
  return field.required !== true || field.default !== void 0;
@@ -69,7 +97,17 @@ function validateArgOrdering(args) {
69
97
  * the ordering check is skipped for that field.
70
98
  */
71
99
  function defineCommand(input) {
72
- if (input.args) validateArgOrdering(input.args);
100
+ if (input.args) {
101
+ validateFieldShape(input.args, "argument");
102
+ validateUniqueFieldNames(input.args, "argument");
103
+ validateArgOrdering(input.args);
104
+ }
105
+ if (input.options) {
106
+ validateFieldShape(input.options, "option");
107
+ validateUniqueFieldNames(input.options, "option");
108
+ validateOptionNames(input.options);
109
+ validateOptionAliases(input.options);
110
+ }
73
111
  const command = {
74
112
  description: input.description,
75
113
  args: input.args ?? [],
@@ -92,8 +130,10 @@ function formatExecutionError(error) {
92
130
  }
93
131
  async function executeCommand(command, input = {}) {
94
132
  try {
133
+ const options = { ...input.options };
134
+ for (const field of command.options) if (options[field.name] === void 0 && !isSchemaField(field) && field.type === "boolean") options[field.name] = false;
95
135
  await command.run({
96
- options: input.options ?? {},
136
+ options,
97
137
  args: input.args ?? {},
98
138
  cwd: input.cwd ?? process.cwd(),
99
139
  rawArgs: input.rawArgs ?? []
@@ -389,6 +429,7 @@ async function parseCommand(command, rawArgs) {
389
429
  const result = await resolveMissingField(field, () => missingRequiredOption(field));
390
430
  if (!result.ok) return result;
391
431
  if (result.present) parsedOptions[field.name] = result.value;
432
+ else if (!isSchemaField(field) && field.type === "boolean") parsedOptions[field.name] = false;
392
433
  }
393
434
  return {
394
435
  ok: true,
@@ -81,7 +81,13 @@ declare namespace StandardSchemaV1 {
81
81
  type PrimitiveFieldType = "string" | "number" | "boolean";
82
82
  type PrimitiveFieldValue<TType extends PrimitiveFieldType> = TType extends "string" ? string : TType extends "number" ? number : boolean;
83
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. */
84
+ /**
85
+ * Identifier used as the key in `ctx.args` / `ctx.options`.
86
+ *
87
+ * For args, any non-empty name is allowed.
88
+ * For options, names must start with a letter and may contain only letters,
89
+ * numbers, and internal hyphens (for example: `dry-run`, `dryRun`, `v2`).
90
+ */
85
91
  readonly name: TName;
86
92
  /** One-line help text shown in `--help` output. */
87
93
  readonly description?: string | undefined;
@@ -91,7 +97,8 @@ interface PrimitiveFieldBase<TName extends string, TType extends PrimitiveFieldT
91
97
  readonly type: TType;
92
98
  /**
93
99
  * When `true`, the field must be provided by the user.
94
- * Omitted or `false` makes the field optional (absent fields are `undefined` in `ctx`).
100
+ * Omitted or `false` makes the field optional. Absent fields are `undefined`
101
+ * in `ctx`, except primitive boolean options, which default to `false`.
95
102
  */
96
103
  readonly required?: boolean | undefined;
97
104
  /** Value used when the user does not provide this field. Makes the field always present in `ctx`. */
@@ -153,9 +160,13 @@ type FieldInputValue<TField> = TField extends {
153
160
  type HasDefaultValue<TField> = TField extends {
154
161
  readonly default: infer TDefault;
155
162
  } ? [TDefault] extends [undefined] ? false : true : false;
156
- type IsRequiredField<TField> = TField extends {
163
+ type IsRequiredField<TField, TBooleanAlwaysPresent extends boolean = false> = TField extends {
157
164
  readonly schema: infer TSchema;
158
- } ? IsOptionalSchemaOutput<InferSchemaOutput<TSchema>> extends true ? false : true : HasDefaultValue<TField> extends true ? true : TField extends {
165
+ } ? IsOptionalSchemaOutput<InferSchemaOutput<TSchema>> extends true ? false : true : HasDefaultValue<TField> extends true ? true : TBooleanAlwaysPresent extends true ? TField extends {
166
+ readonly type: "boolean";
167
+ } ? true : TField extends {
168
+ readonly required: true;
169
+ } ? true : false : TField extends {
159
170
  readonly required: true;
160
171
  } ? true : false;
161
172
  type IsArgOptional<TField> = TField extends {
@@ -170,7 +181,7 @@ type ValidateArgOrder<TArgs> = TArgs extends readonly CommandArgField[] ? IsVali
170
181
  readonly args: never;
171
182
  } : unknown : unknown;
172
183
  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> }>;
184
+ 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> }>;
174
185
  type InferExecutionFields<TFields extends readonly NamedField[]> = Simplify<{ [TField in TFields[number] as FieldName<TField>]?: FieldInputValue<TField> }>;
175
186
  /** Runtime data passed into a command's `run` function. */
176
187
  interface CommandContext<TOptions, TArgs> {
@@ -193,6 +204,7 @@ interface DefineCommandInput<TArgsFields extends readonly CommandArgField[] | un
193
204
  /**
194
205
  * Positional arguments declared in the order they appear on the command line.
195
206
  * Required arguments must come before optional ones.
207
+ * Argument names must be non-empty and unique within the command.
196
208
  *
197
209
  * Each entry is either a primitive field (`{ name, type }`) or a schema
198
210
  * field (`{ name, schema }`).
@@ -200,6 +212,8 @@ interface DefineCommandInput<TArgsFields extends readonly CommandArgField[] | un
200
212
  readonly args?: TArgsFields;
201
213
  /**
202
214
  * Options declared as `--name` flags, with optional single-character aliases.
215
+ * Option names must be unique within the command, start with a letter, and
216
+ * contain only letters, numbers, and internal hyphens.
203
217
  *
204
218
  * Each entry is either a primitive field (`{ name, type }`) or a schema
205
219
  * field (`{ name, schema }`).
@@ -209,13 +223,13 @@ interface DefineCommandInput<TArgsFields extends readonly CommandArgField[] | un
209
223
  * The function executed when this command is invoked.
210
224
  * Receives a {@link CommandContext} with fully parsed `args` and `options`.
211
225
  */
212
- readonly run: (ctx: CommandContext<InferNamedFields<NormalizeFields<TOptionsFields, CommandOptionField>>, InferNamedFields<NormalizeFields<TArgsFields, CommandArgField>>>) => void | Promise<void>;
226
+ readonly run: (ctx: CommandContext<InferNamedFields<NormalizeFields<TOptionsFields, CommandOptionField>, true>, InferNamedFields<NormalizeFields<TArgsFields, CommandArgField>>>) => void | Promise<void>;
213
227
  }
214
228
  interface DefinedCommand<TArgsFields extends readonly CommandArgField[] = readonly [], TOptionsFields extends readonly CommandOptionField[] = readonly []> {
215
229
  readonly description?: string | undefined;
216
230
  readonly args: TArgsFields;
217
231
  readonly options: TOptionsFields;
218
- readonly run: (ctx: CommandContext<InferNamedFields<TOptionsFields>, InferNamedFields<TArgsFields>>) => void | Promise<void>;
232
+ readonly run: (ctx: CommandContext<InferNamedFields<TOptionsFields, true>, InferNamedFields<TArgsFields>>) => void | Promise<void>;
219
233
  } //#endregion
220
234
  //#region src/define-command.d.ts
221
235
  /**
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-B6XsJ6ON.mjs";
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
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 };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as defineCommand } from "./dist-FWLEc-op.mjs";
1
+ import { n as defineCommand } from "./dist-uz53Uv1e.mjs";
2
2
  export { defineCommand };
@@ -1,4 +1,4 @@
1
- import { a as isSchemaField, i as isDefinedCommand, o as parseCommand, r as executeCommand } from "./dist-FWLEc-op.mjs";
1
+ import { a as isSchemaField, i as isDefinedCommand, o as parseCommand, r as executeCommand } from "./dist-uz53Uv1e.mjs";
2
2
  import { pathToFileURL } from "node:url";
3
3
  //#region src/cli/flags.ts
4
4
  function isHelpFlag(token) {
@@ -1,4 +1,4 @@
1
- import { i as DefinedCommand, r as CommandOptionField, t as CommandArgField } from "./index-B6XsJ6ON.mjs";
1
+ import { i as DefinedCommand, r as CommandOptionField, t as CommandArgField } from "./index-BWxfSwrT.mjs";
2
2
 
3
3
  //#region src/manifest/manifest-types.d.ts
4
4
  type CommandManifestPath = readonly string[];
package/dist/runtime.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import "./dist-FWLEc-op.mjs";
2
- import { t as runManifestCommand } from "./run-manifest-command-BmeVwPA5.mjs";
1
+ import "./dist-uz53Uv1e.mjs";
2
+ import { t as runManifestCommand } from "./run-manifest-command-Dq_lBv-H.mjs";
3
3
  export { runManifestCommand };
package/dist/test.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ExecuteCommandInput, i as DefinedCommand, o as InferExecutionFields, r as CommandOptionField, t as CommandArgField } from "./index-B6XsJ6ON.mjs";
1
+ import { a as ExecuteCommandInput, i as DefinedCommand, o as InferExecutionFields, r as CommandOptionField, t as CommandArgField } from "./index-BWxfSwrT.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 { r as executeCommand, t as captureProcessOutput } from "./dist-FWLEc-op.mjs";
1
+ import { r as executeCommand, t as captureProcessOutput } from "./dist-uz53Uv1e.mjs";
2
2
  //#region src/test.ts
3
3
  /**
4
4
  * Runs a command definition directly in-process for testing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rune-cli/rune",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
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": {