@szzbmy/lowcode-cli
Version:
🐇 lowcode-cli is an efficient cli tool for Rabbitpre plugin component secondary development. ❤️
525 lines (524 loc) • 19.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.openDebugUrl = exports.createLowcodeDebugServer = exports.getLaunchConfigList = exports.getDebugConfigsByLaunchConfig = exports.getDebugConfig = exports.getDebugConfigByConfigJson = exports.updateLaunchConfig = exports.getLaunchConfigByDebugId = exports.updateLaunchConfigJson = exports.getLaunchJson = exports.createLaunchJson = exports.createDefaultLaunchConfig = exports.checkServersStarted = exports.checkPortAvailable = exports.writeFileAndMkdir = exports.createDebugId = exports.isFileExisted = void 0;
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const url_1 = __importDefault(require("url"));
const http_1 = __importDefault(require("http"));
const net_1 = __importDefault(require("net"));
const open_1 = __importStar(require("open"));
const fs_2 = require("../../config/fs");
const request_1 = require("../../utils/request");
const config_1 = __importDefault(require("../../config"));
const server_helper_1 = require("../../utils/server-helper");
const json_1 = require("../../utils/json");
const logger_1 = __importDefault(require("../../utils/logger"));
// 配置缓存,可动态更新
let launchJson = null;
/**
* 根据文件/文件夹相对当前文件夹路径判断是否存在
* @param relativePath 相对路径
* @returns
*/
function isFileExisted(relativePath) {
const cwd = process.cwd();
const absolutePath = path_1.default.resolve(cwd, relativePath);
try {
fs_1.default.accessSync(absolutePath, fs_1.default.constants.F_OK);
return true;
}
catch (error) {
return false;
}
}
exports.isFileExisted = isFileExisted;
/**
* 生成调试 id
* @param length
* @returns
*/
function createDebugId(length) {
// 默认长度 24
return Array.from(Array(Number(length) || 24), () => Math.floor(Math.random() * 36).toString(36)).join('');
}
exports.createDebugId = createDebugId;
/**
* 创建文件,如果文件所在文件夹不存在,先创建文件夹
* @param {Record<string, any>} content
* @param {String} filePath
*/
async function writeFileAndMkdir(content, filePath) {
const cwd = process.cwd();
const dirPath = filePath.substring(0, filePath.lastIndexOf('/'));
const isDirExisted = isFileExisted(dirPath);
const parseContent = (0, json_1.stringify)(content);
// 调试配置文件夹不存在,先生成再创建缓存文件
if (!isDirExisted) {
fs_1.default.mkdirSync(dirPath, { recursive: true });
fs_1.default.writeFileSync(path_1.default.resolve(cwd, filePath), parseContent);
}
else {
fs_1.default.writeFileSync(path_1.default.resolve(cwd, filePath), parseContent);
}
}
exports.writeFileAndMkdir = writeFileAndMkdir;
/** 检查端口是否可用 true-可用 */
const isAvailablePort = async (pluginCdn) => {
try {
await (0, request_1.get)(`${pluginCdn}/config.jsonc`, null, {
customOptions: { blob: true },
});
/** 静态资源服务已启动,端口不可用 */
return false;
}
catch (err) {
/** 请求出错,静态资源服务未启动,端口可用 */
return true;
}
};
/** 检查端口是否占用 */
function checkPortAvailable(port) {
return new Promise(resolve => {
const server = net_1.default.createServer().listen(port);
server.on('listening', () => {
server.close();
resolve(true);
});
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
server.close();
resolve(false);
}
});
});
}
exports.checkPortAvailable = checkPortAvailable;
// 校验是否多组件库静态服务已启动
async function checkServersStarted(launchConfig) {
let isServersStarted = true;
const debugConfigs = getDebugConfigsByLaunchConfig(launchConfig);
const pluginCdns = debugConfigs.map(item => item.debugPluginCDN);
const noRepeatCdns = pluginCdns.reduce((prev, cur) => {
if (!prev.includes(cur)) {
prev.push(cur);
}
return prev;
}, []);
// 如果不重复数组的长度不等于原数组长度,说明有重复
if (noRepeatCdns.length !== pluginCdns.length) {
isServersStarted = false;
}
else {
const availablePortPromises = [];
noRepeatCdns.forEach((item) => {
availablePortPromises.push(isAvailablePort(item));
});
// 批量请求判断端口是否占用
const isServerStartArr = await Promise.all(availablePortPromises);
const isAllServerStart = isServerStartArr.every(item => !item);
isServersStarted = isAllServerStart;
}
return isServersStarted;
}
exports.checkServersStarted = checkServersStarted;
/**
* 创建默认启动配置
* @returns {Promise<LaunchConfig>} 启动配置信息
*/
async function createDefaultLaunchConfig() {
const debugId = createDebugId(8);
const randomPort = await (0, server_helper_1.getRandomPort)();
return {
debugId,
name: 'launch',
appid: '',
shortUrl: '',
debugServerDomain: `http://127.0.0.1:${randomPort}`,
workspaces: [
{
path: './config.jsonc',
},
],
};
}
exports.createDefaultLaunchConfig = createDefaultLaunchConfig;
/**
* 创建 launch.json
*/
async function createLaunchJson() {
const config = await createDefaultLaunchConfig();
// 启动配置文件信息
const launchConfigInfo = {
version: '1.0.0',
configurations: [config],
};
writeFileAndMkdir(launchConfigInfo, './.lowcode/launch.json');
}
exports.createLaunchJson = createLaunchJson;
/**
* 获取 launch.json 文件数据
* @returns
*/
function getLaunchJson() {
if (launchJson)
return launchJson;
let launchConfig = null;
try {
const cwd = process.cwd();
const launchConfigPath = path_1.default.resolve(cwd, './.lowcode/launch.json');
const launchConfigInfo = fs_1.default.readFileSync(launchConfigPath, 'utf8');
launchConfig = (0, json_1.parse)(launchConfigInfo);
launchJson = launchConfig;
}
catch (error) {
logger_1.default.debug('找不到组件配置');
}
return launchConfig;
}
exports.getLaunchJson = getLaunchJson;
/**
* 更新 launch.json 文件
* @param configJson 更新信息
*/
function updateLaunchConfigJson(configJson) {
const cwd = process.cwd();
const launchInfo = getLaunchJson();
const launchConfigPath = path_1.default.resolve(cwd, './.lowcode/launch.json');
if (launchInfo) {
const content = Object.assign(launchInfo, configJson);
const parseContent = (0, json_1.stringify)(content);
launchJson = content;
fs_1.default.writeFileSync(launchConfigPath, parseContent);
}
}
exports.updateLaunchConfigJson = updateLaunchConfigJson;
/**
* 获取配置根据调试id
* @param {String} debugId 调试id
* @returns
*/
function getLaunchConfigByDebugId(debugId) {
const launchInfo = getLaunchJson();
if (launchInfo) {
const { configurations } = launchInfo;
return configurations.find(item => item.debugId === debugId);
}
return null;
}
exports.getLaunchConfigByDebugId = getLaunchConfigByDebugId;
/** 更新 launch 配置 */
async function updateLaunchConfig(configInfo) {
const { debugId } = configInfo;
const launchInfo = getLaunchJson();
if (launchInfo) {
const { configurations } = launchInfo;
const newConfigs = [...configurations];
const debugConfigs = newConfigs.map(item => {
if (debugId === item.debugId) {
return { ...item, ...configInfo };
}
return { ...item };
});
updateLaunchConfigJson({
...launchInfo,
configurations: [...debugConfigs],
});
}
}
exports.updateLaunchConfig = updateLaunchConfig;
/**
* 根据config配置获取调试配置
* @param configJson config 配置信息
* @param machineDebugCdn 真机调试 cdn
* @returns
*/
function getDebugConfigByConfigJson(configJson, cdnUrl) {
const { schemaType, schemaVersion = '', bindPluginName } = configJson;
const { debug } = configJson;
const packageConfig = (0, fs_2.getPackageConfig)();
const { name: packageName } = packageConfig;
const debugComponentList = configJson.components.map((cmp) => {
const debugCmp = { ...cmp };
if (debugCmp.isControllerComponent === true) {
debugCmp.key = configJson.key;
debugCmp.label = configJson.label;
debugCmp.desc = configJson.desc;
}
return debugCmp;
});
return {
schemaType,
schemaVersion,
debugComponentList,
packageName,
bindPluginName,
debugPluginCDN: cdnUrl || `http://127.0.0.1:${debug.port}`,
};
}
exports.getDebugConfigByConfigJson = getDebugConfigByConfigJson;
function getDebugConfig(debugPath) {
try {
const { path: configPath, cdnUrl } = debugPath;
const configRelativePath = path_1.default.resolve(configPath);
const configInfoStr = fs_1.default.readFileSync(configRelativePath, 'utf8');
const configJson = (0, json_1.parse)(configInfoStr);
const debugConfig = getDebugConfigByConfigJson(configJson, cdnUrl);
return debugConfig;
}
catch (error) {
logger_1.default.debug('找不到Config配置信息');
return undefined;
}
}
exports.getDebugConfig = getDebugConfig;
let debugConfigsCache = [];
function getDebugConfigsByLaunchConfig(launchConfig) {
if (debugConfigsCache.length)
return debugConfigsCache;
const debugConfigs = [];
if (launchConfig) {
const { workspaces } = launchConfig;
for (let i = 0; i < workspaces.length; i++) {
const item = workspaces[i];
const debugConfig = getDebugConfig(item);
if (debugConfig) {
debugConfigs.push(debugConfig);
}
else {
// 如果某一个configPath下拿不到数据,
// 清空debugConfigs,并跳出循环
debugConfigs.length = 0;
break;
}
}
}
debugConfigsCache = [...debugConfigs];
return debugConfigs;
}
exports.getDebugConfigsByLaunchConfig = getDebugConfigsByLaunchConfig;
function getLaunchConfigList() {
let list = [];
try {
const launchInfo = getLaunchJson();
if (launchInfo) {
const { configurations } = launchInfo;
list = configurations.map(item => ({
value: item.debugId,
label: item.name,
}));
}
}
catch (error) {
logger_1.default.error('获取启动配置信息列表失败');
}
return list;
}
exports.getLaunchConfigList = getLaunchConfigList;
/**
* 生成 debugSession
* @returns
*/
function generateDebugSession(launchConfig) {
const { debugServerDomain } = launchConfig;
const { debugId } = launchConfig;
try {
return Buffer.from(encodeURIComponent(JSON.stringify({ debugServerUrl: debugServerDomain, debugId }))).toString('base64');
}
catch (err) {
throw Error(err.message || '未获取调试配置');
}
}
/** 生成编辑器调试链接 */
function getDebugUrl(launchConfig) {
try {
const debugSession = generateDebugSession(launchConfig);
const { appid, shortUrl } = launchConfig;
if (appid && shortUrl) {
return `${config_1.default.editorHost}/?debugSession=${debugSession}&debug=1&appid=${appid}`;
}
return `${config_1.default.editorHost}/?debugSession=${debugSession}&debug=1`;
}
catch (err) {
throw Error(err.message || '未获取 debugSession');
}
}
/** 生成渲染引擎调试链接 */
function getDebugShortUrl(launchConfig) {
try {
const debugSession = generateDebugSession(launchConfig);
const { appid, shortUrl } = launchConfig;
if (appid && shortUrl) {
return `${config_1.default.rendererHost}/m2/${shortUrl}?debugSession=${debugSession}&debug=1&appid=${appid}`;
}
return `${config_1.default.rendererHost}/m2/${shortUrl}?debugSession=${debugSession}&debug=1`;
}
catch (err) {
throw Error(err.message || '未获取 debugSession');
}
}
/** 生成调试链接并输出提示 */
function generateDebugUrl(launchConfig) {
const debugUrl = getDebugUrl(launchConfig);
const debugShortUrl = getDebugShortUrl(launchConfig);
return {
debugUrl,
debugShortUrl,
};
}
async function createLowcodeDebugServer(launchConfig) {
const { appid, debugServerPort, debugServerDomain, debugBrowser } = launchConfig;
const { debugUrl, debugShortUrl } = generateDebugUrl(launchConfig);
// 获取调试链接信息
const debugUrlInfo = url_1.default.parse(debugServerDomain, false);
const port = debugUrlInfo.port || '';
// 如果调试服务访问地址的端口号和调试服务端口号均不存在,直接退出
if (!port && !debugServerPort)
return undefined;
const server = (0, server_helper_1.createLowCodeServer)({
randomPort: debugServerPort || +port,
routers: [
{
/** 创建打开调试链接的服务与拼接请求链接 */
path: '/openDebugUrl',
func: function openDebugUrl(_req, res) {
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf8',
});
// 配置了调试浏览器,根据配置打开调试链接
if (debugBrowser) {
(0, open_1.default)(debugUrl, { app: { name: open_1.apps[debugBrowser] } });
}
else {
// 没有配置调试浏览器,使用默认浏览器打开调试链接
(0, open_1.default)(debugUrl);
}
if (appid) {
logDebugUrlIfNeed({ debugUrl, debugShortUrl });
}
res.end(JSON.stringify({ code: 200, msg: '打开调试链接成功!' }));
},
},
{
/** 创建保存调试作品信息(appid、shortUrl)的服务 */
path: '/getDebugConfigInfo',
func: function getDebugConfigInfo(req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
const urlInfo = url_1.default.parse(req.url || '', true);
const { query } = urlInfo;
const { debugId } = query;
const launchConfig = getLaunchConfigByDebugId(debugId || '');
const debugConfigs = getDebugConfigsByLaunchConfig(launchConfig);
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf8',
});
res.end(JSON.stringify({
code: 200,
msg: '获取缓存配置信息成功!',
data: debugConfigs,
}));
},
},
{
/** 创建保存调试作品信息(appid、shortUrl)的服务 */
path: '/saveDebugAppInfo',
func: function saveDebugAppInfo(req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
let debugAppInfo = '';
req.on('data', chunk => {
debugAppInfo += chunk;
});
req.on('end', () => {
if (debugAppInfo) {
const { appid, shortUrl, debugId } = (0, json_1.parse)(debugAppInfo);
updateLaunchConfig({ appid, shortUrl, debugId });
const launchConfig = getLaunchConfigByDebugId(debugId);
if (launchConfig) {
const { debugShortUrl, debugUrl } = generateDebugUrl(launchConfig);
logDebugUrlIfNeed({ debugShortUrl, debugUrl });
}
}
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf8',
});
res.end(JSON.stringify({ code: 200, msg: '绑定作品成功!' }));
});
},
},
],
});
return server;
}
exports.createLowcodeDebugServer = createLowcodeDebugServer;
let hasLoggedDebugUrl = false;
function logDebugUrlIfNeed({ debugUrl, debugShortUrl, }) {
if (!hasLoggedDebugUrl) {
logger_1.default.info(`调试链接为:${debugUrl} \n`);
logger_1.default.info(`生成 H5 调试链接为:${debugShortUrl} \n`);
hasLoggedDebugUrl = true;
}
}
const openDebugUrl = (openDebugUrl) => {
http_1.default
.get(openDebugUrl, res => {
const { statusCode } = res;
const contentType = res.headers['content-type'] || '';
let error;
/** 任何 2xx 状态码都表示成功响应,但这里只检查 200。 */
if (statusCode !== 200) {
error = new Error(`Request Failed.\nStatus Code: ${statusCode}`);
}
else if (!/^application\/json/.test(contentType)) {
error = new Error(`Invalid content-type.\nExpected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
/** 消费响应数据以释放内存 */
res.resume();
return;
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', chunk => {
rawData += chunk;
});
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
if (parsedData.code === 200) {
console.log(parsedData.msg || '');
}
}
catch (e) {
console.error(e.message);
}
});
})
.on('error', e => {
console.error(`Got error: ${e.message}`);
});
};
exports.openDebugUrl = openDebugUrl;