From baeaf08ef9f39ef66f3a3291f61981199c5edf20 Mon Sep 17 00:00:00 2001 From: linbudu599 Date: Fri, 8 Apr 2022 10:23:36 +0800 Subject: [PATCH] feat: initial release script --- packages/apollo-element-starter/package.json | 2 +- packages/rtt/package.json | 4 +- scripts/release/index.ts | 265 +++++++++++++++++-- scripts/utils.ts | 35 +++ 4 files changed, 284 insertions(+), 22 deletions(-) diff --git a/packages/apollo-element-starter/package.json b/packages/apollo-element-starter/package.json index 9d7639a..5234db6 100644 --- a/packages/apollo-element-starter/package.json +++ b/packages/apollo-element-starter/package.json @@ -1,7 +1,7 @@ { "main": "index.html", "name": "apollo-elements-app", - "version": "3.0.0", + "version": "3.0.1-0", "type": "module", "author": "", "license": "ISC", diff --git a/packages/rtt/package.json b/packages/rtt/package.json index ddecf11..2753b49 100644 --- a/packages/rtt/package.json +++ b/packages/rtt/package.json @@ -1,6 +1,6 @@ { - "name": "rtt", - "version": "0.0.1", + "name": "@llab/rtt", + "version": "0.0.2-4", "description": "", "main": "./dist/index.js", "license": "MIT", diff --git a/scripts/release/index.ts b/scripts/release/index.ts index 55dabb9..dc457b5 100644 --- a/scripts/release/index.ts +++ b/scripts/release/index.ts @@ -1,7 +1,7 @@ import { CAC } from 'cac'; import fs from 'fs-extra'; -import path from 'path/posix'; -import inquirer from 'inquirer'; +import path from 'path'; +import enquirer from 'enquirer'; import chalk from 'chalk'; import ora from 'ora'; import consola from 'consola'; @@ -10,27 +10,24 @@ import execa from 'execa'; import { CLIUtils, Constants } from '../utils'; import { ReleaseType, inc, compare } from 'semver'; import simpleGit from 'simple-git'; +import { PackageJson } from 'type-fest'; interface ReleaseCommandOptions { - dryRun: boolean; + dry: boolean; quick: boolean; } +const RELEASE_TYPE_LIST: ReleaseType[] = [ + 'major', + 'minor', + 'patch', + 'premajor', + 'preminor', + 'prepatch', + 'prerelease', +]; + /** - * Workflow: - * - * 1. Select release project and release version(if not specified, use prompt) - * 2. Check current version and registry version - * 3. Bump version - * 4. Compose Tag - * 5. Update version - * 6. Build with dependencies - * 7. Run git diff - * 8. Run git add and git cz - * 9. Run git tag - * 10. Run git push - * 11. Check npm whoami - * 12. Publish package * * TODO: ChangeLog Generator * @@ -48,7 +45,8 @@ export default function useReleaseProject(cli: CAC) { cli .command('release [project] [releaseType]', 'release project') .alias('r') - .option('-d,--dryRun', 'execute in dry run mode') + .option('--no-dry', 'donot execute in dry run mode') + .option('--dry', 'execute in dry run mode') .option( '-q,--quick', 'perform a quick patch release without any checks or confirmation' @@ -59,7 +57,236 @@ export default function useReleaseProject(cli: CAC) { releaseType?: ReleaseType, options?: Partial ) => { - console.log(project, releaseType, options); + const { dry = false, quick = false } = options ?? {}; + + const dryRunInfoLogger = (msg: MaybeArray) => + dry + ? consola.info( + `${chalk.white('DRY RUN MODE')}: ${ensureArray(msg).join(' ')}` + ) + : consola.info(msg); + + const dryRunSuccessLogger = (msg: MaybeArray) => + dry + ? consola.success( + `${chalk.white('DRY RUN MODE')}: ${ensureArray(msg).join(' ')}` + ) + : consola.success(msg); + + let projectToRelease: string; + let projectReleaseType: ReleaseType; + + if (quick) { + projectReleaseType = 'patch'; + } + + if (project) { + const { existPackages } = CLIUtils; + if (!existPackages.includes(project)) { + consola.error(`Project ${chalk.green(project)} not found.`); + process.exit(1); + } else { + projectToRelease = project; + } + } else { + projectToRelease = ( + await enquirer.prompt<{ project: string }>({ + name: 'project', + message: 'Select project to release:', + type: 'select', + choices: CLIUtils.existPackages, + muliple: false, + }) + ).project; + } + + if (releaseType) { + if (!RELEASE_TYPE_LIST.includes(releaseType)) { + consola.error( + `Release type ${chalk.green(releaseType)} not valid.` + ); + process.exit(1); + } else { + projectReleaseType = releaseType; + } + } else if (quick) { + } else { + projectReleaseType = ( + await enquirer.prompt<{ type: ReleaseType }>({ + name: 'type', + message: 'Select release type:', + type: 'select', + choices: RELEASE_TYPE_LIST.reverse(), + muliple: false, + }) + ).type; + } + + const projectDirPath = CLIUtils.resolvePackageDir(projectToRelease); + const projectPackageJsonPath = path.join( + projectDirPath, + 'package.json' + ); + + const { version: rawVersion, name: packageName } = + CLIUtils.readJsonSync(projectPackageJsonPath); + + const bumpedVersion = inc(rawVersion, projectReleaseType); + + let remoteVersion: string | null = null; + + try { + const { version } = await pacote.manifest(packageName); + + remoteVersion = version; + } catch (error) { + consola.warn(`Package ${chalk.cyan(packageName)} not published yet.`); + } + + consola.info( + `Current version: ${chalk.green(rawVersion)}, Remote version: ${ + remoteVersion ? chalk.cyan(remoteVersion) : chalk.red('NOT FOUND') + }, Bump to: ${chalk.green(bumpedVersion)}` + ); + + const releaseTag = `${projectToRelease}@${bumpedVersion}`; + + dryRunInfoLogger(`Release ${chalk.cyan(releaseTag)}?`); + + const confirmVersion = await CLIUtils.createConfirmSelector('Confirm?'); + + if (!confirmVersion) { + consola.info('Release aborted.'); + process.exit(1); + } else { + consola.info('Executing release flow...'); + } + + dryRunInfoLogger( + `Updating ${chalk.cyan(packageName)} version to ${chalk.green( + bumpedVersion + )}` + ); + + !dry && + CLIUtils.modifyPackageJSON( + projectPackageJsonPath, + 'version', + bumpedVersion + ); + + dryRunSuccessLogger( + `Bumped version info in ${chalk.green(projectPackageJsonPath)}` + ); + + consola.info(`Building project...`); + + await CLIUtils.useChildProcess( + `pnpm run build --filter=${packageName}`, + { + cwd: process.cwd(), + } + ); + + consola.success( + `Package ${chalk.white(projectToRelease)}(${chalk.green( + packageName + )}) built successfully.` + ); + + const { stdout } = await execa('git', ['diff'], { + stdio: 'pipe', + cwd: projectDirPath, + }); + + if (!stdout) { + consola.error('No commit changes found, exit.'); + process.exit(0); + } + + await CLIUtils.useChildProcess( + `git add ${projectDirPath} --verbose --dry-run` + ); + + const gitCZCommandArgs = [ + '--type=release', + `--scope=${projectToRelease.split('-')[0]}`, + `--subject=Release ${releaseTag}`, + '--non-interactive', + ]; + + dry + ? consola.info( + `${chalk.white('DRY RUN MODE')}: Executing >>> ${chalk.cyan( + `git-cz ${gitCZCommandArgs.join(' ')}` + )}` + ) + : await CLIUtils.useChildProcess( + `git-cz --${gitCZCommandArgs.join('')}` + ); + + dry + ? consola.info( + `${chalk.white('DRY RUN MODE')}: Executing >>> ${chalk.cyan( + `git tag ${releaseTag}` + )}` + ) + : await CLIUtils.useChildProcess(`git tag ${releaseTag}`); + + dry + ? consola.info( + `${chalk.white('DRY RUN MODE')}: Executing >>> ${chalk.cyan( + `git push origin refs/tags/${releaseTag} --verbose --progress` + )}` + ) + : await CLIUtils.useChildProcess( + `git ${[ + 'push', + 'origin', + `refs/tags/${releaseTag}`, + '--verbose', + '--progress', + ].join(' ')}` + ); + + dryRunInfoLogger(['Pubishing package...']); + + const { stdout: logAs } = await execa( + 'npm', + ['whoami', `--registry=${Constants.releaseRegistry}`], + { + cwd: projectDirPath, + stdio: 'pipe', + shell: true, + preferLocal: true, + } + ); + + consola.info(`You're now logged as ${chalk.bold(chalk.white(logAs))}`); + + await execa( + 'pnpm', + [ + 'publish', + `--registry=${Constants.releaseRegistry}`, + '--access=public', + '--no-git-checks', + ].concat(dry ? ['--dry-run'] : []), + { + cwd: projectDirPath, + stdio: 'inherit', + shell: true, + preferLocal: true, + } + ); + + dryRunSuccessLogger(['Package published.']); } ); } + +type MaybeArray = T | T[]; + +export const ensureArray = (value: MaybeArray): T[] => { + return Array.isArray(value) ? value : [value]; +}; diff --git a/scripts/utils.ts b/scripts/utils.ts index accc0aa..efdeb78 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -169,6 +169,41 @@ export class CLIUtils { return res.confirm; } + public static async createPackageSelector( + name: T, + message: string, + color = false + ): Promise { + const existPackages = CLIUtils.existPackages; + + const res = await enquirer.prompt({ + type: 'select', + choices: color + ? existPackages.map((p) => { + return { + name: p, + value: p, + message: color + ? chalk.hex( + ( + CLIUtils.findInfoFromKeywords(p) ?? + Constants.starterInfoMap['other'] + ).color + )(p) + : p, + }; + }) + : existPackages, + muliple: false, + sort: true, + scroll: true, + name, + message, + }); + + return res; + } + public static async createPackageMultiSelector( name: T, message: string,