Skip to content

Commit

Permalink
feat(changelog): 新增脚手架支持自动记录变更日志
Browse files Browse the repository at this point in the history
  • Loading branch information
mmdapl committed Jul 14, 2024
1 parent 7e9f87f commit d5a1a04
Show file tree
Hide file tree
Showing 14 changed files with 922 additions and 0 deletions.
145 changes: 145 additions & 0 deletions packages/changelog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# @142vip/changelog

根据git提交记录,自动生成changelog文档

[![NPM version](https://img.shields.io/npm/v/@142vip/changelog?color=a1b858&label=version)](https://www.npmjs.com/package/@142vip/changelog)

Generate changelog for GitHub releases from [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/),
powered by [changelogen](https://github.com/unjs/changelogen).

[👉 使用示例](https://github.com/unocss/unocss/releases/tag/v0.39.0)

## 新功能

- Support exclamation mark as breaking change, e.g. `chore!: drop node v10`
- Grouped scope in changelog
- Create the release note, or update the existing one
- List contributors

## 使用

### 生成CHANGELOG.md文档

```bash
# output参数可以配置,支持做本地文档更新
npx changelog --output CHANGELOG.md
```

### 配合Github Actions使用

```yml
# .github/workflows/release.yml

name: Release

permissions:
contents: write

on:
push:
tags:
- 'v*'

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

# 安装node版本,大于16
- uses: actions/setup-node@v3
with:
node-version: 16.x

# Github发布版本,并更新Release信息
- run: npx changelog
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
```
向 GitHub 推送以“v”开头的标签时,`github actions`会被触发。

在142vip所有的开源仓库中,都可以通过`@142vip/changelog`模块来实现发布,例如:

```yaml
# CD持续交付
# - 部署到Github Pages
# - 部署到Vercel托管平台
# - 发布新的Github Release
# 参考:https://v2.vuepress.vuejs.org/zh/guide/deployment.html#github-pages
#
name: CD
on:
push:
branches:
- next
workflow_dispatch:
jobs:
# 版本发布
release:
name: 创建Github发布
runs-on: ubuntu-latest
# 主库next且执行release更新时执行
if: github.repository == '142vip/core-x' && startsWith(github.event.head_commit.message, 'chore(release):')
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
token: ${{ secrets.TOKEN }}
persist-credentials: false
# “最近更新时间” 等 git 日志相关信息,需要拉取全部提交记录
fetch-depth: 0
# 安装node版本,大于16
- uses: actions/setup-node@v3
with:
node-version: 16.x
# Github发布版本,并更新Release信息
- run: npx changelog
env:
GITHUB_TOKEN: ${{secrets.TOKEN}}
# 提取版本号
- name: Get New Version Number
id: releaseVersion
run: |
echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
# 更新资源 区分压缩包上传
- name: Upload Resource Assets
uses: actions/upload-release-asset@latest
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ./142vip-oauth.zip
asset_name: 142vip-oauth.zip
asset_content_type: application/zip
```

## 配置

You can put a configuration file in the project root, named
as `changelogithub.config.{json,ts,js,mjs,cjs}`, `.changelogithubrc` or use the `changelogithub` field
in `package.json`.

## 本地预览y

```bash
# 只本地生成创建版本的URL
npx changelogithub --dry
```

## 感谢

- changelogen: <https://github.com/unjs/changelogen>
- changelogithub: <https://github.com/antfu/changelogithub>

## 证书
14 changes: 14 additions & 0 deletions packages/changelog/build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: [
'src/index',
'src/changelog',
],
declaration: true,
clean: true,
rollup: {
emitCJS: true,
inlineDependencies: true,
},
})
3 changes: 3 additions & 0 deletions packages/changelog/cli.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node
// eslint-disable-next-line antfu/no-import-dist
import './dist/changelog.mjs'
69 changes: 69 additions & 0 deletions packages/changelog/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"name": "@142vip/changelog",
"version": "0.0.1",
"private": false,
"type": "module",
"description": "公众号搜:储凡",
"author": "mmdapl <[email protected]>",
"license": "MIT",
"keywords": [
"公众号搜:储凡",
"142vip",
"@142vip/changelog",
"github",
"release",
"releases",
"conventional",
"changelog"
],
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"bin": "./cli.mjs",
"files": [
"*.mjs",
"dist"
],
"engines": {
"node": ">=16.0.0"
},
"scripts": {
"dev": "unbuild --stub",
"build": "unbuild",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@antfu/utils": "^0.7.10",
"c12": "^1.11.1",
"cac": "^6.7.14",
"changelogen": "0.5.5",
"convert-gitmoji": "^0.1.5",
"dayjs": "^1.11.11",
"execa": "^8.0.1",
"kolorist": "^1.8.0",
"ofetch": "^1.3.4",
"semver": "^7.6.2"
},
"devDependencies": {
"@antfu/eslint-config": "^2.22.0",
"@types/debug": "^4.1.12",
"@types/fs-extra": "^11.0.4",
"@types/minimist": "^1.2.5",
"@types/semver": "^7.5.8",
"bumpp": "^9.4.1",
"eslint": "^9.7.0",
"esno": "^4.7.0",
"fs-extra": "^11.2.0",
"typescript": "^5.5.3",
"unbuild": "^2.0.0",
"vitest": "^2.0.2"
}
}
100 changes: 100 additions & 0 deletions packages/changelog/src/changelog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env node

/* eslint-disable no-console */
import process from 'node:process'
import { blue, bold, cyan, dim, red, yellow } from 'kolorist'
import cac from 'cac'
import { version } from '../package.json'
import { generate, isRepoShallow, sendRelease, updateChangelog } from './index'

const cli = cac('changelog')

// 参数
cli
.version(version)
.option('-t, --tokens <path>', 'GitHub Token')
.option('--from <ref>', 'From tag')
.option('--to <ref>', 'To tag')
.option('--github <path>', 'GitHub Repository, e.g. @142vip/core-x')
.option('--name <name>', 'Name of the release')
.option('--prerelease', 'Mark release as prerelease')
.option('--output <path>', 'Output to file instead of sending to GitHub')
.option('--dry', 'Dry run')
.help()

// 命令
cli
.command('')
.action(async (args) => {
args.token = args.token || process.env.GITHUB_TOKEN

let webUrl = ''

try {
console.log()
console.log(dim(`${bold('@142vip/changelog')} `) + dim(`v${version}`))

const { config, markdown, commits } = await generate(args)
webUrl = `https://${config.baseUrl}/${config.repo}/releases/new?title=${encodeURIComponent(String(config.name || config.to))}&body=${encodeURIComponent(String(markdown))}&tag=${encodeURIComponent(String(config.to))}&prerelease=${config.prerelease}`

console.log(cyan(config.from) + dim(' -> ') + blue(config.to) + dim(` (${commits.length} commits)`))
console.log(dim('--------------'))
console.log()
console.log(markdown.replace(/&nbsp;/g, ''))
console.log()
console.log(dim('--------------'))

function printWebUrl() {
console.log()
console.error(yellow('使用以下链接手动发布新的版本:'))
console.error(yellow(webUrl))
console.log()
}

if (config.dry) {
console.log(yellow('试运行。已跳过版本发布。'))
printWebUrl()
return
}

// 更新changelog文档
if (typeof config.output === 'string') {
await updateChangelog(config.output, markdown, config.to)
return
}

// 带token上传
if (!config.tokens) {
console.error(red('未找到 GitHub 令牌,请通过 GITHUB_TOKEN 环境变量指定。已跳过版本发布。'))
printWebUrl()
return
}

if (!commits.length && await isRepoShallow()) {
console.error(yellow('存储库似乎克隆得很浅,这使得更改日志无法生成。您可能希望在 CI 配置中指定 \'fetch-depth: 0\'。'))
printWebUrl()
return
}

// 调用api 直接发布
await sendRelease(config, markdown)
}
catch (e: any) {
console.error(red(String(e)))
if (e?.stack)
console.error(dim(e.stack?.split('\n').slice(1).join('\n')))

// 手动执行,创建release
if (webUrl) {
console.log()
console.error(red('无法创建发布。使用以下链接手动创建它:'))
console.error(yellow(webUrl))
console.log()
}
}
finally {
process.exitCode = 1
}
})

cli.parse()
57 changes: 57 additions & 0 deletions packages/changelog/src/core/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import process from 'node:process'
import {
getCurrentGitBranch,
getFirstGitCommit,
getGitHubRepo,
getLastMatchingTag,
isPrerelease,
} from './git'
import type { ChangelogOptions, ResolvedChangelogOptions } from './types'

// const defaultOutput = 'CHANGELOG.md'
const defaultConfig: ChangelogOptions = {
scopeMap: {},
types: {
feat: { title: '✨ Features', semver: 'minor' },
perf: { title: '🔥 Performance', semver: 'patch' },
fix: { title: '🐛 Bug Fixes', semver: 'patch' },
refactor: { title: '💅 Refactors', semver: 'patch' },
docs: { title: '📖 Documentation', semver: 'patch' },
build: { title: '📦 Build', semver: 'patch' },
types: { title: '🌊 Types', semver: 'patch' },
},
titles: {
breakingChanges: '🚨 Breaking Changes',
},
tokens: {
github: process.env.GITHUB_TOKEN || process.env.TOKEN,
},
contributors: true,
capitalize: true,
group: true,
emoji: true,
// output: defaultOutput,
}

export async function resolveConfig(options: ChangelogOptions) {
const { loadConfig } = await import('c12')
const config = await loadConfig<ChangelogOptions>({
name: '@142vip/changelog',
defaults: defaultConfig,
overrides: options,
packageJson: '@142vip/changelog',
}).then(r => r.config || defaultConfig)

config.baseUrl = config.baseUrl ?? 'github.com'
config.baseUrlApi = config.baseUrlApi ?? 'api.github.com'
config.to = config.to || await getCurrentGitBranch()
config.from = config.from || await getLastMatchingTag(config.to) || await getFirstGitCommit()
// @ts-expect-error backward compatibility
config.repo = config.repo || config.github || await getGitHubRepo(config.baseUrl)
config.prerelease = config.prerelease ?? isPrerelease(config.to)

if (typeof config.repo !== 'string')
throw new Error(`Invalid GitHub repository, expected a string but got ${JSON.stringify(config.repo)}`)

return config as ResolvedChangelogOptions
}
Loading

0 comments on commit d5a1a04

Please sign in to comment.