CD: Build & Publish workflow (amperser/proselint)
The CD: Build & Publish workflow from amperser/proselint, 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 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
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
- 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.
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.