diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 7ada0057b..367317fe4 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - v1.x types: - opened - synchronize diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7272577f7..43d65ca65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,8 +22,8 @@ jobs: - name: Check branch is releasable run: | BRANCH="${{ github.ref_name }}" - if [ "$BRANCH" != "main" ] && ! echo "$BRANCH" | grep -qE '^release/v[0-9]+\.[0-9]+$'; then - echo "::error::Releases can only be created from 'main' or 'release/vX.Y' branches. Current branch: $BRANCH" + if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "v1.x" ] && ! echo "$BRANCH" | grep -qE '^release/v[0-9]+\.[0-9]+$'; then + echo "::error::Releases can only be created from 'main', 'v1.x' or 'release/vX.Y' branches. Current branch: $BRANCH" exit 1 fi echo "Branch '$BRANCH' is valid for release." @@ -95,14 +95,14 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20.19.5' + node-version: '24.13.1' - name: Install semantic-release run: | npm install -g \ - semantic-release@24 \ + semantic-release@25 \ @semantic-release/commit-analyzer@13 \ @semantic-release/release-notes-generator@14 \ - @semantic-release/github@11 + @semantic-release/github@12 - name: Run semantic-release id: semantic run: | @@ -188,6 +188,25 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Compute image tags + id: image_tags + env: + IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.package.name }} + VERSION: v${{ needs.release.outputs.new_release_version }} + BRANCH: ${{ github.ref_name }} + run: | + { + echo 'tags<> "$GITHUB_OUTPUT" + - name: Docker build and push uses: docker/build-push-action@v6 with: @@ -205,6 +224,4 @@ jobs: ${{ matrix.package.identifier == 'wallet:frontend' && format('NEXT_PUBLIC_AUTH_HOST={0}', matrix.package.auth_host) || '' }} ${{ matrix.package.identifier == 'wallet:frontend' && format('NEXT_PUBLIC_GATEHUB_ENV={0}', matrix.package.gatehub_env) || '' }} ${{ matrix.package.identifier == 'wallet:frontend' && format('NEXT_PUBLIC_THEME={0}', matrix.package.theme) || '' }} - tags: | - ghcr.io/${{ github.repository_owner }}/${{ matrix.package.name }}:v${{ needs.release.outputs.new_release_version }} - ghcr.io/${{ github.repository_owner }}/${{ matrix.package.name }}:latest + tags: ${{ steps.image_tags.outputs.tags }} diff --git a/release.config.js b/release.config.js index b02e0460e..d4e2a9039 100644 --- a/release.config.js +++ b/release.config.js @@ -1,10 +1,30 @@ -// Maintenance branches (release/vX.Y) are added dynamically so semantic-release -// accepts whichever branch the workflow is triggered against, without needing -// to hardcode every future maintenance branch in advance. +// Release branches, in ascending version order. semantic-release derives the +// version range each branch may publish from the tags on the branches after it: +// +// - main The 0.x production line. When v1.x holds a v1.0.0 tag, main is +// clamped to ">=0.x <1.0.0" and accepts only patch and minor. A +// breaking change merged here then fails the release with +// EINVALIDNEXTVERSION. That failure is the guardrail, not a defect: +// the commit belongs on v1.x. +// - v1.x The next-major development line. It publishes on its own "v1.x" +// channel, so its GitHub releases are marked pre-release and never +// take the "Latest" badge away from main. +// +// You might ask, why are we not just using release/v1.0.x instead? The reason +// is because we might want multiple feature versions of v1.x deployed in +// various environments simultaneously, and using release/v1.0.x would tie us to a +// single patch line, which is less flexible. +// const branch = process.env.GITHUB_REF_NAME || 'main' const maintenanceMatch = branch.match(/^release\/v(\d+)\.(\d+)$/) -const branches = ['main'] +const branches = [ + 'main', + { + name: 'v1.x', + channel: 'v1.x' + } +] if (maintenanceMatch) { const [, major, minor] = maintenanceMatch branches.push({