Native and WSL workflow (import-js/eslint-plugin-import)
The Native and WSL workflow from import-js/eslint-plugin-import, explained and optimized by Latchkey.
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.
What it does
This is the Native and WSL workflow from the import-js/eslint-plugin-import 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: Native and WSL
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
build:
runs-on: ${{ matrix.os }}
defaults:
run:
shell: ${{ matrix.configuration == 'wsl' && 'wsl-bash {0}' || 'pwsh' }}
strategy:
fail-fast: false
matrix:
os: [windows-2022]
node-version: [18, 16, 14, 12, 10, 8, 6, 4]
configuration: [wsl, native]
steps:
- uses: actions/checkout@v4
- uses: Vampire/setup-wsl@v7
if: matrix.configuration == 'wsl'
with:
distribution: Ubuntu-22.04
wsl-version: 2
- run: curl --version
- name: 'WSL: do all npm install steps'
if: matrix.configuration == 'wsl'
env:
ESLINT_VERSION: 7
TRAVIS_NODE_VERSION: ${{ matrix.node-version }}
run: |
export ESLINT_IMPORT_WSL_TEST_TIMEOUT=60000
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
nvm install --latest-npm ${{ matrix.node-version }}
if [ ${{ matrix.node-version }} -ge 4 ] && [ ${{ matrix.node-version }} -lt 6 ]; then
npm install eslint@4 --no-save --ignore-scripts
npm install
npm install eslint-import-resolver-typescript@1.0.2 --no-save
npm uninstall @angular-eslint/template-parser @typescript-eslint/parser --no-save
fi
if [ ${{ matrix.node-version }} -ge 6 ] && [ ${{ matrix.node-version }} -lt 7 ]; then
npm install eslint@5 --no-save --ignore-scripts
npm install
npm uninstall @angular-eslint/template-parser --no-save
npm install eslint-import-resolver-typescript@1.0.2 @typescript-eslint/parser@3 --no-save
fi
if [ ${{ matrix.node-version }} -ge 7 ] && [ ${{ matrix.node-version }} -lt 8 ]; then
npm install eslint@6 --no-save --ignore-scripts
npm install
npm install eslint-import-resolver-typescript@1.0.2 typescript-eslint-parser@20 --no-save
npm uninstall @angular-eslint/template-parser --no-save
fi
if [ ${{ matrix.node-version }} -eq 8 ]; then
npm install eslint@6 --no-save --ignore-scripts
npm install
npm uninstall @angular-eslint/template-parser --no-save
npm install @typescript-eslint/parser@3 --no-save
fi
if [ ${{ matrix.node-version }} -gt 8 ] && [ ${{ matrix.node-version }} -lt 10 ]; then
npm install eslint@7 --no-save --ignore-scripts
npm install
npm install @typescript-eslint/parser@3 --no-save
fi
if [ ${{ matrix.node-version }} -ge 10 ] && [ ${{ matrix.node-version }} -lt 12 ]; then
npm install
npm install @typescript-eslint/parser@4 --no-save
fi
if [ ${{ matrix.node-version }} -ge 12 ]; then
npm install
fi
npm run copy-metafiles
npm run pretest
npm run mocha -- tests/src --timeout "$ESLINT_IMPORT_WSL_TEST_TIMEOUT"
- name: install dependencies for node <= 10
if: matrix.node-version <= '10' && matrix.configuration == 'native'
run: |
npm install --legacy-peer-deps
npm install eslint@7 --no-save
- name: Install dependencies for node > 10
if: matrix.node-version > '10' && matrix.configuration == 'native'
run: npm install
- name: install the latest version of nyc
if: matrix.configuration == 'native'
run: npm install nyc@latest --no-save
- name: copy metafiles for node <= 8
if: matrix.node-version <= 8 && matrix.configuration == 'native'
env:
ESLINT_VERSION: 6
TRAVIS_NODE_VERSION: ${{ matrix.node-version }}
run: |
npm run copy-metafiles
bash ./tests/dep-time-travel.sh 2>&1
- name: copy metafiles for Node > 8
if: matrix.node-version > 8 && matrix.configuration == 'native'
env:
ESLINT_VERSION: 7
TRAVIS_NODE_VERSION: ${{ matrix.node-version }}
run: |
npm run copy-metafiles
bash ./tests/dep-time-travel.sh 2>&1
- name: install ./resolver dependencies in Native
if: matrix.configuration == 'native'
shell: pwsh
run: |
npm config set package-lock false
$resolverDir = "./resolvers"
Get-ChildItem -Directory $resolverDir |
ForEach {
Write-output $(Resolve-Path $(Join-Path $resolverDir $_.Name))
Push-Location $(Resolve-Path $(Join-Path $resolverDir $_.Name))
npm install
npm ls nyc > $null;
if ($?) {
npm install nyc@latest --no-save
}
Pop-Location
}
- name: run tests in Native
if: matrix.configuration == 'native'
shell: pwsh
run: |
npm run pretest
npm run tests-only
$resolverDir = "./resolvers";
$resolvers = @();
Get-ChildItem -Directory $resolverDir |
ForEach {
$resolvers += "$(Resolve-Path $(Join-Path $resolverDir $_.Name))";
}
$env:RESOLVERS = [string]::Join(";", $resolvers);
foreach ($resolver in $resolvers) {
Set-Location -Path $resolver.Trim('"')
npm run tests-only
Set-Location -Path $PSScriptRoot
}
- name: codecov
uses: codecov/codecov-action@v7.0.0
windows:
runs-on: ubuntu-latest
needs: [build]
steps:
- run: true
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: Native and WSL on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read jobs: build: timeout-minutes: 30 runs-on: ${{ matrix.os }} defaults: run: shell: ${{ matrix.configuration == 'wsl' && 'wsl-bash {0}' || 'pwsh' }} strategy: fail-fast: false matrix: os: [windows-2022] node-version: [18, 16, 14, 12, 10, 8, 6, 4] configuration: [wsl, native] steps: - uses: actions/checkout@v4 - uses: Vampire/setup-wsl@v7 if: matrix.configuration == 'wsl' with: distribution: Ubuntu-22.04 wsl-version: 2 - run: curl --version - name: 'WSL: do all npm install steps' if: matrix.configuration == 'wsl' env: ESLINT_VERSION: 7 TRAVIS_NODE_VERSION: ${{ matrix.node-version }} run: | export ESLINT_IMPORT_WSL_TEST_TIMEOUT=60000 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm nvm install --latest-npm ${{ matrix.node-version }} if [ ${{ matrix.node-version }} -ge 4 ] && [ ${{ matrix.node-version }} -lt 6 ]; then npm install eslint@4 --no-save --ignore-scripts npm install npm install eslint-import-resolver-typescript@1.0.2 --no-save npm uninstall @angular-eslint/template-parser @typescript-eslint/parser --no-save fi if [ ${{ matrix.node-version }} -ge 6 ] && [ ${{ matrix.node-version }} -lt 7 ]; then npm install eslint@5 --no-save --ignore-scripts npm install npm uninstall @angular-eslint/template-parser --no-save npm install eslint-import-resolver-typescript@1.0.2 @typescript-eslint/parser@3 --no-save fi if [ ${{ matrix.node-version }} -ge 7 ] && [ ${{ matrix.node-version }} -lt 8 ]; then npm install eslint@6 --no-save --ignore-scripts npm install npm install eslint-import-resolver-typescript@1.0.2 typescript-eslint-parser@20 --no-save npm uninstall @angular-eslint/template-parser --no-save fi if [ ${{ matrix.node-version }} -eq 8 ]; then npm install eslint@6 --no-save --ignore-scripts npm install npm uninstall @angular-eslint/template-parser --no-save npm install @typescript-eslint/parser@3 --no-save fi if [ ${{ matrix.node-version }} -gt 8 ] && [ ${{ matrix.node-version }} -lt 10 ]; then npm install eslint@7 --no-save --ignore-scripts npm install npm install @typescript-eslint/parser@3 --no-save fi if [ ${{ matrix.node-version }} -ge 10 ] && [ ${{ matrix.node-version }} -lt 12 ]; then npm install npm install @typescript-eslint/parser@4 --no-save fi if [ ${{ matrix.node-version }} -ge 12 ]; then npm install fi npm run copy-metafiles npm run pretest npm run mocha -- tests/src --timeout "$ESLINT_IMPORT_WSL_TEST_TIMEOUT" - name: install dependencies for node <= 10 if: matrix.node-version <= '10' && matrix.configuration == 'native' run: | npm install --legacy-peer-deps npm install eslint@7 --no-save - name: Install dependencies for node > 10 if: matrix.node-version > '10' && matrix.configuration == 'native' run: npm install - name: install the latest version of nyc if: matrix.configuration == 'native' run: npm install nyc@latest --no-save - name: copy metafiles for node <= 8 if: matrix.node-version <= 8 && matrix.configuration == 'native' env: ESLINT_VERSION: 6 TRAVIS_NODE_VERSION: ${{ matrix.node-version }} run: | npm run copy-metafiles bash ./tests/dep-time-travel.sh 2>&1 - name: copy metafiles for Node > 8 if: matrix.node-version > 8 && matrix.configuration == 'native' env: ESLINT_VERSION: 7 TRAVIS_NODE_VERSION: ${{ matrix.node-version }} run: | npm run copy-metafiles bash ./tests/dep-time-travel.sh 2>&1 - name: install ./resolver dependencies in Native if: matrix.configuration == 'native' shell: pwsh run: | npm config set package-lock false $resolverDir = "./resolvers" Get-ChildItem -Directory $resolverDir | ForEach { Write-output $(Resolve-Path $(Join-Path $resolverDir $_.Name)) Push-Location $(Resolve-Path $(Join-Path $resolverDir $_.Name)) npm install npm ls nyc > $null; if ($?) { npm install nyc@latest --no-save } Pop-Location } - name: run tests in Native if: matrix.configuration == 'native' shell: pwsh run: | npm run pretest npm run tests-only $resolverDir = "./resolvers"; $resolvers = @(); Get-ChildItem -Directory $resolverDir | ForEach { $resolvers += "$(Resolve-Path $(Join-Path $resolverDir $_.Name))"; } $env:RESOLVERS = [string]::Join(";", $resolvers); foreach ($resolver in $resolvers) { Set-Location -Path $resolver.Trim('"') npm run tests-only Set-Location -Path $PSScriptRoot } - name: codecov uses: codecov/codecov-action@v7.0.0 windows: timeout-minutes: 30 runs-on: latchkey-small needs: [build] steps: - run: true
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. - Add a job timeout so a hung step cannot burn hours of runner time.
2 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 2 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.