Skip to content
Latchkey

on pull request workflow (module-federation/module-federation-examples)

The on pull request workflow from module-federation/module-federation-examples, explained and optimized by Latchkey.

B

CI health: B - good

Point runs-on at Latchkey and get job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: module-federation/module-federation-examples.github/workflows/on-pull-request.ymlLicense MITView source

What it does

This is the on pull request workflow from the module-federation/module-federation-examples repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: on pull request
on: pull_request

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  # Stop previous runs
  stop-previous-run:
    runs-on: ubuntu-22.04
    steps:
      - name: Cancel Previous Runs
        uses: styfle/cancel-workflow-action@0.12.1
        with:
          access_token: ${{ secrets.GITHUB_TOKEN }}

  # Save necessary env variables to use them in next jobs
  save-env:
    runs-on: ubuntu-22.04
    needs: stop-previous-run
    if: always()
    steps:
      - name: Save env variables
        id: save-env-variables
        run: |
          mkdir -p ./workflow
          echo "${{ github.event.pull_request.number }}" > ./workflow/prNum
          echo "${{ github.run_id }}" > ./workflow/runId
          echo "${{ github.repository }}" > ./workflow/repoFullName
          echo "${{ github.repository_owner }}" > ./workflow/ownerName
      - name: Upload artifact
        id: upload-artifact
        uses: actions/upload-artifact@v4
        with:
          name: env_for_comment
          path: workflow/

  # Check if forked master is up to date with origin master in module federation examples repo
  forked_master_status:
    runs-on: ubuntu-22.04
    needs: stop-previous-run
    steps:
      - name: Check if forked master is up to date
        if: github.repository_owner == 'module-federation'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          BASE_REF="${{ github.event.pull_request.base.ref }}"
          FORK_REPO="${{ github.event.pull_request.head.repo.full_name }}"
          BASE_REPO="${{ github.repository }}"

          FORKED_SHA=$(gh api "repos/${FORK_REPO}/commits/${BASE_REF}" --jq '.sha')
          BASE_SHA=$(gh api "repos/${BASE_REPO}/commits/${BASE_REF}" --jq '.sha')

          echo "Fork SHA:  $FORKED_SHA"
          echo "Base SHA:  $BASE_SHA"

          if [ "$FORKED_SHA" == "$BASE_SHA" ]; then
            echo "The forked master is up to date with the base master branch"
          else
            echo "The forked master branch is not up to date with the base master branch, Please update your fork!"
            exit 1
          fi

  # Setup matrix from changed samples by lerna ls --since origin/master command
  setup-matrix:
    runs-on: ubuntu-22.04
    needs: forked_master_status
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - name: Checkout
        id: checkout-matrix
        uses: actions/checkout@v4
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}
          fetch-depth: 1

      - name: Fetch origin/master for diff
        run: git fetch origin master --depth=1

      - name: Get Playwright version
        id: playwright-version
        shell: bash
        run: |
          # Use the repo-pinned Playwright version as cache key input.
          version="$(node -p "String(require('./package.json').devDependencies['@playwright/test']||'').replace(/^[^0-9]*/, '')")"
          echo "version=$version" >> "$GITHUB_OUTPUT"

      - name: Enable corepack
        run: corepack enable

      - name: Setup Node.js with caching
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
          cache-dependency-path: '**/pnpm-lock.yaml'

      - name: Re-enable corepack after setup-node
        run: corepack enable

      - name: Get pnpm store directory
        id: pnpm-cache
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

      - name: Setup pnpm cache
        uses: actions/cache@v4
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            pnpm-store-${{ runner.os }}-

      - name: Cache Playwright browsers
        uses: actions/cache@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-headless-shell

      - name: Set Playwright cache status
        run: echo "PLAYWRIGHT_CACHE_HIT=${{ steps.playwright-cache.outputs.cache-hit }}" >> $GITHUB_ENV

      - name: Install dependencies
        run: |
          echo "Installing all dependencies to populate cache..."
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 pnpm install --frozen-lockfile --prefer-offline

      - name: Install Playwright with deps
        run: |
          # Install both system deps and browser in setup-matrix.
          # The browser cache will be shared with e2e jobs.
          pnpm exec playwright install --with-deps chromium chromium-headless-shell

      - name: Create matrix
        id: set-matrix
        run: |
          all="$(pnpm list --filter '*' --only-projects --depth -1 --json)"
          diff="$(pnpm list --filter '...[origin/master]' --only-projects --depth -1 --json)"
          matrix="$(node checkChangedWorkspaces.js "$all" "$diff")"
          echo $matrix
          echo "matrix=$matrix" >> $GITHUB_OUTPUT

  # Run e2e tests for changed samples (additionally install deps for all changed samples if there is no any created cache in master branch)
  run-e2e-test:
    needs: [setup-matrix]
    if: ${{ needs.setup-matrix.outputs.matrix != '{"container":[]}' }}
    runs-on: ubuntu-22.04
    timeout-minutes: 45
    strategy:
      fail-fast: false
      matrix: ${{fromJson(needs.setup-matrix.outputs.matrix)}}
    steps:
      - name: Checkout
        id: checkout-e2e
        uses: actions/checkout@v4
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}
          fetch-depth: 1

      - name: Get Playwright version
        id: playwright-version
        shell: bash
        run: |
          version="$(node -p "String(require('./package.json').devDependencies['@playwright/test']||'').replace(/^[^0-9]*/, '')")"
          echo "version=$version" >> "$GITHUB_OUTPUT"

      - name: Enable corepack
        run: corepack enable

      - name: Setup Node.js with caching
        id: setup-node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
          cache-dependency-path: '**/pnpm-lock.yaml'

      - name: Re-enable corepack after setup-node
        run: corepack enable

      - name: Get pnpm store directory
        id: pnpm-cache
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

      - name: Restore pnpm cache
        uses: actions/cache/restore@v4
        id: pnpm-store-cache
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            pnpm-store-${{ runner.os }}-
          fail-on-cache-miss: false

      - name: Restore Playwright cache
        uses: actions/cache/restore@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-headless-shell
          fail-on-cache-miss: false

      - name: Install dependencies
        id: install-deps-e2e
        env:
          NODE_OPTIONS: '--max_old_space_size=6144'
          FORCE_COLOR: 3
        run: |
          echo "Installing dependencies from cached pnpm store..."
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 pnpm install --frozen-lockfile --prefer-offline

      - name: Install Playwright browsers and deps
        run: |
          # Install both browser and system deps.
          # Use --with-deps to install system dependencies alongside browser.
          # Retry up to 3 times to handle apt lock contention from parallel jobs.
          for i in 1 2 3; do
            pnpm exec playwright install --with-deps chromium chromium-headless-shell && break
            echo "Attempt $i failed, retrying in 10 seconds..."
            sleep 10
          done

      - name: Run sample webpack e2e tests
        timeout-minutes: 30
        id: run-webpack-e2e-tests
        run: |
          node -v
          pnpm --filter "${{ matrix.container }}" run --if-present legacy:e2e:ci
          (lsof -i tcp:3000-3999 -i tcp:4000-4999 -i tcp:8080-8100 | awk 'NR!=1 {print $2}' | xargs -r kill) 2> /dev/null

      - name: Run sample rspack e2e tests
        timeout-minutes: 30
        id: run-rspack-e2e-tests
        run: |
          node -v
          pnpm --filter "${{ matrix.container }}" run --if-present e2e:ci
          (lsof -i tcp:3000-3999 -i tcp:4000-4999 -i tcp:8080-8100 | awk 'NR!=1 {print $2}' | xargs -r kill) 2> /dev/null

      - name: Create artifacts for test reports
        id: create-artifacts-test-reports
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results-${{ matrix.container }}
          path: |
            playwright-e2e/results/allure-results
            ${{ matrix.container }}/playwright-report
            ${{ matrix.container }}/test-results
          retention-days: 7

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

