| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- import fs from 'fs'
- import path from 'path'
- import { fileURLToPath } from 'url'
- import { execSync } from 'child_process'
- import readline from 'readline'
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
- const projectRoot = path.dirname(__dirname)
- process.chdir(projectRoot)
- // 读取当前版本
- function getCurrentVersion() {
- const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
- return pkg.version
- }
- let currentVersion = getCurrentVersion()
- // 清屏并显示标题
- function showHeader() {
- console.log('')
- console.log('+--------------------------------------------+')
- console.log('| 版本号设置工具 |')
- console.log('| Version Bump Tool |')
- console.log('+--------------------------------------------+')
- console.log('')
- console.log(`当前版本: ${currentVersion}`)
- console.log('')
- console.log('支持的输入格式:')
- console.log(' - 具体版本号: 1.0.0, 2.1.3')
- console.log(' - patch: 补丁版本 +1 (如 0.0.6 -> 0.0.7)')
- console.log(' - minor: 次版本 +1 (如 0.0.6 -> 0.1.0)')
- console.log(' - major: 主版本 +1 (如 0.0.6 -> 1.0.0)')
- console.log(' - 按 q 键退出')
- console.log('')
- }
- // 单键读取
- function readKey() {
- return new Promise((resolve) => {
- const wasRaw = process.stdin.isRaw
- readline.emitKeypressEvents(process.stdin)
- if (process.stdin.isTTY) {
- process.stdin.setRawMode(true)
- }
- process.stdin.once('keypress', (ch, key) => {
- if (process.stdin.isTTY) {
- process.stdin.setRawMode(wasRaw)
- }
- resolve({ ch, key })
- })
- })
- }
- // 读取输入,支持单键 q 退出
- async function readInput(prompt) {
- process.stdout.write(prompt)
- let input = ''
- readline.emitKeypressEvents(process.stdin)
- if (process.stdin.isTTY) {
- process.stdin.setRawMode(true)
- }
- return new Promise((resolve) => {
- const onKeypress = (ch, key) => {
- if (key && key.name === 'return') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('')
- resolve(input)
- } else if (key && key.name === 'escape') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('')
- resolve('__EXIT__')
- } else if (key && key.name === 'backspace') {
- if (input.length > 0) {
- input = input.slice(0, -1)
- process.stdout.write('\b \b')
- }
- } else if (key && key.ctrl && key.name === 'c') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('')
- process.exit(0)
- } else if (ch && /[a-zA-Z0-9.\-]/.test(ch)) {
- input += ch
- process.stdout.write(ch)
- // 单独输入 q 立即退出
- if (input === 'q' || input === 'Q') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('')
- resolve('__EXIT__')
- }
- }
- }
- process.stdin.on('keypress', onKeypress)
- })
- }
- // 读取 Y/N
- async function readYesNo(prompt, defaultYes = true) {
- const hint = defaultYes ? 'Y/n' : 'y/N'
- process.stdout.write(`${prompt} (${hint}): `)
- readline.emitKeypressEvents(process.stdin)
- if (process.stdin.isTTY) {
- process.stdin.setRawMode(true)
- }
- return new Promise((resolve) => {
- const onKeypress = (ch, key) => {
- if (key && key.name === 'return') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log(defaultYes ? 'y' : 'n')
- resolve(defaultYes)
- } else if (ch === 'y' || ch === 'Y') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('y')
- resolve(true)
- } else if (ch === 'n' || ch === 'N') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('n')
- resolve(false)
- } else if (key && key.ctrl && key.name === 'c') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('')
- process.exit(0)
- }
- }
- process.stdin.on('keypress', onKeypress)
- })
- }
- // 等待按键继续或退出
- async function waitForContinueOrExit() {
- process.stdout.write('按 Enter 继续修改,按 q 退出...')
- readline.emitKeypressEvents(process.stdin)
- if (process.stdin.isTTY) {
- process.stdin.setRawMode(true)
- }
- return new Promise((resolve) => {
- const onKeypress = (ch, key) => {
- if (key && key.name === 'return') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('\n')
- resolve('continue')
- } else if (ch === 'q' || ch === 'Q') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('\n')
- resolve('exit')
- } else if (key && key.ctrl && key.name === 'c') {
- process.stdin.removeListener('keypress', onKeypress)
- if (process.stdin.isTTY) process.stdin.setRawMode(false)
- console.log('')
- process.exit(0)
- }
- }
- process.stdin.on('keypress', onKeypress)
- })
- }
- // 验证版本号格式
- function validateVersion(input) {
- const keywords = ['patch', 'minor', 'major']
- if (keywords.includes(input.toLowerCase())) return true
- return /^\d+\.\d+\.\d+$/.test(input)
- }
- // 获取新版本号(dry-run)
- function getNewVersion(input) {
- try {
- const result = execSync(`node scripts/bump-version.mjs ${input} --dry-run`, {
- encoding: 'utf-8',
- stdio: ['pipe', 'pipe', 'pipe']
- })
- return result.trim()
- } catch {
- return null
- }
- }
- // 执行版本更新
- function updateVersion(input) {
- try {
- execSync(`node scripts/bump-version.mjs ${input}`, {
- encoding: 'utf-8',
- stdio: 'inherit'
- })
- return true
- } catch {
- return false
- }
- }
- // Git 提交
- function gitCommit(version) {
- try {
- // 注意:Cargo.lock 在 .gitignore 中,不需要添加
- execSync('git add package.json src-tauri/tauri.conf.json src-tauri/Cargo.toml', {
- encoding: 'utf-8',
- stdio: 'inherit'
- })
- execSync(`git commit -m "v${version}"`, {
- encoding: 'utf-8',
- stdio: 'inherit'
- })
- return true
- } catch {
- return false
- }
- }
- // Git 添加 tag(如果已存在则先删除)
- function gitTag(version) {
- try {
- // 先尝试删除已存在的 tag
- try {
- execSync(`git tag -d v${version}`, {
- encoding: 'utf-8',
- stdio: 'pipe'
- })
- console.log(`[提示] 已删除旧 tag: v${version}`)
- } catch {
- // tag 不存在,忽略错误
- }
- // 添加新 tag
- execSync(`git tag v${version}`, {
- encoding: 'utf-8',
- stdio: 'inherit'
- })
- return true
- } catch {
- return false
- }
- }
- // Git 推送(包含 tags)
- function gitPush(version) {
- try {
- // 确保终端处于正常模式,以便 git 可以进行交互
- if (process.stdin.isTTY) {
- process.stdin.setRawMode(false)
- }
- // 先推送提交
- execSync('git push', {
- encoding: 'utf-8',
- stdio: 'inherit'
- })
- // 再单独推送 tag(强制更新远程 tag)
- execSync(`git push origin v${version} --force`, {
- encoding: 'utf-8',
- stdio: 'inherit'
- })
- return true
- } catch {
- return false
- }
- }
- // 主循环
- async function main() {
- showHeader()
- while (true) {
- const versionInput = await readInput('请输入新版本号: ')
- if (versionInput === '__EXIT__') {
- console.log('')
- console.log('再见!')
- process.exit(0)
- }
- if (!versionInput.trim()) {
- console.log('[错误] 版本号不能为空,请重新输入')
- console.log('')
- continue
- }
- if (!validateVersion(versionInput)) {
- console.log('[错误] 无效的版本号格式,请输入 x.y.z 格式或 patch/minor/major')
- console.log('')
- continue
- }
- const newVersion = getNewVersion(versionInput)
- if (!newVersion) {
- console.log('[错误] 无法计算新版本号,请检查输入')
- console.log('')
- continue
- }
- console.log('')
- console.log('+--------------------------------------------+')
- console.log('| 版本变更预览 |')
- console.log('+--------------------------------------------+')
- console.log(`| 当前版本: ${currentVersion}`)
- console.log(`| 新版本: ${newVersion}`)
- console.log('+--------------------------------------------+')
- console.log('')
- const confirm = await readYesNo('确认更新版本号?', false)
- if (!confirm) {
- console.log('')
- console.log('已取消,请重新输入版本号')
- console.log('')
- continue
- }
- // 检查版本号是否有变化
- const versionChanged = currentVersion !== newVersion
- console.log('')
- console.log('正在更新版本号...')
- console.log('')
- if (!updateVersion(versionInput)) {
- console.log('')
- console.log('[错误] 版本号更新失败')
- continue
- }
- currentVersion = newVersion
- console.log('')
- console.log('============================================')
- console.log(` 版本号已成功更新到 ${newVersion}`)
- console.log('============================================')
- console.log('')
- if (versionChanged) {
- const gitChoice = await readYesNo('是否将修改提交到 Git?', true)
- if (gitChoice) {
- console.log('')
- console.log('正在提交到 Git...')
- if (gitCommit(newVersion)) {
- console.log('')
- console.log(`[成功] 已提交到 Git,提交消息: v${newVersion}`)
- } else {
- console.log('')
- console.log('[警告] Git 提交失败,可能没有变更或发生错误')
- }
- } else {
- console.log('')
- console.log('已跳过 Git 提交')
- }
- } else {
- console.log('[提示] 版本号未变化,跳过 Git 提交')
- }
- // 无论版本是否变化,都询问是否添加 tag
- const tagChoice = await readYesNo('是否添加 Git tag?', false)
- if (tagChoice) {
- console.log('')
- console.log('正在添加 tag...')
- if (gitTag(newVersion)) {
- console.log(`[成功] 已添加 tag: v${newVersion}`)
- // 询问是否推送
- const pushChoice = await readYesNo('是否推送到远程仓库?', true)
- if (pushChoice) {
- console.log('')
- console.log('正在推送...')
- if (gitPush(newVersion)) {
- console.log('[成功] 已推送到远程仓库')
- } else {
- console.log('[警告] 推送失败')
- }
- } else {
- console.log('')
- console.log('已跳过推送')
- }
- } else {
- console.log('[警告] 添加 tag 失败,可能 tag 已存在')
- }
- } else {
- console.log('')
- console.log('已跳过添加 tag')
- }
- console.log('')
- const action = await waitForContinueOrExit()
- if (action === 'exit') {
- console.log('再见!')
- process.exit(0)
- }
- }
- }
- main().catch(console.error)
|