Files
orbit/scripts/bump-version.mjs
Gabriel Donadel Dall'Agnol a44a7c0fd6 Add release automation scripts (#327)
* Add release automation scripts

* Add changelog entry
2026-03-25 01:41:01 +01:00

160 lines
4.5 KiB
JavaScript

#!/usr/bin/env node
import { readFileSync, writeFileSync } from 'fs';
import { MENU_BAR_PKG, ELECTRON_PKG, INFO_PLIST, CHANGELOG } from './constants.mjs';
function parseArgs() {
const args = process.argv.slice(2);
const idx = args.indexOf('--version');
if (idx === -1 || !args[idx + 1]) {
console.error('Usage: bump-version.mjs --version <X.Y.Z>');
process.exit(1);
}
const version = args[idx + 1];
if (!/^\d+\.\d+\.\d+$/.test(version)) {
console.error(`Invalid semver version: ${version}`);
process.exit(1);
}
return version;
}
function updatePackageJson(filePath, version) {
const content = JSON.parse(readFileSync(filePath, 'utf8'));
const oldVersion = content.version;
content.version = version;
writeFileSync(filePath, JSON.stringify(content, null, 2) + '\n');
return oldVersion;
}
function updateInfoPlist(filePath, version) {
let content = readFileSync(filePath, 'utf8');
// Update CFBundleShortVersionString
content = content.replace(
/(<key>CFBundleShortVersionString<\/key>\s*<string>)(.*?)(<\/string>)/,
`$1${version}$3`
);
// Read and increment CFBundleVersion
const buildMatch = content.match(
/<key>CFBundleVersion<\/key>\s*<string>(\d+)<\/string>/
);
if (!buildMatch) {
console.error('Could not find CFBundleVersion in Info.plist');
process.exit(1);
}
const oldBuild = parseInt(buildMatch[1], 10);
const newBuild = oldBuild + 1;
content = content.replace(
/(<key>CFBundleVersion<\/key>\s*<string>)\d+(<\/string>)/,
`$1${newBuild}$2`
);
writeFileSync(filePath, content);
return { oldBuild, newBuild };
}
function updateChangelog(filePath, version) {
const content = readFileSync(filePath, 'utf8');
const today = new Date().toISOString().split('T')[0];
const unpublishedHeader = '## Unpublished';
const unpublishedIdx = content.indexOf(unpublishedHeader);
if (unpublishedIdx === -1) {
console.error('Could not find "## Unpublished" section in CHANGELOG.md');
process.exit(1);
}
// Find the next ## heading after Unpublished
const afterUnpublished = content.indexOf('\n## ', unpublishedIdx + unpublishedHeader.length);
const unpublishedContent =
afterUnpublished === -1
? content.slice(unpublishedIdx + unpublishedHeader.length)
: content.slice(unpublishedIdx + unpublishedHeader.length, afterUnpublished);
const rest = afterUnpublished === -1 ? '' : content.slice(afterUnpublished);
const before = content.slice(0, unpublishedIdx);
// Strip empty category sections from the release content
// A section is empty if it has a ### heading followed by either another ### heading, a ## heading, or EOF
const strippedContent = stripEmptySections(unpublishedContent);
if (!strippedContent.trim()) {
console.error('No changes found in the Unpublished section of CHANGELOG.md');
process.exit(1);
}
const freshUnpublished = `## Unpublished
### \u{1F6E0} Breaking changes
### \u{1F389} New features
### \u{1F41B} Bug fixes
### \u{1F4A1} Others
`;
const newContent =
before +
freshUnpublished +
'\n' +
`## ${version} \u{2014} ${today}` +
strippedContent +
rest;
writeFileSync(filePath, newContent);
}
function stripEmptySections(text) {
const lines = text.split('\n');
const result = [];
let i = 0;
while (i < lines.length) {
if (lines[i].startsWith('### ')) {
// Look ahead to see if this section has content
const sectionLines = [lines[i]];
let j = i + 1;
// Collect blank lines
while (j < lines.length && lines[j].trim() === '') {
sectionLines.push(lines[j]);
j++;
}
// Check if next non-blank line is another heading or end of content
if (j >= lines.length || lines[j].startsWith('### ') || lines[j].startsWith('## ')) {
// Empty section, skip it
i = j;
} else {
// Section has content, keep it
result.push(...sectionLines);
i = j;
}
} else {
result.push(lines[i]);
i++;
}
}
return result.join('\n');
}
const version = parseArgs();
console.log(`Bumping version to ${version}...\n`);
const oldVersion = updatePackageJson(MENU_BAR_PKG, version);
updatePackageJson(ELECTRON_PKG, version);
console.log(` package.json: ${oldVersion} -> ${version}`);
const { oldBuild, newBuild } = updateInfoPlist(INFO_PLIST, version);
console.log(` Info.plist: ${oldVersion} (${oldBuild}) -> ${version} (${newBuild})`);
updateChangelog(CHANGELOG, version);
console.log(` CHANGELOG.md: Unpublished -> ${version}`);
console.log(`\nBuild number: ${newBuild}`);