切换语言
切换主题

GitHub Actions 复合 Action 开发:从 action.yml 到 Marketplace 发布的完整实战

凌晨三点,我盯着屏幕上第 15 个报错的工作流日志。报错原因是什么?某个私有仓库的 npm install 步骤忘了加 NODE_AUTH_TOKEN

这已经是本周第三次了。我们有八个仓库,每个都复制粘贴了几乎相同的工作流配置:checkout、setup-node、install、build、test。改一个地方要同步八个仓库。某天升级 Node 版本,我改了五个仓库,剩下三个忘了。

那一刻我意识到:不能再这样下去了。GitHub Actions 的复合 Action(Composite Action)就是为此而生的——把重复的步骤打包成一个可复用组件,像调用函数一样在多个工作流中使用。本文将带你从开发第一个复合 Action 到发布到 Marketplace,掌握 CI/CD 组件化的核心技能。

第一章:复合 Action 核心概念

1.1 什么是复合 Action

复合 Action 是 GitHub Actions 提供的一种组件化机制。它把多个步骤(steps)封装成一个独立的 Action,可以在不同工作流中复用。

与 JavaScript Action 或 Docker Action 不同,复合 Action 不需要编写代码。你只需要写 YAML 配置,定义一系列步骤,GitHub 会自动执行。简单来说,复合 Action 就是一组步骤的「函数封装」。

官方定义是这样的:复合 Action 使用 runs.using: "composite" 标识,所有步骤在同一 Runner 中执行。这意味着你可以直接访问工作目录、环境变量,甚至调用其他 Action。

1.2 三种 Action 类型对比

GitHub Actions 支持三种 Action 类型:

类型实现方式适用场景
JavaScript Action编写 JS/TS 代码需要复杂逻辑、API 调用
Docker Action编写 Dockerfile需要特定环境、依赖
复合 Action纯 YAML 配置组合现有步骤、快速复用

复合 Action 的优势很明显:零代码、快速开发、易于维护。你不需要处理打包、编译、依赖管理,只需要写 YAML。

1.3 复合 Action vs 可复用工作流

这是很多人困惑的地方。乍一看,它们都是「复用」,但本质不同:

复合 Action 是步骤组。它在 Job 内执行,使用调用者的 Runner,需要显式传递 Secrets。

可复用工作流 是完整的流水线。它创建独立的 Job,可以有自己的 Runner,Secrets 自动继承。

举个例子:如果你想把「安装依赖 + 运行测试」打包复用,用复合 Action。如果你想标准化整个「构建→测试→部署」管道,用可复用工作流。第四章会详细对比。

第二章:开发第一个复合 Action

2.1 action.yml 结构详解

复合 Action 的核心是 action.yml 文件。这是一个元数据文件,定义了 Action 的名称、输入、输出和执行步骤。

先看一个最小示例:

name: 'Hello World'
description: 'A simple composite action'
runs:
  using: "composite"
  steps:
    - run: echo "Hello from composite action!"
      shell: bash

这个 Action 只做一件事:打印一行文字。但实际项目中,我们需要参数化和输出。

2.2 完整的 action.yml 示例

下面是一个实际可用的复合 Action,用于 Node.js 项目的构建和测试:

name: 'Build and Test'
description: 'Install dependencies, build project, and run tests'
author: 'Your Name'

inputs:
  node-version:
    description: 'Node.js version to use'
    required: true
    default: '20'
  install-command:
    description: 'Command to install dependencies'
    required: false
    default: 'npm ci'
  build-command:
    description: 'Command to build the project'
    required: false
    default: 'npm run build'
  test-command:
    description: 'Command to run tests'
    required: false
    default: 'npm test'

outputs:
  build-path:
    description: 'Path to the build output'
    value: ${{ steps.build.outputs.path }}
  test-coverage:
    description: 'Test coverage percentage'
    value: ${{ steps.coverage.outputs.value }}

runs:
  using: "composite"
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'

    - name: Install dependencies
      run: ${{ inputs.install-command }}
      shell: bash

    - name: Build project
      id: build
      run: |
        ${{ inputs.build-command }}
        echo "path=dist" >> $GITHUB_OUTPUT
      shell: bash

    - name: Run tests
      id: coverage
      run: |
        ${{ inputs.test-command }}
        echo "value=85" >> $GITHUB_OUTPUT
      shell: bash

2.3 字段详解

inputs:定义 Action 接收的参数。每个参数可以指定:

  • description:参数说明(会在 Marketplace 显示)
  • required:是否必填
  • default:默认值

