UNPKG

@ygyg/yg-cli

Version:

A simple CLI for front-end engineering automation construction tool.

295 lines (266 loc) 11.8 kB
const getPackages = require('./getPackages'); const robotMessage = require('./robotMessage'); const path = require('path'); const execa = require('execa'); const exec = require('./exec'); const chalk = require('chalk'); // 用于高亮终端打印出的信息(命令行字体颜色) const semanticRelease = require('semantic-release'); // const { yParser, execa, chalk, lodash } = utils; const lodash = require('lodash'); const utils = require('./utils'); const checkVersion = require('../lib/check-version'); // 检查版本 const execSync = require('child_process').execSync; // 同步子进程 const inquirer = require('inquirer'); // 常用的交互式命令行用户界面的集合。表现是控制台输出提问 // const standardVersion = require('standard-version'); // getProfileData const npmLocalRegistry = 'http://registry.ygego.prod/nexus/repository/npm-local/'; exports.checkRegistry = async function() { const pkg = await utils.getPkg(process.cwd()); if (!pkg.publishConfig) { // that.printErrorAndExit('.ygclirc.js not found'); console.error(chalk.red('未配置publishConfig not found')); } // const userRegistry = await execa.sync('npm', ['config', 'get', 'registry']).stdout; // const currentRegistry = pkg.publishConfig.registry || this.options.registry || userRegistry; if (pkg.name && (pkg.name.includes('@ygfish') | pkg.name.includes('@yg-cube'))) { utils.logStep('', 'Skipping all user and access validation due to third-party registry'); utils.logStep('', 'Yg 内部组件 - ^-^'); const userRegistry = await execa.sync('npm', ['config', 'get', 'registry']).stdout; // if (userRegistry.includes('https://registry.yarnpkg.com/')) { // utils.printErrorAndExit(`请执行 ${chalk.blue('npm run release')}.`); // } if (!userRegistry.includes(npmLocalRegistry) && this.options.registry !== npmLocalRegistry) { const registry = chalk.blue(npmLocalRegistry); utils.printErrorAndExit(`npm registry 必须是 ${registry}.`); } } // if (pkg.publishConfig && npmLocalRegistry !== pkg.publishConfig.registry) { // if (this.options.yes) { // log.info('checkRegistry', 'auto-confirmed'); // return true; // } // let {registry} = await inquirer.prompt([{ // type: 'confirm', // name: 'registry', // message: '你确认发布到公网npm吗?', // default: true, // }]); // if (!registry) {return false;} // } return true; }; exports.prepareRelease = async function(isLernaPackage) { utils.logStep(`isLernaPackage=${isLernaPackage}`); if (isLernaPackage && !this.options.prerelease) { const pkgs = getPackages(); if (lodash.isEmpty(pkgs)) { utils.printErrorAndExit('添加pkg, 请阅读readme'); } } utils.logStep('check git Status'); const gitStatus = execa.sync('git', ['status', '--porcelain']).stdout; utils.logStep(gitStatus); // if (gitStatus.length) { // utils.printErrorAndExit('Your git status is not clean. Aborting.'); // } utils.logStep('check npm registry'); const allowPub = await this.checkRegistry(); if (!allowPub) {utils.printErrorAndExit('中断npm发布');} utils.logStep('check npm whoami'); // const stdout = execa.sync('npm', ['whoami']).stdout; // utils.logStep(`npm login: ${stdout}`); }; /** * 功能指定alpha,beta,rc等包名为后缀 * 分两种场景 * lerna monorepo * 标准单包 * process.cwd() 应该为lerna 具体某个包package.json对应的根下 * 单包的package.json对应的根下 */ exports.pubRelease = async function(opt) { this.options = Object.assign({}, {yes: opt.yes, access: opt.access, releaseAs: opt.releaseAs, dryRun: opt.dryRun, registry: opt.registry || 'http://registry.ygego.prod/nexus/repository/npm-local/', prerelease: opt.prerelease}); this.pkg = await utils.getPkg(process.cwd()); const isLernaPackage = utils.isLernaPackage(); // utils.logStep('check pkgs version'); await this.prepareRelease(isLernaPackage); utils.logStep('process 1: build 开始执行构建'); if (!this.pkg.scripts.build) { utils.logStep('execScript', 'No build script found at '); utils.printErrorAndExit('请检查,此项目中的package.json中 npm scripts 不包含 build '); } await exec('npm', ['run', 'build']); utils.logStep('process 2: publish'); utils.logStep('发版中'); const commits = await utils.getCommits(); utils.logStep(JSON.stringify(commits)); if (isLernaPackage) { await this.lernaRelease(commits); } else { this.options.releaseAs ? await this.standardRelease(commits) : await this.singleRelease(commits); } utils.logStep('发版完成'); utils.logStep('done'); }; exports.lernaRelease = async function(currCommits) { try { // '--skip-npm', // '--skip-git', // father-build && npm version prerelease --preid=alpha && npm publish --registry http://registry.ygego.prod/nexus/repository/npm-local/ --access public --dry-run // console.log(this.options.prerelease, 'this.options.prerelease'); if (this.options.prerelease) { this.standardPreRelease(currCommits); return; } const result = execSync( `lerna publish --conventional-commits --yes --message "chore: release" --registry=${npmLocalRegistry}`, ).toString(); utils.logStep(result, typeof result, 'result ci 结果'); const content = ['CI发版实时反馈,请相关同事注意。']; content.push('**发版详情: **'); content.push(`>项目:<font color=\"info\">${currCommits.projectName}</font>`); content.push(`>分支:<font color=\"info\">${currCommits.branch}</font>`); if (result) { content.push(`${result}`); content.push(`> [点击查看ChangeLog](${currCommits.projectUrl}/blob/master/CHANGELOG.md)`); } else { utils.logStep('No release published.'); content.push('>本次发版没有更新'); } await robotMessage.sendInsight({ content }); } catch (err) { console.error('The automated release failed with %O', err); } }; /** * 注意 alpha 和 beta rc 不要来回切换 * @param {*} currCommits */ exports.standardPreRelease = async function (currCommits) { // const result = execSync( // `standard-version --prerelease ${this.options.prerelease} --tag-prefix "${this.pkg.name}@" --skip.changelog --message "chore: prerelease alpha" --dry-run=${this.options.dryRun} --registry=${npmLocalRegistry}`, // ).toString(); // if (!this.options.dryRun) { // execa.sync('git', ['status']).stdout; // execa.sync('git', ['push', '--follow-tags', 'origin', currCommits.branch]).stdout; // execa.sync('npm', ['publish', '--tag', this.options.prerelease, `--registry=${npmLocalRegistry}`]).stdout; // } // utils.logStep(result); const result = 'robot'; this.sendInsight(currCommits, 3, result, false); }; exports.sendInsight = async function(currCommits, maxRow, result, outputChangeLog) { const resArr = result.split('\n').filter(function(value, index) { return index < maxRow; }); // console.log(resArr.join('\n')); const content = ['CI发版prerelease反馈,请相关同事注意。']; content.push('**发版详情: **'); content.push(`>项目:<font color=\"info\">${currCommits.projectName}</font>`); content.push(`>分支:<font color=\"info\">${currCommits.branch}</font>`); if (result) { content.push(`${resArr.join('\n')}`); if (outputChangeLog) { content.push(`> [点击查看ChangeLog](${currCommits.projectUrl}/blob/master/CHANGELOG.md)`); } } await robotMessage.sendInsight({ content }); }; exports.singleRelease = async function(currCommits) { if (this.options.prerelease) { this.standardPreRelease(currCommits); return; } try { let stdoutBuffer; let stderrBuffer; const result = await semanticRelease({ // Core options 'branches': ['master'], 'repositoryUrl': currCommits.repositoryUrl, 'gitlabUrl': currCommits.gitlabUrl, 'gitlabApiPathPrefix': '/api/v4', plugins: [ [ '@semantic-release/commit-analyzer', { 'preset': 'conventionalcommits', }, ], // ['@semantic-release/release-notes-generator'], '@semantic-release/npm', '@semantic-release/changelog', [ '@semantic-release/git', { 'assets': ['package.json', 'CHANGELOG.md'], 'message': 'chore: ${nextRelease.version} [skip ci] [skip release]\n\n${nextRelease.notes}', }, ], [ '@semantic-release/exec', { 'successCmd': 'echo "VERSION=${nextRelease.version}" ', }, ], ], }, { env: {...process.env, MY_ENV_VAR: 'MY_ENV_VAR_VALUE'}, // Store stdout and stderr to use later instead of writing to `process.stdout` and `process.stderr` stdout: stdoutBuffer, stderr: stderrBuffer, }); const content = ['CI发版实时反馈,请相关同事注意。']; content.push('**发版详情:**'); content.push(`>项目:<font color=\"info\">${currCommits.projectName}</font>`); content.push(`>分支:<font color=\"info\">${currCommits.branch}</font>`); if (result) { const {lastRelease, commits, nextRelease, releases} = result; utils.logStep(`发布类型: "${nextRelease.type}", 版本号: ${nextRelease.version}, 其中包含 (${commits.length}) 个提交.`); content.push(`>类型:<font color=\"comment\">${nextRelease.type}</font>`); content.push(`>版本号:<font color=\"comment\">${nextRelease.version}</font>`); content.push(`>包含 :<font color=\"comment\">(${commits.length}) </font>个提交`); if (lastRelease.version) { utils.logStep(`上一个版本号: "${lastRelease.version}".`); content.push(`>上一个版本号:<font color=\"comment\">${lastRelease.version}</font>`); } // content.push(`${nextRelease.notes}`); content.push(`> [点击查看ChangeLog](${currCommits.projectUrl}/blob/master/CHANGELOG.md)`); for (const release of releases) { utils.logStep(`The release was published with plugin "${release.pluginName}".`); } } else { utils.logStep('No release published.'); content.push('>本次发版没有更新'); } await robotMessage.sendInsight({content}); // Get stdout and stderr content // const logs = stdoutBuffer; // const errors = stderrBuffer; } catch (err) { console.error('The automated release failed with %O', err); } }; exports.standardRelease = async function(currCommits) { utils.logStep('standardRelease begin'); const releaseAsCfg = ['major', 'minor', 'patch']; const releaseAs = this.options.releaseAs; // 主版本号(major):当你做了不兼容的 API 修改, // 次版本号(minor):当你做了向下兼容的功能性新增,可以理解为Feature版本, // 修订号(patch):当你做了向下兼容的问题修正,可以理解为Bug fix版本。 if (!releaseAsCfg.includes(releaseAs)) { utils.printErrorAndExit(`版本状态错误, 请输入--releaseAs, 支持${releaseAsCfg.join(' ')}`); } try { const result = execSync( `standard-version -r ${releaseAs} --message "chore: release" --dry-run=${this.options.dryRun} --registry=${npmLocalRegistry}`, ).toString(); execa.sync('git', ['push', '--follow-tags', 'origin', 'master']).stdout; execa.sync('npm', ['publish', `--registry=${npmLocalRegistry}`]).stdout; // git push --follow-tags origin master && npm publish --registry= if (result) { this.sendInsight(currCommits, 4, result, true); } } catch (err) { console.error('The automated release failed with %O', err); } };