GitHub Actions: Reusable Workflow 與 Composite Action 深度
標籤:#DevOps #GitHub Actions #Reusable Workflow #Composite Action #CI/CD
深入比較 GitHub Actions 兩種複用機制,搞清楚哪一種該用、怎麼用、為什麼
目錄
- 為什麼需要複用?
- 三種複用機制總覽
- Reusable Workflow 詳解
- Composite Action 詳解
- Reusable Workflow vs Composite Action
- 輸入輸出與 Secrets
- 版本管理策略
- 權限與安全
- 實戰範例
- 最佳實踐
- 常見問題
- 總結
為什麼需要複用?
當 organization 裡有 10 個以上的 repo,每個都需要相同的「lint → test → build → publish image」流程,用複製貼上的方式維護會變成災難:
- 修一個 bug 要改 10 個 repo:升級 Node 版本、修改 cache key、改 Slack 通知 webhook
- 流程容易飄移:有的 repo 用 Node 18、有的還在用 16,標準難以強制
- 安全更新成本高:Action 版本要全部追蹤,寫死 SHA 後升級工作量倍增
GitHub Actions 提供兩種主要複用機制:
- Reusable Workflow:整個 workflow / job 複用
- Composite Action:多個 step 包成一個自訂 action
選錯機制會讓設計變得彆扭,所以先搞清楚兩者定位再開始寫。
三種複用機制總覽
| 機制 | 複用範圍 | 用途 | 典型場景 |
|---|---|---|---|
| Reusable Workflow | 整個 workflow 或 job | 標準化整套 CI/CD pipeline | 公司統一的 build/deploy 流程 |
| Composite Action | 多個 step | 抽出可被嵌入既有 job 的步驟組 | 重複的 setup + cache + login 步驟 |
| JavaScript/Docker Action | 程式邏輯 | 寫真正的程式邏輯(可發到 Marketplace) | 內部工具、外掛 |
本筆記聚焦在前兩種,JavaScript/Docker Action 留給單獨筆記。
簡單心法
整個 job 都一樣 → Reusable Workflow 幾個 step 重複 → Composite Action
Reusable Workflow 詳解
定義 Reusable Workflow
關鍵:on: workflow_call
# .github/workflows/reusable-go-build.yml
name: Reusable Go Build
on:
workflow_call:
inputs:
go-version:
description: "Go 版本"
required: false
type: string
default: "1.22"
os:
description: "執行 runner OS"
required: false
type: string
default: "ubuntu-latest"
secrets:
GORELEASER_TOKEN:
required: false
outputs:
build-sha:
description: "建置出的 commit SHA"
value: ${{ jobs.build.outputs.sha }}
jobs:
build:
runs-on: ${{ inputs.os }}
outputs:
sha: ${{ steps.meta.outputs.sha }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ inputs.go-version }}
- id: meta
run: echo "sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
- run: go build ./...
- run: go test ./...
呼叫 Reusable Workflow
呼叫端使用 uses:,注意要寫在 jobs.<id> 層級,不是 steps 層級
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
build:
uses: my-org/ci-templates/.github/workflows/reusable-go-build.yml@v1
with:
go-version: "1.22"
os: ubuntu-latest
secrets:
GORELEASER_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
跨 Repo 呼叫的關鍵限制
| 來源 | 路徑語法 |
|---|---|
| 同 repo | ./.github/workflows/file.yml(不指定 ref) |
| 跨 repo | owner/repo/.github/workflows/file.yml@ref |
| 同 organization 內 | 與跨 repo 同,但 organization 設定可放寬權限 |
跨 repo 呼叫必須指定 ref(branch、tag、SHA),不能省略
多層巢狀的限制
Reusable Workflow 可以呼叫另一個 Reusable Workflow,但有層數限制:
caller-workflow.yml
└─> reusable-A.yml # 第 1 層
└─> reusable-B.yml # 第 2 層
└─> reusable-C.yml # 第 3 層
└─> reusable-D.yml # 第 4 層(極限)
GitHub 官方目前允許最多 4 層巢狀,超過會直接報錯。建議實務上不要超過 2 層,debug 會非常痛苦。
Composite Action 詳解
定義 Composite Action
Composite Action 用 action.yml 定義,不是 workflow,所以放在獨立 repo 或 repo 內的子目錄都可以。
# .github/actions/setup-and-cache-go/action.yml
name: "Setup Go and Cache"
description: "Setup Go with module + build cache configured"
inputs:
go-version:
description: "Go 版本"
required: false
default: "1.22"
cache-key-suffix:
description: "Cache key 附加識別符"
required: false
default: "default"
outputs:
cache-hit:
description: "是否命中快取"
value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: "composite"
steps:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ inputs.go-version }}
- name: Cache Go modules
id: cache
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ inputs.go-version }}-${{ inputs.cache-key-suffix }}-${{ hashFiles('**/go.sum') }}
- name: Show Go version
shell: bash
run: go version
重點細節
runs.using: "composite"是 Composite Action 的識別- 每個自訂
run:step 都必須指定shell:(bash、pwsh、sh、python...) - 不需要寫
shell:的場景:當 step 用uses:引用其他 action 時 - 沒有
runs-on::Composite Action 跑在呼叫它的 job 的 runner 上
呼叫 Composite Action
# .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup
id: setup
uses: ./.github/actions/setup-and-cache-go
with:
go-version: "1.22"
cache-key-suffix: "test"
- name: Show cache hit
run: echo "cache hit = ${{ steps.setup.outputs.cache-hit }}"
- run: go test ./...
跨 repo 引用:
- uses: my-org/shared-actions/setup-go@v2
Composite Action 沒有的東西
Composite Action 不能:
- 宣告
secrets:(只能透過inputs:傳入) - 指定獨立的
runs-on: - 定義
if:條件來控制整個 action 是否執行(只能在呼叫端做) - 使用 matrix(matrix 是 job 層級)
這些限制都是因為 Composite Action 本質上是「被嵌入 job 的 step 群」。
Reusable Workflow vs Composite Action
並列比較表
| 比較項目 | Reusable Workflow | Composite Action |
|---|---|---|
| 檔案位置 | .github/workflows/*.yml |
action.yml(任意路徑) |
| 觸發方式 | on: workflow_call |
runs.using: composite |
| 複用單位 | 整個 workflow / job | 一組 steps |
| 執行 runner | 自己指定 runs-on |
跑在呼叫它的 job 上 |
| 能用 matrix? | ✅ 可以,呼叫端傳 matrix | ❌ 不能(matrix 在 job 層) |
| 能用 secrets? | ✅ 宣告 secrets: 區塊 |
❌ 只能透過 inputs: |
| 能多個 job? | ✅ 可以(整個 workflow) | ❌ 只是 steps |
| 權限控制 | ✅ 獨立 permissions: |
繼承呼叫端 |
| Marketplace? | ❌ 不能發佈 | ✅ 可以發佈 |
| 顯示在 Actions 頁面? | ✅ 獨立顯示 | ❌ 被嵌入呼叫端的 step log |
怎麼選?決策樹
要複用什麼?
├─ 整套 CI/CD pipeline(含 runner / 環境 / 多個 job)
│ └─ Reusable Workflow
│
├─ 一個 job 的完整內容(在指定 runner 跑一系列事)
│ └─ Reusable Workflow(更乾淨)
│
├─ 幾個 step 序列(setup + cache + login 三步)
│ └─ Composite Action
│
└─ 想做成可發佈的工具(別人也能用)
└─ Composite Action(可發 Marketplace)
容易踩坑的選擇
錯誤示範:把「checkout + setup-node + npm install」做成 Reusable Workflow
# ❌ 這只是 3 個 step,不該做成 Reusable Workflow
jobs:
setup:
uses: ./.github/workflows/just-setup.yml
test:
needs: setup
runs-on: ubuntu-latest
steps:
- run: npm test # 但 setup 在另一個 job 跑,這裡又要重新 checkout...
問題:Reusable Workflow 被當作獨立 job 執行,跑完後檔案系統不會傳到下一個 job。要傳 artifacts 又得用 upload-artifact / download-artifact,反而更累贅。這種情境應該用 Composite Action。
正確做法:
# .github/actions/setup-node-deps/action.yml
runs:
using: "composite"
steps:
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
shell: bash
# 使用
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node-deps
- run: npm test
輸入輸出與 Secrets
Inputs 型別差異
Reusable Workflow 的 inputs 必須指定 type(string / boolean / number):
on:
workflow_call:
inputs:
environment:
type: string
required: true
enable-feature-x:
type: boolean
default: false
replicas:
type: number
default: 3
Composite Action 的 inputs 沒有 type(全部都是 string):
inputs:
environment:
required: true
description: "deploy 環境"
enable-feature-x:
default: "false" # 字串
要在 Composite Action 處理 boolean 輸入時要小心:
# Composite Action 內
- name: Maybe do something
if: inputs.enable-feature-x == 'true' # 字串比較!不是 boolean
shell: bash
run: echo "feature enabled"
Outputs 傳遞
Reusable Workflow Outputs
需要兩層宣告:job 層 output 和 workflow 層 output
# Reusable workflow 內
on:
workflow_call:
outputs:
image-tag:
description: "built image tag"
value: ${{ jobs.build.outputs.tag }} # 從 job output 取
jobs:
build:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.meta.outputs.tag }} # 從 step 取
steps:
- id: meta
run: echo "tag=v1.2.3" >> "$GITHUB_OUTPUT"
# 呼叫端取用
jobs:
build:
uses: ./.github/workflows/build.yml
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "deploying ${{ needs.build.outputs.image-tag }}"
Composite Action Outputs
直接從 step 取值:
# action.yml
outputs:
result:
value: ${{ steps.calc.outputs.value }}
runs:
using: "composite"
steps:
- id: calc
shell: bash
run: echo "value=42" >> "$GITHUB_OUTPUT"
# 呼叫端
- id: my-action
uses: ./.github/actions/calc
- run: echo "got ${{ steps.my-action.outputs.result }}"
Secrets 傳遞
Reusable Workflow 三種傳遞方式
1. 明確列出
jobs:
deploy:
uses: ./.github/workflows/deploy.yml
secrets:
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
2. secrets: inherit(全部繼承)
jobs:
deploy:
uses: ./.github/workflows/deploy.yml
secrets: inherit
inherit只在同 repo / 同 organization 呼叫時可用,且 organization 設定要允許
3. 跨 repo 必須明確傳遞
跨 repo 的 reusable workflow 不能用 secrets: inherit,必須一個一個列出。
Composite Action 沒有 secrets
Composite Action 只能用 inputs 接收,呼叫端要明確傳:
- uses: ./.github/actions/deploy
with:
aws-key: ${{ secrets.AWS_ACCESS_KEY }} # 把 secret 當 input 傳
⚠️ 注意:傳給 Composite Action 的 secret 在 log 還是會被自動 mask,但是只要 secret 出現在 action.yml 的
inputs.default或 hardcoded 字串中,就會洩漏。
版本管理策略
三種引用方式
| 方式 | 範例 | 安全性 | 維護性 |
|---|---|---|---|
| Branch | @main |
❌ 低 | ⚠️ 變動快 |
| Tag | @v1 或 @v1.2.3 |
⚠️ 中(tag 可被移動) | ✅ 好 |
| Full SHA | @a1b2c3d... |
✅ 高 | ❌ 升級麻煩 |
對外部 Action 的建議
# ❌ 危險:branch 隨時會變
- uses: some-org/some-action@main
# ⚠️ 一般:可接受,信任維護者
- uses: some-org/some-action@v3
# ✅ 安全:鎖死 SHA,但要記得用 Dependabot 升級
- uses: some-org/some-action@a1b2c3d4e5f6789... # v3.1.0
GitHub 推薦的折衷做法:寫 full SHA + 註解寫 tag
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
這樣 Dependabot 可以同時識別 tag 和升 SHA。
對內部 Reusable Workflow 的策略
公司內部 reusable workflow 建議:
- 主要 caller 使用 major tag(
@v1) - 發佈 minor / patch tag(
@v1.2.0) - 同時維護 floating tag(讓
v1指向最新的v1.x)
# 發佈新版本
git tag v1.2.0
git tag -f v1 # 強制更新 floating tag
git push origin v1.2.0
git push origin v1 --force
呼叫端:
# 自動拿到 v1 系列最新版
uses: my-org/ci-templates/.github/workflows/build.yml@v1
權限與安全
Reusable Workflow 的 permissions
# 在 reusable workflow 內宣告
on: workflow_call
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
# ...
呼叫端的 permissions 必須大於等於 reusable workflow 需要的權限,否則會失敗:
# 呼叫端
permissions:
contents: read
packages: write # 必須給,否則 reusable workflow 拿不到
jobs:
build:
uses: ./.github/workflows/publish.yml
Composite Action 沒有獨立 permissions
Composite Action 繼承呼叫端 job 的 permissions。要做敏感操作時,需要呼叫端的 workflow 明確開啟對應權限。
Organization 級別的限制
公司可以限制哪些 reusable workflow / action 可以被使用:
Organization Settings → Actions → General → Allowed actions
✅ Allow {org} actions and reusable workflows
✅ Allow actions created by GitHub
✅ Allow specified actions:
- actions/checkout@*
- docker/login-action@v3.*.*
- aws-actions/configure-aws-credentials@v4
這個設定能避免有人偷偷用未稽核的 action。
實戰範例
範例 1:統一的多語言 CI 模板(Reusable Workflow)
# my-org/ci-templates/.github/workflows/standard-ci.yml
name: Standard CI
on:
workflow_call:
inputs:
language:
type: string
required: true # go | node | python
version:
type: string
required: true
run-tests:
type: boolean
default: true
coverage-threshold:
type: number
default: 80
secrets:
CODECOV_TOKEN:
required: false
jobs:
setup-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup Go
if: inputs.language == 'go'
uses: actions/setup-go@v5
with:
go-version: ${{ inputs.version }}
- name: Setup Node
if: inputs.language == 'node'
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.version }}
cache: "npm"
- name: Setup Python
if: inputs.language == 'python'
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.version }}
- name: Install deps
run: |
case "${{ inputs.language }}" in
go) go mod download ;;
node) npm ci ;;
python) pip install -r requirements.txt ;;
esac
- name: Run tests
if: inputs.run-tests
run: |
case "${{ inputs.language }}" in
go) go test -race -coverprofile=coverage.out ./... ;;
node) npm test -- --coverage ;;
python) pytest --cov ;;
esac
- name: Upload coverage
if: inputs.run-tests && env.CODECOV_TOKEN != ''
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
呼叫端:
# my-org/some-go-repo/.github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
ci:
uses: my-org/ci-templates/.github/workflows/standard-ci.yml@v1
with:
language: go
version: "1.22"
coverage-threshold: 85
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
範例 2:共用 Docker 推送步驟(Composite Action)
# my-org/shared-actions/build-and-push/action.yml
name: "Build and Push Docker Image"
description: "Build multi-arch image and push to registry"
inputs:
registry:
required: true
description: "registry hostname"
image-name:
required: true
tag:
required: false
default: "latest"
registry-username:
required: true
registry-password:
required: true
platforms:
required: false
default: "linux/amd64,linux/arm64"
outputs:
image-digest:
value: ${{ steps.push.outputs.digest }}
runs:
using: "composite"
steps:
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Login
uses: docker/login-action@v3
with:
registry: ${{ inputs.registry }}
username: ${{ inputs.registry-username }}
password: ${{ inputs.registry-password }}
- name: Build and push
id: push
uses: docker/build-push-action@v5
with:
platforms: ${{ inputs.platforms }}
push: true
tags: ${{ inputs.registry }}/${{ inputs.image-name }}:${{ inputs.tag }}
呼叫端:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: img
uses: my-org/shared-actions/build-and-push@v2
with:
registry: ghcr.io
image-name: ${{ github.repository }}
tag: ${{ github.sha }}
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}
- run: echo "digest = ${{ steps.img.outputs.image-digest }}"
範例 3:Reusable Workflow + Composite Action 組合
複雜場景常會混用:
# my-org/ci-templates/.github/workflows/full-deploy.yml
on:
workflow_call:
inputs:
environment: { type: string, required: true }
jobs:
build:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.img.outputs.image-digest }}
steps:
- uses: actions/checkout@v4
- id: img
uses: my-org/shared-actions/build-and-push@v2 # Composite Action
with:
registry: ghcr.io
image-name: ${{ github.repository }}
tag: ${{ github.sha }}
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}
deploy:
needs: build
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: my-org/shared-actions/k8s-deploy@v2 # 另一個 Composite Action
with:
image: ${{ needs.build.outputs.image }}
env: ${{ inputs.environment }}
最佳實踐
1. 命名規則
.github/workflows/
├── ci.yml # 一般 workflow
├── deploy.yml
└── _reusable-build.yml # 底線開頭表示是被呼叫的
.github/actions/
├── setup-go/
│ └── action.yml
└── deploy-to-k8s/
└── action.yml
底線開頭只是慣例(GitHub 沒強制),但讓人一眼看出這是「不會被自動觸發」的 reusable workflow。
2. 輸入要有 default 與 description
inputs:
environment:
type: string
required: true
description: "Deploy 目標環境(staging | production)"
timeout-minutes:
type: number
required: false
default: 30
description: "整個 deploy 流程的超時(分鐘)"
description 不只是文件,GitHub UI 在 workflow_dispatch 觸發時會顯示。
3. 不要在 Reusable Workflow 內寫死 runner
# ❌ 寫死,無法在 self-hosted 跑
jobs:
build:
runs-on: ubuntu-latest
# ✅ 把 runner 開放成 input
jobs:
build:
runs-on: ${{ inputs.runner-os }}
4. 限制 Reusable Workflow 的 trigger
# 純 reusable workflow 只要 workflow_call
on:
workflow_call:
...
# 同時想手動觸發?加上 workflow_dispatch
on:
workflow_call:
inputs:
environment: { type: string, required: true }
workflow_dispatch:
inputs:
environment: { type: choice, options: [staging, production] }
注意 workflow_call 和 workflow_dispatch 的 inputs schema 可以共用,但要重複寫一次。
5. 避免在 Composite Action 內寫死 path
# ❌ 寫死路徑
- shell: bash
run: ls /github/workspace/dist
# ✅ 用 GITHUB_WORKSPACE
- shell: bash
run: ls "$GITHUB_WORKSPACE/dist"
6. 測試 Reusable Workflow
Reusable Workflow 沒辦法獨立執行(只能被呼叫),建議:
- 同 repo 內建立
test-reusable.yml來測試 - 用
workflow_dispatch觸發手動驗證 - 改完後先發 pre-release tag(
v1.2.0-rc.1),caller 試用沒問題再正式發
常見問題
Q1: Reusable Workflow 呼叫失敗,提示 "secret not provided"
原因:reusable workflow 內的 secrets: 區塊宣告了 required: true,但呼叫端沒傳。
解法:
# 呼叫端
jobs:
build:
uses: ./.github/workflows/build.yml
secrets:
MY_SECRET: ${{ secrets.MY_SECRET }} # 補上
或在 reusable workflow 內改 required: false,內部自己判斷有沒有提供。
Q2: Composite Action 內的 run: 為什麼一定要 shell?
原因:GitHub Actions 規定 Composite Action 的每個 run: step 必須明確指定 shell,因為 Composite Action 可能在不同 OS 上跑,不能自動推測。
runs:
using: "composite"
steps:
- run: echo hello
shell: bash # 必填!
Q3: 為什麼 secrets: inherit 不能用?
可能原因:
- 跨 repo 呼叫:
inherit只在同 repo / 同 org 可用 - Organization 設定限制:org 管理員可以禁用
inherit - Public repo 呼叫 private repo 的 workflow:有額外限制
檢查:Organization Settings → Actions → General → Workflow permissions
Q4: Reusable Workflow 一直顯示「Startup failure」
常見原因:
- 語法錯誤:
on:沒有workflow_call - 路徑寫錯:同 repo 引用要用
./.github/workflows/x.yml,跨 repo 要owner/repo/.github/workflows/x.yml@ref - 權限不足:呼叫端的
permissions:不夠 - 巢狀過深:超過 4 層 reusable workflow
Q5: Composite Action 內可以呼叫 Reusable Workflow 嗎?
不行。Composite Action 是 step 序列,但 Reusable Workflow 是 job / workflow 層級。要把 reusable workflow 嵌入既有 job 是不可能的。
反過來 OK:Reusable Workflow 內可以用 Composite Action。
Q6: Output 取不到值,顯示空字串
原因:Reusable Workflow 的 output 必須兩層宣告:
on:
workflow_call:
outputs:
result:
value: ${{ jobs.work.outputs.r }} # 第 1 層:workflow output
jobs:
work:
outputs:
r: ${{ steps.s.outputs.x }} # 第 2 層:job output
steps:
- id: s
run: echo "x=hello" >> "$GITHUB_OUTPUT" # step output
少了任何一層,呼叫端 needs.<job>.outputs.result 都會是空。
Q7: 怎麼在 PR 上看到 Reusable Workflow 的 step 細節?
Reusable Workflow 跑起來會在 Actions 頁面獨立顯示(雖然由 caller 觸發)。
如果是 Composite Action,step 會被攤平到 caller 的 job log 內,但會用 [action-name] 前綴標示。
總結
核心要點
- 整套 pipeline 要複用 → Reusable Workflow(
workflow_call) - 幾個 step 要複用 → Composite Action(
runs.using: composite) - 不要混淆:Reusable Workflow 是獨立 job,Composite Action 是嵌入 step
- 跨 repo 必須鎖版本:用 tag 或 SHA,不要用 branch
- secrets 處理:Reusable Workflow 有
secrets:區塊和inherit,Composite 只能靠 inputs - 權限傳遞:Reusable Workflow 有獨立
permissions:,但 caller 要給足夠權限
快速決策
| 情境 | 用哪個 |
|---|---|
| 全公司統一的 build/test/deploy | Reusable Workflow |
| 需要 matrix / 多個 job | Reusable Workflow |
| 抽出「setup + cache + login」三步 | Composite Action |
| 想發到 Marketplace | Composite Action |
| 在既有 job 中插入幾個 step | Composite Action |
| 需要獨立 runner / 環境 / 權限 | Reusable Workflow |
速查
# Reusable Workflow 骨架
on:
workflow_call:
inputs: { name: { type: string, required: true } }
secrets: { TOKEN: { required: true } }
outputs: { result: { value: ${{ jobs.x.outputs.r }} } }
jobs:
x:
runs-on: ubuntu-latest
outputs: { r: ${{ steps.s.outputs.v }} }
steps: ...
# Composite Action 骨架
name: My Action
inputs: { name: { required: true, default: "x" } }
outputs: { result: { value: ${{ steps.s.outputs.v }} } }
runs:
using: composite
steps:
- shell: bash
run: echo "v=hi" >> "$GITHUB_OUTPUT"
id: s
建立日期:2026-05-25