API Reference
option
Create command options with schema validation
Creates a command option with schema validation and metadata.
Syntax
function option<S extends StandardSchemaV1>(
schema: S,
metadata?: {
short?: string;
description?: string;
repeatable?: boolean;
argumentKind?: "flag" | "value";
},
): CLIOption<S>;Parameters
schema
A Standard Schema v1 compatible schema (Zod, Valibot, etc.) that validates and transforms the option value.
metadata (optional)
Additional metadata for the option:
short- Short alias for the option (string, e.g.-e)description- Help text shown in --help outputrepeatable- Collect repeated flag occurrences and validate them as an arrayargumentKind- Controls whether the option takes a value ('value', the default) or is a boolean flag ('flag') that doesn't consume a following argument
Returns
A CLIOption object that can be used in command definitions.
Examples
Basic Options
import { option } from "@bunli/core";
import { z } from "zod";
// String option with default
const name = option(z.string().default("world"), { description: "Name to greet" });
// Number with validation
const port = option(z.coerce.number().int().min(1).max(65535).default(3000), {
short: "p",
description: "Port number",
});
// Boolean flag
const verbose = option(z.coerce.boolean().default(false), {
short: "v",
description: "Enable verbose output",
});
// Enum option
const env = option(z.enum(["dev", "staging", "prod"]), {
short: "e",
description: "Target environment",
});Using in Commands
import { defineCommand, option } from "@bunli/core";
import { z } from "zod";
export default defineCommand({
name: "serve",
description: "Start the server",
options: {
port: option(z.coerce.number().default(3000), { short: "p", description: "Port to listen on" }),
host: option(z.string().default("localhost"), { short: "h", description: "Host to bind to" }),
secure: option(z.coerce.boolean().default(false), { short: "s", description: "Use HTTPS" }),
},
handler: async ({ flags }) => {
// flags.port: number
// flags.host: string
// flags.secure: boolean
},
});Schema Types
Coercion
Use z.coerce for automatic type conversion from command-line strings:
// Coerce string to number
option(z.coerce.number()); // "123" → 123
// Coerce string to boolean
option(z.coerce.boolean()); // "true" → true, "false" → false
// Coerce string to date
option(z.coerce.date()); // "2024-01-01" → Date objectTransformations
Transform input values with custom logic:
// Parse JSON
const config = option(
z.string().transform((val) => JSON.parse(val)),
{ description: "JSON configuration" },
);
// Convert to uppercase
const env = option(
z.string().transform((val) => val.toUpperCase()),
{ description: "Environment name" },
);
// Parse comma-separated values
const tags = option(
z.string().transform((val) => val.split(",")),
{ description: "Comma-separated tags" },
);
// Parse file size with units
const size = option(
z
.string()
.regex(/^\d+[kmg]b?$/i)
.transform((val) => {
const match = val.match(/^(\d+)([kmg])b?$/i)!;
const [, num, unit] = match;
const multipliers = { k: 1024, m: 1024 ** 2, g: 1024 ** 3 };
return parseInt(num) * multipliers[unit.toLowerCase()];
}),
{ description: "Size limit (e.g., 512k, 1g)" },
);Complex Schemas
// Object validation
const server = option(
z
.string()
.transform((val) => JSON.parse(val))
.pipe(
z.object({
host: z.string(),
port: z.number(),
secure: z.boolean().optional(),
}),
),
{ description: "Server config as JSON" },
);
// Array with validation
const ignore = option(
z
.string()
.transform((val) => val.split(","))
.pipe(z.array(z.string().min(1))),
{ description: "Comma-separated ignore patterns" },
);
// Union types
const output = option(
z.union([z.literal("json"), z.literal("yaml"), z.literal("toml")]).default("json"),
{ description: "Output format" },
);Validation
Schemas are validated automatically when commands run:
const port = option(
z.coerce
.number()
.int("Port must be an integer")
.min(1, "Port must be at least 1")
.max(65535, "Port must be at most 65535"),
{ short: "p", description: "Port number" },
);
// $ my-cli serve --port abc
// Validation errors:
// --port:
// • Expected number, received nan
// $ my-cli serve --port 0
// Validation errors:
// --port:
// • Port must be at least 1
// $ my-cli serve --port 80000
// Validation errors:
// --port:
// • Port must be at most 65535Optional vs Required
// Required option (no default)
const input = option(z.string(), { description: "Input file" });
// Optional with default
const output = option(z.string().default("./output"), { description: "Output directory" });
// Truly optional (can be undefined)
const config = option(z.string().optional(), { description: "Config file path" });
// Nullable option
const template = option(z.string().nullable().default(null), { description: "Template name" });Standard Schema Support
The option function accepts any Standard Schema v1 compatible schema:
// Using Zod
import { z } from "zod";
option(z.string());
// Using Valibot
import * as v from "valibot";
option(v.pipe(v.string(), v.minLength(1)));
// Any Standard Schema v1 library
import { schema } from "any-standard-schema-lib";
option(schema.string());All options must have a schema. There are no "raw" options in Bunli - this ensures type safety and consistent validation across your CLI.
Best Practices
- Use descriptive names - Option names should clearly indicate their purpose
- Add descriptions - Always include helpful description text
- Use short flags wisely - Reserve single letters for commonly used options
- Provide defaults - Make options easier to use with sensible defaults
- Use coerce for CLI inputs -
z.coercehandles string-to-type conversion - Validate constraints - Add
.min(),.max(),.regex()etc. for validation - Transform when needed - Parse complex inputs with
.transform()
Common Patterns
File Paths
const file = option(
z.string().refine(
(val) => existsSync(val),
(val) => ({ message: `File not found: ${val}` }),
),
{ description: "Input file path" },
);URLs
const url = option(z.string().url("Must be a valid URL"), { description: "API endpoint" });Environment Variables
const apiKey = option(z.string().default(process.env.API_KEY || ""), {
description: "API key (or set API_KEY env var)",
});Multiple Values
// From repeated flags: --tag foo --tag bar
const tags = option(z.array(z.string()).default([]), {
description: "Tags (can be used multiple times)",
repeatable: true,
});
// From comma-separated: --tags foo,bar,baz
const tags = option(
z
.string()
.transform((val) => val.split(",").map((s) => s.trim()))
.pipe(z.array(z.string())),
{ description: "Comma-separated tags" },
);See Also
- defineCommand - Use options in commands
- Validation - Learn about validation
- Type Inference - How types are inferred