# CI

> 用 shutdown-check 在 CI 中测试优雅关闭：包含 GitHub Actions 和 GitLab CI 示例、JUnit 报告、退出码，以及如何为每个 job 分配独立端口。

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

在应用构建完成后运行 shutdown-check。CLI 的退出码决定 job 是否失败；JSON 和 JUnit 输出会保留诊断码、时间线和捕获到的服务输出。

## 准备项目

添加 CI 配置之前，先完成以下准备：

1. 在本地运行 `npx shutdown-check test`。
2. 提交 `shutdown-check.json`。
3. 把 `command` 指向构建后的入口文件。
4. 为这个测试预留一个端口。
5. 使用相互隔离的数据库、队列和文件资源。
6. 确保测试请求所用路由的行为是确定的。

shutdown-check 会自己启动和停止服务。不要在前面的 CI 步骤或 service container 中，在配置的端口上启动同一个服务。

## GitHub Actions

```yaml title=".github/workflows/shutdown.yml"
name: Graceful shutdown

on:
  push:
    branches: [main]
  pull_request:

jobs:
  shutdown-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-node@v5
        with:
          node-version: 22
          cache: npm

      - run: npm ci
      - run: npm run build

      - name: Test graceful shutdown
        run: npx shutdown-check test --json --junit shutdown-result.xml

      - name: Save shutdown report
        uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: shutdown-check-report
          path: shutdown-result.xml
          if-no-files-found: ignore
```

`--json` 会把完整的结构化结果写进 job 日志。如果你更想看可读的文本时间线，去掉这个参数即可。

上传 artifact 的步骤使用了 `if: !cancelled()`，所以检查失败后这一步仍会执行。`if-no-files-found: ignore` 用来处理环境或配置错误：这类错误以退出码 `2` 结束，此时还没有生成 JUnit 结果。

如果想在 pull request 的检查结果里直接显示测试结果，可以把 `shutdown-result.xml` 交给一个 JUnit reporter action，用来替代上传，或者和上传同时使用。

## GitLab CI

```yaml title=".gitlab-ci.yml"
shutdown-check:
  image: node:22
  script:
    - npm ci
    - npm run build
    - npx shutdown-check test --junit shutdown-result.xml
  artifacts:
    when: always
    reports:
      junit: shutdown-result.xml
    paths:
      - shutdown-result.xml
```

GitLab 会读取这份报告，并把测试用例显示在 merge request 中。由于设置了 `when: always`，即使失败，artifact 仍然可以下载。

## CI 中的退出码

| 退出码 | 含义 |
| --- | --- |
| `0` | 通过 |
| `1` | 关闭检查失败 |
| `2` | 环境或配置错误 |

| 退出码 | 在 CI 中的含义                   | JUnit 文件     |
| ------ | -------------------------------- | -------------- |
| `0`    | 关闭行为通过检查                 | 指定时写入     |
| `1`    | 测试已运行，但有一项关闭检查失败 | 指定时写入     |
| `2`    | 参数、配置或环境问题导致测试无法运行 | 不写入     |

不要在关闭测试命令后面加 `|| true`。这样会吞掉退出码，可能让破坏部署行为的改动被合并。

## 为并行 job 分配不同端口

只要 `baseUrl` 已被其他进程占用，shutdown-check 就会以 [SC001](https://shutdown.jscrate.dev/zh/docs/codes/sc001) 失败。给每个 job 或 matrix 条目分配不同的端口，并通过 `env` 传给服务。

```json title="shutdown-check.json"
{
  "env": { "PORT": "3510" },
  "baseUrl": "http://127.0.0.1:3510"
}
```

如果 matrix 是动态生成的，可以在 job 中生成一份小配置，或者调用 Node API，并用 matrix 的值推导出端口。不要让并发的关闭测试操作同一批数据库记录或队列消息。

## 设置 CI 超时

job 的超时时间必须留够以下各项的耗时：

- 安装依赖和构建
- `readiness.timeoutMs`
- `workload.started.timeoutMs`
- `shutdown.deadlineMs`
- 少量的进程和 runner 开销

如果测试运行器的超时先触发，你就拿不到有价值的关闭检查结果。外层超时要大于 shutdown-check 各项超时之和，同时关闭截止时间仍要贴近生产环境的实际情况。

## 排查失败的 job

1. 看输出第一行的 SC 码。
2. 找到最后一个成功的时间线事件。
3. 查看捕获到的 stderr 和 stdout。
4. 打开对应诊断码的页面。
5. 在本地用相同的构建命令和环境复现。

如果失败和时序有关，或者发生在服务就绪之前，请参考[故障排查](https://shutdown.jscrate.dev/zh/docs/troubleshooting)。

## 在现有测试套件中运行

如果 CI 已经在发布 node:test 或 Vitest 的结果，可以在测试文件中调用 `checkShutdown()`。这样现有的测试运行器就能报告断言结果，你也可以在失败时打印时间线。

完整示例和超时设置建议见[使用 node:test 和 Vitest 测试](https://shutdown.jscrate.dev/zh/docs/guides/test-runners)。

## 支持的 runner

请使用 Linux 或 macOS，并搭配 Node.js 22 或更高版本。shutdown-check 依赖 POSIX 信号和进程组，因此不支持 Windows runner。Linux 容器 runner 只要能启动子进程并绑定本地端口，也同样支持。

## 相关内容

- [快速开始](https://shutdown.jscrate.dev/zh/docs/quick-start)
- [输出与报告](https://shutdown.jscrate.dev/zh/docs/output)
- [CLI 参考](https://shutdown.jscrate.dev/zh/docs/cli)
- [使用 node:test 和 Vitest 测试](https://shutdown.jscrate.dev/zh/docs/guides/test-runners)
- [故障排查](https://shutdown.jscrate.dev/zh/docs/troubleshooting)
