pastel icon indicating copy to clipboard operation
pastel copied to clipboard

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

Open ulken opened this issue 1 year ago • 3 comments

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 avatar Nov 27 '23 10:11 ulken

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

ulken avatar Nov 27 '23 10:11 ulken

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

Code

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 avatar Nov 27 '23 13:11 ulken

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

ulken avatar Dec 05 '23 19:12 ulken