This document describes the automated release pipeline for the RocketRide Engine monorepo. All releases are driven by GitHub Actions workflows and require no manual artifact creation.
- Overview
- Packages
- Branching Strategy
- Prereleases
- Stable Releases
- Version Management
- Release Notes
- Tags and GitHub Releases
- Registry Publishing
- Build Matrix
- Troubleshooting
The release pipeline consists of two workflows:
| Workflow | Trigger | Purpose |
|---|---|---|
Prerelease (.github/workflows/prerelease.yaml) |
On successful develop CI (workflow_run) or manual dispatch |
Build and publish GitHub prereleases (one per package) |
Release (.github/workflows/release.yaml) |
Push to main |
Build, publish to registries, and create stable GitHub Releases |
Both workflows build the full project across three platforms (Linux, Windows, macOS), run tests, and package artifacts. The key difference is that nightly creates prereleases on GitHub only, while the release workflow publishes to external registries (npm, PyPI, VS Code Marketplace) and creates stable GitHub Releases.
The monorepo produces five independently versioned and released packages:
| Package | Description | Version Source | Registry |
|---|---|---|---|
| Server | Core RocketRide engine binaries | package.json (root) |
GitHub Releases only |
| TypeScript Client | Node.js/browser SDK | packages/client-typescript/package.json |
npm |
| Python Client | Python SDK | packages/client-python/pyproject.toml |
PyPI |
| MCP Client | Model Context Protocol integration | packages/client-mcp/pyproject.toml |
PyPI |
| VS Code Extension | Visual Studio Code extension | apps/vscode/package.json |
VS Code Marketplace and Open VSX |
Each package produces specific artifacts during the build:
Server (per platform):
rocketride-server-v{version}-win64.zip: Windows x64 binary archiverocketride-server-v{version}-win64.symbols.zip: Windows debug symbolsrocketride-server-v{version}-win64.manifest.json: Build manifest with content hashrocketride-server-v{version}-linux-x64.tar.gz: Linux x64 binary archiverocketride-server-v{version}-linux-x64.manifest.json: Build manifestrocketride-server-v{version}-darwin-arm64.tar.gz: macOS ARM64 binary archiverocketride-server-v{version}-darwin-arm64.manifest.json: Build manifest
TypeScript Client:
rocketride-{version}.tgz: npm package tarball
Python Client:
rocketride-{version}-py3-none-any.whl: Python wheelrocketride-{version}.tar.gz: Python source distribution
MCP Client:
rocketride_mcp-{version}-py3-none-any.whl: Python wheelrocketride_mcp-{version}.tar.gz: Python source distribution
VS Code Extension:
rocketride-{version}.vsix: VS Code extension package
feature/* ──┐
bugfix/* ──┼──> develop ──> stage ──> main
hotfix/* ──┘ │ │
│ │
Nightly builds Stable releases
(prereleases) (registry + GitHub)
develop: Integration branch. All feature and bugfix branches merge here. No prereleases are built from this branch.stage: Prerelease stabilization branch. Changes are promoted fromdeveloptostageonce they are ready to be validated. Nightly prereleases are built from this branch, so a broken commit ondevelopcannot leak into a prerelease.main: Stable release branch. Whenstageis merged intomain, the release workflow triggers automatically.feature/*,bugfix/*,hotfix/*: Short-lived branches that merge intodevelopvia pull request.
Workflow: .github/workflows/prerelease.yaml
Trigger: Runs automatically on every successful develop CI run (workflow_run on the CI workflow), or can be triggered manually via the GitHub Actions UI (workflow_dispatch).
-
Initialize: Extract current versions from all package files.
-
Build: Compile and test the full project on all three platforms in parallel:
- Ubuntu 22.04 (Linux x64)
- Windows Server 2022 (Windows x64)
- macOS 14 (ARM64)
-
Clean up previous prereleases: Delete all existing GitHub Releases and tags with the
-prereleasesuffix. This ensures stale prereleases from previous versions are removed. -
Create prereleases: Create five separate GitHub Releases, one per package, each marked as a prerelease:
GitHub Release Tag Prerelease -- Server {version} server-v{version}-prereleasePrerelease -- TypeScript Client {version} client-typescript-v{version}-prereleasePrerelease -- Python Client {version} client-python-v{version}-prereleasePrerelease -- MCP Client {version} client-mcp-v{version}-prereleasePrerelease -- VS Code Extension {version} vscode-v{version}-prerelease
- Prereleases are not published to any external registry (npm, PyPI, VS Code Marketplace). They are only available as downloadable artifacts on the GitHub Releases page.
- Each nightly run replaces the previous prerelease for the same version. There is only ever one prerelease per package version at any given time.
Visit the Releases page and look for releases tagged with -prerelease. These contain the latest builds from the stage branch.
Workflow: .github/workflows/release.yaml
Trigger: Automatically runs when commits are pushed to the main branch (typically via a merge from stage).
-
Initialize: Extract current versions from all package files.
-
Build: Compile and test the full project on all three platforms in parallel (same as nightly).
-
Publish: Each package is processed independently with
fail-fast: false, meaning one package failure does not block the others. For each package:a. Check if already released: If the git tag (e.g.,
server-v1.0.3) already exists, the package is skipped entirely. This makes the workflow fully idempotent.b. Publish to registry: Push the package to its external registry. Each registry publish includes a check to skip if the version already exists:
- TypeScript Client →
npm publishto npmjs.org - Python Client →
twine uploadto PyPI - MCP Client →
twine uploadto PyPI - VS Code Extension →
vsce publishto VS Code Marketplace andovsx publishto Open VSX - Server → No registry publish (binaries are distributed via GitHub Releases only)
c. Create git tag: Tag the commit (e.g.,
server-v1.0.3).d. Create GitHub Release: Create a GitHub Release with the tag, release notes sourced from
CHANGELOG.md(see Release Notes), and the package artifacts attached. - TypeScript Client →
The release workflow is designed to be fully idempotent:
- If a git tag already exists for a package version, that package is skipped entirely: no registry publish, no GitHub Release creation.
- If a version already exists on a registry (npm, PyPI, Marketplace) but the git tag does not exist, the registry publish step is skipped but the GitHub Release is still created.
- Running the release workflow multiple times with the same versions produces the same result as running it once.
Each package is published independently:
- If the Python Client publish fails, the TypeScript Client, Server, and all other packages are unaffected.
- Partial failures can be resolved by fixing the issue and re-running the workflow (or pushing a new commit to
main). Already-published packages will be skipped automatically.
| Package | File | Field |
|---|---|---|
| Server | package.json (root) |
version |
| TypeScript Client | packages/client-typescript/package.json |
version |
| Python Client | packages/client-python/pyproject.toml |
project.version |
| MCP Client | packages/client-mcp/pyproject.toml |
project.version |
| VS Code Extension | apps/vscode/package.json |
version |
- Bump the version in the appropriate file(s) on the
developbranch. - Cut the changelog in the same pull request, so the cut rides the normal
develop → stage → mainpromotion:This is what makes the stable GitHub Release notes scoped to the release (see Release Notes). Do this in the version-bump PR, never auto-commit a cut to# Archive [Unreleased] -> [<server-version>] - <today> and open a fresh [Unreleased]. node scripts/release/cut-changelog.mjs # uses the root package.json version + today # or pin explicitly: node scripts/release/cut-changelog.mjs 3.2.0 2026-06-05
mainfrom CI (it would re-trigger the release workflow and divergemain's changelog fromdevelop). - Commit and push to
develop, then mergedevelopintostageonce the change is ready to be validated:The next nightly build will create a prerelease with the new version fromgit checkout stage git merge develop git push origin stage
stage. - Verify the prerelease by downloading artifacts from the GitHub Releases page.
- Merge
stageintomain:git checkout main git merge stage git push origin main
- The release workflow triggers automatically and publishes all packages with new versions.
Because each package is versioned independently, you can release a single package by bumping only its version. For example, to release a new TypeScript Client version without releasing the Server:
- Bump only
packages/client-typescript/package.json→version - Merge to
main - The release workflow creates a new
client-typescript-v{version}release; all other packages are skipped because their tags already exist.
- Follow Semantic Versioning (MAJOR.MINOR.PATCH).
- Bump MAJOR for breaking API changes.
- Bump MINOR for new features that are backward compatible.
- Bump PATCH for backward-compatible bug fixes.
GitHub Release bodies are sourced from CHANGELOG.md by the Set release body step in .github/workflows/_release.yaml. The Create GitHub release step keeps generate_release_notes: false, so the curated CHANGELOG section is the entire published body, GitHub's auto-generated notes (under squash-merge stage → main that is just the single batched merge PR) are not appended. The same notes are used for all five packages, because they ship as one synchronized release train sharing this changelog.
| Build | Notes shown |
|---|---|
| Prerelease (nightly) | The current ## [Unreleased] section, the "what's cooking on stage" view. |
Stable (push to main) |
The most recent released section, the first ## [ heading that is not [Unreleased]. |
This is why the changelog must be cut at version-bump time (step 2 of How to release a new version):
scripts/release/cut-changelog.mjsarchives[Unreleased]into a dated, versioned section (labelled with the server version, e.g.## [3.2.2] - 2026-06-10) and opens a fresh, empty[Unreleased].- At release time the stable build emits that newly-cut section, so the notes are scoped to the release instead of re-emitting the entire growing
[Unreleased]blob on every release. - If a version bump forgets to cut, the release does not fail, it emits the previous released section and logs a
::warning::that the top section does not mention[<release-version>]. Re-running the workflow will NOT fix the already-published body: once the tag exists, the tag-skip idempotency skips the "Create GitHub release" step (see Idempotency). To correct it, either edit the GitHub Release body by hand, or run the cut and then delete the stale tag and its GitHub Release before re-running (see "A git tag exists but there is no GitHub Release").
Keep CHANGELOG.md in Keep a Changelog form (### Added / ### Changed / ### Fixed / etc. under [Unreleased]) so each cut section reads as clean release notes.
| Type | Pattern | Example |
|---|---|---|
| Server stable | server-v{version} |
server-v1.0.3 |
| TypeScript Client stable | client-typescript-v{version} |
client-typescript-v1.0.1 |
| Python Client stable | client-python-v{version} |
client-python-v1.0.1 |
| MCP Client stable | client-mcp-v{version} |
client-mcp-v0.1.0 |
| VS Code stable | vscode-v{version} |
vscode-v0.0.1 |
| Server prerelease | server-v{version}-prerelease |
server-v1.0.3-prerelease |
| TypeScript Client prerelease | client-typescript-v{version}-prerelease |
client-typescript-v1.0.1-prerelease |
| Python Client prerelease | client-python-v{version}-prerelease |
client-python-v1.0.1-prerelease |
| MCP Client prerelease | client-mcp-v{version}-prerelease |
client-mcp-v0.1.0-prerelease |
| VS Code prerelease | vscode-v{version}-prerelease |
vscode-v0.0.1-prerelease |
- A
-prereleasetag is created (or force-updated) by the nightly workflow. - When the version is promoted to stable (merged to
main), a stable tag (without-prerelease) is created. - The next nightly run cleans up all
-prereleasetags and recreates them with the current versions.
- Package name:
rocketride - Registry: https://www.npmjs.com/package/rocketride
- Authentication:
NPM_TOKENsecret - Skip logic: Checks
npm view "rocketride@{version}" versionbefore publishing
- Package name:
rocketride - Registry: https://pypi.org/project/rocketride/
- Authentication:
PYPI_TOKENsecret - Skip logic: Checks
https://pypi.org/pypi/rocketride/{version}/jsonHTTP status before publishing
- Package name:
rocketride-mcp - Registry: https://pypi.org/project/rocketride-mcp/
- Authentication:
PYPI_TOKENsecret - Skip logic: Checks
https://pypi.org/pypi/rocketride-mcp/{version}/jsonHTTP status before publishing
- Package name:
rocketride - Registry: https://marketplace.visualstudio.com/
- Authentication:
VSCE_PATsecret - Skip logic: Catches "already exists" error from
vsce publish
- Package name:
rocketride - Registry: https://open-vsx.org/
- Authentication:
OVSX_PATsecret - Skip logic: Catches "already exists" error from
ovsx publish
Both workflows build on the same three platforms:
| Platform | Runner | Server artifact format |
|---|---|---|
| Linux x64 | ubuntu-22.04 |
.tar.gz |
| Windows x64 | windows-2022 |
.zip + .symbols.zip |
| macOS ARM64 | macos-14 |
.tar.gz |
Client packages (TypeScript, Python, MCP) and the VS Code extension are platform-independent and are built only on the Ubuntu runner.
- pnpm 10: Package manager and workspace orchestration
- vcpkg: C++ dependency management with NuGet binary caching via GitHub Packages
- CMake + Ninja: C++ build system
- Node.js 20: TypeScript compilation and npm publishing
- Python 3.12: Python package building and PyPI publishing
This can happen if the workflow fails between the registry publish step and the GitHub Release creation step. To fix:
- The git tag was not created, so re-running the workflow (push a new commit to
mainor re-run the failed workflow) will retry. The registry publish will be skipped (version already exists) and the GitHub Release will be created.
This means the tag was created manually or by a previous version of the workflow. To fix:
- Delete the orphan tag:
git push origin --delete {tag-name} git tag -d {tag-name} - Re-run the release workflow. It will recreate the tag and the GitHub Release.
- Check that the workflow is enabled in the GitHub Actions UI (Settings > Actions > General).
- The nightly always runs on schedule, there is no commit-based skip logic.
- You can manually trigger it via the GitHub Actions UI using "Run workflow".
The release workflow skips a package if its git tag already exists. To check:
git tag -l '{package}-v{version}'If the tag exists and you need to re-release, delete the tag first (see "A git tag exists but there is no GitHub Release" above).
The following secrets must be configured in the release environment:
| Secret | Used for |
|---|---|
NPM_TOKEN |
Publishing to npm |
PYPI_TOKEN |
Publishing to PyPI |
VSCE_PAT |
Publishing to VS Code Marketplace |
OVSX_PAT |
Publishing to Open VSX |
Update these in GitHub Settings > Environments > release > Environment secrets.