padrone 1.5.0 → 1.7.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 +44 -0
- package/README.md +15 -11
- package/dist/{args-D5PNDyNu.mjs → args-Cnq0nwSM.mjs} +91 -41
- package/dist/args-Cnq0nwSM.mjs.map +1 -0
- package/dist/codegen/index.mjs +4 -4
- 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} +12 -82
- package/dist/completion-BEuflbDO.mjs.map +1 -0
- package/dist/docs/index.d.mts +4 -4
- package/dist/docs/index.d.mts.map +1 -1
- package/dist/docs/index.mjs +10 -12
- package/dist/docs/index.mjs.map +1 -1
- package/dist/{errors-BiVrBgi6.mjs → errors-DA4KzK1M.mjs} +26 -3
- package/dist/errors-DA4KzK1M.mjs.map +1 -0
- package/dist/{formatter-DtHzbP22.d.mts → formatter-DrvhDMrq.d.mts} +3 -3
- package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
- package/dist/{help-bbmu9-qd.mjs → help-BtxLgrF_.mjs} +190 -43
- package/dist/help-BtxLgrF_.mjs.map +1 -0
- package/dist/{types-Ch8Mk6Qb.d.mts → index-D6-7dz0l.d.mts} +634 -745
- package/dist/index-D6-7dz0l.d.mts.map +1 -0
- package/dist/index.d.mts +869 -36
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3884 -1699
- package/dist/index.mjs.map +1 -1
- package/dist/{mcp-mLWIdUIu.mjs → mcp-6-Jw4Bpq.mjs} +13 -15
- package/dist/mcp-6-Jw4Bpq.mjs.map +1 -0
- package/dist/{serve-B0u43DK7.mjs → serve-YVTPzBCl.mjs} +12 -14
- package/dist/serve-YVTPzBCl.mjs.map +1 -0
- package/dist/{stream-BcC146Ud.mjs → stream-DC4H8YTx.mjs} +24 -3
- 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 +2 -13
- package/dist/test.mjs.map +1 -1
- package/dist/{update-check-CFX1FV3v.mjs → update-check-CZ2VqjnV.mjs} +16 -17
- package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
- package/dist/zod.d.mts +2 -2
- package/dist/zod.d.mts.map +1 -1
- package/dist/zod.mjs +2 -2
- package/dist/zod.mjs.map +1 -1
- package/package.json +15 -12
- package/src/cli/completions.ts +14 -11
- package/src/cli/docs.ts +13 -10
- package/src/cli/doctor.ts +22 -18
- package/src/cli/index.ts +28 -82
- package/src/cli/init.ts +10 -7
- package/src/cli/link.ts +20 -16
- package/src/cli/wrap.ts +14 -11
- package/src/codegen/schema-to-code.ts +2 -2
- package/src/{args.ts → core/args.ts} +32 -225
- package/src/core/commands.ts +373 -0
- package/src/core/create.ts +301 -0
- package/src/core/default-runtime.ts +239 -0
- 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 +12 -13
- package/src/extension/auto-output.ts +146 -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 +44 -0
- package/src/extension/ink.ts +93 -0
- package/src/extension/interactive.ts +106 -0
- package/src/extension/logger.ts +262 -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} +12 -12
- package/src/{interactive.ts → feature/interactive.ts} +4 -4
- package/src/{mcp.ts → feature/mcp.ts} +12 -15
- package/src/{repl-loop.ts → feature/repl-loop.ts} +10 -13
- package/src/{serve.ts → feature/serve.ts} +11 -15
- 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} +10 -8
- package/src/index.ts +115 -30
- package/src/{formatter.ts → output/formatter.ts} +124 -176
- package/src/{help.ts → output/help.ts} +22 -8
- package/src/output/output-indicator.ts +87 -0
- package/src/output/primitives.ts +335 -0
- package/src/output/styling.ts +221 -0
- package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
- package/src/schema/zod.ts +50 -0
- package/src/test.ts +2 -276
- package/src/types/args-meta.ts +151 -0
- package/src/types/builder.ts +718 -0
- package/src/types/command.ts +157 -0
- package/src/types/index.ts +60 -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/{stream.ts → util/stream.ts} +27 -1
- package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
- package/src/{type-utils.ts → util/type-utils.ts} +71 -33
- package/src/util/utils.ts +51 -0
- package/src/zod.ts +1 -50
- package/dist/args-D5PNDyNu.mjs.map +0 -1
- package/dist/chunk-CjcI7cDX.mjs +0 -15
- package/dist/command-utils-B1D-HqCd.mjs +0 -1117
- package/dist/command-utils-B1D-HqCd.mjs.map +0 -1
- package/dist/completion.d.mts +0 -64
- package/dist/completion.d.mts.map +0 -1
- package/dist/completion.mjs.map +0 -1
- package/dist/errors-BiVrBgi6.mjs.map +0 -1
- package/dist/formatter-DtHzbP22.d.mts.map +0 -1
- package/dist/help-bbmu9-qd.mjs.map +0 -1
- package/dist/mcp-mLWIdUIu.mjs.map +0 -1
- package/dist/serve-B0u43DK7.mjs.map +0 -1
- package/dist/stream-BcC146Ud.mjs.map +0 -1
- package/dist/types-Ch8Mk6Qb.d.mts.map +0 -1
- package/dist/update-check-CFX1FV3v.mjs.map +0 -1
- package/src/command-utils.ts +0 -882
- package/src/create.ts +0 -1829
- package/src/runtime.ts +0 -497
- package/src/types.ts +0 -1291
- package/src/utils.ts +0 -140
- /package/src/{colorizer.ts → output/colorizer.ts} +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A schema that supports both validation (StandardSchemaV1) and JSON schema generation (StandardJSONSchemaV1).
|
|
5
|
+
* This is the type required for command arguments in Padrone.
|
|
6
|
+
*/
|
|
7
|
+
export type PadroneSchema<Input = unknown, Output = Input> = StandardSchemaV1<Input, Output> & StandardJSONSchemaV1<Input, Output>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A schema branded as async. When passed to `.arguments()`, `.configFile()`, or `.env()`,
|
|
11
|
+
* the command is automatically marked as async, causing `parse()` and `cli()` to return Promises.
|
|
12
|
+
*
|
|
13
|
+
* Use the `asyncSchema()` helper to brand an existing schema:
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { asyncSchema } from 'padrone';
|
|
16
|
+
* const schema = asyncSchema(z.object({ name: z.string() }).check(async (v) => { ... }));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export type AsyncPadroneSchema<Input = unknown, Output = Input> = PadroneSchema<Input, Output> & { '~async': true };
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// ── File resolution ─────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
/** Returns ordered list of `.env` file names to load (no fs access). */
|
|
4
|
+
export function resolveEnvFiles(modes: string[] = [], local = true, base = true): string[] {
|
|
5
|
+
const files: string[] = [];
|
|
6
|
+
if (base) {
|
|
7
|
+
files.push('.env');
|
|
8
|
+
if (local) files.push('.env.local');
|
|
9
|
+
}
|
|
10
|
+
for (const mode of modes) {
|
|
11
|
+
files.push(`.env.${mode}`);
|
|
12
|
+
if (local) files.push(`.env.${mode}.local`);
|
|
13
|
+
}
|
|
14
|
+
return files;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ── Parser ──────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/** Parse a `.env` file string into key-value pairs. */
|
|
20
|
+
export function parseEnvFile(content: string): Record<string, string> {
|
|
21
|
+
const result: Record<string, string> = {};
|
|
22
|
+
const lines = content.split('\n');
|
|
23
|
+
let i = 0;
|
|
24
|
+
|
|
25
|
+
while (i < lines.length) {
|
|
26
|
+
const line = lines[i]!.trim();
|
|
27
|
+
i++;
|
|
28
|
+
|
|
29
|
+
// Skip empty lines and comments
|
|
30
|
+
if (!line || line.startsWith('#')) continue;
|
|
31
|
+
|
|
32
|
+
// Strip optional `export ` prefix
|
|
33
|
+
const stripped = line.startsWith('export ') ? line.slice(7) : line;
|
|
34
|
+
|
|
35
|
+
const eqIndex = stripped.indexOf('=');
|
|
36
|
+
if (eqIndex === -1) continue;
|
|
37
|
+
|
|
38
|
+
const key = stripped.slice(0, eqIndex).trim();
|
|
39
|
+
let raw = stripped.slice(eqIndex + 1);
|
|
40
|
+
|
|
41
|
+
// Detect quoted values
|
|
42
|
+
const trimmedRaw = raw.trimStart();
|
|
43
|
+
const quote = trimmedRaw[0];
|
|
44
|
+
|
|
45
|
+
if (quote === '"' || quote === "'" || quote === '`') {
|
|
46
|
+
let value = trimmedRaw.slice(1);
|
|
47
|
+
|
|
48
|
+
// Check for closing quote on the same line
|
|
49
|
+
const closeIndex = findClosingQuote(value, quote);
|
|
50
|
+
if (closeIndex !== -1) {
|
|
51
|
+
value = value.slice(0, closeIndex);
|
|
52
|
+
} else {
|
|
53
|
+
// Multiline: accumulate until closing quote
|
|
54
|
+
while (i < lines.length) {
|
|
55
|
+
const nextLine = lines[i]!;
|
|
56
|
+
i++;
|
|
57
|
+
const ci = findClosingQuote(nextLine, quote);
|
|
58
|
+
if (ci !== -1) {
|
|
59
|
+
value += `\n${nextLine.slice(0, ci)}`;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
value += `\n${nextLine}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (quote === '"') value = unescapeDoubleQuoted(value);
|
|
67
|
+
result[key] = value;
|
|
68
|
+
} else {
|
|
69
|
+
// Unquoted: strip inline comments, trim
|
|
70
|
+
const commentIndex = raw.indexOf(' #');
|
|
71
|
+
if (commentIndex !== -1) raw = raw.slice(0, commentIndex);
|
|
72
|
+
result[key] = raw.trim();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function findClosingQuote(s: string, quote: string): number {
|
|
80
|
+
let i = 0;
|
|
81
|
+
while (i < s.length) {
|
|
82
|
+
if (s[i] === '\\' && quote === '"') {
|
|
83
|
+
i += 2; // skip escaped char
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (s[i] === quote) return i;
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function unescapeDoubleQuoted(s: string): string {
|
|
93
|
+
return s.replace(/\\n/g, '\n').replace(/\\r/g, '\r').replace(/\\t/g, '\t').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Variable expansion ──────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Expand `$VAR`, `${VAR}`, `${VAR:-default}`, `${VAR-default}` in a string.
|
|
100
|
+
* Escaped `\$` produces a literal `$`. Undefined variables resolve to `""`.
|
|
101
|
+
*/
|
|
102
|
+
export function expandVariables(value: string, env: Record<string, string | undefined>): string {
|
|
103
|
+
let result = '';
|
|
104
|
+
let i = 0;
|
|
105
|
+
|
|
106
|
+
while (i < value.length) {
|
|
107
|
+
if (value[i] === '\\' && value[i + 1] === '$') {
|
|
108
|
+
result += '$';
|
|
109
|
+
i += 2;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (value[i] === '$') {
|
|
114
|
+
i++;
|
|
115
|
+
if (i >= value.length) {
|
|
116
|
+
result += '$';
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (value[i] === '{') {
|
|
121
|
+
// ${VAR}, ${VAR:-default}, ${VAR-default}
|
|
122
|
+
i++;
|
|
123
|
+
const closeIdx = value.indexOf('}', i);
|
|
124
|
+
if (closeIdx === -1) {
|
|
125
|
+
result += `\${${value.slice(i)}`;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const expr = value.slice(i, closeIdx);
|
|
130
|
+
i = closeIdx + 1;
|
|
131
|
+
|
|
132
|
+
const colonDashIdx = expr.indexOf(':-');
|
|
133
|
+
const dashIdx = colonDashIdx === -1 ? expr.indexOf('-') : -1;
|
|
134
|
+
|
|
135
|
+
if (colonDashIdx !== -1) {
|
|
136
|
+
// ${VAR:-default} — use default if unset or empty
|
|
137
|
+
const varName = expr.slice(0, colonDashIdx);
|
|
138
|
+
const fallback = expr.slice(colonDashIdx + 2);
|
|
139
|
+
const val = env[varName];
|
|
140
|
+
result += val ? val : expandVariables(fallback, env);
|
|
141
|
+
} else if (dashIdx !== -1) {
|
|
142
|
+
// ${VAR-default} — use default only if unset
|
|
143
|
+
const varName = expr.slice(0, dashIdx);
|
|
144
|
+
const fallback = expr.slice(dashIdx + 1);
|
|
145
|
+
const val = env[varName];
|
|
146
|
+
result += val !== undefined ? val : expandVariables(fallback, env);
|
|
147
|
+
} else {
|
|
148
|
+
result += env[expr] ?? '';
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// $VAR — collect word chars
|
|
152
|
+
let varName = '';
|
|
153
|
+
while (i < value.length && /[\w]/.test(value[i]!)) {
|
|
154
|
+
varName += value[i];
|
|
155
|
+
i++;
|
|
156
|
+
}
|
|
157
|
+
if (varName) {
|
|
158
|
+
result += env[varName] ?? '';
|
|
159
|
+
} else {
|
|
160
|
+
result += '$';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
result += value[i];
|
|
167
|
+
i++;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── File loading ────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
export type LoadEnvFilesOptions = {
|
|
176
|
+
dir?: string;
|
|
177
|
+
modes?: string[];
|
|
178
|
+
local?: boolean;
|
|
179
|
+
override?: boolean;
|
|
180
|
+
base?: boolean;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Load and merge `.env` files, returning the combined key-value map.
|
|
185
|
+
* Variable expansion uses the merged file values + process env as lookup.
|
|
186
|
+
*
|
|
187
|
+
* Returns synchronously when `node:fs`/`node:path` are already cached (typical),
|
|
188
|
+
* or a Promise on the very first call.
|
|
189
|
+
*/
|
|
190
|
+
export function loadEnvFiles(
|
|
191
|
+
options: LoadEnvFilesOptions,
|
|
192
|
+
processEnv: Record<string, string | undefined>,
|
|
193
|
+
): Record<string, string> | Promise<Record<string, string>> {
|
|
194
|
+
if (typeof process === 'undefined') return {};
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
if (_fs && _path) return loadEnvFilesSync(_fs, _path, options, processEnv);
|
|
198
|
+
return initNodeModules()
|
|
199
|
+
.then(() => loadEnvFilesSync(_fs!, _path!, options, processEnv))
|
|
200
|
+
.catch(() => ({}) as Record<string, string>);
|
|
201
|
+
} catch {
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Internals ───────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
let _fs: typeof import('node:fs') | undefined;
|
|
209
|
+
let _path: typeof import('node:path') | undefined;
|
|
210
|
+
|
|
211
|
+
async function initNodeModules(): Promise<void> {
|
|
212
|
+
if (_fs && _path) return;
|
|
213
|
+
_fs = await import('node:fs');
|
|
214
|
+
_path = await import('node:path');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (typeof process !== 'undefined') initNodeModules();
|
|
218
|
+
|
|
219
|
+
function loadEnvFilesSync(
|
|
220
|
+
fs: typeof import('node:fs'),
|
|
221
|
+
path: typeof import('node:path'),
|
|
222
|
+
options: LoadEnvFilesOptions,
|
|
223
|
+
processEnv: Record<string, string | undefined>,
|
|
224
|
+
): Record<string, string> {
|
|
225
|
+
const dir = options.dir ?? process.cwd();
|
|
226
|
+
const fileNames = resolveEnvFiles(options.modes, options.local ?? true, options.base ?? true);
|
|
227
|
+
const merged: Record<string, string> = {};
|
|
228
|
+
|
|
229
|
+
for (const name of fileNames) {
|
|
230
|
+
const filePath = path.resolve(dir, name);
|
|
231
|
+
if (!fs.existsSync(filePath)) continue;
|
|
232
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
233
|
+
const parsed = parseEnvFile(content);
|
|
234
|
+
Object.assign(merged, parsed);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Expand variables: file values + processEnv as lookup (processEnv wins in lookup unless override)
|
|
238
|
+
const lookup = options.override ? { ...processEnv, ...merged } : { ...merged, ...processEnv };
|
|
239
|
+
for (const key of Object.keys(merged)) {
|
|
240
|
+
merged[key] = expandVariables(merged[key]!, lookup);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return merged;
|
|
244
|
+
}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a camelCase string to kebab-case.
|
|
3
|
+
* Returns null if the string has no uppercase letters (no conversion needed).
|
|
4
|
+
*/
|
|
5
|
+
export function camelToKebab(str: string): string | null {
|
|
6
|
+
if (!/[A-Z]/.test(str)) return null;
|
|
7
|
+
return str.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`);
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
export type ShellType = 'bash' | 'zsh' | 'fish' | 'powershell';
|
|
2
11
|
|
|
3
12
|
/**
|
|
4
13
|
* Detects the current shell from environment variables and process info.
|
|
5
14
|
* @returns The detected shell type, or undefined if unknown
|
|
6
15
|
*/
|
|
7
|
-
export function detectShell(): ShellType | undefined {
|
|
16
|
+
export async function detectShell(): Promise<ShellType | undefined> {
|
|
8
17
|
if (typeof process === 'undefined') return undefined;
|
|
9
18
|
|
|
10
19
|
// Method 1: Check SHELL environment variable (most common)
|
|
@@ -22,7 +31,7 @@ export function detectShell(): ShellType | undefined {
|
|
|
22
31
|
try {
|
|
23
32
|
const ppid = process.ppid;
|
|
24
33
|
if (ppid) {
|
|
25
|
-
const { execSync } =
|
|
34
|
+
const { execSync } = (await import('node:child_process')) as typeof import('node:child_process');
|
|
26
35
|
const processName = execSync(`ps -p ${ppid} -o comm=`, {
|
|
27
36
|
encoding: 'utf-8',
|
|
28
37
|
stdio: ['pipe', 'pipe', 'ignore'],
|
|
@@ -39,9 +48,9 @@ export function detectShell(): ShellType | undefined {
|
|
|
39
48
|
return undefined;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
export function getRcFile(shell: ShellType, home?: string): string | null {
|
|
43
|
-
const { homedir } =
|
|
44
|
-
const { join } =
|
|
51
|
+
export async function getRcFile(shell: ShellType, home?: string): Promise<string | null> {
|
|
52
|
+
const { homedir } = await import('node:os');
|
|
53
|
+
const { join } = await import('node:path');
|
|
45
54
|
const h = home ?? homedir();
|
|
46
55
|
switch (shell) {
|
|
47
56
|
case 'bash':
|
|
@@ -51,7 +60,10 @@ export function getRcFile(shell: ShellType, home?: string): string | null {
|
|
|
51
60
|
case 'fish':
|
|
52
61
|
return join(h, '.config', 'fish', 'config.fish');
|
|
53
62
|
case 'powershell':
|
|
54
|
-
return
|
|
63
|
+
return (
|
|
64
|
+
(typeof process !== 'undefined' ? process.env.PROFILE : undefined) ||
|
|
65
|
+
join(h, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1')
|
|
66
|
+
);
|
|
55
67
|
default:
|
|
56
68
|
return null;
|
|
57
69
|
}
|
|
@@ -65,9 +77,14 @@ export function escapeRegExp(str: string): string {
|
|
|
65
77
|
* Writes a snippet to a shell config file using begin/end markers for idempotency.
|
|
66
78
|
* If a block with the same begin marker exists, it is replaced. Otherwise the snippet is appended.
|
|
67
79
|
*/
|
|
68
|
-
export function writeToRcFile(
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
export async function writeToRcFile(
|
|
81
|
+
rcFile: string,
|
|
82
|
+
snippet: string,
|
|
83
|
+
beginMarker: string,
|
|
84
|
+
endMarker: string,
|
|
85
|
+
): Promise<{ file: string; updated: boolean }> {
|
|
86
|
+
const { existsSync, mkdirSync, readFileSync, writeFileSync } = await import('node:fs');
|
|
87
|
+
const { dirname } = await import('node:path');
|
|
71
88
|
const existing = existsSync(rcFile) ? readFileSync(rcFile, 'utf-8') : '';
|
|
72
89
|
|
|
73
90
|
if (existing.includes(beginMarker)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
-
import type { PadroneSchema } from '
|
|
2
|
+
import type { PadroneSchema } from '../types/index.ts';
|
|
3
3
|
|
|
4
4
|
export interface AsyncStreamMeta {
|
|
5
5
|
[x: string]: unknown;
|
|
@@ -73,3 +73,29 @@ export function createStdinStream(stdin: StdinSource | undefined, itemSchema?: S
|
|
|
73
73
|
const emptyAsyncIterable: AsyncIterable<never> = {
|
|
74
74
|
async *[Symbol.asyncIterator]() {},
|
|
75
75
|
};
|
|
76
|
+
|
|
77
|
+
const textEncoder = /* @__PURE__ */ new TextEncoder();
|
|
78
|
+
const textDecoder = /* @__PURE__ */ new TextDecoder();
|
|
79
|
+
|
|
80
|
+
/** Concatenate multiple `Uint8Array` chunks into a single array. */
|
|
81
|
+
export function concatBytes(chunks: Uint8Array[]): Uint8Array {
|
|
82
|
+
if (chunks.length === 1) return chunks[0]!;
|
|
83
|
+
let totalLength = 0;
|
|
84
|
+
for (const chunk of chunks) totalLength += chunk.byteLength;
|
|
85
|
+
const result = new Uint8Array(totalLength);
|
|
86
|
+
let offset = 0;
|
|
87
|
+
for (const chunk of chunks) {
|
|
88
|
+
result.set(chunk, offset);
|
|
89
|
+
offset += chunk.byteLength;
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Read an async iterable of chunks into a UTF-8 string. */
|
|
95
|
+
export async function readStreamAsText(stream: AsyncIterable<Uint8Array | string>): Promise<string> {
|
|
96
|
+
const chunks: Uint8Array[] = [];
|
|
97
|
+
for await (const chunk of stream) {
|
|
98
|
+
chunks.push(typeof chunk === 'string' ? textEncoder.encode(chunk) : chunk);
|
|
99
|
+
}
|
|
100
|
+
return textDecoder.decode(concatBytes(chunks));
|
|
101
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnyPadroneCommand,
|
|
3
|
+
AnyPadroneProgram,
|
|
4
|
+
PadroneCommand,
|
|
5
|
+
PadroneContextInterceptor,
|
|
6
|
+
PadroneInterceptorFn,
|
|
7
|
+
} from '../types/index.ts';
|
|
1
8
|
import type { PickCommandByName, PossibleCommands } from './type-utils.ts';
|
|
2
|
-
import type { AnyPadroneCommand, AnyPadroneProgram, PadroneCommand, PadroneSchema } from './types.ts';
|
|
3
9
|
|
|
4
10
|
/**
|
|
5
11
|
* Extracts the input type of the arguments schema from a command.
|
|
@@ -20,43 +26,44 @@ export type InferArgsInput<T extends AnyPadroneCommand> = T['~types']['argsInput
|
|
|
20
26
|
export type InferArgsOutput<T extends AnyPadroneCommand> = T['~types']['argsOutput'];
|
|
21
27
|
|
|
22
28
|
/**
|
|
23
|
-
* Extracts the
|
|
29
|
+
* Extracts the user-defined context type from a command (excludes interceptor-provided context).
|
|
24
30
|
* @example
|
|
25
31
|
* ```ts
|
|
26
|
-
* type
|
|
32
|
+
* type Ctx = InferContext<typeof myCommand>;
|
|
27
33
|
* ```
|
|
28
34
|
*/
|
|
29
|
-
export type
|
|
35
|
+
export type InferContext<T extends AnyPadroneCommand> = T['~types']['context'];
|
|
30
36
|
|
|
31
37
|
/**
|
|
32
|
-
* Extracts the
|
|
33
|
-
* This is the type after transformation, which should match the arguments shape.
|
|
38
|
+
* Extracts the interceptor-provided context type from a command.
|
|
34
39
|
* @example
|
|
35
40
|
* ```ts
|
|
36
|
-
* type
|
|
41
|
+
* type Provided = InferContextProvided<typeof myCommand>;
|
|
37
42
|
* ```
|
|
38
43
|
*/
|
|
39
|
-
export type
|
|
44
|
+
export type InferContextProvided<T extends AnyPadroneCommand> = T['~types']['contextProvided'];
|
|
40
45
|
|
|
41
46
|
/**
|
|
42
|
-
* Extracts the
|
|
43
|
-
* This represents the raw environment variables shape (e.g., { PORT: string }).
|
|
47
|
+
* Extracts the context type that a context-providing interceptor injects.
|
|
44
48
|
* @example
|
|
45
49
|
* ```ts
|
|
46
|
-
* type
|
|
50
|
+
* type AuthCtx = InferInterceptorContext<typeof withAuth>;
|
|
47
51
|
* ```
|
|
48
52
|
*/
|
|
49
|
-
export type
|
|
53
|
+
export type InferInterceptorContext<T extends PadroneContextInterceptor<any>> = T['~context'];
|
|
50
54
|
|
|
51
55
|
/**
|
|
52
|
-
* Extracts the
|
|
53
|
-
* This is the type after transformation, which should match the arguments shape.
|
|
56
|
+
* Extracts the required context type from an interceptor with `.requires()`.
|
|
54
57
|
* @example
|
|
55
58
|
* ```ts
|
|
56
|
-
* type
|
|
59
|
+
* type Requires = InferInterceptorRequires<typeof withAuth>;
|
|
57
60
|
* ```
|
|
58
61
|
*/
|
|
59
|
-
export type
|
|
62
|
+
export type InferInterceptorRequires<T extends PadroneInterceptorFn & { '~contextRequires': any }> = T['~contextRequires'] extends (
|
|
63
|
+
ctx: infer R,
|
|
64
|
+
) => void
|
|
65
|
+
? R
|
|
66
|
+
: unknown;
|
|
60
67
|
|
|
61
68
|
/**
|
|
62
69
|
* Gets a command type by its path from a program or command tree.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PadroneBuilder, PadroneProgram } from '../types/builder.ts';
|
|
2
|
+
import type { AnyPadroneCommand, PadroneCommand } from '../types/index.ts';
|
|
3
|
+
import type { PadroneSchema } from '../types/schema.ts';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Use this type instead of `any` when you intend to fix it later
|
|
@@ -137,40 +139,76 @@ type GetCommandPathsAndAliases<TCommand extends AnyPadroneCommand> = TCommand['~
|
|
|
137
139
|
/**
|
|
138
140
|
* Find a direct child command in a tuple by name.
|
|
139
141
|
* Unlike PickCommandByName, this does NOT flatten — it only checks direct children by their `name` field.
|
|
142
|
+
* Uses indexed access (O(1) depth) instead of recursive tuple walking.
|
|
140
143
|
*/
|
|
141
|
-
export type FindDirectChild<TCommands extends AnyPadroneCommand[], TName extends string> =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
? First['~types']['name'] extends TName
|
|
146
|
-
? First
|
|
147
|
-
: FindDirectChild<Rest, TName>
|
|
148
|
-
: never;
|
|
144
|
+
export type FindDirectChild<TCommands extends AnyPadroneCommand[], TName extends string> = Extract<
|
|
145
|
+
TCommands[number],
|
|
146
|
+
{ '~types': { name: TName } }
|
|
147
|
+
>;
|
|
149
148
|
|
|
150
149
|
/**
|
|
151
150
|
* Replace a command in a tuple by name, or append if not found.
|
|
152
151
|
* Used by `.command()` override semantics: re-registering a name replaces that entry.
|
|
152
|
+
* Uses mapped type (O(1) depth) instead of recursive tuple walking.
|
|
153
153
|
*/
|
|
154
154
|
export type ReplaceOrAppendCommand<TCommands extends [...AnyPadroneCommand[]], TName extends string, TNew extends AnyPadroneCommand> =
|
|
155
155
|
HasDirectChild<TCommands, TName> extends true ? ReplaceInTuple<TCommands, TName, TNew> : [...TCommands, TNew];
|
|
156
156
|
|
|
157
|
-
type HasDirectChild<TCommands extends AnyPadroneCommand[], TName extends string> =
|
|
158
|
-
|
|
159
|
-
...infer Rest extends AnyPadroneCommand[],
|
|
160
|
-
]
|
|
161
|
-
? First['~types']['name'] extends TName
|
|
162
|
-
? true
|
|
163
|
-
: HasDirectChild<Rest, TName>
|
|
157
|
+
type HasDirectChild<TCommands extends AnyPadroneCommand[], TName extends string> = TName extends TCommands[number]['~types']['name']
|
|
158
|
+
? true
|
|
164
159
|
: false;
|
|
165
160
|
|
|
166
|
-
type ReplaceInTuple<TCommands extends AnyPadroneCommand[], TName extends string, TNew extends AnyPadroneCommand> =
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
161
|
+
type ReplaceInTuple<TCommands extends AnyPadroneCommand[], TName extends string, TNew extends AnyPadroneCommand> = {
|
|
162
|
+
[K in keyof TCommands]: TCommands[K] extends AnyPadroneCommand
|
|
163
|
+
? TCommands[K]['~types']['name'] extends TName
|
|
164
|
+
? TNew
|
|
165
|
+
: TCommands[K]
|
|
166
|
+
: TCommands[K];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Utility type for extensions that add a command to a builder/program.
|
|
171
|
+
* Replaces the boilerplate `With*<T>` pattern used across all extension files.
|
|
172
|
+
*/
|
|
173
|
+
export type WithCommand<T, TName extends string, TCmd extends AnyPadroneCommand> = T extends {
|
|
174
|
+
'~types': {
|
|
175
|
+
programName: infer PN extends string;
|
|
176
|
+
name: infer N extends string;
|
|
177
|
+
parentName: infer PaN extends string;
|
|
178
|
+
argsSchema: infer A extends PadroneSchema;
|
|
179
|
+
result: infer R;
|
|
180
|
+
commands: infer C extends [...AnyPadroneCommand[]];
|
|
181
|
+
async: infer AS extends boolean;
|
|
182
|
+
context: infer CTX;
|
|
183
|
+
contextProvided: infer CTXP;
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
? T extends { run: any }
|
|
187
|
+
? PadroneProgram<PN, N, PaN, A, R, ReplaceOrAppendCommand<C, TName, TCmd>, any, AS, CTX, CTXP>
|
|
188
|
+
: PadroneBuilder<PN, N, PaN, A, R, ReplaceOrAppendCommand<C, TName, TCmd>, any, AS, CTX, CTXP>
|
|
189
|
+
: T;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Utility type for extensions that register a context-providing interceptor.
|
|
193
|
+
* Extends `TContextProvided` with `TProvides` while preserving all other builder/program type params.
|
|
194
|
+
*/
|
|
195
|
+
export type WithInterceptor<T, TProvides> = T extends {
|
|
196
|
+
'~types': {
|
|
197
|
+
programName: infer PN extends string;
|
|
198
|
+
name: infer N extends string;
|
|
199
|
+
parentName: infer PaN extends string;
|
|
200
|
+
argsSchema: infer A extends PadroneSchema;
|
|
201
|
+
result: infer R;
|
|
202
|
+
commands: infer C extends [...AnyPadroneCommand[]];
|
|
203
|
+
async: infer AS extends boolean;
|
|
204
|
+
context: infer CTX;
|
|
205
|
+
contextProvided: infer CTXP;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
? T extends { run: any }
|
|
209
|
+
? PadroneProgram<PN, N, PaN, A, R, C, any, AS, CTX, CTXP & TProvides>
|
|
210
|
+
: PadroneBuilder<PN, N, PaN, A, R, C, any, AS, CTX, CTXP & TProvides>
|
|
211
|
+
: T;
|
|
174
212
|
|
|
175
213
|
export type PickCommandByName<
|
|
176
214
|
TCommands extends AnyPadroneCommand[],
|
|
@@ -187,13 +225,13 @@ export type PickCommandByName<
|
|
|
187
225
|
|
|
188
226
|
export type FlattenCommands<TCommands extends AnyPadroneCommand[]> = TCommands extends []
|
|
189
227
|
? never
|
|
190
|
-
:
|
|
191
|
-
?
|
|
192
|
-
| (RestCommands extends AnyPadroneCommand[] ? FlattenCommands<RestCommands> : never)
|
|
193
|
-
| (FirstCommand extends AnyPadroneCommand ? FlattenCommands<FirstCommand['~types']['commands']> | FirstCommand : never)
|
|
194
|
-
: IsAny<TCommands[number]> extends true
|
|
228
|
+
: number extends TCommands['length']
|
|
229
|
+
? IsAny<TCommands[number]> extends true
|
|
195
230
|
? never
|
|
196
|
-
: TCommands[number]
|
|
231
|
+
: TCommands[number]
|
|
232
|
+
: TCommands[number] extends infer Cmd extends AnyPadroneCommand
|
|
233
|
+
? Cmd | FlattenCommands<Cmd['~types']['commands']>
|
|
234
|
+
: never;
|
|
197
235
|
|
|
198
236
|
/**
|
|
199
237
|
* Get all command paths including alias paths for all commands.
|
|
@@ -258,9 +296,9 @@ type RepathCommand<TCommand extends AnyPadroneCommand, TNewParentName extends st
|
|
|
258
296
|
TCommand['~types']['result'],
|
|
259
297
|
RepathCommands<TCommand['~types']['commands'], FullCommandName<TCommand['~types']['name'], TNewParentName>>,
|
|
260
298
|
TCommand['~types']['aliases'],
|
|
261
|
-
TCommand['~types']['
|
|
262
|
-
TCommand['~types']['
|
|
263
|
-
TCommand['~types']['
|
|
299
|
+
TCommand['~types']['async'],
|
|
300
|
+
TCommand['~types']['context'],
|
|
301
|
+
TCommand['~types']['contextProvided']
|
|
264
302
|
>;
|
|
265
303
|
|
|
266
304
|
export type PickCommandByPossibleCommands<
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AnyPadroneCommand } from '../types/index.ts';
|
|
2
|
+
|
|
3
|
+
export function getRootCommand(cmd: AnyPadroneCommand): AnyPadroneCommand {
|
|
4
|
+
let current = cmd;
|
|
5
|
+
while (current.parent) current = current.parent;
|
|
6
|
+
return current;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function readVersionFromPackageJson(): Promise<string> {
|
|
10
|
+
try {
|
|
11
|
+
const fs = await import('node:fs');
|
|
12
|
+
const path = await import('node:path');
|
|
13
|
+
let dir = process.cwd();
|
|
14
|
+
|
|
15
|
+
// Walk up the directory tree looking for package.json
|
|
16
|
+
for (let i = 0; i < 10; i++) {
|
|
17
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
18
|
+
if (fs.existsSync(pkgPath)) {
|
|
19
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
20
|
+
if (pkg.version) return pkg.version;
|
|
21
|
+
}
|
|
22
|
+
const parentDir = path.dirname(dir);
|
|
23
|
+
if (parentDir === dir) break; // Reached root
|
|
24
|
+
dir = parentDir;
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// Ignore errors (e.g., fs not available in browser)
|
|
28
|
+
}
|
|
29
|
+
return '0.0.0';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Attempts to get the version from various sources:
|
|
34
|
+
* 1. Explicit version set on the command
|
|
35
|
+
* 2. npm_package_version environment variable (set by npm/yarn/pnpm when running scripts)
|
|
36
|
+
* 3. package.json in current or parent directories
|
|
37
|
+
*
|
|
38
|
+
* Returns synchronously when an explicit version or env var is available.
|
|
39
|
+
* Falls back to async package.json discovery otherwise.
|
|
40
|
+
*/
|
|
41
|
+
export function getVersion(explicitVersion?: string): string | Promise<string> {
|
|
42
|
+
if (explicitVersion) return explicitVersion;
|
|
43
|
+
|
|
44
|
+
if (typeof process !== 'undefined' && process.env?.npm_package_version) {
|
|
45
|
+
return process.env.npm_package_version;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof process !== 'undefined') return readVersionFromPackageJson();
|
|
49
|
+
|
|
50
|
+
return '0.0.0';
|
|
51
|
+
}
|