Cookbook Router
@cookbook/router-cli

Generation API

Contracts, register augmentation, manifest serialization, generated route modules, aggregate generation, and idempotent writes.

@cookbook/router-cli exposes generation APIs for producing route contracts, manifest JSON, register augmentation, generated runtime route modules, and aggregate artifacts.

import {
  generateContracts,
  generateManifest,
  generateRegister,
  generateRouterArtifacts,
  generateRoutesModule,
  serializeManifest,
  writeGeneratedFile,
} from '@cookbook/router-cli';

import type {
  CommandResult,
  GeneratedRouteTreeRuntimeOptions,
  ManifestRoute,
  RouteFileExport,
  RouteManifest,
} from '@cookbook/router-cli';

These APIs are lower-level than generateCommand(). Use them when embedding generation in tests, build tools, custom CLIs, or one-off integration pipelines.

generateContracts(routes, options?)

function generateContracts(
  routes: readonly RouteDefinition[],
  options?: DefineRoutesOptions | RouterPathOptions,
): string;

Generates the TypeScript contract file normally written to:

.cookbook-router/contracts.ts

Prop

Type

Generation validates the resolved tree with validateResolvedRouteTree().

When options is a DefineRoutesOptions object, supplied pathConstraints are registered before validation.

When options is a bare RouterPathOptions object, no custom constraints are supplied.

The output includes:

RouteParams
RouteParamsInput
RouteSearch
RouteSearchInput
RouteHash
RouteMeta
RoutePaths
RouteOutletContext
RouterContracts

The generated file begins with:

/* eslint-disable */
/* This file is generated by @cookbook/router-cli. Do not edit directly. */

and ends with:

/* eslint-enable */

Generated contract behavior

Path params:

Route path featureParsed contractInput contract
unconstrained paramstringstring
numeric built-in constraintnumbernumber
custom constraintstringstring
wildcardreadonly string[]`stringreadonly string[]`
optional paramoptional propertyoptional property

Search descriptors:

Descriptor behaviorParsed contractInput contract
required fieldrequired propertyrequired property
optional: trueoptional propertyoptional property
defaultrequired propertyoptional property
many: truereadonly arrayreadonly array

Hash descriptors:

DescriptorGenerated hash contract
no hash descriptornever
string hashstring
optional string hash`stringundefined`
enum hashunion of values
optional enum hashunion of values plus undefined
defaulted hashnon-optional parsed value

Paths:

Route shapeGenerated RoutePaths value
route with fullPathstring literal full path
pathless routenever

Metadata:

  • no metadata becomes {};
  • metadata keys become optional properties;
  • metadata value types are rendered with JavaScript typeof values.

Outlet context:

  • every generated route currently maps to {}.

Slot routes are included because generation flattens primary children and normalized slot routes.

generateRegister()

function generateRegister(): string;

Generates the declaration file normally written to:

.cookbook-router/register.d.ts

The output imports generated contracts:

import type { RouterContracts } from './contracts';

It augments:

@cookbook/router
@cookbook/router-react

It does not augment @cookbook/router-cli.

Generated shape:

declare module '@cookbook/router' {
  interface Register {
    contracts: RouterContracts;
  }
}

declare module '@cookbook/router-react' {
  interface Register {
    contracts: RouterContracts;
  }
}

export {};

Use this file by ensuring TypeScript includes the generated output directory.

generateManifest(routes, options?)

interface ManifestRoute {
  readonly id: string;
  readonly path?: string;
  readonly parentId?: string;
  readonly index: boolean;
  readonly url?: RouterUrlOptions;
}

interface RouteManifest {
  readonly routes: readonly ManifestRoute[];
}

function generateManifest(
  routes: readonly RouteDefinition[],
  options?: DefineRoutesOptions | RouterPathOptions,
): RouteManifest;

Generates a JSON-serializable manifest model.

Prop

Type

Prop

Type

The manifest includes normalized primary routes and normalized slot routes.

It does not serialize:

  • views;
  • layouts as functions/components;
  • middleware;
  • lifecycle hooks;
  • preload functions;
  • module preload functions;
  • path constraint implementations.

Route-level url options are preserved because manifest consumers may need URL policy.

serializeManifest(manifest)

