Files
less.js/.github/workflows/publish.yml
Copilot 2958e9df47 feat: PR-based release flow for alpha; test suite proving all three release properties (#4431)
* Initial plan

* fix: correct release automation for master merges and publishing

- create-release-pr.yml: add set -euo pipefail; track whether a commit
  was created; skip push + gh pr create when no version changes (no-op
  safety - fixes the "no commits between head and base" failure).

- scripts/bump-and-publish.js: on master, use the version already in
  package.json as-is (no auto-increment). Validate it is > NPM version.
  Skip updateAllVersions/git-add/git-commit on master so the published
  tag always points to the release PR merge commit on master, not to a
  local detached commit. Alpha behavior is unchanged.

- Fix error message: on master say "Git tag was pushed" rather than
  "Version bump commit and tag were pushed".

Co-authored-by: matthew-dean <414752+matthew-dean@users.noreply.github.com>

* test: add release automation test suite (20 tests)

Proves the three components of the release flow work correctly:
- publish.yml if: conditions (6 scenarios)
- create-release-pr.yml if: conditions (4 scenarios)
- bump-and-publish.js master path: existing version, no commit, no push (4 tests)
- bump-and-publish.js alpha path: auto-increment, commit, alpha tag (4 tests)
- create-release-pr no-op safety: commit when needed, clean exit when not (2 tests)

Run with: node scripts/test-release-automation.js
         or: npm run test:release (after pnpm install)

Co-authored-by: matthew-dean <414752+matthew-dean@users.noreply.github.com>

* plan: implement PR-based release flow for alpha branch

Co-authored-by: matthew-dean <414752+matthew-dean@users.noreply.github.com>

* feat: PR-based release flow for alpha branch (mirrors master)

- create-release-pr.yml: listen on alpha push; compute alpha version
  increment (X.Y.Z-alpha.N → X.Y.Z-alpha.N+1); use branch-specific PR
  title/base/branch naming; update loop guards for both flavours
- publish.yml: remove push:alpha trigger; add alpha to pull_request
  branches; update if: condition for alpha release PR title+base
- bump-and-publish.js: remove auto-increment/commit/push for alpha;
  add getNpmAlphaVersion(); alpha now validates and publishes like master
- test-release-automation.js: 34 tests covering new flows end-to-end

Co-authored-by: matthew-dean <414752+matthew-dean@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matthew-dean <414752+matthew-dean@users.noreply.github.com>
2026-03-19 15:43:17 -07:00

216 lines
7.8 KiB
YAML

name: Publish to NPM
on:
# Publish when a release PR is merged:
# master branch: "chore: release vX.Y.Z" PR → publishes latest
# alpha branch: "chore: alpha release vX.Y.Z" PR → publishes alpha
# Both release PRs are created automatically by create-release-pr.yml.
pull_request:
types: [closed]
branches:
- master
- alpha
permissions:
id-token: write # Required for OIDC trusted publishing
contents: write # Required for creating releases and pushing tags
jobs:
publish:
name: Publish to NPM
runs-on: ubuntu-latest
# Only run when a release PR with the expected title is merged into master
# or alpha. Any other PR close (or merge without the right title) is
# silently skipped.
if: |
github.repository == 'less/less.js' &&
github.event.pull_request.merged == true &&
(
(github.event.pull_request.base.ref == 'master' &&
startsWith(github.event.pull_request.title, 'chore: release v')) ||
(github.event.pull_request.base.ref == 'alpha' &&
startsWith(github.event.pull_request.title, 'chore: alpha release v'))
)
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
# Check out the base branch (master or alpha) post-merge so the
# version bump from the release PR is already present.
ref: ${{ github.event.pull_request.base.ref }}
- name: Install pnpm
uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run node tests (ESM + CJS)
run: pnpm run test:node
- name: Build
run: |
cd packages/less
pnpm run build
- name: Configure Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Determine branch and tag type
id: branch-info
run: |
# Always a pull_request event; base.ref is master or alpha.
BRANCH="${{ github.event.pull_request.base.ref }}"
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
if [ "$BRANCH" = "alpha" ]; then
echo "is_alpha=true" >> $GITHUB_OUTPUT
echo "npm_tag=alpha" >> $GITHUB_OUTPUT
echo "release_type=prerelease" >> $GITHUB_OUTPUT
else
echo "is_alpha=false" >> $GITHUB_OUTPUT
echo "npm_tag=latest" >> $GITHUB_OUTPUT
echo "release_type=release" >> $GITHUB_OUTPUT
fi
- name: Validate alpha branch requirements
if: steps.branch-info.outputs.is_alpha == 'true'
run: |
# Fetch master branch
git fetch origin master:master || true
# Check 1: Alpha branch must not be behind master
echo "🔍 Checking if alpha branch is up to date with master..."
MASTER_COMMITS=$(git rev-list --count alpha..master 2>/dev/null || echo "0")
if [ "$MASTER_COMMITS" -gt 0 ]; then
echo "❌ ERROR: Alpha branch is behind master by $MASTER_COMMITS commit(s)"
echo " Alpha branch must include all commits from master before publishing"
exit 1
fi
echo "✅ Alpha branch is up to date with master"
# Check 2: Get current version and validate it contains 'alpha'
CURRENT_VERSION=$(node -p "require('./packages/less/package.json').version")
echo "📦 Current version: $CURRENT_VERSION"
if [[ ! "$CURRENT_VERSION" =~ -alpha\. ]]; then
echo "❌ ERROR: Alpha branch version must contain '-alpha.'"
echo " Current version: $CURRENT_VERSION"
echo " Expected format: X.Y.Z-alpha.N"
exit 1
fi
echo "✅ Version contains 'alpha' suffix"
# Check 3: Alpha base version must be >= master version
echo "🔍 Comparing alpha base version with master version..."
MASTER_VERSION=$(git show master:packages/less/package.json 2>/dev/null | node -p "try { JSON.parse(require('fs').readFileSync(0, 'utf-8')).version } catch(e) { '0.0.0' }" || echo "0.0.0")
if [ "$MASTER_VERSION" = "0.0.0" ]; then
echo "⚠️ Could not determine master version, skipping comparison"
else
echo "📦 Master version: $MASTER_VERSION"
# Extract base version (remove -alpha.X suffix)
ALPHA_BASE=$(echo "$CURRENT_VERSION" | sed 's/-alpha\.[0-9]*$//')
echo "📦 Alpha base version: $ALPHA_BASE"
# Compare versions using semver from root workspace
COMPARE_RESULT=$(node -e "
const semver = require('semver');
const alphaBase = process.argv[1];
const master = process.argv[2];
if (semver.lt(alphaBase, master)) {
console.log('ERROR');
} else {
console.log('OK');
}
" "$ALPHA_BASE" "$MASTER_VERSION" 2>/dev/null || echo "ERROR")
if [ "$COMPARE_RESULT" = "ERROR" ]; then
echo "❌ ERROR: Alpha base version ($ALPHA_BASE) is lower than master version ($MASTER_VERSION)"
echo " According to semver, alpha base version must be >= master version"
exit 1
fi
echo "✅ Alpha base version is >= master version"
fi
- name: Ensure npm 11.5.1 or later for trusted publishing
run: npm install -g npm@latest
- name: Bump version and publish
id: publish
env:
# Pass the resolved base branch name (master or alpha) so that
# bump-and-publish.js knows which branch it is publishing for.
GITHUB_REF_NAME: ${{ steps.branch-info.outputs.branch }}
run: |
pnpm run publish
# Extract version from package.json
VERSION=$(node -p "require('./packages/less/package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.publish.outputs.tag }}"
VERSION="${{ steps.publish.outputs.version }}"
IS_ALPHA="${{ steps.branch-info.outputs.is_alpha }}"
if [ "$IS_ALPHA" = "true" ]; then
TITLE="Alpha Release $TAG"
PRERELEASE="--prerelease"
BODY="## Alpha Release
This is an alpha release from the alpha branch.
## Installation
\`\`\`bash
npm install less@${VERSION} --tag alpha
\`\`\`
Or:
\`\`\`bash
npm install less@alpha
\`\`\`"
else
TITLE="Release $TAG"
PRERELEASE=""
BODY="## Changes
See [CHANGELOG.md](https://github.com/less/less.js/blob/master/CHANGELOG.md) for details.
## Installation
\`\`\`bash
npm install less@${VERSION}
\`\`\`"
fi
if gh release view "$TAG" &>/dev/null; then
echo "Release $TAG already exists, uploading assets to existing release"
gh release upload "$TAG" packages/less/dist/less.js packages/less/dist/less.min.js --clobber
else
gh release create "$TAG" \
--title "$TITLE" \
$PRERELEASE \
--notes "$BODY" \
packages/less/dist/less.js \
packages/less/dist/less.min.js
fi