如果关闭检查要放进测试套件或自定义脚本里,适合使用 shutdown-check 的 Node API。它和 CLI 使用同一套引擎,区别在于它把结果作为数据返回,不打印输出,也不会退出你的进程。
导入包
ES 模块:
import { checkShutdown, defineConfig } from "shutdown-check";CommonJS(1.0.1 起支持):
const { checkShutdown, defineConfig } = require("shutdown-check");两个入口都自带 TypeScript 类型声明。
导出项
| 导出项 | 适用场景 |
|---|---|
checkShutdown | 已有输入对象,想一步完成校验和完整运行 |
defineConfig | 编写配置时想要类型检查和自动补全 |
parseConfig | 想校验一个对象并填入默认值 |
loadConfig | 想读取并校验一个 JSON 文件 |
runCheck | 手上已经有校验过的 CheckConfig |
junitXml | 想把 CheckResult 转成 JUnit XML |
包里还导出了配置、结果、诊断和时间线相关的类型。
用 checkShutdown 运行检查
推荐使用高层函数 checkShutdown()。它会校验输入、填入默认值、运行检查、完成清理,最后 resolve 为一个 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}`);
});普通的关闭失败不会抛出异常,而是 resolve 为 pass: false,并带上诊断码。配置无效时,Promise 会以校验 Error reject。
function checkShutdown(input: CheckConfigInput, baseDirectory?: string): Promise<CheckResult>先用 parseConfig() 校验 input,再运行检查。相对路径从 baseDirectory 解析(默认 process.cwd())。函数会自己启动并清理进程;检查失败时返回 pass: false,而不是抛出异常;只有配置无效时才会抛出。
解析相对路径
第二个参数是基准目录,用来解析相对的 cwd,默认是 process.cwd()。
const result = await checkShutdown(config, import.meta.dirname);如果测试运行器可能在别的工作目录下执行,就传入测试文件所在的目录。
用 defineConfig 为配置添加类型
defineConfig() 原样返回传入的对象。它的作用是让编辑器提供补全,并在测试运行前发现类型错误。
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 },
});导入这个对象,再传给 checkShutdown()。注意 CLI 不会加载 TypeScript 配置文件。
function defineConfig<T extends CheckConfigInput>(input: T): T原样返回 input。用 TypeScript 编写配置时,可以借助它获得编辑器补全和类型检查。
用 parseConfig 校验输入
parseConfig() 校验一个类型未知的对象,返回填好默认值的完整 CheckConfig。
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); // 10000可选的第二个参数决定相对 cwd 如何解析。校验失败时会抛出 Error,消息中指出第一个无效字段。
用 loadConfig 读取 JSON 配置
loadConfig() 读取文件、解析 JSON、完成校验,并以文件所在目录为基准解析 cwd。
import { loadConfig } from "shutdown-check";
const config = await loadConfig("config/shutdown-check.json");文件错误、JSON 语法错误和校验错误都会抛出,错误消息与 CLI 使用的一致。
function parseConfig(raw: unknown, baseDirectory?: string): CheckConfig校验配置对象并填入所有默认值,其中 cwd 从 baseDirectory 解析(默认 process.cwd())。配置无效时抛出 Error,并指出第一个无效字段。
function loadConfig(file: string): Promise<CheckConfig>读取并校验 JSON 配置文件。文件中的相对路径从该文件所在目录解析。
编写自定义运行器
如果需要自定义日志或报告处理,先用 parseConfig() 或 loadConfig() 得到配置,再调用 runCheck()。
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() 要求传入有效的 CheckConfig,不会再校验一遍手写的对象。跳过校验可能导致 SC002 或 SC999。
function runCheck(config: CheckConfig): Promise<CheckResult>使用已校验的配置运行检查,配置来自 parseConfig() 或 loadConfig()。
function junitXml(result: CheckResult, suiteName?: string): string把结果转换为 JUnit XML:一个测试套件(名称为 suiteName,默认 "shutdown-check"),其中只有一个测试用例“graceful shutdown”。时间线写入 system-out,stderr 写入 system-err;检查失败时会添加一个 failure 元素,类型为对应的诊断码。
junitXml() 的可选第二个参数用来命名测试套件,默认是 shutdown-check。
理解 CheckResult
每次检查完成后,都会 resolve 为同一个对象,也就是 CLI --json 打印的那个对象。
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| pass* | boolean | — | 仅当结果为 SC000 时为 true。 |
| code* | string | — | 诊断码:通过时为 SC000,否则为第一个失败项的诊断码。 |
| message* | string | — | 用一句话说明发生了什么,并附上观察到的值。 |
| timeline* | TimelineEvent[] | — | 按顺序列出检查的每一步,并标出从检查开始算起的毫秒数。 |
| stdout* | string | — | 服务写入 stdout 的最后 8 KiB 内容。 |
| stderr* | string | — | 服务写入 stderr 的最后 8 KiB 内容。 |
重要字段:
pass:在测试里断言的就是这个值。code:通过时为SC000,否则为第一个失败的诊断码。message:给人看的结果说明。timeline:记录观察到的每一步。stdout和stderr:捕获到的服务输出末尾部分。startedAt和durationMs:本次运行的开始时间和耗时。
timeline 中每一项的结构如下:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| ms* | number | — | 从检查开始算起的毫秒数,已取整。 |
| event* | string | — | 发生了什么:"process launched"、"service ready"、"signal sent"、"work request finished"、"process exited" 等。 |
| detail | string | — | 事件对应的具体值,例如 "HTTP 503" 或 "code=0, signal=none"。 |
所有事件名称及其详情见输出参考。
什么情况会抛出异常?
| 情况 | checkShutdown() | runCheck() |
|---|---|---|
| 输入配置无效 | 以 Error reject | 不校验 |
| 预期内的检查失败 | resolve 为 pass: false | resolve 为 pass: false |
| 运行中出现意外错误 | resolve 为 SC999 | resolve 为 SC999 |
如果配置可能无效,用 try/catch 包住调用。普通的测试失败则查看返回的诊断码和时间线。
清理与并行测试
在 resolve 之前,API 会关闭它发出的客户端请求、清除定时器,必要时强制杀掉服务的进程组。能通过检查的服务,此时已经自行退出并关闭了端口。
并行运行的检查必须使用不同的端口和相互隔离的测试数据。否则某次运行可能以 SC001 失败,或者影响另一次运行的测试请求。