Skip to content

Commit ceb74d5

Browse files
authored
feat: add lockstep alpha release train (#265)
## Summary This PR turns the preview release process into a lockstep alpha release train instead of a manifest-based `zitadel-preview` bundle. A public alpha now has one version across the CLI, public npm packages, GoReleaser tag, server binary, and Docker image: ```text 0.1.0-alpha.N ``` The tester experience becomes: ```sh npx @zitadel/cli@alpha doctor npx @zitadel/cli@alpha start npx @zitadel/cli@alpha setup --framework next --server local ``` Exact reports can still pin a train: ```sh npx @zitadel/cli@0.1.0-alpha.N start ``` ## What changed against main - Configures Changesets to keep all public alpha npm packages in one fixed group. - Extends `release-npm.yml` so npm publish remains the first step, then the same workflow creates `v<version>`, runs GoReleaser, and updates one draft GitHub prerelease titled `ZITADEL Alpha <version>`. - Configures GoReleaser so prereleases do not move Docker `latest` or GitHub latest; only stable releases can do that. - Makes the CLI derive its default local server image from its own alpha version, with `--image` and `ZITADEL_LOCAL_IMAGE` still taking precedence. - Makes generated Next apps pin `@zitadel/sdk-next` to the exact CLI alpha version. - Aligns the current public package baseline to `0.1.0-alpha.1` so PR tarballs and the consumer journey are internally consistent before the next Version Packages PR advances the train to `0.1.0-alpha.2`. - Adds release train scripts, local `check -- --only release` coverage, an ADR, runbook, and CLI/docs updates. ## Why The previous manifest-first preview flow made the tester path feel too complex. This keeps the public UX simple and familiar while preserving the existing tooling split: Changesets owns npm versioning, GoReleaser owns Go/image artifacts, and the GitHub Release is the single public alpha release record. ## Validation - `corepack pnpm nx test @zitadel/cli -- tests/integration/setup-next.test.ts tests/unit/lib/orca/patchers/rule/next/index.test.ts` - `corepack pnpm nx test @zitadel/cli -- tests/unit/scripts/check-alpha-release-plan.test.ts tests/unit/scripts/release-alpha-train.test.ts` - `corepack pnpm exec oxlint scripts/check-alpha-release-plan.mjs apps/cli/tests/unit/scripts/check-alpha-release-plan.test.ts` - `corepack pnpm run check -- --only pack` - `corepack pnpm run check -- --only release` - `corepack pnpm run journey -- --keep` - `git diff --check`
1 parent c097a5f commit ceb74d5

50 files changed

Lines changed: 1902 additions & 94 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Pick the affected packages, the bump type (patch / minor / major), and write a o
2727
The repo is currently in changesets **prerelease mode** with the `alpha` tag (see `.changeset/pre.json`). While in this mode:
2828

2929
- `changeset version` cuts versions like `0.1.0-alpha.0`, `0.1.0-alpha.1`, …
30+
- Public packages are in one fixed group, so an alpha train uses the same
31+
version across `@zitadel/cli`, SDKs, components, and generated API packages.
3032
- `changeset publish` publishes them under the **`alpha`** npm dist-tag, **not** `latest`. So `npm install @zitadel/cli` keeps resolving the last stable release; consumers opt into prereleases with `@zitadel/cli@alpha`.
3133
- A package that has never had a stable release is published to `latest` on its first publish (changesets behaviour), then to `alpha` thereafter until it has a stable release.
3234

@@ -57,7 +59,14 @@ short-lived OIDC credentials, but npm only accepts public provenance
5759
attestations from public source repositories. Re-enable provenance when
5860
`zitadel/nextgen` is public.
5961

60-
The Go server binary is **not** managed by changesets — it is released with `goreleaser` through the manual [`release.yml`](../.github/workflows/release.yml) workflow while the repo is pre-release. See [docs/adrs/002-multi-package-release-strategy.md](../docs/adrs/002-multi-package-release-strategy.md).
62+
Changesets does not build the Go server binary. During alpha, `release-npm.yml`
63+
uses the lockstep npm version as the release train version, creates `v<version>`,
64+
and then runs GoReleaser so the server image and binaries publish into the same
65+
GitHub Release. The manual [`release.yml`](../.github/workflows/release.yml)
66+
workflow remains a server snapshot/fallback path. See
67+
[docs/adrs/002-multi-package-release-strategy.md](../docs/adrs/002-multi-package-release-strategy.md)
68+
and
69+
[docs/adrs/023-lockstep-alpha-release-train.md](../docs/adrs/023-lockstep-alpha-release-train.md).
6170

6271
## Licensing reminder
6372

.changeset/config.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,19 @@
22
"$schema": "https://unpkg.com/@changesets/config@latest/schema.json",
33
"changelog": ["@changesets/changelog-github", { "repo": "zitadel/nextgen" }],
44
"commit": false,
5-
"fixed": [],
5+
"fixed": [
6+
[
7+
"@zitadel/cli",
8+
"@zitadel/api",
9+
"@zitadel/components",
10+
"@zitadel/sdk-core",
11+
"@zitadel/sdk-next",
12+
"@zitadel/sdk-nuxt",
13+
"@zitadel/sdk-react",
14+
"@zitadel/sdk-vue",
15+
"@zitadel/sdk-angular"
16+
]
17+
],
618
"linked": [],
719
"access": "public",
820
"baseBranch": "main",

.changeset/lockstep-alpha-train.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@zitadel/cli": patch
3+
---
4+
5+
Derive alpha local runtime images from the installed CLI version, pin generated SDK dependencies to the same alpha train, and emit exact-version follow-up commands for reproducible tester reports.

.github/instructions/consumer-journey.instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ demo-app e2e suite.
1212
- Produce package artifacts with `corepack pnpm --dir <package> pack` and keep
1313
tarball verification for required package presence plus unresolved
1414
`catalog:` or `workspace:` dependency specs.
15-
- Pack only the six public Zitadel packages. Private support packages such as
15+
- Pack only the public Zitadel packages. Private support packages such as
1616
design tokens must not be uploaded or published to Verdaccio.
1717
- Keep Verdaccio proxying npmjs for third-party dependencies while publishing
1818
Zitadel tarballs under both `alpha` and `latest`.

.github/instructions/typescript.instructions.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ Vitest.
1313
renderer props, or package entry points should include tests and README or
1414
contract updates when user-facing.
1515
- The public npm packages (`apps/cli`, `packages/api`, `packages/components`,
16-
`packages/sdk-core`, `packages/sdk-next`, `packages/sdk-nuxt`) must keep
16+
`packages/sdk-core`, `packages/sdk-next`, `packages/sdk-nuxt`,
17+
`packages/sdk-react`, `packages/sdk-vue`, `packages/sdk-angular`) must keep
1718
`"license": "MIT"`.
1819
- User-visible changes to those packages need a changeset; write the
1920
`.changeset/<slug>.md` file directly rather than via the interactive prompt.

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,14 +541,14 @@ jobs:
541541
542542
- name: Dry-run npm packs
543543
run: |
544-
for dir in apps/cli packages/api packages/components packages/sdk-core packages/sdk-next packages/sdk-nuxt; do
544+
for dir in apps/cli packages/api packages/components packages/sdk-core packages/sdk-next packages/sdk-nuxt packages/sdk-react packages/sdk-vue packages/sdk-angular; do
545545
corepack pnpm --dir "$dir" pack --dry-run
546546
done
547547
548548
- name: Create npm package artifacts
549549
run: |
550550
mkdir -p "$RUNNER_TEMP/zitadel-npm-packages"
551-
for dir in apps/cli packages/api packages/components packages/sdk-core packages/sdk-next packages/sdk-nuxt; do
551+
for dir in apps/cli packages/api packages/components packages/sdk-core packages/sdk-next packages/sdk-nuxt packages/sdk-react packages/sdk-vue packages/sdk-angular; do
552552
corepack pnpm --dir "$dir" pack --pack-destination "$RUNNER_TEMP/zitadel-npm-packages"
553553
done
554554
ls -lh "$RUNNER_TEMP/zitadel-npm-packages"

