# Node API

> shutdown-check 的 Node API：在测试里用 checkShutdown 跑关闭检查，校验配置，用 runCheck 编写自定义运行器，并生成 JUnit XML 报告。

Source: https://shutdown.jscrate.dev/zh/docs/node-api
Last updated: 2026-09-23

如果关闭检查要放进测试套件或自定义脚本里，适合使用 shutdown-check 的 Node API。它和 CLI 使用同一套引擎，区别在于它把结果作为数据返回，不打印输出，也不会退出你的进程。

## 导入包

ES 模块：

```ts
import { checkShutdown, defineConfig } from "shutdown-check";
```

CommonJS（1.0.1 起支持）：

```js
const { checkShutdown, defineConfig } = require("shutdown-check");
```

两个入口都自带 TypeScript 类型声明。

## 导出项

| 导出项          | 适用场景                                   |
| --------------- | ------------------------------------------ |
| `checkShutdown` | 已有输入对象，想一步完成校验和完整运行     |
| `defineConfig`  | 编写配置时想要类型检查和自动补全           |
| `parseConfig`   | 想校验一个对象并填入默认值                 |
| `loadConfig`    | 想读取并校验一个 JSON 文件                 |
| `runCheck`      | 手上已经有校验过的 `CheckConfig`           |
| `junitXml`      | 想把 `CheckResult` 转成 JUnit XML          |

包里还导出了配置、结果、诊断和时间线相关的类型。

## 用 checkShutdown 运行检查

推荐使用高层函数 `checkShutdown()`。它会校验输入、填入默认值、运行检查、完成清理，最后 resolve 为一个 `CheckResult`。

```ts title="test/shutdown.test.ts"
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。

```ts
function checkShutdown(input: CheckConfigInput, baseDirectory?: string): Promise<CheckResult>
```

先用 `parseConfig()` 校验 `input`，再运行检查。相对路径从 `baseDirectory` 解析（默认 `process.cwd()`）。函数会自己启动并清理进程；检查失败时返回 `pass: false`，而不是抛出异常；只有配置无效时才会抛出。

### 解析相对路径

第二个参数是基准目录，用来解析相对的 `cwd`，默认是 `process.cwd()`。

```ts
const result = await checkShutdown(config, import.meta.dirname);
```

如果测试运行器可能在别的工作目录下执行，就传入测试文件所在的目录。

## 用 defineConfig 为配置添加类型

`defineConfig()` 原样返回传入的对象。它的作用是让编辑器提供补全，并在测试运行前发现类型错误。

```ts title="shutdown.config.ts"
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 配置文件。

```ts
function defineConfig<T extends CheckConfigInput>(input: T): T
```

原样返回 `input`。用 TypeScript 编写配置时，可以借助它获得编辑器补全和类型检查。

## 用 parseConfig 校验输入

`parseConfig()` 校验一个类型未知的对象，返回填好默认值的完整 `CheckConfig`。

```ts
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`。

```ts
import { loadConfig } from "shutdown-check";

const config = await loadConfig("config/shutdown-check.json");
```

文件错误、JSON 语法错误和校验错误都会抛出，错误消息与 CLI 使用的一致。

```ts
function parseConfig(raw: unknown, baseDirectory?: string): CheckConfig
```

校验配置对象并填入所有默认值，其中 `cwd` 从 `baseDirectory` 解析（默认 `process.cwd()`）。配置无效时抛出 `Error`，并指出第一个无效字段。

```ts
function loadConfig(file: string): Promise<CheckConfig>
```

读取并校验 JSON 配置文件。文件中的相对路径从该文件所在目录解析。

## 编写自定义运行器

如果需要自定义日志或报告处理，先用 `parseConfig()` 或 `loadConfig()` 得到配置，再调用 `runCheck()`。

```ts title="scripts/check-shutdown.ts"
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](https://shutdown.jscrate.dev/zh/docs/codes/sc002) 或 [SC999](https://shutdown.jscrate.dev/zh/docs/codes/sc999)。

```ts
function runCheck(config: CheckConfig): Promise<CheckResult>
```

使用已校验的配置运行检查，配置来自 `parseConfig()` 或 `loadConfig()`。

```ts
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` | — | [诊断码](https://shutdown.jscrate.dev/zh/docs/codes)：通过时为 `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"`。 |

所有事件名称及其详情见[输出参考](https://shutdown.jscrate.dev/zh/docs/output)。

## 什么情况会抛出异常？

| 情况               | `checkShutdown()`           | `runCheck()`                |
| ------------------ | --------------------------- | --------------------------- |
| 输入配置无效       | 以 `Error` reject           | 不校验                      |
| 预期内的检查失败   | resolve 为 `pass: false`    | resolve 为 `pass: false`    |
| 运行中出现意外错误 | resolve 为 `SC999`          | resolve 为 `SC999`          |

如果配置可能无效，用 `try/catch` 包住调用。普通的测试失败则查看返回的诊断码和时间线。

## 清理与并行测试

在 resolve 之前，API 会关闭它发出的客户端请求、清除定时器，必要时强制杀掉服务的进程组。能通过检查的服务，此时已经自行退出并关闭了端口。

并行运行的检查必须使用不同的端口和相互隔离的测试数据。否则某次运行可能以 [SC001](https://shutdown.jscrate.dev/zh/docs/codes/sc001) 失败，或者影响另一次运行的测试请求。

## 相关内容

- [使用 node:test 和 Vitest 测试](https://shutdown.jscrate.dev/zh/docs/guides/test-runners)
- [配置参考](https://shutdown.jscrate.dev/zh/docs/configuration)
- [输出与报告](https://shutdown.jscrate.dev/zh/docs/output)
- [诊断码](https://shutdown.jscrate.dev/zh/docs/codes)
- [CLI 参考](https://shutdown.jscrate.dev/zh/docs/cli)
