@thinkwise/testwise 0.2.67 → 0.2.99

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 (434) hide show
  1. package/Testwise.ts +35 -0
  2. package/artifact-builder/ArtifactManager.ts +25 -2
  3. package/artifact-builder/ComponentClassModelBuilder.ts +148 -0
  4. package/artifact-builder/InterfaceGenerator.ts +31 -33
  5. package/artifact-builder/ModelDataBuilder.ts +18 -20
  6. package/artifact-builder/ModelDataRefiner.ts +23 -3
  7. package/artifact-builder/SchemaGenerator.ts +45 -35
  8. package/artifact-builder/ScreenInterfaceRefiner.ts +25 -10
  9. package/artifact-builder/SelectorBuilder.ts +30 -24
  10. package/artifact-builder/SubjectClassModelBuilder.ts +209 -0
  11. package/artifact-builder/SubjectComponentGenerator.ts +67 -246
  12. package/artifact-builder/SubjectGenerator.ts +117 -230
  13. package/artifact-builder/SubjectRegistration.ts +41 -32
  14. package/artifact-builder/TsFileModel.ts +94 -0
  15. package/artifact-builder/TsFileSerializer.ts +247 -0
  16. package/artifact-builder/helpers/DataRetriever.ts +64 -27
  17. package/artifact-builder/helpers/NamingHandler.ts +23 -78
  18. package/artifact-builder/helpers/PipelinePaths.ts +123 -0
  19. package/artifact-builder/helpers/Stopwatch.ts +10 -0
  20. package/artifact-builder/helpers/index.ts +1 -0
  21. package/artifact-builder/index.ts +4 -0
  22. package/components/BaseComponentObjects.ts +17 -0
  23. package/components/action-bar/ActionBar.ts +35 -47
  24. package/components/action-bar/ActionBarObjects.ts +2 -0
  25. package/components/action-bar/CustomActionBar.ts +6 -0
  26. package/components/cardlist/Cardlist.ts +6 -0
  27. package/components/chart/Chart.ts +6 -0
  28. package/components/combined-filter/CombinedFilter.ts +9 -0
  29. package/components/cube-view-bar/CubeViewBar.ts +6 -0
  30. package/components/export/ExportComponent.ts +12 -0
  31. package/components/filter/ExcelStyleFilterPopup.ts +35 -2
  32. package/components/filter/FilterForm.ts +10 -0
  33. package/components/filter/FilterPopup.ts +30 -9
  34. package/components/filter/FilterPopupObjects.ts +10 -3
  35. package/components/filter/FindForm.ts +7 -0
  36. package/components/filter/ManagePrefilterPopup.ts +11 -0
  37. package/components/form/Form.ts +16 -0
  38. package/components/grid/Grid.ts +18 -1
  39. package/components/maps/Maps.ts +6 -0
  40. package/components/menu/ListBarMenuComponent.ts +10 -0
  41. package/components/pivot-grid/PivotGrid.ts +6 -0
  42. package/components/pivot-grid/PivotGridFieldList.ts +7 -0
  43. package/components/pop-up/AllRowsPopUp.ts +12 -0
  44. package/components/pop-up/PopUpComponent.ts +11 -0
  45. package/components/prefilter-list/PrefilterList.ts +9 -0
  46. package/components/preview/Preview.ts +6 -0
  47. package/components/report-bar/ReportBar.ts +6 -0
  48. package/components/report-tiles/ReportTiles.ts +6 -0
  49. package/components/scheduler/Scheduler.ts +6 -0
  50. package/components/snackbar/SnackBar.ts +12 -0
  51. package/components/splitter/Splitter.ts +6 -0
  52. package/components/tab/BaseTab.ts +5 -0
  53. package/components/tab/DetailTabPage.ts +11 -0
  54. package/components/tab/Tab.ts +10 -0
  55. package/components/task/TaskBar.ts +13 -3
  56. package/components/task/TaskTiles.ts +12 -3
  57. package/components/topbar/TopBar.ts +10 -0
  58. package/components/tree-view/TreeView.ts +9 -0
  59. package/controls/DateControl.ts +40 -1
  60. package/controls/FormComboBox.ts +30 -0
  61. package/controls/GridColumnControl.ts +34 -3
  62. package/controls/InputFieldControl.ts +49 -0
  63. package/controls/LookupDropdown.ts +29 -0
  64. package/dist/Testwise.js +35 -0
  65. package/dist/Testwise.js.map +1 -1
  66. package/dist/artifact-builder/ArtifactManager.d.ts +18 -1
  67. package/dist/artifact-builder/ArtifactManager.js +24 -2
  68. package/dist/artifact-builder/ArtifactManager.js.map +1 -1
  69. package/dist/artifact-builder/ComponentClassModelBuilder.d.ts +22 -0
  70. package/dist/artifact-builder/ComponentClassModelBuilder.js +115 -0
  71. package/dist/artifact-builder/ComponentClassModelBuilder.js.map +1 -0
  72. package/dist/artifact-builder/InterfaceGenerator.d.ts +15 -6
  73. package/dist/artifact-builder/InterfaceGenerator.js +27 -26
  74. package/dist/artifact-builder/InterfaceGenerator.js.map +1 -1
  75. package/dist/artifact-builder/ModelDataBuilder.d.ts +7 -0
  76. package/dist/artifact-builder/ModelDataBuilder.js +13 -5
  77. package/dist/artifact-builder/ModelDataBuilder.js.map +1 -1
  78. package/dist/artifact-builder/ModelDataRefiner.d.ts +18 -1
  79. package/dist/artifact-builder/ModelDataRefiner.js +18 -3
  80. package/dist/artifact-builder/ModelDataRefiner.js.map +1 -1
  81. package/dist/artifact-builder/SchemaGenerator.d.ts +21 -5
  82. package/dist/artifact-builder/SchemaGenerator.js +38 -26
  83. package/dist/artifact-builder/SchemaGenerator.js.map +1 -1
  84. package/dist/artifact-builder/ScreenInterfaceRefiner.d.ts +18 -1
  85. package/dist/artifact-builder/ScreenInterfaceRefiner.js +23 -9
  86. package/dist/artifact-builder/ScreenInterfaceRefiner.js.map +1 -1
  87. package/dist/artifact-builder/SelectorBuilder.d.ts +18 -5
  88. package/dist/artifact-builder/SelectorBuilder.js +29 -14
  89. package/dist/artifact-builder/SelectorBuilder.js.map +1 -1
  90. package/dist/artifact-builder/SubjectClassModelBuilder.d.ts +38 -0
  91. package/dist/artifact-builder/SubjectClassModelBuilder.js +158 -0
  92. package/dist/artifact-builder/SubjectClassModelBuilder.js.map +1 -0
  93. package/dist/artifact-builder/SubjectComponentGenerator.d.ts +25 -14
  94. package/dist/artifact-builder/SubjectComponentGenerator.js +65 -189
  95. package/dist/artifact-builder/SubjectComponentGenerator.js.map +1 -1
  96. package/dist/artifact-builder/SubjectGenerator.d.ts +32 -13
  97. package/dist/artifact-builder/SubjectGenerator.js +81 -165
  98. package/dist/artifact-builder/SubjectGenerator.js.map +1 -1
  99. package/dist/artifact-builder/SubjectRegistration.d.ts +22 -8
  100. package/dist/artifact-builder/SubjectRegistration.js +29 -14
  101. package/dist/artifact-builder/SubjectRegistration.js.map +1 -1
  102. package/dist/artifact-builder/TsFileModel.d.ts +86 -0
  103. package/dist/artifact-builder/TsFileModel.js +2 -0
  104. package/dist/artifact-builder/TsFileModel.js.map +1 -0
  105. package/dist/artifact-builder/TsFileSerializer.d.ts +19 -0
  106. package/dist/artifact-builder/TsFileSerializer.js +225 -0
  107. package/dist/artifact-builder/TsFileSerializer.js.map +1 -0
  108. package/dist/artifact-builder/helpers/DataRetriever.d.ts +22 -3
  109. package/dist/artifact-builder/helpers/DataRetriever.js +56 -26
  110. package/dist/artifact-builder/helpers/DataRetriever.js.map +1 -1
  111. package/dist/artifact-builder/helpers/NamingHandler.d.ts +18 -6
  112. package/dist/artifact-builder/helpers/NamingHandler.js +20 -61
  113. package/dist/artifact-builder/helpers/NamingHandler.js.map +1 -1
  114. package/dist/artifact-builder/helpers/PipelinePaths.d.ts +52 -0
  115. package/dist/artifact-builder/helpers/PipelinePaths.js +99 -0
  116. package/dist/artifact-builder/helpers/PipelinePaths.js.map +1 -0
  117. package/dist/artifact-builder/helpers/Stopwatch.d.ts +10 -0
  118. package/dist/artifact-builder/helpers/Stopwatch.js +10 -0
  119. package/dist/artifact-builder/helpers/Stopwatch.js.map +1 -1
  120. package/dist/artifact-builder/helpers/index.d.ts +1 -0
  121. package/dist/artifact-builder/helpers/index.js +1 -0
  122. package/dist/artifact-builder/helpers/index.js.map +1 -1
  123. package/dist/artifact-builder/index.d.ts +4 -0
  124. package/dist/artifact-builder/index.js +4 -0
  125. package/dist/artifact-builder/index.js.map +1 -1
  126. package/dist/components/BaseComponentObjects.d.ts +17 -0
  127. package/dist/components/BaseComponentObjects.js +17 -0
  128. package/dist/components/BaseComponentObjects.js.map +1 -1
  129. package/dist/components/action-bar/ActionBar.d.ts +21 -24
  130. package/dist/components/action-bar/ActionBar.js +31 -24
  131. package/dist/components/action-bar/ActionBar.js.map +1 -1
  132. package/dist/components/action-bar/ActionBarObjects.d.ts +1 -0
  133. package/dist/components/action-bar/ActionBarObjects.js +1 -0
  134. package/dist/components/action-bar/ActionBarObjects.js.map +1 -1
  135. package/dist/components/action-bar/CustomActionBar.d.ts +6 -0
  136. package/dist/components/action-bar/CustomActionBar.js +6 -0
  137. package/dist/components/action-bar/CustomActionBar.js.map +1 -1
  138. package/dist/components/cardlist/Cardlist.d.ts +6 -0
  139. package/dist/components/cardlist/Cardlist.js +6 -0
  140. package/dist/components/cardlist/Cardlist.js.map +1 -1
  141. package/dist/components/chart/Chart.d.ts +6 -0
  142. package/dist/components/chart/Chart.js +6 -0
  143. package/dist/components/chart/Chart.js.map +1 -1
  144. package/dist/components/combined-filter/CombinedFilter.d.ts +9 -0
  145. package/dist/components/combined-filter/CombinedFilter.js +9 -0
  146. package/dist/components/combined-filter/CombinedFilter.js.map +1 -1
  147. package/dist/components/cube-view-bar/CubeViewBar.d.ts +6 -0
  148. package/dist/components/cube-view-bar/CubeViewBar.js +6 -0
  149. package/dist/components/cube-view-bar/CubeViewBar.js.map +1 -1
  150. package/dist/components/export/ExportComponent.d.ts +12 -0
  151. package/dist/components/export/ExportComponent.js +12 -0
  152. package/dist/components/export/ExportComponent.js.map +1 -1
  153. package/dist/components/filter/ExcelStyleFilterPopup.d.ts +17 -4
  154. package/dist/components/filter/ExcelStyleFilterPopup.js +35 -23
  155. package/dist/components/filter/ExcelStyleFilterPopup.js.map +1 -1
  156. package/dist/components/filter/ExcelStyleFilterPopupObjects.d.ts +1 -1
  157. package/dist/components/filter/ExcelStyleFilterPopupObjects.js +2 -2
  158. package/dist/components/filter/ExcelStyleFilterPopupObjects.js.map +1 -1
  159. package/dist/components/filter/FilterForm.d.ts +10 -0
  160. package/dist/components/filter/FilterForm.js +10 -0
  161. package/dist/components/filter/FilterForm.js.map +1 -1
  162. package/dist/components/filter/FilterPopup.d.ts +22 -4
  163. package/dist/components/filter/FilterPopup.js +17 -9
  164. package/dist/components/filter/FilterPopup.js.map +1 -1
  165. package/dist/components/filter/FilterPopupObjects.d.ts +5 -2
  166. package/dist/components/filter/FilterPopupObjects.js +5 -2
  167. package/dist/components/filter/FilterPopupObjects.js.map +1 -1
  168. package/dist/components/filter/FindForm.d.ts +7 -0
  169. package/dist/components/filter/FindForm.js +7 -0
  170. package/dist/components/filter/FindForm.js.map +1 -1
  171. package/dist/components/filter/ManagePrefilterPopup.d.ts +11 -0
  172. package/dist/components/filter/ManagePrefilterPopup.js +11 -0
  173. package/dist/components/filter/ManagePrefilterPopup.js.map +1 -1
  174. package/dist/components/form/Form.d.ts +16 -0
  175. package/dist/components/form/Form.js +16 -0
  176. package/dist/components/form/Form.js.map +1 -1
  177. package/dist/components/grid/Grid.d.ts +17 -0
  178. package/dist/components/grid/Grid.js +18 -1
  179. package/dist/components/grid/Grid.js.map +1 -1
  180. package/dist/components/maps/Maps.d.ts +6 -0
  181. package/dist/components/maps/Maps.js +6 -0
  182. package/dist/components/maps/Maps.js.map +1 -1
  183. package/dist/components/menu/ListBarMenuComponent.d.ts +10 -0
  184. package/dist/components/menu/ListBarMenuComponent.js +10 -0
  185. package/dist/components/menu/ListBarMenuComponent.js.map +1 -1
  186. package/dist/components/pivot-grid/PivotGrid.d.ts +6 -0
  187. package/dist/components/pivot-grid/PivotGrid.js +6 -0
  188. package/dist/components/pivot-grid/PivotGrid.js.map +1 -1
  189. package/dist/components/pivot-grid/PivotGridFieldList.d.ts +7 -0
  190. package/dist/components/pivot-grid/PivotGridFieldList.js +7 -0
  191. package/dist/components/pivot-grid/PivotGridFieldList.js.map +1 -1
  192. package/dist/components/pop-up/AllRowsPopUp.d.ts +12 -0
  193. package/dist/components/pop-up/AllRowsPopUp.js +12 -0
  194. package/dist/components/pop-up/AllRowsPopUp.js.map +1 -1
  195. package/dist/components/pop-up/PopUpComponent.d.ts +11 -0
  196. package/dist/components/pop-up/PopUpComponent.js +11 -0
  197. package/dist/components/pop-up/PopUpComponent.js.map +1 -1
  198. package/dist/components/prefilter-list/PrefilterList.d.ts +9 -0
  199. package/dist/components/prefilter-list/PrefilterList.js +9 -0
  200. package/dist/components/prefilter-list/PrefilterList.js.map +1 -1
  201. package/dist/components/preview/Preview.d.ts +6 -0
  202. package/dist/components/preview/Preview.js +6 -0
  203. package/dist/components/preview/Preview.js.map +1 -1
  204. package/dist/components/report-bar/ReportBar.d.ts +6 -0
  205. package/dist/components/report-bar/ReportBar.js +6 -0
  206. package/dist/components/report-bar/ReportBar.js.map +1 -1
  207. package/dist/components/report-tiles/ReportTiles.d.ts +6 -0
  208. package/dist/components/report-tiles/ReportTiles.js +6 -0
  209. package/dist/components/report-tiles/ReportTiles.js.map +1 -1
  210. package/dist/components/scheduler/Scheduler.d.ts +6 -0
  211. package/dist/components/scheduler/Scheduler.js +6 -0
  212. package/dist/components/scheduler/Scheduler.js.map +1 -1
  213. package/dist/components/snackbar/SnackBar.d.ts +12 -0
  214. package/dist/components/snackbar/SnackBar.js +12 -0
  215. package/dist/components/snackbar/SnackBar.js.map +1 -1
  216. package/dist/components/splitter/Splitter.d.ts +6 -0
  217. package/dist/components/splitter/Splitter.js +6 -0
  218. package/dist/components/splitter/Splitter.js.map +1 -1
  219. package/dist/components/tab/BaseTab.d.ts +5 -0
  220. package/dist/components/tab/BaseTab.js +5 -0
  221. package/dist/components/tab/BaseTab.js.map +1 -1
  222. package/dist/components/tab/DetailTabPage.d.ts +11 -0
  223. package/dist/components/tab/DetailTabPage.js +11 -0
  224. package/dist/components/tab/DetailTabPage.js.map +1 -1
  225. package/dist/components/tab/Tab.d.ts +10 -0
  226. package/dist/components/tab/Tab.js +10 -0
  227. package/dist/components/tab/Tab.js.map +1 -1
  228. package/dist/components/task/TaskBar.d.ts +12 -2
  229. package/dist/components/task/TaskBar.js +13 -3
  230. package/dist/components/task/TaskBar.js.map +1 -1
  231. package/dist/components/task/TaskTiles.d.ts +11 -2
  232. package/dist/components/task/TaskTiles.js +12 -3
  233. package/dist/components/task/TaskTiles.js.map +1 -1
  234. package/dist/components/topbar/TopBar.d.ts +10 -0
  235. package/dist/components/topbar/TopBar.js +10 -0
  236. package/dist/components/topbar/TopBar.js.map +1 -1
  237. package/dist/components/tree-view/TreeView.d.ts +9 -0
  238. package/dist/components/tree-view/TreeView.js +9 -0
  239. package/dist/components/tree-view/TreeView.js.map +1 -1
  240. package/dist/controls/DateControl.d.ts +39 -0
  241. package/dist/controls/DateControl.js +18 -1
  242. package/dist/controls/DateControl.js.map +1 -1
  243. package/dist/controls/FormComboBox.d.ts +30 -0
  244. package/dist/controls/FormComboBox.js +8 -0
  245. package/dist/controls/FormComboBox.js.map +1 -1
  246. package/dist/controls/GridColumnControl.d.ts +30 -0
  247. package/dist/controls/GridColumnControl.js +12 -3
  248. package/dist/controls/GridColumnControl.js.map +1 -1
  249. package/dist/controls/InputFieldControl.d.ts +49 -0
  250. package/dist/controls/InputFieldControl.js +30 -0
  251. package/dist/controls/InputFieldControl.js.map +1 -1
  252. package/dist/controls/LookupDropdown.d.ts +29 -0
  253. package/dist/controls/LookupDropdown.js +8 -0
  254. package/dist/controls/LookupDropdown.js.map +1 -1
  255. package/dist/enums/ElementTypes.d.ts +18 -0
  256. package/dist/enums/ElementTypes.js +18 -0
  257. package/dist/enums/ElementTypes.js.map +1 -1
  258. package/dist/enums/ExportFormat.d.ts +8 -0
  259. package/dist/enums/ExportFormat.js +8 -0
  260. package/dist/enums/ExportFormat.js.map +1 -1
  261. package/dist/enums/LogLevel.d.ts +17 -0
  262. package/dist/enums/LogLevel.js +17 -1
  263. package/dist/enums/LogLevel.js.map +1 -1
  264. package/dist/enums/index.d.ts +1 -0
  265. package/dist/enums/index.js +1 -0
  266. package/dist/enums/index.js.map +1 -1
  267. package/dist/helpers/ConfigChecker.d.ts +13 -0
  268. package/dist/helpers/ConfigChecker.js +13 -0
  269. package/dist/helpers/ConfigChecker.js.map +1 -1
  270. package/dist/helpers/Ensure.d.ts +13 -0
  271. package/dist/helpers/Ensure.js +13 -0
  272. package/dist/helpers/Ensure.js.map +1 -1
  273. package/dist/helpers/FlightTracker.d.ts +13 -3
  274. package/dist/helpers/FlightTracker.js +103 -17
  275. package/dist/helpers/FlightTracker.js.map +1 -1
  276. package/dist/helpers/GlobalWaitEventHandler.d.ts +13 -0
  277. package/dist/helpers/GlobalWaitEventHandler.js +13 -0
  278. package/dist/helpers/GlobalWaitEventHandler.js.map +1 -1
  279. package/dist/helpers/LoginHelper.d.ts +35 -1
  280. package/dist/helpers/LoginHelper.js +46 -11
  281. package/dist/helpers/LoginHelper.js.map +1 -1
  282. package/dist/helpers/PathResolver.d.ts +22 -0
  283. package/dist/helpers/PathResolver.js +22 -0
  284. package/dist/helpers/PathResolver.js.map +1 -1
  285. package/dist/helpers/Poll.d.ts +13 -1
  286. package/dist/helpers/Poll.js +12 -0
  287. package/dist/helpers/Poll.js.map +1 -1
  288. package/dist/helpers/RegexHelper.d.ts +15 -0
  289. package/dist/helpers/RegexHelper.js +15 -3
  290. package/dist/helpers/RegexHelper.js.map +1 -1
  291. package/dist/helpers/SubjectNavigator.d.ts +26 -0
  292. package/dist/helpers/SubjectNavigator.js +51 -0
  293. package/dist/helpers/SubjectNavigator.js.map +1 -0
  294. package/dist/helpers/UserSimulationHelper.d.ts +14 -0
  295. package/dist/helpers/UserSimulationHelper.js +14 -0
  296. package/dist/helpers/UserSimulationHelper.js.map +1 -1
  297. package/dist/helpers/index.d.ts +1 -0
  298. package/dist/helpers/index.js +1 -0
  299. package/dist/helpers/index.js.map +1 -1
  300. package/dist/index.d.ts +6 -20
  301. package/dist/index.js +1 -0
  302. package/dist/index.js.map +1 -1
  303. package/dist/interfaces/IComponentObjects.d.ts +12 -0
  304. package/dist/interfaces/IComponentSchemaMeta.d.ts +15 -0
  305. package/dist/interfaces/IComponentSchemaMeta.js +2 -0
  306. package/dist/interfaces/IComponentSchemaMeta.js.map +1 -0
  307. package/dist/interfaces/IProperty.d.ts +13 -0
  308. package/dist/interfaces/IRegisteredSubjects.d.ts +12 -0
  309. package/dist/interfaces/ISeedSubjectDraft.d.ts +16 -0
  310. package/dist/interfaces/ISeedSubjectDraft.js +2 -0
  311. package/dist/interfaces/ISeedSubjectDraft.js.map +1 -0
  312. package/dist/interfaces/ISubject.d.ts +13 -0
  313. package/dist/interfaces/ISubjectDetail.d.ts +11 -0
  314. package/dist/interfaces/ISubjectDetail.js +2 -0
  315. package/dist/interfaces/ISubjectDetail.js.map +1 -0
  316. package/dist/mcp/ArtifactResolver.d.ts +152 -0
  317. package/dist/mcp/ArtifactResolver.js +232 -0
  318. package/dist/mcp/ArtifactResolver.js.map +1 -0
  319. package/dist/mcp/index.d.ts +2 -0
  320. package/dist/mcp/index.js +2 -0
  321. package/dist/mcp/index.js.map +1 -0
  322. package/dist/page-extensions/GlobalComponents.d.ts +30 -0
  323. package/dist/page-extensions/GlobalComponents.js +30 -0
  324. package/dist/page-extensions/GlobalComponents.js.map +1 -1
  325. package/dist/page-extensions/GoToDeepLink.d.ts +9 -0
  326. package/dist/page-extensions/GoToDeepLink.js +9 -0
  327. package/dist/page-extensions/GoToDeepLink.js.map +1 -1
  328. package/dist/page-extensions/LoginFeatures.d.ts +8 -0
  329. package/dist/page-extensions/LoginFeatures.js +8 -0
  330. package/dist/page-extensions/LoginFeatures.js.map +1 -1
  331. package/dist/page-extensions/SubjectRegistry.d.ts +31 -0
  332. package/dist/page-extensions/SubjectRegistry.js +31 -0
  333. package/dist/page-extensions/SubjectRegistry.js.map +1 -1
  334. package/dist/page-extensions/UserSimulation.d.ts +21 -0
  335. package/dist/page-extensions/UserSimulation.js +21 -0
  336. package/dist/page-extensions/UserSimulation.js.map +1 -1
  337. package/dist/page-extensions/WaitEventHandler.d.ts +20 -0
  338. package/dist/page-extensions/WaitEventHandler.js +20 -0
  339. package/dist/page-extensions/WaitEventHandler.js.map +1 -1
  340. package/dist/page-overrides/ClickOverride.d.ts +23 -2
  341. package/dist/page-overrides/ClickOverride.js +34 -50
  342. package/dist/page-overrides/ClickOverride.js.map +1 -1
  343. package/dist/page-overrides/FillOverride.d.ts +23 -0
  344. package/dist/page-overrides/FillOverride.js +46 -66
  345. package/dist/page-overrides/FillOverride.js.map +1 -1
  346. package/dist/services/ConfigBuilder.d.ts +24 -0
  347. package/dist/services/ConfigBuilder.js +24 -0
  348. package/dist/services/ConfigBuilder.js.map +1 -1
  349. package/dist/services/IndiciumApi.service.d.ts +26 -0
  350. package/dist/services/IndiciumApi.service.js +40 -5
  351. package/dist/services/IndiciumApi.service.js.map +1 -1
  352. package/dist/services/Logger.d.ts +13 -0
  353. package/dist/services/Logger.js +39 -0
  354. package/dist/services/Logger.js.map +1 -1
  355. package/dist/types/Components.d.ts +18 -1
  356. package/dist/types/Components.js +0 -1
  357. package/dist/types/Components.js.map +1 -1
  358. package/dist/types/LocatorType.d.ts +5 -0
  359. package/dist/types/LocatorType.js +2 -0
  360. package/dist/types/LocatorType.js.map +1 -0
  361. package/dist/types/PollTypes.d.ts +8 -0
  362. package/dist/types/Test.d.ts +18 -0
  363. package/dist/types/Universal.d.ts +12 -0
  364. package/dist/types/index.d.ts +2 -0
  365. package/dist/types/index.js +2 -0
  366. package/dist/types/index.js.map +1 -1
  367. package/enums/ElementTypes.ts +18 -0
  368. package/enums/ExportFormat.ts +8 -0
  369. package/enums/LogLevel.ts +17 -1
  370. package/enums/index.ts +1 -0
  371. package/helpers/ConfigChecker.ts +13 -0
  372. package/helpers/Ensure.ts +13 -0
  373. package/helpers/FlightTracker.ts +109 -20
  374. package/helpers/GlobalWaitEventHandler.ts +13 -0
  375. package/helpers/LoginHelper.ts +49 -12
  376. package/helpers/PathResolver.ts +22 -0
  377. package/helpers/Poll.ts +13 -1
  378. package/helpers/RegexHelper.ts +15 -3
  379. package/helpers/SubjectNavigator.ts +57 -0
  380. package/helpers/UserSimulationHelper.ts +14 -0
  381. package/helpers/index.ts +1 -0
  382. package/index.ts +1 -0
  383. package/interfaces/IComponentObjects.ts +12 -0
  384. package/interfaces/IComponentSchemaMeta.ts +17 -0
  385. package/interfaces/IProperty.ts +13 -0
  386. package/interfaces/IRegisteredSubjects.ts +12 -0
  387. package/interfaces/ISeedSubjectDraft.ts +17 -0
  388. package/interfaces/ISubject.ts +13 -0
  389. package/interfaces/ISubjectDetail.ts +12 -0
  390. package/mcp/ArtifactResolver.ts +296 -0
  391. package/mcp/index.ts +2 -0
  392. package/package.json +20 -8
  393. package/page-extensions/GlobalComponents.ts +30 -0
  394. package/page-extensions/GoToDeepLink.ts +9 -0
  395. package/page-extensions/LoginFeatures.ts +8 -0
  396. package/page-extensions/SubjectRegistry.ts +32 -0
  397. package/page-extensions/UserSimulation.ts +21 -0
  398. package/page-extensions/WaitEventHandler.ts +20 -0
  399. package/page-overrides/ClickOverride.ts +36 -55
  400. package/page-overrides/FillOverride.ts +59 -73
  401. package/promptCredentials.js +1 -1
  402. package/scripts/Testwise.template.json +1 -1
  403. package/scripts/diagram.js +2267 -0
  404. package/scripts/main.js +84 -63
  405. package/scripts/postinstall.js +55 -42
  406. package/scripts/setup.js +91 -37
  407. package/scripts/sync.js +767 -748
  408. package/services/ConfigBuilder.ts +24 -0
  409. package/services/IndiciumApi.service.ts +44 -6
  410. package/services/Logger.ts +40 -0
  411. package/templates/copilot-instructions.md +130 -0
  412. package/templates/mcp.json +9 -0
  413. package/templates/testwise-test-expert.agent.md +453 -0
  414. package/tests/tsconfig.json +12 -0
  415. package/tsconfig.json +6 -2
  416. package/types/Components.ts +17 -2
  417. package/types/LocatorType.ts +5 -0
  418. package/types/PollTypes.ts +8 -0
  419. package/types/Test.ts +18 -0
  420. package/types/Universal.ts +12 -0
  421. package/types/index.ts +2 -0
  422. package/dist/interfaces/IStoredTestResults.d.ts +0 -5
  423. package/dist/interfaces/IStoredTestResults.js +0 -2
  424. package/dist/interfaces/IStoredTestResults.js.map +0 -1
  425. package/dist/templates/test-artifacts/SubjectPageBase.d.ts +0 -5
  426. package/dist/templates/test-artifacts/SubjectPageBase.js +0 -6
  427. package/dist/templates/test-artifacts/SubjectPageBase.js.map +0 -1
  428. package/dist/templates/test-artifacts/screens/index.d.ts +0 -1
  429. package/dist/templates/test-artifacts/screens/index.js +0 -2
  430. package/dist/templates/test-artifacts/screens/index.js.map +0 -1
  431. package/dist/templates/test-artifacts/subjects/index.d.ts +0 -1
  432. package/dist/templates/test-artifacts/subjects/index.js +0 -2
  433. package/dist/templates/test-artifacts/subjects/index.js.map +0 -1
  434. package/interfaces/IStoredTestResults.ts +0 -6
