Continuous Integration workflow (parcel-bundler/parcel)
The Continuous Integration workflow from parcel-bundler/parcel, explained and optimized by Latchkey.
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.
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
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
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
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:
- Dependency installs
- Network fetches
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.