chore: init read-star monorepo
This commit is contained in:
11
packages/scripts/src/commands/changelog.ts
Normal file
11
packages/scripts/src/commands/changelog.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type { ChangelogOption } from '@soybeanjs/changelog'
|
||||
import { generateChangelog, generateTotalChangelog } from '@soybeanjs/changelog'
|
||||
|
||||
export async function genChangelog(options?: Partial<ChangelogOption>, total = false) {
|
||||
if (total) {
|
||||
await generateTotalChangelog(options)
|
||||
}
|
||||
else {
|
||||
await generateChangelog(options)
|
||||
}
|
||||
}
|
||||
5
packages/scripts/src/commands/cleanup.ts
Normal file
5
packages/scripts/src/commands/cleanup.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { rimraf } from 'rimraf'
|
||||
|
||||
export async function cleanup(paths: string[]) {
|
||||
await rimraf(paths, { glob: true })
|
||||
}
|
||||
85
packages/scripts/src/commands/git-commit.ts
Normal file
85
packages/scripts/src/commands/git-commit.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import type { Lang } from '../locales'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { prompt } from 'enquirer'
|
||||
import { locales } from '../locales'
|
||||
import { execCommand } from '../shared'
|
||||
|
||||
interface PromptObject {
|
||||
types: string
|
||||
scopes: string
|
||||
description: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Git commit with Conventional Commits standard
|
||||
*
|
||||
* @param lang
|
||||
*/
|
||||
export async function gitCommit(lang: Lang = 'en-us') {
|
||||
const { gitCommitMessages, gitCommitTypes, gitCommitScopes } = locales[lang]
|
||||
|
||||
const typesChoices = gitCommitTypes.map(([value, msg]) => {
|
||||
const nameWithSuffix = `${value}:`
|
||||
|
||||
const message = `${nameWithSuffix.padEnd(12)}${msg}`
|
||||
|
||||
return {
|
||||
name: value,
|
||||
message,
|
||||
}
|
||||
})
|
||||
|
||||
const scopesChoices = gitCommitScopes.map(([value, msg]) => ({
|
||||
name: value,
|
||||
message: `${value.padEnd(30)} (${msg})`,
|
||||
}))
|
||||
|
||||
const result = await prompt<PromptObject>([
|
||||
{
|
||||
name: 'types',
|
||||
type: 'select',
|
||||
message: gitCommitMessages.types,
|
||||
choices: typesChoices,
|
||||
},
|
||||
{
|
||||
name: 'scopes',
|
||||
type: 'select',
|
||||
message: gitCommitMessages.scopes,
|
||||
choices: scopesChoices,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
message: gitCommitMessages.description,
|
||||
},
|
||||
])
|
||||
|
||||
const breaking = result.description.startsWith('!') ? '!' : ''
|
||||
|
||||
const description = result.description.replace(/^!/, '').trim()
|
||||
|
||||
const commitMsg = `${result.types}(${result.scopes})${breaking}: ${description}`
|
||||
|
||||
await execCommand('git', ['commit', '-m', commitMsg], { stdio: 'inherit' })
|
||||
}
|
||||
|
||||
/** Git commit message verify */
|
||||
export async function gitCommitVerify(lang: Lang = 'en-us', ignores: RegExp[] = []) {
|
||||
const gitPath = await execCommand('git', ['rev-parse', '--show-toplevel'])
|
||||
|
||||
const gitMsgPath = path.join(gitPath, '.git', 'COMMIT_EDITMSG')
|
||||
|
||||
const commitMsg = readFileSync(gitMsgPath, 'utf8').trim()
|
||||
|
||||
if (ignores.some(regExp => regExp.test(commitMsg)))
|
||||
return
|
||||
|
||||
const REG_EXP = /(?<type>[a-z]+)(?:\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i
|
||||
|
||||
if (!REG_EXP.test(commitMsg)) {
|
||||
const errorMsg = locales[lang].gitCommitVerify
|
||||
|
||||
throw new Error(errorMsg)
|
||||
}
|
||||
}
|
||||
6
packages/scripts/src/commands/index.ts
Normal file
6
packages/scripts/src/commands/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './changelog'
|
||||
export * from './cleanup'
|
||||
export * from './git-commit'
|
||||
export * from './release'
|
||||
export * from './router'
|
||||
export * from './update-pkg'
|
||||
12
packages/scripts/src/commands/release.ts
Normal file
12
packages/scripts/src/commands/release.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { versionBump } from 'bumpp'
|
||||
|
||||
export async function release(execute = 'pnpm sa changelog', push = true) {
|
||||
await versionBump({
|
||||
files: ['**/package.json', '!**/node_modules'],
|
||||
execute,
|
||||
all: true,
|
||||
tag: true,
|
||||
commit: 'chore(projects): release v%s',
|
||||
push,
|
||||
})
|
||||
}
|
||||
91
packages/scripts/src/commands/router.ts
Normal file
91
packages/scripts/src/commands/router.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { existsSync, mkdirSync } from 'node:fs'
|
||||
import { writeFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { prompt } from 'enquirer'
|
||||
import { green, red } from 'kolorist'
|
||||
|
||||
interface PromptObject {
|
||||
routeName: string
|
||||
addRouteParams: boolean
|
||||
routeParams: string
|
||||
}
|
||||
|
||||
/** generate route */
|
||||
export async function generateRoute() {
|
||||
const result = await prompt<PromptObject>([
|
||||
{
|
||||
name: 'routeName',
|
||||
type: 'text',
|
||||
message: 'please enter route name',
|
||||
initial: 'demo-route_child',
|
||||
},
|
||||
{
|
||||
name: 'addRouteParams',
|
||||
type: 'confirm',
|
||||
message: 'add route params?',
|
||||
initial: false,
|
||||
},
|
||||
])
|
||||
|
||||
if (result.addRouteParams) {
|
||||
const answers = await prompt<PromptObject>({
|
||||
name: 'routeParams',
|
||||
type: 'text',
|
||||
message: 'please enter route params',
|
||||
initial: 'id',
|
||||
})
|
||||
|
||||
Object.assign(result, answers)
|
||||
}
|
||||
|
||||
const PAGE_DIR_NAME_PATTERN = /^[\w-]+[0-9a-z]$/i
|
||||
|
||||
if (!PAGE_DIR_NAME_PATTERN.test(result.routeName)) {
|
||||
throw new Error(`${red('route name is invalid, it only allow letters, numbers, "-" or "_"')}.
|
||||
For example:
|
||||
(1) one level route: ${green('demo-route')}
|
||||
(2) two level route: ${green('demo-route_child')}
|
||||
(3) multi level route: ${green('demo-route_child_child')}
|
||||
(4) group route: ${green('_ignore_demo-route')}'
|
||||
`)
|
||||
}
|
||||
|
||||
const PARAM_REG = /^\w+$/g
|
||||
|
||||
if (result.routeParams && !PARAM_REG.test(result.routeParams)) {
|
||||
throw new Error(red('route params is invalid, it only allow letters, numbers or "_".'))
|
||||
}
|
||||
|
||||
const cwd = process.cwd()
|
||||
|
||||
const [dir, ...rest] = result.routeName.split('_') as string[]
|
||||
|
||||
let routeDir = path.join(cwd, 'src', 'views', dir)
|
||||
|
||||
if (rest.length) {
|
||||
routeDir = path.join(routeDir, rest.join('_'))
|
||||
}
|
||||
|
||||
if (!existsSync(routeDir)) {
|
||||
mkdirSync(routeDir, { recursive: true })
|
||||
}
|
||||
else {
|
||||
throw new Error(red('route already exists'))
|
||||
}
|
||||
|
||||
const fileName = result.routeParams ? `[${result.routeParams}].vue` : 'index.vue'
|
||||
|
||||
const vueTemplate = `<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>${result.routeName}</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
`
|
||||
|
||||
const filePath = path.join(routeDir, fileName)
|
||||
|
||||
await writeFile(filePath, vueTemplate)
|
||||
}
|
||||
5
packages/scripts/src/commands/update-pkg.ts
Normal file
5
packages/scripts/src/commands/update-pkg.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { execCommand } from '../shared'
|
||||
|
||||
export async function updatePkg(args: string[] = ['--deep', '-u']) {
|
||||
execCommand('npx', ['npm-check-updates', ...args], { stdio: 'inherit' })
|
||||
}
|
||||
39
packages/scripts/src/config/index.ts
Normal file
39
packages/scripts/src/config/index.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import type { CliOption } from '../types'
|
||||
import process from 'node:process'
|
||||
import { loadConfig } from 'c12'
|
||||
|
||||
const defaultOptions: CliOption = {
|
||||
cwd: process.cwd(),
|
||||
cleanupDirs: [
|
||||
'**/dist',
|
||||
'**/package-lock.json',
|
||||
'**/yarn.lock',
|
||||
'**/pnpm-lock.yaml',
|
||||
'**/node_modules',
|
||||
'!node_modules/**',
|
||||
],
|
||||
ncuCommandArgs: ['--deep', '-u'],
|
||||
changelogOptions: {},
|
||||
gitCommitVerifyIgnores: [
|
||||
/^((Merge pull request)|(Merge (.*?) into (.*)|(Merge branch (.*)))(?:\r?\n)*$)/m,
|
||||
/^(Merge tag (.*))(?:\r?\n)*$/m,
|
||||
/^(R|r)evert (.*)/,
|
||||
/^(amend|fixup|squash)!/,
|
||||
/^(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))/,
|
||||
/^Merge remote-tracking branch(\s*)(.*)/,
|
||||
/^Automatic merge(.*)/,
|
||||
/^Auto-merged (.*?) into (.*)/,
|
||||
],
|
||||
}
|
||||
|
||||
export async function loadCliOptions(overrides?: Partial<CliOption>, cwd = process.cwd()) {
|
||||
const { config } = await loadConfig<Partial<CliOption>>({
|
||||
name: 'soybean',
|
||||
defaults: defaultOptions,
|
||||
overrides,
|
||||
cwd,
|
||||
packageJson: true,
|
||||
})
|
||||
|
||||
return config as CliOption
|
||||
}
|
||||
109
packages/scripts/src/index.ts
Executable file
109
packages/scripts/src/index.ts
Executable file
@ -0,0 +1,109 @@
|
||||
import type { Lang } from './locales'
|
||||
import cac from 'cac'
|
||||
import { blue, lightGreen } from 'kolorist'
|
||||
import { version } from '../package.json'
|
||||
import { cleanup, genChangelog, generateRoute, gitCommit, gitCommitVerify, release, updatePkg } from './commands'
|
||||
import { loadCliOptions } from './config'
|
||||
|
||||
type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'changelog' | 'release' | 'gen-route'
|
||||
|
||||
type CommandAction<A extends object> = (args?: A) => Promise<void> | void
|
||||
|
||||
type CommandWithAction<A extends object = object> = Record<Command, { desc: string, action: CommandAction<A> }>
|
||||
|
||||
interface CommandArg {
|
||||
/** Execute additional command after bumping and before git commit. Defaults to 'pnpm sa changelog' */
|
||||
execute?: string
|
||||
/** Indicates whether to push the git commit and tag. Defaults to true */
|
||||
push?: boolean
|
||||
/** Generate changelog by total tags */
|
||||
total?: boolean
|
||||
/**
|
||||
* The glob pattern of dirs to clean up
|
||||
*
|
||||
* If not set, it will use the default value
|
||||
*
|
||||
* Multiple values use "," to separate them
|
||||
*/
|
||||
cleanupDir?: string
|
||||
/**
|
||||
* display lang of cli
|
||||
*
|
||||
* @default 'en-us'
|
||||
*/
|
||||
lang?: Lang
|
||||
}
|
||||
|
||||
export async function setupCli() {
|
||||
const cliOptions = await loadCliOptions()
|
||||
|
||||
const cli = cac(blue('reader-star'))
|
||||
|
||||
cli
|
||||
.version(lightGreen(version))
|
||||
.option(
|
||||
'-e, --execute [command]',
|
||||
'Execute additional command after bumping and before git commit. Defaults to \'npx soy changelog\'',
|
||||
)
|
||||
.option('-p, --push', 'Indicates whether to push the git commit and tag')
|
||||
.option('-t, --total', 'Generate changelog by total tags')
|
||||
.option(
|
||||
'-c, --cleanupDir <dir>',
|
||||
'The glob pattern of dirs to cleanup, If not set, it will use the default value, Multiple values use "," to separate them',
|
||||
)
|
||||
.option('-l, --lang <lang>', 'display lang of cli', { default: 'en-us', type: [String] })
|
||||
.help()
|
||||
|
||||
const commands: CommandWithAction<CommandArg> = {
|
||||
'cleanup': {
|
||||
desc: 'delete dirs: node_modules, dist, etc.',
|
||||
action: async () => {
|
||||
await cleanup(cliOptions.cleanupDirs)
|
||||
},
|
||||
},
|
||||
'update-pkg': {
|
||||
desc: 'update package.json dependencies versions',
|
||||
action: async () => {
|
||||
await updatePkg(cliOptions.ncuCommandArgs)
|
||||
},
|
||||
},
|
||||
'git-commit': {
|
||||
desc: 'git commit, generate commit message which match Conventional Commits standard',
|
||||
action: async (args) => {
|
||||
await gitCommit(args?.lang)
|
||||
},
|
||||
},
|
||||
'git-commit-verify': {
|
||||
desc: 'verify git commit message, make sure it match Conventional Commits standard',
|
||||
action: async (args) => {
|
||||
await gitCommitVerify(args?.lang, cliOptions.gitCommitVerifyIgnores)
|
||||
},
|
||||
},
|
||||
'changelog': {
|
||||
desc: 'generate changelog',
|
||||
action: async (args) => {
|
||||
await genChangelog(cliOptions.changelogOptions, args?.total)
|
||||
},
|
||||
},
|
||||
'release': {
|
||||
desc: 'release: update version, generate changelog, commit code',
|
||||
action: async (args) => {
|
||||
await release(args?.execute, args?.push)
|
||||
},
|
||||
},
|
||||
'gen-route': {
|
||||
desc: 'generate route',
|
||||
action: async () => {
|
||||
await generateRoute()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for (const [command, { desc, action }] of Object.entries(commands)) {
|
||||
cli.command(command, lightGreen(desc)).action(action)
|
||||
}
|
||||
|
||||
cli.parse()
|
||||
}
|
||||
|
||||
setupCli()
|
||||
82
packages/scripts/src/locales/index.ts
Normal file
82
packages/scripts/src/locales/index.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { bgRed, green, red, yellow } from 'kolorist'
|
||||
|
||||
export type Lang = 'zh-cn' | 'en-us'
|
||||
|
||||
export const locales = {
|
||||
'zh-cn': {
|
||||
gitCommitMessages: {
|
||||
types: '请选择提交类型',
|
||||
scopes: '请选择提交范围',
|
||||
description: `请输入描述信息(${yellow('!')}开头表示破坏性改动`,
|
||||
},
|
||||
gitCommitTypes: [
|
||||
['feat', '新功能'],
|
||||
['feat-wip', '开发中的功能,比如某功能的部分代码'],
|
||||
['fix', '修复Bug'],
|
||||
['docs', '只涉及文档更新'],
|
||||
['typo', '代码或文档勘误,比如错误拼写'],
|
||||
['style', '修改代码风格,不影响代码含义的变更'],
|
||||
['refactor', '代码重构,既不修复 bug 也不添加功能的代码变更'],
|
||||
['perf', '可提高性能的代码更改'],
|
||||
['optimize', '优化代码质量的代码更改'],
|
||||
['test', '添加缺失的测试或更正现有测试'],
|
||||
['build', '影响构建系统或外部依赖项的更改'],
|
||||
['ci', '对 CI 配置文件和脚本的更改'],
|
||||
['chore', '没有修改src或测试文件的其他变更'],
|
||||
['revert', '还原先前的提交'],
|
||||
] as [string, string][],
|
||||
gitCommitScopes: [
|
||||
['projects', '项目'],
|
||||
['packages', '包'],
|
||||
['components', '组件'],
|
||||
['hooks', '钩子函数'],
|
||||
['utils', '工具函数'],
|
||||
['types', 'TS类型声明'],
|
||||
['styles', '代码风格'],
|
||||
['deps', '项目依赖'],
|
||||
['release', '发布项目新版本'],
|
||||
['other', '其他的变更'],
|
||||
] as [string, string][],
|
||||
gitCommitVerify: `${bgRed(' 错误 ')} ${red('git 提交信息必须符合 Conventional Commits 标准!')}\n\n${green(
|
||||
'推荐使用命令 `pnpm commit` 生成符合 Conventional Commits 标准的提交信息。\n获取有关 Conventional Commits 的更多信息,请访问此链接: https://conventionalcommits.org',
|
||||
)}`,
|
||||
},
|
||||
'en-us': {
|
||||
gitCommitMessages: {
|
||||
types: 'Please select a type',
|
||||
scopes: 'Please select a scope',
|
||||
description: `Please enter a description (add prefix ${yellow('!')} to indicate breaking change)`,
|
||||
},
|
||||
gitCommitTypes: [
|
||||
['feat', 'A new feature'],
|
||||
['feat-wip', 'Features in development, such as partial code for a certain feature'],
|
||||
['fix', 'A bug fix'],
|
||||
['docs', 'Documentation only changes'],
|
||||
['typo', 'Code or document corrections, such as spelling errors'],
|
||||
['style', 'Changes that do not affect the meaning of the code'],
|
||||
['refactor', 'A code change that neither fixes a bug nor adds a feature'],
|
||||
['perf', 'A code change that improves performance'],
|
||||
['optimize', 'A code change that optimizes code quality'],
|
||||
['test', 'Adding missing tests or correcting existing tests'],
|
||||
['build', 'Changes that affect the build system or external dependencies'],
|
||||
['ci', 'Changes to our CI configuration files and scripts'],
|
||||
['chore', 'Other changes that don\'t modify src or test files'],
|
||||
['revert', 'Reverts a previous commit'],
|
||||
] as [string, string][],
|
||||
gitCommitScopes: [
|
||||
['projects', 'project'],
|
||||
['packages', 'packages'],
|
||||
['components', 'components'],
|
||||
['hooks', 'hook functions'],
|
||||
['utils', 'utils functions'],
|
||||
['types', 'TS declaration'],
|
||||
['styles', 'style'],
|
||||
['deps', 'project dependencies'],
|
||||
['release', 'release project'],
|
||||
['other', 'other changes'],
|
||||
] as [string, string][],
|
||||
gitCommitVerify: `${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green(
|
||||
'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org',
|
||||
)}`,
|
||||
},
|
||||
} satisfies Record<Lang, Record<string, unknown>>
|
||||
7
packages/scripts/src/shared/index.ts
Normal file
7
packages/scripts/src/shared/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { Options } from 'execa'
|
||||
|
||||
export async function execCommand(cmd: string, args: string[], options?: Options) {
|
||||
const { execa } = await import('execa')
|
||||
const res = await execa(cmd, args, options)
|
||||
return (res?.stdout as string)?.trim() || ''
|
||||
}
|
||||
31
packages/scripts/src/types/index.ts
Normal file
31
packages/scripts/src/types/index.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type { ChangelogOption } from '@soybeanjs/changelog'
|
||||
|
||||
export interface CliOption {
|
||||
/** The project root directory */
|
||||
cwd: string
|
||||
/**
|
||||
* Cleanup dirs
|
||||
*
|
||||
* Glob pattern syntax {@link https://github.com/isaacs/minimatch}
|
||||
*
|
||||
* @default
|
||||
* ```json
|
||||
* ["** /dist", "** /pnpm-lock.yaml", "** /node_modules", "!node_modules/**"]
|
||||
* ```
|
||||
*/
|
||||
cleanupDirs: string[]
|
||||
/**
|
||||
* Npm-check-updates command args
|
||||
*
|
||||
* @default ['--deep', '-u']
|
||||
*/
|
||||
ncuCommandArgs: string[]
|
||||
/**
|
||||
* Options of generate changelog
|
||||
*
|
||||
* @link https://github.com/soybeanjs/changelog
|
||||
*/
|
||||
changelogOptions: Partial<ChangelogOption>
|
||||
/** The ignore pattern list of git commit verify */
|
||||
gitCommitVerifyIgnores: RegExp[]
|
||||
}
|
||||
Reference in New Issue
Block a user