feat(push): 代码和注释均增加,提升可维护性与可读性
parent
13f8f61b1a
commit
240caf3c66
|
|
@ -2,6 +2,14 @@
|
|||
|
||||
const { version } = require('../package.json');
|
||||
|
||||
/*
|
||||
* 命令启动入口:
|
||||
* 1. package.json 的 bin 配置把全局命令 zy-gbs-cmd 指向本文件。
|
||||
* 2. 用户执行 zy-gbs-cmd push java production。
|
||||
* 3. 本文件从 process.argv 取出 push、java、production。
|
||||
* 4. 确认是 push java 后,调用 src/push.js 开始真正的构建和推送。
|
||||
*/
|
||||
|
||||
function printHelp() {
|
||||
console.log(`zy-gbs-cmd <command>
|
||||
|
||||
|
|
@ -13,6 +21,7 @@ function printHelp() {
|
|||
}
|
||||
|
||||
async function main() {
|
||||
// 第 1 步:解析命令。process.argv 前两项是 Node.js 路径和本文件路径,从第三项开始才是用户参数。
|
||||
const [, , command, argument, environment] = process.argv;
|
||||
|
||||
if (!command || command === 'help') {
|
||||
|
|
@ -26,6 +35,7 @@ async function main() {
|
|||
}
|
||||
|
||||
if (command === 'push' && argument === 'java') {
|
||||
// 第 2 步:进入推送主流程。production 会作为 environment 传给 pushJava。
|
||||
await require('../src/push')({ environment });
|
||||
return;
|
||||
}
|
||||
|
|
@ -35,6 +45,7 @@ async function main() {
|
|||
}
|
||||
|
||||
main().catch(error => {
|
||||
// 统一设置非零退出码,便于终端和 CI 判断命令执行失败。
|
||||
console.error(`推送失败: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
const childProcess = require('child_process');
|
||||
|
||||
function getExecutable(command) {
|
||||
// Windows 全局安装的命令通常是 .cmd 文件,其他系统直接使用命令名。
|
||||
return process.platform === 'win32' ? `${command}.cmd` : command;
|
||||
}
|
||||
|
||||
function commandExists(command) {
|
||||
// 使用系统自带的命令查找能力,避免为了检测 Yarn 引入额外依赖。
|
||||
const lookupCommand = process.platform === 'win32' ? 'where.exe' : 'which';
|
||||
const result = childProcess.spawnSync(lookupCommand, [getExecutable(command)], {
|
||||
stdio: 'ignore',
|
||||
|
|
@ -13,6 +15,7 @@ function commandExists(command) {
|
|||
}
|
||||
|
||||
function ensureYarnAvailable(isCommandAvailable = commandExists) {
|
||||
// 参数可注入是为了让测试覆盖 Yarn 存在和不存在两种情况。
|
||||
if (!isCommandAvailable('yarn')) {
|
||||
throw new Error('未检测到 Yarn,请先安装 Yarn');
|
||||
}
|
||||
|
|
@ -23,6 +26,7 @@ function runYarn(args, cwd) {
|
|||
const executable = getExecutable('yarn');
|
||||
console.log(`执行: yarn ${args.join(' ')}`);
|
||||
if (process.platform === 'win32') {
|
||||
// Node.js 不能始终直接执行 yarn.cmd,因此交给 cmd.exe 解析,避免 spawnSync yarn ENOENT。
|
||||
childProcess.execFileSync(process.env.ComSpec || 'cmd.exe', [
|
||||
'/d',
|
||||
'/s',
|
||||
|
|
|
|||
35
src/push.js
35
src/push.js
|
|
@ -7,9 +7,11 @@ const { ensureYarnAvailable, runYarn } = require('./package-manager');
|
|||
|
||||
const DEFAULT_BRANCH = 'master';
|
||||
const CONFIG_FILE_NAME = 'jjb.config.js';
|
||||
// 前端构建产物在 Java 仓库中的固定部署目录。
|
||||
const JAVA_TEMPLATE_PATH = path.join('start', 'src', 'main', 'resources', 'templates');
|
||||
|
||||
function run(command, args, cwd) {
|
||||
// Git 命令同步执行并继承终端输出,失败时由 execFileSync 直接抛出异常。
|
||||
console.log(`执行: ${command} ${args.join(' ')}`);
|
||||
childProcess.execFileSync(command, args, { cwd, stdio: 'inherit' });
|
||||
}
|
||||
|
|
@ -20,8 +22,10 @@ function readProjectConfig(projectRoot) {
|
|||
throw new Error(`未找到 ${CONFIG_FILE_NAME}`);
|
||||
}
|
||||
|
||||
// 清除 Node.js 模块缓存,确保每次执行都读取最新的业务项目配置。
|
||||
delete require.cache[require.resolve(configPath)];
|
||||
const importedConfig = require(configPath);
|
||||
// 同时兼容 CommonJS 导出和经过转译后位于 default 中的配置。
|
||||
const config = importedConfig.default || importedConfig;
|
||||
const requiredFields = ['javaGit', 'javaGitName', 'appIdentifier'];
|
||||
const missingFields = requiredFields.filter(field => !config[field]);
|
||||
|
|
@ -43,6 +47,7 @@ function readPackageJson(projectRoot) {
|
|||
}
|
||||
|
||||
async function selectOption(input, message, options) {
|
||||
// 只有一个选项时直接返回,避免无意义的终端交互。
|
||||
if (options.length === 1) {
|
||||
return options[0];
|
||||
}
|
||||
|
|
@ -61,6 +66,7 @@ async function selectOption(input, message, options) {
|
|||
}
|
||||
|
||||
async function resolveEnvironment(input, config, environmentArgument) {
|
||||
// 环境名称以 jjb.config.js 的 environment 键为准,不在 CLI 中写死。
|
||||
const environments = config.environment && typeof config.environment === 'object'
|
||||
? Object.keys(config.environment)
|
||||
: [];
|
||||
|
|
@ -80,6 +86,7 @@ async function resolveEnvironment(input, config, environmentArgument) {
|
|||
}
|
||||
|
||||
function resolveBuildScript(buildScripts, environment) {
|
||||
// 显式传入 production 时只允许使用 build:production,防止构建错环境。
|
||||
const buildScript = `build:${environment}`;
|
||||
if (!buildScripts.includes(buildScript)) {
|
||||
throw new Error(`package.json 中没有构建脚本 ${buildScript}`);
|
||||
|
|
@ -88,6 +95,7 @@ function resolveBuildScript(buildScripts, environment) {
|
|||
}
|
||||
|
||||
function ensureDependencies(projectRoot) {
|
||||
// 业务项目没有 node_modules 时才安装依赖,并且始终使用 Yarn。
|
||||
if (fs.existsSync(path.join(projectRoot, 'node_modules'))) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -111,13 +119,28 @@ function copyBuildOutput(projectRoot, javaRepository, appIdentifier) {
|
|||
const templateRoot = path.join(javaRepository, JAVA_TEMPLATE_PATH);
|
||||
const targetApplicationPath = path.join(templateRoot, appIdentifier);
|
||||
fs.mkdirSync(templateRoot, { recursive: true });
|
||||
// 先删除旧应用目录再整体复制,避免已从新构建中删除的静态文件残留。
|
||||
fs.rmSync(targetApplicationPath, { recursive: true, force: true });
|
||||
fs.cpSync(applicationPath, targetApplicationPath, { recursive: true });
|
||||
fs.copyFileSync(indexPath, path.join(templateRoot, `${appIdentifier}.html`));
|
||||
}
|
||||
|
||||
/*
|
||||
* zy-gbs-cmd push java production 的主执行链:
|
||||
* 1. 把执行命令的当前目录当作前端业务项目。
|
||||
* 2. 读取业务项目的 package.json 和 jjb.config.js。
|
||||
* 3. 将 production 匹配成 build:production,并读取 production 对应的 Java 分支。
|
||||
* 4. 把 jjb.config.js 中的 javaGit 克隆到系统临时目录。
|
||||
* 5. 在前端业务项目中执行 yarn run build:production。
|
||||
* 6. 把 dist/index.html 和 dist/<appIdentifier>/ 复制到临时 Java 仓库。
|
||||
* 7. 在临时 Java 仓库中执行 git add、git commit、git push。
|
||||
* 8. 删除临时 Java 仓库。
|
||||
*/
|
||||
async function pushJava({ environment: environmentArgument } = {}) {
|
||||
// 第 1 步:命令必须在业务项目根目录执行,process.cwd() 就是用户执行命令时所在的目录。
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
// 第 2 步:从业务项目读取构建脚本和 Java 仓库配置,不读取 zy-gbs-cmd 自己的配置。
|
||||
const packageJson = readPackageJson(projectRoot);
|
||||
const config = readProjectConfig(projectRoot);
|
||||
const buildScripts = Object.keys(packageJson.scripts || {}).filter(name =>
|
||||
|
|
@ -135,18 +158,23 @@ async function pushJava({ environment: environmentArgument } = {}) {
|
|||
let buildScript;
|
||||
let environment;
|
||||
|
||||
// 第 3 步:确定本次使用哪个构建脚本和环境。
|
||||
if (environmentArgument) {
|
||||
// 例如 production 会自动匹配 build:production,不再要求用户选择。
|
||||
environment = await resolveEnvironment(input, config, environmentArgument);
|
||||
buildScript = resolveBuildScript(buildScripts, environment);
|
||||
} else {
|
||||
// 未带环境参数时交互选择。
|
||||
buildScript = await selectOption(input, '请选择构建命令:', buildScripts);
|
||||
environment = await resolveEnvironment(input, config);
|
||||
}
|
||||
|
||||
const environmentConfig = environment ? config.environment[environment] || {} : {};
|
||||
// 第 4 步准备:分支优先读取 environment.production.javaGitBranch,最后才回退到 master。
|
||||
const branch = environmentConfig.javaGitBranch || config.javaGitBranch || DEFAULT_BRANCH;
|
||||
ensureYarnAvailable();
|
||||
|
||||
// 第 4 步:创建临时目录,并把 config.javaGit 指向的 Java 仓库克隆进去。
|
||||
temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'zy-gbs-cmd-java-'));
|
||||
const javaRepository = path.join(temporaryRoot, config.javaGitName);
|
||||
|
||||
|
|
@ -155,10 +183,14 @@ async function pushJava({ environment: environmentArgument } = {}) {
|
|||
run('git', ['checkout', branch], javaRepository);
|
||||
run('git', ['pull'], javaRepository);
|
||||
|
||||
// 第 5 步:回到前端业务项目安装依赖并构建,Java 仓库中不会执行 Yarn。
|
||||
ensureDependencies(projectRoot);
|
||||
runYarn(['run', buildScript], projectRoot);
|
||||
|
||||
// 第 6 步:把前端 dist 产物复制到 Java 仓库的 resources/templates 目录。
|
||||
copyBuildOutput(projectRoot, javaRepository, config.appIdentifier);
|
||||
|
||||
// 第 7 步:下面所有 Git 命令都在临时 Java 仓库中执行。
|
||||
run('git', ['add', '.'], javaRepository);
|
||||
const changedFiles = childProcess.execFileSync('git', ['status', '--porcelain'], {
|
||||
cwd: javaRepository,
|
||||
|
|
@ -166,6 +198,7 @@ async function pushJava({ environment: environmentArgument } = {}) {
|
|||
}).trim();
|
||||
|
||||
if (!changedFiles) {
|
||||
// 没有差异时不创建空提交,也不执行远程推送。
|
||||
console.log('构建产物没有变化,无需提交。');
|
||||
return;
|
||||
}
|
||||
|
|
@ -173,6 +206,7 @@ async function pushJava({ environment: environmentArgument } = {}) {
|
|||
const defaultMessage = `zy-gbs-cmd: update ${config.appIdentifier}`;
|
||||
const message = (await input.question(`请输入提交信息 (${defaultMessage}): `)).trim()
|
||||
|| defaultMessage;
|
||||
// Git 认证和提交作者均由本机 Git 配置提供,工具本身不保存凭据或写死 author。
|
||||
run('git', ['commit', '-m', message, '--no-verify'], javaRepository);
|
||||
run('git', ['push'], javaRepository);
|
||||
console.log('代码推送完成。');
|
||||
|
|
@ -180,6 +214,7 @@ async function pushJava({ environment: environmentArgument } = {}) {
|
|||
input.close();
|
||||
if (temporaryRoot) {
|
||||
try {
|
||||
// 第 8 步:成功或失败都会删除临时 Java 仓库;清理失败只告警,不覆盖原始错误。
|
||||
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
||||
} catch (error) {
|
||||
console.warn(`临时目录清理失败: ${error.message}`);
|
||||
|
|
|
|||
Loading…
Reference in New Issue