package/scripts/sync.js CHANGED
@@ -1,748 +1,767 @@
1
- /* eslint-disable @typescript-eslint/naming-convention */
2
-
3
- import { execSync } from 'node:child_process';
4
- import fs from 'node:fs';
5
- import path from 'node:path';
6
- import process from 'node:process';
7
- import { fileURLToPath } from 'node:url';
8
- import { SubjectRegistration } from '../dist/artifact-builder/SubjectRegistration.js';
9
- import { PathResolver } from '../dist/helpers/index.js';
10
- import { indiciumApi } from '../dist/services/IndiciumApi.service.js';
11
-
12
- // ============================================================================
13
- // Constants & Configuration
14
- // ============================================================================
15
-
16
- const __filename = fileURLToPath(import.meta.url);
17
- const __dirname = path.dirname(__filename);
18
-
19
- /** @type {Readonly<{ROOT: string, DIST: string, SEED_DATA: string, TEST_ARTIFACTS: string, TEMP_TEST_ARTIFACTS: string, TEMPLATE_TEST_ARTIFACTS: string}>} */
20
- const PATHS = Object.freeze({
21
- ROOT: path.resolve(__dirname, '..'),
22
- DIST: path.resolve(__dirname, '..', 'dist'),
23
- SEED_DATA: path.resolve(__dirname, '..', 'seed-data'),
24
- TEST_ARTIFACTS: path.resolve(__dirname, '..', 'test-artifacts'),
25
- TEMP_TEST_ARTIFACTS: path.resolve(__dirname, '..', 'test-artifacts-temp'),
26
- TEMPLATE_TEST_ARTIFACTS: path.resolve(__dirname, '..', 'templates', 'test-artifacts')
27
- });
28
-
29
- /** @type {readonly string[]} */
30
- const REQUIRED_SEED_FILES = Object.freeze(['screensToBuild.json', 'subjectsToBuild.json']);
31
-
32
- /** @type {Readonly<{CLEANUP: string, BUILD: string, REFINE: string, GENERATE: string, COMPILE: string, BACKUP: string}>} */
33
- const PHASE_NAMES = Object.freeze({
34
- CLEANUP: 'Cleanup and Initial Compilation',
35
- BUILD: 'Build Model Data',
36
- REFINE: 'Refine Model Data',
37
- GENERATE: 'Generate Artifacts',
38
- COMPILE: 'Final Compilation',
39
- BACKUP: 'Backup'
40
- });
41
-
42
- /** @type {Readonly<{INFO: string, SUCCESS: string, ERROR: string, WARNING: string, PHASE: string, STEP: string, DIVIDER: string}>} */
43
- const LOG_SYMBOLS = Object.freeze({
44
- INFO: 'ℹ️',
45
- SUCCESS: '✅',
46
- ERROR: '❌',
47
- WARNING: '⚠️',
48
- PHASE: '📦',
49
- STEP: '▸',
50
- DIVIDER: '═'
51
- });
52
-
53
- // ============================================================================
54
- // Types & Interfaces
55
- // ============================================================================
56
-
57
- /**
58
- * @typedef {Object} SyncContext
59
- * @property {import('../dist/artifact-builder/helpers/index.js').Stopwatch} stopwatch - Stopwatch instance for timing
60
- * @property {ReturnType<import('../dist/artifact-builder/helpers/index.js').Stopwatch['start']>} totalTimer - Total elapsed timer
61
- * @property {AbortSignal} [signal] - Optional abort signal for graceful shutdown
62
- * @property {SyncOptions} options - Sync configuration options
63
- */
64
-
65
- /**
66
- * @typedef {Object} SyncOptions
67
- * @property {boolean} [dryRun=false] - If true, only logs what would happen without executing
68
- * @property {boolean} [verbose=false] - If true, enables verbose logging
69
- * @property {boolean} [skipBackup=false] - If true, skips the backup phase
70
- * @property {number} [retryAttempts=0] - Number of retry attempts for failed operations
71
- * @property {number} [retryDelay=1000] - Delay in ms between retry attempts
72
- */
73
-
74
- /**
75
- * @typedef {Object} StepResult
76
- * @property {boolean} success - Whether the step succeeded
77
- * @property {number} duration - Duration in milliseconds
78
- * @property {Error} [error] - Error if step failed
79
- */
80
-
81
- // ============================================================================
82
- // Logger Utility
83
- // ============================================================================
84
-
85
- /**
86
- * Centralized logging utility with consistent formatting.
87
- */
88
- const Logger = {
89
- /**
90
- * Logs an informational message.
91
- * @param {string} message - The message to log
92
- */
93
- info(message) {
94
- console.info(`${LOG_SYMBOLS.INFO} ${message}`);
95
- },
96
-
97
- /**
98
- * Logs a success message.
99
- * @param {string} message - The message to log
100
- */
101
- success(message) {
102
- console.info(`${LOG_SYMBOLS.SUCCESS} ${message}`);
103
- },
104
-
105
- /**
106
- * Logs an error message.
107
- * @param {string} message - The message to log
108
- * @param {Error} [error] - Optional error object
109
- */
110
- error(message, error) {
111
- console.error(`${LOG_SYMBOLS.ERROR} ${message}`);
112
- if (error?.stack) {
113
- console.error('\nStack trace:');
114
- console.error(error.stack);
115
- }
116
- },
117
-
118
- /**
119
- * Logs a warning message.
120
- * @param {string} message - The message to log
121
- */
122
- warn(message) {
123
- console.warn(`${LOG_SYMBOLS.WARNING} ${message}`);
124
- },
125
-
126
- /**
127
- * Logs a phase header.
128
- * @param {number} phaseNumber - The phase number
129
- * @param {string} phaseName - The phase name
130
- */
131
- phase(phaseNumber, phaseName) {
132
- console.info(`\n${LOG_SYMBOLS.PHASE} Phase ${phaseNumber}: ${phaseName}\n`);
133
- },
134
-
135
- /**
136
- * Logs a step execution.
137
- * @param {string} stepName - The step name
138
- */
139
- step(stepName) {
140
- console.info(` ${LOG_SYMBOLS.STEP} ${stepName}`);
141
- },
142
-
143
- /**
144
- * Logs a divider line.
145
- * @param {number} [width=60] - The width of the divider
146
- */
147
- divider(width = 60) {
148
- console.info(LOG_SYMBOLS.DIVIDER.repeat(width));
149
- },
150
-
151
- /**
152
- * Logs a header with dividers.
153
- * @param {string} message - The header message
154
- */
155
- header(message) {
156
- this.divider();
157
- console.info(message);
158
- this.divider();
159
- }
160
- };
161
-
162
- // ============================================================================
163
- // Utility Functions
164
- // ============================================================================
165
-
166
- /**
167
- * Sleeps for the specified duration.
168
- * @param {number} ms - Duration in milliseconds
169
- * @returns {Promise<void>}
170
- */
171
- function sleep(ms) {
172
- return new Promise((resolve) => setTimeout(resolve, ms));
173
- }
174
-
175
- /**
176
- * Executes a function with retry logic.
177
- * @template T
178
- * @param {() => Promise<T> | T} fn - The function to execute
179
- * @param {number} [attempts=3] - Number of retry attempts
180
- * @param {number} [delay=1000] - Delay between retries in ms
181
- * @returns {Promise<T>}
182
- */
183
- async function withRetry(fn, attempts = 3, delay = 1000) {
184
- let lastError;
185
-
186
- for (let attempt = 1; attempt <= attempts; attempt++) {
187
- try {
188
- return await fn();
189
- } catch (error) {
190
- lastError = error;
191
- if (attempt < attempts) {
192
- Logger.warn(`Attempt ${attempt}/${attempts} failed, retrying in ${delay}ms...`);
193
- await sleep(delay);
194
- }
195
- }
196
- }
197
-
198
- throw lastError;
199
- }
200
-
201
- /**
202
- * Safely cleans a directory without deleting the root folder
203
- * to avoid EPERM errors during npm postinstall.
204
- * @param {string} dirPath - The directory path to clean
205
- * @returns {void}
206
- */
207
- function safeCleanDir(dirPath) {
208
- if (!fs.existsSync(dirPath)) {
209
- return;
210
- }
211
-
212
- // Rename test-artifacts to temp directory if needed (only once)
213
- if (fs.existsSync(PATHS.TEST_ARTIFACTS) && !fs.existsSync(PATHS.TEMP_TEST_ARTIFACTS)) {
214
- fs.renameSync(PATHS.TEST_ARTIFACTS, PATHS.TEMP_TEST_ARTIFACTS);
215
- fs.cpSync(PATHS.TEMPLATE_TEST_ARTIFACTS, PATHS.TEST_ARTIFACTS, { recursive: true });
216
- }
217
-
218
- const files = fs.readdirSync(dirPath);
219
- for (const file of files) {
220
- const filePath = path.join(dirPath, file);
221
- try {
222
- fs.rmSync(filePath, { recursive: true, force: true });
223
- } catch (error) {
224
- Logger.warn(`Could not remove ${filePath}: ${error.message}`);
225
- }
226
- }
227
- }
228
-
229
- /**
230
- * Ensures a JSON file exists to prevent ENOENT crashes
231
- * if the Refiner doesn't handle missing files gracefully.
232
- * @param {string} filePath - The file path to ensure exists
233
- * @param {unknown} [defaultContent=[]] - Default content to write if file doesn't exist
234
- * @returns {void}
235
- */
236
- function ensureJsonExists(filePath, defaultContent = []) {
237
- if (fs.existsSync(filePath)) {
238
- return;
239
- }
240
-
241
- const dir = path.dirname(filePath);
242
- if (!fs.existsSync(dir)) {
243
- fs.mkdirSync(dir, { recursive: true });
244
- }
245
- fs.writeFileSync(filePath, JSON.stringify(defaultContent, null, 2));
246
- }
247
-
248
- /**
249
- * Validates that all required dependencies and directories exist.
250
- * @throws {Error} If validation fails
251
- * @returns {void}
252
- */
253
- function validateEnvironment() {
254
- // Ensure seed-data directory exists
255
- if (!fs.existsSync(PATHS.SEED_DATA)) {
256
- fs.mkdirSync(PATHS.SEED_DATA, { recursive: true });
257
- }
258
-
259
- // Verify TypeScript config exists
260
- const tsconfigPath = path.join(PATHS.ROOT, 'tsconfig.json');
261
- if (!fs.existsSync(tsconfigPath)) {
262
- throw new Error('tsconfig.json not found. Please ensure the project is properly set up.');
263
- }
264
- }
265
-
266
- /**
267
- * Executes a TypeScript compilation.
268
- * @param {string} cwd - The working directory
269
- * @throws {Error} If compilation fails
270
- * @returns {void}
271
- */
272
- function compileTypeScript(cwd) {
273
- try {
274
- execSync('npx tsc', { cwd, stdio: 'inherit' });
275
- } catch (error) {
276
- throw new Error(`TypeScript compilation failed: ${error.message}`);
277
- }
278
- }
279
-
280
- /**
281
- * Resets the SubjectRegistry.ts file from template.
282
- * @returns {void}
283
- */
284
- function resetSubjectRegistry() {
285
- new SubjectRegistration().resetSubjectRegistry();
286
- }
287
-
288
- /**
289
- * Cleans up temporary test artifacts directory.
290
- * @returns {void}
291
- */
292
- function cleanupTempArtifacts() {
293
- if (fs.existsSync(PATHS.TEMP_TEST_ARTIFACTS)) {
294
- fs.rmSync(PATHS.TEMP_TEST_ARTIFACTS, { recursive: true, force: true });
295
- }
296
- }
297
-
298
- // ============================================================================
299
- // Sync Pipeline Steps
300
- // ============================================================================
301
-
302
- /**
303
- * Runs a single sync step with timing and optional dry-run support.
304
- * @param {SyncContext} ctx - The sync context
305
- * @param {string} stepName - Name of the step
306
- * @param {() => Promise<void> | void} fn - The function to execute
307
- * @returns {Promise<StepResult>}
308
- */
309
- async function runStep(ctx, stepName, fn) {
310
- Logger.step(stepName);
311
-
312
- if (ctx.options.dryRun) {
313
- return { success: true, duration: 0 };
314
- }
315
-
316
- const timer = ctx.stopwatch.start(stepName);
317
- const startTime = Date.now();
318
-
319
- try {
320
- // Check for abort signal
321
- if (ctx.signal?.aborted) {
322
- throw new Error('Sync process was aborted');
323
- }
324
-
325
- const execute =
326
- ctx.options.retryAttempts > 0 ? () => withRetry(fn, ctx.options.retryAttempts, ctx.options.retryDelay) : fn;
327
-
328
- await execute();
329
-
330
- return { success: true, duration: Date.now() - startTime };
331
- } catch (error) {
332
- return { success: false, duration: Date.now() - startTime, error };
333
- } finally {
334
- timer.stop();
335
- }
336
- }
337
-
338
- /**
339
- * Phase 1: Cleanup and initial compilation
340
- * @param {SyncContext} ctx - The sync context
341
- * @returns {Promise<void>}
342
- */
343
- async function phaseCleanupAndCompile(ctx) {
344
- Logger.phase(1, PHASE_NAMES.CLEANUP);
345
-
346
- resetSubjectRegistry();
347
-
348
- const cleanResult = await runStep(ctx, 'Clear distribution directory', () => {
349
- safeCleanDir(PATHS.DIST);
350
- });
351
-
352
- if (!cleanResult.success) {
353
- throw cleanResult.error;
354
- }
355
-
356
- const compileResult = await runStep(ctx, 'Recompile TypeScript sources', () => {
357
- compileTypeScript(PATHS.ROOT);
358
- });
359
-
360
- if (!compileResult.success) {
361
- throw compileResult.error;
362
- }
363
- }
364
-
365
- /**
366
- * Phase 2: Build model data
367
- * @param {SyncContext} ctx - The sync context
368
- * @returns {Promise<void>}
369
- */
370
- async function phaseBuildModelData(ctx) {
371
- Logger.phase(2, PHASE_NAMES.BUILD);
372
-
373
- const { buildSubjects, buildScreens } = await import('../dist/artifact-builder/ModelDataBuilder.js');
374
-
375
- const subjectsResult = await runStep(ctx, 'Build subjects from model data', async () => {
376
- await buildSubjects();
377
- });
378
-
379
- if (!subjectsResult.success) {
380
- throw subjectsResult.error;
381
- }
382
-
383
- const screensResult = await runStep(ctx, 'Build screens from model data', async () => {
384
- await buildScreens();
385
- });
386
-
387
- if (!screensResult.success) {
388
- throw screensResult.error;
389
- }
390
- }
391
-
392
- /**
393
- * Phase 3: Refine model data
394
- * @param {SyncContext} ctx - The sync context
395
- * @returns {Promise<void>}
396
- */
397
- async function phaseRefineModelData(ctx) {
398
- Logger.phase(3, PHASE_NAMES.REFINE);
399
-
400
- // Ensure required seed files exist before refinement
401
- for (const seedFile of REQUIRED_SEED_FILES) {
402
- ensureJsonExists(path.join(PATHS.SEED_DATA, seedFile));
403
- }
404
-
405
- const { ModelDataRefiner } = await import('../dist/artifact-builder/index.js');
406
-
407
- const refineResult = await runStep(ctx, 'Refine model data', async () => {
408
- await new ModelDataRefiner().run();
409
- });
410
-
411
- if (!refineResult.success) {
412
- throw refineResult.error;
413
- }
414
- }
415
-
416
- /**
417
- * Phase 4: Generate artifacts
418
- * @param {SyncContext} ctx - The sync context
419
- * @returns {Promise<void>}
420
- */
421
- async function phaseGenerateArtifacts(ctx) {
422
- Logger.phase(4, PHASE_NAMES.GENERATE);
423
-
424
- const {
425
- InterfaceGenerator,
426
- ScreenInterfaceRefiner,
427
- SchemaGenerator,
428
- SubjectComponentGenerator,
429
- SubjectGenerator,
430
- SubjectRegistration
431
- } = await import('../dist/artifact-builder/index.js');
432
-
433
- const interfaceGenerator = new InterfaceGenerator();
434
-
435
- /** @type {Array<{name: string, fn: () => Promise<void> | void}>} */
436
- const steps = [
437
- {
438
- name: 'Generate screen interfaces from screen schemas',
439
- fn: () => interfaceGenerator.generateScreenInterfacesFromSchemas()
440
- },
441
- {
442
- name: 'Refine interfaces',
443
- fn: () => new ScreenInterfaceRefiner().refine()
444
- },
445
- {
446
- name: 'Generate subject component schemas',
447
- fn: () => new SchemaGenerator().generateSchema()
448
- },
449
- {
450
- name: 'Generate subject component interfaces from schemas',
451
- fn: () => interfaceGenerator.generateSubjectInterfacesFromSchemas()
452
- },
453
- {
454
- name: 'Build components that implement interfaces',
455
- fn: () => new SubjectComponentGenerator().run()
456
- },
457
- {
458
- name: 'Generate subject classes',
459
- fn: () => new SubjectGenerator().run()
460
- },
461
- {
462
- name: 'Add subjects to registry',
463
- fn: async () => await new SubjectRegistration().run()
464
- }
465
- ];
466
-
467
- for (const step of steps) {
468
- const result = await runStep(ctx, step.name, step.fn);
469
- if (!result.success) {
470
- throw result.error;
471
- }
472
- }
473
- }
474
-
475
- /**
476
- * Phase 5: Final compilation
477
- * @param {SyncContext} ctx - The sync context
478
- * @returns {Promise<void>}
479
- */
480
- async function phaseFinalCompilation(ctx) {
481
- Logger.phase(5, PHASE_NAMES.COMPILE);
482
-
483
- const cleanResult = await runStep(ctx, 'Clear distribution directory', () => {
484
- safeCleanDir(PATHS.DIST);
485
- });
486
-
487
- if (!cleanResult.success) {
488
- throw cleanResult.error;
489
- }
490
-
491
- const compileResult = await runStep(ctx, 'Compile generated artifacts', () => {
492
- compileTypeScript(PATHS.ROOT);
493
- });
494
-
495
- if (!compileResult.success) {
496
- throw compileResult.error;
497
- }
498
- }
499
-
500
- /**
501
- * Phase 6: Backup
502
- * @param {SyncContext} ctx - The sync context
503
- * @returns {Promise<void>}
504
- */
505
- async function phaseBackup(ctx) {
506
- if (ctx.options.skipBackup) {
507
- Logger.info('Skipping backup phase (--skip-backup flag)');
508
- return;
509
- }
510
-
511
- Logger.phase(6, PHASE_NAMES.BACKUP);
512
-
513
- const { ArtifactManager } = await import('../dist/artifact-builder/index.js');
514
-
515
- const backupResult = await runStep(ctx, 'Perform backup', () => {
516
- new ArtifactManager().performBackup();
517
- cleanupTempArtifacts();
518
- });
519
-
520
- if (!backupResult.success) {
521
- throw backupResult.error;
522
- }
523
- }
524
-
525
- // ============================================================================
526
- // Main Sync Function
527
- // ============================================================================
528
-
529
- /**
530
- * Parses command line arguments into sync options.
531
- * @returns {SyncOptions}
532
- */
533
- function parseCommandLineArgs() {
534
- const args = process.argv.slice(2);
535
-
536
- return {
537
- dryRun: args.includes('--dry-run'),
538
- verbose: args.includes('--verbose') || args.includes('-v'),
539
- skipBackup: args.includes('--skip-backup'),
540
- retryAttempts: parseInt(args.find((arg) => arg.startsWith('--retry='))?.split('=')[1] ?? '0', 10),
541
- retryDelay: parseInt(args.find((arg) => arg.startsWith('--retry-delay='))?.split('=')[1] ?? '1000', 10)
542
- };
543
- }
544
-
545
- /**
546
- * Sets up graceful shutdown handlers.
547
- * @param {AbortController} controller - The abort controller
548
- * @returns {void}
549
- */
550
- function setupShutdownHandlers(controller) {
551
- const shutdown = () => {
552
- Logger.warn('Received shutdown signal, attempting graceful shutdown...');
553
- controller.abort();
554
- };
555
-
556
- process.on('SIGINT', shutdown);
557
- process.on('SIGTERM', shutdown);
558
- }
559
-
560
- /**
561
- * Executes the complete synchronization process.
562
- *
563
- * This process:
564
- * 1. Cleans the dist directory and compiles TypeScript
565
- * 2. Builds model data from subjects and screens
566
- * 3. Refines the model data
567
- * 4. Generates all artifacts (interfaces, schemas, components, subjects)
568
- * 5. Performs final compilation
569
- * 6. Creates a backup
570
- *
571
- * @param {SyncOptions} [options={}] - Optional sync configuration
572
- * @returns {Promise<void>}
573
- * @throws {Error} If sync process fails
574
- */
575
- export async function sync(options = {}) {
576
- // Merge provided options with command line args (CLI takes precedence)
577
- const cliOptions = parseCommandLineArgs();
578
- const mergedOptions = { ...options, ...cliOptions };
579
-
580
- // Setup abort controller for graceful shutdown
581
- const abortController = new AbortController();
582
- setupShutdownHandlers(abortController);
583
-
584
- // Late import to avoid circular dependency issues during initial compile
585
- const { Stopwatch } = await import('../dist/artifact-builder/helpers/index.js');
586
-
587
- const stopwatch = new Stopwatch();
588
- const totalTimer = stopwatch.start('Sync process start to end');
589
-
590
- /** @type {SyncContext} */
591
- const ctx = {
592
- stopwatch,
593
- totalTimer,
594
- signal: abortController.signal,
595
- options: mergedOptions
596
- };
597
-
598
- Logger.header('🚀 Starting Testwise Sync Process');
599
-
600
- if (mergedOptions.dryRun) {
601
- Logger.warn('Running in DRY RUN mode - no changes will be made');
602
- }
603
-
604
- if (mergedOptions.verbose) {
605
- Logger.info(`Options: ${JSON.stringify(mergedOptions, null, 2)}`);
606
- }
607
-
608
- // Model data verification before any sync steps
609
- if (!(await verifiedModelData())) {
610
- Logger.info('Model data verification failed. Sync aborted.');
611
- process.exit(0);
612
- }
613
-
614
- try {
615
- // Pre-flight validation
616
- validateEnvironment();
617
-
618
- // Execute phases in sequence
619
- await phaseCleanupAndCompile(ctx);
620
- await phaseBuildModelData(ctx);
621
- await phaseRefineModelData(ctx);
622
- await phaseGenerateArtifacts(ctx);
623
- await phaseFinalCompilation(ctx);
624
-
625
- Logger.header('✅ Sync process completed successfully!');
626
- } catch (error) {
627
- Logger.divider();
628
- Logger.error('Sync process failed!', error);
629
- Logger.divider();
630
- process.exit(1);
631
- } finally {
632
- try {
633
- await phaseBackup(ctx);
634
- } catch (backupError) {
635
- Logger.warn(`Backup failed: ${backupError.message}`);
636
- }
637
-
638
- totalTimer.stop();
639
- }
640
- }
641
-
642
- /**
643
- * Verify that model data is properly configured
644
- * @returns {Promise<boolean>} True if model data is verified, false otherwise
645
- */
646
- async function verifiedModelData() {
647
- try {
648
- const consumerRoot = new PathResolver().getConsumerRootDirectory();
649
- const seedDataDir = path.join(consumerRoot, 'seed-data');
650
- const subjectsJson = path.join(seedDataDir, 'subjects.json');
651
- const screensDir = path.join(seedDataDir, 'screen-schemas');
652
- const projectIsConfigured = await isConfigured();
653
-
654
- // Check if seed-data directory exists
655
- if (!fs.existsSync(seedDataDir)) {
656
- return handleMissingSeedDataDirectory(seedDataDir, screensDir, projectIsConfigured);
657
- }
658
-
659
- // Check if subjects.json exists
660
- if (!fs.existsSync(subjectsJson) && !projectIsConfigured) {
661
- Logger.info('Required file not found: subjects.json');
662
- return false;
663
- }
664
-
665
- // Check if screens directory exists
666
- if (!fs.existsSync(screensDir)) {
667
- return handleMissingScreensDirectory(screensDir, projectIsConfigured);
668
- }
669
-
670
- // Check if there are any screen files
671
- const screenFiles = fs.readdirSync(screensDir).filter((f) => f.endsWith('.json'));
672
- if (screenFiles.length === 0 && !projectIsConfigured) {
673
- Logger.info('No screen JSON files found in seed-data/screen-schemas');
674
- return false;
675
- }
676
-
677
- return true;
678
- } catch (error) {
679
- Logger.error('Model data verification error:', error);
680
- return false;
681
- }
682
- }
683
-
684
- /**
685
- * Handle missing seed-data directory
686
- * @param {string} seedDataDir - Path to seed-data directory
687
- * @param {string} screensDir - Path to screens directory
688
- * @param {boolean} projectIsConfigured - Whether project is configured
689
- * @returns {boolean} True if handled successfully, false otherwise
690
- */
691
- function handleMissingSeedDataDirectory(seedDataDir, screensDir, projectIsConfigured) {
692
- if (projectIsConfigured) {
693
- try {
694
- fs.mkdirSync(seedDataDir, { recursive: true });
695
- fs.mkdirSync(screensDir, { recursive: true });
696
- Logger.info('Created seed-data directory structure.');
697
- return true;
698
- } catch (error) {
699
- Logger.error('Failed to create seed-data directory:', error);
700
- return false;
701
- }
702
- }
703
-
704
- Logger.info('No seed-data directory found.');
705
- return false;
706
- }
707
-
708
- /**
709
- * Handle missing screens directory
710
- * @param {string} screensDir - Path to screens directory
711
- * @param {boolean} projectIsConfigured - Whether project is configured
712
- * @returns {boolean} True if handled successfully, false otherwise
713
- */
714
- function handleMissingScreensDirectory(screensDir, projectIsConfigured) {
715
- if (projectIsConfigured) {
716
- try {
717
- fs.mkdirSync(screensDir, { recursive: true });
718
- Logger.info('Created screen-schemas directory.');
719
- return true;
720
- } catch (error) {
721
- Logger.error('Failed to create screen-schemas directory:', error);
722
- return false;
723
- }
724
- }
725
-
726
- Logger.info('No screen-schemas directory found.');
727
- return false;
728
- }
729
-
730
- /**
731
- * Check if project is configured with valid credentials
732
- * @returns {Promise<boolean>} True if project is configured, false otherwise
733
- */
734
- async function isConfigured() {
735
- try {
736
- if (typeof indiciumApi.canConnect === 'function') {
737
- return await indiciumApi.canConnect();
738
- }
739
- if (typeof indiciumApi.canConnectToProject === 'function') {
740
- return await indiciumApi.canConnectToProject();
741
- }
742
- Logger.warn('No valid connection check found on indiciumApi.');
743
- return false;
744
- } catch (error) {
745
- Logger.error('Configuration check failed:', error);
746
- return false;
747
- }
748
- }
1
+ /* eslint-disable @typescript-eslint/naming-convention */
2
+
3
+ /**
4
+ * Runs the full artifact-builder pipeline to regenerate all typed subject
5
+ * classes and screen interfaces from the live Thinkwise application model.
6
+ *
7
+ * **What it does (in order):**
8
+ * 1. Backs up `test-artifacts/` via `ArtifactManager` (protects against partial runs).
9
+ * 2. `ModelDataBuilder` — fetches subjects + screen schemas from the Indicium API.
10
+ * 3. `ModelDataRefiner` — filters to only registered subjects.
11
+ * 4. `SchemaGenerator` — produces JSON Schema files for quicktype.
12
+ * 5. `InterfaceGenerator` — runs quicktype to generate TypeScript screen interfaces.
13
+ * 6. `ScreenInterfaceRefiner` — cleans up quicktype output.
14
+ * 7. `SubjectGenerator` + `SubjectComponentGenerator` — writes typed subject classes.
15
+ * 8. `SubjectRegistration` — updates `SubjectRegistry.ts` from the template.
16
+ *
17
+ * On any failure, restores the backup so the consumer project's tests
18
+ * continue to work while the issue is investigated.
19
+ *
20
+ * **How to invoke:** Run `testwise sync` from the consumer project root, or
21
+ * let `postinstall` call it automatically after `npm install`.
22
+ */
23
+
24
+ import { execSync } from 'node:child_process';
25
+ import fs from 'node:fs';
26
+ import path from 'node:path';
27
+ import process from 'node:process';
28
+ import { fileURLToPath } from 'node:url';
29
+ import { SubjectRegistration } from '../dist/artifact-builder/SubjectRegistration.js';
30
+ import { indiciumApi } from '../dist/services/IndiciumApi.service.js';
31
+
32
+ // ============================================================================
33
+ // Constants & Configuration
34
+ // ============================================================================
35
+
36
+ const __filename = fileURLToPath(import.meta.url);
37
+ const __dirname = path.dirname(__filename);
38
+
39
+ /** @type {Readonly<{ROOT: string, DIST: string, SEED_DATA: string, TEST_ARTIFACTS: string, TEMP_TEST_ARTIFACTS: string, TEMPLATE_TEST_ARTIFACTS: string}>} */
40
+ const PATHS = Object.freeze({
41
+ ROOT: path.resolve(__dirname, '..'),
42
+ DIST: path.resolve(__dirname, '..', 'dist'),
43
+ SEED_DATA: path.resolve(__dirname, '..', 'seed-data'),
44
+ TEST_ARTIFACTS: path.resolve(__dirname, '..', 'test-artifacts'),
45
+ TEMP_TEST_ARTIFACTS: path.resolve(__dirname, '..', 'test-artifacts-temp'),
46
+ TEMPLATE_TEST_ARTIFACTS: path.resolve(__dirname, '..', 'templates', 'test-artifacts')
47
+ });
48
+
49
+ /** @type {readonly string[]} */
50
+ const REQUIRED_SEED_FILES = Object.freeze(['screensToBuild.json', 'subjectsToBuild.json']);
51
+
52
+ /** @type {Readonly<{CLEANUP: string, BUILD: string, REFINE: string, GENERATE: string, COMPILE: string, BACKUP: string}>} */
53
+ const PHASE_NAMES = Object.freeze({
54
+ CLEANUP: 'Cleanup and Initial Compilation',
55
+ BUILD: 'Build Model Data',
56
+ REFINE: 'Refine Model Data',
57
+ GENERATE: 'Generate Artifacts',
58
+ COMPILE: 'Final Compilation',
59
+ BACKUP: 'Backup'
60
+ });
61
+
62
+ /** @type {Readonly<{INFO: string, SUCCESS: string, ERROR: string, WARNING: string, PHASE: string, STEP: string, DIVIDER: string}>} */
63
+ const LOG_SYMBOLS = Object.freeze({
64
+ INFO: 'ℹ️',
65
+ SUCCESS: '✅',
66
+ ERROR: '❌',
67
+ WARNING: '⚠️',
68
+ PHASE: '📦',
69
+ STEP: '▸',
70
+ DIVIDER: '═'
71
+ });
72
+
73
+ // ============================================================================
74
+ // Types & Interfaces
75
+ // ============================================================================
76
+
77
+ /**
78
+ * @typedef {Object} SyncContext
79
+ * @property {import('../dist/artifact-builder/helpers/index.js').Stopwatch} stopwatch - Stopwatch instance for timing
80
+ * @property {ReturnType<import('../dist/artifact-builder/helpers/index.js').Stopwatch['start']>} totalTimer - Total elapsed timer
81
+ * @property {AbortSignal} [signal] - Optional abort signal for graceful shutdown
82
+ * @property {SyncOptions} options - Sync configuration options
83
+ */
84
+
85
+ /**
86
+ * @typedef {Object} SyncOptions
87
+ * @property {boolean} [dryRun=false] - If true, only logs what would happen without executing
88
+ * @property {boolean} [verbose=false] - If true, enables verbose logging
89
+ * @property {boolean} [skipBackup=false] - If true, skips the backup phase
90
+ * @property {number} [retryAttempts=0] - Number of retry attempts for failed operations
91
+ * @property {number} [retryDelay=1000] - Delay in ms between retry attempts
92
+ */
93
+
94
+ /**
95
+ * @typedef {Object} StepResult
96
+ * @property {boolean} success - Whether the step succeeded
97
+ * @property {number} duration - Duration in milliseconds
98
+ * @property {Error} [error] - Error if step failed
99
+ */
100
+
101
+ // ============================================================================
102
+ // Logger Utility
103
+ // ============================================================================
104
+
105
+ /**
106
+ * Centralized logging utility with consistent formatting.
107
+ */
108
+ const Logger = {
109
+ /**
110
+ * Logs an informational message.
111
+ * @param {string} message - The message to log
112
+ */
113
+ info(message) {
114
+ console.info(`${LOG_SYMBOLS.INFO} ${message}`);
115
+ },
116
+
117
+ /**
118
+ * Logs a success message.
119
+ * @param {string} message - The message to log
120
+ */
121
+ success(message) {
122
+ console.info(`${LOG_SYMBOLS.SUCCESS} ${message}`);
123
+ },
124
+
125
+ /**
126
+ * Logs an error message.
127
+ * @param {string} message - The message to log
128
+ * @param {Error} [error] - Optional error object
129
+ */
130
+ error(message, error) {
131
+ console.error(`${LOG_SYMBOLS.ERROR} ${message}`);
132
+ if (error?.stack) {
133
+ console.error('\nStack trace:');
134
+ console.error(error.stack);
135
+ }
136
+ },
137
+
138
+ /**
139
+ * Logs a warning message.
140
+ * @param {string} message - The message to log
141
+ */
142
+ warn(message) {
143
+ console.warn(`${LOG_SYMBOLS.WARNING} ${message}`);
144
+ },
145
+
146
+ /**
147
+ * Logs a phase header.
148
+ * @param {number} phaseNumber - The phase number
149
+ * @param {string} phaseName - The phase name
150
+ */
151
+ phase(phaseNumber, phaseName) {
152
+ console.info(`\n${LOG_SYMBOLS.PHASE} Phase ${phaseNumber}: ${phaseName}\n`);
153
+ },
154
+
155
+ /**
156
+ * Logs a step execution.
157
+ * @param {string} stepName - The step name
158
+ */
159
+ step(stepName) {
160
+ console.info(` ${LOG_SYMBOLS.STEP} ${stepName}`);
161
+ },
162
+
163
+ /**
164
+ * Logs a divider line.
165
+ * @param {number} [width=60] - The width of the divider
166
+ */
167
+ divider(width = 60) {
168
+ console.info(LOG_SYMBOLS.DIVIDER.repeat(width));
169
+ },
170
+
171
+ /**
172
+ * Logs a header with dividers.
173
+ * @param {string} message - The header message
174
+ */
175
+ header(message) {
176
+ this.divider();
177
+ console.info(message);
178
+ this.divider();
179
+ }
180
+ };
181
+
182
+ // ============================================================================
183
+ // Utility Functions
184
+ // ============================================================================
185
+
186
+ /**
187
+ * Sleeps for the specified duration.
188
+ * @param {number} ms - Duration in milliseconds
189
+ * @returns {Promise<void>}
190
+ */
191
+ function sleep(ms) {
192
+ return new Promise((resolve) => setTimeout(resolve, ms));
193
+ }
194
+
195
+ /**
196
+ * Executes a function with retry logic.
197
+ * @template T
198
+ * @param {() => Promise<T> | T} fn - The function to execute
199
+ * @param {number} [attempts=3] - Number of retry attempts
200
+ * @param {number} [delay=1000] - Delay between retries in ms
201
+ * @returns {Promise<T>}
202
+ */
203
+ async function withRetry(fn, attempts = 3, delay = 1000) {
204
+ let lastError;
205
+
206
+ for (let attempt = 1; attempt <= attempts; attempt++) {
207
+ try {
208
+ return await fn();
209
+ } catch (error) {
210
+ lastError = error;
211
+ if (attempt < attempts) {
212
+ Logger.warn(`Attempt ${attempt}/${attempts} failed, retrying in ${delay}ms...`);
213
+ await sleep(delay);
214
+ }
215
+ }
216
+ }
217
+
218
+ throw lastError;
219
+ }
220
+
221
+ /**
222
+ * Safely cleans a directory without deleting the root folder
223
+ * to avoid EPERM errors during npm postinstall.
224
+ * @param {string} dirPath - The directory path to clean
225
+ * @returns {void}
226
+ */
227
+ function safeCleanDir(dirPath) {
228
+ if (!fs.existsSync(dirPath)) {
229
+ return;
230
+ }
231
+
232
+ // Rename test-artifacts to temp directory if needed (only once)
233
+ if (fs.existsSync(PATHS.TEST_ARTIFACTS) && !fs.existsSync(PATHS.TEMP_TEST_ARTIFACTS)) {
234
+ fs.renameSync(PATHS.TEST_ARTIFACTS, PATHS.TEMP_TEST_ARTIFACTS);
235
+ fs.cpSync(PATHS.TEMPLATE_TEST_ARTIFACTS, PATHS.TEST_ARTIFACTS, { recursive: true });
236
+ }
237
+
238
+ const files = fs.readdirSync(dirPath);
239
+ for (const file of files) {
240
+ const filePath = path.join(dirPath, file);
241
+ try {
242
+ fs.rmSync(filePath, { recursive: true, force: true });
243
+ } catch (error) {
244
+ Logger.warn(`Could not remove ${filePath}: ${error.message}`);
245
+ }
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Ensures a JSON file exists to prevent ENOENT crashes
251
+ * if the Refiner doesn't handle missing files gracefully.
252
+ * @param {string} filePath - The file path to ensure exists
253
+ * @param {unknown} [defaultContent=[]] - Default content to write if file doesn't exist
254
+ * @returns {void}
255
+ */
256
+ function ensureJsonExists(filePath, defaultContent = []) {
257
+ if (fs.existsSync(filePath)) {
258
+ return;
259
+ }
260
+
261
+ const dir = path.dirname(filePath);
262
+ if (!fs.existsSync(dir)) {
263
+ fs.mkdirSync(dir, { recursive: true });
264
+ }
265
+ fs.writeFileSync(filePath, JSON.stringify(defaultContent, null, 2));
266
+ }
267
+
268
+ /**
269
+ * Validates that all required dependencies and directories exist.
270
+ * @throws {Error} If validation fails
271
+ * @returns {void}
272
+ */
273
+ function validateEnvironment() {
274
+ // Ensure seed-data directory exists
275
+ if (!fs.existsSync(PATHS.SEED_DATA)) {
276
+ fs.mkdirSync(PATHS.SEED_DATA, { recursive: true });
277
+ }
278
+
279
+ // Verify TypeScript config exists
280
+ const tsconfigPath = path.join(PATHS.ROOT, 'tsconfig.json');
281
+ if (!fs.existsSync(tsconfigPath)) {
282
+ throw new Error('tsconfig.json not found. Please ensure the project is properly set up.');
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Executes a TypeScript compilation.
288
+ * @param {string} cwd - The working directory
289
+ * @throws {Error} If compilation fails
290
+ * @returns {void}
291
+ */
292
+ function compileTypeScript(cwd) {
293
+ try {
294
+ execSync('npx tsc', { cwd, stdio: 'inherit' });
295
+ } catch (error) {
296
+ throw new Error(`TypeScript compilation failed: ${error.message}`);
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Resets the SubjectRegistry.ts file from template.
302
+ * @returns {void}
303
+ */
304
+ function resetSubjectRegistry() {
305
+ new SubjectRegistration().resetSubjectRegistry();
306
+ }
307
+
308
+ /**
309
+ * Cleans up temporary test artifacts directory.
310
+ * @returns {void}
311
+ */
312
+ function cleanupTempArtifacts() {
313
+ if (fs.existsSync(PATHS.TEMP_TEST_ARTIFACTS)) {
314
+ fs.rmSync(PATHS.TEMP_TEST_ARTIFACTS, { recursive: true, force: true });
315
+ }
316
+ }
317
+
318
+ // ============================================================================
319
+ // Sync Pipeline Steps
320
+ // ============================================================================
321
+
322
+ /**
323
+ * Runs a single sync step with timing and optional dry-run support.
324
+ * @param {SyncContext} ctx - The sync context
325
+ * @param {string} stepName - Name of the step
326
+ * @param {() => Promise<void> | void} fn - The function to execute
327
+ * @returns {Promise<StepResult>}
328
+ */
329
+ async function runStep(ctx, stepName, fn) {
330
+ Logger.step(stepName);
331
+
332
+ if (ctx.options.dryRun) {
333
+ return { success: true, duration: 0 };
334
+ }
335
+
336
+ const timer = ctx.stopwatch.start(stepName);
337
+ const startTime = Date.now();
338
+
339
+ try {
340
+ // Check for abort signal
341
+ if (ctx.signal?.aborted) {
342
+ throw new Error('Sync process was aborted');
343
+ }
344
+
345
+ const execute =
346
+ ctx.options.retryAttempts > 0 ? () => withRetry(fn, ctx.options.retryAttempts, ctx.options.retryDelay) : fn;
347
+
348
+ await execute();
349
+
350
+ return { success: true, duration: Date.now() - startTime };
351
+ } catch (error) {
352
+ return { success: false, duration: Date.now() - startTime, error };
353
+ } finally {
354
+ timer.stop();
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Phase 1: Cleanup and initial compilation
360
+ * @param {SyncContext} ctx - The sync context
361
+ * @returns {Promise<void>}
362
+ */
363
+ async function phaseCleanupAndCompile(ctx) {
364
+ Logger.phase(1, PHASE_NAMES.CLEANUP);
365
+
366
+ resetSubjectRegistry();
367
+
368
+ const cleanResult = await runStep(ctx, 'Clear distribution directory', () => {
369
+ safeCleanDir(PATHS.DIST);
370
+ });
371
+
372
+ if (!cleanResult.success) {
373
+ throw cleanResult.error;
374
+ }
375
+
376
+ const compileResult = await runStep(ctx, 'Recompile TypeScript sources', () => {
377
+ compileTypeScript(PATHS.ROOT);
378
+ });
379
+
380
+ if (!compileResult.success) {
381
+ throw compileResult.error;
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Phase 2: Build model data
387
+ * @param {SyncContext} ctx - The sync context
388
+ * @returns {Promise<void>}
389
+ */
390
+ async function phaseBuildModelData(ctx) {
391
+ Logger.phase(2, PHASE_NAMES.BUILD);
392
+
393
+ const { buildSubjects, buildScreens } = await import('../dist/artifact-builder/ModelDataBuilder.js');
394
+
395
+ const subjectsResult = await runStep(ctx, 'Build subjects from model data', async () => {
396
+ await buildSubjects();
397
+ });
398
+
399
+ if (!subjectsResult.success) {
400
+ throw subjectsResult.error;
401
+ }
402
+
403
+ const screensResult = await runStep(ctx, 'Build screens from model data', async () => {
404
+ await buildScreens();
405
+ });
406
+
407
+ if (!screensResult.success) {
408
+ throw screensResult.error;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Phase 3: Refine model data
414
+ * @param {SyncContext} ctx - The sync context
415
+ * @returns {Promise<void>}
416
+ */
417
+ async function phaseRefineModelData(ctx) {
418
+ Logger.phase(3, PHASE_NAMES.REFINE);
419
+
420
+ // Ensure required seed files exist before refinement
421
+ for (const seedFile of REQUIRED_SEED_FILES) {
422
+ ensureJsonExists(path.join(PATHS.SEED_DATA, seedFile));
423
+ }
424
+
425
+ const { ModelDataRefiner } = await import('../dist/artifact-builder/index.js');
426
+
427
+ const refineResult = await runStep(ctx, 'Refine model data', async () => {
428
+ await new ModelDataRefiner().run();
429
+ });
430
+
431
+ if (!refineResult.success) {
432
+ throw refineResult.error;
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Phase 4: Generate artifacts
438
+ * @param {SyncContext} ctx - The sync context
439
+ * @returns {Promise<void>}
440
+ */
441
+ async function phaseGenerateArtifacts(ctx) {
442
+ Logger.phase(4, PHASE_NAMES.GENERATE);
443
+
444
+ const {
445
+ InterfaceGenerator,
446
+ ScreenInterfaceRefiner,
447
+ SchemaGenerator,
448
+ SubjectComponentGenerator,
449
+ SubjectGenerator,
450
+ SubjectRegistration
451
+ } = await import('../dist/artifact-builder/index.js');
452
+
453
+ const interfaceGenerator = new InterfaceGenerator();
454
+
455
+ /** @type {Array<{name: string, fn: () => Promise<void> | void}>} */
456
+ const steps = [
457
+ {
458
+ name: 'Generate screen interfaces from screen schemas',
459
+ fn: () => interfaceGenerator.generateScreenInterfacesFromSchemas()
460
+ },
461
+ {
462
+ name: 'Refine interfaces',
463
+ fn: () => new ScreenInterfaceRefiner().refine()
464
+ },
465
+ {
466
+ name: 'Generate subject component schemas',
467
+ fn: () => new SchemaGenerator().generateSchema()
468
+ },
469
+ {
470
+ name: 'Generate subject component interfaces from schemas',
471
+ fn: () => interfaceGenerator.generateSubjectInterfacesFromSchemas()
472
+ },
473
+ {
474
+ name: 'Build components that implement interfaces',
475
+ fn: () => new SubjectComponentGenerator().run()
476
+ },
477
+ {
478
+ name: 'Generate subject classes',
479
+ fn: () => new SubjectGenerator().run()
480
+ },
481
+ {
482
+ name: 'Add subjects to registry',
483
+ fn: async () => await new SubjectRegistration().run()
484
+ }
485
+ ];
486
+
487
+ for (const step of steps) {
488
+ const result = await runStep(ctx, step.name, step.fn);
489
+ if (!result.success) {
490
+ throw result.error;
491
+ }
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Phase 5: Final compilation
497
+ * @param {SyncContext} ctx - The sync context
498
+ * @returns {Promise<void>}
499
+ */
500
+ async function phaseFinalCompilation(ctx) {
501
+ Logger.phase(5, PHASE_NAMES.COMPILE);
502
+
503
+ const cleanResult = await runStep(ctx, 'Clear distribution directory', () => {
504
+ safeCleanDir(PATHS.DIST);
505
+ });
506
+
507
+ if (!cleanResult.success) {
508
+ throw cleanResult.error;
509
+ }
510
+
511
+ const compileResult = await runStep(ctx, 'Compile generated artifacts', () => {
512
+ compileTypeScript(PATHS.ROOT);
513
+ });
514
+
515
+ if (!compileResult.success) {
516
+ throw compileResult.error;
517
+ }
518
+ }
519
+
520
+ /**
521
+ * Phase 6: Backup
522
+ * @param {SyncContext} ctx - The sync context
523
+ * @returns {Promise<void>}
524
+ */
525
+ async function phaseBackup(ctx) {
526
+ if (ctx.options.skipBackup) {
527
+ Logger.info('Skipping backup phase (--skip-backup flag)');
528
+ return;
529
+ }
530
+
531
+ Logger.phase(6, PHASE_NAMES.BACKUP);
532
+
533
+ const { ArtifactManager } = await import('../dist/artifact-builder/index.js');
534
+
535
+ const backupResult = await runStep(ctx, 'Perform backup', () => {
536
+ new ArtifactManager().performBackup();
537
+ cleanupTempArtifacts();
538
+ });
539
+
540
+ if (!backupResult.success) {
541
+ throw backupResult.error;
542
+ }
543
+ }
544
+
545
+ // ============================================================================
546
+ // Main Sync Function
547
+ // ============================================================================
548
+
549
+ /**
550
+ * Parses command line arguments into sync options.
551
+ * @returns {SyncOptions}
552
+ */
553
+ function parseCommandLineArgs() {
554
+ const args = process.argv.slice(2);
555
+
556
+ return {
557
+ dryRun: args.includes('--dry-run'),
558
+ verbose: args.includes('--verbose') || args.includes('-v'),
559
+ skipBackup: args.includes('--skip-backup'),
560
+ retryAttempts: parseInt(args.find((arg) => arg.startsWith('--retry='))?.split('=')[1] ?? '0', 10),
561
+ retryDelay: parseInt(args.find((arg) => arg.startsWith('--retry-delay='))?.split('=')[1] ?? '1000', 10)
562
+ };
563
+ }
564
+
565
+ /**
566
+ * Sets up graceful shutdown handlers.
567
+ * @param {AbortController} controller - The abort controller
568
+ * @returns {void}
569
+ */
570
+ function setupShutdownHandlers(controller) {
571
+ const shutdown = () => {
572
+ Logger.warn('Received shutdown signal, attempting graceful shutdown...');
573
+ controller.abort();
574
+ };
575
+
576
+ process.on('SIGINT', shutdown);
577
+ process.on('SIGTERM', shutdown);
578
+ }
579
+
580
+ /**
581
+ * Executes the complete synchronization process.
582
+ *
583
+ * This process:
584
+ * 1. Cleans the dist directory and compiles TypeScript
585
+ * 2. Builds model data from subjects and screens
586
+ * 3. Refines the model data
587
+ * 4. Generates all artifacts (interfaces, schemas, components, subjects)
588
+ * 5. Performs final compilation
589
+ * 6. Creates a backup
590
+ *
591
+ * @param {SyncOptions} [options={}] - Optional sync configuration
592
+ * @returns {Promise<void>}
593
+ * @throws {Error} If sync process fails
594
+ */
595
+ export async function sync(options = {}) {
596
+ // Merge provided options with command line args (CLI takes precedence)
597
+ const cliOptions = parseCommandLineArgs();
598
+ const mergedOptions = { ...options, ...cliOptions };
599
+
600
+ // Setup abort controller for graceful shutdown
601
+ const abortController = new AbortController();
602
+ setupShutdownHandlers(abortController);
603
+
604
+ // Late import to avoid circular dependency issues during initial compile
605
+ const { Stopwatch } = await import('../dist/artifact-builder/helpers/index.js');
606
+
607
+ const stopwatch = new Stopwatch();
608
+ const totalTimer = stopwatch.start('Sync process start to end');
609
+
610
+ /** @type {SyncContext} */
611
+ const ctx = {
612
+ stopwatch,
613
+ totalTimer,
614
+ signal: abortController.signal,
615
+ options: mergedOptions
616
+ };
617
+
618
+ Logger.header('🚀 Starting Testwise Sync Process');
619
+
620
+ if (mergedOptions.dryRun) {
621
+ Logger.warn('Running in DRY RUN mode - no changes will be made');
622
+ }
623
+
624
+ if (mergedOptions.verbose) {
625
+ Logger.info(`Options: ${JSON.stringify(mergedOptions, null, 2)}`);
626
+ }
627
+
628
+ // Model data verification before any sync steps
629
+ if (!(await verifiedModelData())) {
630
+ Logger.info('Model data verification failed. Sync aborted.');
631
+ process.exit(0);
632
+ }
633
+
634
+ try {
635
+ // Pre-flight validation
636
+ validateEnvironment();
637
+
638
+ // Execute phases in sequence
639
+ await phaseCleanupAndCompile(ctx);
640
+ await phaseBuildModelData(ctx);
641
+ await phaseRefineModelData(ctx);
642
+ await phaseGenerateArtifacts(ctx);
643
+ await phaseFinalCompilation(ctx);
644
+
645
+ Logger.header('✅ Sync process completed successfully!');
646
+ } catch (error) {
647
+ Logger.divider();
648
+ Logger.error('Sync process failed!', error);
649
+ Logger.divider();
650
+ process.exit(1);
651
+ } finally {
652
+ try {
653
+ await phaseBackup(ctx);
654
+ } catch (backupError) {
655
+ Logger.warn(`Backup failed: ${backupError.message}`);
656
+ }
657
+
658
+ totalTimer.stop();
659
+ }
660
+ }
661
+
662
+ /**
663
+ * Verify that model data is properly configured
664
+ * @returns {Promise<boolean>} True if model data is verified, false otherwise
665
+ */
666
+ async function verifiedModelData() {
667
+ try {
668
+ const seedDataDir = PATHS.SEED_DATA;
669
+ const subjectsJson = path.join(seedDataDir, 'subjects.json');
670
+ const screensDir = path.join(seedDataDir, 'screen-schemas');
671
+ const projectIsConfigured = await isConfigured();
672
+
673
+ // Check if seed-data directory exists
674
+ if (!fs.existsSync(seedDataDir)) {
675
+ return handleMissingSeedDataDirectory(seedDataDir, screensDir, projectIsConfigured);
676
+ }
677
+
678
+ // Check if subjects.json exists
679
+ if (!fs.existsSync(subjectsJson) && !projectIsConfigured) {
680
+ Logger.info('Required file not found: subjects.json');
681
+ return false;
682
+ }
683
+
684
+ // Check if screens directory exists
685
+ if (!fs.existsSync(screensDir)) {
686
+ return handleMissingScreensDirectory(screensDir, projectIsConfigured);
687
+ }
688
+
689
+ // Check if there are any screen files
690
+ const screenFiles = fs.readdirSync(screensDir).filter((f) => f.endsWith('.json'));
691
+ if (screenFiles.length === 0 && !projectIsConfigured) {
692
+ Logger.info('No screen JSON files found in seed-data/screen-schemas');
693
+ return false;
694
+ }
695
+
696
+ return true;
697
+ } catch (error) {
698
+ Logger.error('Model data verification error:', error);
699
+ return false;
700
+ }
701
+ }
702
+
703
+ /**
704
+ * Handle missing seed-data directory
705
+ * @param {string} seedDataDir - Path to seed-data directory
706
+ * @param {string} screensDir - Path to screens directory
707
+ * @param {boolean} projectIsConfigured - Whether project is configured
708
+ * @returns {boolean} True if handled successfully, false otherwise
709
+ */
710
+ function handleMissingSeedDataDirectory(seedDataDir, screensDir, projectIsConfigured) {
711
+ if (projectIsConfigured) {
712
+ try {
713
+ fs.mkdirSync(seedDataDir, { recursive: true });
714
+ fs.mkdirSync(screensDir, { recursive: true });
715
+ Logger.info('Created seed-data directory structure.');
716
+ return true;
717
+ } catch (error) {
718
+ Logger.error('Failed to create seed-data directory:', error);
719
+ return false;
720
+ }
721
+ }
722
+
723
+ Logger.info('No seed-data directory found.');
724
+ return false;
725
+ }
726
+
727
+ /**
728
+ * Handle missing screens directory
729
+ * @param {string} screensDir - Path to screens directory
730
+ * @param {boolean} projectIsConfigured - Whether project is configured
731
+ * @returns {boolean} True if handled successfully, false otherwise
732
+ */
733
+ function handleMissingScreensDirectory(screensDir, projectIsConfigured) {
734
+ if (projectIsConfigured) {
735
+ try {
736
+ fs.mkdirSync(screensDir, { recursive: true });
737
+ Logger.info('Created screen-schemas directory.');
738
+ return true;
739
+ } catch (error) {
740
+ Logger.error('Failed to create screen-schemas directory:', error);
741
+ return false;
742
+ }
743
+ }
744
+
745
+ Logger.info('No screen-schemas directory found.');
746
+ return false;
747
+ }
748
+
749
+ /**
750
+ * Check if project is configured with valid credentials
751
+ * @returns {Promise<boolean>} True if project is configured, false otherwise
752
+ */
753
+ async function isConfigured() {
754
+ try {
755
+ if (typeof indiciumApi.canConnect === 'function') {
756
+ return await indiciumApi.canConnect();
757
+ }
758
+ if (typeof indiciumApi.canConnectToProject === 'function') {
759
+ return await indiciumApi.canConnectToProject();
760
+ }
761
+ Logger.warn('No valid connection check found on indiciumApi.');
762
+ return false;
763
+ } catch (error) {
764
+ Logger.error('Configuration check failed:', error);
765
+ return false;
766
+ }
767
+ }