.github/workflows/release-npm.yml

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
name: release-npm
22

3-
# Publishes the public npm packages (@zitadel/cli, @zitadel/api,
3+
# Publishes the public alpha train: npm packages (@zitadel/cli, @zitadel/api,
44
# @zitadel/components, @zitadel/sdk-core, @zitadel/sdk-next, @zitadel/sdk-nuxt,
55
# @zitadel/sdk-react, @zitadel/sdk-vue, @zitadel/sdk-angular)
6-
# via changesets. Pushing changesets to main opens a "Version Packages" PR;
7-
# merging that PR publishes to npm.
6+
# via Changesets, then the matching Go server image/binaries via GoReleaser.
7+
# Pushing changesets to main opens a "Version Packages" PR; merging that PR
8+
# publishes to npm, creates v<version>, and publishes the single GitHub Release.
89
#
910
# The repo is in changesets PRERELEASE mode (.changeset/pre.json, tag "alpha"),
1011
# so versions are cut as X.Y.Z-alpha.N and published under the `alpha` npm
@@ -39,6 +40,7 @@ on:
3940
permissions:
4041
contents: write
4142
pull-requests: write
43+
packages: write
4244
id-token: write
4345

4446
concurrency:
@@ -88,12 +90,14 @@ jobs:
8890
run: corepack pnpm nx run-many -t build
8991

