diff --git a/package.json b/package.json index 8a1a7ad..4c949de 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "@vexip-ui/commitlint-config": "^0.3.0", "@vexip-ui/eslint-config": "^0.9.0", "@vexip-ui/icons": "^1.2.0", + "@vexip-ui/scripts": "^1.0.0", "conventional-changelog-angular": "^7.0.0", "conventional-changelog-cli": "^4.1.0", "eslint": "^8.52.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00bdbdf..102019f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: '@vexip-ui/icons': specifier: ^1.2.0 version: 1.2.0(vue@3.3.4) + '@vexip-ui/scripts': + specifier: ^1.0.0 + version: 1.0.0 conventional-changelog-angular: specifier: ^7.0.0 version: 7.0.0(patch_hash=mnuz4pbg7c37j4ofaayqrj5u6y) @@ -2819,6 +2822,15 @@ packages: vue: 3.3.4 dev: true + /@vexip-ui/scripts@1.0.0: + resolution: {integrity: sha512-7emOi0PdpcNME+IPFB557p1LaQakzbmPGCIUAPpgopO/Jf88TFr7aQSg13vYxTXhdY3DgLapAqyzH14WQMHJxg==} + dependencies: + execa: 8.0.1 + kolorist: 1.8.0 + prompts: 2.4.2 + semver: 7.5.4 + dev: true + /@vexip-ui/utils@2.0.0: resolution: {integrity: sha512-LSpchBFUlVQl+pYNskoPQ5ZJ4i+o2gbUlxd9dYhfb/9dDxRF+apnCdE6Z7QiHqaVIy++X3JkoVnX2th2jzK3Wg==} dev: true diff --git a/scripts/constant.ts b/scripts/constant.ts new file mode 100644 index 0000000..30ef6bf --- /dev/null +++ b/scripts/constant.ts @@ -0,0 +1,4 @@ +import { resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +export const rootDir = resolve(fileURLToPath(import.meta.url), '../..') diff --git a/scripts/publish.ts b/scripts/publish.ts index f87d18a..5f71222 100644 --- a/scripts/publish.ts +++ b/scripts/publish.ts @@ -1,8 +1,8 @@ import { resolve } from 'node:path' -import { readFile } from 'node:fs/promises' import minimist from 'minimist' -import { logger, rootDir, run } from './utils' +import { logger, publish } from '@vexip-ui/scripts' +import { rootDir } from './constant' const args = minimist<{ d?: boolean, @@ -14,47 +14,11 @@ const args = minimist<{ const isDryRun = args.dry || args.d const releaseTag = args.tag || args.t -async function main() { - const pkg = JSON.parse(await readFile(resolve(rootDir, 'package.json'), 'utf-8')) - const currentVersion: string = pkg.version - - logger.withStartLn(() => logger.infoText('Publishing package...')) - - const publishArgs = [ - 'publish', - '--access', - 'public', - '--registry', - 'https://registry.npmjs.org/', - '--no-git-checks' - ] - - if (isDryRun) { - publishArgs.push('--dry-run') - } - - if (releaseTag) { - publishArgs.push('--tag', releaseTag) - } else if (currentVersion.includes('-')) { - const [, preversion] = currentVersion.split('-') - const tag = preversion && preversion.split('.')[0] - - tag && publishArgs.push('--tag', tag) - } - - try { - await run('pnpm', publishArgs, { stdio: 'pipe', cwd: rootDir }) - logger.successText(`Successfully published v${currentVersion}'`) - } catch (error) { - if (error.stderr?.match(/previously published/)) { - logger.errorText(`Skipping already published v'${currentVersion}'`) - } else { - throw error - } - } -} - -main().catch(error => { +publish({ + pkgDir: resolve(rootDir, 'package.json'), + isDryRun, + releaseTag +}).catch(error => { logger.error(error) process.exit(1) }) diff --git a/scripts/release.ts b/scripts/release.ts index 74992dc..836922f 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -1,142 +1,29 @@ -import { join, resolve } from 'node:path' -import { readFile, writeFile } from 'node:fs/promises' -import { fileURLToPath } from 'node:url' +import { resolve } from 'node:path' import minimist from 'minimist' -import semver from 'semver' -import prompts from 'prompts' -import { dryRun, logger, run } from './utils' +import { logger, release, run } from '@vexip-ui/scripts' +import { rootDir } from './constant' const args = minimist<{ d?: boolean, dry?: boolean, - t?: string, - tag?: string, p: string, preid?: string }>(process.argv.slice(2)) const isDryRun = args.dry || args.d -const rootDir = resolve(fileURLToPath(import.meta.url), '../..') - -const runIfNotDry = isDryRun ? dryRun : run -const logStep = (msg: string) => { - logger.ln() - logger.infoText(msg) -} -const logSkipped = (msg = 'Skipped') => { - logger.warningText(`(${msg})`) -} - -main() - -async function main() { - const pkg = JSON.parse( - await readFile(join(rootDir, 'package.json'), 'utf-8') - ) - const currentVersion = pkg.version - - const preId = - args.preid || - args.p || - semver.prerelease(currentVersion)?.[0] - - const versionIncrements = [ - 'patch', - 'minor', - 'major', - ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : []) - ] - - const inc = (i: any) => semver.inc(currentVersion, i, preId as string) - - const { release } = await prompts({ - type: 'select', - name: 'release', - message: 'Select release type:', - choices: versionIncrements - .map(i => `${i} (${inc(i)})`) - .concat(['custom']) - .map(i => ({ title: i, value: i })) - }) - - const version = - release === 'custom' - ? (await prompts({ - type: 'text', - name: 'version', - message: 'Input custom version:' - })).version - : release.match(/\((.*)\)/)?.[1] - - if (!semver.valid(version)) { - throw new Error(`Invalid target version: ${version}`) - } - - const { confirm } = await prompts({ - type: 'confirm', - name: 'confirm', - message: `Confirm release ${version}?` - }) - - if (!confirm) return - - // 执行单元测试 - logStep('Running test...') - - if (!isDryRun) { - await run('pnpm', ['test']) - } else { - logSkipped() - } - - logStep('Updating version...') - - pkg.version = version - await writeFile(resolve(rootDir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n') - - // 构建库 - logStep('Building package...') - - if (!isDryRun) { +release({ + pkgDir: resolve(rootDir, 'package.json'), + isDryRun, + preId: args.preid, + runTest: () => run('pnpm', ['test']), + runBuild: async () => { await run('pnpm', ['dev:prepare']) await run('pnpm', ['build']) - } else { - logSkipped() - } - - // 更新 Change Log - logStep('Updating changelog...') - - await run('pnpm', ['changelog']) - - // 提交改动 - logStep('Comitting changes...') - - const { stdout } = await run('git', ['diff'], { stdio: 'pipe' }) - - if (stdout) { - await runIfNotDry('git', ['add', '-A']) - await runIfNotDry('git', ['commit', '-m', `release: v${version}`]) - } else { - logSkipped('No changes to commit') - } - - // 推送到远程仓库 - logStep('Pushing to Remote Repository...') - - await runIfNotDry('git', ['tag', `v${version}`]) - await runIfNotDry('git', ['push', 'origin', `refs/tags/v${version}`]) - await runIfNotDry('git', ['push']) - - logger.ln() - - if (isDryRun) { - logger.success('Dry run finished - run git diff to see package changes') - } else { - logger.success('Release successfully') - } - - logger.ln() -} + }, + runChangelog: () => run('pnpm', ['changelog']) +}).catch(error => { + logger.error(error) + process.exit(1) +}) diff --git a/scripts/utils.ts b/scripts/utils.ts deleted file mode 100644 index 90904d2..0000000 --- a/scripts/utils.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { resolve } from 'node:path' -import { fileURLToPath } from 'node:url' - -import { execa } from 'execa' -import { bgCyan, bgGreen, bgRed, bgYellow, cyan, green, lightBlue, red, yellow } from 'kolorist' - -import type { Options } from 'execa' - -export const rootDir = resolve(fileURLToPath(import.meta.url), '../..') - -type LogFn = () => void - -export const logger = { - ln: () => console.log(), - withStartLn: (log: LogFn) => { - logger.ln() - log() - }, - withEndLn: (log: LogFn) => { - log() - logger.ln() - }, - withBothLn: (log: LogFn) => { - logger.ln() - log() - logger.ln() - }, - warning: (msg: string) => { - console.warn(`${bgYellow(' WARNING ')} ${yellow(msg)}`) - }, - info: (msg: string) => { - console.log(`${bgCyan(' INFO ')} ${cyan(msg)}`) - }, - success: (msg: string) => { - console.log(`${bgGreen(' SUCCESS ')} ${green(msg)}`) - }, - error: (msg: string) => { - console.error(`${bgRed(' ERROR ')} ${red(msg)}`) - }, - warningText: (msg: string) => { - console.warn(`${yellow(msg)}`) - }, - infoText: (msg: string) => { - console.log(`${cyan(msg)}`) - }, - successText: (msg: string) => { - console.log(`${green(msg)}`) - }, - errorText: (msg: string) => { - console.error(`${red(msg)}`) - } -} - -export function bin(name: string) { - return resolve(rootDir, 'node_modules/.bin/' + name) -} - -export async function run(bin: string, args: string[], opts: Options = {}) { - return execa(bin, args, { stdio: 'inherit', ...opts }) -} - -export async function dryRun(bin: string, args: string[], opts: Options = {}) { - console.log(lightBlue(`[dryrun] ${bin} ${args.join(' ')}`), opts) -} - -function allCapital(value: string) { - const matched = value.match(/[A-Z]+/) - - return matched && matched[0] === value -} - -// 短横线命名 -export function toKebabCase(value: string) { - if (allCapital(value)) { - return value.toLocaleLowerCase() - } - - return ( - value.charAt(0).toLowerCase() + - value - .slice(1) - .replace(/([A-Z])/g, '-$1') - .toLowerCase() - ) -} - -// 全大写命名 -export function toCapitalCase(value: string) { - return ( - value.charAt(0).toUpperCase() + - value.slice(1).replace(/-([a-z])/g, (_, char) => (char ? char.toUpperCase() : '')) - ) -} - -// 驼峰命名 -export function toCamelCase(value: string) { - const capitalName = toCapitalCase(value) - - if (allCapital(capitalName)) { - return capitalName.toLocaleLowerCase() - } - - return capitalName.charAt(0).toLowerCase() + capitalName.slice(1) -} - -export function fuzzyMatch(partials: string[], total: string[], includeAll = false) { - const matched: string[] = [] - - partials.forEach(partial => { - for (const target of total) { - if (target.match(partial)) { - matched.push(target) - - if (!includeAll) break - } - } - }) - - return matched -} - -export async function runParallel( - maxConcurrency: number, - source: T[], - iteratorFn: (item: T, source: T[]) => Promise -) { - const ret: Array> = [] - const executing: Array> = [] - - for (const item of source) { - const p = Promise.resolve().then(() => iteratorFn(item, source)) - - ret.push(p) - - if (maxConcurrency <= source.length) { - const e: Promise = p.then(() => executing.splice(executing.indexOf(e), 1)) - - executing.push(e) - - if (executing.length >= maxConcurrency) { - await Promise.race(executing) - } - } - } - - return Promise.all(ret) -}