padrone 1.4.0 → 1.6.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 +115 -0
- package/README.md +108 -283
- package/dist/args-Cnq0nwSM.mjs +272 -0
- package/dist/args-Cnq0nwSM.mjs.map +1 -0
- package/dist/codegen/index.d.mts +28 -3
- package/dist/codegen/index.d.mts.map +1 -1
- package/dist/codegen/index.mjs +169 -19
- package/dist/codegen/index.mjs.map +1 -1
- package/dist/commands-B_gufyR9.mjs +514 -0
- package/dist/commands-B_gufyR9.mjs.map +1 -0
- package/dist/{completion.mjs → completion-BEuflbDO.mjs} +86 -108
- package/dist/completion-BEuflbDO.mjs.map +1 -0
- package/dist/docs/index.d.mts +22 -2
- package/dist/docs/index.d.mts.map +1 -1
- package/dist/docs/index.mjs +92 -7
- package/dist/docs/index.mjs.map +1 -1
- package/dist/errors-CL63UOzt.mjs +137 -0
- package/dist/errors-CL63UOzt.mjs.map +1 -0
- package/dist/{formatter-ClUK5hcQ.d.mts → formatter-DrvhDMrq.d.mts} +35 -6
- package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
- package/dist/help-B5Kk83of.mjs +849 -0
- package/dist/help-B5Kk83of.mjs.map +1 -0
- package/dist/index-BaU3X6dY.d.mts +1178 -0
- package/dist/index-BaU3X6dY.d.mts.map +1 -0
- package/dist/index.d.mts +763 -36
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3608 -1534
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-BM-d0nZi.mjs +377 -0
- package/dist/mcp-BM-d0nZi.mjs.map +1 -0
- package/dist/serve-Bk0JUlCj.mjs +402 -0
- package/dist/serve-Bk0JUlCj.mjs.map +1 -0
- package/dist/stream-DC4H8YTx.mjs +77 -0
- package/dist/stream-DC4H8YTx.mjs.map +1 -0
- package/dist/test.d.mts +5 -8
- package/dist/test.d.mts.map +1 -1
- package/dist/test.mjs +5 -27
- package/dist/test.mjs.map +1 -1
- package/dist/{update-check-EbNDkzyV.mjs → update-check-CZ2VqjnV.mjs} +16 -17
- package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
- package/dist/zod.d.mts +32 -0
- package/dist/zod.d.mts.map +1 -0
- package/dist/zod.mjs +50 -0
- package/dist/zod.mjs.map +1 -0
- package/package.json +20 -9
- package/src/cli/completions.ts +14 -11
- package/src/cli/docs.ts +13 -16
- package/src/cli/doctor.ts +213 -24
- package/src/cli/index.ts +28 -82
- package/src/cli/init.ts +12 -10
- package/src/cli/link.ts +22 -18
- package/src/cli/wrap.ts +14 -11
- package/src/codegen/discovery.ts +80 -28
- package/src/codegen/index.ts +2 -1
- package/src/codegen/parsers/bash.ts +179 -0
- package/src/codegen/schema-to-code.ts +2 -1
- package/src/core/args.ts +296 -0
- package/src/core/commands.ts +373 -0
- package/src/core/create.ts +268 -0
- package/src/{runtime.ts → core/default-runtime.ts} +70 -135
- package/src/{errors.ts → core/errors.ts} +22 -0
- package/src/core/exec.ts +259 -0
- package/src/core/interceptors.ts +302 -0
- package/src/{parse.ts → core/parse.ts} +36 -89
- package/src/core/program-methods.ts +301 -0
- package/src/core/results.ts +229 -0
- package/src/core/runtime.ts +246 -0
- package/src/core/validate.ts +247 -0
- package/src/docs/index.ts +124 -11
- package/src/extension/auto-output.ts +95 -0
- package/src/extension/color.ts +38 -0
- package/src/extension/completion.ts +49 -0
- package/src/extension/config.ts +262 -0
- package/src/extension/env.ts +101 -0
- package/src/extension/help.ts +192 -0
- package/src/extension/index.ts +43 -0
- package/src/extension/ink.ts +93 -0
- package/src/extension/interactive.ts +106 -0
- package/src/extension/logger.ts +214 -0
- package/src/extension/man.ts +51 -0
- package/src/extension/mcp.ts +52 -0
- package/src/extension/progress-renderer.ts +338 -0
- package/src/extension/progress.ts +299 -0
- package/src/extension/repl.ts +94 -0
- package/src/extension/serve.ts +48 -0
- package/src/extension/signal.ts +87 -0
- package/src/extension/stdin.ts +62 -0
- package/src/extension/suggestions.ts +114 -0
- package/src/extension/timing.ts +81 -0
- package/src/extension/tracing.ts +175 -0
- package/src/extension/update-check.ts +77 -0
- package/src/extension/utils.ts +51 -0
- package/src/extension/version.ts +63 -0
- package/src/{completion.ts → feature/completion.ts} +130 -57
- package/src/{interactive.ts → feature/interactive.ts} +47 -6
- package/src/feature/mcp.ts +387 -0
- package/src/{repl-loop.ts → feature/repl-loop.ts} +26 -16
- package/src/feature/serve.ts +438 -0
- package/src/feature/test.ts +262 -0
- package/src/{update-check.ts → feature/update-check.ts} +16 -16
- package/src/{wrap.ts → feature/wrap.ts} +27 -27
- package/src/index.ts +120 -11
- package/src/output/colorizer.ts +154 -0
- package/src/{formatter.ts → output/formatter.ts} +281 -135
- package/src/{help.ts → output/help.ts} +62 -15
- package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
- package/src/schema/zod.ts +50 -0
- package/src/test.ts +2 -285
- package/src/types/args-meta.ts +151 -0
- package/src/types/builder.ts +697 -0
- package/src/types/command.ts +157 -0
- package/src/types/index.ts +59 -0
- package/src/types/interceptor.ts +296 -0
- package/src/types/preferences.ts +83 -0
- package/src/types/result.ts +71 -0
- package/src/types/schema.ts +19 -0
- package/src/util/dotenv.ts +244 -0
- package/src/{shell-utils.ts → util/shell-utils.ts} +26 -9
- package/src/util/stream.ts +101 -0
- package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
- package/src/{type-utils.ts → util/type-utils.ts} +99 -37
- package/src/util/utils.ts +51 -0
- package/src/zod.ts +1 -0
- package/dist/args-CVDbyyzG.mjs +0 -199
- package/dist/args-CVDbyyzG.mjs.map +0 -1
- package/dist/chunk-y_GBKt04.mjs +0 -5
- package/dist/completion.d.mts +0 -64
- package/dist/completion.d.mts.map +0 -1
- package/dist/completion.mjs.map +0 -1
- package/dist/formatter-ClUK5hcQ.d.mts.map +0 -1
- package/dist/help-CcBe91bV.mjs +0 -1254
- package/dist/help-CcBe91bV.mjs.map +0 -1
- package/dist/types-DjIdJN5G.d.mts +0 -1059
- package/dist/types-DjIdJN5G.d.mts.map +0 -1
- package/dist/update-check-EbNDkzyV.mjs.map +0 -1
- package/src/args.ts +0 -461
- package/src/colorizer.ts +0 -41
- package/src/command-utils.ts +0 -532
- package/src/create.ts +0 -1477
- package/src/types.ts +0 -1109
- package/src/utils.ts +0 -140
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { n as asyncStreamRegistry } from "./stream-DC4H8YTx.mjs";
|
|
2
|
+
//#region src/util/shell-utils.ts
|
|
3
|
+
/**
|
|
4
|
+
* Convert a camelCase string to kebab-case.
|
|
5
|
+
* Returns null if the string has no uppercase letters (no conversion needed).
|
|
6
|
+
*/
|
|
7
|
+
function camelToKebab(str) {
|
|
8
|
+
if (!/[A-Z]/.test(str)) return null;
|
|
9
|
+
return str.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Detects the current shell from environment variables and process info.
|
|
13
|
+
* @returns The detected shell type, or undefined if unknown
|
|
14
|
+
*/
|
|
15
|
+
async function detectShell() {
|
|
16
|
+
if (typeof process === "undefined") return void 0;
|
|
17
|
+
const shellEnv = process.env.SHELL || "";
|
|
18
|
+
if (shellEnv.includes("zsh")) return "zsh";
|
|
19
|
+
if (shellEnv.includes("bash")) return "bash";
|
|
20
|
+
if (shellEnv.includes("fish")) return "fish";
|
|
21
|
+
if (process.env.PSModulePath || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) return "powershell";
|
|
22
|
+
try {
|
|
23
|
+
const ppid = process.ppid;
|
|
24
|
+
if (ppid) {
|
|
25
|
+
const { execSync } = await import("node:child_process");
|
|
26
|
+
const processName = execSync(`ps -p ${ppid} -o comm=`, {
|
|
27
|
+
encoding: "utf-8",
|
|
28
|
+
stdio: [
|
|
29
|
+
"pipe",
|
|
30
|
+
"pipe",
|
|
31
|
+
"ignore"
|
|
32
|
+
]
|
|
33
|
+
}).trim();
|
|
34
|
+
if (processName.includes("zsh")) return "zsh";
|
|
35
|
+
if (processName.includes("bash")) return "bash";
|
|
36
|
+
if (processName.includes("fish")) return "fish";
|
|
37
|
+
}
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
async function getRcFile(shell, home) {
|
|
41
|
+
const { homedir } = await import("node:os");
|
|
42
|
+
const { join } = await import("node:path");
|
|
43
|
+
const h = home ?? homedir();
|
|
44
|
+
switch (shell) {
|
|
45
|
+
case "bash": return join(h, ".bashrc");
|
|
46
|
+
case "zsh": return join(h, ".zshrc");
|
|
47
|
+
case "fish": return join(h, ".config", "fish", "config.fish");
|
|
48
|
+
case "powershell": return (typeof process !== "undefined" ? process.env.PROFILE : void 0) || join(h, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
49
|
+
default: return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function escapeRegExp(str) {
|
|
53
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Writes a snippet to a shell config file using begin/end markers for idempotency.
|
|
57
|
+
* If a block with the same begin marker exists, it is replaced. Otherwise the snippet is appended.
|
|
58
|
+
*/
|
|
59
|
+
async function writeToRcFile(rcFile, snippet, beginMarker, endMarker) {
|
|
60
|
+
const { existsSync, mkdirSync, readFileSync, writeFileSync } = await import("node:fs");
|
|
61
|
+
const { dirname } = await import("node:path");
|
|
62
|
+
const existing = existsSync(rcFile) ? readFileSync(rcFile, "utf-8") : "";
|
|
63
|
+
if (existing.includes(beginMarker)) {
|
|
64
|
+
const pattern = new RegExp(`${escapeRegExp(beginMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}`);
|
|
65
|
+
writeFileSync(rcFile, existing.replace(pattern, snippet));
|
|
66
|
+
return {
|
|
67
|
+
file: rcFile,
|
|
68
|
+
updated: true
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
mkdirSync(dirname(rcFile), { recursive: true });
|
|
72
|
+
writeFileSync(rcFile, `${existing}${existing.length > 0 && !existing.endsWith("\n") ? "\n" : ""}\n${snippet}\n`);
|
|
73
|
+
return {
|
|
74
|
+
file: rcFile,
|
|
75
|
+
updated: false
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/core/args.ts
|
|
80
|
+
/** Extract the JSON schema from a Standard Schema, returning it as a plain record. */
|
|
81
|
+
function getJsonSchema(schema) {
|
|
82
|
+
return schema["~standard"].jsonSchema.input({
|
|
83
|
+
target: "draft-2020-12",
|
|
84
|
+
libraryOptions: { unrepresentable: "any" }
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function getFieldJsonSchema(schema, field) {
|
|
88
|
+
if (!schema) return void 0;
|
|
89
|
+
try {
|
|
90
|
+
const jsonSchema = getJsonSchema(schema);
|
|
91
|
+
if (jsonSchema.type === "object" && jsonSchema.properties) return jsonSchema.properties[field];
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Checks if a field in the schema is an array type (e.g. `z.string().array()`).
|
|
96
|
+
*/
|
|
97
|
+
function isArrayField(schema, field) {
|
|
98
|
+
return getFieldJsonSchema(schema, field)?.type === "array";
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Checks if a field is an async stream (marked with `asyncStream()` metadata).
|
|
102
|
+
* Returns the item schema if provided, or `true` if it's a plain string stream.
|
|
103
|
+
*/
|
|
104
|
+
function isAsyncStreamField(schema, field) {
|
|
105
|
+
const asyncStreamId = getFieldJsonSchema(schema, field)?.asyncStream;
|
|
106
|
+
if (asyncStreamId && asyncStreamRegistry.has(asyncStreamId)) return { itemSchema: asyncStreamRegistry.get(asyncStreamId)?.itemSchema };
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Parse positional configuration to extract names and variadic info.
|
|
111
|
+
*/
|
|
112
|
+
function parsePositionalConfig(positional) {
|
|
113
|
+
return positional.map((p) => {
|
|
114
|
+
const variadic = p.startsWith("...");
|
|
115
|
+
return {
|
|
116
|
+
name: variadic ? p.slice(3) : p,
|
|
117
|
+
variadic
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function addEntries(target, key, items, filter) {
|
|
122
|
+
const list = typeof items === "string" ? [items] : items;
|
|
123
|
+
for (const item of list) if (typeof item === "string" && item && item !== key && !(item in target) && (!filter || filter(item))) target[item] = key;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Extract all arg metadata from schema and meta in a single pass.
|
|
127
|
+
* Returns flags (single-char, stackable) and aliases (multi-char, long names) separately.
|
|
128
|
+
* When `autoAlias` is true (default), camelCase property names automatically get kebab-case aliases.
|
|
129
|
+
*/
|
|
130
|
+
function extractSchemaMetadata(schema, meta, autoAlias) {
|
|
131
|
+
const flags = {};
|
|
132
|
+
const aliases = {};
|
|
133
|
+
if (meta) for (const [key, value] of Object.entries(meta)) {
|
|
134
|
+
if (!value) continue;
|
|
135
|
+
if (value.flags) addEntries(flags, key, value.flags, (item) => item.length === 1);
|
|
136
|
+
if (value.alias) addEntries(aliases, key, value.alias, (item) => item.length > 1);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const jsonSchema = getJsonSchema(schema);
|
|
140
|
+
if (jsonSchema.type === "object" && jsonSchema.properties) for (const [propertyName, propertySchema] of Object.entries(jsonSchema.properties)) {
|
|
141
|
+
if (!propertySchema) continue;
|
|
142
|
+
const propFlags = propertySchema.flags;
|
|
143
|
+
if (propFlags) addEntries(flags, propertyName, propFlags, (item) => item.length === 1);
|
|
144
|
+
const propAlias = propertySchema.alias;
|
|
145
|
+
if (propAlias) {
|
|
146
|
+
const list = typeof propAlias === "string" ? [propAlias] : propAlias;
|
|
147
|
+
if (Array.isArray(list)) addEntries(aliases, propertyName, list, (item) => item.length > 1);
|
|
148
|
+
}
|
|
149
|
+
if (autoAlias !== false) {
|
|
150
|
+
const kebab = camelToKebab(propertyName);
|
|
151
|
+
if (kebab && !(kebab in aliases)) aliases[kebab] = propertyName;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch {}
|
|
155
|
+
return {
|
|
156
|
+
flags,
|
|
157
|
+
aliases
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function preprocessMappings(data, mappings) {
|
|
161
|
+
const result = { ...data };
|
|
162
|
+
for (const [mappedKey, fullArgName] of Object.entries(mappings)) if (mappedKey in data && mappedKey !== fullArgName) {
|
|
163
|
+
const mappedValue = data[mappedKey];
|
|
164
|
+
if (!(fullArgName in result)) result[fullArgName] = mappedValue;
|
|
165
|
+
delete result[mappedKey];
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Apply values to arguments using "set if not present" semantics.
|
|
171
|
+
* Existing values take precedence — only fills in undefined or missing keys.
|
|
172
|
+
*/
|
|
173
|
+
function applyValues(data, values) {
|
|
174
|
+
const result = { ...data };
|
|
175
|
+
for (const [key, value] of Object.entries(values)) {
|
|
176
|
+
if (key in result && result[key] !== void 0) continue;
|
|
177
|
+
if (value !== void 0) result[key] = value;
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
/** Applies flag and alias mappings to raw arguments. */
|
|
182
|
+
function preprocessArgs(data, ctx) {
|
|
183
|
+
let result = { ...data };
|
|
184
|
+
if (ctx.flags && Object.keys(ctx.flags).length > 0) result = preprocessMappings(result, ctx.flags);
|
|
185
|
+
if (ctx.aliases && Object.keys(ctx.aliases).length > 0) result = preprocessMappings(result, ctx.aliases);
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Auto-coerce CLI string values to match the expected schema types.
|
|
190
|
+
* Handles: string → number, string → boolean for primitive schema fields.
|
|
191
|
+
* Arrays of primitives are also coerced element-wise.
|
|
192
|
+
*/
|
|
193
|
+
function coerceArgs(data, schema) {
|
|
194
|
+
let properties;
|
|
195
|
+
try {
|
|
196
|
+
const jsonSchema = getJsonSchema(schema);
|
|
197
|
+
if (jsonSchema.type !== "object" || !jsonSchema.properties) return data;
|
|
198
|
+
properties = jsonSchema.properties;
|
|
199
|
+
} catch {
|
|
200
|
+
return data;
|
|
201
|
+
}
|
|
202
|
+
const result = { ...data };
|
|
203
|
+
for (const [key, value] of Object.entries(result)) {
|
|
204
|
+
const prop = properties[key];
|
|
205
|
+
if (!prop) continue;
|
|
206
|
+
const targetType = prop.type;
|
|
207
|
+
if (targetType === "number" || targetType === "integer") {
|
|
208
|
+
if (typeof value === "string") {
|
|
209
|
+
const num = Number(value);
|
|
210
|
+
if (!Number.isNaN(num)) result[key] = num;
|
|
211
|
+
}
|
|
212
|
+
} else if (targetType === "boolean") {
|
|
213
|
+
if (typeof value === "string") {
|
|
214
|
+
const lower = value.toLowerCase();
|
|
215
|
+
if (lower === "true" || lower === "1" || lower === "yes" || lower === "on") result[key] = true;
|
|
216
|
+
else if (lower === "false" || lower === "0" || lower === "no" || lower === "off") result[key] = false;
|
|
217
|
+
}
|
|
218
|
+
} else if (targetType === "array") {
|
|
219
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
220
|
+
const itemType = prop.items?.type;
|
|
221
|
+
if (itemType === "number" || itemType === "integer") result[key] = arr.map((v) => {
|
|
222
|
+
if (typeof v === "string") {
|
|
223
|
+
const num = Number(v);
|
|
224
|
+
return Number.isNaN(num) ? v : num;
|
|
225
|
+
}
|
|
226
|
+
return v;
|
|
227
|
+
});
|
|
228
|
+
else if (itemType === "boolean") result[key] = arr.map((v) => {
|
|
229
|
+
if (typeof v === "string") {
|
|
230
|
+
const lower = v.toLowerCase();
|
|
231
|
+
if (lower === "true" || lower === "1" || lower === "yes" || lower === "on") return true;
|
|
232
|
+
if (lower === "false" || lower === "0" || lower === "no" || lower === "off") return false;
|
|
233
|
+
}
|
|
234
|
+
return v;
|
|
235
|
+
});
|
|
236
|
+
else if (!Array.isArray(value)) result[key] = arr;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Detect unknown keys in the args that don't match any schema property.
|
|
243
|
+
* Returns an array of { key } for each unknown key.
|
|
244
|
+
* Framework-reserved keys (--config, -c) are always allowed.
|
|
245
|
+
*/
|
|
246
|
+
function detectUnknownArgs(data, schema, flags, aliases) {
|
|
247
|
+
let properties;
|
|
248
|
+
let isLoose = false;
|
|
249
|
+
try {
|
|
250
|
+
const jsonSchema = getJsonSchema(schema);
|
|
251
|
+
if (jsonSchema.type !== "object" || !jsonSchema.properties) return [];
|
|
252
|
+
properties = jsonSchema.properties;
|
|
253
|
+
if (jsonSchema.additionalProperties !== void 0 && jsonSchema.additionalProperties !== false) isLoose = true;
|
|
254
|
+
} catch {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
if (isLoose) return [];
|
|
258
|
+
const knownKeys = new Set([
|
|
259
|
+
...Object.keys(properties),
|
|
260
|
+
...Object.keys(flags),
|
|
261
|
+
...Object.values(flags),
|
|
262
|
+
...Object.keys(aliases),
|
|
263
|
+
...Object.values(aliases)
|
|
264
|
+
]);
|
|
265
|
+
const unknowns = [];
|
|
266
|
+
for (const key of Object.keys(data)) if (!knownKeys.has(key)) unknowns.push({ key });
|
|
267
|
+
return unknowns;
|
|
268
|
+
}
|
|
269
|
+
//#endregion
|
|
270
|
+
export { getJsonSchema as a, parsePositionalConfig as c, detectShell as d, escapeRegExp as f, extractSchemaMetadata as i, preprocessArgs as l, writeToRcFile as m, coerceArgs as n, isArrayField as o, getRcFile as p, detectUnknownArgs as r, isAsyncStreamField as s, applyValues as t, camelToKebab as u };
|
|
271
|
+
|
|
272
|
+
//# sourceMappingURL=args-Cnq0nwSM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args-Cnq0nwSM.mjs","names":[],"sources":["../src/util/shell-utils.ts","../src/core/args.ts"],"sourcesContent":["/**\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\nexport type ShellType = 'bash' | 'zsh' | 'fish' | 'powershell';\n\n/**\n * Detects the current shell from environment variables and process info.\n * @returns The detected shell type, or undefined if unknown\n */\nexport async function detectShell(): Promise<ShellType | undefined> {\n if (typeof process === 'undefined') return undefined;\n\n // Method 1: Check SHELL environment variable (most common)\n const shellEnv = process.env.SHELL || '';\n if (shellEnv.includes('zsh')) return 'zsh';\n if (shellEnv.includes('bash')) return 'bash';\n if (shellEnv.includes('fish')) return 'fish';\n\n // Method 2: Check Windows-specific shells\n if (process.env.PSModulePath || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) {\n return 'powershell';\n }\n\n // Method 3: Check parent process on Unix-like systems\n try {\n const ppid = process.ppid;\n if (ppid) {\n const { execSync } = (await import('node:child_process')) as typeof import('node:child_process');\n const processName = execSync(`ps -p ${ppid} -o comm=`, {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'ignore'],\n }).trim();\n\n if (processName.includes('zsh')) return 'zsh';\n if (processName.includes('bash')) return 'bash';\n if (processName.includes('fish')) return 'fish';\n }\n } catch {\n // Ignore errors (e.g., ps not available)\n }\n\n return undefined;\n}\n\nexport async function getRcFile(shell: ShellType, home?: string): Promise<string | null> {\n const { homedir } = await import('node:os');\n const { join } = await import('node:path');\n const h = home ?? homedir();\n switch (shell) {\n case 'bash':\n return join(h, '.bashrc');\n case 'zsh':\n return join(h, '.zshrc');\n case 'fish':\n return join(h, '.config', 'fish', 'config.fish');\n case 'powershell':\n return (\n (typeof process !== 'undefined' ? process.env.PROFILE : undefined) ||\n join(h, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1')\n );\n default:\n return null;\n }\n}\n\nexport function escapeRegExp(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Writes a snippet to a shell config file using begin/end markers for idempotency.\n * If a block with the same begin marker exists, it is replaced. Otherwise the snippet is appended.\n */\nexport async function writeToRcFile(\n rcFile: string,\n snippet: string,\n beginMarker: string,\n endMarker: string,\n): Promise<{ file: string; updated: boolean }> {\n const { existsSync, mkdirSync, readFileSync, writeFileSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n const existing = existsSync(rcFile) ? readFileSync(rcFile, 'utf-8') : '';\n\n if (existing.includes(beginMarker)) {\n const pattern = new RegExp(`${escapeRegExp(beginMarker)}[\\\\s\\\\S]*?${escapeRegExp(endMarker)}`);\n writeFileSync(rcFile, existing.replace(pattern, snippet));\n return { file: rcFile, updated: true };\n }\n\n mkdirSync(dirname(rcFile), { recursive: true });\n const separator = existing.length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n writeFileSync(rcFile, `${existing}${separator}\\n${snippet}\\n`);\n return { file: rcFile, updated: false };\n}\n","import type { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';\nimport type { PadroneFieldMeta } from '../types/args-meta.ts';\nimport { camelToKebab } from '../util/shell-utils.ts';\nimport { asyncStreamRegistry } from '../util/stream.ts';\n\nexport type { PadroneArgsSchemaMeta, PadroneFieldMeta, SingleChar, StdinConfig } from '../types/args-meta.ts';\n\n/** Extract the JSON schema from a Standard Schema, returning it as a plain record. */\nexport function getJsonSchema(schema: StandardJSONSchemaV1): Record<string, any> {\n return schema['~standard'].jsonSchema.input({\n target: 'draft-2020-12',\n libraryOptions: { unrepresentable: 'any' },\n }) as Record<string, any>;\n}\n\nfunction getFieldJsonSchema(schema: StandardJSONSchemaV1 | undefined, field: string): Record<string, any> | undefined {\n if (!schema) return undefined;\n try {\n const jsonSchema = getJsonSchema(schema);\n if (jsonSchema.type === 'object' && jsonSchema.properties) return jsonSchema.properties[field];\n } catch {}\n return undefined;\n}\n\n/**\n * Checks if a field in the schema is an array type (e.g. `z.string().array()`).\n */\nexport function isArrayField(schema: StandardJSONSchemaV1 | undefined, field: string): boolean {\n return getFieldJsonSchema(schema, field)?.type === 'array';\n}\n\n/**\n * Checks if a field is an async stream (marked with `asyncStream()` metadata).\n * Returns the item schema if provided, or `true` if it's a plain string stream.\n */\nexport function isAsyncStreamField(schema: StandardJSONSchemaV1 | undefined, field: string): { itemSchema?: StandardSchemaV1 } | false {\n const prop = getFieldJsonSchema(schema, field);\n const asyncStreamId = prop?.asyncStream;\n if (asyncStreamId && asyncStreamRegistry.has(asyncStreamId)) {\n const meta = asyncStreamRegistry.get(asyncStreamId);\n return { itemSchema: meta?.itemSchema };\n }\n\n return false;\n}\n\n/**\n * Parse positional configuration to extract names and variadic info.\n */\nexport function parsePositionalConfig(positional: readonly string[]): { name: string; variadic: boolean }[] {\n return positional.map((p) => {\n const variadic = p.startsWith('...');\n const name = variadic ? p.slice(3) : p;\n return { name, variadic };\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 | readonly 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 = getJsonSchema(schema) 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\n/**\n * Apply values to arguments using \"set if not present\" semantics.\n * Existing values take precedence — only fills in undefined or missing keys.\n */\nexport function 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 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/** Applies flag and alias mappings to raw arguments. */\nexport function preprocessArgs(\n data: Record<string, unknown>,\n ctx: { flags?: Record<string, string>; aliases?: Record<string, string> },\n): Record<string, unknown> {\n let result = { ...data };\n\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 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 = getJsonSchema(schema) 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 const lower = value.toLowerCase();\n if (lower === 'true' || lower === '1' || lower === 'yes' || lower === 'on') result[key] = true;\n else if (lower === 'false' || lower === '0' || lower === 'no' || lower === 'off') 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 const lower = v.toLowerCase();\n if (lower === 'true' || lower === '1' || lower === 'yes' || lower === 'on') return true;\n if (lower === 'false' || lower === '0' || lower === 'no' || lower === 'off') 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/**\n * Detect unknown keys in the args that don't match any schema property.\n * Returns an array of { key } 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): { key: string }[] {\n let properties: Record<string, any>;\n let isLoose = false;\n try {\n const jsonSchema = getJsonSchema(schema) 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 unknowns: { key: string }[] = [];\n\n for (const key of Object.keys(data)) {\n if (!knownKeys.has(key)) {\n unknowns.push({ key });\n }\n }\n\n return unknowns;\n}\n"],"mappings":"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"}
|
package/dist/codegen/index.d.mts
CHANGED
|
@@ -149,11 +149,15 @@ type TemplateFunction = (text: string) => (data: Record<string, unknown>) => str
|
|
|
149
149
|
declare function createCodeBuilder(): CodeBuilder;
|
|
150
150
|
//#endregion
|
|
151
151
|
//#region src/codegen/discovery.d.ts
|
|
152
|
-
type DiscoverySource = 'help' | 'fish' | 'zsh';
|
|
152
|
+
type DiscoverySource = 'help' | 'completion' | 'bash' | 'fish' | 'zsh';
|
|
153
153
|
interface DiscoveryOptions {
|
|
154
154
|
/** The command to discover (e.g. 'gh', 'docker', 'kubectl'). */
|
|
155
155
|
command: string;
|
|
156
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Which parsing sources to use. Default: ['help'].
|
|
158
|
+
* Use `'completion'` to auto-detect the best shell completion source
|
|
159
|
+
* by probing `<cmd> completion <shell>` (bash → fish → zsh).
|
|
160
|
+
*/
|
|
157
161
|
sources?: DiscoverySource[];
|
|
158
162
|
/** Max subcommand depth. 0 = root only, undefined = unlimited. */
|
|
159
163
|
depth?: number;
|
|
@@ -177,6 +181,12 @@ interface DiscoveryResult {
|
|
|
177
181
|
* parsing shell completion scripts.
|
|
178
182
|
*/
|
|
179
183
|
declare function discoverCli(options: DiscoveryOptions): Promise<DiscoveryResult>;
|
|
184
|
+
/**
|
|
185
|
+
* Detect the best shell for completion parsing by probing the command.
|
|
186
|
+
* Tries `<cmd> completion <shell>` for bash, fish, zsh (in that order).
|
|
187
|
+
* Returns the shell name if successful, or null if no completion command exists.
|
|
188
|
+
*/
|
|
189
|
+
declare function detectCompletionShell(command: string, timeout?: number): Promise<'bash' | 'fish' | 'zsh' | null>;
|
|
180
190
|
//#endregion
|
|
181
191
|
//#region src/codegen/file-emitter.d.ts
|
|
182
192
|
/**
|
|
@@ -224,6 +234,21 @@ interface CommandTreeOptions {
|
|
|
224
234
|
*/
|
|
225
235
|
declare function generateCommandTree(root: CommandMeta, ctx: GeneratorContext, options?: CommandTreeOptions): void;
|
|
226
236
|
//#endregion
|
|
237
|
+
//#region src/codegen/parsers/bash.d.ts
|
|
238
|
+
/**
|
|
239
|
+
* Parse bash completion scripts into CommandMeta.
|
|
240
|
+
*
|
|
241
|
+
* Bash completions typically use `complete -F <func> <command>` and define
|
|
242
|
+
* a function that sets COMPREPLY. Common patterns:
|
|
243
|
+
*
|
|
244
|
+
* local commands="init build deploy"
|
|
245
|
+
* local args="--verbose --output --format"
|
|
246
|
+
* case "$prev" in --format) COMPREPLY=($(compgen -W "json yaml toml" ...)) ;;
|
|
247
|
+
* COMPREPLY=($(compgen -W "$commands" ...))
|
|
248
|
+
* COMPREPLY=($(compgen -W "$args" ...))
|
|
249
|
+
*/
|
|
250
|
+
declare function parseBashCompletions(text: string): CommandMeta;
|
|
251
|
+
//#endregion
|
|
227
252
|
//#region src/codegen/parsers/fish.d.ts
|
|
228
253
|
/**
|
|
229
254
|
* Parse fish shell completion scripts into CommandMeta.
|
|
@@ -301,5 +326,5 @@ type TemplateRenderer = (data: Record<string, unknown>, partials?: Record<string
|
|
|
301
326
|
*/
|
|
302
327
|
declare function template(text: string): TemplateRenderer;
|
|
303
328
|
//#endregion
|
|
304
|
-
export { type CodeBuildResult, type CodeBuilder, type CommandFileOptions, type CommandMeta, type CommandTreeOptions, type DiscoveryOptions, type DiscoveryResult, type DiscoverySource, type EmitResult, type FieldMeta, type FileEmitter, type FileEmitterOptions, type GeneratorContext, type GeneratorLogger, type TemplateFunction, createCodeBuilder, createFileEmitter, discoverCli, fieldMetaToCode, generateBarrelFile, generateCommandFile, generateCommandTree, mergeCommandMeta, parseFishCompletions, parseHelpOutput, parseZshCompletions, schemaToCode, template };
|
|
329
|
+
export { type CodeBuildResult, type CodeBuilder, type CommandFileOptions, type CommandMeta, type CommandTreeOptions, type DiscoveryOptions, type DiscoveryResult, type DiscoverySource, type EmitResult, type FieldMeta, type FileEmitter, type FileEmitterOptions, type GeneratorContext, type GeneratorLogger, type TemplateFunction, createCodeBuilder, createFileEmitter, detectCompletionShell, discoverCli, fieldMetaToCode, generateBarrelFile, generateCommandFile, generateCommandTree, mergeCommandMeta, parseBashCompletions, parseFishCompletions, parseHelpOutput, parseZshCompletions, schemaToCode, template };
|
|
305
330
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/codegen/types.ts","../../src/codegen/code-builder.ts","../../src/codegen/discovery.ts","../../src/codegen/file-emitter.ts","../../src/codegen/generators/barrel-file.ts","../../src/codegen/generators/command-file.ts","../../src/codegen/generators/command-tree.ts","../../src/codegen/parsers/fish.ts","../../src/codegen/parsers/help.ts","../../src/codegen/parsers/merge.ts","../../src/codegen/parsers/zsh.ts","../../src/codegen/schema-to-code.ts","../../src/codegen/template.ts"],"mappings":";;;;;;UAGiB,SAAA;EACf,IAAA;EACA,IAAA;EAFwB;EAIxB,KAAA;EAFA;EAIA,UAAA;EACA,WAAA;EACA,OAAA;EACA,QAAA;EACA,OAAA;EACA,UAAA;EAAA;EAEA,SAAA;AAAA;;AAOF;;;UAAiB,WAAA;EACf,IAAA;EACA,WAAA;EACA,OAAA;EAMyB;EAJzB,SAAA,GAAY,SAAA;EAHZ;EAKA,WAAA,GAAc,SAAA;EAFd;EAIA,WAAA,GAAc,WAAA;EACd,QAAA;EACA,UAAA;AAAA;;;;UAMe,eAAA;EACf,IAAA,CAAK,OAAA;EACL,IAAA,CAAK,OAAA;EACL,KAAA,CAAM,OAAA;EACN,OAAA,CAAQ,OAAA;AAAA;;;;UAMO,gBAAA;EAPf;EASA,MAAA;EARA;EAUA,iBAAA,QAAyB,WAAA;EAVF;EAYvB,OAAA,EAAS,WAAA;EANM;EAQf,QAAA,EAAU,gBAAA;;EAEV,GAAA,EAAK,eAAA;AAAA;;;;UAMU,eAAA;EAdf;EAgBA,IAAA;EAdyB;EAgBzB,OAAA,EAAS,GAAA;IAAc,UAAA,EAAY,GAAA;IAAa,QAAA;EAAA;AAAA;;;;UAMjC,WAAA;EAVe;EAY9B,MAAA,CAAO,SAAA,qBAA8B,MAAA,WAAiB,WAAA;EAR1C;EAUZ,aAAA,CAAc,IAAA,UAAc,MAAA,WAAiB,WAAA;EAV7C;EAYA,UAAA,CAAW,SAAA,qBAA8B,MAAA,WAAiB,WAAA;EAZnC;EAcvB,IAAA,CAAK,IAAA,YAAgB,WAAA;EAd2B;EAgBhD,KAAA,CAAM,OAAA,GAAU,CAAA,EAAG,WAAA,KAAgB,WAAA,GAAc,WAAA;EAhBO;EAkBxD,KAAA,CAAM,IAAA,UAAc,OAAA,GAAU,CAAA,EAAG,WAAA,KAAgB,WAAA,EAAa,KAAA,YAAiB,WAAA;EAZrD;EAc1B,KAAA,CAAM,IAAA,UAAc,KAAA,UAAe,OAAA,GAAU,CAAA,EAAG,WAAA,KAAgB,WAAA,GAAc,WAAA;EAZxB;EActD,OAAA,CAAQ,IAAA,WAAe,WAAA;EAVmC;EAY1D,UAAA,CAAW,IAAA,WAAe,WAAA;EARP;EAUnB,WAAA,CAAY,IAAA,WAAe,WAAA;EAVsB;EAYjD,GAAA,CAAI,IAAA,WAAe,WAAA;EAV8B;EAYjD,KAAA,IAAS,eAAA;AAAA;;;;UAMM,UAAA;EAVY;EAY3B,OAAA;EARS;EAUT,OAAA;EAVwB;EAYxB,MAAA;IAAU,IAAA;IAAc,KAAA,EAAO,KAAA;EAAA;AAAA;;;;UAMhB,kBAAA;EApCJ;EAsCX,MAAA;EAtC0D;EAwC1D,MAAA;EAtCK;EAwCL,SAAA;EAtCA;EAwCA,MAAA;AAAA;;;;UAMe,WAAA;EA5CT;EA8CN,OAAA,CAAQ,IAAA,UAAc,OAAA,WAAkB,eAAA;EA9CV;EAgD9B,IAAA,IAAQ,OAAA,CAAQ,UAAA;AAAA;;;;KAMN,gBAAA,IAAoB,IAAA,cAAkB,IAAA,EAAM,MAAA;;;;;AA3IxD;iBC4NgB,iBAAA,CAAA,GAAqB,WAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/codegen/types.ts","../../src/codegen/code-builder.ts","../../src/codegen/discovery.ts","../../src/codegen/file-emitter.ts","../../src/codegen/generators/barrel-file.ts","../../src/codegen/generators/command-file.ts","../../src/codegen/generators/command-tree.ts","../../src/codegen/parsers/bash.ts","../../src/codegen/parsers/fish.ts","../../src/codegen/parsers/help.ts","../../src/codegen/parsers/merge.ts","../../src/codegen/parsers/zsh.ts","../../src/codegen/schema-to-code.ts","../../src/codegen/template.ts"],"mappings":"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"}
|
package/dist/codegen/index.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { a as getJsonSchema } from "../args-Cnq0nwSM.mjs";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
1
4
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
5
|
import { dirname, join, resolve } from "node:path";
|
|
3
6
|
//#region src/codegen/code-builder.ts
|
|
@@ -182,6 +185,115 @@ function createCodeBuilder() {
|
|
|
182
185
|
return new CodeBuilderImpl();
|
|
183
186
|
}
|
|
184
187
|
//#endregion
|
|
188
|
+
//#region src/codegen/parsers/bash.ts
|
|
189
|
+
/**
|
|
190
|
+
* Parse bash completion scripts into CommandMeta.
|
|
191
|
+
*
|
|
192
|
+
* Bash completions typically use `complete -F <func> <command>` and define
|
|
193
|
+
* a function that sets COMPREPLY. Common patterns:
|
|
194
|
+
*
|
|
195
|
+
* local commands="init build deploy"
|
|
196
|
+
* local args="--verbose --output --format"
|
|
197
|
+
* case "$prev" in --format) COMPREPLY=($(compgen -W "json yaml toml" ...)) ;;
|
|
198
|
+
* COMPREPLY=($(compgen -W "$commands" ...))
|
|
199
|
+
* COMPREPLY=($(compgen -W "$args" ...))
|
|
200
|
+
*/
|
|
201
|
+
function parseBashCompletions(text) {
|
|
202
|
+
const result = {
|
|
203
|
+
name: "",
|
|
204
|
+
arguments: [],
|
|
205
|
+
subcommands: []
|
|
206
|
+
};
|
|
207
|
+
const completeMatch = text.match(/complete\s+(?:[^-]|-[^F])*-F\s+\S+\s+(\S+)/);
|
|
208
|
+
if (completeMatch) result.name = completeMatch[1];
|
|
209
|
+
if (!result.name) {
|
|
210
|
+
const markerMatch = text.match(/###-begin-(\S+)-completion-###/);
|
|
211
|
+
if (markerMatch) result.name = markerMatch[1];
|
|
212
|
+
}
|
|
213
|
+
const joined = text.replace(/\\\n\s*/g, " ");
|
|
214
|
+
const variables = extractVariables(joined);
|
|
215
|
+
const commandWords = variables.get("commands") ?? variables.get("cmds") ?? variables.get("subcommands");
|
|
216
|
+
if (commandWords) for (const name of splitWords(commandWords)) result.subcommands.push({ name });
|
|
217
|
+
const argWords = variables.get("args") ?? variables.get("opts") ?? variables.get("options") ?? variables.get("flags");
|
|
218
|
+
const optionNames = /* @__PURE__ */ new Set();
|
|
219
|
+
if (argWords) {
|
|
220
|
+
for (const word of splitWords(argWords)) if (word.startsWith("-")) optionNames.add(word);
|
|
221
|
+
}
|
|
222
|
+
const compgenRegex = /compgen\s+-W\s+["']([^"']+)["']/g;
|
|
223
|
+
let compgenMatch;
|
|
224
|
+
while ((compgenMatch = compgenRegex.exec(joined)) !== null) {
|
|
225
|
+
if (compgenMatch[1].startsWith("$")) continue;
|
|
226
|
+
for (const word of splitWords(compgenMatch[1])) if (word.startsWith("-")) optionNames.add(word);
|
|
227
|
+
}
|
|
228
|
+
const enumMap = parseCaseStatement(joined);
|
|
229
|
+
const seenArgs = /* @__PURE__ */ new Set();
|
|
230
|
+
for (const rawName of optionNames) {
|
|
231
|
+
if (rawName === "--help" || rawName === "-h" || rawName === "--version" || rawName === "-V") continue;
|
|
232
|
+
const name = rawName.replace(/^-+/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
233
|
+
if (!name || seenArgs.has(name)) continue;
|
|
234
|
+
seenArgs.add(name);
|
|
235
|
+
const enumValues = enumMap.get(rawName);
|
|
236
|
+
const isShort = rawName.startsWith("-") && !rawName.startsWith("--") && rawName.length === 2;
|
|
237
|
+
const field = {
|
|
238
|
+
name,
|
|
239
|
+
type: enumValues ? "enum" : "string",
|
|
240
|
+
enumValues,
|
|
241
|
+
ambiguous: !enumValues
|
|
242
|
+
};
|
|
243
|
+
if (isShort) field.aliases = [rawName];
|
|
244
|
+
result.arguments.push(field);
|
|
245
|
+
}
|
|
246
|
+
if (result.arguments.length === 0) delete result.arguments;
|
|
247
|
+
if (result.subcommands.length === 0) delete result.subcommands;
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Extract variable assignments: `local VAR="value"`, `VAR="value"`, `local VAR='value'`
|
|
252
|
+
*/
|
|
253
|
+
function extractVariables(text) {
|
|
254
|
+
const vars = /* @__PURE__ */ new Map();
|
|
255
|
+
const regex = /(?:local\s+)?(\w+)=["']([^"']+)["']/g;
|
|
256
|
+
let match;
|
|
257
|
+
while ((match = regex.exec(text)) !== null) vars.set(match[1], match[2]);
|
|
258
|
+
return vars;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Parse case statements to find option → enum value mappings.
|
|
262
|
+
*
|
|
263
|
+
* Matches patterns like:
|
|
264
|
+
* case "$prev" in
|
|
265
|
+
* --format) COMPREPLY=($(compgen -W "json yaml toml" ...)) ;;
|
|
266
|
+
* --env|--environment) COMPREPLY=($(compgen -W "dev staging prod" ...)) ;;
|
|
267
|
+
*/
|
|
268
|
+
function parseCaseStatement(text) {
|
|
269
|
+
const result = /* @__PURE__ */ new Map();
|
|
270
|
+
const caseRegex = /case\s+[^i]*\bin\b\s*([\s\S]*?)esac/g;
|
|
271
|
+
let caseMatch;
|
|
272
|
+
while ((caseMatch = caseRegex.exec(text)) !== null) {
|
|
273
|
+
const branches = caseMatch[1].split(";;");
|
|
274
|
+
for (const branch of branches) {
|
|
275
|
+
const patternMatch = branch.match(/^\s*([-\w|]+)\)/);
|
|
276
|
+
if (!patternMatch) continue;
|
|
277
|
+
const compgenMatch = branch.match(/compgen\s+-W\s+["']([^"']+)["']/);
|
|
278
|
+
if (!compgenMatch) continue;
|
|
279
|
+
const values = compgenMatch[1];
|
|
280
|
+
const words = splitWords(values).filter((w) => !w.startsWith("$"));
|
|
281
|
+
if (words.length === 0) continue;
|
|
282
|
+
for (const pattern of patternMatch[1].split("|")) {
|
|
283
|
+
const trimmed = pattern.trim();
|
|
284
|
+
if (trimmed.startsWith("-")) result.set(trimmed, words);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Split a space-separated word list, filtering empty strings.
|
|
292
|
+
*/
|
|
293
|
+
function splitWords(text) {
|
|
294
|
+
return text.split(/\s+/).filter(Boolean);
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
185
297
|
//#region src/codegen/parsers/fish.ts
|
|
186
298
|
/**
|
|
187
299
|
* Parse fish shell completion scripts into CommandMeta.
|
|
@@ -792,7 +904,8 @@ function findSubcommands(text) {
|
|
|
792
904
|
* parsing shell completion scripts.
|
|
793
905
|
*/
|
|
794
906
|
async function discoverCli(options) {
|
|
795
|
-
const { command, sources = ["help"], depth, delay = 50, log, timeout = 1e4 } = options;
|
|
907
|
+
const { command, sources: rawSources = ["help"], depth, delay = 50, log, timeout = 1e4 } = options;
|
|
908
|
+
const sources = await resolveSources(rawSources, command, timeout, log);
|
|
796
909
|
const warnings = [];
|
|
797
910
|
let invocations = 0;
|
|
798
911
|
const results = [];
|
|
@@ -812,6 +925,12 @@ async function discoverCli(options) {
|
|
|
812
925
|
});
|
|
813
926
|
results.push(helpResult);
|
|
814
927
|
}
|
|
928
|
+
if (sources.includes("bash")) {
|
|
929
|
+
log?.info(`Parsing bash completions for ${command}...`);
|
|
930
|
+
const bashText = await getCompletionScript(command, "bash", timeout);
|
|
931
|
+
if (bashText) results.push(parseBashCompletions(bashText));
|
|
932
|
+
else warnings.push("Could not obtain bash completion script");
|
|
933
|
+
}
|
|
815
934
|
if (sources.includes("fish")) {
|
|
816
935
|
log?.info(`Parsing fish completions for ${command}...`);
|
|
817
936
|
const fishText = await getCompletionScript(command, "fish", timeout);
|
|
@@ -875,20 +994,17 @@ async function runHelp(command, args, timeout) {
|
|
|
875
994
|
*/
|
|
876
995
|
async function runCommand(command, args, timeout) {
|
|
877
996
|
try {
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
997
|
+
const { stdout, stderr } = await new Promise((resolve) => {
|
|
998
|
+
execFile(command, args, {
|
|
999
|
+
timeout,
|
|
1000
|
+
maxBuffer: 10 * 1024 * 1024
|
|
1001
|
+
}, (_error, stdout, stderr) => {
|
|
1002
|
+
resolve({
|
|
1003
|
+
stdout: (stdout ?? "").trim(),
|
|
1004
|
+
stderr: (stderr ?? "").trim()
|
|
1005
|
+
});
|
|
1006
|
+
});
|
|
882
1007
|
});
|
|
883
|
-
const timer = setTimeout(() => proc.kill(), timeout);
|
|
884
|
-
const [_exitCode, stdoutBuf, stderrBuf] = await Promise.all([
|
|
885
|
-
proc.exited,
|
|
886
|
-
new Response(proc.stdout).arrayBuffer(),
|
|
887
|
-
new Response(proc.stderr).arrayBuffer()
|
|
888
|
-
]);
|
|
889
|
-
clearTimeout(timer);
|
|
890
|
-
const stdout = new TextDecoder().decode(stdoutBuf).trim();
|
|
891
|
-
const stderr = new TextDecoder().decode(stderrBuf).trim();
|
|
892
1008
|
const combined = stdout || stderr;
|
|
893
1009
|
if (!combined) return null;
|
|
894
1010
|
if (combined.length < 10) return null;
|
|
@@ -906,13 +1022,47 @@ async function getCompletionScript(command, shell, timeout) {
|
|
|
906
1022
|
if (result) return result;
|
|
907
1023
|
result = await runCommand(command, ["completions", shell], timeout);
|
|
908
1024
|
if (result) return result;
|
|
909
|
-
const paths = shell === "fish" ? [`/usr/share/fish/vendor_completions.d/${command}.fish`, `/usr/local/share/fish/vendor_completions.d/${command}.fish`] : [
|
|
1025
|
+
const paths = shell === "fish" ? [`/usr/share/fish/vendor_completions.d/${command}.fish`, `/usr/local/share/fish/vendor_completions.d/${command}.fish`] : shell === "bash" ? [
|
|
1026
|
+
`/usr/share/bash-completion/completions/${command}`,
|
|
1027
|
+
`/usr/local/share/bash-completion/completions/${command}`,
|
|
1028
|
+
`/etc/bash_completion.d/${command}`
|
|
1029
|
+
] : [`/usr/share/zsh/site-functions/_${command}`, `/usr/local/share/zsh/site-functions/_${command}`];
|
|
910
1030
|
for (const path of paths) try {
|
|
911
|
-
|
|
912
|
-
if (await file.exists()) return await file.text();
|
|
1031
|
+
return await readFile(path, "utf-8");
|
|
913
1032
|
} catch {}
|
|
914
1033
|
return null;
|
|
915
1034
|
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Detect the best shell for completion parsing by probing the command.
|
|
1037
|
+
* Tries `<cmd> completion <shell>` for bash, fish, zsh (in that order).
|
|
1038
|
+
* Returns the shell name if successful, or null if no completion command exists.
|
|
1039
|
+
*/
|
|
1040
|
+
async function detectCompletionShell(command, timeout = 5e3) {
|
|
1041
|
+
for (const shell of [
|
|
1042
|
+
"bash",
|
|
1043
|
+
"fish",
|
|
1044
|
+
"zsh"
|
|
1045
|
+
]) if (await getCompletionScript(command, shell, timeout)) return shell;
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Resolve 'completion' entries in the sources array by probing for the best available shell.
|
|
1050
|
+
* Other sources are passed through unchanged.
|
|
1051
|
+
*/
|
|
1052
|
+
async function resolveSources(sources, command, timeout, log) {
|
|
1053
|
+
if (!sources.includes("completion")) return sources;
|
|
1054
|
+
log?.info(`Probing ${command} for completion command...`);
|
|
1055
|
+
const shell = await detectCompletionShell(command, timeout);
|
|
1056
|
+
return sources.flatMap((s) => {
|
|
1057
|
+
if (s !== "completion") return s;
|
|
1058
|
+
if (shell) {
|
|
1059
|
+
log?.info(` Found ${shell} completion support`);
|
|
1060
|
+
return shell;
|
|
1061
|
+
}
|
|
1062
|
+
log?.info(" No completion command found, skipping");
|
|
1063
|
+
return [];
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
916
1066
|
function sleep(ms) {
|
|
917
1067
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
918
1068
|
}
|
|
@@ -1011,7 +1161,7 @@ function jsonSchemaPropertyToZod(prop, required, ambiguous) {
|
|
|
1011
1161
|
*/
|
|
1012
1162
|
function schemaToCode(schema) {
|
|
1013
1163
|
try {
|
|
1014
|
-
return jsonSchemaToCode(schema
|
|
1164
|
+
return jsonSchemaToCode(getJsonSchema(schema));
|
|
1015
1165
|
} catch {
|
|
1016
1166
|
return {
|
|
1017
1167
|
code: "z.unknown()",
|
|
@@ -1353,6 +1503,6 @@ function render(text, data, partials) {
|
|
|
1353
1503
|
return result;
|
|
1354
1504
|
}
|
|
1355
1505
|
//#endregion
|
|
1356
|
-
export { createCodeBuilder, createFileEmitter, discoverCli, fieldMetaToCode, generateBarrelFile, generateCommandFile, generateCommandTree, mergeCommandMeta, parseFishCompletions, parseHelpOutput, parseZshCompletions, schemaToCode, template };
|
|
1506
|
+
export { createCodeBuilder, createFileEmitter, detectCompletionShell, discoverCli, fieldMetaToCode, generateBarrelFile, generateCommandFile, generateCommandTree, mergeCommandMeta, parseBashCompletions, parseFishCompletions, parseHelpOutput, parseZshCompletions, schemaToCode, template };
|
|
1357
1507
|
|
|
1358
1508
|
//# sourceMappingURL=index.mjs.map
|