CI/CD 流水线:GitHub Actions 实战

1. CI / CD 到底是什么

你可能在公司用过 Jenkins、GitLab CI、飞书流水线。CI/CD 两个词分开理解:

为什么要

Java 对照


2. GitHub Actions 基础模型

一个 Action 工作流(Workflow)放在仓库的 .github/workflows/xxx.yml

name: CI                      # 工作流名字

on:                           # 触发条件
  push:
    branches: [main]
  pull_request:

jobs:                         # 并行的任务
  test:
    runs-on: ubuntu-latest    # 跑在什么机器上
    steps:                    # 每个任务的步骤
      - uses: actions/checkout@v4
      - run: echo "hello"

核心概念

概念 含义 Jenkins 对照
Workflow 一整套流程(.yml 文件) Pipeline
Job 一个独立任务(能并行) Stage(但 Actions 的 job 默认并行)
Step 任务内的单条命令 Step
Runner 执行环境 Jenkins Agent
Action 可复用的 step 包(社区共享) Shared Library

3. 第一个实用 CI:push 自动跑 lint + test + build

in4vue 的 .github/workflows/ci.yml

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

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

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

      - name: Install deps
        run: pnpm install --frozen-lockfile

      - name: Lint
        run: pnpm lint

      - name: Type check
        run: pnpm exec vue-tsc --noEmit

      - name: Test
        run: pnpm test:run

      - name: Build
        run: pnpm build

push 到 main 或 PR 打开时,GitHub 自动跑这套流程。任一 step 失败 → workflow 标红 → PR 上显示 ❌ 阻止合并(配合仓库的"保护分支规则")。

关键点


4. 缓存策略:CI 快不快看缓存

no cache:每次 pnpm install 从 registry 下载所有依赖,3-5 分钟。 有 cache:几秒钟命中缓存。

- uses: actions/setup-node@v4
  with:
    node-version: 22
    cache: pnpm                    # 自动根据 pnpm-lock.yaml 缓存

高级场景还能缓存 Vite 的 node_modules/.vite(开发服务器预构建),但 build 产物一般不缓存(hash 变化频繁)。


5. 矩阵构建:一次测多个环境

想在 Node 20 / 22 两个版本下都跑测试:

jobs:
  test:
    strategy:
      matrix:
        node: [20, 22]
        os: [ubuntu-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm test:run

效果:会并行跑 4 个组合(2 Node × 2 OS)。个人项目不用开矩阵,公共库发布前再开。


6. 部署到 Cloudflare Pages

in4vue 的生产部署。两种方式:

6.1 Cloudflare 官方的 Git 集成(最简单)

这是 in4vue 当前的部署方式。Actions 都不用写。

6.2 用 Action 主动推

适合"想自己控制部署时机"的场景:

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

      - name: Publish to Cloudflare Pages
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          projectName: in4vue
          directory: dist
          gitHubToken: ${{ secrets.GITHUB_TOKEN }}

秘密配置

在仓库的 Settings → Secrets and variables → Actions 里加。


7. Secrets 的正确姿势

永远不要把密钥、token 写在代码里或 yml 里:

# ❌ 灾难
- run: curl -H "Authorization: Bearer abc123def" ...

# ✅ 从 Secrets 拿
- run: curl -H "Authorization: Bearer $TOKEN" ...
  env:
    TOKEN: ${{ secrets.API_TOKEN }}

7.1 Secrets 的层级

分环境 secrets 示例

jobs:
  deploy-staging:
    environment: staging      # 用 staging 环境的 secrets
    steps:
      - env:
          API_KEY: ${{ secrets.API_KEY }}  # 自动从 staging env 取

  deploy-prod:
    environment: production   # 用 production 环境的 secrets(可配需审批)
    needs: deploy-staging     # 等 staging 完成
    steps:
      - env:
          API_KEY: ${{ secrets.API_KEY }}

8. PR 预览:每个 PR 一个独立环境

Cloudflare Pages 默认带:每个 PR 自动部预览站点 pr-42.in4vue.pages.dev。PR 关闭后自动清理。

如果用其他平台,可以手动实现:

name: PR Preview

on:
  pull_request:

jobs:
  preview:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

      - name: Deploy to preview
        id: deploy
        run: |
          # 部署到子域名,如 pr-${{ github.event.number }}.preview.example.com
          URL="https://pr-${{ github.event.number }}.preview.example.com"
          echo "url=$URL" >> $GITHUB_OUTPUT

      - name: Comment on PR
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          message: |
            ✅ 预览站点: ${{ steps.deploy.outputs.url }}

效果:PR 下面出现机器人评论贴预览链接。Reviewer 点进去直接看效果。


9. 版本发布:tag 触发

想做"打 tag → 自动发 release"?

name: Release

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          files: dist/**/*
          generate_release_notes: true

      - name: Notify
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
            -d '{"text":"in4vue ${{ github.ref_name }} 发布成功 🎉"}'

发布流程:

git tag v0.2.0
git push origin v0.2.0
# → GitHub Actions 自动跑 release workflow
# → 生成 GitHub Release + 附件是 dist/
# → Slack 通知

10. 分阶段部署:staging → prod

典型企业流程:

push main  →  自动部 staging
手动点按钮 →  部 prod (需要审批)
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.in4vue.com
    steps:
      - uses: actions/checkout@v4
      # ... 构建 + 部署到 staging

  deploy-prod:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production            # 这个 environment 在仓库设置里配"需要审批"
      url: https://in4vue.com
    steps:
      - uses: actions/checkout@v4
      # ... 构建 + 部署到 prod

审批流程:在 Settings → Environments → production 里勾选 "Required reviewers",指定 1-3 个人。deploy-prod job 到这里会暂停,等审批人点"Approve"才继续。

Java 对照:类似 Jenkins 的 input 步骤 —— 需要人工确认才往下。


11. 失败时自动化

11.1 失败 → 钉钉 / Slack 通知

- name: Notify Slack on failure
  if: failure()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "❌ in4vue CI 失败: ${{ github.workflow }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

11.2 失败 → 自动开 issue

- name: Open issue on failure
  if: failure() && github.ref == 'refs/heads/main'
  uses: JasonEtco/create-an-issue@v2
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
    filename: .github/workflows/ci-failure-issue.md

12. 手动触发 workflow

偶尔想不 push 代码也能跑一下部署?用 workflow_dispatch

on:
  workflow_dispatch:
    inputs:
      env:
        type: choice
        options: [staging, production]
      dry_run:
        type: boolean
        default: false

GitHub 仓库的 Actions 标签页就有个"Run workflow"按钮,填参数跑。

用途:紧急补救、重跑某次失败的部署、手动触发缓存清理。


13. 定时任务

on:
  schedule:
    - cron: '0 3 * * *'    # 每天 UTC 3 点(北京 11 点)

用途


14. 并行 vs 串行

默认所有 job 并行。需要串行时用 needs

jobs:
  test: ...
  build:
    needs: test       # test 通过才跑
    ...
  deploy:
    needs: [test, build]
    ...

技巧lint + type-check + test + build 能并行的全并行,CI 时间缩半。


15. 依赖审计:自动发现漏洞

- name: Audit
  run: pnpm audit --prod
  continue-on-error: true       # 发现漏洞不直接失败,只报警

更强的工具

Dependabot 最简配置.github/dependabot.yml):