9092
- name: Create release PR or publish to npm
93+
id: changesets
9194
uses: changesets/action@v1
9295
with:
9396
version: corepack pnpm changeset version
9497
publish: corepack pnpm changeset publish
9598
title: "chore: version packages"
9699
commit: "chore: version packages"
100+
createGithubReleases: false
97101
env:
98102
# App token (not GITHUB_TOKEN) so the Version Packages PR triggers CI.
99103
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
@@ -103,3 +107,87 @@ jobs:
103107
# Keep trusted publishing via OIDC, but disable provenance until this
104108
# repository is public.
105109
NPM_CONFIG_PROVENANCE: "false"
110+
111+
- name: Inspect alpha release train candidate
112+
id: alpha-status
113+
env:
114+
PUBLISHED: ${{ steps.changesets.outputs.published }}
115+
run: |
116+
set -euo pipefail
117+
alpha_env="$RUNNER_TEMP/alpha-release-status.env"
118+
node scripts/release-alpha-train.mjs status --published "$PUBLISHED" --remote false | tee "$alpha_env"
119+
cat "$alpha_env" >> "$GITHUB_OUTPUT"
120+
121+
- name: Login to GHCR
122+
if: ${{ steps.alpha-status.outputs.should_complete == 'true' }}
123+
uses: docker/login-action@v4
124+
with:
125+
registry: ghcr.io
126+
username: ${{ github.actor }}
127+
password: ${{ secrets.GITHUB_TOKEN }}
128+
129+
- name: Prepare alpha release train
130+
if: ${{ steps.alpha-status.outputs.should_complete == 'true' }}
131+
id: alpha
132+
env:
133+
PUBLISHED: ${{ steps.changesets.outputs.published }}
134+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
135+
run: |
136+
set -euo pipefail
137+
alpha_env="$RUNNER_TEMP/alpha-release.env"
138+
node scripts/release-alpha-train.mjs prepare --published "$PUBLISHED" --out-dir "$RUNNER_TEMP/alpha-release" | tee "$alpha_env"
139+
cat "$alpha_env" >> "$GITHUB_OUTPUT"
140+
141+
- name: Set up QEMU
142+
if: ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
143+
uses: docker/setup-qemu-action@v4
144+
145+
- name: Set up Docker Buildx
146+
if: ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
147+
uses: docker/setup-buildx-action@v4
148+
149+
- name: Create and push Go release tag
150+
if: ${{ steps.alpha.outputs.create_tag == 'true' }}
151+
env:
152+
TAG: ${{ steps.alpha.outputs.tag }}
153+
TITLE: ${{ steps.alpha.outputs.title }}
154+
run: |
155+
set -euo pipefail
156+
git config user.name "zitadel-release"
157+
git config user.email "noreply@zitadel.com"
158+
git tag -a "$TAG" -m "$TITLE"
159+
git push origin "$TAG"
160+
161+
- name: Prune npm package tags for GoReleaser
162+
if: ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
163+
run: |
164+
set -euo pipefail
165+
git tag -l '@zitadel/*' | while read -r tag; do
166+
git tag -d "$tag"
167+
done
168+
169+
- name: Run GoReleaser
170+
if: ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
171+
uses: goreleaser/goreleaser-action@v7
172+
with:
173+
distribution: goreleaser
174+
version: "~> v2"
175+
args: release --clean
176+
env:
177+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
178+
179+
- name: Update GitHub Release notes
180+
if: ${{ steps.alpha.outputs.update_release == 'true' }}
181+
env:
182+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
183+
TAG: ${{ steps.alpha.outputs.tag }}
184+
TITLE: ${{ steps.alpha.outputs.title }}
185+
NOTES_PATH: ${{ steps.alpha.outputs.notes_path }}
186+
run: |
187+
set -euo pipefail
188+
gh release edit "$TAG" \
189+
--draft \
190+
--prerelease \
191+
--latest=false \
192+
--title "$TITLE" \
193+
--notes-file "$NOTES_PATH"

