@aryacode/dsh-engineering 0.0.0-stage → 0.4.0

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 (284) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/LICENSE +21 -0
  3. package/README.md +230 -2
  4. package/cordis.patch.yml +123 -0
  5. package/lib/client.js +3448 -0
  6. package/lib/index.d.ts +2 -0
  7. package/lib/index.js +9 -0
  8. package/locale/en.json +6 -0
  9. package/locale/zh.json +6 -0
  10. package/package.json +94 -3
  11. package/vendor/ftview/README.md +55 -0
  12. package/vendor/ftview/SKILL.md +74 -0
  13. package/vendor/ftview/lib/checks.d.ts +27 -0
  14. package/vendor/ftview/lib/checks.js +39 -0
  15. package/vendor/ftview/lib/core.d.ts +208 -0
  16. package/vendor/ftview/lib/core.js +160 -0
  17. package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
  18. package/vendor/ftview/lib/edit/drafts.js +84 -0
  19. package/vendor/ftview/lib/edit/operations.d.ts +62 -0
  20. package/vendor/ftview/lib/edit/operations.js +126 -0
  21. package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
  22. package/vendor/ftview/lib/edit/prepare.js +133 -0
  23. package/vendor/ftview/lib/edit/tools.d.ts +25 -0
  24. package/vendor/ftview/lib/edit/tools.js +198 -0
  25. package/vendor/ftview/lib/edit.d.ts +30 -0
  26. package/vendor/ftview/lib/edit.js +38 -0
  27. package/vendor/ftview/lib/index.d.ts +31 -0
  28. package/vendor/ftview/lib/index.js +36 -0
  29. package/vendor/ftview/lib/model.d.ts +80 -0
  30. package/vendor/ftview/lib/model.js +267 -0
  31. package/vendor/ftview/lib/session.d.ts +48 -0
  32. package/vendor/ftview/lib/session.js +98 -0
  33. package/vendor/ftview/lib/skill.d.ts +29 -0
  34. package/vendor/ftview/lib/skill.js +43 -0
  35. package/vendor/ftview/lib/studio/discover.d.ts +36 -0
  36. package/vendor/ftview/lib/studio/discover.js +87 -0
  37. package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
  38. package/vendor/ftview/lib/studio/preflight.js +64 -0
  39. package/vendor/ftview/lib/studio/tools.d.ts +15 -0
  40. package/vendor/ftview/lib/studio/tools.js +83 -0
  41. package/vendor/ftview/lib/studio.d.ts +30 -0
  42. package/vendor/ftview/lib/studio.js +37 -0
  43. package/vendor/ftview/lib/tools.d.ts +14 -0
  44. package/vendor/ftview/lib/tools.js +202 -0
  45. package/vendor/l5x/README.md +62 -0
  46. package/vendor/l5x/SKILL.md +81 -0
  47. package/vendor/l5x/lib/approvals.d.ts +50 -0
  48. package/vendor/l5x/lib/approvals.js +93 -0
  49. package/vendor/l5x/lib/client.js +412 -0
  50. package/vendor/l5x/lib/client.js.map +1 -0
  51. package/vendor/l5x/lib/core/errors.d.ts +38 -0
  52. package/vendor/l5x/lib/core/errors.js +48 -0
  53. package/vendor/l5x/lib/core/params.d.ts +49 -0
  54. package/vendor/l5x/lib/core/params.js +39 -0
  55. package/vendor/l5x/lib/core/result.d.ts +46 -0
  56. package/vendor/l5x/lib/core/result.js +21 -0
  57. package/vendor/l5x/lib/core/tool.d.ts +102 -0
  58. package/vendor/l5x/lib/core/tool.js +19 -0
  59. package/vendor/l5x/lib/drafts.d.ts +66 -0
  60. package/vendor/l5x/lib/drafts.js +98 -0
  61. package/vendor/l5x/lib/editing.d.ts +30 -0
  62. package/vendor/l5x/lib/editing.js +42 -0
  63. package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
  64. package/vendor/l5x/lib/engine/acd-edit.js +115 -0
  65. package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
  66. package/vendor/l5x/lib/engine/analysis.js +258 -0
  67. package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
  68. package/vendor/l5x/lib/engine/capabilities.js +40 -0
  69. package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
  70. package/vendor/l5x/lib/engine/change-set.js +446 -0
  71. package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
  72. package/vendor/l5x/lib/engine/configuration.js +42 -0
  73. package/vendor/l5x/lib/engine/details.d.ts +47 -0
  74. package/vendor/l5x/lib/engine/details.js +173 -0
  75. package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
  76. package/vendor/l5x/lib/engine/direct-polling.js +193 -0
  77. package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
  78. package/vendor/l5x/lib/engine/download-review.js +63 -0
  79. package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
  80. package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
  81. package/vendor/l5x/lib/engine/observations.d.ts +41 -0
  82. package/vendor/l5x/lib/engine/observations.js +67 -0
  83. package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
  84. package/vendor/l5x/lib/engine/project-analysis.js +314 -0
  85. package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
  86. package/vendor/l5x/lib/engine/review-evidence.js +49 -0
  87. package/vendor/l5x/lib/engine/review.d.ts +18 -0
  88. package/vendor/l5x/lib/engine/review.js +123 -0
  89. package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
  90. package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
  91. package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
  92. package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
  93. package/vendor/l5x/lib/engine/usage.d.ts +16 -0
  94. package/vendor/l5x/lib/engine/usage.js +92 -0
  95. package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
  96. package/vendor/l5x/lib/engine/xml-regions.js +77 -0
  97. package/vendor/l5x/lib/fs.d.ts +17 -0
  98. package/vendor/l5x/lib/fs.js +36 -0
  99. package/vendor/l5x/lib/index.d.ts +36 -0
  100. package/vendor/l5x/lib/index.js +42 -0
  101. package/vendor/l5x/lib/live.d.ts +48 -0
  102. package/vendor/l5x/lib/live.js +48 -0
  103. package/vendor/l5x/lib/model/aois.d.ts +39 -0
  104. package/vendor/l5x/lib/model/aois.js +112 -0
  105. package/vendor/l5x/lib/model/errors.d.ts +15 -0
  106. package/vendor/l5x/lib/model/errors.js +18 -0
  107. package/vendor/l5x/lib/model/index.d.ts +20 -0
  108. package/vendor/l5x/lib/model/index.js +19 -0
  109. package/vendor/l5x/lib/model/module.d.ts +37 -0
  110. package/vendor/l5x/lib/model/module.js +54 -0
  111. package/vendor/l5x/lib/model/modules.d.ts +24 -0
  112. package/vendor/l5x/lib/model/modules.js +55 -0
  113. package/vendor/l5x/lib/model/parse.d.ts +32 -0
  114. package/vendor/l5x/lib/model/parse.js +116 -0
  115. package/vendor/l5x/lib/model/project.d.ts +15 -0
  116. package/vendor/l5x/lib/model/project.js +31 -0
  117. package/vendor/l5x/lib/model/routines.d.ts +71 -0
  118. package/vendor/l5x/lib/model/routines.js +183 -0
  119. package/vendor/l5x/lib/model/tags.d.ts +51 -0
  120. package/vendor/l5x/lib/model/tags.js +119 -0
  121. package/vendor/l5x/lib/model/types.d.ts +304 -0
  122. package/vendor/l5x/lib/model/types.js +12 -0
  123. package/vendor/l5x/lib/model/udts.d.ts +34 -0
  124. package/vendor/l5x/lib/model/udts.js +81 -0
  125. package/vendor/l5x/lib/model/xml.d.ts +139 -0
  126. package/vendor/l5x/lib/model/xml.js +291 -0
  127. package/vendor/l5x/lib/model/xref.d.ts +28 -0
  128. package/vendor/l5x/lib/model/xref.js +154 -0
  129. package/vendor/l5x/lib/session.d.ts +119 -0
  130. package/vendor/l5x/lib/session.js +236 -0
  131. package/vendor/l5x/lib/skill.d.ts +30 -0
  132. package/vendor/l5x/lib/skill.js +44 -0
  133. package/vendor/l5x/lib/studio-state.d.ts +77 -0
  134. package/vendor/l5x/lib/studio-state.js +79 -0
  135. package/vendor/l5x/lib/studio.d.ts +29 -0
  136. package/vendor/l5x/lib/studio.js +40 -0
  137. package/vendor/l5x/lib/tools/checks.d.ts +19 -0
  138. package/vendor/l5x/lib/tools/checks.js +140 -0
  139. package/vendor/l5x/lib/tools/common.d.ts +41 -0
  140. package/vendor/l5x/lib/tools/common.js +84 -0
  141. package/vendor/l5x/lib/tools/controller.d.ts +21 -0
  142. package/vendor/l5x/lib/tools/controller.js +170 -0
  143. package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
  144. package/vendor/l5x/lib/tools/drafts.js +270 -0
  145. package/vendor/l5x/lib/tools/live.d.ts +21 -0
  146. package/vendor/l5x/lib/tools/live.js +175 -0
  147. package/vendor/l5x/lib/tools/read.d.ts +19 -0
  148. package/vendor/l5x/lib/tools/read.js +245 -0
  149. package/vendor/l5x/lib/tools/references.d.ts +20 -0
  150. package/vendor/l5x/lib/tools/references.js +269 -0
  151. package/vendor/l5x/lib/tools/studio.d.ts +21 -0
  152. package/vendor/l5x/lib/tools/studio.js +193 -0
  153. package/vendor/l5x/poll-worker.mjs +77 -0
  154. package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
  155. package/vendor/l5x/studio-bridge/Program.cs +304 -0
  156. package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
  157. package/vendor/mwi/README.md +60 -0
  158. package/vendor/mwi/SKILL.md +87 -0
  159. package/vendor/mwi/bridge/README.md +8 -0
  160. package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
  161. package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
  162. package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
  163. package/vendor/mwi/bridge/start_bridge.cmd +21 -0
  164. package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
  165. package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
  166. package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
  167. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  168. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
  169. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
  170. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
  171. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
  172. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
  173. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
  174. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
  175. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
  176. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
  177. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
  178. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
  179. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
  180. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
  181. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
  182. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
  183. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
  184. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
  185. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
  186. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
  187. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
  188. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
  189. package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
  190. package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
  191. package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
  192. package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
  193. package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
  194. package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
  195. package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
  196. package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
  197. package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
  198. package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
  199. package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
  200. package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
  201. package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
  202. package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
  203. package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
  204. package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
  205. package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
  206. package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
  207. package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
  208. package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
  209. package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
  210. package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
  211. package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
  212. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
  213. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
  214. package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
  215. package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
  216. package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
  217. package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
  218. package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
  219. package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
  220. package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
  221. package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
  222. package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
  223. package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
  224. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
  225. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
  226. package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
  227. package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
  228. package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
  229. package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
  230. package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
  231. package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
  232. package/vendor/mwi/code/mw_code.py +1258 -0
  233. package/vendor/mwi/legacy/edit-session.js +50 -0
  234. package/vendor/mwi/legacy/fb-insertion.js +74 -0
  235. package/vendor/mwi/legacy/native-code.js +83 -0
  236. package/vendor/mwi/legacy/native-groups.js +75 -0
  237. package/vendor/mwi/legacy/native-structure.js +143 -0
  238. package/vendor/mwi/legacy/native-variables.js +77 -0
  239. package/vendor/mwi/legacy/pou-conversion.js +47 -0
  240. package/vendor/mwi/legacy/pou-package.js +66 -0
  241. package/vendor/mwi/lib/core/bridge.d.ts +71 -0
  242. package/vendor/mwi/lib/core/bridge.js +53 -0
  243. package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
  244. package/vendor/mwi/lib/core/code-engine.js +109 -0
  245. package/vendor/mwi/lib/core/errors.d.ts +37 -0
  246. package/vendor/mwi/lib/core/errors.js +47 -0
  247. package/vendor/mwi/lib/core/params.d.ts +52 -0
  248. package/vendor/mwi/lib/core/params.js +46 -0
  249. package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
  250. package/vendor/mwi/lib/core/ps-bridge.js +127 -0
  251. package/vendor/mwi/lib/core/result.d.ts +45 -0
  252. package/vendor/mwi/lib/core/result.js +20 -0
  253. package/vendor/mwi/lib/core/tool.d.ts +80 -0
  254. package/vendor/mwi/lib/core/tool.js +55 -0
  255. package/vendor/mwi/lib/edit/compare.d.ts +33 -0
  256. package/vendor/mwi/lib/edit/compare.js +70 -0
  257. package/vendor/mwi/lib/edit.d.ts +20 -0
  258. package/vendor/mwi/lib/edit.js +32 -0
  259. package/vendor/mwi/lib/index.d.ts +21 -0
  260. package/vendor/mwi/lib/index.js +33 -0
  261. package/vendor/mwi/lib/read/service.d.ts +117 -0
  262. package/vendor/mwi/lib/read/service.js +229 -0
  263. package/vendor/mwi/lib/read.d.ts +21 -0
  264. package/vendor/mwi/lib/read.js +33 -0
  265. package/vendor/mwi/lib/session/project.d.ts +34 -0
  266. package/vendor/mwi/lib/session/project.js +54 -0
  267. package/vendor/mwi/lib/session/service.d.ts +167 -0
  268. package/vendor/mwi/lib/session/service.js +566 -0
  269. package/vendor/mwi/lib/session/stage.d.ts +84 -0
  270. package/vendor/mwi/lib/session/stage.js +187 -0
  271. package/vendor/mwi/lib/session/workspace.d.ts +52 -0
  272. package/vendor/mwi/lib/session/workspace.js +90 -0
  273. package/vendor/mwi/lib/skill.d.ts +36 -0
  274. package/vendor/mwi/lib/skill.js +56 -0
  275. package/vendor/mwi/lib/tools/edit.d.ts +20 -0
  276. package/vendor/mwi/lib/tools/edit.js +286 -0
  277. package/vendor/mwi/lib/tools/read.d.ts +19 -0
  278. package/vendor/mwi/lib/tools/read.js +161 -0
  279. package/vendor/mwi/lib/tools/session.d.ts +20 -0
  280. package/vendor/mwi/lib/tools/session.js +166 -0
  281. package/vendor/mwi/lib/tools/verify.d.ts +19 -0
  282. package/vendor/mwi/lib/tools/verify.js +85 -0
  283. package/vendor/mwi/lib/verify.d.ts +17 -0
  284. package/vendor/mwi/lib/verify.js +29 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * FactoryTalk View ME export scanning and parsing.
