D3:撰寫與維護 actions
D3-O1 建立與疑難排解自訂 actions
自訂 action 是把一段可重用的自動化包起來,讓工作流程在步驟層級用 uses: 呼叫。官方把 action 分成三種類型,選型的第一個問題是「它要在哪些作業系統的 runner 上跑」。
三種 action 類型
| 類型 | 可以跑在 | 適合 | 要留意 |
|---|---|---|---|
| Docker 容器 action | 只有 Linux runner;自管 runner 需安裝 Docker | 需要特定環境、語言或系統工具 | 啟動比較慢;在 macOS、Windows runner 上無法執行 |
| JavaScript action | Linux、macOS、Windows | 需要跨平台、啟動快、用 toolkit 呼叫 API | 為了相容所有 GitHub 代管 runner,程式碼應是純 JavaScript,不依賴其他二進位檔 |
| 複合(composite)action | Linux、macOS、Windows | 把多個步驟包成一個 action | 不含 job;跑在呼叫它的 job 的機器上;不能直接用 secrets context |
JavaScript action
runs.using的值是node20或node24;main必填,指向執行的檔案。- 典型結構:
action.yml、package.json、src/index.js(原始碼),以及打包後的dist/index.js(main指向它)。 - 不要提交
node_modules:官方說提交它可能造成問題。改用 rollup.js 或@vercel/ncc之類的工具,把程式碼與相依套件打包成單一檔案並一起提交。 @actions/core提供與工作流程命令、輸入與輸出變數、結束狀態與除錯訊息的介面(例如core.setOutput設定輸出,供之後的步驟使用);@actions/github回傳已驗證的 Octokit REST 用戶端,並可存取 Actions 的 context。- 發生錯誤時用
core.setFailed(error.message)記錄訊息並設定失敗的結束碼。 pre在 job 開始、main之前執行,post在main完成之後執行;pre-if、post-if預設都是always()。這兩個條件裡的狀態檢查函式,是依 job 的狀態評估,不是依 action 自己的狀態;pre-if裡沒有stepcontext,因為還沒有任何步驟執行過。本機 action(local action)不支援pre。
Docker 容器 action
runs.using必須是'docker',image必填:可以是 repository 內命名為Dockerfile的檔案(路徑相對於中繼資料檔),或公開的映像參考。args是傳給容器進入點的陣列;entrypoint可以覆寫 Dockerfile 的ENTRYPOINT。官方頁面敘述:輸入值不會自動成為INPUT_*環境變數,除非用args傳入。- 可用
env、pre-entrypoint、post-entrypoint;pre-entrypoint、post-entrypoint各自在以同一個基底映像啟動的獨立容器中執行。 - Docker 容器 action 的教學頁面(Dockerfile 的撰寫細節、
WORKDIR與工作目錄的對應)這次沒有讀到,細節請在官方頁面確認。
複合 action
runs.using必須是'composite',steps必填;每個步驟是run或uses,有run的步驟必須同時設定shell(官方的 metadata 頁面寫明)。github.action_path指向 action 所在的目錄;官方範例把它放進環境變數,並加到GITHUB_PATH,讓目錄內的指令碼可以用名稱呼叫。- 輸出要有
value,把它對應到某個步驟的輸出,例如${{ steps.random-number-generator.outputs.random-number }};產生輸出的步驟要有id,並寫入$GITHUB_OUTPUT。 - 輸入值不要直接插進指令碼,而是先放進步驟的
env(官方範例是INPUT_WHO_TO_GREET: ${{ inputs.who-to-greet }})。官方提醒要隨時考慮程式碼是否可能執行不受信任的輸入,這就是領域五談的 script injection。
不可變 release(與「不可變 actions」)
官方的 immutable releases 說明:發佈之後,release 的 Git 標籤與資產都不能再更動——標籤被鎖定在特定 commit,不能移動,且在該 release 存在時不能刪除;資產(二進位檔、封存檔)不能修改或刪除;標題、發行說明與是否為 pre-release/latest 仍可編輯。刪掉 release 之後可以刪標籤,但不能重複使用標籤名稱。建立不可變的 release 會自動產生記錄標籤、commit SHA 與資產的 release attestation。
官方 Study Guide 同時提到「不可變 actions(immutable actions)在 GitHub 代管 runner 的推出,與它對版本固定與 registry 來源的影響」。我找不到可核對的官方頁面(先前猜的網址回 404,搜尋也只找到第三方文章),所以本站不寫這部分的細節,備考前請在官方頁面確認。
疑難排解 action 的執行
- 日誌看不出原因時,用
ACTIONS_STEP_DEBUG(或重新執行時勾選 Enable debug logging)取得更詳細的步驟記錄;::debug::訊息只有在ACTIONS_STEP_DEBUG是true時才顯示。 required: true的輸入,在沒傳值時不會自動報錯,action 必須自己檢查;deprecationMessage則在輸入被使用時以警告記錄。- 輸出可以不事先宣告就設定,但沒宣告的輸出不會出現在 metadata,也不利於其他人理解;JavaScript 與 Docker action 靠工作流程命令設定輸出。
- 想讓 action 失敗,JavaScript action 用
core.setFailed;一般步驟靠非零結束碼。
判斷練習
情境 A: 你要寫一個 action,團隊同時用 Ubuntu、Windows 與 macOS 的 runner,邏輯是呼叫 REST API 並整理結果。
解析: 用 JavaScript action(或把步驟包成複合 action)。Docker 容器 action 只能在 Linux runner 執行,Windows 與 macOS 會失敗。
情境 B: 同事把 node_modules 提交進 action 的 repository,說這樣使用者不用另外安裝。
解析: 官方說明提交 node_modules 可能造成問題。改成用 rollup.js 或 @vercel/ncc 把程式碼與相依套件打包成 dist/index.js 並提交,main 指向它。
情境 C: 複合 action 的某個步驟用 run: ./cleanup.sh,但被標為無效。
解析: 複合 action 內有 run 的步驟必須指定 shell(例如 shell: bash)。
來源:About custom actions(GH200-DOC-23)、Metadata syntax(GH200-DOC-24)、Create a JavaScript action(GH200-DOC-25)、Create a composite action(GH200-DOC-26)、Immutable releases(GH200-DOC-30)、Workflow commands(GH200-DOC-03);2026-11-03 以頁面原文比對本節的數字與條件。Docker 容器 action 的教學頁面回 404,沒有讀到;「不可變 actions」沒有可核對的官方頁面。
D3-O2 定義 action 結構與中繼資料
必要的檔案與位置
- 中繼資料檔必須命名為
action.yml或action.yaml,官方偏好action.yml;它定義輸入、輸出與runs設定。 - 頂層必填的鍵是
name、description、runs;author與inputs是選填。 - 要發佈到 Marketplace 的 repository,根目錄只能有一個 action 中繼資料檔;放在子資料夾的中繼資料檔可以使用,但不會被 Marketplace 自動列出。
inputs 與 outputs
| 項目 | 規則 |
|---|---|
輸入 description |
每個輸入都需要 |
輸入 required |
選填的布林值;設成 true 並不會自動在缺值時報錯 |
輸入 default |
選填字串,工作流程沒提供該輸入時使用 |
輸入 deprecationMessage |
選填;使用該輸入時以警告記錄 |
| 輸出(JavaScript、Docker) | 每個輸出需要 description;沒有 value 鍵 |
| 輸出(composite) | 還需要 value,指向字串或運算式(例如某個步驟的輸出) |
| 輸出大小 | 每個 job 1 MB、每次工作流程執行合計 50 MB |
在 action 內使用工作流程命令
action 靠工作流程命令與環境檔跟執行器溝通:寫入 $GITHUB_OUTPUT 設定輸出,$GITHUB_ENV 設定後續步驟的環境變數,$GITHUB_PATH 擴充 PATH,$GITHUB_STEP_SUMMARY 寫入工作摘要;::add-mask:: 遮蔽值,::error、::warning、::notice 建立註解。toolkit 函式與它們的對應是:core.setOutput 對應 GITHUB_OUTPUT,core.saveState 對應 GITHUB_STATE。細節見領域一。
品牌(branding)
選填的 branding 控制 Marketplace 上的圖示:color 可用 white、black、yellow、blue、green、orange、red、purple、gray-dark;icon 是 Feather 圖示名稱(部分圖示,包括品牌圖示,不在支援清單內)。
判斷練習
情境 A: 複合 action 宣告了輸出 result,但沒有 value,呼叫端拿不到值。
解析: 複合 action 的輸出必須有 value,並指向步驟的輸出,例如 ${{ steps.calc.outputs.result }};產生輸出的步驟要有 id 並寫入 $GITHUB_OUTPUT。JavaScript 與 Docker action 的輸出則只需要 description。
情境 B: 輸入 token 設了 required: true,使用者沒傳值,action 卻照常執行並在後面才出錯。
解析: required: true 不會自動讓缺值報錯。action 要自己檢查輸入,必要時 core.setFailed。
來源:Metadata syntax(GH200-DOC-24)、About custom actions(GH200-DOC-23)、Publish in GitHub Marketplace(GH200-DOC-28)、Workflow commands(GH200-DOC-03);2026-11-03 以頁面原文比對本節的數字與條件。
D3-O3 散布與維護 actions
三種散布模式
| 模式 | 做法 | 要記住 |
|---|---|---|
| 公開 | 放在公開 repository,任何人都能用 | 要讓所有人使用,repository 必須是公開的 |
| 私有 | 放在私有 repository,在 Settings → Actions → General → Access 開放給其他 repository 使用 | runner 以範圍受限的安裝權杖下載,只有該 repository 的讀取權限,一小時後自動過期 |
| Marketplace | 公開 repository,發佈 release 時選擇發佈到 GitHub Marketplace | 見下方條件 |
私有 repository 的共用有一個安全注意:若讓其他 repository 的工作流程能存取私有 repository,那些 repository 的外部協作者也能間接存取它,並且可以看到使用該 action 或工作流程的工作流程執行日誌。官方這一頁只寫了「使用者帳號擁有的 repository」那個選項;組織與企業層級的選項(例如只開放給同組織)這次沒有在所讀頁面讀到,請在官方頁面確認。
發佈到 GitHub Marketplace
- 條件:action 必須在公開 repository;根目錄必須有單一的
action.yml或action.yaml;中繼資料的name必須唯一;擁有該 repository 的帳號必須接受 GitHub Marketplace Developer Agreement(否則 Publish 核取方塊是停用的);發佈需要雙因素驗證。 - 符合條件時,action 會立即發佈,GitHub 不會先審核。
- 步驟:repository 首頁開啟
action.yml→ 橫幅上的 Draft a release → 在 Release Action 選 Publish this Action to the GitHub Marketplace → 修正中繼資料的錯誤或警告直到顯示「Everything looks good!」→ 選主要分類(可再選一個次要分類)→ 填版本標籤與 release 標題 → Publish release(需要雙因素驗證)。 name不能:與 Marketplace 上已發佈的 action 同名;與 GitHub 使用者或組織同名(除非由該擁有者發佈);與既有的 Marketplace 分類同名;與 GitHub 的功能名稱相同(GitHub 保留這些名稱)。
版本與發佈策略
- 用語意化版本的標籤建立 release(例如
v1.1.3),並讓**主版本標籤(v1)與次版本標籤(v1.1)**維持指向最新且適當的 commit。使用者釘在v1就能持續取得最新穩定、不破壞相容性的功能。 - 建議的發佈流程由工作流程自動編譯並調整標籤;官方範例用
JasonEtco/build-and-tag-action強制推送語意化的主、次、修補版本標籤。如果啟用了 immutable releases,就無法用它強制推送綁定到 release 的標籤——不可變 release 與「移動v1標籤」的做法互相衝突,要在兩者之間取捨。 - 不把相依套件提交到
main,只提交到有標籤的發佈 commit。 - 維護有大量使用範例的
README,並放工作流程狀態徽章;加上SECURITY社群健康檔,並用 GitHub 的安全功能溝通弱點與修復。 - 使用端想要最穩定,就釘到完整的 commit SHA(可重用工作流程的官方說明:用 commit SHA 是穩定與安全的最佳選項)。
判斷練習
情境 A: 你維護的 action 剛發佈 v1.4.0,要讓釘在 v1 的使用者自動取得,但他們不應該被破壞性變更影響。
解析: 發佈 v1.4.0 的 release,並把 v1 與 v1.4 標籤移到這個 commit。主版本標籤只應指向相容的版本;破壞性變更要發 v2。
情境 B: 你的組織啟用了 immutable releases,之後發佈流程在移動 v1 標籤時失敗。
解析: 不可變 release 鎖定標籤到特定 commit,無法被移動,所以不能再用強制推送去移動綁定 release 的標籤。需要調整版本策略(例如讓使用者釘到具體版本標籤或 SHA)。
情境 C: 你想把 action 發佈到 Marketplace,但 Publish 核取方塊是灰的。
解析: 檢查帳號是否已接受 GitHub Marketplace Developer Agreement;其他條件包括 repository 公開、根目錄單一 action.yml、name 唯一,以及雙因素驗證。
來源:Release and maintain actions(GH200-DOC-27)、Publish in GitHub Marketplace(GH200-DOC-28)、Share across private repositories(GH200-DOC-29)、Immutable releases(GH200-DOC-30)、Reuse workflows(GH200-DOC-08);2026-11-03 以頁面原文比對本節的數字與條件。