ef-compiler.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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, 'actions', '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('./actions/set-parser.js')
  32. const expressionEvaluator = require('./expression-evaluator.js')
  33. const runtimeApi = require('./runtime-api.js')
  34. const workflowJsonParser = require('./workflow-json-parser.js')
  35. const sequenceRunner = require('./sequence-runner.js')
  36. const actions = require('./actions/fun/fun-parser.js')
  37. // --- 功能模块(fun 目录)与运行时 API ---
  38. const { matchImageAndGetCoordinate } = require('./actions/fun/img-center-point-location.js')
  39. const { readTextFile } = require('./actions/fun/read-txt.js')
  40. const { writeTextFile } = require('./actions/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. declaredVariableNames: [], // workflow.variables 的 key,用于校验 inVars/outVars 引用是否已声明
  49. }
  50. // --- 从各组件抽出的工具方法(供本文件与 ctx 使用)---
  51. const extractVarName = setParser.extractVarName
  52. const replaceVariablesInString = setParser.replaceVariablesInString
  53. const resolveValue = setParser.resolveValue
  54. const parseDelayString = setParser.parseDelayString
  55. const calculateWaitTime = setParser.calculateWaitTime
  56. const evaluateCondition = expressionEvaluator.evaluateCondition
  57. const evaluateExpression = expressionEvaluator.evaluateExpression
  58. const getActionName = workflowJsonParser.getActionName
  59. // --- 日志与变量输出 ---
  60. async function logMessage(message, folderPath = null) {
  61. try {
  62. const targetFolderPath = folderPath || state.currentWorkflowFolderPath
  63. if (targetFolderPath && electronAPI.appendLog) await electronAPI.appendLog(targetFolderPath, message)
  64. } catch (err) {}
  65. }
  66. async function logOutVars(action, variableContext, folderPath = null) {
  67. if (!action.outVars || !Array.isArray(action.outVars) || action.outVars.length === 0) return
  68. const outVarsInfo = action.outVars.map((varName) => {
  69. const varNameClean = extractVarName(varName)
  70. const value = variableContext[varNameClean]
  71. let displayValue = value
  72. if (typeof value === 'string' && value.length > 100) displayValue = value.substring(0, 100) + '...'
  73. return `${varNameClean}: ${JSON.stringify(displayValue)}`
  74. })
  75. }
  76. // --- 对外入口 1:解析整份工作流 ---
  77. function parseWorkflow(workflow) {
  78. const loaderState = {
  79. variableContext: state.variableContext,
  80. getInitialized: () => state.variableContextInitialized,
  81. setInitialized: (v) => { state.variableContextInitialized = v },
  82. }
  83. const result = workflowJsonParser.parseWorkflow(workflow, loaderState)
  84. state.declaredVariableNames = workflow && typeof workflow.variables === 'object' ? Object.keys(workflow.variables) : []
  85. return result
  86. }
  87. // --- 对外入口 2:仅解析动作列表 ---
  88. function parseActions(actions) {
  89. const loaderState = {
  90. variableContext: state.variableContext,
  91. getInitialized: () => state.variableContextInitialized,
  92. setInitialized: () => {},
  93. }
  94. return workflowJsonParser.parseActions(actions, loaderState)
  95. }
  96. // --- 对外入口 3:执行单条动作 ---
  97. async function executeAction(action, device, folderPath, resolution) {
  98. const ctx = {
  99. variableContext: state.variableContext,
  100. compilerConfig,
  101. electronAPI,
  102. extractVarName,
  103. replaceVariablesInString,
  104. resolveValue,
  105. evaluateCondition,
  106. evaluateExpression,
  107. getActionName,
  108. logMessage,
  109. logOutVars,
  110. parseDelayString,
  111. calculateWaitTime,
  112. DEFAULT_SCROLL_DISTANCE,
  113. registry: workflowJsonParser.registry,
  114. executeAction: workflowJsonParser.executeAction,
  115. }
  116. try {
  117. return await actions.runAction(action, device, folderPath, resolution, ctx)
  118. } catch (error) {
  119. const now = new Date()
  120. 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')}`
  121. const errDetail = error && (error.message || error.stack || String(error)) || 'unknown'
  122. const errorMsg = `[ef-compiler] [ERROR] Action failed: ${errDetail} [time: ${timeStr}]`
  123. await logMessage(errorMsg, folderPath).catch(() => {})
  124. return { success: false, error: errDetail }
  125. }
  126. }
  127. // --- 对外入口 4:按顺序执行动作序列(含 schedule/if/for/while 等)---
  128. async function executeActionSequence(actions, device, folderPath, resolution, stepInterval = DEFAULT_STEP_INTERVAL, onStepComplete = null, shouldStop = null, depth = 0) {
  129. const ctx = {
  130. executeAction,
  131. logMessage,
  132. evaluateCondition,
  133. getActionName,
  134. parseDelayString,
  135. calculateWaitTime,
  136. replaceVariablesInString,
  137. state,
  138. DEFAULT_STEP_INTERVAL,
  139. }
  140. return sequenceRunner.executeActionSequence(actions, device, folderPath, resolution, stepInterval, onStepComplete, shouldStop, depth, ctx)
  141. }
  142. // --- 编译/执行入口:对外暴露的 API ---
  143. module.exports = {
  144. parseWorkflow,
  145. parseActions,
  146. executeAction,
  147. executeActionSequence,
  148. }