3
+ *
4
+ * Only documented text exports are read: display XML and the semicolon-separated
5
+ * HMI tag CSV. Native MER/MED databases are not decoded or rewritten. A display
6
+ * contributes its root identity and its named objects; the tag CSV contributes
7
+ * typed rows, and its structural problems are reported rather than hidden.
8
+ *
9
+ * @module dsh-engineering-ftview/model
10
+ */
11
+ import { createHash } from 'node:crypto';
12
+ import { readdir } from 'node:fs/promises';
13
+ import { XMLParser } from 'fast-xml-parser';
14
+ import { EngineeringError } from "./core.js";
15
+ /** Attribute prefix the XML parser uses. */
16
+ const ATTRIBUTE = '@_';
17
+ /** Split one semicolon-separated CSV line, honouring double quotes. */
18
+ export function splitCsv(line) {
19
+ const cells = [];
20
+ let current = '';
21
+ let quoted = false;
22
+ for (let index = 0; index < line.length; index += 1) {
23
+ const character = line[index];
24
+ if (quoted) {
25
+ if (character === '"') {
26
+ if (line[index + 1] === '"') {
27
+ current += '"';
28
+ index += 1;
29
+ }
30
+ else {
31
+ quoted = false;
32
+ }
33
+ }
34
+ else {
35
+ current += character;
36
+ }
37
+ }
38
+ else if (character === '"') {
39
+ quoted = true;
40
+ }
41
+ else if (character === ';') {
42
+ cells.push(current);
43
+ current = '';
44
+ }
45
+ else {
46
+ current += character;
47
+ }
48
+ }
49
+ cells.push(current);
50
+ return cells;
51
+ }
52
+ /** Decode bytes as UTF-8, dropping a byte-order mark. */
53
+ function decode(bytes) {
54
+ const text = new TextDecoder('utf-8').decode(bytes);
55
+ return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
56
+ }
57
+ /**
58
+ * Parse the HMI tag CSV.
59
+ *
60
+ * @param text - Whole CSV file.
61
+ * @returns Rows plus structural errors; a file with no `###002` marker yields no rows.
62
+ */
63
+ export function parseTagCsv(text) {
64
+ const lines = text.split(/\r?\n/);
65
+ const versionLine = lines.findIndex(line => line.includes('###002'));
66
+ if (versionLine < 0) {
67
+ return { rows: [], errors: ['Unsupported tag CSV: the `###002` version line is missing.'] };
68
+ }
69
+ const headerIndex = lines.findIndex((line, index) => index > versionLine && line.trim() !== '');
70
+ if (headerIndex < 0) {
71
+ return { rows: [], errors: ['Unsupported tag CSV: no header row follows the version line.'] };
72
+ }
73
+ const header = splitCsv(lines[headerIndex]).map(cell => cell.trim());
74
+ const nameIndex = header.indexOf('Tag Name');
75
+ if (nameIndex < 0) {
76
+ return { rows: [], errors: ['Unsupported tag CSV: the header has no `Tag Name` column.'] };
77
+ }
78
+ const descriptionIndex = header.indexOf('Tag Description');
79
+ const typeIndex = header.indexOf('Tag Type');
80
+ const rows = [];
81
+ const errors = [];
82
+ const seen = new Set();
83
+ for (let index = headerIndex + 1; index < lines.length; index += 1) {
84
+ const line = lines[index];
85
+ if (line.trim() === '')
86
+ continue;
87
+ const cells = splitCsv(line);
88
+ if (cells.length < header.length) {
89
+ errors.push(`Row ${index + 1}: expected ${header.length} columns, found ${cells.length}.`);
90
+ continue;
91
+ }
92
+ const raw = {};
93
+ header.forEach((column, columnIndex) => {
94
+ if (column !== '')
95
+ raw[column] = (cells[columnIndex] ?? '').trim();
96
+ });
97
+ const name = (cells[nameIndex] ?? '').trim();
98
+ if (name === '') {
99
+ errors.push(`Row ${index + 1}: empty Tag Name.`);
100
+ continue;
101
+ }
102
+ if (seen.has(name))
103
+ errors.push(`Row ${index + 1}: duplicate tag name ${JSON.stringify(name)}.`);
104
+ seen.add(name);
105
+ rows.push({
106
+ name,
107
+ description: descriptionIndex >= 0 ? (cells[descriptionIndex] ?? '').trim() : '',
108
+ type: typeIndex >= 0 ? (cells[typeIndex] ?? '').trim() : '',
109
+ raw,
110
+ });
111
+ }
112
+ return { rows, errors };
113
+ }
114
+ /** The parser used for display XML, preserving attributes and order-independent names. */
115
+ const PARSER = new XMLParser({
116
+ ignoreAttributes: false,
117
+ attributeNamePrefix: ATTRIBUTE,
118
+ trimValues: true,
119
+ parseTagValue: false,
120
+ isArray: () => false,
121
+ });
122
+ /** Strip the attribute prefix from one attribute bag. */
123
+ function attributesOf(node) {
124
+ const attributes = {};
125
+ for (const [key, value] of Object.entries(node)) {
126
+ if (key.startsWith(ATTRIBUTE) && typeof value === 'string')
127
+ attributes[key.slice(ATTRIBUTE.length)] = value;
128
+ if (key.startsWith(ATTRIBUTE) && typeof value === 'number')
129
+ attributes[key.slice(ATTRIBUTE.length)] = String(value);
130
+ }
131
+ return attributes;
132
+ }
133
+ /** Collect one element and its descendants as display objects. */
134
+ function collectObjects(node, type, out, depth) {
135
+ if (depth > 8 || node === null || typeof node !== 'object' || Array.isArray(node))
136
+ return;
137
+ const record = node;
138
+ const attributes = attributesOf(record);
139
+ const named = attributes.name ?? attributes.Name;
140
+ out.push({ type, ...(named === undefined ? {} : { name: named }), attributes });
141
+ for (const [key, value] of Object.entries(record)) {
142
+ if (key.startsWith(ATTRIBUTE) || key === '#text')
143
+ continue;
144
+ if (Array.isArray(value)) {
145
+ for (const item of value)
146
+ collectObjects(item, key, out, depth + 1);
147
+ }
148
+ else {
149
+ collectObjects(value, key, out, depth + 1);
150
+ }
151
+ }
152
+ }
153
+ /**
154
+ * Parse one display XML file.
155
+ *
156
+ * @param file - Relative path inside the export folder.
157
+ * @param text - Whole XML file.
158
+ * @returns The display, or the parse error with a synthetic entry.
159
+ */
160
+ export function parseDisplay(file, text) {
161
+ const fallbackName = file.replace(/\\/gu, '/').split('/').pop()?.replace(/\.xml$/iu, '') ?? file;
162
+ try {
163
+ const parsed = PARSER.parse(text);
164
+ const rootKey = Object.keys(parsed).find(key => !key.startsWith('?'));
165
+ if (rootKey === undefined)
166
+ return { file, name: fallbackName, objects: [], parseError: 'The file has no root element.' };
167
+ const rootNode = parsed[rootKey];
168
+ if (!/display/iu.test(rootKey)) {
169
+ return { file, name: fallbackName, objects: [], parseError: `Root element ${rootKey} is not an ME display.` };
170
+ }
171
+ const root = (rootNode ?? {});
172
+ const attributes = attributesOf(root);
173
+ const objects = [];
174
+ for (const [key, value] of Object.entries(root)) {
175
+ if (key.startsWith(ATTRIBUTE) || key === '#text')
176
+ continue;
177
+ if (Array.isArray(value)) {
178
+ for (const item of value)
179
+ collectObjects(item, key, objects, 1);
180
+ }
181
+ else {
182
+ collectObjects(value, key, objects, 1);
183
+ }
184
+ }
185
+ const name = attributes.name ?? attributes.Name ?? fallbackName;
186
+ return { file, name, objects };
187
+ }
188
+ catch (error) {
189
+ return { file, name: fallbackName, objects: [], parseError: error instanceof Error ? error.message : String(error) };
190
+ }
191
+ }
192
+ /** List one directory through the provider, or Node when the provider cannot list. */
193
+ async function listDirectory(fs, path, signal) {
194
+ if (fs.listDir !== undefined)
195
+ return await fs.listDir(path, signal);
196
+ const entries = await readdir(path, { withFileTypes: true });
197
+ return entries.map(entry => ({ name: entry.name, type: entry.isDirectory() ? 'directory' : 'file' }));
198
+ }
199
+ /** Walk the export folder, calling `visit` for every regular file. */
200
+ async function walk(fs, root, relative, signal, visit, budget) {
201
+ signal?.throwIfAborted();
202
+ const path = relative === '' ? root : `${root.replace(/[\\/]$/u, '')}/${relative}`;
203
+ let entries;
204
+ try {
205
+ entries = await listDirectory(fs, path, signal);
206
+ }
207
+ catch (error) {
208
+ throw new EngineeringError('VIEW_UNREADABLE', `Cannot list ${path}: ${error instanceof Error ? error.message : String(error)}`, 'Pass an existing export folder the session can read.');
209
+ }
210
+ for (const entry of [...entries].sort((left, right) => left.name.localeCompare(right.name))) {
211
+ signal?.throwIfAborted();
212
+ const child = relative === '' ? entry.name : `${relative}/${entry.name}`;
213
+ if (entry.type === 'directory') {
214
+ await walk(fs, root, child, signal, visit, budget);
215
+ continue;
216
+ }
217
+ if (!/\.(xml|csv)$/iu.test(entry.name))
218
+ continue;
219
+ budget.count += 1;
220
+ if (budget.count > budget.max) {
221
+ throw new EngineeringError('VIEW_TOO_LARGE', `The export has more than ${budget.max} display and tag files.`, 'Point ftview_open at one application export folder, not a whole drive.');
222
+ }
223
+ await visit(child, `${root.replace(/[\\/]$/u, '')}/${child}`);
224
+ }
225
+ }
226
+ /**
227
+ * Scan one export folder.
228
+ *
229
+ * @param fs - Filesystem provider.
230
+ * @param root - Resolved export folder.
231
+ * @param signal - Cancellation.
232
+ * @param maxFiles - Largest number of display/tag files accepted.
233
+ * @returns The scanned application.
234
+ */
235
+ export async function scanApplication(fs, root, signal, maxFiles) {
236
+ const displays = [];
237
+ const tags = [];
238
+ const tagFiles = [];
239
+ const tagErrors = [];
240
+ const diagnostics = [];
241
+ const files = [];
242
+ await walk(fs, root, '', signal, async (file, absolute) => {
243
+ const bytes = await fs.readBytes(absolute, signal, 32 * 1024 * 1024);
244
+ const text = decode(bytes);
245
+ files.push({ file, bytes: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') });
246
+ if (/\.csv$/iu.test(file)) {
247
+ const csv = parseTagCsv(text);
248
+ if (csv.rows.length === 0 && csv.errors.length > 0) {
249
+ diagnostics.push(`${file}: ${csv.errors.join(' ')}`);
250
+ return;
251
+ }
252
+ tagFiles.push(file);
253
+ tags.push(...csv.rows);
254
+ for (const error of csv.errors)
255
+ tagErrors.push(`${file}: ${error}`);
256
+ return;
257
+ }
258
+ const display = parseDisplay(file, text);
259
+ if (display.parseError !== undefined) {
260
+ diagnostics.push(`${file}: ${display.parseError}`);
261
+ return;
262
+ }
263
+ displays.push(display);
264
+ }, { count: 0, max: maxFiles });
265
+ const name = root.replace(/[\\/]$/u, '').split(/[\\/]/u).pop() ?? root;
266
+ return { root, name, displays, tags, tagFiles, tagErrors, diagnostics, files };
267
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Open ME export folders for one chat.
3
+ *
4
+ * A folder is scanned once when opened; later calls read the same application
5
+ * model. Handles belong to the calling chat and are bounded, so an abandoned
6
+ * conversation does not retain export trees.
7
+ *
8
+ * @module dsh-engineering-ftview/session
9
+ */
10
+ import { type ExecContext, type FsService } from './core.ts';
11
+ import { type Application } from './model.ts';
12
+ /** One opened export folder. */
13
+ export interface OpenView {
14
+ readonly id: string;
15
+ readonly app: Application;
16
+ }
17
+ /** The scanned export store, keyed by chat. */
18
+ export declare class ViewSession {
19
+ readonly fs: FsService;
20
+ private readonly maxFiles;
21
+ private readonly maxViews;
22
+ private readonly chats;
23
+ private readonly detached;
24
+ /**
25
+ * @param fs - Filesystem provider.
26
+ * @param maxFiles - Most display and tag files one open may scan.
27
+ * @param maxViews - Most export folders one chat retains.
28
+ */
29
+ constructor(fs: FsService, maxFiles: number, maxViews?: number);
30
+ /**
31
+ * Resolve, check and scan one export folder for the calling chat.
32
+ *
33
+ * @param requestedRoot - Folder as the caller supplied it.
34
+ * @param exec - Calling context: cancellation, workspace and chat identity.
35
+ * @returns The retained view.
36
+ */
37
+ open(requestedRoot: string, exec?: ExecContext): Promise<OpenView>;
38
+ /**
39
+ * Resolve the view a call applies to.
40
+ *
41
+ * @param viewId - Explicit handle, or undefined to use the chat's only view.
42
+ * @param exec - Calling context.
43
+ * @returns The open view.
44
+ */
45
+ require(viewId: string | undefined, exec?: ExecContext): OpenView;
46
+ /** The view map for one calling chat. */
47
+ private viewsOf;
48
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Open ME export folders for one chat.
3
+ *
4
+ * A folder is scanned once when opened; later calls read the same application
5
+ * model. Handles belong to the calling chat and are bounded, so an abandoned
6
+ * conversation does not retain export trees.
7
+ *
8
+ * @module dsh-engineering-ftview/session
9
+ */
10
+ import { randomUUID } from 'node:crypto';
11
+ import { EngineeringError } from "./core.js";
12
+ import { scanApplication } from "./model.js";
13
+ /** The scanned export store, keyed by chat. */
14
+ export class ViewSession {
15
+ fs;
16
+ maxFiles;
17
+ maxViews;
18
+ chats = new WeakMap();
19
+ detached = new Map();
20
+ /**
21
+ * @param fs - Filesystem provider.
22
+ * @param maxFiles - Most display and tag files one open may scan.
23
+ * @param maxViews - Most export folders one chat retains.
24
+ */
25
+ constructor(fs, maxFiles, maxViews = 2) {
26
+ this.fs = fs;
27
+ this.maxFiles = maxFiles;
28
+ this.maxViews = maxViews;
29
+ }
30
+ /**
31
+ * Resolve, check and scan one export folder for the calling chat.
32
+ *
33
+ * @param requestedRoot - Folder as the caller supplied it.
34
+ * @param exec - Calling context: cancellation, workspace and chat identity.
35
+ * @returns The retained view.
36
+ */
37
+ async open(requestedRoot, exec) {
38
+ const root = await this.fs.resolve(requestedRoot, {
39
+ ...(exec?.cwd === undefined ? {} : { cwd: exec.cwd }),
40
+ ...(exec?.signal === undefined ? {} : { signal: exec.signal }),
41
+ });
42
+ const info = await this.fs.stat(root, exec?.signal);
43
+ if (info === undefined) {
44
+ throw new EngineeringError('VIEW_NOT_FOUND', `No export folder exists at ${requestedRoot}.`, 'Pass an absolute path or a path inside this workspace, for example "C:/Projects/Line/ME Export".');
45
+ }
46
+ if (info.type !== 'directory') {
47
+ throw new EngineeringError('INVALID_VIEW', `${requestedRoot} is not a folder.`, 'Pass the export folder that contains the display XML files and the tag CSV.');
48
+ }
49
+ const app = await scanApplication(this.fs, root, exec?.signal, this.maxFiles);
50
+ const view = { id: randomUUID(), app };
51
+ const views = this.viewsOf(exec);
52
+ views.set(view.id, view);
53
+ while (views.size > this.maxViews) {
54
+ const oldest = views.keys().next().value;
55
+ if (oldest !== undefined)
56
+ views.delete(oldest);
57
+ }
58
+ return view;
59
+ }
60
+ /**
61
+ * Resolve the view a call applies to.
62
+ *
63
+ * @param viewId - Explicit handle, or undefined to use the chat's only view.
64
+ * @param exec - Calling context.
65
+ * @returns The open view.
66
+ */
67
+ require(viewId, exec) {
68
+ const views = this.viewsOf(exec);
69
+ const ids = [...views.keys()];
70
+ if (viewId === undefined || viewId === '') {
71
+ const only = ids[0];
72
+ if (only === undefined) {
73
+ throw new EngineeringError('NO_VIEW', 'No ME export is open in this chat.', 'Call ftview_open with the export folder first.');
74
+ }
75
+ if (ids.length > 1) {
76
+ throw new EngineeringError('AMBIGUOUS_VIEW', `${ids.length} ME exports are open in this chat, so view_id is required.`, `Pass one of this chat's view ids: ${ids.join(', ')}.`, ids);
77
+ }
78
+ return views.get(only);
79
+ }
80
+ const view = views.get(viewId);
81
+ if (view === undefined) {
82
+ throw new EngineeringError('UNKNOWN_VIEW', `No ME export is open with view_id ${JSON.stringify(viewId)} in this chat.`, ids.length === 0 ? 'Call ftview_open with the export folder first.' : `Pass one of this chat's view ids: ${ids.join(', ')}.`, ids);
83
+ }
84
+ return view;
85
+ }
86
+ /** The view map for one calling chat. */
87
+ viewsOf(exec) {
88
+ const chat = exec?.chat;
89
+ if (chat === undefined)
90
+ return this.detached;
91
+ const existing = this.chats.get(chat);
92
+ if (existing !== undefined)
93
+ return existing;
94
+ const created = new Map();
95
+ this.chats.set(chat, created);
96
+ return created;
97
+ }
98
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The guidance half of the FTView port.
3
+ *
4
+ * Registers the `factorytalk-view` skill from SKILL.md. It is a separate loader
5
+ * entry, so a profile can run the tools without the guidance or the guidance
6
+ * without the tools.
7
+ *
8
+ * @module dsh-engineering-ftview/skill
9
+ */
10
+ /** Plugin name in a composition. */
11
+ export declare const name = "ftview-read-skill";
12
+ /** Services this plugin cannot register without. */
13
+ export declare const inject: readonly ["skills"];
14
+ /** The plugin context this module needs. */
15
+ export interface SkillContext {
16
+ get(name: string): unknown;
17
+ }
18
+ /** Registration metadata, kept here so a test can assert it without the file. */
19
+ export declare const SKILL: {
20
+ readonly name: "factorytalk-view";
21
+ readonly description: "Design and support FactoryTalk View ME and PanelView Plus applications from documented text exports: displays, HMI tags, structural checks and export comparison.";
22
+ readonly whenToUse: "Use when the user asks about FactoryTalk View ME, PanelView Plus, ME display exports, HMI tags, or comparing ME exports. Route FactoryTalk View SE and Optix to their own surfaces.";
23
+ };
24
+ /**
25
+ * Register the FactoryTalk View skill.
26
+ *
27
+ * @param ctx - Plugin context whose skill registry receives the registration.
28
+ */
29
+ export declare function apply(ctx: SkillContext): void;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The guidance half of the FTView port.
3
+ *
4
+ * Registers the `factorytalk-view` skill from SKILL.md. It is a separate loader
5
+ * entry, so a profile can run the tools without the guidance or the guidance
6
+ * without the tools.
7
+ *
8
+ * @module dsh-engineering-ftview/skill
9
+ */
10
+ import { readFileSync } from 'node:fs';
11
+ import { fileURLToPath } from 'node:url';
12
+ /** Plugin name in a composition. */
13
+ export const name = 'ftview-read-skill';
14
+ /** Services this plugin cannot register without. */
15
+ export const inject = ['skills'];
16
+ /** Registration metadata, kept here so a test can assert it without the file. */
17
+ export const SKILL = {
18
+ name: 'factorytalk-view',
19
+ description: 'Design and support FactoryTalk View ME and PanelView Plus applications from documented text exports: displays, HMI tags, structural checks and export comparison.',
20
+ whenToUse: 'Use when the user asks about FactoryTalk View ME, PanelView Plus, ME display exports, HMI tags, or comparing ME exports. Route FactoryTalk View SE and Optix to their own surfaces.',
21
+ };
22
+ /**
23
+ * Register the FactoryTalk View skill.
24
+ *
25
+ * @param ctx - Plugin context whose skill registry receives the registration.
26
+ */
27
+ export function apply(ctx) {
28
+ const skills = ctx.get('skills');
29
+ if (skills === undefined || typeof skills.register !== 'function') {
30
+ throw new Error('ftview-read-skill: the skills service is unavailable, so the factorytalk-view guidance cannot register');
31
+ }
32
+ const skillPath = fileURLToPath(new URL('../SKILL.md', import.meta.url));
33
+ const raw = readFileSync(skillPath, 'utf8');
34
+ const content = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/u, '');
35
+ skills.register({
36
+ ...SKILL,
37
+ content,
38
+ path: skillPath,
39
+ source: 'custom',
40
+ provider: '@aryacode/dsh-engineering-ftview',
41
+ invocation: { modelInvocable: true, userInvocable: true },
42
+ });
43
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Bounded discovery of FactoryTalk View Studio ME tooling.
3
+ *
4
+ * Reads directories through the filesystem provider, never executes anything.
5
+ * A missing install is reported as such; absence of a utility is not an error,
6
+ * because the manual Studio workflow does not need it.
7
+ *
8
+ * @module dsh-engineering-ftview/studio/discover
9
+ */
10
+ import type { FsService } from '../core.ts';
11
+ /** One executable the discovery found. */
12
+ export interface DiscoveredUtility {
13
+ readonly path: string;
14
+ readonly bytes: number;
15
+ readonly sha256: string;
16
+ }
17
+ /** What the discovery found. */
18
+ export interface StudioDiscovery {
19
+ readonly searched: readonly string[];
20
+ readonly products: readonly string[];
21
+ readonly schemas: readonly string[];
22
+ readonly utilities: readonly DiscoveredUtility[];
23
+ readonly help: readonly string[];
24
+ readonly truncated: boolean;
25
+ }
26
+ /** Known install roots for FactoryTalk View on Windows. */
27
+ export declare const DEFAULT_SEARCH_ROOTS: readonly string[];
28
+ /**
29
+ * Discover View Studio tooling under the configured roots.
30
+ *
31
+ * @param fs - Filesystem provider.
32
+ * @param roots - Absolute roots to search.
33
+ * @param signal - Cancellation.
34
+ * @returns What was found, plus the roots that were searched.
35
+ */
36
+ export declare function discoverStudio(fs: FsService, roots: readonly string[], signal: AbortSignal | undefined): Promise<StudioDiscovery>;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Bounded discovery of FactoryTalk View Studio ME tooling.
3
+ *
4
+ * Reads directories through the filesystem provider, never executes anything.
5
+ * A missing install is reported as such; absence of a utility is not an error,
6
+ * because the manual Studio workflow does not need it.
7
+ *
8
+ * @module dsh-engineering-ftview/studio/discover
9
+ */
10
+ import { createHash } from 'node:crypto';
11
+ /** Directory depth the discovery walks below each root. */
12
+ const MAX_DEPTH = 4;
13
+ /** Directory entries the discovery visits per root before it stops. */
14
+ const MAX_ENTRIES_PER_ROOT = 4000;
15
+ /** Known install roots for FactoryTalk View on Windows. */
16
+ export const DEFAULT_SEARCH_ROOTS = [
17
+ 'C:/Program Files (x86)/Rockwell Software',
18
+ 'C:/Program Files/Rockwell Software',
19
+ ];
20
+ /** Walk one root and collect products, schemas, utilities and help files. */
21
+ async function walkRoot(fs, root, signal, found) {
22
+ const queue = [{ path: root, depth: 0 }];
23
+ let visited = 0;
24
+ while (queue.length > 0) {
25
+ signal?.throwIfAborted();
26
+ const current = queue.shift();
27
+ let entries;
28
+ try {
29
+ entries = fs.listDir === undefined ? [] : await fs.listDir(current.path, signal);
30
+ }
31
+ catch {
32
+ continue;
33
+ }
34
+ for (const entry of entries) {
35
+ visited += 1;
36
+ if (visited > MAX_ENTRIES_PER_ROOT) {
37
+ found.truncated = true;
38
+ return;
39
+ }
40
+ const path = `${current.path}/${entry.name}`;
41
+ if (entry.type === 'directory') {
42
+ if (/FactoryTalk View/i.test(entry.name))
43
+ found.products.push(path);
44
+ if (current.depth < MAX_DEPTH)
45
+ queue.push({ path, depth: current.depth + 1 });
46
+ continue;
47
+ }
48
+ if (/^SEAddComponent\.exe$/iu.test(entry.name)) {
49
+ try {
50
+ const bytes = await fs.readBytes(path, signal, 64 * 1024 * 1024);
51
+ found.utilities.push({ path, bytes: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') });
52
+ }
53
+ catch {
54
+ found.utilities.push({ path, bytes: 0, sha256: '' });
55
+ }
56
+ continue;
57
+ }
58
+ if (/^Gfx-ME.*\.xsd$/iu.test(entry.name))
59
+ found.schemas.push(path);
60
+ if (/\.chm$/iu.test(entry.name))
61
+ found.help.push(path);
62
+ }
63
+ }
64
+ }
65
+ /**
66
+ * Discover View Studio tooling under the configured roots.
67
+ *
68
+ * @param fs - Filesystem provider.
69
+ * @param roots - Absolute roots to search.
70
+ * @param signal - Cancellation.
71
+ * @returns What was found, plus the roots that were searched.
72
+ */
73
+ export async function discoverStudio(fs, roots, signal) {
74
+ const found = { products: [], schemas: [], utilities: [], help: [], truncated: false };
75
+ const searched = [];
76
+ for (const root of roots) {
77
+ const resolved = await fs.resolve(root, {
78
+ ...(signal === undefined ? {} : { signal }),
79
+ });
80
+ const info = await fs.stat(resolved, signal);
81
+ searched.push(resolved);
82
+ if (info === undefined || info.type !== 'directory')
83
+ continue;
84
+ await walkRoot(fs, resolved, signal, found);
85
+ }
86
+ return { searched, ...found };
87
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Preflight and handoff for a manual FactoryTalk View Studio import.
3
+ *
4
+ * This entry never runs a vendor utility. Preflight reports what a Studio
5
+ * import would touch and which prerequisites are missing; handoff returns the
6
+ * ordered manual steps. Automation through `SEAddComponent.exe` is deliberately
7
+ * absent until it can be validated in a licensed environment.
8
+ *
9
+ * @module dsh-engineering-ftview/studio/preflight
10
+ */
11
+ import type { Application } from '../model.ts';
12
+ import type { StudioDiscovery } from './discover.ts';
13
+ /** The result of checking one change set for Studio. */
14
+ export interface StudioCheck {
15
+ readonly changed: readonly string[];
16
+ readonly blockers: readonly string[];
17
+ readonly warnings: readonly string[];
18
+ readonly ready: boolean;
19
+ readonly utility_available: boolean;
20
+ }
21
+ /** One manual step. */
22
+ export interface HandoffStep {
23
+ readonly step: number;
24
+ readonly action: string;
25
+ readonly files?: readonly string[];
26
+ }
27
+ /** The manual plan. */
28
+ export interface HandoffPlan {
29
+ readonly application: string;
30
+ readonly changed: readonly string[];
31
+ readonly steps: readonly HandoffStep[];
32
+ readonly utility_available: boolean;
33
+ }
34
+ /**
35
+ * Compare two exports and check whether the changed displays can be imported.
36
+ *
37
+ * @param before - The earlier export.
38
+ * @param after - The edited export.
39
+ * @param discovery - Studio tooling that was found.
40
+ * @returns Changed files, blockers, warnings and readiness.
41
+ */
42
+ export declare function studioPreflight(before: Application, after: Application, discovery: StudioDiscovery): StudioCheck;
43
+ /**
44
+ * Produce the ordered manual import plan.
45
+ *
46
+ * @param before - The earlier export.
47
+ * @param after - The edited export.
48
+ * @param discovery - Studio tooling that was found.
49
+ * @returns The plan.
50
+ */
51
+ export declare function handoffPlan(before: Application, after: Application, discovery: StudioDiscovery): HandoffPlan;