mirror of
https://github.com/wuzf/2fa.git
synced 2026-09-03 07:17:09 +08:00
- Replace hardcoded 1.5.0 in logger/monitoring with APP_VERSION import - Update README version badges to 1.6.0 with test-enforced consistency - Add scripts/release.js: npm run release:patch/minor/major runs tests, bumps package.json, syncs version.js and README badges, commits and tags
185 lines
6.5 KiB
JavaScript
185 lines
6.5 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* 发版脚本 - 一条命令完成版本号同步、提交和打 tag
|
||
*
|
||
* 版本号唯一数据源是 package.json,其余位置由本脚本同步:
|
||
* - src/utils/version.js (APP_VERSION,前端 footer 和新版本检测依赖)
|
||
* - README.md / README_EN.md (版本徽章)
|
||
*
|
||
* 使用方式:
|
||
* npm run release:patch # 1.6.0 → 1.6.1
|
||
* npm run release:minor # 1.6.0 → 1.7.0
|
||
* npm run release:major # 1.6.0 → 2.0.0
|
||
* node scripts/release.js 1.8.0 # 指定版本号
|
||
* node scripts/release.js --sync # 仅同步(修复各处版本不一致,不提交)
|
||
*
|
||
* 发版流程:
|
||
* 1. 检查 tag 未存在、版本相关文件无未提交修改(其他文件如 wrangler.toml 允许 dirty)
|
||
* 2. 运行全量测试(--skip-tests 跳过)
|
||
* 3. bump package.json + package-lock.json
|
||
* 4. 同步 version.js 和 README 徽章
|
||
* 5. 运行版本一致性测试自检
|
||
* 6. git commit + git tag v{x.y.z}(tag 需符合 v{x.y.z} 格式,新版本检测依赖)
|
||
* 7. 提示手动 push(不自动 push)
|
||
*/
|
||
|
||
import { execSync } from 'child_process';
|
||
import { readFileSync, writeFileSync } from 'fs';
|
||
import { fileURLToPath } from 'url';
|
||
import { dirname, join } from 'path';
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const ROOT = join(__dirname, '..');
|
||
|
||
/** 版本号写入的所有位置(新增位置时在此登记,并同步更新 tests/utils/version.test.js 的一致性校验) */
|
||
const SYNC_TARGETS = [
|
||
{
|
||
file: 'src/utils/version.js',
|
||
pattern: /(APP_VERSION = ')\d+\.\d+\.\d+(')/,
|
||
replacement: (v) => `$1${v}$2`,
|
||
},
|
||
{
|
||
file: 'README.md',
|
||
pattern: /(badge\/version-)\d+\.\d+\.\d+(-blue)/,
|
||
replacement: (v) => `$1${v}$2`,
|
||
},
|
||
{
|
||
file: 'README_EN.md',
|
||
pattern: /(badge\/version-)\d+\.\d+\.\d+(-blue)/,
|
||
replacement: (v) => `$1${v}$2`,
|
||
},
|
||
];
|
||
|
||
/** 发版 commit 包含的全部文件 */
|
||
const RELEASE_FILES = ['package.json', 'package-lock.json', ...SYNC_TARGETS.map((t) => t.file)];
|
||
|
||
function run(cmd, options = {}) {
|
||
return execSync(cmd, { cwd: ROOT, encoding: 'utf-8', ...options });
|
||
}
|
||
|
||
function readPackageVersion() {
|
||
return JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8')).version;
|
||
}
|
||
|
||
function bumpVersion(current, type) {
|
||
const [major, minor, patch] = current.split('.').map(Number);
|
||
switch (type) {
|
||
case 'major':
|
||
return `${major + 1}.0.0`;
|
||
case 'minor':
|
||
return `${major}.${minor + 1}.0`;
|
||
case 'patch':
|
||
return `${major}.${minor}.${patch + 1}`;
|
||
default:
|
||
return type; // 显式版本号
|
||
}
|
||
}
|
||
|
||
/** 将版本号同步到 SYNC_TARGETS 中的所有文件,任一文件匹配失败则报错退出 */
|
||
function syncVersion(version) {
|
||
let failed = false;
|
||
for (const { file, pattern, replacement } of SYNC_TARGETS) {
|
||
const path = join(ROOT, file);
|
||
const content = readFileSync(path, 'utf-8');
|
||
if (!pattern.test(content)) {
|
||
console.error(`❌ ${file}: 未匹配到版本号模式 ${pattern},请检查文件内容或更新 SYNC_TARGETS`);
|
||
failed = true;
|
||
continue;
|
||
}
|
||
const updated = content.replace(pattern, replacement(version));
|
||
if (updated === content) {
|
||
console.log(`✓ ${file} 已是 ${version}`);
|
||
} else {
|
||
writeFileSync(path, updated);
|
||
console.log(`✅ ${file} → ${version}`);
|
||
}
|
||
}
|
||
if (failed) {
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
function main() {
|
||
const args = process.argv.slice(2);
|
||
const skipTests = args.includes('--skip-tests');
|
||
const positional = args.filter((a) => !a.startsWith('--'));
|
||
|
||
// --sync 模式:仅把 package.json 的版本同步到其他位置
|
||
if (args.includes('--sync')) {
|
||
const version = readPackageVersion();
|
||
console.log(`🔄 同步版本号 ${version}(数据源: package.json)\n`);
|
||
syncVersion(version);
|
||
return;
|
||
}
|
||
|
||
const type = positional[0];
|
||
if (!type || (!['major', 'minor', 'patch'].includes(type) && !/^\d+\.\d+\.\d+$/.test(type))) {
|
||
console.error('用法: node scripts/release.js <major|minor|patch|x.y.z> [--skip-tests]');
|
||
console.error(' node scripts/release.js --sync');
|
||
process.exit(1);
|
||
}
|
||
|
||
const currentVersion = readPackageVersion();
|
||
const newVersion = bumpVersion(currentVersion, type);
|
||
const tag = `v${newVersion}`;
|
||
|
||
console.log(`\n🚀 发版: ${currentVersion} → ${newVersion}\n`);
|
||
|
||
// 1. tag 不能已存在
|
||
if (run(`git tag -l ${tag}`).trim()) {
|
||
console.error(`❌ tag ${tag} 已存在,请检查版本号`);
|
||
process.exit(1);
|
||
}
|
||
|
||
// 2. 版本相关文件必须干净,避免发版 commit 混入无关修改
|
||
// (其他文件如 wrangler.toml 允许有本地修改,仅提示)
|
||
const dirty = run('git status --porcelain')
|
||
.split('\n')
|
||
.filter(Boolean)
|
||
.map((line) => line.slice(3).trim());
|
||
const dirtyReleaseFiles = dirty.filter((f) => RELEASE_FILES.includes(f));
|
||
if (dirtyReleaseFiles.length > 0) {
|
||
console.error(`❌ 以下版本相关文件有未提交修改,请先提交或还原:\n ${dirtyReleaseFiles.join('\n ')}`);
|
||
process.exit(1);
|
||
}
|
||
const dirtyOthers = dirty.filter((f) => !RELEASE_FILES.includes(f));
|
||
if (dirtyOthers.length > 0) {
|
||
console.warn(`⚠️ 工作区存在其他未提交修改(不会包含在发版 commit 中):\n ${dirtyOthers.join('\n ')}\n`);
|
||
}
|
||
|
||
// 3. 全量测试
|
||
if (skipTests) {
|
||
console.warn('⚠️ 已跳过测试 (--skip-tests)\n');
|
||
} else {
|
||
console.log('🧪 运行全量测试...\n');
|
||
run('npx vitest run', { stdio: 'inherit' });
|
||
}
|
||
|
||
// 4. bump package.json(仅替换顶层 version 字段),并同步 package-lock.json
|
||
const pkgPath = join(ROOT, 'package.json');
|
||
const pkgContent = readFileSync(pkgPath, 'utf-8');
|
||
writeFileSync(pkgPath, pkgContent.replace(/("version":\s*")\d+\.\d+\.\d+(")/, `$1${newVersion}$2`));
|
||
console.log(`✅ package.json → ${newVersion}`);
|
||
run('npm install --package-lock-only --ignore-scripts', { stdio: 'ignore' });
|
||
console.log(`✅ package-lock.json → ${newVersion}`);
|
||
|
||
// 5. 同步其余位置
|
||
syncVersion(newVersion);
|
||
|
||
// 6. 版本一致性自检(tests/utils/version.test.js 校验 APP_VERSION 和 README 徽章)
|
||
console.log('\n🔍 版本一致性自检...\n');
|
||
run('npx vitest run tests/utils/version.test.js', { stdio: 'inherit' });
|
||
|
||
// 7. 提交并打 tag(commit message 符合 Conventional Commits,通过 husky commit-msg 校验)
|
||
run(`git add ${RELEASE_FILES.join(' ')}`);
|
||
run(`git commit -m "chore(release): bump version to ${newVersion}"`, { stdio: 'inherit' });
|
||
run(`git tag ${tag}`);
|
||
|
||
console.log(`\n✅ 发版完成: ${tag}(commit + tag 均已创建)`);
|
||
console.log('\n📤 请推送以发布(用户端新版本检测依赖远端 tag):');
|
||
console.log('\n git push && git push --tags\n');
|
||
}
|
||
|
||
main();
|