init
commit
13f8f61b1a
|
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
node_modules/
|
||||
*.tgz
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
# zy-gbs-cmd
|
||||
|
||||
`zy-gbs-cmd` 用于构建前端项目,并将构建产物提交到业务项目配置的 Java Git 仓库。
|
||||
|
||||
工具不调用授权接口,也不保存账号密码。Git 克隆和推送使用当前电脑已有的 Git 凭据。
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Node.js 18 或更高版本
|
||||
- Git
|
||||
- Yarn
|
||||
|
||||
`src/push.js` 使用的模块均为 Node.js 内置模块,无需安装运行时依赖。
|
||||
|
||||
业务项目的依赖安装和项目构建只使用 Yarn;Windows 下会自动调用 `yarn.cmd`。
|
||||
|
||||
## 安装
|
||||
|
||||
安装已发布版本可以使用 npm 或 Yarn:
|
||||
|
||||
```bash
|
||||
npm install -g zy-gbs-cmd
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn global add zy-gbs-cmd
|
||||
```
|
||||
|
||||
本地安装当前目录:
|
||||
|
||||
```bash
|
||||
npm install -g
|
||||
```
|
||||
|
||||
## 业务项目配置
|
||||
|
||||
在业务项目根目录创建或修改 `jjb.config.js`:
|
||||
|
||||
`javaGit` 必须是完整的 Git 克隆地址,不能只填写 Git 服务地址。
|
||||
|
||||
## 使用
|
||||
|
||||
在业务项目根目录执行:
|
||||
|
||||
```bash
|
||||
zy-gbs-cmd push java
|
||||
```
|
||||
|
||||
也可以直接指定环境:
|
||||
|
||||
```bash
|
||||
zy-gbs-cmd push java production
|
||||
```
|
||||
|
||||
指定环境后会自动执行同名构建脚本,例如 `production` 对应 `build:production`,不再询问构建命令和环境。
|
||||
|
||||
工具会把:
|
||||
|
||||
```text
|
||||
dist/index.html
|
||||
dist/<appIdentifier>/
|
||||
```
|
||||
|
||||
复制到 Java 仓库:
|
||||
|
||||
```text
|
||||
start/src/main/resources/templates/<appIdentifier>.html
|
||||
start/src/main/resources/templates/<appIdentifier>/
|
||||
```
|
||||
|
||||
## 发布到 npm Registry
|
||||
|
||||
登录 Registry:
|
||||
|
||||
```bash
|
||||
yarn login
|
||||
```
|
||||
|
||||
首次发布:
|
||||
|
||||
```bash
|
||||
yarn publish
|
||||
```
|
||||
|
||||
后续发布前需要先更新版本号,例如:
|
||||
|
||||
```bash
|
||||
yarn version --patch
|
||||
yarn publish
|
||||
```
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const { version } = require('../package.json');
|
||||
|
||||
function printHelp() {
|
||||
console.log(`zy-gbs-cmd <command>
|
||||
|
||||
使用:
|
||||
zy-gbs-cmd help 查看帮助
|
||||
zy-gbs-cmd v 查看版本
|
||||
zy-gbs-cmd push java [environment] 构建并推送到 Java Git 仓库
|
||||
`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const [, , command, argument, environment] = process.argv;
|
||||
|
||||
if (!command || command === 'help') {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === 'v') {
|
||||
console.log(`当前版本: v${version}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === 'push' && argument === 'java') {
|
||||
await require('../src/push')({ environment });
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('无效命令,请执行 zy-gbs-cmd help 查看用法。');
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(`推送失败: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "zy-gbs-cmd",
|
||||
"version": "1.0.0",
|
||||
"private": false,
|
||||
"description": "",
|
||||
"author": "LiuJiaNan",
|
||||
"license": "MIT",
|
||||
"type": "commonjs",
|
||||
"files": [
|
||||
"bin",
|
||||
"src",
|
||||
"README.md"
|
||||
],
|
||||
"bin": {
|
||||
"zy-gbs-cmd": "bin/command.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node test/package-manager.test.js && node test/push.test.js && node bin/command.js help",
|
||||
"prepublishOnly": "yarn test",
|
||||
"patch": "npm version patch",
|
||||
"minor": "npm version minor",
|
||||
"release": "npm publish"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
const childProcess = require('child_process');
|
||||
|
||||
function getExecutable(command) {
|
||||
return process.platform === 'win32' ? `${command}.cmd` : command;
|
||||
}
|
||||
|
||||
function commandExists(command) {
|
||||
const lookupCommand = process.platform === 'win32' ? 'where.exe' : 'which';
|
||||
const result = childProcess.spawnSync(lookupCommand, [getExecutable(command)], {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
return result.status === 0;
|
||||
}
|
||||
|
||||
function ensureYarnAvailable(isCommandAvailable = commandExists) {
|
||||
if (!isCommandAvailable('yarn')) {
|
||||
throw new Error('未检测到 Yarn,请先安装 Yarn');
|
||||
}
|
||||
}
|
||||
|
||||
function runYarn(args, cwd) {
|
||||
ensureYarnAvailable();
|
||||
const executable = getExecutable('yarn');
|
||||
console.log(`执行: yarn ${args.join(' ')}`);
|
||||
if (process.platform === 'win32') {
|
||||
childProcess.execFileSync(process.env.ComSpec || 'cmd.exe', [
|
||||
'/d',
|
||||
'/s',
|
||||
'/c',
|
||||
executable,
|
||||
...args,
|
||||
], { cwd, stdio: 'inherit' });
|
||||
return;
|
||||
}
|
||||
childProcess.execFileSync(executable, args, { cwd, stdio: 'inherit' });
|
||||
}
|
||||
|
||||
module.exports = { commandExists, ensureYarnAvailable, runYarn };
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
const childProcess = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const readline = require('readline/promises');
|
||||
const { ensureYarnAvailable, runYarn } = require('./package-manager');
|
||||
|
||||
const DEFAULT_BRANCH = 'master';
|
||||
const CONFIG_FILE_NAME = 'jjb.config.js';
|
||||
const JAVA_TEMPLATE_PATH = path.join('start', 'src', 'main', 'resources', 'templates');
|
||||
|
||||
function run(command, args, cwd) {
|
||||
console.log(`执行: ${command} ${args.join(' ')}`);
|
||||
childProcess.execFileSync(command, args, { cwd, stdio: 'inherit' });
|
||||
}
|
||||
|
||||
function readProjectConfig(projectRoot) {
|
||||
const configPath = path.join(projectRoot, CONFIG_FILE_NAME);
|
||||
if (!fs.existsSync(configPath)) {
|
||||
throw new Error(`未找到 ${CONFIG_FILE_NAME}`);
|
||||
}
|
||||
|
||||
delete require.cache[require.resolve(configPath)];
|
||||
const importedConfig = require(configPath);
|
||||
const config = importedConfig.default || importedConfig;
|
||||
const requiredFields = ['javaGit', 'javaGitName', 'appIdentifier'];
|
||||
const missingFields = requiredFields.filter(field => !config[field]);
|
||||
|
||||
if (missingFields.length) {
|
||||
throw new Error(`${CONFIG_FILE_NAME} 缺少字段: ${missingFields.join(', ')}`);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function readPackageJson(projectRoot) {
|
||||
const packagePath = path.join(projectRoot, 'package.json');
|
||||
if (!fs.existsSync(packagePath)) {
|
||||
throw new Error('未找到 package.json');
|
||||
}
|
||||
|
||||
return JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||||
}
|
||||
|
||||
async function selectOption(input, message, options) {
|
||||
if (options.length === 1) {
|
||||
return options[0];
|
||||
}
|
||||
|
||||
console.log(message);
|
||||
options.forEach((option, index) => console.log(` ${index + 1}. ${option}`));
|
||||
|
||||
while (true) {
|
||||
const answer = await input.question('请输入序号: ');
|
||||
const selected = options[Number(answer) - 1];
|
||||
if (selected) {
|
||||
return selected;
|
||||
}
|
||||
console.log('请输入有效序号。');
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveEnvironment(input, config, environmentArgument) {
|
||||
const environments = config.environment && typeof config.environment === 'object'
|
||||
? Object.keys(config.environment)
|
||||
: [];
|
||||
|
||||
if (!environments.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (environmentArgument) {
|
||||
if (!environments.includes(environmentArgument)) {
|
||||
throw new Error(`未找到环境 ${environmentArgument}`);
|
||||
}
|
||||
return environmentArgument;
|
||||
}
|
||||
|
||||
return selectOption(input, '请选择环境:', environments);
|
||||
}
|
||||
|
||||
function resolveBuildScript(buildScripts, environment) {
|
||||
const buildScript = `build:${environment}`;
|
||||
if (!buildScripts.includes(buildScript)) {
|
||||
throw new Error(`package.json 中没有构建脚本 ${buildScript}`);
|
||||
}
|
||||
return buildScript;
|
||||
}
|
||||
|
||||
function ensureDependencies(projectRoot) {
|
||||
if (fs.existsSync(path.join(projectRoot, 'node_modules'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('未检测到 node_modules,正在安装依赖。');
|
||||
runYarn(['install'], projectRoot);
|
||||
}
|
||||
|
||||
function copyBuildOutput(projectRoot, javaRepository, appIdentifier) {
|
||||
const distPath = path.join(projectRoot, 'dist');
|
||||
const indexPath = path.join(distPath, 'index.html');
|
||||
const applicationPath = path.join(distPath, appIdentifier);
|
||||
|
||||
if (!fs.existsSync(indexPath)) {
|
||||
throw new Error('构建产物缺少 dist/index.html');
|
||||
}
|
||||
if (!fs.existsSync(applicationPath)) {
|
||||
throw new Error(`构建产物缺少 dist/${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`));
|
||||
}
|
||||
|
||||
async function pushJava({ environment: environmentArgument } = {}) {
|
||||
const projectRoot = process.cwd();
|
||||
const packageJson = readPackageJson(projectRoot);
|
||||
const config = readProjectConfig(projectRoot);
|
||||
const buildScripts = Object.keys(packageJson.scripts || {}).filter(name =>
|
||||
name.toLowerCase().includes('build')
|
||||
);
|
||||
|
||||
if (!buildScripts.length) {
|
||||
throw new Error('package.json 中没有名称包含 build 的脚本');
|
||||
}
|
||||
|
||||
const input = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
let temporaryRoot;
|
||||
|
||||
try {
|
||||
let buildScript;
|
||||
let environment;
|
||||
|
||||
if (environmentArgument) {
|
||||
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] || {} : {};
|
||||
const branch = environmentConfig.javaGitBranch || config.javaGitBranch || DEFAULT_BRANCH;
|
||||
ensureYarnAvailable();
|
||||
|
||||
temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'zy-gbs-cmd-java-'));
|
||||
const javaRepository = path.join(temporaryRoot, config.javaGitName);
|
||||
|
||||
console.log(`正在克隆 Java 仓库: ${config.javaGit}`);
|
||||
run('git', ['clone', config.javaGit, javaRepository], temporaryRoot);
|
||||
run('git', ['checkout', branch], javaRepository);
|
||||
run('git', ['pull'], javaRepository);
|
||||
|
||||
ensureDependencies(projectRoot);
|
||||
runYarn(['run', buildScript], projectRoot);
|
||||
copyBuildOutput(projectRoot, javaRepository, config.appIdentifier);
|
||||
|
||||
run('git', ['add', '.'], javaRepository);
|
||||
const changedFiles = childProcess.execFileSync('git', ['status', '--porcelain'], {
|
||||
cwd: javaRepository,
|
||||
encoding: 'utf8',
|
||||
}).trim();
|
||||
|
||||
if (!changedFiles) {
|
||||
console.log('构建产物没有变化,无需提交。');
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultMessage = `zy-gbs-cmd: update ${config.appIdentifier}`;
|
||||
const message = (await input.question(`请输入提交信息 (${defaultMessage}): `)).trim()
|
||||
|| defaultMessage;
|
||||
run('git', ['commit', '-m', message, '--no-verify'], javaRepository);
|
||||
run('git', ['push'], javaRepository);
|
||||
console.log('代码推送完成。');
|
||||
} finally {
|
||||
input.close();
|
||||
if (temporaryRoot) {
|
||||
try {
|
||||
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
||||
} catch (error) {
|
||||
console.warn(`临时目录清理失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = pushJava;
|
||||
module.exports.resolveBuildScript = resolveBuildScript;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
const assert = require('assert');
|
||||
const { commandExists, ensureYarnAvailable } = require('../src/package-manager');
|
||||
|
||||
assert.strictEqual(commandExists('yarn'), true, '当前环境未安装 Yarn');
|
||||
assert.doesNotThrow(() => ensureYarnAvailable(() => true));
|
||||
assert.throws(() => ensureYarnAvailable(() => false), /未检测到 Yarn/);
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
const assert = require('assert');
|
||||
const { resolveBuildScript } = require('../src/push');
|
||||
|
||||
const buildScripts = ['build', 'build:development', 'build:production'];
|
||||
|
||||
assert.strictEqual(resolveBuildScript(buildScripts, 'production'), 'build:production');
|
||||
assert.strictEqual(resolveBuildScript(buildScripts, 'development'), 'build:development');
|
||||
assert.throws(
|
||||
() => resolveBuildScript(buildScripts, 'test'),
|
||||
/package.json 中没有构建脚本 build:test/
|
||||
);
|
||||
Loading…
Reference in New Issue