.goreleaser.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ dockers_v2:
4444
- 'ghcr.io/zitadel/nextgen'
4545
tags:
4646
- '{{ .Version }}'
47-
# Skip `latest` for snapshots so they don't clobber the published tag.
48-
- '{{ if not .IsSnapshot }}latest{{ end }}'
47+
# Only stable releases move the convenience tag. Alpha/beta/rc trains
48+
# must stay on their immutable version tag.
49+
- '{{ if and (not .IsSnapshot) (eq .Prerelease "") }}latest{{ end }}'
4950
platforms:
5051
- linux/amd64
5152
- linux/arm64
@@ -70,3 +71,5 @@ changelog:
7071

7172
release:
7273
draft: true
74+
prerelease: auto
75+
make_latest: '{{ if .Prerelease }}false{{ else }}true{{ end }}'

AGENTS.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ Secrets").
4949
Playwright project. It installs local package tarballs through a temporary
5050
registry and verifies CLI setup plus real registration/login flows.
5151
- `packages/components/` contains shared Lit components.
52-
- `packages/sdk-core/`, `packages/sdk-next/`, and `packages/sdk-nuxt/` contain
53-
public TypeScript SDKs.
52+
- `packages/sdk-core/`, `packages/sdk-next/`, `packages/sdk-nuxt/`,
53+
`packages/sdk-react/`, `packages/sdk-vue/`, and `packages/sdk-angular/`
54+
contain public TypeScript SDKs.
5455
- `packages/api-mock/` contains the in-process MSW handlers and standalone
5556
mock auth server used by demos and e2e tests.
5657
- `packages/lint/` contains the local Nx plugin that infers Oxlint targets.
@@ -107,6 +108,8 @@ workflows.
107108

108109
Use `corepack pnpm run check -- --full` for slower CI-parity phases and
109110
`corepack pnpm run check -- --only <phase>` to rerun one named phase.
111+
Use `corepack pnpm run check -- --only release` after touching Changesets,
112+
GoReleaser, release workflows, or alpha train behavior.
110113

111114
Prefer Nx project targets for narrow package work, for example
112115
`corepack pnpm nx test @zitadel/cli`.
@@ -212,8 +215,9 @@ For customer-local runtime workflows, agents should prefer
212215
`docs` type, for example `docs: add preview status disclaimer`.
213216
- User-visible changes to a public npm package need a changeset. The public
214217
packages are `@zitadel/cli` (`apps/cli/`), `@zitadel/api`,
215-
`@zitadel/components`, `@zitadel/sdk-core`, `@zitadel/sdk-next`, and
216-
`@zitadel/sdk-nuxt`. CI fails a PR that touches them without one
218+
`@zitadel/components`, `@zitadel/sdk-core`, `@zitadel/sdk-next`,
219+
`@zitadel/sdk-nuxt`, `@zitadel/sdk-react`, `@zitadel/sdk-vue`, and
220+
`@zitadel/sdk-angular`. CI fails a PR that touches them without one
217221
(`changeset-check` in `.github/workflows/ci.yml`).
218222
- Add a changeset by writing the file directly — do not depend on the
219223
interactive `pnpm changeset` prompt. Create `.changeset/<short-slug>.md`:
@@ -228,8 +232,9 @@ For customer-local runtime workflows, agents should prefer
228232

