Skip to content

Versioning and Release ​

This doc explains how we version and publish artifacts. Platform version (root package.json, apps, npm packages, and semver image tags) is managed together via Changesets.

Version source (for contributors) ​

ArtifactVersion sourcePublished tags / registry
Platform (grant)Changesets fixed groupRoot package.json version
Apps (grant-api, grant-web, grant-docs)Same fixed groupNot published to npm (private)
npm (@grantjs/schema, client, server, cli)Same fixed groupregistry.npmjs.org
Docker imagesapps/api/package.json after version PR:demo, :sha-<commit>, :<version>, :latest
example-nextjs imageSame as platform apps (image tags, not npm)Same GHCR tags as other images
Demo environment:demo after version PR mergeSame build as :<semver> and :latest
Web header / GET /api/config appVersionapps/api/package.json at API startupNo env vars; matches running API image semver
OpenAPI info.versionSame as config.app.version—

Do not set APP_VERSION or NEXT_PUBLIC_APP_VERSION — they were removed. The API reads semver from apps/api/package.json via readPlatformVersion() so the UI and OpenAPI stay aligned with the Docker tag CI publishes.

Fixed versioning group ​

All of these bump together when you add a changeset for any member of the fixed group (for example @grantjs/schema or grant-api):

  • grant-api, grant-web, grant-docs
  • @grantjs/schema, @grantjs/client, @grantjs/server, @grantjs/cli

The root grant package is private and not in the pnpm workspace, so changesets cannot target it by name.

grant-api, grant-web, and grant-docs are private too. Changesets skips private packages unless privatePackages.version is set, and a changeset that only names a skipped package makes the release job open an empty "version packages" pull request instead of publishing. .changeset/config.json sets privatePackages.version so a changeset for any fixed-group app still bumps the whole group. tag stays false: platform tags come from the release workflow, and private packages are not published to npm.

Internal packages (@grantjs/core, @grantjs/database, etc.) and examples remain in the changeset ignore list. Do not add a changeset for an ignored package — Changesets will not version it, and the leftover file makes the release job try to open an empty version PR instead of publishing.

How Changesets work ​

  1. Add a changeset when you change platform or publishable package behavior:

    bash
    pnpm changeset

    Choose bump type (patch/minor/major) for any fixed-group member (not the root grant package). This creates a file under .changeset/.

  2. Open a PR (or push to an existing PR). The release workflow runs on every push to main. If there are unversioned changesets, it creates or updates a PR titled "chore: version packages" by running pnpm version (changeset version plus scripts/update-root-changelog.mjs). That updates package versions, package changelogs, root CHANGELOG.md, and the root package.json version. Nothing is published yet, and GHCR is not updated on this pass (:demo, :latest, and :<semver> stay on the previous release).

  3. Merge the "chore: version packages" PR. The workflow then:

    • Runs pnpm release (builds and publishes to npm)
    • Builds and pushes Docker images with :<version>, :latest, :demo, and :sha-<commit> together
    • Creates git tag v* and a platform GitHub Release (notes from changeset summaries / root changelog)
    • .changeset/* files from that PR are removed in the version commit
  4. Feature merges that only open or update the version PR do not build or push images. Demo stays on the previous :demo until the version PR merges. Manual workflow_dispatch with rebuild_release_images rebuilds :<semver>, :latest, and :demo from apps/api/package.json without publishing npm.

npm trusted publishing ​

@grantjs/schema, @grantjs/client, @grantjs/server, and @grantjs/cli publish from GitHub Actions via npm trusted publishing (OIDC). There is no NPM_TOKEN on the release job.

RequirementWhy
Trusted publisher workflow filename is release.ymlnpm matches the calling workflow file; renaming it breaks publish
release job runs on ubuntu-latestnpm does not accept OIDC from self-hosted runners
Job has id-token: write and no NPM_TOKEN / NODE_AUTH_TOKENA classic token (including an expired one) skips the OIDC exchange
npm CLI ≥ 11.5.1That is when the token exchange landed; pnpm publish packs and shells out to npm publish

Each package is configured on npmjs.com → Settings → Trusted publishing: repository grant-js/grant, workflow release.yml. After a green publish, revoke any leftover automation token.

Docker image tags ​

TagWhenUse
:demoSame build as the newest semver (version PR merge, or rebuild_release_images)Demo / rolling release
:sha-<full-sha>Same build as :demoTraceability
:1.0.0, :1.1.0, …Version PR merge, rebuild_release_images, or release-baselinePin production / Helm
:latestSame as newest semver releaseLocal compose default

Baseline semver tags (one-time) ​

To tag existing :demo images as 1.0.0 without a version bump:

bash
gh workflow run release-baseline.yml -f version=1.0.0 -f source_tag=demo

Then tag the repo and publish notes:

bash
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0

Pushing a v* tag manually also triggers github-release.yml when a release for that tag does not already exist.

When to add a changeset ​

  • Platform or publishable package API/behavior changes (use the fixed group).
  • No changeset for: docs-only (unless releasing platform), internal ignored packages, or example apps.
  • The changeset summary is the release note. Write it for humans; it appears in package changelogs and the platform GitHub Release.

GitHub Releases ​

  • Platform release (vX.Y.Z) — created by release.yml after npm publish. Notes come from scripts/extract-release-notes.sh: root CHANGELOG.md when present, otherwise aggregated changeset entries from the fixed-group package changelogs.
  • Not created: per-package GitHub Releases (@grantjs/client@…, etc.). Those tags may still exist for npm; the public release page is the platform v* release only.
  • CHANGELOG.md — platform-wide notes (kept in sync by pnpm version via scripts/update-root-changelog.mjs; preferred source for GitHub Release notes when present)
  • docs/releases/vX.Y.Z.md — optional long-form detail for a release
  • Package histories: apps/*/CHANGELOG.md, packages/@grantjs/*/CHANGELOG.md

Released under the MIT License.