create-appraisejs 0.2.0 → 0.3.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/README.md +12 -1
  2. package/package.json +1 -1
  3. package/templates/default/.appraise-template-meta.json +2 -2
  4. package/templates/default/.env.example +2 -2
  5. package/templates/default/README.md +57 -53
  6. package/templates/default/automation/steps/actions/click.step.ts +58 -58
  7. package/templates/default/automation/steps/actions/hover.step.ts +27 -27
  8. package/templates/default/automation/steps/actions/navigation.step.ts +70 -70
  9. package/templates/default/automation/steps/actions/random_data.step.ts +142 -142
  10. package/templates/default/automation/steps/actions/store.step.ts +86 -86
  11. package/templates/default/automation/steps/actions/wait.step.ts +110 -90
  12. package/templates/default/automation/steps/validations/active_state_assertion.step.ts +30 -30
  13. package/templates/default/automation/steps/validations/navigation_assertion.step.ts +22 -22
  14. package/templates/default/automation/steps/validations/text_assertion.step.ts +107 -107
  15. package/templates/default/automation/steps/validations/visibility_assertion.step.ts +26 -26
  16. package/templates/default/components.json +24 -24
  17. package/templates/default/cucumber.mjs +16 -16
  18. package/templates/default/eslint.config.mjs +20 -16
  19. package/templates/default/next-env.d.ts +6 -6
  20. package/templates/default/next.config.ts +20 -11
  21. package/templates/default/package-lock.json +1775 -74
  22. package/templates/default/package.json +8 -1
  23. package/templates/default/packages/cucumber-runtime/package.json +13 -13
  24. package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -93
  25. package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -68
  26. package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -21
  27. package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -32
  28. package/templates/default/packages/cucumber-runtime/src/index.ts +17 -17
  29. package/templates/default/packages/cucumber-runtime/src/locator.util.ts +234 -234
  30. package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -7
  31. package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -35
  32. package/templates/default/packages/cucumber-runtime/src/types.ts +13 -13
  33. package/templates/default/packages/cucumber-runtime/src/world.ts +44 -44
  34. package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -11
  35. package/templates/default/postcss.config.mjs +8 -8
  36. package/templates/default/prisma/dev.db +0 -0
  37. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
  38. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
  39. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
  40. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
  41. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
  42. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
  43. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
  44. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
  45. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
  46. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
  47. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
  48. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
  49. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
  50. package/templates/default/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -1
  51. package/templates/default/scripts/build-step-registry.ts +33 -0
  52. package/templates/default/scripts/install-playwright.ts +17 -8
  53. package/templates/default/scripts/install-template-step.ts +128 -0
  54. package/templates/default/scripts/lib/filename-utils.test.ts +24 -0
  55. package/templates/default/scripts/lib/filename-utils.ts +24 -0
  56. package/templates/default/scripts/lib/jsdoc-parser.test.ts +63 -0
  57. package/templates/default/scripts/lib/jsdoc-parser.ts +88 -0
  58. package/templates/default/scripts/lib/step-file-parser.test.ts +71 -0
  59. package/templates/default/scripts/lib/step-file-parser.ts +315 -0
  60. package/templates/default/scripts/lib/step-matcher.test.ts +86 -0
  61. package/templates/default/scripts/lib/step-matcher.ts +120 -0
  62. package/templates/default/scripts/lib/sync-script-runner.ts +23 -0
  63. package/templates/default/scripts/lib/sync-summary.ts +29 -0
  64. package/templates/default/scripts/lib/tag-parsing.test.ts +20 -0
  65. package/templates/default/scripts/lib/tag-parsing.ts +10 -0
  66. package/templates/default/scripts/lib/template-step-installer.test.ts +225 -0
  67. package/templates/default/scripts/lib/template-step-installer.ts +404 -0
  68. package/templates/default/scripts/lib/template-step-registry.test.ts +118 -0
  69. package/templates/default/scripts/lib/template-step-registry.ts +137 -0
  70. package/templates/default/scripts/protect-seeded-files.ts +51 -38
  71. package/templates/default/scripts/regenerate-features.ts +98 -94
  72. package/templates/default/scripts/run-vitest.ts +59 -0
  73. package/templates/default/scripts/setup-env.ts +26 -19
  74. package/templates/default/scripts/sync-all.ts +44 -54
  75. package/templates/default/scripts/sync-appraise-base-template.ts +44 -16
  76. package/templates/default/scripts/sync-environments.ts +22 -66
  77. package/templates/default/scripts/sync-locator-groups.ts +358 -410
  78. package/templates/default/scripts/sync-locators.ts +348 -398
  79. package/templates/default/scripts/sync-modules.ts +302 -341
  80. package/templates/default/scripts/sync-tags.ts +29 -65
  81. package/templates/default/scripts/sync-template-step-groups.ts +24 -182
  82. package/templates/default/scripts/sync-template-steps.ts +36 -493
  83. package/templates/default/scripts/sync-test-cases.ts +296 -539
  84. package/templates/default/scripts/sync-test-suites.ts +32 -79
  85. package/templates/default/src/actions/dashboard/dashboard-actions.ts +70 -241
  86. package/templates/default/src/actions/environments/environment-actions.ts +102 -188
  87. package/templates/default/src/actions/locator/locator-actions.ts +77 -490
  88. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +34 -212
  89. package/templates/default/src/actions/locator-picker/locator-picker-actions.test.ts +81 -0
  90. package/templates/default/src/actions/locator-picker/locator-picker-actions.ts +20 -161
  91. package/templates/default/src/actions/modules/module-actions.ts +99 -135
  92. package/templates/default/src/actions/reports/report-actions.ts +28 -565
  93. package/templates/default/src/actions/settings/sync-actions.test.ts +58 -0
  94. package/templates/default/src/actions/tags/tag-actions.ts +99 -107
  95. package/templates/default/src/actions/template-step/template-step-actions.ts +33 -194
  96. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +35 -92
  97. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +98 -238
  98. package/templates/default/src/actions/test-case/test-case-actions.ts +108 -356
  99. package/templates/default/src/actions/test-run/test-run-actions.ts +74 -1081
  100. package/templates/default/src/actions/test-suite/test-suite-actions.ts +35 -202
  101. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
  102. package/templates/default/src/app/(base)/environments/environment-form.test.tsx +92 -0
  103. package/templates/default/src/app/(base)/environments/environment-form.tsx +228 -219
  104. package/templates/default/src/app/(base)/environments/environment-helpers.ts +58 -0
  105. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
  106. package/templates/default/src/app/(base)/environments/environment-table.tsx +25 -24
  107. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +49 -46
  108. package/templates/default/src/app/(base)/environments/page.tsx +59 -59
  109. package/templates/default/src/app/(base)/layout.tsx +10 -10
  110. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
  111. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
  112. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
  113. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
  114. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
  115. package/templates/default/src/app/(base)/locators/create/create-locator-workspace-helpers.test.ts +71 -0
  116. package/templates/default/src/app/(base)/locators/create/create-locator-workspace-helpers.ts +333 -0
  117. package/templates/default/src/app/(base)/locators/create/create-locator-workspace.test.tsx +125 -0
  118. package/templates/default/src/app/(base)/locators/create/create-locator-workspace.tsx +56 -274
  119. package/templates/default/src/app/(base)/locators/create/page.tsx +8 -4
  120. package/templates/default/src/app/(base)/locators/create/use-locator-workspace.ts +183 -0
  121. package/templates/default/src/app/(base)/locators/locator-helpers.test.ts +28 -0
  122. package/templates/default/src/app/(base)/locators/locator-helpers.ts +59 -0
  123. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +74 -73
  124. package/templates/default/src/app/(base)/locators/locator-table.tsx +30 -28
  125. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +20 -8
  126. package/templates/default/src/app/(base)/locators/page.tsx +3 -6
  127. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +67 -66
  128. package/templates/default/src/app/(base)/modules/create/page.tsx +33 -34
  129. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +43 -46
  130. package/templates/default/src/app/(base)/modules/module-form.test.tsx +84 -0
  131. package/templates/default/src/app/(base)/modules/module-form.tsx +159 -126
  132. package/templates/default/src/app/(base)/modules/module-helpers.ts +64 -0
  133. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +81 -85
  134. package/templates/default/src/app/(base)/modules/module-table.tsx +25 -24
  135. package/templates/default/src/app/(base)/modules/page.tsx +59 -59
  136. package/templates/default/src/app/(base)/reports/[id]/page.tsx +20 -260
  137. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
  138. package/templates/default/src/app/(base)/reports/feature-chart.tsx +79 -78
  139. package/templates/default/src/app/(base)/reports/overview-chart.tsx +49 -49
  140. package/templates/default/src/app/(base)/reports/page.tsx +98 -98
  141. package/templates/default/src/app/(base)/reports/report-detail-helpers.test.ts +109 -0
  142. package/templates/default/src/app/(base)/reports/report-detail-helpers.ts +247 -0
  143. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +78 -78
  144. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
  145. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
  146. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +40 -40
  147. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
  148. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
  149. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
  150. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
  151. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
  152. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +58 -58
  153. package/templates/default/src/app/(base)/settings/settings-sync-panel-helpers.test.tsx +40 -0
  154. package/templates/default/src/app/(base)/settings/settings-sync-panel-helpers.tsx +110 -0
  155. package/templates/default/src/app/(base)/settings/settings-sync-panel.test.tsx +127 -0
  156. package/templates/default/src/app/(base)/settings/settings-sync-panel.tsx +19 -134
  157. package/templates/default/src/app/(base)/settings/use-settings-sync.ts +66 -0
  158. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
  159. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
  160. package/templates/default/src/app/(base)/tags/page.tsx +58 -58
  161. package/templates/default/src/app/(base)/tags/tag-form-helpers.ts +13 -0
  162. package/templates/default/src/app/(base)/tags/tag-form.test.tsx +83 -0
  163. package/templates/default/src/app/(base)/tags/tag-form.tsx +143 -147
  164. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
  165. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
  166. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
  167. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +43 -45
  168. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
  169. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.test.tsx +82 -0
  170. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +181 -167
  171. package/templates/default/src/app/(base)/template-step-groups/template-step-group-helpers.ts +54 -0
  172. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
  173. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +34 -32
  174. package/templates/default/src/app/(base)/template-steps/create/page.tsx +40 -37
  175. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +54 -49
  176. package/templates/default/src/app/(base)/template-steps/page.tsx +59 -58
  177. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +233 -213
  178. package/templates/default/src/app/(base)/template-steps/template-step-form.test.tsx +132 -0
  179. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +342 -384
  180. package/templates/default/src/app/(base)/template-steps/template-step-helpers.test.ts +99 -0
  181. package/templates/default/src/app/(base)/template-steps/template-step-helpers.ts +176 -0
  182. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +153 -158
  183. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +26 -24
  184. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
  185. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
  186. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
  187. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.test.tsx +109 -0
  188. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +45 -84
  189. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.test.tsx +140 -0
  190. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +154 -262
  191. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
  192. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
  193. package/templates/default/src/app/(base)/test-cases/create/page.tsx +90 -76
  194. package/templates/default/src/app/(base)/test-cases/create-from-template/create-from-template-helpers.test.ts +94 -0
  195. package/templates/default/src/app/(base)/test-cases/create-from-template/create-from-template-helpers.ts +171 -0
  196. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +105 -96
  197. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +40 -38
  198. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.test.tsx +87 -0
  199. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +83 -73
  200. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
  201. package/templates/default/src/app/(base)/test-cases/page.tsx +3 -2
  202. package/templates/default/src/app/(base)/test-cases/test-case-flow.test.tsx +108 -0
  203. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +43 -82
  204. package/templates/default/src/app/(base)/test-cases/test-case-form.test.tsx +202 -0
  205. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +263 -395
  206. package/templates/default/src/app/(base)/test-cases/test-case-route-helpers.test.ts +95 -0
  207. package/templates/default/src/app/(base)/test-cases/test-case-route-helpers.ts +147 -0
  208. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +4 -2
  209. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +11 -10
  210. package/templates/default/src/app/(base)/test-runs/create/page.tsx +4 -5
  211. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
  212. package/templates/default/src/app/(base)/test-runs/test-run-form-helpers.test.ts +50 -0
  213. package/templates/default/src/app/(base)/test-runs/test-run-form-helpers.ts +168 -0
  214. package/templates/default/src/app/(base)/test-runs/test-run-form.test.tsx +138 -0
  215. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +111 -256
  216. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
  217. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
  218. package/templates/default/src/app/(base)/test-runs/use-test-run-name-validation.ts +74 -0
  219. package/templates/default/src/app/(base)/test-suites/create/page.tsx +17 -12
  220. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +22 -19
  221. package/templates/default/src/app/(base)/test-suites/page.tsx +14 -56
  222. package/templates/default/src/app/(base)/test-suites/test-suite-form.test.tsx +127 -0
  223. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +45 -64
  224. package/templates/default/src/app/(base)/test-suites/test-suite-helpers.test.ts +67 -0
  225. package/templates/default/src/app/(base)/test-suites/test-suite-helpers.ts +215 -0
  226. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +32 -29
  227. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
  228. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
  229. package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
  230. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
  231. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
  232. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
  233. package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.test.ts +83 -0
  234. package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.ts +52 -52
  235. package/templates/default/src/app/api/test-runs/[runId]/download/route.test.ts +169 -0
  236. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +1 -1
  237. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.test.ts +135 -0
  238. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -146
  239. package/templates/default/src/app/globals.css +147 -147
  240. package/templates/default/src/app/page.tsx +1 -1
  241. package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
  242. package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
  243. package/templates/default/src/components/base-node.tsx +21 -21
  244. package/templates/default/src/components/chart/pie-chart.tsx +73 -73
  245. package/templates/default/src/components/data-extraction/locator-inspector-helpers.test.ts +32 -0
  246. package/templates/default/src/components/data-extraction/locator-inspector-helpers.ts +183 -0
  247. package/templates/default/src/components/data-extraction/locator-inspector.tsx +349 -460
  248. package/templates/default/src/components/data-state/empty-state.tsx +40 -40
  249. package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
  250. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
  251. package/templates/default/src/components/diagram/button-edge.tsx +54 -54
  252. package/templates/default/src/components/diagram/dynamic-parameters-helpers.test.ts +83 -0
  253. package/templates/default/src/components/diagram/dynamic-parameters-helpers.ts +158 -0
  254. package/templates/default/src/components/diagram/dynamic-parameters.tsx +350 -474
  255. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
  256. package/templates/default/src/components/diagram/flow-diagram-helpers.test.ts +117 -0
  257. package/templates/default/src/components/diagram/flow-diagram-helpers.ts +251 -0
  258. package/templates/default/src/components/diagram/flow-diagram.tsx +247 -470
  259. package/templates/default/src/components/diagram/flow-host-helpers.test.ts +74 -0
  260. package/templates/default/src/components/diagram/flow-host-helpers.ts +51 -0
  261. package/templates/default/src/components/diagram/node-form-helpers.test.ts +92 -0
  262. package/templates/default/src/components/diagram/node-form-helpers.ts +100 -0
  263. package/templates/default/src/components/diagram/node-form.test.tsx +168 -0
  264. package/templates/default/src/components/diagram/node-form.tsx +199 -262
  265. package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
  266. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
  267. package/templates/default/src/components/diagram/use-flow-node-order.ts +49 -0
  268. package/templates/default/src/components/form/error-message.tsx +7 -7
  269. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
  270. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
  271. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
  272. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
  273. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
  274. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
  275. package/templates/default/src/components/navigation/command-badge.tsx +34 -34
  276. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
  277. package/templates/default/src/components/navigation/entity-search-command.tsx +118 -116
  278. package/templates/default/src/components/navigation/nav-card.tsx +31 -31
  279. package/templates/default/src/components/navigation/nav-command-helpers.ts +122 -0
  280. package/templates/default/src/components/navigation/nav-command-search.tsx +125 -0
  281. package/templates/default/src/components/navigation/nav-command.test.tsx +106 -0
  282. package/templates/default/src/components/navigation/nav-command.tsx +49 -472
  283. package/templates/default/src/components/navigation/nav-link.tsx +60 -60
  284. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
  285. package/templates/default/src/components/navigation/use-nav-command.ts +58 -0
  286. package/templates/default/src/components/node-header.tsx +159 -159
  287. package/templates/default/src/components/reports/test-case-logs-modal.tsx +310 -310
  288. package/templates/default/src/components/table/table-actions.tsx +174 -172
  289. package/templates/default/src/components/test-case/test-case-form-helpers.test.ts +100 -0
  290. package/templates/default/src/components/test-case/test-case-form-helpers.ts +140 -0
  291. package/templates/default/src/components/test-case/test-case-picker-helpers.test.ts +40 -0
  292. package/templates/default/src/components/test-case/test-case-picker-helpers.ts +41 -0
  293. package/templates/default/src/components/test-case/test-case-picker.test.tsx +44 -0
  294. package/templates/default/src/components/test-case/test-case-picker.tsx +16 -35
  295. package/templates/default/src/components/test-case/test-scenario-preview.tsx +34 -0
  296. package/templates/default/src/components/test-run/download-logs-button.tsx +92 -92
  297. package/templates/default/src/components/test-run/log-viewer-helpers.test.ts +37 -0
  298. package/templates/default/src/components/test-run/log-viewer-helpers.ts +80 -0
  299. package/templates/default/src/components/test-run/log-viewer.test.tsx +118 -0
  300. package/templates/default/src/components/test-run/log-viewer.tsx +51 -350
  301. package/templates/default/src/components/test-run/test-run-details-helpers.test.ts +31 -0
  302. package/templates/default/src/components/test-run/test-run-details-helpers.ts +208 -0
  303. package/templates/default/src/components/test-run/test-run-details.test.tsx +174 -0
  304. package/templates/default/src/components/test-run/test-run-details.tsx +155 -457
  305. package/templates/default/src/components/test-run/test-run-header-helpers.test.ts +31 -0
  306. package/templates/default/src/components/test-run/test-run-header-helpers.ts +23 -0
  307. package/templates/default/src/components/test-run/test-run-header.test.tsx +103 -0
  308. package/templates/default/src/components/test-run/test-run-header.tsx +27 -149
  309. package/templates/default/src/components/test-run/use-log-viewer.ts +213 -0
  310. package/templates/default/src/components/test-run/use-test-run-details.ts +184 -0
  311. package/templates/default/src/components/test-run/use-test-run-header.ts +89 -0
  312. package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
  313. package/templates/default/src/components/test-suite/test-suite-picker-helpers.test.ts +68 -0
  314. package/templates/default/src/components/test-suite/test-suite-picker-helpers.ts +76 -0
  315. package/templates/default/src/components/test-suite/test-suite-picker.test.tsx +65 -0
  316. package/templates/default/src/components/test-suite/test-suite-picker.tsx +4 -72
  317. package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
  318. package/templates/default/src/components/theme/theme-provider.tsx +8 -8
  319. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
  320. package/templates/default/src/components/typography/page-header.tsx +7 -7
  321. package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
  322. package/templates/default/src/components/ui/alert.tsx +43 -43
  323. package/templates/default/src/components/ui/avatar.tsx +40 -40
  324. package/templates/default/src/components/ui/badge.tsx +29 -29
  325. package/templates/default/src/components/ui/button.tsx +47 -47
  326. package/templates/default/src/components/ui/calendar.tsx +158 -158
  327. package/templates/default/src/components/ui/card.tsx +43 -43
  328. package/templates/default/src/components/ui/checkbox.tsx +28 -28
  329. package/templates/default/src/components/ui/command.tsx +135 -135
  330. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
  331. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
  332. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
  333. package/templates/default/src/components/ui/data-table.test.tsx +122 -0
  334. package/templates/default/src/components/ui/data-table.tsx +298 -261
  335. package/templates/default/src/components/ui/dialog.tsx +97 -97
  336. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
  337. package/templates/default/src/components/ui/input.tsx +22 -22
  338. package/templates/default/src/components/ui/kbd.tsx +28 -28
  339. package/templates/default/src/components/ui/label.tsx +19 -19
  340. package/templates/default/src/components/ui/loading.tsx +12 -12
  341. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
  342. package/templates/default/src/components/ui/multi-select.test.tsx +45 -0
  343. package/templates/default/src/components/ui/multi-select.tsx +158 -142
  344. package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
  345. package/templates/default/src/components/ui/popover.tsx +33 -33
  346. package/templates/default/src/components/ui/progress.tsx +25 -25
  347. package/templates/default/src/components/ui/radio-group.tsx +44 -44
  348. package/templates/default/src/components/ui/scroll-area.tsx +40 -40
  349. package/templates/default/src/components/ui/select.tsx +151 -151
  350. package/templates/default/src/components/ui/separator.tsx +22 -22
  351. package/templates/default/src/components/ui/skeleton.tsx +7 -7
  352. package/templates/default/src/components/ui/table.tsx +76 -76
  353. package/templates/default/src/components/ui/tabs.tsx +55 -55
  354. package/templates/default/src/components/ui/textarea.tsx +21 -21
  355. package/templates/default/src/components/ui/toast.tsx +113 -113
  356. package/templates/default/src/components/ui/toaster.tsx +26 -26
  357. package/templates/default/src/components/user-prompt/delete-prompt.test.tsx +60 -0
  358. package/templates/default/src/components/user-prompt/delete-prompt.tsx +118 -87
  359. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
  360. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
  361. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
  362. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
  363. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
  364. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
  365. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
  366. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
  367. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
  368. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
  369. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
  370. package/templates/default/src/hooks/use-toast.ts +187 -187
  371. package/templates/default/src/lib/automation/automation-path-roots.ts +95 -0
  372. package/templates/default/src/lib/automation/automation-workspace.ts +147 -0
  373. package/templates/default/src/lib/automation/paths.ts +6 -211
  374. package/templates/default/src/lib/bidirectional-sync.ts +432 -432
  375. package/templates/default/src/lib/environment-file-utils.ts +2 -1
  376. package/templates/default/src/lib/executor/local-executor-adapter.ts +2 -5
  377. package/templates/default/src/lib/feature-file-generator.ts +2 -1
  378. package/templates/default/src/lib/gherkin-parser.ts +0 -2
  379. package/templates/default/src/lib/locator-group-file-utils.ts +304 -307
  380. package/templates/default/src/lib/locator-picker/session-manager.ts +0 -21
  381. package/templates/default/src/lib/locator-picker/suggestions.ts +4 -2
  382. package/templates/default/src/lib/metrics/metric-calculator.ts +2 -6
  383. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
  384. package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
  385. package/templates/default/src/lib/sync/sync-executor.test.ts +76 -0
  386. package/templates/default/src/lib/sync/sync-pending-counts.test.ts +227 -226
  387. package/templates/default/src/lib/sync/sync-pending-counts.ts +2 -5
  388. package/templates/default/src/lib/template-sync-utils.d.ts +6 -6
  389. package/templates/default/src/lib/template-sync-utils.js +46 -46
  390. package/templates/default/src/lib/template-sync-utils.ts +63 -63
  391. package/templates/default/src/lib/test-case-utils.ts +6 -6
  392. package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
  393. package/templates/default/src/lib/test-run/report-parser.ts +352 -352
  394. package/templates/default/src/lib/test-run/test-run-executor.ts +13 -13
  395. package/templates/default/src/lib/test-run/winston-logger.ts +65 -64
  396. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
  397. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
  398. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
  399. package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
  400. package/templates/default/src/lib/utils/template-step-file-generator.ts +2 -2
  401. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
  402. package/templates/default/src/lib/utils.ts +31 -31
  403. package/templates/default/src/services/dashboard/dashboard-service.test.ts +106 -0
  404. package/templates/default/src/services/dashboard/dashboard-service.ts +173 -0
  405. package/templates/default/src/services/environment/environment-service.test.ts +137 -0
  406. package/templates/default/src/services/environment/environment-service.ts +96 -0
  407. package/templates/default/src/services/locator/locator-path-utils.test.ts +14 -0
  408. package/templates/default/src/services/locator/locator-path-utils.ts +14 -0
  409. package/templates/default/src/services/locator/locator-service.test.ts +63 -0
  410. package/templates/default/src/services/locator/locator-service.ts +479 -0
  411. package/templates/default/src/services/locator/locator-sync-utils.test.ts +19 -0
  412. package/templates/default/src/services/locator/locator-sync-utils.ts +21 -0
  413. package/templates/default/src/services/locator-group/locator-group-service.test.ts +123 -0
  414. package/templates/default/src/services/locator-group/locator-group-service.ts +180 -0
  415. package/templates/default/src/services/module/module-service.test.ts +89 -0
  416. package/templates/default/src/services/module/module-service.ts +66 -0
  417. package/templates/default/src/services/report/report-service.test.ts +244 -0
  418. package/templates/default/src/services/report/report-service.ts +438 -0
  419. package/templates/default/src/services/shared/constants.ts +2 -0
  420. package/templates/default/src/services/shared/errors.test.ts +38 -0
  421. package/templates/default/src/services/shared/errors.ts +44 -0
  422. package/templates/default/src/services/shared/index.ts +7 -0
  423. package/templates/default/src/services/tag/tag-service.test.ts +22 -0
  424. package/templates/default/src/services/tag/tag-service.ts +41 -0
  425. package/templates/default/src/services/template-step/template-step-service.test.ts +22 -0
  426. package/templates/default/src/services/template-step/template-step-service.ts +171 -0
  427. package/templates/default/src/services/template-step-group/template-step-group-service.test.ts +22 -0
  428. package/templates/default/src/services/template-step-group/template-step-group-service.ts +81 -0
  429. package/templates/default/src/services/template-test-case/template-test-case-service.test.ts +22 -0
  430. package/templates/default/src/services/template-test-case/template-test-case-service.ts +128 -0
  431. package/templates/default/src/services/test-case/test-case-service.test.ts +175 -0
  432. package/templates/default/src/services/test-case/test-case-service.ts +298 -0
  433. package/templates/default/src/services/test-run/test-run-helpers.ts +61 -0
  434. package/templates/default/src/services/test-run/test-run-service.test.ts +647 -0
  435. package/templates/default/src/services/test-run/test-run-service.ts +917 -0
  436. package/templates/default/src/services/test-suite/test-suite-service.test.ts +127 -0
  437. package/templates/default/src/services/test-suite/test-suite-service.ts +197 -0
  438. package/templates/default/src/types/diagram/diagram.ts +34 -34
  439. package/templates/default/src/types/diagram/template-step.ts +11 -11
  440. package/templates/default/src/types/executor/browser.type.ts +1 -1
  441. package/templates/default/src/types/form/actionHandler.ts +19 -6
  442. package/templates/default/src/types/locator/locator.type.ts +11 -11
  443. package/templates/default/src/types/step/step.type.ts +1 -1
  444. package/templates/default/src/types/table/data-table.ts +6 -6
  445. package/templates/default/tailwind.config.ts +62 -62
  446. package/templates/default/tsconfig.json +1 -1
  447. package/dist/cli.e2e.test.d.ts +0 -2
  448. package/dist/cli.e2e.test.d.ts.map +0 -1
  449. package/dist/cli.e2e.test.js +0 -73
  450. package/dist/cli.e2e.test.js.map +0 -1
  451. package/dist/config.test.d.ts +0 -2
  452. package/dist/config.test.d.ts.map +0 -1
  453. package/dist/config.test.js +0 -65
  454. package/dist/config.test.js.map +0 -1
  455. package/dist/copy-template.test.d.ts +0 -2
  456. package/dist/copy-template.test.d.ts.map +0 -1
  457. package/dist/copy-template.test.js +0 -71
  458. package/dist/copy-template.test.js.map +0 -1
  459. package/dist/download-repo.test.d.ts +0 -2
  460. package/dist/download-repo.test.d.ts.map +0 -1
  461. package/dist/download-repo.test.js +0 -14
  462. package/dist/download-repo.test.js.map +0 -1
  463. package/dist/install.test.d.ts +0 -2
  464. package/dist/install.test.d.ts.map +0 -1
  465. package/dist/install.test.js +0 -119
  466. package/dist/install.test.js.map +0 -1
  467. package/dist/prompts.test.d.ts +0 -2
  468. package/dist/prompts.test.d.ts.map +0 -1
  469. package/dist/prompts.test.js +0 -58
  470. package/dist/prompts.test.js.map +0 -1
  471. package/templates/default/src/actions/conflict/conflict.action.ts +0 -33
  472. package/templates/default/src/actions/review/review-actions.ts +0 -147
  473. package/templates/default/src/actions/user/user-actions.ts +0 -13
  474. package/templates/default/src/app/(base)/locators/locator-form.tsx +0 -163
  475. package/templates/default/src/app/(base)/reviews/create/page.tsx +0 -26
  476. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +0 -15
  477. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +0 -26
  478. package/templates/default/src/app/(base)/reviews/page.tsx +0 -26
  479. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +0 -26
  480. package/templates/default/src/app/(base)/reviews/review-form.tsx +0 -11
  481. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +0 -9
  482. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +0 -9
  483. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +0 -15
  484. package/templates/default/src/constants/form-opts/locator-form-opts.ts +0 -20
  485. package/templates/default/src/constants/form-opts/review-form-opts.ts +0 -23