version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    groups:
      all:
        patterns: ['*']

每周一自动检查依赖,有更新打包在一个 PR 里。


16. 分支保护:让 CI 真有用

光跑 CI 没用——关键是不通过不让合。仓库 Settings → Branches → Add rule

效果

Java 对照:类似 GitLab 的"保护分支" + "流水线状态必须 success"才合并。


17. in4vue 的完整工作流集合

推荐一套:

.github/
└── workflows/
    ├── ci.yml              # push/PR 触发的测试构建
    ├── preview.yml         # PR 预览(Cloudflare 集成的话不用写)
    ├── release.yml         # tag 触发发 release
    └── audit.yml           # 定时依赖审计

小项目可以精简到一个 ci.yml,其他按需加。


18. 常见坑点

现象 原因 解法
Actions 跑不起来,说找不到 workflow 文件名不对或不在 .github/workflows/ 检查路径
每次 install 超慢 没配 cache actions/setup-nodecache: pnpm
secrets 在 PR 里是空的 fork 的 PR 默认拿不到 secrets(安全考虑) 单独的 workflow 处理或用 pull_request_target
部署成功但访问 404 构建输出目录不对 确认 dist 路径和 Cloudflare 设置一致
CI 通过但本地跑失败 环境差异(Node 版本、lockfile 过期) 本地用 nvm use 22 对齐
Secrets 泄漏 commit 里写了 立刻轮换 token + git filter-repo 清历史
矩阵构建某个 OS 挂了 平台差异(CRLF、路径) continue-on-error: true 或单独处理

19. 心智模型

代码进仓库的三道关:

1. 本地 pre-commit(husky + lint-staged)
   ↓ 挡住 commit 时的格式问题

2. CI (GitHub Actions)
   ↓ 挡住 PR 合并前的测试失败

3. CD (自动部署 + 环境保护)
   ↓ 挡住坏版本到生产

每道关失败都应该:
  - 阻止往下走
  - 给出清晰反馈
  - 不需要靠人盯着

一旦 CI/CD 建立起来,交付节奏从"每周发一次,紧张半天"变成"每个 PR 合并即发,无感"——这就是现代工程化的基本盘。


20. in4vue 今天就能加的 3 件事

按收益排:

  1. CI workflow:push/PR 跑 lint + test + build(10 分钟上手)
  2. Dependabot:依赖漏洞自动开 PR(2 分钟配置)
  3. 分支保护:main 必须 PR + CI 通过(点几下设置)

做完这三件,项目工程化水平就到业界及格线了。


小练习

  1. .github/workflows/ci.yml,按本篇第 3 节的内容
  2. 给仓库加 dependabot.yml,等一周看它开的 PR
  3. Settings → Branches 给 main 加保护规则
  4. 故意推一个 lint 不通过的 PR,看 CI 是否拦下
  5. 试试 workflow_dispatch 手动触发

延伸阅读