Skip to content

Publishing

The publish step takes a prepared release and pushes it to package registries, creates Git tags, and optionally creates GitHub Releases.

Terminal window
cursus publish

Or for specific packages:

Terminal window
cursus publish -p my-package
  1. Publish to registries — each package is published to its configured registry (crates.io for Cargo, npm registry for npm packages)
  2. Create Git tags — version tags are created for each published package
  3. Push tags — tags are pushed to the remote
  4. GitHub Releases — if GitHub integration is enabled, a release is created with changelog notes and any configured build artifacts

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.

To publish without creating tags or GitHub Releases:

Terminal window
cursus publish --no-git

When [github] is enabled in your configuration, publish will:

  1. Run your build_command to produce artifacts
  2. Create a GitHub Release with the changelog as the body
  3. Upload any files listed in artifacts

See the configuration reference for details on setting up GitHub integration.

  • Cargo — uses your existing cargo login credentials or the CARGO_REGISTRY_TOKEN environment variable
  • npm — uses your existing npm login credentials or the NODE_AUTH_TOKEN environment variable
  • GitHub — uses the GH_TOKEN or GITHUB_TOKEN environment variable (checked in that order)

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-interactive

The 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_TOKEN is not set, no OIDC environment detected — no Cargo authentication is configured; the publish is likely to fail. Set CARGO_REGISTRY_TOKEN or run cargo login locally.
  • CARGO_REGISTRY_TOKEN is not set, OIDC environment detected — an OIDC-capable CI environment is present but no token has been exchanged. Add a token exchange step (such as rust-lang/crates-io-auth-action) before cursus 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.

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_TOKEN is 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_TOKEN is present — no recognised npm authentication is configured; the publish is likely to fail.
  • OIDC is active, access = "public", but publishConfig.provenance is not true in package.json — npm attaches provenance attestations automatically via trusted publishing, but declaring publishConfig.provenance = true in your package.json makes the intent explicit and ensures provenance is attached even in non-OIDC publish scenarios.

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 = true
publish_private_packages = ["my-github-action", "my-cli"]

Listed packages receive the non-registry parts of the publish workflow:

  1. Git tag — same tag format as registry-published packages
  2. 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.

Preview what publish would do:

Terminal window
cursus publish --dry-run