@tuzki/cli
Version:
🐇 lowcode-cli is an efficient cli tool for Rabbitpre plugin component secondary development. ❤️
355 lines (354 loc) • 13.1 kB
JavaScript
/*
* 项目相关工具方法
*
* @Author: xu.jin
* @Date: 2023-08-15 19:21:16
*
* Copyright © 2014-2023 Rabbitpre.com. All Rights Reserved.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import Logger from '@tuzki/scaffold-logger';
import archiver from 'archiver';
import compressing from 'compressing';
import FormData from 'form-data';
import fse from 'fs-extra';
import inquirer from 'inquirer';
import { join, resolve } from 'path';
import { deployModule } from '../apis/deploy.js';
import { checkVersionOnline, downloadModuleCode, findDevVersionList, getModuleList, getModuleTypes, } from '../apis/init-project.js';
import { parse } from './comment-json.js';
import { DIST_PATH, EXTRA_MODULE, TipsText, ZIP_PATH } from './constants.js';
import { cdTargetDir } from './shell.js';
const logger = Logger.get('cli:utils:project');
const selectModule = (opts) => __awaiter(void 0, void 0, void 0, function* () {
const { moduleType } = opts;
try {
const list = yield getModuleList(moduleType);
const moduleList = list.filter(item => item.name && item.key);
if (!moduleList.length) {
logger.warn(`请先在低代码平台创建${TipsText[moduleType]}`);
return;
}
// 询问选择模块
const answer = yield inquirer.prompt({
type: 'search-list',
name: 'module',
message: `Please select a ${moduleType} which created in lowcode manager`,
default: moduleList[0],
choices: moduleList.map(item => ({
name: item.name,
value: item.key,
})),
});
const module = moduleList.find(m => m.key === answer.module);
return module;
}
catch (err) {
logger.error(`获取低代码平台${TipsText[moduleType]}列表失败:${err}`);
process.exit(1);
}
});
const getModuleConfig = opt => {
try {
const { targetDir, schemaType, moduleType } = opt;
const filePath = schemaType && moduleType
? join(targetDir, `${moduleType}-${schemaType}.jsonc`)
: join(targetDir);
if (!fse.pathExistsSync(filePath))
return {};
const schema = fse.readFileSync(filePath, 'utf-8');
return parse(schema);
}
catch (err) {
logger.error(`获取模块配置数据 schema 失败,读取配置:${JSON.stringify(opt)}, ${err}`);
process.exit(1);
}
};
const downloadCode = (opts) => __awaiter(void 0, void 0, void 0, function* () {
const { moduleId, projectDir, moduleType, versionId } = opts;
try {
const templateRes = yield downloadModuleCode(Object.assign({ id: moduleId, type: moduleType }, (versionId ? { versionId } : {})));
// 解压到对应目录
yield compressing.zip.uncompress(templateRes, projectDir);
logger.info(`${TipsText[moduleType]}源码下载成功:${projectDir}`);
}
catch (err) {
logger.error(`${TipsText[moduleType]}源码下载失败:${err}`);
process.exit(1);
}
});
const askProjectInfo = () => __awaiter(void 0, void 0, void 0, function* () {
const questions = [
{
type: 'input',
name: 'author',
message: '请输入项目开发者名称(author):',
validate(author) {
if (!author)
return '开发者名称不能为空';
return true;
},
},
{
type: 'input',
name: 'desc',
message: '请输入项目描述(description):',
validate(desc) {
if (!desc)
return '项目描述不能为空';
return true;
},
},
];
const answers = yield inquirer.prompt(questions);
return answers;
});
/**
* 源码上传
*/
const uploadSource = (dirPath, moduleType) => __awaiter(void 0, void 0, void 0, function* () {
try {
// cd 前一个目录
cdTargetDir('..');
// 获取当前项目路径
const zipPath = resolve(dirPath, `../${ZIP_PATH}`);
/** 压缩 */
yield compress(dirPath, zipPath, moduleType);
/** 发布 */
yield release(dirPath, zipPath, moduleType);
}
catch (err) {
logger.error(`${TipsText[moduleType]}发布失败:${err}`);
process.exit(1);
}
});
/**
* 压缩方法
*/
const compress = (projectPath, zipPath, moduleType) => {
return new Promise((resolve, reject) => {
const output = fse.createWriteStream(zipPath);
const archive = archiver('zip', {
zlib: { level: 9 },
});
// 扫描除 node_modules 和 dist 文件夹之外的文件进行压缩
archive.glob('**/*', {
cwd: projectPath,
ignore: ['node_modules/**', `${DIST_PATH}/**`, '.DS_Store'],
dot: true,
});
output.on('close', () => {
logger.info(`${TipsText[moduleType]}压缩完成,压缩大小:${archive.pointer()}bytes`);
resolve(archive.pointer());
});
// 监听结束
output.on('end', () => {
logger.info(`${TipsText[moduleType]}压缩结束`);
});
// 监听错误
archive.on('error', (err) => {
reject(err);
});
archive.pipe(output);
archive.finalize();
});
};
const release = (dirPath, zipPath, moduleType) => __awaiter(void 0, void 0, void 0, function* () {
if (!fse.existsSync(zipPath)) {
logger.error('代码包[source.zip]不存在');
return;
}
try {
// 模型数据获取
const model = getModuleConfig({
targetDir: join(dirPath, '.tuzki'),
schemaType: "model" /* SchemaType.MODEL */,
moduleType,
});
// 实现数据获取
const implement = getModuleConfig({
targetDir: join(dirPath, '.tuzki'),
schemaType: "implement" /* SchemaType.IMPLEMENT */,
moduleType,
});
// 读取 source.zip 存在 formData 中
const form = new FormData();
form.append('file', fse.createReadStream(zipPath), {
filename: ZIP_PATH,
contentType: 'application/zip',
});
// 将模型数据塞到 formData 中
const modelDataStr = JSON.stringify(model);
form.append('modelStr', modelDataStr);
// 将实现数据塞到 formData 中
const implementDataStr = JSON.stringify(implement);
form.append('implementStr', implementDataStr);
form.append('type', moduleType);
yield deployModule(form);
logger.info(`部署${TipsText[moduleType]}代码包成功`);
fse.removeSync(zipPath);
}
catch (error) {
fse.removeSync(zipPath);
logger.error(`部署${TipsText[moduleType]}代码包失败:${error}`);
process.exit(1);
}
});
const askCoverProjectDir = (dirPath) => __awaiter(void 0, void 0, void 0, function* () {
try {
const answers = yield inquirer.prompt([
{
type: 'confirm',
name: 'isCover',
message: `The project directory ${dirPath} already exists, do you want to cover it?`,
},
]);
return answers.isCover;
}
catch (err) {
logger.error('覆盖已存在项目目录出错:', err);
process.exit(1);
}
});
const askProjectName = () => __awaiter(void 0, void 0, void 0, function* () {
try {
const answers = yield inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Please input project name:',
validate(name) {
if (!name)
return 'project name is required';
return true;
},
},
]);
return answers.name;
}
catch (err) {
logger.error('项目名称输入出错', err);
process.exit(1);
}
});
const selectModuleType = () => __awaiter(void 0, void 0, void 0, function* () {
try {
const types = yield getModuleTypes();
const parsedTypes = [...types, ...EXTRA_MODULE].map((type, idx) => ({
name: `${idx + 1}. ${type} project${type === "component" /* ProjectType.COMPONENT */ ? ' (Coming soon)' : ''}`,
value: type,
}));
const answer = yield inquirer.prompt([
{
type: 'list',
name: 'type',
message: 'Please select a project type:',
choices: parsedTypes,
default: parsedTypes[0],
pageSize: 15,
},
]);
return answer.type;
}
catch (err) {
logger.error('选择模块项目类型出错', err);
process.exit(1);
}
});
const askSelectDevelopVersion = (versions, update) => __awaiter(void 0, void 0, void 0, function* () {
try {
// 解析 versions 为问询列表数据
const parsedVersions = versions.map(version => ({
name: version.version,
value: version.versionId,
}));
const answer = yield inquirer.prompt([
{
type: 'list',
name: 'version',
message: `Please select a developing version to ${update ? 'update' : 'deploy'}:`,
choices: parsedVersions,
default: parsedVersions[0],
pageSize: 15,
},
]);
const module = versions.find(m => m.versionId === answer.version);
return module;
}
catch (err) {
logger.error('问询选择开发版本失败');
process.exit(1);
}
});
const selectDevelopVersion = (moduleId, moduleType, update) => __awaiter(void 0, void 0, void 0, function* () {
try {
const versions = yield findDevVersionList(moduleId, moduleType);
// 无开发版本
if (!versions.length) {
logger.error('当前模块无开发中的版本,请先在低码平台创建开发版本');
process.exit(1);
}
return askSelectDevelopVersion(versions, update);
}
catch (err) {
logger.error(`选择开发版本失败,原因:${err}`);
process.exit(1);
}
});
const checkoutDevelopVersion = (localModel, moduleType) => __awaiter(void 0, void 0, void 0, function* () {
try {
let versionId;
const params = {
id: localModel.id,
type: moduleType,
};
(localModel === null || localModel === void 0 ? void 0 : localModel.versionId) && (params.versionId = localModel.versionId);
const isRelease = yield checkVersionOnline(params);
// 判断当前版本是否已上线
if (isRelease) {
const versions = yield findDevVersionList(localModel.id, moduleType);
if (!versions.length) {
logger.warn('当前版本已上线,且无开发中的版本,请先在低码平台创建开发版本');
process.exit(1);
}
const isCheckout = yield askCheckoutVersion();
// 不切换版本号,退出
if (!isCheckout) {
process.exit(1);
}
const version = yield askSelectDevelopVersion(versions);
versionId = version.versionId;
}
return versionId;
}
catch (err) {
logger.error('版本切换失败:', err);
process.exit(1);
}
});
const askCheckoutVersion = () => __awaiter(void 0, void 0, void 0, function* () {
try {
const answer = yield inquirer.prompt([
{
type: 'confirm',
name: 'isCheckout',
message: 'The current version has been released, do you want to switch to the developing version? If switch, will excute `update-project` to update project',
default: true,
},
]);
return answer.isCheckout;
}
catch (err) {
logger.error('问询是否切换开发版本失败:', err);
process.exit(1);
}
});
export { askCheckoutVersion, askCoverProjectDir, askProjectInfo, askProjectName, checkoutDevelopVersion, compress, downloadCode, getModuleConfig, release, selectDevelopVersion, selectModule, selectModuleType, uploadSource, };