229233
List only public package names; pick `patch` (fixes), `minor` (features), or
230234
`major` (breaking). The repo is in `alpha` prerelease mode
231-
(`.changeset/pre.json`), so versions cut as `X.Y.Z-alpha.N` automatically — no
232-
extra action needed.
235+
(`.changeset/pre.json`) and public packages are in one fixed group, so
236+
versions cut as one `X.Y.Z-alpha.N` train automatically — no extra action
237+
needed.
233238

234239
- For changes that release nothing (docs, tests, CI, chores), add an empty
235240
changeset: `corepack pnpm changeset --empty`.

README.md

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,12 @@ service logs. These artifacts expire after 7 days and are not release artifacts.
166166

167167
## Build & release
168168

169-
This monorepo separates Go release artifacts, console build output, and npm
170-
package artifacts. The full rationale lives in
171-
[docs/adrs/002-multi-package-release-strategy.md](docs/adrs/002-multi-package-release-strategy.md).
169+
This monorepo uses GoReleaser for Go artifacts and Changesets for npm package
170+
versioning. During the public alpha period, the release train intentionally
171+
publishes one version across the server image, server binary, CLI, and public
172+
npm packages. The full rationale lives in
173+
[docs/adrs/002-multi-package-release-strategy.md](docs/adrs/002-multi-package-release-strategy.md)
174+
and [docs/adrs/023-lockstep-alpha-release-train.md](docs/adrs/023-lockstep-alpha-release-train.md).
172175

173176
### Go server binary + embedded UIs (`goreleaser`)
174177

@@ -186,10 +189,10 @@ docker run --rm -p 8080:8080 \
186189
ghcr.io/zitadel/nextgen:<snapshot-tag>-amd64
187190
```
188191

189-
The publish-capable release workflow is currently manual-only via
190-
`.github/workflows/release.yml` (`workflow_dispatch`). It can run a dry snapshot
191-
or, when intentionally invoked for a release tag, produce multi-arch tarballs and
192-
push a multi-arch image manifest to `ghcr.io/zitadel/nextgen`.
192+
The manual `.github/workflows/release.yml` workflow remains available for server
193+
snapshots and fallback releases. The normal public alpha path runs GoReleaser
194+
from [`release-npm.yml`](.github/workflows/release-npm.yml) after npm publishing
195+
succeeds, so the server image and npm packages share the same alpha version.
193196

194197
### npm packages (`changesets`)
195198

@@ -201,8 +204,32 @@ change to those packages:
201204
corepack pnpm changeset
202205
```
203206

204-
The changesets workflow opens a "Version Packages" PR. Merging that PR versions
205-
and publishes the affected packages through npm trusted publishing.
207+
The changesets workflow opens a "Version Packages" PR. Merging that PR publishes
208+
the fixed alpha package group through npm trusted publishing, validates that all
209+
public packages share the same `0.1.0-alpha.N` version, creates `v<version>`,
210+
runs GoReleaser, and updates one draft GitHub Release titled
211+
`ZITADEL Alpha <version>`. Alpha releases are GitHub prereleases and publish only
212+
the immutable image tag, for example `ghcr.io/zitadel/nextgen:0.1.0-alpha.N`;
213+
they do not move `ghcr.io/zitadel/nextgen:latest`.
214+
215+
Follow the short [alpha release runbook](docs/runbooks/release-alpha-train.md)
216+
when cutting a public alpha train.
217+
218+
Release process checks can be run locally with:
219+
220+
```sh
221+
corepack pnpm run check -- --only release
222+
```
223+
224+
Tester commands use either the latest alpha stream or an exact train:
225+
226+
```sh
227+
npx @zitadel/cli@alpha doctor
228+
npx @zitadel/cli@alpha start
229+
npx @zitadel/cli@alpha setup --framework next --server local
230+
231+
npx @zitadel/cli@0.1.0-alpha.N start
232+
```
206233

207234
### Local development
208235

0 commit comments

Comments
 (0)