Skip to content

v1.10.0

v1.10.0 #14

Workflow file for this run

# Publish the `deckfile` Rust crate to crates.io when a GitHub release is promoted from prerelease
# to full release — the Rust half of the same release that publishes @spacedevin/deck to npm.
#
# The crate is GENERATED from src/index.tish (see scripts/build-rust.mjs), so it is emitted here
# rather than committed: one source, and the published crate cannot drift from the published npm
# package because both come out of the same tag.
#
# AUTH: crates.io Trusted Publishing (OIDC, no long-lived token), matching npm-release.yml — with a
# CARGO_REGISTRY_TOKEN fallback used only to bootstrap.
#
# Trusted Publishing config is attached to a crate you already own, so it cannot publish a crate that
# does not exist yet. Bootstrapping, once:
# 1. Add CARGO_REGISTRY_TOKEN as a repo secret and run this workflow — that claims `deckfile`.
# 2. Configure Trusted Publishing on crates.io:
# deckfile > Settings > Trusted Publishing > GitHub Actions
# Repository owner: spacedevin
# Repository name: deck
# Workflow filename: crates-release.yml
# Environment: (leave blank)
# 3. DELETE the secret. The step below falls back to OIDC automatically, so the long-lived token
# exists only for as long as the bootstrap takes.
name: Crates.io release
on:
release:
types: [published, edited]
# Re-dispatch to recover a failed/throttled run. Publishing is idempotent below: a version already
# on crates.io is skipped rather than failing the job.
workflow_dispatch:
inputs:
tag:
description: "Release tag to (re)publish, e.g. v1.0.0"
required: true
type: string
permissions:
contents: read
id-token: write # required for crates.io Trusted Publishing
jobs:
publish:
name: Publish deckfile to crates.io
if: ${{ github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: refs/tags/${{ github.event.release.tag_name || inputs.tag }}
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Install dependencies
run: npm ci
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Resolve version from the tag
id: version
run: |
TAG="${{ github.event.release.tag_name || inputs.tag }}"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
- name: Skip if this version is already on crates.io
id: check
run: |
V="${{ steps.version.outputs.version }}"
if curl -sf "https://crates.io/api/v1/crates/deckfile/$V" -H 'User-Agent: deck-release' >/dev/null 2>&1; then
echo "already=true" >> "$GITHUB_OUTPUT"
echo "::notice::deckfile@$V is already published — skipping."
else
echo "already=false" >> "$GITHUB_OUTPUT"
fi
# Uses the tish from `npm ci` above — the package's own dev dependency pins >= 3.2, which is
# where the rust-lib emit landed (tishlang/tish#588). One source of truth for the compiler
# version, so a released crate is always emitted by the tish the package declares.
- name: Emit the crate
if: steps.check.outputs.already == 'false'
env:
DECKFILE_VERSION: ${{ steps.version.outputs.version }}
run: |
export PATH="$PWD/node_modules/.bin:$PATH"
tish -V
npm run build:rust
- name: Test the emitted crate
if: steps.check.outputs.already == 'false'
working-directory: crate
run: cargo test
# Two auth paths, because Trusted Publishing alone cannot bootstrap a crate that does not exist
# yet: a config is attached to a crate you already own, so there is nothing to attach to before
# the first publish. That is exactly how this failed on v1.2.1 —
# Status: 400. No Trusted Publishing config found for repository `spacedevin/deck`.
#
# So: if a CARGO_REGISTRY_TOKEN secret is set, use it. Otherwise use Trusted Publishing.
# To claim the name, add the secret and release once; then configure Trusted Publishing on
# crates.io (deckfile > Settings > Trusted Publishing > owner `spacedevin`, repo `deck`,
# workflow `crates-release.yml`) and DELETE the secret — this falls back to OIDC on its own,
# so the long-lived token exists only for as long as it takes to bootstrap.
# `secrets` is not available in a step-level `if`, only in `env` — so the presence of the token
# has to be turned into a step output before it can gate anything.
- name: Detect a crates.io token
id: cred
if: steps.check.outputs.already == 'false'
env:
TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
if [ -n "${TOKEN:-}" ]; then
echo "have_token=true" >> "$GITHUB_OUTPUT"
echo "::notice::Publishing with CARGO_REGISTRY_TOKEN. Once deckfile exists, configure Trusted Publishing and delete this secret."
else
echo "have_token=false" >> "$GITHUB_OUTPUT"
fi
- name: Authenticate to crates.io (Trusted Publishing)
if: ${{ steps.check.outputs.already == 'false' && steps.cred.outputs.have_token == 'false' }}
uses: rust-lang/crates-io-auth-action@v1
id: auth
# --allow-dirty: `crate/` is generated and gitignored, so cargo sees it as untracked.
- name: Publish
if: steps.check.outputs.already == 'false'
working-directory: crate
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || steps.auth.outputs.token }}
run: |
if [ -z "${CARGO_REGISTRY_TOKEN:-}" ]; then
echo "No crates.io credential."
echo "Either set the CARGO_REGISTRY_TOKEN repo secret (needed for the FIRST publish of a"
echo "crate, which Trusted Publishing cannot bootstrap), or configure Trusted Publishing"
echo "for \`deckfile\` at https://crates.io/crates/deckfile/settings."
exit 1
fi
cargo publish --allow-dirty