iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🦾

Patterns for Organizing GitHub Actions

に公開

GitHub Actions can be started with a single YAML file, but as you try to write multiple workflows or jobs, redundant parts increase rapidly.

For example:

  • The build and test steps are the same for both merging and pushing, but you want to perform release processing only during a merge.
  • You want to separate jobs, but you need to perform the same setup every time.
  • You want to parallelize parts that can be checked concurrently (e.g., running tests and lints) as much as possible. If they are serial, one failure prevents the other from running, and you don't get the results.

To meet these requirements while reducing code duplication and keeping things organized, I have my own set of patterns for writing them, so I will summarize those methods here.

Summary

  • Divide roles into Single Workflows and Composite Workflows
    • Role of Single Workflow
      • Called only via workflow_call.
      • Created for units like build, test, or release processing.
    • Role of Composite Workflow
      • Called by external triggers such as on push or on schedule.
      • Combines and arranges multiple Single Workflows as Jobs.
        • Example: Build -> Test for testing on push.
        • Example: Build -> Test -> Release for releasing on merge.
  • Exchange artifacts between multiple Jobs using cache
  • Utilize composite actions

Sample

https://github.com/ykyki/blog/tree/2ddb39e48de7862c2772c114201edd25cfb22b42

composite action

Example of an action that performs common setup

name: common-setup
description: |
  action for common setup of node/pnpm/bun

inputs:
  NODE_VERSION:
    description: ''
    required: false
    default: '20'
  PNPM_VERSION:
    description: ''
    required: false
    default: '8'
  BUN_VERSION:
    description: ''
    required: false
    default: '1'
  REQUIRE_BUN:
    description: ''
    required: false
    default: 'true'

runs:
  using: 'composite'
  steps:
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.NODE_VERSION }}
    - uses: pnpm/action-setup@v3
      with:
        version: ${{ inputs.PNPM_VERSION }}
        run_install: false
    - uses: oven-sh/setup-bun@v1
      if: inputs.REQUIRE_BUN == 'true'
      with:
        bun-version: ${{ inputs.BUN_VERSION }}

Single Workflow

name: _install-pnpm-deps

on:
  workflow_call:
    inputs:
      use-pnpm-store-cache:
        type: boolean
        required: false
        default: true

jobs:
  cache-and-install:
    runs-on: ubuntu-latest
    timeout-minutes: 1
    env:
      PNPM_STORE_PATH: ''
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/common-setup
        with:
          REQUIRE_BUN: 'false'
      - uses: ./.github/actions/restore-pnpm-store-cache
        id: restore-pnpm-store-cache
        if: inputs.use-pnpm-store-cache == true
      - run: pnpm install
        if: steps.restore-pnpm-store-cache.outputs.cache-hit != 'true'
      - name: Get pnpm store directory
        run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
      - name: Save pnpm-store cache
        uses: actions/cache/save@v4
        if: steps.restore-pnpm-store-cache.outputs.cache-hit != 'true'
        with:
          path: ${{ env.PNPM_STORE_PATH }}
          key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}

Composite Workflow

name: Build🔨, Verify🧪 on push

on:
  push:

jobs:
  install-pnpm-deps:
    uses: ./.github/workflows/_install-pnpm-deps.yaml

  build-uniproc:
    needs: install-pnpm-deps
    uses: ./.github/workflows/_build-uniproc.yaml

  build-sveltecf:
    needs:
      - install-pnpm-deps
      - build-uniproc
    uses: ./.github/workflows/_build-sveltecf.yaml

  verify-uniproc:
    needs: build-uniproc
    uses: ./.github/workflows/_verify-uniproc.yaml

  verify-sveltecf:
    needs: build-sveltecf
    uses: ./.github/workflows/_verify-sveltecf.yaml

Execution Results

When including failures
GitHub Actions result 2

When all succeeded
GitHub Actions result

Directory Structure

GitHub Actions directory structure

Supplement

You can grasp the overall atmosphere by reading the summary and samples above.

Here are a few additional notes.

  • Write in the order of Single Workflow → Composite Workflow. As you continue writing and start noticing common parts, extract them into a composite action.
  • Composite action syntax: Metadata syntax for GitHub Actions - GitHub Docs
  • Because the cache is used frequently, be mindful of GitHub Storage usage in private repositories, etc.
    • While up to 1GB/month is available for free, the cache size can grow larger than expected.
  • Starting the filename of a Single Workflow with _ makes it easier to distinguish from Composite Workflows.

References

Discussion