name: on pull request
on: pull_request
 
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true
 
jobs:
  # Stop previous runs
  stop-previous-run:
    timeout-minutes: 30
    runs-on: latchkey-small
    steps:
      - name: Cancel Previous Runs
        uses: styfle/cancel-workflow-action@0.12.1
        with:
          access_token: ${{ secrets.GITHUB_TOKEN }}
 
  # Save necessary env variables to use them in next jobs
  save-env:
    timeout-minutes: 30
    runs-on: latchkey-small
    needs: stop-previous-run
    if: always()
    steps:
      - name: Save env variables
        id: save-env-variables
        run: |
          mkdir -p ./workflow
          echo "${{ github.event.pull_request.number }}" > ./workflow/prNum
          echo "${{ github.run_id }}" > ./workflow/runId
          echo "${{ github.repository }}" > ./workflow/repoFullName
          echo "${{ github.repository_owner }}" > ./workflow/ownerName
      - name: Upload artifact
        id: upload-artifact
        uses: actions/upload-artifact@v4
        with:
          name: env_for_comment
          path: workflow/
 
  # Check if forked master is up to date with origin master in module federation examples repo
  forked_master_status:
    timeout-minutes: 30
    runs-on: latchkey-small
    needs: stop-previous-run
    steps:
      - name: Check if forked master is up to date
        if: github.repository_owner == 'module-federation'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          BASE_REF="${{ github.event.pull_request.base.ref }}"
          FORK_REPO="${{ github.event.pull_request.head.repo.full_name }}"
          BASE_REPO="${{ github.repository }}"
 
          FORKED_SHA=$(gh api "repos/${FORK_REPO}/commits/${BASE_REF}" --jq '.sha')
          BASE_SHA=$(gh api "repos/${BASE_REPO}/commits/${BASE_REF}" --jq '.sha')
 
          echo "Fork SHA:  $FORKED_SHA"
          echo "Base SHA:  $BASE_SHA"
 
          if [ "$FORKED_SHA" == "$BASE_SHA" ]; then
            echo "The forked master is up to date with the base master branch"
          else
            echo "The forked master branch is not up to date with the base master branch, Please update your fork!"
            exit 1
          fi
 
  # Setup matrix from changed samples by lerna ls --since origin/master command
  setup-matrix:
    timeout-minutes: 30
    runs-on: latchkey-small
    needs: forked_master_status
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - name: Checkout
        id: checkout-matrix
        uses: actions/checkout@v4
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}
          fetch-depth: 1
 
      - name: Fetch origin/master for diff
        run: git fetch origin master --depth=1
 
      - name: Get Playwright version
        id: playwright-version
        shell: bash
        run: |
          # Use the repo-pinned Playwright version as cache key input.
          version="$(node -p "String(require('./package.json').devDependencies['@playwright/test']||'').replace(/^[^0-9]*/, '')")"
          echo "version=$version" >> "$GITHUB_OUTPUT"
 
      - name: Enable corepack
        run: corepack enable
 
      - name: Setup Node.js with caching
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
          cache-dependency-path: '**/pnpm-lock.yaml'
 
      - name: Re-enable corepack after setup-node
        run: corepack enable
 
      - name: Get pnpm store directory
        id: pnpm-cache
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
 
      - name: Setup pnpm cache
        uses: actions/cache@v4
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            pnpm-store-${{ runner.os }}-
 
      - name: Cache Playwright browsers
        uses: actions/cache@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-headless-shell
 
      - name: Set Playwright cache status
        run: echo "PLAYWRIGHT_CACHE_HIT=${{ steps.playwright-cache.outputs.cache-hit }}" >> $GITHUB_ENV
 
      - name: Install dependencies
        run: |
          echo "Installing all dependencies to populate cache..."
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 pnpm install --frozen-lockfile --prefer-offline
 
      - name: Install Playwright with deps
        run: |
          # Install both system deps and browser in setup-matrix.
          # The browser cache will be shared with e2e jobs.
          pnpm exec playwright install --with-deps chromium chromium-headless-shell
 
      - name: Create matrix
        id: set-matrix
        run: |
          all="$(pnpm list --filter '*' --only-projects --depth -1 --json)"
          diff="$(pnpm list --filter '...[origin/master]' --only-projects --depth -1 --json)"
          matrix="$(node checkChangedWorkspaces.js "$all" "$diff")"
          echo $matrix
          echo "matrix=$matrix" >> $GITHUB_OUTPUT
 
  # Run e2e tests for changed samples (additionally install deps for all changed samples if there is no any created cache in master branch)
  run-e2e-test:
    needs: [setup-matrix]
    if: ${{ needs.setup-matrix.outputs.matrix != '{"container":[]}' }}
    runs-on: latchkey-small
    timeout-minutes: 45
    strategy:
      fail-fast: false
      matrix: ${{fromJson(needs.setup-matrix.outputs.matrix)}}
    steps:
      - name: Checkout
        id: checkout-e2e
        uses: actions/checkout@v4
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}
          fetch-depth: 1
 
      - name: Get Playwright version
        id: playwright-version
        shell: bash
        run: |
          version="$(node -p "String(require('./package.json').devDependencies['@playwright/test']||'').replace(/^[^0-9]*/, '')")"
          echo "version=$version" >> "$GITHUB_OUTPUT"
 
      - name: Enable corepack
        run: corepack enable
 
      - name: Setup Node.js with caching
        id: setup-node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
          cache-dependency-path: '**/pnpm-lock.yaml'
 
      - name: Re-enable corepack after setup-node
        run: corepack enable
 
      - name: Get pnpm store directory
        id: pnpm-cache
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
 
      - name: Restore pnpm cache
        uses: actions/cache/restore@v4
        id: pnpm-store-cache
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            pnpm-store-${{ runner.os }}-
          fail-on-cache-miss: false
 
      - name: Restore Playwright cache
        uses: actions/cache/restore@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-headless-shell
          fail-on-cache-miss: false
 
      - name: Install dependencies
        id: install-deps-e2e
        env:
          NODE_OPTIONS: '--max_old_space_size=6144'
          FORCE_COLOR: 3
        run: |
          echo "Installing dependencies from cached pnpm store..."
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 pnpm install --frozen-lockfile --prefer-offline
 
      - name: Install Playwright browsers and deps
        run: |
          # Install both browser and system deps.
          # Use --with-deps to install system dependencies alongside browser.
          # Retry up to 3 times to handle apt lock contention from parallel jobs.
          for i in 1 2 3; do
            pnpm exec playwright install --with-deps chromium chromium-headless-shell && break
            echo "Attempt $i failed, retrying in 10 seconds..."
            sleep 10
          done
 
      - name: Run sample webpack e2e tests
        timeout-minutes: 30
        id: run-webpack-e2e-tests
        run: |
          node -v
          pnpm --filter "${{ matrix.container }}" run --if-present legacy:e2e:ci
          (lsof -i tcp:3000-3999 -i tcp:4000-4999 -i tcp:8080-8100 | awk 'NR!=1 {print $2}' | xargs -r kill) 2> /dev/null
 
      - name: Run sample rspack e2e tests
        timeout-minutes: 30
        id: run-rspack-e2e-tests
        run: |
          node -v
          pnpm --filter "${{ matrix.container }}" run --if-present e2e:ci
          (lsof -i tcp:3000-3999 -i tcp:4000-4999 -i tcp:8080-8100 | awk 'NR!=1 {print $2}' | xargs -r kill) 2> /dev/null
 
      - name: Create artifacts for test reports
        id: create-artifacts-test-reports
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results-${{ matrix.container }}
          path: |
            playwright-e2e/results/allure-results
            ${{ matrix.container }}/playwright-report
            ${{ matrix.container }}/test-results
          retention-days: 7
 

What changed

1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 5 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow