create-appraise 0.1.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 (330) hide show
  1. package/README.md +52 -0
  2. package/package.json +63 -0
  3. package/templates/default/.env.example +2 -0
  4. package/templates/default/README.md +51 -0
  5. package/templates/default/appraise.config.json +4 -0
  6. package/templates/default/components.json +24 -0
  7. package/templates/default/eslint.config.mjs +15 -0
  8. package/templates/default/next-env.d.ts +6 -0
  9. package/templates/default/next.config.ts +7 -0
  10. package/templates/default/package-lock.json +14321 -0
  11. package/templates/default/package.json +124 -0
  12. package/templates/default/postcss.config.mjs +8 -0
  13. package/templates/default/prisma/migrations/20251026202316_migrate_back_to_sqlite/migration.sql +257 -0
  14. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -0
  15. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -0
  16. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -0
  17. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -0
  18. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -0
  19. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -0
  20. package/templates/default/prisma/migrations/20251130190737_add_trace_path_to_test_run_test_case/migration.sql +2 -0
  21. package/templates/default/prisma/migrations/20251213074835_add_log_path_to_test_run/migration.sql +2 -0
  22. package/templates/default/prisma/migrations/20251213183952_add_name_property_for_the_test_run_entities/migration.sql +30 -0
  23. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -0
  24. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -0
  25. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -0
  26. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -0
  27. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -0
  28. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -0
  29. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -0
  30. package/templates/default/prisma/migrations/migration_lock.toml +3 -0
  31. package/templates/default/prisma/schema.prisma +554 -0
  32. package/templates/default/public/favicon.ico +0 -0
  33. package/templates/default/public/file.svg +1 -0
  34. package/templates/default/public/globe.svg +1 -0
  35. package/templates/default/public/next.svg +1 -0
  36. package/templates/default/public/vercel.svg +1 -0
  37. package/templates/default/public/window.svg +1 -0
  38. package/templates/default/scripts/regenerate-features.ts +94 -0
  39. package/templates/default/scripts/setup-env.ts +19 -0
  40. package/templates/default/scripts/sync-all.ts +341 -0
  41. package/templates/default/scripts/sync-environments.ts +323 -0
  42. package/templates/default/scripts/sync-locator-groups.ts +413 -0
  43. package/templates/default/scripts/sync-locators.ts +402 -0
  44. package/templates/default/scripts/sync-modules.ts +349 -0
  45. package/templates/default/scripts/sync-tags.ts +292 -0
  46. package/templates/default/scripts/sync-template-step-groups.ts +399 -0
  47. package/templates/default/scripts/sync-template-steps.ts +806 -0
  48. package/templates/default/scripts/sync-test-cases.ts +905 -0
  49. package/templates/default/scripts/sync-test-suites.ts +411 -0
  50. package/templates/default/src/actions/conflict/conflict.action.ts +33 -0
  51. package/templates/default/src/actions/dashboard/dashboard-actions.ts +241 -0
  52. package/templates/default/src/actions/environments/environment-actions.ts +205 -0
  53. package/templates/default/src/actions/locator/locator-actions.ts +547 -0
  54. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +344 -0
  55. package/templates/default/src/actions/modules/module-actions.ts +133 -0
  56. package/templates/default/src/actions/reports/report-actions.ts +614 -0
  57. package/templates/default/src/actions/review/review-actions.ts +147 -0
  58. package/templates/default/src/actions/tags/tag-actions.ts +104 -0
  59. package/templates/default/src/actions/template-step/template-step-actions.ts +332 -0
  60. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +278 -0
  61. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -0
  62. package/templates/default/src/actions/test-case/test-case-actions.ts +419 -0
  63. package/templates/default/src/actions/test-run/test-run-actions.ts +1185 -0
  64. package/templates/default/src/actions/test-suite/test-suite-actions.ts +253 -0
  65. package/templates/default/src/actions/user/user-actions.ts +13 -0
  66. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -0
  67. package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -0
  68. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -0
  69. package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -0
  70. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -0
  71. package/templates/default/src/app/(base)/environments/page.tsx +59 -0
  72. package/templates/default/src/app/(base)/layout.tsx +10 -0
  73. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -0
  74. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -0
  75. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -0
  76. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -0
  77. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -0
  78. package/templates/default/src/app/(base)/locator-groups/page.tsx +61 -0
  79. package/templates/default/src/app/(base)/locators/create/page.tsx +38 -0
  80. package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -0
  81. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +90 -0
  82. package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -0
  83. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +45 -0
  84. package/templates/default/src/app/(base)/locators/page.tsx +65 -0
  85. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -0
  86. package/templates/default/src/app/(base)/modules/create/page.tsx +34 -0
  87. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -0
  88. package/templates/default/src/app/(base)/modules/module-form.tsx +126 -0
  89. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -0
  90. package/templates/default/src/app/(base)/modules/module-table.tsx +24 -0
  91. package/templates/default/src/app/(base)/modules/page.tsx +59 -0
  92. package/templates/default/src/app/(base)/reports/[id]/page.tsx +517 -0
  93. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -0
  94. package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -0
  95. package/templates/default/src/app/(base)/reports/overview-chart.tsx +46 -0
  96. package/templates/default/src/app/(base)/reports/page.tsx +98 -0
  97. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +16 -0
  98. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -0
  99. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -0
  100. package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +131 -0
  101. package/templates/default/src/app/(base)/reports/report-view-table.tsx +82 -0
  102. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +42 -0
  103. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -0
  104. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -0
  105. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -0
  106. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -0
  107. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -0
  108. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +60 -0
  109. package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -0
  110. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -0
  111. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -0
  112. package/templates/default/src/app/(base)/reviews/page.tsx +26 -0
  113. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -0
  114. package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -0
  115. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -0
  116. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -0
  117. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -0
  118. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -0
  119. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -0
  120. package/templates/default/src/app/(base)/tags/page.tsx +58 -0
  121. package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -0
  122. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -0
  123. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -0
  124. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -0
  125. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -0
  126. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -0
  127. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -0
  128. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -0
  129. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -0
  130. package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -0
  131. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -0
  132. package/templates/default/src/app/(base)/template-steps/page.tsx +59 -0
  133. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -0
  134. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -0
  135. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -0
  136. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -0
  137. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -0
  138. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -0
  139. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -0
  140. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -0
  141. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -0
  142. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -0
  143. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -0
  144. package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -0
  145. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -0
  146. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -0
  147. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -0
  148. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -0
  149. package/templates/default/src/app/(base)/test-cases/page.tsx +60 -0
  150. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -0
  151. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -0
  152. package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +90 -0
  153. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +35 -0
  154. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +56 -0
  155. package/templates/default/src/app/(base)/test-runs/create/page.tsx +47 -0
  156. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -0
  157. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +512 -0
  158. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -0
  159. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -0
  160. package/templates/default/src/app/(base)/test-suites/create/page.tsx +45 -0
  161. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +55 -0
  162. package/templates/default/src/app/(base)/test-suites/page.tsx +82 -0
  163. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +269 -0
  164. package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +97 -0
  165. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -0
  166. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -0
  167. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +13 -0
  168. package/templates/default/src/app/(dashboard-components)/data-card.tsx +27 -0
  169. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +57 -0
  170. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -0
  171. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +45 -0
  172. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +133 -0
  173. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +420 -0
  174. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -0
  175. package/templates/default/src/app/favicon.ico +0 -0
  176. package/templates/default/src/app/globals.css +147 -0
  177. package/templates/default/src/app/layout.tsx +171 -0
  178. package/templates/default/src/app/page.tsx +64 -0
  179. package/templates/default/src/assets/icons/empty-tube.tsx +23 -0
  180. package/templates/default/src/assets/icons/tube-plus.tsx +29 -0
  181. package/templates/default/src/components/base-node.tsx +21 -0
  182. package/templates/default/src/components/chart/pie-chart.tsx +73 -0
  183. package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -0
  184. package/templates/default/src/components/data-state/empty-state.tsx +40 -0
  185. package/templates/default/src/components/data-visualization/info-card.tsx +70 -0
  186. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -0
  187. package/templates/default/src/components/devtools/providers.tsx +13 -0
  188. package/templates/default/src/components/diagram/button-edge.tsx +54 -0
  189. package/templates/default/src/components/diagram/dynamic-parameters.tsx +438 -0
  190. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -0
  191. package/templates/default/src/components/diagram/flow-diagram.tsx +470 -0
  192. package/templates/default/src/components/diagram/node-form.tsx +262 -0
  193. package/templates/default/src/components/diagram/options-header-node.tsx +57 -0
  194. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -0
  195. package/templates/default/src/components/form/error-message.tsx +7 -0
  196. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -0
  197. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -0
  198. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -0
  199. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -0
  200. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -0
  201. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -0
  202. package/templates/default/src/components/logo.tsx +15 -0
  203. package/templates/default/src/components/navigation/command-badge.tsx +34 -0
  204. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -0
  205. package/templates/default/src/components/navigation/entity-search-command.tsx +116 -0
  206. package/templates/default/src/components/navigation/nav-card.tsx +31 -0
  207. package/templates/default/src/components/navigation/nav-command.tsx +508 -0
  208. package/templates/default/src/components/navigation/nav-link.tsx +60 -0
  209. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -0
  210. package/templates/default/src/components/node-header.tsx +159 -0
  211. package/templates/default/src/components/reports/test-case-logs-modal.tsx +253 -0
  212. package/templates/default/src/components/table/table-actions.tsx +172 -0
  213. package/templates/default/src/components/test-run/download-logs-button.tsx +99 -0
  214. package/templates/default/src/components/test-run/log-viewer.tsx +445 -0
  215. package/templates/default/src/components/test-run/test-run-details.tsx +611 -0
  216. package/templates/default/src/components/test-run/test-run-header.tsx +149 -0
  217. package/templates/default/src/components/test-run/view-report-button.tsx +102 -0
  218. package/templates/default/src/components/theme/mode-toggle.tsx +54 -0
  219. package/templates/default/src/components/theme/theme-provider.tsx +8 -0
  220. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -0
  221. package/templates/default/src/components/typography/page-header.tsx +7 -0
  222. package/templates/default/src/components/ui/alert-dialog.tsx +106 -0
  223. package/templates/default/src/components/ui/alert.tsx +43 -0
  224. package/templates/default/src/components/ui/avatar.tsx +40 -0
  225. package/templates/default/src/components/ui/badge.tsx +29 -0
  226. package/templates/default/src/components/ui/button.tsx +47 -0
  227. package/templates/default/src/components/ui/calendar.tsx +158 -0
  228. package/templates/default/src/components/ui/card.tsx +43 -0
  229. package/templates/default/src/components/ui/chart.tsx +369 -0
  230. package/templates/default/src/components/ui/checkbox.tsx +28 -0
  231. package/templates/default/src/components/ui/command.tsx +135 -0
  232. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -0
  233. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -0
  234. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -0
  235. package/templates/default/src/components/ui/data-table.tsx +267 -0
  236. package/templates/default/src/components/ui/dialog.tsx +97 -0
  237. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -0
  238. package/templates/default/src/components/ui/empty.tsx +104 -0
  239. package/templates/default/src/components/ui/input.tsx +22 -0
  240. package/templates/default/src/components/ui/kbd.tsx +28 -0
  241. package/templates/default/src/components/ui/label.tsx +19 -0
  242. package/templates/default/src/components/ui/loading.tsx +12 -0
  243. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -0
  244. package/templates/default/src/components/ui/multi-select.tsx +142 -0
  245. package/templates/default/src/components/ui/navigation-menu.tsx +120 -0
  246. package/templates/default/src/components/ui/popover.tsx +33 -0
  247. package/templates/default/src/components/ui/progress.tsx +25 -0
  248. package/templates/default/src/components/ui/radio-group.tsx +44 -0
  249. package/templates/default/src/components/ui/scroll-area.tsx +40 -0
  250. package/templates/default/src/components/ui/select.tsx +144 -0
  251. package/templates/default/src/components/ui/separator.tsx +22 -0
  252. package/templates/default/src/components/ui/skeleton.tsx +7 -0
  253. package/templates/default/src/components/ui/table.tsx +76 -0
  254. package/templates/default/src/components/ui/tabs.tsx +55 -0
  255. package/templates/default/src/components/ui/textarea.tsx +21 -0
  256. package/templates/default/src/components/ui/toast.tsx +113 -0
  257. package/templates/default/src/components/ui/toaster.tsx +26 -0
  258. package/templates/default/src/components/ui/tooltip.tsx +32 -0
  259. package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -0
  260. package/templates/default/src/config/db-config.ts +10 -0
  261. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -0
  262. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -0
  263. package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -0
  264. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -0
  265. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -0
  266. package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -0
  267. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -0
  268. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -0
  269. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -0
  270. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -0
  271. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -0
  272. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -0
  273. package/templates/default/src/constants/form-opts/test-run-form-opts.ts +31 -0
  274. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -0
  275. package/templates/default/src/hooks/use-toast.ts +187 -0
  276. package/templates/default/src/lib/bidirectional-sync.ts +432 -0
  277. package/templates/default/src/lib/database-sync.ts +531 -0
  278. package/templates/default/src/lib/environment-file-utils.ts +221 -0
  279. package/templates/default/src/lib/feature-file-generator.ts +411 -0
  280. package/templates/default/src/lib/gherkin-parser.ts +259 -0
  281. package/templates/default/src/lib/locator-group-file-utils.ts +370 -0
  282. package/templates/default/src/lib/metrics/metric-calculator.ts +613 -0
  283. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -0
  284. package/templates/default/src/lib/path-helpers/module-path.ts +71 -0
  285. package/templates/default/src/lib/test-case-utils.ts +6 -0
  286. package/templates/default/src/lib/test-run/log-formatter.ts +83 -0
  287. package/templates/default/src/lib/test-run/process-manager.ts +191 -0
  288. package/templates/default/src/lib/test-run/report-parser.ts +316 -0
  289. package/templates/default/src/lib/test-run/test-run-executor.ts +144 -0
  290. package/templates/default/src/lib/test-run/winston-logger.ts +95 -0
  291. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -0
  292. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -0
  293. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -0
  294. package/templates/default/src/lib/utils/node-param-validation.ts +81 -0
  295. package/templates/default/src/lib/utils/template-step-file-generator.ts +167 -0
  296. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +723 -0
  297. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -0
  298. package/templates/default/src/lib/utils.ts +31 -0
  299. package/templates/default/src/tests/config/environments/environments.json +14 -0
  300. package/templates/default/src/tests/config/executor/world.ts +41 -0
  301. package/templates/default/src/tests/executor.ts +80 -0
  302. package/templates/default/src/tests/hooks/hooks.ts +99 -0
  303. package/templates/default/src/tests/mapping/locator-map.json +1 -0
  304. package/templates/default/src/tests/steps/actions/click.step.ts +62 -0
  305. package/templates/default/src/tests/steps/actions/hover.step.ts +31 -0
  306. package/templates/default/src/tests/steps/actions/input.step.ts +149 -0
  307. package/templates/default/src/tests/steps/actions/navigation.step.ts +72 -0
  308. package/templates/default/src/tests/steps/actions/random_data.step.ts +146 -0
  309. package/templates/default/src/tests/steps/actions/store.step.ts +90 -0
  310. package/templates/default/src/tests/steps/actions/wait.step.ts +107 -0
  311. package/templates/default/src/tests/steps/validations/active_state_assertion.step.ts +34 -0
  312. package/templates/default/src/tests/steps/validations/navigation_assertion.step.ts +23 -0
  313. package/templates/default/src/tests/steps/validations/text_assertion.step.ts +111 -0
  314. package/templates/default/src/tests/steps/validations/visibility_assertion.step.ts +30 -0
  315. package/templates/default/src/tests/support/parameter-types.ts +12 -0
  316. package/templates/default/src/tests/utils/cache.util.ts +260 -0
  317. package/templates/default/src/tests/utils/cli.util.ts +177 -0
  318. package/templates/default/src/tests/utils/environment.util.ts +65 -0
  319. package/templates/default/src/tests/utils/locator.util.ts +248 -0
  320. package/templates/default/src/tests/utils/random-data.util.ts +45 -0
  321. package/templates/default/src/tests/utils/spawner.util.ts +617 -0
  322. package/templates/default/src/types/diagram/diagram.ts +34 -0
  323. package/templates/default/src/types/diagram/template-step.ts +11 -0
  324. package/templates/default/src/types/executor/browser.type.ts +1 -0
  325. package/templates/default/src/types/form/actionHandler.ts +6 -0
  326. package/templates/default/src/types/locator/locator.type.ts +11 -0
  327. package/templates/default/src/types/step/step.type.ts +1 -0
  328. package/templates/default/src/types/table/data-table.ts +6 -0
  329. package/templates/default/tailwind.config.ts +62 -0
  330. package/templates/default/tsconfig.json +28 -0
