(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('path'), require('prettier'), require('axios'), require('lodash'), require('@vue/compiler-sfc'), require('magic-string'), require('glob'), require('node:util'), require('svgo'), require('postcss-value-parser')) : typeof define === 'function' && define.amd ? define(['exports', 'fs', 'path', 'prettier', 'axios', 'lodash', '@vue/compiler-sfc', 'magic-string', 'glob', 'node:util', 'svgo', 'postcss-value-parser'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.index = {}, global.fs, global.path, global.prettier, global.axios, global.lodash, global.compilerSfc, global.magicString, global.glob, global.util, global.svgo, global.valueParser)); })(this, (function (exports, fs, path, prettier, axios, lodash, compilerSfc, magicString, glob, util, svgo, valueParser) { 'use strict'; const config = { type: "admin", reqUrl: "", nameTag: true, eps: { enable: true, api: "", dist: "./build/cool", mapping: [ { // 自定义匹配 custom: ({ propertyName, type }) => { // 如果没有,返回null或者不返回,则继续遍历其他匹配规则 return null; }, }, { type: "string", test: ["varchar", "text", "simple-json"], }, { type: "string[]", test: ["simple-array"], }, { type: "Date", test: ["datetime", "date"], }, { type: "number", test: ["tinyint", "int", "decimal"], }, { type: "BigInt", test: ["bigint"], }, { type: "any", test: ["json"], }, ], }, svg: { skipNames: ["base"], }, tailwind: { enable: true, remUnit: 14, remPrecision: 6, rpxRatio: 2, darkTextClass: "dark:text-surface-50", }, }; // 根目录 function rootDir(path$1) { switch (config.type) { case "app": case "uniapp-x": return path.join(process.env.UNI_INPUT_DIR, path$1); default: return path.join(process.cwd(), path$1); } } // 首字母大写 function firstUpperCase(value) { return value.replace(/\b(\w)(\w*)/g, function ($0, $1, $2) { return $1.toUpperCase() + $2; }); } // 横杠转驼峰 function toCamel(str) { return str.replace(/([^-])(?:-+([^-]))/g, function ($0, $1, $2) { return $1 + $2.toUpperCase(); }); } // 创建目录 function createDir(path, recursive) { try { if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive }); } catch (err) { } } // 读取文件 function readFile(path, json) { try { const content = fs.readFileSync(path, "utf8"); return json ? JSON.parse(content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "")) : content; } catch (err) { } return ""; } // 写入文件 function writeFile(path, data) { try { return fs.writeFileSync(path, data); } catch (err) { } return ""; } // 解析body function parseJson(req) { return new Promise((resolve) => { let d = ""; req.on("data", function (chunk) { d += chunk; }); req.on("end", function () { try { resolve(JSON.parse(d)); } catch { resolve({}); } }); }); } // 格式化内容 function formatContent(content, options) { return prettier.format(content, { parser: "typescript", useTabs: true, tabWidth: 4, endOfLine: "lf", semi: true, ...options, }); } function error(message) { console.log("\x1B[31m%s\x1B[0m", message); } /** * 将模板字符串扁平化处理,转换为 Service 类型定义 * @param template - 包含 Service 类型定义的模板字符串 * @returns 处理后的 Service 类型定义字符串 * @throws {Error} 当模板中找不到 Service 类型定义时抛出错误 */ function flatten(template) { // 查找 Service 类型定义的起始位置 const startIndex = template.indexOf("export type Service = {"); // 保留 Service 类型定义前的内容 let header = template.substring(0, startIndex); // 获取 Service 类型定义及其内容,去除换行和制表符 const serviceContent = template.substring(startIndex).replace(/\n|\t/g, ""); let interfaces = ""; let serviceFields = ""; // 解析内容并生成接口定义 parse(serviceContent).forEach(({ key, content, level }) => { interfaces += `\nexport interface ${firstUpperCase(key)}Interface {${content}}\n`; serviceFields += `${key}: ${firstUpperCase(key)}Interface;`; }); return `${header}${interfaces}\nexport type Service = {${serviceFields}}`; } /** * 查找匹配的右花括号位置 * @param str - 要搜索的字符串 * @param startIndex - 开始搜索的位置 * @returns 匹配的右花括号位置 * @throws {Error} 当找不到匹配的右花括号时抛出错误 */ function findClosingBrace(str, startIndex) { let braceCount = 1; let currentIndex = startIndex; while (currentIndex < str.length && braceCount > 0) { if (str[currentIndex] === "{") braceCount++; if (str[currentIndex] === "}") braceCount--; currentIndex++; } if (braceCount !== 0) { throw new Error("Unmatched braces in the template"); } return currentIndex - 1; } /** * 解析内容中的嵌套结构 * @param content - 要解析的内容字符串 * @returns 解析结果数组,包含解析出的键值对 */ function parse(content, level = 0) { // 匹配形如 xxx: { ... } 的结构 const interfacePattern = /(\w+)\s*:\s*\{/g; const result = []; let match; while ((match = interfacePattern.exec(content)) !== null) { const startIndex = match.index + match[0].length; const endIndex = findClosingBrace(content, startIndex); if (endIndex > startIndex) { let parsedContent = content.substring(startIndex, endIndex).trim(); // 处理嵌套结构 if (parsedContent.includes("{") && parsedContent.includes("}")) { const nestedInterfaces = parse(parsedContent, level + 1); // 替换嵌套的内容为接口引用 if (nestedInterfaces.length > 0) { nestedInterfaces.forEach((nestedInterface) => { const pattern = `${nestedInterface.key}: {${nestedInterface.content}};`; const replacement = `${nestedInterface.key}: ${firstUpperCase(nestedInterface.key)}Interface`; parsedContent = parsedContent.replace(pattern, replacement); }); } } // 将解析结果添加到数组开头 result.unshift({ key: match[1], level, content: parsedContent, }); } } return result; } /** * 获取动态类名 */ const getDynamicClassNames = (value) => { const names = new Set(); // 匹配函数调用中的对象参数(如 parseClass({'!bg-surface-50': hoverable})) const functionCallRegex = /\w+\s*\(\s*\{([^}]*)\}\s*\)/gs; let funcMatch; while ((funcMatch = functionCallRegex.exec(value)) !== null) { const objContent = funcMatch[1]; // 提取对象中的键 const keyRegex = /['"](.*?)['"]\s*:/gs; let keyMatch; while ((keyMatch = keyRegex.exec(objContent)) !== null) { keyMatch[1].trim() && names.add(keyMatch[1]); } } // 匹配对象键(如 { 'text-a': 1 })- 优化版本,避免跨行错误匹配 const objKeyRegex = /[{,]\s*['"](.*?)['"]\s*:/gs; let objKeyMatch; while ((objKeyMatch = objKeyRegex.exec(value)) !== null) { const className = objKeyMatch[1].trim(); // 确保是有效的CSS类名,避免匹配到错误内容 if (className && !className.includes("\n") && !className.includes("\t")) { names.add(className); } } // 匹配数组中的字符串元素(如 'text-center')- 优化版本 const arrayStringRegex = /(?:^|[,\[\s])\s*['"](.*?)['"]/gs; let arrayMatch; while ((arrayMatch = arrayStringRegex.exec(value)) !== null) { const className = arrayMatch[1].trim(); // 确保是有效的CSS类名 if (className && !className.includes("\n") && !className.includes("\t")) { names.add(className); } } // 匹配三元表达式中的字符串(如 'dark' 和 'light') const ternaryRegex = /(\?|:)\s*['"](.*?)['"]/gs; let ternaryMatch; while ((ternaryMatch = ternaryRegex.exec(value)) !== null) { ternaryMatch[2].trim() && names.add(ternaryMatch[2]); } // 匹配反引号模板字符串 - 改进版本 const templateRegex = /`([^`]*)`/gs; let templateMatch; while ((templateMatch = templateRegex.exec(value)) !== null) { const templateContent = templateMatch[1]; // 提取模板字符串中的普通文本部分(排除 ${} 表达式) const textParts = templateContent.split(/\$\{[^}]*\}/); textParts.forEach((part) => { part.trim() .split(/\s+/) .forEach((className) => { className.trim() && names.add(className.trim()); }); }); // 提取模板字符串中 ${} 表达式内的字符串 const expressionRegex = /\$\{([^}]*)\}/gs; let expressionMatch; while ((expressionMatch = expressionRegex.exec(templateContent)) !== null) { const expression = expressionMatch[1]; // 递归处理表达式中的动态类名 getDynamicClassNames(expression).forEach((name) => names.add(name)); } } // 处理混合字符串(模板字符串 + 普通文本),如 "`text-red-900` text-red-1000" const mixedStringRegex = /`[^`]*`\s+([a-zA-Z0-9\-_\s]+)/g; let mixedMatch; while ((mixedMatch = mixedStringRegex.exec(value)) !== null) { const additionalClasses = mixedMatch[1].trim().split(/\s+/); additionalClasses.forEach((className) => { className.trim() && names.add(className.trim()); }); } // 处理普通字符串,多个类名用空格分割 const stringRegex = /['"]([\w\s\-!:\/]+?)['"]/gs; let stringMatch; while ((stringMatch = stringRegex.exec(value)) !== null) { const classNames = stringMatch[1].trim().split(/\s+/); classNames.forEach((className) => { className.trim() && names.add(className.trim()); }); } return Array.from(names); }; /** * 获取类名 */ function getClassNames(code) { // 修改正则表达式以支持多行匹配,避免内层引号冲突 const classRegex = /(?:class|:class|:pt|:hover-class)\s*=\s*(['"`])((?:[^'"`\\]|\\.|`[^`]*`|'[^']*'|"[^"]*")*?)\1/gis; const classNames = new Set(); let match; while ((match = classRegex.exec(code)) !== null) { const attribute = match[0].split("=")[0].trim(); const isStaticClass = attribute === "class" || attribute === "hover-class"; const isPtAttribute = attribute.includes("pt"); const value = match[2].trim(); if (isStaticClass) { // 处理静态 class 和 hover-class value.split(/\s+/).forEach((name) => name && classNames.add(name)); } else if (isPtAttribute) { // 处理 :pt 属性中的 className parseClasNameFromPt(value, classNames); } else { // 处理动态 :class 和 :hover-class getDynamicClassNames(value).forEach((name) => classNames.add(name)); } } return Array.from(classNames); } /** * 从 :pt 属性中解析 className */ function parseClasNameFromPt(value, classNames) { // 递归查找所有 className 属性 const classNameRegex = /className\s*:\s*/g; let match; while ((match = classNameRegex.exec(value)) !== null) { const startPos = match.index + match[0].length; const classNameValue = extractComplexValue(value, startPos); if (classNameValue) { // 如果是字符串字面量 if (classNameValue.startsWith('"') || classNameValue.startsWith("'") || classNameValue.startsWith("`")) { if (classNameValue.startsWith("`")) { // 处理模板字符串 getDynamicClassNames(classNameValue).forEach((name) => classNames.add(name)); } else { // 处理普通字符串 const strMatch = classNameValue.match(/['"](.*?)['"]/); if (strMatch) { strMatch[1].split(/\s+/).forEach((name) => name && classNames.add(name)); } } } else { // 处理动态值(如函数调用、对象等) getDynamicClassNames(classNameValue).forEach((name) => classNames.add(name)); } } } } /** * 提取复杂值(支持嵌套引号和括号) */ function extractComplexValue(text, startPos) { let pos = startPos; let depth = 0; let inString = false; let stringChar = ""; let result = ""; // 跳过开头的空白字符 while (pos < text.length && /\s/.test(text[pos])) { pos++; } while (pos < text.length) { const char = text[pos]; if (!inString) { if (char === '"' || char === "'" || char === "`") { inString = true; stringChar = char; result += char; } else if (char === "{" || char === "(" || char === "[") { depth++; result += char; } else if (char === "}" || char === ")" || char === "]") { if (depth === 0 && char === "}") { // 遇到顶层的 } 时结束 break; } depth--; result += char; } else if (char === "," && depth === 0) { // 遇到顶层的逗号时结束 break; } else if (char === "\n" && depth === 0 && result.trim() !== "") { // 如果遇到换行且不在嵌套结构中,且已有内容,则结束 break; } else { result += char; } } else { result += char; if (char === stringChar && text[pos - 1] !== "\\") { inString = false; stringChar = ""; // 如果字符串结束且depth为0,检查是否应该结束 if (depth === 0) { // 看看下一个非空白字符是什么 let nextPos = pos + 1; while (nextPos < text.length && /\s/.test(text[nextPos])) { nextPos++; } if (nextPos < text.length && (text[nextPos] === "," || text[nextPos] === "}")) { // 如果下一个字符是逗号或右括号,则结束 break; } } } } pos++; } return result.trim() || null; } /** * 获取 class 内容 */ function getClassContent(code) { // 修改正则表达式以支持多行匹配,避免内层引号冲突 const regex = /(?:class|:class|:pt|:hover-class)\s*=\s*(['"`])((?:[^'"`\\]|\\.|`[^`]*`|'[^']*'|"[^"]*")*?)\1/gis; const texts = []; let match; while ((match = regex.exec(code)) !== null) { const attribute = match[0].split("=")[0].trim(); const isPtAttribute = attribute.includes("pt"); const value = match[2]; if (isPtAttribute) { // 手动解析 className 值 const classNameRegex = /className\s*:\s*/g; let classNameMatchResult; while ((classNameMatchResult = classNameRegex.exec(value)) !== null) { const startPos = classNameMatchResult.index + classNameMatchResult[0].length; const classNameValue = extractComplexValue(value, startPos); if (classNameValue) { texts.push(classNameValue); } } } else { texts.push(value); } } return texts; } /** * 获取节点 */ function getNodes(code) { const nodes = []; // 找到所有顶级template标签的完整内容 function findTemplateContents(content) { const results = []; let index = 0; while (index < content.length) { const templateStart = content.indexOf("", templateStart); if (tagEnd === -1) break; // 使用栈来匹配配对的template标签 let stack = 1; let currentPos = tagEnd + 1; while (currentPos < content.length && stack > 0) { const nextTemplateStart = content.indexOf("", currentPos); if (nextTemplateEnd === -1) break; // 如果开始标签更近,说明有嵌套 if (nextTemplateStart !== -1 && nextTemplateStart < nextTemplateEnd) { // 找到开始标签的完整结束 const nestedTagEnd = content.indexOf(">", nextTemplateStart); if (nestedTagEnd !== -1) { stack++; currentPos = nestedTagEnd + 1; } else { break; } } else { // 找到结束标签 stack--; currentPos = nextTemplateEnd + 11; // ''.length } } if (stack === 0) { // 提取template内容(不包括template标签本身) const templateContent = content.substring(tagEnd + 1, currentPos - 11); results.push(templateContent); index = currentPos; } else { // 如果没有找到匹配的结束标签,跳过这个开始标签 index = tagEnd + 1; } } return results; } // 递归提取所有template内容中的节点 function extractNodesFromContent(content) { // 先提取当前内容中的所有标签 const regex = /<([^>]+)>/g; let match; while ((match = regex.exec(content)) !== null) { if (!match[1].startsWith("/") && !match[1].startsWith("template")) { nodes.push(match[1]); } } // 递归处理嵌套的template const nestedTemplates = findTemplateContents(content); nestedTemplates.forEach((templateContent) => { extractNodesFromContent(templateContent); }); } // 获取所有顶级template内容 const templateContents = findTemplateContents(code); // 处理每个template内容 templateContents.forEach((templateContent) => { extractNodesFromContent(templateContent); }); return nodes.map((e) => `<${e}>`); } /** * 添加 script 标签内容 */ function addScriptContent(code, content) { const scriptMatch = /]*>([\s\S]*?)<\/script>/g.exec(code); if (!scriptMatch) { return code; } const scriptContent = scriptMatch[1]; const scriptStartIndex = scriptMatch.index + scriptMatch[0].indexOf(">") + 1; const scriptEndIndex = scriptStartIndex + scriptContent.length; return (code.substring(0, scriptStartIndex) + "\n" + content + "\n" + scriptContent.trim() + code.substring(scriptEndIndex)); } /** * 判断是否为 Tailwind 类名 */ function isTailwindClass(className) { const prefixes = [ // 布局 "container", "flex", "grid", "block", "inline", "hidden", "visible", // 间距 "p-", "px-", "py-", "pt-", "pr-", "pb-", "pl-", "m-", "mx-", "my-", "mt-", "mr-", "mb-", "ml-", "space-", "gap-", // 尺寸 "w-", "h-", "min-w-", "max-w-", "min-h-", "max-h-", // 颜色 "bg-", "text-", "border-", "ring-", "shadow-", // 边框 "border", "rounded", "ring", // 字体 "font-", "text-", "leading-", "tracking-", "antialiased", // 定位 "absolute", "relative", "fixed", "sticky", "static", "top-", "right-", "bottom-", "left-", "inset-", "z-", // 变换 "transform", "translate-", "rotate-", "scale-", "skew-", // 过渡 "transition", "duration-", "ease-", "delay-", // 交互 "cursor-", "select-", "pointer-events-", // 溢出 "overflow-", "truncate", // 滚动 "scroll-", // 伪类和响应式 "hover:", "focus:", "active:", "disabled:", "group-hover:", ]; const statePrefixes = ["dark:", "dark:!", "light:", "sm:", "md:", "lg:", "xl:", "2xl:"]; if (className.startsWith("!") && !className.includes("!=")) { return true; } for (const prefix of prefixes) { if (className.startsWith(prefix)) { return true; } for (const statePrefix of statePrefixes) { if (className.startsWith(statePrefix + prefix)) { return true; } } } return false; } /** * 将 interface 转换为 type */ function interfaceToType(code) { // 匹配 interface 定义 const interfaceRegex = /interface\s+(\w+)(\s*extends\s+\w+)?\s*\{([^}]*)\}/g; // 将 interface 转换为 type return code.replace(interfaceRegex, (match, name, extends_, content) => { // 处理可能存在的 extends const extendsStr = extends_ ? extends_ : ""; // 返回转换后的 type 定义 return `type ${name}${extendsStr} = {${content}}`; }); } // 全局 service 对象,用于存储服务结构 const service = {}; // eps 实体列表 let list = []; /** * 获取 eps 请求地址 * @returns {string} eps url */ function getEpsUrl() { let url = config.eps.api; if (!url) { url = config.type; } switch (url) { case "app": case "uniapp-x": url = "/app/base/comm/eps"; break; case "admin": url = "/admin/base/open/eps"; break; } return url; } /** * 获取 eps 路径 * @param filename 文件名 * @returns {string} 完整路径 */ function getEpsPath(filename) { return path.join(config.type == "admin" ? config.eps.dist : rootDir(config.eps.dist), filename || ""); } /** * 获取对象方法名(排除 namespace、permission 字段) * @param v 对象 * @returns {string[]} 方法名数组 */ function getNames(v) { return Object.keys(v).filter((e) => !["namespace", "permission"].includes(e)); } /** * 获取字段类型 */ function getType({ propertyName, type }) { for (const map of config.eps.mapping) { if (map.custom) { const resType = map.custom({ propertyName, type }); if (resType) return resType; } if (map.test) { if (map.test.includes(type)) return map.type; } } return type; } /** * 格式化方法名,去除特殊字符 */ function formatName(name) { return (name || "").replace(/[:,\s,\/,-]/g, ""); } /** * 检查方法名是否合法(不包含特殊字符) */ function checkName(name) { return name && !["{", "}", ":"].some((e) => name.includes(e)); } /** * 不支持 uniapp-x 平台显示 */ function noUniappX(text, defaultText = "") { if (config.type == "uniapp-x") { return defaultText; } else { return text; } } /** * 查找字段 * @param sources 字段 source 数组 * @param item eps 实体 * @returns {Eps.Column[]} 字段数组 */ function findColumns(sources, item) { const columns = [item.columns, item.pageColumns].flat().filter(Boolean); return (sources || []) .map((e) => columns.find((c) => c.source == e)) .filter(Boolean); } /** * 使用 prettier 格式化 TypeScript 代码 * @param text 代码文本 * @returns {Promise} 格式化后的代码 */ async function formatCode(text) { return prettier .format(text, { parser: "typescript", useTabs: true, tabWidth: 4, endOfLine: "lf", semi: true, singleQuote: false, printWidth: 100, trailingComma: "none", }) .catch(() => { error(`[cool-eps] Failed to format /build/cool/eps.d.ts. Please delete the file and try again`); return null; }); } /** * 获取 eps 数据(本地优先,远程兜底) */ async function getData() { // 读取本地 eps.json list = readFile(getEpsPath("eps.json"), true) || []; // 拼接请求地址 const url = config.reqUrl + getEpsUrl(); // 请求远程 eps 数据 await axios .get(url, { timeout: 5000, }) .then((res) => { const { code, data, message } = res.data; if (code === 1000) { if (!lodash.isEmpty(data) && data) { list = lodash.values(data).flat(); } } else { error(`[cool-eps] ${message || "Failed to fetch data"}`); } }) .catch(() => { error(`[cool-eps] API service is not running → ${url}`); }); // 初始化处理,补全缺省字段 list.forEach((e) => { if (!e.namespace) e.namespace = ""; if (!e.api) e.api = []; if (!e.columns) e.columns = []; if (!e.search) { e.search = { fieldEq: findColumns(e.pageQueryOp?.fieldEq, e), fieldLike: findColumns(e.pageQueryOp?.fieldLike, e), keyWordLikeFields: findColumns(e.pageQueryOp?.keyWordLikeFields, e), }; } }); if (config.type == "uniapp-x" || config.type == "app") { list = list.filter((e) => e.prefix.startsWith("/app")); } } /** * 创建 eps.json 文件 * @returns {boolean} 是否有更新 */ function createJson() { if (config.type == "uniapp-x") { return false; } const arr = list.map((e) => { return { prefix: e.prefix, name: e.name || "", api: e.api.map((apiItem) => ({ name: apiItem.name, method: apiItem.method, path: apiItem.path, })), search: e.search, }; }); const content = JSON.stringify(arr); const local_content = readFile(getEpsPath("eps.json")); // 判断是否需要更新 const isUpdate = content != local_content; if (isUpdate) { fs.createWriteStream(getEpsPath("eps.json"), { flags: "w", }).write(content); } return isUpdate; } /** * 创建 eps 类型描述文件(d.ts/ts) * @param param0 list: eps实体列表, service: service对象 */ async function createDescribe({ list, service }) { /** * 创建 Entity 接口定义 */ function createEntity() { const ignore = []; let t0 = ""; for (const item of list) { if (!checkName(item.name)) continue; let t = `interface ${formatName(item.name)} {`; // 合并 columns 和 pageColumns,去重 const columns = lodash.uniqBy(lodash.compact([...(item.columns || []), ...(item.pageColumns || [])]), "source"); for (const col of columns || []) { t += ` /** * ${col.comment} */ ${col.propertyName}?: ${getType({ propertyName: col.propertyName, type: col.type, })}; `; } t += ` /** * 任意键值 */ [key: string]: any; } `; if (!ignore.includes(item.name)) { ignore.push(item.name); t0 += t + "\n\n"; } } return t0; } /** * 创建 Controller 接口定义 */ async function createController() { let controller = ""; let chain = ""; let pageResponse = ""; /** * 递归处理 service 树,生成接口定义 * @param d 当前节点 * @param k 前缀 */ function deep(d, k) { if (!k) k = ""; for (const i in d) { const name = k + toCamel(firstUpperCase(formatName(i))); // 检查方法名 if (!checkName(name)) continue; if (d[i].namespace) { // 查找配置 const item = list.find((e) => (e.prefix || "") === `/${d[i].namespace}`); if (item) { // let t = `interface ${name} {`; // 插入方法 if (item.api) { // 权限列表 const permission = []; item.api.forEach((a) => { // 方法名 const n = toCamel(formatName(a.name || lodash.last(a.path.split("/")))); // 检查方法名 if (!checkName(n)) return; if (n) { // 参数类型 let q = []; // 参数列表 const { parameters = [] } = a.dts || {}; parameters.forEach((p) => { if (p.description) { q.push(`\n/** ${p.description} */\n`); } // 检查参数名 if (!checkName(p.name)) { return false; } const a = `${p.name}${p.required ? "" : "?"}`; const b = `${p.schema.type || "string"}`; q.push(`${a}: ${b};`); }); if (lodash.isEmpty(q)) { q = ["any"]; } else { q.unshift("{"); q.push("}"); } // 返回类型 let res = ""; // 实体名 const en = item.name || "any"; switch (a.path) { case "/page": res = `${name}PageResponse`; pageResponse += ` interface ${name}PageResponse { pagination: PagePagination; list: ${en}[]; } `; break; case "/list": res = `${en} []`; break; case "/info": res = en; break; default: res = "any"; break; } // 方法描述 t += ` /** * ${a.summary || n} */ ${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>; `; if (!permission.includes(n)) { permission.push(n); } } }); // 权限标识 t += noUniappX(` /** * 权限标识 */ permission: { ${permission.map((e) => `${e}: string;`).join("\n")} }; `); // 权限状态 t += noUniappX(` /** * 权限状态 */ _permission: { ${permission.map((e) => `${e}: boolean;`).join("\n")} }; `); // 请求 t += noUniappX(` request: Request; `); } t += "}\n\n"; controller += t; chain += `${formatName(i)}: ${name};`; } } else { chain += `${formatName(i)}: {`; deep(d[i], name); chain += "};"; } } } // 遍历 service 树 deep(service); return ` type json = any; interface PagePagination { size: number; page: number; total: number; [key: string]: any; }; interface PageResponse { pagination: PagePagination; list: T[]; [key: string]: any; }; ${pageResponse} ${controller} ${noUniappX(`interface RequestOptions { url: string; method?: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'; data?: any; params?: any; headers?: any; timeout?: number; [key: string]: any; }`)} ${noUniappX("type Request = (options: RequestOptions) => Promise;")} ${await createDict()} type Service = { ${noUniappX("request: Request;")} ${chain} } `; } // 组装文件内容 let text = ` ${createEntity()} ${await createController()} `; // 文件名 let name = "eps.d.ts"; if (config.type == "uniapp-x") { name = "eps.ts"; text = text .replaceAll("interface ", "export interface ") .replaceAll("type ", "export type ") .replaceAll("[key: string]: any;", ""); text = flatten(text); text = interfaceToType(text); } else { text = ` declare namespace Eps { ${text} } `; } // 格式化文本内容 const content = await formatCode(text); const local_content = readFile(getEpsPath(name)); // 是否需要更新 if (content && content != local_content && list.length > 0) { // 创建 eps 描述文件 fs.createWriteStream(getEpsPath(name), { flags: "w", }).write(content); } } /** * 构建 service 对象树 */ function createService() { // 路径第一层作为 id 标识 const id = getEpsUrl().split("/")[1]; list.forEach((e) => { // 请求地址 const path = e.prefix[0] == "/" ? e.prefix.substring(1, e.prefix.length) : e.prefix; // 分隔路径,去除 id,转驼峰 const arr = path.replace(id, "").split("/").filter(Boolean).map(toCamel); /** * 递归构建 service 树 * @param d 当前节点 * @param i 当前索引 */ function deep(d, i) { const k = arr[i]; if (k) { // 是否最后一个 if (arr[i + 1]) { if (!d[k]) { d[k] = {}; } deep(d[k], i + 1); } else { // 不存在则创建 if (!d[k]) { d[k] = { permission: {}, }; } if (!d[k].namespace) { d[k].namespace = path; } // 创建权限 if (d[k].namespace) { getNames(d[k]).forEach((i) => { d[k].permission[i] = `${d[k].namespace.replace(`${id}/`, "")}/${i}`.replace(/\//g, ":"); }); } // 创建搜索 d[k].search = e.search; // 创建方法 e.api.forEach((a) => { // 方法名 const n = a.path.replace("/", ""); if (n && !/[-:]/g.test(n)) { d[k][n] = a; } }); } } } deep(service, 0); }); } /** * 创建 service 代码 * @returns {string} service 代码 */ function createServiceCode() { const types = []; let chain = ""; /** * 递归处理 service 树,生成接口代码 * @param d 当前节点 * @param k 前缀 */ function deep(d, k) { if (!k) k = ""; for (const i in d) { if (["swagger"].includes(i)) { continue; } const name = k + toCamel(firstUpperCase(formatName(i))); // 检查方法名 if (!checkName(name)) continue; if (d[i].namespace) { // 查找配置 const item = list.find((e) => (e.prefix || "") === `/${d[i].namespace}`); if (item) { // let t = `{`; // 插入方法 if (item.api) { item.api.forEach((a) => { // 方法名 const n = toCamel(formatName(a.name || lodash.last(a.path.split("/")))); // 检查方法名 if (!checkName(n)) return; if (n) { // 参数类型 let q = []; // 参数列表 const { parameters = [] } = a.dts || {}; parameters.forEach((p) => { if (p.description) { q.push(`\n/** ${p.description} */\n`); } // 检查参数名 if (!checkName(p.name)) { return false; } const a = `${p.name}${p.required ? "" : "?"}`; const b = `${p.schema.type || "string"}`; q.push(`${a}: ${b}, `); }); if (lodash.isEmpty(q)) { q = ["any"]; } else { q.unshift("{"); q.push("}"); } if (item.name) { types.push(item.name); } // 返回类型 let res = ""; // 实体名 const en = item.name || "any"; switch (a.path) { case "/page": res = `${name}PageResponse`; types.push(res); break; case "/list": res = `${en}[]`; break; case "/info": res = en; break; default: res = "any"; break; } // 方法描述 t += ` /** * ${a.summary || n} */ ${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")})${noUniappX(`: Promise<${res}>`)} { return request<${res}>({ url: "/${d[i].namespace}${a.path}", method: "${(a.method || "get").toLocaleUpperCase()}", data, }); }, `; } }); } t += `} as ${name}\n`; types.push(name); chain += `${formatName(i)}: ${t},\n`; } } else { chain += `${formatName(i)}: {`; deep(d[i], name); chain += `} as ${firstUpperCase(i)}Interface,`; types.push(`${firstUpperCase(i)}Interface`); } } } // 遍历 service 树 deep(service); return { content: `{ ${chain} }`, types, }; } /** * 获取字典类型定义 * @returns {Promise} 字典类型 type 定义 */ async function createDict() { let p = ""; switch (config.type) { case "app": case "uniapp-x": p = "/app"; break; case "admin": p = "/admin"; break; } const url = config.reqUrl + p + "/dict/info/types"; const text = await axios .get(url) .then((res) => { const { code, data } = res.data; if (code === 1000) { let v = "string"; if (!lodash.isEmpty(data)) { v = data.map((e) => `"${e.key}"`).join(" | "); } return `type DictKey = ${v}`; } }) .catch(() => { error(`[cool-eps] Error:${url}`); }); return text || ""; } /** * 主入口:创建 eps 相关文件和 service */ async function createEps() { if (config.eps.enable) { // 获取 eps 数据 await getData(); // 构建 service 对象 createService(); const serviceCode = createServiceCode(); // 创建 eps 目录 createDir(getEpsPath(), true); // 创建 eps.json 文件 const isUpdate = createJson(); // 创建类型描述文件 createDescribe({ service, list }); return { service, serviceCode, list, isUpdate, }; } else { return { service: {}, list: [], }; } } function getPlugin(name) { let code = readFile(rootDir(`./src/plugins/${name}/config.ts`)); // 设置插件配置 const set = (key, value) => { const regex = new RegExp(`(return\\s*{[^}]*?\\b${key}\\b\\s*:\\s*)([^,}]+)`); if (regex.test(code)) { code = code.replace(regex, `$1${JSON.stringify(value)}`); } else { const insertPos = code.indexOf("return {") + 8; code = code.slice(0, insertPos) + `\n ${key}: ${JSON.stringify(value)},` + code.slice(insertPos); } }; // 保存插件配置 const save = async () => { const content = await formatContent(code); writeFile(rootDir(`./src/plugins/${name}/config.ts`), content); }; return { set, save, }; } // 修改插件 async function updatePlugin(options) { const plugin = getPlugin(options.name); if (options.enable !== undefined) { plugin.set("enable", options.enable); } await plugin.save(); } function getPath() { return rootDir(`.${config.type == "admin" ? "/src" : ""}/config/proxy.ts`); } async function updateProxy(data) { let code = readFile(getPath()); const regex = /const\s+value\s*=\s*['"]([^'"]+)['"]/; if (regex.test(code)) { code = code.replace(regex, `const value = '${data.name}'`); } writeFile(getPath(), code); } function getProxyTarget(proxy) { const code = readFile(getPath()); const regex = /const\s+value\s*=\s*['"]([^'"]+)['"]/; const match = code.match(regex); if (match) { const value = match[1]; try { const { target, rewrite } = proxy[`/${value}/`]; return target + rewrite(`/${value}`); } catch (err) { error(`[cool-proxy] Error:${value} → ` + getPath()); return ""; } } } // 创建文件 async function createFile(data) { const list = lodash.isArray(data) ? data : [data]; for (const item of list) { const { path: path$1, code } = item; // 格式化内容 const content = await formatContent(code, { parser: "vue", }); // 目录路径 const dir = (path$1 || "").split("/"); // 文件名 const fname = dir.pop(); // 源码路径 const srcPath = `./src/${dir.join("/")}`; // 创建目录 createDir(srcPath, true); // 创建文件 fs.createWriteStream(path.join(srcPath, fname), { flags: "w", }).write(content); } } function createTag(code, id) { if (/\.vue$/.test(id)) { let s; const str = () => s || (s = new magicString(code)); const { descriptor } = compilerSfc.parse(code); if (!descriptor.script && descriptor.scriptSetup) { const res = compilerSfc.compileScript(descriptor, { id }); const { name, lang } = res.attrs; str().appendLeft(0, `