From 6ec374c1fb242302b65166ff533bbf083e59e09a Mon Sep 17 00:00:00 2001 From: Brad DerManouelian Date: Tue, 8 Sep 2026 16:37:47 -0500 Subject: [PATCH] ci(release): drop the retired beta branch from every workflow trigger The beta channel closed with the 1.0 release; the branch is deleted and archived as the archive/beta tag. Remove it from the package, CLI, CI, DB-integration and E2E-smoke workflow triggers, delete the beta-only publish script, and give the release/** branches the two gates beta had. --- .github/scripts/packages-publish-beta.mjs | 145 --------------------- .github/workflows/ci.yml | 4 +- .github/workflows/cli-semantic-release.yml | 19 +-- .github/workflows/db-integration.yml | 2 +- .github/workflows/e2e-smoke.yml | 2 +- .github/workflows/packages-release.yml | 36 +---- cli/.releaserc.json | 2 +- packages/RELEASING.md | 49 +------ 8 files changed, 24 insertions(+), 235 deletions(-) delete mode 100644 .github/scripts/packages-publish-beta.mjs diff --git a/.github/scripts/packages-publish-beta.mjs b/.github/scripts/packages-publish-beta.mjs deleted file mode 100644 index 4802251e1..000000000 --- a/.github/scripts/packages-publish-beta.mjs +++ /dev/null @@ -1,145 +0,0 @@ -// Publish pre-release packages from the `beta` branch under the `beta` npm tag. -// -// The `beta` branch carries package changes that depend on the unreleased 1.0 -// app and therefore cannot ship to `latest` — a user on the released app would -// install them and hit missing endpoints. They go out under the `beta` dist-tag -// instead, so `npm i @testplanit/mcp-server` keeps resolving to the `latest` -// release and testers opt in explicitly with `@beta`. -// -// Beta releases are NOT Changesets-managed. Changesets versions from `main` -// only (its "Version Packages" PR never lands on `beta`, which is why every -// packages/* version here trails npm). Cutting a beta is instead a deliberate -// manual step: set the package's version to an explicit pre-release on `beta` -// and push. Versions track the app's 1.0 beta line — `1.0.0-beta.N` — which is -// comfortably ahead of every packages/* `latest` (all still 0.x). -// -// That drives the safety gate below: this script publishes a package ONLY when -// its version contains a pre-release identifier. Every other packages/* on this -// branch carries a plain version that trails what is already on npm, so without -// the gate a push to `beta` would try to republish stale `latest` versions. -// -// This runs from packages-release.yml on purpose. npm trusted publishing (OIDC) -// authorizes ONE workflow filename per package, and that slot is spent on -// packages-release.yml — publishing from a new workflow file would fall back to -// anonymous auth and E404. See the workflow header. -import { readFileSync, readdirSync, existsSync, appendFileSync } from "node:fs"; -import { spawnSync } from "node:child_process"; - -const TAG = "beta"; - -function readJson(path) { - return JSON.parse(readFileSync(path, "utf8")); -} - -// Direct registry check, matching packages-publish.mjs: `npm view @ -// version` exits 0 and echoes the version when published, non-zero (E404) when -// not. Re-publishing an existing version is a hard error, so pushes to `beta` -// that don't bump the version must be a no-op. -function isPublished(name, version) { - const result = spawnSync("npm", ["view", `${name}@${version}`, "version"], { - encoding: "utf8", - }); - return result.status === 0 && result.stdout.trim() === version; -} - -const published = []; -const skipped = []; -const failedNames = new Set(); -let failed = false; - -const candidates = []; -for (const entry of readdirSync("packages", { withFileTypes: true })) { - if (!entry.isDirectory()) continue; - const dir = `packages/${entry.name}`; - const pkgPath = `${dir}/package.json`; - if (!existsSync(pkgPath)) continue; - const pkg = readJson(pkgPath); - if (pkg.private || !pkg.name || !pkg.version) continue; - candidates.push({ dir, pkg }); -} - -// Publish dependencies before dependents. The reporters depend on -// `@testplanit/api` as `workspace:^`, which pnpm rewrites at pack time to the -// api version being published in this same run — so api has to reach the -// registry first or the reporters land pointing at a version nobody can install -// yet. Filesystem order happens to put api first today; don't rely on it. -const names = new Set(candidates.map((c) => c.pkg.name)); -const internalDeps = ({ pkg }) => - Object.keys({ ...pkg.dependencies, ...pkg.peerDependencies }).filter((d) => - names.has(d) - ); -candidates.sort((a, b) => internalDeps(a).length - internalDeps(b).length); - -for (const { dir, pkg } of candidates) { - // The gate: no pre-release identifier, no beta publish. - if (!pkg.version.includes("-")) { - skipped.push(`${pkg.name}@${pkg.version} — not a pre-release version`); - continue; - } - - if (isPublished(pkg.name, pkg.version)) { - skipped.push(`${pkg.name}@${pkg.version} — already on npm`); - continue; - } - - // Don't ship a package whose in-repo dependency just failed to publish — it - // would resolve to a version that never reached the registry. - const brokenDeps = internalDeps({ pkg }).filter((d) => failedNames.has(d)); - if (brokenDeps.length) { - skipped.push( - `${pkg.name}@${pkg.version} — dependency failed to publish: ${brokenDeps.join(", ")}` - ); - failed = true; - continue; - } - - console.log( - `[packages-publish-beta] publishing ${pkg.name}@${pkg.version} --tag ${TAG}` - ); - // `--tag beta` overrides publishConfig.tag ("latest"), which stays correct for - // the main-branch release path. `--no-git-checks` is required: pnpm otherwise - // refuses to publish from a branch that isn't its default publish branch. - const result = spawnSync( - "pnpm", - ["publish", "--tag", TAG, "--no-git-checks", "--access", "public"], - { cwd: dir, stdio: "inherit" } - ); - - if (result.status === 0) { - published.push(`${pkg.name}@${pkg.version}`); - } else { - failed = true; - failedNames.add(pkg.name); - console.error( - `[packages-publish-beta] FAILED to publish ${pkg.name}@${pkg.version}` - ); - } -} - -const summary = [ - "## Beta Pre-releases", - "", - published.length - ? `Published under the \`${TAG}\` tag (install with \`@${TAG}\`):\n\n${published - .map((p) => `- ${p}`) - .join("\n")}` - : "No packages published.", - ...(skipped.length - ? [ - "", - "
Skipped", - "", - ...skipped.map((s) => `- ${s}`), - "", - "
", - ] - : []), - "", -].join("\n"); - -console.log(summary); -if (process.env.GITHUB_STEP_SUMMARY) { - appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary); -} - -process.exit(failed ? 1 : 0); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c68a63659..f74eec235 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ main, beta, develop, 'feature/**', 'hotfix/**', 'release/**', 'users/**' ] + branches: [ main, develop, 'feature/**', 'hotfix/**', 'release/**', 'users/**' ] pull_request: - branches: [ main, beta, develop ] + branches: [ main, develop, 'release/**' ] permissions: contents: read diff --git a/.github/workflows/cli-semantic-release.yml b/.github/workflows/cli-semantic-release.yml index 44cbbc730..b08b15c1a 100644 --- a/.github/workflows/cli-semantic-release.yml +++ b/.github/workflows/cli-semantic-release.yml @@ -1,24 +1,17 @@ # Releases @testplanit/cli via semantic-release. # -# `main` cuts normal releases to the `latest` npm tag. `beta` is configured in -# cli/.releaserc.json as a prerelease branch, so pushes there cut `1.x.y-beta.N` -# under the `beta` npm tag instead — matching the beta channel the packages/* -# workspace publishes through packages-release.yml. Testers opt in with -# `npm i @testplanit/cli@beta`; `@latest` is untouched. +# `main` cuts releases to the `latest` npm tag. Versions are computed by +# semantic-release from conventional commits, NOT set by hand; a push with no +# releasable commits publishes nothing. # -# Versions are computed by semantic-release from conventional commits, NOT set -# by hand: a `feat` on beta over the 1.3.0 baseline yields 1.4.0-beta.1 (its -# prerelease counter starts at 1). A push with no releasable commits publishes -# nothing. -# -# Both branches release from THIS workflow file on purpose — npm trusted -# publishing (OIDC) authorizes a single workflow filename per package. +# npm trusted publishing (OIDC) authorizes a single workflow filename per +# package — every CLI release must come from THIS workflow file. name: CLI Semantic Release on: workflow_dispatch: push: - branches: [main, beta] + branches: [main] paths: - 'cli/**' - '.github/workflows/cli-semantic-release.yml' diff --git a/.github/workflows/db-integration.yml b/.github/workflows/db-integration.yml index 539309d23..566220b28 100644 --- a/.github/workflows/db-integration.yml +++ b/.github/workflows/db-integration.yml @@ -11,7 +11,7 @@ name: DB Integration Tests on: workflow_dispatch: push: - branches: [main, beta] + branches: [main, 'release/**'] paths: - "testplanit/__tests__/integration/**" - "testplanit/lib/**" diff --git a/.github/workflows/e2e-smoke.yml b/.github/workflows/e2e-smoke.yml index 2ee6b6a8c..aa93a9403 100644 --- a/.github/workflows/e2e-smoke.yml +++ b/.github/workflows/e2e-smoke.yml @@ -16,7 +16,7 @@ name: E2E Smoke on: workflow_dispatch: push: - branches: [main, beta] + branches: [main, 'release/**'] paths: - "testplanit/app/**" - "testplanit/components/**" diff --git a/.github/workflows/packages-release.yml b/.github/workflows/packages-release.yml index 169db6ad8..15814c045 100644 --- a/.github/workflows/packages-release.yml +++ b/.github/workflows/packages-release.yml @@ -13,41 +13,24 @@ # 2. Commit and merge to main # 3. Review and merge the auto-created "Version Packages" PR # -# Beta pre-releases (the `beta` branch): -# Package changes that depend on the unreleased 1.0 app can't ship to `latest`, -# so `beta` publishes under the `beta` dist-tag instead — testers opt in with -# `npm i @testplanit/mcp-server@beta` while `@latest` stays on the main release. -# Versions track the app's 1.0 beta line: `1.0.0-beta.N`. +# @testplanit/cli lives outside packages/ and is released by +# cli-semantic-release.yml — it is not part of this workflow. # -# That path is NOT Changesets-managed (Changesets versions from `main` only): -# set an explicit pre-release version on `beta` and push. -# -# # on the beta branch -# # packages/mcp-server/package.json: "version": "1.0.0-beta.1" -# git commit -am 'chore(mcp-server): 1.0.0-beta.1' && git push origin beta -# -# Only packages/* carrying a pre-release version publish from `beta`; see -# .github/scripts/packages-publish-beta.mjs. @testplanit/cli lives outside -# packages/ and is released by cli-semantic-release.yml — it is not part of this -# channel. -# -# Both paths live in THIS file on purpose: npm trusted publishing (OIDC) -# authorizes a single workflow filename per package, and it is this one. A -# separate beta workflow file would fall back to anonymous auth and E404. +# npm trusted publishing (OIDC) authorizes a single workflow filename per +# package, and it is this one: every publish path must live in THIS file, or +# it falls back to anonymous auth and E404. name: Package Release on: push: branches: - main - - beta paths: - 'packages/**' - '.changeset/**' - '.github/workflows/packages-release.yml' - # The publish commands live here; a fix to one must be able to trigger a run. + # The publish command lives here; a fix to it must be able to trigger a run. - '.github/scripts/packages-publish.mjs' - - '.github/scripts/packages-publish-beta.mjs' workflow_dispatch: concurrency: ${{ github.workflow }}-${{ github.ref }} @@ -127,13 +110,6 @@ jobs: # No NPM_TOKEN — publishing authenticates via OIDC trusted publishing # (id-token: write above + the trusted publisher configured on npm). - # Beta has no "Version Packages" PR step: versions are set by hand on the - # branch, and only pre-release versions are eligible. Writes its own job - # summary. - - name: Publish beta pre-releases - if: github.ref_name == 'beta' - run: node .github/scripts/packages-publish-beta.mjs - - name: Summary if: steps.changesets.outputs.published == 'true' run: | diff --git a/cli/.releaserc.json b/cli/.releaserc.json index e6c0ecca4..ecdfe1c8c 100644 --- a/cli/.releaserc.json +++ b/cli/.releaserc.json @@ -1,5 +1,5 @@ { - "branches": ["main", { "name": "beta", "prerelease": true }], + "branches": ["main"], "tagFormat": "cli-v${version}", "plugins": [ [ diff --git a/packages/RELEASING.md b/packages/RELEASING.md index 104335a08..d897aca38 100644 --- a/packages/RELEASING.md +++ b/packages/RELEASING.md @@ -74,50 +74,16 @@ When you're ready to release, merge the "Version Packages" PR. This will: 2. Publish packages to npm 3. Create GitHub releases with release notes -## Beta Pre-releases (the `beta` branch) - -Package changes that depend on the unreleased 1.0 app cannot ship to `latest` — a -user on the released app would install them and hit endpoints that do not exist -yet. They publish from the `beta` branch under the `beta` npm dist-tag instead, -so the default install is unaffected and testers opt in explicitly: - -```bash -npm install @testplanit/mcp-server@beta -``` - -Beta versions track the app's 1.0 line: `1.0.0-beta.N`. - -This path is **not** Changesets-managed — Changesets versions from `main` only, -which is why `packages/*` versions on `beta` trail npm. Cutting a beta is a -deliberate manual step: - -1. On the `beta` branch, set the package's `version` to the next pre-release - (e.g. `1.0.0-beta.1`) in its `package.json`. -2. Commit and push to `beta`. - -`packages-release.yml` then publishes it via -`.github/scripts/packages-publish-beta.mjs`. Two rules govern what goes out: - -- **Only pre-release versions publish.** A package whose version has no `-` - suffix is skipped, so the packages you did not bump can never be republished. -- **Already-published versions are skipped**, so pushing to `beta` without a - version bump is a no-op. - Packages publish dependencies-first, so `@testplanit/api` reaches the registry before the reporters that declare it as `workspace:^`. -Keep writing changesets for beta work as normal. They are consumed on `main` when -the change lands there, and produce the real changelog entry then; the beta -version number is deliberately outside that flow. - `@testplanit/cli` is separate: it lives outside `packages/` and is released by -`cli-semantic-release.yml`, where `beta` is configured as a semantic-release -prerelease branch. Its version is computed from conventional commits rather than -set by hand, and a push with no releasable commits publishes nothing. +`cli-semantic-release.yml`. Its version is computed from conventional commits +rather than set by hand, and a push with no releasable commits publishes nothing. -> Both branches release from their existing workflow file on purpose. npm trusted -> publishing (OIDC) authorizes a single workflow filename per package, so adding a -> separate beta workflow would fall back to anonymous auth and fail with E404. +> Each package releases from its existing workflow file on purpose. npm trusted +> publishing (OIDC) authorizes a single workflow filename per package, so a +> publish from any other workflow falls back to anonymous auth and fails with E404. ## Version Bump Guidelines @@ -153,9 +119,8 @@ pnpm --filter "@testplanit/*" test The release process is automated via GitHub Actions: -- **Trigger**: Push to `main` (Changesets release) or `beta` (pre-release) with - changes in `packages/` or `.changeset/` -- **Workflow**: `.github/workflows/packages-release.yml` for both branches +- **Trigger**: Push to `main` with changes in `packages/` or `.changeset/` +- **Workflow**: `.github/workflows/packages-release.yml` - **Authentication**: npm trusted publishing (OIDC) — the job's `id-token: write` permission plus the trusted publisher configured on npm. There is no `NPM_TOKEN` secret, and the trusted publisher is bound to this one workflow