CLI commands and command APIs
Exact commands, flags, programmatic options, result behavior, watch behavior, and embedding APIs.
@cookbook/router-cli exposes two binary names and one public package entrypoint.
Binary aliases:
cbr
cookbook-routerProgrammatic entrypoint:
import {
createCliProgram,
generateCommand,
initCommand,
manifestCommand,
resolveRoutes,
runCli,
shouldRunCli,
validateCommand,
watchCommand,
} from '@cookbook/router-cli';
import type {
CliFileSystem,
CliRouteOptions,
CliRunnerOptions,
CommandResult,
GenerateOptions,
InitOptions,
ManifestOptions,
ValidateOptions,
WatchCommandOptions,
WatchHandle,
} from '@cookbook/router-cli';This page covers the CLI command layer. Generator, config, route-file loading, manifest, and build-runner APIs are documented on their owning CLI API pages.
Command inventory
The CLI exposes these commands:
init
generate
generate --watch
manifest
validateThere is no standalone watch CLI command. Watch mode is generate --watch.
Programmatic watch mode is exposed through watchCommand().
Shared command-line flags
init, generate, manifest, and validate all accept the shared route-input flags.
| Flag | Meaning |
|---|---|
--config <file> | Explicit router config path. Otherwise config discovery checks cookbook-router.config.*. |
--routes <file> | Route source file or glob. May be repeated. Overrides config routeFiles. |
--out-dir <dir> | Generated output directory override. |
--cwd <dir> | Project directory used to resolve config, routes, and output paths. |
--json | Print the CommandResult as formatted JSON. |
--verbose | Include additional diagnostic context where supported. |
generate also accepts:
| Flag | Meaning |
|---|---|
--watch | Watch config and route inputs, then regenerate after changes. |
Common command shape:
cbr generate --routes "src/**/*.route.tsx" --out-dir .cookbook-routerJSON result shape:
cbr validate --routes "src/**/*.route.tsx" --jsonVersion flag:
cbr --versionThe version flag also has a short alias:
cbr -vCommand output behavior
Non-JSON success with files writes:
Generated 3 files.
.cookbook-router/contracts.ts
.cookbook-router/manifest.json
.cookbook-router/register.d.tsNon-JSON success with files but no changed contents writes:
Router artifacts are up to date.Non-JSON success with no files writes:
Routes are valid.Non-JSON failure writes joined errors to stderr and sets exit code 1.
JSON mode writes the CommandResult object to stdout.
{
"ok": true,
"files": [],
"errors": []
}CommandResult
interface CommandResult {
readonly ok: boolean;
readonly files: readonly string[];
readonly errors: readonly string[];
readonly changedFiles?: readonly string[];
}Prop
Type
Use ok as the success check.
Do not use files.length.
validateCommand() succeeds with files: [].
Shared programmatic route options
interface CliRouteOptions extends CliOutputOptions {
readonly routes?: readonly RouteDefinition[];
readonly routeFiles?: readonly string[];
readonly routeFileWatchPaths?: readonly string[];
readonly routeOptions?: DefineRoutesOptions;
readonly configFile?: string;
readonly cwd?: string;
readonly verbose?: boolean;
readonly allowEmptyRouteFiles?: boolean;
readonly runtimeRouteOptions?: GeneratedRouteTreeRuntimeOptions;
}Prop
Type
CliOutputOptions is:
interface CliOutputOptions {
readonly outDir?: string;
readonly fs?: CliFileSystem;
}Prop
Type
initCommand(options?)
interface InitOptions {
readonly cwd?: string;
readonly fs?: CliFileSystem;
readonly routeFiles?: string | readonly string[];
readonly outDir?: string;
readonly configFile?: string;
readonly starterRouteFile?: string;
readonly skipGenerate?: boolean;
readonly verbose?: boolean;
}
function initCommand(
options?: InitOptions,
): Promise<CommandResult>;Bootstraps a project.
It can create or update:
- router config;
- starter route file;
- route scripts in an existing
package.json; - TypeScript
includeentries in an existingtsconfig.json; - output directory;
- generated artifacts, unless
skipGenerateistrue.
Prop
Type
Default config file:
cookbook-router.config.tsDefault output directory:
.cookbook-routerDefault route glob:
src/**/*.route.{ts,tsx,js,jsx,mts,cts,mjs,cjs}If an app directory exists, initCommand() uses app as the source root.
If a package script suggests Vite, Next, Remix, or React Router and app/app.tsx exists, app is also selected.
Starter route default:
src/root.route.tsxor:
app/root.route.tsxwhen the inferred source root is app.
initCommand() refuses to overwrite an existing discovered config.
Diagnostic:
Refusing to overwrite existing router config "cookbook-router.config.ts".When package.json exists, missing scripts are added:
{
"routes:generate": "cbr generate",
"routes:watch": "cbr generate --watch",
"routes:validate": "cbr validate"
}With a custom config file, the scripts include --config.
{
"routes:generate": "cbr generate --config router.config.ts",
"routes:watch": "cbr generate --config router.config.ts --watch",
"routes:validate": "cbr validate --config router.config.ts"
}The CLI init command exposes only the shared flags.
starterRouteFile and skipGenerate are programmatic options. They are not current CLI flags.
generateCommand(options)
interface GenerateOptions extends CliRouteOptions {}
function generateCommand(
options: GenerateOptions,
): Promise<CommandResult>;Runs aggregate generation.
It writes the generated artifacts applicable to the route source.
Always applicable:
contracts.ts
manifest.json
register.d.tsConditional:
routes.tsroutes.ts is generated when route files expose static route exports that can be composed into a generated runtime route module.
Successful result without routes.ts:
{
ok: true,
files: [
'.cookbook-router/contracts.ts',
'.cookbook-router/manifest.json',
'.cookbook-router/register.d.ts',
],
errors: [],
changedFiles: [
'.cookbook-router/contracts.ts',
'.cookbook-router/manifest.json',
'.cookbook-router/register.d.ts',
],
}Successful result with routes.ts:
{
ok: true,
files: [
'.cookbook-router/routes.ts',
'.cookbook-router/contracts.ts',
'.cookbook-router/manifest.json',
'.cookbook-router/register.d.ts',
],
errors: [],
changedFiles: [
'.cookbook-router/routes.ts',
'.cookbook-router/contracts.ts',
'.cookbook-router/manifest.json',
'.cookbook-router/register.d.ts',
],
}If contents are already stable, changedFiles is empty.
Route input resolution and validation happen before artifact writes begin. Invalid route graphs do not overwrite previous valid artifacts.
Filesystem failures can still happen during the write phase.
manifestCommand(options)
interface ManifestOptions extends CliRouteOptions {}
function manifestCommand(
options: ManifestOptions,
): Promise<CommandResult>;Generates only manifest.json.
It uses the same effective route input as generateCommand().
It:
- resolves config and route-file globs;
- loads route files when needed;
- registers route path constraints;
- validates the resolved route graph;
- refuses to write generated output over a route source file;
- writes only
manifest.json.
Successful result:
{
ok: true,
files: [
'.cookbook-router/manifest.json',
],
errors: [],
changedFiles: [
'.cookbook-router/manifest.json',
],
}When the manifest content is already identical:
{
ok: true,
files: [
'.cookbook-router/manifest.json',
],
errors: [],
changedFiles: [],
}validateCommand(options)
interface ValidateOptions extends CliRouteOptions {}
function validateCommand(
options: ValidateOptions,
): Promise<CommandResult>;Validates the resolved route graph without writing artifacts.
It:
- resolves config and route-file globs;
- loads route files when needed;
- registers custom path constraints;
- validates route definitions with effective path options;
- returns no files on success.
Successful result:
{
ok: true,
files: [],
errors: [],
}Failure result:
{
ok: false,
files: [],
errors: [
'No routes or routeFiles were provided.',
],
}watchCommand(options)
interface WatchCommandOptions extends WatchOptions {}
interface WatchOptions extends CliRouteOptions {
readonly debounceMs?: number;
readonly onChange?: (
result: CommandResult,
) => void | Promise<void>;
}
interface WatchHandle {
readonly initial: Promise<CommandResult>;
close(): void;
}
function watchCommand(
options: WatchCommandOptions,
): WatchHandle;Starts watch-mode generation and returns a handle immediately.
watchCommand() is synchronous. The initial generation is represented by handle.initial.
Prop
Type
Watch mode requires at least one route-file input after config resolution.
Diagnostic:
Watch mode requires at least one route file. Pass --routes <file>.Watch mode also requires fs.watch.
Diagnostic:
Watch mode requires a file system with watch support.Watch setup observes:
- explicit route files;
- expanded route files;
- glob watch roots;
- resolved config file.
When config route patterns change, watcher roots are reconciled.
Rapid events are debounced and coalesced.
If a run is already executing, another change requests one more run after the current run finishes.
Invalid route graphs do not rewrite previous valid artifacts because generation validates before writing.
The watch handle does not expose intermediate promises. Observe later runs through onChange.
resolveRoutes(options)
function resolveRoutes(
options: CliRouteOptions,
): Promise<readonly RouteDefinition[]>;Resolves route input and returns only the route array.
It uses the same route-input pipeline as generation.
Resolution order:
- direct
options.routes; - explicit
options.routeFiles; - config-discovered route files.
It validates the resolved routes before returning.
If no route source exists, it rejects with:
No routes or routeFiles were provided.Use resolveRouteInput() when route options, source exports, or route-source metadata matter.
createCliProgram(options?)
function createCliProgram(
options?: {
readonly stdout?: (message: string) => void;
readonly stderr?: (message: string) => void;
readonly version?: string;
readonly setExitCode?: (code: number) => void;
},
): Command;Creates the Commander-backed CLI program without parsing arguments.
Prop
Type
The named source interfaces CreateCliProgramOptions and CliProgramIo are not exported from the package root.
Treat the inline option shape as the public command API.
runCli(argv, runnerOptions?)
interface CliRunnerOptions {
readonly stdout?: (message: string) => void;
readonly stderr?: (message: string) => void;
readonly version?: string;
}
function runCli(
argv: readonly string[],
runnerOptions?: CliRunnerOptions,
): Promise<number>;Runs the CLI command dispatcher and returns a process-style exit code.
Prop
Type
No arguments prints help and returns 0.
await runCli([]);
// 0Commander failures return Commander’s exit code.
Unexpected failures are written to stderr and return 1.
Command failures produce exit code 1 through setExitCode.
shouldRunCli(moduleUrl?, argv?)
function shouldRunCli(
moduleUrl?: string,
argv?: readonly string[],
): boolean;Returns true when the current module URL is the process entrypoint.
It compares:
fileURLToPath(moduleUrl);argv[1].
Both are resolved to absolute paths before comparison.
This is used by the binary wrapper so importing @cookbook/router-cli as a library does not execute the CLI.
Missing argv[1] returns false.
CliFileSystem
interface CliFileSystem {
readFile(path: string): Promise<string>;
writeFile(
path: string,
contents: string,
): Promise<void>;
mkdir(
path: string,
options?: {
readonly recursive?: boolean;
},
): Promise<void>;
readdir?(
path: string,
options?: {
readonly withFileTypes?: false;
},
): Promise<readonly string[]>;
stat?(path: string): Promise<{
readonly mtimeMs?: number;
readonly isDirectory?: () => boolean;
readonly isFile?: () => boolean;
}>;
watch?(
path: string,
listener: (
event: 'rename' | 'change',
filename: string | null,
) => void,
): {
close: () => void;
};
}Prop
Type
Programmatic commands can inject fs for tests and alternate runtimes.
Watch mode requires watch. Glob expansion still needs directory/stat support through the route-file discovery pipeline.
Export inventory for this page
@cookbook/router-cli exports these command values:
createCliProgram
generateCommand
initCommand
manifestCommand
resolveRoutes
runCli
shouldRunCli
validateCommand
watchCommandIt exports these command-related public types:
CliFileSystem
CliOutputOptions
CliRouteOptions
CommandResult
GenerateOptions
InitOptions
ManifestOptions
ValidateOptions
WatchCommandOptions
WatchHandle
WatchOptions
CliRunnerOptionsIt also exports resolveRouteInput, resolveRouteInputWithOptions, and ResolvedRouteInput; those are route-input APIs and should be documented on the route input/config page rather than treated as command wrappers.
Where this bites
watchCommand() is not a CLI command
This is wrong:
cbr watchUse:
cbr generate --watchor programmatic watch mode:
watchCommand({
routeFiles: ['src/**/*.route.tsx'],
});onChange receives the initial result
watchCommand() calls onChange for the initial generation and later reruns.
Do not assume the first callback is caused by a file change.
changedFiles is not always present
This is wrong:
if (result.changedFiles.length) {
// ...
}Use:
if (result.changedFiles?.length) {
// ...
}initCommand() currently does not return changedFiles.
files.length is not success
This is wrong:
if (result.files.length) {
// command succeeded
}Use:
if (result.ok) {
// command succeeded
}validateCommand() succeeds with no files.
initCommand() can return partial files on generation failure
initCommand() writes setup files before running generation.
If generation then fails, the result can be:
{
ok: false,
files: [
'cookbook-router.config.ts',
'src/root.route.tsx',
],
errors: [
'...',
],
}That means setup wrote files, not that the full command succeeded.
Custom route globs disable implicit starter creation
When routeFiles is provided to initCommand(), the implicit starter route is not created.
Use starterRouteFile programmatically when you want both custom route globs and a starter route.