outputs:定义 Action 的输出。通过 $GITHUB_OUTPUT 环境变量传递。注意格式:

echo "name=value" >> $GITHUB_OUTPUT

runs.steps:执行步骤。与普通工作流类似,但有两个关键区别:

  1. 必须显式指定 shell。每一步的 run 命令必须有 shell: bash(或 shpwsh)。这是复合 Action 的强制要求。

  2. 可以使用 uses 调用其他 Action。比如上面的 actions/setup-node@v4

2.4 常见坑点

开发过程中,我踩过几个坑:

坑点一:inputs 没有 type 字段

复合 Action 的 inputs 只支持 string 类型。不能像可复用工作流那样定义 type: booleantype: number。如果需要布尔值,只能传字符串 "true""false",然后在步骤中判断。

坑点二:shell 必须显式指定

普通工作流中,run 命令可以省略 shell,GitHub 会根据 Runner 自动选择。但复合 Action 必须显式指定,否则会报错。

坑点三:outputs 必须通过 step id 引用

输出值的 value 字段必须引用步骤的输出:

outputs:
  my-output:
    value: ${{ steps.my-step.outputs.result }}

而步骤中必须设置 id

- id: my-step
  run: echo "result=hello" >> $GITHUB_OUTPUT

第三章:使用复合 Action

3.1 本地引用

最简单的使用方式是在同一个仓库内引用。假设你的 Action 位于 .github/actions/build-test/action.yml

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

      # 本地引用
      - uses: ./.github/actions/build-test
        with:
          node-version: '20'
          test-command: 'npm run test:ci'

路径以 ./ 开头,相对于仓库根目录。这种方式适合团队内部复用,不需要发布到 Marketplace。

3.2 跨仓库引用

如果想在多个仓库间共享,可以把 Action 放在独立的仓库,然后跨仓库引用:

steps:
  # 引用组织内的共享 Action
  - uses: your-org/shared-actions/build@v1
    with:
      node-version: '18'

路径格式是 owner/repo/path@version。其中 path 是 Action 在仓库中的相对路径。

推荐的组织级 Action 仓库设计

shared-actions/
├── build/
│   └── action.yml        # 构建相关
├── deploy/
│   └── action.yml        # 部署相关
├── lint/
│   └── action.yml        # 代码检查
└── README.md

这样引用时就很清晰:

  • your-org/shared-actions/build@v1
  • your-org/shared-actions/deploy@v1

3.3 从 Marketplace 使用

发布到 Marketplace 后,用户可以直接搜索和引用:

steps:
  # 假设你的 Action 已发布为 "build-test-action"
  - uses: your-org/[email protected]
    with:
      node-version: '20'

Marketplace 提供了版本选择、使用统计、README 展示等功能,适合开源项目或公开分享的 Action。

3.4 版本选择策略

引用 Action 时,有三种版本选择方式:

# 方式一:commit SHA(最安全,不可变)
- uses: your-org/action@a1b2c3d4e5f6...

# 方式二:语义化版本 tag(推荐)
- uses: your-org/[email protected]

# 方式三:major tag(自动追踪最新 v1.x)
- uses: your-org/action@v1

# 不推荐:latest tag(可能意外升级)
# - uses: your-org/action@latest

安全建议

对于生产环境,使用 commit SHA 是最安全的选择。因为 SHA 是不可变的,不会因为维护者更新 tag 而意外升级。

对于内部项目,使用 major tag(如 @v1)是灵活的选择。它会自动追踪最新的 v1.x 版本,获得 bug 修复和新功能。

避免使用 @latest。它可能导致意外升级,破坏构建。

第四章:复合 Action vs 可复用工作流

4.1 功能对比表

这是选择时的核心参考:

维度复合 Action可复用工作流
执行层级Job 内的步骤组独立 Job
Runner使用调用者的 Runner创建新 Runner(或指定)
Secrets需显式传递自动继承
输入类型只有 stringboolean / number / string
输出传递$GITHUB_OUTPUToutputs + workflow_call
并发控制继承调用者的并发限制可以独立设置
环境变量继承 + 可添加独立作用域
适用场景单功能封装整管道标准化

4.2 选择决策矩阵

根据场景选择合适的复用方式:

用复合 Action

  • 打包重复的构建/测试/部署步骤
  • 需要在 Job 内与其他步骤交互
  • 想保持工作流灵活性,只复用部分步骤
  • Secrets 传递可控,安全性要求高

