diff --git a/CHANGELOG.md b/CHANGELOG.md index a89a1c4..dc1fc96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [1.0.2] - 2026-08-09 + +### Added + +- `placeholder?: string` flag descriptor field: overrides the `` placeholder in the auto-generated usage string (e.g. ``) for a value-taking flag. + ## [1.0.1] - 2026-08-09 ### Added diff --git a/README.md b/README.md index 4b0262e..8da83cd 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,12 @@ cli.parse(argv, overrideSchema); A schema without `.optional()`/`.default()` is required - omitting the flag is a parse error, and it's shown without brackets in the auto-generated usage string. `.optional()`/`.default()` short-circuit on a missing flag before any coercion/transform runs, so an omitted flag resolves to `undefined`/the default rather than failing (e.g. on `Number(undefined)`). +The `` placeholder shown for value-taking flags can be overridden per-flag with `placeholder`: + +```ts +dir: { schema: z.string(), placeholder: "dir" } // --dir +``` + **Booleans**: `negatable: true` only applies to a (possibly `.optional()`/`.default()`-wrapped) `z.boolean()` and throws at `defineCli()` time on anything else. `z.stringbool()` parses `"true"`/`"1"`/`"yes"`/`"on"`/`"y"`/`"enabled"` (case-insensitive) as `true` and their negatives as `false` - don't reach for `z.coerce.boolean()` instead, it's plain JS truthiness, so `z.coerce.boolean().parse("false")` is `true` (any non-empty string is truthy). A union of boolean literals doesn't behave like `z.boolean()` either: only a bare `z.boolean()` registers a presence-style parseArgs boolean, so `z.union([z.literal(true), z.literal(false)])` is registered as a value-taking string flag, and fails against the literal string `"true"` it would actually receive. ```ts diff --git a/package-lock.json b/package-lock.json index d275073..63426ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zod-cli-flags", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zod-cli-flags", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "devDependencies": { "@types/node": "^22", diff --git a/package.json b/package.json index c1ae2aa..7ed750f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zod-cli-flags", - "version": "1.0.1", + "version": "1.0.2", "description": "Define a CLI's flags as a single Zod schema and parse argv into a typed result.", "type": "module", "main": "./dist/index.js", diff --git a/src/defineCli.test.ts b/src/defineCli.test.ts index dd2f935..bcd3c77 100644 --- a/src/defineCli.test.ts +++ b/src/defineCli.test.ts @@ -271,6 +271,20 @@ describe("defineCli - positionals", () => { if (result.success) expect(result.positionals).toEqual(["one", "two"]); }); + it("substitutes a flag's placeholder into the auto-generated usage", () => { + const cli = defineCli({ + flags: { + output: { schema: z.string(), placeholder: "dir" }, + tag: { + schema: z.array(z.string()).default([]), + multiple: true, + placeholder: "tag", + }, + }, + }); + expect(cli.usage).toBe("Usage: --output [--tag ...]"); + }); + it("prepends the label before the flags when positionals carry one", () => { const cli = defineCli({ flags: { output: { schema: z.string() } }, diff --git a/src/types.ts b/src/types.ts index 9384704..9510f80 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,8 @@ export interface FlagDescriptor { negatable?: boolean; /** Shown in the auto-generated usage string. */ description?: string; + /** Value placeholder in the auto-generated usage string, e.g. "dir" for . Defaults to "value". */ + placeholder?: string; } export type FlagDescriptors = Record>; diff --git a/src/usage.ts b/src/usage.ts index b27d74e..4ef3e4d 100644 --- a/src/usage.ts +++ b/src/usage.ts @@ -5,6 +5,7 @@ interface UsageFlag { multiple?: boolean; negatable?: boolean; description?: string; + placeholder?: string; }; isBoolean: boolean; isOptional: boolean; @@ -26,7 +27,10 @@ export function buildUsage( } else if (isBoolean) { core = alias; } else { - const value = descriptor.multiple ? "..." : ""; + const placeholder = descriptor.placeholder ?? "value"; + const value = descriptor.multiple + ? `<${placeholder}>...` + : `<${placeholder}>`; core = `${alias} ${value}`; }