function serializeManifest(
  manifest: RouteManifest,
): string;

Serializes a manifest with stable JSON formatting.

Output rules:

  • JSON.stringify(manifest, null, 2);
  • trailing newline;
  • no custom sorting beyond the route order produced by normalization and flattening.
{
  "routes": [
    {
      "id": "home",
      "path": "/",
      "index": true
    }
  ]
}

The serialized string ends with \n.

generateRoutesModule(sources, routesPath?, options?, runtimeOptions?)

function generateRoutesModule(
  sources: readonly CliRouteSource[],
  routesPath?: string,
  options?: DefineRoutesOptions,
  runtimeOptions?: GeneratedRouteTreeRuntimeOptions,
): string;

Renders the generated route module normally written to:

.cookbook-router/routes.ts

CliRouteSource is the structural source shape used by the implementation. It is not exported from the package root as a named public type.

Equivalent public shape:

interface CliRouteSource {
  readonly path: string;
  readonly routes: readonly RouteDefinition[];
  readonly routeOptions?: DefineRoutesOptions;
  readonly routeExports?: readonly RouteFileExport[];
}

RouteFileExport is exported:

interface RouteFileExport {
  readonly exportName: string;
  readonly kind: 'route' | 'routes' | 'routeTree';
}

Prop

Type

Empty source behavior

Calling generateRoutesModule() with no importable route exports returns an empty generated route module:

export const routes = [] as const;

Aggregate generation does not call this function unless at least one loaded route source exposes route exports.

Static re-export behavior

A single compatible static routes export can be re-exported directly.

This preserves the original route module and its route-options metadata.

Direct re-export is used only when all of these are true:

  • there is exactly one import binding;
  • the binding kind is routes;
  • there is exactly one source;
  • the source has exactly one route export;
  • no runtime imports are required;
  • source route options match the requested generation options;
  • the source route tree does not contain nested absolute child paths.

The generated module imports the original export as routes and re-exports it.

Wrapped module behavior

When direct re-export is not safe, the generator wraps imports.

If any import binding is a single defineRoute() declaration, it emits defineRouteTree().

import { defineRouteTree } from '@cookbook/router/route-config';

If all import bindings are route arrays or route trees, it emits defineRoutes().

import { defineRoutes } from '@cookbook/router/route-config';

It always imports these types for wrapped output:

import type {
  RouteDefinition,
  RouteModulePreload,
} from '@cookbook/router/route-config';

Module preload preservation

Wrapped route modules attach a generated modulePreload helper to imported routes.

The generated helper points back to the physical source module with a dynamic import:

() => import('../src/routes').then(() => undefined)

Known TypeScript and JavaScript extensions are stripped from import specifiers:

.ts
.tsx
.mts
.cts
.js
.jsx
.mjs
.cjs

Runtime path-constraint import preservation

runtimeOptions.pathConstraints can be rendered into generated routes.ts.

interface RuntimeImportReference {
  readonly path: string;
  readonly exportName: string;
}

interface GeneratedRouteTreeRuntimeOptions {
  readonly pathConstraints?: RuntimeImportReference;
}

The generated route module imports the named export and passes it to defineRoutes() or defineRouteTree():

pathConstraints: __cookbookPathConstraints,

Source-level path constraints inside multiple or rewrapped route source files cannot be safely preserved.

Error:

Generated routes.ts cannot safely preserve pathConstraints declared inside multiple or rewrapped route source files. Move custom pathConstraints to cookbook-router.config.ts as a named import from a runtime-safe module, or generate from a single static route-tree source so the original defineRoutes()/defineRouteTree() export can be re-exported directly.

generateRouterArtifacts(options)

interface GenerateRouterArtifactsOptions
  extends CliRouteOptions {}

function generateRouterArtifacts(
  options: GenerateRouterArtifactsOptions,
): Promise<CommandResult>;

Runs aggregate generation.

GenerateRouterArtifactsOptions is the source-level interface. It is not exported from the package root as a named public type. Use CliRouteOptions when annotating call sites.

const options: CliRouteOptions = {
  routes,
  outDir: '.cookbook-router',
};

Generation resolves route input from the same pipeline as generateCommand().

