Publishing
The publish step takes a prepared release and pushes it to package registries, creates Git tags, and optionally creates GitHub Releases.
Running publish
Section titled “Running publish”cursus publishOr for specific packages:
cursus publish -p my-packageWhat happens during publish
Section titled “What happens during publish”- Publish to registries — each package is published to its configured registry (crates.io for Cargo, npm registry for npm packages)
- Create Git tags — version tags are created for each published package
- Push tags — tags are pushed to the remote
- GitHub Releases — if GitHub integration is enabled, a release is created with changelog notes and any configured build artifacts
Idempotency
Section titled “Idempotency”Publish is designed to be safely re-runnable across all three stages:
- Registry publish — if a package is already at the target version on the registry, Cursus skips it rather than failing.
- Git tags — if the tag for a package already exists, Cursus skips creating it.
- GitHub Releases — if a published GitHub Release already exists for a tag, Cursus skips creating it.
This means re-running cursus publish after a partial failure automatically completes any missing tags or GitHub Releases for packages that were successfully published in a previous run.
Draft releases block recovery. If Cursus finds an existing draft GitHub Release for a tag, it will not modify it — it reports an actionable error instead. Finalise or delete the draft (e.g. via the GitHub UI or gh release delete <tag>) and re-run cursus publish.
Skipping Git operations
Section titled “Skipping Git operations”To publish without creating tags or GitHub Releases:
cursus publish --no-gitGitHub Releases
Section titled “GitHub Releases”When [github] is enabled in your configuration, publish will:
- Run your
build_commandto produce artifacts - Create a GitHub Release with the changelog as the body
- Upload any files listed in
artifacts
See the configuration reference for details on setting up GitHub integration.
Authentication
Section titled “Authentication”- Cargo — uses your existing
cargo logincredentials or theCARGO_REGISTRY_TOKENenvironment variable - npm — uses your existing
npm logincredentials or theNODE_AUTH_TOKENenvironment variable - GitHub — uses the
GH_TOKENorGITHUB_TOKENenvironment variable (checked in that order)
crates.io trusted publishing
Section titled “crates.io trusted publishing”crates.io supports OIDC-based trusted publishing on GitHub Actions and GitLab CI. Unlike npm (which exchanges the OIDC token internally), cargo publish does not perform the exchange itself — you must add a dedicated step to your workflow that exchanges the CI-issued OIDC token for a short-lived CARGO_REGISTRY_TOKEN.
A minimal GitHub Actions publish workflow looks like this:
jobs: publish: runs-on: ubuntu-latest permissions: id-token: write steps: - uses: actions/checkout@v6 - uses: rust-lang/crates-io-auth-action@v1 - run: cursus publish --no-interactiveThe rust-lang/crates-io-auth-action step exchanges the workflow’s OIDC identity for a short-lived token and exports it as CARGO_REGISTRY_TOKEN — the same environment variable cargo publish (and Cursus) already use for classic token auth. No long-lived secret is required.
Cursus will warn before publishing in the following situations:
CARGO_REGISTRY_TOKENis not set, no OIDC environment detected — no Cargo authentication is configured; the publish is likely to fail. SetCARGO_REGISTRY_TOKENor runcargo loginlocally.CARGO_REGISTRY_TOKENis not set, OIDC environment detected — an OIDC-capable CI environment is present but no token has been exchanged. Add a token exchange step (such asrust-lang/crates-io-auth-action) beforecursus publish.
When CARGO_REGISTRY_TOKEN is present — whether it came from a long-lived secret or from an exchange action — Cursus publishes without warning. There is no conflict between OIDC and a token: the exchange action’s job is to produce that token.
npm OIDC trusted publishing
Section titled “npm OIDC trusted publishing”On GitHub Actions (with id-token: write permission) and GitLab CI (with OIDC configured), Cursus detects the OIDC environment automatically. npm exchanges the CI identity token for a short-lived publish credential — no NODE_AUTH_TOKEN secret is required.
Cursus will warn before publishing in the following situations:
NODE_AUTH_TOKENis also set — the classic token takes precedence over OIDC token exchange. The publish may not use trusted publishing. This is intentional if you are publishing to a registry that does not support OIDC, but is often an accidental leftover secret.- Neither OIDC nor
NODE_AUTH_TOKENis present — no recognised npm authentication is configured; the publish is likely to fail. - OIDC is active,
access = "public", butpublishConfig.provenanceis nottrueinpackage.json— npm attaches provenance attestations automatically via trusted publishing, but declaringpublishConfig.provenance = truein yourpackage.jsonmakes the intent explicit and ensures provenance is attached even in non-OIDC publish scenarios.
Private packages
Section titled “Private packages”By default, packages marked as private by their manifest ("private": true in npm, publish = false in Cargo) are silently skipped during cursus publish. This is the right behavior for internal packages that should never reach a registry.
Some packages, however, are private from a registry perspective but still need release artifacts — GitHub Actions, CLIs distributed as GitHub Release attachments, or any git-tag-distributed software. For these, use publish_private_packages in the [git] section:
[git]enabled = truepublish_private_packages = ["my-github-action", "my-cli"]Listed packages receive the non-registry parts of the publish workflow:
- Git tag — same tag format as registry-published packages
- GitHub Release — when
[github].enabled = true, a release is created with changelog notes and any configured artifacts
They do not have any registry publish command invoked. Packages that are listed but not actually marked private follow the normal registry publish path.
Dry run
Section titled “Dry run”Preview what publish would do:
cursus publish --dry-run