UNPKG

@szzbmy/lowcode-cli

Version:

🐇 lowcode-cli is an efficient cli tool for Rabbitpre plugin component secondary development. ❤️

525 lines (524 loc) 19.8 kB
"use strict"; 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;