create-appraisejs 0.1.10-alpha.8 → 0.2.0-alpha.1

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 (414) hide show
  1. package/README.md +52 -52
  2. package/dist/cli.e2e.test.d.ts +2 -0
  3. package/dist/cli.e2e.test.d.ts.map +1 -0
  4. package/dist/cli.e2e.test.js +73 -0
  5. package/dist/cli.e2e.test.js.map +1 -0
  6. package/dist/config.test.d.ts +2 -0
  7. package/dist/config.test.d.ts.map +1 -0
  8. package/dist/config.test.js +65 -0
  9. package/dist/config.test.js.map +1 -0
  10. package/dist/copy-template.test.d.ts +2 -0
  11. package/dist/copy-template.test.d.ts.map +1 -0
  12. package/dist/copy-template.test.js +71 -0
  13. package/dist/copy-template.test.js.map +1 -0
  14. package/dist/download-repo.test.d.ts +2 -0
  15. package/dist/download-repo.test.d.ts.map +1 -0
  16. package/dist/download-repo.test.js +14 -0
  17. package/dist/download-repo.test.js.map +1 -0
  18. package/dist/install.test.d.ts +2 -0
  19. package/dist/install.test.d.ts.map +1 -0
  20. package/dist/install.test.js +119 -0
  21. package/dist/install.test.js.map +1 -0
  22. package/dist/prepare-template-utils.d.ts.map +1 -1
  23. package/dist/prepare-template-utils.js +5 -0
  24. package/dist/prepare-template-utils.js.map +1 -1
  25. package/dist/prompts.test.d.ts +2 -0
  26. package/dist/prompts.test.d.ts.map +1 -0
  27. package/dist/prompts.test.js +58 -0
  28. package/dist/prompts.test.js.map +1 -0
  29. package/dist/scaffold-gitignore.d.ts +1 -0
  30. package/dist/scaffold-gitignore.d.ts.map +1 -1
  31. package/dist/scaffold-gitignore.js +3 -0
  32. package/dist/scaffold-gitignore.js.map +1 -1
  33. package/package.json +3 -2
  34. package/templates/default/.appraise-template-meta.json +2 -2
  35. package/templates/default/.env.example +2 -2
  36. package/templates/default/.vscode/settings.json +2 -1
  37. package/templates/default/README.md +53 -53
  38. package/templates/default/automation/mapping/locator-map.json +1 -14
  39. package/templates/default/automation/steps/actions/click.step.ts +58 -58
  40. package/templates/default/automation/steps/actions/hover.step.ts +27 -27
  41. package/templates/default/automation/steps/actions/input.step.ts +84 -84
  42. package/templates/default/automation/steps/actions/navigation.step.ts +70 -70
  43. package/templates/default/automation/steps/actions/random_data.step.ts +142 -142
  44. package/templates/default/automation/steps/actions/store.step.ts +86 -86
  45. package/templates/default/automation/steps/actions/wait.step.ts +90 -90
  46. package/templates/default/automation/steps/validations/active_state_assertion.step.ts +30 -30
  47. package/templates/default/automation/steps/validations/navigation_assertion.step.ts +22 -22
  48. package/templates/default/automation/steps/validations/text_assertion.step.ts +107 -107
  49. package/templates/default/automation/steps/validations/visibility_assertion.step.ts +26 -26
  50. package/templates/default/components.json +24 -24
  51. package/templates/default/cucumber.mjs +16 -16
  52. package/templates/default/eslint.config.mjs +16 -16
  53. package/templates/default/gitignore +3 -0
  54. package/templates/default/next-env.d.ts +6 -6
  55. package/templates/default/next.config.ts +11 -11
  56. package/templates/default/package-lock.json +146 -75
  57. package/templates/default/package.json +2 -1
  58. package/templates/default/packages/cucumber-runtime/package.json +13 -13
  59. package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -93
  60. package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -68
  61. package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -21
  62. package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -32
  63. package/templates/default/packages/cucumber-runtime/src/hooks.ts +25 -3
  64. package/templates/default/packages/cucumber-runtime/src/index.ts +17 -17
  65. package/templates/default/packages/cucumber-runtime/src/locator.util.ts +234 -234
  66. package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -7
  67. package/templates/default/packages/cucumber-runtime/src/paths.ts +66 -1
  68. package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -35
  69. package/templates/default/packages/cucumber-runtime/src/types.ts +13 -13
  70. package/templates/default/packages/cucumber-runtime/src/world.ts +44 -44
  71. package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -11
  72. package/templates/default/packages/locator-picker-companion/dist/cli.d.ts +1 -0
  73. package/templates/default/packages/locator-picker-companion/dist/cli.js +302 -0
  74. package/templates/default/packages/locator-picker-companion/dist/index.d.ts +3 -0
  75. package/templates/default/packages/locator-picker-companion/dist/index.js +3 -0
  76. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.d.ts +1 -0
  77. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.js +615 -0
  78. package/templates/default/packages/locator-picker-companion/dist/launcher.d.ts +11 -0
  79. package/templates/default/packages/locator-picker-companion/dist/launcher.js +59 -0
  80. package/templates/default/packages/locator-picker-companion/dist/selector-generator.d.ts +3 -0
  81. package/templates/default/packages/locator-picker-companion/dist/selector-generator.js +257 -0
  82. package/templates/default/packages/locator-picker-companion/dist/session-file.d.ts +22 -0
  83. package/templates/default/packages/locator-picker-companion/dist/session-file.js +150 -0
  84. package/templates/default/packages/locator-picker-companion/dist/types.d.ts +31 -0
  85. package/templates/default/packages/locator-picker-companion/dist/types.js +1 -0
  86. package/templates/default/packages/locator-picker-companion/package.json +17 -0
  87. package/templates/default/packages/locator-picker-companion/src/cli.ts +428 -0
  88. package/templates/default/packages/locator-picker-companion/src/index.ts +3 -0
  89. package/templates/default/packages/locator-picker-companion/src/injected-picker-script.ts +771 -0
  90. package/templates/default/packages/locator-picker-companion/src/launcher.ts +77 -0
  91. package/templates/default/packages/locator-picker-companion/src/selector-generator.ts +339 -0
  92. package/templates/default/packages/locator-picker-companion/src/session-file.ts +226 -0
  93. package/templates/default/packages/locator-picker-companion/src/types.ts +42 -0
  94. package/templates/default/packages/locator-picker-companion/tsconfig.json +14 -0
  95. package/templates/default/postcss.config.mjs +8 -8
  96. package/templates/default/prisma/dev.db +0 -0
  97. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
  98. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
  99. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
  100. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
  101. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
  102. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
  103. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
  104. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
  105. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
  106. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
  107. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
  108. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
  109. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
  110. package/templates/default/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -0
  111. package/templates/default/prisma/migrations/20260318120000_add_test_suite_context_to_test_run_test_case/migration.sql +20 -0
  112. package/templates/default/prisma/migrations/20260318173512_add_support_of_test_suite_level_runs/migration.sql +2 -0
  113. package/templates/default/prisma/schema.prisma +4 -0
  114. package/templates/default/scripts/install-playwright.ts +20 -0
  115. package/templates/default/scripts/protect-seeded-files.ts +38 -38
  116. package/templates/default/scripts/regenerate-features.ts +94 -94
  117. package/templates/default/scripts/setup-env.ts +19 -19
  118. package/templates/default/scripts/sync-all.ts +22 -66
  119. package/templates/default/scripts/sync-appraise-base-template.ts +57 -19
  120. package/templates/default/scripts/sync-environments.ts +40 -21
  121. package/templates/default/scripts/sync-locator-groups.ts +410 -410
  122. package/templates/default/scripts/sync-locators.ts +398 -398
  123. package/templates/default/scripts/sync-modules.ts +341 -341
  124. package/templates/default/scripts/sync-tags.ts +2 -3
  125. package/templates/default/scripts/sync-template-step-groups.ts +0 -3
  126. package/templates/default/scripts/sync-template-steps.ts +37 -11
  127. package/templates/default/scripts/sync-test-cases.ts +68 -16
  128. package/templates/default/scripts/sync-test-suites.ts +7 -5
  129. package/templates/default/src/actions/conflict/conflict.action.ts +33 -33
  130. package/templates/default/src/actions/dashboard/dashboard-actions.ts +240 -240
  131. package/templates/default/src/actions/environments/environment-actions.ts +188 -188
  132. package/templates/default/src/actions/locator/locator-actions.ts +490 -490
  133. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +6 -3
  134. package/templates/default/src/actions/locator-picker/locator-picker-actions.ts +243 -0
  135. package/templates/default/src/actions/modules/module-actions.ts +135 -135
  136. package/templates/default/src/actions/reports/report-actions.ts +51 -49
  137. package/templates/default/src/actions/review/review-actions.ts +147 -147
  138. package/templates/default/src/actions/settings/sync-actions.ts +46 -0
  139. package/templates/default/src/actions/tags/tag-actions.ts +107 -107
  140. package/templates/default/src/actions/template-step/template-step-actions.ts +38 -4
  141. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +10 -3
  142. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -238
  143. package/templates/default/src/actions/test-case/test-case-actions.ts +356 -356
  144. package/templates/default/src/actions/test-run/test-run-actions.ts +222 -129
  145. package/templates/default/src/actions/test-suite/test-suite-actions.ts +95 -17
  146. package/templates/default/src/actions/user/user-actions.ts +13 -13
  147. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
  148. package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -219
  149. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
  150. package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -24
  151. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -46
  152. package/templates/default/src/app/(base)/environments/page.tsx +59 -59
  153. package/templates/default/src/app/(base)/layout.tsx +10 -10
  154. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
  155. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
  156. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
  157. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
  158. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
  159. package/templates/default/src/app/(base)/locator-groups/page.tsx +20 -8
  160. package/templates/default/src/app/(base)/locators/create/create-locator-workspace.tsx +542 -0
  161. package/templates/default/src/app/(base)/locators/create/page.tsx +32 -19
  162. package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -163
  163. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +73 -73
  164. package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -28
  165. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +56 -27
  166. package/templates/default/src/app/(base)/locators/page.tsx +13 -13
  167. package/templates/default/src/app/(base)/locators/picker/page.tsx +7 -0
  168. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -66
  169. package/templates/default/src/app/(base)/modules/create/page.tsx +34 -34
  170. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -46
  171. package/templates/default/src/app/(base)/modules/module-form.tsx +126 -126
  172. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -85
  173. package/templates/default/src/app/(base)/modules/module-table.tsx +24 -24
  174. package/templates/default/src/app/(base)/modules/page.tsx +59 -59
  175. package/templates/default/src/app/(base)/reports/[id]/page.tsx +40 -33
  176. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
  177. package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -78
  178. package/templates/default/src/app/(base)/reports/overview-chart.tsx +49 -46
  179. package/templates/default/src/app/(base)/reports/page.tsx +98 -98
  180. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +78 -16
  181. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
  182. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
  183. package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +12 -1
  184. package/templates/default/src/app/(base)/reports/report-view-table.tsx +1 -0
  185. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +40 -42
  186. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
  187. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
  188. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
  189. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
  190. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
  191. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +58 -60
  192. package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -26
  193. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -15
  194. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -26
  195. package/templates/default/src/app/(base)/reviews/page.tsx +26 -26
  196. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -26
  197. package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -11
  198. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -9
  199. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -9
  200. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -15
  201. package/templates/default/src/app/(base)/settings/page.tsx +30 -0
  202. package/templates/default/src/app/(base)/settings/settings-sync-panel.tsx +261 -0
  203. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
  204. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
  205. package/templates/default/src/app/(base)/tags/page.tsx +58 -58
  206. package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -147
  207. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
  208. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
  209. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
  210. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -45
  211. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
  212. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -167
  213. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
  214. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -32
  215. package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -37
  216. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -49
  217. package/templates/default/src/app/(base)/template-steps/page.tsx +58 -58
  218. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -213
  219. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -384
  220. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -158
  221. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -24
  222. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
  223. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
  224. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
  225. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -84
  226. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -262
  227. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
  228. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
  229. package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -76
  230. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -96
  231. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -38
  232. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -73
  233. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
  234. package/templates/default/src/app/(base)/test-cases/page.tsx +2 -2
  235. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -82
  236. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -395
  237. package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +6 -71
  238. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +2 -2
  239. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +4 -1
  240. package/templates/default/src/app/(base)/test-runs/create/page.tsx +11 -9
  241. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
  242. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +37 -44
  243. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
  244. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
  245. package/templates/default/src/app/(base)/test-suites/create/page.tsx +3 -2
  246. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +2 -1
  247. package/templates/default/src/app/(base)/test-suites/page.tsx +57 -21
  248. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +16 -17
  249. package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +2 -1
  250. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -29
  251. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
  252. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
  253. package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
  254. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
  255. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
  256. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
  257. package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.ts +52 -0
  258. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +97 -34
  259. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +4 -1
  260. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -147
  261. package/templates/default/src/app/globals.css +147 -147
  262. package/templates/default/src/app/layout.tsx +4 -0
  263. package/templates/default/src/app/page.tsx +2 -0
  264. package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
  265. package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
  266. package/templates/default/src/components/base-node.tsx +21 -21
  267. package/templates/default/src/components/chart/pie-chart.tsx +73 -73
  268. package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -460
  269. package/templates/default/src/components/data-state/empty-state.tsx +40 -40
  270. package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
  271. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
  272. package/templates/default/src/components/diagram/button-edge.tsx +54 -54
  273. package/templates/default/src/components/diagram/dynamic-parameters.tsx +474 -474
  274. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
  275. package/templates/default/src/components/diagram/flow-diagram.tsx +470 -470
  276. package/templates/default/src/components/diagram/node-form.tsx +262 -262
  277. package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
  278. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
  279. package/templates/default/src/components/form/error-message.tsx +7 -7
  280. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
  281. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
  282. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
  283. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
  284. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
  285. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
  286. package/templates/default/src/components/logo.tsx +15 -15
  287. package/templates/default/src/components/navigation/command-badge.tsx +34 -34
  288. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
  289. package/templates/default/src/components/navigation/entity-search-command.tsx +116 -116
  290. package/templates/default/src/components/navigation/nav-card.tsx +31 -31
  291. package/templates/default/src/components/navigation/nav-command.tsx +13 -0
  292. package/templates/default/src/components/navigation/nav-link.tsx +60 -60
  293. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
  294. package/templates/default/src/components/node-header.tsx +159 -159
  295. package/templates/default/src/components/reports/test-case-logs-modal.tsx +310 -253
  296. package/templates/default/src/components/table/table-actions.tsx +172 -172
  297. package/templates/default/src/components/test-case/test-case-columns.tsx +76 -0
  298. package/templates/default/src/components/test-case/test-case-picker.tsx +244 -0
  299. package/templates/default/src/components/test-run/download-logs-button.tsx +92 -99
  300. package/templates/default/src/components/test-run/log-viewer.tsx +1 -14
  301. package/templates/default/src/components/test-run/test-run-details.tsx +15 -2
  302. package/templates/default/src/components/test-run/test-run-header.tsx +149 -149
  303. package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
  304. package/templates/default/src/components/test-suite/test-suite-picker.tsx +444 -0
  305. package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
  306. package/templates/default/src/components/theme/theme-provider.tsx +8 -8
  307. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
  308. package/templates/default/src/components/typography/page-header.tsx +7 -7
  309. package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
  310. package/templates/default/src/components/ui/alert.tsx +43 -43
  311. package/templates/default/src/components/ui/avatar.tsx +40 -40
  312. package/templates/default/src/components/ui/badge.tsx +29 -29
  313. package/templates/default/src/components/ui/button.tsx +47 -47
  314. package/templates/default/src/components/ui/calendar.tsx +158 -158
  315. package/templates/default/src/components/ui/card.tsx +43 -43
  316. package/templates/default/src/components/ui/checkbox.tsx +28 -28
  317. package/templates/default/src/components/ui/command.tsx +135 -135
  318. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
  319. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
  320. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
  321. package/templates/default/src/components/ui/data-table.tsx +261 -261
  322. package/templates/default/src/components/ui/dialog.tsx +97 -97
  323. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
  324. package/templates/default/src/components/ui/input.tsx +22 -22
  325. package/templates/default/src/components/ui/kbd.tsx +28 -28
  326. package/templates/default/src/components/ui/label.tsx +19 -19
  327. package/templates/default/src/components/ui/loading.tsx +12 -12
  328. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
  329. package/templates/default/src/components/ui/multi-select.tsx +142 -142
  330. package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
  331. package/templates/default/src/components/ui/popover.tsx +33 -33
  332. package/templates/default/src/components/ui/progress.tsx +25 -25
  333. package/templates/default/src/components/ui/radio-group.tsx +44 -44
  334. package/templates/default/src/components/ui/scroll-area.tsx +40 -40
  335. package/templates/default/src/components/ui/select.tsx +151 -151
  336. package/templates/default/src/components/ui/separator.tsx +22 -22
  337. package/templates/default/src/components/ui/skeleton.tsx +7 -7
  338. package/templates/default/src/components/ui/table.tsx +76 -76
  339. package/templates/default/src/components/ui/tabs.tsx +55 -55
  340. package/templates/default/src/components/ui/textarea.tsx +21 -21
  341. package/templates/default/src/components/ui/toast.tsx +113 -113
  342. package/templates/default/src/components/ui/toaster.tsx +26 -26
  343. package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -87
  344. package/templates/default/src/config/db-config.ts +10 -10
  345. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
  346. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
  347. package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -20
  348. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
  349. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
  350. package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -23
  351. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
  352. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
  353. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
  354. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
  355. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
  356. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
  357. package/templates/default/src/constants/form-opts/test-run-form-opts.ts +5 -3
  358. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
  359. package/templates/default/src/hooks/use-toast.ts +187 -187
  360. package/templates/default/src/lib/automation/paths.ts +42 -12
  361. package/templates/default/src/lib/automation/projection-service.ts +2 -1
  362. package/templates/default/src/lib/bidirectional-sync.ts +432 -432
  363. package/templates/default/src/lib/database-sync.ts +2 -7
  364. package/templates/default/src/lib/environment-file-utils.ts +184 -184
  365. package/templates/default/src/lib/executor/local-executor-adapter.ts +24 -25
  366. package/templates/default/src/lib/executor/types.ts +2 -2
  367. package/templates/default/src/lib/feature-file-generator.ts +5 -66
  368. package/templates/default/src/lib/gherkin-parser.ts +259 -259
  369. package/templates/default/src/lib/locator-group-file-utils.ts +307 -307
  370. package/templates/default/src/lib/locator-picker/session-manager.ts +452 -0
  371. package/templates/default/src/lib/locator-picker/suggestions.ts +124 -0
  372. package/templates/default/src/lib/metrics/metric-calculator.ts +5 -0
  373. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
  374. package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
  375. package/templates/default/src/lib/process/task-spawner.ts +39 -2
  376. package/templates/default/src/lib/sync/projected-feature-utils.ts +113 -0
  377. package/templates/default/src/lib/sync/sync-executor.ts +130 -0
  378. package/templates/default/src/lib/sync/sync-pending-counts.test.ts +239 -0
  379. package/templates/default/src/lib/sync/sync-pending-counts.ts +1358 -0
  380. package/templates/default/src/lib/sync/sync-registry.ts +149 -0
  381. package/templates/default/src/lib/tag-utils.ts +28 -0
  382. package/templates/default/src/lib/template-sync-utils.d.ts +6 -6
  383. package/templates/default/src/lib/template-sync-utils.js +46 -46
  384. package/templates/default/src/lib/template-sync-utils.ts +63 -63
  385. package/templates/default/src/lib/test-case-utils.ts +6 -6
  386. package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
  387. package/templates/default/src/lib/test-run/matching.ts +117 -0
  388. package/templates/default/src/lib/test-run/process-manager.ts +2 -0
  389. package/templates/default/src/lib/test-run/report-parser.ts +352 -316
  390. package/templates/default/src/lib/test-run/test-run-executor.ts +13 -15
  391. package/templates/default/src/lib/test-run/winston-logger.ts +64 -66
  392. package/templates/default/src/lib/test-suite-identifier-service.ts +76 -0
  393. package/templates/default/src/lib/test-suite-utils.ts +6 -0
  394. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
  395. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
  396. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
  397. package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
  398. package/templates/default/src/lib/utils/template-step-file-generator.ts +104 -104
  399. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +0 -8
  400. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
  401. package/templates/default/src/lib/utils.ts +31 -31
  402. package/templates/default/src/types/diagram/diagram.ts +34 -34
  403. package/templates/default/src/types/diagram/template-step.ts +11 -11
  404. package/templates/default/src/types/executor/browser.type.ts +1 -1
  405. package/templates/default/src/types/form/actionHandler.ts +6 -6
  406. package/templates/default/src/types/locator/locator.type.ts +11 -11
  407. package/templates/default/src/types/locator-picker.ts +65 -0
  408. package/templates/default/src/types/step/step.type.ts +1 -1
  409. package/templates/default/src/types/table/data-table.ts +6 -6
  410. package/templates/default/src/types/test-case-picker.ts +6 -0
  411. package/templates/default/src/types/test-suite-picker.ts +18 -0
  412. package/templates/default/tailwind.config.ts +62 -62
  413. package/templates/default/tsconfig.json +4 -1
  414. package/templates/default/appraisejs.config.json +0 -1
