first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
# parser
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `parser` module turns a raw `argv` token array into a structured `ParsedArgs` object with typed options, named positional arguments, and a `rest` passthrough channel. It is the low-level argument-parsing engine that higher-level command definitions build on, designed to model the behaviour CLI users expect from mature tools like `git`, `npm`, and `cargo`.
The module is deliberately imperative and framework-free: no reflection, no decorators, no hidden state. Every behaviour — type coercion, `--no-*` negation, short-flag resolution, choice validation, required-option checking — is enforced in a single linear pass over `argv`, and every failure mode raises a `CliError` with a stable code drawn from `CLI_CODES.PARSE.*` so callers can branch on machine-readable codes instead of message strings.
## Features
- Boolean flags (`--verbose`) and negation (`--no-verbose`)
- String/number/array options via `--key value`, `--key=value`, or short-flag (`-v`) forms
- Short-flag mapping via `short: "v"` on the option config
- Passthrough `rest` channel after `--`
- Variadic positionals (`<files...>`) collected as arrays
- Required positionals and required options with dedicated error codes
- Choice validation with `INVALID_VALUE`
- `allowUnknown: true` to silently ignore unrecognised flags
- Defaults for options and positionals
- Helper parsers for flag specs (`parseOptionFlags`) and positional specs (`parseArgumentSpec`)
## Usage: Basic Example
```ts
import { parseArgs } from "@suaveplan/cli/parser";
const result = parseArgs(["--verbose", "src/file.ts"], {
options: { verbose: { type: "boolean" } },
arguments: [{ name: "file", required: true, variadic: false }],
});
console.log(result.options.verbose); // true
console.log(result.positionals.file); // "src/file.ts"
```
## Usage: Advanced Example
```ts
import { parseArgs, parseOptionFlags, parseArgumentSpec } from "@suaveplan/cli/parser";
const flag = parseOptionFlags("-o, --output <file>", "Output file");
const posArg = parseArgumentSpec("<sources...>", "Source files");
const result = parseArgs(
["-o", "out.txt", "--format=json", "a.ts", "b.ts", "--", "--not-a-flag"],
{
options: {
[flag.name]: flag.config,
format: { type: "string", choices: ["json", "text"], default: "text" },
retries: { type: "number", default: 3 },
},
arguments: [posArg],
}
);
// result.options.output === "out.txt"
// result.options.format === "json"
// result.positionals.sources === ["a.ts", "b.ts"]
// result.rest === ["--not-a-flag"]
```
## API Reference
### `parseArgs(argv, config?)`
- `argv: string[]` — raw CLI tokens (without the node/bun prefix)
- `config?: ParserConfig` — optional options/arguments definitions, `allowUnknown`, `stopEarly`
- Returns `ParsedArgs` with `options`, `positionals`, and `rest`
- Throws `CliError` with codes `UNKNOWN_OPTION`, `MISSING_VALUE`, `INVALID_VALUE`, `MISSING_ARGUMENT`, `MISSING_REQUIRED_OPTION`
### `parseOptionFlags(flags, description?, defaultValue?)`
- `flags: string` — commander-style spec, e.g. `"-v, --verbose"` or `"--output <file>"`
- Returns `{ name, config }` where `name` is the camelCased long-flag name
- Throws `CliError(UNKNOWN_OPTION)` if no long flag is provided
### `parseArgumentSpec(spec, description?)`
- `spec: string` — `"<name>"`, `"[name]"`, `"<name...>"`, or `"[name...]"`
- Returns an `ArgumentConfig` with `required`, `variadic`, `name`, and optional `description`
## Implementation Notes
- Single-pass state machine: tokens are consumed left-to-right with a small `restMode` flag that flips when `--` is encountered. There are no lookaheads beyond peeking at `argv[i + 1]` to decide whether a flag's value is the next token.
- `--no-*` handling strips the literal `--no-` prefix (not the camelCased form) and then camelCases the remainder, so `--no-dry-run` correctly maps to the `dryRun` option.
- Values are only consumed as the next token when that token does not start with `-`, preventing `--foo --bar` from stealing `--bar` as the value of `--foo`.
- `number` type uses `Number(raw)` and rejects `NaN` so strings like `"abc"` raise `INVALID_VALUE` rather than silently becoming `NaN`.
- All errors are `CliError` instances carrying `context` metadata (option name, offending value, allowed choices) so UIs and logs can render actionable diagnostics without re-parsing message strings.
- Schemas mirror the TypeScript types exactly and are exported separately so callers can validate untrusted `ParserConfig` inputs at the boundary without pulling Zod into hot paths.