Skip to content

Getting started

This page takes a repository from nothing to its first release. It uses a small Rust workspace as the example: one library crate, onto, and one CLI crate, onto-cli, both on crates.io, plus the CLI's binaries on a GitHub release.

You need Python 3.11 or newer, git and PyYAML (pip install pyyaml) on your machine. release.py tag also uses the GitHub CLI (gh) to follow the release run; install it and run gh auth login first.

1. Install the copy

rab, the Release Asset Bundle, is a public repository. Clone it next to your repository, then run install from your repository's root:

git clone https://github.com/buchochelliq-labs/release-asset-bundle ../release-asset-bundle
cd your-repository
python3 ../release-asset-bundle/scripts/release.py install

install writes:

Path What it is
.github/rab/scripts/release.py the driver script
.github/rab/<action>/action.yml the composite actions: build, gate, plan, publish-crates, publish-github, publish-npm, publish-pypi, publish-vscode
.github/rab/publish-pypi/requirements.txt what publish-pypi installs to upload (twine, pypi-attestations), every hash pinned
.github/rab/README.md a notice: do not edit, reinstall instead
.github/rab/manifest.json the kit's version and commit, and a SHA-256 checksum of every installed file
.github/workflows/rab-build.yml the reusable build workflow
.github/workflows/release.yml the starter release workflow, only if the repository has none

Why a copy, and not uses: buchochelliq-labs/release-asset-bundle/gate@v2? A copy is pinned and reviewable. Your repository runs exactly the code in its own history, a reviewer sees every line of it, and it changes only when someone installs again and opens a PR. install --into DIR installs into another directory instead of the repository you are in.

To update later: git -C ../release-asset-bundle pull, run install again, and open a PR. That replaces .github/rab/ (files the new version dropped are removed) and the build workflow, and leaves your release.yml alone.

Coming from release-kit 1.x?

install migrates the old layout (release-kit.yml, .github/release-kit/, release-kit-build.yml) to the new names. See Migrating from release-kit 1.x.

2. Write rab.yml

At the repository root. List every package, in publication order: each after the listed packages of the same registry it depends on.

release:
  name_from: onto-cli        # the release is named by onto-cli's version

packages:
  - name: onto
    registry: crates.io
    manifest: crates/onto/Cargo.toml
    version: "0.1.0"

  - name: onto-cli
    registry: crates.io
    manifest: crates/onto-cli/Cargo.toml
    version: "0.1.0"

  - name: onto-cli-binaries     # a GitHub package's name is free
    registry: github
    manifest: crates/onto-cli/Cargo.toml
    version: "0.1.0"
    tag: "onto-cli-bin-v{version}"
    artifacts:
      - kind: rust-binary
        bin: onto
        crate: onto-cli
        include: [README.md, LICENSE]

Then check it:

python3 .github/rab/scripts/release.py check
rab.yml matches the repository (release 0.1.0).

Quote every version. YAML reads 0.10 as the number 0.1, so rab refuses an unquoted version. See the rab.yml reference for every field.

3. Wire release.yml

install wrote .github/workflows/release.yml from the template. It is your file: edit it. Two things usually need attention.

Delete the publish jobs you do not use. The template has publish-github, publish-crates, publish-pypi, publish-npm and publish-vscode. Each runs only when the gate selects a package of its registry, so unused jobs are harmless, but they name environments and secrets you would otherwise have to explain.

Make your CI callable. The release run calls your .github/workflows/ci.yml on the tagged commit:

  ci:
    needs: gate
    uses: ./.github/workflows/ci.yml
    with:
      ref: ${{ needs.gate.outputs.sha }}

So ci.yml needs a workflow_call trigger with a ref input, and must check that ref out:

on:
  push:
    branches: [main]
  pull_request:
  workflow_call:
    inputs:
      ref:
        type: string
        required: false

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: ${{ inputs.ref }}    # empty on push and pull_request: the default
          persist-credentials: false
      # ...

If your CI file has another name, change uses: in the ci job. If its input has another name (rs-rich-cli's is checkout_ref), change ref: under with: to that name.

Edit the trigger if you change the release tag. The template starts on tags matching release-*. If you set release: tag: to another format, change on: push: tags: to match it.

4. Check the copy in CI

Add these steps to a job in ci.yml:

      - run: python3 -m pip install --quiet pyyaml
      - run: python3 .github/rab/scripts/release.py check
      - run: python3 .github/rab/scripts/release.py vendored

check fails when rab.yml disagrees with a manifest, or when crates.io would refuse a crate (Publishing to crates.io). vendored fails when anyone edits, deletes or adds a file in the installed copy, so the copy changes only by installing again.

Dependabot

If your repository's Dependabot updates GitHub Actions, it may open PRs that change .github/workflows/rab-build.yml. That file is part of the installed copy, so vendored fails on them. Close those PRs and reinstall a newer rab instead; rab's own Dependabot keeps its pins current.

5. Set up each registry once

Registry Credential Set up once
GitHub Releases the job's github.token, with contents: write nothing
crates.io Trusted Publishing; a crate's first version uses a token a crates-io environment; a CARGO_REGISTRY_TOKEN secret in it for new crates; a Trusted Publisher on each crate
PyPI Trusted Publishing a pypi environment; a (pending) trusted publisher on PyPI
npm Trusted Publishing (npm 11.5+), or a token an npm environment; a trusted publisher on npmjs.com
VS Code Marketplace VSCE_PAT (and OVSX_PAT for a package with open-vsx: true) both secrets in a vscode-marketplace environment

Trusted publishers name the workflow file release.yml and the environment. Keep both names, or change the publisher settings to match.

6. Commit, merge, release

Commit the copy, rab.yml, release.yml and the CI changes in a PR, and merge it. Then, on the default branch:

git switch main && git pull
python3 .github/rab/scripts/release.py status
python3 .github/rab/scripts/release.py tag --dry-run
python3 .github/rab/scripts/release.py tag

tag pushes the tags, then waits until the run has published everything (up to three hours by default). See Commands.

For each later release: bump the versions in the manifests, run sync to copy them into rab.yml, merge the PR, and run tag again.