release.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import inquirer from 'inquirer'
  2. import { execSync } from 'child_process'
  3. import fs from 'fs'
  4. // 运行命令
  5. const run = (cmd) => execSync(cmd, { stdio: 'inherit' })
  6. // Git 工作区是否干净
  7. const isGitClean = () => {
  8. const status = execSync('git status --porcelain').toString().trim()
  9. return status === ''
  10. }
  11. // 获取最新 tag
  12. const getLatestTag = () => {
  13. try {
  14. return execSync('git describe --tags --abbrev=0').toString().trim()
  15. } catch {
  16. return ''
  17. }
  18. }
  19. // 获取最新 tag 到现在的提交记录
  20. const getCommitsSinceTag = (tag) => {
  21. try {
  22. const range = tag ? `${tag}..HEAD` : 'HEAD'
  23. return execSync(`git log ${range} --pretty=format:%s`).toString().trim()
  24. } catch {
  25. return ''
  26. }
  27. }
  28. // 检查是否有符合 Angular 规范的提交
  29. const hasConventionalCommits = (log) => {
  30. return /^(feat|fix|docs|style|refactor|perf|test|chore)(\(.+\))?:/m.test(log)
  31. }
  32. // 1. 检查 git 工作区
  33. if (!isGitClean()) {
  34. console.error('❌ Git 工作区有未提交的改动,请先提交后再发版!')
  35. process.exit(1)
  36. }
  37. // 2. 检查提交记录
  38. const latestTag = getLatestTag()
  39. const commitLog = getCommitsSinceTag(latestTag)
  40. if (!hasConventionalCommits(commitLog)) {
  41. console.error('⚠️ 没有检测到符合 Angular 提交规范的提交,跳过 changelog 生成!')
  42. process.exit(1)
  43. }
  44. // 3. 选择发布模式
  45. const { mode } = await inquirer.prompt([
  46. {
  47. type: 'list',
  48. name: 'mode',
  49. message: '请选择发布模式:',
  50. choices: [
  51. { name: '正式发版(更新版本号 + 生成 tag + 推送)', value: 'release' },
  52. { name: '自测模式(仅生成 changelog,不推送)', value: 'test' },
  53. ],
  54. },
  55. ])
  56. // 4. 选择 changelog 生成方式
  57. const { changelogMode } = await inquirer.prompt([
  58. {
  59. type: 'list',
  60. name: 'changelogMode',
  61. message: '请选择 changelog 生成方式:',
  62. choices: [
  63. { name: '增量追加日志(推荐)', value: 'incremental' },
  64. { name: '全量生成日志(会覆盖原文件)', value: 'all' },
  65. ],
  66. },
  67. ])
  68. // 5. 生成 changelog
  69. if (changelogMode === 'all') {
  70. run('npx conventional-changelog -p angular -i CHANGELOG.md -s -r 0')
  71. } else {
  72. run('npx conventional-changelog -p angular -i CHANGELOG.md -s')
  73. }
  74. // 6. 检查 changelog 是否更新
  75. const changelogContent = fs.readFileSync('CHANGELOG.md', 'utf8')
  76. if (!changelogContent.includes(new Date().getFullYear().toString())) {
  77. console.error('⚠️ changelog 没有生成新的内容,请检查提交记录!')
  78. process.exit(1)
  79. }
  80. // 7. 如果是正式模式,选择版本号递增类型
  81. if (mode === 'release') {
  82. const { versionType } = await inquirer.prompt([
  83. {
  84. type: 'list',
  85. name: 'versionType',
  86. message: '请选择版本号递增类型:',
  87. choices: [
  88. { name: '补丁版本(patch)', value: 'patch' },
  89. { name: '小版本(minor)', value: 'minor' },
  90. { name: '大版本(major)', value: 'major' },
  91. ],
  92. },
  93. ])
  94. // 更新版本号 & 提交 tag
  95. run(`npm version ${versionType} -m "release: v%s"`)
  96. // 推送代码 & tag
  97. run('git push && git push --tags')
  98. console.log('✅ 正式发版完成!')
  99. } else {
  100. console.log('✅ 自测模式完成(未生成 tag / 未推送)')
  101. }