The shutdown-check Node API is useful when the check belongs inside a test suite or custom script. It runs the same engine as the CLI, but it returns data instead of printing output or exiting your process.
Import the package
ES modules:
import { checkShutdown, defineConfig } from "shutdown-check";CommonJS, supported since 1.0.1:
const { checkShutdown, defineConfig } = require("shutdown-check");TypeScript declarations are included for both entry points.
Exports
| Export | Use it when |
|---|---|
checkShutdown | You have an input object and want validation plus a full run |
defineConfig | You want type checking and completion while writing config |
parseConfig | You want to validate an object and apply defaults |
loadConfig | You want to read and validate a JSON file |
runCheck | You already have a validated CheckConfig |
junitXml | You want to turn a CheckResult into JUnit XML |
The package also exports the config, result, diagnostic, and timeline types.
Run a check with checkShutdown
checkShutdown() is the recommended high-level function. It validates the
input, fills in defaults, runs the check, performs cleanup, and resolves to a
CheckResult.
import assert from "node:assert/strict";
import { test } from "node:test";
import { checkShutdown } from "shutdown-check";
test("finishes active requests on SIGTERM", { timeout: 30_000 }, async () => {
const result = await checkShutdown({
command: ["node", "dist/server.js"],
env: { PORT: "3510" },
baseUrl: "http://127.0.0.1:3510",
readiness: { path: "/health" },
workload: {
path: "/slow",
bodyIncludes: "work complete",
started: { type: "response-headers" },
},
shutdown: {
deadlineMs: 10_000,
readinessWithdrawal: true,
},
});
assert.equal(result.pass, true, `${result.code}: ${result.message}`);
});A normal shutdown failure does not throw. It resolves with pass: false and a
diagnostic code. Invalid config rejects with a validation Error.
function checkShutdown(input: CheckConfigInput, baseDirectory?: string): Promise<CheckResult>Validates input with parseConfig() and runs the check. Relative paths resolve from baseDirectory (default process.cwd()). It starts and cleans up its own process, and returns pass: false for a failed check rather than throwing; it throws only for an invalid config.
Resolve relative paths
The second argument is the base directory used for relative cwd values. It
defaults to process.cwd().
const result = await checkShutdown(config, import.meta.dirname);Pass the test file's directory when the test runner may use another working directory.
Type a config with defineConfig
defineConfig() returns the object unchanged. It exists to provide editor
completion and catch invalid types before the test runs.
import { defineConfig } from "shutdown-check";
export default defineConfig({
command: ["node", "dist/server.js"],
baseUrl: "http://127.0.0.1:3510",
readiness: { path: "/health", timeoutMs: 30_000 },
workload: {
path: "/slow",
concurrent: 2,
started: { type: "response-headers" },
},
shutdown: { deadlineMs: 15_000 },
});Import the object and pass it to checkShutdown(). The CLI does not load
TypeScript config files.
function defineConfig<T extends CheckConfigInput>(input: T): TReturns input unchanged. Use it for editor completion and type checking when you write a config in TypeScript.
Validate input with parseConfig
parseConfig() validates an unknown object and returns a complete
CheckConfig with defaults applied.
import { parseConfig } from "shutdown-check";
const config = parseConfig(
{
command: ["node", "dist/server.js"],
baseUrl: "http://127.0.0.1:3510",
readiness: { path: "/health" },
workload: {
path: "/slow",
started: { type: "response-headers" },
},
},
process.cwd()
);
console.log(config.shutdown.deadlineMs); // 10000The optional second argument controls how relative cwd is resolved.
Validation throws an Error naming the first invalid field.
Read a JSON config with loadConfig
loadConfig() reads a file, parses its JSON, validates it, and resolves cwd
from the file's folder.
import { loadConfig } from "shutdown-check";
const config = await loadConfig("config/shutdown-check.json");File errors, JSON syntax errors, and validation errors are thrown. The messages match the ones used by the CLI.
function parseConfig(raw: unknown, baseDirectory?: string): CheckConfigValidates a config object and fills in every default, resolving cwd from baseDirectory (default process.cwd()). Throws an Error naming the first invalid field.
function loadConfig(file: string): Promise<CheckConfig>Reads and validates a JSON config file. Relative paths in it resolve from the file's folder.
Build a custom runner
Use runCheck() after parseConfig() or loadConfig() when you need custom
logging or report handling.
import { mkdir, writeFile } from "node:fs/promises";
import { junitXml, loadConfig, runCheck } from "shutdown-check";
const config = await loadConfig("shutdown-check.json");
const result = await runCheck(config);
for (const item of result.timeline) {
const detail = item.detail ? ` — ${item.detail}` : "";
console.log(`+${item.ms} ms ${item.event}${detail}`);
}
await mkdir("reports", { recursive: true });
await writeFile(
"reports/shutdown.xml",
junitXml(result, "api-server shutdown")
);
if (!result.pass) {
console.error(`${result.code}: ${result.message}`);
if (result.stderr) console.error(result.stderr);
process.exitCode = 1;
}runCheck() expects a valid CheckConfig; it does not validate a hand-built
object again. Bypassing validation can lead to SC002 or
SC999.
function runCheck(config: CheckConfig): Promise<CheckResult>Runs the check with an already validated config, from parseConfig() or loadConfig().
function junitXml(result: CheckResult, suiteName?: string): stringTurns a result into JUnit XML: one suite (suiteName, default "shutdown-check") with one test case, "graceful shutdown". The timeline goes in system-out, stderr in system-err, and a failed check adds a failure element typed with its code.
The optional second argument to junitXml() names the test suite. It defaults
to shutdown-check.
Understand CheckResult
Every completed check resolves to the same object printed by CLI --json.
| Option | Type | Default | Description |
|---|---|---|---|
| pass* | boolean | — | true only for SC000. |
| code* | string | — | The diagnostic code: SC000 on a pass, the first failure otherwise. |
| message* | string | — | What happened, in one sentence, with the values observed. |
| timeline* | TimelineEvent[] | — | Every step of the check in order, with milliseconds since it started. |
| stdout* | string | — | The last 8 KiB the service wrote to stdout. |
| stderr* | string | — | The last 8 KiB the service wrote to stderr. |
Important fields:
passis the value to assert in a test.codeisSC000on a pass or the first failure code.messageis the human-readable result.timelinerecords each observed step.stdoutandstderrcontain the captured service output tails.startedAtanddurationMsdescribe the run.
Each item in timeline has this shape:
| Option | Type | Default | Description |
|---|---|---|---|
| ms* | number | — | Milliseconds since the check started, rounded. |
| event* | string | — | What happened: "process launched", "service ready", "signal sent", "work request finished", "process exited" and so on. |
| detail | string | — | The values behind it, such as "HTTP 503" or "code=0, signal=none". |
The output reference lists every event name and its detail.
What throws?
| Situation | checkShutdown() | runCheck() |
|---|---|---|
| Invalid input config | Rejects with Error | Not validated |
| Expected check failure | Resolves with pass: false | Resolves with pass: false |
| Unexpected error during the run | Resolves with SC999 | Resolves with SC999 |
Wrap calls in try/catch when configuration may be invalid. For an ordinary
test failure, inspect the returned code and timeline.
Cleanup and parallel tests
Before resolving, the API closes its client requests, clears timers, and force-kills the service process group when necessary. A passing service has already exited and closed its port by itself.
Parallel checks must use separate ports and isolated test data. Otherwise one run may fail with SC001 or affect another run's workload.