@@ -0,0 +1,723 @@
1
+ import { promises as fs } from 'fs'
2
+ import { TemplateStep, TemplateStepGroupType } from '@prisma/client'
3
+ import { ensureStepsDirectory, getFilePath, formatFileContent } from './template-step-file-generator'
4
+
5
+ /**
6
+ * Generates JSDoc comments for a template step group
7
+ */
8
+ function generateGroupJSDocComment(
9
+ name: string,
10
+ description: string | null,
11
+ type: TemplateStepGroupType | string,
12
+ ): string {
13
+ const lines = ['/**']
14
+ lines.push(` * @name ${name}`)
15
+ if (description) {
16
+ lines.push(` * @description ${description}`)
17
+ }
18
+ lines.push(` * @type ${type}`)
19
+ lines.push(' */')
20
+ return lines.join('\n')
21
+ }
22
+
23
+ /**
24
+ * Extracts the bounds of the group JSDoc comment block at the top of the file
25
+ * Returns null if no group JSDoc is found
26
+ * Group JSDoc must be at the very top (line 0) and contain @type (which distinguishes it from step JSDoc)
27
+ */
28
+ function extractGroupJSDocBounds(content: string): { startLine: number; endLine: number } | null {
29
+ const lines = content.split('\n')
30
+
31
+ // Look for JSDoc comment at the very top of the file
32
+ if (lines.length === 0) {
33
+ return null
34
+ }
35
+
36
+ const firstLine = lines[0].trim()
37
+ if (!firstLine.startsWith('/**')) {
38
+ return null
39
+ }
40
+
41
+ // Check if this JSDoc contains group metadata (@type distinguishes group from step JSDoc)
42
+ let hasType = false
43
+ let endLine = -1
44
+
45
+ // Look through the JSDoc block (should end within first few lines)
46
+ for (let i = 0; i < lines.length && i < 10; i++) {
47
+ const line = lines[i].trim()
48
+
49
+ if (line === '*/') {
50
+ // Found end of JSDoc
51
+ endLine = i
52
+ break
53
+ } else if (line.startsWith('* @type') || line.startsWith('*@type')) {
54
+ // Found @type - this is group metadata (steps have @icon, not @type)
55
+ hasType = true
56
+ }
57
+ }
58
+
59
+ // If we found a JSDoc block at the top with @type, return its bounds
60
+ if (hasType && endLine >= 0) {
61
+ return { startLine: 0, endLine }
62
+ }
63
+
64
+ return null
65
+ }
66
+
67
+ /**
68
+ * Ensures group JSDoc exists and is up-to-date at the top of the file
69
+ * Preserves all other content including imports and template steps
70
+ */
71
+ export function ensureGroupJSDoc(
72
+ content: string,
73
+ name: string,
74
+ description: string | null,
75
+ type: TemplateStepGroupType | string,
76
+ ): string {
77
+ const jsdocBounds = extractGroupJSDocBounds(content)
78
+ const newJSDoc = generateGroupJSDocComment(name, description, type)
79
+
80
+ if (jsdocBounds) {
81
+ // Replace existing group JSDoc
82
+ const lines = content.split('\n')
83
+ const afterJSDoc = lines.slice(jsdocBounds.endLine + 1).join('\n')
84
+
85
+ // Combine: new JSDoc + content after old JSDoc
86
+ // Handle spacing - ensure there's proper spacing after JSDoc
87
+ if (afterJSDoc.trim()) {
88
+ return `${newJSDoc}\n${afterJSDoc.trimStart()}`
89
+ }
90
+ return `${newJSDoc}\n${afterJSDoc}`
91
+ } else {
92
+ // No existing group JSDoc, add it at the very top
93
+ if (content.trim()) {
94
+ return `${newJSDoc}\n${content.trimStart()}`
95
+ }
96
+ return `${newJSDoc}\n${content}`
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Generates JSDoc comments for a template step
102
+ */
103
+ function generateJSDocComment(templateStep: TemplateStep): string {
104
+ const lines = ['/**']
105
+ lines.push(` * @name ${templateStep.name}`)
106
+ if (templateStep.description) {
107
+ lines.push(` * @description ${templateStep.description}`)
108
+ }
109
+ lines.push(` * @icon ${templateStep.icon}`)
110
+ lines.push(' */')
111
+ return lines.join('\n')
112
+ }
113
+
114
+ /**
115
+ * Wraps a function definition with JSDoc comments
116
+ * If the function already has JSDoc comments, replaces them
117
+ */
118
+ function wrapFunctionWithJSDoc(functionDefinition: string, templateStep: TemplateStep): string {
119
+ const jsdoc = generateJSDocComment(templateStep)
120
+
121
+ // Remove existing JSDoc comments if present
122
+ const cleanedDefinition = functionDefinition.replace(/\/\*\*[\s\S]*?\*\/\s*/g, '').trim()
123
+
124
+ // Prepend JSDoc and return
125
+ return `${jsdoc}\n${cleanedDefinition}`
126
+ }
127
+
128
+ /**
129
+ * Checks if a template step update requires file changes
130
+ * Signature, parameter, and metadata (name, description, icon) changes require file updates
131
+ */
132
+ function requiresFileUpdate(oldStep: TemplateStep, newStep: TemplateStep): boolean {
133
+ // Check if signature changed
134
+ if (oldStep.signature !== newStep.signature) {
135
+ return true
136
+ }
137
+
138
+ // Check if function definition changed (this includes parameter changes)
139
+ if (oldStep.functionDefinition !== newStep.functionDefinition) {
140
+ return true
141
+ }
142
+
143
+ // Check if metadata changed (name, description, icon) - these affect JSDoc comments
144
+ if (oldStep.name !== newStep.name) {
145
+ return true
146
+ }
147
+
148
+ if (oldStep.description !== newStep.description) {
149
+ return true
150
+ }
151
+
152
+ if (oldStep.icon !== newStep.icon) {
153
+ return true
154
+ }
155
+
156
+ // No file changes needed for other updates
157
+ return false
158
+ }
159
+
160
+ /**
161
+ * Finds the start and end lines of a step definition function in the file
162
+ * Uses flexible signature matching to handle prettier formatting variations
163
+ * Handles JSDoc comments that may precede the function
164
+ */
165
+ function findStepFunctionBounds(content: string, signature: string): { startLine: number; endLine: number } | null {
166
+ const lines = content.split('\n')
167
+
168
+ // Search for the signature content across multiple lines (handles prettier formatting)
169
+ for (let i = 0; i < lines.length; i++) {
170
+ const line = lines[i]
171
+ const trimmedLine = line.trim()
172
+
173
+ // Skip JSDoc comment lines (but not the function definition itself)
174
+ if (
175
+ trimmedLine.startsWith('/**') ||
176
+ trimmedLine === '*/' ||
177
+ (trimmedLine.startsWith('*') && !trimmedLine.startsWith('When(') && !trimmedLine.startsWith('Then('))
178
+ ) {
179
+ continue
180
+ }
181
+
182
+ // Check if this line starts a step definition
183
+ if (trimmedLine.startsWith('When(') || trimmedLine.startsWith('Then(')) {
184
+ // Look ahead to find the complete signature across multiple lines
185
+ let signatureFound = false
186
+ let currentSignature = ''
187
+
188
+ // Collect signature content from current and following lines until we hit the function start
189
+ for (let j = i; j < lines.length; j++) {
190
+ const currentLine = lines[j]
191
+ currentSignature += currentLine
192
+
193
+ // Check if we've found our signature
194
+ if (currentSignature.includes(signature)) {
195
+ signatureFound = true
196
+ break
197
+ }
198
+
199
+ // If we hit the function opening brace, stop looking for signature
200
+ if (currentLine.includes('async function') || currentLine.includes('function(')) {
201
+ break
202
+ }
203
+ }
204
+
205
+ if (signatureFound) {
206
+ // Found the start, now find the end using bracket and parenthesis counting
207
+ // Also check backwards for JSDoc comments to include them in the bounds
208
+ let functionStartLine = i
209
+ let braceCount = 0
210
+ let parenCount = 0
211
+ let startParenFound = false
212
+
213
+ // Check if there are JSDoc comments before this function
214
+ if (i > 0) {
215
+ let jsdocStart = i - 1
216
+ // Look backwards for JSDoc comment block
217
+ // First, check if the previous line is the end of a JSDoc block
218
+ if (lines[jsdocStart].trim() === '*/') {
219
+ // Found end of JSDoc, continue looking backwards for the start
220
+ jsdocStart--
221
+ while (jsdocStart >= 0) {
222
+ const prevLine = lines[jsdocStart].trim()
223
+ if (prevLine.startsWith('/**')) {
224
+ // Found start of JSDoc block
225
+ functionStartLine = jsdocStart
226
+ break
227
+ } else if (prevLine.startsWith('*') || prevLine === '') {
228
+ jsdocStart--
229
+ } else {
230
+ break
231
+ }
232
+ }
233
+ }
234
+ }
235
+
236
+ for (let j = i; j < lines.length; j++) {
237
+ const currentLine = lines[j]
238
+
239
+ // Count opening and closing parentheses and braces
240
+ for (const char of currentLine) {
241
+ if (char === '(') {
242
+ parenCount++
243
+ startParenFound = true
244
+ } else if (char === ')') {
245
+ parenCount--
246
+ } else if (char === '{') {
247
+ braceCount++
248
+ } else if (char === '}') {
249
+ braceCount--
250
+ }
251
+ }
252
+
253
+ // If we found the opening parenthesis and both parentheses and braces are balanced, we're done
254
+ // This ensures we capture the complete When(...) or Then(...) call including the closing )
255
+ if (startParenFound && parenCount === 0 && braceCount === 0) {
256
+ return { startLine: functionStartLine, endLine: j }
257
+ }
258
+ }
259
+
260
+ // If we reach here, something went wrong with bracket counting
261
+ console.warn(`Could not find end of function for signature: ${signature}`)
262
+ return null
263
+ }
264
+ }
265
+ }
266
+
267
+ return null
268
+ }
269
+
270
+ /**
271
+ * Required import definitions
272
+ */
273
+ interface RequiredImport {
274
+ module: string
275
+ namedExports: string[]
276
+ from: string
277
+ }
278
+
279
+ const REQUIRED_IMPORTS: RequiredImport[] = [
280
+ {
281
+ module: '@cucumber/cucumber',
282
+ namedExports: ['When'],
283
+ from: '@cucumber/cucumber',
284
+ },
285
+ {
286
+ module: '../../config/executor/world',
287
+ namedExports: ['CustomWorld'],
288
+ from: '../../config/executor/world.js',
289
+ },
290
+ {
291
+ module: '@/types/locator/locator.type',
292
+ namedExports: ['SelectorName'],
293
+ from: '@/types/locator/locator.type',
294
+ },
295
+ {
296
+ module: '../../utils/locator.util',
297
+ namedExports: ['resolveLocator'],
298
+ from: '../../utils/locator.util.js',
299
+ },
300
+ ]
301
+
302
+ /**
303
+ * Parses import statements from file content
304
+ * Returns an array of import objects with their line numbers
305
+ */
306
+ interface ParsedImport {
307
+ line: number
308
+ fullLine: string
309
+ namedExports: string[]
310
+ from: string
311
+ }
312
+
313
+ function parseImports(content: string): ParsedImport[] {
314
+ const lines = content.split('\n')
315
+ const imports: ParsedImport[] = []
316
+
317
+ for (let i = 0; i < lines.length; i++) {
318
+ const line = lines[i].trim()
319
+ // Match import statements: import { ... } from '...'
320
+ const importMatch = line.match(/^import\s+{([^}]+)}\s+from\s+['"]([^'"]+)['"];?$/)
321
+ if (importMatch) {
322
+ const namedExportsStr = importMatch[1]
323
+ const fromPath = importMatch[2]
324
+ // Parse named exports, handling whitespace
325
+ const namedExports = namedExportsStr
326
+ .split(',')
327
+ .map(exp => exp.trim())
328
+ .filter(Boolean)
329
+
330
+ imports.push({
331
+ line: i,
332
+ fullLine: line,
333
+ namedExports,
334
+ from: fromPath,
335
+ })
336
+ }
337
+ }
338
+
339
+ return imports
340
+ }
341
+
342
+ /**
343
+ * Checks if a required import is present in the parsed imports
344
+ * Handles variations like with/without .js extension, different import styles
345
+ */
346
+ function hasRequiredImport(parsedImports: ParsedImport[], required: RequiredImport): boolean {
347
+ for (const parsed of parsedImports) {
348
+ // Normalize paths for comparison (remove .js extension if present)
349
+ const normalizedParsedFrom = parsed.from.replace(/\.js$/, '')
350
+ const normalizedRequiredFrom = required.from.replace(/\.js$/, '')
351
+
352
+ // Check if the module path matches (with or without .js)
353
+ if (normalizedParsedFrom === normalizedRequiredFrom || parsed.from === required.from) {
354
+ // Check if all required named exports are present
355
+ const hasAllExports = required.namedExports.every(exp => parsed.namedExports.includes(exp))
356
+ if (hasAllExports) {
357
+ return true
358
+ }
359
+ }
360
+ }
361
+ return false
362
+ }
363
+
364
+ /**
365
+ * Generates an import statement string for a required import
366
+ */
367
+ function generateImportStatement(required: RequiredImport): string {
368
+ return `import { ${required.namedExports.join(', ')} } from '${required.from}';`
369
+ }
370
+
371
+ /**
372
+ * Ensures required imports are present in the file content
373
+ * Only adds missing imports, preserves existing imports and their order
374
+ * Preserves group JSDoc at the top if it exists
375
+ * @internal - exported for testing purposes
376
+ */
377
+ export function ensureRequiredImports(content: string): string {
378
+ // Parse existing imports
379
+ const parsedImports = parseImports(content)
380
+
381
+ // Check which required imports are missing
382
+ const missingImports: RequiredImport[] = []
383
+ for (const required of REQUIRED_IMPORTS) {
384
+ if (!hasRequiredImport(parsedImports, required)) {
385
+ missingImports.push(required)
386
+ }
387
+ }
388
+
389
+ // If all imports are present, return content unchanged
390
+ if (missingImports.length === 0) {
391
+ return content
392
+ }
393
+
394
+ // Generate import statements for missing imports
395
+ const newImportStatements = missingImports.map(generateImportStatement).join('\n') + '\n'
396
+
397
+ // Check if there's a group JSDoc at the top
398
+ const jsdocBounds = extractGroupJSDocBounds(content)
399
+
400
+ if (jsdocBounds) {
401
+ // Preserve JSDoc, add missing imports after it
402
+ const lines = content.split('\n')
403
+ const jsdoc = lines.slice(jsdocBounds.startLine, jsdocBounds.endLine + 1).join('\n')
404
+ const afterJSDoc = lines.slice(jsdocBounds.endLine + 1).join('\n')
405
+
406
+ // Check if there are already imports after JSDoc
407
+ const afterJSDocTrimmed = afterJSDoc.trimStart()
408
+ if (afterJSDocTrimmed.startsWith('import ')) {
409
+ // Imports already exist, add missing ones right after JSDoc (before existing imports)
410
+ // This preserves the existing import order
411
+ return `${jsdoc}\n${newImportStatements}${afterJSDoc}`
412
+ } else {
413
+ // No imports after JSDoc, add them
414
+ return `${jsdoc}\n${newImportStatements}${afterJSDocTrimmed}`
415
+ }
416
+ }
417
+
418
+ // No JSDoc, check if there are existing imports
419
+ if (parsedImports.length > 0) {
420
+ // Find the first import line
421
+ const firstImportLine = parsedImports[0].line
422
+ const lines = content.split('\n')
423
+ const beforeImports = lines.slice(0, firstImportLine).join('\n')
424
+ const afterImports = lines.slice(firstImportLine).join('\n')
425
+ // Add missing imports before existing imports
426
+ return `${beforeImports}${beforeImports ? '\n' : ''}${newImportStatements}${afterImports}`
427
+ }
428
+
429
+ // No JSDoc, no existing imports, add imports at the beginning
430
+ return newImportStatements + content
431
+ }
432
+
433
+ /**
434
+ * Intelligently adds a new template step to the file
435
+ * Preserves existing content including imports, types, and other code
436
+ */
437
+ export async function addTemplateStepToFile(
438
+ groupName: string,
439
+ templateStep: TemplateStep,
440
+ type: TemplateStepGroupType | string,
441
+ ): Promise<void> {
442
+ try {
443
+ await ensureStepsDirectory()
444
+ const filePath = getFilePath(groupName, type)
445
+
446
+ let existingContent = ''
447
+ try {
448
+ existingContent = await fs.readFile(filePath, 'utf8')
449
+ } catch {
450
+ // File doesn't exist, start with empty content
451
+ existingContent = ''
452
+ }
453
+
454
+ // Ensure required imports are present
455
+ existingContent = ensureRequiredImports(existingContent)
456
+
457
+ // Check if step with this signature already exists
458
+ const bounds = findStepFunctionBounds(existingContent, templateStep.signature)
459
+
460
+ let newContent: string
461
+
462
+ if (bounds) {
463
+ // Replace existing step - replace the entire function with JSDoc comments
464
+ const lines = existingContent.split('\n')
465
+ const beforeStep = lines.slice(0, bounds.startLine).join('\n')
466
+ const afterStep = lines.slice(bounds.endLine + 1).join('\n')
467
+
468
+ // Wrap the function definition with JSDoc comments
469
+ const wrappedStepDefinition = wrapFunctionWithJSDoc(templateStep.functionDefinition || '', templateStep)
470
+ newContent =
471
+ beforeStep +
472
+ (beforeStep.trim() ? '\n\n' : '') +
473
+ wrappedStepDefinition +
474
+ (afterStep.trim() ? '\n' : '') +
475
+ afterStep
476
+ } else {
477
+ // Add new step at the end - wrap with JSDoc comments
478
+ const wrappedStepDefinition = wrapFunctionWithJSDoc(templateStep.functionDefinition || '', templateStep)
479
+ newContent = existingContent + (existingContent ? '\n\n' : '') + wrappedStepDefinition
480
+ }
481
+
482
+ // Format and write the file
483
+ const formattedContent = await formatFileContent(newContent)
484
+ await fs.writeFile(filePath, formattedContent, 'utf8')
485
+
486
+ console.log(`Template step added to file: ${filePath}`)
487
+ } catch (error) {
488
+ console.error(`Failed to add template step to file for group "${groupName}":`, error)
489
+ throw new Error(`File update failed: ${error}`)
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Intelligently removes a template step from the file
495
+ * Only removes the specific step, preserves everything else including imports and types
496
+ */
497
+ export async function removeTemplateStepFromFile(
498
+ groupName: string,
499
+ templateStep: TemplateStep,
500
+ type: TemplateStepGroupType | string,
501
+ ): Promise<void> {
502
+ try {
503
+ await ensureStepsDirectory()
504
+ const filePath = getFilePath(groupName, type)
505
+
506
+ let existingContent = ''
507
+ try {
508
+ existingContent = await fs.readFile(filePath, 'utf8')
509
+ } catch {
510
+ // File doesn't exist, nothing to remove
511
+ return
512
+ }
513
+
514
+ // Ensure required imports are present
515
+ existingContent = ensureRequiredImports(existingContent)
516
+
517
+ // Find the step to remove
518
+ const bounds = findStepFunctionBounds(existingContent, templateStep.signature)
519
+
520
+ if (!bounds) {
521
+ // Step not found, nothing to remove
522
+ return
523
+ }
524
+
525
+ // Remove the entire function block
526
+ const lines = existingContent.split('\n')
527
+ const beforeStep = lines.slice(0, bounds.startLine).join('\n')
528
+ const afterStep = lines.slice(bounds.endLine + 1).join('\n')
529
+
530
+ // Combine content, handling empty sections
531
+ let newContent = ''
532
+ if (beforeStep.trim()) {
533
+ newContent += beforeStep.trim()
534
+ }
535
+ if (afterStep.trim()) {
536
+ if (newContent) newContent += '\n'
537
+ newContent += afterStep.trim()
538
+ }
539
+
540
+ // Format and write the file
541
+ const formattedContent = await formatFileContent(newContent)
542
+ await fs.writeFile(filePath, formattedContent, 'utf8')
543
+
544
+ console.log(`Template step removed from file: ${filePath}`)
545
+ } catch (error) {
546
+ console.error(`Failed to remove template step from file for group "${groupName}":`, error)
547
+ throw new Error(`File update failed: ${error}`)
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Intelligently updates a template step in the file
553
+ * Only updates the specific step, preserves everything else including imports and types
554
+ */
555
+ export async function updateTemplateStepInFile(
556
+ groupName: string,
557
+ templateStep: TemplateStep,
558
+ type: TemplateStepGroupType | string,
559
+ oldStep?: TemplateStep,
560
+ ): Promise<void> {
561
+ try {
562
+ // If we have the old step data, check if file changes are needed
563
+ if (oldStep && !requiresFileUpdate(oldStep, templateStep)) {
564
+ console.log(`No file changes needed for template step update: ${templateStep.signature}`)
565
+ return
566
+ }
567
+
568
+ await ensureStepsDirectory()
569
+ const filePath = getFilePath(groupName, type)
570
+
571
+ let existingContent = ''
572
+ try {
573
+ existingContent = await fs.readFile(filePath, 'utf8')
574
+ } catch {
575
+ // File doesn't exist, create it with the updated step
576
+ await addTemplateStepToFile(groupName, templateStep, type)
577
+ return
578
+ }
579
+
580
+ // Ensure required imports are present
581
+ existingContent = ensureRequiredImports(existingContent)
582
+
583
+ // Find the step to update
584
+ const bounds = findStepFunctionBounds(existingContent, oldStep!.signature)
585
+
586
+ if (bounds) {
587
+ // Update existing step - replace the entire function with JSDoc comments
588
+ const lines = existingContent.split('\n')
589
+ const beforeStep = lines.slice(0, bounds.startLine).join('\n')
590
+ const afterStep = lines.slice(bounds.endLine + 1).join('\n')
591
+
592
+ // Wrap the function definition with JSDoc comments
593
+ const wrappedStepDefinition = wrapFunctionWithJSDoc(templateStep.functionDefinition || '', templateStep)
594
+ const newContent =
595
+ beforeStep +
596
+ (beforeStep.trim() ? '\n\n' : '') +
597
+ wrappedStepDefinition +
598
+ (afterStep.trim() ? '\n' : '') +
599
+ afterStep
600
+
601
+ // Format and write the file
602
+ const formattedContent = await formatFileContent(newContent)
603
+ await fs.writeFile(filePath, formattedContent, 'utf8')
604
+
605
+ console.log(`Template step updated in file: ${filePath}`)
606
+ } else {
607
+ // Step not found, add it
608
+ await addTemplateStepToFile(groupName, templateStep, type)
609
+ }
610
+ } catch (error) {
611
+ console.error(`Failed to update template step in file for group "${groupName}":`, error)
612
+ throw new Error(`File update failed: ${error}`)
613
+ }
614
+ }
615
+
616
+ /**
617
+ * Creates a placeholder file for a new template step group
618
+ */
619
+ export async function createTemplateStepGroupFile(
620
+ groupName: string,
621
+ type: TemplateStepGroupType | string,
622
+ description?: string | null,
623
+ ): Promise<void> {
624
+ try {
625
+ await ensureStepsDirectory()
626
+ const filePath = getFilePath(groupName, type)
627
+
628
+ // Generate content with JSDoc at the top, then imports, then placeholder comment
629
+ const groupJSDoc = generateGroupJSDocComment(groupName, description || null, type)
630
+ const requiredImports = `import { When } from '@cucumber/cucumber';
631
+ import { CustomWorld } from '../../config/executor/world.js';
632
+ import { SelectorName } from '@/types/locator/locator.type';
633
+ import { resolveLocator } from '../../utils/locator.util.js';
634
+
635
+ `
636
+ const placeholderComment =
637
+ '// This file is generated automatically. Add template steps to this group to generate content.'
638
+
639
+ // Combine: JSDoc + imports + placeholder comment
640
+ const placeholderContent = `${groupJSDoc}\n${requiredImports}${placeholderComment}`
641
+
642
+ // Format and write the file
643
+ const formattedContent = await formatFileContent(placeholderContent)
644
+ await fs.writeFile(filePath, formattedContent, 'utf8')
645
+
646
+ console.log(`Placeholder file created for template step group: ${groupName}`)
647
+ } catch (error) {
648
+ console.error(`Failed to create placeholder file for group "${groupName}":`, error)
649
+ throw new Error(`File creation failed: ${error}`)
650
+ }
651
+ }
652
+
653
+ /**
654
+ * Deletes the file for a template step group
655
+ */
656
+ export async function removeTemplateStepGroupFile(
657
+ groupName: string,
658
+ type: TemplateStepGroupType | string,
659
+ ): Promise<void> {
660
+ try {
661
+ const filePath = getFilePath(groupName, type)
662
+
663
+ try {
664
+ await fs.access(filePath)
665
+ } catch {
666
+ // File doesn't exist, nothing to delete
667
+ return
668
+ }
669
+
670
+ await fs.unlink(filePath)
671
+
672
+ console.log(`Template step group file deleted: ${filePath}`)
673
+ } catch (error) {
674
+ console.error(`Failed to delete file for group "${groupName}":`, error)
675
+ throw new Error(`File deletion failed: ${error}`)
676
+ }
677
+ }
678
+
679
+ /**
680
+ * Renames a template step group file when the group name or type changes
681
+ * Preserves all existing content and updates JSDoc metadata
682
+ * If type changed, moves file from old folder to new folder
683
+ */
684
+ export async function renameTemplateStepGroupFile(
685
+ oldGroupName: string,
686
+ newGroupName: string,
687
+ oldType: TemplateStepGroupType | string,
688
+ newType: TemplateStepGroupType | string,
689
+ newDescription?: string | null,
690
+ ): Promise<void> {
691
+ try {
692
+ await ensureStepsDirectory()
693
+ const oldFilePath = getFilePath(oldGroupName, oldType)
694
+ const newFilePath = getFilePath(newGroupName, newType)
695
+
696
+ try {
697
+ // Read the existing file content
698
+ let existingContent = await fs.readFile(oldFilePath, 'utf8')
699
+
700
+ // Update the group JSDoc with new metadata
701
+ existingContent = ensureGroupJSDoc(existingContent, newGroupName, newDescription || null, newType)
702
+
703
+ // Format and write the content to the new file
704
+ const formattedContent = await formatFileContent(existingContent)
705
+ await fs.writeFile(newFilePath, formattedContent, 'utf8')
706
+
707
+ // Remove the old file
708
+ await fs.unlink(oldFilePath)
709
+
710
+ console.log(`Template step group file renamed: ${oldFilePath} → ${newFilePath}`)
711
+ } catch (error) {
712
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
713
+ // Old file doesn't exist, which is fine
714
+ console.log(`Old template step group file doesn't exist: ${oldFilePath}`)
715
+ } else {
716
+ throw error
717
+ }
718
+ }
719
+ } catch (error) {
720
+ console.error(`Failed to rename template step group file from "${oldGroupName}" to "${newGroupName}":`, error)
721
+ throw new Error(`File rename failed: ${error}`)
722
+ }
723
+ }