Skip to content
Latchkey

CD: Build & Publish workflow (amperser/proselint)

The CD: Build & Publish workflow from amperser/proselint, explained and optimized by Latchkey.

B

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.

Grade your own workflow free or run it on Latchkey →
Source: amperser/proselint.github/workflows/cd-publish.ymlLicense BSD-3-ClauseView source

What it does

This is the CD: Build & Publish workflow from the amperser/proselint repository, a real project running GitHub Actions. It is shown here with attribution under its BSD-3-Clause license.

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

The workflow

workflow (.yml)
name: "CD: Build & Publish"

on:
  workflow_dispatch:
    inputs:
      version:
        description: "Version"
        required: true
        type: string
      prerelease:
        description: "Mark as prerelease"
        required: false
        default: false
        type: boolean

jobs:
  version:
    name: Bump Version & Tag
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: "[INIT] Get privileged app token"
        id: app-token
        uses: actions/create-github-app-token@v3
        with:
          app-id: ${{ vars.APP_ID }}
          private-key: ${{ secrets.APP_KEY }}

      - name: "[INIT] Checkout"
        uses: actions/checkout@v7
        with:
          fetch-depth: 0
          token: ${{ steps.app-token.outputs.token }}

      - name: "[INIT] Git Config"
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: "[INIT] Install Nix"
        uses: cachix/install-nix-action@v31
        with:
          nix_path: nixpkgs=channel:nixos-unstable

      - name: "[INIT] Setup Cachix"
        uses: cachix/cachix-action@v17
        with:
          name: amperser
          authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}

      - name: "[VERSION] Bump & Commit"
        run: |
          nix develop --command uv version ${{ github.event.inputs.version }}
          nix develop --command git-cliff -c cliff.toml -u \
          --tag v${{ github.event.inputs.version }} \
          -p CHANGELOG.md

          git commit -am "chore: prepare release v${{ github.event.inputs.version }}" --no-verify || echo "no changes to commit"

      - name: "[GIT] Create tag"
        run: |
          git tag v${{ github.event.inputs.version }}
          git push origin HEAD --tags

  build:
    name: Build Artifacts
    runs-on: ubuntu-latest
    needs: [version]
    permissions:
      contents: read
      id-token: write
      attestations: write
    steps:
      - name: "[INIT] Checkout"
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - name: "[INIT] Install Nix"
        uses: cachix/install-nix-action@v31
        with:
          nix_path: nixpkgs=channel:nixos-unstable

      - name: "[INIT] Setup Cachix"
        uses: cachix/cachix-action@v17
        with:
          name: amperser
          authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}

      - name: "[BUILD] Wheel"
        run: |
          mkdir -p dist
          nix build -L .#wheel
          cp result/*.whl dist/


      - name: "[BUILD] Source dist"
        run: |
          nix build -L .#sdist
          cp result/*.tar.gz dist/

      - name: "[CHANGELOG] Generate release notes"
        run: |
          nix develop --command git-cliff -c cliff.toml \
            --latest --verbose \
            -o dist/RELEASE_NOTES.md

      - name: "[VERIFY] Provenance"
        uses: actions/attest@v4
        with:
          subject-path: 'dist/*'

      - name: "[UPLOAD] Artifacts"
        uses: actions/upload-artifact@v7
        with:
          name: dist-artifacts
          path: dist/
          if-no-files-found: error

  github-release:
    name: GitHub Release
    runs-on: ubuntu-latest
    needs: [build]
    permissions:
      contents: write
    steps:
      - name: "[INIT] Checkout"
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - name: "[DOWNLOAD] Artifacts"
        uses: actions/download-artifact@v8
        with:
          name: dist-artifacts
          path: dist/

      - name: "[INPUT] Get input"
        id: input
        run: |
          echo "tag=v${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
          echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
          echo "prerelease=${{ github.event.inputs.prerelease }}" >> $GITHUB_OUTPUT

      - name: "[INIT] Move release notes"
        run: mv dist/RELEASE_NOTES.md ./RELEASE_NOTES.md

      - name: "[RELEASE] Create GitHub release"
        uses: softprops/action-gh-release@v3
        with:
          name: Release ${{ steps.input.outputs.tag }}
          tag_name: ${{ steps.input.outputs.tag }}
          prerelease: ${{ steps.input.outputs.prerelease }}
          body_path: ./RELEASE_NOTES.md
          files: dist/*

  pypi-publish:
    name: Publish to PyPI
    runs-on: ubuntu-latest
    needs: [build]
    environment: release
    permissions:
      id-token: write
    steps:
      - name: "[DOWNLOAD] Artifacts"
        uses: actions/download-artifact@v8
        with:
          name: dist-artifacts
          path: dist/

      - name: "[INIT] Remove release notes"
        run: rm dist/RELEASE_NOTES.md

      - name: "[PUBLISH] PyPI"
        uses: pypa/gh-action-pypi-publish@release/v1

The same workflow, on Latchkey

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

name: "CD: Build & Publish"
 
on:
  workflow_dispatch:
    inputs:
      version:
        description: "Version"
        required: true
        type: string
      prerelease:
        description: "Mark as prerelease"
        required: false
        default: false
        type: boolean
 
jobs:
  version:
    timeout-minutes: 30
    name: Bump Version & Tag
    runs-on: latchkey-small
    permissions:
      contents: write
    steps:
      - name: "[INIT] Get privileged app token"
        id: app-token
        uses: actions/create-github-app-token@v3
        with:
          app-id: ${{ vars.APP_ID }}
          private-key: ${{ secrets.APP_KEY }}
 
      - name: "[INIT] Checkout"
        uses: actions/checkout@v7
        with:
          fetch-depth: 0
          token: ${{ steps.app-token.outputs.token }}
 
      - name: "[INIT] Git Config"
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
 
      - name: "[INIT] Install Nix"
        uses: cachix/install-nix-action@v31
        with:
          nix_path: nixpkgs=channel:nixos-unstable
 
      - name: "[INIT] Setup Cachix"
        uses: cachix/cachix-action@v17
        with:
          name: amperser
          authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
 
      - name: "[VERSION] Bump & Commit"
        run: |
          nix develop --command uv version ${{ github.event.inputs.version }}
          nix develop --command git-cliff -c cliff.toml -u \
          --tag v${{ github.event.inputs.version }} \
          -p CHANGELOG.md
 
          git commit -am "chore: prepare release v${{ github.event.inputs.version }}" --no-verify || echo "no changes to commit"
 
      - name: "[GIT] Create tag"
        run: |
          git tag v${{ github.event.inputs.version }}
          git push origin HEAD --tags
 
  build:
    timeout-minutes: 30
    name: Build Artifacts
    runs-on: latchkey-small
    needs: [version]
    permissions:
      contents: read
      id-token: write
      attestations: write
    steps:
      - name: "[INIT] Checkout"
        uses: actions/checkout@v7
        with:
          fetch-depth: 0
 
      - name: "[INIT] Install Nix"
        uses: cachix/install-nix-action@v31
        with:
          nix_path: nixpkgs=channel:nixos-unstable
 
      - name: "[INIT] Setup Cachix"
        uses: cachix/cachix-action@v17
        with:
          name: amperser
          authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
 
      - name: "[BUILD] Wheel"
        run: |
          mkdir -p dist
          nix build -L .#wheel
          cp result/*.whl dist/
 
 
      - name: "[BUILD] Source dist"
        run: |
          nix build -L .#sdist
          cp result/*.tar.gz dist/
 
      - name: "[CHANGELOG] Generate release notes"
        run: |
          nix develop --command git-cliff -c cliff.toml \
            --latest --verbose \
            -o dist/RELEASE_NOTES.md
 
      - name: "[VERIFY] Provenance"
        uses: actions/attest@v4
        with:
          subject-path: 'dist/*'
 
      - name: "[UPLOAD] Artifacts"
        uses: actions/upload-artifact@v7
        with:
          name: dist-artifacts
          path: dist/
          if-no-files-found: error
 
  github-release:
    timeout-minutes: 30
    name: GitHub Release
    runs-on: latchkey-small
    needs: [build]
    permissions:
      contents: write
    steps:
      - name: "[INIT] Checkout"
        uses: actions/checkout@v7
        with:
          fetch-depth: 0
 
      - name: "[DOWNLOAD] Artifacts"
        uses: actions/download-artifact@v8
        with:
          name: dist-artifacts
          path: dist/
 
      - name: "[INPUT] Get input"
        id: input
        run: |
          echo "tag=v${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
          echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
          echo "prerelease=${{ github.event.inputs.prerelease }}" >> $GITHUB_OUTPUT
 
      - name: "[INIT] Move release notes"
        run: mv dist/RELEASE_NOTES.md ./RELEASE_NOTES.md
 
      - name: "[RELEASE] Create GitHub release"
        uses: softprops/action-gh-release@v3
        with:
          name: Release ${{ steps.input.outputs.tag }}
          tag_name: ${{ steps.input.outputs.tag }}
          prerelease: ${{ steps.input.outputs.prerelease }}
          body_path: ./RELEASE_NOTES.md
          files: dist/*
 
  pypi-publish:
    timeout-minutes: 30
    name: Publish to PyPI
    runs-on: latchkey-small
    needs: [build]
    environment: release
    permissions:
      id-token: write
    steps:
      - name: "[DOWNLOAD] Artifacts"
        uses: actions/download-artifact@v8
        with:
          name: dist-artifacts
          path: dist/
 
      - name: "[INIT] Remove release notes"
        run: rm dist/RELEASE_NOTES.md
 
      - name: "[PUBLISH] PyPI"
        uses: pypa/gh-action-pypi-publish@release/v1
 

What changed

4 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.

This workflow runs 4 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