|
|
@@ -0,0 +1,317 @@
|
|
|
+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 {
|
|
|
+ execSync('git add package.json src-tauri/tauri.conf.json src-tauri/Cargo.toml src-tauri/Cargo.lock', {
|
|
|
+ encoding: 'utf-8',
|
|
|
+ stdio: 'inherit'
|
|
|
+ })
|
|
|
+ execSync(`git commit -m "v${version}"`, {
|
|
|
+ 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
|
|
|
+ }
|
|
|
+
|
|
|
+ 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('')
|
|
|
+
|
|
|
+ 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 提交')
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('')
|
|
|
+ const action = await waitForContinueOrExit()
|
|
|
+ if (action === 'exit') {
|
|
|
+ console.log('再见!')
|
|
|
+ process.exit(0)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+main().catch(console.error)
|