GitHub Actions 第一个工作流 #

本节将通过一个完整的实践案例,带你创建一个真实的CI工作流,涵盖代码检查、测试和构建全过程。

项目准备 #

创建示例项目 #

我们创建一个简单的Node.js项目作为示例:

bash
# 创建项目目录
mkdir my-ci-demo
cd my-ci-demo

# 初始化项目
npm init -y

# 安装依赖
npm install --save-dev jest eslint prettier

# 创建项目结构
mkdir -p src tests

创建源代码 #

创建 src/index.js

javascript
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}

module.exports = { add, subtract, multiply, divide };

创建测试文件 #

创建 tests/index.test.js

javascript
const { add, subtract, multiply, divide } = require('../src/index');

describe('Calculator', () => {
  test('adds 1 + 2 to equal 3', () => {
    expect(add(1, 2)).toBe(3);
  });

  test('subtracts 5 - 3 to equal 2', () => {
    expect(subtract(5, 3)).toBe(2);
  });

  test('multiplies 2 * 3 to equal 6', () => {
    expect(multiply(2, 3)).toBe(6);
  });

  test('divides 6 / 2 to equal 3', () => {
    expect(divide(6, 2)).toBe(3);
  });

  test('throws error when dividing by zero', () => {
    expect(() => divide(1, 0)).toThrow('Division by zero');
  });
});

配置package.json #

更新 package.json

json
{
  "name": "my-ci-demo",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "lint": "eslint src/",
    "format": "prettier --write src/",
    "format:check": "prettier --check src/"
  },
  "devDependencies": {
    "eslint": "^8.57.0",
    "jest": "^29.7.0",
    "prettier": "^3.2.5"
  }
}

创建ESLint配置 #

创建 .eslintrc.json

json
{
  "env": {
    "node": true,
    "es2021": true,
    "jest": true
  },
  "extends": "eslint:recommended",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  }
}

创建Prettier配置 #

创建 .prettierrc

json
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}

创建CI工作流 #

基础版本 #

创建 .github/workflows/ci.yml

yaml
name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linter
        run: npm run lint
      
      - name: Run tests
        run: npm test

进阶版本 - 添加缓存 #

yaml
name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linter
        run: npm run lint
      
      - name: Check formatting
        run: npm run format:check
      
      - name: Run tests
        run: npm test -- --coverage
      
      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

高级版本 - 矩阵构建 #

yaml
name: CI

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

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run format:check

  test:
    needs: lint
    runs-on: ${{ matrix.os }}
    
    strategy:
      matrix:
        node: [16, 18, 20]
        os: [ubuntu-latest, windows-latest, macos-latest]
      fail-fast: false
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'
      
      - run: npm ci
      
      - name: Run tests
        run: npm test -- --coverage
      
      - name: Upload coverage
        if: matrix.node == '20' && matrix.os == 'ubuntu-latest'
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
        if: ${{ hashFiles('package.json') != '' && contains(fromJson('{"scripts": {}}'), 'build') }}
      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/

工作流详解 #

触发条件 #

yaml
on:
  push:
    branches: [main]      # 推送到main分支时触发
  pull_request:
    branches: [main]      # 针对main分支的PR时触发

作业依赖 #

yaml
jobs:
  lint:
    # 第一个运行的作业
    
  test:
    needs: lint           # 等待lint作业完成
    
  build:
    needs: test           # 等待test作业完成

矩阵策略 #

yaml
strategy:
  matrix:
    node: [16, 18, 20]    # 测试多个Node版本
    os: [ubuntu-latest, windows-latest, macos-latest]  # 测试多个操作系统
  fail-fast: false        # 一个失败不取消其他

条件执行 #

yaml
- name: Upload coverage
  if: matrix.node == '20' && matrix.os == 'ubuntu-latest'
  # 只在特定配置下上传覆盖率

添加状态徽章 #

在README.md中添加工作流状态徽章:

