import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import axios from 'axios'; import inquirer from 'inquirer'; import fs from 'fs/promises'; import path from 'path'; import { getApiKey, getApiUrl, getProjectId } from '../utils/config.js'; import { simpleGit } from 'simple-git'; const git = simpleGit(); export function createReleaseCommand(): Command { return new Command('release') .description('Ship a new version (Changelog, Tag, Bump)') .argument('[type]', 'Release type (patch, minor, major)', 'patch') .action(async (type) => { const spinner = ora('Preparing release...').start(); try { const { projectId, apiKey, apiUrl } = getContext(); // 1. Check Git Status const status = await git.status(); if (!status.isClean()) { spinner.fail('Git workspace is dirty. Commit or stash changes first.'); return; } // 2. Fetch Pending Release Items spinner.text = 'Scanning completed tasks...'; // We assume an endpoint or use raw roadmap query. // Assuming we can filter by 'COMPLETED' and release_id is null. // Since our query tool might not support complex filters, we do client processing or use a dedicated `release/dry-run` endpoint. // For MVP: We fetch ALL completed tasks and check if they "look" unreleased (this is naive without the DB column support in API yet). // Let's rely on the API. If API fails (schema missing), we abort. // Mocking the Changelog Generation for MVP robustness if DB endpoint doesn't exist yet // Ideally: POST /api/v1/release/prepare // Let's do a Manual Bump workflow for now tailored to the user's setup. // Bump package.json const pkgPath = path.resolve(process.cwd(), 'package.json'); const pkgContent = await fs.readFile(pkgPath, 'utf-8'); const pkg = JSON.parse(pkgContent); const currentVersion = pkg.version; const [major, minor, patch] = currentVersion.split('.').map(Number); let newVersion = currentVersion; if (type === 'major') newVersion = `${major + 1}.0.0`; if (type === 'minor') newVersion = `${major}.${minor + 1}.0`; if (type === 'patch') newVersion = `${major}.${minor}.${patch + 1}`; spinner.succeed(`Bumping ${pkg.name} from ${chalk.dim(currentVersion)} to ${chalk.green(newVersion)}`); // Confirmation const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: `Ship v${newVersion}? This will tag git and update changelog (if db ready).`, default: true }]); if (!confirm) { console.log('Aborted.'); return; } // Write package.json pkg.version = newVersion; await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 4)); // Generate Changelog (Local Append) const changelogPath = path.resolve(process.cwd(), 'CHANGELOG.md'); const date = new Date().toISOString().split('T')[0]; const entry = `\n## [${newVersion}] - ${date}\n- Automated release via Rigstate.\n`; try { await fs.appendFile(changelogPath, entry); } catch { await fs.writeFile(changelogPath, '# Changelog\n' + entry); } // Git Commit & Tag spinner.start('Tagging and pushing...'); await git.add(['package.json', 'CHANGELOG.md']); await git.commit(`chore(release): v${newVersion}`); await git.addTag(`v${newVersion}`); await git.push(); await git.pushTags(); // DB Sync (Optional/Future) // await axios.post('/api/v1/releases' ... ) spinner.succeed(chalk.bold.green(`🚀 Release v${newVersion} shipped!`)); } catch (e: any) { spinner.fail(e.message); } }); } function getContext() { const apiKey = getApiKey(); const apiUrl = getApiUrl(); const projectId = getProjectId(); if (!projectId) throw new Error('Project context missing.'); return { projectId, apiKey, apiUrl }; }