Skip to content

Checkpoints and dynamic phases

Some routes cannot be fully configured, or even discovered, until your application performs I/O. parse() never performs I/O. Instead it pauses at a phase boundary and returns a step. Your application does the I/O, then calls resume() on the step with the result.

Two elements create a phase boundary:

  • checkpoint() pauses so you can load configuration, then resume with value sources.
  • dynamic(schema, extension) pauses so you can supply any value, then adds options or routes built from it.

The real gh reads its configuration from the directory in GH_CONFIG_DIR. Declare that option before checkpoint(), and everything after the checkpoint is parsed once the configuration is loaded:

import
var process: NodeJS.Process
process
from "node:process";
import {
function checkpoint(): DynamicElement<ValueSource[], ReturnType<typeof withValues>>
checkpoint
,
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 printErrors(result: MethodNotAllowed | UnprocessableContent): string
printErrors
,
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
,
type
type ValueSource = {
name: string;
value: unknown;
}
ValueSource
,
} from "@bomb.sh/router";
import * as
import z
z
from "zod";
const
const app: Route<"gh", "help" | "execute", readonly [Next<{
configDir: string;
}, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
limit: number;
}, []>]>]>]>]>]>
app
=
command<"gh", readonly [ModelElement<ParamModel<"configDir", string>>, DynamicElement<ValueSource[], IdentityElement<AnyRoute>>, Route<...>]>(start: Definition<...>, elements_0: ModelElement<ParamModel<"configDir", string>>, elements_1: DynamicElement<...>, elements_2: Route<...>): Route<...>
command
(
name<"gh">(name: "gh"): Definition<"gh">
name
("gh"),
option<"configDir", readonly [IdentityElement<AnyParam>, TransformElement<SchemaTransform<string>>]>(named: Definition<"configDir">, elements_0: IdentityElement<...>, elements_1: TransformElement<...>): ModelElement<...>
option
(
name<"configDir">(name: "configDir"): Definition<"configDir">
name
("configDir"),
function env(key: string): IdentityElement<AnyParam>
env
("GH_CONFIG_DIR"),
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.ZodString
export string
string
().
ZodType<string, ZodStringDef, string>.default(def: string): z.ZodDefault<z.ZodString> (+1 overload)
default
("~/.config/gh")),
),
function checkpoint(): DynamicElement<ValueSource[], ReturnType<typeof withValues>>
checkpoint
(),
route<"pr", readonly [Route<"list", "help" | "execute", readonly [Done<{
limit: number;
}, []>]>]>(start: Definition<"pr">, elements_0: Route<"list", "help" | "execute", readonly [Done<{
limit: number;
}, []>]>): Route<...>
route
(
name<"pr">(name: "pr"): Definition<"pr">
name
("pr"),
command<"list", readonly [ModelElement<ParamModel<"limit", number>>]>(start: Definition<"list">, elements_0: ModelElement<ParamModel<"limit", number>>): Route<...>
command
(
name<"list">(name: "list"): Definition<"list">
name
("list"),
option<"limit", readonly [TransformElement<SchemaTransform<number>>]>(named: Definition<"limit">, elements_0: TransformElement<SchemaTransform<number>>): ModelElement<...>
option
(
name<"limit">(name: "limit"): Definition<"limit">
name
("limit"),
schema<z.ZodDefault<z.ZodNumber>>(schema: z.ZodDefault<z.ZodNumber>): TransformElement<SchemaTransform<number>>
schema
(
import z
z
.
function number(params?: z.RawCreateParams & {
coerce?: boolean;
}): z.ZodNumber
export number
number
().
ZodType<number, ZodNumberDef, number>.default(def: number): z.ZodDefault<z.ZodNumber> (+1 overload)
default
(30))),
),
),
);
const
const step: Parse<Route<"gh", "help" | "execute", readonly [Next<{
configDir: string;
}, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
limit: number;
}, []>]>]>]>]>]>>
step
=
parse<Route<"gh", "help" | "execute", readonly [Next<{
configDir: string;
}, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<...>]>]>]>]>]>>(route: Route<...>, input: Input): Parse<...>
parse
(
const app: Route<"gh", "help" | "execute", readonly [Next<{
configDir: string;
}, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
limit: number;
}, []>]>]>]>]>]>
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.argv
argv.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/node
1: /Users/mjr/work/node/process-args.js
2: one
3: two=three
4: four

@since ― v0.1.27

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.

@param ― start The beginning index of the specified portion of the array. If start is undefined, then the slice begins at index 0.

@param ― end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'. If end is undefined, then the slice extends to the end 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"' &#x26;&#x26; 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.

