Skip to main content

Standalone Pipeline Commands

Run publish and announce as independent CI jobs with anodizer publish and anodizer announce

Anodizer provides three commands — anodizer publish, anodizer announce, and anodizer continue --merge — that let you break the release pipeline into separate CI jobs. This gives you finer control over retries, secrets access, and job dependencies.

Commands

anodizer publish

Runs the publish stages (GitHub Release creation, package registry publishing, blob storage upload) against a dist/ directory that already contains built artifacts. It does not rebuild binaries.

Use this command when:

  • You want to publish without re-running the build.
  • Publish requires secrets (e.g. a crates.io API token) that you prefer to keep in a separate job from the build.
  • A publish job failed and you need to re-run only that job.

Flags:

FlagDescription
--dry-runRun all stages without side effects
--tokenGitHub token (overrides GITHUB_TOKEN)
--distCustom dist directory (overrides config)

Global flags like --config / -f and --verbose also apply.

anodizer announce

Runs only the announce stage against a dist/ directory. All configured announcement providers (Slack, Discord, Twitter/X, Mastodon, etc.) are invoked.

Use this command when:

  • Announcements should run after publish completes and succeeds.
  • You want to gate announcements on a manual approval step.
  • Announcement secrets are stored separately from build/publish secrets.

Flags:

FlagDescription
--dry-runRun without sending any announcements
--tokenGitHub token (overrides GITHUB_TOKEN)
--distCustom dist directory (overrides config)
--skipComma-separated list of providers to skip (e.g. slack,twitter)

anodizer continue --merge

Merges artifacts produced by parallel split-build jobs and runs all post-build stages (archive, sign, changelog, release, publish, announce, etc.) in a single job. See the advanced Split & Merge guide for full details on setting up a fan-out build.

Example: four-job GitHub Actions workflow

This workflow separates build, merge, publish, and announce into four jobs so each can carry its own secrets and retry independently. It uses tj-smith47/anodizer-action, whose built-in upload-dist / download-dist inputs replace the manual upload-artifact/download-artifact plumbing.

name: Release

on:
  push:
    tags: ["v*"]

permissions:
  contents: write

jobs:
  # Job 1: build binaries on each platform in parallel
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: tj-smith47/anodizer-action@v1
        with:
          install-rust: true
          install: zig,cargo-zigbuild
          upload-dist: true           # uploads dist/ as dist-$RUNNER_OS
          args: release --split --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  # Job 2: merge artifacts and run post-build stages (everything except publish/announce)
  merge:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: tj-smith47/anodizer-action@v1
        with:
          auto-install: true
          download-dist: true         # downloads + merges dist-* artifacts
          upload-dist: true           # re-uploads merged dist for downstream jobs
          gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
          args: continue --merge --skip publish,announce
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }}

  # Job 3: publish releases and packages
  publish:
    needs: merge
    runs-on: ubuntu-latest
    environment: production           # optional: require approval before publishing
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: tj-smith47/anodizer-action@v1
        with:
          download-dist: true
          args: publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

  # Job 4: send announcements
  announce:
    needs: publish
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: tj-smith47/anodizer-action@v1
        with:
          download-dist: true
          args: announce
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
          TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }}
          TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }}
          TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
          TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
          MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}

Simpler two-job split

If you only need to separate publish from announce (without fan-out builds), you can skip the split/merge jobs:

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: tj-smith47/anodizer-action@v1
        with:
          install-rust: true
          auto-install: true
          install: cargo-zigbuild
          upload-dist: true
          args: release --skip announce
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  announce:
    needs: publish
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: tj-smith47/anodizer-action@v1
        with:
          download-dist: true
          args: announce
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

Dry-run testing

Both anodizer publish and anodizer announce support --dry-run. Use it in pull request workflows to verify configuration without sending real requests:

- run: anodizer announce --dry-run