hvigorfile.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. import { appTasks } from '@ohos/hvigor-ohos-plugin';
  2. import { HvigorPlugin, HvigorNode } from '@ohos/hvigor';
  3. import fs from 'fs';
  4. import path from 'path';
  5. import { execSync } from 'child_process';
  6. function customPlugin(): HvigorPlugin {
  7. return {
  8. pluginId: 'customPlugin',
  9. apply(node: HvigorNode) {
  10. try {
  11. // console.log('node', JSON.stringify(node))
  12. // 根据node对象结构获取项目路径
  13. const projectPath = node.nodeDir.filePath;
  14. console.log('目标路径:', projectPath);
  15. // 获取所有子模块
  16. const modules = node.allSubNodes.filter(subNode => subNode.node.classKind === 'module');
  17. console.log('模块数量:', modules.length);
  18. // 打印每个模块的路径
  19. const modulePaths = [];
  20. modules.forEach((module, index) => {
  21. try {
  22. // 从module.node._nodePath获取模块路径
  23. const modulePath = module.node._nodePath;
  24. if (modulePath) {
  25. console.log(`模块 ${index + 1} 路径:`, modulePath);
  26. modulePaths.push(modulePath);
  27. } else {
  28. console.log(`模块 ${index + 1} 路径: 无法获取路径`);
  29. }
  30. } catch (error) {
  31. console.error(`获取模块 ${index + 1} 路径时出错:`, error);
  32. }
  33. });
  34. // 遍历并处理所有模块
  35. modulePaths.forEach((modulePath, index) => {
  36. console.log(`处理模块 ${index + 1}:`, modulePath);
  37. processModule(modulePath, projectPath);
  38. });
  39. } catch (error) {
  40. console.error('Error in custom plugin:', error);
  41. }
  42. }
  43. }
  44. function processModule(modulePath: string, projectPath: string) {
  45. try {
  46. // 构建pages目录路径 (基于模块路径)
  47. const pagesPath = path.join(modulePath, 'src', 'main', 'ets', 'pages');
  48. console.log('pages路径:', pagesPath);
  49. // 构建profile目录路径 (基于模块路径)
  50. const rawfilePath = path.join(modulePath, 'src', 'main', 'resources', 'base', 'profile');
  51. console.log('profile路径:', rawfilePath);
  52. // 确保profile目录存在
  53. if (!fs.existsSync(rawfilePath)) {
  54. fs.mkdirSync(rawfilePath, { recursive: true });
  55. }
  56. // router_map.json文件路径
  57. const routerMapPath = path.join(rawfilePath, 'router_map.json');
  58. // module.json5文件路径
  59. const moduleJsonPath = path.join(modulePath, 'src', 'main', 'module.json5');
  60. // 初始化routerMap
  61. let routerMap = {
  62. routerMap: []
  63. };
  64. // 如果router_map.json文件已存在,则读取现有内容
  65. if (fs.existsSync(routerMapPath)) {
  66. try {
  67. const existingContent = fs.readFileSync(routerMapPath, 'utf8');
  68. routerMap = JSON.parse(existingContent);
  69. console.log('读取现有的router_map.json文件');
  70. } catch (error) {
  71. console.error('读取router_map.json文件出错:', error);
  72. }
  73. }
  74. // 检查pages目录是否存在
  75. if (fs.existsSync(pagesPath)) {
  76. // 读取目录内容
  77. const files = fs.readdirSync(pagesPath);
  78. console.log(`Found ${files.length} files/directories in pages folder:`);
  79. // 遍历并处理每个文件
  80. files.forEach((file, index) => {
  81. const fullPath = path.join(pagesPath, file);
  82. const stat = fs.statSync(fullPath);
  83. const type = stat.isDirectory() ? '[DIR]' : '[FILE]';
  84. console.log(`${index + 1}. ${type} ${file}`);
  85. // 如果是文件,读取文件内容
  86. if (!stat.isDirectory() && file.endsWith('.ets')) {
  87. try {
  88. const content = fs.readFileSync(fullPath, 'utf8');
  89. // console.log(`Content of ${file}:`);
  90. // console.log(content);
  91. // 检查是否有@RouterPage修饰器
  92. if (content.includes('@RouterPage')) {
  93. // 生成Builder函数名(去掉Page后缀,加上Builder后缀)
  94. const fileNameWithoutExtension = file.replace('.ets', '');
  95. const builderName = fileNameWithoutExtension.replace(/Page$/, '') + 'Builder';
  96. // 检查是否已经存在Builder函数
  97. const builderRegex = new RegExp(`@Builder\\s+function\\s+${builderName}\\s*\\(\\)`, 'g');
  98. if (!builderRegex.test(content)) {
  99. // 构造新的内容,在文件末尾添加Builder函数
  100. const structName = fileNameWithoutExtension; // struct名称需与文件名相同
  101. // 检查是否已经有NavDestination包装
  102. const hasNavDestination = content.includes('NavDestination()');
  103. let builderFunction = ''
  104. if (!hasNavDestination) {
  105. builderFunction =
  106. `\n@Builder\nfunction ${builderName}() {\n NavDestination() {\n ${structName}()\n }\n .hideTitleBar(true)\n}\n`;
  107. } else {
  108. builderFunction = `\n@Builder\nfunction ${builderName}() {\n ${structName}()\n}\n`;
  109. }
  110. const newContent = content + builderFunction;
  111. // 写入文件
  112. fs.writeFileSync(fullPath, newContent, 'utf8');
  113. console.log(`Added ${builderName} function to ${file}`);
  114. } else {
  115. console.log(`Builder function ${builderName} already exists in ${file}`);
  116. }
  117. // 添加到routerMap中
  118. const pageEntry = {
  119. name: fileNameWithoutExtension,
  120. pageSourceFile: `src/main/ets/pages/${file}`,
  121. buildFunction: builderName
  122. };
  123. // 检查是否已存在该条目
  124. const existingIndex = routerMap.routerMap.findIndex(item => item.name === fileNameWithoutExtension);
  125. if (existingIndex >= 0) {
  126. // 更新现有条目
  127. routerMap.routerMap[existingIndex] = pageEntry;
  128. } else {
  129. // 添加新条目
  130. routerMap.routerMap.push(pageEntry);
  131. }
  132. }
  133. console.log('----------------------------------------');
  134. } catch (readError) {
  135. console.error(`Error reading file ${file}:`, readError);
  136. }
  137. }
  138. });
  139. // 将routerMap写入router_map.json文件
  140. try {
  141. fs.writeFileSync(routerMapPath, JSON.stringify(routerMap, null, 2), 'utf8');
  142. console.log('router_map.json文件已更新');
  143. } catch (writeError) {
  144. console.error('写入router_map.json文件出错:', writeError);
  145. }
  146. // 更新module.json5文件,添加routerMap字段
  147. try {
  148. if (fs.existsSync(moduleJsonPath)) {
  149. const moduleJsonContent = fs.readFileSync(moduleJsonPath, 'utf8');
  150. // 使用正则表达式在"module": {后添加routerMap字段
  151. if (moduleJsonContent.includes('"module"')) {
  152. // 检查是否已经存在routerMap字段
  153. if (!moduleJsonContent.includes('"routerMap"')) {
  154. // 在"module": {后添加routerMap字段
  155. const updatedContent = moduleJsonContent.replace(
  156. /("module"\s*:\s*{)/,
  157. '$1\n "routerMap": "$profile:router_map",'
  158. );
  159. fs.writeFileSync(moduleJsonPath, updatedContent, 'utf8');
  160. console.log('module.json5文件已更新,添加了routerMap字段');
  161. } else {
  162. console.log('module.json5中已存在routerMap字段');
  163. }
  164. } else {
  165. console.log('module.json5中未找到"module"字段');
  166. }
  167. } else {
  168. console.log('module.json5文件不存在');
  169. }
  170. } catch (error) {
  171. console.error('更新module.json5文件出错:', error);
  172. }
  173. } else {
  174. console.log('Pages directory not found');
  175. }
  176. } catch (error) {
  177. console.error(`处理模块 ${modulePath} 时出错:`, error);
  178. }
  179. }
  180. }
  181. function rDBPlugin(): HvigorPlugin {
  182. return {
  183. pluginId: 'customPlugin',
  184. apply(node: HvigorNode) {
  185. try {
  186. // 根据node对象结构获取项目路径
  187. const projectPath = node.nodeDir.filePath;
  188. console.log('目标路径:', projectPath);
  189. // 获取所有子模块
  190. const modules = node.allSubNodes.filter(subNode => subNode.node.classKind === 'module');
  191. console.log('模块数量:', modules.length);
  192. // 打印每个模块的路径
  193. const modulePaths = [];
  194. modules.forEach((module, index) => {
  195. try {
  196. // 从module.node._nodePath获取模块路径
  197. const modulePath = module.node._nodePath;
  198. if (modulePath) {
  199. console.log(`模块 ${index + 1} 路径:`, modulePath);
  200. modulePaths.push(modulePath);
  201. if (modulePath.includes('basic')) {
  202. processTables(modulePath)
  203. }
  204. } else {
  205. console.log(`模块 ${index + 1} 路径: 无法获取路径`);
  206. }
  207. } catch (error) {
  208. console.error(`获取模块 ${index + 1} 路径时出错:`, error);
  209. }
  210. });
  211. // 遍历并处理所有模块
  212. // modulePaths.forEach((modulePath, index) => {
  213. // console.log(`处理模块 ${index + 1}:`, modulePath);
  214. // processTables(); // 处理tables目录的逻辑
  215. // });
  216. // processTables(modulePaths[])
  217. } catch (error) {
  218. console.error('Error in custom plugin:', error);
  219. }
  220. }
  221. }
  222. function processTables(currentDir: string) {
  223. try {
  224. // 构建tables目录路径
  225. const tablesPath = path.join(currentDir, 'src', 'main', 'ets', 'rdb', 'tables');
  226. console.log('tables路径:', tablesPath);
  227. // 检查tables目录是否存在
  228. if (fs.existsSync(tablesPath)) {
  229. // 读取目录内容
  230. const files = fs.readdirSync(tablesPath);
  231. console.log(`Found ${files.length} files/directories in tables folder:`);
  232. const tableNames: string[] = [];
  233. const sqlStatements: string[] = [];
  234. // 替换原有的字段处理逻辑
  235. files.forEach((file, index) => {
  236. const fullPath = path.join(tablesPath, file);
  237. const stat = fs.statSync(fullPath);
  238. const type = stat.isDirectory() ? '[DIR]' : '[FILE]';
  239. console.log(`${index + 1}. ${type} ${file}`);
  240. // 如果是文件,读取文件内容
  241. if (!stat.isDirectory() && file.endsWith('.ets')) {
  242. try {
  243. const content = fs.readFileSync(fullPath, 'utf8');
  244. // 使用正则表达式匹配interface名称
  245. const typeRegex = /export\s+(interface|class)\s+(\w+)/g;
  246. let match;
  247. while ((match = typeRegex.exec(content)) !== null) {
  248. const typeKind = match[1]; // 'interface' 或 'class'
  249. const typeName = match[2]; // 类型名称
  250. tableNames.push(typeName);
  251. // 解析字段定义
  252. const fieldDefinitions = parseTypeFields(content, typeName);
  253. // 生成CREATE TABLE SQL语句
  254. const sqlStatement =
  255. `static readonly CREATE_${typeName.toUpperCase()}_TABLE = 'CREATE TABLE IF NOT EXISTS ${typeName} (${fieldDefinitions})';`;
  256. sqlStatements.push(sqlStatement);
  257. console.log(`Found ${typeKind}: ${typeName}`);
  258. }
  259. } catch (readError) {
  260. console.error(`Error reading file ${file}:`, readError);
  261. }
  262. }
  263. });
  264. // 添加解析接口字段的辅助函数
  265. function parseTypeFields(content: string, typeName: string): string {
  266. // 使用正则表达式查找 interface 或 class 定义,允许中间有多个空格
  267. const classRegex = new RegExp(`export\\s+class\\s+${typeName}`, 'g');
  268. const interfaceRegex = new RegExp(`export\\s+interface\\s+${typeName}`, 'g');
  269. const classMatch = classRegex.exec(content);
  270. const interfaceMatch = interfaceRegex.exec(content);
  271. let typeStart = -1;
  272. if (classMatch) {
  273. typeStart = classMatch.index;
  274. } else if (interfaceMatch) {
  275. typeStart = interfaceMatch.index;
  276. }
  277. console.log('开始解析类型体内容:');
  278. console.log('typeStart内容:', typeStart);
  279. if (typeStart === -1) {
  280. return 'ID INTEGER PRIMARY KEY AUTOINCREMENT';
  281. }
  282. // 后续逻辑保持不变...
  283. const openBrace = content.indexOf('{', typeStart);
  284. const closeBrace = content.indexOf('}', openBrace);
  285. if (openBrace === -1 || closeBrace === -1) {
  286. console.log('openBrace和closeBrace内容:', openBrace, closeBrace);
  287. return 'ID INTEGER PRIMARY KEY AUTOINCREMENT';
  288. }
  289. const typeBody = content.substring(openBrace + 1, closeBrace);
  290. const lines = typeBody.split('\n');
  291. const fields: string[] = [];
  292. let hasPrimaryKey = false;
  293. // 改进字段解析逻辑
  294. lines.forEach(line => {
  295. const trimmedLine = line.trim();
  296. // 使用更宽松的正则表达式,支持多种格式
  297. // 匹配格式: fieldName?: type; 或 fieldName: type; (允许前后空格)
  298. const fieldMatch = trimmedLine.match(/^(\w+)(\??)\s*:\s*([^;]+?)\s*(?:;|$)/);
  299. if (fieldMatch) {
  300. const fieldName = fieldMatch[1];
  301. const isOptional = fieldMatch[2] === '?';
  302. const fieldType = fieldMatch[3].trim();
  303. console.log(`解析字段: ${fieldName}, 可选: ${isOptional}, 类型: ${fieldType}`); // 调试信息
  304. // 根据TypeScript类型映射到SQLite类型
  305. let sqliteType = 'TEXT';
  306. const normalizedFieldType = fieldType.toLowerCase();
  307. if (normalizedFieldType.includes('number') || normalizedFieldType.includes('int') ||
  308. normalizedFieldType.includes('float')) {
  309. sqliteType = 'INTEGER';
  310. } else if (normalizedFieldType.includes('boolean')) {
  311. sqliteType = 'INTEGER';
  312. } else if (normalizedFieldType.includes('date')) {
  313. sqliteType = 'TEXT';
  314. }
  315. // 检查是否为主键字段
  316. if (fieldName.toLowerCase() === 'id') {
  317. fields.push(`${fieldName} INTEGER PRIMARY KEY AUTOINCREMENT`);
  318. hasPrimaryKey = true;
  319. } else {
  320. // 处理可选字段
  321. const nullable = isOptional ? '' : ' NOT NULL';
  322. fields.push(`${fieldName} ${sqliteType}${nullable}`);
  323. }
  324. }
  325. });
  326. // 如果没有定义主键,则添加默认主键
  327. if (!hasPrimaryKey) {
  328. fields.unshift('ID INTEGER PRIMARY KEY AUTOINCREMENT');
  329. }
  330. return fields.join(', ');
  331. }
  332. // 更新RelationalStoreUtils.ets文件中的tables数组
  333. const relationalStorePath =
  334. path.join(currentDir, 'src', 'main', 'ets', 'rdb', 'utils', 'RelationalStoreUtis.ets');
  335. if (fs.existsSync(relationalStorePath)) {
  336. let content = fs.readFileSync(relationalStorePath, 'utf8');
  337. // 更新tables数组
  338. if (tableNames.length > 0) {
  339. const tablesArray = `private static tables: string[] = [${tableNames.map(name => `'${name}'`).join(', ')}]`;
  340. const updatedContent = content.replace(/private static tables: string\[\] = \[[^\]]*\]/, tablesArray);
  341. fs.writeFileSync(relationalStorePath, updatedContent, 'utf8');
  342. console.log('RelationalStoreUtis.ets文件已更新');
  343. }
  344. }
  345. // 替换原有的更新RDBSql.ets文件的代码块
  346. // 更新RDBSql.ts文件
  347. // 更新RDBSql.ts文件的改进逻辑
  348. const rdbSqlPath = path.join(currentDir, 'src', 'main', 'ets', 'rdb', 'sqls', 'RDBSql.ts');
  349. if (fs.existsSync(rdbSqlPath)) {
  350. let content = fs.readFileSync(rdbSqlPath, 'utf8');
  351. // 解析现有rDbSql对象中的属性
  352. const existingProperties: Map<string, string> = new Map();
  353. const propertyRegex = /(\w+):\s*'([^']+)'/g;
  354. let match;
  355. while ((match = propertyRegex.exec(content)) !== null) {
  356. existingProperties.set(match[1], match[2]);
  357. }
  358. // 构建SQL语句对象属性
  359. if (sqlStatements.length > 0) {
  360. const sqlProperties: string[] = [];
  361. // 首先保留现有的属性(用于手动添加的SQL)
  362. existingProperties.forEach((value, key) => {
  363. // 检查是否是自动生成的CREATE_*_TABLE属性
  364. if (!key.startsWith('CREATE_') || !key.endsWith('_TABLE')) {
  365. sqlProperties.push(` ${key}: '${value}'`);
  366. }
  367. });
  368. // 添加或更新基于类定义生成的SQL属性
  369. sqlStatements.forEach(statement => {
  370. const propertyName = statement.split('=')[0].trim().replace('static readonly ', '');
  371. const propertyValue = statement.split('=')[1].trim().slice(1, -2);
  372. sqlProperties.push(` ${propertyName}: '${propertyValue}'`);
  373. });
  374. // 构建新的对象内容
  375. const newContent = `export const rDbSql = {\n${sqlProperties.join(',\n')}\n}`;
  376. fs.writeFileSync(rdbSqlPath, newContent, 'utf8');
  377. console.log('RDBSql.ts文件已更新,保留了手动添加的SQL语句');
  378. }
  379. } else {
  380. // 如果文件不存在,创建新文件(原有逻辑保持不变)
  381. if (sqlStatements.length > 0) {
  382. const sqlProperties: string[] = [];
  383. sqlStatements.forEach(statement => {
  384. const propertyName = statement.split('=')[0].trim().replace('static readonly ', '');
  385. const propertyValue = statement.split('=')[1].trim().slice(1, -2);
  386. sqlProperties.push(` ${propertyName}: '${propertyValue}'`);
  387. });
  388. const newContent = `export const rDbSql = {\n${sqlProperties.join(',\n')}\n}`;
  389. // 确保目录存在
  390. const dirPath = path.dirname(rdbSqlPath);
  391. if (!fs.existsSync(dirPath)) {
  392. fs.mkdirSync(dirPath, { recursive: true });
  393. }
  394. fs.writeFileSync(rdbSqlPath, newContent, 'utf8');
  395. console.log('RDBSql.ts文件已创建并初始化为对象形式');
  396. }
  397. }
  398. console.log('----------------------------------------');
  399. } else {
  400. console.log('Tables directory not found');
  401. }
  402. } catch (error) {
  403. console.error(`处理tables目录时出错:`, error);
  404. }
  405. }
  406. }
  407. // function forwardingPort(){
  408. // return {
  409. // pluginId: 'customPlugin',
  410. // apply(node: HvigorNode) {
  411. // try {
  412. // var connectKeys = execSync('hdc list targets', { encoding: 'utf-8' }).split('\n');
  413. //
  414. // var targetKey = connectKeys[0].trim();
  415. //
  416. // var operation = `hdc -t ${targetKey} fport rm tcp:8080 tcp:8080`
  417. // var result = execSync(operation, { encoding: 'utf-8' })
  418. // console.log(`执行命令 ${operation}, 删除端口结果:${result.trim()}`);
  419. //
  420. // operation = `hdc -t ${targetKey} fport tcp:8080 tcp:8080`
  421. // result = execSync(operation, { encoding: 'utf-8' })
  422. // console.log(`执行命令 ${operation}, 转发端口结果:${result.trim()}`);
  423. // } catch (error) {
  424. // console.error(`执行失败: ${error.message}`);
  425. // }
  426. // }
  427. // }
  428. // }
  429. export default {
  430. system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
  431. plugins: [
  432. customPlugin(), // 应用自定义Plugin
  433. rDBPlugin(),
  434. // forwardingPort()
  435. ] /* Custom plugin to extend the functionality of Hvigor. */
  436. }