markdown
![CI](https://github.com/YOUR_USERNAME/my-ci-demo/workflows/CI/badge.svg)

完整示例:

markdown
# My CI Demo

![CI](https://github.com/YOUR_USERNAME/my-ci-demo/workflows/CI/badge.svg)

A simple Node.js project with GitHub Actions CI.

本地测试工作流 #

使用act工具 #

act 可以在本地运行GitHub Actions:

bash
# 安装act (macOS)
brew install act

# 列出工作流
act -l

# 运行push事件
act push

# 运行特定作业
act -j test

# 使用特定事件
act pull_request

运行结果示例 #

text
[CI/test] 🚀  Start image=node:16
[CI/test]   🐳  docker run image=node:16
[CI/test] ⭐  Run actions/checkout@v4
[CI/test]   ✅  Success - actions/checkout@v4
[CI/test] ⭐  Run actions/setup-node@v4
[CI/test]   ✅  Success - actions/setup-node@v4
[CI/test] ⭐  Run npm ci
[CI/test]   ✅  Success - npm ci
[CI/test] ⭐  Run npm test
[CI/test]   ✅  Success - npm test

查看运行结果 #

GitHub网页 #

  1. 进入仓库的 “Actions” 标签
  2. 选择工作流运行
  3. 查看每个步骤的日志

使用GitHub CLI #

bash
# 列出最近的工作流运行
gh run list --limit 5

# 查看特定运行
gh run view <run-id>

# 实时查看日志
gh run watch

# 下载日志
gh run download <run-id>

添加更多功能 #

代码覆盖率报告 #

yaml
- name: Run tests with coverage
  run: npm test -- --coverage --reporters=default --reporters=jest-junit
  env:
    JEST_JUNIT_OUTPUT_DIR: ./coverage

- name: Upload coverage to Codecov
  uses: codecov/codecov-action@v4
  with:
    token: ${{ secrets.CODECOV_TOKEN }}
    files: ./coverage/lcov.info

依赖安全检查 #

yaml
- name: Run security audit
  run: npm audit --audit-level=moderate
  continue-on-error: true

通知 #

yaml
- name: Notify on failure
  if: failure()
  uses: slackapi/slack-github-action@v1
  with:
    channel-id: 'C0123456789'
    slack-message: 'CI failed for ${{ github.repository }}'
  env:
    SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

完整工作流示例 #

yaml
name: CI

on:
  push:
    branches: [main, develop]
    paths-ignore:
      - '**.md'
      - 'docs/**'
  pull_request:
    branches: [main]
  workflow_dispatch:

env:
  NODE_VERSION: '20'

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint
        run: npm run lint
        
      - name: Check formatting
        run: npm run format:check

  test:
    name: Test (Node ${{ matrix.node }} on ${{ matrix.os }})
    needs: lint
    runs-on: ${{ matrix.os }}
    
    strategy:
      matrix:
        node: [16, 18, 20]
        os: [ubuntu-latest]
        include:
          - node: 20
            os: windows-latest
          - node: 20
            os: macos-latest
      fail-fast: false
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm test -- --coverage
        
      - name: Upload coverage
        if: matrix.node == '20' && matrix.os == 'ubuntu-latest'
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/
          retention-days: 7

  build:
    name: Build
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
        
      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/
          retention-days: 7

  notify:
    name: Notify
    needs: [lint, test, build]
    if: always()
    runs-on: ubuntu-latest
    
    steps:
      - name: Determine status
        id: status
        run: |
          if [ "${{ needs.build.result }}" == "success" ]; then
            echo "status=success" >> $GITHUB_OUTPUT
            echo "emoji=✅" >> $GITHUB_OUTPUT
          elif [ "${{ needs.build.result }}" == "skipped" ]; then
            echo "status=skipped" >> $GITHUB_OUTPUT
            echo "emoji=⏭️" >> $GITHUB_OUTPUT
          else
            echo "status=failure" >> $GITHUB_OUTPUT
            echo "emoji=❌" >> $GITHUB_OUTPUT
          fi
      
      - name: Log result
        run: |
          echo "## Workflow Result" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "${{ steps.status.outputs.emoji }} Status: ${{ steps.status.outputs.status }}" >> $GITHUB_STEP_SUMMARY

最佳实践总结 #

1. 使用缓存加速 #

yaml
- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'  # 自动缓存npm依赖

2. 合理使用矩阵 #

yaml
strategy:
  matrix:
    include:
      - node: 20
        os: ubuntu-latest
        experimental: false

3. 添加作业依赖 #

yaml
jobs:
  test:
    needs: lint

4. 使用并发控制 #

yaml
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

5. 条件执行 #

yaml
if: github.ref == 'refs/heads/main'

下一步学习 #

小结 #

  • 创建完整的项目结构,包含源代码、测试和配置
  • 工作流应包含lint、test、build等关键步骤
  • 使用矩阵策略测试多版本和多平台
  • 添加缓存加速构建过程
  • 使用作业依赖控制执行顺序
  • 添加状态徽章展示项目质量
最后更新:2026-03-28