Skip to content

Commit

Permalink
feat: initial release script
Browse files Browse the repository at this point in the history
  • Loading branch information
linbudu599 committed Apr 8, 2022
1 parent 41709b4 commit baeaf08
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 22 deletions.
2 changes: 1 addition & 1 deletion packages/apollo-element-starter/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/rtt/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
265 changes: 246 additions & 19 deletions scripts/release/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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
*
Expand All @@ -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'
Expand All @@ -59,7 +57,236 @@ export default function useReleaseProject(cli: CAC) {
releaseType?: ReleaseType,
options?: Partial<ReleaseCommandOptions>
) => {
console.log(project, releaseType, options);
const { dry = false, quick = false } = options ?? {};

const dryRunInfoLogger = (msg: MaybeArray<string>) =>
dry
? consola.info(
`${chalk.white('DRY RUN MODE')}: ${ensureArray(msg).join(' ')}`
)
: consola.info(msg);

const dryRunSuccessLogger = (msg: MaybeArray<string>) =>
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<PackageJson>(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 | T[];

export const ensureArray = <T>(value: MaybeArray<T>): T[] => {
return Array.isArray(value) ? value : [value];
};
35 changes: 35 additions & 0 deletions scripts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,41 @@ export class CLIUtils {
return res.confirm;
}

public static async createPackageSelector<T extends string>(
name: T,
message: string,
color = false
): Promise<string> {
const existPackages = CLIUtils.existPackages;

const res = await enquirer.prompt<string>({
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<T extends string>(
name: T,
message: string,
Expand Down

0 comments on commit baeaf08

Please sign in to comment.