用可复用工作流

  • 标准化整个 CI/CD 管道
  • 多个项目需要相同的完整流程
  • 需要 Secrets 自动继承(减少配置)
  • 需要 iftimeout-minutes 等工作流级配置

组合使用

实际上,最佳实践是组合使用。可复用工作流调用复合 Action:

# .github/workflows/ci.yml(可复用工作流)
on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      # 调用复合 Action
      - uses: ./.github/actions/build-test
        with:
          node-version: ${{ inputs.node-version }}

这样既有了工作流级的标准配置,又有了 Action 级的步骤复用。

第五章:版本管理与发布

5.1 Git Tags 最佳实践

发布 Action 的核心是 Git Tags 管理。推荐使用 语义化版本 + major tag 双重策略:

# 创建语义化版本 tag
git tag -a v1.0.0 -m "Initial release"
git push origin v1.0.0

# 创建 major tag(追踪最新 v1.x)
git tag -fa v1 -m "Update v1 tag to latest"
git push origin v1 --force

当发布 v1.1.0 时,更新 major tag:

git tag -a v1.1.0 -m "Add new feature"
git push origin v1.1.0

# 更新 v1 tag 指向最新版本
git tag -fa v1 -m "Update v1 tag to v1.1.0"
git push origin v1 --force

这样用户可以选择:

  • @v1.1.0 锁定具体版本
  • @v1 自动获得 v1.x 的更新

5.2 Marketplace 发布流程

发布到 GitHub Marketplace 需要:

1. 准备 README.md

README 必须包含:

  • Action 名称和描述
  • Inputs 和 Outputs 说明
  • 使用示例
  • 许可证

2. 准备 action.yml

确保 namedescriptionauthorbranding(可选)字段完整。

3. 创建 Release

