Files
euchre_camp/scripts/bump-version.js
T
david 7ee336c51c
Test / unit-tests (push) Has been cancelled
fix: resolve release workflow version bump issues
- Fixed bump-version.js to properly handle --yes flag in CI environments
- Added check to skip commit if no changes to package.json or CHANGELOG.md
- Ensured script exits cleanly after version bump with --yes flag
2026-03-31 23:18:18 -07:00

223 lines
6.6 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Version Bumping Script
*
* Bumps the version based on conventional commits since last release.
* Supports:
* - major: Breaking changes (! or BREAKING CHANGE)
* - minor: Features (feat:)
* - patch: Fixes (fix:) and other changes
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
// Parse command line arguments
const args = process.argv.slice(2);
const forcedVersion = args[0]; // e.g., "0.2.0" or "patch/minor/major"
const skipConfirm = args.includes('--yes') || args.includes('-y'); // Skip confirmation prompt
// Paths
const packageJsonPath = path.join(__dirname, '..', 'package.json');
const changelogPath = path.join(__dirname, '..', 'CHANGELOG.md');
// Load current version from package.json
function getCurrentVersion() {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
return packageJson.version;
}
// Parse version string into components
function parseVersion(version) {
const [major, minor, patch] = version.split('.').map(Number);
return { major, minor, patch };
}
// Format version string
function formatVersion(major, minor, patch) {
return `${major}.${minor}.${patch}`;
}
// Get commits since last tag
function getCommitsSinceLastTag() {
try {
const tags = execSync('git tag --sort=-version:refname', { encoding: 'utf8' }).trim();
const latestTag = tags.split('\n')[0];
if (!latestTag) {
// No tags yet, get all commits
return execSync('git log --oneline --format=%s', { encoding: 'utf8' }).trim().split('\n');
}
const commits = execSync(`git log ${latestTag}..HEAD --oneline --format=%s`, { encoding: 'utf8' }).trim();
return commits ? commits.split('\n') : [];
} catch (error) {
// If no commits since tag or other error, get recent commits
return execSync('git log --oneline --format=%s -n 20', { encoding: 'utf8' }).trim().split('\n');
}
}
// Analyze commits to determine version bump type
function analyzeCommits(commits) {
let bumpType = 'patch'; // default
for (const commit of commits) {
if (!commit) continue;
// Breaking change indicators
if (commit.includes('!') || commit.includes('BREAKING CHANGE')) {
bumpType = 'major';
break; // Major takes precedence
}
// Feature indicators
if (commit.startsWith('feat:') || commit.startsWith('feat(')) {
if (bumpType === 'patch') {
bumpType = 'minor';
}
}
// Fix indicators (already patch by default)
}
return bumpType;
}
// Generate new version based on bump type
function generateNewVersion(currentVersion, bumpType) {
const { major, minor, patch } = parseVersion(currentVersion);
switch (bumpType) {
case 'major':
return formatVersion(major + 1, 0, 0);
case 'minor':
return formatVersion(major, minor + 1, 0);
case 'patch':
default:
return formatVersion(major, minor, patch + 1);
}
}
// Update package.json version
function updatePackageJson(newVersion) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
packageJson.version = newVersion;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
console.log(`✓ Updated package.json to version ${newVersion}`);
}
// Generate changelog entry
function generateChangelogEntry(newVersion, commits, bumpType) {
const date = new Date().toISOString().split('T')[0];
const relevantCommits = commits.filter(commit => {
if (!commit) return false;
// Filter out non-user-facing commits
if (commit.startsWith('chore:') && !commit.includes('BREAKING')) return false;
return true;
});
const changelogEntry = `## [${newVersion}] - ${date}\n\n` +
`### ${bumpType.charAt(0).toUpperCase() + bumpType.slice(1)} Changes\n\n` +
relevantCommits.map(commit => `- ${commit}`).join('\n') +
'\n\n';
return changelogEntry;
}
// Update CHANGELOG.md
function updateChangelog(newVersion, commits, bumpType) {
const changelogEntry = generateChangelogEntry(newVersion, commits, bumpType);
if (fs.existsSync(changelogPath)) {
const content = fs.readFileSync(changelogPath, 'utf8');
const newContent = changelogEntry + content;
fs.writeFileSync(changelogPath, newContent);
} else {
fs.writeFileSync(changelogPath, `# Changelog\n\n${changelogEntry}`);
}
console.log(`✓ Updated CHANGELOG.md`);
}
// Main function
function main() {
console.log('🔄 Version Bumping Tool\n');
const currentVersion = getCurrentVersion();
console.log(`Current version: ${currentVersion}`);
let newVersion;
let bumpType;
if (forcedVersion) {
// Check if it's a bump type or explicit version
if (['patch', 'minor', 'major'].includes(forcedVersion.toLowerCase())) {
bumpType = forcedVersion.toLowerCase();
newVersion = generateNewVersion(currentVersion, bumpType);
console.log(`Forced bump type: ${bumpType}`);
} else {
// Assume it's an explicit version
newVersion = forcedVersion;
bumpType = 'custom';
console.log(`Forced version: ${newVersion}`);
}
} else {
// Analyze commits to determine bump type
const commits = getCommitsSinceLastTag();
console.log(`\nAnalyzing ${commits.length} commits since last release...`);
bumpType = analyzeCommits(commits);
newVersion = generateNewVersion(currentVersion, bumpType);
console.log(`\nDetected bump type: ${bumpType}`);
console.log(`New version: ${currentVersion}${newVersion}`);
}
// Confirm with user (or skip if --yes flag is set)
if (skipConfirm) {
// Update package.json
updatePackageJson(newVersion);
// Update changelog
if (bumpType !== 'custom') {
const commits = getCommitsSinceLastTag();
updateChangelog(newVersion, commits, bumpType);
}
console.log(`\n✅ Version bumped to ${newVersion}`);
process.exit(0);
} else {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question(`\nApply version ${newVersion}? (y/N) `, (answer) => {
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
// Update package.json
updatePackageJson(newVersion);
// Update changelog
if (bumpType !== 'custom') {
const commits = getCommitsSinceLastTag();
updateChangelog(newVersion, commits, bumpType);
}
console.log(`\n✅ Version bumped to ${newVersion}`);
process.exit(0);
} else {
console.log('❌ Version bump cancelled');
process.exit(1);
}
rl.close();
});
}
}
// Run main function
main();