From b80b0ef464e401a8772fe5a6360fafa2d2b97599 Mon Sep 17 00:00:00 2001 From: gcharang <21151592+gcharang@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:42:21 +0400 Subject: [PATCH] release: add dev promotion flow --- .changeset/README.md | 22 ++++++++------- .changeset/config.json | 2 +- .github/workflows/ci.yml | 3 +- .github/workflows/release.yml | 33 +++++++++++++--------- .github/workflows/version.yml | 52 +++++++++++++++++++++++++++++++++++ README.md | 14 ++++++++-- 6 files changed, 98 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/version.yml diff --git a/.changeset/README.md b/.changeset/README.md index 259a0a5..26f34e8 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -1,13 +1,15 @@ -# Changesets + -Release flow (single-branch, adapted from anchorage's two-branch setup): +# Release packages with Changesets -1. Every PR that changes a published package (`@understudy/protocol`, - `@understudy/connector`) adds a changeset: `pnpm changeset`. -2. On push to `master`, `.github/workflows/release.yml` either opens/updates - the standing "Version Packages" PR (when changesets are pending) or - publishes any package whose version is not yet on npm (when none are). -3. Merging the Version Packages PR is the release trigger. +Understudy versions published packages on `dev` and publishes them from `master`. -The apps (`apps/backend`, `apps/extension`) are `private: true` and never -publish. Docs: https://github.com/changesets/changesets +1. Target feature and fix pull requests at `dev`. Add a changeset with `pnpm changeset` when a pull request changes `@understudy/protocol` or `@understudy/connector`. +2. Merge the pull request into `dev`. `.github/workflows/version.yml` opens or updates `changeset-release/dev` with version bumps, changelog entries, and consumed changesets. +3. Review and merge the `Version Packages` pull request into `dev`. +4. Verify the versioned `dev` commit, then immediately open and merge a promotion pull request from `dev` to `master`. +5. `.github/workflows/release.yml` publishes unpublished public package versions with npm provenance, tags them, and creates GitHub releases. + +If another changeset reaches `dev` before promotion, merge the regenerated `Version Packages` pull request and update the promotion. The release workflow rejects pending changesets on `master`. + +Do not merge `master` back into `dev`. The apps in `apps/backend` and `apps/extension` are private and never publish. Read the [Changesets documentation](https://github.com/changesets/changesets) for CLI details. diff --git a/.changeset/config.json b/.changeset/config.json index e8fc32b..a9b4f90 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -5,7 +5,7 @@ "fixed": [], "linked": [], "access": "public", - "baseBranch": "master", + "baseBranch": "dev", "updateInternalDependencies": "patch", "ignore": [], "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdc3660..59e024e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,8 +2,9 @@ name: CI on: push: - branches: [master] + branches: [master, dev] pull_request: + branches: [master, dev] concurrency: group: ci-${{ github.ref }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f04dcf..0076936 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,15 @@ name: Release -# Single-branch changesets flow (adapted from anchorage's two-branch -# version.yml + release.yml — understudy has no dev/main split, so one -# workflow does both halves). On every push to master the changesets action -# either: -# (a) opens/updates the standing "Version Packages" PR when changesets are -# pending (version bumps + CHANGELOG entries + changeset deletion), or -# (b) publishes any public package whose package.json version is not yet on -# npm, creates git tags, and creates GitHub releases. -# A push with nothing pending and nothing unpublished is a clean no-op. +# Publish the versions promoted from dev by a dev-to-master release PR. +# version.yml owns version bumps and changelogs on dev, so this workflow never +# opens pull requests or commits to master. It publishes public package +# versions that are not yet on npm, creates git tags, and creates GitHub +# releases. A push with nothing unpublished is a clean no-op. +# +# A pending changeset on master means dev changed after its Version Packages +# PR merged but before promotion. Publishing that tree would attach the new +# change to a version and changelog that did not include it, so fail before +# installing or publishing anything. # # Requires the NPM_TOKEN repo secret: an npm automation token with publish # rights on the @understudy scope (the org must exist on npm first). Without @@ -24,7 +25,6 @@ concurrency: release-${{ github.ref }} permissions: contents: write - pull-requests: write id-token: write jobs: @@ -49,6 +49,15 @@ jobs: cache: pnpm registry-url: https://registry.npmjs.org + - name: Refuse pending changesets (freeze-window tripwire) + run: | + PENDING=$(find .changeset -maxdepth 1 -name '*.md' ! -name 'README.md') + if [ -n "$PENDING" ]; then + echo "::error::Unversioned changesets reached master. Merge the regenerated 'Version Packages' PR into dev, then re-cut the release PR (dev -> master)." + echo "$PENDING" + exit 1 + fi + - name: Install (frozen lockfile) run: pnpm install --frozen-lockfile @@ -57,12 +66,10 @@ jobs: - name: Build publishable packages run: pnpm --filter "./packages/*" build - - name: Version PR or publish + - name: Publish unpublished versions to npm uses: changesets/action@v1 with: publish: pnpm changeset publish - title: 'release: version packages' - commit: 'release: version packages' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 0000000..1aefbe5 --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,52 @@ +name: Version + +# Version packages on dev before promotion to master. Pending changesets cause +# the action to open or update changeset-release/dev. With no pending +# changesets, the workflow is a clean no-op. +# +# Merge the Version Packages PR into dev, verify that commit, then promote dev +# to master. The release workflow rejects a promotion that still contains a +# pending changeset. + +on: + push: + branches: [dev] + +concurrency: version-${{ github.ref }} + +permissions: + contents: write + pull-requests: write + +jobs: + version: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + # v5/v6 majors run on the node24 action runtime (changesets/action@v1 + # already does); the v4 majors ran on node20, which GitHub now flags + # with a deprecation annotation. + - uses: actions/checkout@v5 + with: + # Changesets reads history to compute what changed. + fetch-depth: 0 + + # Reads the pnpm version from package.json "packageManager". + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v5 + with: + node-version: 22 + cache: pnpm + + - name: Install (frozen lockfile) + run: pnpm install --frozen-lockfile + + # No publish input: versioning happens on dev, while master publishes. + - name: Open or update the Version Packages PR (base dev) + uses: changesets/action@v1 + with: + title: 'release: version packages' + commit: 'release: version packages' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index c8afbe0..135ae70 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Read [`docs/technical-plan.md`](docs/technical-plan.md) for the architecture, sa | `apps/extension` | WXT and React extension with attended and two-tab unattended hosting | | `apps/cdp-spike` | Historical Manifest V3 CDP capability harness | -`@understudy/protocol@0.7.0` and `@understudy/connector@0.5.0` are prepared in this repository. A local build does not publish them. +`@understudy/protocol@0.8.0` and `@understudy/connector@0.5.1` are the current published versions. A local build does not publish them. ## Understand the isolation boundary @@ -62,13 +62,21 @@ Load `apps/extension/.output/chrome-mv3/` through `chrome://extensions`. Follow ## Release published packages -Changesets and GitHub Actions manage releases from `master`. A pull request that changes a published package adds a changeset with: +Use `dev` as the integration branch and `master` as the default release branch. Target feature and fix pull requests at `dev`. Add a changeset when a pull request changes a published package: ```bash pnpm changeset ``` -The release workflow opens or updates the **Version Packages** pull request. Merging that pull request publishes approved versions with npm provenance. `NPM_TOKEN` needs publish access to the `@understudy` scope. +The Version workflow opens or updates the `Version Packages` pull request against `dev`. Release an approved version in this order: + +1. Merge `Version Packages` into `dev`. +2. Verify the versioned `dev` commit. +3. Open and merge a promotion pull request from `dev` to `master`. + +The Release workflow publishes the promoted versions with npm provenance. It rejects `master` commits that contain pending changesets. If another changeset reaches `dev` before promotion, merge the regenerated `Version Packages` pull request and update the promotion. + +Do not merge `master` back into `dev`. `NPM_TOKEN` needs publish access to the `@understudy` scope. Backend deployment remains a separate Wrangler operation. Keep `UNATTENDED_ENABLED_TENANTS=[]` until the canary extension passes the production acceptance suite.