release.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. #!/usr/bin/env node
  2. import { execSync } from 'child_process'
  3. import inquirer from 'inquirer'
  4. import fs from 'fs'
  5. function run(cmd, options = {}) {
  6. execSync(cmd, { stdio: 'inherit', ...options })
  7. }
  8. function runSilent(cmd) {
  9. return execSync(cmd, { encoding: 'utf-8' }).trim()
  10. }
  11. function getVersion() {
  12. return JSON.parse(fs.readFileSync('package.json', 'utf8')).version
  13. }
  14. function getLastTag() {
  15. try {
  16. return runSilent('git describe --tags --abbrev=0')
  17. } catch {
  18. return null // 没有 tag
  19. }
  20. }
  21. function getCommitsSince(tag) {
  22. const range = tag ? `${tag}..HEAD` : ''
  23. try {
  24. return runSilent(`git log ${range} --pretty=format:"- %s (%h)"`)
  25. } catch {
  26. return ''
  27. }
  28. }
  29. function updateChangelog(version, mode = 'incremental') {
  30. const lastTag = getLastTag()
  31. const commits = getCommitsSince(lastTag)
  32. if (!commits) {
  33. console.log('⚠️ 没有新提交,但依旧会记录版本变化。')
  34. }
  35. const date = new Date().toISOString().slice(0, 10)
  36. const newEntry = `\n## ${version} (${date})\n${commits || '- No changes'}\n`
  37. let changelog = ''
  38. if (fs.existsSync('CHANGELOG.md')) {
  39. changelog = fs.readFileSync('CHANGELOG.md', 'utf-8')
  40. }
  41. if (mode === 'full') {
  42. fs.writeFileSync('CHANGELOG.md', newEntry, 'utf-8')
  43. } else {
  44. fs.writeFileSync('CHANGELOG.md', newEntry + changelog, 'utf-8')
  45. }
  46. console.log(`✅ changelog 已更新到版本 ${version}`)
  47. }
  48. async function main() {
  49. console.log('🚀 开始发布流程...')
  50. // 选择版本类型
  51. const { versionType } = await inquirer.prompt([
  52. {
  53. type: 'list',
  54. name: 'versionType',
  55. message: '请选择版本类型:',
  56. default: 'patch',
  57. choices: [
  58. { name: '补丁版本 (patch)', value: 'patch' },
  59. { name: '小版本 (minor)', value: 'minor' },
  60. { name: '大版本 (major)', value: 'major' },
  61. ],
  62. },
  63. ])
  64. // 更新版本号(不自动打 tag)
  65. run(`npm version ${versionType} --no-git-tag-version`)
  66. const version = getVersion()
  67. // 同步远程 tag,防止增量 changelog 范围不准
  68. console.log('🔄 正在同步远程标签...')
  69. run('git fetch --tags')
  70. console.log('✅ 远程标签已同步')
  71. // 选择 changelog 生成模式
  72. const { changelogMode } = await inquirer.prompt([
  73. {
  74. type: 'list',
  75. name: 'changelogMode',
  76. message: '请选择 changelog 生成模式:',
  77. default: 'incremental',
  78. choices: [
  79. // { name: '全量生成(会覆盖整个文件)', value: 'full' },
  80. { name: '增量生成(只追加本次更新的内容)', value: 'incremental' },
  81. ],
  82. },
  83. ])
  84. // 生成 changelog
  85. updateChangelog(`v${version}`, changelogMode)
  86. // 提交变更
  87. if (fs.existsSync('package-lock.json')) {
  88. run('git add package.json package-lock.json')
  89. } else {
  90. run('git add package.json')
  91. }
  92. run('git add CHANGELOG.md')
  93. try {
  94. run(`git diff --cached --quiet || git commit -m "chore: release v${version}"`)
  95. } catch {
  96. console.log('⚠️ 没有需要提交的文件,跳过 commit')
  97. }
  98. // 打 tag & 推送
  99. run(`git tag v${version}`)
  100. run('git push && git push --tags')
  101. console.log('\x1b[36m=========================\x1b[0m')
  102. console.log('\x1b[32m🎉 发布成功!\x1b[0m')
  103. console.log(`\x1b[33m📦 版本号:v${version}\x1b[0m`)
  104. console.log('\x1b[35m⚙️ 部署:\x1b[0m')
  105. console.log('\x1b[34m * 测试环境:npm run deploy:test\x1b[0m')
  106. console.log('\x1b[34m * 开发环境:npm run build:dev\x1b[0m')
  107. console.log('\x1b[34m * 生产环境:npm run build\x1b[0m')
  108. console.log('\x1b[36m=========================\x1b[0m')
  109. }
  110. main()