autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
353 lines (352 loc) • 11.9 kB
JavaScript
/**
* AutoSnippet 发布辅助脚本
* 用途:自动化发布前检查和发布流程
* 使用:node scripts/release.js [patch|minor|major]
*/
import { DASHBOARD_DIR, PACKAGE_ROOT } from '../lib/shared/package-root.js';
const __dirname = import.meta.dirname;
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
const require = createRequire(import.meta.url);
// 颜色输出
const _colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[36m',
bold: '\x1b[1m',
};
function log(message, color = 'reset') { }
function success(message) {
log(`✅ ${message}`, 'green');
}
function error(message) {
log(`❌ ${message}`, 'red');
}
function warning(message) {
log(`⚠️ ${message}`, 'yellow');
}
function info(message) {
log(`ℹ️ ${message}`, 'blue');
}
function header(message) {
log(`\n${'='.repeat(60)}`, 'bold');
log(` ${message}`, 'bold');
log(`${'='.repeat(60)}`, 'bold');
}
function exec(command, options = {}) {
try {
return execSync(command, {
encoding: 'utf8',
stdio: options.silent ? 'pipe' : 'inherit',
...options,
});
}
catch (err) {
if (!options.ignoreError) {
throw err;
}
return null;
}
}
// 检查项
class ReleaseChecker {
errors;
warnings;
constructor() {
this.errors = [];
this.warnings = [];
}
// 检查 Git 状态
checkGitStatus() {
header('Git 状态检查');
// 检查分支
const branch = exec('git branch --show-current', { silent: true })?.trim();
if (branch !== 'main' && branch !== 'master') {
this.errors.push(`当前分支不是 main/master: ${branch}`);
error(`当前分支: ${branch}`);
}
else {
success(`当前分支: ${branch}`);
}
// 检查工作区
const status = exec('git status --short', { silent: true });
if (status?.trim()) {
this.errors.push('工作区有未提交的变更');
error('工作区不干净:');
}
else {
success('工作区干净');
}
// 检查远程同步
try {
exec('git fetch origin', { silent: true });
const behind = exec('git rev-list HEAD..origin/main --count', {
silent: true,
ignoreError: true,
})?.trim();
if (behind && parseInt(behind) > 0) {
this.warnings.push(`本地落后远程 ${behind} 个提交`);
warning(`需要先 pull: git pull origin main`);
}
else {
success('与远程同步');
}
}
catch (_err) {
warning('无法检查远程同步状态');
}
}
// 检查 Node.js 环境
checkNodeEnvironment() {
header('Node.js 环境检查');
const nodeVersion = process.version;
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
if (majorVersion < 16) {
this.errors.push(`Node.js 版本过低: ${nodeVersion} (需要 >=16)`);
error(`Node.js: ${nodeVersion}`);
}
else {
success(`Node.js: ${nodeVersion}`);
}
// 检查环境变量配置
const envPath = path.join(PACKAGE_ROOT, '.env');
if (!fs.existsSync(envPath)) {
this.errors.push('.env 文件不存在');
error('.env: 不存在');
}
else {
const envContent = fs.readFileSync(envPath, 'utf8');
const nodeEnv = envContent.match(/NODE_ENV=(\w+)/)?.[1];
if (nodeEnv === 'production') {
this.errors.push('.env 已是生产环境,发布前应该是开发环境');
error(`环境: ${nodeEnv} (应该是 development)`);
}
else {
success(`环境: ${nodeEnv || 'development'}`);
}
// 检查是否有备份
const backupPath = path.join(PACKAGE_ROOT, '.env.backup');
if (fs.existsSync(backupPath)) {
warning('.env.backup 已存在,可能有未完成的发布');
}
}
}
// 检查并构建前端(生产环境)
buildFrontend() {
header('构建前端(生产环境)');
// 备份 .env
info('备份 .env 文件...');
const envPath = path.join(PACKAGE_ROOT, '.env');
const backupPath = path.join(PACKAGE_ROOT, '.env.backup');
if (fs.existsSync(envPath)) {
fs.copyFileSync(envPath, backupPath);
success('.env 已备份');
}
// 切换到生产环境
info('切换到生产环境...');
const envContent = fs.readFileSync(envPath, 'utf8');
const productionEnv = envContent
.replace(/NODE_ENV=.*/g, 'NODE_ENV=production')
.replace(/VITE_API_BASE_URL=.*/g, 'VITE_API_BASE_URL=https://your-production-api.com');
fs.writeFileSync(envPath, productionEnv);
success('已切换到生产环境');
// 构建 Dashboard
try {
info('构建 Dashboard...');
exec('cd dashboard && npm run build');
const distPath = path.join(DASHBOARD_DIR, 'dist/index.html');
if (fs.existsSync(distPath)) {
success('Dashboard 构建成功');
}
else {
throw new Error('dist/index.html 不存在');
}
}
catch (err) {
this.errors.push('Dashboard 构建失败');
error('Dashboard 构建失败');
// 恢复环境
warning('恢复开发环境...');
fs.copyFileSync(backupPath, envPath);
fs.unlinkSync(backupPath);
throw err;
}
// 恢复开发环境(稍后在发布完成后再恢复)
info('⚠️ 记得在发布完成后恢复开发环境');
}
// 恢复开发环境
restoreEnvironment() {
header('恢复开发环境');
const envPath = path.join(PACKAGE_ROOT, '.env');
const backupPath = path.join(PACKAGE_ROOT, '.env.backup');
if (fs.existsSync(backupPath)) {
fs.copyFileSync(backupPath, envPath);
fs.unlinkSync(backupPath);
success('已恢复开发环境');
}
else {
warning('未找到 .env.backup,请手动检查环境变量');
}
}
// 检查其他构建产物
checkBuildArtifacts() {
header('其他构建产物检查');
success('No platform-specific binaries to check');
}
// 运行测试
runTests() {
header('运行测试');
try {
info('运行单元测试...');
exec('npm run test:unit');
success('单元测试通过');
}
catch (_err) {
this.errors.push('单元测试失败');
error('单元测试失败');
}
try {
info('运行集成测试...');
exec('npm run test:integration');
success('集成测试通过');
}
catch (_err) {
this.errors.push('集成测试失败');
error('集成测试失败');
}
}
// 总结
summary() {
header('检查总结');
if (this.errors.length === 0 && this.warnings.length === 0) {
success('所有检查通过,可以发布!');
return true;
}
if (this.errors.length > 0) {
error(`发现 ${this.errors.length} 个错误:`);
this.errors.forEach((err, i) => { });
}
if (this.warnings.length > 0) {
warning(`发现 ${this.warnings.length} 个警告:`);
this.warnings.forEach((warn, i) => { });
}
return this.errors.length === 0;
}
}
// 发布流程
function release(versionType, checker) {
header(`开始发布流程 (${versionType})`);
// 读取当前版本
const packageJson = require('../package.json');
const currentVersion = packageJson.version;
info(`当前版本: ${currentVersion}`);
// 构建前端(生产环境)
try {
checker.buildFrontend();
}
catch (_err) {
error('前端构建失败,发布中止');
process.exit(1);
}
// 执行版本升级
try {
info(`执行 npm version ${versionType}...`);
const newVersion = exec(`npm version ${versionType}`, { silent: true })?.trim();
success(`版本已更新: ${currentVersion} → ${newVersion}`);
info('请手动编辑 CHANGELOG.md,然后按回车继续...');
// 等待用户输入
require('node:child_process').spawnSync('read', ['-p', ''], {
stdio: 'inherit',
shell: true,
});
// 修正 commit(包含 dist/ 文件)
info('提交所有变更(包括构建产物)...');
exec('git add .');
exec(`git commit --amend -m "chore: release ${newVersion}"`);
exec(`git tag -f ${newVersion}`);
success('Commit 和 tag 已更新');
// 推送到 GitHub
info('推送到 GitHub(触发自动发布)...');
exec('git push origin main --tags');
success('已推送到 GitHub,等待 Actions 自动发布');
// 恢复开发环境
checker.restoreEnvironment();
header('🎉 发布流程完成!');
}
catch (err) {
error('发布失败!');
console.error(err.message);
// 尝试恢复环境
try {
checker.restoreEnvironment();
}
catch (_restoreErr) {
error('恢复环境失败,请手动检查 .env 文件');
}
process.exit(1);
}
}
// 主函数
function main() {
const args = process.argv.slice(2);
const command = args[0];
// 显示帮助
if (!command || command === '--help' || command === '-h') {
process.exit(0);
}
// 执行检查
if (command === 'check') {
const checker = new ReleaseChecker();
checker.checkGitStatus();
checker.checkNodeEnvironment();
checker.checkBuildArtifacts();
if (checker.summary()) {
info('\n运行 `npm run test` 来执行完整测试');
info('运行 `npm run release:patch/minor/major` 开始发布');
process.exit(0);
}
else {
error('\n请修复错误后再试');
process.exit(1);
}
}
// 执行发布
if (['patch', 'minor', 'major'].includes(command)) {
// 先执行检查
const checker = new ReleaseChecker();
checker.checkGitStatus();
checker.checkNodeEnvironment();
checker.checkBuildArtifacts();
checker.runTests();
if (!checker.summary()) {
error('\n发布前检查未通过,请修复后再试');
process.exit(1);
}
warning(`即将发布 ${command} 版本,是否继续?(y/N)`);
const readline = require('node:readline').createInterface({
input: process.stdin,
output: process.stdout,
});
readline.question('> ', (answer) => {
readline.close();
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
release(command, checker);
}
else {
info('已取消发布');
process.exit(0);
}
});
return;
}
// 未知命令
error(`未知命令: ${command}`);
process.exit(1);
}
// 执行
main();