All files / scripts/changelog index.ts

0% Statements 0/81
100% Branches 1/1
100% Functions 1/1
0% Lines 0/81

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107                                                                                                                                                                                                                     
import type { ParsedCommit, Tag } from '../git-utils/tags'
import path from 'node:path'
import process from 'node:process'
import chalk from 'chalk'
import { x } from 'tinyexec'
import { gitCommit } from '../git-utils/commit'
import { GitTagParser } from '../git-utils/tags'
import { detectMonorepo } from '../monorepo/detect'
import { findPackages } from '../monorepo/packages'
import { createFile } from '../utils/file'
 
/* eslint-disable unused-imports/no-unused-vars */
const commitTypes = [
  { value: 'feat', name: '🚀 Features: 新功能', emoji: '🚀' },
  { value: 'perf', name: '🔥 Performance: 性能优化', emoji: '🔥' },
  { value: 'fix', name: '🩹 Fixes: 缺陷修复', emoji: '🩹' },
  { value: 'refactor', name: '💅 Refactors: 代码重构', emoji: '💅' },
  { value: 'docs', name: '📖 Documentation: 文档', emoji: '📖' },
  { value: 'build', name: '📦 Build: 构建工具', emoji: '📦' },
  { value: 'types', name: '🌊 Types: 类型定义', emoji: '🌊' },
  { value: 'chore', name: '🏡 Chore: 简修处理', emoji: '🏡' },
  { value: 'examples', name: '🏀 Examples: 例子展示', emoji: '🏀' },
  { value: 'test', name: '✅ Tests: 测试用例', emoji: '✅' },
  { value: 'style', name: '🎨 Styles: 代码风格', emoji: '🎨' },
  { value: 'ci', name: '🤖 CI: 持续集成', emoji: '🤖' },
  { value: 'init', name: '🎉 Init: 项目初始化', emoji: '🎉' },
]
 
// 根据提交类型生成 changelog
export function generateChangelog(commits: ParsedCommit[], title = 'Changelog'): string {
  // 按时间排序
  const sortedCommits = commits.sort((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0))
 
  const changelog: string[] = [`## ${title}`]
 
  // 根据类型分类
  commitTypes.forEach(({ value, name, emoji }, index) => {
    const filteredCommits = sortedCommits.filter(commit => commit.type === value)
 
    if (filteredCommits.length > 0) {
      changelog.push(`\n### ${name}\n`)
      filteredCommits.forEach((commit) => {
        let sha = commit.sha ? `(${commit.sha})` : undefined
        sha = sha?.padStart(sha.length + 1, ' ') || ''
 
        let author = commit.author ? `- by @${commit.author}` : undefined
        author = author?.padStart(author.length + 1, ' ') || ''
 
        changelog.push(`- ${commit.raw}${author}${sha}`)
      })
    }
  })
 
  changelog.length && changelog.push(`${changelog.pop()}\n`) // 在最后添加换行
 
  return changelog.join('\n')
}
 
function getAllTags(tag: Tag): Tag[] {
  const tags: Tag[] = [tag] // 初始化一个包含当前标签的数组
  // 如果有 `pre` 标签,递归调用
  if (tag.pre) {
    tags.push(...getAllTags(tag.pre)) // 合并结果
  }
  return tags
}
 
/**
 * 重建整个项目的 changelog
 */
export async function resetChangelog() {
  const parser = new GitTagParser()
  await parser.fetchTags()
 
  console.log(chalk.blue('Checking monorepo structure...'))
  const monorepo = await detectMonorepo()
  if (!monorepo) {
    console.log(chalk.red('Not a pnpm monorepo project'))
    return process.exit(1)
  }
 
  const allPackages = await findPackages(monorepo)
  const publishable = allPackages.filter(p => !p.private)
 
  for (const pkg of publishable) {
    const currTag = `${pkg.name}@${pkg.version}`
    const prevTag = await parser.getPreviousTag(currTag, true, true)
    if (prevTag) {
      const tagsInfo = getAllTags(prevTag).reverse()
      const tags = [...tagsInfo.map(item => item.tag), currTag]
 
      const changelogs: string[] = []
      for (let index = 0; index < tags.length; index++) {
        const currentTag = tags[index]!
        const prevTag = tags[index - 1]
        const commits = await parser.getCommitsBetweenTags(currentTag, prevTag, true)
        changelogs.push(generateChangelog(commits, currentTag))
      }
      // TODO: 可以选择拆分更新日志至细分文件
      const changelogPath = createFile(path.join(pkg.path, 'changelog.md'), changelogs.reverse().join('\n'), { cover: true })
      await x('npx', ['eslint', '--fix', changelogPath])
      const commitMsg = `chore(changelog): ${pkg.name}@{${parser.extractScopeProjectVersion(tagsInfo[0]!.tag).version}..${pkg.version}}`
      await gitCommit([changelogPath], commitMsg)
    }
  }
}