Parameters
Parameters are the values a route reads from the command line. There are three kinds:
option()reads a named value, such as--limit 30.toggle()reads an on or off switch, such as--draft.argument()reads a positional value, such as thebombshell-dev/routeringh repo clone bombshell-dev/router.
Each parameter belongs to the route that declares it, and its value appears in that route’s model.
Add an option
Section titled “Add an option”option(name("body")) reads --body <value> or --body=<value>. The flag is the kebab-case form of the name, so option(name("bodyFile")) reads --body-file. When the flag is absent, the value is undefined.
Validate with a schema
Section titled “Validate with a schema”Without a schema, a parameter’s type is unknown. Add schema() with any Standard Schema library to give it a type, a default, or a requirement:
import var process: NodeJS.Process
process from "node:process";import { function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command, function name<N extends string>(name: N): Definition<N>
name, function option<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
option, function parse<const R extends AnyRoute>(route: R, input: Input): Parse<R>
parse, function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema } from "@bomb.sh/router";import * as import z
z from "zod";
const const app: Route<"create", "help" | "execute", readonly [Done<{ body: unknown; base: string; title: string;}, []>]>
app = command<"create", readonly [ModelElement<ParamModel<"body", unknown>>, ModelElement<ParamModel<"base", string>>, ModelElement<ParamModel<"title", string>>]>(start: Definition<...>, elements_0: ModelElement<ParamModel<"body", unknown>>, elements_1: ModelElement<ParamModel<"base", string>>, elements_2: ModelElement<ParamModel<"title", string>>): Route<...>
command( name<"create">(name: "create"): Definition<"create">
name("create"), option<"body", readonly []>(named: Definition<"body">): ModelElement<ParamModel<"body", unknown>>
option(name<"body">(name: "body"): Definition<"body">
name("body")), option<"base", readonly [TransformElement<SchemaTransform<string>>]>(named: Definition<"base">, elements_0: TransformElement<SchemaTransform<string>>): ModelElement<...>
option(name<"base">(name: "base"): Definition<"base">
name("base"), schema<z.ZodDefault<z.ZodString>>(schema: z.ZodDefault<z.ZodString>): TransformElement<SchemaTransform<string>>
schema(import z
z.function string(params?: z.RawCreateParams & { coerce?: true;}): z.ZodStringexport string
string().ZodType<string, ZodStringDef, string>.default(def: string): z.ZodDefault<z.ZodString> (+1 overload)
default("main"))), option<"title", readonly [TransformElement<SchemaTransform<string>>]>(named: Definition<"title">, elements_0: TransformElement<SchemaTransform<string>>): ModelElement<...>
option(name<"title">(name: "title"): Definition<"title">
name("title"), schema<z.ZodString>(schema: z.ZodString): TransformElement<SchemaTransform<string>>
schema(import z
z.function string(params?: z.RawCreateParams & { coerce?: true;}): z.ZodStringexport string
string())),);
const const result: Parse<Route<"create", "help" | "execute", readonly [Done<{ body: unknown; base: string; title: string;}, []>]>>
result = parse<Route<"create", "help" | "execute", readonly [Done<{ body: unknown; base: string; title: string;}, []>]>>(route: Route<"create", "help" | "execute", readonly [Done<{ body: unknown; base: string; title: string;}, []>]>, input: Input): Parse<...>
parse(const app: Route<"create", "help" | "execute", readonly [Done<{ body: unknown; base: string; title: string;}, []>]>
app, { argv: string[]
argv: var process: NodeJS.Process
process.NodeJS.Process.argv: string[]
The process.argv property returns an array containing the command-line
arguments passed when the Node.js process was launched. The first element will
be
execPath
. See process.argv0 if access to the original value
of argv[0] is needed. The second element will be the path to the JavaScript
file being executed. The remaining elements will be any additional command-line
arguments.
For example, assuming the following script for process-args.js:
import { argv } from 'node:process';
// print process.argvargv.forEach((val, index) => { console.log(`${index}: ${val}`);});
Launching the Node.js process as:
Terminal window node process-args.js one two=three four
Would generate the output:
0: /usr/local/bin/node1: /Users/mjr/work/node/process-args.js2: one3: two=three4: four
argv.Array<string>.slice(start?: number, end?: number): string[]
Returns a copy of a section of an array.
For both start and end, a negative index can be used to indicate an offset from the end of the array.
For example, -2 refers to the second to last element of the array.
slice(2) });
if (const result: Parse<Route<"create", "help" | "execute", readonly [Done<{ body: unknown; base: string; title: string;}, []>]>>
result.ok: boolean
ok && const result: Help<"/"> | Execute<"/", { "/": { body: unknown; base: string; title: string; };}>
result.Intent<M extends Method, P extends RoutePath>.method: "help" | "execute"
method === "execute") { const result: Execute<"/", { "/": { body: unknown; base: string; title: string; };}>
result.Execute<"/", { "/": { body: unknown; base: string; title: string; }; }>.model: { body: unknown; base: string; title: string;}
model.body: unknown
body; // unknown const result: Execute<"/", { "/": { body: unknown; base: string; title: string; };}>
result.Execute<"/", { "/": { body: unknown; base: string; title: string; }; }>.model: { body: unknown; base: string; title: string;}
model.base: string
base; // string const result: Execute<"/", { "/": { body: unknown; base: string; title: string; };}>
result.Execute<"/", { "/": { body: unknown; base: string; title: string; }; }>.model: { body: unknown; base: string; title: string;}
model.title: string
title; // string}A schema that rejects undefined makes the parameter required. Here --title is required, so create alone fails with unprocessable-content:
title: Invalid input: expected string, received undefinedInvalid values fail the same way, and they never reach your application.
Know how values are decoded
Section titled “Know how values are decoded”Command-line text is decoded before the schema sees it. The router tries a number first, then the original text, and the schema picks the first candidate it accepts. Without a schema, the first candidate wins:
| Input | No schema | schema(z.string()) |
|---|---|---|
--milestone 2024 | 2024 | "2024" |
--milestone 007 | 7 | "007" |
--milestone 1e3 | 1000 | "1e3" |
--milestone v2 | "v2" | "v2" |
To keep text that looks like a number, such as a milestone or a version, use a string schema.
Add a toggle
Section titled “Add a toggle”toggle(name("draft")) reads a switch. It is true with --draft, false with --no-draft, and false when absent. Its type is always boolean, so it needs no schema. --draft=false is not accepted.
Read positional arguments
Section titled “Read positional arguments”argument(name("repository")) reads one positional value. A second positional value, such as the docs in clone bombshell-dev/router docs, fails with unexpected: `docs` . As with options, add a schema to make the argument required or typed:
import { function argument<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
argument, function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command, function name<N extends string>(name: N): Definition<N>
name, function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema } from "@bomb.sh/router";import * as import z
z from "zod";
const const clone: Route<"clone", "help" | "execute", readonly [Done<{ repository: string;}, []>]>
clone = command<"clone", readonly [ModelElement<ParamModel<"repository", string>>]>(start: Definition<"clone">, elements_0: ModelElement<ParamModel<"repository", string>>): Route<...>
command( name<"clone">(name: "clone"): Definition<"clone">
name("clone"), argument<"repository", readonly [TransformElement<SchemaTransform<string>>]>(named: Definition<"repository">, elements_0: TransformElement<SchemaTransform<string>>): ModelElement<...>
argument(name<"repository">(name: "repository"): Definition<"repository">
name("repository"), schema<z.ZodString>(schema: z.ZodString): TransformElement<SchemaTransform<string>>
schema(import z
z.function string(params?: z.RawCreateParams & { coerce?: true;}): z.ZodStringexport string
string())),);Accept repeated values
Section titled “Accept repeated values”multiple() turns a parameter into a list. An option collects every occurrence, so --label bug --label docs reads ["bug", "docs"]. An argument collects every positional value, so gist create a.ts b.ts reads ["a.ts", "b.ts"]. With multiple(), the schema validates the whole list:
import { function argument<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
argument, function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command, function multiple(): TransformElement<MultipleTransform>
multiple, function name<N extends string>(name: N): Definition<N>
name, function option<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
option, function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema,} from "@bomb.sh/router";import * as import z
z from "zod";
const const prCreate: Route<"create", "help" | "execute", readonly [Done<{ label: string[];}, []>]>
prCreate = command<"create", readonly [ModelElement<ParamModel<"label", string[]>>]>(start: Definition<"create">, elements_0: ModelElement<ParamModel<"label", string[]>>): Route<...>
command( name<"create">(name: "create"): Definition<"create">
name("create"), option<"label", readonly [TransformElement<MultipleTransform>, TransformElement<SchemaTransform<string[]>>]>(named: Definition<...>, elements_0: TransformElement<...>, elements_1: TransformElement<...>): ModelElement<...>
option( name<"label">(name: "label"): Definition<"label">
name("label"), function multiple(): TransformElement<MultipleTransform>
multiple(), schema<z.ZodDefault<z.ZodArray<z.ZodString, "many">>>(schema: z.ZodDefault<z.ZodArray<z.ZodString, "many">>): TransformElement<SchemaTransform<string[]>>
schema(import z
z.array<z.ZodString>(schema: z.ZodString, params?: z.RawCreateParams): z.ZodArray<z.ZodString, "many">export array
array(import z
z.function string(params?: z.RawCreateParams & { coerce?: true;}): z.ZodStringexport string
string()).ZodType<string[], ZodArrayDef<ZodString>, string[]>.default(def: string[]): z.ZodDefault<z.ZodArray<z.ZodString, "many">> (+1 overload)
default([])), ),);
const const gistCreate: Route<"create", "help" | "execute", readonly [Done<{ files: string[];}, []>]>
gistCreate = command<"create", readonly [ModelElement<ParamModel<"files", string[]>>]>(start: Definition<"create">, elements_0: ModelElement<ParamModel<"files", string[]>>): Route<...>
command( name<"create">(name: "create"): Definition<"create">
name("create"), argument<"files", readonly [TransformElement<MultipleTransform>, TransformElement<SchemaTransform<string[]>>]>(named: Definition<...>, elements_0: TransformElement<...>, elements_1: TransformElement<...>): ModelElement<...>
argument( name<"files">(name: "files"): Definition<"files">
name("files"), function multiple(): TransformElement<MultipleTransform>
multiple(), schema<z.ZodArray<z.ZodString, "many">>(schema: z.ZodArray<z.ZodString, "many">): TransformElement<SchemaTransform<string[]>>
schema(import z
z.array<z.ZodString>(schema: z.ZodString, params?: z.RawCreateParams): z.ZodArray<z.ZodString, "many">export array
array(import z
z.function string(params?: z.RawCreateParams & { coerce?: true;}): z.ZodStringexport string
string())), ),);Both values are typed string[]. Without --label, the default gives an empty list.
Rename a flag
Section titled “Rename a flag”cli() replaces the flags an option reads. After cli(["--repo", "-R"]), an option named repository reads --repo and -R, and --repository is no longer accepted:
import { function cli(names: readonly string[], options?: CLIOptions): IdentityElement<AnyParam>
cli, function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command, function name<N extends string>(name: N): Definition<N>
name, function option<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
option, function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema } from "@bomb.sh/router";import * as import z
z from "zod";
const const app: Route<"list", "help" | "execute", readonly [Done<{ repository: string; web: unknown;}, []>]>
app = command<"list", readonly [ModelElement<ParamModel<"repository", string>>, ModelElement<ParamModel<"web", unknown>>]>(start: Definition<"list">, elements_0: ModelElement<ParamModel<"repository", string>>, elements_1: ModelElement<ParamModel<"web", unknown>>): Route<...>
command( name<"list">(name: "list"): Definition<"list">
name("list"), option<"repository", readonly [IdentityElement<AnyParam>, TransformElement<SchemaTransform<string>>]>(named: Definition<"repository">, elements_0: IdentityElement<...>, elements_1: TransformElement<...>): ModelElement<...>
option(name<"repository">(name: "repository"): Definition<"repository">
name("repository"), function cli(names: readonly string[], options?: CLIOptions): IdentityElement<AnyParam>
cli(["--repo", "-R"]), schema<z.ZodString>(schema: z.ZodString): TransformElement<SchemaTransform<string>>
schema(import z
z.function string(params?: z.RawCreateParams & { coerce?: true;}): z.ZodStringexport string
string())), option<"web", readonly [IdentityElement<AnyParam>]>(named: Definition<"web">, elements_0: IdentityElement<AnyParam>): ModelElement<...>
option(name<"web">(name: "web"): Definition<"web">
name("web"), function cli(names: readonly string[], options?: CLIOptions): IdentityElement<AnyParam>
cli(["--web", "-w"], { CLIOptions.switch?: true
switch: true })),);For most on or off flags, use toggle() instead. It reads --web and --no-web, defaults to false, and is always a boolean.
{ switch: true } makes an option take no value. --web or -w reads true, the value is undefined when absent, and its type is unknown unless you add a schema. Use it only when you need something a toggle cannot do, such as a short flag like -w.
Read a different environment variable
Section titled “Read a different environment variable”When you pass the environment to parse(), each parameter also reads a variable named after its route and its name. The repo option of the /pr/list route reads PR_LIST_REPO. env() replaces that name:
import var process: NodeJS.Process
process from "node:process";import { function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command, function env(key: string): IdentityElement<AnyParam>
env, function name<N extends string>(name: N): Definition<N>
name, function option<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
option, function parse<const R extends AnyRoute>(route: R, input: Input): Parse<R>
parse, function route<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<RouteZero<N>, E>): Materialize<Fold<RouteZero<N>, Normalize<E>>>
route, function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema,} from "@bomb.sh/router";import * as import z
z from "zod";
const const app: Route<"gh", "help" | "execute", readonly [Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{ repo: string | undefined;}, []>]>]>]>]>]>
app = command<"gh", readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{ repo: string | undefined;}, []>]>]>]>]>(start: Definition<"gh">, elements_0: Route<...>): Route<...>
command( name<"gh">(name: "gh"): Definition<"gh">
name("gh"), route<"pr", readonly [Route<"list", "help" | "execute", readonly [Done<{ repo: string | undefined;}, []>]>]>(start: Definition<"pr">, elements_0: Route<"list", "help" | "execute", readonly [...]>): Route<...>
route( name<"pr">(name: "pr"): Definition<"pr">
name("pr"), command<"list", readonly [ModelElement<ParamModel<"repo", string | undefined>>]>(start: Definition<"list">, elements_0: ModelElement<ParamModel<"repo", string | undefined>>): Route<...>
command( name<"list">(name: "list"): Definition<"list">
name("list"), option<"repo", readonly [IdentityElement<AnyParam>, TransformElement<SchemaTransform<string | undefined>>]>(named: Definition<"repo">, elements_0: IdentityElement<...>, elements_1: TransformElement<...>): ModelElement<...>
option( name<"repo">(name: "repo"): Definition<"repo">
name("repo"), function env(key: string): IdentityElement<AnyParam>
env("GH_REPO"), schema<z.ZodOptional<z.ZodString>>(schema: z.ZodOptional<z.ZodString>): TransformElement<SchemaTransform<string | undefined>>
schema(import z
z.function string(params?: z.RawCreateParams & { coerce?: true;}): z.ZodStringexport string
string().ZodType<string, ZodStringDef, string>.optional(): z.ZodOptional<z.ZodString>
optional()), ), ), ),);
const const result: Parse<Route<"gh", "help" | "execute", readonly [Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{ repo: string | undefined;}, []>]>]>]>]>]>>
result = parse<Route<"gh", "help" | "execute", readonly [Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{ repo: string | undefined;}, []>]>]>]>]>]>>(route: Route<...>, input: Input): Parse<...>
parse(const app: Route<"gh", "help" | "execute", readonly [Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{ repo: string | undefined;}, []>]>]>]>]>]>
app, { argv: string[]
argv: var process: NodeJS.Process
process.NodeJS.Process.argv: string[]
The process.argv property returns an array containing the command-line
arguments passed when the Node.js process was launched. The first element will
be
execPath
. See process.argv0 if access to the original value
of argv[0] is needed. The second element will be the path to the JavaScript
file being executed. The remaining elements will be any additional command-line
arguments.
For example, assuming the following script for process-args.js:
import { argv } from 'node:process';
// print process.argvargv.forEach((val, index) => { console.log(`${index}: ${val}`);});
Launching the Node.js process as:
Terminal window node process-args.js one two=three four
Would generate the output:
0: /usr/local/bin/node1: /Users/mjr/work/node/process-args.js2: one3: two=three4: four
argv.Array<string>.slice(start?: number, end?: number): string[]
Returns a copy of a section of an array.
For both start and end, a negative index can be used to indicate an offset from the end of the array.
For example, -2 refers to the second to last element of the array.
slice(2), envs?: readonly EnvSource[]
envs: [{ EnvSource.name: string
name: "process", EnvSource.value: Readonly<Record<string, string | undefined>>
value: var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The process.env property returns an object containing the user environment.
See environ(7).
An example of this object looks like:
{ TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node'}
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other Worker threads.
In other words, the following example would not work:
Terminal window node -e 'process.env.foo = "bar"' && echo $foo
While the following will:
import { env } from 'node:process';
env.foo = 'bar';console.log(env.foo);
Assigning a property on process.env will implicitly convert the value
to a string. This behavior is deprecated. Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
import { env } from 'node:process';
env.test = null;console.log(env.test);// => 'null'env.test = undefined;console.log(env.test);// => 'undefined'
Use delete to delete a property from process.env.
import { env } from 'node:process';
env.TEST = 1;delete env.TEST;console.log(env.TEST);// => undefined
On Windows operating systems, environment variables are case-insensitive.
import { env } from 'node:process';
env.TEST = 1;console.log(env.test);// => 1
Unless explicitly specified when creating a Worker instance,
each Worker thread has its own copy of process.env, based on its
parent thread's process.env, or whatever was specified as the env option
to the Worker constructor. Changes to process.env will not be visible
across Worker threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons. On Windows, a copy of process.env on a Worker instance operates in a case-sensitive manner
unlike the main thread.
env }],});With env("GH_REPO"), GH_REPO=bombshell-dev/router gh pr list sets the repository, and PR_LIST_REPO is ignored.