@@ -0,0 +1,404 @@
1
+ import { promises as fs } from 'fs'
2
+ import path from 'path'
3
+ import prettier from 'prettier'
4
+ import { parse } from '@babel/parser'
5
+ import * as t from '@babel/types'
6
+ import { glob } from 'glob'
7
+ import { TemplateStepGroupType, TemplateStepIcon } from '@prisma/client'
8
+ import { createContentSha256, type RegistryStepEntry } from './template-step-registry'
9
+ import { parseStepFile } from './step-file-parser'
10
+
11
+ export type TemplateStepInstallPayload = {
12
+ version: 1
13
+ step: RegistryStepEntry
14
+ source: string
15
+ }
16
+
17
+ export type InstallTemplateStepOptions = {
18
+ projectRoot: string
19
+ overwrite?: boolean
20
+ dryRun?: boolean
21
+ }
22
+
23
+ export type InstallTemplateStepResult = {
24
+ status: 'installed' | 'noop'
25
+ targetFilePath: string
26
+ createdGroupFile: boolean
27
+ changed: boolean
28
+ reason: string
29
+ }
30
+
31
+ type ExistingStepMatch = {
32
+ filePath: string
33
+ source: string
34
+ }
35
+
36
+ const STEP_FILE_PATTERNS = ['automation/steps/actions/**/*.step.ts', 'automation/steps/validations/**/*.step.ts'] as const
37
+ const RUNTIME_IMPORT_PATH = '../../../packages/cucumber-runtime/src/index.js'
38
+ const REQUIRED_RUNTIME_IMPORTS = [
39
+ 'When',
40
+ 'Then',
41
+ 'CustomWorld',
42
+ 'expect',
43
+ 'SelectorName',
44
+ 'resolveLocator',
45
+ 'getEnvironment',
46
+ 'generateRandomData',
47
+ 'RandomDataType',
48
+ ] as const
49
+ const PLACEHOLDER_COMMENT = '// This file is generated automatically. Add template steps to this group to generate content.'
50
+
51
+ function parseTypeScriptModule(content: string) {
52
+ return parse(content, {
53
+ sourceType: 'module',
54
+ plugins: ['typescript', 'decorators-legacy'],
55
+ })
56
+ }
57
+
58
+ function getGroupFileName(groupName: string): string {
59
+ return groupName
60
+ .toLowerCase()
61
+ .trim()
62
+ .replace(/\s+/g, '_')
63
+ .replace(/[^a-z0-9_]/g, '')
64
+ }
65
+
66
+ function getGroupDirectoryName(type: TemplateStepGroupType): 'actions' | 'validations' {
67
+ return type === 'ACTION' ? 'actions' : 'validations'
68
+ }
69
+
70
+ function getGroupFilePath(projectRoot: string, groupName: string, type: TemplateStepGroupType): string {
71
+ return path.join(projectRoot, 'automation', 'steps', getGroupDirectoryName(type), `${getGroupFileName(groupName)}.step.ts`)
72
+ }
73
+
74
+ function generateGroupJSDoc(name: string, description: string | null, type: TemplateStepGroupType): string {
75
+ const lines = ['/**', ` * @name ${name}`]
76
+ if (description) {
77
+ lines.push(` * @description ${description}`)
78
+ }
79
+ lines.push(` * @type ${type}`, ' */')
80
+ return lines.join('\n')
81
+ }
82
+
83
+ function extractGroupJSDocRange(content: string): { start: number; end: number } | null {
84
+ const match = content.match(/^\/\*\*[\s\S]*?\*\//)
85
+ if (!match || !match[0].includes('@type')) {
86
+ return null
87
+ }
88
+
89
+ return {
90
+ start: match.index ?? 0,
91
+ end: (match.index ?? 0) + match[0].length,
92
+ }
93
+ }
94
+
95
+ function renderRequiredRuntimeImport(extraImports: string[] = []): string {
96
+ const orderedNames = [
97
+ ...REQUIRED_RUNTIME_IMPORTS,
98
+ ...extraImports
99
+ .filter(name => !REQUIRED_RUNTIME_IMPORTS.includes(name as (typeof REQUIRED_RUNTIME_IMPORTS)[number]))
100
+ .sort((left, right) => left.localeCompare(right)),
101
+ ]
102
+
103
+ return `import { ${orderedNames.join(', ')} } from '${RUNTIME_IMPORT_PATH}'`
104
+ }
105
+
106
+ function mergeImports(content: string): string {
107
+ const ast = parseTypeScriptModule(content)
108
+ const importNodes = ast.program.body.filter(node => t.isImportDeclaration(node)) as t.ImportDeclaration[]
109
+ const preservedImports: string[] = []
110
+ const runtimeNamedImports = new Set<string>()
111
+ const complexRuntimeImports: string[] = []
112
+
113
+ for (const node of importNodes) {
114
+ const code = content.slice(node.start ?? 0, node.end ?? 0).trim()
115
+ const importSource = node.source.value
116
+
117
+ if (importSource !== RUNTIME_IMPORT_PATH) {
118
+ if (code) preservedImports.push(code)
119
+ continue
120
+ }
121
+
122
+ let hasComplexSpecifiers = false
123
+ for (const specifier of node.specifiers) {
124
+ if (t.isImportSpecifier(specifier)) {
125
+ runtimeNamedImports.add(specifier.local.name)
126
+ } else {
127
+ hasComplexSpecifiers = true
128
+ }
129
+ }
130
+
131
+ if (hasComplexSpecifiers && code) {
132
+ complexRuntimeImports.push(code)
133
+ }
134
+ }
135
+
136
+ const imports = [...preservedImports]
137
+ if (runtimeNamedImports.size > 0) {
138
+ imports.push(renderRequiredRuntimeImport(Array.from(runtimeNamedImports)))
139
+ } else {
140
+ imports.push(renderRequiredRuntimeImport())
141
+ }
142
+ imports.push(...complexRuntimeImports)
143
+
144
+ return imports.join('\n')
145
+ }
146
+
147
+ async function normalizeTypeScriptSource(source: string): Promise<string> {
148
+ const trimmed = source.trim()
149
+ if (!trimmed) {
150
+ return ''
151
+ }
152
+
153
+ try {
154
+ return (
155
+ await prettier.format(trimmed, {
156
+ parser: 'typescript',
157
+ semi: true,
158
+ singleQuote: true,
159
+ trailingComma: 'es5',
160
+ printWidth: 80,
161
+ tabWidth: 2,
162
+ })
163
+ ).trim()
164
+ } catch {
165
+ return trimmed
166
+ }
167
+ }
168
+
169
+ async function ensureDirectoryExists(filePath: string): Promise<void> {
170
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
171
+ }
172
+
173
+ async function createPlaceholderGroupFile(
174
+ filePath: string,
175
+ group: RegistryStepEntry['group'],
176
+ dryRun: boolean,
177
+ ): Promise<boolean> {
178
+ const content = await normalizeTypeScriptSource(
179
+ `${generateGroupJSDoc(group.name, group.description, group.type)}
180
+
181
+ ${renderRequiredRuntimeImport()}
182
+
183
+ ${PLACEHOLDER_COMMENT}
184
+ `,
185
+ )
186
+
187
+ if (!dryRun) {
188
+ await ensureDirectoryExists(filePath)
189
+ await fs.writeFile(filePath, `${content}\n`, 'utf8')
190
+ }
191
+
192
+ return true
193
+ }
194
+
195
+ async function findExistingSignatureMatches(projectRoot: string, signature: string): Promise<ExistingStepMatch[]> {
196
+ const matches: ExistingStepMatch[] = []
197
+
198
+ for (const pattern of STEP_FILE_PATTERNS) {
199
+ const files = await glob(pattern, { cwd: projectRoot })
200
+ for (const relativePath of files) {
201
+ const absolutePath = path.join(projectRoot, relativePath)
202
+ const content = await fs.readFile(absolutePath, 'utf8')
203
+ const parsed = parseStepFile(content, relativePath)
204
+ if (!parsed) {
205
+ continue
206
+ }
207
+
208
+ for (const step of parsed.steps) {
209
+ if (step.signature === signature) {
210
+ matches.push({
211
+ filePath: absolutePath,
212
+ source: step.source,
213
+ })
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ return matches
220
+ }
221
+
222
+ function validatePayload(payload: TemplateStepInstallPayload): void {
223
+ if (payload.version !== 1) {
224
+ throw new Error(`Unsupported install payload version: ${payload.version}`)
225
+ }
226
+
227
+ if (!payload.step.slug || !payload.step.signature || !payload.source.trim()) {
228
+ throw new Error('Install payload is missing required step metadata')
229
+ }
230
+
231
+ const expectedSha = createContentSha256(payload.source.endsWith('\n') ? payload.source : `${payload.source}\n`)
232
+ if (expectedSha !== payload.step.sourceSha256) {
233
+ throw new Error(`Downloaded step payload failed checksum validation for ${payload.step.slug}`)
234
+ }
235
+ }
236
+
237
+ async function buildUpdatedGroupFileContent(
238
+ existingContent: string,
239
+ payload: TemplateStepInstallPayload,
240
+ overwrite: boolean,
241
+ ): Promise<{ changed: boolean; content: string; reason: string }> {
242
+ const parsed = parseStepFile(existingContent, payload.step.sourcePath)
243
+ const groupRange = extractGroupJSDocRange(existingContent)
244
+ const existingSteps = parsed?.steps.slice().sort((left, right) => left.start - right.start) ?? []
245
+ const firstStepStart = existingSteps[0]?.start ?? existingContent.length
246
+ const lastStepEnd = existingSteps[existingSteps.length - 1]?.end ?? 0
247
+ const ast = parseTypeScriptModule(existingContent)
248
+ const importNodes = ast.program.body.filter(node => t.isImportDeclaration(node)) as t.ImportDeclaration[]
249
+ const importsEnd = importNodes[importNodes.length - 1]?.end ?? groupRange?.end ?? 0
250
+ const preamble = existingContent.slice(importsEnd, firstStepStart).trim()
251
+ const postamble = lastStepEnd > 0 ? existingContent.slice(lastStepEnd).trim() : ''
252
+ const normalizedIncomingSource = await normalizeTypeScriptSource(payload.source)
253
+ const matchingIndex = existingSteps.findIndex(step => step.signature === payload.step.signature)
254
+
255
+ if (matchingIndex >= 0) {
256
+ const normalizedExistingSource = await normalizeTypeScriptSource(existingSteps[matchingIndex]!.source)
257
+ if (normalizedExistingSource === normalizedIncomingSource) {
258
+ return {
259
+ changed: false,
260
+ content: existingContent,
261
+ reason: `Step "${payload.step.signature}" is already installed.`,
262
+ }
263
+ }
264
+
265
+ if (!overwrite) {
266
+ throw new Error(
267
+ `A different step with signature "${payload.step.signature}" already exists in ${payload.step.group.name}. Re-run with --overwrite to replace it.`,
268
+ )
269
+ }
270
+
271
+ existingSteps[matchingIndex] = {
272
+ ...existingSteps[matchingIndex]!,
273
+ source: payload.source,
274
+ }
275
+ } else {
276
+ existingSteps.push({
277
+ jsdoc: {
278
+ name: payload.step.name,
279
+ description: payload.step.description,
280
+ icon: payload.step.icon as TemplateStepIcon,
281
+ },
282
+ signature: payload.step.signature,
283
+ source: payload.source,
284
+ start: 0,
285
+ end: 0,
286
+ functionDefinition: payload.source,
287
+ parameters: [],
288
+ keyword: payload.step.group.type === 'ACTION' ? 'When' : 'Then',
289
+ })
290
+ }
291
+
292
+ const sections = [generateGroupJSDoc(payload.step.group.name, payload.step.group.description, payload.step.group.type)]
293
+ const mergedImports = mergeImports(existingContent)
294
+ if (mergedImports.trim()) {
295
+ sections.push(mergedImports)
296
+ }
297
+ if (preamble) {
298
+ sections.push(preamble)
299
+ }
300
+ sections.push(existingSteps.map(step => step.source.trim()).join('\n\n'))
301
+ if (postamble) {
302
+ sections.push(postamble)
303
+ }
304
+
305
+ return {
306
+ changed: true,
307
+ content: `${await normalizeTypeScriptSource(sections.filter(Boolean).join('\n\n'))}\n`,
308
+ reason:
309
+ matchingIndex >= 0
310
+ ? `Replaced step "${payload.step.signature}" in ${payload.step.group.name}.`
311
+ : `Installed step "${payload.step.signature}" into ${payload.step.group.name}.`,
312
+ }
313
+ }
314
+
315
+ export async function installTemplateStepPayload(
316
+ payload: TemplateStepInstallPayload,
317
+ options: InstallTemplateStepOptions,
318
+ ): Promise<InstallTemplateStepResult> {
319
+ validatePayload(payload)
320
+
321
+ const projectRoot = path.resolve(options.projectRoot)
322
+ const overwrite = options.overwrite ?? false
323
+ const dryRun = options.dryRun ?? false
324
+ const targetFilePath = getGroupFilePath(projectRoot, payload.step.group.name, payload.step.group.type)
325
+ const oppositeType = payload.step.group.type === 'ACTION' ? 'VALIDATION' : 'ACTION'
326
+ const oppositeFilePath = getGroupFilePath(projectRoot, payload.step.group.name, oppositeType)
327
+
328
+ try {
329
+ await fs.access(oppositeFilePath)
330
+ throw new Error(
331
+ `Found a same-named step group in ${getGroupDirectoryName(oppositeType)} at ${path.relative(projectRoot, oppositeFilePath)}. Move or rename that group before installing this step.`,
332
+ )
333
+ } catch (error) {
334
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
335
+ throw error
336
+ }
337
+ }
338
+
339
+ const existingMatches = await findExistingSignatureMatches(projectRoot, payload.step.signature)
340
+ const normalizedIncomingSource = await normalizeTypeScriptSource(payload.source)
341
+ for (const match of existingMatches) {
342
+ if (match.filePath !== targetFilePath) {
343
+ const normalizedExistingSource = await normalizeTypeScriptSource(match.source)
344
+ if (normalizedExistingSource === normalizedIncomingSource) {
345
+ return {
346
+ status: 'noop',
347
+ targetFilePath: match.filePath,
348
+ createdGroupFile: false,
349
+ changed: false,
350
+ reason: `Step "${payload.step.signature}" is already installed at ${path.relative(projectRoot, match.filePath)}.`,
351
+ }
352
+ }
353
+
354
+ throw new Error(
355
+ `A conflicting step with signature "${payload.step.signature}" already exists at ${path.relative(projectRoot, match.filePath)}.`,
356
+ )
357
+ }
358
+ }
359
+
360
+ let createdGroupFile = false
361
+ try {
362
+ await fs.access(targetFilePath)
363
+ } catch (error) {
364
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
365
+ throw error
366
+ }
367
+ createdGroupFile = await createPlaceholderGroupFile(targetFilePath, payload.step.group, dryRun)
368
+ }
369
+
370
+ const existingContent = createdGroupFile
371
+ ? await normalizeTypeScriptSource(
372
+ `${generateGroupJSDoc(payload.step.group.name, payload.step.group.description, payload.step.group.type)}
373
+
374
+ ${renderRequiredRuntimeImport()}
375
+
376
+ ${PLACEHOLDER_COMMENT}
377
+ `,
378
+ ).then(value => `${value}\n`)
379
+ : await fs.readFile(targetFilePath, 'utf8')
380
+
381
+ const updated = await buildUpdatedGroupFileContent(existingContent, payload, overwrite)
382
+ if (!updated.changed) {
383
+ return {
384
+ status: 'noop',
385
+ targetFilePath,
386
+ createdGroupFile,
387
+ changed: false,
388
+ reason: updated.reason,
389
+ }
390
+ }
391
+
392
+ if (!dryRun) {
393
+ await ensureDirectoryExists(targetFilePath)
394
+ await fs.writeFile(targetFilePath, updated.content, 'utf8')
395
+ }
396
+
397
+ return {
398
+ status: 'installed',
399
+ targetFilePath,
400
+ createdGroupFile,
401
+ changed: true,
402
+ reason: updated.reason,
403
+ }
404
+ }
@@ -0,0 +1,118 @@
1
+ import os from 'os'
2
+ import path from 'path'
3
+ import { promises as fs } from 'fs'
4
+ import { afterEach, describe, expect, it } from 'vitest'
5
+ import { buildStepRegistry, createContentSha256, slugifyRegistryName } from './template-step-registry'
6
+
7
+ const tempDirs: string[] = []
8
+
9
+ async function createTempWorkspace(): Promise<string> {
10
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'appraise-step-registry-'))
11
+ tempDirs.push(dir)
12
+ return dir
13
+ }
14
+
15
+ async function writeFile(root: string, relativePath: string, content: string): Promise<void> {
16
+ const absolutePath = path.join(root, relativePath)
17
+ await fs.mkdir(path.dirname(absolutePath), { recursive: true })
18
+ await fs.writeFile(absolutePath, content)
19
+ }
20
+
21
+ afterEach(async () => {
22
+ await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true })))
23
+ })
24
+
25
+ describe('slugifyRegistryName', () => {
26
+ it('creates hyphenated slugs for CLI lookup', () => {
27
+ expect(slugifyRegistryName('Text Assertion')).toBe('text-assertion')
28
+ expect(slugifyRegistryName('Assert element contains text')).toBe('assert-element-contains-text')
29
+ })
30
+ })
31
+
32
+ describe('buildStepRegistry', () => {
33
+ it('builds manifest entries and source fragments from canonical step files', async () => {
34
+ const workspace = await createTempWorkspace()
35
+
36
+ await writeFile(
37
+ workspace,
38
+ 'automation/steps/actions/click.step.ts',
39
+ `/**
40
+ * @name click
41
+ * @description Click helpers
42
+ * @type ACTION
43
+ */
44
+ import { When, CustomWorld, SelectorName, resolveLocator } from '../../../packages/cucumber-runtime/src/index.js'
45
+
46
+ /**
47
+ * @name click element
48
+ * @description clicks the target element
49
+ * @icon MOUSE
50
+ */
51
+ When('click {string}', async function (this: CustomWorld, elementName: SelectorName) {
52
+ const selector = await resolveLocator(this.page, elementName)
53
+ await this.page.locator(selector!).click()
54
+ })
55
+ `,
56
+ )
57
+
58
+ const registry = await buildStepRegistry(workspace)
59
+ expect(registry.manifest.version).toBe(1)
60
+ expect(registry.manifest.steps).toHaveLength(1)
61
+
62
+ const entry = registry.manifest.steps[0]
63
+ expect(entry.slug).toBe('click/click-element')
64
+ expect(entry.sourcePath).toBe('fragments/click/click-element.ts')
65
+ expect(entry.signature).toBe('click {string}')
66
+ expect(entry.group).toEqual({
67
+ slug: 'click',
68
+ name: 'click',
69
+ description: 'Click helpers',
70
+ type: 'ACTION',
71
+ })
72
+
73
+ expect(registry.fragments).toEqual([
74
+ {
75
+ path: 'fragments/click/click-element.ts',
76
+ content: expect.stringContaining("@name click element"),
77
+ },
78
+ ])
79
+ expect(registry.fragments[0]?.content).toContain("When('click {string}'")
80
+ expect(entry.sourceSha256).toBe(createContentSha256(registry.fragments[0]!.content))
81
+ })
82
+
83
+ it('fails when duplicate signatures would produce an ambiguous registry', async () => {
84
+ const workspace = await createTempWorkspace()
85
+
86
+ await writeFile(
87
+ workspace,
88
+ 'automation/steps/actions/click.step.ts',
89
+ `/**
90
+ * @name click
91
+ * @type ACTION
92
+ */
93
+ /**
94
+ * @name click element
95
+ * @icon MOUSE
96
+ */
97
+ When('click {string}', async function () {})
98
+ `,
99
+ )
100
+
101
+ await writeFile(
102
+ workspace,
103
+ 'automation/steps/actions/hover.step.ts',
104
+ `/**
105
+ * @name hover
106
+ * @type ACTION
107
+ */
108
+ /**
109
+ * @name hover element
110
+ * @icon MOUSE
111
+ */
112
+ When('click {string}', async function () {})
113
+ `,
114
+ )
115
+
116
+ await expect(buildStepRegistry(workspace)).rejects.toThrow('Duplicate step signature')
117
+ })
118
+ })
@@ -0,0 +1,137 @@
1
+ import crypto from 'crypto'
2
+ import { promises as fs } from 'fs'
3
+ import path from 'path'
4
+ import { glob } from 'glob'
5
+ import { TemplateStepGroupType, TemplateStepIcon } from '@prisma/client'
6
+ import { parseStepFile } from './step-file-parser'
7
+
8
+ export type RegistryStepEntry = {
9
+ slug: string
10
+ sourcePath: string
11
+ sourceSha256: string
12
+ signature: string
13
+ name: string
14
+ description: string | null
15
+ icon: TemplateStepIcon
16
+ group: {
17
+ slug: string
18
+ name: string
19
+ description: string | null
20
+ type: TemplateStepGroupType
21
+ }
22
+ }
23
+
24
+ export type StepRegistryManifest = {
25
+ version: 1
26
+ generatedAt: string
27
+ steps: RegistryStepEntry[]
28
+ }
29
+
30
+ export type StepRegistryFragment = {
31
+ path: string
32
+ content: string
33
+ }
34
+
35
+ export type BuiltStepRegistry = {
36
+ manifest: StepRegistryManifest
37
+ fragments: StepRegistryFragment[]
38
+ }
39
+
40
+ const STEP_FILE_PATTERNS = ['automation/steps/actions/**/*.step.ts', 'automation/steps/validations/**/*.step.ts'] as const
41
+
42
+ export function slugifyRegistryName(value: string): string {
43
+ return value
44
+ .toLowerCase()
45
+ .trim()
46
+ .replace(/[^a-z0-9]+/g, '-')
47
+ .replace(/^-+|-+$/g, '')
48
+ }
49
+
50
+ export function renderStepSourceFragment(source: string): string {
51
+ return `${source.trim()}\n`
52
+ }
53
+
54
+ export function createContentSha256(content: string): string {
55
+ return crypto.createHash('sha256').update(content).digest('hex')
56
+ }
57
+
58
+ export async function buildStepRegistry(baseDir: string): Promise<BuiltStepRegistry> {
59
+ const fragments: StepRegistryFragment[] = []
60
+ const steps: RegistryStepEntry[] = []
61
+ const seenSlugs = new Set<string>()
62
+ const seenSourcePaths = new Set<string>()
63
+ const seenSignatures = new Set<string>()
64
+
65
+ for (const pattern of STEP_FILE_PATTERNS) {
66
+ const files = await glob(pattern, { cwd: baseDir })
67
+
68
+ for (const file of files.sort()) {
69
+ const absolutePath = path.join(baseDir, file)
70
+ const content = await fs.readFile(absolutePath, 'utf8')
71
+ const parsed = parseStepFile(content, file)
72
+ if (!parsed) {
73
+ throw new Error(`Step file is missing a valid group JSDoc: ${file}`)
74
+ }
75
+
76
+ const groupSlug = slugifyRegistryName(parsed.group.name)
77
+ if (!groupSlug) {
78
+ throw new Error(`Unable to derive registry slug for step group "${parsed.group.name}" (${file})`)
79
+ }
80
+
81
+ for (const step of parsed.steps) {
82
+ const stepSlug = slugifyRegistryName(step.jsdoc.name)
83
+ if (!stepSlug) {
84
+ throw new Error(`Unable to derive registry slug for step "${step.jsdoc.name}" (${file})`)
85
+ }
86
+
87
+ const slug = `${groupSlug}/${stepSlug}`
88
+ const sourcePath = path.posix.join('fragments', groupSlug, `${stepSlug}.ts`)
89
+ const fragmentContent = renderStepSourceFragment(step.source)
90
+ const sourceSha256 = createContentSha256(fragmentContent)
91
+
92
+ if (seenSlugs.has(slug)) {
93
+ throw new Error(`Duplicate registry slug "${slug}" generated from ${file}`)
94
+ }
95
+ if (seenSourcePaths.has(sourcePath)) {
96
+ throw new Error(`Duplicate registry source path "${sourcePath}" generated from ${file}`)
97
+ }
98
+ if (seenSignatures.has(step.signature)) {
99
+ throw new Error(`Duplicate step signature "${step.signature}" encountered while building the registry`)
100
+ }
101
+
102
+ seenSlugs.add(slug)
103
+ seenSourcePaths.add(sourcePath)
104
+ seenSignatures.add(step.signature)
105
+
106
+ fragments.push({
107
+ path: sourcePath,
108
+ content: fragmentContent,
109
+ })
110
+ steps.push({
111
+ slug,
112
+ sourcePath,
113
+ sourceSha256,
114
+ signature: step.signature,
115
+ name: step.jsdoc.name,
116
+ description: step.jsdoc.description,
117
+ icon: step.jsdoc.icon,
118
+ group: {
119
+ slug: groupSlug,
120
+ name: parsed.group.name,
121
+ description: parsed.group.description,
122
+ type: parsed.group.type,
123
+ },
124
+ })
125
+ }
126
+ }
127
+ }
128
+
129
+ return {
130
+ manifest: {
131
+ version: 1,
132
+ generatedAt: new Date().toISOString(),
133
+ steps: steps.sort((left, right) => left.slug.localeCompare(right.slug)),
134
+ },
135
+ fragments: fragments.sort((left, right) => left.path.localeCompare(right.path)),
136
+ }
137
+ }