padrone 1.2.0 → 1.4.0
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/CHANGELOG.md +74 -0
- package/dist/{args-CKNh7Dm9.mjs → args-CVDbyyzG.mjs} +46 -22
- package/dist/args-CVDbyyzG.mjs.map +1 -0
- package/dist/codegen/index.mjs +12 -2
- package/dist/codegen/index.mjs.map +1 -1
- package/dist/completion.d.mts +1 -1
- package/dist/completion.mjs +2 -2
- package/dist/completion.mjs.map +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +15 -14
- package/dist/docs/index.mjs.map +1 -1
- package/dist/{formatter-Dvx7jFXr.d.mts → formatter-ClUK5hcQ.d.mts} +4 -2
- package/dist/formatter-ClUK5hcQ.d.mts.map +1 -0
- package/dist/{help-mUIX0T0V.mjs → help-CcBe91bV.mjs} +89 -30
- package/dist/help-CcBe91bV.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +50 -17
- package/dist/index.mjs.map +1 -1
- package/dist/test.d.mts +1 -1
- package/dist/{types-qrtt0135.d.mts → types-DjIdJN5G.d.mts} +27 -5
- package/dist/types-DjIdJN5G.d.mts.map +1 -0
- package/package.json +1 -1
- package/src/args.ts +124 -28
- package/src/cli/doctor.ts +28 -10
- package/src/cli/index.ts +0 -0
- package/src/codegen/generators/command-file.ts +16 -3
- package/src/command-utils.ts +33 -2
- package/src/completion.ts +1 -1
- package/src/create.ts +22 -12
- package/src/docs/index.ts +16 -16
- package/src/formatter.ts +65 -27
- package/src/help.ts +10 -4
- package/src/parse.ts +22 -6
- package/src/type-utils.ts +2 -2
- package/dist/args-CKNh7Dm9.mjs.map +0 -1
- package/dist/formatter-Dvx7jFXr.d.mts.map +0 -1
- package/dist/help-mUIX0T0V.mjs.map +0 -1
- package/dist/types-qrtt0135.d.mts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,79 @@
|
|
|
1
1
|
# padrone
|
|
2
2
|
|
|
3
|
+
## 1.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 365ad1f: Fix positional arguments and make results always thenable
|
|
8
|
+
|
|
9
|
+
- Show choices and default values in help output for positional arguments
|
|
10
|
+
- Fix type detection for optional array enum positionals (`z.array(z.enum([...])).optional()`)
|
|
11
|
+
- Coerce single values to arrays when schema expects an array type
|
|
12
|
+
- Make `cli()`, `eval()`, and `parse()` results always thenable (supports `.then()` and `await`)
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 79f51ae: Fix false async warning when action is async but validation is sync
|
|
17
|
+
|
|
18
|
+
## 1.3.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- 6f6bfe5: Auto-generate kebab-case aliases for camelCase option names
|
|
23
|
+
|
|
24
|
+
Options like `dryRun` automatically accept `--dry-run` on the CLI. This is enabled by default and can be disabled per-command with `autoAlias: false`. Auto-aliases are shown as the primary name in help text when available.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// --dry-run automatically resolves to dryRun
|
|
28
|
+
.arguments(z.object({ dryRun: z.boolean() }))
|
|
29
|
+
|
|
30
|
+
// Disable auto-aliases
|
|
31
|
+
.arguments(z.object({ dryRun: z.boolean() }), { autoAlias: false })
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- 07cbf35: Split option `alias` into `flags` (single-char, stackable) and `alias` (multi-char long names)
|
|
35
|
+
|
|
36
|
+
**Breaking:** `PadroneFieldMeta.alias` for single-character shortcuts is now `flags`.
|
|
37
|
+
|
|
38
|
+
- `flags`: single-char short flags used with single dash (`-v`, `-o file`). Stackable: `-abc` = `-a -b -c`.
|
|
39
|
+
- `alias`: multi-char alternative long names used with double dash (`--dry-run` for `--dryRun`).
|
|
40
|
+
|
|
41
|
+
### Migration
|
|
42
|
+
|
|
43
|
+
```diff
|
|
44
|
+
- { fields: { verbose: { alias: 'v' } } }
|
|
45
|
+
+ { fields: { verbose: { flags: 'v' } } }
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```diff
|
|
49
|
+
- z.string().meta({ alias: ['v'] })
|
|
50
|
+
+ z.string().meta({ flags: ['v'] })
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Multi-char aliases remain as `alias`:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
{
|
|
57
|
+
fields: {
|
|
58
|
+
dryRun: {
|
|
59
|
+
alias: "dry-run";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- 186c2be: Improve help output formatting
|
|
66
|
+
|
|
67
|
+
- Use bracket convention for option types: `<type>` for required, `[type]` for optional, nothing for booleans
|
|
68
|
+
- Show kebab-case alias as primary name when available (e.g. `--dry-run` instead of `--dryRun`)
|
|
69
|
+
- Move choices and default values after the description
|
|
70
|
+
- Show array item types (e.g. `[string[]]` instead of `[array] (repeatable)`)
|
|
71
|
+
- Hide empty default values (empty strings and arrays)
|
|
72
|
+
- Cap description alignment at 32 characters
|
|
73
|
+
- Show `(stdin)` marker on arguments that accept stdin input
|
|
74
|
+
- Show `--no-` negation hint only when relevant (boolean options defaulting to true)
|
|
75
|
+
- Remove `[no-]` prefix from individual boolean options
|
|
76
|
+
|
|
3
77
|
## 1.2.0
|
|
4
78
|
|
|
5
79
|
### Minor Changes
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
//#region src/args.ts
|
|
2
2
|
/**
|
|
3
|
+
* Convert a camelCase string to kebab-case.
|
|
4
|
+
* Returns null if the string has no uppercase letters (no conversion needed).
|
|
5
|
+
*/
|
|
6
|
+
function camelToKebab(str) {
|
|
7
|
+
if (!/[A-Z]/.test(str)) return null;
|
|
8
|
+
return str.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
3
11
|
* Normalizes stdin config into its explicit form.
|
|
4
12
|
*/
|
|
5
13
|
function parseStdinConfig(stdin) {
|
|
@@ -24,40 +32,51 @@ function parsePositionalConfig(positional) {
|
|
|
24
32
|
};
|
|
25
33
|
});
|
|
26
34
|
}
|
|
35
|
+
function addEntries(target, key, items, filter) {
|
|
36
|
+
const list = typeof items === "string" ? [items] : items;
|
|
37
|
+
for (const item of list) if (typeof item === "string" && item && item !== key && !(item in target) && (!filter || filter(item))) target[item] = key;
|
|
38
|
+
}
|
|
27
39
|
/**
|
|
28
40
|
* Extract all arg metadata from schema and meta in a single pass.
|
|
29
|
-
*
|
|
41
|
+
* Returns flags (single-char, stackable) and aliases (multi-char, long names) separately.
|
|
42
|
+
* When `autoAlias` is true (default), camelCase property names automatically get kebab-case aliases.
|
|
30
43
|
*/
|
|
31
|
-
function extractSchemaMetadata(schema, meta) {
|
|
44
|
+
function extractSchemaMetadata(schema, meta, autoAlias) {
|
|
45
|
+
const flags = {};
|
|
32
46
|
const aliases = {};
|
|
33
47
|
if (meta) for (const [key, value] of Object.entries(meta)) {
|
|
34
48
|
if (!value) continue;
|
|
35
|
-
if (value.
|
|
36
|
-
|
|
37
|
-
for (const aliasKey of list) if (typeof aliasKey === "string" && aliasKey && aliasKey !== key) aliases[aliasKey] = key;
|
|
38
|
-
}
|
|
49
|
+
if (value.flags) addEntries(flags, key, value.flags, (item) => item.length === 1);
|
|
50
|
+
if (value.alias) addEntries(aliases, key, value.alias, (item) => item.length > 1);
|
|
39
51
|
}
|
|
40
52
|
try {
|
|
41
53
|
const jsonSchema = schema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
42
54
|
if (jsonSchema.type === "object" && jsonSchema.properties) for (const [propertyName, propertySchema] of Object.entries(jsonSchema.properties)) {
|
|
43
55
|
if (!propertySchema) continue;
|
|
56
|
+
const propFlags = propertySchema.flags;
|
|
57
|
+
if (propFlags) addEntries(flags, propertyName, propFlags, (item) => item.length === 1);
|
|
44
58
|
const propAlias = propertySchema.alias;
|
|
45
59
|
if (propAlias) {
|
|
46
60
|
const list = typeof propAlias === "string" ? [propAlias] : propAlias;
|
|
47
|
-
if (Array.isArray(list))
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
if (Array.isArray(list)) addEntries(aliases, propertyName, list, (item) => item.length > 1);
|
|
62
|
+
}
|
|
63
|
+
if (autoAlias !== false) {
|
|
64
|
+
const kebab = camelToKebab(propertyName);
|
|
65
|
+
if (kebab && !(kebab in aliases)) aliases[kebab] = propertyName;
|
|
50
66
|
}
|
|
51
67
|
}
|
|
52
68
|
} catch {}
|
|
53
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
flags,
|
|
71
|
+
aliases
|
|
72
|
+
};
|
|
54
73
|
}
|
|
55
|
-
function
|
|
74
|
+
function preprocessMappings(data, mappings) {
|
|
56
75
|
const result = { ...data };
|
|
57
|
-
for (const [
|
|
58
|
-
const
|
|
59
|
-
if (!(fullArgName in result)) result[fullArgName] =
|
|
60
|
-
delete result[
|
|
76
|
+
for (const [mappedKey, fullArgName] of Object.entries(mappings)) if (mappedKey in data && mappedKey !== fullArgName) {
|
|
77
|
+
const mappedValue = data[mappedKey];
|
|
78
|
+
if (!(fullArgName in result)) result[fullArgName] = mappedValue;
|
|
79
|
+
delete result[mappedKey];
|
|
61
80
|
}
|
|
62
81
|
return result;
|
|
63
82
|
}
|
|
@@ -79,7 +98,8 @@ function applyValues(data, values) {
|
|
|
79
98
|
*/
|
|
80
99
|
function preprocessArgs(data, ctx) {
|
|
81
100
|
let result = { ...data };
|
|
82
|
-
if (ctx.
|
|
101
|
+
if (ctx.flags && Object.keys(ctx.flags).length > 0) result = preprocessMappings(result, ctx.flags);
|
|
102
|
+
if (ctx.aliases && Object.keys(ctx.aliases).length > 0) result = preprocessMappings(result, ctx.aliases);
|
|
83
103
|
if (ctx.stdinData) result = applyValues(result, ctx.stdinData);
|
|
84
104
|
if (ctx.envData) result = applyValues(result, ctx.envData);
|
|
85
105
|
if (ctx.configData) result = applyValues(result, ctx.configData);
|
|
@@ -114,22 +134,24 @@ function coerceArgs(data, schema) {
|
|
|
114
134
|
if (value === "true" || value === "1") result[key] = true;
|
|
115
135
|
else if (value === "false" || value === "0") result[key] = false;
|
|
116
136
|
}
|
|
117
|
-
} else if (targetType === "array"
|
|
137
|
+
} else if (targetType === "array") {
|
|
138
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
118
139
|
const itemType = prop.items?.type;
|
|
119
|
-
if (itemType === "number" || itemType === "integer") result[key] =
|
|
140
|
+
if (itemType === "number" || itemType === "integer") result[key] = arr.map((v) => {
|
|
120
141
|
if (typeof v === "string") {
|
|
121
142
|
const num = Number(v);
|
|
122
143
|
return Number.isNaN(num) ? v : num;
|
|
123
144
|
}
|
|
124
145
|
return v;
|
|
125
146
|
});
|
|
126
|
-
else if (itemType === "boolean") result[key] =
|
|
147
|
+
else if (itemType === "boolean") result[key] = arr.map((v) => {
|
|
127
148
|
if (typeof v === "string") {
|
|
128
149
|
if (v === "true" || v === "1") return true;
|
|
129
150
|
if (v === "false" || v === "0") return false;
|
|
130
151
|
}
|
|
131
152
|
return v;
|
|
132
153
|
});
|
|
154
|
+
else if (!Array.isArray(value)) result[key] = arr;
|
|
133
155
|
}
|
|
134
156
|
}
|
|
135
157
|
return result;
|
|
@@ -141,7 +163,7 @@ const frameworkReservedKeys = new Set(["config", "c"]);
|
|
|
141
163
|
* Returns an array of { key, suggestion? } for each unknown key.
|
|
142
164
|
* Framework-reserved keys (--config, -c) are always allowed.
|
|
143
165
|
*/
|
|
144
|
-
function detectUnknownArgs(data, schema, aliases, suggestFn) {
|
|
166
|
+
function detectUnknownArgs(data, schema, flags, aliases, suggestFn) {
|
|
145
167
|
let properties;
|
|
146
168
|
let isLoose = false;
|
|
147
169
|
try {
|
|
@@ -155,6 +177,8 @@ function detectUnknownArgs(data, schema, aliases, suggestFn) {
|
|
|
155
177
|
if (isLoose) return [];
|
|
156
178
|
const knownKeys = new Set([
|
|
157
179
|
...Object.keys(properties),
|
|
180
|
+
...Object.keys(flags),
|
|
181
|
+
...Object.values(flags),
|
|
158
182
|
...Object.keys(aliases),
|
|
159
183
|
...Object.values(aliases)
|
|
160
184
|
]);
|
|
@@ -170,6 +194,6 @@ function detectUnknownArgs(data, schema, aliases, suggestFn) {
|
|
|
170
194
|
return unknowns;
|
|
171
195
|
}
|
|
172
196
|
//#endregion
|
|
173
|
-
export {
|
|
197
|
+
export { parsePositionalConfig as a, extractSchemaMetadata as i, coerceArgs as n, parseStdinConfig as o, detectUnknownArgs as r, preprocessArgs as s, camelToKebab as t };
|
|
174
198
|
|
|
175
|
-
//# sourceMappingURL=args-
|
|
199
|
+
//# sourceMappingURL=args-CVDbyyzG.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args-CVDbyyzG.mjs","names":[],"sources":["../src/args.ts"],"sourcesContent":["import type { StandardJSONSchemaV1 } from '@standard-schema/spec';\n\ntype Letter =\n | 'a'\n | 'b'\n | 'c'\n | 'd'\n | 'e'\n | 'f'\n | 'g'\n | 'h'\n | 'i'\n | 'j'\n | 'k'\n | 'l'\n | 'm'\n | 'n'\n | 'o'\n | 'p'\n | 'q'\n | 'r'\n | 's'\n | 't'\n | 'u'\n | 'v'\n | 'w'\n | 'x'\n | 'y'\n | 'z';\n\n/** A single letter character, valid as a short CLI flag (e.g. `'v'`, `'n'`, `'V'`). */\nexport type SingleChar = Letter | Uppercase<Letter>;\n\nexport interface PadroneFieldMeta {\n description?: string;\n /** Single-character short flags (stackable: `-abc` = `-a -b -c`). Used with single dash. */\n flags?: SingleChar[] | SingleChar;\n /** Multi-character alternative long names. Used with double dash (e.g. `--dry-run` for `--dryRun`). */\n alias?: string[] | string;\n deprecated?: boolean | string;\n hidden?: boolean;\n examples?: unknown[];\n}\n\ntype PositionalArgs<TObj> =\n TObj extends Record<string, any>\n ? {\n [K in keyof TObj]: NonNullable<TObj[K]> extends Array<any> ? `...${K & string}` | (K & string) : K & string;\n }[keyof TObj]\n : string;\n\n/**\n * Meta configuration for arguments, including positional arguments.\n * The `positional` array defines which arguments are positional and their order.\n * Use '...name' prefix to indicate variadic (rest) arguments, matching JS/TS rest syntax.\n *\n * @example\n * ```ts\n * .arguments(schema, {\n * positional: ['source', '...files', 'dest'], // '...files' is variadic\n * })\n * ```\n */\n/**\n * Configuration for reading from stdin and mapping it to an argument field.\n */\nexport type StdinConfig<TObj = Record<string, any>> =\n | (keyof TObj & string)\n | {\n /** The argument field to populate with stdin data. */\n field: keyof TObj & string;\n /**\n * How to consume stdin:\n * - `'text'` (default): read all stdin as a single string.\n * - `'lines'`: read stdin as an array of lines (string[]).\n */\n as?: 'text' | 'lines';\n };\n\nexport interface PadroneArgsSchemaMeta<TObj = Record<string, any>> {\n /**\n * Array of argument names that should be treated as positional arguments.\n * Order in array determines position. Use '...name' prefix for variadic args.\n * @example ['source', '...files', 'dest'] - 'files' captures multiple values\n */\n positional?: PositionalArgs<TObj>[];\n /**\n * Per-argument metadata.\n */\n fields?: { [K in keyof TObj]?: PadroneFieldMeta };\n /**\n * Automatically generate kebab-case aliases for camelCase option names.\n * For example, `dryRun` automatically gets `--dry-run` as an alias.\n * Defaults to `true`. Set to `false` to disable.\n *\n * @default true\n * @example\n * ```ts\n * // Auto-aliases enabled (default): --dry-run → dryRun\n * .arguments(z.object({ dryRun: z.boolean() }))\n *\n * // Disable auto-aliases\n * .arguments(z.object({ dryRun: z.boolean() }), { autoAlias: false })\n * ```\n */\n autoAlias?: boolean;\n /**\n * Read from stdin and inject the data into the specified argument field.\n * Only reads when stdin is piped (not a TTY) and the field wasn't already provided via CLI flags.\n *\n * - `string`: shorthand for `{ field: name, as: 'text' }` — read all stdin as a string.\n * - `{ field, as }`: explicit form. `as: 'text'` reads all stdin as a string,\n * `as: 'lines'` reads stdin as an array of line strings.\n *\n * Precedence: CLI flags > stdin > env vars > config file > schema defaults.\n *\n * @example\n * ```ts\n * // Shorthand: read all stdin as text into 'data' field\n * .arguments(z.object({ data: z.string() }), { stdin: 'data' })\n *\n * // Explicit: read stdin lines into 'lines' field\n * .arguments(z.object({ lines: z.string().array() }), {\n * stdin: { field: 'lines', as: 'lines' },\n * })\n * ```\n */\n stdin?: StdinConfig<TObj>;\n /**\n * Fields to interactively prompt for when their values are missing after CLI/env/config resolution.\n * - `true`: prompt for all required fields that are missing.\n * - `string[]`: prompt for these specific fields if missing.\n *\n * Interactive prompting only occurs in `cli()` when the runtime has `interactive: true`.\n * Setting this makes `parse()` and `cli()` return Promises.\n *\n * @example\n * ```ts\n * .arguments(schema, {\n * interactive: true, // prompt all missing required fields\n * interactive: ['name', 'template'], // prompt only these fields\n * })\n * ```\n */\n interactive?: true | (keyof TObj & string)[];\n /**\n * Optional fields offered after required interactive prompts.\n * Users are shown a multi-select to choose which of these fields to configure.\n * - `true`: offer all optional fields that are missing.\n * - `string[]`: offer these specific fields.\n *\n * @example\n * ```ts\n * .arguments(schema, {\n * interactive: ['name'],\n * optionalInteractive: ['typescript', 'eslint', 'prettier'],\n * })\n * ```\n */\n optionalInteractive?: true | (keyof TObj & string)[];\n}\n\n/**\n * Convert a camelCase string to kebab-case.\n * Returns null if the string has no uppercase letters (no conversion needed).\n */\nexport function camelToKebab(str: string): string | null {\n if (!/[A-Z]/.test(str)) return null;\n return str.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`);\n}\n\n/**\n * Normalizes stdin config into its explicit form.\n */\nexport function parseStdinConfig(stdin: StdinConfig): { field: string; as: 'text' | 'lines' } {\n if (typeof stdin === 'string') return { field: stdin, as: 'text' };\n return { field: stdin.field as string, as: stdin.as ?? 'text' };\n}\n\n/**\n * Parse positional configuration to extract names and variadic info.\n */\nexport function parsePositionalConfig(positional: string[]): { name: string; variadic: boolean }[] {\n return positional.map((p) => {\n const isVariadic = p.startsWith('...');\n const name = isVariadic ? p.slice(3) : p;\n return { name, variadic: isVariadic };\n });\n}\n\n/**\n * Result type for extractSchemaMetadata function.\n */\ninterface SchemaMetadataResult {\n /** Single-char flags: maps flag char → full arg name (e.g. `{ v: 'verbose' }`) */\n flags: Record<string, string>;\n /** Multi-char aliases: maps alias → full arg name (e.g. `{ 'dry-run': 'dryRun' }`) */\n aliases: Record<string, string>;\n}\n\nfunction addEntries(target: Record<string, string>, key: string, items: string | string[], filter?: (item: string) => boolean) {\n const list = typeof items === 'string' ? [items] : items;\n for (const item of list) {\n if (typeof item === 'string' && item && item !== key && !(item in target) && (!filter || filter(item))) {\n target[item] = key;\n }\n }\n}\n\n/**\n * Extract all arg metadata from schema and meta in a single pass.\n * Returns flags (single-char, stackable) and aliases (multi-char, long names) separately.\n * When `autoAlias` is true (default), camelCase property names automatically get kebab-case aliases.\n */\nexport function extractSchemaMetadata(\n schema: StandardJSONSchemaV1,\n meta?: Record<string, PadroneFieldMeta | undefined>,\n autoAlias?: boolean,\n): SchemaMetadataResult {\n const flags: Record<string, string> = {};\n const aliases: Record<string, string> = {};\n\n // Extract from meta object\n if (meta) {\n for (const [key, value] of Object.entries(meta)) {\n if (!value) continue;\n\n if (value.flags) {\n addEntries(flags, key, value.flags, (item) => item.length === 1);\n }\n if (value.alias) {\n addEntries(aliases, key, value.alias, (item) => item.length > 1);\n }\n }\n }\n\n // Extract from JSON schema properties\n try {\n const jsonSchema = schema['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;\n if (jsonSchema.type === 'object' && jsonSchema.properties) {\n for (const [propertyName, propertySchema] of Object.entries(jsonSchema.properties as Record<string, any>)) {\n if (!propertySchema) continue;\n\n // Extract flags from schema `.meta({ flags: ... })`\n const propFlags = propertySchema.flags;\n if (propFlags) {\n addEntries(flags, propertyName, propFlags, (item) => item.length === 1);\n }\n\n // Extract aliases from schema `.meta({ alias: ... })`\n const propAlias = propertySchema.alias;\n if (propAlias) {\n const list = typeof propAlias === 'string' ? [propAlias] : propAlias;\n if (Array.isArray(list)) {\n addEntries(aliases, propertyName, list, (item) => item.length > 1);\n }\n }\n\n // Auto-generate kebab-case alias for camelCase property names\n if (autoAlias !== false) {\n const kebab = camelToKebab(propertyName);\n if (kebab && !(kebab in aliases)) {\n aliases[kebab] = propertyName;\n }\n }\n }\n }\n } catch {\n // Ignore errors from JSON schema generation\n }\n\n return { flags, aliases };\n}\n\nfunction preprocessMappings(data: Record<string, unknown>, mappings: Record<string, string>): Record<string, unknown> {\n const result = { ...data };\n\n for (const [mappedKey, fullArgName] of Object.entries(mappings)) {\n if (mappedKey in data && mappedKey !== fullArgName) {\n const mappedValue = data[mappedKey];\n // Prefer full arg name if it exists\n if (!(fullArgName in result)) result[fullArgName] = mappedValue;\n delete result[mappedKey];\n }\n }\n\n return result;\n}\n\ninterface ParseArgsContext {\n flags?: Record<string, string>;\n aliases?: Record<string, string>;\n stdinData?: Record<string, unknown>;\n envData?: Record<string, unknown>;\n configData?: Record<string, unknown>;\n}\n\n/**\n * Apply values directly to arguments.\n * CLI values take precedence over the provided values.\n */\nfunction applyValues(data: Record<string, unknown>, values: Record<string, unknown>): Record<string, unknown> {\n const result = { ...data };\n\n for (const [key, value] of Object.entries(values)) {\n // Only apply value if arg wasn't already set\n if (key in result && result[key] !== undefined) continue;\n if (value !== undefined) {\n result[key] = value;\n }\n }\n\n return result;\n}\n\n/**\n * Combined preprocessing of arguments with all features.\n * Precedence order (highest to lowest): CLI args > stdin > env vars > config file\n */\nexport function preprocessArgs(data: Record<string, unknown>, ctx: ParseArgsContext): Record<string, unknown> {\n let result = { ...data };\n\n // 1. Apply flags and aliases first\n if (ctx.flags && Object.keys(ctx.flags).length > 0) {\n result = preprocessMappings(result, ctx.flags);\n }\n if (ctx.aliases && Object.keys(ctx.aliases).length > 0) {\n result = preprocessMappings(result, ctx.aliases);\n }\n\n // 2. Apply stdin data (higher precedence than env)\n // Only applies if CLI didn't set the arg\n if (ctx.stdinData) {\n result = applyValues(result, ctx.stdinData);\n }\n\n // 3. Apply environment variables (higher precedence than config)\n // These only apply if CLI/stdin didn't set the arg\n if (ctx.envData) {\n result = applyValues(result, ctx.envData);\n }\n\n // 4. Apply config file values (lowest precedence)\n // These only apply if neither CLI, stdin, nor env set the arg\n if (ctx.configData) {\n result = applyValues(result, ctx.configData);\n }\n\n return result;\n}\n\n/**\n * Auto-coerce CLI string values to match the expected schema types.\n * Handles: string → number, string → boolean for primitive schema fields.\n * Arrays of primitives are also coerced element-wise.\n */\nexport function coerceArgs(data: Record<string, unknown>, schema: StandardJSONSchemaV1): Record<string, unknown> {\n let properties: Record<string, any>;\n try {\n const jsonSchema = schema['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;\n if (jsonSchema.type !== 'object' || !jsonSchema.properties) return data;\n properties = jsonSchema.properties;\n } catch {\n return data;\n }\n\n const result = { ...data };\n\n for (const [key, value] of Object.entries(result)) {\n const prop = properties[key];\n if (!prop) continue;\n\n const targetType = prop.type as string | undefined;\n\n if (targetType === 'number' || targetType === 'integer') {\n if (typeof value === 'string') {\n const num = Number(value);\n if (!Number.isNaN(num)) result[key] = num;\n }\n } else if (targetType === 'boolean') {\n if (typeof value === 'string') {\n if (value === 'true' || value === '1') result[key] = true;\n else if (value === 'false' || value === '0') result[key] = false;\n }\n } else if (targetType === 'array') {\n // Coerce single items to array\n const arr = Array.isArray(value) ? value : [value];\n const itemType = prop.items?.type as string | undefined;\n if (itemType === 'number' || itemType === 'integer') {\n result[key] = arr.map((v) => {\n if (typeof v === 'string') {\n const num = Number(v);\n return Number.isNaN(num) ? v : num;\n }\n return v;\n });\n } else if (itemType === 'boolean') {\n result[key] = arr.map((v) => {\n if (typeof v === 'string') {\n if (v === 'true' || v === '1') return true;\n if (v === 'false' || v === '0') return false;\n }\n return v;\n });\n } else if (!Array.isArray(value)) {\n result[key] = arr;\n }\n }\n }\n\n return result;\n}\n\n/** Keys consumed by the CLI framework that are not user-defined args. */\nconst frameworkReservedKeys = new Set(['config', 'c']);\n\n/**\n * Detect unknown keys in the args that don't match any schema property.\n * Returns an array of { key, suggestion? } for each unknown key.\n * Framework-reserved keys (--config, -c) are always allowed.\n */\nexport function detectUnknownArgs(\n data: Record<string, unknown>,\n schema: StandardJSONSchemaV1,\n flags: Record<string, string>,\n aliases: Record<string, string>,\n suggestFn: (input: string, candidates: string[]) => string,\n): { key: string; suggestion: string }[] {\n let properties: Record<string, any>;\n let isLoose = false;\n try {\n const jsonSchema = schema['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;\n if (jsonSchema.type !== 'object' || !jsonSchema.properties) return [];\n properties = jsonSchema.properties;\n // If additionalProperties is set (true, {}, or a schema), the schema allows extra keys\n if (jsonSchema.additionalProperties !== undefined && jsonSchema.additionalProperties !== false) isLoose = true;\n } catch {\n return [];\n }\n\n if (isLoose) return [];\n\n const knownKeys = new Set<string>([\n ...Object.keys(properties),\n ...Object.keys(flags),\n ...Object.values(flags),\n ...Object.keys(aliases),\n ...Object.values(aliases),\n ]);\n const propertyNames = Object.keys(properties);\n const unknowns: { key: string; suggestion: string }[] = [];\n\n for (const key of Object.keys(data)) {\n if (!knownKeys.has(key) && !frameworkReservedKeys.has(key)) {\n const suggestion = suggestFn(key, propertyNames);\n unknowns.push({ key, suggestion });\n }\n }\n\n return unknowns;\n}\n"],"mappings":"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"}
|
package/dist/codegen/index.mjs
CHANGED
|
@@ -1234,9 +1234,19 @@ function generateCommandFile(command, ctx, options) {
|
|
|
1234
1234
|
function buildFieldsMap(fields) {
|
|
1235
1235
|
const entries = [];
|
|
1236
1236
|
for (const field of fields) if (field.aliases && field.aliases.length > 0) {
|
|
1237
|
-
const
|
|
1237
|
+
const singleChar = field.aliases.filter((a) => a.replace(/^-+/, "").length === 1).map((a) => a.replace(/^-+/, ""));
|
|
1238
|
+
const multiChar = field.aliases.filter((a) => a.replace(/^-+/, "").length > 1).map((a) => a.replace(/^-+/, ""));
|
|
1238
1239
|
const key = /^[a-zA-Z_$][\w$]*$/.test(field.name) ? field.name : JSON.stringify(field.name);
|
|
1239
|
-
|
|
1240
|
+
const parts = [];
|
|
1241
|
+
if (singleChar.length > 0) {
|
|
1242
|
+
const flags = singleChar.length === 1 ? JSON.stringify(singleChar[0]) : `[${singleChar.map((a) => JSON.stringify(a)).join(", ")}]`;
|
|
1243
|
+
parts.push(`flags: ${flags}`);
|
|
1244
|
+
}
|
|
1245
|
+
if (multiChar.length > 0) {
|
|
1246
|
+
const alias = multiChar.length === 1 ? JSON.stringify(multiChar[0]) : `[${multiChar.map((a) => JSON.stringify(a)).join(", ")}]`;
|
|
1247
|
+
parts.push(`alias: ${alias}`);
|
|
1248
|
+
}
|
|
1249
|
+
if (parts.length > 0) entries.push(`${key}: { ${parts.join(", ")} }`);
|
|
1240
1250
|
}
|
|
1241
1251
|
if (entries.length === 0) return null;
|
|
1242
1252
|
return `{ ${entries.join(", ")} }`;
|