diff --git a/packages/changelog/README.md b/packages/changelog/README.md index d3f2631..8c8abeb 100644 --- a/packages/changelog/README.md +++ b/packages/changelog/README.md @@ -138,18 +138,16 @@ jobs: ### 更多功能 ```shell -@142vip/changelog/0.0.1-alpha.1 -Usage: - $ @142vip/changelog +# cli 参数帮助 +npx changelog -h -Commands: +@142vip/changelog/0.0.1-alpha.1 -For more info, run any command with the `--help` flag: - $ @142vip/changelog --help +Usage: @142vip/changelog [options] Options: - -v, --version Display version number + -v --version Package Version -t, --tokens GitHub Token --from From tag --to To tag @@ -158,8 +156,8 @@ Options: --prerelease Mark release as prerelease --output Output to file instead of sending to GitHub --scopeName Package name in Monorepo,Match the scope in the git commit information - --dry Dry run - -h, --help Display this message + --dry Dry run (default: false) + -h, --help display help for command ``` ## 配置 diff --git a/packages/changelog/build.config.ts b/packages/changelog/build.config.ts index 04c8d0c..0f67978 100644 --- a/packages/changelog/build.config.ts +++ b/packages/changelog/build.config.ts @@ -1,10 +1,9 @@ import { defineBuildConfig } from 'unbuild' export default defineBuildConfig({ - // entries: [ - // 'src/index', - // 'src/changelog.ts', - // ], + entries: [ + 'src/index', + ], declaration: true, clean: true, rollup: { diff --git a/packages/changelog/cli.mjs b/packages/changelog/cli.mjs index 485d2b2..5937f04 100755 --- a/packages/changelog/cli.mjs +++ b/packages/changelog/cli.mjs @@ -1,3 +1,4 @@ +// eslint-disable-next-line antfu/no-import-dist import { changelogMain } from './dist/index.mjs' changelogMain() diff --git a/packages/changelog/src/changelog.ts b/packages/changelog/src/changelog.ts deleted file mode 100644 index b92a595..0000000 --- a/packages/changelog/src/changelog.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* eslint-disable no-console */ -import process from 'node:process' -import { blue, bold, cyan, dim, red, yellow } from 'kolorist' -import { Command } from 'commander' -import qs from 'qs' -import { name as packageName, version as packageVersion } from '../package.json' -import { generate, isRepoShallow, sendRelease, updateChangelog } from './core' - -/** - * cli 入口 - */ -export function changelogMain() { - const program = new Command(packageName) - - // 查看版本 - program.version(packageVersion, '-v --version', 'Package Version') - - // 参数 - program - .option('-t, --tokens ', 'GitHub Token') - .option('--from ', 'From tag') - .option('--to ', 'To tag') - .option('--github ', 'GitHub Repository, e.g. @142vip/core-x') - .option('--name ', 'Name of the release') - .option('--prerelease', 'Mark release as prerelease') - .option('--output ', 'Output to file instead of sending to GitHub') - .option('--scopeName ', 'Package name in Monorepo,Match the scope in the git commit information') - .option('--dry', 'Dry run', false) - .action(async (args) => { - args.token = args.token || process.env.GITHUB_TOKEN - - let webUrl = '' - - try { - console.log() - console.log(dim(`${bold('@142vip/changelog')} `) + dim(`v${packageVersion}`)) - - const { config, markdown, commits } = await generate(args) - webUrl = generateWebUrl(config, markdown) - - console.log(cyan(config.from) + dim(' -> ') + blue(config.to) + dim(` (${commits.length} commits)`)) - console.log(dim('--------------')) - console.log() - console.log(markdown.replace(/ /g, '')) - console.log() - console.log(dim('--------------')) - - if (config.dry) { - console.log(yellow('试运行。已跳过版本发布。')) - printUrl(webUrl) - return - } - - // 更新changelog文档 - if (typeof config.output === 'string') { - await updateChangelog(config.output, markdown, config.name) - return - } - - // 带token上传 - if (!config.tokens) { - console.error(red('未找到 GitHub 令牌,请通过 GITHUB_TOKEN 环境变量指定。已跳过版本发布。')) - printUrl(webUrl) - process.exitCode = 1 - return - } - - if (!commits.length && await isRepoShallow()) { - console.error(yellow('存储库似乎克隆得很浅,这使得更改日志无法生成。您可能希望在 CI 配置中指定 \'fetch-depth: 0\'。')) - printUrl(webUrl) - process.exitCode = 1 - 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() - } - process.exitCode = 1 - } - }) - - // 解析参数 - program.parse(process.argv) -} - -/** - * 生成webUrl链接 - */ -function generateWebUrl(config: any, markdown: string) { - const baseUrl = `https://${config.baseUrl}/${config.repo}/releases/new` - const queryParams = qs.stringify({ - title: config.name || config.to, - body: markdown, - tag: config.to, - prerelease: config.prerelease, - }) - // `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}` - return `${baseUrl}?${queryParams}` -} - -/** - * 打印手动发布地址 - * @param webUrl - */ -function printUrl(webUrl: string) { - console.log() - console.error(yellow('使用以下链接手动发布新的版本:')) - console.error(yellow(webUrl)) - console.log() -} diff --git a/packages/changelog/src/core/changelog.ts b/packages/changelog/src/core/changelog.ts new file mode 100644 index 0000000..21ac0c0 --- /dev/null +++ b/packages/changelog/src/core/changelog.ts @@ -0,0 +1,185 @@ +/* eslint-disable no-console */ +import process from 'node:process' +import { blue, bold, cyan, dim, red, yellow } from 'kolorist' +import { Command } from 'commander' +import type { GitCommit, RawGitCommit } from 'changelogen' +import { getGitDiff, parseGitCommit } from 'changelogen' +import { notNullish } from '@antfu/utils' +import { name as packageName, version as packageVersion } from '../../package.json' +import { + generateMarkdown, + generateWebUrl, + getCurrentGitBranch, + getFirstGitCommit, + getGitHubRepo, + getLastMatchingTag, + isPrerelease, + isRepoShallow, + printUrl, + resolveAuthors, + sendRelease, + updateChangelog, +} from '../utils' +import type { + ChangelogEnOptions, + ChangelogOptions, + ResolvedChangelogOptions, +} from '../types' +import { ChangelogDefaultConfig } from './config' + +/** + * 加载配置 + * 将用户自定义配置和默认配置合并 + * @param options + */ +async function resolveConfig(options: ChangelogOptions) { + const { loadConfig } = await import('c12') + const config = await loadConfig({ + // 配置文件名,eg: changelog.config.ts + name: 'changelog', + defaults: ChangelogDefaultConfig, + overrides: options, + // 在package.json中的配置关键字 + packageJson: packageName, + }).then(r => r.config || ChangelogDefaultConfig) + + config.baseUrl = config.baseUrl ?? 'github.com' + config.baseUrlApi = config.baseUrlApi ?? 'api.github.com' + // 发布的版本 + config.to = config.to || await getCurrentGitBranch() + // release name + config.name = config.name ?? config.to + 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) + + // todo 支持多个scope生成 + config.scopeName = options.scopeName + + if (typeof config.repo !== 'string') + throw new Error(`无效的 GitHub 存储库,需要一个字符串,但实际repo参数是: ${JSON.stringify(config.repo)}`) + + return config as ResolvedChangelogOptions +} + +/** + * 解析git commit信息 + * @param commits + * @param config + */ +function parseCommits(commits: RawGitCommit[], config: ChangelogEnOptions): GitCommit[] { + return commits.map(commit => parseGitCommit(commit, config)).filter(notNullish) +} + +/** + * 生成markdown文档信息 + * @param options + */ +async function generate(options: ChangelogOptions) { + const config = await resolveConfig(options) + + const rawCommits = await getGitDiff(config.from, config.to) + const commits = parseCommits(rawCommits, config) + + // 添加贡献者 + if (config.contributors) { + await resolveAuthors(commits, config) + } + // 生成文档 + const markdown = await generateMarkdown(commits, config) + + return { config, markdown, commits } +} + +async function dealChangelog(args: ChangelogOptions) { + args.token = args.token || process.env.GITHUB_TOKEN + + let webUrl = '' + + try { + console.log() + console.log(dim(`${bold('@142vip/changelog')} `) + dim(`v${packageVersion}`)) + + const { config, markdown, commits } = await generate(args) + webUrl = generateWebUrl(config, markdown) + + console.log(cyan(config.from) + dim(' -> ') + blue(config.to) + dim(` (${commits.length} commits)`)) + console.log(dim('--------------')) + console.log() + console.log(markdown.replace(/ /g, '')) + console.log() + console.log(dim('--------------')) + + if (config.dry) { + console.log(yellow('试运行。已跳过版本发布。')) + printUrl(webUrl) + return + } + + // 更新changelog文档 + if (typeof config.output === 'string') { + await updateChangelog(config.output, markdown, config.name) + return + } + + // 带token上传 + if (!config.tokens) { + console.error(red('未找到 GitHub 令牌,请通过 GITHUB_TOKEN 环境变量指定。已跳过版本发布。')) + printUrl(webUrl) + process.exitCode = 1 + return + } + + if (!commits.length && await isRepoShallow()) { + console.error(yellow('存储库似乎克隆得很浅,这使得更改日志无法生成。您可能希望在 CI 配置中指定 \'fetch-depth: 0\'。')) + printUrl(webUrl) + process.exitCode = 1 + 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) { + printUrl(webUrl, false) + } + process.exitCode = 1 + } +} + +/** + * cli 入口 + */ +export function changelogMain() { + const program = new Command(packageName) + + // 查看版本 + program.version(packageVersion, '-v --version', 'Package Version') + + // cli参数 + program + .option('-t, --tokens ', 'GitHub Token') + .option('--from ', 'From tag') + .option('--to ', 'To tag') + .option('--github ', 'GitHub Repository, e.g. @142vip/core-x') + .option('--name ', 'Name of the release') + .option('--prerelease', 'Mark release as prerelease') + .option('--output ', 'Output to file instead of sending to GitHub') + .option('--scopeName ', 'Package name in Monorepo,Match the scope in the git commit information') + .option('--dry', 'Dry run', false) + .action(async (args: ChangelogOptions) => { + await dealChangelog(args) + }) + + // 解析参数 + program.parse(process.argv) +} diff --git a/packages/changelog/src/core/config.ts b/packages/changelog/src/core/config.ts index 1774e2e..a86604c 100644 --- a/packages/changelog/src/core/config.ts +++ b/packages/changelog/src/core/config.ts @@ -1,15 +1,10 @@ import process from 'node:process' -import { name as packageName } from '../../package.json' -import { - getCurrentGitBranch, - getFirstGitCommit, - getGitHubRepo, - getLastMatchingTag, - isPrerelease, -} from './git' -import type { ChangelogOptions, ResolvedChangelogOptions } from './types' +import type { ChangelogOptions } from '../types/changelog.interface' -const defaultConfig: ChangelogOptions = { +/** + * 默认配置 + */ +export const ChangelogDefaultConfig: ChangelogOptions = { scopeMap: {}, types: { feat: { title: '✨ Features', semver: 'minor' }, @@ -35,41 +30,7 @@ const defaultConfig: ChangelogOptions = { /** * 定义@142vip/changelog模块的默认配置文件 - * @param config */ export function defineChangelogDefaultConfig(config: ChangelogOptions) { return config } - -export async function resolveConfig(options: ChangelogOptions) { - const { loadConfig } = await import('c12') - const config = await loadConfig({ - // 配置文件名,eg: changelog.config.ts - name: 'changelog', - defaults: defaultConfig, - overrides: options, - // 在package.json中的配置关键字 - packageJson: packageName, - }).then(r => r.config || defaultConfig) - - config.baseUrl = config.baseUrl ?? 'github.com' - config.baseUrlApi = config.baseUrlApi ?? 'api.github.com' - // 发布的版本 - config.to = config.to || await getCurrentGitBranch() - // release name - config.name = config.name ?? config.to - 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) - - // todo 支持多个scope生成 - config.scopeName = options.scopeName - - if (typeof config.repo !== 'string') - throw new Error(`无效的 GitHub 存储库,需要一个字符串,但实际repo参数是: ${JSON.stringify(config.repo)}`) - - return config as ResolvedChangelogOptions -} diff --git a/packages/changelog/src/core/generate.ts b/packages/changelog/src/core/generate.ts deleted file mode 100644 index b8ec05b..0000000 --- a/packages/changelog/src/core/generate.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { GitCommit, RawGitCommit } from 'changelogen' -import { getGitDiff, parseGitCommit } from 'changelogen' -import { notNullish } from '@antfu/utils' -import type { ChangelogEnOptions, ChangelogOptions } from './types' -import { generateMarkdown } from './markdown' -import { resolveConfig } from './config' -import { resolveAuthors } from './github' - -export async function generate(options: ChangelogOptions) { - const config = await resolveConfig(options) - - const rawCommits = await getGitDiff(config.from, config.to) - const commits = parseCommits(rawCommits, config) - - // 添加贡献者 - if (config.contributors) { - await resolveAuthors(commits, config) - } - // 生成文档 - const markdown = await generateMarkdown(commits, config) - - return { config, markdown, commits } -} - -/** - * 解析git commit信息 - * @param commits - * @param config - */ -export function parseCommits(commits: RawGitCommit[], config: ChangelogEnOptions): GitCommit[] { - return commits.map(commit => parseGitCommit(commit, config)).filter(notNullish) -} diff --git a/packages/changelog/src/core/index.ts b/packages/changelog/src/core/index.ts index f65552e..77c8d06 100644 --- a/packages/changelog/src/core/index.ts +++ b/packages/changelog/src/core/index.ts @@ -1,6 +1,2 @@ -export * from './types' -export * from './github' -export * from './git' -export * from './markdown' -export * from './generate' +export * from './changelog' export * from './config' diff --git a/packages/changelog/src/index.ts b/packages/changelog/src/index.ts index f2c5624..61d7d60 100644 --- a/packages/changelog/src/index.ts +++ b/packages/changelog/src/index.ts @@ -1,2 +1,2 @@ +export * from './types' export * from './core' -export * from './changelog' diff --git a/packages/changelog/src/core/types.ts b/packages/changelog/src/types/changelog.interface.ts similarity index 97% rename from packages/changelog/src/core/types.ts rename to packages/changelog/src/types/changelog.interface.ts index 61acb91..1aa272e 100644 --- a/packages/changelog/src/core/types.ts +++ b/packages/changelog/src/types/changelog.interface.ts @@ -75,6 +75,9 @@ export interface ChangelogOptions extends Partial { */ baseUrlApi?: string + /** + * git commit scope name + */ scopeName?: string } diff --git a/packages/changelog/src/types/index.ts b/packages/changelog/src/types/index.ts new file mode 100644 index 0000000..9d518ce --- /dev/null +++ b/packages/changelog/src/types/index.ts @@ -0,0 +1 @@ +export * from './changelog.interface' diff --git a/packages/changelog/src/core/git.ts b/packages/changelog/src/utils/git.ts similarity index 100% rename from packages/changelog/src/core/git.ts rename to packages/changelog/src/utils/git.ts diff --git a/packages/changelog/src/core/github.ts b/packages/changelog/src/utils/github.ts similarity index 73% rename from packages/changelog/src/core/github.ts rename to packages/changelog/src/utils/github.ts index 42d6206..fc6b872 100644 --- a/packages/changelog/src/core/github.ts +++ b/packages/changelog/src/utils/github.ts @@ -1,7 +1,12 @@ import { $fetch } from 'ofetch' -import { cyan, green } from 'kolorist' +import { cyan, green, red, yellow } from 'kolorist' import { notNullish } from '@antfu/utils' -import type { AuthorInfo, ChangelogOptions, Commit } from './types' +import qs from 'qs' +import type { + AuthorInfo, + ChangelogOptions, + Commit, +} from '../types/changelog.interface' export async function sendRelease( options: ChangelogOptions, @@ -128,6 +133,11 @@ export async function resolveAuthors(commits: Commit[], options: ChangelogOption }) } +/** + * 判断是否有tag + * @param tag + * @param options + */ export async function hasTagOnGitHub(tag: string, options: ChangelogOptions) { try { await $fetch(`https://${options.baseUrlApi}/repos/${options.repo}/git/ref/tags/${tag}`, { @@ -139,3 +149,33 @@ export async function hasTagOnGitHub(tag: string, options: ChangelogOptions) { return false } } + +/** + * 生成webUrl链接 + */ +export function generateWebUrl(config: any, markdown: string) { + const baseUrl = `https://${config.baseUrl}/${config.repo}/releases/new` + const queryParams = qs.stringify({ + title: config.name || config.to, + body: markdown, + tag: config.to, + prerelease: config.prerelease, + }) + // `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}` + return `${baseUrl}?${queryParams}` +} + +/** + * 打印手动发布地址 + * - 默认成功输出 + * @param webUrl + * @param success + */ +export function printUrl(webUrl: string, success: boolean = true) { + if (success) { + console.error(`\n${yellow('使用以下链接手动发布新的版本:')}\n${yellow(webUrl)}\n`) + } + else { + console.error(`\n${red('无法创建发布。使用以下链接手动创建:')}\n${yellow(webUrl)}\n`) + } +} diff --git a/packages/changelog/src/utils/index.ts b/packages/changelog/src/utils/index.ts new file mode 100644 index 0000000..8ff2d12 --- /dev/null +++ b/packages/changelog/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './github' +export * from './git' +export * from './markdown' diff --git a/packages/changelog/src/core/markdown.ts b/packages/changelog/src/utils/markdown.ts similarity index 98% rename from packages/changelog/src/core/markdown.ts rename to packages/changelog/src/utils/markdown.ts index fabff92..3b20815 100644 --- a/packages/changelog/src/core/markdown.ts +++ b/packages/changelog/src/utils/markdown.ts @@ -3,7 +3,7 @@ import { partition } from '@antfu/utils' import type { Reference } from 'changelogen' import { convert } from 'convert-gitmoji' import dayjs from 'dayjs' -import type { Commit, ResolvedChangelogOptions } from './types' +import type { Commit, ResolvedChangelogOptions } from '../types/changelog.interface' const emojisRE = /([\u2700-\u27BF\uE000-\uF8FF\u2011-\u26FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|\uD83E[\uDD10-\uDDFF])/g