Work order:

  1. Resolve effective route options from direct options, route files, config, globs, output directory, and runtime route options.
  2. Resolve generated output paths.
  3. Validate generated output does not overwrite a route source file.
  4. Resolve and validate route input.
  5. Register route path constraints from resolved route options.
  6. Create the output directory.
  7. Determine whether routes.ts is applicable.
  8. Render and write applicable outputs.
  9. Return CommandResult.

Route input resolution and validation happen before artifact writes begin.

The output directory can still be created before a later rendering or filesystem failure.

Applicable artifacts

When no generated route module is applicable:

contracts.ts
manifest.json
register.d.ts

When route sources expose importable route exports:

routes.ts
contracts.ts
manifest.json
register.d.ts

routes.ts is conditional.

contracts.ts, manifest.json, and register.d.ts are always applicable for successful aggregate generation.

File order

Without routes.ts, files is:

[
  '.cookbook-router/contracts.ts',
  '.cookbook-router/manifest.json',
  '.cookbook-router/register.d.ts',
]

With routes.ts, files is:

[
  '.cookbook-router/routes.ts',
  '.cookbook-router/contracts.ts',
  '.cookbook-router/manifest.json',
  '.cookbook-router/register.d.ts',
]

changedFiles preserves the same write order, but includes only files whose contents changed.

If all applicable files are already byte-for-byte identical:

{
  ok: true,
  files: [
    '.cookbook-router/contracts.ts',
    '.cookbook-router/manifest.json',
    '.cookbook-router/register.d.ts',
  ],
  errors: [],
  changedFiles: [],
}

Write safety

Generated artifact paths are fixed inside outDir.

The generator refuses to write generated files over route source files.

Error:

Refusing to write generated router artifacts over route source file "...".

It also refuses generated artifact paths that escape the resolved output directory.

Error:

Refusing to write generated router artifact outside outDir: "...".

writeGeneratedFile(fs, path, contents)

function writeGeneratedFile(
  fs: CliFileSystem,
  path: string,
  contents: string,
): Promise<boolean>;

Writes one generated file only when contents changed.

Prop

Type

Return value:

ResultMeaning
falseExisting file contents are byte-for-byte identical. No write occurred.
trueFile was missing, unreadable, or different, and writeFile() completed.

A read failure is treated as “file absent.” The write is still attempted.

A write failure rejects the returned promise.

Export inventory for this page

@cookbook/router-cli exports these generation values:

generateContracts
generateManifest
generateRegister
generateRouterArtifacts
generateRoutesModule
serializeManifest
writeGeneratedFile

It exports these generation-related public types:

ManifestRoute
RouteManifest
GeneratedRouteTreeRuntimeOptions
RuntimeImportReference
RouteFileExport
CommandResult
CliRouteOptions
CliFileSystem

The package root does not export these source-level implementation types as named public types:

GenerateRouterArtifactsOptions
CliRouteSource

generateRoutesModule() and generateRouterArtifacts() are still public functions. Their option/source shapes are structural.

Where this bites

register.d.ts does not augment @cookbook/router-cli

Generated registration covers:

@cookbook/router
@cookbook/router-react

It does not cover:

@cookbook/router-cli

The CLI consumes route definitions and emits contracts. Runtime packages consume the generated augmentation.

routes.ts is conditional

A valid route tree can produce:

contracts.ts
manifest.json
register.d.ts

without producing:

routes.ts

Direct routes input and JSON route files can generate contracts without providing importable runtime route exports.

Empty low-level route module output is not aggregate behavior

This low-level call can return:

export const routes = [] as const;

Aggregate generation does not intentionally write an empty routes.ts just because routes are empty. It writes routes.ts only when route sources expose route exports.

Source-local path constraints can block generated routes.ts

Contracts and manifest can validate with source-local constraints.

A generated runtime route module also needs a runtime-safe import for those constraints unless it can re-export the original static route tree.

Move shared path constraints to a named relative import in config when generated routes.ts must wrap route sources.

Idempotent writes are byte-for-byte

writeGeneratedFile() skips only exact string matches.

Changing formatting, comments, ordering, or trailing newlines counts as a changed file.

Validation happens before artifact writes, not before every side effect

Invalid route graphs do not overwrite previous valid artifacts.

The output directory may still be created before later generation or filesystem failures.

On this page