gapmiss_badges/release.mjs
@gapmiss bac3097ff7 fix: update tsconfig, esbuild, and release script
Remove unnecessary baseUrl, use Bundler moduleResolution, fix
indentation in tsconfig. Bump esbuild to 0.28.1. Read minAppVersion
from manifest.json in release script instead of hardcoding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-14 11:25:57 -05:00

332 lines
11 KiB
JavaScript

import { readFileSync, writeFileSync } from 'fs';
import { execSync } from 'child_process';
import path from 'path';
/**
* Version Update and Deployment Automation Script
*
* 1. Update version in package.json
* 2. Synchronize version in manifest.json, versions.json
* 3. Run build
* 4. Create Git commit
* 5. Create Git tag
* 6. Push changes to GitHub (REQUIRES Github CLI: https://cli.github.com)
*/
// Version type definitions
const VERSION_TYPES = {
PATCH: 'patch',
MINOR: 'minor',
MAJOR: 'major'
};
// Default settings
const DEFAULT_VERSION_TYPE = VERSION_TYPES.PATCH;
const RELEASE_FILES = ['main.js', 'manifest.json', 'styles.css'];
const MIN_APP_VERSION = JSON.parse(readFileSync(path.resolve(process.cwd(), 'manifest.json'), 'utf8')).minAppVersion;
// Store original versions for rollback if needed
let originalPackageVersion = '';
let originalManifestVersion = '';
/**
* Updates version value from the version string.
* @param {string} version - Current version (e.g., '0.2.2')
* @param {string} type - Update type ('patch', 'minor', 'major')
* @returns {string} Updated version
*/
const updateVersion = (version, type = DEFAULT_VERSION_TYPE) => {
const [major, minor, patch] = version.split('.').map(Number);
switch (type) {
case VERSION_TYPES.MAJOR:
return `${major + 1}.0.0`;
case VERSION_TYPES.MINOR:
return `${major}.${minor + 1}.0`;
case VERSION_TYPES.PATCH:
default:
return `${major}.${minor}.${patch + 1}`;
}
};
/**
* Gets version information before any changes are made.
* @returns {string} Previous version
*/
const getPreviousVersion = () => {
const manifestPath = path.resolve(process.cwd(), 'manifest.json');
const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8'));
return manifestData.version;
}
/**
* Updates version information in package.json file.
* @param {string} versionType - Version type to update
* @returns {string} New version
*/
const updatePackageVersion = (versionType) => {
const packagePath = path.resolve(process.cwd(), 'package.json');
const packageData = JSON.parse(readFileSync(packagePath, 'utf8'));
const currentVersion = packageData.version;
originalPackageVersion = currentVersion; // Store original version for possible rollback
const newVersion = updateVersion(currentVersion, versionType);
packageData.version = newVersion;
writeFileSync(packagePath, JSON.stringify(packageData, null, '\t') + '\n');
console.log(`📦 Updated package.json version: ${currentVersion}${newVersion}`);
return newVersion;
};
/**
* Updates version information in manifest.json file.
* @param {string} newVersion - Version to update
*/
const updateManifestVersion = (newVersion) => {
const manifestPath = path.resolve(process.cwd(), 'manifest.json');
const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8'));
const currentVersion = manifestData.version;
originalManifestVersion = currentVersion; // Store original version for possible rollback
manifestData.version = newVersion;
writeFileSync(manifestPath, JSON.stringify(manifestData, null, '\t') + '\n');
console.log(`📋 Updated manifest.json version: ${currentVersion}${newVersion}`);
};
/**
* Updates version information in versions.json file.
* @param {string} previousVersion - Previous version
* @param {string} newVersion - Version to update
*/
const updateVersionsVersion = (previousVersion, newVersion, minAppVersion) => {
const versionsPath = path.resolve(process.cwd(), 'versions.json');
const versionsData = JSON.parse(readFileSync(versionsPath, 'utf8'));
const manifestPath = path.resolve(process.cwd(), 'manifest.json');
const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8'));
const currentVersion = manifestData.version;
versionsData[newVersion] = minAppVersion;
writeFileSync(versionsPath, JSON.stringify(versionsData, null, '\t') + '\n');
console.log(`📋 Updated versions.json version: ${previousVersion}${newVersion}`);
};
/**
* Run project build
*/
const buildProject = () => {
try {
console.log('🔨 Starting project build...');
execSync('npm run build', { stdio: 'inherit' });
console.log('✅ Build completed');
return true;
} catch (error) {
console.error('❌ Build failed:', error.message);
return false;
}
};
/**
* Rollback version changes if the release process fails
*/
const rollbackVersions = () => {
if (originalPackageVersion) {
try {
const packagePath = path.resolve(process.cwd(), 'package.json');
const packageData = JSON.parse(readFileSync(packagePath, 'utf8'));
const currentVersion = packageData.version;
packageData.version = originalPackageVersion;
writeFileSync(packagePath, JSON.stringify(packageData, null, '\t') + '\n');
console.log(`♻️ Rolled back package.json version: ${currentVersion}${originalPackageVersion}`);
} catch (error) {
console.error('❌ Failed to rollback package.json version:', error.message);
}
}
if (originalManifestVersion) {
try {
const manifestPath = path.resolve(process.cwd(), 'manifest.json');
const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8'));
const currentVersion = manifestData.version;
const versionsPath = path.resolve(process.cwd(), 'versions.json');
const versionsData = JSON.parse(readFileSync(versionsPath, 'utf8'));
// remove last version from JSON
let keys = Object.keys(versionsData)
delete versionsData[keys[keys.length-1]]
writeFileSync(versionsPath, JSON.stringify(versionsData, null, '\t') + '\n');
console.log(`♻️ Rolled back versions.json version: ${currentVersion}${originalManifestVersion}`);
} catch (error) {
console.error('❌ Failed to rollback versions.json version:', error.message);
}
}
if (originalManifestVersion) {
try {
const manifestPath = path.resolve(process.cwd(), 'manifest.json');
const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8'));
const currentVersion = manifestData.version;
manifestData.version = originalManifestVersion;
writeFileSync(manifestPath, JSON.stringify(manifestData, null, '\t') + '\n');
console.log(`♻️ Rolled back manifest.json version: ${currentVersion}${originalManifestVersion}`);
} catch (error) {
console.error('❌ Failed to rollback manifest.json version:', error.message);
}
}
};
/**
* Create Git commit and tag
* @param {string} version - New version
*/
const createGitCommitAndTag = (version) => {
try {
// Stage changed files
try {
execSync('git add package.json manifest.json versions.json', { stdio: 'inherit' });
} catch (error) {
console.error('❌ Failed to stage package.json, versions.json and manifest.json:', error.message);
return false;
}
// Try to stage release files
try {
execSync(`git add ${RELEASE_FILES.join(' ')}`, { stdio: 'inherit' });
} catch (error) {
console.warn('⚠️ Note: Some release files could not be staged. This may be normal if they are gitignored.');
}
// Create commit
const commitMessage = `chore: release ${version}`;
execSync(`git commit -m "${commitMessage}"`, { stdio: 'inherit' });
console.log(`✅ Commit created: ${commitMessage}`);
// Create tag
const tagName = `${version}`;
execSync(`git tag -a ${tagName} -m "Release ${tagName}"`, { stdio: 'inherit' });
console.log(`🏷️ Tag created: ${tagName}`);
// Push changes
execSync('git push', { stdio: 'inherit' });
execSync('git push --tags', { stdio: 'inherit' });
console.log('🚀 Changes pushed to GitHub');
console.log('📦 GitHub Actions will create the release with artifact attestations.');
return true; // Successfully created commit, tag, and pushed
} catch (error) {
console.error('❌ Error during Git operations:', error.message);
return false; // Failed to complete Git operations
}
};
/**
* Check if Git working tree is clean
* @returns {boolean} Whether the working tree is clean
*/
const isGitWorkingTreeClean = () => {
try {
// Check for uncommitted changes
const output = execSync('git status --porcelain', { encoding: 'utf-8' });
return output.trim() === '';
} catch (error) {
console.error('❌ Error checking Git status:', error.message);
return false;
}
};
/**
* Main function
*/
const main = () => {
let success = true;
let newVersion = '';
try {
// Check for uncommitted changes
if (!isGitWorkingTreeClean()) {
console.error('❌ Cannot proceed with release: You have uncommitted changes.');
console.log('Please commit or stash your changes before running the release script.');
process.exit(1);
}
// Check command line arguments
const args = process.argv.slice(2);
const versionType = args[0] || DEFAULT_VERSION_TYPE;
if (!Object.values(VERSION_TYPES).includes(versionType)) {
console.error(`❌ Invalid version type: ${versionType}`);
console.log(`Valid options: ${Object.values(VERSION_TYPES).join(', ')}`);
process.exit(1);
}
let previousVersion = getPreviousVersion()
// Step 1: Update package.json version
try {
newVersion = updatePackageVersion(versionType);
} catch (error) {
console.error('❌ Failed to update package.json version:', error.message);
success = false;
}
// Step 2: Update manifest.json version
if (success) {
try {
updateManifestVersion(newVersion);
} catch (error) {
console.error('❌ Failed to update manifest.json version:', error.message);
success = false;
}
}
// Step 2.5: Update versions.json version
if (success) {
try {
updateVersionsVersion(previousVersion, newVersion, MIN_APP_VERSION);
} catch (error) {
console.error('❌ Failed to update versions.json version:', error.message);
success = false;
}
}
// Step 3: Build the project
if (success) {
if (!buildProject()) {
console.error('❌ Build process failed.');
success = false;
}
}
// Step 4: Git operations
if (success) {
if (!createGitCommitAndTag(newVersion)) {
console.error('❌ Git operations failed.');
success = false;
}
}
// Check overall success
if (success) {
console.log(`\n🎉 Release ${newVersion} completed successfully!`);
} else {
console.error('❌ Release process failed. Rolling back version changes...');
rollbackVersions();
process.exit(1);
}
} catch (error) {
console.error('❌ Unexpected error during release process:', error.message);
rollbackVersions();
process.exit(1);
}
};
// Run script
main();