@xiuchang-midscene/shared 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +9 -0
  2. package/dist/es/baseDB.mjs +109 -0
  3. package/dist/es/baseDB.mjs.bak +109 -0
  4. package/dist/es/build/copy-static.mjs +31 -0
  5. package/dist/es/build/copy-static.mjs.bak +31 -0
  6. package/dist/es/build/rspack-config.mjs +4 -0
  7. package/dist/es/build/rspack-config.mjs.bak +4 -0
  8. package/dist/es/cli/cli-runner.mjs +140 -0
  9. package/dist/es/cli/cli-runner.mjs.bak +140 -0
  10. package/dist/es/cli/index.mjs +2 -0
  11. package/dist/es/cli/index.mjs.bak +2 -0
  12. package/dist/es/common.mjs +37 -0
  13. package/dist/es/common.mjs.bak +37 -0
  14. package/dist/es/constants/example-code.mjs +223 -0
  15. package/dist/es/constants/example-code.mjs.bak +223 -0
  16. package/dist/es/constants/index.mjs +23 -0
  17. package/dist/es/constants/index.mjs.bak +23 -0
  18. package/dist/es/env/basic.mjs +6 -0
  19. package/dist/es/env/basic.mjs.bak +6 -0
  20. package/dist/es/env/constants.mjs +70 -0
  21. package/dist/es/env/constants.mjs.bak +70 -0
  22. package/dist/es/env/global-config-manager.mjs +94 -0
  23. package/dist/es/env/global-config-manager.mjs.bak +94 -0
  24. package/dist/es/env/helper.mjs +43 -0
  25. package/dist/es/env/helper.mjs.bak +43 -0
  26. package/dist/es/env/index.mjs +5 -0
  27. package/dist/es/env/index.mjs.bak +5 -0
  28. package/dist/es/env/init-debug.mjs +18 -0
  29. package/dist/es/env/init-debug.mjs.bak +18 -0
  30. package/dist/es/env/model-config-manager.mjs +79 -0
  31. package/dist/es/env/model-config-manager.mjs.bak +79 -0
  32. package/dist/es/env/parse-model-config.mjs +132 -0
  33. package/dist/es/env/parse-model-config.mjs.bak +132 -0
  34. package/dist/es/env/types.mjs +220 -0
  35. package/dist/es/env/types.mjs.bak +220 -0
  36. package/dist/es/env/utils.mjs +26 -0
  37. package/dist/es/env/utils.mjs.bak +26 -0
  38. package/dist/es/extractor/constants.mjs +2 -0
  39. package/dist/es/extractor/constants.mjs.bak +2 -0
  40. package/dist/es/extractor/debug.mjs +6 -0
  41. package/dist/es/extractor/debug.mjs.bak +6 -0
  42. package/dist/es/extractor/dom-util.mjs +92 -0
  43. package/dist/es/extractor/dom-util.mjs.bak +92 -0
  44. package/dist/es/extractor/index.mjs +5 -0
  45. package/dist/es/extractor/index.mjs.bak +5 -0
  46. package/dist/es/extractor/locator.mjs +250 -0
  47. package/dist/es/extractor/locator.mjs.bak +250 -0
  48. package/dist/es/extractor/tree.mjs +78 -0
  49. package/dist/es/extractor/tree.mjs.bak +78 -0
  50. package/dist/es/extractor/util.mjs +245 -0
  51. package/dist/es/extractor/util.mjs.bak +245 -0
  52. package/dist/es/extractor/web-extractor.mjs +303 -0
  53. package/dist/es/extractor/web-extractor.mjs.bak +303 -0
  54. package/dist/es/img/box-select.mjs +824 -0
  55. package/dist/es/img/box-select.mjs.bak +824 -0
  56. package/dist/es/img/canvas-fallback.mjs +238 -0
  57. package/dist/es/img/canvas-fallback.mjs.bak +238 -0
  58. package/dist/es/img/get-photon.mjs +45 -0
  59. package/dist/es/img/get-photon.mjs.bak +45 -0
  60. package/dist/es/img/get-sharp.mjs +11 -0
  61. package/dist/es/img/get-sharp.mjs.bak +11 -0
  62. package/dist/es/img/index.mjs +4 -0
  63. package/dist/es/img/index.mjs.bak +4 -0
  64. package/dist/es/img/info.mjs +29 -0
  65. package/dist/es/img/info.mjs.bak +29 -0
  66. package/dist/es/img/transform.mjs +295 -0
  67. package/dist/es/img/transform.mjs.bak +295 -0
  68. package/dist/es/index.mjs +4 -0
  69. package/dist/es/index.mjs.bak +4 -0
  70. package/dist/es/logger.mjs +64 -0
  71. package/dist/es/logger.mjs.bak +64 -0
  72. package/dist/es/mcp/base-server.mjs +281 -0
  73. package/dist/es/mcp/base-server.mjs.bak +281 -0
  74. package/dist/es/mcp/base-tools.mjs +91 -0
  75. package/dist/es/mcp/base-tools.mjs.bak +91 -0
  76. package/dist/es/mcp/chrome-path.mjs +35 -0
  77. package/dist/es/mcp/chrome-path.mjs.bak +35 -0
  78. package/dist/es/mcp/index.mjs +7 -0
  79. package/dist/es/mcp/index.mjs.bak +7 -0
  80. package/dist/es/mcp/inject-report-html-plugin.mjs +53 -0
  81. package/dist/es/mcp/inject-report-html-plugin.mjs.bak +53 -0
  82. package/dist/es/mcp/launcher-helper.mjs +52 -0
  83. package/dist/es/mcp/launcher-helper.mjs.bak +52 -0
  84. package/dist/es/mcp/tool-generator.mjs +297 -0
  85. package/dist/es/mcp/tool-generator.mjs.bak +297 -0
  86. package/dist/es/mcp/types.mjs +3 -0
  87. package/dist/es/mcp/types.mjs.bak +3 -0
  88. package/dist/es/node/fs.mjs +44 -0
  89. package/dist/es/node/fs.mjs.bak +44 -0
  90. package/dist/es/node/index.mjs +2 -0
  91. package/dist/es/node/index.mjs.bak +2 -0
  92. package/dist/es/node/port.mjs +24 -0
  93. package/dist/es/node/port.mjs.bak +24 -0
  94. package/dist/es/oss/demo.mjs +30 -0
  95. package/dist/es/oss/demo.mjs.bak +30 -0
  96. package/dist/es/oss/index.mjs +90 -0
  97. package/dist/es/oss/index.mjs.bak +90 -0
  98. package/dist/es/polyfills/async-hooks.mjs +2 -0
  99. package/dist/es/polyfills/async-hooks.mjs.bak +2 -0
  100. package/dist/es/polyfills/index.mjs +1 -0
  101. package/dist/es/polyfills/index.mjs.bak +1 -0
  102. package/dist/es/types/index.mjs +3 -0
  103. package/dist/es/types/index.mjs.bak +3 -0
  104. package/dist/es/us-keyboard-layout.mjs +1414 -0
  105. package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
  106. package/dist/es/us-keyboard-layout.mjs.bak +1414 -0
  107. package/dist/es/utils.mjs +72 -0
  108. package/dist/es/utils.mjs.bak +72 -0
  109. package/dist/es/zod-schema-utils.mjs +54 -0
  110. package/dist/es/zod-schema-utils.mjs.bak +54 -0
  111. package/dist/lib/baseDB.js +149 -0
  112. package/dist/lib/baseDB.js.bak +149 -0
  113. package/dist/lib/build/copy-static.js +79 -0
  114. package/dist/lib/build/copy-static.js.bak +79 -0
  115. package/dist/lib/build/rspack-config.js +38 -0
  116. package/dist/lib/build/rspack-config.js.bak +38 -0
  117. package/dist/lib/cli/cli-runner.js +196 -0
  118. package/dist/lib/cli/cli-runner.js.bak +196 -0
  119. package/dist/lib/cli/index.js +48 -0
  120. package/dist/lib/cli/index.js.bak +48 -0
  121. package/dist/lib/common.js +93 -0
  122. package/dist/lib/common.js.bak +93 -0
  123. package/dist/lib/constants/example-code.js +260 -0
  124. package/dist/lib/constants/example-code.js.bak +260 -0
  125. package/dist/lib/constants/index.js +96 -0
  126. package/dist/lib/constants/index.js.bak +96 -0
  127. package/dist/lib/env/basic.js +40 -0
  128. package/dist/lib/env/basic.js.bak +40 -0
  129. package/dist/lib/env/constants.js +113 -0
  130. package/dist/lib/env/constants.js.bak +113 -0
  131. package/dist/lib/env/global-config-manager.js +128 -0
  132. package/dist/lib/env/global-config-manager.js.bak +128 -0
  133. package/dist/lib/env/helper.js +80 -0
  134. package/dist/lib/env/helper.js.bak +80 -0
  135. package/dist/lib/env/index.js +90 -0
  136. package/dist/lib/env/index.js.bak +90 -0
  137. package/dist/lib/env/init-debug.js +52 -0
  138. package/dist/lib/env/init-debug.js.bak +52 -0
  139. package/dist/lib/env/model-config-manager.js +113 -0
  140. package/dist/lib/env/model-config-manager.js.bak +113 -0
  141. package/dist/lib/env/parse-model-config.js +178 -0
  142. package/dist/lib/env/parse-model-config.js.bak +178 -0
  143. package/dist/lib/env/types.js +554 -0
  144. package/dist/lib/env/types.js.bak +554 -0
  145. package/dist/lib/env/utils.js +72 -0
  146. package/dist/lib/env/utils.js.bak +72 -0
  147. package/dist/lib/extractor/constants.js +42 -0
  148. package/dist/lib/extractor/constants.js.bak +42 -0
  149. package/dist/lib/extractor/debug.js +12 -0
  150. package/dist/lib/extractor/debug.js.bak +12 -0
  151. package/dist/lib/extractor/dom-util.js +153 -0
  152. package/dist/lib/extractor/dom-util.js.bak +153 -0
  153. package/dist/lib/extractor/index.js +81 -0
  154. package/dist/lib/extractor/index.js.bak +81 -0
  155. package/dist/lib/extractor/locator.js +296 -0
  156. package/dist/lib/extractor/locator.js.bak +296 -0
  157. package/dist/lib/extractor/tree.js +124 -0
  158. package/dist/lib/extractor/tree.js.bak +124 -0
  159. package/dist/lib/extractor/util.js +336 -0
  160. package/dist/lib/extractor/util.js.bak +336 -0
  161. package/dist/lib/extractor/web-extractor.js +349 -0
  162. package/dist/lib/extractor/web-extractor.js.bak +349 -0
  163. package/dist/lib/img/box-select.js +875 -0
  164. package/dist/lib/img/box-select.js.bak +875 -0
  165. package/dist/lib/img/canvas-fallback.js +305 -0
  166. package/dist/lib/img/canvas-fallback.js.bak +305 -0
  167. package/dist/lib/img/get-photon.js +82 -0
  168. package/dist/lib/img/get-photon.js.bak +82 -0
  169. package/dist/lib/img/get-sharp.js +45 -0
  170. package/dist/lib/img/get-sharp.js.bak +45 -0
  171. package/dist/lib/img/index.js +95 -0
  172. package/dist/lib/img/index.js.bak +95 -0
  173. package/dist/lib/img/info.js +83 -0
  174. package/dist/lib/img/info.js.bak +83 -0
  175. package/dist/lib/img/transform.js +387 -0
  176. package/dist/lib/img/transform.js.bak +387 -0
  177. package/dist/lib/index.js +47 -0
  178. package/dist/lib/index.js.bak +47 -0
  179. package/dist/lib/logger.js +114 -0
  180. package/dist/lib/logger.js.bak +114 -0
  181. package/dist/lib/mcp/base-server.js +331 -0
  182. package/dist/lib/mcp/base-server.js.bak +331 -0
  183. package/dist/lib/mcp/base-tools.js +125 -0
  184. package/dist/lib/mcp/base-tools.js.bak +125 -0
  185. package/dist/lib/mcp/chrome-path.js +72 -0
  186. package/dist/lib/mcp/chrome-path.js.bak +72 -0
  187. package/dist/lib/mcp/index.js +100 -0
  188. package/dist/lib/mcp/index.js.bak +100 -0
  189. package/dist/lib/mcp/inject-report-html-plugin.js +98 -0
  190. package/dist/lib/mcp/inject-report-html-plugin.js.bak +98 -0
  191. package/dist/lib/mcp/launcher-helper.js +86 -0
  192. package/dist/lib/mcp/launcher-helper.js.bak +86 -0
  193. package/dist/lib/mcp/tool-generator.js +334 -0
  194. package/dist/lib/mcp/tool-generator.js.bak +334 -0
  195. package/dist/lib/mcp/types.js +40 -0
  196. package/dist/lib/mcp/types.js.bak +40 -0
  197. package/dist/lib/node/fs.js +97 -0
  198. package/dist/lib/node/fs.js.bak +97 -0
  199. package/dist/lib/node/index.js +65 -0
  200. package/dist/lib/node/index.js.bak +65 -0
  201. package/dist/lib/node/port.js +61 -0
  202. package/dist/lib/node/port.js.bak +61 -0
  203. package/dist/lib/oss/demo.js +36 -0
  204. package/dist/lib/oss/demo.js.bak +36 -0
  205. package/dist/lib/oss/index.js +138 -0
  206. package/dist/lib/oss/index.js.bak +138 -0
  207. package/dist/lib/polyfills/async-hooks.js +36 -0
  208. package/dist/lib/polyfills/async-hooks.js.bak +36 -0
  209. package/dist/lib/polyfills/index.js +58 -0
  210. package/dist/lib/polyfills/index.js.bak +58 -0
  211. package/dist/lib/types/index.js +37 -0
  212. package/dist/lib/types/index.js.bak +37 -0
  213. package/dist/lib/us-keyboard-layout.js +1457 -0
  214. package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
  215. package/dist/lib/us-keyboard-layout.js.bak +1457 -0
  216. package/dist/lib/utils.js +148 -0
  217. package/dist/lib/utils.js.bak +148 -0
  218. package/dist/lib/zod-schema-utils.js +97 -0
  219. package/dist/lib/zod-schema-utils.js.bak +97 -0
  220. package/dist/types/baseDB.d.ts +25 -0
  221. package/dist/types/baseDB.d.ts.bak +25 -0
  222. package/dist/types/build/copy-static.d.ts +31 -0
  223. package/dist/types/build/copy-static.d.ts.bak +31 -0
  224. package/dist/types/build/rspack-config.d.ts +8 -0
  225. package/dist/types/build/rspack-config.d.ts.bak +8 -0
  226. package/dist/types/cli/cli-runner.d.ts +14 -0
  227. package/dist/types/cli/cli-runner.d.ts.bak +14 -0
  228. package/dist/types/cli/index.d.ts +2 -0
  229. package/dist/types/cli/index.d.ts.bak +2 -0
  230. package/dist/types/common.d.ts +12 -0
  231. package/dist/types/common.d.ts.bak +12 -0
  232. package/dist/types/constants/example-code.d.ts +2 -0
  233. package/dist/types/constants/example-code.d.ts.bak +2 -0
  234. package/dist/types/constants/index.d.ts +21 -0
  235. package/dist/types/constants/index.d.ts.bak +21 -0
  236. package/dist/types/env/basic.d.ts +6 -0
  237. package/dist/types/env/basic.d.ts.bak +6 -0
  238. package/dist/types/env/constants.d.ts +40 -0
  239. package/dist/types/env/constants.d.ts.bak +40 -0
  240. package/dist/types/env/global-config-manager.d.ts +32 -0
  241. package/dist/types/env/global-config-manager.d.ts.bak +32 -0
  242. package/dist/types/env/helper.d.ts +4 -0
  243. package/dist/types/env/helper.d.ts.bak +4 -0
  244. package/dist/types/env/index.d.ts +4 -0
  245. package/dist/types/env/index.d.ts.bak +4 -0
  246. package/dist/types/env/init-debug.d.ts +1 -0
  247. package/dist/types/env/init-debug.d.ts.bak +1 -0
  248. package/dist/types/env/model-config-manager.d.ts +25 -0
  249. package/dist/types/env/model-config-manager.d.ts.bak +25 -0
  250. package/dist/types/env/parse-model-config.d.ts +31 -0
  251. package/dist/types/env/parse-model-config.d.ts.bak +31 -0
  252. package/dist/types/env/types.d.ts +318 -0
  253. package/dist/types/env/types.d.ts.bak +318 -0
  254. package/dist/types/env/utils.d.ts +38 -0
  255. package/dist/types/env/utils.d.ts.bak +38 -0
  256. package/dist/types/extractor/constants.d.ts +1 -0
  257. package/dist/types/extractor/constants.d.ts.bak +1 -0
  258. package/dist/types/extractor/debug.d.ts +1 -0
  259. package/dist/types/extractor/debug.d.ts.bak +1 -0
  260. package/dist/types/extractor/dom-util.d.ts +56 -0
  261. package/dist/types/extractor/dom-util.d.ts.bak +56 -0
  262. package/dist/types/extractor/index.d.ts +32 -0
  263. package/dist/types/extractor/index.d.ts.bak +32 -0
  264. package/dist/types/extractor/locator.d.ts +9 -0
  265. package/dist/types/extractor/locator.d.ts.bak +9 -0
  266. package/dist/types/extractor/tree.d.ts +6 -0
  267. package/dist/types/extractor/tree.d.ts.bak +6 -0
  268. package/dist/types/extractor/util.d.ts +47 -0
  269. package/dist/types/extractor/util.d.ts.bak +47 -0
  270. package/dist/types/extractor/web-extractor.d.ts +19 -0
  271. package/dist/types/extractor/web-extractor.d.ts.bak +19 -0
  272. package/dist/types/img/box-select.d.ts +26 -0
  273. package/dist/types/img/box-select.d.ts.bak +26 -0
  274. package/dist/types/img/canvas-fallback.d.ts +105 -0
  275. package/dist/types/img/canvas-fallback.d.ts.bak +105 -0
  276. package/dist/types/img/get-photon.d.ts +19 -0
  277. package/dist/types/img/get-photon.d.ts.bak +19 -0
  278. package/dist/types/img/get-sharp.d.ts +3 -0
  279. package/dist/types/img/get-sharp.d.ts.bak +3 -0
  280. package/dist/types/img/index.d.ts +3 -0
  281. package/dist/types/img/index.d.ts.bak +3 -0
  282. package/dist/types/img/info.d.ts +29 -0
  283. package/dist/types/img/info.d.ts.bak +29 -0
  284. package/dist/types/img/transform.d.ts +107 -0
  285. package/dist/types/img/transform.d.ts.bak +107 -0
  286. package/dist/types/index.d.ts +4 -0
  287. package/dist/types/index.d.ts.bak +4 -0
  288. package/dist/types/logger.d.ts +5 -0
  289. package/dist/types/logger.d.ts.bak +5 -0
  290. package/dist/types/mcp/base-server.d.ts +93 -0
  291. package/dist/types/mcp/base-server.d.ts.bak +93 -0
  292. package/dist/types/mcp/base-tools.d.ts +79 -0
  293. package/dist/types/mcp/base-tools.d.ts.bak +79 -0
  294. package/dist/types/mcp/chrome-path.d.ts +2 -0
  295. package/dist/types/mcp/chrome-path.d.ts.bak +2 -0
  296. package/dist/types/mcp/index.d.ts +7 -0
  297. package/dist/types/mcp/index.d.ts.bak +7 -0
  298. package/dist/types/mcp/inject-report-html-plugin.d.ts +18 -0
  299. package/dist/types/mcp/inject-report-html-plugin.d.ts.bak +18 -0
  300. package/dist/types/mcp/launcher-helper.d.ts +94 -0
  301. package/dist/types/mcp/launcher-helper.d.ts.bak +94 -0
  302. package/dist/types/mcp/tool-generator.d.ts +10 -0
  303. package/dist/types/mcp/tool-generator.d.ts.bak +10 -0
  304. package/dist/types/mcp/types.d.ts +103 -0
  305. package/dist/types/mcp/types.d.ts.bak +103 -0
  306. package/dist/types/node/fs.d.ts +15 -0
  307. package/dist/types/node/fs.d.ts.bak +15 -0
  308. package/dist/types/node/index.d.ts +2 -0
  309. package/dist/types/node/index.d.ts.bak +2 -0
  310. package/dist/types/node/port.d.ts +8 -0
  311. package/dist/types/node/port.d.ts.bak +8 -0
  312. package/dist/types/oss/demo.d.ts +1 -0
  313. package/dist/types/oss/demo.d.ts.bak +1 -0
  314. package/dist/types/oss/index.d.ts +34 -0
  315. package/dist/types/oss/index.d.ts.bak +34 -0
  316. package/dist/types/polyfills/async-hooks.d.ts +6 -0
  317. package/dist/types/polyfills/async-hooks.d.ts.bak +6 -0
  318. package/dist/types/polyfills/index.d.ts +4 -0
  319. package/dist/types/polyfills/index.d.ts.bak +4 -0
  320. package/dist/types/types/index.d.ts +34 -0
  321. package/dist/types/types/index.d.ts.bak +34 -0
  322. package/dist/types/us-keyboard-layout.d.ts +32 -0
  323. package/dist/types/us-keyboard-layout.d.ts.bak +32 -0
  324. package/dist/types/utils.d.ts +34 -0
  325. package/dist/types/utils.d.ts.bak +34 -0
  326. package/dist/types/zod-schema-utils.d.ts +23 -0
  327. package/dist/types/zod-schema-utils.d.ts.bak +23 -0
  328. package/package.json +132 -0
  329. package/src/baseDB.ts +158 -0
  330. package/src/build/copy-static.ts +68 -0
  331. package/src/build/rspack-config.ts +12 -0
  332. package/src/cli/cli-runner.ts +224 -0
  333. package/src/cli/index.ts +8 -0
  334. package/src/common.ts +67 -0
  335. package/src/constants/example-code.ts +223 -0
  336. package/src/constants/index.ts +29 -0
  337. package/src/env/basic.ts +12 -0
  338. package/src/env/constants.ts +234 -0
  339. package/src/env/global-config-manager.ts +191 -0
  340. package/src/env/helper.ts +58 -0
  341. package/src/env/index.ts +4 -0
  342. package/src/env/init-debug.ts +34 -0
  343. package/src/env/model-config-manager.ts +149 -0
  344. package/src/env/parse-model-config.ts +294 -0
  345. package/src/env/types.ts +547 -0
  346. package/src/env/utils.ts +89 -0
  347. package/src/extractor/constants.ts +5 -0
  348. package/src/extractor/debug.ts +10 -0
  349. package/src/extractor/dom-util.ts +226 -0
  350. package/src/extractor/index.ts +48 -0
  351. package/src/extractor/locator.ts +469 -0
  352. package/src/extractor/tree.ts +179 -0
  353. package/src/extractor/util.ts +482 -0
  354. package/src/extractor/web-extractor.ts +481 -0
  355. package/src/img/box-select.ts +588 -0
  356. package/src/img/canvas-fallback.ts +393 -0
  357. package/src/img/get-photon.ts +108 -0
  358. package/src/img/get-sharp.ts +18 -0
  359. package/src/img/index.ts +26 -0
  360. package/src/img/info.ts +75 -0
  361. package/src/img/transform.ts +594 -0
  362. package/src/index.ts +8 -0
  363. package/src/logger.ts +96 -0
  364. package/src/mcp/base-server.ts +502 -0
  365. package/src/mcp/base-tools.ts +185 -0
  366. package/src/mcp/chrome-path.ts +48 -0
  367. package/src/mcp/index.ts +7 -0
  368. package/src/mcp/inject-report-html-plugin.ts +119 -0
  369. package/src/mcp/launcher-helper.ts +200 -0
  370. package/src/mcp/tool-generator.ts +429 -0
  371. package/src/mcp/types.ts +112 -0
  372. package/src/node/fs.ts +84 -0
  373. package/src/node/index.ts +2 -0
  374. package/src/node/port.ts +37 -0
  375. package/src/oss/demo.ts +61 -0
  376. package/src/oss/index.ts +187 -0
  377. package/src/polyfills/async-hooks.ts +6 -0
  378. package/src/polyfills/index.ts +4 -0
  379. package/src/types/index.ts +52 -0
  380. package/src/us-keyboard-layout.ts +723 -0
  381. package/src/utils.ts +149 -0
  382. package/src/zod-schema-utils.ts +133 -0
