release.ts•6.02 kB
#!/usr/bin/env tsx
/**
* Release Script
*
* This script automates the process of creating and publishing releases
* for the current package.
*
* Usage:
* pnpm tsx scripts/release.ts [version-type] [--alpha] [--no-git]
*
* version-type: 'major', 'minor', 'patch', or specific version (default: 'patch')
* --alpha: Create an alpha release
* --no-git: Skip git commit and tag
*/
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
// Parse command line arguments
const args = process.argv.slice(2);
const versionBumpArg = args.find(arg => !arg.startsWith('--')) || 'patch';
const isAlpha = args.includes('--alpha');
const skipGit = args.includes('--no-git');
const rootPath = path.resolve('.');
function run(command: string, cwd: string) {
console.log(`Executing: ${command} in ${cwd}`);
execSync(command, { stdio: 'inherit', cwd });
}
/**
* Bump version in package.json
* @param pkgPath Path to the package directory (project root)
* @param type Version bump type: 'major', 'minor', 'patch', or specific version
* @param isAlpha Whether to create an alpha version
* @returns The new version
*/
function bumpVersion(pkgPath: string, type: 'major' | 'minor' | 'patch' | string, isAlpha: boolean = false): string {
const pkgJsonPath = path.join(pkgPath, 'package.json');
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
const currentVersion = pkgJson.version;
let newVersion: string;
// Parse current version to check if it's already an alpha version
const versionRegex = /^(\d+\.\d+\.\d+)(?:-alpha\.(\d+))?$/;
const match = currentVersion.match(versionRegex);
if (!match) {
throw new Error(`Invalid version format: ${currentVersion}`);
}
let baseVersion = match[1];
const currentAlphaVersion = match[2] ? parseInt(match[2], 10) : -1;
// Handle version bumping
if (type === 'major' || type === 'minor' || type === 'patch') {
const [major, minor, patch] = baseVersion.split('.').map(Number);
// Bump version according to type
if (type === 'major') {
baseVersion = `${major + 1}.0.0`;
} else if (type === 'minor') {
baseVersion = `${major}.${minor + 1}.0`;
} else { // patch
baseVersion = `${major}.${minor}.${patch + 1}`;
}
} else if (type.match(/^\d+\.\d+\.\d+$/)) {
// Use the provided version string directly as base version
baseVersion = type;
} else {
throw new Error(`Invalid version bump type: ${type}. Use 'major', 'minor', 'patch', or a specific version like '1.2.3'.`);
}
// Create final version string
if (isAlpha) {
// For alpha releases, always start at alpha.0 when base version changes
// If the base version is the same, increment the alpha number.
const alphaVersion = baseVersion === match[1] ? currentAlphaVersion + 1 : 0;
if (alphaVersion < 0) {
throw new Error(`Cannot create alpha version from non-alpha version ${currentVersion} without bumping base version (major, minor, patch, or specific).`);
}
newVersion = `${baseVersion}-alpha.${alphaVersion}`;
} else {
// If bumping from an alpha version to a stable version, use the current or bumped baseVersion
newVersion = baseVersion;
}
// Update package.json
pkgJson.version = newVersion;
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n');
console.log(`Bumped version from ${currentVersion} to ${newVersion} in ${pkgJsonPath}`);
return newVersion;
}
/**
* Create a git commit and tag for the release
* @param version The version to tag
* @param isAlpha Whether this is an alpha release
*/
function createGitCommitAndTag(version: string, isAlpha: boolean = false) {
console.log('Creating git commit and tag...');
try {
// Stage package.json and any other changes
run('git add package.json', rootPath); // Specifically add package.json
// Optional: Add other specific files if needed, or 'git add .' if all changes should be included
// Create commit with version message
const commitMsg = isAlpha
? `chore: alpha release v${version}`
: `chore: release v${version}`;
run(`git commit -m "${commitMsg}"`, rootPath);
// Create tag
const tagMsg = isAlpha
? `Alpha Release v${version}`
: `Release v${version}`;
run(`git tag -a v${version} -m "${tagMsg}"`, rootPath);
// Push commit and tag to remote
console.log('Pushing commit and tag to remote...');
run('git push', rootPath);
run('git push --tags', rootPath);
console.log(`Successfully created and pushed git tag v${version}`);
} catch (error) {
console.error('Failed to create git commit and tag:', error);
// Decide if we should proceed with publishing even if git fails
// For now, let's throw to stop the process.
throw error;
}
}
async function publishPackage() {
console.log(`🚀 Starting ${isAlpha ? 'alpha' : ''} release process...`);
console.log(`📝 Version bump: ${versionBumpArg}`);
// Build package first (assuming a build script exists in package.json)
console.log('🔨 Building package...');
run('pnpm build', rootPath); // Use the build script from package.json
// Bump the version in the root package.json
const newVersion = bumpVersion(rootPath, versionBumpArg, isAlpha);
// Create git commit and tag if not skipped
if (!skipGit) {
createGitCommitAndTag(newVersion, isAlpha);
}
// Publish the package to npm
console.log(`📤 Publishing package@${newVersion} to npm...`);
const publishCmd = isAlpha
? 'pnpm publish --tag alpha --no-git-checks --access public'
: 'pnpm publish --no-git-checks --access public'; // --no-git-checks is often needed if git tagging is manual or separate
run(publishCmd, rootPath);
console.log(`✅ Successfully completed ${isAlpha ? 'alpha' : ''} release v${newVersion}!`);
}
// Run the publish process
publishPackage().catch(error => {
console.error('❌ Error during release process:', error);
process.exit(1);
});