hacksh 0.11.0 → 0.13.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/README.md +127 -32
- package/dist/index.d.ts +21 -3
- package/dist/index.js +44 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# hacksh
|
|
2
2
|
|
|
3
3
|
Define a type-safe command-line interface with a Zod 4 object schema. The same schema parses and
|
|
4
|
-
validates arguments, infers the value passed to `run`, and generates help.
|
|
4
|
+
validates arguments, infers the value passed to `run`, and generates help. Run external commands
|
|
5
|
+
with the context’s `exec`, and use its `logger` for progress and interactive prompts.
|
|
5
6
|
|
|
6
7
|
The name comes from 拍手 (_hakushu_), the Japanese word for "clap", as a nod to
|
|
7
8
|
[clap](https://github.com/clap-rs/clap), the command-line argument parser for Rust. It is spelled
|
|
@@ -30,21 +31,22 @@ import { z } from "zod";
|
|
|
30
31
|
import { defineCommand, runMain } from "hacksh";
|
|
31
32
|
|
|
32
33
|
const main = defineCommand({
|
|
33
|
-
name: "
|
|
34
|
+
name: "git-summary",
|
|
34
35
|
version: "0.1.0",
|
|
35
|
-
description: "
|
|
36
|
+
description: "Show a commit summary",
|
|
36
37
|
args: z.object({
|
|
37
|
-
_: z.tuple([
|
|
38
|
-
|
|
39
|
-
]),
|
|
40
|
-
repo: z.string().optional().describe("Repository in OWNER/REPO format"),
|
|
41
|
-
verbose: z.boolean().default(false).describe("Show details"),
|
|
38
|
+
_: z.tuple([z.string().default("HEAD").describe("Commit to inspect").meta({ title: "REF" })]),
|
|
39
|
+
repo: z.string().optional().describe("Repository directory"),
|
|
42
40
|
}),
|
|
43
|
-
argOptions: { repo: { alias: "
|
|
44
|
-
async run({ args,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
argOptions: { repo: { alias: "C" } },
|
|
42
|
+
async run({ args, exec }) {
|
|
43
|
+
const [ref] = args._;
|
|
44
|
+
const { stdout } = await exec(
|
|
45
|
+
"git",
|
|
46
|
+
["show", "--no-patch", "--format=%h %s", "--end-of-options", ref],
|
|
47
|
+
{ cwd: args.repo },
|
|
48
|
+
);
|
|
49
|
+
console.log(stdout);
|
|
48
50
|
},
|
|
49
51
|
});
|
|
50
52
|
|
|
@@ -52,11 +54,22 @@ await runMain(main);
|
|
|
52
54
|
```
|
|
53
55
|
|
|
54
56
|
```sh
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
git-summary
|
|
58
|
+
git-summary HEAD~1 -C ./my-repo
|
|
59
|
+
git-summary --help
|
|
60
|
+
git-summary --version
|
|
58
61
|
```
|
|
59
62
|
|
|
63
|
+
Use an `async run` handler and its `exec` for external commands. It captures output, removes one
|
|
64
|
+
final newline, throws on failure, and observes invocation cancellation. Pass arguments as an array;
|
|
65
|
+
there is no command string to quote or split. You do not need a `node:child_process` import or a
|
|
66
|
+
custom process wrapper for these behaviors. See [External commands](#external-commands) for expected
|
|
67
|
+
nonzero exits, stdin, and terminal access.
|
|
68
|
+
|
|
69
|
+
`console.log` above writes the command’s data to stdout. Direct stdout writes are not converted
|
|
70
|
+
to JSON by hacksh’s output mode. Logger events go through the printer; their stream depends on
|
|
71
|
+
the level, so informational logs also share stdout by default.
|
|
72
|
+
|
|
60
73
|
`--version` is a root-only builtin. It prints the root command's `version`. When the root has
|
|
61
74
|
none, `runMain` resolves the executable (`process.argv[1]`) through symlinks and prints the
|
|
62
75
|
`version` of the nearest ancestor `package.json` that declares one, so a published CLI reports its
|
|
@@ -67,8 +80,8 @@ are accepted (`--dryRun` and `--dry-run`). Every boolean flag also accepts a `--
|
|
|
67
80
|
that sets it to `false`, such as `--no-verbose`, which is how a default-on flag is disabled.
|
|
68
81
|
Boolean flags also accept the explicit forms `--verbose=true` and
|
|
69
82
|
`--verbose=false`; other inline values and values on a negated flag (such as
|
|
70
|
-
`--no-verbose=false`) are validation errors. Value options accept `--repo
|
|
71
|
-
`--repo
|
|
83
|
+
`--no-verbose=false`) are validation errors. Value options accept `--repo ./my-repo` and
|
|
84
|
+
`--repo=./my-repo`; values such as `false` remain strings for non-boolean fields.
|
|
72
85
|
|
|
73
86
|
## Positional arguments
|
|
74
87
|
|
|
@@ -98,8 +111,58 @@ copy out a.txt b.txt # { _: ["out", "a.txt", "b.txt"] }
|
|
|
98
111
|
Help and validation errors label each item with its `.meta({ title })`, such as `<DESTINATION>` and
|
|
99
112
|
`[FILES...]`. Untitled items are labeled by position (`ARG1`, `ARG2`, ...) and an untitled rest is
|
|
100
113
|
labeled `ARGS`. Use `z.array` instead of a tuple to accept only variadic values. Without a rest,
|
|
101
|
-
additional positional arguments are
|
|
102
|
-
|
|
114
|
+
additional positional arguments are rejected by default, as are unknown options.
|
|
115
|
+
|
|
116
|
+
### Unknown arguments and forwarding
|
|
117
|
+
|
|
118
|
+
A command rejects unknown options and excess positional arguments before its `run` handler or
|
|
119
|
+
schema transforms execute. For ordinary commands, declare the accepted arguments in `args`;
|
|
120
|
+
there is no need to inspect `extraArgs` manually. Errors are `HackshValidationError` instances,
|
|
121
|
+
with each rejected token in `issues[].arg`.
|
|
122
|
+
|
|
123
|
+
Everything after `--` is always collected unchanged in `passthrough`, independently of this policy.
|
|
124
|
+
Use that explicit boundary when forwarding arguments to another executable:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const forward = defineCommand({
|
|
128
|
+
name: "git-forward",
|
|
129
|
+
async run({ exec, passthrough }) {
|
|
130
|
+
const { stdout } = await exec("git", passthrough);
|
|
131
|
+
console.log(stdout);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
// git-forward -- status --short
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
For a wrapper that also accepts undeclared arguments before `--`, set `allowUnknownArgs: true`:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const wrapper = defineCommand({
|
|
141
|
+
allowUnknownArgs: true,
|
|
142
|
+
async run({ extraArgs, passthrough, exec }) {
|
|
143
|
+
await exec("some-tool", [...extraArgs, ...passthrough]);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`extraArgs` preserves the order of unknown option tokens and excess positional values. Unknown
|
|
149
|
+
options do not consume a following value: if the schema has an unfilled positional slot, that
|
|
150
|
+
value fills it. Known options are still parsed and validated. Use `--` when a child's arguments
|
|
151
|
+
must not be interpreted by this command. A handler that does not use `passthrough` ignores those
|
|
152
|
+
arguments; commands that require rejecting them can check `passthrough.length` explicitly.
|
|
153
|
+
|
|
154
|
+
`allowUnknownArgs` defaults to `false`, inherits down the selected subcommand route, and can be
|
|
155
|
+
overridden by each child. A child's setting does not change validation of its parent's arguments.
|
|
156
|
+
Definitions are not mutated, so reusing a child under another root does not retain inherited settings.
|
|
157
|
+
Standalone `parseArgs` uses the supplied command's setting and does not resolve a parent route.
|
|
158
|
+
|
|
159
|
+
Once routing succeeds, help and root version output take precedence over argument validation,
|
|
160
|
+
including unknown arguments. Routing errors such as an unknown subcommand still fail. A child's
|
|
161
|
+
undeclared `--version` is an unknown argument, since the version builtin belongs to the root.
|
|
162
|
+
|
|
163
|
+
**Migrating from 0.11.0:** unknown input was previously accepted by default. Add
|
|
164
|
+
`allowUnknownArgs: true` to commands that intentionally use `extraArgs`, or to their shared parent.
|
|
165
|
+
No migration is needed for `--` forwarding.
|
|
103
166
|
|
|
104
167
|
## Argument options
|
|
105
168
|
|
|
@@ -325,21 +388,27 @@ positive `columns` value, the renderer conservatively counts explicit lines only
|
|
|
325
388
|
Every `run` context has an `exec` function that starts an executable with an argument array. It
|
|
326
389
|
never uses a shell, so spaces, quotes, `$`, `;`, `|`, and `*` in arguments reach the child
|
|
327
390
|
unchanged. By default it resolves with the captured output and rejects with `HackshExecError`
|
|
328
|
-
when the command fails. Pass `reject: false` to receive failures as results instead.
|
|
391
|
+
when the command fails. Pass `reject: false` to receive failures as results instead. Prefer this
|
|
392
|
+
context API to `spawnSync` or a custom subprocess wrapper inside a command handler so execution
|
|
393
|
+
uses the invocation’s environment, cancellation, and printer.
|
|
329
394
|
|
|
330
395
|
```ts
|
|
331
|
-
const
|
|
332
|
-
name: "
|
|
396
|
+
const check = defineCommand({
|
|
397
|
+
name: "check-head",
|
|
333
398
|
async run({ exec, logger }) {
|
|
334
|
-
const
|
|
335
|
-
const upstream = await exec("git", ["rev-parse", "--abbrev-ref", "@{upstream}"], {
|
|
399
|
+
const head = await exec("git", ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], {
|
|
336
400
|
reject: false,
|
|
337
401
|
});
|
|
338
|
-
if (
|
|
339
|
-
|
|
340
|
-
|
|
402
|
+
if (head.status === "failed") {
|
|
403
|
+
// Only this specific exit means that the revision was not found.
|
|
404
|
+
if (head.reason === "exit-code" && head.exitCode === 1) {
|
|
405
|
+
logger.warn("The repository has no HEAD commit");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
// Missing executables, cancellation, and other failures must still fail the command.
|
|
409
|
+
throw new Error("Could not inspect HEAD", { cause: head });
|
|
341
410
|
}
|
|
342
|
-
|
|
411
|
+
logger.info("HEAD is {commit}", { commit: head.stdout });
|
|
343
412
|
},
|
|
344
413
|
});
|
|
345
414
|
```
|
|
@@ -352,10 +421,24 @@ UTF-8 `stdout` and `stderr` with one final newline removed. A failure's `reason`
|
|
|
352
421
|
gives `ExecSuccess`, while `false` or a `boolean` variable gives `ExecResult`, which must be
|
|
353
422
|
narrowed before reading `exitCode`.
|
|
354
423
|
|
|
424
|
+
### Working with captured output
|
|
425
|
+
|
|
426
|
+
Use `stdout` directly for a single value such as a commit ID or repository path. Do not call
|
|
427
|
+
`.trim()` on paths: surrounding whitespace can be part of the name. `exec` already removes one
|
|
428
|
+
final LF or CRLF; it preserves other whitespace and NUL separators.
|
|
429
|
+
|
|
430
|
+
Interpret structured output according to the child command’s format. If it offers JSON, request
|
|
431
|
+
that format and parse it with `JSON.parse` (and a Zod schema when validation is needed). For Git’s
|
|
432
|
+
`--porcelain -z` output, keep the NUL delimiters and parse the records in a named, application-specific
|
|
433
|
+
helper. Splitting that format on newlines would break paths containing newlines. hacksh does not
|
|
434
|
+
parse Git records or infer whether stdout is text, JSON, or a list.
|
|
435
|
+
|
|
436
|
+
### Execution options
|
|
437
|
+
|
|
355
438
|
Options are `cwd`, `env`, `input`, `reject`, `timeout` (milliseconds), `signal` (an
|
|
356
|
-
`AbortSignal`), `maxBuffer` (bytes per stream, default 100,000,000),
|
|
357
|
-
(milliseconds, default 5,000)
|
|
358
|
-
aborts. `input` is written to stdin and closed; otherwise stdin is empty.
|
|
439
|
+
`AbortSignal`), `maxBuffer` (bytes per stream, default 100,000,000), `forceKillAfterDelay`
|
|
440
|
+
(milliseconds, default 5,000), and `dryRun`. Every call also stops with `aborted` when the
|
|
441
|
+
invocation's `signal` aborts. `input` is written to stdin and closed; otherwise stdin is empty.
|
|
359
442
|
Output beyond `maxBuffer` is truncated to that many bytes. On timeout, abort, or output limit,
|
|
360
443
|
hacksh sends `SIGTERM` to the child it started and `SIGKILL` after `forceKillAfterDelay`; it does
|
|
361
444
|
not stop grandchildren, and it does not wait for them to close inherited pipes. Invalid arguments
|
|
@@ -375,6 +458,18 @@ JSON event has `phase: "start"`, `command` (`file`, `args`, and `cwd` when set),
|
|
|
375
458
|
in the event, the result's `command`, the `HackshExecError` message, and a `spawn` failure's
|
|
376
459
|
`error`. The child still receives the real values, and its captured output is returned unchanged.
|
|
377
460
|
|
|
461
|
+
`dryRun: true` does not start the child and does not write `input`. The result is
|
|
462
|
+
`{ status: "skipped", command }` with the redacted command and no `stdout`, `stderr`, or `exitCode`.
|
|
463
|
+
The `exec` event uses `phase: "skip"` and the same command, stdio, and `interactive` flag.
|
|
464
|
+
Pretty output marks that line with `#` instead of `$`. Omit `dryRun`, or pass `false`, to keep
|
|
465
|
+
today's success or failure result. A boolean `dryRun` returns the union of a skip and the executed
|
|
466
|
+
result, so narrow `status` before reading output. `reject` does not apply to a skip. An invocation
|
|
467
|
+
signal that is already aborted still fails as `aborted` before any skip event, and `reject` applies
|
|
468
|
+
to that failure. Option checks still run, including `input` together with `stdin: "inherit"`.
|
|
469
|
+
JSON mode, a non-interactive environment, and a busy terminal still show the plan and return
|
|
470
|
+
a skip. `display: false` hides the skip event. `dryRun` is not read from CLI flags or
|
|
471
|
+
`context.args`.
|
|
472
|
+
|
|
378
473
|
```ts
|
|
379
474
|
await exec("git", ["worktree", "add", path, branch], { cwd: repository });
|
|
380
475
|
await exec("gh", ["auth", "login", "--with-token"], { input: token, display: false });
|
package/dist/index.d.ts
CHANGED
|
@@ -242,7 +242,7 @@ interface PromptPrintEvent {
|
|
|
242
242
|
}
|
|
243
243
|
interface ExecPrintEvent {
|
|
244
244
|
type: "exec";
|
|
245
|
-
phase: "start";
|
|
245
|
+
phase: "start" | "skip";
|
|
246
246
|
command: Omit<ExecCommand, "cwd"> & {
|
|
247
247
|
cwd?: string;
|
|
248
248
|
};
|
|
@@ -308,6 +308,7 @@ interface ExecOptions {
|
|
|
308
308
|
interactive?: boolean;
|
|
309
309
|
display?: boolean;
|
|
310
310
|
redact?: readonly string[];
|
|
311
|
+
dryRun?: boolean;
|
|
311
312
|
}
|
|
312
313
|
interface ExecCommand {
|
|
313
314
|
file: string;
|
|
@@ -342,7 +343,22 @@ type ExecFailure = {
|
|
|
342
343
|
});
|
|
343
344
|
type ExecFailureReason = ExecFailure["reason"];
|
|
344
345
|
type ExecResult = ExecSuccess | ExecFailure;
|
|
345
|
-
|
|
346
|
+
interface ExecSkipped {
|
|
347
|
+
status: "skipped";
|
|
348
|
+
command: ExecCommand;
|
|
349
|
+
}
|
|
350
|
+
type ExecOutcome<O extends ExecOptions> = "reject" extends keyof O ? false extends O["reject"] ? ExecResult : ExecSuccess : ExecSuccess;
|
|
351
|
+
type SpecifiedDryRun<O> = "dryRun" extends keyof O ? O["dryRun"] : false;
|
|
352
|
+
type ReturnsFailure<O extends ExecOptions> = "reject" extends keyof O ? false extends O["reject"] ? true : false : false;
|
|
353
|
+
type AbortedFailure = {
|
|
354
|
+
status: "failed";
|
|
355
|
+
command: ExecCommand;
|
|
356
|
+
stdout: string;
|
|
357
|
+
stderr: string;
|
|
358
|
+
reason: "aborted";
|
|
359
|
+
};
|
|
360
|
+
type DefiniteSkip<O extends ExecOptions> = ReturnsFailure<O> extends true ? ExecSkipped | AbortedFailure : ExecSkipped;
|
|
361
|
+
type ExecReturn<O extends ExecOptions> = true extends SpecifiedDryRun<O> ? false extends SpecifiedDryRun<O> ? ExecSkipped | ExecOutcome<O> : undefined extends SpecifiedDryRun<O> ? ExecSkipped | ExecOutcome<O> : DefiniteSkip<O> : ExecOutcome<O>;
|
|
346
362
|
type RejectInheritedInput<O> = O extends {
|
|
347
363
|
input: string;
|
|
348
364
|
stdin: "inherit";
|
|
@@ -379,6 +395,8 @@ interface CommandDef<S extends ArgsSchema = EmptyArgs> {
|
|
|
379
395
|
version?: string;
|
|
380
396
|
description?: string;
|
|
381
397
|
hidden?: boolean;
|
|
398
|
+
/** Keep unknown options and excess positionals in extraArgs. Inherits from the parent; defaults to false. */
|
|
399
|
+
allowUnknownArgs?: boolean;
|
|
382
400
|
args?: S;
|
|
383
401
|
argOptions?: ArgOptionsMap<S>;
|
|
384
402
|
subCommands?: Record<string, CommandDef<ArgsSchema>>;
|
|
@@ -422,5 +440,5 @@ interface RunMainOptions extends OutputOptions {
|
|
|
422
440
|
declare function runMain<S extends ArgsSchema>(command: CommandDef<S>, options?: RunMainOptions): Promise<void>;
|
|
423
441
|
declare function createMain<S extends ArgsSchema>(command: CommandDef<S>, options?: RunMainOptions): (argv?: string[]) => Promise<void>;
|
|
424
442
|
//#endregion
|
|
425
|
-
export { type ArgOptions, type ArgOptionsMap, type CommandContext, type CommandDef, type ConfirmOptions, type ConfirmPromptState, type ErrorPrintEvent, type Exec, type ExecCommand, type ExecFailure, type ExecFailureReason, type ExecOptions, type ExecPrintEvent, type ExecResult, type ExecReturn, type ExecSuccess, type HackshErrorKind, type HackshErrorSummary, HackshExecError, HackshExitError, type HackshIssue, HackshPromptError, type HackshPromptErrorReason, HackshUsageError, HackshValidationError, type JsonPrinterOptions, type LogKind, type LogLevel, type LogPrintEvent, type LogProperties, type LogRecord, type Logger, type OutputFormat, type OutputMode, type OutputOptions, type OutputPreference, type OutputWriter, type PrettyPrinterOptions, type PrintEvent, type PrintStream, type Printer, type PromptChoiceState, type PromptErrorDetails, type PromptKind, type PromptPrintEvent, type PromptState, type PromptStatus, type Prompter, type ResolveOutputModeOptions, type RunCommandOptions, type RunMainOptions, type SelectOption, type SelectOptions, type SelectPromptState, type ShowUsageCallback, type ShowUsageOptions, type StreamPrinterOptions, type TerminalInput, type TerminalOutput, type TextOptions, type TextPromptState, type UsageArgumentSummary, type UsageCommandSummary, type UsageOptionSummary, type UsageOutput, type UsagePrintEvent, type UsageSummary, type UsageWriter, type ValidationIssueSummary, type VersionPrintEvent, consolePrinter, createJsonPrinter, createMain, createPrettyPrinter, createStreamPrinter, defineCommand, parseArgs, renderUsage, resolveOutputMode, runCommand, runMain, showUsage, summarizeUsage };
|
|
443
|
+
export { type ArgOptions, type ArgOptionsMap, type CommandContext, type CommandDef, type ConfirmOptions, type ConfirmPromptState, type ErrorPrintEvent, type Exec, type ExecCommand, type ExecFailure, type ExecFailureReason, type ExecOptions, type ExecPrintEvent, type ExecResult, type ExecReturn, type ExecSkipped, type ExecSuccess, type HackshErrorKind, type HackshErrorSummary, HackshExecError, HackshExitError, type HackshIssue, HackshPromptError, type HackshPromptErrorReason, HackshUsageError, HackshValidationError, type JsonPrinterOptions, type LogKind, type LogLevel, type LogPrintEvent, type LogProperties, type LogRecord, type Logger, type OutputFormat, type OutputMode, type OutputOptions, type OutputPreference, type OutputWriter, type PrettyPrinterOptions, type PrintEvent, type PrintStream, type Printer, type PromptChoiceState, type PromptErrorDetails, type PromptKind, type PromptPrintEvent, type PromptState, type PromptStatus, type Prompter, type ResolveOutputModeOptions, type RunCommandOptions, type RunMainOptions, type SelectOption, type SelectOptions, type SelectPromptState, type ShowUsageCallback, type ShowUsageOptions, type StreamPrinterOptions, type TerminalInput, type TerminalOutput, type TextOptions, type TextPromptState, type UsageArgumentSummary, type UsageCommandSummary, type UsageOptionSummary, type UsageOutput, type UsagePrintEvent, type UsageSummary, type UsageWriter, type ValidationIssueSummary, type VersionPrintEvent, consolePrinter, createJsonPrinter, createMain, createPrettyPrinter, createStreamPrinter, defineCommand, parseArgs, renderUsage, resolveOutputMode, runCommand, runMain, showUsage, summarizeUsage };
|
|
426
444
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -108,6 +108,7 @@ function schemaShape(schema) {
|
|
|
108
108
|
//#endregion
|
|
109
109
|
//#region src/command.ts
|
|
110
110
|
function defineCommand(def) {
|
|
111
|
+
if (def.allowUnknownArgs !== void 0 && typeof def.allowUnknownArgs !== "boolean") throw new TypeError("allowUnknownArgs must be a boolean");
|
|
111
112
|
const shape = def.args ? schemaShape(def.args) : {};
|
|
112
113
|
const positionals = readPositionals(shape);
|
|
113
114
|
if (positionals) {
|
|
@@ -611,6 +612,10 @@ async function parseCommandArgs(command, rawArgs, tokenized = tokenizeCommandArg
|
|
|
611
612
|
else if (token.kind === "unknown-option") extraArgs.push(token.value);
|
|
612
613
|
else if (positionals && (positionals.rest || positionalValues.length < positionals.items.length)) positionalValues.push(token.value);
|
|
613
614
|
else extraArgs.push(token.value);
|
|
615
|
+
if (!command.allowUnknownArgs && extraArgs.length > 0) throw new HackshValidationError(command, extraArgs.map((arg) => ({
|
|
616
|
+
arg,
|
|
617
|
+
message: "unexpected argument"
|
|
618
|
+
})));
|
|
614
619
|
const envSources = /* @__PURE__ */ new Map();
|
|
615
620
|
if (env && command.args) {
|
|
616
621
|
const shape = schemaShape(command.args);
|
|
@@ -689,7 +694,8 @@ const OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
689
694
|
"stderr",
|
|
690
695
|
"interactive",
|
|
691
696
|
"display",
|
|
692
|
-
"redact"
|
|
697
|
+
"redact",
|
|
698
|
+
"dryRun"
|
|
693
699
|
]);
|
|
694
700
|
const MAX_DELAY = 2147483647;
|
|
695
701
|
const REFUSAL_MESSAGE = {
|
|
@@ -809,7 +815,8 @@ function parsePlan(file, args, options, context) {
|
|
|
809
815
|
stderr,
|
|
810
816
|
interactive: raw.interactive === void 0 ? false : readBoolean(raw.interactive, "interactive"),
|
|
811
817
|
display: raw.display === void 0 ? true : readBoolean(raw.display, "display"),
|
|
812
|
-
redact
|
|
818
|
+
redact,
|
|
819
|
+
dryRun: raw.dryRun === void 0 ? false : readBoolean(raw.dryRun, "dryRun")
|
|
813
820
|
};
|
|
814
821
|
}
|
|
815
822
|
function stripFinalNewline(text) {
|
|
@@ -877,6 +884,23 @@ function toResult(command, outcome, stdout, stderr) {
|
|
|
877
884
|
function isSpawnErrno(error) {
|
|
878
885
|
return error instanceof Error && "syscall" in error;
|
|
879
886
|
}
|
|
887
|
+
function execEvent(plan, phase) {
|
|
888
|
+
const { file, args, cwd } = plan.reported;
|
|
889
|
+
return {
|
|
890
|
+
type: "exec",
|
|
891
|
+
phase,
|
|
892
|
+
command: cwd === void 0 ? {
|
|
893
|
+
file,
|
|
894
|
+
args
|
|
895
|
+
} : {
|
|
896
|
+
file,
|
|
897
|
+
args,
|
|
898
|
+
cwd
|
|
899
|
+
},
|
|
900
|
+
stdio: commandStdio(plan),
|
|
901
|
+
interactive: plan.interactive
|
|
902
|
+
};
|
|
903
|
+
}
|
|
880
904
|
function commandStdio(plan) {
|
|
881
905
|
return {
|
|
882
906
|
stdin: plan.stdin === "inherit" ? "inherit" : plan.input === void 0 ? "ignore" : "input",
|
|
@@ -1015,29 +1039,20 @@ function blockedReason(plan, context, stdio) {
|
|
|
1015
1039
|
async function execute(file, args, options, context) {
|
|
1016
1040
|
const plan = parsePlan(file, args, options, context);
|
|
1017
1041
|
if (plan.signal.aborted) return complete(plan, emptyFailure(plan, "aborted"));
|
|
1042
|
+
if (plan.dryRun) {
|
|
1043
|
+
if (plan.display) await context.print(execEvent(plan, "skip"));
|
|
1044
|
+
return {
|
|
1045
|
+
status: "skipped",
|
|
1046
|
+
command: plan.reported
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1018
1049
|
const stdio = commandStdio(plan);
|
|
1019
1050
|
const blocked = blockedReason(plan, context, stdio);
|
|
1020
1051
|
if (blocked) return complete(plan, emptyFailure(plan, blocked));
|
|
1021
1052
|
const holdTerminal = usesTerminal(plan, stdio);
|
|
1022
1053
|
if (holdTerminal && !context.terminal.acquire("exec")) return complete(plan, emptyFailure(plan, "overlapping"));
|
|
1023
1054
|
try {
|
|
1024
|
-
if (plan.display)
|
|
1025
|
-
const { file, args, cwd } = plan.reported;
|
|
1026
|
-
await context.print({
|
|
1027
|
-
type: "exec",
|
|
1028
|
-
phase: "start",
|
|
1029
|
-
command: cwd === void 0 ? {
|
|
1030
|
-
file,
|
|
1031
|
-
args
|
|
1032
|
-
} : {
|
|
1033
|
-
file,
|
|
1034
|
-
args,
|
|
1035
|
-
cwd
|
|
1036
|
-
},
|
|
1037
|
-
stdio,
|
|
1038
|
-
interactive: plan.interactive
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1055
|
+
if (plan.display) await context.print(execEvent(plan, "start"));
|
|
1041
1056
|
return complete(plan, await runPlan(plan));
|
|
1042
1057
|
} finally {
|
|
1043
1058
|
if (holdTerminal) context.terminal.release();
|
|
@@ -1187,7 +1202,7 @@ function formatTextEvent(event) {
|
|
|
1187
1202
|
case "error": return event.error.kind === "usage" || event.error.kind === "validation" ? event.error.message : `Error: ${event.error.message}`;
|
|
1188
1203
|
case "log": return event.record.message;
|
|
1189
1204
|
case "prompt": return event.prompt.message;
|
|
1190
|
-
case "exec": return `$ ${formatExecCommand(event.command)}`;
|
|
1205
|
+
case "exec": return `${event.phase === "skip" ? "#" : "$"} ${formatExecCommand(event.command)}`;
|
|
1191
1206
|
}
|
|
1192
1207
|
}
|
|
1193
1208
|
/** Undecorated console compatibility adapter; automatic output uses the selected format printer. */
|
|
@@ -1278,7 +1293,8 @@ const unicodeSymbols = {
|
|
|
1278
1293
|
version: "●",
|
|
1279
1294
|
errorEvent: "■",
|
|
1280
1295
|
prompt: "?",
|
|
1281
|
-
exec: "$"
|
|
1296
|
+
exec: "$",
|
|
1297
|
+
execSkip: "#"
|
|
1282
1298
|
}
|
|
1283
1299
|
};
|
|
1284
1300
|
const asciiSymbols = {
|
|
@@ -1296,7 +1312,8 @@ const asciiSymbols = {
|
|
|
1296
1312
|
version: "i",
|
|
1297
1313
|
errorEvent: "x",
|
|
1298
1314
|
prompt: "?",
|
|
1299
|
-
exec: "$"
|
|
1315
|
+
exec: "$",
|
|
1316
|
+
execSkip: "#"
|
|
1300
1317
|
}
|
|
1301
1318
|
};
|
|
1302
1319
|
const markStyles = {
|
|
@@ -1316,7 +1333,8 @@ const markStyles = {
|
|
|
1316
1333
|
version: "blue",
|
|
1317
1334
|
errorEvent: "red",
|
|
1318
1335
|
prompt: "cyan",
|
|
1319
|
-
exec: "gray"
|
|
1336
|
+
exec: "gray",
|
|
1337
|
+
execSkip: "gray"
|
|
1320
1338
|
};
|
|
1321
1339
|
function safelyInspect(value) {
|
|
1322
1340
|
if (typeof value === "string") return value;
|
|
@@ -1363,7 +1381,7 @@ function eventMark(event) {
|
|
|
1363
1381
|
if (event.type === "version") return "version";
|
|
1364
1382
|
if (event.type === "error") return "errorEvent";
|
|
1365
1383
|
if (event.type === "prompt") return event.prompt.status === "submitted" ? "success" : "prompt";
|
|
1366
|
-
if (event.type === "exec") return "exec";
|
|
1384
|
+
if (event.type === "exec") return event.phase === "skip" ? "execSkip" : "exec";
|
|
1367
1385
|
return event.record.kind === "message" ? event.record.level : event.record.kind;
|
|
1368
1386
|
}
|
|
1369
1387
|
function eventLines(event) {
|
|
@@ -2067,7 +2085,8 @@ function resolveCommand(root, rawArgs) {
|
|
|
2067
2085
|
const parentPath = commandEnvPath(command);
|
|
2068
2086
|
command = {
|
|
2069
2087
|
...child,
|
|
2070
|
-
name: child.name ?? key
|
|
2088
|
+
name: child.name ?? key,
|
|
2089
|
+
allowUnknownArgs: child.allowUnknownArgs ?? command.allowUnknownArgs
|
|
2071
2090
|
};
|
|
2072
2091
|
registerCommandPath(command, parentPath === void 0 ? void 0 : [...parentPath, key]);
|
|
2073
2092
|
remaining = remaining.slice(index + 1);
|