@@ -0,0 +1,594 @@
1
+ import assert from 'node:assert';
2
+ import { Buffer } from 'node:buffer';
3
+ import { readFileSync } from 'node:fs';
4
+ import { writeFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon-node';
7
+ import type { Rect } from 'src/types';
8
+ import { getDebug } from '../logger';
9
+ import { ifInNode } from '../utils';
10
+ import getPhoton from './get-photon';
11
+ import getSharp from './get-sharp';
12
+
13
+ const imgDebug = getDebug('img');
14
+
15
+ /**
16
+ * Saves a Base64-encoded image to a file
17
+ *
18
+ * @param options - An object containing the Base64-encoded image data and the output file path
19
+ * @param options.base64Data - The Base64-encoded image data
20
+ * @param options.outputPath - The path where the image will be saved
21
+ * @throws Error if there is an error during the saving process
22
+ */
23
+ export async function saveBase64Image(options: {
24
+ base64Data: string;
25
+ outputPath: string;
26
+ }): Promise<void> {
27
+ const { base64Data, outputPath } = options;
28
+ const { body } = parseBase64(base64Data);
29
+
30
+ const imageBuffer = Buffer.from(body, 'base64');
31
+ await writeFile(outputPath, imageBuffer);
32
+ }
33
+
34
+ /**
35
+ * Resizes an image from Buffer, maybe return a new format
36
+ * - If the image is Resized, the returned format will be jpg.
37
+ * - If the image is not Resized, it will return to its original format.
38
+ * @returns { buffer: resized buffer, format: the new format}
39
+ */
40
+ export async function resizeAndConvertImgBuffer(
41
+ inputFormat: string,
42
+ inputData: Buffer,
43
+ newSize: {
44
+ width: number;
45
+ height: number;
46
+ },
47
+ ): Promise<{
48
+ buffer: Buffer;
49
+ // jpg, png, etc.
50
+ format: string;
51
+ }> {
52
+ if (typeof inputData === 'string')
53
+ throw Error('inputData is base64, use resizeImgBase64 instead');
54
+
55
+ assert(
56
+ newSize && newSize.width > 0 && newSize.height > 0,
57
+ 'newSize must be positive',
58
+ );
59
+
60
+ const resizeStartTime = Date.now();
61
+ imgDebug(`resizeImg start, target size: ${newSize.width}x${newSize.height}`);
62
+
63
+ if (ifInNode) {
64
+ // Node.js environment: use Sharp
65
+ try {
66
+ const Sharp = await getSharp();
67
+ const metadata = await Sharp(inputData).metadata();
68
+ const { width: originalWidth, height: originalHeight } = metadata;
69
+
70
+ if (!originalWidth || !originalHeight) {
71
+ throw Error('Undefined width or height from the input image.');
72
+ }
73
+
74
+ if (
75
+ newSize.width === originalWidth &&
76
+ newSize.height === originalHeight
77
+ ) {
78
+ return {
79
+ buffer: inputData,
80
+ format: inputFormat,
81
+ };
82
+ }
83
+
84
+ const resizedBuffer = await Sharp(inputData)
85
+ .resize(newSize.width, newSize.height)
86
+ .jpeg({ quality: 90 })
87
+ .toBuffer();
88
+
89
+ const resizeEndTime = Date.now();
90
+ imgDebug(
91
+ `resizeImg done (Sharp), target size: ${newSize.width}x${newSize.height}, cost: ${resizeEndTime - resizeStartTime}ms`,
92
+ );
93
+
94
+ return {
95
+ buffer: resizedBuffer,
96
+ // by Sharp.jpeg()
97
+ format: 'jpeg',
98
+ };
99
+ } catch (error) {
100
+ imgDebug('Sharp failed, falling back to Photon:', error);
101
+ }
102
+ }
103
+
104
+ // browser environment: use Photon (or Canvas fallback)
105
+ const { PhotonImage, SamplingFilter, resize } = await getPhoton();
106
+ const inputBytes = new Uint8Array(inputData);
107
+ // Support both sync (Photon) and async (Canvas fallback) versions
108
+ const bytesliceResult = PhotonImage.new_from_byteslice(inputBytes);
109
+ const inputImage =
110
+ bytesliceResult instanceof Promise
111
+ ? await bytesliceResult
112
+ : bytesliceResult;
113
+ const originalWidth = inputImage.get_width();
114
+ const originalHeight = inputImage.get_height();
115
+
116
+ if (!originalWidth || !originalHeight) {
117
+ inputImage.free();
118
+ throw Error('Undefined width or height from the input image.');
119
+ }
120
+
121
+ if (newSize.width === originalWidth && newSize.height === originalHeight) {
122
+ inputImage.free();
123
+ return {
124
+ buffer: inputData,
125
+ format: inputFormat,
126
+ };
127
+ }
128
+
129
+ // Resize image using photon with bicubic-like sampling
130
+ const outputImage = resize(
131
+ inputImage,
132
+ newSize.width,
133
+ newSize.height,
134
+ SamplingFilter.CatmullRom,
135
+ );
136
+
137
+ const outputBytes = outputImage.get_bytes_jpeg(90);
138
+ const resizedBuffer = Buffer.from(outputBytes);
139
+
140
+ // Free memory
141
+ inputImage.free();
142
+ outputImage.free();
143
+
144
+ const resizeEndTime = Date.now();
145
+
146
+ imgDebug(
147
+ `resizeImg done (Photon), target size: ${newSize.width}x${newSize.height}, cost: ${resizeEndTime - resizeStartTime}ms`,
148
+ );
149
+
150
+ return {
151
+ buffer: resizedBuffer,
152
+ // by Photon.get_bytes_jpeg()
153
+ format: 'jpeg',
154
+ };
155
+ }
156
+
157
+ export const createImgBase64ByFormat = (format: string, body: string) => {
158
+ return `data:image/${format};base64,${body}`;
159
+ };
160
+
161
+ export async function resizeImgBase64(
162
+ inputBase64: string,
163
+ newSize: {
164
+ width: number;
165
+ height: number;
166
+ },
167
+ ): Promise<string> {
168
+ const { body, mimeType } = parseBase64(inputBase64);
169
+ const imageBuffer = Buffer.from(body, 'base64');
170
+ const { buffer, format } = await resizeAndConvertImgBuffer(
171
+ mimeType.split('/')[1],
172
+ imageBuffer,
173
+ newSize,
174
+ );
175
+ return createImgBase64ByFormat(format, buffer.toString('base64'));
176
+ }
177
+
178
+ /**
179
+ * Calculates new dimensions for an image while maintaining its aspect ratio.
180
+ *
181
+ * This function is designed to resize an image to fit within a specified maximum width and height
182
+ * while maintaining the original aspect ratio. If the original width or height exceeds the maximum
183
+ * dimensions, the image will be scaled down to fit.
184
+ *
185
+ * @param {number} originalWidth - The original width of the image.
186
+ * @param {number} originalHeight - The original height of the image.
187
+ * @returns {Object} An object containing the new width and height.
188
+ * @throws {Error} Throws an error if the width or height is not a positive number.
189
+ */
190
+ export function zoomForGPT4o(originalWidth: number, originalHeight: number) {
191
+ // In low mode, the image is scaled to 512x512 pixels and 85 tokens are used to represent the image.
192
+ // In high mode, the model looks at low-resolution images and then creates detailed crop images, using 170 tokens for each 512x512 pixel tile. In practical applications, it is recommended to control the image size within 2048x768 pixels
193
+ const maxWidth = 2048; // Maximum width
194
+ const maxHeight = 768; // Maximum height
195
+ let newWidth = originalWidth;
196
+ let newHeight = originalHeight;
197
+
198
+ // Calculate the aspect ratio
199
+ const aspectRatio = originalWidth / originalHeight;
200
+
201
+ // Width adjustment
202
+ if (originalWidth > maxWidth) {
203
+ newWidth = maxWidth;
204
+ newHeight = newWidth / aspectRatio;
205
+ }
206
+
207
+ // Adjust height
208
+ if (newHeight > maxHeight) {
209
+ newHeight = maxHeight;
210
+ newWidth = newHeight * aspectRatio;
211
+ }
212
+
213
+ return {
214
+ width: Math.round(newWidth),
215
+ height: Math.round(newHeight),
216
+ };
217
+ }
218
+
219
+ export async function photonFromBase64(
220
+ base64: string,
221
+ ): Promise<PhotonImageType> {
222
+ const { PhotonImage } = await getPhoton();
223
+ const { body } = parseBase64(base64);
224
+ // Support both sync (Photon) and async (Canvas fallback) versions
225
+ const result = PhotonImage.new_from_base64(body);
226
+ return result instanceof Promise ? await result : result;
227
+ }
228
+
229
+ // https://help.aliyun.com/zh/model-studio/user-guide/vision/
230
+ export async function paddingToMatchBlock(
231
+ image: PhotonImageType,
232
+ blockSize = 28,
233
+ ): Promise<{
234
+ width: number;
235
+ height: number;
236
+ image: PhotonImageType;
237
+ }> {
238
+ const width = image.get_width();
239
+ const height = image.get_height();
240
+
241
+ const targetWidth = Math.ceil(width / blockSize) * blockSize;
242
+ const targetHeight = Math.ceil(height / blockSize) * blockSize;
243
+
244
+ if (targetWidth === width && targetHeight === height) {
245
+ return { width, height, image };
246
+ }
247
+
248
+ const { padding_right, padding_bottom, Rgba } = await getPhoton();
249
+
250
+ const rightPadding = targetWidth - width;
251
+ const bottomPadding = targetHeight - height;
252
+
253
+ let result = image;
254
+ if (rightPadding > 0) {
255
+ // Rgba object is consumed by padding_right, so create new one for each call
256
+ const white = new Rgba(255, 255, 255, 255);
257
+ result = padding_right(result, rightPadding, white);
258
+ }
259
+ if (bottomPadding > 0) {
260
+ const white = new Rgba(255, 255, 255, 255);
261
+ const previousResult = result;
262
+ result = padding_bottom(previousResult, bottomPadding, white);
263
+ // Free intermediate PhotonImage created by padding_right, but not the original input
264
+ if (previousResult !== image) {
265
+ previousResult.free();
266
+ }
267
+ }
268
+
269
+ return { width: targetWidth, height: targetHeight, image: result };
270
+ }
271
+
272
+ export async function paddingToMatchBlockByBase64(
273
+ imageBase64: string,
274
+ blockSize = 28,
275
+ ): Promise<{
276
+ width: number;
277
+ height: number;
278
+ imageBase64: string;
279
+ }> {
280
+ const photonImage = await photonFromBase64(imageBase64);
281
+ try {
282
+ const paddedResult = await paddingToMatchBlock(photonImage, blockSize);
283
+ const result = {
284
+ width: paddedResult.width,
285
+ height: paddedResult.height,
286
+ imageBase64: await photonToBase64(paddedResult.image),
287
+ };
288
+ if (paddedResult.image !== photonImage) {
289
+ paddedResult.image.free();
290
+ }
291
+ return result;
292
+ } finally {
293
+ photonImage.free();
294
+ }
295
+ }
296
+
297
+ export async function cropByRect(
298
+ imageBase64: string,
299
+ rect: Rect,
300
+ paddingImage: boolean,
301
+ ): Promise<{
302
+ width: number;
303
+ height: number;
304
+ imageBase64: string;
305
+ }> {
306
+ const { crop } = await getPhoton();
307
+ const photonImage = await photonFromBase64(imageBase64);
308
+ const { left, top, width, height } = rect;
309
+
310
+ // Photon crop uses coordinates (x1, y1, x2, y2), not (x, y, width, height)
311
+ const cropped = crop(photonImage, left, top, left + width, top + height);
312
+ photonImage.free();
313
+
314
+ try {
315
+ if (paddingImage) {
316
+ const paddedResult = await paddingToMatchBlock(cropped);
317
+ const result = {
318
+ width: paddedResult.width,
319
+ height: paddedResult.height,
320
+ imageBase64: await photonToBase64(paddedResult.image),
321
+ };
322
+ if (paddedResult.image !== cropped) {
323
+ paddedResult.image.free();
324
+ }
325
+ return result;
326
+ }
327
+ return {
328
+ width: cropped.get_width(),
329
+ height: cropped.get_height(),
330
+ imageBase64: await photonToBase64(cropped),
331
+ };
332
+ } finally {
333
+ cropped.free();
334
+ }
335
+ }
336
+
337
+ export async function photonToBase64(
338
+ image: PhotonImageType,
339
+ quality = 90,
340
+ ): Promise<string> {
341
+ const bytes = image.get_bytes_jpeg(quality);
342
+ const base64Body = Buffer.from(bytes).toString('base64');
343
+ return `data:image/jpeg;base64,${base64Body}`;
344
+ }
345
+
346
+ export const httpImg2Base64 = async (url: string): Promise<string> => {
347
+ const response = await fetch(url);
348
+ if (!response.ok) {
349
+ throw new Error(`Failed to fetch image: ${url}`);
350
+ }
351
+ const contentType = response.headers.get('content-type');
352
+ if (!contentType) {
353
+ throw new Error(`Failed to fetch image: ${url}`);
354
+ }
355
+ assert(
356
+ contentType.startsWith('image/'),
357
+ `The url ${url} is not a image, because of content-type in header is ${contentType}.`,
358
+ );
359
+ const buffer = Buffer.from(await response.arrayBuffer());
360
+ return `data:${contentType};base64,${buffer.toString('base64')}`;
361
+ };
362
+
363
+ /**
364
+ * Convert image file to base64 string
365
+ * Because this method is synchronous, the npm package `sharp` cannot be used to detect the file type.
366
+ * TODO: convert to webp to reduce base64 size.
367
+ */
368
+ export const localImg2Base64 = (
369
+ imgPath: string,
370
+ withoutHeader = false,
371
+ ): string => {
372
+ const body = readFileSync(imgPath).toString('base64');
373
+ if (withoutHeader) {
374
+ return body;
375
+ }
376
+
377
+ // Detect image type by extname.
378
+ const type = path.extname(imgPath).slice(1);
379
+ const finalType = type === 'svg' ? 'svg+xml' : type || 'jpg';
380
+
381
+ return `data:image/${finalType};base64,${body}`;
382
+ };
383
+
384
+ /**
385
+ * PreProcess image url to ensure image is accessible to LLM.
386
+ * @param url - The url of the image, it can be a http url or a base64 string or a file path
387
+ * @param convertHttpImage2Base64 - Whether to convert http image to base64, if true, the http image will be converted to base64, otherwise, the http image will be returned as is
388
+ * @returns The base64 string of the image (when convertHttpImage2Base64 is true or url is a file path) or the http image url
389
+ */
390
+ export const preProcessImageUrl = async (
391
+ url: string,
392
+ convertHttpImage2Base64: boolean,
393
+ ) => {
394
+ if (typeof url !== 'string') {
395
+ throw new Error(
396
+ `url must be a string, but got ${url} with type ${typeof url}`,
397
+ );
398
+ }
399
+ if (url.startsWith('data:')) {
400
+ return url;
401
+ } else if (url.startsWith('http://') || url.startsWith('https://')) {
402
+ if (!convertHttpImage2Base64) {
403
+ return url;
404
+ }
405
+ return await httpImg2Base64(url);
406
+ } else {
407
+ return await localImg2Base64(url);
408
+ }
409
+ };
410
+
411
+ /**
412
+ * parse base64 string to get mimeType and body
413
+ */
414
+ export const parseBase64 = (
415
+ fullBase64String: string,
416
+ ): {
417
+ mimeType: string;
418
+ body: string;
419
+ } => {
420
+ try {
421
+ const separator = ';base64,';
422
+ const index = fullBase64String.indexOf(separator);
423
+ if (index === -1) {
424
+ throw new Error('Invalid base64 string');
425
+ }
426
+ return {
427
+ // 5 means 'data:'
428
+ mimeType: fullBase64String.slice(5, index),
429
+ body: fullBase64String.slice(index + separator.length),
430
+ };
431
+ } catch (e) {
432
+ throw new Error(
433
+ `parseBase64 fail because intput is not a valid base64 string: ${fullBase64String}`,
434
+ {
435
+ cause: e,
436
+ },
437
+ );
438
+ }
439
+ };
440
+
441
+ /**
442
+ * Convert a base64 image to JPEG with specified quality (no resize).
443
+ * If the image is already JPEG, re-encodes at the given quality.
444
+ *
445
+ * @param inputBase64 - Full data-URI base64 string (e.g. "data:image/png;base64,...")
446
+ * @param quality - JPEG quality 1-100
447
+ * @returns JPEG data-URI base64 string
448
+ */
449
+ export async function convertToJpegBase64(
450
+ inputBase64: string,
451
+ quality: number,
452
+ ): Promise<string> {
453
+ const { body } = parseBase64(inputBase64);
454
+ const buffer = Buffer.from(body, 'base64');
455
+
456
+ if (ifInNode) {
457
+ try {
458
+ const Sharp = await getSharp();
459
+ const jpegBuffer = await Sharp(buffer).jpeg({ quality }).toBuffer();
460
+ return `data:image/jpeg;base64,${jpegBuffer.toString('base64')}`;
461
+ } catch (error) {
462
+ imgDebug(
463
+ 'Sharp failed for JPEG conversion, falling back to Photon:',
464
+ error,
465
+ );
466
+ }
467
+ }
468
+
469
+ // Fallback: Photon / Canvas
470
+ const { PhotonImage } = await getPhoton();
471
+ const inputBytes = new Uint8Array(buffer);
472
+ const bytesliceResult = PhotonImage.new_from_byteslice(inputBytes);
473
+ const inputImage =
474
+ bytesliceResult instanceof Promise
475
+ ? await bytesliceResult
476
+ : bytesliceResult;
477
+ const outputBytes = inputImage.get_bytes_jpeg(quality);
478
+ inputImage.free();
479
+ return `data:image/jpeg;base64,${Buffer.from(outputBytes).toString('base64')}`;
480
+ }
481
+
482
+ /**
483
+ * Scales an image by a specified factor using Sharp or Photon
484
+ * @param imageBase64 - Base64 encoded image
485
+ * @param scale - Scale factor (e.g., 2 for 2x, 1.5 for 1.5x)
486
+ * @returns Scaled image with new dimensions
487
+ */
488
+ export async function scaleImage(
489
+ imageBase64: string,
490
+ scale: number,
491
+ ): Promise<{
492
+ width: number;
493
+ height: number;
494
+ imageBase64: string;
495
+ }> {
496
+ if (scale <= 0) {
497
+ throw new Error('Scale factor must be positive');
498
+ }
499
+
500
+ const { body } = parseBase64(imageBase64);
501
+ const buffer = Buffer.from(body, 'base64');
502
+
503
+ const scaleStartTime = Date.now();
504
+ imgDebug(`scaleImage start, scale factor: ${scale}`);
505
+
506
+ if (ifInNode) {
507
+ // Node.js environment: use Sharp
508
+ try {
509
+ const Sharp = await getSharp();
510
+ const metadata = await Sharp(buffer).metadata();
511
+ const originalWidth = metadata.width || 0;
512
+ const originalHeight = metadata.height || 0;
513
+
514
+ if (originalWidth === 0 || originalHeight === 0) {
515
+ throw new Error('Failed to get image dimensions');
516
+ }
517
+
518
+ const newWidth = Math.round(originalWidth * scale);
519
+ const newHeight = Math.round(originalHeight * scale);
520
+
521
+ const resizedBuffer = await Sharp(buffer)
522
+ .resize(newWidth, newHeight, {
523
+ kernel: 'lanczos3',
524
+ fit: 'fill',
525
+ })
526
+ .jpeg({
527
+ quality: 90,
528
+ })
529
+ .toBuffer();
530
+
531
+ const scaleEndTime = Date.now();
532
+ imgDebug(
533
+ `scaleImage done (Sharp): ${originalWidth}x${originalHeight} -> ${newWidth}x${newHeight} (scale=${scale}), cost: ${scaleEndTime - scaleStartTime}ms`,
534
+ );
535
+
536
+ const base64 = `data:image/jpeg;base64,${resizedBuffer.toString('base64')}`;
537
+
538
+ return {
539
+ width: newWidth,
540
+ height: newHeight,
541
+ imageBase64: base64,
542
+ };
543
+ } catch (error) {
544
+ imgDebug('Sharp failed, falling back to Photon:', error);
545
+ }
546
+ }
547
+
548
+ // Browser environment or Sharp failed: use Photon (or Canvas fallback)
549
+ const { PhotonImage, SamplingFilter, resize } = await getPhoton();
550
+ const inputBytes = new Uint8Array(buffer);
551
+ // Support both sync (Photon) and async (Canvas fallback) versions
552
+ const bytesliceResult = PhotonImage.new_from_byteslice(inputBytes);
553
+ const inputImage =
554
+ bytesliceResult instanceof Promise
555
+ ? await bytesliceResult
556
+ : bytesliceResult;
557
+ const originalWidth = inputImage.get_width();
558
+ const originalHeight = inputImage.get_height();
559
+
560
+ if (!originalWidth || !originalHeight) {
561
+ inputImage.free();
562
+ throw new Error('Failed to get image dimensions');
563
+ }
564
+
565
+ const newWidth = Math.round(originalWidth * scale);
566
+ const newHeight = Math.round(originalHeight * scale);
567
+
568
+ const outputImage = resize(
569
+ inputImage,
570
+ newWidth,
571
+ newHeight,
572
+ SamplingFilter.CatmullRom,
573
+ );
574
+
575
+ const outputBytes = outputImage.get_bytes_jpeg(90);
576
+ const resizedBuffer = Buffer.from(outputBytes);
577
+
578
+ // Free memory
579
+ inputImage.free();
580
+ outputImage.free();
581
+
582
+ const scaleEndTime = Date.now();
583
+ imgDebug(
584
+ `scaleImage done (Photon): ${originalWidth}x${originalHeight} -> ${newWidth}x${newHeight} (scale=${scale}), cost: ${scaleEndTime - scaleStartTime}ms`,
585
+ );
586
+
587
+ const base64 = `data:image/jpeg;base64,${resizedBuffer.toString('base64')}`;
588
+
589
+ return {
590
+ width: newWidth,
591
+ height: newHeight,
592
+ imageBase64: base64,
593
+ };
594
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export {
2
+ createCopyStaticPlugin,
3
+ createPlaygroundCopyPlugin,
4
+ } from './build/copy-static';
5
+
6
+ export { commonIgnoreWarnings } from './build/rspack-config';
7
+
8
+ export default {};
package/src/logger.ts ADDED
@@ -0,0 +1,96 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import util from 'node:util';
4
+ import debug from 'debug';
5
+ import { getMidsceneRunSubDir } from './common';
6
+ import { ifInNode } from './utils';
7
+
8
+ const topicPrefix = 'midscene';
9
+ // Map to store file streams
10
+ const logStreams = new Map<string, fs.WriteStream>();
11
+ // Map to store debug instances
12
+ const debugInstances = new Map<string, DebugFunction>();
13
+
14
+ // Function to get or create a log stream
15
+ function getLogStream(topic: string): fs.WriteStream {
16
+ const topicFileName = topic.replace(/:/g, '-');
17
+ if (!logStreams.has(topicFileName)) {
18
+ const logFile = path.join(
19
+ getMidsceneRunSubDir('log'),
20
+ `${topicFileName}.log`,
21
+ );
22
+ const stream = fs.createWriteStream(logFile, { flags: 'a' });
23
+ logStreams.set(topicFileName, stream);
24
+ }
25
+ return logStreams.get(topicFileName)!;
26
+ }
27
+
28
+ // Function to write log to file
29
+ function writeLogToFile(topic: string, message: string): void {
30
+ if (!ifInNode) return;
31
+
32
+ const stream = getLogStream(topic);
33
+ // Generate ISO format timestamp with local timezone
34
+ const now = new Date();
35
+ // Use sv-SE locale to get ISO-like format (YYYY-MM-DD HH:mm:ss)
36
+ const isoDate = now.toLocaleDateString('sv-SE'); // YYYY-MM-DD
37
+ const isoTime = now.toLocaleTimeString('sv-SE'); // HH:mm:ss
38
+ const milliseconds = now.getMilliseconds().toString().padStart(3, '0');
39
+ // Calculate timezone offset manually for correct format (+HH:mm)
40
+ const timezoneOffsetMinutes = now.getTimezoneOffset();
41
+ const sign = timezoneOffsetMinutes <= 0 ? '+' : '-';
42
+ const hours = Math.floor(Math.abs(timezoneOffsetMinutes) / 60)
43
+ .toString()
44
+ .padStart(2, '0');
45
+ const minutes = (Math.abs(timezoneOffsetMinutes) % 60)
46
+ .toString()
47
+ .padStart(2, '0');
48
+ const timezoneString = `${sign}${hours}:${minutes}`;
49
+ const localISOTime = `${isoDate}T${isoTime}.${milliseconds}${timezoneString}`;
50
+ stream.write(`[${localISOTime}] ${message}\n`);
51
+ }
52
+
53
+ export type DebugFunction = (...args: unknown[]) => void;
54
+
55
+ export function getDebug(
56
+ topic: string,
57
+ options?: { console?: boolean },
58
+ ): DebugFunction {
59
+ const fullTopic = `${topicPrefix}:${topic}`;
60
+ const withConsole = options?.console ?? false;
61
+ const cacheKey = withConsole ? `${fullTopic}:withConsole` : fullTopic;
62
+
63
+ if (!debugInstances.has(cacheKey)) {
64
+ if (withConsole) {
65
+ const baseFn = getDebug(topic);
66
+ const wrapper = (...args: unknown[]): void => {
67
+ baseFn(...args);
68
+ console.warn('[Midscene]', ...args);
69
+ };
70
+ debugInstances.set(cacheKey, wrapper);
71
+ } else {
72
+ const debugFn = debug(fullTopic) as DebugFunction;
73
+
74
+ // Create wrapper that handles both file logging and debug output
75
+ const wrapper = (...args: unknown[]): void => {
76
+ if (ifInNode) {
77
+ const message = util.format(...args);
78
+ writeLogToFile(topic, message);
79
+ }
80
+ debugFn(...args);
81
+ };
82
+
83
+ debugInstances.set(cacheKey, wrapper);
84
+ }
85
+ }
86
+
87
+ return debugInstances.get(cacheKey)!;
88
+ }
89
+
90
+ export function enableDebug(topic: string): void {
91
+ if (ifInNode) {
92
+ // In Node.js, we don't need to enable debug as we're using file logging
93
+ return;
94
+ }
95
+ debug.enable(`${topicPrefix}:${topic}`);
96
+ }