From 4ac0768503fd9cda41e3a024b57095cac5f060de Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Fri, 4 Sep 2026 13:29:45 +0200 Subject: [PATCH 1/2] ci: bootstrap the v2 beta line for the five 1.x packages Phase 4a of the v1/v2 branch split. Puts client, react-sdk, react-bindings, react-native-sdk and styling on the 2.0.0-beta line and marks their GitHub releases as prereleases, so a beta does not outrank the real 1.x releases on the repo page. `releaseAs: premajor` is deliberate and temporary. It is what lifts a package from 1.x to 2.0.0-beta.0, and unlike a global --releaseAs flag it is scoped per package, so the 0.x satellites and the dogfood app keep their own version lines. It is strictly single-use: from 2.0.0-beta.0 premajor yields 3.0.0-beta.0, so a follow-up flips these five to `releaseAs: prerelease` once the bootstrap release has published. Steady-state `prerelease` alone cannot do the bootstrap. From 1.42.0 it computes 1.43.0-beta.0, because it only promotes to premajor when the recommended bump is already major, and relying on a breaking commit per package would lift only the package that commit touches. scripts/release/beta-line-config.test.mts guards both halves of the split: the five must share one supported releaseAs and the `beta` preid, and the independent packages must set neither. Verified that it fails on drift, not just vacuously. Note that setting releaseAs disables dependency tracking, so on the beta line each of the five releases only when it has its own qualifying commits. Beta runs therefore need to cover the whole dependency closure; the publish guard added in #2411 catches a mismatch before it reaches npm. --- packages/client/project.json | 3 + packages/react-bindings/project.json | 3 + packages/react-native-sdk/project.json | 3 + packages/react-sdk/project.json | 3 + packages/styling/project.json | 3 + scripts/release/beta-line-config.test.mts | 109 ++++++++++++++++++++++ 6 files changed, 124 insertions(+) create mode 100644 scripts/release/beta-line-config.test.mts diff --git a/packages/client/project.json b/packages/client/project.json index a5d0ef5add..34fc07f1f4 100644 --- a/packages/client/project.json +++ b/packages/client/project.json @@ -23,6 +23,8 @@ }, "push": true, "skipCommitTypes": ["ci", "refactor", "test", "docs"], + "releaseAs": "premajor", + "preid": "beta", "postTargets": [ "@stream-io/video-client:update-version", "@stream-io/video-client:github", @@ -44,6 +46,7 @@ "github": { "executor": "@jscutlery/semver:github", "options": { + "prerelease": true, "tag": "${tag}", "notes": "${notes}" } diff --git a/packages/react-bindings/project.json b/packages/react-bindings/project.json index 41b9397415..0624d4b737 100644 --- a/packages/react-bindings/project.json +++ b/packages/react-bindings/project.json @@ -24,6 +24,8 @@ "trackDeps": true, "push": true, "skipCommitTypes": ["ci", "refactor", "test", "docs"], + "releaseAs": "premajor", + "preid": "beta", "postTargets": [ "@stream-io/video-react-bindings:enrich-changelog", "@stream-io/video-react-bindings:github", @@ -34,6 +36,7 @@ "github": { "executor": "@jscutlery/semver:github", "options": { + "prerelease": true, "tag": "${tag}", "notesFile": "packages/react-bindings/.release-notes.md" } diff --git a/packages/react-native-sdk/project.json b/packages/react-native-sdk/project.json index 21c291805f..ee2da8c8a7 100644 --- a/packages/react-native-sdk/project.json +++ b/packages/react-native-sdk/project.json @@ -24,6 +24,8 @@ "trackDeps": true, "push": true, "skipCommitTypes": ["ci", "docs"], + "releaseAs": "premajor", + "preid": "beta", "postTargets": [ "@stream-io/video-react-native-sdk:update-version", "@stream-io/video-react-native-sdk:enrich-changelog", @@ -46,6 +48,7 @@ "github": { "executor": "@jscutlery/semver:github", "options": { + "prerelease": true, "tag": "${tag}", "notesFile": "packages/react-native-sdk/.release-notes.md" } diff --git a/packages/react-sdk/project.json b/packages/react-sdk/project.json index 486d12a827..03170765b1 100644 --- a/packages/react-sdk/project.json +++ b/packages/react-sdk/project.json @@ -24,6 +24,8 @@ "trackDeps": true, "push": true, "skipCommitTypes": ["ci", "refactor", "test", "docs"], + "releaseAs": "premajor", + "preid": "beta", "postTargets": [ "@stream-io/video-react-sdk:update-version", "@stream-io/video-react-sdk:enrich-changelog", @@ -47,6 +49,7 @@ "github": { "executor": "@jscutlery/semver:github", "options": { + "prerelease": true, "tag": "${tag}", "notesFile": "packages/react-sdk/.release-notes.md" } diff --git a/packages/styling/project.json b/packages/styling/project.json index dff151782f..deebd77a2e 100644 --- a/packages/styling/project.json +++ b/packages/styling/project.json @@ -23,6 +23,8 @@ "trackDeps": true, "push": true, "skipCommitTypes": ["ci", "refactor", "test"], + "releaseAs": "premajor", + "preid": "beta", "postTargets": [ "@stream-io/video-styling:github", "@stream-io/video-styling:publish" @@ -32,6 +34,7 @@ "github": { "executor": "@jscutlery/semver:github", "options": { + "prerelease": true, "tag": "${tag}", "notes": "${notes}" } diff --git a/scripts/release/beta-line-config.test.mts b/scripts/release/beta-line-config.test.mts new file mode 100644 index 0000000000..ed631201a7 --- /dev/null +++ b/scripts/release/beta-line-config.test.mts @@ -0,0 +1,109 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * Guards the v2 prerelease configuration. + * + * The five packages that carry the v2 major release together on the + * `2.0.0-beta.N` line; every other package keeps its own independent version + * line. Both halves of that split are easy to break silently in a project.json, + * so they are asserted here. + */ + +const V2_PACKAGES = [ + 'client', + 'react-sdk', + 'react-bindings', + 'react-native-sdk', + 'styling', +]; + +// Packages that must stay on their own version line, never the v2 beta line. +const INDEPENDENT_PACKAGES = [ + 'audio-filters-web', + 'video-filters-web', + 'video-filters-react-native', + 'noise-cancellation-react-native', + 'react-native-callingx', + 'codemod', + 'typescript-config', +]; + +interface ProjectJson { + targets?: { + version?: { options?: { releaseAs?: string; preid?: string } }; + github?: { options?: { prerelease?: boolean } }; + }; +} + +function readProject(pkg: string): ProjectJson { + const path = join(process.cwd(), 'packages', pkg, 'project.json'); + return JSON.parse(readFileSync(path, 'utf8')) as ProjectJson; +} + +test('every v2 package releases on the beta preid', () => { + for (const pkg of V2_PACKAGES) { + const options = readProject(pkg).targets?.version?.options; + assert.equal( + options?.preid, + 'beta', + `packages/${pkg} must set preid "beta" to release on the v2 line`, + ); + } +}); + +// `premajor` is single-use: it establishes 2.0.0-beta.0 from 1.x, but from +// 2.0.0-beta.0 it yields 3.0.0-beta.0. `prerelease` is the steady state and +// increments beta.N. Anything else silently leaves the v2 line. +test('every v2 package uses a supported releaseAs', () => { + for (const pkg of V2_PACKAGES) { + const releaseAs = readProject(pkg).targets?.version?.options?.releaseAs; + assert.ok( + releaseAs === 'premajor' || releaseAs === 'prerelease', + `packages/${pkg} has releaseAs "${releaseAs}", expected "premajor" (bootstrap) or "prerelease" (steady state)`, + ); + } +}); + +// Drift here would split the five across two majors, and because workspace:* +// publishes as an exact pin, a mismatched set is a broken install. +test('the v2 packages share one releaseAs', () => { + const values = new Set( + V2_PACKAGES.map( + (pkg) => readProject(pkg).targets?.version?.options?.releaseAs, + ), + ); + assert.equal( + values.size, + 1, + `the v2 packages must all use the same releaseAs, found: ${[...values].join(', ')}`, + ); +}); + +test('v2 GitHub releases are marked as prereleases', () => { + for (const pkg of V2_PACKAGES) { + assert.equal( + readProject(pkg).targets?.github?.options?.prerelease, + true, + `packages/${pkg} must set prerelease on its github target, or beta releases outrank the 1.x releases on the repo page`, + ); + } +}); + +test('independent packages stay off the v2 beta line', () => { + for (const pkg of INDEPENDENT_PACKAGES) { + const options = readProject(pkg).targets?.version?.options; + assert.equal( + options?.preid, + undefined, + `packages/${pkg} keeps its own version line and must not set a preid`, + ); + assert.equal( + options?.releaseAs, + undefined, + `packages/${pkg} keeps its own version line and must not set releaseAs`, + ); + } +}); From 37746fffe2dc3edeb6caa13c6d2feb4d12e15c84 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Fri, 4 Sep 2026 13:30:49 +0200 Subject: [PATCH 2/2] ci: switch the v2 packages to the steady-state prerelease bump Phase 4b of the v1/v2 branch split. Merge this only after the bootstrap release from #2412 has published 2.0.0-beta.0. `releaseAs: premajor` did its single job of lifting the five packages from 1.x onto the 2.0.0-beta line. Leaving it in place would compute 3.0.0-beta.0 on the next release, so the five move to `releaseAs: prerelease`, which increments beta.N and keeps the 2.0.0 core sticky. The config test no longer accepts `premajor`, so a forgotten flip fails CI rather than silently shipping a new major. --- packages/client/project.json | 2 +- packages/react-bindings/project.json | 2 +- packages/react-native-sdk/project.json | 2 +- packages/react-sdk/project.json | 2 +- packages/styling/project.json | 2 +- scripts/release/beta-line-config.test.mts | 15 ++++++++------- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/client/project.json b/packages/client/project.json index 34fc07f1f4..7e3c789cd8 100644 --- a/packages/client/project.json +++ b/packages/client/project.json @@ -23,7 +23,7 @@ }, "push": true, "skipCommitTypes": ["ci", "refactor", "test", "docs"], - "releaseAs": "premajor", + "releaseAs": "prerelease", "preid": "beta", "postTargets": [ "@stream-io/video-client:update-version", diff --git a/packages/react-bindings/project.json b/packages/react-bindings/project.json index 0624d4b737..d9e9121741 100644 --- a/packages/react-bindings/project.json +++ b/packages/react-bindings/project.json @@ -24,7 +24,7 @@ "trackDeps": true, "push": true, "skipCommitTypes": ["ci", "refactor", "test", "docs"], - "releaseAs": "premajor", + "releaseAs": "prerelease", "preid": "beta", "postTargets": [ "@stream-io/video-react-bindings:enrich-changelog", diff --git a/packages/react-native-sdk/project.json b/packages/react-native-sdk/project.json index ee2da8c8a7..8b1d009a71 100644 --- a/packages/react-native-sdk/project.json +++ b/packages/react-native-sdk/project.json @@ -24,7 +24,7 @@ "trackDeps": true, "push": true, "skipCommitTypes": ["ci", "docs"], - "releaseAs": "premajor", + "releaseAs": "prerelease", "preid": "beta", "postTargets": [ "@stream-io/video-react-native-sdk:update-version", diff --git a/packages/react-sdk/project.json b/packages/react-sdk/project.json index 03170765b1..30ae333d7a 100644 --- a/packages/react-sdk/project.json +++ b/packages/react-sdk/project.json @@ -24,7 +24,7 @@ "trackDeps": true, "push": true, "skipCommitTypes": ["ci", "refactor", "test", "docs"], - "releaseAs": "premajor", + "releaseAs": "prerelease", "preid": "beta", "postTargets": [ "@stream-io/video-react-sdk:update-version", diff --git a/packages/styling/project.json b/packages/styling/project.json index deebd77a2e..db0445b4f9 100644 --- a/packages/styling/project.json +++ b/packages/styling/project.json @@ -23,7 +23,7 @@ "trackDeps": true, "push": true, "skipCommitTypes": ["ci", "refactor", "test"], - "releaseAs": "premajor", + "releaseAs": "prerelease", "preid": "beta", "postTargets": [ "@stream-io/video-styling:github", diff --git a/scripts/release/beta-line-config.test.mts b/scripts/release/beta-line-config.test.mts index ed631201a7..89c06393d2 100644 --- a/scripts/release/beta-line-config.test.mts +++ b/scripts/release/beta-line-config.test.mts @@ -54,15 +54,16 @@ test('every v2 package releases on the beta preid', () => { } }); -// `premajor` is single-use: it establishes 2.0.0-beta.0 from 1.x, but from -// 2.0.0-beta.0 it yields 3.0.0-beta.0. `prerelease` is the steady state and -// increments beta.N. Anything else silently leaves the v2 line. -test('every v2 package uses a supported releaseAs', () => { +// The bootstrap is done: 2.0.0-beta.0 is published, so `premajor` must no +// longer appear. From 2.0.0-beta.0 it would yield 3.0.0-beta.0. `prerelease` is +// the steady state and increments beta.N. +test('every v2 package uses the steady-state releaseAs', () => { for (const pkg of V2_PACKAGES) { const releaseAs = readProject(pkg).targets?.version?.options?.releaseAs; - assert.ok( - releaseAs === 'premajor' || releaseAs === 'prerelease', - `packages/${pkg} has releaseAs "${releaseAs}", expected "premajor" (bootstrap) or "prerelease" (steady state)`, + assert.equal( + releaseAs, + 'prerelease', + `packages/${pkg} has releaseAs "${releaseAs}"; the v2 line is bootstrapped, so it must be "prerelease"`, ); } });