@since ― v0.1.27

env
}],
});
if (!
const step: Parse<Route<"gh", "help" | "execute", readonly [Next<{
configDir: string;
}, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
limit: number;
}, []>]>]>]>]>]>>
step
.
ok: boolean
ok
) {
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.error(message?: any, ...optionalParams: any[]): void

Prints to stderr with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const code = 5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr

If formatting elements (e.g. %d) are not found in the first string then util.inspect() is called on each argument and the resulting string values are concatenated. See util.format() for more information.

@since ― v0.1.100

error
(
function printErrors(result: MethodNotAllowed | UnprocessableContent): string
printErrors
(
const step: MethodNotAllowed | UnprocessableContent
step
));
var process: NodeJS.Process
process
.
NodeJS.Process.exit(code?: number | string | null): never

The process.exit() method instructs Node.js to terminate the process synchronously with an exit status of code. If code is omitted, exit uses either the 'success' code 0 or the value of process.exitCode if it has been set. Node.js will not terminate until all the 'exit' event listeners are called.

To exit with a 'failure' code:

import { exit } from 'node:process';
exit(1);

The shell that executed Node.js should see the exit code as 1.

Calling process.exit() will force the process to exit as quickly as possible even if there are still asynchronous operations pending that have not yet completed fully, including I/O operations to process.stdout and process.stderr.

In most situations, it is not actually necessary to call process.exit() explicitly. The Node.js process will exit on its own if there is no additional work pending in the event loop. The process.exitCode property can be set to tell the process which exit code to use when the process exits gracefully.

For instance, the following example illustrates a misuse of the process.exit() method that could lead to data printed to stdout being truncated and lost:

import { exit } from 'node:process';
// This is an example of what *not* to do:
if (someConditionNotMet()) {
printUsageToStdout();
exit(1);
}

The reason this is problematic is because writes to process.stdout in Node.js are sometimes asynchronous and may occur over multiple ticks of the Node.js event loop. Calling process.exit(), however, forces the process to exit before those additional writes to stdout can be performed.

Rather than calling process.exit() directly, the code should set the process.exitCode and allow the process to exit naturally by avoiding scheduling any additional work for the event loop:

import process from 'node:process';
// How to properly set the exit code while letting
// the process exit gracefully.
if (someConditionNotMet()) {
printUsageToStdout();
process.exitCode = 1;
}

If it is necessary to terminate the Node.js process due to an error condition, throwing an uncaught error and allowing the process to terminate accordingly is safer than calling process.exit().

In Worker threads, this function stops the current thread rather than the current process.

@since ― v0.1.13

@param ― code The exit code. For string type, only integer strings (e.g.,'1') are allowed.

exit
(1);
}
const step: ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{
configDir: string;
}, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
limit: number;
}, []>]>]>]>]>]>, "/", {}>
step
.
ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{ configDir: string; }, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{ ...; }, []>]>]>]>]>]>, "/", {}>.model: {
configDir: string;
}
model
.
configDir: string
configDir
; // string, resolved before the checkpoint
const
const result: Outcome<Help<"/"> | Execute<"/", {
"/": {
configDir: string;
};
}> | Help<"/pr"> | Help<"/pr/list"> | Execute<"/pr/list", {
"/": {
configDir: string;
};
"/pr": {};
"/pr/list": {
limit: number;
};
}>>
result
=
const step: ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{
configDir: string;
}, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
limit: number;
}, []>]>]>]>]>]>, "/", {}>
step
.
ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{ configDir: string; }, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{ ...; }, []>]>]>]>]>]>, "/", {}>.resume(value: ValueSource[]): Outcome<Help<"/"> | Execute<"/", {
"/": {
configDir: string;
};
}> | Help<"/pr"> | Help<"/pr/list"> | Execute<"/pr/list", {
"/": {
configDir: string;
};
"/pr": {};
"/pr/list": {
...;
};
}>>
resume
(await
function loadConfig(dir: string): Promise<ValueSource[]>
loadConfig
(
const step: ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{
configDir: string;
}, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
limit: number;
}, []>]>]>]>]>]>, "/", {}>
step
.
ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{ configDir: string; }, [], ValueSource[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{ ...; }, []>]>]>]>]>]>, "/", {}>.model: {
configDir: string;
}
model
.
configDir: string
configDir
));
if (
const result: Outcome<Help<"/"> | Execute<"/", {
"/": {
configDir: string;
};
}> | Help<"/pr"> | Help<"/pr/list"> | Execute<"/pr/list", {
"/": {
configDir: string;
};
"/pr": {};
"/pr/list": {
limit: number;
};
}>>
result
.
ok: boolean
ok
&&
const result: Help<"/"> | Execute<"/", {
"/": {
configDir: string;
};
}> | Help<"/pr"> | Help<"/pr/list"> | Execute<"/pr/list", {
"/": {
configDir: string;
};
"/pr": {};
"/pr/list": {
limit: number;
};
}>
result
.
Intent<M extends Method, P extends RoutePath>.method: "help" | "execute"
method
=== "execute" &&
const result: Execute<"/", {
"/": {
configDir: string;
};
}> | Execute<"/pr/list", {
"/": {
configDir: string;
};
"/pr": {};
"/pr/list": {
limit: number;
};
}>
result
.
Intent<M extends Method, P extends RoutePath>.route: "/" | "/pr/list"
route
=== "/pr/list"
) {
const result: Execute<"/pr/list", {
"/": {
configDir: string;
};
"/pr": {};
"/pr/list": {
limit: number;
};
}>
result
.
Execute<"/pr/list", { "/": { configDir: string; }; "/pr": {}; "/pr/list": { limit: number; }; }>.model: {
limit: number;
}
model
.
limit: number
limit
; // number
}
declare function
function loadConfig(dir: string): Promise<ValueSource[]>
loadConfig
(
dir: string
dir
: string):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
type ValueSource = {
name: string;
value: unknown;
}
ValueSource
[]>;

