ef-compiler.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. // EasyFlow 编译器 - 工作流任务解析和执行器(主机,单文件 ≤500 行)
  2. // ========== 入口说明:外部通过 require 本模块后,使用下方 module.exports 导出的 5 个方法作为调用入口 ==========
  3. const path = require('path')
  4. const fs = require('fs')
  5. // --- 配置:projectRoot = config 文件所在目录的上级(config 在 <根>/configs/config.js)---
  6. const defaultRoot = path.resolve(__dirname, '..', '..')
  7. const configPath = process.env.STATIC_ROOT
  8. ? path.join(path.dirname(process.env.STATIC_ROOT), 'configs', 'config.js')
  9. : path.join(defaultRoot, 'configs', 'config.js')
  10. let projectRoot = path.dirname(path.dirname(path.resolve(configPath)))
  11. let adbInteractPath = path.join(projectRoot, 'nodejs', 'adb', 'adb-interact.js')
  12. let nodeExePath = 'node'
  13. if (fs.existsSync(configPath)) {
  14. try {
  15. const cfg = require(configPath)
  16. nodeExePath = cfg.nodejsPath
  17. ? (path.isAbsolute(cfg.nodejsPath) ? cfg.nodejsPath : path.join(projectRoot, cfg.nodejsPath))
  18. : (process.env.STATIC_ROOT ? path.join(projectRoot, 'node', process.platform === 'win32' ? 'node.exe' : 'node') : 'node')
  19. adbInteractPath = path.join(projectRoot, 'nodejs', 'adb', 'adb-interact.js')
  20. if (process.env.STATIC_ROOT) {
  21. const unpacked = path.join(projectRoot, 'resources', 'app.asar.unpacked', 'nodejs', 'adb', 'adb-interact.js')
  22. if (fs.existsSync(unpacked)) adbInteractPath = unpacked
  23. }
  24. } catch (e) {}
  25. }
  26. const funcDir = path.join(__dirname, 'fun')
  27. const DEFAULT_STEP_INTERVAL = 1000
  28. const DEFAULT_SCROLL_DISTANCE = 100
  29. const compilerConfig = { projectRoot, funcDir, adbInteractPath, nodeExePath, DEFAULT_STEP_INTERVAL, DEFAULT_SCROLL_DISTANCE }
  30. // --- 依赖 ---
  31. const setParser = require('./components/actions/set-parser.js')
  32. const expressionEvaluator = require('./components/expression-evaluator.js')
  33. const runtimeApi = require('./components/runtime-api.js')
  34. const workflowJsonParser = require('./components/workflow-json-parser.js')
  35. const sequenceRunner = require('./components/sequence-runner.js')
  36. const actions = require('./components/actions/fun-parser.js')
  37. // --- 功能模块(fun 目录)与运行时 API ---
  38. const { matchImageAndGetCoordinate } = require('./fun/img-center-point-location.js')
  39. const { readTextFile } = require('./fun/read-txt.js')
  40. const { writeTextFile } = require('./fun/save-txt.js')
  41. const electronAPI = runtimeApi.createElectronAPI({ matchImageAndGetCoordinate, readTextFile, writeTextFile }, compilerConfig)
  42. // --- 共享状态(变量上下文、步骤计数、当前工作流目录等)---
  43. const state = {
  44. variableContext: {},
  45. variableContextInitialized: false,
  46. globalStepCounter: 0,
  47. currentWorkflowFolderPath: null,
  48. }
  49. // --- 从各组件抽出的工具方法(供本文件与 ctx 使用)---
  50. const extractVarName = setParser.extractVarName
  51. const replaceVariablesInString = setParser.replaceVariablesInString
  52. const resolveValue = setParser.resolveValue
  53. const parseDelayString = setParser.parseDelayString
  54. const calculateWaitTime = setParser.calculateWaitTime
  55. const evaluateCondition = expressionEvaluator.evaluateCondition
  56. const evaluateExpression = expressionEvaluator.evaluateExpression
  57. const getActionName = workflowJsonParser.getActionName
  58. // --- 日志与变量输出 ---
  59. async function logMessage(message, folderPath = null) {
  60. try {
  61. const targetFolderPath = folderPath || state.currentWorkflowFolderPath
  62. if (targetFolderPath && electronAPI.appendLog) await electronAPI.appendLog(targetFolderPath, message)
  63. } catch (err) {}
  64. }
  65. async function logOutVars(action, variableContext, folderPath = null) {
  66. if (!action.outVars || !Array.isArray(action.outVars) || action.outVars.length === 0) return
  67. const outVarsInfo = action.outVars.map((varName) => {
  68. const varNameClean = extractVarName(varName)
  69. const value = variableContext[varNameClean]
  70. let displayValue = value
  71. if (typeof value === 'string' && value.length > 100) displayValue = value.substring(0, 100) + '...'
  72. return `${varNameClean}: ${JSON.stringify(displayValue)}`
  73. })
  74. }
  75. // --- 对外入口 1:解析整份工作流 ---
  76. function parseWorkflow(workflow) {
  77. const loaderState = {
  78. variableContext: state.variableContext,
  79. getInitialized: () => state.variableContextInitialized,
  80. setInitialized: (v) => { state.variableContextInitialized = v },
  81. }
  82. return workflowJsonParser.parseWorkflow(workflow, loaderState)
  83. }
  84. // --- 对外入口 2:仅解析动作列表 ---
  85. function parseActions(actions) {
  86. const loaderState = {
  87. variableContext: state.variableContext,
  88. getInitialized: () => state.variableContextInitialized,
  89. setInitialized: () => {},
  90. }
  91. return workflowJsonParser.parseActions(actions, loaderState)
  92. }
  93. // --- 对外入口 3:执行单条动作 ---
  94. async function executeAction(action, device, folderPath, resolution) {
  95. const ctx = {
  96. variableContext: state.variableContext,
  97. compilerConfig,
  98. electronAPI,
  99. extractVarName,
  100. replaceVariablesInString,
  101. resolveValue,
  102. evaluateCondition,
  103. evaluateExpression,
  104. getActionName,
  105. logMessage,
  106. logOutVars,
  107. parseDelayString,
  108. calculateWaitTime,
  109. DEFAULT_SCROLL_DISTANCE,
  110. registry: workflowJsonParser.registry,
  111. executeAction: workflowJsonParser.executeAction,
  112. }
  113. try {
  114. return await actions.runAction(action, device, folderPath, resolution, ctx)
  115. } catch (error) {
  116. const now = new Date()
  117. const timeStr = `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
  118. const errDetail = error && (error.message || error.stack || String(error)) || 'unknown'
  119. const errorMsg = `[ef-compiler] [ERROR] Action failed: ${errDetail} [time: ${timeStr}]`
  120. await logMessage(errorMsg, folderPath).catch(() => {})
  121. return { success: false, error: errDetail }
  122. }
  123. }
  124. // --- 对外入口 4:按顺序执行动作序列(含 schedule/if/for/while 等)---
  125. async function executeActionSequence(actions, device, folderPath, resolution, stepInterval = DEFAULT_STEP_INTERVAL, onStepComplete = null, shouldStop = null, depth = 0) {
  126. const ctx = {
  127. executeAction,
  128. logMessage,
  129. evaluateCondition,
  130. getActionName,
  131. parseDelayString,
  132. calculateWaitTime,
  133. replaceVariablesInString,
  134. state,
  135. DEFAULT_STEP_INTERVAL,
  136. }
  137. return sequenceRunner.executeActionSequence(actions, device, folderPath, resolution, stepInterval, onStepComplete, shouldStop, depth, ctx)
  138. }
  139. // --- 编译/执行入口:对外暴露的 API ---
  140. module.exports = {
  141. parseWorkflow,
  142. parseActions,
  143. executeAction,
  144. executeActionSequence,
  145. }