Skip to content
Latchkey

Dev Publish workflow (google/zx)

The Dev Publish workflow from google/zx, explained and optimized by Latchkey.

A

CI health: A - excellent

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

Grade your own workflow free or run it on Latchkey →
Source: google/zx.github/workflows/dev-publish.ymlLicense Apache-2.0View source

What it does

This is the Dev Publish workflow from the google/zx repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

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

The workflow

workflow (.yml)
name: Dev Publish

on:
  workflow_dispatch:

permissions: {}

env:
  npm_config_audit: false
  npm_config_fund: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: 'npm'
      - run: npm ci
      - run: npm test
        env:
          FORCE_COLOR: 3
      - uses: actions/upload-artifact@v6
        with:
          name: build-${{ github.run_id }}
          path: |
            build
            jsr.json
            package.json
            package-lite.json
            package-main.json
          retention-days: 1

  version:
    runs-on: ubuntu-latest
    outputs:
      v: ${{ steps.ref.outputs.ZX_VERSION }}
      lite: ${{ steps.ref.outputs.ZX_VERSION }}-lite
      dev: ${{ steps.ref.outputs.ZX_VERSION }}-dev.${{ steps.ref.outputs.SHA_SHORT }}
      lite-dev: ${{ steps.ref.outputs.ZX_VERSION }}-lite-dev.${{ steps.ref.outputs.SHA_SHORT }}
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - id: ref
        run: |
          echo SHA_SHORT=$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
          echo ZX_VERSION=$(jq -r '.version' package.json) >> $GITHUB_OUTPUT

  npm-publish:
    needs: [build, version]
    runs-on: ubuntu-latest
    permissions:
      checks: read
      statuses: write
      contents: write
      packages: write
      id-token: write
    env:
      GOOGLE_NPM_REGISTRY: wombat-dressing-room.appspot.com
      GOOGLE_NPM_TOKEN: ${{ secrets.AUTH_TOKEN }}
      GH_NPM_REGISTRY: npm.pkg.github.com
      GH_NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      ZX_VERSION: ${{ needs.version.outputs.v }}
      ZX_DEV_VERSION: ${{ needs.version.outputs.dev }}
      ZX_LITE_DEV_VERSION: ${{ needs.version.outputs.lite-dev }}
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: 'npm'
      - name: Configure npmrc
        run: |
          echo "//${{ env.GOOGLE_NPM_REGISTRY }}/:_authToken=$GOOGLE_NPM_TOKEN" >> .npmrc
          echo "//${{ env.GH_NPM_REGISTRY }}/:_authToken=$GH_NPM_TOKEN" >> .npmrc

      - uses: actions/download-artifact@v7
        with:
          name: build-${{ github.run_id }}

      - name: pushing lite snapshot to ${{ env.GOOGLE_NPM_REGISTRY }}
        run: |
          mv -f package-lite.json package.json
          cat <<< $(jq '.version="${ZX_LITE_DEV_VERSION}"' package.json) > package.json
          npm publish --provenance --access=public --no-git-tag-version --tag dev --registry https://${{ env.GOOGLE_NPM_REGISTRY }}

      - name: pushing to ${{ env.GOOGLE_NPM_REGISTRY }}
        run: |
          mv -f package-main.json package.json
          cat <<< $(jq '.version="${ZX_DEV_VERSION}"' package.json) > package.json
          npm publish --provenance --access=public --no-git-tag-version --tag dev --registry https://${{ env.GOOGLE_NPM_REGISTRY }}

      - name: pushing to ${{ env.GH_NPM_REGISTRY }}
        run: |
          cat <<< $(jq '.name="@${{ github.repository }}"' package.json) > package.json
          npm publish --no-git-tag-version --access=public --tag dev --registry https://${{ env.GH_NPM_REGISTRY }}

  jsr-publish:
    needs: [build, version]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    env:
      ZX_DEV_VERSION: ${{ needs.version.outputs.dev }}
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: 'npm'
      - uses: actions/download-artifact@v7
        with:
          name: build-${{ github.run_id }}
      - name: pushing to jsr.io
        run: |
          cat <<< $(jq '.version="${ZX_DEV_VERSION}"' jsr.json) > jsr.json
          npx jsr publish --allow-dirty

  # https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-docker-images
  docker-publish:
    needs: [build, version]
    runs-on: ubuntu-latest
    # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write

    # Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
    env:
      REGISTRY: ghcr.io
      IMAGE_NAME: ${{ github.repository }}
      ZX_DEV_VERSION: ${{ needs.version.outputs.dev }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - uses: actions/download-artifact@v7
        with:
          name: build-${{ github.run_id }}

      # Uses the `docker/login-action` action to log in to the Container registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
      - name: Log in to the Container registry
        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha
            type=semver,pattern={{version}},value=v${{ env.ZX_DEV_VERSION }}
      # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
      # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository.
      # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
      - name: Build and push Docker image
        id: push
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 #v6.18.0
        with:
          context: ./
          file: ./dcr/Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

      # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see [Using artifact attestations to establish provenance for builds](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds).
      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@v3
        with:
          subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
          subject-digest: ${{ steps.push.outputs.digest }}
          push-to-registry: true

The same workflow, on Latchkey

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

name: Dev Publish
 
on:
  workflow_dispatch:
 
permissions: {}
 
env:
  npm_config_audit: false
  npm_config_fund: false
 
jobs:
  build:
    timeout-minutes: 30
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: 'npm'
      - run: npm ci
      - run: npm test
        env:
          FORCE_COLOR: 3
      - uses: actions/upload-artifact@v6
        with:
          name: build-${{ github.run_id }}
          path: |
            build
            jsr.json
            package.json
            package-lite.json
            package-main.json
          retention-days: 1
 
  version:
    timeout-minutes: 30
    runs-on: latchkey-small
    outputs:
      v: ${{ steps.ref.outputs.ZX_VERSION }}
      lite: ${{ steps.ref.outputs.ZX_VERSION }}-lite
      dev: ${{ steps.ref.outputs.ZX_VERSION }}-dev.${{ steps.ref.outputs.SHA_SHORT }}
      lite-dev: ${{ steps.ref.outputs.ZX_VERSION }}-lite-dev.${{ steps.ref.outputs.SHA_SHORT }}
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - id: ref
        run: |
          echo SHA_SHORT=$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
          echo ZX_VERSION=$(jq -r '.version' package.json) >> $GITHUB_OUTPUT
 
  npm-publish:
    timeout-minutes: 30
    needs: [build, version]
    runs-on: latchkey-small
    permissions:
      checks: read
      statuses: write
      contents: write
      packages: write
      id-token: write
    env:
      GOOGLE_NPM_REGISTRY: wombat-dressing-room.appspot.com
      GOOGLE_NPM_TOKEN: ${{ secrets.AUTH_TOKEN }}
      GH_NPM_REGISTRY: npm.pkg.github.com
      GH_NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      ZX_VERSION: ${{ needs.version.outputs.v }}
      ZX_DEV_VERSION: ${{ needs.version.outputs.dev }}
      ZX_LITE_DEV_VERSION: ${{ needs.version.outputs.lite-dev }}
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: 'npm'
      - name: Configure npmrc
        run: |
          echo "//${{ env.GOOGLE_NPM_REGISTRY }}/:_authToken=$GOOGLE_NPM_TOKEN" >> .npmrc
          echo "//${{ env.GH_NPM_REGISTRY }}/:_authToken=$GH_NPM_TOKEN" >> .npmrc
 
      - uses: actions/download-artifact@v7
        with:
          name: build-${{ github.run_id }}
 
      - name: pushing lite snapshot to ${{ env.GOOGLE_NPM_REGISTRY }}
        run: |
          mv -f package-lite.json package.json
          cat <<< $(jq '.version="${ZX_LITE_DEV_VERSION}"' package.json) > package.json
          npm publish --provenance --access=public --no-git-tag-version --tag dev --registry https://${{ env.GOOGLE_NPM_REGISTRY }}
 
      - name: pushing to ${{ env.GOOGLE_NPM_REGISTRY }}
        run: |
          mv -f package-main.json package.json
          cat <<< $(jq '.version="${ZX_DEV_VERSION}"' package.json) > package.json
          npm publish --provenance --access=public --no-git-tag-version --tag dev --registry https://${{ env.GOOGLE_NPM_REGISTRY }}
 
      - name: pushing to ${{ env.GH_NPM_REGISTRY }}
        run: |
          cat <<< $(jq '.name="@${{ github.repository }}"' package.json) > package.json
          npm publish --no-git-tag-version --access=public --tag dev --registry https://${{ env.GH_NPM_REGISTRY }}
 
  jsr-publish:
    timeout-minutes: 30
    needs: [build, version]
    runs-on: latchkey-small
    permissions:
      contents: read
      id-token: write
    env:
      ZX_DEV_VERSION: ${{ needs.version.outputs.dev }}
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: 'npm'
      - uses: actions/download-artifact@v7
        with:
          name: build-${{ github.run_id }}
      - name: pushing to jsr.io
        run: |
          cat <<< $(jq '.version="${ZX_DEV_VERSION}"' jsr.json) > jsr.json
          npx jsr publish --allow-dirty
 
  # https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-docker-images
  docker-publish:
    timeout-minutes: 30
    needs: [build, version]
    runs-on: latchkey-small
    # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write
 
    # Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
    env:
      REGISTRY: ghcr.io
      IMAGE_NAME: ${{ github.repository }}
      ZX_DEV_VERSION: ${{ needs.version.outputs.dev }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          persist-credentials: false
 
      - uses: actions/download-artifact@v7
        with:
          name: build-${{ github.run_id }}
 
      # Uses the `docker/login-action` action to log in to the Container registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
      - name: Log in to the Container registry
        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha
            type=semver,pattern={{version}},value=v${{ env.ZX_DEV_VERSION }}
      # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
      # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository.
      # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
      - name: Build and push Docker image
        id: push
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 #v6.18.0
        with:
          context: ./
          file: ./dcr/Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
 
      # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see [Using artifact attestations to establish provenance for builds](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds).
      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@v3
        with:
          subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
          subject-digest: ${{ steps.push.outputs.digest }}
          push-to-registry: true
 

What changed

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