# CI/CD 实战

前提：仓库已提交 `deno.json` 与 `deno.lock`，本地门禁已通过。本页只讲流水线编排；门禁本身与安全基线见[生产工程基线](/docs/deploy/production-baseline)。

## 安装与固定 Deno

GitHub Actions 使用官方 action，并固定 major 版本：

```yaml
- uses: denoland/setup-deno@v2
  with:
    deno-version: v2.x
```

- `deno-version` 接受 `v2.x`、`v2.1.x`、精确版本或 `lts`；追求严格可复现时固定到精确版本，并把升级作为独立变更评审。
- 也可用 `deno-version-file` 从 `.tool-versions` 等文件读取，保证 CI 与本地一致。

## 缓存依赖

`setup-deno` 内置缓存，无需手写 `actions/cache`：

```yaml
- uses: denoland/setup-deno@v2
  with:
    deno-version: v2.x
    cache: true
```

`cache: true` 缓存 Deno 下载的依赖（即 `DENO_DIR` 内容），缓存键由 job id、runner OS/arch 和 `deno.lock` 哈希组成；需要自定义哈希时用 `cache-hash`（设置它即隐含开启缓存）。若 workflow 自行设置了 `DENO_DIR` 环境变量，保证 action 与后续步骤使用同一目录。

## 安装依赖：deno ci

```bash
deno ci
```

`deno ci`（Deno 2.8+）是 CI 与 Dockerfile 的可复现安装命令：`deno.lock` 缺失时报错，删除已有 `node_modules`，并以 frozen 语义安装——lockfile 必须与配置文件精确匹配，任何漂移都会失败而不是静默更新。构建生产产物时加 `--prod` 跳过 devDependencies；要连 `@types/*` 一起排除需另加 `--skip-types`——它按包名启发式判断，可能误跳过附带运行时代码的包，用前核对产物是否仍完整。

## 门禁顺序

按"廉价且快失败在前"排序：

```bash
deno ci
deno fmt --check
deno lint
deno check "**/*.ts" "**/*.tsx"
deno test
```

格式与 lint 秒级完成，先拦住机械性问题；类型检查再拦住接口错误；测试最贵，放最后。不要把门禁合并成一条命令，分开才能在 CI 日志里一眼定位失败层级。glob 要加引号交给 Deno 展开（裸 `**/*.ts` 由 shell 展开，各 runner 行为不一，而且漏掉 `.tsx`）；Fresh 等有自带 `check` task 的项目直接跑 `deno task check`。

## 跨 OS/arch 矩阵

库与 CLI 至少跑三个系统：

```yaml
strategy:
  matrix:
    os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
```

Windows 注意 CRLF：在 checkout 前设置 `git config --system core.autocrlf false`，避免 `deno fmt --check` 因换行符误报。可用 `continue-on-error` 加一个 canary Deno 版本任务，提前发现上游变更，但不阻塞合并。覆盖率报告等只需一次的步骤用 `if: matrix.os == 'ubuntu-latest'` 限定。

## 构建与发布产物

- 静态站点：`deno task build` 生成产物目录，用 `actions/upload-artifact` 传递或直接交给部署步骤。
- 单文件二进制：`deno compile --target <target>` 交叉编译各平台产物，随 release 上传。编译参数以当前 `deno help compile` 为准。
- 发布 JSR 包：不要在每个 push 上发布。按 tag 触发，用 OIDC 发布以获得 provenance，完整配置见[发布 JSR 包](/docs/reference/publishing-jsr)。

## 部署到 Deno Deploy

两条路径，按团队习惯选一条：

1. **内置 GitHub 集成（默认路径）**：在 Deno Deploy 控制台把 app 关联到 GitHub 仓库，推送即触发构建，不需要自己维护部署 YAML。
2. **外部 CI 部署**：需要自定义流水线（例如先跑完整矩阵再发布）时，用 `deno deploy` CLI：

```bash
deno deploy --org <org> --app <app> --prod
```

CLI 在 CI 中的认证方式是组织 token：创建 organization token，存入 GitHub 仓库 secret，通过 `DENO_DEPLOY_TOKEN` 环境变量传入。注意 Deno Deploy 文档中的 OIDC 页面讲的是运行中的应用向第三方服务（AWS、Vault 等）认证，不是 CLI 部署的认证方式——不要把两者混淆。Deploy Classic 已被官方宣布于 2026-07-20 停用，不要在新项目中使用 deployctl。

## 完整示例

```yaml title=".github/workflows/ci.yml"
name: ci
on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    # Windows runner 上先关掉 CRLF 转换，否则 fmt --check 会误报
    steps:
      - run: |
          git config --system core.autocrlf false
          git config --system core.eol lf
      - uses: actions/checkout@v7
      - uses: denoland/setup-deno@v2
        with:
          deno-version: v2.x
          cache: true
      - run: deno ci
      - run: deno fmt --check
      - run: deno lint
      - run: deno check "**/*.ts" "**/*.tsx"
      - run: deno test

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v7
      - uses: denoland/setup-deno@v2
        with:
          deno-version: v2.x
      - run: deno ci --prod
      - run: deno task build # 需要构建步骤的项目
      - run: deno deploy --org my-org --app my-app --prod
        env:
          DENO_DEPLOY_TOKEN: ${{ secrets.DENO_DEPLOY_TOKEN }}
```

要点：`environment: production` 配合 GitHub 环境保护规则做人工审批；token 只授予目标组织；deploy job 与测试矩阵使用同一 Deno major 版本。

<Callout type="warn" title="部署是不可逆的外部动作">
`deno deploy --prod` 与 `deno publish` 都会立刻影响外部用户。Agent 与自动化脚本不应绕过 environment 审批直接触发生产部署。
</Callout>

官方参考：[Continuous integration](https://docs.deno.com/runtime/reference/continuous_integration/)、[setup-deno](https://github.com/denoland/setup-deno)、[Deno 2.8 发布说明（deno ci）](https://deno.com/blog/v2.8)、[deno deploy CLI 参考](https://docs.deno.com/runtime/reference/cli/deploy/)、[Deno Deploy changelog](https://docs.deno.com/deploy/changelog/)。
