@ygyg/yg-cli
Version:
A simple CLI for front-end engineering automation construction tool.
295 lines (266 loc) • 11.8 kB
JavaScript
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);
}
};