Releases
Three independently versioned artifacts, each with its own release workflow:
| Artifact | Version source | Tag | Workflow |
|---|---|---|---|
@tumaet/apollon (npm) | library/package.json | @tumaet/apollon@X.Y.Z | release-library.yml |
| Standalone Docker images | standalone/{webapp,server}/package.json | vX.Y.Z | release-standalone.yml |
aet-tum.apollon-extension (VS Marketplace + Open VSX) | vscode-extension/package.json | apollon-extension@X.Y.Z | release-vscode-extension.yml |
Standalone starts at 4.2.18 (the library version at the time of the release-pipeline switchover). Future vX.Y.Z tags advance from there and do not collide with legacy tags.
All three tracks are versioned by Changesets, and all three share one number: @tumaet/apollon, the standalone pair and apollon-extension sit in a single fixed group (.changeset/config.json), so a release advances them together. The publish workflows trigger automatically when their version changes on main. There is one manual step per release: merge the version PR.
The cost of that group is worth naming: a changeset touching only the extension still bumps and republishes the library and the standalone images at the new version, with identical content. A linked group would avoid that at the price of the numbers drifting apart. One product number was the deliberate trade.
apollon-extension ships to the VS Marketplace, never to npm. Nothing publishes it there today — release.yml runs Changesets in version-only mode — and a publish input must never be added to it.
The per-PR side is Changesets: authors run pnpm changeset on every user-visible PR to record a changelog entry with its bump type (see Release notes). On every push to main, release.yml runs changesets/action in version-only mode (no publish input — the bespoke release-*.yml workflows own publishing) and opens or updates a single Version Packages PR. That PR runs pnpm changeset:version, which:
- consumes the accumulated
.changeset/*.mdfiles and bumps@tumaet/apollonand the paired@tumaet/webapp+@tumaet/serverfrom the declared bump types — no human picks a bump; - bumps
@tumaet/apollon, the standalone pair andapollon-extensionto one shared version, taking the largest bump any of them earned.scripts/cascade-standalone-bump.mjsstill runs first and raises the standalone's floor to the library's bump; inside afixedgroup that floor is already met, so it is a safety net rather than the mechanism. Because the group is fixed, a bump earned by any member raises all of them — a standalone-only or extension-only change does drag the library up, which is the price of one product number. So a library change always ships to npm and as a comparable Docker release and as a Marketplace release from the same merge; - regenerates every
CHANGELOG.md, rewrites the pinned@tumaet/apollon@X.Y.ZCDN URLs (viascripts/sync-library-version.mjs), and refreshes the lockfile.
The GitHub Release body for each track is built from that CHANGELOG.md section (via scripts/extract-changelog.mjs), regrouped by category — Features, Bug Fixes, Performance, … — from each entry's Conventional Commit type (resolved from the commit SHA via git; full history is checked out for this, falling back to the semver bump when git can't resolve it) instead of the raw ### Minor/Patch Changes bump headings (see Release notes); it falls back to GitHub's auto-generated notes only when a version carried no changeset. PR Health Checks also run sync-library-version.mjs --check, so a CDN-URL drift can never merge — run pnpm sync:version locally to fix one.
Cut a release
- Let the Version Packages PR (titled
chore: version packages, opened byrelease.yml) accumulate as changesets land, then merge it when you want to cut a release. The library, the paired standalone packages and the VS Code extension bump together; merging is the only manual step. - On merge:
release-library.ymlfires whenlibrary/package.jsonchanges: builds with pnpm, packs the tarball withpnpm pack, publishes withnpm publishfor OIDC trusted publishing + provenance (pnpm does not yet support OIDC trusted publishing natively — tracked in pnpm#9812). Tags@tumaet/apollon@X.Y.Z→ GitHub Release. Skipped if the version is already on npm.release-standalone.ymlfires after the push-to-main Docker build succeeds: retagsha-<commit>→X.Y.Z→ cosign-sign → tagvX.Y.Z→ GitHub Release. Staging is already running the same digest under thesha-<commit>tag from the push-to-main deploy, so no second deploy is needed. Skipped if a release for that version already exists.release-vscode-extension.ymlfires when theversioninvscode-extension/package.jsonchanges: builds the library + extension, packages the VSIX, attests it via sigstore (actions/attest-build-provenance), then publishes to the VS Marketplace (vsce) and, whenOVSX_PATis set, to Open VSX (ovsx) — both gated on thevscode-marketplaceenvironment. Tagsapollon-extension@X.Y.Z→ GitHub Release with the VSIX attached. Any other edit to the manifest, including thepublisherandnamethat decide the Marketplace identity, is picked up by the next release rather than triggering one; publish it with aworkflow_dispatch.
- Promote standalone to production: Actions → Deploy to Production →
image-tag: X.Y.Z.
Verify a Docker image signature
cosign verify \
--certificate-identity-regexp='^https://github\.com/ls1intum/Apollon/\.github/workflows/release-standalone\.yml@refs/heads/main$' \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/ls1intum/apollon/server:<version>
One-time setup
npm (library)
- npm trusted publisher on npmjs.com:
ls1intum/Apollon→.github/workflows/release-library.yml→ environmentnpm-publish(noNPM_TOKENneeded). - GitHub Environment
npm-publishwith deployment branch rulerefs/heads/main.
VS Marketplace + Open VSX (vscode-extension)
The extension publishes as aet-tum.apollon-extension, from the publisher and name fields of vscode-extension/package.json. aet-tum is the organization's publisher — the one that also owns aet-tum.iris-thaumantias.
- Azure DevOps PAT (required): create at
https://dev.azure.com/<your-org>/_usersSettings/tokenswith scopeMarketplace → Manage, organization "All accessible organizations". Max lifetime is 1 year — calendar a rotation reminder. The PAT's account must be a member of theaet-tumpublisher: a token that is valid but not a member fails withAccess Denied … needs the following permission(s) on the resource /aet-tum/apollon-extension. Check withvsce verify-pat aet-tum; the release workflow runs the same command before it uploads anything. - Open VSX PAT (optional): create at
https://open-vsx.org/user-settings/tokens. WhenOVSX_PATis unset the release skips Open VSX and publishes to the Marketplace alone; the workflow logs a notice and stays green. The namespaceaet-tummust exist on Open VSX — verify athttps://open-vsx.org/namespace/aet-tum, and if it is missing runovsx create-namespace aet-tum -p <PAT>once locally. - GitHub Environment
vscode-marketplace:- Settings → Environments → New environment → name
vscode-marketplace. - Deployment branches and tags → "Selected branches and tags" → add
main. - Required reviewers → add the release maintainer; turn on "Prevent self-review" if a second maintainer is available.
- Environment secrets:
VSCE_PAT= Azure DevOps PAT (above).OVSX_PAT= Open VSX PAT (above), if Open VSX is wanted.
- Delete any pre-existing repo-level
VSCE_PAT/OVSX_PATafter the environment-scoped ones are in place — repo secrets bypass the environment's deployment-branch and reviewer gates.
- Settings → Environments → New environment → name