mirror of
https://github.com/less/less.js.git
synced 2026-01-06 22:34:06 -05:00
Set up auto-publishing and alpha branch (#4390)
* Set up alpha branch and auto-publishing * Restore 4.5.0 version number * Set up post-merge hook to stablize version and fix tests * Restore .gitattributes * Fix to publish.yml * Revert PNPM version to version 8 for Node 16 compatibility * Another attempt to resolve PNPM version * Another attempt to resolve PNPM version
This commit is contained in:
192
.github/TESTING_PUBLISHING.md
vendored
Normal file
192
.github/TESTING_PUBLISHING.md
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
# Testing the Publishing Flow
|
||||
|
||||
This guide explains how to test the publishing workflow without actually publishing to npm.
|
||||
|
||||
## Dry Run Mode
|
||||
|
||||
The publishing script supports a dry-run mode that shows what would happen without making any changes:
|
||||
|
||||
```bash
|
||||
# Test from master branch
|
||||
git checkout master
|
||||
DRY_RUN=true pnpm run publish
|
||||
|
||||
# Or use the flag
|
||||
pnpm run publish --dry-run
|
||||
```
|
||||
|
||||
Dry-run mode will:
|
||||
- ✅ Show what version would be created
|
||||
- ✅ Show what packages would be published
|
||||
- ✅ Show what git operations would happen
|
||||
- ❌ **NOT** commit any changes
|
||||
- ❌ **NOT** create git tags
|
||||
- ❌ **NOT** push to remote
|
||||
- ❌ **NOT** publish to npm
|
||||
|
||||
## Testing Locally
|
||||
|
||||
### 1. Test Version Calculation
|
||||
|
||||
```bash
|
||||
# Check current version
|
||||
node -p "require('./packages/less/package.json').version"
|
||||
|
||||
# Run dry-run to see what version would be created
|
||||
DRY_RUN=true pnpm run publish
|
||||
```
|
||||
|
||||
### 2. Test Branch Validation
|
||||
|
||||
```bash
|
||||
# Try from a feature branch (should fail)
|
||||
git checkout -b test-branch
|
||||
pnpm run publish
|
||||
# Should error: "Publishing is only allowed from 'master' or 'alpha' branches"
|
||||
```
|
||||
|
||||
### 3. Test Alpha Branch Validations
|
||||
|
||||
```bash
|
||||
# Switch to alpha branch
|
||||
git checkout alpha
|
||||
|
||||
# Test with dry-run
|
||||
DRY_RUN=true GITHUB_REF_NAME=alpha pnpm run publish
|
||||
|
||||
# This will show:
|
||||
# - Version validation (must contain -alpha.)
|
||||
# - Master sync check
|
||||
# - Version comparison with master
|
||||
```
|
||||
|
||||
### 4. Test Version Override
|
||||
|
||||
```bash
|
||||
# Test explicit version override
|
||||
EXPLICIT_VERSION=4.5.0 DRY_RUN=true pnpm run publish
|
||||
# Should show: "✨ Using explicit version: 4.5.0"
|
||||
```
|
||||
|
||||
## Testing the GitHub Actions Workflow
|
||||
|
||||
### 1. Test Workflow Syntax
|
||||
|
||||
```bash
|
||||
# Validate workflow YAML
|
||||
gh workflow view publish.yml
|
||||
# Or use act (local GitHub Actions runner)
|
||||
act push -W .github/workflows/publish.yml
|
||||
```
|
||||
|
||||
### 2. Test on a Test Branch
|
||||
|
||||
Create a test branch that mimics master/alpha:
|
||||
|
||||
```bash
|
||||
# Create test branch from master
|
||||
git checkout -b test-publish-master master
|
||||
|
||||
# Make a small change
|
||||
echo "# test" >> TEST.md
|
||||
git add TEST.md
|
||||
git commit -m "test: publishing workflow"
|
||||
|
||||
# Push to trigger workflow (if you want to test the full flow)
|
||||
# Note: This will actually try to publish if version changed!
|
||||
```
|
||||
|
||||
### 3. Test Workflow Manually
|
||||
|
||||
You can manually trigger the workflow from GitHub Actions UI:
|
||||
1. Go to Actions tab
|
||||
2. Select "Publish to NPM" workflow
|
||||
3. Click "Run workflow"
|
||||
4. Select branch and run
|
||||
|
||||
**Warning**: This will actually publish if conditions are met!
|
||||
|
||||
## Testing Specific Scenarios
|
||||
|
||||
### Test Master Branch Publishing
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
DRY_RUN=true pnpm run publish
|
||||
|
||||
# Should show:
|
||||
# - Patch version increment (e.g., 4.4.2 → 4.4.3)
|
||||
# - Publishing with 'latest' tag
|
||||
# - Regular release creation
|
||||
```
|
||||
|
||||
### Test Alpha Branch Publishing
|
||||
|
||||
```bash
|
||||
git checkout alpha
|
||||
DRY_RUN=true GITHUB_REF_NAME=alpha pnpm run publish
|
||||
|
||||
# Should show:
|
||||
# - Alpha version increment (e.g., 5.0.0-alpha.1 → 5.0.0-alpha.2)
|
||||
# - Publishing with 'alpha' tag
|
||||
# - Pre-release creation
|
||||
# - All alpha validations passing
|
||||
```
|
||||
|
||||
### Test Version Validation
|
||||
|
||||
```bash
|
||||
# Test that alpha versions can't go to latest
|
||||
# (This is enforced in the script, so it will fail before publishing)
|
||||
|
||||
# Test that non-alpha versions can't go to alpha tag
|
||||
# (Also enforced in the script)
|
||||
```
|
||||
|
||||
## Safe Testing Checklist
|
||||
|
||||
Before actually publishing:
|
||||
|
||||
- [ ] Run dry-run mode to verify version calculation
|
||||
- [ ] Verify branch restrictions work (try from wrong branch)
|
||||
- [ ] Test alpha validations (if testing alpha branch)
|
||||
- [ ] Check that version override works (if needed)
|
||||
- [ ] Verify package.json files would be updated correctly
|
||||
- [ ] Review what git operations would happen
|
||||
- [ ] Confirm npm tag assignment is correct
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Script fails with "branch not allowed"
|
||||
|
||||
Make sure you're on `master` or `alpha` branch, or set `GITHUB_REF_NAME` environment variable:
|
||||
|
||||
```bash
|
||||
GITHUB_REF_NAME=master DRY_RUN=true pnpm run publish
|
||||
```
|
||||
|
||||
### Version calculation seems wrong
|
||||
|
||||
Check the current version in `packages/less/package.json`:
|
||||
|
||||
```bash
|
||||
node -p "require('./packages/less/package.json').version"
|
||||
```
|
||||
|
||||
### Alpha validations failing
|
||||
|
||||
Make sure:
|
||||
- Alpha branch is up-to-date with master
|
||||
- Current version contains `-alpha.`
|
||||
- Alpha base version is >= master version
|
||||
|
||||
## Real Publishing Test (Use with Caution)
|
||||
|
||||
If you want to test the actual publishing flow:
|
||||
|
||||
1. **Use a test npm package** (create a scoped package like `@your-username/less-test`)
|
||||
2. **Temporarily modify the script** to use your test package name
|
||||
3. **Test on a separate branch** that won't trigger the workflow
|
||||
4. **Clean up** after testing
|
||||
|
||||
**Never test on the actual `less` package unless you're ready to publish!**
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -32,8 +32,6 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
194
.github/workflows/publish.yml
vendored
Normal file
194
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
name: Publish to NPM
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- alpha
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.github/**'
|
||||
- 'docs/**'
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC trusted publishing
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish to NPM
|
||||
runs-on: ubuntu-latest
|
||||
# Only run on push events, not pull requests
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- 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 tests
|
||||
run: pnpm run test
|
||||
|
||||
- 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: |
|
||||
BRANCH="${{ github.ref_name }}"
|
||||
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:
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
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 (Master)
|
||||
if: steps.branch-info.outputs.is_alpha != 'true'
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.publish.outputs.tag }}
|
||||
release_name: Release ${{ steps.publish.outputs.tag }}
|
||||
body: |
|
||||
## Changes
|
||||
|
||||
See [CHANGELOG.md](https://github.com/less/less.js/blob/master/CHANGELOG.md) for details.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install less@${{ steps.publish.outputs.version }}
|
||||
```
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Create GitHub Pre-Release (Alpha)
|
||||
if: steps.branch-info.outputs.is_alpha == 'true'
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.publish.outputs.tag }}
|
||||
release_name: Alpha Release ${{ steps.publish.outputs.tag }}
|
||||
body: |
|
||||
## Alpha Release
|
||||
|
||||
This is an alpha release from the alpha branch.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install less@${{ steps.publish.outputs.version }} --tag alpha
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```bash
|
||||
npm install less@alpha
|
||||
```
|
||||
draft: false
|
||||
prerelease: true
|
||||
5
.husky/post-merge
Executable file
5
.husky/post-merge
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Post-merge hook to preserve alpha versions when merging master into alpha
|
||||
node scripts/post-merge-version-fix.js
|
||||
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
||||
pnpm test
|
||||
139
CONTRIBUTING.md
139
CONTRIBUTING.md
@@ -1,54 +1,145 @@
|
||||
# Contributing to Less.js
|
||||
|
||||
> We welcome feature requests and bug reports. Please read these guidelines before submitting one.
|
||||
Thank you for your interest in contributing to Less.js! Contributions come in many forms—fixing bugs, improving code quality, enhancing tooling, updating documentation, and occasionally adding new features. This guide will help you get started.
|
||||
|
||||
## Getting Started
|
||||
|
||||
<span class="warning">**Words that begin with the at sign (`@`) must be wrapped in backticks!** </span>. As a courtesy to avoid sending notifications to any user that might have the `@username` being referenced, please remember that GitHub usernames also start with the at sign. If you don't wrap them in backticks, users will get unintended notifications from you.
|
||||
|
||||
GitHub has other great markdown features as well, [go here to learn more about them](https://help.github.com/articles/github-flavored-markdown).
|
||||
Before you begin, please note: **Words that begin with the at sign (`@`) must be wrapped in backticks!** This prevents unintended notifications to GitHub users. For example, use `` `@username` `` instead of `@username`.
|
||||
|
||||
GitHub has many great markdown features—[learn more about them here](https://help.github.com/articles/github-flavored-markdown).
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
We only accept issues that are bug reports or feature requests. Bugs must be isolated and reproducible problems that we can fix within the Less.js core. Please read the following guidelines before opening any issue.
|
||||
We welcome bug reports and feature requests! To help us help you, please follow these guidelines:
|
||||
|
||||
1. **Search for existing issues.** We get a lot of duplicate issues, and you'd help us out a lot by first checking if someone else has reported the same issue. Moreover, the issue may have already been resolved with a fix available.
|
||||
2. **Create an isolated and reproducible test case.** Be sure the problem exists in Less.js's code with [reduced test cases](http://css-tricks.com/reduced-test-cases/) that should be included in each bug report.
|
||||
3. **Test with the latest version**. We get a lot of issues that could be resolved by updating your version of Less.js.
|
||||
4. **Include an example with source.** E.g. You can use [Less Preview](http://lesscss.org/less-preview/) to create a short test case.
|
||||
5. **Share as much information as possible.** Include operating system and version. Describe how you use Less. If you use it in the browser, please include browser and version, and the version of Less.js you're using. Let us know if you're using the command line (`lessc`) or an external tool. And try to include steps to reproduce the bug.
|
||||
6. If you have a solution or suggestion for how to fix the bug you're reporting, please include it, or make a pull request - don't assume the maintainers know how to fix it just because you do.
|
||||
1. **Search for existing issues first.** Many issues have already been reported or resolved. Checking first saves everyone time.
|
||||
2. **Create an isolated and reproducible test case.** Include [reduced test cases](http://css-tricks.com/reduced-test-cases/) that demonstrate the problem clearly.
|
||||
3. **Test with the latest version.** Many issues are resolved in newer versions—please update first.
|
||||
4. **Include examples with source code.** You can use [Less Preview](http://lesscss.org/less-preview/) to create a short test case.
|
||||
5. **Share as much information as possible.** Include:
|
||||
- Operating system and version
|
||||
- How you're using Less (browser, command line, build tool, etc.)
|
||||
- Browser and version (if applicable)
|
||||
- Version of Less.js you're using
|
||||
- Clear steps to reproduce the issue
|
||||
6. **Suggest solutions if you have them.** If you know how to fix it, share your approach or submit a pull request!
|
||||
|
||||
Please report documentation issues in [the documentation project](https://github.com/less/less-docs).
|
||||
|
||||
## Feature Requests
|
||||
|
||||
* Please search for existing feature requests first to see if something similar already exists.
|
||||
* Include a clear and specific use-case. We love new ideas, but we do not add language features without a reason.
|
||||
* Consider whether or not your language feature would be better as a function or implemented in a 3rd-party build system
|
||||
When suggesting features:
|
||||
|
||||
* **Search existing feature requests first** to see if something similar already exists. Many features are already planned or under consideration.
|
||||
* **Include a clear and specific use-case.** Help us understand the practical need and how it would be used.
|
||||
* **Consider alternatives.** Sometimes a function or a 3rd-party build system might be a better fit than a core language feature.
|
||||
|
||||
**Note:** Most helpful contributions to Less.js are organizational—addressing bugs, improving code quality, enhancing tooling, and updating documentation. The language features are generally stable, even if not all planned features have been implemented yet.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
_Pull requests are encouraged!_
|
||||
Pull requests are welcome! Here's how to make them go smoothly:
|
||||
|
||||
* Start by adding a feature request to get feedback and see how your idea is received.
|
||||
* If your pull request solves an existing issue, but it's different in some way, _please create a new issue_ and make sure to discuss it with the core contributors. Otherwise you risk your hard work being rejected.
|
||||
* Do not change the **./dist/** folder, we do this when releasing
|
||||
* _Please add tests_ for your work. Tests are invoked using `npm test` command. It will run both node.js tests and browser (Headless Chrome) tests.
|
||||
* **For new features, start with a feature request** to get feedback and see how your idea is received.
|
||||
* **If your PR solves an existing issue**, but approaches it differently, please create a new issue first and discuss it with core contributors. This helps avoid wasted effort.
|
||||
* **Don't modify the `./dist/` folder**—we handle that during releases.
|
||||
* **Please add tests** for your work. Run tests using `npm test`, which runs both Node.js and browser (Headless Chrome) tests.
|
||||
|
||||
### Coding Standards
|
||||
|
||||
* Always use spaces, never tabs
|
||||
* End lines in semi-colons.
|
||||
* Loosely aim towards eslint standards
|
||||
|
||||
* End lines with semicolons
|
||||
* Aim for ESLint standards
|
||||
|
||||
## Developing
|
||||
If you want to take an issue just add a small comment saying you are having a go at something, so we don't get duplication.
|
||||
|
||||
If you want to work on an issue, add a comment saying you're taking it on—this helps prevent duplicate work.
|
||||
|
||||
Learn more about [developing Less.js](http://lesscss.org/usage/#developing-less).
|
||||
|
||||
## Releases
|
||||
|
||||
Releases are managed using PNPM. Instructions TBD
|
||||
Releases are fully automated! Here's how it works:
|
||||
|
||||
### Automated Publishing
|
||||
|
||||
When code is pushed to specific branches, GitHub Actions automatically:
|
||||
|
||||
1. **Runs tests and builds** the project
|
||||
2. **Bumps the version** automatically
|
||||
3. **Publishes to npm** with the appropriate tag
|
||||
4. **Creates a GitHub release**
|
||||
|
||||
### Release Branches
|
||||
|
||||
- **`master` branch**:
|
||||
- Publishes regular releases (e.g., `4.4.2` → `4.4.3`)
|
||||
- Published to npm with `latest` tag
|
||||
- Creates regular GitHub releases
|
||||
- Version auto-increments by patch unless explicitly set
|
||||
|
||||
- **`alpha` branch**:
|
||||
- Publishes alpha releases (e.g., `5.0.0-alpha.1` → `5.0.0-alpha.2`)
|
||||
- Published to npm with `alpha` tag
|
||||
- Creates GitHub pre-releases
|
||||
- Version auto-increments alpha suffix
|
||||
|
||||
### How to Publish
|
||||
|
||||
**For regular releases:**
|
||||
1. Update version in `packages/less/package.json` (or let it auto-increment)
|
||||
2. Commit and push to `master`
|
||||
3. The workflow automatically publishes if the version changed
|
||||
|
||||
**For alpha releases:**
|
||||
1. Make your changes on the `alpha` branch
|
||||
2. Commit and push
|
||||
3. The workflow automatically increments the alpha version and publishes
|
||||
|
||||
### Version Override
|
||||
|
||||
You can override auto-increment by including a version in your commit message:
|
||||
|
||||
```
|
||||
feat: new feature
|
||||
|
||||
version: 4.5.0
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
We use npm's [trusted publishing](https://docs.npmjs.com/trusted-publishers) with OIDC authentication. This means:
|
||||
- No long-lived tokens needed
|
||||
- Automatic provenance generation
|
||||
- Enhanced security through short-lived, workflow-specific credentials
|
||||
|
||||
The publishing workflow (`.github/workflows/publish.yml`) handles both release types automatically.
|
||||
|
||||
### Important Notes
|
||||
|
||||
- Publishing only works from `master` or `alpha` branches
|
||||
- Alpha versions must contain `-alpha.` and are published to the `alpha` tag
|
||||
- Regular versions are published to the `latest` tag
|
||||
- Alpha branch must be up-to-date with master before publishing
|
||||
- Alpha base version must be >= master version (semver)
|
||||
|
||||
### Merging Master into Alpha
|
||||
|
||||
When merging `master` into `alpha`, the version in `package.json` might be overwritten. We have two layers of protection:
|
||||
|
||||
1. **Post-merge git hook** (automatic): Automatically restores alpha versions after merges
|
||||
- Installed automatically via husky when you run `pnpm install`
|
||||
- Runs automatically after `git merge`
|
||||
- Restores and increments the alpha version if it was overwritten
|
||||
- Prompts you to commit the restored version
|
||||
|
||||
2. **Publishing script detection** (safety net): The publishing script also detects overwritten versions
|
||||
- Searches git history for the last alpha version
|
||||
- Restores and increments it (e.g., if it was `5.0.0-alpha.3`, it becomes `5.0.0-alpha.4`)
|
||||
- Updates all package.json files accordingly
|
||||
|
||||
**Note**: The git hook is managed by husky and installs automatically. The publishing script protection works as a backup even if the hook isn't installed.
|
||||
|
||||
---
|
||||
|
||||
Thank you for contributing to Less.js!
|
||||
|
||||
62
package.json
62
package.json
@@ -1,31 +1,35 @@
|
||||
{
|
||||
"name": "@less/root",
|
||||
"private": true,
|
||||
"version": "4.4.2",
|
||||
"description": "Less monorepo",
|
||||
"homepage": "http://lesscss.org",
|
||||
"scripts": {
|
||||
"publish": "lerna publish from-package --no-private",
|
||||
"changelog": "github-changes -o less -r less.js -a --only-pulls --use-commit-body -m \"(YYYY-MM-DD)\"",
|
||||
"test": "cd packages/less && npm test",
|
||||
"postinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"author": "Alexis Sellier <self@cloudhead.net>",
|
||||
"contributors": [
|
||||
"The Core Less Team"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/less/less.js/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/less/less.js.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "~6.26.1",
|
||||
"github-changes": "^1.1.2",
|
||||
"lerna": "^3.22.1",
|
||||
"npm-run-all": "^4.1.5"
|
||||
}
|
||||
"name": "@less/root",
|
||||
"private": true,
|
||||
"version": "4.5.0",
|
||||
"description": "Less monorepo",
|
||||
"homepage": "http://lesscss.org",
|
||||
"scripts": {
|
||||
"publish": "node scripts/bump-and-publish.js",
|
||||
"publish:dry-run": "DRY_RUN=true node scripts/bump-and-publish.js",
|
||||
"prepare": "husky",
|
||||
"changelog": "github-changes -o less -r less.js -a --only-pulls --use-commit-body -m \"(YYYY-MM-DD)\"",
|
||||
"test": "cd packages/less && npm test",
|
||||
"postinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"author": "Alexis Sellier <self@cloudhead.net>",
|
||||
"contributors": [
|
||||
"The Core Less Team"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/less/less.js/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/less/less.js.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "~6.26.1",
|
||||
"github-changes": "^1.1.2",
|
||||
"husky": "~9.1.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "less",
|
||||
"version": "4.4.2",
|
||||
"version": "4.5.0",
|
||||
"description": "Leaner CSS",
|
||||
"homepage": "http://lesscss.org",
|
||||
"author": {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@less/test-data",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "4.4.2",
|
||||
"description": "Less files and CSS results",
|
||||
"author": "Alexis Sellier <self@cloudhead.net>",
|
||||
"contributors": [
|
||||
"The Core Less Team"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"gitHead": "1df9072ee9ebdadc791bf35dfb1dbc3ef9f1948f"
|
||||
"name": "@less/test-data",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "4.5.0",
|
||||
"description": "Less files and CSS results",
|
||||
"author": "Alexis Sellier <self@cloudhead.net>",
|
||||
"contributors": [
|
||||
"The Core Less Team"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"gitHead": "1df9072ee9ebdadc791bf35dfb1dbc3ef9f1948f"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// https://github.com/less/less.js/issues/3541
|
||||
@import (reference) url(https://cdn.jsdelivr.net/npm/@less/test-data/less/_main/selectors.less);
|
||||
@import (reference) url("https://cdn.jsdelivr.net/npm/@less/test-data/less/_main/media.less");
|
||||
@import (reference) url("https://cdn.jsdelivr.net/npm/@less/test-data/less/_main/empty.less?arg");
|
||||
@import (reference) url(https://cdn.jsdelivr.net/npm/@less/test-data/tests-unit/selectors/selectors.less);
|
||||
@import (reference) url("https://cdn.jsdelivr.net/npm/@less/test-data/tests-unit/media/media.less");
|
||||
@import (reference) url("https://cdn.jsdelivr.net/npm/@less/test-data/tests-unit/empty/empty.less?arg");
|
||||
|
||||
.test {
|
||||
color: @var;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@less/test-import-module",
|
||||
"private": true,
|
||||
"version": "4.0.0",
|
||||
"description": "Less files to be included in node_modules directory for testing import from node_modules",
|
||||
"author": "Alexis Sellier <self@cloudhead.net>",
|
||||
"contributors": [
|
||||
"The Core Less Team"
|
||||
],
|
||||
"license": "Apache-2.0"
|
||||
"name": "@less/test-import-module",
|
||||
"private": true,
|
||||
"version": "4.5.0",
|
||||
"description": "Less files to be included in node_modules directory for testing import from node_modules",
|
||||
"author": "Alexis Sellier <self@cloudhead.net>",
|
||||
"contributors": [
|
||||
"The Core Less Team"
|
||||
],
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
11664
pnpm-lock.yaml
generated
11664
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
467
scripts/bump-and-publish.js
Executable file
467
scripts/bump-and-publish.js
Executable file
@@ -0,0 +1,467 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Version bumping and publishing script for Less.js monorepo
|
||||
*
|
||||
* This script:
|
||||
* 1. Determines the next version (patch increment or explicit)
|
||||
* 2. Updates all package.json files to the same version
|
||||
* 3. Creates a git tag
|
||||
* 4. Commits version changes
|
||||
* 5. Publishes all packages to NPM
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const semver = require('semver');
|
||||
|
||||
const ROOT_DIR = path.resolve(__dirname, '..');
|
||||
const PACKAGES_DIR = path.join(ROOT_DIR, 'packages');
|
||||
|
||||
// Get all package.json files
|
||||
function getPackageFiles() {
|
||||
const packages = [];
|
||||
|
||||
// Root package.json
|
||||
const rootPkgPath = path.join(ROOT_DIR, 'package.json');
|
||||
if (fs.existsSync(rootPkgPath)) {
|
||||
packages.push(rootPkgPath);
|
||||
}
|
||||
|
||||
// Package directories
|
||||
const packageDirs = fs.readdirSync(PACKAGES_DIR, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => path.join(PACKAGES_DIR, dirent.name));
|
||||
|
||||
for (const pkgDir of packageDirs) {
|
||||
const pkgPath = path.join(pkgDir, 'package.json');
|
||||
if (fs.existsSync(pkgPath)) {
|
||||
packages.push(pkgPath);
|
||||
}
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
// Read package.json
|
||||
function readPackage(pkgPath) {
|
||||
return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
}
|
||||
|
||||
// Write package.json
|
||||
function writePackage(pkgPath, pkg) {
|
||||
const content = JSON.stringify(pkg, null, '\t') + '\n';
|
||||
fs.writeFileSync(pkgPath, content, 'utf8');
|
||||
}
|
||||
|
||||
// Parse version string
|
||||
function parseVersion(version) {
|
||||
const parts = version.split('.');
|
||||
return {
|
||||
major: parseInt(parts[0], 10),
|
||||
minor: parseInt(parts[1], 10),
|
||||
patch: parseInt(parts[2], 10),
|
||||
prerelease: parts[3] || null
|
||||
};
|
||||
}
|
||||
|
||||
// Increment patch version
|
||||
function incrementPatch(version) {
|
||||
const parsed = parseVersion(version);
|
||||
return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
|
||||
}
|
||||
|
||||
// Get current version from main package
|
||||
function getCurrentVersion() {
|
||||
const lessPkgPath = path.join(PACKAGES_DIR, 'less', 'package.json');
|
||||
const pkg = readPackage(lessPkgPath);
|
||||
return pkg.version;
|
||||
}
|
||||
|
||||
// Check if version was explicitly set (via environment variable or git commit message)
|
||||
function getExplicitVersion() {
|
||||
// Check for explicit version in environment
|
||||
if (process.env.EXPLICIT_VERSION) {
|
||||
return process.env.EXPLICIT_VERSION;
|
||||
}
|
||||
|
||||
// Check git commit message for version bump instruction
|
||||
try {
|
||||
const commitMsg = execSync('git log -1 --pretty=%B', { encoding: 'utf8' });
|
||||
const versionMatch = commitMsg.match(/version[:\s]+v?(\d+\.\d+\.\d+(?:-[a-z]+\.\d+)?)/i);
|
||||
if (versionMatch) {
|
||||
return versionMatch[1];
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update all package.json files with new version
|
||||
function updateAllVersions(newVersion) {
|
||||
const packageFiles = getPackageFiles();
|
||||
const updated = [];
|
||||
|
||||
for (const pkgPath of packageFiles) {
|
||||
const pkg = readPackage(pkgPath);
|
||||
if (pkg.version) {
|
||||
pkg.version = newVersion;
|
||||
writePackage(pkgPath, pkg);
|
||||
updated.push(pkgPath);
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
// Get packages that should be published (not private)
|
||||
function getPublishablePackages() {
|
||||
const packageFiles = getPackageFiles();
|
||||
const publishable = [];
|
||||
|
||||
for (const pkgPath of packageFiles) {
|
||||
const pkg = readPackage(pkgPath);
|
||||
// Skip root package and private packages
|
||||
if (!pkg.private && pkg.name && pkg.name !== '@less/root') {
|
||||
publishable.push({
|
||||
path: pkgPath,
|
||||
name: pkg.name,
|
||||
dir: path.dirname(pkgPath)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return publishable;
|
||||
}
|
||||
|
||||
// Main function
|
||||
function main() {
|
||||
const dryRun = process.env.DRY_RUN === 'true' || process.argv.includes('--dry-run');
|
||||
const branch = process.env.GITHUB_REF_NAME || execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
|
||||
const isAlpha = branch === 'alpha';
|
||||
const isMaster = branch === 'master';
|
||||
|
||||
if (dryRun) {
|
||||
console.log(`🧪 DRY RUN MODE - No changes will be committed or published\n`);
|
||||
}
|
||||
|
||||
// Enforce branch restrictions - only allow publishing from master or alpha branches
|
||||
if (!isMaster && !isAlpha) {
|
||||
console.error(`❌ ERROR: Publishing is only allowed from 'master' or 'alpha' branches`);
|
||||
console.error(` Current branch: ${branch}`);
|
||||
console.error(` Please switch to 'master' or 'alpha' branch before publishing`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`🚀 Starting publish process for branch: ${branch}`);
|
||||
|
||||
// Get current version
|
||||
let currentVersion = getCurrentVersion();
|
||||
console.log(`📦 Current version: ${currentVersion}`);
|
||||
|
||||
// Protection: If on alpha branch and version was overwritten by a merge from master
|
||||
if (isAlpha && !currentVersion.includes('-alpha.')) {
|
||||
console.log(`\n⚠️ WARNING: Alpha branch version (${currentVersion}) doesn't contain '-alpha.'`);
|
||||
console.log(` This likely happened due to merging master into alpha.`);
|
||||
console.log(` Attempting to restore alpha version...`);
|
||||
|
||||
// Try to find the last alpha version from alpha branch history
|
||||
let restoredVersion = null;
|
||||
try {
|
||||
// Get recent commits on alpha that modified package.json
|
||||
const commits = execSync(
|
||||
'git log alpha --oneline -20 -- packages/less/package.json',
|
||||
{ cwd: ROOT_DIR, encoding: 'utf8' }
|
||||
).trim().split('\n');
|
||||
|
||||
// Search through commits to find the last alpha version
|
||||
for (const commitLine of commits) {
|
||||
const commitHash = commitLine.split(' ')[0];
|
||||
try {
|
||||
const pkgContent = execSync(
|
||||
`git show ${commitHash}:packages/less/package.json 2>/dev/null`,
|
||||
{ cwd: ROOT_DIR, encoding: 'utf8' }
|
||||
);
|
||||
const pkg = JSON.parse(pkgContent);
|
||||
if (pkg.version && pkg.version.includes('-alpha.')) {
|
||||
restoredVersion = pkg.version;
|
||||
console.log(` Found previous alpha version in commit ${commitHash}: ${restoredVersion}`);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// Continue to next commit
|
||||
}
|
||||
}
|
||||
|
||||
if (restoredVersion) {
|
||||
// Increment the alpha number from the restored version
|
||||
const alphaMatch = restoredVersion.match(/^(\d+\.\d+\.\d+)-alpha\.(\d+)$/);
|
||||
if (alphaMatch) {
|
||||
const alphaNum = parseInt(alphaMatch[2], 10);
|
||||
const newAlphaVersion = `${alphaMatch[1]}-alpha.${alphaNum + 1}`;
|
||||
console.log(` Restoring and incrementing to: ${newAlphaVersion}`);
|
||||
currentVersion = newAlphaVersion;
|
||||
updateAllVersions(newAlphaVersion);
|
||||
} else {
|
||||
console.log(` Restoring to: ${restoredVersion}`);
|
||||
currentVersion = restoredVersion;
|
||||
updateAllVersions(restoredVersion);
|
||||
}
|
||||
} else {
|
||||
// No previous alpha version found, create one from current version
|
||||
const parsed = parseVersion(currentVersion);
|
||||
const nextMajor = parsed.major + 1;
|
||||
const newAlphaVersion = `${nextMajor}.0.0-alpha.1`;
|
||||
console.log(` No previous alpha version found. Creating new: ${newAlphaVersion}`);
|
||||
currentVersion = newAlphaVersion;
|
||||
updateAllVersions(newAlphaVersion);
|
||||
}
|
||||
} catch (e) {
|
||||
// If we can't find previous version, create a new alpha version
|
||||
const parsed = parseVersion(currentVersion);
|
||||
const nextMajor = parsed.major + 1;
|
||||
const newAlphaVersion = `${nextMajor}.0.0-alpha.1`;
|
||||
console.log(` Could not find previous alpha version. Creating: ${newAlphaVersion}`);
|
||||
currentVersion = newAlphaVersion;
|
||||
updateAllVersions(newAlphaVersion);
|
||||
}
|
||||
|
||||
console.log(`✅ Restored/created alpha version: ${currentVersion}\n`);
|
||||
}
|
||||
|
||||
// Determine next version
|
||||
const explicitVersion = getExplicitVersion();
|
||||
let nextVersion;
|
||||
|
||||
if (explicitVersion) {
|
||||
nextVersion = explicitVersion;
|
||||
console.log(`✨ Using explicit version: ${nextVersion}`);
|
||||
} else if (isAlpha) {
|
||||
// For alpha branch, use alpha versions
|
||||
const parsed = parseVersion(currentVersion);
|
||||
if (parsed.prerelease) {
|
||||
// Already an alpha, increment alpha number
|
||||
const alphaMatch = currentVersion.match(/^(\d+\.\d+\.\d+)-alpha\.(\d+)$/);
|
||||
if (alphaMatch) {
|
||||
const alphaNum = parseInt(alphaMatch[2], 10);
|
||||
nextVersion = `${alphaMatch[1]}-alpha.${alphaNum + 1}`;
|
||||
} else {
|
||||
// Other prerelease format, determine base version and start alpha.1
|
||||
const baseVersion = `${parsed.major}.${parsed.minor}.${parsed.patch}`;
|
||||
nextVersion = `${baseVersion}-alpha.1`;
|
||||
}
|
||||
} else {
|
||||
// Not an alpha version, determine next major and start alpha.1
|
||||
const parsed = parseVersion(currentVersion);
|
||||
const nextMajor = parsed.major + 1;
|
||||
nextVersion = `${nextMajor}.0.0-alpha.1`;
|
||||
}
|
||||
console.log(`🔢 Auto-incrementing alpha version: ${nextVersion}`);
|
||||
} else {
|
||||
// For master, increment patch
|
||||
nextVersion = incrementPatch(currentVersion);
|
||||
console.log(`🔢 Auto-incrementing patch version: ${nextVersion}`);
|
||||
}
|
||||
|
||||
// Update all package.json files
|
||||
console.log(`📝 Updating all package.json files to version ${nextVersion}...`);
|
||||
const updated = updateAllVersions(nextVersion);
|
||||
console.log(`✅ Updated ${updated.length} package.json files`);
|
||||
|
||||
// Get publishable packages
|
||||
const publishable = getPublishablePackages();
|
||||
console.log(`📦 Found ${publishable.length} publishable packages:`);
|
||||
publishable.forEach(pkg => console.log(` - ${pkg.name}`));
|
||||
|
||||
// Stage changes
|
||||
console.log(`📌 Staging version changes...`);
|
||||
if (!dryRun) {
|
||||
execSync('git add package.json packages/*/package.json', { cwd: ROOT_DIR, stdio: 'inherit' });
|
||||
} else {
|
||||
console.log(` [DRY RUN] Would stage: package.json packages/*/package.json`);
|
||||
}
|
||||
|
||||
// Commit
|
||||
console.log(`💾 Committing version bump...`);
|
||||
if (!dryRun) {
|
||||
try {
|
||||
execSync(`git commit -m "chore: bump version to ${nextVersion}"`, {
|
||||
cwd: ROOT_DIR,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
} catch (e) {
|
||||
// Commit might fail if nothing changed, that's okay
|
||||
console.log(`⚠️ Commit skipped (no changes or already committed)`);
|
||||
}
|
||||
} else {
|
||||
console.log(` [DRY RUN] Would commit: "chore: bump version to ${nextVersion}"`);
|
||||
}
|
||||
|
||||
// Create tag
|
||||
const tagName = `v${nextVersion}`;
|
||||
console.log(`🏷️ Creating git tag: ${tagName}...`);
|
||||
if (!dryRun) {
|
||||
try {
|
||||
execSync(`git tag -a "${tagName}" -m "Release ${tagName}"`, {
|
||||
cwd: ROOT_DIR,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(`⚠️ Tag might already exist, continuing...`);
|
||||
}
|
||||
} else {
|
||||
console.log(` [DRY RUN] Would create tag: ${tagName}`);
|
||||
}
|
||||
|
||||
// Push commit and tag
|
||||
console.log(`📤 Pushing to ${branch}...`);
|
||||
if (!dryRun) {
|
||||
try {
|
||||
execSync(`git push origin ${branch}`, { cwd: ROOT_DIR, stdio: 'inherit' });
|
||||
execSync(`git push origin "${tagName}"`, { cwd: ROOT_DIR, stdio: 'inherit' });
|
||||
} catch (e) {
|
||||
console.log(`⚠️ Push failed, but continuing with publish...`);
|
||||
}
|
||||
} else {
|
||||
console.log(` [DRY RUN] Would push to: origin ${branch}`);
|
||||
console.log(` [DRY RUN] Would push tag: origin ${tagName}`);
|
||||
}
|
||||
|
||||
// Validate alpha branch requirements
|
||||
if (isAlpha) {
|
||||
console.log(`\n🔍 Validating alpha branch requirements...`);
|
||||
|
||||
// Validation 1: Version must contain 'alpha'
|
||||
if (!nextVersion.includes('-alpha.')) {
|
||||
console.error(`❌ ERROR: Alpha branch version must contain '-alpha.'`);
|
||||
console.error(` Generated version: ${nextVersion}`);
|
||||
console.error(` Expected format: X.Y.Z-alpha.N`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`✅ Version contains 'alpha' suffix: ${nextVersion}`);
|
||||
|
||||
// Validation 2: Must publish with 'alpha' tag
|
||||
// (This is enforced in the code below, but we log it for clarity)
|
||||
console.log(`✅ Will publish with 'alpha' tag (enforced)`);
|
||||
|
||||
// Validation 3: Check if alpha is behind master
|
||||
try {
|
||||
execSync('git fetch origin master:master 2>/dev/null || true', { cwd: ROOT_DIR });
|
||||
const masterCommits = execSync('git rev-list --count alpha..master 2>/dev/null || echo "0"', {
|
||||
cwd: ROOT_DIR,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
|
||||
if (parseInt(masterCommits, 10) > 0) {
|
||||
console.error(`❌ ERROR: Alpha branch is behind master by ${masterCommits} commit(s)`);
|
||||
console.error(` Alpha branch must include all commits from master before publishing`);
|
||||
console.error(` Please merge master into alpha first`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`✅ Alpha branch is up to date with master`);
|
||||
} catch (e) {
|
||||
console.log(`⚠️ Could not verify master sync status, continuing...`);
|
||||
}
|
||||
|
||||
// Validation 4: Alpha base version must be >= master version
|
||||
try {
|
||||
const masterVersionStr = execSync('git show master:packages/less/package.json 2>/dev/null', {
|
||||
cwd: ROOT_DIR,
|
||||
encoding: 'utf8'
|
||||
});
|
||||
const masterPkg = JSON.parse(masterVersionStr);
|
||||
const masterVersion = masterPkg.version;
|
||||
|
||||
// Extract base version from alpha version (remove -alpha.X)
|
||||
const alphaBase = nextVersion.replace(/-alpha\.\d+$/, '');
|
||||
|
||||
// Semver comparison using semver library
|
||||
const isGreaterOrEqual = semver.gte(alphaBase, masterVersion);
|
||||
|
||||
if (!isGreaterOrEqual) {
|
||||
console.error(`❌ ERROR: Alpha base version (${alphaBase}) is lower than master version (${masterVersion})`);
|
||||
console.error(` According to semver, alpha base version must be >= master version`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`✅ Alpha base version (${alphaBase}) is >= master version (${masterVersion})`);
|
||||
} catch (e) {
|
||||
console.log(`⚠️ Could not compare with master version, continuing...`);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine NPM tag based on branch and version
|
||||
const npmTag = isAlpha ? 'alpha' : 'latest';
|
||||
const isAlphaVersion = nextVersion.includes('-alpha.');
|
||||
|
||||
// Validation: Alpha versions must use 'alpha' tag, non-alpha versions must use 'latest' tag
|
||||
if (isAlphaVersion && npmTag !== 'alpha') {
|
||||
console.error(`❌ ERROR: Alpha version (${nextVersion}) must be published with 'alpha' tag, not '${npmTag}'`);
|
||||
console.error(` Alpha versions cannot be published to 'latest' tag`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!isAlphaVersion && npmTag === 'alpha') {
|
||||
console.error(`❌ ERROR: Non-alpha version (${nextVersion}) cannot be published with 'alpha' tag`);
|
||||
console.error(` Only versions containing '-alpha.' can be published to 'alpha' tag`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Enforce alpha tag for alpha branch
|
||||
if (isAlpha && npmTag !== 'alpha') {
|
||||
console.error(`❌ ERROR: Alpha branch must publish with 'alpha' tag, not '${npmTag}'`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\n📦 Publishing packages to NPM with tag: ${npmTag}...`);
|
||||
|
||||
for (const pkg of publishable) {
|
||||
console.log(`\n📤 Publishing ${pkg.name}...`);
|
||||
if (dryRun) {
|
||||
console.log(` [DRY RUN] Would publish: ${pkg.name}@${nextVersion} with tag: ${npmTag}`);
|
||||
console.log(` [DRY RUN] Command: npm publish --tag ${npmTag}`);
|
||||
} else {
|
||||
try {
|
||||
execSync(`npm publish --tag ${npmTag}`, {
|
||||
cwd: pkg.dir,
|
||||
stdio: 'inherit',
|
||||
env: { ...process.env, NODE_AUTH_TOKEN: process.env.NPM_TOKEN }
|
||||
});
|
||||
console.log(`✅ Successfully published ${pkg.name}@${nextVersion}`);
|
||||
} catch (e) {
|
||||
console.error(`❌ Failed to publish ${pkg.name}:`, e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log(`\n🧪 DRY RUN COMPLETE - No changes were made`);
|
||||
console.log(` Would publish version: ${nextVersion}`);
|
||||
console.log(` Would create tag: ${tagName}`);
|
||||
console.log(` Would use NPM tag: ${npmTag}`);
|
||||
} else {
|
||||
console.log(`\n🎉 Successfully published all packages!`);
|
||||
console.log(` Version: ${nextVersion}`);
|
||||
console.log(` Tag: ${tagName}`);
|
||||
console.log(` NPM Tag: ${npmTag}`);
|
||||
}
|
||||
|
||||
// Output version for GitHub Actions
|
||||
if (process.env.GITHUB_OUTPUT) {
|
||||
const fs = require('fs');
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${nextVersion}\n`);
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=${tagName}\n`);
|
||||
}
|
||||
|
||||
return { version: nextVersion, tag: tagName };
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { main };
|
||||
153
scripts/post-merge-version-fix.js
Executable file
153
scripts/post-merge-version-fix.js
Executable file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Post-merge hook to preserve alpha versions when merging master into alpha branch
|
||||
*
|
||||
* This script runs after a merge and checks if:
|
||||
* 1. We're on the alpha branch
|
||||
* 2. The version in package.json doesn't contain '-alpha.' (was overwritten)
|
||||
* 3. If so, restores the previous alpha version from git history
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const ROOT_DIR = path.resolve(__dirname, '..');
|
||||
const LESS_PKG_PATH = path.join(ROOT_DIR, 'packages', 'less', 'package.json');
|
||||
|
||||
// Get current branch
|
||||
function getCurrentBranch() {
|
||||
try {
|
||||
return execSync('git rev-parse --abbrev-ref HEAD', {
|
||||
cwd: ROOT_DIR,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Read package.json version
|
||||
function getVersion(pkgPath) {
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
return pkg.version;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update version in package.json
|
||||
function updateVersion(pkgPath, newVersion) {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
pkg.version = newVersion;
|
||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, '\t') + '\n', 'utf8');
|
||||
}
|
||||
|
||||
// Find last alpha version from git history
|
||||
function findLastAlphaVersion() {
|
||||
try {
|
||||
// Get recent commits on alpha that modified package.json
|
||||
const commits = execSync(
|
||||
'git log alpha --oneline -20 -- packages/less/package.json',
|
||||
{ cwd: ROOT_DIR, encoding: 'utf8' }
|
||||
).trim().split('\n');
|
||||
|
||||
// Search through commits to find the last alpha version
|
||||
for (const commitLine of commits) {
|
||||
const commitHash = commitLine.split(' ')[0];
|
||||
try {
|
||||
const pkgContent = execSync(
|
||||
`git show ${commitHash}:packages/less/package.json 2>/dev/null`,
|
||||
{ cwd: ROOT_DIR, encoding: 'utf8' }
|
||||
);
|
||||
const pkg = JSON.parse(pkgContent);
|
||||
if (pkg.version && pkg.version.includes('-alpha.')) {
|
||||
return pkg.version;
|
||||
}
|
||||
} catch (e) {
|
||||
// Continue to next commit
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update all package.json files with new version
|
||||
function updateAllVersions(newVersion) {
|
||||
const packageFiles = [
|
||||
path.join(ROOT_DIR, 'package.json'),
|
||||
path.join(ROOT_DIR, 'packages', 'less', 'package.json'),
|
||||
path.join(ROOT_DIR, 'packages', 'test-data', 'package.json')
|
||||
];
|
||||
|
||||
for (const pkgPath of packageFiles) {
|
||||
if (fs.existsSync(pkgPath)) {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
if (pkg.version) {
|
||||
pkg.version = newVersion;
|
||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, '\t') + '\n', 'utf8');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main function
|
||||
function main() {
|
||||
const branch = getCurrentBranch();
|
||||
|
||||
// Only run on alpha branch
|
||||
if (branch !== 'alpha') {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentVersion = getVersion(LESS_PKG_PATH);
|
||||
|
||||
if (!currentVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if version was overwritten (doesn't contain -alpha.)
|
||||
if (!currentVersion.includes('-alpha.')) {
|
||||
console.log(`\n⚠️ Post-merge: Alpha version was overwritten (${currentVersion})`);
|
||||
console.log(` Attempting to restore alpha version...`);
|
||||
|
||||
const lastAlphaVersion = findLastAlphaVersion();
|
||||
|
||||
if (lastAlphaVersion) {
|
||||
// Increment the alpha number
|
||||
const alphaMatch = lastAlphaVersion.match(/^(\d+\.\d+\.\d+)-alpha\.(\d+)$/);
|
||||
if (alphaMatch) {
|
||||
const alphaNum = parseInt(alphaMatch[2], 10);
|
||||
const newAlphaVersion = `${alphaMatch[1]}-alpha.${alphaNum + 1}`;
|
||||
console.log(` Restoring and incrementing: ${lastAlphaVersion} → ${newAlphaVersion}`);
|
||||
updateAllVersions(newAlphaVersion);
|
||||
console.log(`✅ Restored alpha version: ${newAlphaVersion}`);
|
||||
console.log(` Please commit this change: git add package.json packages/*/package.json && git commit -m "chore: restore alpha version after merge"`);
|
||||
} else {
|
||||
console.log(` Restoring to: ${lastAlphaVersion}`);
|
||||
updateAllVersions(lastAlphaVersion);
|
||||
console.log(`✅ Restored alpha version: ${lastAlphaVersion}`);
|
||||
console.log(` Please commit this change: git add package.json packages/*/package.json && git commit -m "chore: restore alpha version after merge"`);
|
||||
}
|
||||
} else {
|
||||
// No previous alpha version found, create one
|
||||
const parts = currentVersion.split('.');
|
||||
const nextMajor = parseInt(parts[0], 10) + 1;
|
||||
const newAlphaVersion = `${nextMajor}.0.0-alpha.1`;
|
||||
console.log(` No previous alpha version found. Creating: ${newAlphaVersion}`);
|
||||
updateAllVersions(newAlphaVersion);
|
||||
console.log(`✅ Created new alpha version: ${newAlphaVersion}`);
|
||||
console.log(` Please commit this change: git add package.json packages/*/package.json && git commit -m "chore: restore alpha version after merge"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { main };
|
||||
Reference in New Issue
Block a user