Zod Schema Validation
Demonstrates how to use Zod schemas for argument validation and transformation. Schemas are defined in separate files, making them reusable across commands and testable in isolation.
4 FILESTry in Playground
Deployment Schema
Define a schema for deployment configuration. The schema validates input and transforms it to add computed properties like requiresApproval based on the environment.
1import { z } from 'zod';
2
3/**
4 * Valid deployment environments.
5 */
6export const environments = ['development', 'staging', 'production'] as const;
7
8/**
9 * Deployment configuration schema.
10 * Validates and transforms deployment options.
11 */
12export const deployConfigSchema = z
13 .object({
14 env: z.enum(environments),
15 replicas: z.number().min(1).max(10),
16 dryRun: z.boolean().optional(),
17 })
18 .transform((config) => ({
19 ...config,
20 // Add computed properties based on environment
21 requiresApproval: config.env === 'production',
22 defaultTimeout: config.env === 'production' ? 300 : 60,
23 }));
24
25/**
26 * Type inferred from the schema output (after transform).
27 */
28export type DeployConfig = z.output<typeof deployConfigSchema>;User Schema
The user schema validates email format and normalizes it to lowercase. It also computes permission flags based on the user's role.
1import { z } from 'zod';
2
3/**
4 * User roles with their permission levels.
5 */
6export const roles = ['viewer', 'editor', 'admin'] as const;
7export type Role = (typeof roles)[number];
8
9/**
10 * User data schema with validation and normalization.
11 */
12export const userSchema = z
13 .object({
14 email: z.string().email('Invalid email format'),
15 role: z.enum(roles),
16 name: z.string().optional(),
17 })
18 .transform((user) => ({
19 ...user,
20 // Normalize email to lowercase
21 email: user.email.toLowerCase(),
22 // Compute permission flags based on role
23 canEdit: user.role === 'editor' || user.role === 'admin',
24 canAdmin: user.role === 'admin',
25 }));
26
27/**
28 * Type inferred from the schema output.
29 */
30export type User = z.output<typeof userSchema>;CLI with Zod Middleware
The main CLI applies zodMiddleware to each command. This validates parsed arguments against the schema and adds transformed properties to the args object.
1import { cli } from 'cli-forge';
2import { zodMiddleware } from 'cli-forge/middleware/zod';
3
4import { deployConfigSchema, environments } from './schemas/config';
5import { userSchema, roles } from './schemas/user';
6
7const app = cli('validated-cli')
8 .demandCommand()
9
10 .command('deploy', {
11 description: 'Deploy the application',
12 builder: (cmd) =>
13 cmd
14 .option('env', {
15 type: 'string',
16 description: 'Deployment environment',
17 choices: environments,
18 required: true,
19 })
20 .option('replicas', {
21 type: 'number',
22 description: 'Number of replicas',
23 default: 1,
24 })
25 .option('dryRun', {
26 type: 'boolean',
27 description: 'Preview deployment without executing',
28 })
29 .middleware(zodMiddleware(deployConfigSchema)),
30
31 handler: (args) => {
32 // TypeScript knows about transformed properties
33 console.log(`Deploying to ${args.env}...`);
34 console.log(` replicas: ${args.replicas}`);
35 console.log(` requiresApproval: ${args.requiresApproval}`);
36 console.log(` timeout: ${args.defaultTimeout}s`);
37
38 if (args.dryRun) {
39 console.log('Dry run complete.');
40 }
41 },
42 })
43
44 .command('user-info', {
45 description: 'Display user information',
46 builder: (cmd) =>
47 cmd
48 .option('email', {
49 type: 'string',
50 description: 'User email address',
51 required: true,
52 })
53 .option('role', {
54 type: 'string',
55 description: 'User role',
56 choices: roles,
57 required: true,
58 })
59 .option('name', {
60 type: 'string',
61 description: 'Display name',
62 })
63 .middleware(zodMiddleware(userSchema)),
64
65 handler: (args) => {
66 // Email is normalized, permission flags are computed
67 console.log(`User Information:`);
68 console.log(` Email: ${args.email}`);
69 console.log(` Role: ${args.role}`);
70 console.log(` Can Edit: ${args.canEdit}`);
71 console.log(` Can Admin: ${args.canAdmin}`);
72
73 if (args.name) {
74 console.log(` Name: ${args.name}`);
75 }
76 },
77 });
78
79export default app;
80
81if (require.main === module) {
82 app.forge();
83}How It Works
- CLI options are defined with basic types (
string,number,boolean) zodMiddlewarewraps a Zod schema and returns a middleware function- When the command runs, the middleware validates args against the schema
- If validation fails, Zod's error messages are displayed
- If validation passes, the transformed output (including computed properties) becomes the new args
Benefits
- Validation: Complex validation rules (email format, number ranges) with clear error messages
- Transformation: Normalize data and compute derived values
- Type Safety: TypeScript infers the output type from the schema, including transformed properties
- Reusability: Schemas can be shared across commands or used in other parts of your application
All Example Files
FILE EXPLORER
cli.ts
1import { cli } from 'cli-forge';
2import { zodMiddleware } from 'cli-forge/middleware/zod';
3
4import { deployConfigSchema, environments } from './schemas/config';
5import { userSchema, roles } from './schemas/user';
6
7const app = cli('validated-cli')
8 .demandCommand()
9
10 .command('deploy', {
11 description: 'Deploy the application',
12 builder: (cmd) =>
13 cmd
14 .option('env', {
15 type: 'string',
16 description: 'Deployment environment',
17 choices: environments,
18 required: true,
19 })
20 .option('replicas', {
21 type: 'number',
22 description: 'Number of replicas',
23 default: 1,
24 })
25 .option('dryRun', {
26 type: 'boolean',
27 description: 'Preview deployment without executing',
28 })
29 .middleware(zodMiddleware(deployConfigSchema)),
30
31 handler: (args) => {
32 // TypeScript knows about transformed properties
33 console.log(`Deploying to ${args.env}...`);
34 console.log(` replicas: ${args.replicas}`);
35 console.log(` requiresApproval: ${args.requiresApproval}`);
36 console.log(` timeout: ${args.defaultTimeout}s`);
37
38 if (args.dryRun) {
39 console.log('Dry run complete.');
40 }
41 },
42 })
43
44 .command('user-info', {
45 description: 'Display user information',
46 builder: (cmd) =>
47 cmd
48 .option('email', {
49 type: 'string',
50 description: 'User email address',
51 required: true,
52 })
53 .option('role', {
54 type: 'string',
55 description: 'User role',
56 choices: roles,
57 required: true,
58 })
59 .option('name', {
60 type: 'string',
61 description: 'Display name',
62 })
63 .middleware(zodMiddleware(userSchema)),
64
65 handler: (args) => {
66 // Email is normalized, permission flags are computed
67 console.log(`User Information:`);
68 console.log(` Email: ${args.email}`);
69 console.log(` Role: ${args.role}`);
70 console.log(` Can Edit: ${args.canEdit}`);
71 console.log(` Can Admin: ${args.canAdmin}`);
72
73 if (args.name) {
74 console.log(` Name: ${args.name}`);
75 }
76 },
77 });
78
79export default app;
80
81if (require.main === module) {
82 app.forge();
83}
84