UNPKG

create-cttq

Version:

CTTQ大前端脚手架项目

326 lines (303 loc) 16 kB
/** * 集成能力库的入口 */ const fs = require("fs"); const path = require("path"); const { logWithSpinner, succeedSpinner, failSpinner } = require("./util/spinner"); const TemplateManager = require("./TemplateManager"); const prompts = require("prompts"); const globby = require('./util/globby'); const recast = require("recast"); const ejs = require("ejs") const mergePackage = require("./util/mergePackage"); const { FindImport, FindNode } = require("./recast/FilterDeclaration"); const { hasProjectNpm, hasProjectPnpm, hasProjectYarn, tryRun } = require("./util/shared") const sortPkg = require("./util/sortPkg") const injectInfo = require("./util/injectInfo") function hasMonitor(main) { return main.includes("CTTQMonitor().init("); } function hasSensors(main) { return main.includes(".SensorsConfig.init("); } function getSetProperty(ast) { let setproperty; recast.visit(ast, { visitExpressionStatement({ node }) { if (node.expression && node.expression.callee && node.expression.callee.object && node.expression.callee.object.name == "Object" && node.expression.callee.property && node.expression.callee.property.name == "defineProperty" && node.expression.arguments && node.expression.arguments.length > 2 && node.expression.arguments[0].name == "window" && node.expression.arguments[1].value == "$app" && node.expression.arguments[2].properties && node.expression.arguments[2].properties.length > 1) { setproperty = node.expression.arguments[2].properties.filter(property => property.key && property.key.name == "set" ).pop(); } return false; }, }) return setproperty; } async function integrateAbility() { let context = process.cwd(); const { capability } = await prompts( [{ type: "multiselect", name: "capability", message: "选择接入的能力", choices: [ { title: "监控", value: "monitor" }, { title: "埋点", value: "sensors" }, { title: "自动刷新", value: "autorefresh" }, ], min: 1, // hint: "- 用 Space / ← / → 键 来切换选择/取消状态,用 ↑ ↓ 切换问题。回车 确认", instructions: "\n ↑/↓: 高亮可选项\n ←/→/[space]: 切换选择/取消状态\n a: 全选\n enter/return: 确认选择", onState: (state) => {}, }], { onCancel: () => { throw new Error(red("✖") + " 取消接入能力库"); }, }, ); try { logWithSpinner("开始集成能力库..."); let templateManager = new TemplateManager(context, process.env.CTTQ_TEMPLATE_VERSION); logWithSpinner("下载模版库中..."); await templateManager.download().catch(() => { failSpinner("模版库下载失败, 无法连接Gitlab"); templateManager.delete(); throw new Error("能力库集成失败"); }); logWithSpinner("集成能力库配置..."); let monitorpath = templateManager.pathAt("monitor"); let sensorspath = templateManager.pathAt("sensors"); let autorefreshpath = templateManager.pathAt("auto-refresh"); // 合并pkg let conflictDepSources = {}; let pkgpath = path.resolve(context, "package.json"); let pkg = JSON.parse(fs.readFileSync(pkgpath, "utf-8") || "{}"); if (capability.includes("monitor")) { let monitorpkg = JSON.parse(fs.readFileSync(path.resolve(monitorpath, "package.json"), "utf-8") || "{}"); mergePackage("monitor", pkg, monitorpkg, conflictDepSources); } if (capability.includes("sensors")) { let sensorspkg = JSON.parse(fs.readFileSync(path.resolve(sensorspath, "package.json"), "utf-8") || "{}"); mergePackage("sensors", pkg, sensorspkg, conflictDepSources); } if (capability.includes("autorefresh")) { let autorefreshpkg = JSON.parse(fs.readFileSync(path.resolve(autorefreshpath, "package.json"), "utf-8") || "{}"); mergePackage("autorefresh", pkg, autorefreshpkg, conflictDepSources); } pkg = sortPkg(pkg); fs.writeFileSync(pkgpath, JSON.stringify(pkg, null, 2) + "\n"); injectInfo(context, "ability"); // 渲染入口文件 const files = await globby(context, ["**/*/main.js", "!node_modules", "!**/.template/**/*", "!.git", "!.git"]); // 变更文件列表 let changeFiles = []; for (const rawPath of Object.keys(files)) { const fileContext = files[rawPath]; const needMonitor = capability.includes("monitor") && !hasMonitor(fileContext); const needSensors = capability.includes("sensors") && !hasSensors(fileContext); const isApp = fileContext.includes('"@cttq/cttq"') || fileContext.includes("'@cttq/cttq'"); let ast = recast.parse(fileContext); // 找到最后一个ImportDeclaration节点 let lastImportIndex = -1; const lastImport = ast.program.body.filter(node => recast.types.namedTypes.ImportDeclaration.check(node) ).pop(); if (lastImport) { lastImportIndex = ast.program.body.indexOf(lastImport); } // 查找Vue变量 if (!isApp) { let vueVariableName = ""; let vueVariable; // 设置vue实例变量 recast.visit(ast, { visitExpressionStatement(path) { const node = path.node; if (recast.types.namedTypes.CallExpression.check(node.expression) && recast.types.namedTypes.MemberExpression.check(node.expression.callee) && recast.types.namedTypes.NewExpression.check(node.expression.callee.object) && recast.types.namedTypes.Identifier.check(node.expression.callee.object.callee) && node.expression.callee.object.callee.name == "Vue") { return recast.types.builders.variableDeclaration( "const", [ recast.types.builders.variableDeclarator(recast.types.builders.identifier("appVue"),recast.parse(recast.print(node).code).program.body[0].expression) ] ); } return false; } }); vueVariable = ast.program.body.filter(node => { if (recast.types.namedTypes.VariableDeclaration.check(node) && node.declarations && node.declarations.length > 0) { for (const variable of node.declarations) { if (recast.types.namedTypes.VariableDeclarator.check(variable) && recast.types.namedTypes.CallExpression.check(variable.init) && recast.types.namedTypes.MemberExpression.check(variable.init.callee) && variable.init.callee.object && variable.init.callee.object.callee && variable.init.callee.object.callee.name == "Vue") { vueVariableName = variable.id.name; return true; } } } } ).pop(); // 引入监控 if (needMonitor) { let mainContext = ejs.render(fs.readFileSync(templateManager.pathAt("monitor/main.js"), 'utf-8'), { name: pkg.name, userInfo: `${vueVariableName}.$store.state` }); let mainAST = recast.parse(mainContext); let imports = FindImport(mainAST); if (imports && imports.length > 0) { ast.program.body.splice(lastImportIndex + 1, 0, ...imports); } let otherNode = FindNode(mainAST, {exclude: ["ImportDeclaration"]}); if (otherNode && otherNode.length > 0) { const vueVariableIndex = ast.program.body.indexOf(vueVariable); ast.program.body.splice(vueVariableIndex + 1, 0, ...otherNode); } } // 引入埋点 if (needSensors) { let mainContext = ejs.render(fs.readFileSync(templateManager.pathAt("sensors/main.js"), 'utf-8'), { name: pkg.name, description: pkg.description, userInfo: "newValue" }); let mainAST = recast.parse(mainContext); let imports = FindImport(mainAST); if (imports && imports.length > 0) { ast.program.body.splice(lastImportIndex + 1, 0, ...imports); for (const importDec of imports) { mainAST.program.body.splice(mainAST.program.body.indexOf(importDec), 1); } } let otherNode = FindNode(mainAST, {exclude: ["ImportDeclaration"]}); mainContext = `${vueVariableName}.$store.watch((state) => { return (state.user || {}).userInfo; }, (newValue) => { if (newValue) { ${recast.print(mainAST).code} } })` otherNode = recast.parse(mainContext).program.body; if (otherNode && otherNode.length > 0) { const vueVariableIndex = ast.program.body.indexOf(vueVariable); ast.program.body.splice(vueVariableIndex + 1, 0, ...otherNode); } } if (needMonitor || needSensors) { // 将AST转换回代码字符串 const sourcePath = path.resolve(context, rawPath); let code = recast.prettyPrint(ast, {tabWidth: 4, useTabs: true}).code; code = code.replace(/ {4}/g, "\t"); code = code.replace(/^\s*[\r\n]/gm, ""); fs.writeFileSync(sourcePath, code + "\n"); changeFiles.push(rawPath); } } else { // 引入监控 let monitorCode = ""; if (needMonitor) { let mainContext = ejs.render(fs.readFileSync(templateManager.pathAt("monitor/main.js"), 'utf-8'), { name: pkg.name, userInfo: `app.$store.state` }); let mainAST = recast.parse(mainContext); let imports = FindImport(mainAST); if (imports && imports.length > 0) { ast.program.body.splice(lastImportIndex + 1, 0, ...imports); for (const importDec of imports) { mainAST.program.body.splice(mainAST.program.body.indexOf(importDec), 1); } } monitorCode = recast.print(mainAST).code; } // 引入埋点 let sensorsCode = ""; if (needSensors) { let mainContext = ejs.render(fs.readFileSync(templateManager.pathAt("sensors/main.js"), 'utf-8'), { name: pkg.name, description: pkg.description, userInfo: "newValue" }); let mainAST = recast.parse(mainContext); let imports = FindImport(mainAST); if (imports && imports.length > 0) { ast.program.body.splice(lastImportIndex + 1, 0, ...imports); for (const importDec of imports) { mainAST.program.body.splice(mainAST.program.body.indexOf(importDec), 1); } } sensorsCode = `app.$store.watch((state) => { return (state.userInfo || {}).person; }, (newValue) => { if (newValue) { ${recast.print(mainAST).code} } });` } if (monitorCode || sensorsCode) { let setproperty = getSetProperty(ast); if (!setproperty) { let injectAst = recast.parse(`Object.defineProperty(window, "$app", { get() {return this.__app;}, set(app) { this.__app = app; }}) `).program.body; ast.program.body.splice(ast.program.body.length, 0, ...injectAst); setproperty = getSetProperty(ast); } if (setproperty.value) { let paramname = (setproperty.value.params[0] || {}).name || "app"; if (setproperty.value.body && setproperty.value.body.body) { if (monitorCode) { monitorCode.replace("app.$store", `${paramname}.$store`); setproperty.value.body.body.push(...recast.parse(monitorCode).program.body); } if (sensorsCode) { sensorsCode.replace("app.$store", `${paramname}.$store`); setproperty.value.body.body.push(...recast.parse(sensorsCode).program.body); } } } // 将AST转换回代码字符串 const sourcePath = path.resolve(context, rawPath); let code = recast.prettyPrint(ast, {tabWidth: 4, useTabs: true}).code; code = code.replace(/ {4}/g, "\t"); code = code.replace(/^\s*[\r\n]/gm, ""); fs.writeFileSync(sourcePath, code + "\n"); changeFiles.push(rawPath); } } } templateManager.delete(); // 自动更新项目 if (hasProjectPnpm(context)) { logWithSpinner("运行 pnpm install 更新项目..."); await tryRun("pnpm install").catch(() => { failSpinner("pnpm install 更新项目失败,需要自己手动更新"); }); } else if (hasProjectYarn(context)) { logWithSpinner("运行 yarn install 更新项目..."); await tryRun("yarn install").catch(() => { failSpinner("yarn install 更新项目失败,需要自己手动更新"); }); } else if (hasProjectNpm(context) || fs.existsSync(path.resolve(context, "node_modules"))) { logWithSpinner("运行 npm install 更新项目..."); await tryRun("npm install").catch(() => { failSpinner("npm install 更新项目失败,需要自己手动更新"); }); } succeedSpinner("能力库集成成功"); if (changeFiles.length > 0) { console.log("已变更如下入口文件:"); console.log(" " + changeFiles.join("\n ")); } } catch (error) { failSpinner("能力库集成失败"); } } module.exports = (...args) => { return integrateAbility().catch((error) => { process.exit(1); }); }