一键发布monorepo仓库

需求:

一键执行monorepo的发布流程:

  1. changelog生成
  2. 版本tag生成
  3. publish到npm
  4. 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();