一键发布monorepo仓库
需求:
一键执行monorepo的发布流程:
- changelog生成
- 版本tag生成
- publish到npm
- push到git
const fs = require('fs');
const os = require('os');
const path = require('path');
const chalk = require('chalk');
const yaml = require('yamljs');
const semver = require('semver');
const shell = require('shelljs');
const inquirer = require('inquirer');
const CWD = process.cwd();
const CURRENT_VERSION = require(path.resolve(CWD, 'package.json')).version;
const step = (msg) => console.log(chalk.green(os.EOL + msg));
const stepCyan = (msg) => console.log(chalk.cyan(os.EOL + msg));
const stepRed = (msg) => console.log(chalk.red(os.EOL + msg));
const standVersions = {
type: 'list',
message: '请选择发布类型',
name: 'release',
default: 'patch',
choices: [
{ name: 'patch(小功能、修复)', value: 'patch' },
{ name: 'minor(小版本)', value: 'minor' },
{ name: 'major(大版本)', value: 'major' },
'prepatch',
'preminor',
'premajor',
'prerelease',
],
};
function getWorkspaceConfig() {
const ls = shell.ls();
return ls.find((v) => v.endsWith('workspace.yaml'));
}
/**
* 获取当前所有包所在目录
* @returns
* ```
[
{
relPath: 'packages/components',
absPath: 'D:\\xxx\\packages\\components',
pkgName: 'xxx/components'
},
...
]/
function getPackagesPathInfo() {
const workspaceConfig = getWorkspaceConfig();
if (!workspaceConfig) {
stepRed(‘当前目录非workspace目录,已停止操作’);
shell.exit();
}
const yml = yaml.load(workspaceConfig);
const workspaceList = yml.packages;
const packagesPathInfo = [];
workspaceList.forEach((_packagePath) ⇒ {
if (_packagePath.includes('')) {
const packagesDirPath = _packagePath.split(’/*’)[0];
const tmpList = shell.ls(packagesDirPath).map((dirName) ⇒ {
const relPath = ${packagesDirPath}/${dirName};
const absPath = path.resolve(CWD, relPath);
const pkgName = require(path.resolve(absPath, ‘package.json’)).name;
return {
relPath,
absPath,
pkgName,
};
});
packagesPathInfo.push(…tmpList);
} else {
const relPath = _packagePath;
const absPath = path.resolve(CWD, relPath);
const pkgName = require(path.resolve(absPath, ‘package.json’)).name;
packagesPathInfo.push({
relPath,
absPath,
pkgName,
});
}
});
return packagesPathInfo;
}
// 升级版本号
function updatePackage(pkgRoot, version) {
const pkgPath = path.resolve(pkgRoot, ‘package.json’);
const pkg = JSON.parse(fs.readFileSync(pkgPath, ‘utf-8’));
pkg.version = version;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2).replace(/\n/g, os.EOL) + os.EOL, ‘utf-8’);
}
function sleep(sec = 0) {
return new Promise((r) ⇒ {
setTimeout(r, sec * 1000);
});
}
async function main() {
const currentBranch = shell
.exec(‘git symbolic-ref —short -q HEAD’, { silent: true })
.toString()
.trim();
if (![‘develop’, ‘master’, ‘release’, ‘main’, ”, undefined].includes(currentBranch)) {
stepRed(
发布失败,只允许在'develop', 'master', 'release', 'main'分支发布,当前分支${currentBranch},
);
return;
}
if (!!shell.exec(‘git status -s’).toString()) {
stepRed(‘发布失败,当前git工作区有未提交内容,请先提交或者暂存’);
return;
}
if (!shell.exec(‘npm whoami’).toString()) {
stepRed(请先登录npm,指令:npm login);
return;
}
step(正在生成changelog并发布...);
const { release } = await inquirer.prompt([standVersions]);
const TARGET_VERSION = semver.inc(CURRENT_VERSION, release);
const { yes } = await inquirer.prompt({
type: ‘confirm’,
name: ‘yes’,
message: 确定发布 v${TARGET_VERSION}?,
});
if (!yes) {
return;
}
step(Updating package versions [${CURRENT_VERSION} -> ${TARGET_VERSION}]...);
updatePackage(CWD, TARGET_VERSION);
const packagesInfo = getPackagesPathInfo();
packagesInfo.forEach(({ absPath, pkgName }) ⇒ {
shell.cd(absPath);
// 修改版本号
updatePackage(absPath, TARGET_VERSION);
step( - Generating changelog for [${pkgName}]...);
// 生成changelog
shell.exec(conventional-changelog -p angular --commit-path ./ -i CHANGELOG.md -s);
});
step(Committing changes...);
shell.cd(CWD);
shell.exec(git add packages/* package.json, { silent: true }); // 可能会提示changelog是CRLF,这里隐藏log
shell.exec(git commit -m "release: v${TARGET_VERSION}");
step(‘git commit 完成,3秒后publish并push’);
await sleep(3);
stepCyan(Publishing packages...);
// 发布到npm // pnpm publish —filter xxx —filter xxxx —no-git-checks —dry-run; —dry-run是空跑
const { stderr } = shell.exec(
pnpm publish --filter "${packagesInfo .map(({ pkgName }) => pkgName) .join('" --filter "')}" --no-git-checks,
{
silent: true,
},
);
if (code !== 0 || stderr.includes(‘npm ERR’)) {
stepRed(
发布失败,请注意:本地版本号已变更,已commit,未提交!\n${stderr .split('\n') .slice(-3) .join(os.EOL)},
);
return;
}
// push
stepCyan(‘Pushing to git…’);
shell.cd(CWD);
shell.exec(git tag ${TARGET_VERSION});
shell.exec(git push origin refs/tags/${TARGET_VERSION});
shell.exec(git push);
stepCyan(‘Done’);
}
main();