step.model holds the parameters declared before the checkpoint. resume() takes a list of value sources, the same shape as the values you pass to parse(). See Value and environment sources.

Command-line input survives the pause, so a flag still beats the loaded file. With a config.yml of { pr: { list: { limit: 50 } } }:

InvocationResult
gh pr listEXECUTE /pr/list with { limit: 50 }
gh pr list --limit 5EXECUTE /pr/list with { limit: 5 }

With a checkpoint at the root, parse() returns a step for every input, including --help, --version, and input that will fail. Help, version, and errors appear only after you resume:

gh --help pr list
→ a step at /, with configDir resolved
→ load the configuration and resume
→ HELP /pr/list

The router cannot produce a help or version intent until it knows the deepest route, and a later phase may add that route or its options. Do not inspect argv to skip a checkpoint. Make the I/O safe for help, version, and execute alike, and run side effects only after you receive an execute intent.

gh extensions such as gh dash are installed separately and only known at runtime. dynamic() adds them as commands:

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 dynamic<Input, Output, E extends Element>(schema: Schema<Input, Output>, extension: (requires: Output) => E): DynamicElement<Input, Extract<E, AnyElement>>
dynamic
,
function extend<const E extends readonly Input[]>(...elements: E): Extension<Normalize<E>>
extend
,
function name<N extends string>(name: N): Definition<N>
name
,
function parse<const R extends AnyRoute>(route: R, input: Input): Parse<R>
parse
,
function printErrors(result: MethodNotAllowed | UnprocessableContent): string
printErrors
,
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
,
} from "@bomb.sh/router";
import * as
import z
z
from "zod";
const
const gh: Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", [Done<{}, []>]>]>]>]>]>
gh
=
command<"gh", readonly [DynamicElement<string[], Extension<RoutesElement<readonly [Route<string, "help" | "execute", [Done<{}, []>]>]>[]>>, Route<...>]>(start: Definition<...>, elements_0: DynamicElement<...>, elements_1: Route<...>): Route<...>
command
(
name<"gh">(name: "gh"): Definition<"gh">
name
("gh"),
dynamic<string[], string[], Extension<RoutesElement<readonly [Route<string, "help" | "execute", [Done<{}, []>]>]>[]>>(schema: Schema<string[], string[]>, extension: (requires: string[]) => Extension<...>): DynamicElement<...>
dynamic
(
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.ZodString
export string
string
()),
(
extensions: string[]
extensions
) =>
extend<Route<string, "help" | "execute", [Done<{}, []>]>[]>(...elements: Route<string, "help" | "execute", [Done<{}, []>]>[]): Extension<RoutesElement<readonly [Route<string, "help" | "execute", [...]>]>[]>
extend
(...
extensions: string[]
extensions
.
Array<string>.map<Route<string, "help" | "execute", [Done<{}, []>]>>(callbackfn: (value: string, index: number, array: string[]) => Route<string, "help" | "execute", [Done<{}, []>]>, thisArg?: any): Route<...>[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
((
ext: string
ext
) =>
command<string, readonly []>(start: Definition<string>): Route<string, "help" | "execute", [Done<{}, []>]>
command
(
name<string>(name: string): Definition<string>
name
(
ext: string
ext
)))),
),
route<"pr", readonly [Route<"list", "help" | "execute", [Done<{}, []>]>]>(start: Definition<"pr">, elements_0: Route<"list", "help" | "execute", [Done<{}, []>]>): Route<...>
route
(
name<"pr">(name: "pr"): Definition<"pr">
name
("pr"),
command<"list", readonly []>(start: Definition<"list">): Route<"list", "help" | "execute", [Done<{}, []>]>
command
(
name<"list">(name: "list"): Definition<"list">
name
("list"))),
);
const
const step: Parse<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", [Done<{}, []>]>]>]>]>]>>
step
=
parse<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", [Done<{}, []>]>]>]>]>]>>(route: Route<...>, input: Input): Parse<...>
parse
(
const gh: Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", [Done<{}, []>]>]>]>]>]>
gh
, {
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.argv
argv.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/node
1: /Users/mjr/work/node/process-args.js
2: one
3: two=three
4: four

@since ― v0.1.27

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.

@param ― start The beginning index of the specified portion of the array. If start is undefined, then the slice begins at index 0.

@param ― end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'. If end is undefined, then the slice extends to the end of the array.

slice
(2) });
if (!
const step: Parse<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", [Done<{}, []>]>]>]>]>]>>
step
.
ok: boolean
ok
) {
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.error(message?: any, ...optionalParams: any[]): void

Prints to stderr with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const code = 5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr

If formatting elements (e.g. %d) are not found in the first string then util.inspect() is called on each argument and the resulting string values are concatenated. See util.format() for more information.

@since ― v0.1.100

error
(
function printErrors(result: MethodNotAllowed | UnprocessableContent): string
printErrors
(
const step: MethodNotAllowed | UnprocessableContent
step
));
var process: NodeJS.Process
process
.
NodeJS.Process.exit(code?: number | string | null): never

The process.exit() method instructs Node.js to terminate the process synchronously with an exit status of code. If code is omitted, exit uses either the 'success' code 0 or the value of process.exitCode if it has been set. Node.js will not terminate until all the 'exit' event listeners are called.

To exit with a 'failure' code:

import { exit } from 'node:process';
exit(1);

The shell that executed Node.js should see the exit code as 1.

Calling process.exit() will force the process to exit as quickly as possible even if there are still asynchronous operations pending that have not yet completed fully, including I/O operations to process.stdout and process.stderr.

In most situations, it is not actually necessary to call process.exit() explicitly. The Node.js process will exit on its own if there is no additional work pending in the event loop. The process.exitCode property can be set to tell the process which exit code to use when the process exits gracefully.

For instance, the following example illustrates a misuse of the process.exit() method that could lead to data printed to stdout being truncated and lost:

import { exit } from 'node:process';
// This is an example of what *not* to do:
if (someConditionNotMet()) {
printUsageToStdout();
exit(1);
}

The reason this is problematic is because writes to process.stdout in Node.js are sometimes asynchronous and may occur over multiple ticks of the Node.js event loop. Calling process.exit(), however, forces the process to exit before those additional writes to stdout can be performed.

Rather than calling process.exit() directly, the code should set the process.exitCode and allow the process to exit naturally by avoiding scheduling any additional work for the event loop:

import process from 'node:process';
// How to properly set the exit code while letting
// the process exit gracefully.
if (someConditionNotMet()) {
printUsageToStdout();
process.exitCode = 1;
}

If it is necessary to terminate the Node.js process due to an error condition, throwing an uncaught error and allowing the process to terminate accordingly is safer than calling process.exit().

In Worker threads, this function stops the current thread rather than the current process.

@since ― v0.1.13

@param ― code The exit code. For string type, only integer strings (e.g.,'1') are allowed.

exit
(1);
}
const
const result: Outcome<Help<"/"> | Execute<"/", {
"/": {};
}> | Help<"/pr"> | Help<"/pr/list"> | Execute<"/pr/list", {
"/": {};
"/pr": {};
"/pr/list": {};
}>>
result
=
const step: ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", [Done<{}, []>]>]>]>]>]>, "/", {}>
step
.
ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", [Done<{}, []>]>]>]>]>]>, "/", {}>.resume(value: string[]): Outcome<Help<"/"> | Execute<"/", {
"/": {};
}> | Help<"/pr"> | Help<"/pr/list"> | Execute<"/pr/list", {
"/": {};
"/pr": {};
"/pr/list": {};
}>>
resume
(await
function listExtensions(): Promise<string[]>
listExtensions
());
declare function
function listExtensions(): Promise<string[]>
listExtensions
():
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<string[]>;

