diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 8a8bc58c..23f63a5f 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -13,12 +13,16 @@ } }, "devDependencies": { + "@soybeanjs/changelog": "0.3.12", + "@types/prompts": "2.4.9", + "bumpp": "9.3.0", "c12": "1.6.1", "cac": "6.7.14", "consola": "3.2.3", - "enquirer": "2.4.1", "execa": "8.0.1", + "kolorist": "1.8.0", "npm-check-updates": "16.14.12", + "prompts": "2.4.2", "rimraf": "5.0.5" } } diff --git a/packages/scripts/src/commands/changelog.ts b/packages/scripts/src/commands/changelog.ts new file mode 100644 index 00000000..e01ce401 --- /dev/null +++ b/packages/scripts/src/commands/changelog.ts @@ -0,0 +1,10 @@ +import { generateChangelog, generateTotalChangelog } from '@soybeanjs/changelog'; +import type { ChangelogOption } from '@soybeanjs/changelog'; + +export async function genChangelog(options?: Partial, total = false) { + if (total) { + await generateTotalChangelog(options); + } else { + await generateChangelog(options); + } +} diff --git a/packages/scripts/src/commands/git-commit.ts b/packages/scripts/src/commands/git-commit.ts index 27c19dab..2e1f6095 100644 --- a/packages/scripts/src/commands/git-commit.ts +++ b/packages/scripts/src/commands/git-commit.ts @@ -1,37 +1,37 @@ import path from 'node:path'; import { readFileSync } from 'node:fs'; -import enquirer from 'enquirer'; -import { bgRed, green, red } from 'kolorist'; +import prompts from 'prompts'; +import { bgRed, green, red, yellow } from 'kolorist'; import { execCommand } from '../shared'; import type { CliOption } from '../types'; -interface PromptObject { - types: string; - scopes: string; - description: string; -} - +/** + * Git commit with Conventional Commits standard + * + * @param gitCommitTypes + * @param gitCommitScopes + */ export async function gitCommit( gitCommitTypes: CliOption['gitCommitTypes'], gitCommitScopes: CliOption['gitCommitScopes'] ) { - const typesChoices = gitCommitTypes.map(([name, title]) => { - const nameWithSuffix = `${name}:`; + const typesChoices = gitCommitTypes.map(([value, msg]) => { + const nameWithSuffix = `${value}:`; - const message = `${nameWithSuffix.padEnd(12)}${title}`; + const title = `${nameWithSuffix.padEnd(12)}${msg}`; return { - name, - message + value, + title }; }); - const scopesChoices = gitCommitScopes.map(([name, title]) => ({ - name, - message: `${name.padEnd(30)} (${title})` + const scopesChoices = gitCommitScopes.map(([value, msg]) => ({ + value, + title: `${value.padEnd(30)} (${msg})` })); - const result = await enquirer.prompt([ + const result = await prompts([ { name: 'types', type: 'select', @@ -47,15 +47,20 @@ export async function gitCommit( { name: 'description', type: 'text', - message: 'Please enter a description' + message: `Please enter a description (add prefix ${yellow('!')} to indicate breaking change)` } ]); - const commitMsg = `${result.types}(${result.scopes}): ${result.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() { const gitPath = await execCommand('git', ['rev-parse', '--show-toplevel']); @@ -63,7 +68,7 @@ export async function gitCommitVerify() { const commitMsg = readFileSync(gitMsgPath, 'utf8').trim(); - const REG_EXP = /(?[a-z]+)(\((?.+)\))?(?!)?: (?.+)/i; + const REG_EXP = /(?[a-z]+)(?:\((?.+)\))?(?!)?: (?.+)/i; if (!REG_EXP.test(commitMsg)) { throw new Error( diff --git a/packages/scripts/src/commands/index.ts b/packages/scripts/src/commands/index.ts index 90848324..78815bc5 100644 --- a/packages/scripts/src/commands/index.ts +++ b/packages/scripts/src/commands/index.ts @@ -1,3 +1,5 @@ export * from './git-commit'; export * from './cleanup'; export * from './update-pkg'; +export * from './changelog'; +export * from './release'; diff --git a/packages/scripts/src/commands/release.ts b/packages/scripts/src/commands/release.ts new file mode 100644 index 00000000..1cf3bc3b --- /dev/null +++ b/packages/scripts/src/commands/release.ts @@ -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 + }); +} diff --git a/packages/scripts/src/config/index.ts b/packages/scripts/src/config/index.ts index 428bc00c..e5978d14 100644 --- a/packages/scripts/src/config/index.ts +++ b/packages/scripts/src/config/index.ts @@ -2,8 +2,6 @@ import process from 'node:process'; import { loadConfig } from 'c12'; import type { CliOption } from '../types'; -const eslintExt = '*.{js,jsx,mjs,cjs,ts,tsx,vue}'; - const defaultOptions: CliOption = { cwd: process.cwd(), cleanupDirs: [ @@ -39,27 +37,7 @@ const defaultOptions: CliOption = { ['other', 'other changes'] ], ncuCommandArgs: ['--deep', '-u'], - prettierWriteGlob: [ - `!**/${eslintExt}`, - '!*.min.*', - '!CHANGELOG.md', - '!dist', - '!LICENSE*', - '!output', - '!coverage', - '!public', - '!temp', - '!package-lock.json', - '!pnpm-lock.yaml', - '!yarn.lock', - '!.github', - '!__snapshots__', - '!node_modules' - ], - lintStagedConfig: { - [eslintExt]: 'eslint --fix', - '*': 'sa prettier-write' - } + changelogOptions: {} }; export async function loadCliOptions(overrides?: Partial, cwd = process.cwd()) { diff --git a/packages/scripts/src/index.ts b/packages/scripts/src/index.ts index e72df4da..70048fb1 100755 --- a/packages/scripts/src/index.ts +++ b/packages/scripts/src/index.ts @@ -1,25 +1,50 @@ import cac from 'cac'; import { blue, lightGreen } from 'kolorist'; import { version } from '../package.json'; -import { cleanup, gitCommit, gitCommitVerify, updatePkg } from './commands'; +import { cleanup, genChangelog, gitCommit, gitCommitVerify, release, updatePkg } from './commands'; import { loadCliOptions } from './config'; -type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify'; +type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'changelog' | 'release'; type CommandAction = (args?: A) => Promise | void; type CommandWithAction = Record }>; interface CommandArg { + /** Execute additional command after bumping and before git commit. Defaults to 'npx soy 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 cleanup + * + * If not set, it will use the default value + * + * Multiple values use "," to separate them + */ + cleanupDir?: string; } export async function setupCli() { const cliOptions = await loadCliOptions(); - const cli = cac(blue('soybean')); + const cli = cac(blue('soybean-admin')); - cli.version(lightGreen(version)).help(); + 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 ', + 'The glob pattern of dirs to cleanup, If not set, it will use the default value, Multiple values use "," to separate them' + ) + .help(); const commands: CommandWithAction = { cleanup: { @@ -45,6 +70,18 @@ export async function setupCli() { action: async () => { await gitCommitVerify(); } + }, + 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); + } } }; diff --git a/packages/scripts/src/types/index.ts b/packages/scripts/src/types/index.ts index 2bbb2e55..598760ef 100644 --- a/packages/scripts/src/types/index.ts +++ b/packages/scripts/src/types/index.ts @@ -1,3 +1,5 @@ +import type { ChangelogOption } from '@soybeanjs/changelog'; + export interface CliOption { /** The project root directory */ cwd: string; @@ -23,11 +25,9 @@ export interface CliOption { */ ncuCommandArgs: string[]; /** - * Prettier write glob + * Options of generate changelog * - * Glob pattern syntax {@link https://github.com/micromatch/micromatch} + * @link https://github.com/soybeanjs/changelog */ - prettierWriteGlob: string[]; - /** Lint-staged config */ - lintStagedConfig: Record; + changelogOptions: Partial; } diff --git a/packages/scripts/typings/pkg.d.ts b/packages/scripts/typings/pkg.d.ts deleted file mode 100644 index 79049453..00000000 --- a/packages/scripts/typings/pkg.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -declare module 'lint-staged' { - interface LintStagedOptions { - config?: Record; - allowEmpty?: boolean; - } - - type LintStagedFn = (options: LintStagedOptions) => Promise; - interface LintStaged extends LintStagedFn { - default: LintStagedFn; - } - - const lintStaged: LintStaged; - - export default lintStaged; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1827c23..e3491d10 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -217,6 +217,15 @@ importers: packages/scripts: devDependencies: + '@soybeanjs/changelog': + specifier: 0.3.12 + version: 0.3.12(eslint@8.56.0)(typescript@5.3.3) + '@types/prompts': + specifier: 2.4.9 + version: 2.4.9 + bumpp: + specifier: 9.3.0 + version: 9.3.0 c12: specifier: 1.6.1 version: 1.6.1 @@ -226,15 +235,18 @@ importers: consola: specifier: 3.2.3 version: 3.2.3 - enquirer: - specifier: 2.4.1 - version: 2.4.1 execa: specifier: 8.0.1 version: 8.0.1 + kolorist: + specifier: 1.8.0 + version: 1.8.0 npm-check-updates: specifier: 16.14.12 version: 16.14.12 + prompts: + specifier: 2.4.2 + version: 2.4.2 rimraf: specifier: 5.0.5 version: 5.0.5 @@ -1591,6 +1603,38 @@ packages: - vue-eslint-parser dev: true + /@soybeanjs/changelog@0.3.12(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-pqxINyDJo8Z9DHffrus2lu8U1huN0a11OSgzQrYJtMFLz3LXW3uJM3+bkuf4s4MzS6DfSsKtf365dq19KHzRfw==} + engines: {node: '>=14'} + dependencies: + '@soybeanjs/eslint-config': 1.1.7(eslint@8.56.0)(typescript@5.3.3) + cli-progress: 3.12.0 + convert-gitmoji: 0.1.3 + dayjs: 1.11.10 + execa: 8.0.1 + ofetch: 1.3.3 + transitivePeerDependencies: + - '@toml-tools/parser' + - '@types/eslint' + - eslint + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - eslint-plugin-astro + - eslint-plugin-react + - eslint-plugin-react-hooks + - eslint-plugin-react-native + - eslint-plugin-react-refresh + - eslint-plugin-solid + - eslint-plugin-svelte + - eslint-plugin-vue + - prettier-plugin-astro + - prettier-plugin-svelte + - prettier-plugin-toml + - supports-color + - typescript + - vue-eslint-parser + dev: true + /@soybeanjs/cli@1.0.2(eslint-plugin-vue@9.20.1)(eslint@8.56.0)(typescript@5.3.3)(vue-eslint-parser@9.4.0): resolution: {integrity: sha512-l+fmIYAhw4H+8SA6fujZb7SaIvRdwjp/7X4W4PHfKuEkjSlazRhTzc89UEMyS8YDdmkSVyGU3QdGKVDt4yIpaw==} hasBin: true @@ -1704,6 +1748,79 @@ packages: - supports-color dev: true + /@soybeanjs/eslint-config@1.1.7(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-CeKvYyRA+SGQJPJJJvjD0ajVesLQEhRhiQOzU3EUD1nSwoNO/n0E0TjZCUquF1UE9pcQrcUzf9/NKkmzJ27xyA==} + peerDependencies: + '@toml-tools/parser': '*' + eslint: '>=8.40.0' + eslint-plugin-astro: '>=0.30.0' + eslint-plugin-react: '>=7.0.0' + eslint-plugin-react-hooks: '>=4.0.0' + eslint-plugin-react-native: '>=4.0.0' + eslint-plugin-react-refresh: '>=0.4.0' + eslint-plugin-solid: '>=0.10.0' + eslint-plugin-svelte: '>=2.0.0' + eslint-plugin-vue: '>=9.19.0' + prettier-plugin-astro: '>=0.12.0' + prettier-plugin-svelte: '>=3.0.0' + prettier-plugin-toml: '>=2.0.0' + typescript: '>=5.0.0' + vue-eslint-parser: '>=9.3.2' + peerDependenciesMeta: + '@toml-tools/parser': + optional: true + eslint-plugin-astro: + optional: true + eslint-plugin-react: + optional: true + eslint-plugin-react-hooks: + optional: true + eslint-plugin-react-native: + optional: true + eslint-plugin-react-refresh: + optional: true + eslint-plugin-solid: + optional: true + eslint-plugin-svelte: + optional: true + eslint-plugin-vue: + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-svelte: + optional: true + prettier-plugin-toml: + optional: true + vue-eslint-parser: + optional: true + dependencies: + '@antfu/eslint-define-config': 1.23.0-2 + '@antfu/install-pkg': 0.3.1 + '@eslint/eslintrc': 3.0.0 + '@eslint/js': 8.56.0 + '@typescript-eslint/eslint-plugin': 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + eslint-config-prettier: 9.1.0(eslint@8.56.0) + eslint-parser-plain: 0.1.0 + eslint-plugin-i: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0) + eslint-plugin-n: 16.6.2(eslint@8.56.0) + eslint-plugin-prettier: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.4) + eslint-plugin-unicorn: 50.0.1(eslint@8.56.0) + globals: 13.24.0 + local-pkg: 0.5.0 + prettier: 3.2.4 + prettier-plugin-jsdoc: 1.3.0(prettier@3.2.4) + prettier-plugin-json-sort: 0.0.2(prettier@3.2.4) + prompts: 2.4.2 + typescript: 5.3.3 + transitivePeerDependencies: + - '@types/eslint' + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + /@szmarczak/http-timer@5.0.1: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -1811,6 +1928,13 @@ packages: resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} dev: true + /@types/prompts@2.4.9: + resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} + dependencies: + '@types/node': 20.11.5 + kleur: 3.0.3 + dev: true + /@types/semver@7.5.6: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} dev: true @@ -2747,6 +2871,20 @@ packages: semver: 7.5.4 dev: true + /bumpp@9.3.0: + resolution: {integrity: sha512-P46VikoEZadYCqx7mbClKlaJnOyvc+JfRJPRf1YwlOjwqeYmutgFe1w9hvfXe819VhpU0N0TNXtxyVAUlAgaNA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jsdevtools/ez-spawn': 3.0.4 + c12: 1.6.1 + cac: 6.7.14 + fast-glob: 3.3.2 + js-yaml: 4.1.0 + prompts: 2.4.2 + semver: 7.5.4 + dev: true + /bundle-name@3.0.0: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} @@ -3869,6 +4007,27 @@ packages: synckit: 0.8.8 dev: true + /eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.4): + resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.56.0 + eslint-config-prettier: 9.1.0(eslint@8.56.0) + prettier: 3.2.4 + prettier-linter-helpers: 1.0.0 + synckit: 0.8.8 + dev: true + /eslint-plugin-unicorn@50.0.1(eslint@8.56.0): resolution: {integrity: sha512-KxenCZxqSYW0GWHH18okDlOQcpezcitm5aOSz6EnobyJ6BIByiPDviQRjJIUAjG/tMN11958MxaQ+qCoU6lfDA==} engines: {node: '>=16'} @@ -6886,6 +7045,20 @@ packages: - supports-color dev: true + /prettier-plugin-jsdoc@1.3.0(prettier@3.2.4): + resolution: {integrity: sha512-cQm8xIa0fN9ieJFMXACQd6JPycl+8ouOijAqUqu44EF/s4fXL3Wi9sKXuEaodsEWgCN42Xby/bNhqgM1iWx4uw==} + engines: {node: '>=14.13.1 || >=16.0.0'} + peerDependencies: + prettier: ^3.0.0 + dependencies: + binary-searching: 2.0.5 + comment-parser: 1.4.1 + mdast-util-from-markdown: 2.0.0 + prettier: 3.2.4 + transitivePeerDependencies: + - supports-color + dev: true + /prettier-plugin-json-sort@0.0.2(prettier@3.2.2): resolution: {integrity: sha512-xd5VVfneeUBdWhTm5uh0rAto3qnkkosbte6poO5WVTZEAiQdndMQMRPv1SROXx968zfyAlS+Z+C6rkr4jbVOgg==} peerDependencies: @@ -6894,12 +7067,26 @@ packages: prettier: 3.2.2 dev: true + /prettier-plugin-json-sort@0.0.2(prettier@3.2.4): + resolution: {integrity: sha512-xd5VVfneeUBdWhTm5uh0rAto3qnkkosbte6poO5WVTZEAiQdndMQMRPv1SROXx968zfyAlS+Z+C6rkr4jbVOgg==} + peerDependencies: + prettier: '>=2.0.0' + dependencies: + prettier: 3.2.4 + dev: true + /prettier@3.2.2: resolution: {integrity: sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==} engines: {node: '>=14'} hasBin: true dev: true + /prettier@3.2.4: + resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==} + engines: {node: '>=14'} + hasBin: true + dev: true + /proc-log@3.0.0: resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}