Error handling
Which failures throw ZodlineError, which throw ZodError, and how to turn them into good CLI output.
processConfig throws on every failure it cannot resolve. Three error types come out of it, and telling them
apart is what lets you print something useful.
The three error types
| Failure | Thrown |
|---|---|
No command given, and no defaultCommand |
ZodlineError |
| Unknown command | ZodlineError |
| Unknown option | ZodlineError |
| Option failed schema validation | ZodError (from Zod) |
| Positional args failed schema validation | Error, message prefixed Argument validation failed: |
ZodlineError is exported from the package and extends Error with name: 'ZodlineError'.
A complete entry point
#!/usr/bin/env node
import { z } from 'zod';
import { processConfig, ZodlineError } from 'zodline';
import { config } from './config.js';
try {
const result = processConfig(config, process.argv.slice(2));
await result.command.action(result.options, result.args);
} catch (error) {
if (error instanceof ZodlineError) {
console.error(error.message);
process.exit(2);
}
if (error instanceof z.ZodError) {
for (const issue of error.issues) {
console.error(`--${issue.path.join('.')}: ${issue.message}`);
}
process.exit(2);
}
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
Separating exit codes is worth the few lines: 2 for “you typed it wrong”, 1 for “the command failed”.
Unknown options
Any flag that is not a schema key (camelCase or kebab-case), not an alias, and not help/version is
rejected before validation:
my-cli greet --name World --shout
# Unknown option: --shout
The check also applies to commands that declare no options at all — there is no permissive mode.
my-cli version --anything
# Unknown option: --anything
Validation errors
Option failures surface as Zod issues, with the schema key as the path:
my-cli serve --port abc
# --port: Invalid input: expected number, received NaN
Argument failures are flattened into one message by zodline itself, prefixed with the failing path:
my-cli copy src.txt
# Argument validation failed: : Too small: expected array to have >=2 items
The path is the position index when one argument is wrong, and empty when the array as a whole is — a too-short tuple reports a length error, not a missing element.
Errors from your action
Anything your action throws propagates to the caller — zodline is not in the call stack at that point.
The try/catch above already covers it, which is another benefit of
owning the call site.
Improving the message
Zod’s own messages are the ones users read, so it pays to set them where the default is unhelpful:
z.object({
port: z.coerce
.number({ message: 'Expected a port number, for example --port 8080' })
.int()
.min(1)
.max(65535)
.describe('Port to listen on'),
});
Reference: API
Every export, with its full signature.