The schema validates the value you pass to resume(), and its input type sets what resume() accepts. The extension receives the validated value and returns the elements to add. Wrap several elements in extend(). A bare command() is a type error.

With ["dash", "copilot"] installed:

InvocationResult
gh dashEXECUTE /dash
gh dash --helpHELP /dash
gh nopeunprocessable-content with unexpected: `nope`

Routes added by dynamic() exist only at runtime, so the result type does not list them. In 0.8.0, result.route is typed as the statically declared routes only. To branch on an added route, read it as a string:

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 dynamic<Input, Output, E extends Element>(schema: Schema<Input, Output>, extension: (requires: Output) => E): DynamicElement<Input, Extract<E, AnyElement>>
dynamic
,
function extend<const E extends readonly Input[]>(...elements: E): Extension<Normalize<E>>
extend
,
function name<N extends string>(name: N): Definition<N>
name
,
function parse<const R extends AnyRoute>(route: R, input: Input): Parse<R>
parse
} from "@bomb.sh/router";
import * as
import z
z
from "zod";
const
const gh: Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, []>]>
gh
=
command<"gh", readonly [DynamicElement<string[], Extension<RoutesElement<readonly [Route<string, "help" | "execute", [Done<{}, []>]>]>[]>>]>(start: Definition<...>, elements_0: DynamicElement<...>): Route<...>
command
(
name<"gh">(name: "gh"): Definition<"gh">
name
("gh"),
dynamic<string[], string[], Extension<RoutesElement<readonly [Route<string, "help" | "execute", [Done<{}, []>]>]>[]>>(schema: Schema<string[], string[]>, extension: (requires: string[]) => Extension<...>): DynamicElement<...>
dynamic
(
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.ZodString
export string
string
()),
(
extensions: string[]
extensions
) =>
extend<Route<string, "help" | "execute", [Done<{}, []>]>[]>(...elements: Route<string, "help" | "execute", [Done<{}, []>]>[]): Extension<RoutesElement<readonly [Route<string, "help" | "execute", [...]>]>[]>
extend
(...
extensions: string[]
extensions
.
Array<string>.map<Route<string, "help" | "execute", [Done<{}, []>]>>(callbackfn: (value: string, index: number, array: string[]) => Route<string, "help" | "execute", [Done<{}, []>]>, thisArg?: any): Route<...>[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
((
ext: string
ext
) =>
command<string, readonly []>(start: Definition<string>): Route<string, "help" | "execute", [Done<{}, []>]>
command
(
name<string>(name: string): Definition<string>
name
(
ext: string
ext
)))),
),
);
const
const step: Parse<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, []>]>>
step
=
parse<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, []>]>>(route: Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, []>]>, input: Input): Parse<...>
parse
(
const gh: Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, []>]>
gh
, {
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.argv
argv.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/node
1: /Users/mjr/work/node/process-args.js
2: one
3: two=three
4: four

@since ― v0.1.27

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.

@param ― start The beginning index of the specified portion of the array. If start is undefined, then the slice begins at index 0.

@param ― end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'. If end is undefined, then the slice extends to the end of the array.

slice
(2) });
if (
const step: Parse<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, []>]>>
step
.
ok: boolean
ok
) {
const
const result: Outcome<Help<"/"> | Execute<"/", {
"/": {};
}>>
result
=
const step: ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, []>]>, "/", {}>
step
.
ParseIncrement<Route<"gh", "help" | "execute", readonly [Next<{}, [], string[]>, Done<{}, []>]>, "/", {}>.resume(value: string[]): Outcome<Help<"/"> | Execute<"/", {
"/": {};
}>>
resume
(["dash", "copilot"]);
if (
const result: Outcome<Help<"/"> | Execute<"/", {
"/": {};
}>>
result
.
ok: boolean
ok
&&
const result: Help<"/"> | Execute<"/", {
"/": {};
}>
result
.
Intent<M extends Method, P extends RoutePath>.method: "help" | "execute"
method
=== "execute") {
const
const route: string
route
: string =
const result: Execute<"/", {
"/": {};
}>
result
.
Intent<"execute", "/">.route: "/"
route
;
if (
const route: string
route
=== "/dash") {
// run the dash extension
}
}
}

resume() validates its input before parsing continues. Invalid input fails with unprocessable-content, and a dynamic extension never runs:

CallMessage
checkpoint() step, resume(42)expected an array of value sources
checkpoint() step, resume([{ name: "x" }])[0].value: expected a value
dynamic(z.array(z.string()), …) step, resume(42)Invalid input: expected array, received number

Validation is synchronous. A dynamic() schema with an async check fails with async schemas are not allowed.