vadimdemedes / pastel

🎨 Next.js-like framework for CLIs made with Ink
https://term.ink/pastel
MIT License
2.2k stars 36 forks source link

Option `alias` doesn't work in combination with `zod.default()`/`zod.optional()` #65

Open ulken opened 1 year ago

ulken commented 1 year ago

I'm trying to define an alias for an option, but it's not working if used with neither zod.default() nor zod.optional().

Try it out: https://codesandbox.io/p/devbox/infallible-jones-ys358t?file=/string-default-alias/source/commands/index.tsx:6,1

Run npm run build (maybe followed by npm link) and then da -h.

Works 👍

export const options = zod.object({
    name: zod
        .string()
        .describe(option({alias: 'n', description: 'Name'})),
});

Outputs:

❭  sda -h       
Usage: sda [options]

Options:
  -n, --name <name>  Name
  -v, --version      Show version number
  -h, --help         Show help

Doesn't work 👎

export const options = zod.object({
    name: zod
        .string()
        .default('Stranger')
        .describe(option({alias: 'n', description: 'Name'})),
});

and

export const options = zod.object({
    name: zod
        .string()
        .optional()
        .describe(option({alias: 'n', description: 'Name'})),
});

Outputs:

❭  sda -h       
Usage: sda [options]

Options:
  --name [name]  Name (default: 1)
  -v, --version  Show version number
  -h, --help     Show help

(default only in the first case, obviously)

ulken commented 1 year ago

Same thing happens with zod.number(), so not isolated to zod.string().

ulken commented 1 year ago

@vadimdemedes I started looking into it. Even though I lack knowledge of Zod's internals, judging by generate-options.ts#L66C3-L89C52:

Code

```ts const description = getDescription(optionSchema.description); let valueDescription = getValueDescription(optionSchema.description); let isOptional = isOptionalByDefault; // Unwrap zod.string().optional() if (optionSchema instanceof ZodOptional) { isOptional = true; optionSchema = optionSchema._def.innerType; } // Unwrap zod.string().optional().default() if (optionSchema instanceof ZodDefault) { isOptional = true; defaultValue = optionSchema._def.defaultValue(); optionSchema = optionSchema._def.innerType; } // Unwrap zod.string().default().optional() if (optionSchema instanceof ZodOptional) { isOptional = true; optionSchema = optionSchema._def.innerType; } const alias = getAlias(optionSchema.description); let flag = `--${name}`; ```

my best guess is by reassigning optionSchema to _def.innerType, we lose the special __parsel prefix of description, causing the config lookup to "fail".

getAlias() is the only config lookup from there on, so I suppose it supports that theory?

Potential fixes:

  1. Move getAlias() before the reassignment
  2. Store the original "raw" description value and pass that to getAlias()

I might be missing something, though.

ulken commented 12 months ago

@vadimdemedes I'll gladly help out here if you could just give your thoughts on the matter.