Skip to content
Latchkey

Continuous Integration workflow (parcel-bundler/parcel)

The Continuous Integration workflow from parcel-bundler/parcel, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get run de-duplication, 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: parcel-bundler/parcel.github/workflows/ci.ymlLicense MITView source

What it does

This is the Continuous Integration workflow from the parcel-bundler/parcel 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: Continuous Integration

on:
  merge_group:
  pull_request:
  push:
    branches:
      - v2

permissions:
  contents: read

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
          components: rustfmt
      - run: yarn --immutable
      - run: yarn lint

  flow:
    name: Flow
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
      - run: yarn --immutable
      - run: yarn flow check

  ts-types:
    name: TypeScript types
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
      - run: yarn --immutable
      - run: yarn build-ts

  benchmarks:
    name: Benchmarks
    runs-on: ubuntu-latest
    steps:
      - name: PR Benchmarks
        uses: parcel-bundler/parcel-benchmark-action@master
        env:
          PARCEL_BENCHMARK_APIKEY: ${{ secrets.PARCEL_BENCHMARK_APIKEY }}

  unit_tests:
    name: Unit tests (${{ matrix.os }}, Node ${{ matrix.node }})
    strategy:
      matrix:
        node: [20, 22]
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
          node-version: ${{ matrix.node }}
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: ${{ matrix.os }}
      - name: Bump max inotify watches (Linux only)
        if: ${{ runner.os == 'Linux' }}
        run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p;
      - run: yarn --immutable
      - run: yarn build-native-release
      - run: yarn test:unit
      - name: Upload @parcel/rust artifacts on Linux with Node v20
        if: ${{ runner.os == 'Linux' && matrix.node == 20 }}
        uses: actions/upload-artifact@v4
        with:
          name: Rust Linux Binaries
          path: |
            packages/core/rust/index.d.ts
            packages/core/rust/index.js
            packages/core/rust/*.node

  integration_tests:
    name: Integration tests (${{ matrix.os }}, Node ${{ matrix.node }})
    strategy:
      matrix:
        node: [20, 22]
        os: [ubuntu-latest, macos-latest, windows-latest]
      # These tend to be quite flakey, so one failed instance shouldn't stop
      # others from potentially succeeding
      fail-fast: false
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
          node-version: ${{ matrix.node }}
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: ${{ matrix.os }}
      - name: Bump max inotify watches (Linux only)
        run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p;
        if: ${{ runner.os == 'Linux' }}
      - run: yarn --immutable
      - run: yarn build-native-release
      - run: yarn build
      - run: yarn test:integration-ci

  # Deployment steps taken from https://github.com/colinwilson/static-site-to-vercel/blob/master/.github/workflows/deploy-preview.yml
  repl_build:
    name: Build REPL
    if: ${{ github.event_name == 'pull_request' }}
    runs-on: ubuntu-latest
    permissions:
      deployments: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
          node-version: 20
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
          targets: wasm32-unknown-unknown
      - name: Install wasm-opt
        run: |
          curl -L -O https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-linux.tar.gz
          tar -xf binaryen-version_116-x86_64-linux.tar.gz
          echo "$PWD/binaryen-version_116/bin" >> $GITHUB_PATH
      - uses: Swatinem/rust-cache@v2
        with:
          key: wasm
      - name: Bump max inotify watches
        run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p;
      - run: yarn --immutable
      - name: Build native packages
        run: yarn build-native-release
      - run: yarn build
      - run: yarn build-native-wasm
      - run: yarn workspace @parcel/repl build
      # - name: Upload REPL
      #   uses: actions/upload-artifact@v4
      #   with:
      #     name: REPL
      #     path: 'packages/dev/repl/dist'
      - name: Start Deployment
        uses: bobheadxi/deployments@v1
        id: deployment
        with:
          step: start
          token: ${{ secrets.GITHUB_TOKEN }}
          ref: ${{ github.head_ref }}
          env: Preview
          override: false
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        id: vercel-action
        with:
          vercel-token: ${{ secrets.REPL_VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.REPL_VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.REPL_VERCEL_PROJECT_ID }}
          github-comment: false
          working-directory: packages/dev/repl
          # vercel-args: '--prod'
          scope: parcel
          alias-domains: |
            pr-{{PR_NUMBER}}.repl.parceljs.org
      - name: Update Deployment Status
        uses: bobheadxi/deployments@v1
        if: always()
        with:
          step: finish
          token: ${{ secrets.GITHUB_TOKEN }}
          env: Preview
          override: false
          status: ${{ job.status }}
          deployment_id: ${{ steps.deployment.outputs.deployment_id }}
          env_url: ${{ steps.vercel-action.outputs.preview-url }}

The same workflow, on Latchkey

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

name: Continuous Integration
 
on:
  merge_group:
  pull_request:
  push:
    branches:
      - v2
 
permissions:
  contents: read
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  lint:
    timeout-minutes: 30
    name: Lint
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
          components: rustfmt
      - run: yarn --immutable
      - run: yarn lint
 
  flow:
    timeout-minutes: 30
    name: Flow
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
      - run: yarn --immutable
      - run: yarn flow check
 
  ts-types:
    timeout-minutes: 30
    name: TypeScript types
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
      - run: yarn --immutable
      - run: yarn build-ts
 
  benchmarks:
    timeout-minutes: 30
    name: Benchmarks
    runs-on: latchkey-small
    steps:
      - name: PR Benchmarks
        uses: parcel-bundler/parcel-benchmark-action@master
        env:
          PARCEL_BENCHMARK_APIKEY: ${{ secrets.PARCEL_BENCHMARK_APIKEY }}
 
  unit_tests:
    timeout-minutes: 30
    name: Unit tests (${{ matrix.os }}, Node ${{ matrix.node }})
    strategy:
      matrix:
        node: [20, 22]
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
          node-version: ${{ matrix.node }}
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: ${{ matrix.os }}
      - name: Bump max inotify watches (Linux only)
        if: ${{ runner.os == 'Linux' }}
        run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p;
      - run: yarn --immutable
      - run: yarn build-native-release
      - run: yarn test:unit
      - name: Upload @parcel/rust artifacts on Linux with Node v20
        if: ${{ runner.os == 'Linux' && matrix.node == 20 }}
        uses: actions/upload-artifact@v4
        with:
          name: Rust Linux Binaries
          path: |
            packages/core/rust/index.d.ts
            packages/core/rust/index.js
            packages/core/rust/*.node
 
  integration_tests:
    timeout-minutes: 30
    name: Integration tests (${{ matrix.os }}, Node ${{ matrix.node }})
    strategy:
      matrix:
        node: [20, 22]
        os: [ubuntu-latest, macos-latest, windows-latest]
      # These tend to be quite flakey, so one failed instance shouldn't stop
      # others from potentially succeeding
      fail-fast: false
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
          node-version: ${{ matrix.node }}
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: ${{ matrix.os }}
      - name: Bump max inotify watches (Linux only)
        run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p;
        if: ${{ runner.os == 'Linux' }}
      - run: yarn --immutable
      - run: yarn build-native-release
      - run: yarn build
      - run: yarn test:integration-ci
 
  # Deployment steps taken from https://github.com/colinwilson/static-site-to-vercel/blob/master/.github/workflows/deploy-preview.yml
  repl_build:
    timeout-minutes: 30
    name: Build REPL
    if: ${{ github.event_name == 'pull_request' }}
    runs-on: latchkey-small
    permissions:
      deployments: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: yarn
          node-version: 20
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
          targets: wasm32-unknown-unknown
      - name: Install wasm-opt
        run: |
          curl -L -O https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-linux.tar.gz
          tar -xf binaryen-version_116-x86_64-linux.tar.gz
          echo "$PWD/binaryen-version_116/bin" >> $GITHUB_PATH
      - uses: Swatinem/rust-cache@v2
        with:
          key: wasm
      - name: Bump max inotify watches
        run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p;
      - run: yarn --immutable
      - name: Build native packages
        run: yarn build-native-release
      - run: yarn build
      - run: yarn build-native-wasm
      - run: yarn workspace @parcel/repl build
      # - name: Upload REPL
      #   uses: actions/upload-artifact@v4
      #   with:
      #     name: REPL
      #     path: 'packages/dev/repl/dist'
      - name: Start Deployment
        uses: bobheadxi/deployments@v1
        id: deployment
        with:
          step: start
          token: ${{ secrets.GITHUB_TOKEN }}
          ref: ${{ github.head_ref }}
          env: Preview
          override: false
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        id: vercel-action
        with:
          vercel-token: ${{ secrets.REPL_VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.REPL_VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.REPL_VERCEL_PROJECT_ID }}
          github-comment: false
          working-directory: packages/dev/repl
          # vercel-args: '--prod'
          scope: parcel
          alias-domains: |
            pr-{{PR_NUMBER}}.repl.parceljs.org
      - name: Update Deployment Status
        uses: bobheadxi/deployments@v1
        if: always()
        with:
          step: finish
          token: ${{ secrets.GITHUB_TOKEN }}
          env: Preview
          override: false
          status: ${{ job.status }}
          deployment_id: ${{ steps.deployment.outputs.deployment_id }}
          env_url: ${{ steps.vercel-action.outputs.preview-url }}
 

What changed

5 third-party actions are referenced by a movable tag. Pin them 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 7 jobs (17 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow