padrone 1.9.0 → 2.0.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 +13 -0
- package/README.md +2 -2
- package/dist/codegen/code-builder.d.mts +10 -0
- package/dist/codegen/code-builder.d.mts.map +1 -0
- package/dist/codegen/code-builder.mjs +185 -0
- package/dist/codegen/code-builder.mjs.map +1 -0
- package/dist/codegen/discovery.d.mts +44 -0
- package/dist/codegen/discovery.d.mts.map +1 -0
- package/dist/codegen/discovery.mjs +179 -0
- package/dist/codegen/discovery.mjs.map +1 -0
- package/dist/codegen/file-emitter.d.mts +10 -0
- package/dist/codegen/file-emitter.d.mts.map +1 -0
- package/dist/codegen/file-emitter.mjs +58 -0
- package/dist/codegen/file-emitter.mjs.map +1 -0
- package/dist/codegen/generators/barrel-file.d.mts +10 -0
- package/dist/codegen/generators/barrel-file.d.mts.map +1 -0
- package/dist/codegen/generators/barrel-file.mjs +16 -0
- package/dist/codegen/generators/barrel-file.mjs.map +1 -0
- package/dist/codegen/generators/command-file.d.mts +25 -0
- package/dist/codegen/generators/command-file.d.mts.map +1 -0
- package/dist/codegen/generators/command-file.mjs +130 -0
- package/dist/codegen/generators/command-file.mjs.map +1 -0
- package/dist/codegen/generators/command-tree.d.mts +17 -0
- package/dist/codegen/generators/command-tree.d.mts.map +1 -0
- package/dist/codegen/generators/command-tree.mjs +72 -0
- package/dist/codegen/generators/command-tree.mjs.map +1 -0
- package/dist/codegen/index.d.mts +15 -330
- package/dist/codegen/index.mjs +13 -1507
- package/dist/codegen/parsers/bash.d.mts +19 -0
- package/dist/codegen/parsers/bash.d.mts.map +1 -0
- package/dist/codegen/parsers/bash.mjs +112 -0
- package/dist/codegen/parsers/bash.mjs.map +1 -0
- package/dist/codegen/parsers/fish.d.mts +13 -0
- package/dist/codegen/parsers/fish.d.mts.map +1 -0
- package/dist/codegen/parsers/fish.mjs +108 -0
- package/dist/codegen/parsers/fish.mjs.map +1 -0
- package/dist/codegen/parsers/help.d.mts +15 -0
- package/dist/codegen/parsers/help.d.mts.map +1 -0
- package/dist/codegen/parsers/help.mjs +271 -0
- package/dist/codegen/parsers/help.mjs.map +1 -0
- package/dist/codegen/parsers/merge.d.mts +14 -0
- package/dist/codegen/parsers/merge.d.mts.map +1 -0
- package/dist/codegen/parsers/merge.mjs +76 -0
- package/dist/codegen/parsers/merge.mjs.map +1 -0
- package/dist/codegen/parsers/zsh.d.mts +16 -0
- package/dist/codegen/parsers/zsh.d.mts.map +1 -0
- package/dist/codegen/parsers/zsh.mjs +161 -0
- package/dist/codegen/parsers/zsh.mjs.map +1 -0
- package/dist/codegen/schema-to-code.d.mts +24 -0
- package/dist/codegen/schema-to-code.d.mts.map +1 -0
- package/dist/codegen/schema-to-code.mjs +147 -0
- package/dist/codegen/schema-to-code.mjs.map +1 -0
- package/dist/codegen/template.d.mts +18 -0
- package/dist/codegen/template.d.mts.map +1 -0
- package/dist/codegen/template.mjs +37 -0
- package/dist/codegen/template.mjs.map +1 -0
- package/dist/codegen/types.d.mts +144 -0
- package/dist/codegen/types.d.mts.map +1 -0
- package/dist/completion.d.mts +2 -0
- package/dist/completion.mjs +2 -0
- package/dist/core/args.d.mts +3 -0
- package/dist/{args-WmyGc59s.mjs → core/args.mjs} +5 -81
- package/dist/core/args.mjs.map +1 -0
- package/dist/core/commands.d.mts +13 -0
- package/dist/core/commands.d.mts.map +1 -0
- package/dist/{commands-ohEApqIw.mjs → core/commands.mjs} +4 -232
- package/dist/core/commands.mjs.map +1 -0
- package/dist/core/create.d.mts +63 -0
- package/dist/core/create.d.mts.map +1 -0
- package/dist/core/create.mjs +230 -0
- package/dist/core/create.mjs.map +1 -0
- package/dist/core/default-runtime.mjs +198 -0
- package/dist/core/default-runtime.mjs.map +1 -0
- package/dist/core/errors.d.mts +107 -0
- package/dist/core/errors.d.mts.map +1 -0
- package/dist/{errors-DA4KzK1M.mjs → core/errors.mjs} +2 -2
- package/dist/core/errors.mjs.map +1 -0
- package/dist/core/exec.mjs +191 -0
- package/dist/core/exec.mjs.map +1 -0
- package/dist/core/interceptors.d.mts +33 -0
- package/dist/core/interceptors.d.mts.map +1 -0
- package/dist/core/interceptors.mjs +253 -0
- package/dist/core/interceptors.mjs.map +1 -0
- package/dist/core/parse.mjs +181 -0
- package/dist/core/parse.mjs.map +1 -0
- package/dist/core/program-methods.mjs +256 -0
- package/dist/core/program-methods.mjs.map +1 -0
- package/dist/core/results.d.mts +13 -0
- package/dist/core/results.d.mts.map +1 -0
- package/dist/core/results.mjs +150 -0
- package/dist/core/results.mjs.map +1 -0
- package/dist/core/runtime.d.mts +213 -0
- package/dist/core/runtime.d.mts.map +1 -0
- package/dist/core/runtime.mjs +10 -0
- package/dist/core/runtime.mjs.map +1 -0
- package/dist/core/validate.mjs +220 -0
- package/dist/core/validate.mjs.map +1 -0
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.d.mts.map +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/docs/index.mjs.map +1 -1
- package/dist/extension/auto-output.d.mts +53 -0
- package/dist/extension/auto-output.d.mts.map +1 -0
- package/dist/extension/auto-output.mjs +116 -0
- package/dist/extension/auto-output.mjs.map +1 -0
- package/dist/extension/color.d.mts +19 -0
- package/dist/extension/color.d.mts.map +1 -0
- package/dist/extension/color.mjs +37 -0
- package/dist/extension/color.mjs.map +1 -0
- package/dist/extension/completion.d.mts +25 -0
- package/dist/extension/completion.d.mts.map +1 -0
- package/dist/extension/completion.mjs +40 -0
- package/dist/extension/completion.mjs.map +1 -0
- package/dist/extension/config.d.mts +55 -0
- package/dist/extension/config.d.mts.map +1 -0
- package/dist/extension/config.mjs +162 -0
- package/dist/extension/config.mjs.map +1 -0
- package/dist/extension/env.d.mts +42 -0
- package/dist/extension/env.d.mts.map +1 -0
- package/dist/extension/env.mjs +44 -0
- package/dist/extension/env.mjs.map +1 -0
- package/dist/extension/help.d.mts +29 -0
- package/dist/extension/help.d.mts.map +1 -0
- package/dist/extension/help.mjs +161 -0
- package/dist/extension/help.mjs.map +1 -0
- package/dist/extension/index.d.mts +18 -0
- package/dist/extension/ink.d.mts +31 -0
- package/dist/extension/ink.d.mts.map +1 -0
- package/dist/extension/ink.mjs +69 -0
- package/dist/extension/ink.mjs.map +1 -0
- package/dist/extension/interactive.d.mts +16 -0
- package/dist/extension/interactive.d.mts.map +1 -0
- package/dist/extension/interactive.mjs +89 -0
- package/dist/extension/interactive.mjs.map +1 -0
- package/dist/extension/logger.d.mts +67 -0
- package/dist/extension/logger.d.mts.map +1 -0
- package/dist/extension/logger.mjs +188 -0
- package/dist/extension/logger.mjs.map +1 -0
- package/dist/extension/man.d.mts +25 -0
- package/dist/extension/man.d.mts.map +1 -0
- package/dist/extension/man.mjs +41 -0
- package/dist/extension/man.mjs.map +1 -0
- package/dist/extension/mcp.d.mts +28 -0
- package/dist/extension/mcp.d.mts.map +1 -0
- package/dist/extension/mcp.mjs +44 -0
- package/dist/extension/mcp.mjs.map +1 -0
- package/dist/extension/progress-renderer.d.mts +13 -0
- package/dist/extension/progress-renderer.d.mts.map +1 -0
- package/dist/extension/progress-renderer.mjs +351 -0
- package/dist/extension/progress-renderer.mjs.map +1 -0
- package/dist/extension/progress.d.mts +76 -0
- package/dist/extension/progress.d.mts.map +1 -0
- package/dist/extension/progress.mjs +225 -0
- package/dist/extension/progress.mjs.map +1 -0
- package/dist/extension/repl.d.mts +26 -0
- package/dist/extension/repl.d.mts.map +1 -0
- package/dist/extension/repl.mjs +67 -0
- package/dist/extension/repl.mjs.map +1 -0
- package/dist/extension/serve.d.mts +27 -0
- package/dist/extension/serve.d.mts.map +1 -0
- package/dist/extension/serve.mjs +41 -0
- package/dist/extension/serve.mjs.map +1 -0
- package/dist/extension/signal.d.mts +19 -0
- package/dist/extension/signal.d.mts.map +1 -0
- package/dist/extension/signal.mjs +81 -0
- package/dist/extension/signal.mjs.map +1 -0
- package/dist/extension/stdin.d.mts +19 -0
- package/dist/extension/stdin.d.mts.map +1 -0
- package/dist/extension/stdin.mjs +52 -0
- package/dist/extension/stdin.mjs.map +1 -0
- package/dist/extension/suggestions.d.mts +6 -0
- package/dist/extension/suggestions.d.mts.map +1 -0
- package/dist/extension/suggestions.mjs +113 -0
- package/dist/extension/suggestions.mjs.map +1 -0
- package/dist/extension/timing.d.mts +27 -0
- package/dist/extension/timing.d.mts.map +1 -0
- package/dist/extension/timing.mjs +71 -0
- package/dist/extension/timing.mjs.map +1 -0
- package/dist/extension/tracing.d.mts +85 -0
- package/dist/extension/tracing.d.mts.map +1 -0
- package/dist/extension/tracing.mjs +107 -0
- package/dist/extension/tracing.mjs.map +1 -0
- package/dist/extension/update-check.d.mts +20 -0
- package/dist/extension/update-check.d.mts.map +1 -0
- package/dist/extension/update-check.mjs +62 -0
- package/dist/extension/update-check.mjs.map +1 -0
- package/dist/extension/utils.mjs +43 -0
- package/dist/extension/utils.mjs.map +1 -0
- package/dist/extension/version.d.mts +20 -0
- package/dist/extension/version.d.mts.map +1 -0
- package/dist/extension/version.mjs +51 -0
- package/dist/extension/version.mjs.map +1 -0
- package/dist/{completion-D8qkAinX.mjs → feature/completion.mjs} +4 -3
- package/dist/feature/completion.mjs.map +1 -0
- package/dist/feature/interactive.mjs +145 -0
- package/dist/feature/interactive.mjs.map +1 -0
- package/dist/feature/mcp.d.mts +18 -0
- package/dist/feature/mcp.d.mts.map +1 -0
- package/dist/{mcp-wCoVFTXz.mjs → feature/mcp.mjs} +6 -6
- package/dist/feature/mcp.mjs.map +1 -0
- package/dist/feature/repl-loop.mjs +217 -0
- package/dist/feature/repl-loop.mjs.map +1 -0
- package/dist/feature/serve.d.mts +18 -0
- package/dist/feature/serve.d.mts.map +1 -0
- package/dist/{serve-ICZFl3xr.mjs → feature/serve.mjs} +7 -7
- package/dist/feature/serve.mjs.map +1 -0
- package/dist/feature/test.d.mts +109 -0
- package/dist/feature/test.d.mts.map +1 -0
- package/dist/feature/test.mjs +116 -0
- package/dist/feature/test.mjs.map +1 -0
- package/dist/feature/update-check.d.mts +35 -0
- package/dist/feature/update-check.d.mts.map +1 -0
- package/dist/{update-check-CZ2VqjnV.mjs → feature/update-check.mjs} +2 -2
- package/dist/feature/update-check.mjs.map +1 -0
- package/dist/feature/wrap.d.mts +58 -0
- package/dist/feature/wrap.d.mts.map +1 -0
- package/dist/feature/wrap.mjs +77 -0
- package/dist/feature/wrap.mjs.map +1 -0
- package/dist/index.d.mts +38 -902
- package/dist/index.mjs +25 -4377
- package/dist/ink.d.mts +2 -0
- package/dist/ink.mjs +2 -0
- package/dist/man.d.mts +2 -0
- package/dist/man.mjs +2 -0
- package/dist/mcp.d.mts +3 -0
- package/dist/mcp.mjs +2 -0
- package/dist/output/colorizer.d.mts +28 -0
- package/dist/output/colorizer.d.mts.map +1 -0
- package/dist/output/colorizer.mjs +94 -0
- package/dist/output/colorizer.mjs.map +1 -0
- package/dist/{formatter-CY3KrOEd.d.mts → output/formatter.d.mts} +2 -28
- package/dist/output/formatter.d.mts.map +1 -0
- package/dist/output/formatter.mjs +360 -0
- package/dist/output/formatter.mjs.map +1 -0
- package/dist/output/help.d.mts +18 -0
- package/dist/output/help.d.mts.map +1 -0
- package/dist/output/help.mjs +267 -0
- package/dist/output/help.mjs.map +1 -0
- package/dist/output/output-indicator.d.mts +27 -0
- package/dist/output/output-indicator.d.mts.map +1 -0
- package/dist/output/output-indicator.mjs +54 -0
- package/dist/output/output-indicator.mjs.map +1 -0
- package/dist/output/primitives.d.mts +33 -0
- package/dist/output/primitives.d.mts.map +1 -0
- package/dist/output/primitives.mjs +206 -0
- package/dist/output/primitives.mjs.map +1 -0
- package/dist/output/styling.d.mts +38 -0
- package/dist/output/styling.d.mts.map +1 -0
- package/dist/output/styling.mjs +146 -0
- package/dist/output/styling.mjs.map +1 -0
- package/dist/schema/zod.d.mts +6 -0
- package/dist/schema/zod.d.mts.map +1 -0
- package/dist/schema/zod.mjs +50 -0
- package/dist/schema/zod.mjs.map +1 -0
- package/dist/serve.d.mts +2 -0
- package/dist/serve.mjs +2 -0
- package/dist/test.d.mts +2 -109
- package/dist/test.mjs +1 -115
- package/dist/tracing.d.mts +2 -0
- package/dist/tracing.mjs +2 -0
- package/dist/types/args-meta.d.mts +129 -0
- package/dist/types/args-meta.d.mts.map +1 -0
- package/dist/types/builder.d.mts +212 -0
- package/dist/types/builder.d.mts.map +1 -0
- package/dist/types/command.d.mts +112 -0
- package/dist/types/command.d.mts.map +1 -0
- package/dist/types/index.d.mts +8 -0
- package/dist/types/interceptor.d.mts +232 -0
- package/dist/types/interceptor.d.mts.map +1 -0
- package/dist/types/preferences.d.mts +79 -0
- package/dist/types/preferences.d.mts.map +1 -0
- package/dist/types/result.d.mts +61 -0
- package/dist/types/result.d.mts.map +1 -0
- package/dist/types/schema.d.mts +24 -0
- package/dist/types/schema.d.mts.map +1 -0
- package/dist/util/dotenv.mjs +174 -0
- package/dist/util/dotenv.mjs.map +1 -0
- package/dist/util/shell-utils.mjs +80 -0
- package/dist/util/shell-utils.mjs.map +1 -0
- package/dist/util/stream.d.mts +33 -0
- package/dist/util/stream.d.mts.map +1 -0
- package/dist/{stream-DC4H8YTx.mjs → util/stream.mjs} +2 -2
- package/dist/util/stream.mjs.map +1 -0
- package/dist/util/type-helpers.d.mts +74 -0
- package/dist/util/type-helpers.d.mts.map +1 -0
- package/dist/util/type-utils.d.mts +186 -0
- package/dist/util/type-utils.d.mts.map +1 -0
- package/dist/util/utils.mjs +43 -0
- package/dist/util/utils.mjs.map +1 -0
- package/dist/zod.d.mts +1 -32
- package/dist/zod.mjs +1 -49
- package/package.json +65 -17
- package/src/completion.ts +2 -0
- package/src/extension/completion.ts +3 -0
- package/src/extension/index.ts +0 -12
- package/src/extension/ink.ts +2 -1
- package/src/extension/man.ts +3 -0
- package/src/extension/mcp.ts +3 -0
- package/src/extension/serve.ts +3 -0
- package/src/extension/tracing.ts +2 -0
- package/src/index.ts +0 -19
- package/src/ink.ts +2 -0
- package/src/man.ts +2 -0
- package/src/mcp.ts +3 -0
- package/src/serve.ts +2 -0
- package/src/tracing.ts +9 -0
- package/src/zod.ts +1 -1
- package/dist/args-WmyGc59s.mjs.map +0 -1
- package/dist/codegen/index.d.mts.map +0 -1
- package/dist/codegen/index.mjs.map +0 -1
- package/dist/commands-ohEApqIw.mjs.map +0 -1
- package/dist/completion-D8qkAinX.mjs.map +0 -1
- package/dist/errors-DA4KzK1M.mjs.map +0 -1
- package/dist/formatter-CY3KrOEd.d.mts.map +0 -1
- package/dist/help-CeI45CJx.mjs +0 -892
- package/dist/help-CeI45CJx.mjs.map +0 -1
- package/dist/index-C3Ed1LYN.d.mts +0 -1432
- package/dist/index-C3Ed1LYN.d.mts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/mcp-wCoVFTXz.mjs.map +0 -1
- package/dist/serve-ICZFl3xr.mjs.map +0 -1
- package/dist/stream-DC4H8YTx.mjs.map +0 -1
- package/dist/test.d.mts.map +0 -1
- package/dist/test.mjs.map +0 -1
- package/dist/update-check-CZ2VqjnV.mjs.map +0 -1
- package/dist/zod.d.mts.map +0 -1
- package/dist/zod.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4377 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
* Makes a sync result object thenable by adding `.then()`, `.catch()`, and `.finally()` methods.
|
|
27
|
-
* If the value is already a Promise, returns it as-is.
|
|
28
|
-
* This allows users to write `await program.cli()` or `program.cli().then(...)` regardless of sync/async.
|
|
29
|
-
*
|
|
30
|
-
* The `.then()` resolves with a plain copy (without thenable methods) to avoid infinite
|
|
31
|
-
* recursive unwrapping by the Promise resolution algorithm.
|
|
32
|
-
*/
|
|
33
|
-
function makeThenable(value) {
|
|
34
|
-
if (value instanceof Promise) return value;
|
|
35
|
-
if (value !== null && typeof value === "object" && !("then" in value)) {
|
|
36
|
-
const toPlain = () => {
|
|
37
|
-
const plain = { ...value };
|
|
38
|
-
delete plain.then;
|
|
39
|
-
delete plain.catch;
|
|
40
|
-
delete plain.finally;
|
|
41
|
-
return plain;
|
|
42
|
-
};
|
|
43
|
-
value.then = (onfulfilled, onrejected) => {
|
|
44
|
-
try {
|
|
45
|
-
const result = onfulfilled ? onfulfilled(toPlain()) : toPlain();
|
|
46
|
-
return Promise.resolve(result);
|
|
47
|
-
} catch (err) {
|
|
48
|
-
if (onrejected) return Promise.resolve(onrejected(err));
|
|
49
|
-
return Promise.reject(err);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
value.catch = (onrejected) => value.then(void 0, onrejected);
|
|
53
|
-
value.finally = (onfinally) => value.then((v) => {
|
|
54
|
-
onfinally?.();
|
|
55
|
-
return v;
|
|
56
|
-
}, (err) => {
|
|
57
|
-
onfinally?.();
|
|
58
|
-
throw err;
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return value;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Wraps a Promise to include a `drain()` method at the top level.
|
|
65
|
-
* This allows `await promise.drain()` without first awaiting the promise.
|
|
66
|
-
* Since cli/eval never reject, this just delegates to the resolved result's `drain()`.
|
|
67
|
-
*/
|
|
68
|
-
function withPromiseDrain(promise) {
|
|
69
|
-
promise.drain = async () => {
|
|
70
|
-
return (await promise).drain();
|
|
71
|
-
};
|
|
72
|
-
return promise;
|
|
73
|
-
}
|
|
74
|
-
function isIterator(value) {
|
|
75
|
-
return typeof value === "object" && value !== null && Symbol.iterator in value && typeof value[Symbol.iterator] === "function";
|
|
76
|
-
}
|
|
77
|
-
function isAsyncIterator(value) {
|
|
78
|
-
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Resolves a result value by unwrapping Promises and collecting iterables into arrays.
|
|
82
|
-
* This is the runtime counterpart of the `Drained<T>` type.
|
|
83
|
-
*/
|
|
84
|
-
async function drainValue(value) {
|
|
85
|
-
if (value instanceof Promise) return drainValue(await value);
|
|
86
|
-
if (isAsyncIterator(value)) {
|
|
87
|
-
const items = [];
|
|
88
|
-
const iter = value[Symbol.asyncIterator]();
|
|
89
|
-
while (true) {
|
|
90
|
-
const { done, value: item } = await iter.next();
|
|
91
|
-
if (done) break;
|
|
92
|
-
items.push(item);
|
|
93
|
-
}
|
|
94
|
-
return items;
|
|
95
|
-
}
|
|
96
|
-
if (typeof value !== "string" && !Array.isArray(value) && isIterator(value)) {
|
|
97
|
-
const items = [];
|
|
98
|
-
const iter = value[Symbol.iterator]();
|
|
99
|
-
while (true) {
|
|
100
|
-
const { done, value: item } = iter.next();
|
|
101
|
-
if (done) break;
|
|
102
|
-
items.push(item);
|
|
103
|
-
}
|
|
104
|
-
return items;
|
|
105
|
-
}
|
|
106
|
-
return value;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Attaches a `drain()` method to a command result object.
|
|
110
|
-
* If the result has an `error` field, `drain()` returns `{ error }`.
|
|
111
|
-
* Otherwise, resolves the result (unwrapping Promises, collecting iterables), catches errors,
|
|
112
|
-
* and returns a discriminated union `{ value } | { error }` that never throws.
|
|
113
|
-
*/
|
|
114
|
-
function withDrain(obj) {
|
|
115
|
-
obj.drain = async () => {
|
|
116
|
-
if ("error" in obj && obj.error !== void 0) return { error: obj.error };
|
|
117
|
-
try {
|
|
118
|
-
return { value: await drainValue(obj.result) };
|
|
119
|
-
} catch (err) {
|
|
120
|
-
return { error: err };
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
return obj;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Creates an error command result with a `drain()` that returns the error.
|
|
127
|
-
*/
|
|
128
|
-
function errorResult(error, partial) {
|
|
129
|
-
return withDrain({
|
|
130
|
-
error,
|
|
131
|
-
result: void 0,
|
|
132
|
-
command: partial?.command,
|
|
133
|
-
args: partial?.args,
|
|
134
|
-
argsResult: partial?.argsResult
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
function isAsyncBranded(schema) {
|
|
138
|
-
return !!schema && typeof schema === "object" && "~async" in schema && schema["~async"] === true;
|
|
139
|
-
}
|
|
140
|
-
function hasInteractiveConfig(meta) {
|
|
141
|
-
if (!meta || typeof meta !== "object") return false;
|
|
142
|
-
const m = meta;
|
|
143
|
-
return m.interactive === true || Array.isArray(m.interactive) || m.optionalInteractive === true || Array.isArray(m.optionalInteractive);
|
|
144
|
-
}
|
|
145
|
-
function warnIfUnexpectedAsync(value, command) {
|
|
146
|
-
const runtime = getCommandRuntime(command);
|
|
147
|
-
if (runtime.env().NODE_ENV === "production") return value;
|
|
148
|
-
if (value instanceof Promise && !command.isAsync) runtime.error(`[padrone] Command "${command.path || command.name}" returned a Promise from validation, but was not marked as async. Use \`.async()\` on the builder or \`asyncSchema()\` to brand your schema. Without this, TypeScript will infer a sync return type and the result will be a Promise at runtime.`);
|
|
149
|
-
return value;
|
|
150
|
-
}
|
|
151
|
-
//#endregion
|
|
152
|
-
//#region src/core/interceptors.ts
|
|
153
|
-
function buildInterceptorFn(meta, factory) {
|
|
154
|
-
Object.defineProperty(factory, "name", {
|
|
155
|
-
value: meta.name,
|
|
156
|
-
configurable: true
|
|
157
|
-
});
|
|
158
|
-
if (meta.id !== void 0) factory.id = meta.id;
|
|
159
|
-
if (meta.order !== void 0) factory.order = meta.order;
|
|
160
|
-
if (meta.disabled !== void 0) factory.disabled = meta.disabled;
|
|
161
|
-
if (meta.inherit !== void 0) factory.inherit = meta.inherit;
|
|
162
|
-
factory.provides = () => factory;
|
|
163
|
-
factory.requires = () => factory;
|
|
164
|
-
return factory;
|
|
165
|
-
}
|
|
166
|
-
function defineInterceptor(meta, factory) {
|
|
167
|
-
if (factory) return buildInterceptorFn(meta, factory);
|
|
168
|
-
const builder = {
|
|
169
|
-
requires: () => builder,
|
|
170
|
-
factory: (f) => buildInterceptorFn(meta, f)
|
|
171
|
-
};
|
|
172
|
-
return builder;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Normalizes an interceptor input (single-value form or two-arg form) into the internal
|
|
176
|
-
* `RegisteredInterceptor` storage format.
|
|
177
|
-
*/
|
|
178
|
-
function toRegisteredInterceptor(metaOrFn, factory) {
|
|
179
|
-
if (typeof metaOrFn === "function") return {
|
|
180
|
-
meta: {
|
|
181
|
-
name: metaOrFn.name,
|
|
182
|
-
id: metaOrFn.id,
|
|
183
|
-
order: metaOrFn.order,
|
|
184
|
-
disabled: metaOrFn.disabled,
|
|
185
|
-
inherit: metaOrFn.inherit
|
|
186
|
-
},
|
|
187
|
-
factory: metaOrFn
|
|
188
|
-
};
|
|
189
|
-
return {
|
|
190
|
-
meta: metaOrFn,
|
|
191
|
-
factory
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Resolves registered interceptors by calling their factories and merging the resulting
|
|
196
|
-
* phase handlers with the static metadata. Uses a cache to ensure each factory is called
|
|
197
|
-
* at most once per execution (so root interceptor closures are shared across all phases).
|
|
198
|
-
*/
|
|
199
|
-
function resolveRegisteredInterceptors(registered, cache) {
|
|
200
|
-
return registered.map((reg) => {
|
|
201
|
-
let resolved = cache.get(reg);
|
|
202
|
-
if (!resolved) {
|
|
203
|
-
resolved = {
|
|
204
|
-
...reg.meta,
|
|
205
|
-
...reg.factory()
|
|
206
|
-
};
|
|
207
|
-
cache.set(reg, resolved);
|
|
208
|
-
}
|
|
209
|
-
return resolved;
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Deduplicates interceptors by `id`. When multiple interceptors share the same `id`,
|
|
214
|
-
* only the last one in the array is kept. Interceptors without an `id` are always kept.
|
|
215
|
-
*/
|
|
216
|
-
function deduplicateInterceptors(interceptors) {
|
|
217
|
-
if (!interceptors.some((p) => p.id)) return interceptors;
|
|
218
|
-
const lastIndex = /* @__PURE__ */ new Map();
|
|
219
|
-
for (let i = 0; i < interceptors.length; i++) {
|
|
220
|
-
const id = interceptors[i].id;
|
|
221
|
-
if (id) lastIndex.set(id, i);
|
|
222
|
-
}
|
|
223
|
-
return interceptors.filter((p, i) => !p.id || lastIndex.get(p.id) === i);
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Runs an interceptor chain for a given phase using the onion/middleware pattern.
|
|
227
|
-
* Interceptors are sorted by `order` (ascending, stable), then composed so that
|
|
228
|
-
* the first interceptor in sorted order is the outermost wrapper.
|
|
229
|
-
* If no interceptors handle this phase, `core` is called directly.
|
|
230
|
-
*
|
|
231
|
-
* Each interceptor's `next()` accepts optional partial overrides that are merged
|
|
232
|
-
* into the context before passing to the next interceptor or core function.
|
|
233
|
-
*/
|
|
234
|
-
function runInterceptorChain(phase, interceptors, ctx, core) {
|
|
235
|
-
const phaseInterceptors = deduplicateInterceptors(interceptors).filter((p) => p[phase] && !p.disabled);
|
|
236
|
-
if (phaseInterceptors.length === 0) return core(ctx);
|
|
237
|
-
phaseInterceptors.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
238
|
-
let next = core;
|
|
239
|
-
for (let i = phaseInterceptors.length - 1; i >= 0; i--) {
|
|
240
|
-
const handler = phaseInterceptors[i][phase];
|
|
241
|
-
const prevNext = next;
|
|
242
|
-
next = (currentCtx) => handler(currentCtx, (overrides) => {
|
|
243
|
-
if (!overrides) return prevNext(currentCtx);
|
|
244
|
-
if (overrides.context != null && typeof overrides.context === "object") overrides = {
|
|
245
|
-
...overrides,
|
|
246
|
-
context: Object.assign({}, currentCtx.context, overrides.context)
|
|
247
|
-
};
|
|
248
|
-
return prevNext(Object.assign({}, currentCtx, overrides));
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
return next(ctx);
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Wraps a pipeline with start → error → shutdown lifecycle hooks.
|
|
255
|
-
* - `start` interceptors wrap the pipeline (onion pattern, root interceptors only).
|
|
256
|
-
* - On error: `error` interceptors run (can transform/suppress the error).
|
|
257
|
-
* - Always: `shutdown` interceptors run (success or failure).
|
|
258
|
-
*/
|
|
259
|
-
function wrapWithLifecycle(interceptors, command, input, pipeline, wrapErrorResult, signal, context, runtime, program, caller = "eval", pipelineState) {
|
|
260
|
-
const defaultSignal = typeof AbortSignal !== "undefined" ? AbortSignal.abort() : void 0;
|
|
261
|
-
const hasStart = interceptors.some((p) => p.start);
|
|
262
|
-
const hasError = interceptors.some((p) => p.error);
|
|
263
|
-
const hasShutdown = interceptors.some((p) => p.shutdown);
|
|
264
|
-
const effectivePipelineState = pipelineState ?? { phase: "start" };
|
|
265
|
-
if (!hasStart && !hasError && !hasShutdown) return pipeline(signal ?? defaultSignal, context);
|
|
266
|
-
let effectiveSignal = signal ?? defaultSignal;
|
|
267
|
-
let effectiveContext = context;
|
|
268
|
-
const runShutdown = (error, result) => {
|
|
269
|
-
if (!hasShutdown) return;
|
|
270
|
-
return runInterceptorChain("shutdown", interceptors, {
|
|
271
|
-
command,
|
|
272
|
-
input,
|
|
273
|
-
error,
|
|
274
|
-
result,
|
|
275
|
-
signal: effectiveSignal,
|
|
276
|
-
context: effectiveContext,
|
|
277
|
-
runtime,
|
|
278
|
-
program,
|
|
279
|
-
caller,
|
|
280
|
-
...effectivePipelineState
|
|
281
|
-
}, () => {});
|
|
282
|
-
};
|
|
283
|
-
const runError = (error) => {
|
|
284
|
-
if (!hasError) {
|
|
285
|
-
const s = runShutdown(error);
|
|
286
|
-
if (s instanceof Promise) return s.then(() => {
|
|
287
|
-
throw error;
|
|
288
|
-
});
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
291
|
-
return thenMaybe(runInterceptorChain("error", interceptors, {
|
|
292
|
-
command,
|
|
293
|
-
input,
|
|
294
|
-
error,
|
|
295
|
-
signal: effectiveSignal,
|
|
296
|
-
context: effectiveContext,
|
|
297
|
-
runtime,
|
|
298
|
-
program,
|
|
299
|
-
caller,
|
|
300
|
-
...effectivePipelineState
|
|
301
|
-
}, () => ({ error })), (er) => {
|
|
302
|
-
if (er.error !== void 0) return thenMaybe(runShutdown(er.error), () => {
|
|
303
|
-
throw er.error;
|
|
304
|
-
});
|
|
305
|
-
const wrapped = wrapErrorResult ? wrapErrorResult(er.result) : er.result;
|
|
306
|
-
return thenMaybe(runShutdown(void 0, wrapped), () => wrapped);
|
|
307
|
-
});
|
|
308
|
-
};
|
|
309
|
-
const handleSuccess = (result) => {
|
|
310
|
-
const s = runShutdown(void 0, result);
|
|
311
|
-
if (s instanceof Promise) return s.then(() => result);
|
|
312
|
-
return result;
|
|
313
|
-
};
|
|
314
|
-
const startCtx = {
|
|
315
|
-
command,
|
|
316
|
-
signal: effectiveSignal,
|
|
317
|
-
context: effectiveContext,
|
|
318
|
-
runtime,
|
|
319
|
-
program,
|
|
320
|
-
input,
|
|
321
|
-
caller
|
|
322
|
-
};
|
|
323
|
-
let result;
|
|
324
|
-
try {
|
|
325
|
-
result = hasStart ? runInterceptorChain("start", interceptors, startCtx, (ctx) => {
|
|
326
|
-
effectiveSignal = ctx.signal;
|
|
327
|
-
effectiveContext = ctx.context;
|
|
328
|
-
return pipeline(ctx.signal, ctx.context);
|
|
329
|
-
}) : pipeline(effectiveSignal, effectiveContext);
|
|
330
|
-
} catch (e) {
|
|
331
|
-
return runError(e);
|
|
332
|
-
}
|
|
333
|
-
if (result instanceof Promise) return result.then(handleSuccess, runError);
|
|
334
|
-
return handleSuccess(result);
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Wraps a command-level pipeline (validate + execute) with error → shutdown lifecycle hooks.
|
|
338
|
-
* Unlike `wrapWithLifecycle`, this has no `start` phase and uses the resolved command context.
|
|
339
|
-
* Only interceptors exclusive to the command chain (not in root) should be passed here.
|
|
340
|
-
*/
|
|
341
|
-
function wrapWithCommandLifecycle(interceptors, command, input, pipeline, wrapErrorResult, signal, context, runtime, program, caller, pipelineState) {
|
|
342
|
-
const hasError = interceptors.some((p) => p.error);
|
|
343
|
-
const hasShutdown = interceptors.some((p) => p.shutdown);
|
|
344
|
-
if (!hasError && !hasShutdown) return pipeline();
|
|
345
|
-
const runShutdown = (error, result) => {
|
|
346
|
-
if (!hasShutdown) return;
|
|
347
|
-
return runInterceptorChain("shutdown", interceptors, {
|
|
348
|
-
command,
|
|
349
|
-
input,
|
|
350
|
-
error,
|
|
351
|
-
result,
|
|
352
|
-
signal,
|
|
353
|
-
context,
|
|
354
|
-
runtime,
|
|
355
|
-
program,
|
|
356
|
-
caller,
|
|
357
|
-
...pipelineState
|
|
358
|
-
}, () => {});
|
|
359
|
-
};
|
|
360
|
-
const runError = (error) => {
|
|
361
|
-
if (!hasError) {
|
|
362
|
-
const s = runShutdown(error);
|
|
363
|
-
if (s instanceof Promise) return s.then(() => {
|
|
364
|
-
throw error;
|
|
365
|
-
});
|
|
366
|
-
throw error;
|
|
367
|
-
}
|
|
368
|
-
return thenMaybe(runInterceptorChain("error", interceptors, {
|
|
369
|
-
command,
|
|
370
|
-
input,
|
|
371
|
-
error,
|
|
372
|
-
signal,
|
|
373
|
-
context,
|
|
374
|
-
runtime,
|
|
375
|
-
program,
|
|
376
|
-
caller,
|
|
377
|
-
...pipelineState
|
|
378
|
-
}, () => ({ error })), (er) => {
|
|
379
|
-
if (er.error !== void 0) return thenMaybe(runShutdown(er.error), () => {
|
|
380
|
-
throw er.error;
|
|
381
|
-
});
|
|
382
|
-
const wrapped = wrapErrorResult ? wrapErrorResult(er.result) : er.result;
|
|
383
|
-
return thenMaybe(runShutdown(void 0, wrapped), () => wrapped);
|
|
384
|
-
});
|
|
385
|
-
};
|
|
386
|
-
const handleSuccess = (result) => {
|
|
387
|
-
const s = runShutdown(void 0, result);
|
|
388
|
-
if (s instanceof Promise) return s.then(() => result);
|
|
389
|
-
return result;
|
|
390
|
-
};
|
|
391
|
-
let result;
|
|
392
|
-
try {
|
|
393
|
-
result = pipeline();
|
|
394
|
-
} catch (e) {
|
|
395
|
-
return runError(e);
|
|
396
|
-
}
|
|
397
|
-
if (result instanceof Promise) return result.then(handleSuccess, runError);
|
|
398
|
-
return handleSuccess(result);
|
|
399
|
-
}
|
|
400
|
-
//#endregion
|
|
401
|
-
//#region src/output/primitives.ts
|
|
402
|
-
function stringifyCell(value) {
|
|
403
|
-
if (value === void 0 || value === null) return "";
|
|
404
|
-
if (typeof value === "string") return value;
|
|
405
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
406
|
-
return JSON.stringify(value);
|
|
407
|
-
}
|
|
408
|
-
function truncate(text, max) {
|
|
409
|
-
if (max <= 0 || text.length <= max) return text;
|
|
410
|
-
return max <= 1 ? "…" : `${text.slice(0, max - 1)}…`;
|
|
411
|
-
}
|
|
412
|
-
function padCell(text, width, alignment = "left") {
|
|
413
|
-
const pad = width - text.length;
|
|
414
|
-
if (pad <= 0) return text;
|
|
415
|
-
if (alignment === "right") return " ".repeat(pad) + text;
|
|
416
|
-
if (alignment === "center") {
|
|
417
|
-
const left = Math.floor(pad / 2);
|
|
418
|
-
return " ".repeat(left) + text + " ".repeat(pad - left);
|
|
419
|
-
}
|
|
420
|
-
return text + " ".repeat(pad);
|
|
421
|
-
}
|
|
422
|
-
function renderTable(data, options, ctx) {
|
|
423
|
-
if (data.length === 0) return "";
|
|
424
|
-
if (ctx.format === "json") return JSON.stringify(data, null, 2);
|
|
425
|
-
const columns = options?.columns ?? Object.keys(data[0]);
|
|
426
|
-
if (columns.length === 0) return "";
|
|
427
|
-
const headers = columns.map((col) => options?.headers?.[col] ?? col);
|
|
428
|
-
const maxCol = options?.maxColumnWidth;
|
|
429
|
-
const rows = data.map((row) => columns.map((col) => {
|
|
430
|
-
const text = stringifyCell(row[col]);
|
|
431
|
-
return maxCol ? truncate(text, maxCol) : text;
|
|
432
|
-
}));
|
|
433
|
-
const colWidths = columns.map((_, i) => {
|
|
434
|
-
const headerWidth = headers[i].length;
|
|
435
|
-
const maxCellWidth = rows.reduce((max, row) => Math.max(max, row[i].length), 0);
|
|
436
|
-
return Math.max(headerWidth, maxCellWidth);
|
|
437
|
-
});
|
|
438
|
-
const getAlign = (i) => options?.align?.[columns[i]] ?? "left";
|
|
439
|
-
if (ctx.format === "markdown") return renderTableMarkdown(headers, rows, colWidths, getAlign);
|
|
440
|
-
if (ctx.format === "html") return renderTableHtml(columns, headers, rows, data, getAlign);
|
|
441
|
-
return renderTableText(headers, rows, colWidths, getAlign, options?.border !== false, ctx);
|
|
442
|
-
}
|
|
443
|
-
function renderTableText(headers, rows, colWidths, getAlign, border, ctx) {
|
|
444
|
-
const { styler } = ctx;
|
|
445
|
-
const formatRow = (cells, style) => cells.map((cell, i) => {
|
|
446
|
-
const padded = padCell(cell, colWidths[i], getAlign(i));
|
|
447
|
-
return style ? style(padded) : padded;
|
|
448
|
-
});
|
|
449
|
-
if (border) {
|
|
450
|
-
const sep = ctx.styler.meta("─");
|
|
451
|
-
const divider = colWidths.map((w) => sep.repeat(w + 2)).join(styler.meta("┼"));
|
|
452
|
-
const row = (cells) => cells.map((c, i) => ` ${padCell(c, colWidths[i], getAlign(i))} `).join(styler.meta("│"));
|
|
453
|
-
const headerRow = row(headers.map((h) => styler.label(h)));
|
|
454
|
-
const dataRows = rows.map((r) => row(r.map((c) => styler.description(c))));
|
|
455
|
-
return [
|
|
456
|
-
headerRow,
|
|
457
|
-
styler.meta("─") + divider + styler.meta("─"),
|
|
458
|
-
...dataRows
|
|
459
|
-
].join("\n");
|
|
460
|
-
}
|
|
461
|
-
const headerCells = formatRow(headers, styler.label);
|
|
462
|
-
const dataCells = rows.map((r) => formatRow(r, styler.description));
|
|
463
|
-
const gap = " ";
|
|
464
|
-
return [headerCells.join(gap), ...dataCells.map((r) => r.join(gap))].join("\n");
|
|
465
|
-
}
|
|
466
|
-
function renderTableMarkdown(headers, rows, colWidths, getAlign) {
|
|
467
|
-
return [
|
|
468
|
-
`| ${headers.map((h, i) => padCell(h, colWidths[i], "left")).join(" | ")} |`,
|
|
469
|
-
"| " + colWidths.map((w, i) => {
|
|
470
|
-
const a = getAlign(i);
|
|
471
|
-
const dashes = "─".repeat(Math.max(w, 3));
|
|
472
|
-
if (a === "center") return `:${dashes}:`;
|
|
473
|
-
if (a === "right") return `${dashes}:`;
|
|
474
|
-
return dashes;
|
|
475
|
-
}).join(" | ") + " |",
|
|
476
|
-
...rows.map((r) => `| ${r.map((c, i) => padCell(c, colWidths[i], "left")).join(" | ")} |`)
|
|
477
|
-
].join("\n");
|
|
478
|
-
}
|
|
479
|
-
function renderTableHtml(columns, headers, _rows, data, getAlign) {
|
|
480
|
-
const ths = headers.map((h, i) => {
|
|
481
|
-
const a = getAlign(i);
|
|
482
|
-
return `<th${a !== "left" ? ` style="text-align: ${a};"` : ""}>${escapeHtml(h)}</th>`;
|
|
483
|
-
});
|
|
484
|
-
const trs = data.map((row) => "<tr>" + columns.map((col, i) => {
|
|
485
|
-
const a = getAlign(i);
|
|
486
|
-
return `<td${a !== "left" ? ` style="text-align: ${a};"` : ""}>${escapeHtml(stringifyCell(row[col]))}</td>`;
|
|
487
|
-
}).join("") + "</tr>");
|
|
488
|
-
return `<table><thead><tr>${ths.join("")}</tr></thead><tbody>${trs.join("")}</tbody></table>`;
|
|
489
|
-
}
|
|
490
|
-
function renderTree(data, options, ctx) {
|
|
491
|
-
const nodes = Array.isArray(data) ? data : [data];
|
|
492
|
-
if (nodes.length === 0) return "";
|
|
493
|
-
if (ctx.format === "json") return JSON.stringify(nodes, null, 2);
|
|
494
|
-
if (ctx.format === "markdown") return renderTreeMarkdown(nodes, 0);
|
|
495
|
-
if (ctx.format === "html") return renderTreeHtml(nodes);
|
|
496
|
-
return renderTreeText(nodes, "", options?.guides !== false, ctx).join("\n");
|
|
497
|
-
}
|
|
498
|
-
function renderTreeText(nodes, prefix, guides, ctx) {
|
|
499
|
-
const lines = [];
|
|
500
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
501
|
-
const node = nodes[i];
|
|
502
|
-
const isLast = i === nodes.length - 1;
|
|
503
|
-
if (guides) {
|
|
504
|
-
const connector = isLast ? "└── " : "├── ";
|
|
505
|
-
const childPrefix = isLast ? " " : "│ ";
|
|
506
|
-
lines.push(prefix + ctx.styler.meta(connector) + ctx.styler.label(node.label));
|
|
507
|
-
if (node.children?.length) lines.push(...renderTreeText(node.children, prefix + ctx.styler.meta(childPrefix), guides, ctx));
|
|
508
|
-
} else {
|
|
509
|
-
const indent = prefix ? `${prefix} ` : "";
|
|
510
|
-
lines.push(indent + ctx.styler.label(node.label));
|
|
511
|
-
if (node.children?.length) lines.push(...renderTreeText(node.children, indent, guides, ctx));
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
return lines;
|
|
515
|
-
}
|
|
516
|
-
function renderTreeMarkdown(nodes, depth) {
|
|
517
|
-
return nodes.map((node) => {
|
|
518
|
-
const line = `${" ".repeat(depth)}- ${node.label}`;
|
|
519
|
-
if (!node.children?.length) return line;
|
|
520
|
-
return `${line}\n${renderTreeMarkdown(node.children, depth + 1)}`;
|
|
521
|
-
}).join("\n");
|
|
522
|
-
}
|
|
523
|
-
function renderTreeHtml(nodes) {
|
|
524
|
-
return `<ul>${nodes.map((node) => {
|
|
525
|
-
const label = escapeHtml(node.label);
|
|
526
|
-
if (!node.children?.length) return `<li>${label}</li>`;
|
|
527
|
-
return `<li>${label}${renderTreeHtml(node.children)}</li>`;
|
|
528
|
-
}).join("")}</ul>`;
|
|
529
|
-
}
|
|
530
|
-
function renderList(data, options, ctx) {
|
|
531
|
-
if (data.length === 0) return "";
|
|
532
|
-
if (ctx.format === "json") {
|
|
533
|
-
const normalized = data.map((item) => typeof item === "string" ? { label: item } : item);
|
|
534
|
-
return JSON.stringify(normalized, null, 2);
|
|
535
|
-
}
|
|
536
|
-
if (ctx.format === "markdown") return renderListMarkdown(data, options);
|
|
537
|
-
if (ctx.format === "html") return renderListHtml(data, options);
|
|
538
|
-
return renderListText(data, options, ctx);
|
|
539
|
-
}
|
|
540
|
-
function renderListText(data, options, ctx) {
|
|
541
|
-
const { styler } = ctx;
|
|
542
|
-
const numbered = options?.numbered ?? false;
|
|
543
|
-
const bullet = options?.bullet ?? (ctx.format === "ansi" ? "•" : "-");
|
|
544
|
-
const baseIndent = " ".repeat(options?.indent ?? 0);
|
|
545
|
-
return data.map((item, i) => {
|
|
546
|
-
const prefix = numbered ? `${i + 1}.` : bullet;
|
|
547
|
-
const label = typeof item === "string" ? item : item.label;
|
|
548
|
-
const desc = typeof item === "object" && item.description ? item.description : void 0;
|
|
549
|
-
const line = `${baseIndent}${styler.meta(prefix)} ${styler.label(label)}`;
|
|
550
|
-
if (!desc) return line;
|
|
551
|
-
return `${line} ${styler.description(desc)}`;
|
|
552
|
-
}).join("\n");
|
|
553
|
-
}
|
|
554
|
-
function renderListMarkdown(data, options) {
|
|
555
|
-
const numbered = options?.numbered ?? false;
|
|
556
|
-
return data.map((item, i) => {
|
|
557
|
-
const prefix = numbered ? `${i + 1}.` : "-";
|
|
558
|
-
const label = typeof item === "string" ? item : item.label;
|
|
559
|
-
const desc = typeof item === "object" && item.description ? item.description : void 0;
|
|
560
|
-
if (!desc) return `${prefix} ${label}`;
|
|
561
|
-
return `${prefix} **${label}** — ${desc}`;
|
|
562
|
-
}).join("\n");
|
|
563
|
-
}
|
|
564
|
-
function renderListHtml(data, options) {
|
|
565
|
-
const tag = options?.numbered ? "ol" : "ul";
|
|
566
|
-
return `<${tag}>${data.map((item) => {
|
|
567
|
-
const label = typeof item === "string" ? item : item.label;
|
|
568
|
-
const desc = typeof item === "object" && item.description ? item.description : void 0;
|
|
569
|
-
if (!desc) return `<li>${escapeHtml(label)}</li>`;
|
|
570
|
-
return `<li><strong>${escapeHtml(label)}</strong> — ${escapeHtml(desc)}</li>`;
|
|
571
|
-
}).join("")}</${tag}>`;
|
|
572
|
-
}
|
|
573
|
-
function renderKeyValue(data, options, ctx) {
|
|
574
|
-
const entries = Object.entries(data);
|
|
575
|
-
if (entries.length === 0) return "";
|
|
576
|
-
if (ctx.format === "json") return JSON.stringify(data, null, 2);
|
|
577
|
-
if (ctx.format === "markdown") return renderKeyValueMarkdown(entries, options);
|
|
578
|
-
if (ctx.format === "html") return renderKeyValueHtml(entries, options);
|
|
579
|
-
return renderKeyValueText(entries, options, ctx);
|
|
580
|
-
}
|
|
581
|
-
function getLabel(key, labels) {
|
|
582
|
-
return labels?.[key] ?? key;
|
|
583
|
-
}
|
|
584
|
-
function renderKeyValueText(entries, options, ctx) {
|
|
585
|
-
const { styler } = ctx;
|
|
586
|
-
const sep = options?.separator ?? ": ";
|
|
587
|
-
const shouldAlign = options?.align !== false;
|
|
588
|
-
const displayLabels = entries.map(([k]) => getLabel(k, options?.labels));
|
|
589
|
-
const maxWidth = shouldAlign ? Math.max(...displayLabels.map((l) => l.length)) : 0;
|
|
590
|
-
return entries.map(([_key, value], i) => {
|
|
591
|
-
const label = displayLabels[i];
|
|
592
|
-
const paddedLabel = shouldAlign ? label + " ".repeat(maxWidth - label.length) : label;
|
|
593
|
-
return `${styler.label(paddedLabel)}${styler.meta(sep)}${styler.description(stringifyCell(value))}`;
|
|
594
|
-
}).join("\n");
|
|
595
|
-
}
|
|
596
|
-
function renderKeyValueMarkdown(entries, options) {
|
|
597
|
-
return entries.map(([key, value]) => `- **${getLabel(key, options?.labels)}**: ${stringifyCell(value)}`).join("\n");
|
|
598
|
-
}
|
|
599
|
-
function renderKeyValueHtml(entries, options) {
|
|
600
|
-
return `<dl>${entries.map(([key, value]) => `<dt>${escapeHtml(getLabel(key, options?.labels))}</dt><dd>${escapeHtml(stringifyCell(value))}</dd>`).join("")}</dl>`;
|
|
601
|
-
}
|
|
602
|
-
//#endregion
|
|
603
|
-
//#region src/output/output-indicator.ts
|
|
604
|
-
/** Create an output indicator that renders through the given output function and format context. */
|
|
605
|
-
function createOutputIndicator(outputFn, ctx) {
|
|
606
|
-
let _called = false;
|
|
607
|
-
const emit = (rendered) => {
|
|
608
|
-
_called = true;
|
|
609
|
-
outputFn(rendered);
|
|
610
|
-
};
|
|
611
|
-
return {
|
|
612
|
-
table(data, options) {
|
|
613
|
-
emit(renderTable(data, options, ctx));
|
|
614
|
-
},
|
|
615
|
-
tree(data, options) {
|
|
616
|
-
emit(renderTree(data, options, ctx));
|
|
617
|
-
},
|
|
618
|
-
list(data, options) {
|
|
619
|
-
emit(renderList(data, options, ctx));
|
|
620
|
-
},
|
|
621
|
-
kv(data, options) {
|
|
622
|
-
emit(renderKeyValue(data, options, ctx));
|
|
623
|
-
},
|
|
624
|
-
raw(...args) {
|
|
625
|
-
_called = true;
|
|
626
|
-
outputFn(...args);
|
|
627
|
-
},
|
|
628
|
-
get called() {
|
|
629
|
-
return _called;
|
|
630
|
-
}
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
/** Format a return value using a declarative output config. */
|
|
634
|
-
function formatDeclarativeOutput(value, config, ctx) {
|
|
635
|
-
const type = typeof config === "string" ? config : config.type;
|
|
636
|
-
const options = typeof config === "object" ? config.options : void 0;
|
|
637
|
-
switch (type) {
|
|
638
|
-
case "table":
|
|
639
|
-
if (!Array.isArray(value)) return void 0;
|
|
640
|
-
return renderTable(value, options, ctx);
|
|
641
|
-
case "tree": return renderTree(value, options, ctx);
|
|
642
|
-
case "list":
|
|
643
|
-
if (!Array.isArray(value)) return void 0;
|
|
644
|
-
return renderList(value, options, ctx);
|
|
645
|
-
case "kv":
|
|
646
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
647
|
-
return renderKeyValue(value, options, ctx);
|
|
648
|
-
case "json": return JSON.stringify(value, null, 2);
|
|
649
|
-
default: return;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
//#endregion
|
|
653
|
-
//#region src/extension/auto-output.ts
|
|
654
|
-
/**
|
|
655
|
-
* Outputs each value and collects into a result.
|
|
656
|
-
* For iterators: outputs each yielded value, returns collected array.
|
|
657
|
-
* For promises: awaits, then recurses.
|
|
658
|
-
* For other values: outputs directly, returns as-is.
|
|
659
|
-
*/
|
|
660
|
-
function outputAndCollect(value, output) {
|
|
661
|
-
if (value == null) return value;
|
|
662
|
-
if (isAsyncIterator(value)) return (async () => {
|
|
663
|
-
const items = [];
|
|
664
|
-
const iter = value[Symbol.asyncIterator]();
|
|
665
|
-
while (true) {
|
|
666
|
-
const { done, value: item } = await iter.next();
|
|
667
|
-
if (done) break;
|
|
668
|
-
items.push(item);
|
|
669
|
-
if (item != null) output(item);
|
|
670
|
-
}
|
|
671
|
-
return items;
|
|
672
|
-
})();
|
|
673
|
-
if (typeof value !== "string" && !Array.isArray(value) && isIterator(value)) {
|
|
674
|
-
const items = [];
|
|
675
|
-
const iter = value[Symbol.iterator]();
|
|
676
|
-
while (true) {
|
|
677
|
-
const { done, value: item } = iter.next();
|
|
678
|
-
if (done) break;
|
|
679
|
-
items.push(item);
|
|
680
|
-
if (item != null) output(item);
|
|
681
|
-
}
|
|
682
|
-
return items;
|
|
683
|
-
}
|
|
684
|
-
if (value instanceof Promise) return value.then((resolved) => outputAndCollect(resolved, output));
|
|
685
|
-
output(value);
|
|
686
|
-
return value;
|
|
687
|
-
}
|
|
688
|
-
const autoOutputMeta = {
|
|
689
|
-
id: "padrone:auto-output",
|
|
690
|
-
name: "padrone:auto-output",
|
|
691
|
-
order: -1100
|
|
692
|
-
};
|
|
693
|
-
function createAutoOutputInterceptor(outputConfig, errorOutput) {
|
|
694
|
-
return defineInterceptor(autoOutputMeta, () => ({
|
|
695
|
-
error(ctx, next) {
|
|
696
|
-
const handleResult = (er) => {
|
|
697
|
-
if (!er.error || errorOutput === false || ctx.caller !== "cli" || ctx.phase !== "execute") return er;
|
|
698
|
-
const message = er.error instanceof Error ? er.error.message : String(er.error);
|
|
699
|
-
ctx.runtime.error(message);
|
|
700
|
-
return er;
|
|
701
|
-
};
|
|
702
|
-
const result = next();
|
|
703
|
-
if (result instanceof Promise) return result.then(handleResult);
|
|
704
|
-
return handleResult(result);
|
|
705
|
-
},
|
|
706
|
-
execute(ctx, next) {
|
|
707
|
-
const outputCtx = resolveOutputFormat(ctx.runtime, ctx.caller);
|
|
708
|
-
const indicator = createOutputIndicator(ctx.runtime.output, outputCtx);
|
|
709
|
-
const handleResult = (e) => {
|
|
710
|
-
if (indicator.called) return e;
|
|
711
|
-
const autoOutput = (value) => {
|
|
712
|
-
if (value == null) return value;
|
|
713
|
-
if (outputConfig) {
|
|
714
|
-
const rendered = formatDeclarativeOutput(value, outputConfig, outputCtx);
|
|
715
|
-
if (rendered !== void 0) {
|
|
716
|
-
ctx.runtime.output(rendered);
|
|
717
|
-
return value;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
return outputAndCollect(value, ctx.runtime.output);
|
|
721
|
-
};
|
|
722
|
-
if (e.result instanceof Promise) return { result: e.result.then(autoOutput) };
|
|
723
|
-
const collected = autoOutput(e.result);
|
|
724
|
-
if (collected instanceof Promise) return collected.then((v) => ({ result: v }));
|
|
725
|
-
return { result: collected };
|
|
726
|
-
};
|
|
727
|
-
const executedOrPromise = next({ context: { output: indicator } });
|
|
728
|
-
if (executedOrPromise instanceof Promise) return executedOrPromise.then(handleResult);
|
|
729
|
-
return handleResult(executedOrPromise);
|
|
730
|
-
}
|
|
731
|
-
}));
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Extension that automatically writes a command's return value to output after execution.
|
|
735
|
-
*
|
|
736
|
-
* - Values are passed directly to the runtime's `output` function (no stringification).
|
|
737
|
-
* - Promises are awaited before output.
|
|
738
|
-
* - Iterators and async iterators are consumed, outputting each yielded value as it arrives.
|
|
739
|
-
* The result is replaced with the collected array so `drain()` still works.
|
|
740
|
-
* - `undefined` and `null` results produce no output.
|
|
741
|
-
*
|
|
742
|
-
* Also injects `ctx.context.output` with format-aware output primitives (table, tree, list, kv).
|
|
743
|
-
* When action handlers use these methods, auto-output skips to avoid double output.
|
|
744
|
-
*
|
|
745
|
-
* Included in the default extensions. Can also be applied per-command:
|
|
746
|
-
* ```ts
|
|
747
|
-
* createPadrone('my-cli')
|
|
748
|
-
* .command('users', (c) =>
|
|
749
|
-
* c.extend(padroneAutoOutput({ output: 'table' }))
|
|
750
|
-
* .action(() => fetchUsers())
|
|
751
|
-
* )
|
|
752
|
-
* ```
|
|
753
|
-
*/
|
|
754
|
-
function padroneAutoOutput(options) {
|
|
755
|
-
const interceptor = options?.disabled ? defineInterceptor({
|
|
756
|
-
...autoOutputMeta,
|
|
757
|
-
disabled: true
|
|
758
|
-
}, () => ({})) : createAutoOutputInterceptor(options?.output, options?.errorOutput);
|
|
759
|
-
return ((builder) => builder.intercept(interceptor));
|
|
760
|
-
}
|
|
761
|
-
//#endregion
|
|
762
|
-
//#region src/extension/color.ts
|
|
763
|
-
const colorInterceptor = defineInterceptor({
|
|
764
|
-
id: "padrone:color",
|
|
765
|
-
name: "padrone:color",
|
|
766
|
-
order: -1001
|
|
767
|
-
}, () => ({ parse(ctx, next) {
|
|
768
|
-
return thenMaybe(next(), (res) => {
|
|
769
|
-
if ("color" in res.rawArgs) {
|
|
770
|
-
const color = res.rawArgs.color;
|
|
771
|
-
delete res.rawArgs.color;
|
|
772
|
-
ctx.runtime.theme = color;
|
|
773
|
-
}
|
|
774
|
-
return res;
|
|
775
|
-
});
|
|
776
|
-
} }));
|
|
777
|
-
/**
|
|
778
|
-
* Extension that handles `--color` / `--no-color` flags:
|
|
779
|
-
* - `--color` or `--color=true` → use default theme
|
|
780
|
-
* - `--color=false` or `--no-color` → disable colors (text format)
|
|
781
|
-
* - `--color=<theme>` → use the named theme
|
|
782
|
-
*
|
|
783
|
-
* Modifies the runtime's format and theme accordingly.
|
|
784
|
-
*
|
|
785
|
-
* Usage:
|
|
786
|
-
* ```ts
|
|
787
|
-
* createPadrone('my-cli').extend(padroneColor())
|
|
788
|
-
* ```
|
|
789
|
-
*/
|
|
790
|
-
function padroneColor() {
|
|
791
|
-
return ((builder) => builder.intercept(colorInterceptor));
|
|
792
|
-
}
|
|
793
|
-
//#endregion
|
|
794
|
-
//#region src/core/parse.ts
|
|
795
|
-
/**
|
|
796
|
-
* Split a string by a delimiter, respecting quoted segments and optional bracket nesting.
|
|
797
|
-
* Handles escape sequences within quotes (\\" and \\\\).
|
|
798
|
-
*/
|
|
799
|
-
function splitQuoteAware(input, delimiter, opts) {
|
|
800
|
-
const results = [];
|
|
801
|
-
let current = "";
|
|
802
|
-
let inQuote = null;
|
|
803
|
-
let bracketDepth = 0;
|
|
804
|
-
let i = 0;
|
|
805
|
-
while (i < input.length) {
|
|
806
|
-
const char = input[i];
|
|
807
|
-
if (inQuote) {
|
|
808
|
-
if (char === "\\" && i + 1 < input.length) {
|
|
809
|
-
const nextChar = input[i + 1];
|
|
810
|
-
if (nextChar === inQuote || nextChar === "\\") {
|
|
811
|
-
current += nextChar;
|
|
812
|
-
i += 2;
|
|
813
|
-
continue;
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
if (char === inQuote) inQuote = null;
|
|
817
|
-
else current += char;
|
|
818
|
-
} else if (opts?.brackets && char === "[") {
|
|
819
|
-
bracketDepth++;
|
|
820
|
-
current += char;
|
|
821
|
-
} else if (opts?.brackets && char === "]") {
|
|
822
|
-
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
823
|
-
current += char;
|
|
824
|
-
} else if (bracketDepth > 0) current += char;
|
|
825
|
-
else if (char === "\"" || char === "'" || char === "`") inQuote = char;
|
|
826
|
-
else if (char === delimiter || delimiter === " " && char === " ") {
|
|
827
|
-
if (delimiter === " " ? current : true) {
|
|
828
|
-
results.push(opts?.trim ? current.trim() : current);
|
|
829
|
-
current = "";
|
|
830
|
-
}
|
|
831
|
-
} else current += char;
|
|
832
|
-
i++;
|
|
833
|
-
}
|
|
834
|
-
if (delimiter === " " ? current : current || results.length > 0) results.push(opts?.trim ? current.trim() : current);
|
|
835
|
-
return results;
|
|
836
|
-
}
|
|
837
|
-
function parseCliInputToParts(input) {
|
|
838
|
-
const parts = splitQuoteAware(input.trim(), " ", { brackets: true });
|
|
839
|
-
const result = [];
|
|
840
|
-
let pendingIdx = -1;
|
|
841
|
-
let allowTerm = true;
|
|
842
|
-
let afterDoubleDash = false;
|
|
843
|
-
for (const part of parts) {
|
|
844
|
-
if (!part) continue;
|
|
845
|
-
if (part === "--" && !afterDoubleDash) {
|
|
846
|
-
pendingIdx = -1;
|
|
847
|
-
afterDoubleDash = true;
|
|
848
|
-
allowTerm = false;
|
|
849
|
-
continue;
|
|
850
|
-
}
|
|
851
|
-
if (afterDoubleDash) {
|
|
852
|
-
result.push({
|
|
853
|
-
type: "arg",
|
|
854
|
-
value: part
|
|
855
|
-
});
|
|
856
|
-
continue;
|
|
857
|
-
}
|
|
858
|
-
const hadPending = pendingIdx;
|
|
859
|
-
pendingIdx = -1;
|
|
860
|
-
if (part.startsWith("--no-") && part.length > 5) {
|
|
861
|
-
const key = part.slice(5).split(".");
|
|
862
|
-
result.push({
|
|
863
|
-
type: "named",
|
|
864
|
-
key,
|
|
865
|
-
value: void 0,
|
|
866
|
-
negated: true
|
|
867
|
-
});
|
|
868
|
-
} else if (part.startsWith("--")) {
|
|
869
|
-
const [keyStr = "", value] = splitNamedArgValue(part.slice(2));
|
|
870
|
-
const key = keyStr.split(".");
|
|
871
|
-
result.push({
|
|
872
|
-
type: "named",
|
|
873
|
-
key,
|
|
874
|
-
value
|
|
875
|
-
});
|
|
876
|
-
if (typeof value === "undefined") pendingIdx = result.length - 1;
|
|
877
|
-
} else if (part.startsWith("-") && part.length > 1 && !/^-\d/.test(part)) {
|
|
878
|
-
const [keyStr = "", value] = splitNamedArgValue(part.slice(1));
|
|
879
|
-
if (keyStr.length > 1 && typeof value === "undefined") {
|
|
880
|
-
for (let ci = 0; ci < keyStr.length - 1; ci++) result.push({
|
|
881
|
-
type: "alias",
|
|
882
|
-
key: [keyStr[ci]],
|
|
883
|
-
value: void 0
|
|
884
|
-
});
|
|
885
|
-
result.push({
|
|
886
|
-
type: "alias",
|
|
887
|
-
key: [keyStr[keyStr.length - 1]],
|
|
888
|
-
value: void 0
|
|
889
|
-
});
|
|
890
|
-
pendingIdx = result.length - 1;
|
|
891
|
-
} else if (keyStr.length > 1 && typeof value !== "undefined") {
|
|
892
|
-
for (let ci = 0; ci < keyStr.length - 1; ci++) result.push({
|
|
893
|
-
type: "alias",
|
|
894
|
-
key: [keyStr[ci]],
|
|
895
|
-
value: void 0
|
|
896
|
-
});
|
|
897
|
-
result.push({
|
|
898
|
-
type: "alias",
|
|
899
|
-
key: [keyStr[keyStr.length - 1]],
|
|
900
|
-
value
|
|
901
|
-
});
|
|
902
|
-
} else {
|
|
903
|
-
result.push({
|
|
904
|
-
type: "alias",
|
|
905
|
-
key: [keyStr],
|
|
906
|
-
value
|
|
907
|
-
});
|
|
908
|
-
if (typeof value === "undefined") pendingIdx = result.length - 1;
|
|
909
|
-
}
|
|
910
|
-
} else if (hadPending >= 0) result[hadPending].value = part;
|
|
911
|
-
else if (/^[a-zA-Z0-9_-]+$/.test(part) && allowTerm) result.push({
|
|
912
|
-
type: "term",
|
|
913
|
-
value: part
|
|
914
|
-
});
|
|
915
|
-
else {
|
|
916
|
-
result.push({
|
|
917
|
-
type: "arg",
|
|
918
|
-
value: part
|
|
919
|
-
});
|
|
920
|
-
allowTerm = false;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
return result;
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* Split named arg key and value, handling quoted values after =.
|
|
927
|
-
*/
|
|
928
|
-
function splitNamedArgValue(str) {
|
|
929
|
-
const eqIndex = str.indexOf("=");
|
|
930
|
-
if (eqIndex === -1) return [str, void 0];
|
|
931
|
-
const key = str.slice(0, eqIndex);
|
|
932
|
-
let value = str.slice(eqIndex + 1);
|
|
933
|
-
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'") || value.startsWith("`") && value.endsWith("`")) {
|
|
934
|
-
value = value.slice(1, -1);
|
|
935
|
-
return [key, value];
|
|
936
|
-
}
|
|
937
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
938
|
-
const inner = value.slice(1, -1);
|
|
939
|
-
if (inner === "") return [key, []];
|
|
940
|
-
return [key, splitQuoteAware(inner, ",", { trim: true })];
|
|
941
|
-
}
|
|
942
|
-
return [key, value];
|
|
943
|
-
}
|
|
944
|
-
/**
|
|
945
|
-
* Sets a value at a nested path in an object.
|
|
946
|
-
* For example: setNestedValue(obj, ['user', 'profile', 'name'], 'John')
|
|
947
|
-
* Creates intermediate objects as needed.
|
|
948
|
-
*/
|
|
949
|
-
function setNestedValue(obj, path, value) {
|
|
950
|
-
let current = obj;
|
|
951
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
952
|
-
const part = path[i];
|
|
953
|
-
if (!(part in current) || typeof current[part] !== "object" || current[part] === null) current[part] = {};
|
|
954
|
-
current = current[part];
|
|
955
|
-
}
|
|
956
|
-
const lastPart = path[path.length - 1];
|
|
957
|
-
current[lastPart] = value;
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* Gets a value at a nested path in an object.
|
|
961
|
-
* Returns undefined if the path doesn't exist.
|
|
962
|
-
*/
|
|
963
|
-
function getNestedValue(obj, path) {
|
|
964
|
-
let current = obj;
|
|
965
|
-
for (const part of path) {
|
|
966
|
-
if (current === null || current === void 0 || typeof current !== "object") return;
|
|
967
|
-
current = current[part];
|
|
968
|
-
}
|
|
969
|
-
return current;
|
|
970
|
-
}
|
|
971
|
-
//#endregion
|
|
972
|
-
//#region src/core/validate.ts
|
|
973
|
-
/**
|
|
974
|
-
* Parses CLI input to find the command and extract raw arguments without validation.
|
|
975
|
-
*/
|
|
976
|
-
function parseCommand(input, rootCommand, findCommandByName) {
|
|
977
|
-
input ??= getCommandRuntime(rootCommand).argv().join(" ") || void 0;
|
|
978
|
-
if (!input) {
|
|
979
|
-
const defaultCommand = findCommandByName("", rootCommand.commands);
|
|
980
|
-
if (defaultCommand) return {
|
|
981
|
-
command: defaultCommand,
|
|
982
|
-
rawArgs: {},
|
|
983
|
-
args: [],
|
|
984
|
-
unmatchedTerms: []
|
|
985
|
-
};
|
|
986
|
-
return {
|
|
987
|
-
command: rootCommand,
|
|
988
|
-
rawArgs: {},
|
|
989
|
-
args: [],
|
|
990
|
-
unmatchedTerms: []
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
const parts = parseCliInputToParts(input);
|
|
994
|
-
const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
|
|
995
|
-
const argTokens = parts.filter((p) => p.type === "arg").map((p) => p.value);
|
|
996
|
-
let curCommand = rootCommand;
|
|
997
|
-
let unmatchedTerms = [];
|
|
998
|
-
if (terms[0] === rootCommand.name) terms.shift();
|
|
999
|
-
for (let i = 0; i < terms.length; i++) {
|
|
1000
|
-
const found = findCommandByName(terms[i] || "", curCommand.commands);
|
|
1001
|
-
if (found) curCommand = found;
|
|
1002
|
-
else {
|
|
1003
|
-
unmatchedTerms = terms.slice(i);
|
|
1004
|
-
argTokens.unshift(...unmatchedTerms);
|
|
1005
|
-
break;
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
if (unmatchedTerms.length === 0 && curCommand.commands?.length) {
|
|
1009
|
-
const defaultCommand = findCommandByName("", curCommand.commands);
|
|
1010
|
-
if (defaultCommand) curCommand = defaultCommand;
|
|
1011
|
-
}
|
|
1012
|
-
if (!curCommand) return {
|
|
1013
|
-
command: rootCommand,
|
|
1014
|
-
rawArgs: {},
|
|
1015
|
-
args: argTokens,
|
|
1016
|
-
unmatchedTerms
|
|
1017
|
-
};
|
|
1018
|
-
const argsMeta = curCommand.meta?.fields;
|
|
1019
|
-
const { flags, aliases, negatives, customNegation } = curCommand.argsSchema ? extractSchemaMetadata(curCommand.argsSchema, argsMeta, curCommand.meta?.autoAlias) : {
|
|
1020
|
-
flags: {},
|
|
1021
|
-
aliases: {},
|
|
1022
|
-
negatives: {},
|
|
1023
|
-
customNegation: /* @__PURE__ */ new Set()
|
|
1024
|
-
};
|
|
1025
|
-
const arrayArguments = /* @__PURE__ */ new Set();
|
|
1026
|
-
if (curCommand.argsSchema) try {
|
|
1027
|
-
const jsonSchema = getJsonSchema(curCommand.argsSchema);
|
|
1028
|
-
if (jsonSchema.type === "object" && jsonSchema.properties) {
|
|
1029
|
-
for (const [key, prop] of Object.entries(jsonSchema.properties)) if (prop?.type === "array") arrayArguments.add(key);
|
|
1030
|
-
}
|
|
1031
|
-
} catch {}
|
|
1032
|
-
const argParts = parts.filter((p) => p.type === "named" || p.type === "alias");
|
|
1033
|
-
const rawArgs = {};
|
|
1034
|
-
for (const arg of argParts) {
|
|
1035
|
-
let key;
|
|
1036
|
-
if (arg.type === "alias" && arg.key.length === 1 && flags[arg.key[0]]) key = [flags[arg.key[0]]];
|
|
1037
|
-
else if (arg.type === "named" && arg.key.length === 1 && aliases[arg.key[0]]) key = [aliases[arg.key[0]]];
|
|
1038
|
-
else if (arg.type === "named" && arg.key.length === 1 && negatives[arg.key[0]]) {
|
|
1039
|
-
setNestedValue(rawArgs, [negatives[arg.key[0]]], false);
|
|
1040
|
-
continue;
|
|
1041
|
-
} else key = arg.key;
|
|
1042
|
-
const rootKey = key[0];
|
|
1043
|
-
if (arg.type === "named" && arg.negated) {
|
|
1044
|
-
if (customNegation.has(rootKey)) {
|
|
1045
|
-
setNestedValue(rawArgs, [`no-${key.join(".")}`], false);
|
|
1046
|
-
continue;
|
|
1047
|
-
}
|
|
1048
|
-
setNestedValue(rawArgs, key, false);
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
const value = arg.value ?? true;
|
|
1052
|
-
if (arrayArguments.has(rootKey)) {
|
|
1053
|
-
const existing = getNestedValue(rawArgs, key);
|
|
1054
|
-
if (existing !== void 0) if (Array.isArray(existing)) if (Array.isArray(value)) existing.push(...value);
|
|
1055
|
-
else existing.push(value);
|
|
1056
|
-
else if (Array.isArray(value)) setNestedValue(rawArgs, key, [existing, ...value]);
|
|
1057
|
-
else setNestedValue(rawArgs, key, [existing, value]);
|
|
1058
|
-
else setNestedValue(rawArgs, key, Array.isArray(value) ? value : [value]);
|
|
1059
|
-
} else {
|
|
1060
|
-
const existing = getNestedValue(rawArgs, key);
|
|
1061
|
-
if (existing !== void 0) if (Array.isArray(existing)) if (Array.isArray(value)) existing.push(...value);
|
|
1062
|
-
else existing.push(value);
|
|
1063
|
-
else if (Array.isArray(value)) setNestedValue(rawArgs, key, [existing, ...value]);
|
|
1064
|
-
else setNestedValue(rawArgs, key, [existing, value]);
|
|
1065
|
-
else setNestedValue(rawArgs, key, value);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
return {
|
|
1069
|
-
command: curCommand,
|
|
1070
|
-
rawArgs,
|
|
1071
|
-
args: argTokens,
|
|
1072
|
-
unmatchedTerms
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Preprocesses raw arguments: maps positional arguments and performs auto-coercion.
|
|
1077
|
-
* External data sources (stdin, env, config) are handled by extensions before this runs.
|
|
1078
|
-
*/
|
|
1079
|
-
function buildCommandArgs(command, rawArgs, positionalArgs) {
|
|
1080
|
-
let preprocessedArgs = preprocessArgs(rawArgs, {
|
|
1081
|
-
flags: {},
|
|
1082
|
-
aliases: {}
|
|
1083
|
-
});
|
|
1084
|
-
let issues;
|
|
1085
|
-
const positionalConfig = command.meta?.positional ? parsePositionalConfig(command.meta.positional) : [];
|
|
1086
|
-
if (positionalConfig.length > 0) {
|
|
1087
|
-
let argIndex = 0;
|
|
1088
|
-
for (let i = 0; i < positionalConfig.length; i++) {
|
|
1089
|
-
const { name, variadic } = positionalConfig[i];
|
|
1090
|
-
if (argIndex >= positionalArgs.length) break;
|
|
1091
|
-
if (name in preprocessedArgs) {
|
|
1092
|
-
issues ??= [];
|
|
1093
|
-
issues.push({
|
|
1094
|
-
path: [name],
|
|
1095
|
-
message: `Ambiguous argument "${name}": provided both positionally and as a named option`
|
|
1096
|
-
});
|
|
1097
|
-
continue;
|
|
1098
|
-
}
|
|
1099
|
-
if (variadic) {
|
|
1100
|
-
const nonVariadicAfter = positionalConfig.slice(i + 1).filter((p) => !p.variadic).length;
|
|
1101
|
-
const variadicEnd = positionalArgs.length - nonVariadicAfter;
|
|
1102
|
-
preprocessedArgs[name] = positionalArgs.slice(argIndex, variadicEnd);
|
|
1103
|
-
argIndex = variadicEnd;
|
|
1104
|
-
} else if (i === positionalConfig.length - 1 && positionalArgs.length > argIndex + 1) {
|
|
1105
|
-
preprocessedArgs[name] = positionalArgs.slice(argIndex).join(" ");
|
|
1106
|
-
argIndex = positionalArgs.length;
|
|
1107
|
-
} else {
|
|
1108
|
-
preprocessedArgs[name] = positionalArgs[argIndex];
|
|
1109
|
-
argIndex++;
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
if (command.argsSchema) preprocessedArgs = coerceArgs(preprocessedArgs, command.argsSchema);
|
|
1114
|
-
return {
|
|
1115
|
-
args: preprocessedArgs,
|
|
1116
|
-
issues
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
/**
|
|
1120
|
-
* Detects unknown options in args that aren't defined in the schema.
|
|
1121
|
-
* Returns unknown key info with suggestions, or empty array if schema is loose.
|
|
1122
|
-
*/
|
|
1123
|
-
function checkUnknownArgs(command, preprocessedArgs) {
|
|
1124
|
-
if (!command.argsSchema) {
|
|
1125
|
-
const unknowns = [];
|
|
1126
|
-
for (const key of Object.keys(preprocessedArgs)) unknowns.push({ key });
|
|
1127
|
-
return unknowns;
|
|
1128
|
-
}
|
|
1129
|
-
const argsMeta = command.meta?.fields;
|
|
1130
|
-
const { flags, aliases, negatives } = extractSchemaMetadata(command.argsSchema, argsMeta, command.meta?.autoAlias);
|
|
1131
|
-
return detectUnknownArgs(preprocessedArgs, command.argsSchema, flags, aliases, negatives);
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* Validates preprocessed arguments against the command's schema.
|
|
1135
|
-
* First checks for unknown args (strict by default), then runs schema validation.
|
|
1136
|
-
* Returns sync or async result depending on the schema's validate method.
|
|
1137
|
-
*/
|
|
1138
|
-
function validateCommandArgs(command, preprocessedArgs) {
|
|
1139
|
-
const unknownArgs = checkUnknownArgs(command, preprocessedArgs);
|
|
1140
|
-
if (unknownArgs.length > 0) return {
|
|
1141
|
-
args: void 0,
|
|
1142
|
-
argsResult: { issues: unknownArgs.map(({ key }) => ({
|
|
1143
|
-
path: [key],
|
|
1144
|
-
message: `Unknown option: "${key}"`
|
|
1145
|
-
})) }
|
|
1146
|
-
};
|
|
1147
|
-
const argsParsed = command.argsSchema ? command.argsSchema["~standard"].validate(preprocessedArgs) : { value: {} };
|
|
1148
|
-
const buildResult = (parsed) => ({
|
|
1149
|
-
args: parsed.issues ? void 0 : parsed.value,
|
|
1150
|
-
argsResult: parsed
|
|
1151
|
-
});
|
|
1152
|
-
return thenMaybe(argsParsed, buildResult);
|
|
1153
|
-
}
|
|
1154
|
-
/**
|
|
1155
|
-
* Returns the list of known option names from a command's schema (for fuzzy suggestion).
|
|
1156
|
-
*/
|
|
1157
|
-
function getKnownOptionNames(command) {
|
|
1158
|
-
if (!command.argsSchema) return [];
|
|
1159
|
-
try {
|
|
1160
|
-
const js = getJsonSchema(command.argsSchema);
|
|
1161
|
-
if (js.type === "object" && js.properties) return Object.keys(js.properties);
|
|
1162
|
-
} catch {}
|
|
1163
|
-
return [];
|
|
1164
|
-
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Formats validation issue messages for display.
|
|
1167
|
-
*/
|
|
1168
|
-
function formatIssueMessages(issues) {
|
|
1169
|
-
return issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n");
|
|
1170
|
-
}
|
|
1171
|
-
/**
|
|
1172
|
-
* Core validate function for parse() — preprocesses and validates CLI args.
|
|
1173
|
-
* Used by the parse program method (lighter weight than the full exec pipeline).
|
|
1174
|
-
* External data sources (stdin, env, config) are not resolved here — use eval() for that.
|
|
1175
|
-
*/
|
|
1176
|
-
function coreValidateForParse(command, rawArgs, positionalArgs) {
|
|
1177
|
-
const { args: preprocessedArgs, issues } = buildCommandArgs(command, rawArgs, positionalArgs);
|
|
1178
|
-
if (issues) return {
|
|
1179
|
-
args: void 0,
|
|
1180
|
-
argsResult: { issues }
|
|
1181
|
-
};
|
|
1182
|
-
return thenMaybe(validateCommandArgs(command, preprocessedArgs), (v) => v);
|
|
1183
|
-
}
|
|
1184
|
-
//#endregion
|
|
1185
|
-
//#region src/extension/utils.ts
|
|
1186
|
-
/** Minimal Standard Schema that passes through known fields, ignoring unknown ones. */
|
|
1187
|
-
function passthroughSchema(fields) {
|
|
1188
|
-
return { "~standard": {
|
|
1189
|
-
version: 1,
|
|
1190
|
-
vendor: "padrone",
|
|
1191
|
-
jsonSchema: {
|
|
1192
|
-
input: () => ({}),
|
|
1193
|
-
output: () => ({})
|
|
1194
|
-
},
|
|
1195
|
-
validate: (value) => {
|
|
1196
|
-
const input = value && typeof value === "object" ? value : {};
|
|
1197
|
-
const result = {};
|
|
1198
|
-
for (const [name, type] of Object.entries(fields)) {
|
|
1199
|
-
const v = input[name];
|
|
1200
|
-
if (v === void 0) continue;
|
|
1201
|
-
if (type === "string[]") {
|
|
1202
|
-
if (Array.isArray(v)) result[name] = v.map(String);
|
|
1203
|
-
else if (typeof v === "string") result[name] = [v];
|
|
1204
|
-
} else if (type === "string") {
|
|
1205
|
-
if (typeof v === "string") result[name] = v;
|
|
1206
|
-
else if (Array.isArray(v) && v.length > 0) result[name] = String(v[0]);
|
|
1207
|
-
} else if (type === "boolean") result[name] = v === true || v === "true";
|
|
1208
|
-
}
|
|
1209
|
-
return { value: result };
|
|
1210
|
-
}
|
|
1211
|
-
} };
|
|
1212
|
-
}
|
|
1213
|
-
/** Find a command by space-separated name in the command tree. */
|
|
1214
|
-
function findCommandInTree(name, rootCommand) {
|
|
1215
|
-
const parts = name.split(" ").filter(Boolean);
|
|
1216
|
-
let current = rootCommand;
|
|
1217
|
-
for (const part of parts) {
|
|
1218
|
-
const found = current.commands?.find((c) => c.name === part || c.aliases?.includes(part));
|
|
1219
|
-
if (!found) return void 0;
|
|
1220
|
-
current = found;
|
|
1221
|
-
}
|
|
1222
|
-
return current;
|
|
1223
|
-
}
|
|
1224
|
-
//#endregion
|
|
1225
|
-
//#region src/extension/help.ts
|
|
1226
|
-
const helpInterceptor = defineInterceptor({
|
|
1227
|
-
id: "padrone:help",
|
|
1228
|
-
name: "padrone:help",
|
|
1229
|
-
order: -1e3
|
|
1230
|
-
}, () => {
|
|
1231
|
-
let helpText;
|
|
1232
|
-
let showDefaultHelp = false;
|
|
1233
|
-
return {
|
|
1234
|
-
parse(ctx, next) {
|
|
1235
|
-
return thenMaybe(next(), (res) => {
|
|
1236
|
-
const hasHelpFlag = res.rawArgs.help || res.rawArgs.h;
|
|
1237
|
-
const reverseHelp = !hasHelpFlag && res.positionalArgs?.length > 0 && res.positionalArgs[res.positionalArgs.length - 1] === "help";
|
|
1238
|
-
if (hasHelpFlag || reverseHelp) {
|
|
1239
|
-
delete res.rawArgs.help;
|
|
1240
|
-
delete res.rawArgs.h;
|
|
1241
|
-
const detail = res.rawArgs.detail;
|
|
1242
|
-
const format = res.rawArgs.format;
|
|
1243
|
-
const all = res.rawArgs.all;
|
|
1244
|
-
delete res.rawArgs.detail;
|
|
1245
|
-
delete res.rawArgs.format;
|
|
1246
|
-
delete res.rawArgs.all;
|
|
1247
|
-
delete res.rawArgs.d;
|
|
1248
|
-
delete res.rawArgs.f;
|
|
1249
|
-
const rootCommand = getRootCommand(res.command);
|
|
1250
|
-
resolveAllCommands(rootCommand);
|
|
1251
|
-
helpText = generateHelp(rootCommand, res.command, {
|
|
1252
|
-
detail,
|
|
1253
|
-
format: format ?? ctx.runtime.format,
|
|
1254
|
-
theme: ctx.runtime.theme,
|
|
1255
|
-
all,
|
|
1256
|
-
terminal: ctx.runtime.terminal,
|
|
1257
|
-
env: ctx.runtime.env()
|
|
1258
|
-
});
|
|
1259
|
-
return res;
|
|
1260
|
-
}
|
|
1261
|
-
if (helpText === void 0) {
|
|
1262
|
-
const { command } = res;
|
|
1263
|
-
const hasSubcommands = command.commands && command.commands.length > 0;
|
|
1264
|
-
const hasSchema = command.argsSchema != null;
|
|
1265
|
-
const hasUnmatchedTerms = res.positionalArgs?.length > 0 && !command.meta?.positional?.length;
|
|
1266
|
-
if (!command.action && (hasSubcommands || !hasSchema) && !hasUnmatchedTerms) showDefaultHelp = true;
|
|
1267
|
-
}
|
|
1268
|
-
return res;
|
|
1269
|
-
});
|
|
1270
|
-
},
|
|
1271
|
-
validate(_ctx, next) {
|
|
1272
|
-
if (helpText !== void 0) return {
|
|
1273
|
-
args: void 0,
|
|
1274
|
-
argsResult: { value: void 0 }
|
|
1275
|
-
};
|
|
1276
|
-
return next();
|
|
1277
|
-
},
|
|
1278
|
-
execute(ctx, next) {
|
|
1279
|
-
if (helpText !== void 0) return { result: helpText };
|
|
1280
|
-
if (showDefaultHelp) {
|
|
1281
|
-
const rootCommand = getRootCommand(ctx.command);
|
|
1282
|
-
resolveAllCommands(rootCommand);
|
|
1283
|
-
return { result: generateHelp(rootCommand, ctx.command, {
|
|
1284
|
-
format: ctx.runtime.format,
|
|
1285
|
-
theme: ctx.runtime.theme,
|
|
1286
|
-
terminal: ctx.runtime.terminal,
|
|
1287
|
-
env: ctx.runtime.env()
|
|
1288
|
-
}) };
|
|
1289
|
-
}
|
|
1290
|
-
return next();
|
|
1291
|
-
},
|
|
1292
|
-
error(ctx, next) {
|
|
1293
|
-
return thenMaybe(next(), (er) => {
|
|
1294
|
-
if (ctx.caller !== "cli" || !er.error) return er;
|
|
1295
|
-
const rootCommand = getRootCommand(ctx.command);
|
|
1296
|
-
if (er.error instanceof RoutingError) {
|
|
1297
|
-
const targetPath = er.error.command;
|
|
1298
|
-
const sourceCmd = (targetPath ? findCommandInTree(targetPath, rootCommand) : void 0) ?? rootCommand;
|
|
1299
|
-
ctx.runtime.error(er.error.message);
|
|
1300
|
-
if (er.error.suggestions.length > 0) {
|
|
1301
|
-
const visibleCommands = (sourceCmd.commands ?? []).filter((c) => !c.hidden && c.name);
|
|
1302
|
-
if (visibleCommands.length > 0) {
|
|
1303
|
-
for (const cmd of visibleCommands) resolveCommand(cmd);
|
|
1304
|
-
const cmdList = visibleCommands.map((c) => c.name).join(", ");
|
|
1305
|
-
ctx.runtime.output(`\nAvailable commands: ${cmdList}`);
|
|
1306
|
-
}
|
|
1307
|
-
} else {
|
|
1308
|
-
resolveAllCommands(rootCommand);
|
|
1309
|
-
const helpText = generateHelp(rootCommand, sourceCmd, {
|
|
1310
|
-
format: ctx.runtime.format,
|
|
1311
|
-
theme: ctx.runtime.theme,
|
|
1312
|
-
terminal: ctx.runtime.terminal,
|
|
1313
|
-
env: ctx.runtime.env()
|
|
1314
|
-
});
|
|
1315
|
-
ctx.runtime.error(helpText);
|
|
1316
|
-
}
|
|
1317
|
-
return er;
|
|
1318
|
-
}
|
|
1319
|
-
if (er.error instanceof ValidationError) {
|
|
1320
|
-
const targetPath = er.error.command;
|
|
1321
|
-
const targetCommand = targetPath ? findCommandInTree(targetPath, rootCommand) : void 0;
|
|
1322
|
-
const issueMessages = formatIssueMessages(er.error.issues);
|
|
1323
|
-
resolveAllCommands(rootCommand);
|
|
1324
|
-
const helpText = generateHelp(rootCommand, targetCommand ?? rootCommand, {
|
|
1325
|
-
format: ctx.runtime.format,
|
|
1326
|
-
theme: ctx.runtime.theme,
|
|
1327
|
-
terminal: ctx.runtime.terminal,
|
|
1328
|
-
env: ctx.runtime.env()
|
|
1329
|
-
});
|
|
1330
|
-
ctx.runtime.error(`Validation error:\n${issueMessages}`);
|
|
1331
|
-
ctx.runtime.error(helpText);
|
|
1332
|
-
return er;
|
|
1333
|
-
}
|
|
1334
|
-
return er;
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
};
|
|
1338
|
-
});
|
|
1339
|
-
/**
|
|
1340
|
-
* Extension that adds help support:
|
|
1341
|
-
* - `help` command with aliases `h` and `` (empty = executes on root when no subcommand matches)
|
|
1342
|
-
* - `--help` / `-h` flags
|
|
1343
|
-
* - `<cmd> help` reverse syntax
|
|
1344
|
-
* - Default help display when a command has no action
|
|
1345
|
-
*
|
|
1346
|
-
* Usage:
|
|
1347
|
-
* ```ts
|
|
1348
|
-
* createPadrone('my-cli').extend(padroneHelp())
|
|
1349
|
-
* ```
|
|
1350
|
-
*/
|
|
1351
|
-
function padroneHelp() {
|
|
1352
|
-
return ((builder) => builder.command(["help", "h"], (c) => c.configure({
|
|
1353
|
-
description: "Display help for a command",
|
|
1354
|
-
hidden: true
|
|
1355
|
-
}).arguments(passthroughSchema({
|
|
1356
|
-
command: "string[]",
|
|
1357
|
-
detail: "string",
|
|
1358
|
-
format: "string",
|
|
1359
|
-
all: "boolean"
|
|
1360
|
-
}), { positional: ["...command"] }).action((args, ctx) => {
|
|
1361
|
-
const rootCommand = getRootCommand(ctx.command);
|
|
1362
|
-
resolveAllCommands(rootCommand);
|
|
1363
|
-
const commandName = args.command?.join(" ");
|
|
1364
|
-
return generateHelp(rootCommand, (commandName ? findCommandInTree(commandName, rootCommand) : rootCommand) ?? rootCommand, {
|
|
1365
|
-
detail: args.detail,
|
|
1366
|
-
format: args.format ?? ctx.runtime.format,
|
|
1367
|
-
theme: ctx.runtime.theme,
|
|
1368
|
-
all: args.all,
|
|
1369
|
-
terminal: ctx.runtime.terminal,
|
|
1370
|
-
env: ctx.runtime.env()
|
|
1371
|
-
});
|
|
1372
|
-
})).intercept(helpInterceptor));
|
|
1373
|
-
}
|
|
1374
|
-
//#endregion
|
|
1375
|
-
//#region src/feature/interactive.ts
|
|
1376
|
-
/**
|
|
1377
|
-
* Auto-detect the prompt type for a field based on its JSON schema property definition.
|
|
1378
|
-
*/
|
|
1379
|
-
function detectPromptConfig(name, propSchema, description) {
|
|
1380
|
-
const message = description || propSchema?.description || name;
|
|
1381
|
-
if (!propSchema) return {
|
|
1382
|
-
name,
|
|
1383
|
-
message,
|
|
1384
|
-
type: "input"
|
|
1385
|
-
};
|
|
1386
|
-
if (propSchema.type === "boolean") return {
|
|
1387
|
-
name,
|
|
1388
|
-
message,
|
|
1389
|
-
type: "confirm",
|
|
1390
|
-
default: propSchema.default
|
|
1391
|
-
};
|
|
1392
|
-
if (propSchema.enum) return {
|
|
1393
|
-
name,
|
|
1394
|
-
message,
|
|
1395
|
-
type: "select",
|
|
1396
|
-
choices: propSchema.enum.map((v) => ({
|
|
1397
|
-
label: String(v),
|
|
1398
|
-
value: v
|
|
1399
|
-
})),
|
|
1400
|
-
default: propSchema.default
|
|
1401
|
-
};
|
|
1402
|
-
if (propSchema.type === "array" && propSchema.items?.enum) return {
|
|
1403
|
-
name,
|
|
1404
|
-
message,
|
|
1405
|
-
type: "multiselect",
|
|
1406
|
-
choices: propSchema.items.enum.map((v) => ({
|
|
1407
|
-
label: String(v),
|
|
1408
|
-
value: v
|
|
1409
|
-
})),
|
|
1410
|
-
default: propSchema.default
|
|
1411
|
-
};
|
|
1412
|
-
if (propSchema.format === "password") return {
|
|
1413
|
-
name,
|
|
1414
|
-
message,
|
|
1415
|
-
type: "password",
|
|
1416
|
-
default: propSchema.default
|
|
1417
|
-
};
|
|
1418
|
-
return {
|
|
1419
|
-
name,
|
|
1420
|
-
message,
|
|
1421
|
-
type: "input",
|
|
1422
|
-
default: propSchema.default
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1425
|
-
/**
|
|
1426
|
-
* Prompt a single field and validate it against the command's schema.
|
|
1427
|
-
* Re-prompts with a warning until the user provides a valid value.
|
|
1428
|
-
*/
|
|
1429
|
-
async function promptWithValidation(field, config, currentData, command, runtime) {
|
|
1430
|
-
let promptConfig = config;
|
|
1431
|
-
while (true) {
|
|
1432
|
-
const value = await runtime.prompt(promptConfig);
|
|
1433
|
-
if (!command.argsSchema) return value;
|
|
1434
|
-
const testData = {
|
|
1435
|
-
...currentData,
|
|
1436
|
-
[field]: value
|
|
1437
|
-
};
|
|
1438
|
-
const validated = await command.argsSchema["~standard"].validate(testData);
|
|
1439
|
-
if (!validated.issues) return value;
|
|
1440
|
-
const fieldIssues = validated.issues.filter((issue) => {
|
|
1441
|
-
const rootKey = issue.path?.[0];
|
|
1442
|
-
return rootKey !== void 0 && String(rootKey) === field;
|
|
1443
|
-
});
|
|
1444
|
-
if (fieldIssues.length === 0) return value;
|
|
1445
|
-
const messages = fieldIssues.map((i) => i.message).join("; ");
|
|
1446
|
-
runtime.error(`Invalid value for "${field}": ${messages}`);
|
|
1447
|
-
promptConfig = {
|
|
1448
|
-
...config,
|
|
1449
|
-
default: value
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
/**
|
|
1454
|
-
* Prompt for missing interactive fields.
|
|
1455
|
-
* Runs after env/config preprocessing and before schema validation.
|
|
1456
|
-
*
|
|
1457
|
-
* When `force` is true, all configured interactive fields are prompted even if they already
|
|
1458
|
-
* have values. The current values are used as defaults in the prompts.
|
|
1459
|
-
*/
|
|
1460
|
-
async function promptInteractiveFields(data, command, runtime, force) {
|
|
1461
|
-
if (!runtime.prompt) return data;
|
|
1462
|
-
const meta = command.meta;
|
|
1463
|
-
const interactiveConfig = meta?.interactive;
|
|
1464
|
-
const optionalInteractiveConfig = meta?.optionalInteractive;
|
|
1465
|
-
if (!interactiveConfig && !optionalInteractiveConfig) return data;
|
|
1466
|
-
let jsonProperties = {};
|
|
1467
|
-
let requiredFields = /* @__PURE__ */ new Set();
|
|
1468
|
-
if (command.argsSchema) try {
|
|
1469
|
-
const jsonSchema = getJsonSchema(command.argsSchema);
|
|
1470
|
-
if (jsonSchema.type === "object" && jsonSchema.properties) jsonProperties = jsonSchema.properties;
|
|
1471
|
-
if (Array.isArray(jsonSchema.required)) requiredFields = new Set(jsonSchema.required);
|
|
1472
|
-
} catch {}
|
|
1473
|
-
const fieldDescriptions = {};
|
|
1474
|
-
if (meta?.fields) {
|
|
1475
|
-
for (const [key, value] of Object.entries(meta.fields)) if (value?.description) fieldDescriptions[key] = value.description;
|
|
1476
|
-
}
|
|
1477
|
-
const result = { ...data };
|
|
1478
|
-
let fieldsToPrompt = [];
|
|
1479
|
-
if (interactiveConfig === true) if (force) fieldsToPrompt = [...requiredFields];
|
|
1480
|
-
else fieldsToPrompt = [...requiredFields].filter((name) => result[name] === void 0);
|
|
1481
|
-
else if (Array.isArray(interactiveConfig)) if (force) fieldsToPrompt = [...interactiveConfig];
|
|
1482
|
-
else fieldsToPrompt = interactiveConfig.filter((name) => result[name] === void 0);
|
|
1483
|
-
for (const field of fieldsToPrompt) {
|
|
1484
|
-
const config = detectPromptConfig(field, jsonProperties[field], fieldDescriptions[field]);
|
|
1485
|
-
if (force && result[field] !== void 0) config.default = result[field];
|
|
1486
|
-
result[field] = await promptWithValidation(field, config, result, command, runtime);
|
|
1487
|
-
}
|
|
1488
|
-
let optionalFields = [];
|
|
1489
|
-
if (optionalInteractiveConfig === true) if (force) optionalFields = Object.keys(jsonProperties).filter((name) => !requiredFields.has(name));
|
|
1490
|
-
else optionalFields = Object.keys(jsonProperties).filter((name) => !requiredFields.has(name) && result[name] === void 0);
|
|
1491
|
-
else if (Array.isArray(optionalInteractiveConfig)) if (force) optionalFields = [...optionalInteractiveConfig];
|
|
1492
|
-
else optionalFields = optionalInteractiveConfig.filter((name) => result[name] === void 0);
|
|
1493
|
-
if (optionalFields.length > 0) {
|
|
1494
|
-
const selected = await runtime.prompt({
|
|
1495
|
-
name: "_optionalFields",
|
|
1496
|
-
message: "Would you also like to configure:",
|
|
1497
|
-
type: "multiselect",
|
|
1498
|
-
choices: optionalFields.map((f) => {
|
|
1499
|
-
const label = fieldDescriptions[f] || jsonProperties[f]?.description || f;
|
|
1500
|
-
const currentValue = result[f];
|
|
1501
|
-
return {
|
|
1502
|
-
label: force && currentValue !== void 0 ? `${label} (current: ${currentValue})` : label,
|
|
1503
|
-
value: f
|
|
1504
|
-
};
|
|
1505
|
-
})
|
|
1506
|
-
});
|
|
1507
|
-
if (Array.isArray(selected)) for (const field of selected) {
|
|
1508
|
-
const config = detectPromptConfig(field, jsonProperties[field], fieldDescriptions[field]);
|
|
1509
|
-
if (force && result[field] !== void 0) config.default = result[field];
|
|
1510
|
-
result[field] = await promptWithValidation(field, config, result, command, runtime);
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
return result;
|
|
1514
|
-
}
|
|
1515
|
-
//#endregion
|
|
1516
|
-
//#region src/extension/interactive.ts
|
|
1517
|
-
const interactiveInterceptor = defineInterceptor({
|
|
1518
|
-
id: "padrone:interactive",
|
|
1519
|
-
name: "padrone:interactive",
|
|
1520
|
-
order: -999
|
|
1521
|
-
}, () => ({ validate(ctx, next) {
|
|
1522
|
-
let flagInteractive;
|
|
1523
|
-
if (hasInteractiveConfig(ctx.command.meta)) {
|
|
1524
|
-
if (ctx.rawArgs.interactive !== void 0) {
|
|
1525
|
-
flagInteractive = ctx.rawArgs.interactive !== false && ctx.rawArgs.interactive !== "false";
|
|
1526
|
-
delete ctx.rawArgs.interactive;
|
|
1527
|
-
}
|
|
1528
|
-
if (ctx.rawArgs.i !== void 0) {
|
|
1529
|
-
flagInteractive = ctx.rawArgs.i !== false && ctx.rawArgs.i !== "false";
|
|
1530
|
-
delete ctx.rawArgs.i;
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
const { runtime, command } = ctx;
|
|
1534
|
-
const runtimeDefault = runtime.interactive === "forced" ? true : runtime.interactive === "disabled" ? false : void 0;
|
|
1535
|
-
const effectiveInteractive = flagInteractive ?? ctx.evalInteractive ?? runtimeDefault;
|
|
1536
|
-
const stdinIsPiped = !!command.meta?.stdin && (runtime.stdin ? !runtime.stdin.isTTY : runtime.terminal?.isTTY !== true);
|
|
1537
|
-
const interactivitySuppressed = runtime.interactive === "unsupported" || effectiveInteractive === false || stdinIsPiped && effectiveInteractive !== true;
|
|
1538
|
-
const forceInteractive = !interactivitySuppressed && effectiveInteractive === true;
|
|
1539
|
-
if (!(!interactivitySuppressed && runtime.prompt && hasInteractiveConfig(command.meta))) return next();
|
|
1540
|
-
const { args: preprocessedArgs, issues: positionalIssues } = buildCommandArgs(command, ctx.rawArgs, ctx.positionalArgs);
|
|
1541
|
-
if (positionalIssues) return {
|
|
1542
|
-
args: void 0,
|
|
1543
|
-
argsResult: { issues: positionalIssues }
|
|
1544
|
-
};
|
|
1545
|
-
const unknowns = checkUnknownArgs(command, preprocessedArgs);
|
|
1546
|
-
if (unknowns.length > 0) return {
|
|
1547
|
-
args: void 0,
|
|
1548
|
-
argsResult: { issues: unknowns.map(({ key }) => ({
|
|
1549
|
-
path: [key],
|
|
1550
|
-
message: `Unknown option: "${key}"`
|
|
1551
|
-
})) }
|
|
1552
|
-
};
|
|
1553
|
-
const earlyValidateAndPrompt = () => {
|
|
1554
|
-
if (command.argsSchema) {
|
|
1555
|
-
const providedKeys = new Set(Object.keys(preprocessedArgs).filter((k) => preprocessedArgs[k] !== void 0));
|
|
1556
|
-
const earlyCheck = command.argsSchema["~standard"].validate(preprocessedArgs);
|
|
1557
|
-
const checkForProvidedFieldErrors = (result) => {
|
|
1558
|
-
if (!result.issues) return void 0;
|
|
1559
|
-
const providedFieldIssues = result.issues.filter((issue) => {
|
|
1560
|
-
const rootKey = issue.path?.[0];
|
|
1561
|
-
return rootKey !== void 0 && providedKeys.has(String(rootKey));
|
|
1562
|
-
});
|
|
1563
|
-
if (providedFieldIssues.length > 0) return {
|
|
1564
|
-
args: void 0,
|
|
1565
|
-
argsResult: { issues: providedFieldIssues }
|
|
1566
|
-
};
|
|
1567
|
-
};
|
|
1568
|
-
const earlyResult = thenMaybe(earlyCheck, (result) => checkForProvidedFieldErrors(result) ?? void 0);
|
|
1569
|
-
if (earlyResult instanceof Promise) return earlyResult.then((err) => err ? err : doPrompt());
|
|
1570
|
-
if (earlyResult) return earlyResult;
|
|
1571
|
-
}
|
|
1572
|
-
return doPrompt();
|
|
1573
|
-
};
|
|
1574
|
-
const doPrompt = () => {
|
|
1575
|
-
return thenMaybe(promptInteractiveFields(preprocessedArgs, command, runtime, forceInteractive || void 0), (filledArgs) => {
|
|
1576
|
-
return next({
|
|
1577
|
-
rawArgs: filledArgs,
|
|
1578
|
-
positionalArgs: []
|
|
1579
|
-
});
|
|
1580
|
-
});
|
|
1581
|
-
};
|
|
1582
|
-
return earlyValidateAndPrompt();
|
|
1583
|
-
} }));
|
|
1584
|
-
/**
|
|
1585
|
-
* Extension that handles interactive prompting for missing arguments.
|
|
1586
|
-
* Extracts `--interactive` / `-i` flags, resolves effective interactivity,
|
|
1587
|
-
* and prompts for missing fields before passing filled args to validation.
|
|
1588
|
-
*
|
|
1589
|
-
* Usage:
|
|
1590
|
-
* ```ts
|
|
1591
|
-
* createPadrone('my-cli').extend(padroneInteractive())
|
|
1592
|
-
* ```
|
|
1593
|
-
*/
|
|
1594
|
-
function padroneInteractive() {
|
|
1595
|
-
return ((builder) => builder.intercept(interactiveInterceptor));
|
|
1596
|
-
}
|
|
1597
|
-
//#endregion
|
|
1598
|
-
//#region src/extension/repl.ts
|
|
1599
|
-
/**
|
|
1600
|
-
* Extension that adds REPL support:
|
|
1601
|
-
* - `repl` command that starts an interactive REPL
|
|
1602
|
-
* - `--repl` flag that starts the REPL from any invocation
|
|
1603
|
-
*
|
|
1604
|
-
* Usage:
|
|
1605
|
-
* ```ts
|
|
1606
|
-
* createPadrone('my-cli').extend(padroneRepl())
|
|
1607
|
-
* ```
|
|
1608
|
-
*/
|
|
1609
|
-
function padroneRepl(defaults) {
|
|
1610
|
-
const disabled = defaults?.disabled;
|
|
1611
|
-
return ((builder) => builder.command("repl", (c) => c.configure({
|
|
1612
|
-
description: "Start an interactive REPL",
|
|
1613
|
-
hidden: true
|
|
1614
|
-
}).arguments(passthroughSchema({ scope: "string" }), { positional: ["scope"] }).async().action(async (args, ctx) => {
|
|
1615
|
-
const prefs = {
|
|
1616
|
-
...defaults,
|
|
1617
|
-
scope: args.scope ?? defaults?.scope
|
|
1618
|
-
};
|
|
1619
|
-
const { value } = await ctx.program.repl(prefs).drain();
|
|
1620
|
-
return value;
|
|
1621
|
-
})).intercept(createReplInterceptor(defaults, disabled)));
|
|
1622
|
-
}
|
|
1623
|
-
function createReplInterceptor(defaults, disabled) {
|
|
1624
|
-
return defineInterceptor({
|
|
1625
|
-
id: "padrone:repl",
|
|
1626
|
-
name: "padrone:repl",
|
|
1627
|
-
order: -1e3,
|
|
1628
|
-
disabled
|
|
1629
|
-
}, () => ({ start(ctx, next) {
|
|
1630
|
-
const replInfo = checkReplFlag(ctx.input, ctx.command);
|
|
1631
|
-
if (!replInfo) return next();
|
|
1632
|
-
const program = ctx.program;
|
|
1633
|
-
if (!program?.repl) return next();
|
|
1634
|
-
const prefs = {
|
|
1635
|
-
...defaults,
|
|
1636
|
-
scope: replInfo.scope ?? defaults?.scope
|
|
1637
|
-
};
|
|
1638
|
-
return program.repl(prefs).drain().then((r) => withDrain({
|
|
1639
|
-
command: ctx.command,
|
|
1640
|
-
args: void 0,
|
|
1641
|
-
result: r.value
|
|
1642
|
-
}));
|
|
1643
|
-
} }));
|
|
1644
|
-
}
|
|
1645
|
-
/** Check for --repl flag in input. */
|
|
1646
|
-
function checkReplFlag(input, rootCommand) {
|
|
1647
|
-
if (!input) return null;
|
|
1648
|
-
const parts = parseCliInputToParts(input);
|
|
1649
|
-
const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
|
|
1650
|
-
const args = parts.filter((p) => p.type === "named");
|
|
1651
|
-
const keyIs = (key, name) => key.length === 1 && key[0] === name;
|
|
1652
|
-
if (!args.some((p) => p.type === "named" && keyIs(p.key, "repl"))) return null;
|
|
1653
|
-
const normalizedTerms = [...terms];
|
|
1654
|
-
if (normalizedTerms[0] === rootCommand.name) normalizedTerms.shift();
|
|
1655
|
-
return { scope: normalizedTerms.length > 0 ? normalizedTerms.join(" ") : void 0 };
|
|
1656
|
-
}
|
|
1657
|
-
//#endregion
|
|
1658
|
-
//#region src/extension/signal.ts
|
|
1659
|
-
const signalMeta = {
|
|
1660
|
-
id: "padrone:signal",
|
|
1661
|
-
name: "padrone:signal",
|
|
1662
|
-
order: -2e3
|
|
1663
|
-
};
|
|
1664
|
-
const signalInterceptor = defineInterceptor(signalMeta, () => {
|
|
1665
|
-
const abortController = new AbortController();
|
|
1666
|
-
let receivedSignal;
|
|
1667
|
-
let lastSigintTime = 0;
|
|
1668
|
-
let unsubscribe;
|
|
1669
|
-
const DOUBLE_SIGINT_MS = 2e3;
|
|
1670
|
-
const cleanup = () => {
|
|
1671
|
-
unsubscribe?.();
|
|
1672
|
-
unsubscribe = void 0;
|
|
1673
|
-
};
|
|
1674
|
-
const attachSignalInfo = (result) => {
|
|
1675
|
-
if (receivedSignal && result && typeof result === "object") {
|
|
1676
|
-
result.signal = receivedSignal;
|
|
1677
|
-
result.exitCode = signalExitCode(receivedSignal);
|
|
1678
|
-
}
|
|
1679
|
-
return result;
|
|
1680
|
-
};
|
|
1681
|
-
return {
|
|
1682
|
-
start(ctx, next) {
|
|
1683
|
-
const runtimeExit = ctx.runtime.exit;
|
|
1684
|
-
unsubscribe = ctx.runtime.onSignal?.((sig) => {
|
|
1685
|
-
if (abortController.signal.aborted) {
|
|
1686
|
-
if (sig === "SIGINT") {
|
|
1687
|
-
const elapsed = Date.now() - lastSigintTime;
|
|
1688
|
-
if (elapsed > 0 && elapsed < DOUBLE_SIGINT_MS) runtimeExit?.(signalExitCode(sig));
|
|
1689
|
-
lastSigintTime = Date.now();
|
|
1690
|
-
}
|
|
1691
|
-
return;
|
|
1692
|
-
}
|
|
1693
|
-
if (sig === "SIGINT") lastSigintTime = Date.now();
|
|
1694
|
-
receivedSignal = sig;
|
|
1695
|
-
abortController.abort(sig);
|
|
1696
|
-
});
|
|
1697
|
-
return thenMaybe(next({ signal: abortController.signal }), (r) => {
|
|
1698
|
-
cleanup();
|
|
1699
|
-
return attachSignalInfo(r);
|
|
1700
|
-
});
|
|
1701
|
-
},
|
|
1702
|
-
error(_ctx, next) {
|
|
1703
|
-
return thenMaybe(next(), (er) => {
|
|
1704
|
-
if (receivedSignal && er.error instanceof Error) er.error = new SignalError(receivedSignal, { cause: er.error });
|
|
1705
|
-
return er;
|
|
1706
|
-
});
|
|
1707
|
-
},
|
|
1708
|
-
shutdown(_ctx, next) {
|
|
1709
|
-
cleanup();
|
|
1710
|
-
return next();
|
|
1711
|
-
}
|
|
1712
|
-
};
|
|
1713
|
-
});
|
|
1714
|
-
/**
|
|
1715
|
-
* Extension that wires process signal handling (SIGINT, SIGTERM, SIGHUP) into the interceptor lifecycle.
|
|
1716
|
-
*
|
|
1717
|
-
* - Creates an `AbortController` whose signal is propagated to all downstream phases.
|
|
1718
|
-
* - Subscribes to `runtime.onSignal` to forward OS signals to the abort controller.
|
|
1719
|
-
* - Implements SIGINT double-tap: two SIGINTs within 2 seconds force-exits the process.
|
|
1720
|
-
* - Attaches `signal` and `exitCode` to results and errors when interrupted.
|
|
1721
|
-
* - Cleans up the signal subscription on completion or failure.
|
|
1722
|
-
*
|
|
1723
|
-
* Included in the default extensions. Runs at order `-2000` (outermost).
|
|
1724
|
-
*/
|
|
1725
|
-
function padroneSignalHandling(options) {
|
|
1726
|
-
const interceptor = options?.disabled ? defineInterceptor({
|
|
1727
|
-
...signalMeta,
|
|
1728
|
-
disabled: true
|
|
1729
|
-
}, () => ({})) : signalInterceptor;
|
|
1730
|
-
return ((builder) => builder.intercept(interceptor));
|
|
1731
|
-
}
|
|
1732
|
-
//#endregion
|
|
1733
|
-
//#region src/extension/stdin.ts
|
|
1734
|
-
const stdinInterceptor = defineInterceptor({
|
|
1735
|
-
id: "padrone:stdin",
|
|
1736
|
-
name: "padrone:stdin",
|
|
1737
|
-
order: -1001
|
|
1738
|
-
}, () => ({ validate(ctx, next) {
|
|
1739
|
-
const stdinField = ctx.command.meta?.stdin;
|
|
1740
|
-
if (!stdinField) return next();
|
|
1741
|
-
if (stdinField in ctx.rawArgs && ctx.rawArgs[stdinField] !== void 0) return next();
|
|
1742
|
-
const streamInfo = isAsyncStreamField(ctx.command.argsSchema, stdinField);
|
|
1743
|
-
if (streamInfo) {
|
|
1744
|
-
const stdinForStream = resolveStdinAlways(ctx.runtime);
|
|
1745
|
-
return next({ rawArgs: applyValues(ctx.rawArgs, { [stdinField]: createStdinStream(stdinForStream, streamInfo.itemSchema) }) });
|
|
1746
|
-
}
|
|
1747
|
-
const stdin = resolveStdin(ctx.runtime);
|
|
1748
|
-
if (!stdin) return next();
|
|
1749
|
-
if (isArrayField(ctx.command.argsSchema, stdinField)) return (async () => {
|
|
1750
|
-
const lines = [];
|
|
1751
|
-
for await (const line of stdin.lines()) lines.push(line);
|
|
1752
|
-
return next({ rawArgs: applyValues(ctx.rawArgs, { [stdinField]: lines }) });
|
|
1753
|
-
})();
|
|
1754
|
-
return stdin.text().then((text) => {
|
|
1755
|
-
if (!text) return next();
|
|
1756
|
-
return next({ rawArgs: applyValues(ctx.rawArgs, { [stdinField]: text }) });
|
|
1757
|
-
});
|
|
1758
|
-
} }));
|
|
1759
|
-
/**
|
|
1760
|
-
* Extension that reads stdin data into the argument field specified by `meta.stdin`.
|
|
1761
|
-
* Included by default via `createPadrone()`.
|
|
1762
|
-
*
|
|
1763
|
-
* Read mode is inferred from the schema type:
|
|
1764
|
-
* - `string` field → reads all stdin as a single string
|
|
1765
|
-
* - `string[]` field → reads stdin line-by-line into an array
|
|
1766
|
-
* - `AsyncIterable` field → returns a stream for line-by-line async consumption
|
|
1767
|
-
*
|
|
1768
|
-
* Stdin is only read when piped (not a TTY) and the field wasn't already provided via CLI flags.
|
|
1769
|
-
*/
|
|
1770
|
-
function padroneStdin(options) {
|
|
1771
|
-
const interceptor = options?.disabled ? defineInterceptor({
|
|
1772
|
-
...stdinInterceptor,
|
|
1773
|
-
disabled: true
|
|
1774
|
-
}, () => ({})) : stdinInterceptor;
|
|
1775
|
-
return ((builder) => builder.intercept(interceptor));
|
|
1776
|
-
}
|
|
1777
|
-
//#endregion
|
|
1778
|
-
//#region src/extension/suggestions.ts
|
|
1779
|
-
function formatSuggestions(names, prefix = "") {
|
|
1780
|
-
if (names.length === 0) return "";
|
|
1781
|
-
const quoted = names.map((n) => `"${prefix}${n}"`);
|
|
1782
|
-
if (quoted.length === 1) return `Did you mean ${quoted[0]}?`;
|
|
1783
|
-
return `Did you mean ${quoted.slice(0, -1).join(", ")} or ${quoted.at(-1)}?`;
|
|
1784
|
-
}
|
|
1785
|
-
function findSourceCommand(commandPath, root) {
|
|
1786
|
-
if (!commandPath || commandPath === root.name || commandPath === root.path) return root;
|
|
1787
|
-
const parts = commandPath.split(" ");
|
|
1788
|
-
let current = root;
|
|
1789
|
-
for (const part of parts) {
|
|
1790
|
-
const found = current.commands?.find((c) => {
|
|
1791
|
-
resolveCommand(c);
|
|
1792
|
-
return c.name === part || c.aliases?.includes(part);
|
|
1793
|
-
});
|
|
1794
|
-
if (found) current = found;
|
|
1795
|
-
else break;
|
|
1796
|
-
}
|
|
1797
|
-
return current;
|
|
1798
|
-
}
|
|
1799
|
-
function enrichRoutingError(err, rootCommand) {
|
|
1800
|
-
if (!(err instanceof RoutingError)) return err;
|
|
1801
|
-
const unknownMatch = err.message.match(/^Unknown command: (\S+)/);
|
|
1802
|
-
const unexpectedMatch = err.message.match(/^Unexpected arguments for '[^']+': (\S+)/);
|
|
1803
|
-
const term = unknownMatch?.[1] ?? unexpectedMatch?.[1];
|
|
1804
|
-
if (!term) return err;
|
|
1805
|
-
const sourceCmd = findSourceCommand(err.command, rootCommand);
|
|
1806
|
-
const candidateNames = [];
|
|
1807
|
-
if (sourceCmd.commands) for (const cmd of sourceCmd.commands) {
|
|
1808
|
-
resolveCommand(cmd);
|
|
1809
|
-
if (!cmd.hidden) {
|
|
1810
|
-
candidateNames.push(cmd.name);
|
|
1811
|
-
if (cmd.aliases) candidateNames.push(...cmd.aliases);
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
const suggestionText = formatSuggestions(suggestSimilar(term, candidateNames));
|
|
1815
|
-
if (!suggestionText) return err;
|
|
1816
|
-
const suggestions = [suggestionText];
|
|
1817
|
-
return new RoutingError(`${err.message}\n\n ${suggestionText}`, {
|
|
1818
|
-
suggestions,
|
|
1819
|
-
command: err.command
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
1822
|
-
function enrichIssuesWithSuggestions(issues, knownOptions) {
|
|
1823
|
-
return issues.map((i) => {
|
|
1824
|
-
const unknownMatch = i.message?.match(/^Unknown option: "([^"]+)"$/);
|
|
1825
|
-
if (unknownMatch) {
|
|
1826
|
-
const similar = suggestSimilar(unknownMatch[1], knownOptions());
|
|
1827
|
-
if (similar.length) {
|
|
1828
|
-
const hint = formatSuggestions(similar, "--");
|
|
1829
|
-
return {
|
|
1830
|
-
...i,
|
|
1831
|
-
message: `${i.message} ${hint}`
|
|
1832
|
-
};
|
|
1833
|
-
}
|
|
1834
|
-
return i;
|
|
1835
|
-
}
|
|
1836
|
-
const keys = i.keys ?? i.message?.match(/[Uu]nrecognized key(?:s)?[^"]*"([^"]+)"/)?.slice(1);
|
|
1837
|
-
if (!keys?.length) return i;
|
|
1838
|
-
const hints = keys.flatMap((k) => {
|
|
1839
|
-
const similar = suggestSimilar(k, knownOptions());
|
|
1840
|
-
return similar.length ? [formatSuggestions(similar, "--")] : [];
|
|
1841
|
-
});
|
|
1842
|
-
if (!hints.length) return i;
|
|
1843
|
-
return {
|
|
1844
|
-
...i,
|
|
1845
|
-
message: `${i.message} ${hints.join(" ")}`
|
|
1846
|
-
};
|
|
1847
|
-
});
|
|
1848
|
-
}
|
|
1849
|
-
const suggestionsInterceptor = defineInterceptor({
|
|
1850
|
-
id: "padrone:suggestions",
|
|
1851
|
-
name: "padrone:suggestions",
|
|
1852
|
-
order: -500
|
|
1853
|
-
}, () => ({
|
|
1854
|
-
parse(ctx, next) {
|
|
1855
|
-
try {
|
|
1856
|
-
const result = next();
|
|
1857
|
-
if (result instanceof Promise) return result.catch((err) => {
|
|
1858
|
-
throw enrichRoutingError(err, ctx.command);
|
|
1859
|
-
});
|
|
1860
|
-
return result;
|
|
1861
|
-
} catch (err) {
|
|
1862
|
-
throw enrichRoutingError(err, ctx.command);
|
|
1863
|
-
}
|
|
1864
|
-
},
|
|
1865
|
-
validate(ctx, next) {
|
|
1866
|
-
return thenMaybe(next(), (v) => {
|
|
1867
|
-
if (!v.argsResult?.issues?.length) return v;
|
|
1868
|
-
const enriched = enrichIssuesWithSuggestions(v.argsResult.issues, () => getKnownOptionNames(ctx.command));
|
|
1869
|
-
return {
|
|
1870
|
-
...v,
|
|
1871
|
-
argsResult: {
|
|
1872
|
-
...v.argsResult,
|
|
1873
|
-
issues: enriched
|
|
1874
|
-
}
|
|
1875
|
-
};
|
|
1876
|
-
});
|
|
1877
|
-
}
|
|
1878
|
-
}));
|
|
1879
|
-
function padroneSuggestions() {
|
|
1880
|
-
return ((builder) => builder.intercept(suggestionsInterceptor));
|
|
1881
|
-
}
|
|
1882
|
-
//#endregion
|
|
1883
|
-
//#region src/extension/version.ts
|
|
1884
|
-
const versionInterceptor = defineInterceptor({
|
|
1885
|
-
id: "padrone:version",
|
|
1886
|
-
name: "padrone:version",
|
|
1887
|
-
order: -1e3
|
|
1888
|
-
}, () => ({ parse(_ctx, next) {
|
|
1889
|
-
return thenMaybe(next(), (res) => {
|
|
1890
|
-
if ((res.rawArgs.version || res.rawArgs.v || res.rawArgs.V) && !res.command.parent) {
|
|
1891
|
-
delete res.rawArgs.version;
|
|
1892
|
-
delete res.rawArgs.v;
|
|
1893
|
-
delete res.rawArgs.V;
|
|
1894
|
-
const versionCmd = res.command.commands?.find((c) => c.name === "version");
|
|
1895
|
-
if (versionCmd) {
|
|
1896
|
-
resolveCommand(versionCmd);
|
|
1897
|
-
return {
|
|
1898
|
-
...res,
|
|
1899
|
-
command: versionCmd,
|
|
1900
|
-
rawArgs: {},
|
|
1901
|
-
positionalArgs: []
|
|
1902
|
-
};
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
return res;
|
|
1906
|
-
});
|
|
1907
|
-
} }));
|
|
1908
|
-
/**
|
|
1909
|
-
* Extension that adds version support:
|
|
1910
|
-
* - `version` command
|
|
1911
|
-
* - `--version` / `-v` / `-V` flags (root command only)
|
|
1912
|
-
*
|
|
1913
|
-
* Usage:
|
|
1914
|
-
* ```ts
|
|
1915
|
-
* createPadrone('my-cli').extend(padroneVersion())
|
|
1916
|
-
* ```
|
|
1917
|
-
*/
|
|
1918
|
-
function padroneVersion() {
|
|
1919
|
-
return ((builder) => builder.command("version", (c) => c.configure({
|
|
1920
|
-
description: "Display the version number",
|
|
1921
|
-
hidden: true
|
|
1922
|
-
}).action((_args, ctx) => {
|
|
1923
|
-
return getVersion(getRootCommand(ctx.command).version);
|
|
1924
|
-
})).intercept(versionInterceptor));
|
|
1925
|
-
}
|
|
1926
|
-
//#endregion
|
|
1927
|
-
//#region src/feature/wrap.ts
|
|
1928
|
-
/**
|
|
1929
|
-
* Converts parsed arguments to CLI arguments for an external command.
|
|
1930
|
-
*/
|
|
1931
|
-
function argsToCliArgs(input, positional = []) {
|
|
1932
|
-
const args = [];
|
|
1933
|
-
if (!input) return args;
|
|
1934
|
-
const positionalValues = {};
|
|
1935
|
-
const regularArguments = {};
|
|
1936
|
-
for (const [key, value] of Object.entries(input)) if (positional.includes(key) || positional.includes(`...${key}`)) positionalValues[key] = value;
|
|
1937
|
-
else regularArguments[key] = value;
|
|
1938
|
-
for (const [key, value] of Object.entries(regularArguments)) {
|
|
1939
|
-
if (value === void 0 || value === null) continue;
|
|
1940
|
-
const flag = `--${key}`;
|
|
1941
|
-
if (typeof value === "boolean") {
|
|
1942
|
-
if (value) args.push(flag);
|
|
1943
|
-
} else if (Array.isArray(value)) for (const item of value) args.push(flag, String(item));
|
|
1944
|
-
else args.push(flag, String(value));
|
|
1945
|
-
}
|
|
1946
|
-
for (const posKey of positional) {
|
|
1947
|
-
const isVariadic = posKey.startsWith("...");
|
|
1948
|
-
const value = positionalValues[isVariadic ? posKey.slice(3) : posKey];
|
|
1949
|
-
if (value === void 0 || value === null) continue;
|
|
1950
|
-
if (isVariadic && Array.isArray(value)) args.push(...value.map(String));
|
|
1951
|
-
else args.push(String(value));
|
|
1952
|
-
}
|
|
1953
|
-
return args;
|
|
1954
|
-
}
|
|
1955
|
-
/**
|
|
1956
|
-
* Creates an action handler that wraps an external CLI tool.
|
|
1957
|
-
* @param config - Configuration for wrapping the external command (includes optional schema)
|
|
1958
|
-
* @param commandArguments - The command's arguments schema
|
|
1959
|
-
* @param commandPositional - Default positional config from the wrapping command
|
|
1960
|
-
*/
|
|
1961
|
-
function createWrapHandler(config, commandArguments, commandPositional) {
|
|
1962
|
-
return async (args) => {
|
|
1963
|
-
const { command, args: fixedArgs = [], inheritStdio = true, positional = commandPositional, schema: wrapSchema } = config;
|
|
1964
|
-
const validationResult = (wrapSchema ? typeof wrapSchema === "function" ? wrapSchema(commandArguments) : wrapSchema : commandArguments)["~standard"].validate(args);
|
|
1965
|
-
const processResult = (result) => {
|
|
1966
|
-
if (result.issues) throw new ValidationError(`Wrap schema validation failed:\n${result.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`, result.issues);
|
|
1967
|
-
return result.value;
|
|
1968
|
-
};
|
|
1969
|
-
const regularArgs = argsToCliArgs(validationResult instanceof Promise ? await validationResult.then(processResult) : processResult(validationResult), positional);
|
|
1970
|
-
const allArgs = [...fixedArgs, ...regularArgs];
|
|
1971
|
-
const { spawn } = await import("node:child_process");
|
|
1972
|
-
return new Promise((resolve, reject) => {
|
|
1973
|
-
const proc = spawn(command, allArgs, { stdio: inheritStdio ? "inherit" : [
|
|
1974
|
-
"ignore",
|
|
1975
|
-
"pipe",
|
|
1976
|
-
"pipe"
|
|
1977
|
-
] });
|
|
1978
|
-
const stdoutChunks = [];
|
|
1979
|
-
const stderrChunks = [];
|
|
1980
|
-
if (!inheritStdio) {
|
|
1981
|
-
proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
1982
|
-
proc.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
1983
|
-
}
|
|
1984
|
-
const decoder = new TextDecoder();
|
|
1985
|
-
proc.on("error", reject);
|
|
1986
|
-
proc.on("close", (code) => {
|
|
1987
|
-
const exitCode = code ?? 1;
|
|
1988
|
-
resolve({
|
|
1989
|
-
exitCode,
|
|
1990
|
-
stdout: inheritStdio ? void 0 : decoder.decode(concatBytes(stdoutChunks)),
|
|
1991
|
-
stderr: inheritStdio ? void 0 : decoder.decode(concatBytes(stderrChunks)),
|
|
1992
|
-
success: exitCode === 0
|
|
1993
|
-
});
|
|
1994
|
-
});
|
|
1995
|
-
});
|
|
1996
|
-
};
|
|
1997
|
-
}
|
|
1998
|
-
//#endregion
|
|
1999
|
-
//#region src/core/exec.ts
|
|
2000
|
-
/**
|
|
2001
|
-
* Collects registered interceptors from the command's parent chain (root → ... → target).
|
|
2002
|
-
* Root/program interceptors come first (outermost), target command's interceptors last (innermost).
|
|
2003
|
-
*/
|
|
2004
|
-
function collectInterceptors(cmd, rootCommand) {
|
|
2005
|
-
const chain = [];
|
|
2006
|
-
let current = cmd;
|
|
2007
|
-
while (current) {
|
|
2008
|
-
const isTarget = current === cmd;
|
|
2009
|
-
if (!current.parent) {
|
|
2010
|
-
if (rootCommand.interceptors?.length) {
|
|
2011
|
-
const isRootTarget = cmd === rootCommand || !cmd.parent;
|
|
2012
|
-
chain.unshift(isRootTarget ? rootCommand.interceptors : rootCommand.interceptors.filter((i) => i.meta.inherit !== false));
|
|
2013
|
-
}
|
|
2014
|
-
} else if (current.interceptors?.length) chain.unshift(isTarget ? current.interceptors : current.interceptors.filter((i) => i.meta.inherit !== false));
|
|
2015
|
-
current = current.parent;
|
|
2016
|
-
}
|
|
2017
|
-
return chain.flat();
|
|
2018
|
-
}
|
|
2019
|
-
/** Wraps an error into a result, preserving any signal info from the pipeline. */
|
|
2020
|
-
function errorResultWithSignal(err) {
|
|
2021
|
-
const result = errorResult(err);
|
|
2022
|
-
if (err instanceof SignalError) {
|
|
2023
|
-
result.signal = err.signal;
|
|
2024
|
-
result.exitCode = err.exitCode;
|
|
2025
|
-
}
|
|
2026
|
-
return result;
|
|
2027
|
-
}
|
|
2028
|
-
/** Resolve context by walking the command parent chain and applying transforms from root to target. */
|
|
2029
|
-
function resolveContext(command, initialContext) {
|
|
2030
|
-
const chain = [];
|
|
2031
|
-
let current = command;
|
|
2032
|
-
while (current) {
|
|
2033
|
-
chain.unshift(current);
|
|
2034
|
-
current = current.parent;
|
|
2035
|
-
}
|
|
2036
|
-
let resolved = initialContext;
|
|
2037
|
-
for (const cmd of chain) if (cmd.contextTransform) resolved = cmd.contextTransform(resolved);
|
|
2038
|
-
return resolved;
|
|
2039
|
-
}
|
|
2040
|
-
/** Validate parse result — reject unmatched terms when the command doesn't accept positional args. */
|
|
2041
|
-
function validateParseResult(parseResult, rootCommand) {
|
|
2042
|
-
const { command, rawArgs, args, unmatchedTerms } = parseResult;
|
|
2043
|
-
if (unmatchedTerms.length > 0) {
|
|
2044
|
-
if (!(command.meta?.positional && command.meta.positional.length > 0)) {
|
|
2045
|
-
const isRootCommand = command === rootCommand;
|
|
2046
|
-
const commandDisplayName = command.name || command.aliases?.[0] || command.path || "(default)";
|
|
2047
|
-
throw new RoutingError(isRootCommand ? `Unknown command: ${unmatchedTerms[0]}` : `Unexpected arguments for '${commandDisplayName}': ${unmatchedTerms.join(" ")}`, { command: command.path || command.name });
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
return {
|
|
2051
|
-
command,
|
|
2052
|
-
rawArgs,
|
|
2053
|
-
positionalArgs: args
|
|
2054
|
-
};
|
|
2055
|
-
}
|
|
2056
|
-
/** Handle validation issues based on error mode: throw (hard) or return result with issues (soft). */
|
|
2057
|
-
function handleValidationIssues(argsResult, command, errorMode) {
|
|
2058
|
-
if (errorMode === "hard") throw new ValidationError(`Validation error:\n${formatIssueMessages(argsResult.issues)}`, argsResult.issues, { command: command.path || command.name });
|
|
2059
|
-
return withDrain({
|
|
2060
|
-
command,
|
|
2061
|
-
args: void 0,
|
|
2062
|
-
argsResult,
|
|
2063
|
-
result: void 0
|
|
2064
|
-
});
|
|
2065
|
-
}
|
|
2066
|
-
/**
|
|
2067
|
-
* Core execution logic shared by eval() and cli().
|
|
2068
|
-
* errorMode controls validation error behavior:
|
|
2069
|
-
* - 'soft': return result with issues (eval behavior)
|
|
2070
|
-
* - 'hard': print error + help and throw (cli-without-input behavior)
|
|
2071
|
-
*/
|
|
2072
|
-
function execCommand(resolvedInput, ctx, evalOptions, errorMode = "soft", caller = "eval") {
|
|
2073
|
-
const { rootCommand, parseCommandFn, collectInterceptorsFn } = ctx;
|
|
2074
|
-
const baseRuntime = getCommandRuntime(rootCommand);
|
|
2075
|
-
const runtime = evalOptions?.runtime ? Object.assign({}, baseRuntime, Object.fromEntries(Object.entries(evalOptions.runtime).filter(([, v]) => v !== void 0))) : baseRuntime;
|
|
2076
|
-
const inertSignal = new AbortController().signal;
|
|
2077
|
-
const pipelineState = { phase: "start" };
|
|
2078
|
-
const initialContext = evalOptions?.context;
|
|
2079
|
-
const factoryCache = /* @__PURE__ */ new Map();
|
|
2080
|
-
const rootInterceptors = resolveRegisteredInterceptors(rootCommand.interceptors ?? [], factoryCache);
|
|
2081
|
-
const rootInterceptorSet = new Set(rootInterceptors);
|
|
2082
|
-
const runPipeline = (signal, pipelineContext) => {
|
|
2083
|
-
const parseCtx = {
|
|
2084
|
-
input: resolvedInput,
|
|
2085
|
-
command: rootCommand,
|
|
2086
|
-
signal,
|
|
2087
|
-
context: pipelineContext,
|
|
2088
|
-
runtime,
|
|
2089
|
-
program: ctx.builder,
|
|
2090
|
-
caller
|
|
2091
|
-
};
|
|
2092
|
-
const coreParse = (parseCtx) => validateParseResult(parseCommandFn(parseCtx.input), rootCommand);
|
|
2093
|
-
const parsedOrPromise = runInterceptorChain("parse", rootInterceptors, parseCtx, coreParse);
|
|
2094
|
-
const continueAfterParse = (parsed) => {
|
|
2095
|
-
const { command } = parsed;
|
|
2096
|
-
pipelineState.phase = "parse";
|
|
2097
|
-
pipelineState.rawArgs = parsed.rawArgs;
|
|
2098
|
-
pipelineState.positionalArgs = parsed.positionalArgs;
|
|
2099
|
-
const commandInterceptors = resolveRegisteredInterceptors(collectInterceptorsFn(command), factoryCache);
|
|
2100
|
-
const commandOnlyInterceptors = commandInterceptors.filter((i) => !rootInterceptorSet.has(i));
|
|
2101
|
-
const context = resolveContext(command, pipelineContext);
|
|
2102
|
-
const routedOrPromise = runInterceptorChain("route", commandInterceptors, {
|
|
2103
|
-
...parseCtx,
|
|
2104
|
-
command,
|
|
2105
|
-
rawArgs: parsed.rawArgs,
|
|
2106
|
-
positionalArgs: parsed.positionalArgs,
|
|
2107
|
-
context
|
|
2108
|
-
}, () => {});
|
|
2109
|
-
const continueAfterRoute = () => {
|
|
2110
|
-
pipelineState.phase = "route";
|
|
2111
|
-
const runValidateAndExecute = () => {
|
|
2112
|
-
const validateCtx = {
|
|
2113
|
-
...parseCtx,
|
|
2114
|
-
command,
|
|
2115
|
-
rawArgs: parsed.rawArgs,
|
|
2116
|
-
positionalArgs: parsed.positionalArgs,
|
|
2117
|
-
context,
|
|
2118
|
-
evalInteractive: evalOptions?.interactive
|
|
2119
|
-
};
|
|
2120
|
-
const coreValidate = (validateCtx) => {
|
|
2121
|
-
const { args: preprocessedArgs, issues } = buildCommandArgs(validateCtx.command, validateCtx.rawArgs, validateCtx.positionalArgs);
|
|
2122
|
-
if (issues) return {
|
|
2123
|
-
args: void 0,
|
|
2124
|
-
argsResult: { issues }
|
|
2125
|
-
};
|
|
2126
|
-
return thenMaybe(validateCommandArgs(validateCtx.command, preprocessedArgs), (v) => v);
|
|
2127
|
-
};
|
|
2128
|
-
const validatedOrPromise = runInterceptorChain("validate", commandInterceptors, validateCtx, coreValidate);
|
|
2129
|
-
const continueAfterValidate = (v) => {
|
|
2130
|
-
pipelineState.phase = "validate";
|
|
2131
|
-
pipelineState.args = v.args;
|
|
2132
|
-
if (v.argsResult?.issues) return handleValidationIssues(v.argsResult, command, errorMode);
|
|
2133
|
-
const executeCtx = {
|
|
2134
|
-
...validateCtx,
|
|
2135
|
-
args: v.args
|
|
2136
|
-
};
|
|
2137
|
-
const coreExecute = (executeCtx) => {
|
|
2138
|
-
const handler = command.action ?? noop;
|
|
2139
|
-
const actionCtx = {
|
|
2140
|
-
runtime: executeCtx.runtime,
|
|
2141
|
-
command: executeCtx.command,
|
|
2142
|
-
program: ctx.builder,
|
|
2143
|
-
signal: executeCtx.signal,
|
|
2144
|
-
context: executeCtx.context,
|
|
2145
|
-
caller
|
|
2146
|
-
};
|
|
2147
|
-
return { result: handler(executeCtx.args, actionCtx) };
|
|
2148
|
-
};
|
|
2149
|
-
pipelineState.phase = "execute";
|
|
2150
|
-
return thenMaybe(runInterceptorChain("execute", commandInterceptors, executeCtx, coreExecute), (e) => {
|
|
2151
|
-
const finalize = (result) => withDrain({
|
|
2152
|
-
command,
|
|
2153
|
-
args: v.args,
|
|
2154
|
-
argsResult: v.argsResult,
|
|
2155
|
-
result
|
|
2156
|
-
});
|
|
2157
|
-
if (e.result instanceof Promise) return e.result.then(finalize);
|
|
2158
|
-
return finalize(e.result);
|
|
2159
|
-
});
|
|
2160
|
-
};
|
|
2161
|
-
return thenMaybe(warnIfUnexpectedAsync(validatedOrPromise, command), continueAfterValidate);
|
|
2162
|
-
};
|
|
2163
|
-
return wrapWithCommandLifecycle(commandOnlyInterceptors, command, resolvedInput, runValidateAndExecute, (result) => withDrain({
|
|
2164
|
-
command,
|
|
2165
|
-
args: void 0,
|
|
2166
|
-
argsResult: void 0,
|
|
2167
|
-
result
|
|
2168
|
-
}), signal, context, runtime, ctx.builder, caller, pipelineState);
|
|
2169
|
-
};
|
|
2170
|
-
return thenMaybe(routedOrPromise, continueAfterRoute);
|
|
2171
|
-
};
|
|
2172
|
-
return thenMaybe(parsedOrPromise, continueAfterParse);
|
|
2173
|
-
};
|
|
2174
|
-
return wrapWithLifecycle(rootInterceptors, rootCommand, resolvedInput, runPipeline, (result) => withDrain({
|
|
2175
|
-
command: rootCommand,
|
|
2176
|
-
args: void 0,
|
|
2177
|
-
argsResult: void 0,
|
|
2178
|
-
result
|
|
2179
|
-
}), inertSignal, initialContext, runtime, ctx.builder, caller, pipelineState);
|
|
2180
|
-
}
|
|
2181
|
-
//#endregion
|
|
2182
|
-
//#region src/feature/repl-loop.ts
|
|
2183
|
-
/**
|
|
2184
|
-
* Creates a REPL async iterable for running commands interactively.
|
|
2185
|
-
*/
|
|
2186
|
-
function createReplIterator(deps, options) {
|
|
2187
|
-
const { existingCommand, evalCommand, replActiveRef } = deps;
|
|
2188
|
-
if (replActiveRef.value) {
|
|
2189
|
-
getCommandRuntime(existingCommand).error("REPL is already running. Nested REPL sessions are not supported.");
|
|
2190
|
-
return (async function* () {})();
|
|
2191
|
-
}
|
|
2192
|
-
const runtime = getCommandRuntime(existingCommand);
|
|
2193
|
-
const programName = existingCommand.name || "padrone";
|
|
2194
|
-
const env = runtime.env();
|
|
2195
|
-
const useAnsi = runtime.format === "ansi" || runtime.format === "auto" && !env.NO_COLOR && !env.CI && runtime.terminal?.isTTY === true;
|
|
2196
|
-
const commandHistory = [];
|
|
2197
|
-
const resolveScope = (scope) => {
|
|
2198
|
-
const parts = scope.split(/\s+/);
|
|
2199
|
-
const stack = [];
|
|
2200
|
-
let current = existingCommand;
|
|
2201
|
-
for (const part of parts) {
|
|
2202
|
-
const found = findCommandByName(part, current.commands);
|
|
2203
|
-
if (!found) break;
|
|
2204
|
-
stack.push(found);
|
|
2205
|
-
current = found;
|
|
2206
|
-
}
|
|
2207
|
-
return stack;
|
|
2208
|
-
};
|
|
2209
|
-
async function* replIterator() {
|
|
2210
|
-
replActiveRef.value = true;
|
|
2211
|
-
const showGreeting = options?.greeting !== false;
|
|
2212
|
-
const showHint = options?.hint !== false;
|
|
2213
|
-
if (showGreeting || showHint) runtime.output("");
|
|
2214
|
-
if (showGreeting) if (options?.greeting) runtime.output(options.greeting);
|
|
2215
|
-
else {
|
|
2216
|
-
const displayName = existingCommand.title || programName;
|
|
2217
|
-
const version = existingCommand.version ? await getVersion(existingCommand.version) : void 0;
|
|
2218
|
-
const greeting = version ? `Welcome to ${displayName} v${version}` : `Welcome to ${displayName}`;
|
|
2219
|
-
runtime.output(greeting);
|
|
2220
|
-
}
|
|
2221
|
-
if (showHint) {
|
|
2222
|
-
const hintText = (typeof options?.hint === "string" ? options.hint : void 0) ?? "Type \".help\" for more information, \".exit\" to quit.";
|
|
2223
|
-
runtime.output(useAnsi ? `\x1b[2m${hintText}\x1b[0m` : hintText);
|
|
2224
|
-
}
|
|
2225
|
-
if (showGreeting || showHint) runtime.output("");
|
|
2226
|
-
const scopeStack = options?.scope ? resolveScope(options.scope) : [];
|
|
2227
|
-
const getScopeCommand = () => scopeStack.length ? scopeStack[scopeStack.length - 1] : existingCommand;
|
|
2228
|
-
const getScopePath = () => scopeStack.map((c) => c.name).join(" ");
|
|
2229
|
-
const buildPrompt = () => {
|
|
2230
|
-
if (options?.prompt) return typeof options.prompt === "function" ? options.prompt() : options.prompt;
|
|
2231
|
-
const scopePath = getScopePath();
|
|
2232
|
-
const label = scopePath ? `${programName}/${scopePath.replace(/ /g, "/")}` : programName;
|
|
2233
|
-
return useAnsi ? `\x1b[1m${label}\x1b[0m ❯ ` : `${label} ❯ `;
|
|
2234
|
-
};
|
|
2235
|
-
const buildScopedCompleter = () => {
|
|
2236
|
-
return buildReplCompleter(getScopeCommand(), { inScope: scopeStack.length > 0 });
|
|
2237
|
-
};
|
|
2238
|
-
const sessionConfig = { history: options?.history };
|
|
2239
|
-
if (options?.completion !== false) sessionConfig.completer = buildScopedCompleter();
|
|
2240
|
-
const session = runtime.readLine ? void 0 : createTerminalReplSession(sessionConfig);
|
|
2241
|
-
const questionFn = session ? (prompt) => session.question(prompt) : runtime.readLine;
|
|
2242
|
-
const updateCompleter = () => {
|
|
2243
|
-
if (options?.completion === false) return;
|
|
2244
|
-
const completer = buildScopedCompleter();
|
|
2245
|
-
if (session) session.completer = completer;
|
|
2246
|
-
sessionConfig.completer = completer;
|
|
2247
|
-
};
|
|
2248
|
-
let lastSigintTime = 0;
|
|
2249
|
-
try {
|
|
2250
|
-
while (true) {
|
|
2251
|
-
const input = await questionFn(buildPrompt());
|
|
2252
|
-
if (input === null) break;
|
|
2253
|
-
if (input === REPL_SIGINT) {
|
|
2254
|
-
const now = Date.now();
|
|
2255
|
-
if (now - lastSigintTime < 2e3) break;
|
|
2256
|
-
lastSigintTime = now;
|
|
2257
|
-
runtime.output("(press Ctrl+C again to exit, or Ctrl+D)");
|
|
2258
|
-
continue;
|
|
2259
|
-
}
|
|
2260
|
-
const trimmed = input.trim();
|
|
2261
|
-
if (!trimmed) continue;
|
|
2262
|
-
lastSigintTime = 0;
|
|
2263
|
-
commandHistory.push(trimmed);
|
|
2264
|
-
if (trimmed === ".exit" || trimmed === ".quit") break;
|
|
2265
|
-
if (trimmed === ".clear") {
|
|
2266
|
-
runtime.output("\x1B[2J\x1B[H");
|
|
2267
|
-
continue;
|
|
2268
|
-
}
|
|
2269
|
-
if (trimmed === ".help") {
|
|
2270
|
-
const lines = [
|
|
2271
|
-
"REPL Commands:",
|
|
2272
|
-
" . Execute the current scoped command",
|
|
2273
|
-
" .help Print this help message",
|
|
2274
|
-
" .exit Exit the REPL",
|
|
2275
|
-
" .clear Clear the screen",
|
|
2276
|
-
" .history Show command history",
|
|
2277
|
-
" .scope <cmd> Scope into a subcommand",
|
|
2278
|
-
" .scope .. Go up one scope level"
|
|
2279
|
-
];
|
|
2280
|
-
lines.push("", "Keybindings:", " Ctrl+C Cancel current line (press twice to exit)", " Ctrl+D Exit the REPL", " Up/Down Navigate history", " Tab Auto-complete", "", "Type \"help\" to see available commands.");
|
|
2281
|
-
runtime.output(lines.join("\n"));
|
|
2282
|
-
continue;
|
|
2283
|
-
}
|
|
2284
|
-
if (trimmed === ".history") {
|
|
2285
|
-
const entries = commandHistory.slice(0, -1);
|
|
2286
|
-
if (entries.length === 0) runtime.output("No history.");
|
|
2287
|
-
else runtime.output(entries.map((entry, i) => `${i + 1} ${entry}`).join("\n"));
|
|
2288
|
-
continue;
|
|
2289
|
-
}
|
|
2290
|
-
if (trimmed.startsWith(".scope ") || trimmed === ".scope") {
|
|
2291
|
-
const target = trimmed.slice(6).trim();
|
|
2292
|
-
if (target === ".." || target === "") {
|
|
2293
|
-
if (scopeStack.length > 0) {
|
|
2294
|
-
scopeStack.pop();
|
|
2295
|
-
updateCompleter();
|
|
2296
|
-
}
|
|
2297
|
-
} else {
|
|
2298
|
-
const found = findCommandByName(target, getScopeCommand().commands);
|
|
2299
|
-
if (found) if (found.commands?.length) {
|
|
2300
|
-
scopeStack.push(found);
|
|
2301
|
-
updateCompleter();
|
|
2302
|
-
} else runtime.error(`"${target}" has no subcommands to scope into.`);
|
|
2303
|
-
else runtime.error(`Unknown command: ${target}`);
|
|
2304
|
-
}
|
|
2305
|
-
continue;
|
|
2306
|
-
}
|
|
2307
|
-
if (trimmed === "..") {
|
|
2308
|
-
if (scopeStack.length > 0) {
|
|
2309
|
-
scopeStack.pop();
|
|
2310
|
-
updateCompleter();
|
|
2311
|
-
}
|
|
2312
|
-
continue;
|
|
2313
|
-
}
|
|
2314
|
-
let evalInput = trimmed;
|
|
2315
|
-
if (trimmed === ".") evalInput = "";
|
|
2316
|
-
const prefix = options?.outputPrefix;
|
|
2317
|
-
const prefixLines = prefix ? (text) => text.split("\n").map((l) => prefix + l).join("\n") : void 0;
|
|
2318
|
-
const savedRuntimes = [];
|
|
2319
|
-
if (prefixLines) {
|
|
2320
|
-
const prefixedRuntime = {
|
|
2321
|
-
...existingCommand.runtime,
|
|
2322
|
-
output: (...args) => {
|
|
2323
|
-
const first = args[0];
|
|
2324
|
-
runtime.output(typeof first === "string" ? prefixLines(first) : first, ...args.slice(1));
|
|
2325
|
-
},
|
|
2326
|
-
error: (text) => runtime.error(prefixLines(text))
|
|
2327
|
-
};
|
|
2328
|
-
const patchAll = (cmd) => {
|
|
2329
|
-
savedRuntimes.push({
|
|
2330
|
-
cmd,
|
|
2331
|
-
runtime: cmd.runtime
|
|
2332
|
-
});
|
|
2333
|
-
cmd.runtime = prefixedRuntime;
|
|
2334
|
-
cmd.commands?.forEach(patchAll);
|
|
2335
|
-
};
|
|
2336
|
-
patchAll(existingCommand);
|
|
2337
|
-
}
|
|
2338
|
-
const sp = options?.spacing;
|
|
2339
|
-
const isSpacingObject = typeof sp === "object" && sp !== null && !Array.isArray(sp);
|
|
2340
|
-
const spacingBefore = isSpacingObject ? sp.before : sp;
|
|
2341
|
-
const spacingAfter = isSpacingObject ? sp.after : sp;
|
|
2342
|
-
const emitSpacingLine = (value) => {
|
|
2343
|
-
if (typeof value === "string") {
|
|
2344
|
-
const sep = value.length === 1 ? value.repeat(runtime.terminal?.columns ?? 80) : value;
|
|
2345
|
-
runtime.output(sep);
|
|
2346
|
-
} else if (value) runtime.output("");
|
|
2347
|
-
};
|
|
2348
|
-
const emitSpacing = (value) => {
|
|
2349
|
-
if (!value) return;
|
|
2350
|
-
if (Array.isArray(value)) for (const line of value) emitSpacingLine(line);
|
|
2351
|
-
else emitSpacingLine(value);
|
|
2352
|
-
};
|
|
2353
|
-
emitSpacing(spacingBefore);
|
|
2354
|
-
const scopePath = getScopePath();
|
|
2355
|
-
const scopedInput = scopePath ? evalInput ? `${scopePath} ${evalInput}` : scopePath : evalInput;
|
|
2356
|
-
try {
|
|
2357
|
-
const result = await evalCommand(scopedInput, { caller: "repl" });
|
|
2358
|
-
if (result.error) {
|
|
2359
|
-
const msg = result.error instanceof Error ? result.error.message : String(result.error);
|
|
2360
|
-
runtime.error(prefixLines ? prefixLines(msg) : msg);
|
|
2361
|
-
} else if (result.argsResult?.issues) {
|
|
2362
|
-
const msg = `Validation error:\n${result.argsResult.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`;
|
|
2363
|
-
runtime.error(prefixLines ? prefixLines(msg) : msg);
|
|
2364
|
-
}
|
|
2365
|
-
yield result;
|
|
2366
|
-
} catch (err) {
|
|
2367
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2368
|
-
runtime.error(prefixLines ? prefixLines(msg) : msg);
|
|
2369
|
-
} finally {
|
|
2370
|
-
for (const { cmd, runtime: saved } of savedRuntimes) cmd.runtime = saved;
|
|
2371
|
-
emitSpacing(spacingAfter);
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
} finally {
|
|
2375
|
-
replActiveRef.value = false;
|
|
2376
|
-
session?.close();
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
const iterable = replIterator();
|
|
2380
|
-
iterable.drain = async () => {
|
|
2381
|
-
try {
|
|
2382
|
-
const results = [];
|
|
2383
|
-
for await (const result of iterable) results.push(result);
|
|
2384
|
-
return { value: results };
|
|
2385
|
-
} catch (err) {
|
|
2386
|
-
return { error: err };
|
|
2387
|
-
}
|
|
2388
|
-
};
|
|
2389
|
-
return iterable;
|
|
2390
|
-
}
|
|
2391
|
-
//#endregion
|
|
2392
|
-
//#region src/core/program-methods.ts
|
|
2393
|
-
function createProgramMethods(ctx, evalCommand) {
|
|
2394
|
-
const { rootCommand } = ctx;
|
|
2395
|
-
const inertSignal = new AbortController().signal;
|
|
2396
|
-
const stringify = (command = "", args) => {
|
|
2397
|
-
const commandObj = typeof command === "string" ? findCommandByName(command, rootCommand.commands) : command;
|
|
2398
|
-
if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
|
|
2399
|
-
const parts = [];
|
|
2400
|
-
if (commandObj.path) parts.push(commandObj.path);
|
|
2401
|
-
const positionalConfig = commandObj.meta?.positional ? parsePositionalConfig(commandObj.meta.positional) : [];
|
|
2402
|
-
const positionalNames = new Set(positionalConfig.map((p) => p.name));
|
|
2403
|
-
const negativeKeyword = {};
|
|
2404
|
-
if (commandObj.argsSchema) {
|
|
2405
|
-
const { negatives } = extractSchemaMetadata(commandObj.argsSchema, commandObj.meta?.fields, commandObj.meta?.autoAlias);
|
|
2406
|
-
for (const [keyword, argName] of Object.entries(negatives)) if (!(argName in negativeKeyword)) negativeKeyword[argName] = keyword;
|
|
2407
|
-
}
|
|
2408
|
-
if (args && typeof args === "object") {
|
|
2409
|
-
for (const { name, variadic } of positionalConfig) {
|
|
2410
|
-
const value = args[name];
|
|
2411
|
-
if (value === void 0) continue;
|
|
2412
|
-
if (variadic && Array.isArray(value)) for (const v of value) {
|
|
2413
|
-
const vStr = String(v);
|
|
2414
|
-
if (vStr.includes(" ")) parts.push(`"${vStr}"`);
|
|
2415
|
-
else parts.push(vStr);
|
|
2416
|
-
}
|
|
2417
|
-
else {
|
|
2418
|
-
const argStr = String(value);
|
|
2419
|
-
if (argStr.includes(" ")) parts.push(`"${argStr}"`);
|
|
2420
|
-
else parts.push(argStr);
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
const stringifyValue = (key, value) => {
|
|
2424
|
-
if (value === void 0) return;
|
|
2425
|
-
if (typeof value === "boolean") if (value) parts.push(`--${key}`);
|
|
2426
|
-
else if (negativeKeyword[key]) parts.push(`--${negativeKeyword[key]}`);
|
|
2427
|
-
else parts.push(`--no-${key}`);
|
|
2428
|
-
else if (Array.isArray(value)) for (const v of value) {
|
|
2429
|
-
const vStr = String(v);
|
|
2430
|
-
if (vStr.includes(" ")) parts.push(`--${key}="${vStr}"`);
|
|
2431
|
-
else parts.push(`--${key}=${vStr}`);
|
|
2432
|
-
}
|
|
2433
|
-
else if (typeof value === "object" && value !== null) for (const [nestedKey, nestedValue] of Object.entries(value)) stringifyValue(`${key}.${nestedKey}`, nestedValue);
|
|
2434
|
-
else if (typeof value === "string") if (value.includes(" ")) parts.push(`--${key}="${value}"`);
|
|
2435
|
-
else parts.push(`--${key}=${value}`);
|
|
2436
|
-
else parts.push(`--${key}=${value}`);
|
|
2437
|
-
};
|
|
2438
|
-
for (const [key, value] of Object.entries(args)) {
|
|
2439
|
-
if (value === void 0 || positionalNames.has(key)) continue;
|
|
2440
|
-
stringifyValue(key, value);
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
return parts.join(" ");
|
|
2444
|
-
};
|
|
2445
|
-
const resolveContext = (command, initialContext) => {
|
|
2446
|
-
const chain = [];
|
|
2447
|
-
let current = command;
|
|
2448
|
-
while (current) {
|
|
2449
|
-
chain.unshift(current);
|
|
2450
|
-
current = current.parent;
|
|
2451
|
-
}
|
|
2452
|
-
let resolved = initialContext;
|
|
2453
|
-
for (const cmd of chain) if (cmd.contextTransform) resolved = cmd.contextTransform(resolved);
|
|
2454
|
-
return resolved;
|
|
2455
|
-
};
|
|
2456
|
-
const run = (command, args, prefs) => {
|
|
2457
|
-
try {
|
|
2458
|
-
const commandObj = typeof command === "string" ? findCommandByName(command, rootCommand.commands) : command;
|
|
2459
|
-
if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
|
|
2460
|
-
if (!commandObj.action) throw new RoutingError(`Command "${commandObj.path}" has no action`, { command: commandObj.path });
|
|
2461
|
-
const executeCtx = {
|
|
2462
|
-
command: commandObj,
|
|
2463
|
-
input: void 0,
|
|
2464
|
-
rawArgs: {},
|
|
2465
|
-
positionalArgs: [],
|
|
2466
|
-
args,
|
|
2467
|
-
signal: inertSignal,
|
|
2468
|
-
context: resolveContext(commandObj, prefs?.context),
|
|
2469
|
-
runtime: getCommandRuntime(commandObj),
|
|
2470
|
-
program: ctx.builder,
|
|
2471
|
-
caller: "run"
|
|
2472
|
-
};
|
|
2473
|
-
const coreExecute = (executeCtx) => {
|
|
2474
|
-
const actionCtx = {
|
|
2475
|
-
runtime: executeCtx.runtime,
|
|
2476
|
-
command: executeCtx.command,
|
|
2477
|
-
program: ctx.builder,
|
|
2478
|
-
signal: inertSignal,
|
|
2479
|
-
context: executeCtx.context,
|
|
2480
|
-
caller: "run"
|
|
2481
|
-
};
|
|
2482
|
-
return { result: commandObj.action(executeCtx.args, actionCtx) };
|
|
2483
|
-
};
|
|
2484
|
-
const executedOrPromise = runInterceptorChain("execute", resolveRegisteredInterceptors(collectInterceptors(commandObj, rootCommand), /* @__PURE__ */ new Map()), executeCtx, coreExecute);
|
|
2485
|
-
const toResult = (e) => withDrain({
|
|
2486
|
-
command: commandObj,
|
|
2487
|
-
args,
|
|
2488
|
-
result: e.result
|
|
2489
|
-
});
|
|
2490
|
-
if (executedOrPromise instanceof Promise) return executedOrPromise.then(toResult).catch((err) => errorResult(err, {
|
|
2491
|
-
command: commandObj,
|
|
2492
|
-
args
|
|
2493
|
-
}));
|
|
2494
|
-
return toResult(executedOrPromise);
|
|
2495
|
-
} catch (err) {
|
|
2496
|
-
return errorResult(err);
|
|
2497
|
-
}
|
|
2498
|
-
};
|
|
2499
|
-
const tool = () => {
|
|
2500
|
-
resolveAllCommands(rootCommand);
|
|
2501
|
-
const description = `Run a command. Pass the full command string including arguments. Use "help <command>" for detailed usage.\n\n${generateHelp(rootCommand, void 0, { format: "text" })}`;
|
|
2502
|
-
return {
|
|
2503
|
-
type: "function",
|
|
2504
|
-
name: rootCommand.name,
|
|
2505
|
-
strict: true,
|
|
2506
|
-
title: rootCommand.description,
|
|
2507
|
-
description,
|
|
2508
|
-
inputExamples: [{ input: { command: "<command> [positionals...] [arguments...]" } }],
|
|
2509
|
-
inputSchema: {
|
|
2510
|
-
[Symbol.for("vercel.ai.schema")]: true,
|
|
2511
|
-
jsonSchema: {
|
|
2512
|
-
type: "object",
|
|
2513
|
-
properties: { command: { type: "string" } },
|
|
2514
|
-
additionalProperties: false
|
|
2515
|
-
},
|
|
2516
|
-
_type: void 0,
|
|
2517
|
-
validate: (value) => {
|
|
2518
|
-
const command = value?.command;
|
|
2519
|
-
if (typeof command === "string") return {
|
|
2520
|
-
success: true,
|
|
2521
|
-
value: { command }
|
|
2522
|
-
};
|
|
2523
|
-
return {
|
|
2524
|
-
success: false,
|
|
2525
|
-
error: /* @__PURE__ */ new Error("Expected an object with command property as string.")
|
|
2526
|
-
};
|
|
2527
|
-
}
|
|
2528
|
-
},
|
|
2529
|
-
needsApproval: async (input) => {
|
|
2530
|
-
const parsed = await parse(input.command);
|
|
2531
|
-
if (typeof parsed.command.needsApproval === "function") return parsed.command.needsApproval(parsed.args);
|
|
2532
|
-
if (parsed.command.needsApproval != null) return !!parsed.command.needsApproval;
|
|
2533
|
-
return !!parsed.command.mutation;
|
|
2534
|
-
},
|
|
2535
|
-
execute: async (input) => {
|
|
2536
|
-
const output = [];
|
|
2537
|
-
const errors = [];
|
|
2538
|
-
return {
|
|
2539
|
-
result: (await evalCommand(input.command, {
|
|
2540
|
-
caller: "tool",
|
|
2541
|
-
runtime: {
|
|
2542
|
-
output: (...args) => output.push(args.map(String).join(" ")),
|
|
2543
|
-
error: (text) => errors.push(text),
|
|
2544
|
-
interactive: "unsupported",
|
|
2545
|
-
format: "text"
|
|
2546
|
-
}
|
|
2547
|
-
})).result,
|
|
2548
|
-
logs: output.join("\n"),
|
|
2549
|
-
error: errors.join("\n")
|
|
2550
|
-
};
|
|
2551
|
-
}
|
|
2552
|
-
};
|
|
2553
|
-
};
|
|
2554
|
-
const replActiveRef = { value: false };
|
|
2555
|
-
const replFn = (options) => createReplIterator({
|
|
2556
|
-
existingCommand: rootCommand,
|
|
2557
|
-
evalCommand,
|
|
2558
|
-
replActiveRef
|
|
2559
|
-
}, options);
|
|
2560
|
-
const cli = (cliOptions) => {
|
|
2561
|
-
try {
|
|
2562
|
-
const result = execCommand(getCommandRuntime(rootCommand).argv().join(" ") || void 0, ctx, cliOptions, "hard", "cli");
|
|
2563
|
-
if (result instanceof Promise) return withPromiseDrain(result.catch((err) => errorResultWithSignal(err)));
|
|
2564
|
-
return makeThenable(result);
|
|
2565
|
-
} catch (err) {
|
|
2566
|
-
return makeThenable(errorResultWithSignal(err));
|
|
2567
|
-
}
|
|
2568
|
-
};
|
|
2569
|
-
const find = (command) => {
|
|
2570
|
-
if (typeof command !== "string") return findCommandByName(command.path, rootCommand.commands);
|
|
2571
|
-
return findCommandByName(command, rootCommand.commands);
|
|
2572
|
-
};
|
|
2573
|
-
const parse = (input) => {
|
|
2574
|
-
const { command, rawArgs, args } = ctx.parseCommandFn(input);
|
|
2575
|
-
return makeThenable(warnIfUnexpectedAsync(thenMaybe(coreValidateForParse(command, rawArgs, args), (v) => ({
|
|
2576
|
-
command,
|
|
2577
|
-
args: v.args,
|
|
2578
|
-
argsResult: v.argsResult
|
|
2579
|
-
})), command));
|
|
2580
|
-
};
|
|
2581
|
-
const help = (command, prefs) => {
|
|
2582
|
-
resolveAllCommands(rootCommand);
|
|
2583
|
-
const commandObj = !command ? rootCommand : typeof command === "string" ? findCommandByName(command, rootCommand.commands) : command;
|
|
2584
|
-
if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
|
|
2585
|
-
const runtime = getCommandRuntime(rootCommand);
|
|
2586
|
-
return generateHelp(rootCommand, commandObj, {
|
|
2587
|
-
...prefs,
|
|
2588
|
-
format: prefs?.format ?? runtime.format,
|
|
2589
|
-
theme: prefs?.theme ?? runtime.theme,
|
|
2590
|
-
terminal: prefs?.terminal ?? runtime.terminal,
|
|
2591
|
-
env: prefs?.env ?? runtime.env()
|
|
2592
|
-
});
|
|
2593
|
-
};
|
|
2594
|
-
const api = () => {
|
|
2595
|
-
resolveAllCommands(rootCommand);
|
|
2596
|
-
function buildApi(command) {
|
|
2597
|
-
const runCommand = ((args) => run(command, args).result);
|
|
2598
|
-
if (!command.commands) return runCommand;
|
|
2599
|
-
for (const cmd of command.commands) runCommand[cmd.name] = buildApi(cmd);
|
|
2600
|
-
return runCommand;
|
|
2601
|
-
}
|
|
2602
|
-
return buildApi(rootCommand);
|
|
2603
|
-
};
|
|
2604
|
-
const completion = async (shell) => {
|
|
2605
|
-
resolveAllCommands(rootCommand);
|
|
2606
|
-
const { generateCompletionOutput } = await import("./completion-D8qkAinX.mjs");
|
|
2607
|
-
return generateCompletionOutput(rootCommand, shell);
|
|
2608
|
-
};
|
|
2609
|
-
const mcp = async (prefs) => {
|
|
2610
|
-
resolveAllCommands(rootCommand);
|
|
2611
|
-
const { startMcpServer } = await import("./mcp-wCoVFTXz.mjs");
|
|
2612
|
-
return startMcpServer(ctx.builder, rootCommand, evalCommand, prefs);
|
|
2613
|
-
};
|
|
2614
|
-
const serve = async (prefs) => {
|
|
2615
|
-
resolveAllCommands(rootCommand);
|
|
2616
|
-
const { startServeServer } = await import("./serve-ICZFl3xr.mjs");
|
|
2617
|
-
return startServeServer(ctx.builder, rootCommand, evalCommand, prefs);
|
|
2618
|
-
};
|
|
2619
|
-
return {
|
|
2620
|
-
find,
|
|
2621
|
-
parse,
|
|
2622
|
-
stringify,
|
|
2623
|
-
run,
|
|
2624
|
-
eval: evalCommand,
|
|
2625
|
-
cli,
|
|
2626
|
-
tool,
|
|
2627
|
-
repl: replFn,
|
|
2628
|
-
api,
|
|
2629
|
-
help,
|
|
2630
|
-
completion,
|
|
2631
|
-
mcp,
|
|
2632
|
-
serve
|
|
2633
|
-
};
|
|
2634
|
-
}
|
|
2635
|
-
//#endregion
|
|
2636
|
-
//#region src/core/create.ts
|
|
2637
|
-
function createPadrone(name, options) {
|
|
2638
|
-
let builder = createPadroneBuilder({
|
|
2639
|
-
name,
|
|
2640
|
-
path: "",
|
|
2641
|
-
commands: []
|
|
2642
|
-
});
|
|
2643
|
-
const b = options?.builtins;
|
|
2644
|
-
if (b?.help !== false) builder = builder.extend(padroneHelp());
|
|
2645
|
-
if (b?.version !== false) builder = builder.extend(padroneVersion());
|
|
2646
|
-
if (b?.repl !== false) builder = builder.extend(padroneRepl());
|
|
2647
|
-
if (b?.color !== false) builder = builder.extend(padroneColor());
|
|
2648
|
-
if (b?.suggestions !== false) builder = builder.extend(padroneSuggestions());
|
|
2649
|
-
if (b?.signal !== false) builder = builder.extend(padroneSignalHandling());
|
|
2650
|
-
if (b?.autoOutput !== false) builder = builder.extend(padroneAutoOutput());
|
|
2651
|
-
if (b?.stdin !== false) builder = builder.extend(padroneStdin());
|
|
2652
|
-
if (b?.interactive !== false) builder = builder.extend(padroneInteractive());
|
|
2653
|
-
return builder;
|
|
2654
|
-
}
|
|
2655
|
-
function createPadroneBuilder(inputCommand) {
|
|
2656
|
-
const existingCommand = inputCommand.commands?.length && inputCommand.commands.some((c) => c.parent && c.parent !== inputCommand) ? {
|
|
2657
|
-
...inputCommand,
|
|
2658
|
-
commands: inputCommand.commands.map((c) => c.parent && c.parent !== inputCommand ? {
|
|
2659
|
-
...c,
|
|
2660
|
-
parent: inputCommand
|
|
2661
|
-
} : c)
|
|
2662
|
-
} : inputCommand;
|
|
2663
|
-
const parseCommandFn = (input) => parseCommand(input, existingCommand, findCommandByName);
|
|
2664
|
-
const collectInterceptorsFn = (cmd) => collectInterceptors(cmd, existingCommand);
|
|
2665
|
-
const execCtx = {
|
|
2666
|
-
rootCommand: existingCommand,
|
|
2667
|
-
builder: void 0,
|
|
2668
|
-
parseCommandFn,
|
|
2669
|
-
collectInterceptorsFn
|
|
2670
|
-
};
|
|
2671
|
-
const evalCommand = (input, evalOptions) => {
|
|
2672
|
-
try {
|
|
2673
|
-
const result = execCommand(input, execCtx, evalOptions, "soft", evalOptions?.caller ?? "eval");
|
|
2674
|
-
if (result instanceof Promise) return withPromiseDrain(result.catch((err) => errorResultWithSignal(err)));
|
|
2675
|
-
return makeThenable(result);
|
|
2676
|
-
} catch (err) {
|
|
2677
|
-
return makeThenable(errorResultWithSignal(err));
|
|
2678
|
-
}
|
|
2679
|
-
};
|
|
2680
|
-
const builder = {
|
|
2681
|
-
extend(extension) {
|
|
2682
|
-
return extension(builder);
|
|
2683
|
-
},
|
|
2684
|
-
configure(config) {
|
|
2685
|
-
return createPadroneBuilder({
|
|
2686
|
-
...existingCommand,
|
|
2687
|
-
...config
|
|
2688
|
-
});
|
|
2689
|
-
},
|
|
2690
|
-
runtime(runtimeConfig) {
|
|
2691
|
-
return createPadroneBuilder({
|
|
2692
|
-
...existingCommand,
|
|
2693
|
-
runtime: {
|
|
2694
|
-
...existingCommand.runtime,
|
|
2695
|
-
...runtimeConfig
|
|
2696
|
-
}
|
|
2697
|
-
});
|
|
2698
|
-
},
|
|
2699
|
-
async() {
|
|
2700
|
-
return createPadroneBuilder({
|
|
2701
|
-
...existingCommand,
|
|
2702
|
-
isAsync: true
|
|
2703
|
-
});
|
|
2704
|
-
},
|
|
2705
|
-
context(transform) {
|
|
2706
|
-
if (!transform) return createPadroneBuilder({ ...existingCommand });
|
|
2707
|
-
const existing = existingCommand.contextTransform;
|
|
2708
|
-
const composed = existing ? (ctx) => transform(existing(ctx)) : transform;
|
|
2709
|
-
return createPadroneBuilder({
|
|
2710
|
-
...existingCommand,
|
|
2711
|
-
contextTransform: composed
|
|
2712
|
-
});
|
|
2713
|
-
},
|
|
2714
|
-
arguments(schema, meta) {
|
|
2715
|
-
const resolvedArgs = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema;
|
|
2716
|
-
const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedArgs) || hasInteractiveConfig(meta);
|
|
2717
|
-
return createPadroneBuilder({
|
|
2718
|
-
...existingCommand,
|
|
2719
|
-
argsSchema: resolvedArgs,
|
|
2720
|
-
meta,
|
|
2721
|
-
isAsync
|
|
2722
|
-
});
|
|
2723
|
-
},
|
|
2724
|
-
action(handler = noop) {
|
|
2725
|
-
const baseHandler = existingCommand.action ?? noop;
|
|
2726
|
-
return createPadroneBuilder({
|
|
2727
|
-
...existingCommand,
|
|
2728
|
-
action: (args, ctx) => handler(args, ctx, baseHandler)
|
|
2729
|
-
});
|
|
2730
|
-
},
|
|
2731
|
-
wrap(config) {
|
|
2732
|
-
const handler = createWrapHandler(config, existingCommand.argsSchema, existingCommand.meta?.positional);
|
|
2733
|
-
return createPadroneBuilder({
|
|
2734
|
-
...existingCommand,
|
|
2735
|
-
action: handler
|
|
2736
|
-
});
|
|
2737
|
-
},
|
|
2738
|
-
command(nameOrNames, builderFn) {
|
|
2739
|
-
const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
|
|
2740
|
-
const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
|
|
2741
|
-
const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
|
|
2742
|
-
if (existingSubcommand) resolveCommand(existingSubcommand);
|
|
2743
|
-
const initialCommand = existingSubcommand ? {
|
|
2744
|
-
...existingSubcommand,
|
|
2745
|
-
aliases: aliases ?? existingSubcommand.aliases,
|
|
2746
|
-
parent: existingCommand
|
|
2747
|
-
} : {
|
|
2748
|
-
name,
|
|
2749
|
-
path: existingCommand.path ? `${existingCommand.path} ${name}` : name,
|
|
2750
|
-
aliases,
|
|
2751
|
-
parent: existingCommand,
|
|
2752
|
-
"~types": {}
|
|
2753
|
-
};
|
|
2754
|
-
if (builderFn) {
|
|
2755
|
-
const lazyCmd = { ...initialCommand };
|
|
2756
|
-
lazyCmd[lazyResolver] = (target) => {
|
|
2757
|
-
const savedParent = target.parent;
|
|
2758
|
-
const commandObj = builderFn(createPadroneBuilder(target))?.[commandSymbol] ?? target;
|
|
2759
|
-
const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, commandObj) : commandObj;
|
|
2760
|
-
Object.assign(target, mergedCommandObj);
|
|
2761
|
-
target.parent = savedParent;
|
|
2762
|
-
};
|
|
2763
|
-
const commands = existingCommand.commands || [];
|
|
2764
|
-
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
2765
|
-
const updatedCommands = existingIndex >= 0 ? [
|
|
2766
|
-
...commands.slice(0, existingIndex),
|
|
2767
|
-
lazyCmd,
|
|
2768
|
-
...commands.slice(existingIndex + 1)
|
|
2769
|
-
] : [...commands, lazyCmd];
|
|
2770
|
-
return createPadroneBuilder({
|
|
2771
|
-
...existingCommand,
|
|
2772
|
-
commands: updatedCommands
|
|
2773
|
-
});
|
|
2774
|
-
}
|
|
2775
|
-
const commands = existingCommand.commands || [];
|
|
2776
|
-
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
2777
|
-
const updatedCommands = existingIndex >= 0 ? [
|
|
2778
|
-
...commands.slice(0, existingIndex),
|
|
2779
|
-
initialCommand,
|
|
2780
|
-
...commands.slice(existingIndex + 1)
|
|
2781
|
-
] : [...commands, initialCommand];
|
|
2782
|
-
return createPadroneBuilder({
|
|
2783
|
-
...existingCommand,
|
|
2784
|
-
commands: updatedCommands
|
|
2785
|
-
});
|
|
2786
|
-
},
|
|
2787
|
-
mount(nameOrNames, program, options) {
|
|
2788
|
-
const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
|
|
2789
|
-
const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
|
|
2790
|
-
const programCommand = program[commandSymbol];
|
|
2791
|
-
if (!programCommand) throw new RoutingError("Cannot mount: not a valid Padrone program");
|
|
2792
|
-
const remounted = repathCommandTree(programCommand, name, existingCommand.path || "", existingCommand);
|
|
2793
|
-
remounted.aliases = aliases;
|
|
2794
|
-
if (options?.context) {
|
|
2795
|
-
const existing = remounted.contextTransform;
|
|
2796
|
-
remounted.contextTransform = existing ? (ctx) => existing(options.context(ctx)) : options.context;
|
|
2797
|
-
}
|
|
2798
|
-
const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
|
|
2799
|
-
const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, remounted) : remounted;
|
|
2800
|
-
const commands = existingCommand.commands || [];
|
|
2801
|
-
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
2802
|
-
const updatedCommands = existingIndex >= 0 ? [
|
|
2803
|
-
...commands.slice(0, existingIndex),
|
|
2804
|
-
mergedCommandObj,
|
|
2805
|
-
...commands.slice(existingIndex + 1)
|
|
2806
|
-
] : [...commands, mergedCommandObj];
|
|
2807
|
-
return createPadroneBuilder({
|
|
2808
|
-
...existingCommand,
|
|
2809
|
-
commands: updatedCommands
|
|
2810
|
-
});
|
|
2811
|
-
},
|
|
2812
|
-
intercept(metaOrFn, factory) {
|
|
2813
|
-
const registered = toRegisteredInterceptor(metaOrFn, factory);
|
|
2814
|
-
return createPadroneBuilder({
|
|
2815
|
-
...existingCommand,
|
|
2816
|
-
interceptors: [...existingCommand.interceptors ?? [], registered]
|
|
2817
|
-
});
|
|
2818
|
-
},
|
|
2819
|
-
...createProgramMethods(execCtx, evalCommand),
|
|
2820
|
-
get info() {
|
|
2821
|
-
return {
|
|
2822
|
-
name: existingCommand.name,
|
|
2823
|
-
title: existingCommand.title,
|
|
2824
|
-
description: existingCommand.description,
|
|
2825
|
-
version: existingCommand.version,
|
|
2826
|
-
examples: existingCommand.examples,
|
|
2827
|
-
deprecated: existingCommand.deprecated,
|
|
2828
|
-
commands: (existingCommand.commands ?? []).map((c) => c.name)
|
|
2829
|
-
};
|
|
2830
|
-
},
|
|
2831
|
-
"~types": {},
|
|
2832
|
-
[commandSymbol]: existingCommand
|
|
2833
|
-
};
|
|
2834
|
-
execCtx.builder = builder;
|
|
2835
|
-
return builder;
|
|
2836
|
-
}
|
|
2837
|
-
function defineCommand(fn) {
|
|
2838
|
-
if (fn) return fn;
|
|
2839
|
-
const builder = {
|
|
2840
|
-
requires: () => builder,
|
|
2841
|
-
define: (f) => f
|
|
2842
|
-
};
|
|
2843
|
-
return builder;
|
|
2844
|
-
}
|
|
2845
|
-
//#endregion
|
|
2846
|
-
//#region src/extension/completion.ts
|
|
2847
|
-
/**
|
|
2848
|
-
* Extension that adds the `completion` command for shell completion script generation.
|
|
2849
|
-
*
|
|
2850
|
-
* Usage:
|
|
2851
|
-
* ```ts
|
|
2852
|
-
* createPadrone('my-cli').extend(padroneCompletion())
|
|
2853
|
-
* ```
|
|
2854
|
-
*/
|
|
2855
|
-
function padroneCompletion() {
|
|
2856
|
-
return ((builder) => builder.command("completion", (c) => c.configure({
|
|
2857
|
-
description: "Generate shell completion scripts",
|
|
2858
|
-
hidden: true
|
|
2859
|
-
}).arguments(passthroughSchema({
|
|
2860
|
-
shell: "string",
|
|
2861
|
-
setup: "boolean"
|
|
2862
|
-
}), { positional: ["shell"] }).async().action(async (args, ctx) => {
|
|
2863
|
-
const rootCommand = getRootCommand(ctx.command);
|
|
2864
|
-
resolveAllCommands(rootCommand);
|
|
2865
|
-
const { detectShell, generateCompletionOutput, setupCompletions } = await import("./completion-D8qkAinX.mjs");
|
|
2866
|
-
const shell = args.shell;
|
|
2867
|
-
if (args.setup) {
|
|
2868
|
-
const resolvedShell = shell ?? await detectShell();
|
|
2869
|
-
if (!resolvedShell) throw new Error("Could not detect shell. Specify one: completion bash --setup");
|
|
2870
|
-
const setupResult = await setupCompletions(rootCommand.name, resolvedShell);
|
|
2871
|
-
return `${setupResult.updated ? "Updated" : "Added"} ${rootCommand.name} completions in ${setupResult.file}`;
|
|
2872
|
-
}
|
|
2873
|
-
return generateCompletionOutput(rootCommand, shell);
|
|
2874
|
-
})));
|
|
2875
|
-
}
|
|
2876
|
-
//#endregion
|
|
2877
|
-
//#region src/extension/config.ts
|
|
2878
|
-
let _fs$1;
|
|
2879
|
-
let _path$1;
|
|
2880
|
-
async function initNodeModules$1() {
|
|
2881
|
-
if (_fs$1 && _path$1) return;
|
|
2882
|
-
_fs$1 = await import("node:fs");
|
|
2883
|
-
_path$1 = await import("node:path");
|
|
2884
|
-
}
|
|
2885
|
-
try {
|
|
2886
|
-
if (typeof process !== "undefined") initNodeModules$1();
|
|
2887
|
-
} catch {}
|
|
2888
|
-
function getUserConfigDir(path, appName) {
|
|
2889
|
-
const platform = process.platform;
|
|
2890
|
-
const xdgHome = process.env.XDG_CONFIG_HOME;
|
|
2891
|
-
if (xdgHome) return path.join(xdgHome, appName);
|
|
2892
|
-
const home = process.env.HOME || process.env.USERPROFILE;
|
|
2893
|
-
if (!home) return void 0;
|
|
2894
|
-
if (platform === "win32") {
|
|
2895
|
-
const appData = process.env.APPDATA;
|
|
2896
|
-
return appData ? path.join(appData, appName) : path.join(home, "AppData", "Roaming", appName);
|
|
2897
|
-
}
|
|
2898
|
-
if (platform === "darwin") return path.join(home, "Library", "Application Support", appName);
|
|
2899
|
-
return path.join(home, ".config", appName);
|
|
2900
|
-
}
|
|
2901
|
-
function resolveConfigPath(fs, path, cwd, files, xdgAppName) {
|
|
2902
|
-
if (typeof files === "string") {
|
|
2903
|
-
const abs = path.isAbsolute(files) ? files : path.resolve(cwd, files);
|
|
2904
|
-
if (!fs.existsSync(abs)) {
|
|
2905
|
-
console.error(`Config file not found: ${abs}`);
|
|
2906
|
-
return;
|
|
2907
|
-
}
|
|
2908
|
-
return abs;
|
|
2909
|
-
}
|
|
2910
|
-
for (const candidate of files) {
|
|
2911
|
-
const abs = path.isAbsolute(candidate) ? candidate : path.resolve(cwd, candidate);
|
|
2912
|
-
if (fs.existsSync(abs)) return abs;
|
|
2913
|
-
}
|
|
2914
|
-
if (xdgAppName) {
|
|
2915
|
-
const configDir = getUserConfigDir(path, xdgAppName);
|
|
2916
|
-
if (configDir) for (const candidate of files) {
|
|
2917
|
-
const abs = path.join(configDir, candidate);
|
|
2918
|
-
if (fs.existsSync(abs)) return abs;
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
function loadConfigSync(fs, path, files, xdgAppName) {
|
|
2923
|
-
const absolutePath = resolveConfigPath(fs, path, process.cwd(), files, xdgAppName);
|
|
2924
|
-
if (!absolutePath) return void 0;
|
|
2925
|
-
const getContent = () => fs.readFileSync(absolutePath, "utf-8");
|
|
2926
|
-
const ext = path.extname(absolutePath).toLowerCase();
|
|
2927
|
-
if (ext === ".yaml" || ext === ".yml") return Bun.YAML.parse(getContent());
|
|
2928
|
-
if (ext === ".toml") return Bun.TOML.parse(getContent());
|
|
2929
|
-
if (ext === ".jsonc") return Bun.JSONC.parse(getContent());
|
|
2930
|
-
if (ext === ".json") {
|
|
2931
|
-
if (Bun.JSONC) return Bun.JSONC.parse(getContent());
|
|
2932
|
-
try {
|
|
2933
|
-
return JSON.parse(getContent());
|
|
2934
|
-
} catch {
|
|
2935
|
-
return Bun.JSONC.parse(getContent());
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
if (ext === ".js" || ext === ".cjs" || ext === ".mjs" || ext === ".ts" || ext === ".cts" || ext === ".mts") return import(
|
|
2939
|
-
/* @vite-ignore */
|
|
2940
|
-
absolutePath
|
|
2941
|
-
).then((mod) => mod.default ?? mod);
|
|
2942
|
-
try {
|
|
2943
|
-
return JSON.parse(getContent());
|
|
2944
|
-
} catch {
|
|
2945
|
-
console.error(`Unable to parse config file: ${absolutePath}`);
|
|
2946
|
-
return;
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
/**
|
|
2950
|
-
* Built-in config file loader. Directly accesses the file system.
|
|
2951
|
-
* Returns `undefined` in non-CLI environments where `node:fs` is unavailable.
|
|
2952
|
-
*/
|
|
2953
|
-
function loadConfig(files, xdgAppName) {
|
|
2954
|
-
if (typeof process === "undefined") return void 0;
|
|
2955
|
-
try {
|
|
2956
|
-
if (_fs$1 && _path$1) return loadConfigSync(_fs$1, _path$1, files, xdgAppName);
|
|
2957
|
-
return initNodeModules$1().then(() => loadConfigSync(_fs$1, _path$1, files, xdgAppName));
|
|
2958
|
-
} catch {
|
|
2959
|
-
return;
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
/**
|
|
2963
|
-
* Extension that handles config file loading, validation, and merging into command arguments.
|
|
2964
|
-
*
|
|
2965
|
-
* Features:
|
|
2966
|
-
* - `--config` / `-c` flag for explicit config file path (can be disabled via `flag: false`)
|
|
2967
|
-
* - Auto-detection of config files from a list of candidate names
|
|
2968
|
-
* - Optional schema validation and transformation of config data
|
|
2969
|
-
* - Directly accesses the file system (gracefully no-ops in non-CLI environments)
|
|
2970
|
-
*
|
|
2971
|
-
* Config values have the lowest precedence (CLI > stdin > env > config).
|
|
2972
|
-
*
|
|
2973
|
-
* Not included in the default built-in extensions — must be explicitly added:
|
|
2974
|
-
* ```ts
|
|
2975
|
-
* createPadrone('my-cli')
|
|
2976
|
-
* .extend(padroneConfig({
|
|
2977
|
-
* files: ['config.json', '.myapprc'],
|
|
2978
|
-
* schema: z.object({ port: z.number(), host: z.string() }),
|
|
2979
|
-
* }))
|
|
2980
|
-
* ```
|
|
2981
|
-
*/
|
|
2982
|
-
function padroneConfig(options) {
|
|
2983
|
-
if (options?.disabled) {
|
|
2984
|
-
const disabled = defineInterceptor({
|
|
2985
|
-
id: "padrone:config",
|
|
2986
|
-
name: "padrone:config",
|
|
2987
|
-
order: -999,
|
|
2988
|
-
disabled: true
|
|
2989
|
-
}, () => ({}));
|
|
2990
|
-
return ((builder) => builder.intercept(disabled));
|
|
2991
|
-
}
|
|
2992
|
-
const configFiles = options?.files ? Array.isArray(options.files) ? options.files : [options.files] : void 0;
|
|
2993
|
-
const configSchema = options?.schema;
|
|
2994
|
-
const flagEnabled = options?.flag !== false;
|
|
2995
|
-
const inherit = options?.inherit;
|
|
2996
|
-
const xdgOption = options?.xdg;
|
|
2997
|
-
const configLoader = options?.loadConfig ?? loadConfig;
|
|
2998
|
-
const interceptor = defineInterceptor({
|
|
2999
|
-
id: "padrone:config",
|
|
3000
|
-
name: "padrone:config",
|
|
3001
|
-
order: -999,
|
|
3002
|
-
...inherit === false && { inherit: false }
|
|
3003
|
-
}, () => ({ validate(ctx, next) {
|
|
3004
|
-
let explicitConfigPath;
|
|
3005
|
-
if (flagEnabled) {
|
|
3006
|
-
explicitConfigPath = ctx.rawArgs.config ?? ctx.rawArgs.c;
|
|
3007
|
-
if (typeof explicitConfigPath === "string") {
|
|
3008
|
-
delete ctx.rawArgs.config;
|
|
3009
|
-
delete ctx.rawArgs.c;
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3012
|
-
if (!explicitConfigPath && !configFiles) return next();
|
|
3013
|
-
let xdgAppName;
|
|
3014
|
-
if (typeof xdgOption === "string") xdgAppName = xdgOption;
|
|
3015
|
-
else if (xdgOption === true) xdgAppName = getRootCommand(ctx.command).name;
|
|
3016
|
-
const configDataOrPromise = configLoader(explicitConfigPath ?? configFiles ?? [], xdgAppName);
|
|
3017
|
-
const applyConfig = (configData) => {
|
|
3018
|
-
if (!configData) return next();
|
|
3019
|
-
if (configSchema) return thenMaybe(configSchema["~standard"].validate(configData), (result) => {
|
|
3020
|
-
if (result.issues) throw new ConfigError(`Invalid config file:\n${result.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`, { command: ctx.command.path || ctx.command.name });
|
|
3021
|
-
const validatedData = result.value;
|
|
3022
|
-
return next({ rawArgs: applyValues(ctx.rawArgs, validatedData) });
|
|
3023
|
-
});
|
|
3024
|
-
return next({ rawArgs: applyValues(ctx.rawArgs, configData) });
|
|
3025
|
-
};
|
|
3026
|
-
return thenMaybe(configDataOrPromise, applyConfig);
|
|
3027
|
-
} }));
|
|
3028
|
-
return ((builder) => builder.intercept(interceptor));
|
|
3029
|
-
}
|
|
3030
|
-
//#endregion
|
|
3031
|
-
//#region src/util/dotenv.ts
|
|
3032
|
-
/** Returns ordered list of `.env` file names to load (no fs access). */
|
|
3033
|
-
function resolveEnvFiles(modes = [], local = true, base = true) {
|
|
3034
|
-
const files = [];
|
|
3035
|
-
if (base) {
|
|
3036
|
-
files.push(".env");
|
|
3037
|
-
if (local) files.push(".env.local");
|
|
3038
|
-
}
|
|
3039
|
-
for (const mode of modes) {
|
|
3040
|
-
files.push(`.env.${mode}`);
|
|
3041
|
-
if (local) files.push(`.env.${mode}.local`);
|
|
3042
|
-
}
|
|
3043
|
-
return files;
|
|
3044
|
-
}
|
|
3045
|
-
/** Parse a `.env` file string into key-value pairs. */
|
|
3046
|
-
function parseEnvFile(content) {
|
|
3047
|
-
const result = {};
|
|
3048
|
-
const lines = content.split("\n");
|
|
3049
|
-
let i = 0;
|
|
3050
|
-
while (i < lines.length) {
|
|
3051
|
-
const line = lines[i].trim();
|
|
3052
|
-
i++;
|
|
3053
|
-
if (!line || line.startsWith("#")) continue;
|
|
3054
|
-
const stripped = line.startsWith("export ") ? line.slice(7) : line;
|
|
3055
|
-
const eqIndex = stripped.indexOf("=");
|
|
3056
|
-
if (eqIndex === -1) continue;
|
|
3057
|
-
const key = stripped.slice(0, eqIndex).trim();
|
|
3058
|
-
let raw = stripped.slice(eqIndex + 1);
|
|
3059
|
-
const trimmedRaw = raw.trimStart();
|
|
3060
|
-
const quote = trimmedRaw[0];
|
|
3061
|
-
if (quote === "\"" || quote === "'" || quote === "`") {
|
|
3062
|
-
let value = trimmedRaw.slice(1);
|
|
3063
|
-
const closeIndex = findClosingQuote(value, quote);
|
|
3064
|
-
if (closeIndex !== -1) value = value.slice(0, closeIndex);
|
|
3065
|
-
else while (i < lines.length) {
|
|
3066
|
-
const nextLine = lines[i];
|
|
3067
|
-
i++;
|
|
3068
|
-
const ci = findClosingQuote(nextLine, quote);
|
|
3069
|
-
if (ci !== -1) {
|
|
3070
|
-
value += `\n${nextLine.slice(0, ci)}`;
|
|
3071
|
-
break;
|
|
3072
|
-
}
|
|
3073
|
-
value += `\n${nextLine}`;
|
|
3074
|
-
}
|
|
3075
|
-
if (quote === "\"") value = unescapeDoubleQuoted(value);
|
|
3076
|
-
result[key] = value;
|
|
3077
|
-
} else {
|
|
3078
|
-
const commentIndex = raw.indexOf(" #");
|
|
3079
|
-
if (commentIndex !== -1) raw = raw.slice(0, commentIndex);
|
|
3080
|
-
result[key] = raw.trim();
|
|
3081
|
-
}
|
|
3082
|
-
}
|
|
3083
|
-
return result;
|
|
3084
|
-
}
|
|
3085
|
-
function findClosingQuote(s, quote) {
|
|
3086
|
-
let i = 0;
|
|
3087
|
-
while (i < s.length) {
|
|
3088
|
-
if (s[i] === "\\" && quote === "\"") {
|
|
3089
|
-
i += 2;
|
|
3090
|
-
continue;
|
|
3091
|
-
}
|
|
3092
|
-
if (s[i] === quote) return i;
|
|
3093
|
-
i++;
|
|
3094
|
-
}
|
|
3095
|
-
return -1;
|
|
3096
|
-
}
|
|
3097
|
-
function unescapeDoubleQuoted(s) {
|
|
3098
|
-
return s.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
3099
|
-
}
|
|
3100
|
-
/**
|
|
3101
|
-
* Expand `$VAR`, `${VAR}`, `${VAR:-default}`, `${VAR-default}` in a string.
|
|
3102
|
-
* Escaped `\$` produces a literal `$`. Undefined variables resolve to `""`.
|
|
3103
|
-
*/
|
|
3104
|
-
function expandVariables(value, env) {
|
|
3105
|
-
let result = "";
|
|
3106
|
-
let i = 0;
|
|
3107
|
-
while (i < value.length) {
|
|
3108
|
-
if (value[i] === "\\" && value[i + 1] === "$") {
|
|
3109
|
-
result += "$";
|
|
3110
|
-
i += 2;
|
|
3111
|
-
continue;
|
|
3112
|
-
}
|
|
3113
|
-
if (value[i] === "$") {
|
|
3114
|
-
i++;
|
|
3115
|
-
if (i >= value.length) {
|
|
3116
|
-
result += "$";
|
|
3117
|
-
break;
|
|
3118
|
-
}
|
|
3119
|
-
if (value[i] === "{") {
|
|
3120
|
-
i++;
|
|
3121
|
-
const closeIdx = value.indexOf("}", i);
|
|
3122
|
-
if (closeIdx === -1) {
|
|
3123
|
-
result += `\${${value.slice(i)}`;
|
|
3124
|
-
break;
|
|
3125
|
-
}
|
|
3126
|
-
const expr = value.slice(i, closeIdx);
|
|
3127
|
-
i = closeIdx + 1;
|
|
3128
|
-
const colonDashIdx = expr.indexOf(":-");
|
|
3129
|
-
const dashIdx = colonDashIdx === -1 ? expr.indexOf("-") : -1;
|
|
3130
|
-
if (colonDashIdx !== -1) {
|
|
3131
|
-
const varName = expr.slice(0, colonDashIdx);
|
|
3132
|
-
const fallback = expr.slice(colonDashIdx + 2);
|
|
3133
|
-
const val = env[varName];
|
|
3134
|
-
result += val ? val : expandVariables(fallback, env);
|
|
3135
|
-
} else if (dashIdx !== -1) {
|
|
3136
|
-
const varName = expr.slice(0, dashIdx);
|
|
3137
|
-
const fallback = expr.slice(dashIdx + 1);
|
|
3138
|
-
const val = env[varName];
|
|
3139
|
-
result += val !== void 0 ? val : expandVariables(fallback, env);
|
|
3140
|
-
} else result += env[expr] ?? "";
|
|
3141
|
-
} else {
|
|
3142
|
-
let varName = "";
|
|
3143
|
-
while (i < value.length && /[\w]/.test(value[i])) {
|
|
3144
|
-
varName += value[i];
|
|
3145
|
-
i++;
|
|
3146
|
-
}
|
|
3147
|
-
if (varName) result += env[varName] ?? "";
|
|
3148
|
-
else result += "$";
|
|
3149
|
-
}
|
|
3150
|
-
continue;
|
|
3151
|
-
}
|
|
3152
|
-
result += value[i];
|
|
3153
|
-
i++;
|
|
3154
|
-
}
|
|
3155
|
-
return result;
|
|
3156
|
-
}
|
|
3157
|
-
/**
|
|
3158
|
-
* Load and merge `.env` files, returning the combined key-value map.
|
|
3159
|
-
* Variable expansion uses the merged file values + process env as lookup.
|
|
3160
|
-
*
|
|
3161
|
-
* Returns synchronously when `node:fs`/`node:path` are already cached (typical),
|
|
3162
|
-
* or a Promise on the very first call.
|
|
3163
|
-
*/
|
|
3164
|
-
function loadEnvFiles(options, processEnv) {
|
|
3165
|
-
if (typeof process === "undefined") return {};
|
|
3166
|
-
try {
|
|
3167
|
-
if (_fs && _path) return loadEnvFilesSync(_fs, _path, options, processEnv);
|
|
3168
|
-
return initNodeModules().then(() => loadEnvFilesSync(_fs, _path, options, processEnv)).catch(() => ({}));
|
|
3169
|
-
} catch {
|
|
3170
|
-
return {};
|
|
3171
|
-
}
|
|
3172
|
-
}
|
|
3173
|
-
let _fs;
|
|
3174
|
-
let _path;
|
|
3175
|
-
async function initNodeModules() {
|
|
3176
|
-
if (_fs && _path) return;
|
|
3177
|
-
_fs = await import("node:fs");
|
|
3178
|
-
_path = await import("node:path");
|
|
3179
|
-
}
|
|
3180
|
-
if (typeof process !== "undefined") initNodeModules();
|
|
3181
|
-
function loadEnvFilesSync(fs, path, options, processEnv) {
|
|
3182
|
-
const dir = options.dir ?? process.cwd();
|
|
3183
|
-
const fileNames = resolveEnvFiles(options.modes, options.local ?? true, options.base ?? true);
|
|
3184
|
-
const merged = {};
|
|
3185
|
-
for (const name of fileNames) {
|
|
3186
|
-
const filePath = path.resolve(dir, name);
|
|
3187
|
-
if (!fs.existsSync(filePath)) continue;
|
|
3188
|
-
const parsed = parseEnvFile(fs.readFileSync(filePath, "utf-8"));
|
|
3189
|
-
Object.assign(merged, parsed);
|
|
3190
|
-
}
|
|
3191
|
-
const lookup = options.override ? {
|
|
3192
|
-
...processEnv,
|
|
3193
|
-
...merged
|
|
3194
|
-
} : {
|
|
3195
|
-
...merged,
|
|
3196
|
-
...processEnv
|
|
3197
|
-
};
|
|
3198
|
-
for (const key of Object.keys(merged)) merged[key] = expandVariables(merged[key], lookup);
|
|
3199
|
-
return merged;
|
|
3200
|
-
}
|
|
3201
|
-
//#endregion
|
|
3202
|
-
//#region src/extension/env.ts
|
|
3203
|
-
function isSchema(value) {
|
|
3204
|
-
return value != null && typeof value === "object" && "~standard" in value;
|
|
3205
|
-
}
|
|
3206
|
-
function padroneEnv(schemaOrOptions, maybeOptions) {
|
|
3207
|
-
const schema = isSchema(schemaOrOptions) ? schemaOrOptions : void 0;
|
|
3208
|
-
const options = isSchema(schemaOrOptions) ? maybeOptions : schemaOrOptions;
|
|
3209
|
-
const hasFiles = options?.modes !== void 0;
|
|
3210
|
-
const fileOptions = hasFiles ? options : void 0;
|
|
3211
|
-
const override = options?.override ?? false;
|
|
3212
|
-
const interceptor = defineInterceptor({
|
|
3213
|
-
id: "padrone:env",
|
|
3214
|
-
name: "padrone:env",
|
|
3215
|
-
order: -1e3
|
|
3216
|
-
}, () => ({ validate(ctx, next) {
|
|
3217
|
-
const processEnv = ctx.runtime.env();
|
|
3218
|
-
const applyEnv = (envFromFiles) => {
|
|
3219
|
-
const rawEnv = override ? {
|
|
3220
|
-
...processEnv,
|
|
3221
|
-
...envFromFiles
|
|
3222
|
-
} : {
|
|
3223
|
-
...envFromFiles,
|
|
3224
|
-
...processEnv
|
|
3225
|
-
};
|
|
3226
|
-
if (schema) return thenMaybe(schema["~standard"].validate(rawEnv), (result) => {
|
|
3227
|
-
if (result.issues || !result.value) return next();
|
|
3228
|
-
return next({ rawArgs: applyValues(ctx.rawArgs, result.value) });
|
|
3229
|
-
});
|
|
3230
|
-
if (Object.keys(envFromFiles).length > 0) return next({ rawArgs: applyValues(ctx.rawArgs, envFromFiles) });
|
|
3231
|
-
return next();
|
|
3232
|
-
};
|
|
3233
|
-
if (hasFiles) return thenMaybe(loadEnvFiles(fileOptions, processEnv), applyEnv);
|
|
3234
|
-
return applyEnv({});
|
|
3235
|
-
} }));
|
|
3236
|
-
return ((builder) => builder.intercept(interceptor));
|
|
3237
|
-
}
|
|
3238
|
-
//#endregion
|
|
3239
|
-
//#region src/extension/ink.ts
|
|
3240
|
-
const reactElement = Symbol.for("react.element");
|
|
3241
|
-
const reactTransitional = Symbol.for("react.transitional.element");
|
|
3242
|
-
/** Checks whether a value is a React element (JSX) by inspecting its `$$typeof` symbol. */
|
|
3243
|
-
function isReactElement(value) {
|
|
3244
|
-
if (value === null || typeof value !== "object") return false;
|
|
3245
|
-
const tag = value.$$typeof;
|
|
3246
|
-
return tag === reactElement || tag === reactTransitional;
|
|
3247
|
-
}
|
|
3248
|
-
const inkMeta = {
|
|
3249
|
-
id: "padrone:ink",
|
|
3250
|
-
name: "padrone:ink",
|
|
3251
|
-
order: -1050
|
|
3252
|
-
};
|
|
3253
|
-
function createInkInterceptor(rawOptions) {
|
|
3254
|
-
return defineInterceptor(inkMeta).requires().factory(() => ({ execute(ctx, next) {
|
|
3255
|
-
const options = {
|
|
3256
|
-
...ctx.context?.inkConfig,
|
|
3257
|
-
...rawOptions
|
|
3258
|
-
};
|
|
3259
|
-
const { waitUntilExit = true } = options;
|
|
3260
|
-
const handleResult = async (e) => {
|
|
3261
|
-
let value = e.result;
|
|
3262
|
-
if (value instanceof Promise) value = await value;
|
|
3263
|
-
if (!isReactElement(value)) return e;
|
|
3264
|
-
const { render } = await import("ink");
|
|
3265
|
-
const instance = render(value, options.render);
|
|
3266
|
-
const onAbort = () => instance.unmount();
|
|
3267
|
-
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
3268
|
-
if (waitUntilExit) try {
|
|
3269
|
-
await instance.waitUntilExit();
|
|
3270
|
-
} finally {
|
|
3271
|
-
ctx.signal.removeEventListener("abort", onAbort);
|
|
3272
|
-
}
|
|
3273
|
-
return { result: void 0 };
|
|
3274
|
-
};
|
|
3275
|
-
const executedOrPromise = next();
|
|
3276
|
-
if (executedOrPromise instanceof Promise) return executedOrPromise.then(handleResult);
|
|
3277
|
-
return handleResult(executedOrPromise);
|
|
3278
|
-
} }));
|
|
3279
|
-
}
|
|
3280
|
-
/**
|
|
3281
|
-
* Extension that renders React (Ink) components returned from command actions.
|
|
3282
|
-
*
|
|
3283
|
-
* When a command's action returns a React element (JSX), this extension
|
|
3284
|
-
* renders it using Ink instead of passing it to the normal output path.
|
|
3285
|
-
*
|
|
3286
|
-
* Requires `ink` and `react` as peer dependencies.
|
|
3287
|
-
*
|
|
3288
|
-
* ```ts
|
|
3289
|
-
* import { createPadrone, padroneInk } from 'padrone';
|
|
3290
|
-
*
|
|
3291
|
-
* const program = createPadrone('my-tui')
|
|
3292
|
-
* .extend(padroneInk())
|
|
3293
|
-
* .command('dashboard', (c) =>
|
|
3294
|
-
* c.action(() => <Dashboard />)
|
|
3295
|
-
* );
|
|
3296
|
-
* ```
|
|
3297
|
-
*/
|
|
3298
|
-
function padroneInk(options) {
|
|
3299
|
-
const interceptor = createInkInterceptor(options);
|
|
3300
|
-
return ((builder) => builder.intercept(interceptor));
|
|
3301
|
-
}
|
|
3302
|
-
//#endregion
|
|
3303
|
-
//#region src/extension/logger.ts
|
|
3304
|
-
const LEVEL_ORDER = {
|
|
3305
|
-
trace: 0,
|
|
3306
|
-
debug: 1,
|
|
3307
|
-
info: 2,
|
|
3308
|
-
warn: 3,
|
|
3309
|
-
error: 4,
|
|
3310
|
-
silent: 5
|
|
3311
|
-
};
|
|
3312
|
-
const LEVEL_LABELS = {
|
|
3313
|
-
trace: "TRACE",
|
|
3314
|
-
debug: "DEBUG",
|
|
3315
|
-
info: "INFO",
|
|
3316
|
-
warn: "WARN",
|
|
3317
|
-
error: "ERROR"
|
|
3318
|
-
};
|
|
3319
|
-
const VALID_LEVELS = new Set(Object.keys(LEVEL_ORDER));
|
|
3320
|
-
/** Format specifier pattern: matches %s, %d, %i, %f, %o, %O, %j, %% */
|
|
3321
|
-
const FORMAT_PATTERN = /%%|%[sdifjoO]/g;
|
|
3322
|
-
/**
|
|
3323
|
-
* Applies printf-style format specifiers to args, following the WHATWG Console Standard
|
|
3324
|
-
* and Node.js `util.format` conventions. Remaining args are appended space-separated.
|
|
3325
|
-
*/
|
|
3326
|
-
function formatArgs(args) {
|
|
3327
|
-
if (args.length === 0) return "";
|
|
3328
|
-
if (typeof args[0] !== "string" || !FORMAT_PATTERN.test(args[0])) return args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
3329
|
-
const template = args[0];
|
|
3330
|
-
let argIndex = 1;
|
|
3331
|
-
const result = template.replace(FORMAT_PATTERN, (token) => {
|
|
3332
|
-
if (token === "%%") return "%";
|
|
3333
|
-
if (argIndex >= args.length) return token;
|
|
3334
|
-
const val = args[argIndex++];
|
|
3335
|
-
switch (token) {
|
|
3336
|
-
case "%s": return String(val);
|
|
3337
|
-
case "%d":
|
|
3338
|
-
case "%i": return String(Math.trunc(Number(val)));
|
|
3339
|
-
case "%f": return String(Number(val));
|
|
3340
|
-
case "%j": try {
|
|
3341
|
-
return JSON.stringify(val);
|
|
3342
|
-
} catch {
|
|
3343
|
-
return "[Circular]";
|
|
3344
|
-
}
|
|
3345
|
-
case "%o":
|
|
3346
|
-
case "%O": return typeof val === "string" ? val : JSON.stringify(val);
|
|
3347
|
-
default: return token;
|
|
3348
|
-
}
|
|
3349
|
-
});
|
|
3350
|
-
const remaining = args.slice(argIndex);
|
|
3351
|
-
if (remaining.length === 0) return result;
|
|
3352
|
-
return `${result} ${remaining.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}`;
|
|
3353
|
-
}
|
|
3354
|
-
function resolveCliLevel(rawArgs) {
|
|
3355
|
-
if ("trace" in rawArgs) {
|
|
3356
|
-
const t = rawArgs.trace;
|
|
3357
|
-
delete rawArgs.trace;
|
|
3358
|
-
if (t !== false) return "trace";
|
|
3359
|
-
}
|
|
3360
|
-
if ("verbose" in rawArgs) {
|
|
3361
|
-
const v = rawArgs.verbose;
|
|
3362
|
-
delete rawArgs.verbose;
|
|
3363
|
-
if (v !== false) return "debug";
|
|
3364
|
-
}
|
|
3365
|
-
if ("debug" in rawArgs) {
|
|
3366
|
-
const d = rawArgs.debug;
|
|
3367
|
-
delete rawArgs.debug;
|
|
3368
|
-
if (d !== false) return "debug";
|
|
3369
|
-
}
|
|
3370
|
-
if ("silent" in rawArgs) {
|
|
3371
|
-
const s = rawArgs.silent;
|
|
3372
|
-
delete rawArgs.silent;
|
|
3373
|
-
if (s !== false) return "silent";
|
|
3374
|
-
}
|
|
3375
|
-
if ("quiet" in rawArgs) {
|
|
3376
|
-
const q = rawArgs.quiet;
|
|
3377
|
-
delete rawArgs.quiet;
|
|
3378
|
-
if (q !== false) return "silent";
|
|
3379
|
-
}
|
|
3380
|
-
if ("log-level" in rawArgs) {
|
|
3381
|
-
const val = rawArgs["log-level"];
|
|
3382
|
-
delete rawArgs["log-level"];
|
|
3383
|
-
if (typeof val === "string" && VALID_LEVELS.has(val)) return val;
|
|
3384
|
-
}
|
|
3385
|
-
}
|
|
3386
|
-
function createLogger(runtime, level, config, tracing) {
|
|
3387
|
-
const threshold = LEVEL_ORDER[level];
|
|
3388
|
-
function format(lvl, prefix, args) {
|
|
3389
|
-
const parts = [];
|
|
3390
|
-
if (config.timestamps) parts.push((/* @__PURE__ */ new Date()).toISOString());
|
|
3391
|
-
parts.push(`[${LEVEL_LABELS[lvl]}]`);
|
|
3392
|
-
if (prefix) parts.push(prefix);
|
|
3393
|
-
parts.push(formatArgs(args));
|
|
3394
|
-
return parts.join(" ");
|
|
3395
|
-
}
|
|
3396
|
-
function makeLogger(prefix) {
|
|
3397
|
-
const emit = (lvl, args) => {
|
|
3398
|
-
if (LEVEL_ORDER[lvl] < threshold) return;
|
|
3399
|
-
const message = format(lvl, prefix, args);
|
|
3400
|
-
tracing?.rootSpan.addEvent("log", {
|
|
3401
|
-
"log.level": lvl,
|
|
3402
|
-
"log.message": args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")
|
|
3403
|
-
});
|
|
3404
|
-
if (lvl === "error" || lvl === "warn") runtime.error(message);
|
|
3405
|
-
else runtime.output(message);
|
|
3406
|
-
};
|
|
3407
|
-
return {
|
|
3408
|
-
trace: (...args) => emit("trace", args),
|
|
3409
|
-
debug: (...args) => emit("debug", args),
|
|
3410
|
-
info: (...args) => emit("info", args),
|
|
3411
|
-
warn: (...args) => emit("warn", args),
|
|
3412
|
-
error: (...args) => emit("error", args),
|
|
3413
|
-
level,
|
|
3414
|
-
child: (label) => makeLogger(prefix ? `${prefix} [${label}]` : `[${label}]`)
|
|
3415
|
-
};
|
|
3416
|
-
}
|
|
3417
|
-
return makeLogger(config.prefix);
|
|
3418
|
-
}
|
|
3419
|
-
function loggerInterceptor(rawConfig) {
|
|
3420
|
-
return defineInterceptor({
|
|
3421
|
-
id: "padrone:logger",
|
|
3422
|
-
name: "padrone:logger"
|
|
3423
|
-
}).requires().factory(() => {
|
|
3424
|
-
let cliLevel;
|
|
3425
|
-
return {
|
|
3426
|
-
parse(_ctx, next) {
|
|
3427
|
-
return thenMaybe(next(), (res) => {
|
|
3428
|
-
cliLevel = resolveCliLevel(res.rawArgs);
|
|
3429
|
-
return res;
|
|
3430
|
-
});
|
|
3431
|
-
},
|
|
3432
|
-
execute(ctx, next) {
|
|
3433
|
-
const ctxCfg = ctx.context?.loggerConfig;
|
|
3434
|
-
const resolved = {
|
|
3435
|
-
level: cliLevel ?? rawConfig?.level ?? ctxCfg?.level ?? "info",
|
|
3436
|
-
prefix: rawConfig?.prefix ?? "",
|
|
3437
|
-
timestamps: rawConfig?.timestamps ?? ctxCfg?.timestamps ?? false
|
|
3438
|
-
};
|
|
3439
|
-
return next({ context: { logger: createLogger(ctx.runtime, resolved.level, resolved, ctx.context?.tracing) } });
|
|
3440
|
-
}
|
|
3441
|
-
};
|
|
3442
|
-
});
|
|
3443
|
-
}
|
|
3444
|
-
/**
|
|
3445
|
-
* Extension that injects a structured logger into the command context.
|
|
3446
|
-
*
|
|
3447
|
-
* The logger respects a configurable log level threshold, supports prefixed
|
|
3448
|
-
* child loggers, and routes output through the runtime's `output`/`error`
|
|
3449
|
-
* functions so it works in any environment (terminal, test, web).
|
|
3450
|
-
*
|
|
3451
|
-
* Supports CLI flags for runtime level overrides:
|
|
3452
|
-
* - `--trace` → sets level to `trace`
|
|
3453
|
-
* - `--verbose` or `--debug` → sets level to `debug`
|
|
3454
|
-
* - `--silent` or `--quiet` → sets level to `silent`
|
|
3455
|
-
* - `--log-level=<level>` → sets an explicit level (`trace`, `debug`, `info`, `warn`, `error`, `silent`)
|
|
3456
|
-
*
|
|
3457
|
-
* CLI flags take precedence over the programmatic config.
|
|
3458
|
-
*
|
|
3459
|
-
* Provides `{ logger: PadroneLogger }` on the command context.
|
|
3460
|
-
* Access it in action handlers as `ctx.context.logger`.
|
|
3461
|
-
*
|
|
3462
|
-
* Usage:
|
|
3463
|
-
* ```ts
|
|
3464
|
-
* createPadrone('my-cli')
|
|
3465
|
-
* .extend(padroneLogger({ level: 'info' }))
|
|
3466
|
-
* .command('sync', (c) =>
|
|
3467
|
-
* c.action((_args, ctx) => {
|
|
3468
|
-
* ctx.context.logger.info('starting sync');
|
|
3469
|
-
* const db = ctx.context.logger.child('db');
|
|
3470
|
-
* db.debug('connecting...');
|
|
3471
|
-
* })
|
|
3472
|
-
* )
|
|
3473
|
-
* ```
|
|
3474
|
-
*
|
|
3475
|
-
* Then run:
|
|
3476
|
-
* ```sh
|
|
3477
|
-
* my-cli sync --verbose # debug level
|
|
3478
|
-
* my-cli sync --quiet # silent
|
|
3479
|
-
* my-cli sync --log-level=warn
|
|
3480
|
-
* ```
|
|
3481
|
-
*/
|
|
3482
|
-
function padroneLogger(config) {
|
|
3483
|
-
return ((builder) => builder.intercept(loggerInterceptor(config)));
|
|
3484
|
-
}
|
|
3485
|
-
//#endregion
|
|
3486
|
-
//#region src/extension/man.ts
|
|
3487
|
-
/**
|
|
3488
|
-
* Extension that adds the `man` command for man page generation.
|
|
3489
|
-
*
|
|
3490
|
-
* Usage:
|
|
3491
|
-
* ```ts
|
|
3492
|
-
* createPadrone('my-cli').extend(padroneMan())
|
|
3493
|
-
* ```
|
|
3494
|
-
*/
|
|
3495
|
-
function padroneMan() {
|
|
3496
|
-
return ((builder) => builder.command("man", (c) => c.configure({
|
|
3497
|
-
description: "Generate man pages",
|
|
3498
|
-
hidden: true
|
|
3499
|
-
}).arguments(passthroughSchema({
|
|
3500
|
-
setup: "boolean",
|
|
3501
|
-
remove: "boolean"
|
|
3502
|
-
})).async().action(async (args, ctx) => {
|
|
3503
|
-
const rootCommand = getRootCommand(ctx.command);
|
|
3504
|
-
resolveAllCommands(rootCommand);
|
|
3505
|
-
const { setupManPages, removeManPages, generateDocs } = await import("./docs/index.mjs");
|
|
3506
|
-
if (args.setup) {
|
|
3507
|
-
const setupResult = await setupManPages(rootCommand);
|
|
3508
|
-
return `${setupResult.updated ? "Updated" : "Installed"} ${setupResult.written.length} man page(s) in ${setupResult.dir}`;
|
|
3509
|
-
}
|
|
3510
|
-
if (args.remove) {
|
|
3511
|
-
const removeResult = await removeManPages(rootCommand);
|
|
3512
|
-
return removeResult.removed.length > 0 ? `Removed ${removeResult.removed.length} man page(s) from ${removeResult.dir}` : "No man pages found to remove.";
|
|
3513
|
-
}
|
|
3514
|
-
return generateDocs(rootCommand, { format: "man" }).pages[0]?.content ?? "";
|
|
3515
|
-
})));
|
|
3516
|
-
}
|
|
3517
|
-
//#endregion
|
|
3518
|
-
//#region src/extension/mcp.ts
|
|
3519
|
-
/**
|
|
3520
|
-
* Extension that adds the `mcp` command for starting a Model Context Protocol server.
|
|
3521
|
-
*
|
|
3522
|
-
* Usage:
|
|
3523
|
-
* ```ts
|
|
3524
|
-
* createPadrone('my-cli').extend(padroneMcp())
|
|
3525
|
-
* ```
|
|
3526
|
-
*/
|
|
3527
|
-
function padroneMcp(defaults) {
|
|
3528
|
-
return ((builder) => builder.command("mcp", (c) => c.configure({
|
|
3529
|
-
description: "Start a Model Context Protocol server",
|
|
3530
|
-
hidden: true
|
|
3531
|
-
}).arguments(passthroughSchema({
|
|
3532
|
-
transport: "string",
|
|
3533
|
-
port: "string",
|
|
3534
|
-
host: "string",
|
|
3535
|
-
"base-path": "string"
|
|
3536
|
-
}), { positional: ["transport"] }).async().action(async (args, ctx) => {
|
|
3537
|
-
const rootCommand = getRootCommand(ctx.command);
|
|
3538
|
-
resolveAllCommands(rootCommand);
|
|
3539
|
-
const { startMcpServer } = await import("./mcp-wCoVFTXz.mjs");
|
|
3540
|
-
const transport = args.transport === "stdio" || args.transport === "http" ? args.transport : void 0;
|
|
3541
|
-
const port = args.port ? parseInt(args.port, 10) : void 0;
|
|
3542
|
-
const prefs = {
|
|
3543
|
-
...defaults,
|
|
3544
|
-
transport: transport ?? defaults?.transport,
|
|
3545
|
-
port: port && !Number.isNaN(port) ? port : defaults?.port,
|
|
3546
|
-
host: args.host ?? defaults?.host,
|
|
3547
|
-
basePath: args["base-path"] ?? defaults?.basePath
|
|
3548
|
-
};
|
|
3549
|
-
await startMcpServer(ctx.program, rootCommand, ctx.program.eval, prefs);
|
|
3550
|
-
})));
|
|
3551
|
-
}
|
|
3552
|
-
//#endregion
|
|
3553
|
-
//#region src/extension/progress-renderer.ts
|
|
3554
|
-
const spinnerPresets = {
|
|
3555
|
-
dots: [
|
|
3556
|
-
"⠋",
|
|
3557
|
-
"⠙",
|
|
3558
|
-
"⠹",
|
|
3559
|
-
"⠸",
|
|
3560
|
-
"⠼",
|
|
3561
|
-
"⠴",
|
|
3562
|
-
"⠦",
|
|
3563
|
-
"⠧",
|
|
3564
|
-
"⠇",
|
|
3565
|
-
"⠏"
|
|
3566
|
-
],
|
|
3567
|
-
line: [
|
|
3568
|
-
"-",
|
|
3569
|
-
"\\",
|
|
3570
|
-
"|",
|
|
3571
|
-
"/"
|
|
3572
|
-
],
|
|
3573
|
-
arc: [
|
|
3574
|
-
"◜",
|
|
3575
|
-
"◠",
|
|
3576
|
-
"◝",
|
|
3577
|
-
"◞",
|
|
3578
|
-
"◡",
|
|
3579
|
-
"◟"
|
|
3580
|
-
],
|
|
3581
|
-
bounce: [
|
|
3582
|
-
"⠁",
|
|
3583
|
-
"⠂",
|
|
3584
|
-
"⠄",
|
|
3585
|
-
"⡀",
|
|
3586
|
-
"⢀",
|
|
3587
|
-
"⠠",
|
|
3588
|
-
"⠐",
|
|
3589
|
-
"⠈"
|
|
3590
|
-
]
|
|
3591
|
-
};
|
|
3592
|
-
function resolveSpinnerConfig(config) {
|
|
3593
|
-
if (config === false) return {
|
|
3594
|
-
frames: [],
|
|
3595
|
-
interval: 80,
|
|
3596
|
-
show: "never"
|
|
3597
|
-
};
|
|
3598
|
-
if (config === true) return {
|
|
3599
|
-
frames: spinnerPresets.dots,
|
|
3600
|
-
interval: 80,
|
|
3601
|
-
show: "always"
|
|
3602
|
-
};
|
|
3603
|
-
if (typeof config === "string") return {
|
|
3604
|
-
frames: spinnerPresets[config],
|
|
3605
|
-
interval: 80,
|
|
3606
|
-
show: "auto"
|
|
3607
|
-
};
|
|
3608
|
-
if (typeof config === "object") return {
|
|
3609
|
-
frames: config.frames ?? spinnerPresets.dots,
|
|
3610
|
-
interval: config.interval ?? 80,
|
|
3611
|
-
show: config.show ?? "auto"
|
|
3612
|
-
};
|
|
3613
|
-
return {
|
|
3614
|
-
frames: spinnerPresets.dots,
|
|
3615
|
-
interval: 80,
|
|
3616
|
-
show: "auto"
|
|
3617
|
-
};
|
|
3618
|
-
}
|
|
3619
|
-
const defaultBarConfig = {
|
|
3620
|
-
width: 20,
|
|
3621
|
-
filled: "█",
|
|
3622
|
-
empty: "░",
|
|
3623
|
-
animation: "bounce",
|
|
3624
|
-
show: "auto"
|
|
3625
|
-
};
|
|
3626
|
-
function resolveBarConfig(bar) {
|
|
3627
|
-
if (bar === false) return void 0;
|
|
3628
|
-
if (!bar) return { ...defaultBarConfig };
|
|
3629
|
-
if (bar === true) return {
|
|
3630
|
-
...defaultBarConfig,
|
|
3631
|
-
show: "always"
|
|
3632
|
-
};
|
|
3633
|
-
return {
|
|
3634
|
-
width: bar.width ?? 20,
|
|
3635
|
-
filled: bar.filled ?? "█",
|
|
3636
|
-
empty: bar.empty ?? "░",
|
|
3637
|
-
animation: bar.animation ?? "bounce",
|
|
3638
|
-
show: bar.show ?? "always"
|
|
3639
|
-
};
|
|
3640
|
-
}
|
|
3641
|
-
const SEGMENT_RATIO = .25;
|
|
3642
|
-
const pulseGradient = [
|
|
3643
|
-
"░",
|
|
3644
|
-
"▒",
|
|
3645
|
-
"▓",
|
|
3646
|
-
"█",
|
|
3647
|
-
"▓",
|
|
3648
|
-
"▒",
|
|
3649
|
-
"░"
|
|
3650
|
-
];
|
|
3651
|
-
function formatIndeterminate(cfg, frame) {
|
|
3652
|
-
const { width, filled, empty, animation } = cfg;
|
|
3653
|
-
const pad = "".padStart(4);
|
|
3654
|
-
if (animation === "pulse") return `${pad} ${pulseGradient[frame % pulseGradient.length].repeat(width)}`;
|
|
3655
|
-
const seg = Math.max(2, Math.round(width * SEGMENT_RATIO));
|
|
3656
|
-
const travel = width - seg;
|
|
3657
|
-
if (animation === "slide") {
|
|
3658
|
-
const offset = frame % (travel + 1);
|
|
3659
|
-
return `${pad} ${empty.repeat(offset)}${filled.repeat(seg)}${empty.repeat(travel - offset)}`;
|
|
3660
|
-
}
|
|
3661
|
-
const cycle = travel * 2;
|
|
3662
|
-
const pos = frame % cycle;
|
|
3663
|
-
const offset = pos <= travel ? pos : cycle - pos;
|
|
3664
|
-
return `${pad} ${empty.repeat(offset)}${filled.repeat(seg)}${empty.repeat(width - offset - seg)}`;
|
|
3665
|
-
}
|
|
3666
|
-
function formatBar(progress, cfg, frame) {
|
|
3667
|
-
if (progress === void 0) return formatIndeterminate(cfg, frame);
|
|
3668
|
-
const { width, filled, empty } = cfg;
|
|
3669
|
-
const clamped = Math.max(0, Math.min(1, progress));
|
|
3670
|
-
const filledCount = Math.round(clamped * width);
|
|
3671
|
-
return `${`${Math.round(clamped * 100)}%`.padStart(4)} ${filled.repeat(filledCount)}${empty.repeat(width - filledCount)}`;
|
|
3672
|
-
}
|
|
3673
|
-
function parseUpdate(value) {
|
|
3674
|
-
if (typeof value === "string") return { message: value };
|
|
3675
|
-
if (typeof value === "number") return { progress: value };
|
|
3676
|
-
return value;
|
|
3677
|
-
}
|
|
3678
|
-
function formatDuration$1(ms) {
|
|
3679
|
-
const totalSeconds = Math.floor(ms / 1e3);
|
|
3680
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
3681
|
-
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
3682
|
-
const seconds = totalSeconds % 60;
|
|
3683
|
-
if (hours > 0) return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
3684
|
-
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
3685
|
-
}
|
|
3686
|
-
function estimateEta(samples) {
|
|
3687
|
-
if (samples.length < 2) return void 0;
|
|
3688
|
-
const first = samples[0];
|
|
3689
|
-
const last = samples[samples.length - 1];
|
|
3690
|
-
const progressDelta = last.progress - first.progress;
|
|
3691
|
-
if (progressDelta <= 0) return void 0;
|
|
3692
|
-
const rate = progressDelta / (last.time - first.time);
|
|
3693
|
-
const remaining = 1 - last.progress;
|
|
3694
|
-
if (remaining <= 0) return 0;
|
|
3695
|
-
return remaining / rate;
|
|
3696
|
-
}
|
|
3697
|
-
/**
|
|
3698
|
-
* Creates a terminal progress indicator (spinner, bar, or both).
|
|
3699
|
-
* Returns a no-op indicator in non-TTY/CI environments.
|
|
3700
|
-
*/
|
|
3701
|
-
function createTerminalProgress(message, options) {
|
|
3702
|
-
const spinnerCfg = resolveSpinnerConfig(options?.spinner);
|
|
3703
|
-
const successIcon = options?.successIndicator ?? "✔";
|
|
3704
|
-
const errorIcon = options?.errorIndicator ?? "✖";
|
|
3705
|
-
const barCfg = resolveBarConfig(options?.bar);
|
|
3706
|
-
const formatFinal = (icon, msg) => icon ? `${icon} ${msg}\n` : `${msg}\n`;
|
|
3707
|
-
if (typeof process === "undefined" || !process.stderr?.isTTY) return {
|
|
3708
|
-
update() {},
|
|
3709
|
-
eta: {
|
|
3710
|
-
start() {},
|
|
3711
|
-
stop() {},
|
|
3712
|
-
reset() {}
|
|
3713
|
-
},
|
|
3714
|
-
succeed(msg, opts) {
|
|
3715
|
-
if (msg === null) return;
|
|
3716
|
-
const icon = opts?.indicator ?? successIcon;
|
|
3717
|
-
if (msg || message) process?.stderr?.write?.(formatFinal(icon, msg || message));
|
|
3718
|
-
},
|
|
3719
|
-
fail(msg, opts) {
|
|
3720
|
-
if (msg === null) return;
|
|
3721
|
-
const icon = opts?.indicator ?? errorIcon;
|
|
3722
|
-
if (msg || message) process?.stderr?.write?.(formatFinal(icon, msg || message));
|
|
3723
|
-
},
|
|
3724
|
-
stop() {},
|
|
3725
|
-
pause() {},
|
|
3726
|
-
resume() {}
|
|
3727
|
-
};
|
|
3728
|
-
const ansiPattern = /\x1b\[[0-9;]*m/g;
|
|
3729
|
-
if (spinnerCfg.show === "never" && (!barCfg || barCfg.show === "never") && !message) return {
|
|
3730
|
-
update() {},
|
|
3731
|
-
eta: {
|
|
3732
|
-
start() {},
|
|
3733
|
-
stop() {},
|
|
3734
|
-
reset() {}
|
|
3735
|
-
},
|
|
3736
|
-
succeed() {},
|
|
3737
|
-
fail() {},
|
|
3738
|
-
stop() {},
|
|
3739
|
-
pause() {},
|
|
3740
|
-
resume() {}
|
|
3741
|
-
};
|
|
3742
|
-
const showTime = options?.time ?? false;
|
|
3743
|
-
let etaEnabled = options?.eta ?? false;
|
|
3744
|
-
let spinnerFrame = 0;
|
|
3745
|
-
let barFrame = 0;
|
|
3746
|
-
let text = message;
|
|
3747
|
-
let progress;
|
|
3748
|
-
let indeterminate = false;
|
|
3749
|
-
let stopped = false;
|
|
3750
|
-
let paused = false;
|
|
3751
|
-
let timeEnabled = showTime;
|
|
3752
|
-
let startTime = showTime ? Date.now() : 0;
|
|
3753
|
-
const etaSamples = [];
|
|
3754
|
-
let etaMs;
|
|
3755
|
-
let etaCalculatedAt = 0;
|
|
3756
|
-
const writeStderr = process.stderr.write.bind(process.stderr);
|
|
3757
|
-
const writeStdout = process.stdout.write.bind(process.stdout);
|
|
3758
|
-
let prevLineCount = 0;
|
|
3759
|
-
const clearLines = () => {
|
|
3760
|
-
if (prevLineCount > 1) for (let i = 1; i < prevLineCount; i++) writeStderr("\x1B[1A\x1B[2K");
|
|
3761
|
-
writeStderr("\x1B[2K\r");
|
|
3762
|
-
prevLineCount = 0;
|
|
3763
|
-
};
|
|
3764
|
-
/** Count how many terminal rows `str` occupies, accounting for line wrapping. */
|
|
3765
|
-
const lineCount = (str) => {
|
|
3766
|
-
const cols = process.stderr.columns || 80;
|
|
3767
|
-
const visible = str.replace(ansiPattern, "");
|
|
3768
|
-
return Math.max(1, Math.ceil(visible.length / cols));
|
|
3769
|
-
};
|
|
3770
|
-
const render = () => {
|
|
3771
|
-
if (paused || stopped) return;
|
|
3772
|
-
const barVisible = barCfg && (barCfg.show === "always" || barCfg.show === "auto" && (progress !== void 0 || indeterminate));
|
|
3773
|
-
const spinnerVisible = spinnerCfg.show === "always" || spinnerCfg.show === "auto" && !barVisible;
|
|
3774
|
-
let line = "";
|
|
3775
|
-
if (barVisible) line += formatBar(progress, barCfg, barFrame);
|
|
3776
|
-
const hasEta = etaEnabled && progress !== void 0 && progress < 1 && etaMs !== void 0;
|
|
3777
|
-
if (timeEnabled || hasEta) {
|
|
3778
|
-
const parts = [];
|
|
3779
|
-
if (timeEnabled) parts.push(`⏱ ${formatDuration$1(Date.now() - startTime)}`);
|
|
3780
|
-
if (hasEta) {
|
|
3781
|
-
const elapsed = Date.now() - etaCalculatedAt;
|
|
3782
|
-
parts.push(`ETA ${formatDuration$1(Math.max(0, etaMs - elapsed))}`);
|
|
3783
|
-
}
|
|
3784
|
-
if (line) line += " ";
|
|
3785
|
-
line += parts.join(" | ");
|
|
3786
|
-
}
|
|
3787
|
-
if (spinnerVisible) {
|
|
3788
|
-
if (line) line += " ";
|
|
3789
|
-
line += frames[spinnerFrame] ?? "";
|
|
3790
|
-
}
|
|
3791
|
-
if (text) {
|
|
3792
|
-
if (line) line += " ";
|
|
3793
|
-
line += text;
|
|
3794
|
-
}
|
|
3795
|
-
if (line) {
|
|
3796
|
-
clearLines();
|
|
3797
|
-
writeStderr(line);
|
|
3798
|
-
prevLineCount = lineCount(line);
|
|
3799
|
-
} else clearLines();
|
|
3800
|
-
};
|
|
3801
|
-
const { frames } = spinnerCfg;
|
|
3802
|
-
const needsAnimation = spinnerCfg.show !== "never" || barCfg && barCfg.show !== "never";
|
|
3803
|
-
const tickInterval = barCfg && barCfg.show !== "never" ? Math.min(80, spinnerCfg.interval) : spinnerCfg.interval;
|
|
3804
|
-
const timer = needsAnimation ? setInterval(() => {
|
|
3805
|
-
spinnerFrame = (spinnerFrame + 1) % (frames.length || 1);
|
|
3806
|
-
barFrame++;
|
|
3807
|
-
render();
|
|
3808
|
-
}, tickInterval) : void 0;
|
|
3809
|
-
if (timer && typeof timer === "object" && "unref" in timer) timer.unref();
|
|
3810
|
-
render();
|
|
3811
|
-
const clear = () => {
|
|
3812
|
-
if (stopped) return;
|
|
3813
|
-
stopped = true;
|
|
3814
|
-
paused = false;
|
|
3815
|
-
if (timer) clearInterval(timer);
|
|
3816
|
-
clearLines();
|
|
3817
|
-
};
|
|
3818
|
-
return {
|
|
3819
|
-
update(value) {
|
|
3820
|
-
if (stopped) return;
|
|
3821
|
-
const parsed = parseUpdate(value);
|
|
3822
|
-
if (parsed.message !== void 0) text = parsed.message;
|
|
3823
|
-
if (parsed.progress !== void 0) {
|
|
3824
|
-
progress = parsed.progress;
|
|
3825
|
-
if (etaEnabled) {
|
|
3826
|
-
const now = Date.now();
|
|
3827
|
-
etaSamples.push({
|
|
3828
|
-
time: now,
|
|
3829
|
-
progress: parsed.progress
|
|
3830
|
-
});
|
|
3831
|
-
const estimated = estimateEta(etaSamples);
|
|
3832
|
-
if (estimated !== void 0) {
|
|
3833
|
-
etaMs = estimated;
|
|
3834
|
-
etaCalculatedAt = now;
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
if (parsed.indeterminate !== void 0) {
|
|
3839
|
-
indeterminate = parsed.indeterminate;
|
|
3840
|
-
if (indeterminate) progress = void 0;
|
|
3841
|
-
}
|
|
3842
|
-
if (parsed.time !== void 0) {
|
|
3843
|
-
if (parsed.time && !timeEnabled) {
|
|
3844
|
-
timeEnabled = true;
|
|
3845
|
-
startTime = Date.now();
|
|
3846
|
-
} else if (!parsed.time) timeEnabled = false;
|
|
3847
|
-
}
|
|
3848
|
-
render();
|
|
3849
|
-
},
|
|
3850
|
-
eta: {
|
|
3851
|
-
start() {
|
|
3852
|
-
if (stopped) return;
|
|
3853
|
-
etaEnabled = true;
|
|
3854
|
-
render();
|
|
3855
|
-
},
|
|
3856
|
-
stop() {
|
|
3857
|
-
if (stopped) return;
|
|
3858
|
-
etaEnabled = false;
|
|
3859
|
-
etaMs = void 0;
|
|
3860
|
-
render();
|
|
3861
|
-
},
|
|
3862
|
-
reset() {
|
|
3863
|
-
if (stopped) return;
|
|
3864
|
-
etaSamples.length = 0;
|
|
3865
|
-
etaMs = void 0;
|
|
3866
|
-
etaCalculatedAt = 0;
|
|
3867
|
-
render();
|
|
3868
|
-
}
|
|
3869
|
-
},
|
|
3870
|
-
succeed(msg, opts) {
|
|
3871
|
-
clear();
|
|
3872
|
-
if (msg === null) return;
|
|
3873
|
-
const finalMsg = msg ?? text;
|
|
3874
|
-
const icon = opts?.indicator ?? successIcon;
|
|
3875
|
-
if (finalMsg) writeStderr(formatFinal(icon, finalMsg));
|
|
3876
|
-
},
|
|
3877
|
-
fail(msg, opts) {
|
|
3878
|
-
clear();
|
|
3879
|
-
if (msg === null) return;
|
|
3880
|
-
const finalMsg = msg ?? text;
|
|
3881
|
-
const icon = opts?.indicator ?? errorIcon;
|
|
3882
|
-
if (finalMsg) writeStderr(formatFinal(icon, finalMsg));
|
|
3883
|
-
},
|
|
3884
|
-
stop() {
|
|
3885
|
-
clear();
|
|
3886
|
-
},
|
|
3887
|
-
pause() {
|
|
3888
|
-
if (stopped || paused) return;
|
|
3889
|
-
paused = true;
|
|
3890
|
-
clearLines();
|
|
3891
|
-
writeStdout("\x1B[2K\r");
|
|
3892
|
-
},
|
|
3893
|
-
resume() {
|
|
3894
|
-
if (stopped || !paused) return;
|
|
3895
|
-
paused = false;
|
|
3896
|
-
render();
|
|
3897
|
-
}
|
|
3898
|
-
};
|
|
3899
|
-
}
|
|
3900
|
-
//#endregion
|
|
3901
|
-
//#region src/extension/progress.ts
|
|
3902
|
-
const noopIndicator = {
|
|
3903
|
-
update() {},
|
|
3904
|
-
eta: {
|
|
3905
|
-
start() {},
|
|
3906
|
-
stop() {},
|
|
3907
|
-
reset() {}
|
|
3908
|
-
},
|
|
3909
|
-
succeed() {},
|
|
3910
|
-
fail() {},
|
|
3911
|
-
stop() {},
|
|
3912
|
-
pause() {},
|
|
3913
|
-
resume() {}
|
|
3914
|
-
};
|
|
3915
|
-
function resolveMessage(field, value, fallback) {
|
|
3916
|
-
const raw = typeof field === "function" ? field(value) : field;
|
|
3917
|
-
if (raw === void 0) return { message: fallback };
|
|
3918
|
-
if (raw === null || typeof raw === "string") return { message: raw };
|
|
3919
|
-
if (typeof raw === "object" && raw !== null) {
|
|
3920
|
-
const obj = raw;
|
|
3921
|
-
return {
|
|
3922
|
-
message: obj.message,
|
|
3923
|
-
indicator: obj.indicator
|
|
3924
|
-
};
|
|
3925
|
-
}
|
|
3926
|
-
return { message: fallback };
|
|
3927
|
-
}
|
|
3928
|
-
function cleanup(indicator, successConfig, errorConfig, error, result, isError) {
|
|
3929
|
-
if (isError) {
|
|
3930
|
-
const { message: errorMsg, indicator: errorIcon } = resolveMessage(errorConfig, error, error instanceof Error ? error.message : String(error));
|
|
3931
|
-
indicator.fail(errorMsg, errorIcon !== void 0 ? { indicator: errorIcon } : void 0);
|
|
3932
|
-
} else {
|
|
3933
|
-
const { message: successMsg, indicator: successIcon } = resolveMessage(successConfig, result);
|
|
3934
|
-
indicator.succeed(successMsg, successIcon !== void 0 ? { indicator: successIcon } : void 0);
|
|
3935
|
-
}
|
|
3936
|
-
}
|
|
3937
|
-
function resolveMessages(raw) {
|
|
3938
|
-
if (!raw || typeof raw === "string") return {
|
|
3939
|
-
progress: raw || "Working...",
|
|
3940
|
-
validation: "",
|
|
3941
|
-
success: void 0,
|
|
3942
|
-
error: void 0
|
|
3943
|
-
};
|
|
3944
|
-
return {
|
|
3945
|
-
progress: raw.progress ?? "Working...",
|
|
3946
|
-
validation: raw.validation ?? "",
|
|
3947
|
-
success: raw.success,
|
|
3948
|
-
error: raw.error
|
|
3949
|
-
};
|
|
3950
|
-
}
|
|
3951
|
-
function mergeMessages(cmd, ctx, cmdRaw) {
|
|
3952
|
-
if (typeof cmdRaw === "string") return cmd;
|
|
3953
|
-
const obj = typeof cmdRaw === "object" ? cmdRaw : void 0;
|
|
3954
|
-
return {
|
|
3955
|
-
progress: obj?.progress !== void 0 ? cmd.progress : ctx.progress ?? cmd.progress,
|
|
3956
|
-
validation: obj?.validation !== void 0 ? cmd.validation : ctx.validation || cmd.validation,
|
|
3957
|
-
success: obj?.success !== void 0 ? cmd.success : ctx.success ?? cmd.success,
|
|
3958
|
-
error: obj?.error !== void 0 ? cmd.error : ctx.error ?? cmd.error
|
|
3959
|
-
};
|
|
3960
|
-
}
|
|
3961
|
-
function progressInterceptor(config) {
|
|
3962
|
-
const isObj = typeof config === "object";
|
|
3963
|
-
const rawMessage = typeof config === "string" ? config : isObj ? config.message : void 0;
|
|
3964
|
-
const rawSpinner = isObj ? config.spinner : void 0;
|
|
3965
|
-
const rawBar = isObj ? config.bar : void 0;
|
|
3966
|
-
const rawRenderer = isObj ? config.renderer : void 0;
|
|
3967
|
-
const rawTime = isObj ? config.time : void 0;
|
|
3968
|
-
const rawEta = isObj ? config.eta : void 0;
|
|
3969
|
-
const rawSilent = isObj ? config.silent : void 0;
|
|
3970
|
-
return defineInterceptor({
|
|
3971
|
-
id: "padrone:progress",
|
|
3972
|
-
name: "padrone:progress"
|
|
3973
|
-
}).requires().factory(() => {
|
|
3974
|
-
let indicator;
|
|
3975
|
-
let restoreOutput;
|
|
3976
|
-
let resolvedRenderer;
|
|
3977
|
-
let resolvedOptions;
|
|
3978
|
-
let resolvedSilent = false;
|
|
3979
|
-
let msgs;
|
|
3980
|
-
function resolve(ctx) {
|
|
3981
|
-
if (resolvedRenderer) return;
|
|
3982
|
-
const ctxCfg = ctx.context?.progressConfig;
|
|
3983
|
-
resolvedSilent = rawSilent ?? ctxCfg?.silent ?? false;
|
|
3984
|
-
const spinner = rawSpinner ?? ctxCfg?.spinner;
|
|
3985
|
-
const bar = rawBar ?? ctxCfg?.bar;
|
|
3986
|
-
const time = rawTime ?? ctxCfg?.time;
|
|
3987
|
-
const eta = rawEta ?? ctxCfg?.eta;
|
|
3988
|
-
resolvedRenderer = rawRenderer ?? ctxCfg?.renderer ?? createTerminalProgress;
|
|
3989
|
-
resolvedOptions = spinner !== void 0 || bar !== void 0 || time !== void 0 || eta !== void 0 ? {
|
|
3990
|
-
spinner,
|
|
3991
|
-
bar,
|
|
3992
|
-
time,
|
|
3993
|
-
eta
|
|
3994
|
-
} : void 0;
|
|
3995
|
-
msgs = mergeMessages(resolveMessages(rawMessage), resolveMessages(ctxCfg?.message), rawMessage);
|
|
3996
|
-
}
|
|
3997
|
-
const teardown = () => {
|
|
3998
|
-
restoreOutput?.();
|
|
3999
|
-
indicator = void 0;
|
|
4000
|
-
restoreOutput = void 0;
|
|
4001
|
-
};
|
|
4002
|
-
return {
|
|
4003
|
-
validate(ctx, next) {
|
|
4004
|
-
resolve(ctx);
|
|
4005
|
-
if (resolvedSilent) return next();
|
|
4006
|
-
indicator = resolvedRenderer(msgs.validation || msgs.progress, resolvedOptions);
|
|
4007
|
-
const originalOutput = ctx.runtime.output;
|
|
4008
|
-
const originalError = ctx.runtime.error;
|
|
4009
|
-
ctx.runtime.output = (...args) => {
|
|
4010
|
-
indicator.pause();
|
|
4011
|
-
originalOutput(...args);
|
|
4012
|
-
indicator.resume();
|
|
4013
|
-
};
|
|
4014
|
-
ctx.runtime.error = (text) => {
|
|
4015
|
-
indicator.pause();
|
|
4016
|
-
originalError(text);
|
|
4017
|
-
indicator.resume();
|
|
4018
|
-
};
|
|
4019
|
-
restoreOutput = () => {
|
|
4020
|
-
ctx.runtime.output = originalOutput;
|
|
4021
|
-
ctx.runtime.error = originalError;
|
|
4022
|
-
};
|
|
4023
|
-
const onValidationFailure = (error) => {
|
|
4024
|
-
if (indicator) {
|
|
4025
|
-
cleanup(indicator, msgs.success, msgs.error, error, void 0, true);
|
|
4026
|
-
teardown();
|
|
4027
|
-
}
|
|
4028
|
-
};
|
|
4029
|
-
const checkResult = (result) => {
|
|
4030
|
-
if (result.argsResult?.issues) onValidationFailure(/* @__PURE__ */ new Error("Validation failed"));
|
|
4031
|
-
return result;
|
|
4032
|
-
};
|
|
4033
|
-
let result;
|
|
4034
|
-
try {
|
|
4035
|
-
result = next();
|
|
4036
|
-
} catch (err) {
|
|
4037
|
-
onValidationFailure(err);
|
|
4038
|
-
throw err;
|
|
4039
|
-
}
|
|
4040
|
-
if (result instanceof Promise) return result.then(checkResult, (err) => {
|
|
4041
|
-
onValidationFailure(err);
|
|
4042
|
-
throw err;
|
|
4043
|
-
});
|
|
4044
|
-
return checkResult(result);
|
|
4045
|
-
},
|
|
4046
|
-
execute(_ctx, next) {
|
|
4047
|
-
if (resolvedSilent) return next({ context: { progress: noopIndicator } });
|
|
4048
|
-
if (indicator && msgs.validation) indicator.update(msgs.progress);
|
|
4049
|
-
const effectiveIndicator = indicator ?? noopIndicator;
|
|
4050
|
-
const onSuccess = (value) => {
|
|
4051
|
-
cleanup(effectiveIndicator, msgs.success, msgs.error, void 0, value, false);
|
|
4052
|
-
teardown();
|
|
4053
|
-
};
|
|
4054
|
-
const onError = (err) => {
|
|
4055
|
-
if (indicator) {
|
|
4056
|
-
cleanup(indicator, msgs.success, msgs.error, err, void 0, true);
|
|
4057
|
-
teardown();
|
|
4058
|
-
}
|
|
4059
|
-
throw err;
|
|
4060
|
-
};
|
|
4061
|
-
let result;
|
|
4062
|
-
try {
|
|
4063
|
-
result = next({ context: { progress: effectiveIndicator } });
|
|
4064
|
-
} catch (err) {
|
|
4065
|
-
onError(err);
|
|
4066
|
-
}
|
|
4067
|
-
if (result instanceof Promise) return result.then((r) => {
|
|
4068
|
-
if (r.result instanceof Promise) return { result: r.result.then((value) => {
|
|
4069
|
-
onSuccess(value);
|
|
4070
|
-
return value;
|
|
4071
|
-
}, (err) => onError(err)) };
|
|
4072
|
-
onSuccess(r.result);
|
|
4073
|
-
return r;
|
|
4074
|
-
}, (err) => onError(err));
|
|
4075
|
-
if (result.result instanceof Promise) return { result: result.result.then((value) => {
|
|
4076
|
-
onSuccess(value);
|
|
4077
|
-
return value;
|
|
4078
|
-
}, (err) => onError(err)) };
|
|
4079
|
-
onSuccess(result.result);
|
|
4080
|
-
return result;
|
|
4081
|
-
},
|
|
4082
|
-
shutdown(ctx) {
|
|
4083
|
-
if (indicator) {
|
|
4084
|
-
cleanup(indicator, msgs.success, msgs.error, ctx.error, ctx.result, !!ctx.error);
|
|
4085
|
-
teardown();
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4088
|
-
};
|
|
4089
|
-
}).provides();
|
|
4090
|
-
}
|
|
4091
|
-
/**
|
|
4092
|
-
* Extension that adds an auto-managed progress indicator to the command pipeline.
|
|
4093
|
-
*
|
|
4094
|
-
* - `string` — a single message used for all states.
|
|
4095
|
-
* - `PadroneProgressConfig` — separate messages for validation, progress, success, and error.
|
|
4096
|
-
*
|
|
4097
|
-
* The indicator is automatically started before validation, updated at each phase transition,
|
|
4098
|
-
* and stopped on success (`.succeed()`) or failure (`.fail()`).
|
|
4099
|
-
*
|
|
4100
|
-
* Provides `{ progress: PadroneProgress }` on the command context.
|
|
4101
|
-
* Access it in action handlers as `ctx.context.progress`.
|
|
4102
|
-
*
|
|
4103
|
-
* Uses the built-in terminal renderer by default. Pass a custom `renderer` for non-terminal
|
|
4104
|
-
* environments (web UIs, testing, etc).
|
|
4105
|
-
*
|
|
4106
|
-
* Usage:
|
|
4107
|
-
* ```ts
|
|
4108
|
-
* createPadrone('my-cli')
|
|
4109
|
-
* .command('sync', (c) =>
|
|
4110
|
-
* c.extend(padroneProgress('Syncing...'))
|
|
4111
|
-
* .action((_args, ctx) => {
|
|
4112
|
-
* ctx.context.progress.update('halfway');
|
|
4113
|
-
* })
|
|
4114
|
-
* )
|
|
4115
|
-
* ```
|
|
4116
|
-
*/
|
|
4117
|
-
function padroneProgress(config) {
|
|
4118
|
-
return ((builder) => builder.intercept(progressInterceptor(config ?? "Working...")));
|
|
4119
|
-
}
|
|
4120
|
-
//#endregion
|
|
4121
|
-
//#region src/extension/serve.ts
|
|
4122
|
-
/**
|
|
4123
|
-
* Extension that adds the `serve` command for starting a REST HTTP server.
|
|
4124
|
-
*
|
|
4125
|
-
* Usage:
|
|
4126
|
-
* ```ts
|
|
4127
|
-
* createPadrone('my-cli').extend(padroneServe())
|
|
4128
|
-
* ```
|
|
4129
|
-
*/
|
|
4130
|
-
function padroneServe(defaults) {
|
|
4131
|
-
return ((builder) => builder.command("serve", (c) => c.configure({
|
|
4132
|
-
description: "Start a REST HTTP server",
|
|
4133
|
-
hidden: true
|
|
4134
|
-
}).arguments(passthroughSchema({
|
|
4135
|
-
port: "string",
|
|
4136
|
-
host: "string",
|
|
4137
|
-
"base-path": "string"
|
|
4138
|
-
})).async().action(async (args, ctx) => {
|
|
4139
|
-
const rootCommand = getRootCommand(ctx.command);
|
|
4140
|
-
resolveAllCommands(rootCommand);
|
|
4141
|
-
const { startServeServer } = await import("./serve-ICZFl3xr.mjs");
|
|
4142
|
-
const port = args.port ? parseInt(args.port, 10) : void 0;
|
|
4143
|
-
const prefs = {
|
|
4144
|
-
...defaults,
|
|
4145
|
-
port: port && !Number.isNaN(port) ? port : defaults?.port,
|
|
4146
|
-
host: args.host ?? defaults?.host,
|
|
4147
|
-
basePath: args["base-path"] ?? defaults?.basePath
|
|
4148
|
-
};
|
|
4149
|
-
await startServeServer(ctx.program, rootCommand, ctx.program.eval, prefs);
|
|
4150
|
-
})));
|
|
4151
|
-
}
|
|
4152
|
-
//#endregion
|
|
4153
|
-
//#region src/extension/timing.ts
|
|
4154
|
-
function formatDuration(ms) {
|
|
4155
|
-
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
4156
|
-
if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
|
|
4157
|
-
return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(2)}s`;
|
|
4158
|
-
}
|
|
4159
|
-
const timingMeta = {
|
|
4160
|
-
id: "padrone:timing",
|
|
4161
|
-
name: "padrone:timing",
|
|
4162
|
-
order: -1002
|
|
4163
|
-
};
|
|
4164
|
-
function createTimingInterceptor(enabledByDefault) {
|
|
4165
|
-
return defineInterceptor(timingMeta, () => {
|
|
4166
|
-
let enabled = enabledByDefault;
|
|
4167
|
-
let startTime = 0;
|
|
4168
|
-
return {
|
|
4169
|
-
parse(_ctx, next) {
|
|
4170
|
-
return thenMaybe(next(), (res) => {
|
|
4171
|
-
if ("timing" in res.rawArgs) {
|
|
4172
|
-
enabled = res.rawArgs.timing !== false;
|
|
4173
|
-
delete res.rawArgs.timing;
|
|
4174
|
-
}
|
|
4175
|
-
if ("time" in res.rawArgs) {
|
|
4176
|
-
enabled = res.rawArgs.time !== false;
|
|
4177
|
-
delete res.rawArgs.time;
|
|
4178
|
-
}
|
|
4179
|
-
return res;
|
|
4180
|
-
});
|
|
4181
|
-
},
|
|
4182
|
-
start(_ctx, next) {
|
|
4183
|
-
startTime = performance.now();
|
|
4184
|
-
return next();
|
|
4185
|
-
},
|
|
4186
|
-
shutdown(ctx, next) {
|
|
4187
|
-
return thenMaybe(next(), (res) => {
|
|
4188
|
-
if (enabled) {
|
|
4189
|
-
const elapsed = performance.now() - startTime;
|
|
4190
|
-
ctx.runtime.error(`\nDone in ${formatDuration(elapsed)}`);
|
|
4191
|
-
}
|
|
4192
|
-
return res;
|
|
4193
|
-
});
|
|
4194
|
-
}
|
|
4195
|
-
};
|
|
4196
|
-
});
|
|
4197
|
-
}
|
|
4198
|
-
/**
|
|
4199
|
-
* Extension that tracks command execution time.
|
|
4200
|
-
*
|
|
4201
|
-
* - `--time` / `--timing` → enables timing output
|
|
4202
|
-
* - `--no-time` / `--no-timing` → disables timing output
|
|
4203
|
-
*
|
|
4204
|
-
* Pass `{ enabled: true }` to enable timing by default (can be disabled via `--no-time`).
|
|
4205
|
-
*
|
|
4206
|
-
* Usage:
|
|
4207
|
-
* ```ts
|
|
4208
|
-
* // Opt-in via flag
|
|
4209
|
-
* createPadrone('my-cli').extend(padroneTiming())
|
|
4210
|
-
*
|
|
4211
|
-
* // Always on, opt-out via --no-time
|
|
4212
|
-
* createPadrone('my-cli').extend(padroneTiming({ enabled: true }))
|
|
4213
|
-
* ```
|
|
4214
|
-
*/
|
|
4215
|
-
function padroneTiming(options) {
|
|
4216
|
-
return ((builder) => builder.intercept(createTimingInterceptor(options?.enabled ?? false)));
|
|
4217
|
-
}
|
|
4218
|
-
//#endregion
|
|
4219
|
-
//#region src/extension/tracing.ts
|
|
4220
|
-
const OTEL_ERROR = 2;
|
|
4221
|
-
function tracingInterceptor(config) {
|
|
4222
|
-
return defineInterceptor({
|
|
4223
|
-
id: "padrone:tracing",
|
|
4224
|
-
name: "padrone:tracing",
|
|
4225
|
-
order: -1
|
|
4226
|
-
}, () => {
|
|
4227
|
-
let rootSpan;
|
|
4228
|
-
let tracer;
|
|
4229
|
-
return {
|
|
4230
|
-
start(ctx, next) {
|
|
4231
|
-
tracer = config.provider.getTracer(config.serviceName ?? ctx.command.name);
|
|
4232
|
-
return next();
|
|
4233
|
-
},
|
|
4234
|
-
execute(ctx, next) {
|
|
4235
|
-
rootSpan = tracer.startSpan(`cli ${ctx.command.name}`);
|
|
4236
|
-
return next({ context: { tracing: {
|
|
4237
|
-
tracer,
|
|
4238
|
-
rootSpan,
|
|
4239
|
-
span(name, fn) {
|
|
4240
|
-
const child = tracer.startSpan(name);
|
|
4241
|
-
try {
|
|
4242
|
-
const result = fn(child);
|
|
4243
|
-
if (result != null && typeof result.then === "function") return result.then((v) => {
|
|
4244
|
-
child.end();
|
|
4245
|
-
return v;
|
|
4246
|
-
}, (err) => {
|
|
4247
|
-
child.recordException(err);
|
|
4248
|
-
child.setStatus({ code: OTEL_ERROR });
|
|
4249
|
-
child.end();
|
|
4250
|
-
throw err;
|
|
4251
|
-
});
|
|
4252
|
-
child.end();
|
|
4253
|
-
return result;
|
|
4254
|
-
} catch (err) {
|
|
4255
|
-
child.recordException(err);
|
|
4256
|
-
child.setStatus({ code: OTEL_ERROR });
|
|
4257
|
-
child.end();
|
|
4258
|
-
throw err;
|
|
4259
|
-
}
|
|
4260
|
-
}
|
|
4261
|
-
} } });
|
|
4262
|
-
},
|
|
4263
|
-
error(ctx, next) {
|
|
4264
|
-
rootSpan?.recordException(ctx.error);
|
|
4265
|
-
rootSpan?.setStatus({ code: OTEL_ERROR });
|
|
4266
|
-
return next();
|
|
4267
|
-
},
|
|
4268
|
-
shutdown(_ctx, next) {
|
|
4269
|
-
return thenMaybe(next(), (res) => {
|
|
4270
|
-
rootSpan?.end();
|
|
4271
|
-
return res;
|
|
4272
|
-
});
|
|
4273
|
-
}
|
|
4274
|
-
};
|
|
4275
|
-
});
|
|
4276
|
-
}
|
|
4277
|
-
/**
|
|
4278
|
-
* Extension that adds OpenTelemetry tracing to command execution.
|
|
4279
|
-
*
|
|
4280
|
-
* Creates a root span for each command invocation and provides a `PadroneTracer`
|
|
4281
|
-
* on the command context for creating child spans in action handlers.
|
|
4282
|
-
*
|
|
4283
|
-
* When used with `padroneLogger()`, the logger automatically emits span events
|
|
4284
|
-
* for each log call — no extra configuration needed. The logger detects the
|
|
4285
|
-
* tracing context and bridges log output to span events.
|
|
4286
|
-
*
|
|
4287
|
-
* Uses minimal OTEL-compatible interfaces — pass any `TracerProvider` that
|
|
4288
|
-
* implements `getTracer()`. Works with `@opentelemetry/api` or compatible
|
|
4289
|
-
* libraries.
|
|
4290
|
-
*
|
|
4291
|
-
* Provides `{ tracing: PadroneTracer }` on the command context.
|
|
4292
|
-
* Access it in action handlers as `ctx.context.tracing`.
|
|
4293
|
-
*
|
|
4294
|
-
* Usage:
|
|
4295
|
-
* ```ts
|
|
4296
|
-
* import { trace } from '@opentelemetry/api';
|
|
4297
|
-
*
|
|
4298
|
-
* createPadrone('my-cli')
|
|
4299
|
-
* .extend(padroneTracing({ provider: trace.getTracerProvider() }))
|
|
4300
|
-
* .extend(padroneLogger())
|
|
4301
|
-
* .command('deploy', (c) =>
|
|
4302
|
-
* c.action((_args, ctx) => {
|
|
4303
|
-
* ctx.context.logger.info('deploying'); // also emits a span event
|
|
4304
|
-
* ctx.context.tracing.span('build', (span) => {
|
|
4305
|
-
* span.setAttribute('target', 'production');
|
|
4306
|
-
* });
|
|
4307
|
-
* })
|
|
4308
|
-
* )
|
|
4309
|
-
* ```
|
|
4310
|
-
*/
|
|
4311
|
-
function padroneTracing(config) {
|
|
4312
|
-
const resolved = {
|
|
4313
|
-
provider: config.provider,
|
|
4314
|
-
serviceName: config.serviceName
|
|
4315
|
-
};
|
|
4316
|
-
return ((builder) => builder.intercept(tracingInterceptor(resolved)));
|
|
4317
|
-
}
|
|
4318
|
-
//#endregion
|
|
4319
|
-
//#region src/extension/update-check.ts
|
|
4320
|
-
function createUpdateCheckInterceptor(config) {
|
|
4321
|
-
return defineInterceptor({
|
|
4322
|
-
id: "padrone:update-check",
|
|
4323
|
-
name: "padrone:update-check",
|
|
4324
|
-
order: 1e3
|
|
4325
|
-
}, () => {
|
|
4326
|
-
let checkPromise;
|
|
4327
|
-
let suppressed = false;
|
|
4328
|
-
return {
|
|
4329
|
-
start(ctx, next) {
|
|
4330
|
-
const rootCommand = ctx.command;
|
|
4331
|
-
const runtime = ctx.runtime;
|
|
4332
|
-
checkPromise = Promise.resolve(getVersion(rootCommand.version)).then((currentVersion) => import("./update-check-CZ2VqjnV.mjs").then(({ createUpdateChecker }) => createUpdateChecker(rootCommand.name, currentVersion, config, runtime)));
|
|
4333
|
-
return next();
|
|
4334
|
-
},
|
|
4335
|
-
parse(_ctx, next) {
|
|
4336
|
-
return thenMaybe(next(), (res) => {
|
|
4337
|
-
if ("update-check" in res.rawArgs) {
|
|
4338
|
-
if (res.rawArgs["update-check"] === false) suppressed = true;
|
|
4339
|
-
delete res.rawArgs["update-check"];
|
|
4340
|
-
}
|
|
4341
|
-
return res;
|
|
4342
|
-
});
|
|
4343
|
-
},
|
|
4344
|
-
shutdown(_ctx, next) {
|
|
4345
|
-
const result = next();
|
|
4346
|
-
if (suppressed || !checkPromise) return result;
|
|
4347
|
-
let resolved = null;
|
|
4348
|
-
checkPromise.then((fn) => {
|
|
4349
|
-
resolved = fn;
|
|
4350
|
-
}, () => {
|
|
4351
|
-
resolved = void 0;
|
|
4352
|
-
});
|
|
4353
|
-
if (resolved !== null) resolved?.();
|
|
4354
|
-
return result;
|
|
4355
|
-
}
|
|
4356
|
-
};
|
|
4357
|
-
});
|
|
4358
|
-
}
|
|
4359
|
-
/**
|
|
4360
|
-
* Extension that adds background update checking:
|
|
4361
|
-
* - Checks for newer versions on npm (or custom registry) in the background
|
|
4362
|
-
* - Shows an update notification after command execution
|
|
4363
|
-
* - Respects `--no-update-check` flag to suppress
|
|
4364
|
-
*
|
|
4365
|
-
* Usage:
|
|
4366
|
-
* ```ts
|
|
4367
|
-
* createPadrone('my-cli')
|
|
4368
|
-
* .extend(padroneUpdateCheck({ packageName: 'my-cli' }))
|
|
4369
|
-
* ```
|
|
4370
|
-
*/
|
|
4371
|
-
function padroneUpdateCheck(config = {}) {
|
|
4372
|
-
return ((builder) => builder.intercept(createUpdateCheckInterceptor(config)));
|
|
4373
|
-
}
|
|
4374
|
-
//#endregion
|
|
4375
|
-
export { ActionError, ConfigError, PadroneError, REPL_SIGINT, RoutingError, SignalError, ValidationError, asyncSchema, asyncStream, buildReplCompleter, colorThemes, createPadrone, createTerminalProgress, defineCommand, defineInterceptor, isReactElement, padroneAutoOutput, padroneColor, padroneCompletion, padroneConfig, padroneEnv, padroneHelp, padroneInk, padroneInteractive, padroneLogger, padroneMan, padroneMcp, padroneProgress, padroneRepl, padroneServe, padroneSignalHandling, padroneStdin, padroneSuggestions, padroneTiming, padroneTracing, padroneUpdateCheck, padroneVersion };
|
|
4376
|
-
|
|
4377
|
-
//# sourceMappingURL=index.mjs.map
|
|
1
|
+
import { asyncStream } from "./util/stream.mjs";
|
|
2
|
+
import { REPL_SIGINT } from "./core/runtime.mjs";
|
|
3
|
+
import { buildReplCompleter } from "./core/commands.mjs";
|
|
4
|
+
import { asyncSchema } from "./core/results.mjs";
|
|
5
|
+
import { defineInterceptor } from "./core/interceptors.mjs";
|
|
6
|
+
import { colorThemes } from "./output/colorizer.mjs";
|
|
7
|
+
import { padroneAutoOutput } from "./extension/auto-output.mjs";
|
|
8
|
+
import { padroneColor } from "./extension/color.mjs";
|
|
9
|
+
import { ActionError, ConfigError, PadroneError, RoutingError, SignalError, ValidationError } from "./core/errors.mjs";
|
|
10
|
+
import { padroneHelp } from "./extension/help.mjs";
|
|
11
|
+
import { padroneInteractive } from "./extension/interactive.mjs";
|
|
12
|
+
import { padroneRepl } from "./extension/repl.mjs";
|
|
13
|
+
import { padroneSignalHandling } from "./extension/signal.mjs";
|
|
14
|
+
import { padroneStdin } from "./extension/stdin.mjs";
|
|
15
|
+
import { padroneSuggestions } from "./extension/suggestions.mjs";
|
|
16
|
+
import { padroneVersion } from "./extension/version.mjs";
|
|
17
|
+
import { createPadrone, defineCommand } from "./core/create.mjs";
|
|
18
|
+
import { padroneConfig } from "./extension/config.mjs";
|
|
19
|
+
import { padroneEnv } from "./extension/env.mjs";
|
|
20
|
+
import { padroneLogger } from "./extension/logger.mjs";
|
|
21
|
+
import { createTerminalProgress } from "./extension/progress-renderer.mjs";
|
|
22
|
+
import { padroneProgress } from "./extension/progress.mjs";
|
|
23
|
+
import { padroneTiming } from "./extension/timing.mjs";
|
|
24
|
+
import { padroneUpdateCheck } from "./extension/update-check.mjs";
|
|
25
|
+
export { ActionError, ConfigError, PadroneError, REPL_SIGINT, RoutingError, SignalError, ValidationError, asyncSchema, asyncStream, buildReplCompleter, colorThemes, createPadrone, createTerminalProgress, defineCommand, defineInterceptor, padroneAutoOutput, padroneColor, padroneConfig, padroneEnv, padroneHelp, padroneInteractive, padroneLogger, padroneProgress, padroneRepl, padroneSignalHandling, padroneStdin, padroneSuggestions, padroneTiming, padroneUpdateCheck, padroneVersion };
|