hacksh 0.0.0-stage → 0.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/README.md +261 -2
- package/dist/index.d.ts +81 -0
- package/dist/index.js +528 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -3
package/README.md
CHANGED
|
@@ -1,3 +1,262 @@
|
|
|
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.
|
|
5
|
+
|
|
6
|
+
The name comes from 拍手 (_hakushu_), the Japanese word for "clap", as a nod to
|
|
7
|
+
[clap](https://github.com/clap-rs/clap), the command-line argument parser for Rust. It is spelled
|
|
8
|
+
with _hack_ and _sh_, as in shell.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pnpm add hacksh zod
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
hacksh is an ESM package and supports Node.js 22.18 or newer. Zod is a peer dependency, so an
|
|
17
|
+
application controls the Zod 4 version it uses. The public schema types and schema inspection use
|
|
18
|
+
the shared `zod/v4/core` API and accept schemas created with either `zod` or `zod/mini`.
|
|
19
|
+
|
|
20
|
+
## Define and run a command
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
import { defineCommand, runMain } from "hacksh";
|
|
25
|
+
|
|
26
|
+
const main = defineCommand({
|
|
27
|
+
name: "gh-pr-commits",
|
|
28
|
+
version: "0.1.0",
|
|
29
|
+
description: "List commits in a GitHub pull request",
|
|
30
|
+
args: z.object({
|
|
31
|
+
_: z.tuple([
|
|
32
|
+
z.coerce.number().int().optional().describe("Pull request number").meta({ title: "PR" }),
|
|
33
|
+
]),
|
|
34
|
+
repo: z.string().optional().describe("Repository in OWNER/REPO format"),
|
|
35
|
+
verbose: z.boolean().default(false).describe("Show details"),
|
|
36
|
+
}),
|
|
37
|
+
argOptions: { repo: { alias: "R" }, verbose: { alias: ["v", "V"] } },
|
|
38
|
+
async run({ args, rawArgs, extraArgs, passthrough }) {
|
|
39
|
+
// args is { _: [number?]; repo?: string; verbose: boolean }
|
|
40
|
+
console.log({ args, rawArgs, extraArgs, passthrough });
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await runMain(main);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
gh-pr-commits 42 -R owner/repo --verbose
|
|
49
|
+
gh-pr-commits --help
|
|
50
|
+
gh-pr-commits --version
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Boolean fields are flags and other fields take a value. Both a schema key and its kebab-case form
|
|
54
|
+
are accepted (`--dryRun` and `--dry-run`). A default-on flag can be disabled with `--no-`, such as
|
|
55
|
+
`--no-verbose`. Boolean flags also accept the explicit forms `--verbose=true` and
|
|
56
|
+
`--verbose=false`; other inline values and values on a negated flag (such as
|
|
57
|
+
`--no-verbose=false`) are validation errors. Value options accept `--repo owner/repo` and
|
|
58
|
+
`--repo=owner/repo`; values such as `false` remain strings for non-boolean fields.
|
|
59
|
+
|
|
60
|
+
## Positional arguments
|
|
61
|
+
|
|
62
|
+
Declare positional arguments as a `z.tuple` in the `_` field. Tuple items are assigned in order,
|
|
63
|
+
and the tuple rest collects all remaining values. Optional or defaulted items must come after
|
|
64
|
+
required ones, and items cannot be booleans or arrays. The `_` field is never a named option.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const copy = defineCommand({
|
|
68
|
+
name: "copy",
|
|
69
|
+
args: z.object({
|
|
70
|
+
_: z.tuple(
|
|
71
|
+
[z.string().describe("Output directory").meta({ title: "DESTINATION" })],
|
|
72
|
+
z.string().describe("Input files").meta({ title: "FILES" }),
|
|
73
|
+
),
|
|
74
|
+
}),
|
|
75
|
+
run({ args }) {
|
|
76
|
+
const [destination, ...files] = args._; // [string, ...string[]]
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
copy out a.txt b.txt # { _: ["out", "a.txt", "b.txt"] }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Help and validation errors label each item with its `.meta({ title })`, such as `<DESTINATION>` and
|
|
86
|
+
`[FILES...]`. Untitled items are labeled by position (`ARG1`, `ARG2`, ...) and an untitled rest is
|
|
87
|
+
labeled `ARGS`. Use `z.array` instead of a tuple to accept only variadic values. Without a rest,
|
|
88
|
+
additional positional arguments are kept in `extraArgs`. Unknown options are also kept in
|
|
89
|
+
`extraArgs`; arguments after `--` are kept in `passthrough`.
|
|
90
|
+
|
|
91
|
+
## Argument options
|
|
92
|
+
|
|
93
|
+
Use `argOptions` to set short aliases and help labels for each named option without changing
|
|
94
|
+
schema keys, parsed output, or validation error paths. `z.enum` choices and schema descriptions
|
|
95
|
+
appear in generated help, including enums inside the supported schema wrappers and tuple items.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const convert = defineCommand({
|
|
99
|
+
name: "convert",
|
|
100
|
+
args: z.object({
|
|
101
|
+
_: z.tuple(
|
|
102
|
+
[z.enum(["small", "large"]).describe("Item size").meta({ title: "SIZE" })],
|
|
103
|
+
z.string().describe("Input files").meta({ title: "INPUT" }),
|
|
104
|
+
),
|
|
105
|
+
format: z.enum(["json", "yaml"]).optional().describe("Output format"),
|
|
106
|
+
overwrite: z.boolean().default(true).describe("Replace existing files"),
|
|
107
|
+
verbose: z.boolean().default(false).describe("Show details"),
|
|
108
|
+
}),
|
|
109
|
+
argOptions: {
|
|
110
|
+
format: { alias: "f", valueName: "FORMAT" },
|
|
111
|
+
overwrite: { negatedDescription: "Keep existing files" },
|
|
112
|
+
verbose: { alias: ["v", "V"] },
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
convert large -f yaml -v a.txt b.txt
|
|
119
|
+
# { _: ["large", "a.txt", "b.txt"], format: "yaml", overwrite: true, verbose: true }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
USAGE convert [OPTIONS] <SIZE> [INPUT...]
|
|
124
|
+
|
|
125
|
+
ARGUMENTS
|
|
126
|
+
|
|
127
|
+
SIZE Item size (Choices: small, large) (Required)
|
|
128
|
+
INPUT... Input files
|
|
129
|
+
|
|
130
|
+
OPTIONS
|
|
131
|
+
|
|
132
|
+
-f, --format=<FORMAT> Output format (Choices: json, yaml)
|
|
133
|
+
--overwrite Replace existing files (Default: true)
|
|
134
|
+
--no-overwrite Keep existing files
|
|
135
|
+
-v, -V, --verbose Show details (Default: false)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- `alias` gives a named option one or more single-character short forms.
|
|
139
|
+
- `valueName` sets the value label of a named option.
|
|
140
|
+
- `negatedDescription` describes the `--no-` form of a boolean flag.
|
|
141
|
+
|
|
142
|
+
`argOptions` keys are checked against the argument schema. `defineCommand` throws a `TypeError`
|
|
143
|
+
when an alias is longer than one character or is used by more than one option, and when
|
|
144
|
+
`argOptions` has a `_` key. Without an alias of the same letter, `-h` and `-v` are shorthands
|
|
145
|
+
for `--help` and `--version`. Defining an `h` or `v` alias replaces the shorthand, so `convert -v`
|
|
146
|
+
above sets `verbose`; `--help` and `--version` remain available. Help remains plain text when
|
|
147
|
+
`NO_COLOR` or `CI` is set.
|
|
148
|
+
|
|
149
|
+
## Subcommands
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const main = defineCommand({
|
|
153
|
+
name: "worktree",
|
|
154
|
+
args: z.object({ repo: z.string() }),
|
|
155
|
+
subCommands: {
|
|
156
|
+
new: defineCommand({
|
|
157
|
+
args: z.object({ name: z.string() }),
|
|
158
|
+
async run({ args }) {
|
|
159
|
+
console.log(args.name);
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
migrate: defineCommand({
|
|
163
|
+
hidden: true,
|
|
164
|
+
run() {
|
|
165
|
+
// Internal or deprecated command, still callable as `worktree migrate`.
|
|
166
|
+
},
|
|
167
|
+
}),
|
|
168
|
+
},
|
|
169
|
+
async run() {
|
|
170
|
+
// Runs only when no subcommand is selected.
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Parent options precede the subcommand (`worktree --repo owner/repo new --name feature`). A selected
|
|
176
|
+
subcommand prevents the parent `run` handler from running. `worktree new --help` displays help for
|
|
177
|
+
`new`. Set `hidden: true` on a subcommand to omit its name and description from its parent's help
|
|
178
|
+
listing, including usage shown for an unknown command. Hidden commands remain available by name and
|
|
179
|
+
their own `--help` output remains available. If every subcommand is hidden, help keeps the generic
|
|
180
|
+
`<COMMAND>` syntax but omits the empty `COMMANDS` section.
|
|
181
|
+
|
|
182
|
+
## Programmatic execution and errors
|
|
183
|
+
|
|
184
|
+
`runCommand(command, argv)` handles help and version output without exiting, throws errors to its
|
|
185
|
+
caller, and returns the selected `run` handler's result.
|
|
186
|
+
|
|
187
|
+
For parsing and usage rendering without execution, use the async composition APIs. `parseArgs`
|
|
188
|
+
parses exactly the supplied command definition and argument segment, returns its schema output only,
|
|
189
|
+
and throws `ZittyValidationError` on failure. It does not route subcommands, act on help or version
|
|
190
|
+
tokens, or bypass validation; callers that resolve commands themselves must pass the selected
|
|
191
|
+
command and its segment. `renderUsage` has no output side effects; `showUsage` sends that same text
|
|
192
|
+
to an optional synchronous or asynchronous writer (and otherwise uses `console.log`). These
|
|
193
|
+
functions are async so future command-resolution features can be added without changing their
|
|
194
|
+
return types.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { parseArgs, renderUsage, showUsage } from "hacksh";
|
|
198
|
+
|
|
199
|
+
const args = await parseArgs(main, ["42", "--repo", "owner/repo"]);
|
|
200
|
+
// args is inferred from the schema as:
|
|
201
|
+
// { pr?: number; repo?: string; verbose: boolean }
|
|
202
|
+
|
|
203
|
+
const text: string = await renderUsage(main); // does not write output
|
|
204
|
+
await showUsage(main, {
|
|
205
|
+
async write(usage) {
|
|
206
|
+
await Promise.resolve();
|
|
207
|
+
console.info(usage);
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The `run` context includes `cmd`, the actual resolved command definition. An unnamed subcommand is
|
|
213
|
+
represented by a completed copy whose `name` is its key; the original definition is not mutated.
|
|
214
|
+
|
|
215
|
+
`runMain(command)` reads `process.argv`, prints validation or usage errors, and exits with status 1.
|
|
216
|
+
Use `argv` to inject arguments, or `createMain` to make a reusable entry point. Invocation arguments
|
|
217
|
+
passed to the returned function take precedence over configured `argv`; omitting both falls back to
|
|
218
|
+
`process.argv.slice(2)`. Both functions return `Promise<void>`.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { createMain } from "hacksh";
|
|
222
|
+
|
|
223
|
+
const mainEntry = createMain(main, { argv: ["--verbose"] });
|
|
224
|
+
await mainEntry(); // uses the configured argv
|
|
225
|
+
await mainEntry([]); // an explicit empty invocation overrides it
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`showUsage` customizes both ordinary help and the usage appended to validation or routing errors.
|
|
229
|
+
The supplied `write` function targets stdout for help and stderr for errors. The callback may be
|
|
230
|
+
async. Pass `onError` to replace the entire default error display, including usage:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
await runMain(main, {
|
|
234
|
+
argv: ["--help"],
|
|
235
|
+
async showUsage(command, { write }) {
|
|
236
|
+
await write(await renderUsage(command));
|
|
237
|
+
},
|
|
238
|
+
async onError(error) {
|
|
239
|
+
console.error(`gh-pr-commits: ${error instanceof Error ? error.message : String(error)}`);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Usage failures are `ZittyUsageError` instances. Argument failures are
|
|
245
|
+
`ZittyValidationError` instances whose `issues` identify CLI names such as `--repo` or `<PR>`.
|
|
246
|
+
|
|
247
|
+
## Development
|
|
248
|
+
|
|
249
|
+
| Command | Description |
|
|
250
|
+
| ------------------ | ----------------------------------------- |
|
|
251
|
+
| `vp check` | Format, lint, and type check |
|
|
252
|
+
| `vp test` | Run tests |
|
|
253
|
+
| `vp pack` | Build `src/index.ts` into `dist` |
|
|
254
|
+
| `vp run ready` | Run check, test, and build |
|
|
255
|
+
| `pnpm test:packed` | Test the tarball with Node 22.18 and Deno |
|
|
256
|
+
|
|
257
|
+
The packed-consumer test must execute its Node consumer with exactly Node 22.18.0. Run pnpm itself
|
|
258
|
+
under that version, or supply an absolute executable path explicitly:
|
|
259
|
+
|
|
260
|
+
```sh
|
|
261
|
+
ZITTY_MINIMUM_NODE=/absolute/path/to/node-v22.18.0 pnpm test:packed
|
|
262
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { $ZodIssue, $ZodObject, output } from "zod/v4/core";
|
|
2
|
+
//#region src/schema.d.ts
|
|
3
|
+
type ArgsSchema = $ZodObject;
|
|
4
|
+
type EmptyArgs = $ZodObject<{}>;
|
|
5
|
+
type ShapeOf<S extends ArgsSchema> = S["_zod"]["def"]["shape"];
|
|
6
|
+
type ArgKey<S extends ArgsSchema> = Extract<keyof ShapeOf<S>, string>;
|
|
7
|
+
type OptionKey<S extends ArgsSchema> = Exclude<ArgKey<S>, typeof POSITIONALS_KEY>;
|
|
8
|
+
declare const POSITIONALS_KEY = "_";
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/command.d.ts
|
|
11
|
+
interface ArgOptions {
|
|
12
|
+
alias?: string | readonly string[];
|
|
13
|
+
valueName?: string;
|
|
14
|
+
negatedDescription?: string;
|
|
15
|
+
}
|
|
16
|
+
type ArgOptionsMap<S extends ArgsSchema> = Partial<Record<OptionKey<S>, ArgOptions>>;
|
|
17
|
+
interface CommandContext<S extends ArgsSchema> {
|
|
18
|
+
cmd: CommandDef<S>;
|
|
19
|
+
args: output<S>;
|
|
20
|
+
rawArgs: string[];
|
|
21
|
+
passthrough: string[];
|
|
22
|
+
extraArgs: string[];
|
|
23
|
+
}
|
|
24
|
+
interface CommandDef<S extends ArgsSchema = EmptyArgs> {
|
|
25
|
+
name?: string;
|
|
26
|
+
version?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
hidden?: boolean;
|
|
29
|
+
args?: S;
|
|
30
|
+
argOptions?: ArgOptionsMap<S>;
|
|
31
|
+
subCommands?: Record<string, CommandDef<ArgsSchema>>;
|
|
32
|
+
run?(context: CommandContext<S>): unknown;
|
|
33
|
+
}
|
|
34
|
+
type AnyCommandDef = CommandDef<ArgsSchema>;
|
|
35
|
+
declare function defineCommand<S extends ArgsSchema = EmptyArgs>(def: CommandDef<S>): CommandDef<S>;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/errors.d.ts
|
|
38
|
+
interface ZittyIssue {
|
|
39
|
+
arg: string;
|
|
40
|
+
message: string;
|
|
41
|
+
issue?: $ZodIssue;
|
|
42
|
+
}
|
|
43
|
+
declare class ZittyUsageError extends Error {
|
|
44
|
+
readonly command: AnyCommandDef;
|
|
45
|
+
constructor(message: string, command: AnyCommandDef, options?: ErrorOptions);
|
|
46
|
+
}
|
|
47
|
+
declare class ZittyValidationError extends ZittyUsageError {
|
|
48
|
+
readonly issues: ReadonlyArray<ZittyIssue>;
|
|
49
|
+
constructor(command: AnyCommandDef, issues: ReadonlyArray<ZittyIssue>);
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/parser.d.ts
|
|
53
|
+
/**
|
|
54
|
+
* Parse and validate one command segment without routing, builtin handling, or output.
|
|
55
|
+
*/
|
|
56
|
+
declare function parseArgs<S extends ArgsSchema>(command: CommandDef<S>, argv: string[]): Promise<output<S>>;
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/usage.d.ts
|
|
59
|
+
type UsageWriter = (text: string) => void | Promise<void>;
|
|
60
|
+
interface ShowUsageOptions {
|
|
61
|
+
write?: UsageWriter;
|
|
62
|
+
}
|
|
63
|
+
type UsageOutput = Required<Pick<ShowUsageOptions, "write">>;
|
|
64
|
+
type ShowUsageCallback = (command: AnyCommandDef, output: UsageOutput) => void | Promise<void>;
|
|
65
|
+
/** Generate usage text without writing to stdout or stderr. */
|
|
66
|
+
declare function renderUsage(command: AnyCommandDef): Promise<string>;
|
|
67
|
+
/** Generate and write usage text, awaiting asynchronous writers. */
|
|
68
|
+
declare function showUsage(command: AnyCommandDef, options?: ShowUsageOptions): Promise<void>;
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/run.d.ts
|
|
71
|
+
declare function runCommand<S extends ArgsSchema>(root: CommandDef<S>, argv: string[]): Promise<unknown>;
|
|
72
|
+
interface RunMainOptions {
|
|
73
|
+
onError?: (error: unknown) => void | Promise<void>;
|
|
74
|
+
argv?: string[];
|
|
75
|
+
showUsage?: ShowUsageCallback;
|
|
76
|
+
}
|
|
77
|
+
declare function runMain<S extends ArgsSchema>(command: CommandDef<S>, options?: RunMainOptions): Promise<void>;
|
|
78
|
+
declare function createMain<S extends ArgsSchema>(command: CommandDef<S>, options?: RunMainOptions): (argv?: string[]) => Promise<void>;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { type ArgOptions, type ArgOptionsMap, type CommandContext, type CommandDef, type RunMainOptions, type ShowUsageCallback, type ShowUsageOptions, type UsageOutput, type UsageWriter, type ZittyIssue, ZittyUsageError, ZittyValidationError, createMain, defineCommand, parseArgs, renderUsage, runCommand, runMain, showUsage };
|
|
81
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { globalRegistry, safeParseAsync } from "zod/v4/core";
|
|
2
|
+
import { parseArgs as parseArgs$1 } from "node:util";
|
|
3
|
+
//#region src/options.ts
|
|
4
|
+
function kebabCase(value) {
|
|
5
|
+
return value.replaceAll(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
function aliasList(value) {
|
|
8
|
+
return typeof value === "string" ? [value] : value;
|
|
9
|
+
}
|
|
10
|
+
function definition(schema) {
|
|
11
|
+
return schema._zod.def;
|
|
12
|
+
}
|
|
13
|
+
function innerSchema(def) {
|
|
14
|
+
switch (def.type) {
|
|
15
|
+
case "optional":
|
|
16
|
+
case "nonoptional":
|
|
17
|
+
case "nullable":
|
|
18
|
+
case "default":
|
|
19
|
+
case "prefault":
|
|
20
|
+
case "catch":
|
|
21
|
+
case "readonly": return def.innerType;
|
|
22
|
+
case "pipe": return def.in;
|
|
23
|
+
default: return;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Read only the common zod/v4/core definition and registry structures. */
|
|
27
|
+
function readArgInfo(schema) {
|
|
28
|
+
let current = schema;
|
|
29
|
+
let required = true;
|
|
30
|
+
let defaultValue;
|
|
31
|
+
let description;
|
|
32
|
+
let title;
|
|
33
|
+
while (current) {
|
|
34
|
+
const metadata = globalRegistry.get(current);
|
|
35
|
+
description ??= metadata?.description;
|
|
36
|
+
title ??= metadata?.title;
|
|
37
|
+
const def = definition(current);
|
|
38
|
+
if (def.type === "optional" || def.type === "default" || def.type === "prefault") required = false;
|
|
39
|
+
if (def.type === "default" && defaultValue === void 0) defaultValue = def.defaultValue;
|
|
40
|
+
current = innerSchema(def);
|
|
41
|
+
}
|
|
42
|
+
const base = unwrapInput(schema);
|
|
43
|
+
return {
|
|
44
|
+
takesValue: definition(base).type !== "boolean",
|
|
45
|
+
required,
|
|
46
|
+
defaultValue,
|
|
47
|
+
description,
|
|
48
|
+
title,
|
|
49
|
+
choices: enumChoices(base)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Read the `_` tuple or array schema that declares positional arguments. */
|
|
53
|
+
function readPositionals(shape) {
|
|
54
|
+
const field = shape["_"];
|
|
55
|
+
if (!field) return void 0;
|
|
56
|
+
const def = definition(unwrapInput(field));
|
|
57
|
+
let items;
|
|
58
|
+
let rest;
|
|
59
|
+
if (def.type === "tuple") {
|
|
60
|
+
items = def.items;
|
|
61
|
+
rest = def.rest ?? void 0;
|
|
62
|
+
} else if (def.type === "array") {
|
|
63
|
+
items = [];
|
|
64
|
+
rest = def.element;
|
|
65
|
+
} else throw new TypeError("Positional arguments `_` must use a tuple or array schema");
|
|
66
|
+
return {
|
|
67
|
+
items: items.map((schema, index) => ({
|
|
68
|
+
schema,
|
|
69
|
+
label: readArgInfo(schema).title ?? `ARG${index + 1}`
|
|
70
|
+
})),
|
|
71
|
+
rest: rest && {
|
|
72
|
+
schema: rest,
|
|
73
|
+
label: readArgInfo(rest).title ?? "ARGS"
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function enumChoices(schema) {
|
|
78
|
+
const def = definition(schema);
|
|
79
|
+
const candidate = def.type === "array" ? unwrapInput(def.element) : schema;
|
|
80
|
+
if (definition(candidate).type !== "enum") return void 0;
|
|
81
|
+
const values = candidate._zod.values;
|
|
82
|
+
return values ? [...values].map(String) : void 0;
|
|
83
|
+
}
|
|
84
|
+
/** Whether the schema accepts an array after removing supported input wrappers. */
|
|
85
|
+
function isArrayInput(schema) {
|
|
86
|
+
return definition(unwrapInput(schema)).type === "array";
|
|
87
|
+
}
|
|
88
|
+
function unwrapInput(schema) {
|
|
89
|
+
let current = schema;
|
|
90
|
+
for (;;) {
|
|
91
|
+
const inner = innerSchema(definition(current));
|
|
92
|
+
if (!inner) return current;
|
|
93
|
+
current = inner;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function schemaShape(schema) {
|
|
97
|
+
return schema._zod.def.shape;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/command.ts
|
|
101
|
+
function defineCommand(def) {
|
|
102
|
+
const shape = def.args ? schemaShape(def.args) : {};
|
|
103
|
+
const positionals = readPositionals(shape);
|
|
104
|
+
if (positionals) {
|
|
105
|
+
let sawOptional = false;
|
|
106
|
+
for (const { schema, label } of positionals.items) {
|
|
107
|
+
const info = readArgInfo(schema);
|
|
108
|
+
if (isArrayInput(schema)) throw new TypeError(`Positional argument \`${label}\` cannot be an array; use a tuple rest`);
|
|
109
|
+
if (!info.takesValue) throw new TypeError(`Boolean argument \`${label}\` cannot be positional`);
|
|
110
|
+
if (sawOptional && info.required) throw new TypeError(`Required positional \`${label}\` cannot follow an optional positional`);
|
|
111
|
+
sawOptional ||= !info.required;
|
|
112
|
+
}
|
|
113
|
+
if (def.subCommands && Object.keys(def.subCommands).length > 0) throw new TypeError("A command cannot define both positional arguments and subcommands");
|
|
114
|
+
}
|
|
115
|
+
const usedAliases = /* @__PURE__ */ new Set();
|
|
116
|
+
for (const [key, options] of Object.entries(def.argOptions ?? {})) {
|
|
117
|
+
if (key === "_") throw new TypeError("Positional arguments cannot have argument options");
|
|
118
|
+
if (!shape[key]) throw new TypeError(`Unknown argument options key \`${key}\``);
|
|
119
|
+
if (options?.alias === void 0) continue;
|
|
120
|
+
for (const alias of aliasList(options.alias)) {
|
|
121
|
+
if (alias.length !== 1) throw new TypeError(`Alias \`${alias}\` must be exactly one character`);
|
|
122
|
+
if (usedAliases.has(alias)) throw new TypeError(`Duplicate alias \`${alias}\``);
|
|
123
|
+
usedAliases.add(alias);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const key of Object.keys(shape)) if (key !== "_" && /^no[-A-Z]/.test(key)) throw new TypeError(`Option \`${key}\` conflicts with --no- negation`);
|
|
127
|
+
return def;
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/errors.ts
|
|
131
|
+
var ZittyUsageError = class extends Error {
|
|
132
|
+
command;
|
|
133
|
+
constructor(message, command, options) {
|
|
134
|
+
super(message, options);
|
|
135
|
+
this.name = "ZittyUsageError";
|
|
136
|
+
this.command = command;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var ZittyValidationError = class extends ZittyUsageError {
|
|
140
|
+
issues;
|
|
141
|
+
constructor(command, issues) {
|
|
142
|
+
super(`Invalid arguments:\n${issues.map(({ arg, message }) => ` ${arg}: ${message}`).join("\n")}`, command);
|
|
143
|
+
this.name = "ZittyValidationError";
|
|
144
|
+
this.issues = issues;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/parser.ts
|
|
149
|
+
function optionModel(command) {
|
|
150
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
151
|
+
const parserOptions = {};
|
|
152
|
+
const allAliases = /* @__PURE__ */ new Set();
|
|
153
|
+
if (command.args) for (const [key, schema] of Object.entries(schemaShape(command.args))) {
|
|
154
|
+
if (key === "_") continue;
|
|
155
|
+
const alias = command.argOptions?.[key]?.alias;
|
|
156
|
+
const aliases = alias ? aliasList(alias) : [];
|
|
157
|
+
const definition = {
|
|
158
|
+
key,
|
|
159
|
+
kebabName: kebabCase(key),
|
|
160
|
+
takesValue: readArgInfo(schema).takesValue
|
|
161
|
+
};
|
|
162
|
+
const type = definition.takesValue ? "string" : "boolean";
|
|
163
|
+
for (const name of /* @__PURE__ */ new Set([key, definition.kebabName])) {
|
|
164
|
+
definitions.set(name, definition);
|
|
165
|
+
parserOptions[name] = { type };
|
|
166
|
+
}
|
|
167
|
+
for (const alias of aliases) {
|
|
168
|
+
allAliases.add(alias);
|
|
169
|
+
definitions.set(alias, definition);
|
|
170
|
+
parserOptions[alias] = {
|
|
171
|
+
type,
|
|
172
|
+
short: alias
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
definitions,
|
|
178
|
+
parserOptions,
|
|
179
|
+
aliases: allAliases
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function commandPositionals(command) {
|
|
183
|
+
return command.args ? readPositionals(schemaShape(command.args)) : void 0;
|
|
184
|
+
}
|
|
185
|
+
function displayName(command, path) {
|
|
186
|
+
const [first, index] = path;
|
|
187
|
+
if (typeof first !== "string") return "arguments";
|
|
188
|
+
if (first !== "_") return `--${kebabCase(first)}`;
|
|
189
|
+
const positionals = commandPositionals(command);
|
|
190
|
+
if (!positionals || typeof index !== "number") return "arguments";
|
|
191
|
+
const item = positionals.items[index];
|
|
192
|
+
if (item) return `<${item.label}>`;
|
|
193
|
+
return positionals.rest ? `<${positionals.rest.label}...>` : "arguments";
|
|
194
|
+
}
|
|
195
|
+
function tokenizeCommandArgs(command, rawArgs) {
|
|
196
|
+
const model = optionModel(command);
|
|
197
|
+
const separator = rawArgs.indexOf("--");
|
|
198
|
+
const beforeSeparator = separator === -1 ? [...rawArgs] : rawArgs.slice(0, separator);
|
|
199
|
+
const passthrough = separator === -1 ? [] : rawArgs.slice(separator + 1);
|
|
200
|
+
const negativeIndexes = /* @__PURE__ */ new Set();
|
|
201
|
+
const valuedNegativeIndexes = /* @__PURE__ */ new Set();
|
|
202
|
+
const issues = [];
|
|
203
|
+
let recordedMissingValue = false;
|
|
204
|
+
const parserArgs = beforeSeparator.map((raw, index) => {
|
|
205
|
+
if (!raw.startsWith("--no-")) return raw;
|
|
206
|
+
const equals = raw.indexOf("=");
|
|
207
|
+
const name = raw.slice(5, equals === -1 ? void 0 : equals);
|
|
208
|
+
const definition = model.definitions.get(name);
|
|
209
|
+
if (!definition || definition.takesValue) return raw;
|
|
210
|
+
negativeIndexes.add(index);
|
|
211
|
+
if (equals !== -1) {
|
|
212
|
+
valuedNegativeIndexes.add(index);
|
|
213
|
+
return `--${definition.key}${raw.slice(equals)}`;
|
|
214
|
+
}
|
|
215
|
+
return `--${definition.key}`;
|
|
216
|
+
});
|
|
217
|
+
const parsed = parseArgs$1({
|
|
218
|
+
args: parserArgs,
|
|
219
|
+
options: model.parserOptions,
|
|
220
|
+
allowPositionals: true,
|
|
221
|
+
strict: false,
|
|
222
|
+
tokens: true
|
|
223
|
+
});
|
|
224
|
+
const tokens = [];
|
|
225
|
+
const unknownIndexes = /* @__PURE__ */ new Set();
|
|
226
|
+
let help = false;
|
|
227
|
+
let helpIndex;
|
|
228
|
+
let version = false;
|
|
229
|
+
let versionIndex;
|
|
230
|
+
for (const token of parsed.tokens) {
|
|
231
|
+
if (token.kind === "positional") {
|
|
232
|
+
tokens.push({
|
|
233
|
+
kind: "positional",
|
|
234
|
+
index: token.index,
|
|
235
|
+
value: token.value
|
|
236
|
+
});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (token.kind !== "option") continue;
|
|
240
|
+
const isHelp = token.rawName === "--help" || token.rawName === "-h" && !model.aliases.has("h");
|
|
241
|
+
const isVersion = token.rawName === "--version" || token.rawName === "-v" && !model.aliases.has("v");
|
|
242
|
+
if (isHelp) {
|
|
243
|
+
help = true;
|
|
244
|
+
helpIndex ??= token.index;
|
|
245
|
+
}
|
|
246
|
+
if (isVersion) {
|
|
247
|
+
version = true;
|
|
248
|
+
versionIndex ??= token.index;
|
|
249
|
+
}
|
|
250
|
+
const definition = model.definitions.get(token.name);
|
|
251
|
+
if (definition) {
|
|
252
|
+
if (definition.takesValue && (token.value === void 0 || !token.inlineValue && token.value.startsWith("-") && token.value !== "-")) {
|
|
253
|
+
if (!recordedMissingValue) {
|
|
254
|
+
issues.push({
|
|
255
|
+
index: token.index,
|
|
256
|
+
issue: {
|
|
257
|
+
arg: `--${definition.kebabName}`,
|
|
258
|
+
message: "requires a value"
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
recordedMissingValue = true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
let value = token.value ?? true;
|
|
265
|
+
if (!definition.takesValue) {
|
|
266
|
+
if (valuedNegativeIndexes.has(token.index)) {
|
|
267
|
+
const rawName = beforeSeparator[token.index].split("=", 1)[0];
|
|
268
|
+
issues.push({
|
|
269
|
+
index: token.index,
|
|
270
|
+
issue: {
|
|
271
|
+
arg: rawName,
|
|
272
|
+
message: "does not accept a value"
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
value = false;
|
|
276
|
+
} else if (negativeIndexes.has(token.index)) value = false;
|
|
277
|
+
else if (token.inlineValue) if (token.value === "true") value = true;
|
|
278
|
+
else if (token.value === "false") value = false;
|
|
279
|
+
else issues.push({
|
|
280
|
+
index: token.index,
|
|
281
|
+
issue: {
|
|
282
|
+
arg: token.rawName,
|
|
283
|
+
message: "expects true or false"
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
tokens.push({
|
|
288
|
+
kind: "known-option",
|
|
289
|
+
index: token.index,
|
|
290
|
+
definition,
|
|
291
|
+
value: definition.takesValue ? token.value ?? "" : value
|
|
292
|
+
});
|
|
293
|
+
} else if (!unknownIndexes.has(token.index)) {
|
|
294
|
+
unknownIndexes.add(token.index);
|
|
295
|
+
tokens.push({
|
|
296
|
+
kind: "unknown-option",
|
|
297
|
+
index: token.index,
|
|
298
|
+
value: beforeSeparator[token.index]
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
tokens,
|
|
304
|
+
passthrough,
|
|
305
|
+
issues,
|
|
306
|
+
help,
|
|
307
|
+
helpIndex,
|
|
308
|
+
version,
|
|
309
|
+
versionIndex
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function tokenizedBefore(tokenized, endIndex) {
|
|
313
|
+
const help = tokenized.helpIndex !== void 0 && tokenized.helpIndex < endIndex;
|
|
314
|
+
const version = tokenized.versionIndex !== void 0 && tokenized.versionIndex < endIndex;
|
|
315
|
+
return {
|
|
316
|
+
tokens: tokenized.tokens.filter((token) => token.index < endIndex),
|
|
317
|
+
passthrough: [],
|
|
318
|
+
issues: tokenized.issues.filter(({ index }) => index < endIndex),
|
|
319
|
+
help,
|
|
320
|
+
helpIndex: help ? tokenized.helpIndex : void 0,
|
|
321
|
+
version,
|
|
322
|
+
versionIndex: version ? tokenized.versionIndex : void 0
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function zodIssues(command, issues) {
|
|
326
|
+
return issues.map((issue) => ({
|
|
327
|
+
arg: displayName(command, issue.path),
|
|
328
|
+
message: issue.message,
|
|
329
|
+
issue
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
async function parseCommandArgs(command, rawArgs, tokenized = tokenizeCommandArgs(command, rawArgs)) {
|
|
333
|
+
if (tokenized.issues.length > 0) throw new ZittyValidationError(command, tokenized.issues.map(({ issue }) => issue));
|
|
334
|
+
const input = {};
|
|
335
|
+
const positionals = commandPositionals(command);
|
|
336
|
+
const positionalValues = [];
|
|
337
|
+
if (positionals) input["_"] = positionalValues;
|
|
338
|
+
const extraArgs = [];
|
|
339
|
+
for (const token of tokenized.tokens) if (token.kind === "known-option") input[token.definition.key] = token.value;
|
|
340
|
+
else if (token.kind === "unknown-option") extraArgs.push(token.value);
|
|
341
|
+
else if (positionals && (positionals.rest || positionalValues.length < positionals.items.length)) positionalValues.push(token.value);
|
|
342
|
+
else extraArgs.push(token.value);
|
|
343
|
+
if (!command.args) return {
|
|
344
|
+
args: input,
|
|
345
|
+
passthrough: tokenized.passthrough,
|
|
346
|
+
extraArgs
|
|
347
|
+
};
|
|
348
|
+
const result = await safeParseAsync(command.args, input);
|
|
349
|
+
if (!result.success) throw new ZittyValidationError(command, zodIssues(command, result.error.issues));
|
|
350
|
+
return {
|
|
351
|
+
args: result.data,
|
|
352
|
+
passthrough: tokenized.passthrough,
|
|
353
|
+
extraArgs
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Parse and validate one command segment without routing, builtin handling, or output.
|
|
358
|
+
*/
|
|
359
|
+
async function parseArgs(command, argv) {
|
|
360
|
+
return (await parseCommandArgs(command, argv)).args;
|
|
361
|
+
}
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region src/usage.ts
|
|
364
|
+
function details(description, required, defaultValue, choices) {
|
|
365
|
+
const parts = [description];
|
|
366
|
+
if (choices) parts.push(`(Choices: ${choices.join(", ")})`);
|
|
367
|
+
if (required) parts.push("(Required)");
|
|
368
|
+
if (defaultValue !== void 0) parts.push(`(Default: ${formatDefault(defaultValue)})`);
|
|
369
|
+
return parts.filter(Boolean).join(" ");
|
|
370
|
+
}
|
|
371
|
+
function formatDefault(value) {
|
|
372
|
+
if (typeof value === "string") return value;
|
|
373
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return value.toString();
|
|
374
|
+
if (value === null) return "null";
|
|
375
|
+
return JSON.stringify(value) ?? "undefined";
|
|
376
|
+
}
|
|
377
|
+
function heading(command) {
|
|
378
|
+
const identity = command.name ? `${command.name}${command.version ? ` v${command.version}` : ""}` : command.version ? `v${command.version}` : void 0;
|
|
379
|
+
if (command.description && identity) return `${command.description} (${identity})`;
|
|
380
|
+
return command.description ?? identity;
|
|
381
|
+
}
|
|
382
|
+
function formatUsage(command) {
|
|
383
|
+
const lines = [];
|
|
384
|
+
const title = heading(command);
|
|
385
|
+
if (title) lines.push(title, "");
|
|
386
|
+
const shape = command.args ? schemaShape(command.args) : {};
|
|
387
|
+
const positionals = readPositionals(shape);
|
|
388
|
+
const items = positionals?.items ?? [];
|
|
389
|
+
const rest = positionals?.rest;
|
|
390
|
+
const syntax = items.map(({ schema, label }) => readArgInfo(schema).required ? `<${label}>` : `[${label}]`);
|
|
391
|
+
if (rest) syntax.push(`[${rest.label}...]`);
|
|
392
|
+
if (command.subCommands && Object.keys(command.subCommands).length > 0) syntax.push("<COMMAND>");
|
|
393
|
+
lines.push(`USAGE ${command.name ?? "command"} [OPTIONS]${syntax.length ? ` ${syntax.join(" ")}` : ""}`);
|
|
394
|
+
if (items.length > 0 || rest) {
|
|
395
|
+
lines.push("", "ARGUMENTS", "");
|
|
396
|
+
for (const { schema, label } of items) {
|
|
397
|
+
const info = readArgInfo(schema);
|
|
398
|
+
lines.push(` ${label} ${details(info.description, info.required, info.defaultValue, info.choices)}`.trimEnd());
|
|
399
|
+
}
|
|
400
|
+
if (rest) {
|
|
401
|
+
const info = readArgInfo(rest.schema);
|
|
402
|
+
lines.push(` ${rest.label}... ${details(info.description, false, void 0, info.choices)}`.trimEnd());
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const options = Object.entries(shape).filter(([key]) => key !== "_");
|
|
406
|
+
if (options.length > 0) {
|
|
407
|
+
lines.push("", "OPTIONS", "");
|
|
408
|
+
for (const [key, schema] of options) {
|
|
409
|
+
const info = readArgInfo(schema);
|
|
410
|
+
const argOptions = command.argOptions?.[key];
|
|
411
|
+
const prefix = argOptions?.alias ? `${aliasList(argOptions.alias).map((item) => `-${item}`).join(", ")}, ` : "";
|
|
412
|
+
const valueName = argOptions?.valueName ?? kebabCase(key);
|
|
413
|
+
const long = `--${kebabCase(key)}${info.takesValue ? `=<${valueName}>` : ""}`;
|
|
414
|
+
lines.push(` ${prefix}${long} ${details(info.description, info.required, info.defaultValue, info.choices)}`.trimEnd());
|
|
415
|
+
if (!info.takesValue && argOptions?.negatedDescription) lines.push(` --no-${kebabCase(key)} ${argOptions.negatedDescription}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const visibleSubCommands = Object.entries(command.subCommands ?? {}).filter(([, child]) => !child.hidden);
|
|
419
|
+
if (visibleSubCommands.length > 0) {
|
|
420
|
+
lines.push("", "COMMANDS", "");
|
|
421
|
+
for (const [name, child] of visibleSubCommands) lines.push(` ${name} ${child.description ?? ""}`.trimEnd());
|
|
422
|
+
}
|
|
423
|
+
return lines.join("\n");
|
|
424
|
+
}
|
|
425
|
+
/** Generate usage text without writing to stdout or stderr. */
|
|
426
|
+
async function renderUsage(command) {
|
|
427
|
+
return formatUsage(command);
|
|
428
|
+
}
|
|
429
|
+
/** Generate and write usage text, awaiting asynchronous writers. */
|
|
430
|
+
async function showUsage(command, options = {}) {
|
|
431
|
+
const text = await renderUsage(command);
|
|
432
|
+
return (options.write ?? console.log)(text);
|
|
433
|
+
}
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/run.ts
|
|
436
|
+
function namedChild(child, name) {
|
|
437
|
+
return child.name ? child : {
|
|
438
|
+
...child,
|
|
439
|
+
name
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function resolveCommand(root, rawArgs) {
|
|
443
|
+
let command = root;
|
|
444
|
+
let remaining = rawArgs;
|
|
445
|
+
const parents = [];
|
|
446
|
+
const segments = [];
|
|
447
|
+
for (;;) {
|
|
448
|
+
const tokenized = tokenizeCommandArgs(command, remaining);
|
|
449
|
+
const index = command.subCommands ? tokenized.tokens.find((token) => token.kind === "positional")?.index : void 0;
|
|
450
|
+
if (index === void 0) {
|
|
451
|
+
const segment = {
|
|
452
|
+
command,
|
|
453
|
+
rawArgs: remaining,
|
|
454
|
+
tokenized
|
|
455
|
+
};
|
|
456
|
+
segments.push(segment);
|
|
457
|
+
return {
|
|
458
|
+
...segment,
|
|
459
|
+
parents,
|
|
460
|
+
segments
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
const name = remaining[index];
|
|
464
|
+
const child = command.subCommands?.[name];
|
|
465
|
+
if (!child) throw new ZittyUsageError(`Unknown command \`${name}\``, command);
|
|
466
|
+
const prefix = remaining.slice(0, index);
|
|
467
|
+
const segment = {
|
|
468
|
+
command,
|
|
469
|
+
rawArgs: prefix,
|
|
470
|
+
tokenized: tokenizedBefore(tokenized, index)
|
|
471
|
+
};
|
|
472
|
+
parents.push(segment);
|
|
473
|
+
segments.push(segment);
|
|
474
|
+
command = namedChild(child, name);
|
|
475
|
+
remaining = remaining.slice(index + 1);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function executeCommand(root, argv, displayUsage) {
|
|
479
|
+
const resolved = resolveCommand(root, argv);
|
|
480
|
+
if (resolved.segments[0]?.tokenized.version) {
|
|
481
|
+
console.log(root.version ?? "");
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (resolved.segments.some(({ tokenized }) => tokenized.help)) {
|
|
485
|
+
await displayUsage(resolved.command, { write: (text) => console.log(text) });
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
for (const parent of resolved.parents) await parseCommandArgs(parent.command, parent.rawArgs, parent.tokenized);
|
|
489
|
+
const parsed = await parseCommandArgs(resolved.command, resolved.rawArgs, resolved.tokenized);
|
|
490
|
+
if (!resolved.command.run) throw new ZittyUsageError("No command specified", resolved.command);
|
|
491
|
+
return resolved.command.run({
|
|
492
|
+
...parsed,
|
|
493
|
+
cmd: resolved.command,
|
|
494
|
+
rawArgs: resolved.rawArgs
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
async function runCommand(root, argv) {
|
|
498
|
+
return executeCommand(root, argv, showUsage);
|
|
499
|
+
}
|
|
500
|
+
async function runMain(command, options = {}) {
|
|
501
|
+
const displayUsage = options.showUsage ?? showUsage;
|
|
502
|
+
try {
|
|
503
|
+
await executeCommand(command, options.argv ?? process.argv.slice(2), displayUsage);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
if (options.onError) {
|
|
506
|
+
await options.onError(error);
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
if (error instanceof ZittyUsageError) {
|
|
511
|
+
console.error(error.message);
|
|
512
|
+
await displayUsage(error.command, { write: (text) => console.error(text) });
|
|
513
|
+
} else console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
514
|
+
} finally {
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function createMain(command, options = {}) {
|
|
520
|
+
return (argv) => runMain(command, argv === void 0 ? options : {
|
|
521
|
+
...options,
|
|
522
|
+
argv
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
//#endregion
|
|
526
|
+
export { ZittyUsageError, ZittyValidationError, createMain, defineCommand, parseArgs, renderUsage, runCommand, runMain, showUsage };
|
|
527
|
+
|
|
528
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["parseNodeArgs"],"sources":["../src/options.ts","../src/schema.ts","../src/command.ts","../src/errors.ts","../src/parser.ts","../src/usage.ts","../src/run.ts"],"sourcesContent":["export function kebabCase(value: string): string {\n return value.replaceAll(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n}\n\nexport function aliasList(value: string | readonly string[]): readonly string[] {\n return typeof value === \"string\" ? [value] : value;\n}\n","import { globalRegistry, type $ZodObject, type $ZodType } from \"zod/v4/core\";\n\nexport type ArgsSchema = $ZodObject;\nexport type EmptyArgs = $ZodObject<{}>;\nexport type ShapeOf<S extends ArgsSchema> = S[\"_zod\"][\"def\"][\"shape\"];\nexport type ArgKey<S extends ArgsSchema> = Extract<keyof ShapeOf<S>, string>;\nexport type OptionKey<S extends ArgsSchema> = Exclude<ArgKey<S>, typeof POSITIONALS_KEY>;\n\nexport const POSITIONALS_KEY = \"_\";\n\nexport interface ArgInfo {\n readonly takesValue: boolean;\n readonly required: boolean;\n readonly defaultValue: unknown;\n readonly description: string | undefined;\n readonly title: string | undefined;\n readonly choices: readonly string[] | undefined;\n}\n\nexport interface Positional {\n readonly schema: $ZodType;\n readonly label: string;\n}\n\nexport interface Positionals {\n readonly items: readonly Positional[];\n readonly rest: Positional | undefined;\n}\n\ntype ZodDef = Record<string, unknown> & { type: string };\n\nfunction definition(schema: $ZodType): ZodDef {\n return schema._zod.def as ZodDef;\n}\n\nfunction innerSchema(def: ZodDef): $ZodType | undefined {\n switch (def.type) {\n case \"optional\":\n case \"nonoptional\":\n case \"nullable\":\n case \"default\":\n case \"prefault\":\n case \"catch\":\n case \"readonly\":\n return def.innerType as $ZodType;\n case \"pipe\":\n return def.in as $ZodType;\n default:\n return undefined;\n }\n}\n\n/** Read only the common zod/v4/core definition and registry structures. */\nexport function readArgInfo(schema: $ZodType): ArgInfo {\n let current: $ZodType | undefined = schema;\n let required = true;\n let defaultValue: unknown;\n let description: string | undefined;\n let title: string | undefined;\n\n while (current) {\n const metadata = globalRegistry.get(current);\n description ??= metadata?.description;\n title ??= metadata?.title;\n\n const def = definition(current);\n if (def.type === \"optional\" || def.type === \"default\" || def.type === \"prefault\") {\n required = false;\n }\n if (def.type === \"default\" && defaultValue === undefined) defaultValue = def.defaultValue;\n current = innerSchema(def);\n }\n\n const base = unwrapInput(schema);\n return {\n takesValue: definition(base).type !== \"boolean\",\n required,\n defaultValue,\n description,\n title,\n choices: enumChoices(base),\n };\n}\n\n/** Read the `_` tuple or array schema that declares positional arguments. */\nexport function readPositionals(shape: Record<string, $ZodType>): Positionals | undefined {\n const field = shape[POSITIONALS_KEY];\n if (!field) return undefined;\n const def = definition(unwrapInput(field));\n let items: readonly $ZodType[];\n let rest: $ZodType | undefined;\n if (def.type === \"tuple\") {\n items = def.items as readonly $ZodType[];\n rest = (def.rest as $ZodType | null) ?? undefined;\n } else if (def.type === \"array\") {\n items = [];\n rest = def.element as $ZodType;\n } else {\n throw new TypeError(\"Positional arguments `_` must use a tuple or array schema\");\n }\n return {\n items: items.map((schema, index) => ({\n schema,\n label: readArgInfo(schema).title ?? `ARG${index + 1}`,\n })),\n rest: rest && { schema: rest, label: readArgInfo(rest).title ?? \"ARGS\" },\n };\n}\n\nfunction enumChoices(schema: $ZodType): readonly string[] | undefined {\n const def = definition(schema);\n const candidate = def.type === \"array\" ? unwrapInput(def.element as $ZodType) : schema;\n const candidateDef = definition(candidate);\n if (candidateDef.type !== \"enum\") return undefined;\n const values = candidate._zod.values;\n return values ? [...values].map(String) : undefined;\n}\n\n/** Whether the schema accepts an array after removing supported input wrappers. */\nexport function isArrayInput(schema: $ZodType): boolean {\n return definition(unwrapInput(schema)).type === \"array\";\n}\n\nfunction unwrapInput(schema: $ZodType): $ZodType {\n let current = schema;\n for (;;) {\n const inner = innerSchema(definition(current));\n if (!inner) return current;\n current = inner;\n }\n}\n\nexport function schemaShape(schema: ArgsSchema): Record<string, $ZodType> {\n return schema._zod.def.shape as Record<string, $ZodType>;\n}\n","import type { output } from \"zod/v4/core\";\nimport { aliasList } from \"./options.ts\";\nimport {\n isArrayInput,\n POSITIONALS_KEY,\n readArgInfo,\n readPositionals,\n schemaShape,\n type ArgsSchema,\n type EmptyArgs,\n type OptionKey,\n} from \"./schema.ts\";\n\nexport interface ArgOptions {\n alias?: string | readonly string[];\n valueName?: string;\n negatedDescription?: string;\n}\n\nexport type ArgOptionsMap<S extends ArgsSchema> = Partial<Record<OptionKey<S>, ArgOptions>>;\n\nexport interface CommandContext<S extends ArgsSchema> {\n cmd: CommandDef<S>;\n args: output<S>;\n rawArgs: string[];\n passthrough: string[];\n extraArgs: string[];\n}\n\nexport interface CommandDef<S extends ArgsSchema = EmptyArgs> {\n name?: string;\n version?: string;\n description?: string;\n hidden?: boolean;\n args?: S;\n argOptions?: ArgOptionsMap<S>;\n subCommands?: Record<string, CommandDef<ArgsSchema>>;\n run?(context: CommandContext<S>): unknown;\n}\n\nexport type AnyCommandDef = CommandDef<ArgsSchema>;\n\nexport function defineCommand<S extends ArgsSchema = EmptyArgs>(def: CommandDef<S>): CommandDef<S> {\n const shape = def.args ? schemaShape(def.args) : {};\n const positionals = readPositionals(shape);\n\n if (positionals) {\n let sawOptional = false;\n for (const { schema, label } of positionals.items) {\n const info = readArgInfo(schema);\n if (isArrayInput(schema)) {\n throw new TypeError(\n `Positional argument \\`${label}\\` cannot be an array; use a tuple rest`,\n );\n }\n if (!info.takesValue) {\n throw new TypeError(`Boolean argument \\`${label}\\` cannot be positional`);\n }\n if (sawOptional && info.required) {\n throw new TypeError(\n `Required positional \\`${label}\\` cannot follow an optional positional`,\n );\n }\n sawOptional ||= !info.required;\n }\n if (def.subCommands && Object.keys(def.subCommands).length > 0) {\n throw new TypeError(\"A command cannot define both positional arguments and subcommands\");\n }\n }\n\n const usedAliases = new Set<string>();\n for (const [key, options] of Object.entries(def.argOptions ?? {})) {\n if (key === POSITIONALS_KEY) {\n throw new TypeError(\"Positional arguments cannot have argument options\");\n }\n if (!shape[key]) throw new TypeError(`Unknown argument options key \\`${key}\\``);\n if (options?.alias === undefined) continue;\n for (const alias of aliasList(options.alias)) {\n if (alias.length !== 1)\n throw new TypeError(`Alias \\`${alias}\\` must be exactly one character`);\n if (usedAliases.has(alias)) throw new TypeError(`Duplicate alias \\`${alias}\\``);\n usedAliases.add(alias);\n }\n }\n\n for (const key of Object.keys(shape)) {\n if (key !== POSITIONALS_KEY && /^no[-A-Z]/.test(key)) {\n throw new TypeError(`Option \\`${key}\\` conflicts with --no- negation`);\n }\n }\n\n return def;\n}\n","import type { $ZodIssue } from \"zod/v4/core\";\nimport type { AnyCommandDef } from \"./command.ts\";\n\nexport interface ZittyIssue {\n arg: string;\n message: string;\n issue?: $ZodIssue;\n}\n\nexport class ZittyUsageError extends Error {\n readonly command: AnyCommandDef;\n\n constructor(message: string, command: AnyCommandDef, options?: ErrorOptions) {\n super(message, options);\n this.name = \"ZittyUsageError\";\n this.command = command;\n }\n}\n\nexport class ZittyValidationError extends ZittyUsageError {\n readonly issues: ReadonlyArray<ZittyIssue>;\n\n constructor(command: AnyCommandDef, issues: ReadonlyArray<ZittyIssue>) {\n super(\n `Invalid arguments:\\n${issues.map(({ arg, message }) => ` ${arg}: ${message}`).join(\"\\n\")}`,\n command,\n );\n this.name = \"ZittyValidationError\";\n this.issues = issues;\n }\n}\n","import { parseArgs as parseNodeArgs } from \"node:util\";\nimport { safeParseAsync, type $ZodIssue, type output } from \"zod/v4/core\";\nimport type { AnyCommandDef, CommandDef } from \"./command.ts\";\nimport { ZittyValidationError, type ZittyIssue } from \"./errors.ts\";\nimport { aliasList, kebabCase } from \"./options.ts\";\nimport {\n POSITIONALS_KEY,\n readArgInfo,\n readPositionals,\n schemaShape,\n type ArgsSchema,\n type Positionals,\n} from \"./schema.ts\";\n\nexport interface ParsedCommand<S extends ArgsSchema> {\n args: output<S>;\n passthrough: string[];\n extraArgs: string[];\n}\n\ninterface OptionDefinition {\n readonly key: string;\n readonly kebabName: string;\n readonly takesValue: boolean;\n}\n\ninterface OptionModel {\n readonly definitions: Map<string, OptionDefinition>;\n readonly parserOptions: Record<string, { type: \"string\" | \"boolean\"; short?: string }>;\n readonly aliases: Set<string>;\n}\n\ninterface KnownOptionToken {\n readonly kind: \"known-option\";\n readonly index: number;\n readonly definition: OptionDefinition;\n readonly value: string | boolean;\n}\n\ninterface UnknownOptionToken {\n readonly kind: \"unknown-option\";\n readonly index: number;\n readonly value: string;\n}\n\ninterface PositionalToken {\n readonly kind: \"positional\";\n readonly index: number;\n readonly value: string;\n}\n\ntype CommandToken = KnownOptionToken | UnknownOptionToken | PositionalToken;\n\nexport interface TokenizedCommandArgs {\n readonly tokens: readonly CommandToken[];\n readonly passthrough: string[];\n readonly issues: readonly IndexedIssue[];\n readonly help: boolean;\n readonly helpIndex: number | undefined;\n readonly version: boolean;\n readonly versionIndex: number | undefined;\n}\n\ninterface IndexedIssue {\n readonly index: number;\n readonly issue: ZittyIssue;\n}\n\nfunction optionModel(command: AnyCommandDef): OptionModel {\n const definitions = new Map<string, OptionDefinition>();\n const parserOptions: OptionModel[\"parserOptions\"] = {};\n const allAliases = new Set<string>();\n\n if (command.args) {\n for (const [key, schema] of Object.entries(schemaShape(command.args))) {\n if (key === POSITIONALS_KEY) continue;\n const alias = command.argOptions?.[key]?.alias;\n const aliases = alias ? aliasList(alias) : [];\n const definition: OptionDefinition = {\n key,\n kebabName: kebabCase(key),\n takesValue: readArgInfo(schema).takesValue,\n };\n const type = definition.takesValue ? \"string\" : \"boolean\";\n for (const name of new Set([key, definition.kebabName])) {\n definitions.set(name, definition);\n parserOptions[name] = { type };\n }\n for (const alias of aliases) {\n allAliases.add(alias);\n definitions.set(alias, definition);\n parserOptions[alias] = { type, short: alias };\n }\n }\n }\n return { definitions, parserOptions, aliases: allAliases };\n}\n\nfunction commandPositionals(command: AnyCommandDef): Positionals | undefined {\n return command.args ? readPositionals(schemaShape(command.args)) : undefined;\n}\n\nfunction displayName(command: AnyCommandDef, path: readonly PropertyKey[]): string {\n const [first, index] = path;\n if (typeof first !== \"string\") return \"arguments\";\n if (first !== POSITIONALS_KEY) return `--${kebabCase(first)}`;\n const positionals = commandPositionals(command);\n if (!positionals || typeof index !== \"number\") return \"arguments\";\n const item = positionals.items[index];\n if (item) return `<${item.label}>`;\n return positionals.rest ? `<${positionals.rest.label}...>` : \"arguments\";\n}\n\nexport function tokenizeCommandArgs(\n command: AnyCommandDef,\n rawArgs: readonly string[],\n): TokenizedCommandArgs {\n const model = optionModel(command);\n const separator = rawArgs.indexOf(\"--\");\n const beforeSeparator = separator === -1 ? [...rawArgs] : rawArgs.slice(0, separator);\n const passthrough = separator === -1 ? [] : rawArgs.slice(separator + 1);\n const negativeIndexes = new Set<number>();\n const valuedNegativeIndexes = new Set<number>();\n const issues: IndexedIssue[] = [];\n let recordedMissingValue = false;\n const parserArgs = beforeSeparator.map((raw, index) => {\n if (!raw.startsWith(\"--no-\")) return raw;\n const equals = raw.indexOf(\"=\");\n const name = raw.slice(5, equals === -1 ? undefined : equals);\n const definition = model.definitions.get(name);\n if (!definition || definition.takesValue) return raw;\n negativeIndexes.add(index);\n if (equals !== -1) {\n valuedNegativeIndexes.add(index);\n return `--${definition.key}${raw.slice(equals)}`;\n }\n return `--${definition.key}`;\n });\n\n const parsed = parseNodeArgs({\n args: parserArgs,\n options: model.parserOptions,\n allowPositionals: true,\n strict: false,\n tokens: true,\n });\n const tokens: CommandToken[] = [];\n const unknownIndexes = new Set<number>();\n let help = false;\n let helpIndex: number | undefined;\n let version = false;\n let versionIndex: number | undefined;\n for (const token of parsed.tokens) {\n if (token.kind === \"positional\") {\n tokens.push({ kind: \"positional\", index: token.index, value: token.value });\n continue;\n }\n if (token.kind !== \"option\") continue;\n const isHelp =\n token.rawName === \"--help\" || (token.rawName === \"-h\" && !model.aliases.has(\"h\"));\n const isVersion =\n token.rawName === \"--version\" || (token.rawName === \"-v\" && !model.aliases.has(\"v\"));\n if (isHelp) {\n help = true;\n helpIndex ??= token.index;\n }\n if (isVersion) {\n version = true;\n versionIndex ??= token.index;\n }\n const definition = model.definitions.get(token.name);\n if (definition) {\n if (\n definition.takesValue &&\n (token.value === undefined ||\n (!token.inlineValue && token.value.startsWith(\"-\") && token.value !== \"-\"))\n ) {\n if (!recordedMissingValue) {\n issues.push({\n index: token.index,\n issue: { arg: `--${definition.kebabName}`, message: \"requires a value\" },\n });\n recordedMissingValue = true;\n }\n }\n let value: string | boolean = token.value ?? true;\n if (!definition.takesValue) {\n if (valuedNegativeIndexes.has(token.index)) {\n const rawName = beforeSeparator[token.index]!.split(\"=\", 1)[0]!;\n issues.push({\n index: token.index,\n issue: { arg: rawName, message: \"does not accept a value\" },\n });\n value = false;\n } else if (negativeIndexes.has(token.index)) {\n value = false;\n } else if (token.inlineValue) {\n if (token.value === \"true\") value = true;\n else if (token.value === \"false\") value = false;\n else {\n issues.push({\n index: token.index,\n issue: { arg: token.rawName, message: \"expects true or false\" },\n });\n }\n }\n }\n tokens.push({\n kind: \"known-option\",\n index: token.index,\n definition,\n value: definition.takesValue ? (token.value ?? \"\") : value,\n });\n } else if (!unknownIndexes.has(token.index)) {\n unknownIndexes.add(token.index);\n tokens.push({\n kind: \"unknown-option\",\n index: token.index,\n value: beforeSeparator[token.index]!,\n });\n }\n }\n\n return {\n tokens,\n passthrough,\n issues,\n help,\n helpIndex,\n version,\n versionIndex,\n };\n}\n\nexport function tokenizedBefore(\n tokenized: TokenizedCommandArgs,\n endIndex: number,\n): TokenizedCommandArgs {\n const help = tokenized.helpIndex !== undefined && tokenized.helpIndex < endIndex;\n const version = tokenized.versionIndex !== undefined && tokenized.versionIndex < endIndex;\n return {\n tokens: tokenized.tokens.filter((token) => token.index < endIndex),\n passthrough: [],\n issues: tokenized.issues.filter(({ index }) => index < endIndex),\n help,\n helpIndex: help ? tokenized.helpIndex : undefined,\n version,\n versionIndex: version ? tokenized.versionIndex : undefined,\n };\n}\n\nfunction zodIssues(command: AnyCommandDef, issues: readonly $ZodIssue[]): ZittyIssue[] {\n return issues.map((issue) => ({\n arg: displayName(command, issue.path),\n message: issue.message,\n issue,\n }));\n}\n\nexport async function parseCommandArgs<S extends ArgsSchema>(\n command: CommandDef<S>,\n rawArgs: string[],\n tokenized: TokenizedCommandArgs = tokenizeCommandArgs(command, rawArgs),\n): Promise<ParsedCommand<S>> {\n if (tokenized.issues.length > 0) {\n throw new ZittyValidationError(\n command,\n tokenized.issues.map(({ issue }) => issue),\n );\n }\n\n const input: Record<string, string | boolean | string[] | undefined> = {};\n const positionals = commandPositionals(command);\n const positionalValues: string[] = [];\n if (positionals) input[POSITIONALS_KEY] = positionalValues;\n const extraArgs: string[] = [];\n for (const token of tokenized.tokens) {\n if (token.kind === \"known-option\") input[token.definition.key] = token.value;\n else if (token.kind === \"unknown-option\") extraArgs.push(token.value);\n else if (\n positionals &&\n (positionals.rest || positionalValues.length < positionals.items.length)\n ) {\n positionalValues.push(token.value);\n } else extraArgs.push(token.value);\n }\n\n if (!command.args)\n return { args: input as output<S>, passthrough: tokenized.passthrough, extraArgs };\n const result = await safeParseAsync(command.args, input);\n if (!result.success)\n throw new ZittyValidationError(command, zodIssues(command, result.error.issues));\n return { args: result.data, passthrough: tokenized.passthrough, extraArgs };\n}\n\n/**\n * Parse and validate one command segment without routing, builtin handling, or output.\n */\nexport async function parseArgs<S extends ArgsSchema>(\n command: CommandDef<S>,\n argv: string[],\n): Promise<output<S>> {\n return (await parseCommandArgs(command, argv)).args;\n}\n","import type { AnyCommandDef } from \"./command.ts\";\nimport { aliasList, kebabCase } from \"./options.ts\";\nimport { POSITIONALS_KEY, readArgInfo, readPositionals, schemaShape } from \"./schema.ts\";\n\nfunction details(\n description: string | undefined,\n required: boolean,\n defaultValue: unknown,\n choices: readonly string[] | undefined,\n): string {\n const parts = [description];\n if (choices) parts.push(`(Choices: ${choices.join(\", \")})`);\n if (required) parts.push(\"(Required)\");\n if (defaultValue !== undefined) parts.push(`(Default: ${formatDefault(defaultValue)})`);\n return parts.filter(Boolean).join(\" \");\n}\n\nfunction formatDefault(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\" || typeof value === \"bigint\") {\n return value.toString();\n }\n if (value === null) return \"null\";\n return JSON.stringify(value) ?? \"undefined\";\n}\n\nfunction heading(command: AnyCommandDef): string | undefined {\n const identity = command.name\n ? `${command.name}${command.version ? ` v${command.version}` : \"\"}`\n : command.version\n ? `v${command.version}`\n : undefined;\n if (command.description && identity) return `${command.description} (${identity})`;\n return command.description ?? identity;\n}\n\nfunction formatUsage(command: AnyCommandDef): string {\n const lines: string[] = [];\n const title = heading(command);\n if (title) lines.push(title, \"\");\n\n const shape = command.args ? schemaShape(command.args) : {};\n const positionals = readPositionals(shape);\n const items = positionals?.items ?? [];\n const rest = positionals?.rest;\n const syntax = items.map(({ schema, label }) =>\n readArgInfo(schema).required ? `<${label}>` : `[${label}]`,\n );\n if (rest) syntax.push(`[${rest.label}...]`);\n if (command.subCommands && Object.keys(command.subCommands).length > 0) syntax.push(\"<COMMAND>\");\n lines.push(\n `USAGE ${command.name ?? \"command\"} [OPTIONS]${syntax.length ? ` ${syntax.join(\" \")}` : \"\"}`,\n );\n\n if (items.length > 0 || rest) {\n lines.push(\"\", \"ARGUMENTS\", \"\");\n for (const { schema, label } of items) {\n const info = readArgInfo(schema);\n lines.push(\n ` ${label} ${details(info.description, info.required, info.defaultValue, info.choices)}`.trimEnd(),\n );\n }\n if (rest) {\n const info = readArgInfo(rest.schema);\n lines.push(\n ` ${rest.label}... ${details(info.description, false, undefined, info.choices)}`.trimEnd(),\n );\n }\n }\n\n const options = Object.entries(shape).filter(([key]) => key !== POSITIONALS_KEY);\n if (options.length > 0) {\n lines.push(\"\", \"OPTIONS\", \"\");\n for (const [key, schema] of options) {\n const info = readArgInfo(schema);\n const argOptions = command.argOptions?.[key];\n const prefix = argOptions?.alias\n ? `${aliasList(argOptions.alias)\n .map((item) => `-${item}`)\n .join(\", \")}, `\n : \"\";\n const valueName = argOptions?.valueName ?? kebabCase(key);\n const long = `--${kebabCase(key)}${info.takesValue ? `=<${valueName}>` : \"\"}`;\n lines.push(\n ` ${prefix}${long} ${details(info.description, info.required, info.defaultValue, info.choices)}`.trimEnd(),\n );\n if (!info.takesValue && argOptions?.negatedDescription) {\n lines.push(` --no-${kebabCase(key)} ${argOptions.negatedDescription}`);\n }\n }\n }\n\n const visibleSubCommands = Object.entries(command.subCommands ?? {}).filter(\n ([, child]) => !child.hidden,\n );\n if (visibleSubCommands.length > 0) {\n lines.push(\"\", \"COMMANDS\", \"\");\n for (const [name, child] of visibleSubCommands) {\n lines.push(` ${name} ${child.description ?? \"\"}`.trimEnd());\n }\n }\n return lines.join(\"\\n\");\n}\n\nexport type UsageWriter = (text: string) => void | Promise<void>;\n\nexport interface ShowUsageOptions {\n write?: UsageWriter;\n}\n\nexport type UsageOutput = Required<Pick<ShowUsageOptions, \"write\">>;\n\nexport type ShowUsageCallback = (\n command: AnyCommandDef,\n output: UsageOutput,\n) => void | Promise<void>;\n\n/** Generate usage text without writing to stdout or stderr. */\nexport async function renderUsage(command: AnyCommandDef): Promise<string> {\n return formatUsage(command);\n}\n\n/** Generate and write usage text, awaiting asynchronous writers. */\nexport async function showUsage(\n command: AnyCommandDef,\n options: ShowUsageOptions = {},\n): Promise<void> {\n const text = await renderUsage(command);\n return (options.write ?? console.log)(text);\n}\n","import type { ArgsSchema } from \"./schema.ts\";\nimport type { AnyCommandDef, CommandDef } from \"./command.ts\";\nimport { ZittyUsageError } from \"./errors.ts\";\nimport {\n parseCommandArgs,\n tokenizedBefore,\n tokenizeCommandArgs,\n type TokenizedCommandArgs,\n} from \"./parser.ts\";\nimport { showUsage, type ShowUsageCallback } from \"./usage.ts\";\n\ninterface CommandSegment {\n command: AnyCommandDef;\n rawArgs: string[];\n tokenized: TokenizedCommandArgs;\n}\n\ninterface ResolvedCommand extends CommandSegment {\n parents: CommandSegment[];\n segments: CommandSegment[];\n}\n\nfunction namedChild(child: AnyCommandDef, name: string): AnyCommandDef {\n return child.name ? child : { ...child, name };\n}\n\nfunction resolveCommand(root: AnyCommandDef, rawArgs: string[]): ResolvedCommand {\n let command = root;\n let remaining = rawArgs;\n const parents: ResolvedCommand[\"parents\"] = [];\n const segments: ResolvedCommand[\"segments\"] = [];\n\n for (;;) {\n const tokenized = tokenizeCommandArgs(command, remaining);\n const index = command.subCommands\n ? tokenized.tokens.find((token) => token.kind === \"positional\")?.index\n : undefined;\n if (index === undefined) {\n const segment = { command, rawArgs: remaining, tokenized };\n segments.push(segment);\n return { ...segment, parents, segments };\n }\n const name = remaining[index]!;\n const child = command.subCommands?.[name];\n if (!child) throw new ZittyUsageError(`Unknown command \\`${name}\\``, command);\n const prefix = remaining.slice(0, index);\n const segment = { command, rawArgs: prefix, tokenized: tokenizedBefore(tokenized, index) };\n parents.push(segment);\n segments.push(segment);\n command = namedChild(child, name);\n remaining = remaining.slice(index + 1);\n }\n}\n\nasync function executeCommand<S extends ArgsSchema>(\n root: CommandDef<S>,\n argv: string[],\n displayUsage: ShowUsageCallback,\n): Promise<unknown> {\n const resolved = resolveCommand(root, argv);\n if (resolved.segments[0]?.tokenized.version) {\n console.log(root.version ?? \"\");\n return undefined;\n }\n if (resolved.segments.some(({ tokenized }) => tokenized.help)) {\n await displayUsage(resolved.command, { write: (text) => console.log(text) });\n return undefined;\n }\n\n for (const parent of resolved.parents) {\n await parseCommandArgs(parent.command, parent.rawArgs, parent.tokenized);\n }\n const parsed = await parseCommandArgs(resolved.command, resolved.rawArgs, resolved.tokenized);\n if (!resolved.command.run) {\n throw new ZittyUsageError(\"No command specified\", resolved.command);\n }\n return resolved.command.run({ ...parsed, cmd: resolved.command, rawArgs: resolved.rawArgs });\n}\n\nexport async function runCommand<S extends ArgsSchema>(\n root: CommandDef<S>,\n argv: string[],\n): Promise<unknown> {\n return executeCommand(root, argv, showUsage);\n}\n\nexport interface RunMainOptions {\n onError?: (error: unknown) => void | Promise<void>;\n argv?: string[];\n showUsage?: ShowUsageCallback;\n}\n\nexport async function runMain<S extends ArgsSchema>(\n command: CommandDef<S>,\n options: RunMainOptions = {},\n): Promise<void> {\n const displayUsage = options.showUsage ?? showUsage;\n try {\n await executeCommand(command, options.argv ?? process.argv.slice(2), displayUsage);\n } catch (error) {\n if (options.onError) {\n await options.onError(error);\n process.exit(1);\n }\n try {\n if (error instanceof ZittyUsageError) {\n console.error(error.message);\n await displayUsage(error.command, { write: (text) => console.error(text) });\n } else {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n }\n } finally {\n process.exit(1);\n }\n }\n}\n\nexport function createMain<S extends ArgsSchema>(\n command: CommandDef<S>,\n options: RunMainOptions = {},\n): (argv?: string[]) => Promise<void> {\n return (argv) => runMain(command, argv === undefined ? options : { ...options, argv });\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hacksh",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^24",
|
|
17
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
18
|
+
"typescript": "^7.0.2",
|
|
19
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.8",
|
|
20
|
+
"vite-plus": "0.2.8",
|
|
21
|
+
"zod": "^4.6.7"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"zod": "^4.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devEngines": {
|
|
27
|
+
"packageManager": {
|
|
28
|
+
"name": "pnpm",
|
|
29
|
+
"version": "11.20.0",
|
|
30
|
+
"onFail": "download"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=22.18.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "vp pack",
|
|
38
|
+
"check": "vp check",
|
|
39
|
+
"test": "vp test",
|
|
40
|
+
"test:packed": "node scripts/test-packed-consumers.mjs",
|
|
41
|
+
"ready": "vp run check && vp run test && vp run build"
|
|
42
|
+
}
|
|
6
43
|
}
|