@@ -1,398 +1,398 @@
1
- #!/usr/bin/env tsx
2
-
3
- /**
4
- * Script to synchronize locators from filesystem to database
5
- * Scans locator JSON files to ensure all locators exist in DB
6
- * Filesystem is the source of truth - locators in DB but not in FS will be deleted
7
- * Run this after merging changes to ensure locator sync
8
- *
9
- * Usage: npx tsx scripts/sync-locators.ts
10
- */
11
-
12
- import { promises as fs } from 'fs'
13
- import { join } from 'path'
14
- import { glob } from 'glob'
15
- import prisma from '../src/config/db-config'
16
- import { buildModuleHierarchy } from '../src/lib/module-hierarchy-builder'
17
- import { getLocatorGroupFilePath } from '../src/lib/locator-group-file-utils'
18
- import { extractModulePathFromAutomationFile } from '../src/lib/template-sync-utils'
19
-
20
- interface SyncResult {
21
- locatorsScanned: number
22
- locatorsExisting: number
23
- locatorsCreated: number
24
- locatorsDeleted: number
25
- locatorsUpdated: number
26
- locatorGroupsDeleted: number
27
- errors: string[]
28
- createdLocators: Array<{ name: string; group: string }>
29
- deletedLocators: Array<{ name: string; group: string }>
30
- updatedLocators: Array<{ name: string; group: string }>
31
- deletedLocatorGroups: Array<{ name: string; locatorCount: number }>
32
- }
33
-
34
- /**
35
- * Scans locator directory for all JSON files
36
- */
37
- async function scanLocatorFiles(baseDir: string): Promise<string[]> {
38
- const pattern = 'automation/locators/**/*.json'
39
- try {
40
- const files = await glob(pattern, {
41
- cwd: baseDir,
42
- })
43
- return files.map(file => join(baseDir, file))
44
- } catch (error) {
45
- throw new Error(`Error scanning locator files: ${error}`)
46
- }
47
- }
48
-
49
- /**
50
- * Extracts module path from locator file path
51
- * Example: automation/locators/users/admins/directors/directors.json -> /users/admins/directors
52
- */
53
- function extractModulePathFromLocatorFile(filePath: string, baseDir: string): string {
54
- return extractModulePathFromAutomationFile(filePath, baseDir, 'locators')
55
- }
56
-
57
- /**
58
- * Extracts locator group name from file path
59
- * The group name is just the filename without extension
60
- */
61
- function extractLocatorGroupName(filePath: string): string {
62
- const fileName = filePath.split(/[/\\]/).pop() || ''
63
- return fileName.replace('.json', '')
64
- }
65
-
66
- /**
67
- * Reads and parses a locator JSON file
68
- */
69
- async function readLocatorFile(filePath: string): Promise<Record<string, string>> {
70
- try {
71
- await fs.access(filePath)
72
- } catch {
73
- throw new Error(`Locator file not found at ${filePath}`)
74
- }
75
-
76
- try {
77
- const fileContent = await fs.readFile(filePath, 'utf-8')
78
- const jsonContent = JSON.parse(fileContent) as Record<string, string>
79
-
80
- if (!jsonContent || typeof jsonContent !== 'object') {
81
- throw new Error('Invalid JSON structure: expected an object')
82
- }
83
-
84
- return jsonContent
85
- } catch (error) {
86
- if (error instanceof SyntaxError) {
87
- throw new Error(`Invalid JSON in locator file: ${error.message}`)
88
- }
89
- throw error
90
- }
91
- }
92
-
93
- /**
94
- * Finds or creates a LocatorGroup
95
- */
96
- async function findOrCreateLocatorGroup(
97
- groupName: string,
98
- moduleId: string,
99
- _modulePath: string,
100
- ): Promise<string> {
101
- // Try to find existing locator group
102
- const existingGroup = await prisma.locatorGroup.findFirst({
103
- where: {
104
- name: groupName,
105
- moduleId: moduleId,
106
- },
107
- })
108
-
109
- if (existingGroup) {
110
- return existingGroup.id
111
- }
112
-
113
- // Create new locator group
114
- const newGroup = await prisma.locatorGroup.create({
115
- data: {
116
- name: groupName,
117
- route: `/${groupName}`,
118
- moduleId: moduleId,
119
- },
120
- })
121
-
122
- return newGroup.id
123
- }
124
-
125
- /**
126
- * Syncs locators from a single file to database
127
- */
128
- async function syncLocatorsFromFile(
129
- filePath: string,
130
- baseDir: string,
131
- result: SyncResult,
132
- processedGroupIds: Set<string>,
133
- ): Promise<void> {
134
- try {
135
- // Extract module path and group name
136
- const modulePath = extractModulePathFromLocatorFile(filePath, baseDir)
137
- const groupName = extractLocatorGroupName(filePath)
138
-
139
- console.log(`\n šŸ“„ Processing file: ${filePath.replace(baseDir, '')}`)
140
- console.log(` Module path: ${modulePath}`)
141
- console.log(` Group name: ${groupName}`)
142
-
143
- // Build module hierarchy
144
- const moduleId = await buildModuleHierarchy(modulePath)
145
-
146
- // Find or create locator group
147
- const locatorGroupId = await findOrCreateLocatorGroup(groupName, moduleId, modulePath)
148
-
149
- // Track this group as processed (has a file)
150
- processedGroupIds.add(locatorGroupId)
151
-
152
- // Read locators from file
153
- const fileLocators = await readLocatorFile(filePath)
154
- const fileLocatorNames = Object.keys(fileLocators)
155
- result.locatorsScanned += fileLocatorNames.length
156
-
157
- console.log(` Found ${fileLocatorNames.length} locator(s) in file`)
158
-
159
- // Get existing locators from database for this group
160
- const dbLocators = await prisma.locator.findMany({
161
- where: { locatorGroupId: locatorGroupId },
162
- select: { id: true, name: true, value: true },
163
- })
164
-
165
- const dbLocatorMap = new Map(dbLocators.map(loc => [loc.name, loc]))
166
-
167
- // Add or update locators from file
168
- for (const [locatorName, locatorValue] of Object.entries(fileLocators)) {
169
- const existingLocator = dbLocatorMap.get(locatorName)
170
-
171
- if (existingLocator) {
172
- // Check if value changed
173
- if (existingLocator.value !== locatorValue) {
174
- // Update locator value (file takes precedence)
175
- await prisma.locator.update({
176
- where: { id: existingLocator.id },
177
- data: { value: locatorValue },
178
- })
179
- result.locatorsUpdated++
180
- result.updatedLocators.push({ name: locatorName, group: groupName })
181
- console.log(` šŸ”„ Updated locator '${locatorName}'`)
182
- } else {
183
- result.locatorsExisting++
184
- }
185
- } else {
186
- // Create new locator
187
- await prisma.locator.create({
188
- data: {
189
- name: locatorName,
190
- value: locatorValue,
191
- locatorGroupId: locatorGroupId,
192
- },
193
- })
194
- result.locatorsCreated++
195
- result.createdLocators.push({ name: locatorName, group: groupName })
196
- console.log(` āž• Created locator '${locatorName}'`)
197
- }
198
- }
199
-
200
- // Delete locators that exist in DB but not in file (FS is source of truth)
201
- for (const dbLocator of dbLocators) {
202
- if (!fileLocatorNames.includes(dbLocator.name)) {
203
- await prisma.locator.delete({
204
- where: { id: dbLocator.id },
205
- })
206
- result.locatorsDeleted++
207
- result.deletedLocators.push({ name: dbLocator.name, group: groupName })
208
- console.log(` šŸ—‘ļø Deleted locator '${dbLocator.name}' (not in file)`)
209
- }
210
- }
211
- } catch (error) {
212
- const errorMsg = `Error syncing locator file ${filePath}: ${error}`
213
- result.errors.push(errorMsg)
214
- console.error(` āŒ ${errorMsg}`)
215
- }
216
- }
217
-
218
- /**
219
- * Deletes locator groups that don't have corresponding files
220
- */
221
- async function deleteOrphanedLocatorGroups(
222
- processedGroupIds: Set<string>,
223
- baseDir: string,
224
- result: SyncResult,
225
- ): Promise<void> {
226
- console.log('\nšŸ” Checking for orphaned locator groups (no file in filesystem)...')
227
-
228
- try {
229
- // Get all locator groups from database
230
- const allLocatorGroups = await prisma.locatorGroup.findMany({
231
- include: {
232
- locators: {
233
- select: { id: true },
234
- },
235
- },
236
- })
237
-
238
- for (const group of allLocatorGroups) {
239
- // Skip if this group was processed (has a file)
240
- if (processedGroupIds.has(group.id)) {
241
- continue
242
- }
243
-
244
- // Check if file exists for this group
245
- const relativeFilePath = await getLocatorGroupFilePath(group.id)
246
- if (!relativeFilePath) {
247
- // Can't determine file path, skip
248
- continue
249
- }
250
-
251
- const fullPath = join(baseDir, relativeFilePath)
252
-
253
- try {
254
- // Check if file exists
255
- await fs.access(fullPath)
256
- // File exists, so this group is valid
257
- } catch {
258
- // File doesn't exist - delete the locator group (cascade will delete locators)
259
- const locatorCount = group.locators.length
260
- await prisma.locatorGroup.delete({
261
- where: { id: group.id },
262
- })
263
- result.locatorGroupsDeleted++
264
- result.deletedLocatorGroups.push({ name: group.name, locatorCount })
265
- console.log(` šŸ—‘ļø Deleted locator group '${group.name}' (${locatorCount} locator(s) deleted)`)
266
- }
267
- }
268
- } catch (error) {
269
- const errorMsg = `Error deleting orphaned locator groups: ${error}`
270
- result.errors.push(errorMsg)
271
- console.error(` āŒ ${errorMsg}`)
272
- }
273
- }
274
-
275
- /**
276
- * Syncs all locators to database
277
- */
278
- async function syncLocatorsToDatabase(files: string[], baseDir: string): Promise<SyncResult> {
279
- const result: SyncResult = {
280
- locatorsScanned: 0,
281
- locatorsExisting: 0,
282
- locatorsCreated: 0,
283
- locatorsDeleted: 0,
284
- locatorsUpdated: 0,
285
- locatorGroupsDeleted: 0,
286
- errors: [],
287
- createdLocators: [],
288
- deletedLocators: [],
289
- updatedLocators: [],
290
- deletedLocatorGroups: [],
291
- }
292
-
293
- // Track which locator groups have files
294
- const processedGroupIds = new Set<string>()
295
-
296
- // Process all files
297
- for (const filePath of files) {
298
- await syncLocatorsFromFile(filePath, baseDir, result, processedGroupIds)
299
- }
300
-
301
- // Delete locator groups that don't have files
302
- await deleteOrphanedLocatorGroups(processedGroupIds, baseDir, result)
303
-
304
- return result
305
- }
306
-
307
- /**
308
- * Generates and displays sync summary
309
- */
310
- function generateSummary(result: SyncResult): void {
311
- console.log('\nšŸ“Š Sync Summary:')
312
- console.log(` šŸ“ Locators scanned: ${result.locatorsScanned}`)
313
- console.log(` āœ… Locators existing: ${result.locatorsExisting}`)
314
- console.log(` āž• Locators created: ${result.locatorsCreated}`)
315
- console.log(` šŸ”„ Locators updated: ${result.locatorsUpdated}`)
316
- console.log(` šŸ—‘ļø Locators deleted: ${result.locatorsDeleted}`)
317
- console.log(` šŸ—‘ļø Locator groups deleted: ${result.locatorGroupsDeleted}`)
318
- console.log(` āŒ Errors: ${result.errors.length}`)
319
-
320
- if (result.createdLocators.length > 0) {
321
- console.log('\n Created locators:')
322
- result.createdLocators.forEach((loc, index) => {
323
- console.log(` ${index + 1}. ${loc.name} (group: ${loc.group})`)
324
- })
325
- }
326
-
327
- if (result.updatedLocators.length > 0) {
328
- console.log('\n Updated locators:')
329
- result.updatedLocators.forEach((loc, index) => {
330
- console.log(` ${index + 1}. ${loc.name} (group: ${loc.group})`)
331
- })
332
- }
333
-
334
- if (result.deletedLocators.length > 0) {
335
- console.log('\n Deleted locators:')
336
- result.deletedLocators.forEach((loc, index) => {
337
- console.log(` ${index + 1}. ${loc.name} (group: ${loc.group})`)
338
- })
339
- }
340
-
341
- if (result.deletedLocatorGroups.length > 0) {
342
- console.log('\n Deleted locator groups:')
343
- result.deletedLocatorGroups.forEach((group, index) => {
344
- console.log(` ${index + 1}. ${group.name} (${group.locatorCount} locator(s) deleted)`)
345
- })
346
- }
347
-
348
- if (result.errors.length > 0) {
349
- console.log('\n Errors:')
350
- result.errors.forEach((error, index) => {
351
- console.log(` ${index + 1}. ${error}`)
352
- })
353
- }
354
- }
355
-
356
- /**
357
- * Main function
358
- */
359
- async function main() {
360
- try {
361
- console.log('šŸ”„ Starting locators sync...')
362
- console.log('This will scan locator JSON files and sync locators to database.')
363
- console.log('Filesystem is the source of truth - locators in DB but not in FS will be deleted.\n')
364
-
365
- const baseDir = process.cwd()
366
-
367
- // Scan locator files
368
- console.log('šŸ“ Scanning automation/locators...')
369
- const files = await scanLocatorFiles(baseDir)
370
- console.log(` Found ${files.length} locator file(s)`)
371
-
372
- if (files.length === 0) {
373
- console.log('\nāš ļø No locator files found. Nothing to sync.')
374
- return
375
- }
376
-
377
- // Sync to database
378
- console.log('\nāœ… Syncing locators to database...')
379
- const result = await syncLocatorsToDatabase(files, baseDir)
380
-
381
- // Generate summary
382
- generateSummary(result)
383
-
384
- if (result.errors.length === 0) {
385
- console.log('\nāœ… Sync completed successfully!')
386
- } else {
387
- console.log('\nāš ļø Sync completed with errors. Please review the errors above.')
388
- process.exit(1)
389
- }
390
- } catch (error) {
391
- console.error('\nāŒ Error during sync:', error)
392
- process.exit(1)
393
- } finally {
394
- await prisma.$disconnect()
395
- }
396
- }
397
-
398
- main()
1
+ #!/usr/bin/env tsx
2
+
3
+ /**
4
+ * Script to synchronize locators from filesystem to database
5
+ * Scans locator JSON files to ensure all locators exist in DB
6
+ * Filesystem is the source of truth - locators in DB but not in FS will be deleted
7
+ * Run this after merging changes to ensure locator sync
8
+ *
9
+ * Usage: npx tsx scripts/sync-locators.ts
10
+ */
11
+
12
+ import { promises as fs } from 'fs'
13
+ import { join } from 'path'
14
+ import { glob } from 'glob'
15
+ import prisma from '../src/config/db-config'
16
+ import { buildModuleHierarchy } from '../src/lib/module-hierarchy-builder'
17
+ import { getLocatorGroupFilePath } from '../src/lib/locator-group-file-utils'
18
+ import { extractModulePathFromAutomationFile } from '../src/lib/template-sync-utils'
19
+
20
+ interface SyncResult {
21
+ locatorsScanned: number
22
+ locatorsExisting: number
23
+ locatorsCreated: number
24
+ locatorsDeleted: number
25
+ locatorsUpdated: number
26
+ locatorGroupsDeleted: number
27
+ errors: string[]
28
+ createdLocators: Array<{ name: string; group: string }>
29
+ deletedLocators: Array<{ name: string; group: string }>
30
+ updatedLocators: Array<{ name: string; group: string }>
31
+ deletedLocatorGroups: Array<{ name: string; locatorCount: number }>
32
+ }
33
+
34
+ /**
35
+ * Scans locator directory for all JSON files
36
+ */
37
+ async function scanLocatorFiles(baseDir: string): Promise<string[]> {
38
+ const pattern = 'automation/locators/**/*.json'
39
+ try {
40
+ const files = await glob(pattern, {
41
+ cwd: baseDir,
42
+ })
43
+ return files.map(file => join(baseDir, file))
44
+ } catch (error) {
45
+ throw new Error(`Error scanning locator files: ${error}`)
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Extracts module path from locator file path
51
+ * Example: automation/locators/users/admins/directors/directors.json -> /users/admins/directors
52
+ */
53
+ function extractModulePathFromLocatorFile(filePath: string, baseDir: string): string {
54
+ return extractModulePathFromAutomationFile(filePath, baseDir, 'locators')
55
+ }
56
+
57
+ /**
58
+ * Extracts locator group name from file path
59
+ * The group name is just the filename without extension
60
+ */
61
+ function extractLocatorGroupName(filePath: string): string {
62
+ const fileName = filePath.split(/[/\\]/).pop() || ''
63
+ return fileName.replace('.json', '')
64
+ }
65
+
66
+ /**
67
+ * Reads and parses a locator JSON file
68
+ */
69
+ async function readLocatorFile(filePath: string): Promise<Record<string, string>> {
70
+ try {
71
+ await fs.access(filePath)
72
+ } catch {
73
+ throw new Error(`Locator file not found at ${filePath}`)
74
+ }
75
+
76
+ try {
77
+ const fileContent = await fs.readFile(filePath, 'utf-8')
78
+ const jsonContent = JSON.parse(fileContent) as Record<string, string>
79
+
80
+ if (!jsonContent || typeof jsonContent !== 'object') {
81
+ throw new Error('Invalid JSON structure: expected an object')
82
+ }
83
+
84
+ return jsonContent
85
+ } catch (error) {
86
+ if (error instanceof SyntaxError) {
87
+ throw new Error(`Invalid JSON in locator file: ${error.message}`)
88
+ }
89
+ throw error
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Finds or creates a LocatorGroup
95
+ */
96
+ async function findOrCreateLocatorGroup(
97
+ groupName: string,
98
+ moduleId: string,
99
+ _modulePath: string,
100
+ ): Promise<string> {
101
+ // Try to find existing locator group
102
+ const existingGroup = await prisma.locatorGroup.findFirst({
103
+ where: {
104
+ name: groupName,
105
+ moduleId: moduleId,
106
+ },
107
+ })
108
+
109
+ if (existingGroup) {
110
+ return existingGroup.id
111
+ }
112
+
113
+ // Create new locator group
114
+ const newGroup = await prisma.locatorGroup.create({
115
+ data: {
116
+ name: groupName,
117
+ route: `/${groupName}`,
118
+ moduleId: moduleId,
119
+ },
120
+ })
121
+
122
+ return newGroup.id
123
+ }
124
+
125
+ /**
126
+ * Syncs locators from a single file to database
127
+ */
128
+ async function syncLocatorsFromFile(
129
+ filePath: string,
130
+ baseDir: string,
131
+ result: SyncResult,
132
+ processedGroupIds: Set<string>,
133
+ ): Promise<void> {
134
+ try {
135
+ // Extract module path and group name
136
+ const modulePath = extractModulePathFromLocatorFile(filePath, baseDir)
137
+ const groupName = extractLocatorGroupName(filePath)
138
+
139
+ console.log(`\n šŸ“„ Processing file: ${filePath.replace(baseDir, '')}`)
140
+ console.log(` Module path: ${modulePath}`)
141
+ console.log(` Group name: ${groupName}`)
142
+
143
+ // Build module hierarchy
144
+ const moduleId = await buildModuleHierarchy(modulePath)
145
+
146
+ // Find or create locator group
147
+ const locatorGroupId = await findOrCreateLocatorGroup(groupName, moduleId, modulePath)
148
+
149
+ // Track this group as processed (has a file)
150
+ processedGroupIds.add(locatorGroupId)
151
+
152
+ // Read locators from file
153
+ const fileLocators = await readLocatorFile(filePath)
154
+ const fileLocatorNames = Object.keys(fileLocators)
155
+ result.locatorsScanned += fileLocatorNames.length
156
+
157
+ console.log(` Found ${fileLocatorNames.length} locator(s) in file`)
158
+
159
+ // Get existing locators from database for this group
160
+ const dbLocators = await prisma.locator.findMany({
161
+ where: { locatorGroupId: locatorGroupId },
162
+ select: { id: true, name: true, value: true },
163
+ })
164
+
165
+ const dbLocatorMap = new Map(dbLocators.map(loc => [loc.name, loc]))
166
+
167
+ // Add or update locators from file
168
+ for (const [locatorName, locatorValue] of Object.entries(fileLocators)) {
169
+ const existingLocator = dbLocatorMap.get(locatorName)
170
+
171
+ if (existingLocator) {
172
+ // Check if value changed
173
+ if (existingLocator.value !== locatorValue) {
174
+ // Update locator value (file takes precedence)
175
+ await prisma.locator.update({
176
+ where: { id: existingLocator.id },
177
+ data: { value: locatorValue },
178
+ })
179
+ result.locatorsUpdated++
180
+ result.updatedLocators.push({ name: locatorName, group: groupName })
181
+ console.log(` šŸ”„ Updated locator '${locatorName}'`)
182
+ } else {
183
+ result.locatorsExisting++
184
+ }
185
+ } else {
186
+ // Create new locator
187
+ await prisma.locator.create({
188
+ data: {
189
+ name: locatorName,
190
+ value: locatorValue,
191
+ locatorGroupId: locatorGroupId,
192
+ },
193
+ })
194
+ result.locatorsCreated++
195
+ result.createdLocators.push({ name: locatorName, group: groupName })
196
+ console.log(` āž• Created locator '${locatorName}'`)
197
+ }
198
+ }
199
+
200
+ // Delete locators that exist in DB but not in file (FS is source of truth)
201
+ for (const dbLocator of dbLocators) {
202
+ if (!fileLocatorNames.includes(dbLocator.name)) {
203
+ await prisma.locator.delete({
204
+ where: { id: dbLocator.id },
205
+ })
206
+ result.locatorsDeleted++
207
+ result.deletedLocators.push({ name: dbLocator.name, group: groupName })
208
+ console.log(` šŸ—‘ļø Deleted locator '${dbLocator.name}' (not in file)`)
209
+ }
210
+ }
211
+ } catch (error) {
212
+ const errorMsg = `Error syncing locator file ${filePath}: ${error}`
213
+ result.errors.push(errorMsg)
214
+ console.error(` āŒ ${errorMsg}`)
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Deletes locator groups that don't have corresponding files
220
+ */
221
+ async function deleteOrphanedLocatorGroups(
222
+ processedGroupIds: Set<string>,
223
+ baseDir: string,
224
+ result: SyncResult,
225
+ ): Promise<void> {
226
+ console.log('\nšŸ” Checking for orphaned locator groups (no file in filesystem)...')
227
+
228
+ try {
229
+ // Get all locator groups from database
230
+ const allLocatorGroups = await prisma.locatorGroup.findMany({
231
+ include: {
232
+ locators: {
233
+ select: { id: true },
234
+ },
235
+ },
236
+ })
237
+
238
+ for (const group of allLocatorGroups) {
239
+ // Skip if this group was processed (has a file)
240
+ if (processedGroupIds.has(group.id)) {
241
+ continue
242
+ }
243
+
244
+ // Check if file exists for this group
245
+ const relativeFilePath = await getLocatorGroupFilePath(group.id)
246
+ if (!relativeFilePath) {
247
+ // Can't determine file path, skip
248
+ continue
249
+ }
250
+
251
+ const fullPath = join(baseDir, relativeFilePath)
252
+
253
+ try {
254
+ // Check if file exists
255
+ await fs.access(fullPath)
256
+ // File exists, so this group is valid
257
+ } catch {
258
+ // File doesn't exist - delete the locator group (cascade will delete locators)
259
+ const locatorCount = group.locators.length
260
+ await prisma.locatorGroup.delete({
261
+ where: { id: group.id },
262
+ })
263
+ result.locatorGroupsDeleted++
264
+ result.deletedLocatorGroups.push({ name: group.name, locatorCount })
265
+ console.log(` šŸ—‘ļø Deleted locator group '${group.name}' (${locatorCount} locator(s) deleted)`)
266
+ }
267
+ }
268
+ } catch (error) {
269
+ const errorMsg = `Error deleting orphaned locator groups: ${error}`
270
+ result.errors.push(errorMsg)
271
+ console.error(` āŒ ${errorMsg}`)
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Syncs all locators to database
277
+ */
278
+ async function syncLocatorsToDatabase(files: string[], baseDir: string): Promise<SyncResult> {
279
+ const result: SyncResult = {
280
+ locatorsScanned: 0,
281
+ locatorsExisting: 0,
282
+ locatorsCreated: 0,
283
+ locatorsDeleted: 0,
284
+ locatorsUpdated: 0,
285
+ locatorGroupsDeleted: 0,
286
+ errors: [],
287
+ createdLocators: [],
288
+ deletedLocators: [],
289
+ updatedLocators: [],
290
+ deletedLocatorGroups: [],
291
+ }
292
+
293
+ // Track which locator groups have files
294
+ const processedGroupIds = new Set<string>()
295
+
296
+ // Process all files
297
+ for (const filePath of files) {
298
+ await syncLocatorsFromFile(filePath, baseDir, result, processedGroupIds)
299
+ }
300
+
301
+ // Delete locator groups that don't have files
302
+ await deleteOrphanedLocatorGroups(processedGroupIds, baseDir, result)
303
+
304
+ return result
305
+ }
306
+
307
+ /**
308
+ * Generates and displays sync summary
309
+ */
310
+ function generateSummary(result: SyncResult): void {
311
+ console.log('\nšŸ“Š Sync Summary:')
312
+ console.log(` šŸ“ Locators scanned: ${result.locatorsScanned}`)
313
+ console.log(` āœ… Locators existing: ${result.locatorsExisting}`)
314
+ console.log(` āž• Locators created: ${result.locatorsCreated}`)
315
+ console.log(` šŸ”„ Locators updated: ${result.locatorsUpdated}`)
316
+ console.log(` šŸ—‘ļø Locators deleted: ${result.locatorsDeleted}`)
317
+ console.log(` šŸ—‘ļø Locator groups deleted: ${result.locatorGroupsDeleted}`)
318
+ console.log(` āŒ Errors: ${result.errors.length}`)
319
+
320
+ if (result.createdLocators.length > 0) {
321
+ console.log('\n Created locators:')
322
+ result.createdLocators.forEach((loc, index) => {
323
+ console.log(` ${index + 1}. ${loc.name} (group: ${loc.group})`)
324
+ })
325
+ }
326
+
327
+ if (result.updatedLocators.length > 0) {
328
+ console.log('\n Updated locators:')
329
+ result.updatedLocators.forEach((loc, index) => {
330
+ console.log(` ${index + 1}. ${loc.name} (group: ${loc.group})`)
331
+ })
332
+ }
333
+
334
+ if (result.deletedLocators.length > 0) {
335
+ console.log('\n Deleted locators:')
336
+ result.deletedLocators.forEach((loc, index) => {
337
+ console.log(` ${index + 1}. ${loc.name} (group: ${loc.group})`)
338
+ })
339
+ }
340
+
341
+ if (result.deletedLocatorGroups.length > 0) {
342
+ console.log('\n Deleted locator groups:')
343
+ result.deletedLocatorGroups.forEach((group, index) => {
344
+ console.log(` ${index + 1}. ${group.name} (${group.locatorCount} locator(s) deleted)`)
345
+ })
346
+ }
347
+
348
+ if (result.errors.length > 0) {
349
+ console.log('\n Errors:')
350
+ result.errors.forEach((error, index) => {
351
+ console.log(` ${index + 1}. ${error}`)
352
+ })
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Main function
358
+ */
359
+ async function main() {
360
+ try {
361
+ console.log('šŸ”„ Starting locators sync...')
362
+ console.log('This will scan locator JSON files and sync locators to database.')
363
+ console.log('Filesystem is the source of truth - locators in DB but not in FS will be deleted.\n')
364
+
365
+ const baseDir = process.cwd()
366
+
367
+ // Scan locator files
368
+ console.log('šŸ“ Scanning automation/locators...')
369
+ const files = await scanLocatorFiles(baseDir)
370
+ console.log(` Found ${files.length} locator file(s)`)
371
+
372
+ if (files.length === 0) {
373
+ console.log('\nāš ļø No locator files found. Nothing to sync.')
374
+ return
375
+ }
376
+
377
+ // Sync to database
378
+ console.log('\nāœ… Syncing locators to database...')
379
+ const result = await syncLocatorsToDatabase(files, baseDir)
380
+
381
+ // Generate summary
382
+ generateSummary(result)
383
+
384
+ if (result.errors.length === 0) {
385
+ console.log('\nāœ… Sync completed successfully!')
386
+ } else {
387
+ console.log('\nāš ļø Sync completed with errors. Please review the errors above.')
388
+ process.exit(1)
389
+ }
390
+ } catch (error) {
391
+ console.error('\nāŒ Error during sync:', error)
392
+ process.exit(1)
393
+ } finally {
394
+ await prisma.$disconnect()
395
+ }
396
+ }
397
+
398
+ main()