#!/usr/bin/env node /* Release automation script Automates: version bump → changelog → blotter → commit → push → tag → push tags. The GitHub Actions release workflow triggers on the pushed tag. Usage: yarn release patch --message "Short blotter one-liner" yarn release minor --message "Short blotter one-liner" yarn release major --message "Short blotter one-liner" yarn release 0.7.0 --message "Short blotter one-liner" Flags: --message, -m Blotter one-liner (required). Becomes "vX.Y.Z: " in blotter. --dry-run Run everything except git commit/push/tag. Useful for preview. Prerequisites: - Update oneLinerDescription in scripts/release-body.js before running (manually or via the release-description agent skill). - Working tree should be clean (uncommitted changes will be included in the release commit). */ import { readFileSync, writeFileSync } from 'fs'; import { execSync } from 'child_process'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.join(__dirname, '..'); const PACKAGE_PATH = path.join(ROOT, 'package.json'); function parseArgs(argv) { const result = { positional: [] }; for (let i = 0; i < argv.length; i++) { const arg = argv[i]; if (arg === '--message' || arg === '-m') { result.message = argv[++i]; } else if (arg === '--dry-run') { result.dryRun = true; } else if (!arg.startsWith('-')) { result.positional.push(arg); } } return result; } function bumpVersion(current, bump) { const parts = current.split('.').map(Number); if (bump === 'patch') { parts[2]++; } else if (bump === 'minor') { parts[1]++; parts[2] = 0; } else if (bump === 'major') { parts[0]++; parts[1] = 0; parts[2] = 0; } else if (/^\d+\.\d+\.\d+$/.test(bump)) { return bump; } else { console.error(`Invalid version: ${bump}. Use patch, minor, major, or x.y.z`); process.exit(1); } return parts.join('.'); } function run(cmd) { console.log(` $ ${cmd}`); execSync(cmd, { cwd: ROOT, stdio: 'inherit' }); } function main() { const args = parseArgs(process.argv.slice(2)); const bump = args.positional[0]; if (!bump) { console.error('Usage: yarn release --message "Blotter one-liner"'); process.exit(1); } if (!args.message) { console.error('--message (-m) is required. Provide a short blotter one-liner.'); process.exit(1); } const pkg = JSON.parse(readFileSync(PACKAGE_PATH, 'utf8')); const oldVersion = pkg.version; const newVersion = bumpVersion(oldVersion, bump); console.log(`\n Release: ${oldVersion} -> ${newVersion}\n`); // 1. Bump version in package.json pkg.version = newVersion; writeFileSync(PACKAGE_PATH, JSON.stringify(pkg, null, 2) + '\n', 'utf8'); console.log(` [1/6] Bumped package.json to ${newVersion}`); // 2. Generate changelog run('yarn changelog'); console.log(` [2/6] Generated CHANGELOG.md`); // 3. Update blotter const escapedMessage = args.message.replace(/"/g, '\\"'); run(`node scripts/update-blotter.js release --message "${escapedMessage}"`); console.log(` [3/6] Added blotter entry`); // 4. Verify blotter run('node scripts/update-blotter.js check'); console.log(` [4/6] Blotter check passed`); if (args.dryRun) { console.log('\n --dry-run: skipping git operations. Review changes, then run without --dry-run.\n'); return; } // 5. Commit run('git add -A'); run(`git commit -m "chore(release): v${newVersion}"`); console.log(` [5/6] Committed`); // 6. Push, tag, push tags run('git push'); run(`git tag v${newVersion}`); run('git push --tags'); console.log(` [6/6] Pushed and tagged v${newVersion}`); console.log(`\n Release v${newVersion} complete. GitHub Actions will build artifacts.\n`); } main();