在 GitHub 仓库页面:

  1. 点击 “Releases” → “Draft a new release”
  2. 选择 tag(如 v1.0.0
  3. 填写 Release Notes
  4. 勾选 “Publish this Action to the GitHub Marketplace”
  5. 发布

GitHub 会自动校验 action.yml 并发布到 Marketplace。

5.3 自动化发布工作流

可以创建工作流自动发布:

name: Release Action

on:
  push:
    tags:
      - 'v*'

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

      - name: Update major tag
        run: |
          # 提取 major 版本号
          MAJOR=$(echo $GITHUB_REF | sed 's/refs\/tags\/v\([0-9]*\).*/\1/')
          
          # 更新 major tag
          git config user.name github-actions
          git config user.email [email protected]
          git tag -fa v$MAJOR -m "Update v$MAJOR tag"
          git push origin v$MAJOR --force

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          generate_release_notes: true

这个工作流在推送 tag 时自动更新 major tag 并创建 Release。

第六章:高级技巧与最佳实践

6.1 Secrets 安全传递

复合 Action 不能直接访问 secrets 上下文。必须显式传递:

# 工作流中
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: ./.github/actions/deploy
        with:
          token: ${{ secrets.DEPLOY_TOKEN }}
        env:
          AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
# action.yml 中
inputs:
  token:
    description: 'Deploy token'
    required: true

runs:
  using: "composite"
  steps:
    - run: deploy --token ${{ inputs.token }}
      shell: bash
      env:
        AWS_ACCESS_KEY: ${{ env.AWS_ACCESS_KEY }}

安全建议

  • 不要在 inputs 中传递敏感信息(日志中可能可见)
  • 使用 env 传递敏感信息(会被 masked)
  • 在 README 中明确说明需要哪些 Secrets

6.2 使用本地脚本

复杂逻辑不适合写在 YAML 里。可以在 Action 目录放置脚本文件:

.github/actions/build-test/
├── action.yml
└── scripts/
    └── build.sh

在 action.yml 中调用:

runs:
  using: "composite"
  steps:
    - run: $GITHUB_ACTION_PATH/scripts/build.sh
      shell: bash

$GITHUB_ACTION_PATH 是复合 Action 的根目录路径。

6.3 组织级 Action 仓库设计

对于团队,推荐集中管理共享 Action:

your-org/shared-actions/
├── .github/
│   └── workflows/
│       └── test.yml       # 测试所有 Action
├── build/
│   ├── action.yml
│   └── README.md
├── deploy/
│   ├── action.yml
│   └── README.md
├── lint/
│   ├── action.yml
│   └── README.md
└── README.md

每个子目录是一个独立 Action,有完整的 README 和测试。

6.4 常见陷阱和调试技巧

陷阱一:嵌套深度限制

复合 Action 调用复合 Action,最大深度是 10 层。超过会报错。建议嵌套不超过 3 层,调试困难。

陷阱二:环境变量作用域

复合 Action 中的 env 只在步骤内有效。如果需要跨步骤共享,使用 GITHUB_ENV

steps:
  - run: echo "MY_VAR=value" >> $GITHUB_ENV
    shell: bash
  - run: echo $MY_VAR  # 可以访问
    shell: bash

调试技巧

  1. 使用 ACTIONS_STEP_DEBUG=true 启用详细日志
  2. 在步骤中添加 echo 打印变量
  3. 使用 tmate Action 进行 SSH 调试(不推荐在生产环境)

结论

复合 Action 是 GitHub Actions 组件化的核心工具,让你告别重复配置,像写函数一样封装 CI 步骤。

记住三个要点:

结构清晰:action.yml 定义 inputs/outputs/steps,像设计函数签名。参数化让 Action 更灵活,输出让调用者可以获取结果。

选择正确:复合 Action 封装单一功能(构建、测试、部署),可复用工作流标准化整个管道。组合使用效果最佳。

版本安全:commit SHA 最安全,适合生产环境。major tag 灵活,适合内部项目。避免 latest

下一步:在项目中创建第一个复合 Action,将重复的 build-test 步骤打包,然后尝试发布到 Marketplace 分享给团队或社区。

开发 GitHub Actions 复合 Action

从零创建可复用的复合 Action,打包构建/测试步骤

⏱️ 预计耗时: 30 分钟

  1. 1

    步骤1: 创建 Action 目录结构

    在仓库中创建复合 Action 目录:

    • mkdir -p .github/actions/build-test
    • 创建 action.yml 文件
  2. 2

    步骤2: 编写 action.yml 配置

    定义 inputs、outputs 和执行步骤:

    • inputs 定义参数(node-version、test-command)
    • outputs 定义输出(build-path、coverage)
    • runs.steps 必须显式指定 shell: bash
  3. 3

    步骤3: 本地引用测试

    在工作流中引用并测试:

    • uses: ./.github/actions/build-test
    • with: node-version: '20'
    • 本地测试通过后再发布
  4. 4

    步骤4: 创建 Git Tags

    使用语义化版本 + major tag:

    • git tag -a v1.0.0 -m 'Initial release'
    • git tag -fa v1 -m 'Update v1 tag'
    • git push origin v1.0.0 v1
  5. 5

    步骤5: 发布到 Marketplace

    通过 GitHub UI 发布:

    • Releases → Draft a new release
    • 选择 tag(如 v1.0.0)
    • 勾选 Publish to Marketplace
    • GitHub 自动校验并发布

常见问题

复合 Action 和可复用工作流有什么区别?
复合 Action 是 Job 内的步骤组,使用调用者的 Runner,Secrets 需显式传递。可复用工作流创建独立 Job,Secrets 自动继承。用复合 Action 封装单一功能,用可复用工作流标准化整个管道。
复合 Action 的 inputs 为什么没有 type 字段?
复合 Action 的 inputs 只支持 string 类型。不能像可复用工作流那样定义 boolean 或 number。如果需要布尔值,传字符串 'true' 或 'false',在步骤中判断。
复合 Action 如何传递 Secrets?
复合 Action 不能直接访问 secrets 上下文,必须显式传递。通过 inputs 传递(会在日志中 masked)或通过 env 传递(更安全,不会在日志中显示值)。
引用 Action 时应该用 commit SHA 还是 tag?
生产环境用 commit SHA(最安全,不可变)。内部项目用 major tag(如 @v1,自动追踪最新版本)。避免用 @latest,可能意外升级破坏构建。
复合 Action 最大嵌套深度是多少?
复合 Action 调用复合 Action,最大深度 10 层。建议不超过 3 层,嵌套太深调试困难。
复合 Action 必须显式指定 shell 吗?
是的。复合 Action 中每一步的 run 命令必须有 shell: bash(或 sh、pwsh)。这是强制要求,普通工作流可以省略,但复合 Action 必须指定。

11 分钟阅读 · 发布于: 2026年5月6日 · 修改于: 2026年5月6日

相关文章

BetterLink

想持续收到这个主题的更新?

你可以直接关注作者更新、订阅 RSS,或者继续沿着系列入口往下读,避免下次又回到搜索结果重新找。

关注公众号

评论

使用 GitHub 账号登录后即可评论