Release workflow (middyjs/middy)
The Release workflow from middyjs/middy, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get caching, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Release workflow from the middyjs/middy 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: Release
on:
pull_request:
types: [closed]
branches: [main]
paths:
- 'package.json'
env:
NODE_VERSION: 24.x
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
name: Build
if: ${{ github.event.pull_request.merged }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # actions/attest-build-provenance
attestations: write # actions/attest-build-provenance
steps:
- name: Harden runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
disable-telemetry: true
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: https://registry.npmjs.org
- name: Set env
run: |
tag=$(npm pkg get version | xargs)
echo "tag=${tag}" >> "$GITHUB_ENV"
echo "prerelease=$([ "${tag##*-*}" ] && echo false || echo true)" >> "$GITHUB_ENV"
- name: Install dependencies
run: |
npm ci --ignore-scripts
- name: Verify dependency signatures
run: |
npm audit signatures
- name: Build
run: |
npm run build --if-present
- name: Pack
id: pack
run: |
mapfile -t args < <(npm query '.workspace:not([private])' | jq -r '.[] | "--workspace", .location')
{
echo "packages<<EOF"
npm pack "${args[@]}" --json
echo EOF
} >> "$GITHUB_OUTPUT"
- name: Build Attestations
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: |
**/*.tgz
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ env.tag }}
path: |
**/package.json
**/*.tgz
outputs:
tag: ${{ env.tag }}
prerelease: ${{ env.prerelease }}
release:
name: Release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write # softprops/action-gh-release
steps:
- name: Harden runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
disable-telemetry: true
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # zizmor: ignore[cache-poisoning] no cache configured (no `cache:` input); reconsider if caching is added
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: https://registry.npmjs.org
- name: Download artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ needs.build.outputs.tag }}
- name: Release
# Replaces actions/create-release & actions/upload-release-asset (deprecated)
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.0 # zizmor: ignore[superfluous-actions] prefer maintained action over inline `gh release` script
with:
draft: true
prerelease: ${{ needs.build.outputs.prerelease }}
tag_name: ${{ needs.build.outputs.tag }}
generate_release_notes: true
# Disabled, covered by GitHub Attestations & npm
#files: |
# **/*.tgz
outputs:
tag: ${{ needs.build.outputs.tag }}
prerelease: ${{ needs.build.outputs.prerelease }}
publish:
name: Publish
needs: release
runs-on: ubuntu-latest
environment: npm-publish
permissions:
contents: read
id-token: write # npm publish
attestations: read # gh attestation verify
steps:
- name: Harden runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: block
disable-telemetry: true
allowed-endpoints: >
registry.npmjs.org:443
fulcio.sigstore.dev:443
rekor.sigstore.dev:443
tuf-repo-cdn.sigstore.dev:443
github.com:443
api.github.com:443
tmaproduction.blob.core.windows.net:443
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: https://registry.npmjs.org
- name: Download artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ needs.release.outputs.tag }}
- name: Verify build provenance attestation
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
find . -name '*.tgz' -print -exec gh attestation verify {} --repo ${{ github.repository }} \;
- name: npm publish (next)
if: ${{ needs.release.outputs.prerelease == 'true' }}
run: |
find . -name '*.tgz' -exec npm publish --tag next --provenance --access public {} \;
- name: npm publish (latest)
if: ${{ needs.release.outputs.prerelease == 'false' }}
run: |
find . -name '*.tgz' -exec npm publish --tag latest --provenance --access public {} \;
The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
name: Release on: pull_request: types: [closed] branches: [main] paths: - 'package.json' env: NODE_VERSION: 24.x permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false jobs: build: timeout-minutes: 30 name: Build if: ${{ github.event.pull_request.merged }} runs-on: latchkey-small permissions: contents: read id-token: write # actions/attest-build-provenance attestations: write # actions/attest-build-provenance steps: - name: Harden runner uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit disable-telemetry: true - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: cache: 'npm' node-version: ${{ env.NODE_VERSION }} registry-url: https://registry.npmjs.org - name: Set env run: | tag=$(npm pkg get version | xargs) echo "tag=${tag}" >> "$GITHUB_ENV" echo "prerelease=$([ "${tag##*-*}" ] && echo false || echo true)" >> "$GITHUB_ENV" - name: Install dependencies run: | npm ci --ignore-scripts - name: Verify dependency signatures run: | npm audit signatures - name: Build run: | npm run build --if-present - name: Pack id: pack run: | mapfile -t args < <(npm query '.workspace:not([private])' | jq -r '.[] | "--workspace", .location') { echo "packages<<EOF" npm pack "${args[@]}" --json echo EOF } >> "$GITHUB_OUTPUT" - name: Build Attestations uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 with: subject-path: | **/*.tgz - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ env.tag }} path: | **/package.json **/*.tgz outputs: tag: ${{ env.tag }} prerelease: ${{ env.prerelease }} release: timeout-minutes: 30 name: Release needs: build runs-on: latchkey-small permissions: contents: write # softprops/action-gh-release steps: - name: Harden runner uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit disable-telemetry: true - name: Setup Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # zizmor: ignore[cache-poisoning] no cache configured (no `cache:` input); reconsider if caching is added with: cache: 'npm' node-version: ${{ env.NODE_VERSION }} registry-url: https://registry.npmjs.org - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ${{ needs.build.outputs.tag }} - name: Release # Replaces actions/create-release & actions/upload-release-asset (deprecated) uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.0 # zizmor: ignore[superfluous-actions] prefer maintained action over inline `gh release` script with: draft: true prerelease: ${{ needs.build.outputs.prerelease }} tag_name: ${{ needs.build.outputs.tag }} generate_release_notes: true # Disabled, covered by GitHub Attestations & npm #files: | # **/*.tgz outputs: tag: ${{ needs.build.outputs.tag }} prerelease: ${{ needs.build.outputs.prerelease }} publish: timeout-minutes: 30 name: Publish needs: release runs-on: latchkey-small environment: npm-publish permissions: contents: read id-token: write # npm publish attestations: read # gh attestation verify steps: - name: Harden runner uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: block disable-telemetry: true allowed-endpoints: > registry.npmjs.org:443 fulcio.sigstore.dev:443 rekor.sigstore.dev:443 tuf-repo-cdn.sigstore.dev:443 github.com:443 api.github.com:443 tmaproduction.blob.core.windows.net:443 - name: Setup Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: cache: 'npm' node-version: ${{ env.NODE_VERSION }} registry-url: https://registry.npmjs.org - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ${{ needs.release.outputs.tag }} - name: Verify build provenance attestation env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | find . -name '*.tgz' -print -exec gh attestation verify {} --repo ${{ github.repository }} \; - name: npm publish (next) if: ${{ needs.release.outputs.prerelease == 'true' }} run: | find . -name '*.tgz' -exec npm publish --tag next --provenance --access public {} \; - name: npm publish (latest) if: ${{ needs.release.outputs.prerelease == 'false' }} run: | find . -name '*.tgz' -exec npm publish --tag latest --provenance --access public {} \;
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. - Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
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
This workflow runs 3 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.