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.
- package/README.md +52 -0
- package/package.json +63 -0
- package/templates/default/.env.example +2 -0
- package/templates/default/README.md +51 -0
- package/templates/default/appraise.config.json +4 -0
- package/templates/default/components.json +24 -0
- package/templates/default/eslint.config.mjs +15 -0
- package/templates/default/next-env.d.ts +6 -0
- package/templates/default/next.config.ts +7 -0
- package/templates/default/package-lock.json +14321 -0
- package/templates/default/package.json +124 -0
- package/templates/default/postcss.config.mjs +8 -0
- package/templates/default/prisma/migrations/20251026202316_migrate_back_to_sqlite/migration.sql +257 -0
- package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -0
- package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -0
- package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -0
- package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -0
- package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -0
- package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -0
- package/templates/default/prisma/migrations/20251130190737_add_trace_path_to_test_run_test_case/migration.sql +2 -0
- package/templates/default/prisma/migrations/20251213074835_add_log_path_to_test_run/migration.sql +2 -0
- package/templates/default/prisma/migrations/20251213183952_add_name_property_for_the_test_run_entities/migration.sql +30 -0
- package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -0
- package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -0
- package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -0
- package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -0
- package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -0
- package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -0
- package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -0
- package/templates/default/prisma/migrations/migration_lock.toml +3 -0
- package/templates/default/prisma/schema.prisma +554 -0
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/public/file.svg +1 -0
- package/templates/default/public/globe.svg +1 -0
- package/templates/default/public/next.svg +1 -0
- package/templates/default/public/vercel.svg +1 -0
- package/templates/default/public/window.svg +1 -0
- package/templates/default/scripts/regenerate-features.ts +94 -0
- package/templates/default/scripts/setup-env.ts +19 -0
- package/templates/default/scripts/sync-all.ts +341 -0
- package/templates/default/scripts/sync-environments.ts +323 -0
- package/templates/default/scripts/sync-locator-groups.ts +413 -0
- package/templates/default/scripts/sync-locators.ts +402 -0
- package/templates/default/scripts/sync-modules.ts +349 -0
- package/templates/default/scripts/sync-tags.ts +292 -0
- package/templates/default/scripts/sync-template-step-groups.ts +399 -0
- package/templates/default/scripts/sync-template-steps.ts +806 -0
- package/templates/default/scripts/sync-test-cases.ts +905 -0
- package/templates/default/scripts/sync-test-suites.ts +411 -0
- package/templates/default/src/actions/conflict/conflict.action.ts +33 -0
- package/templates/default/src/actions/dashboard/dashboard-actions.ts +241 -0
- package/templates/default/src/actions/environments/environment-actions.ts +205 -0
- package/templates/default/src/actions/locator/locator-actions.ts +547 -0
- package/templates/default/src/actions/locator-groups/locator-group-actions.ts +344 -0
- package/templates/default/src/actions/modules/module-actions.ts +133 -0
- package/templates/default/src/actions/reports/report-actions.ts +614 -0
- package/templates/default/src/actions/review/review-actions.ts +147 -0
- package/templates/default/src/actions/tags/tag-actions.ts +104 -0
- package/templates/default/src/actions/template-step/template-step-actions.ts +332 -0
- package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +278 -0
- package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -0
- package/templates/default/src/actions/test-case/test-case-actions.ts +419 -0
- package/templates/default/src/actions/test-run/test-run-actions.ts +1185 -0
- package/templates/default/src/actions/test-suite/test-suite-actions.ts +253 -0
- package/templates/default/src/actions/user/user-actions.ts +13 -0
- package/templates/default/src/app/(base)/environments/create/page.tsx +28 -0
- package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -0
- package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -0
- package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -0
- package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -0
- package/templates/default/src/app/(base)/environments/page.tsx +59 -0
- package/templates/default/src/app/(base)/layout.tsx +10 -0
- package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -0
- package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -0
- package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -0
- package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -0
- package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -0
- package/templates/default/src/app/(base)/locator-groups/page.tsx +61 -0
- package/templates/default/src/app/(base)/locators/create/page.tsx +38 -0
- package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -0
- package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +90 -0
- package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -0
- package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +45 -0
- package/templates/default/src/app/(base)/locators/page.tsx +65 -0
- package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -0
- package/templates/default/src/app/(base)/modules/create/page.tsx +34 -0
- package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -0
- package/templates/default/src/app/(base)/modules/module-form.tsx +126 -0
- package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -0
- package/templates/default/src/app/(base)/modules/module-table.tsx +24 -0
- package/templates/default/src/app/(base)/modules/page.tsx +59 -0
- package/templates/default/src/app/(base)/reports/[id]/page.tsx +517 -0
- package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -0
- package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -0
- package/templates/default/src/app/(base)/reports/overview-chart.tsx +46 -0
- package/templates/default/src/app/(base)/reports/page.tsx +98 -0
- package/templates/default/src/app/(base)/reports/report-metric-card.tsx +16 -0
- package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -0
- package/templates/default/src/app/(base)/reports/report-table.tsx +72 -0
- package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +131 -0
- package/templates/default/src/app/(base)/reports/report-view-table.tsx +82 -0
- package/templates/default/src/app/(base)/reports/test-cases/page.tsx +42 -0
- package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -0
- package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -0
- package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -0
- package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -0
- package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -0
- package/templates/default/src/app/(base)/reports/view-logs-button.tsx +60 -0
- package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -0
- package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -0
- package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -0
- package/templates/default/src/app/(base)/reviews/page.tsx +26 -0
- package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -0
- package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -0
- package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -0
- package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -0
- package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -0
- package/templates/default/src/app/(base)/tags/create/page.tsx +39 -0
- package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -0
- package/templates/default/src/app/(base)/tags/page.tsx +58 -0
- package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -0
- package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -0
- package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -0
- package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -0
- package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -0
- package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -0
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -0
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -0
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -0
- package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -0
- package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -0
- package/templates/default/src/app/(base)/template-steps/page.tsx +59 -0
- package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -0
- package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -0
- package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -0
- package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -0
- package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -0
- package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -0
- package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -0
- package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -0
- package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -0
- package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -0
- package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -0
- package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -0
- package/templates/default/src/app/(base)/test-cases/page.tsx +60 -0
- package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -0
- package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -0
- package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +90 -0
- package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +35 -0
- package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +56 -0
- package/templates/default/src/app/(base)/test-runs/create/page.tsx +47 -0
- package/templates/default/src/app/(base)/test-runs/page.tsx +60 -0
- package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +512 -0
- package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -0
- package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -0
- package/templates/default/src/app/(base)/test-suites/create/page.tsx +45 -0
- package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +55 -0
- package/templates/default/src/app/(base)/test-suites/page.tsx +82 -0
- package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +269 -0
- package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +97 -0
- package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -0
- package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -0
- package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +13 -0
- package/templates/default/src/app/(dashboard-components)/data-card.tsx +27 -0
- package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +57 -0
- package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -0
- package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +45 -0
- package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +133 -0
- package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +420 -0
- package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -0
- package/templates/default/src/app/favicon.ico +0 -0
- package/templates/default/src/app/globals.css +147 -0
- package/templates/default/src/app/layout.tsx +171 -0
- package/templates/default/src/app/page.tsx +64 -0
- package/templates/default/src/assets/icons/empty-tube.tsx +23 -0
- package/templates/default/src/assets/icons/tube-plus.tsx +29 -0
- package/templates/default/src/components/base-node.tsx +21 -0
- package/templates/default/src/components/chart/pie-chart.tsx +73 -0
- package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -0
- package/templates/default/src/components/data-state/empty-state.tsx +40 -0
- package/templates/default/src/components/data-visualization/info-card.tsx +70 -0
- package/templates/default/src/components/data-visualization/info-grid.tsx +22 -0
- package/templates/default/src/components/devtools/providers.tsx +13 -0
- package/templates/default/src/components/diagram/button-edge.tsx +54 -0
- package/templates/default/src/components/diagram/dynamic-parameters.tsx +438 -0
- package/templates/default/src/components/diagram/edit-header-option.tsx +36 -0
- package/templates/default/src/components/diagram/flow-diagram.tsx +470 -0
- package/templates/default/src/components/diagram/node-form.tsx +262 -0
- package/templates/default/src/components/diagram/options-header-node.tsx +57 -0
- package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -0
- package/templates/default/src/components/form/error-message.tsx +7 -0
- package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -0
- package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -0
- package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -0
- package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -0
- package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -0
- package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -0
- package/templates/default/src/components/logo.tsx +15 -0
- package/templates/default/src/components/navigation/command-badge.tsx +34 -0
- package/templates/default/src/components/navigation/command-chain-input.tsx +51 -0
- package/templates/default/src/components/navigation/entity-search-command.tsx +116 -0
- package/templates/default/src/components/navigation/nav-card.tsx +31 -0
- package/templates/default/src/components/navigation/nav-command.tsx +508 -0
- package/templates/default/src/components/navigation/nav-link.tsx +60 -0
- package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -0
- package/templates/default/src/components/node-header.tsx +159 -0
- package/templates/default/src/components/reports/test-case-logs-modal.tsx +253 -0
- package/templates/default/src/components/table/table-actions.tsx +172 -0
- package/templates/default/src/components/test-run/download-logs-button.tsx +99 -0
- package/templates/default/src/components/test-run/log-viewer.tsx +445 -0
- package/templates/default/src/components/test-run/test-run-details.tsx +611 -0
- package/templates/default/src/components/test-run/test-run-header.tsx +149 -0
- package/templates/default/src/components/test-run/view-report-button.tsx +102 -0
- package/templates/default/src/components/theme/mode-toggle.tsx +54 -0
- package/templates/default/src/components/theme/theme-provider.tsx +8 -0
- package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -0
- package/templates/default/src/components/typography/page-header.tsx +7 -0
- package/templates/default/src/components/ui/alert-dialog.tsx +106 -0
- package/templates/default/src/components/ui/alert.tsx +43 -0
- package/templates/default/src/components/ui/avatar.tsx +40 -0
- package/templates/default/src/components/ui/badge.tsx +29 -0
- package/templates/default/src/components/ui/button.tsx +47 -0
- package/templates/default/src/components/ui/calendar.tsx +158 -0
- package/templates/default/src/components/ui/card.tsx +43 -0
- package/templates/default/src/components/ui/chart.tsx +369 -0
- package/templates/default/src/components/ui/checkbox.tsx +28 -0
- package/templates/default/src/components/ui/command.tsx +135 -0
- package/templates/default/src/components/ui/data-table-column-header.tsx +61 -0
- package/templates/default/src/components/ui/data-table-pagination.tsx +87 -0
- package/templates/default/src/components/ui/data-table-view-options.tsx +50 -0
- package/templates/default/src/components/ui/data-table.tsx +267 -0
- package/templates/default/src/components/ui/dialog.tsx +97 -0
- package/templates/default/src/components/ui/dropdown-menu.tsx +182 -0
- package/templates/default/src/components/ui/empty.tsx +104 -0
- package/templates/default/src/components/ui/input.tsx +22 -0
- package/templates/default/src/components/ui/kbd.tsx +28 -0
- package/templates/default/src/components/ui/label.tsx +19 -0
- package/templates/default/src/components/ui/loading.tsx +12 -0
- package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -0
- package/templates/default/src/components/ui/multi-select.tsx +142 -0
- package/templates/default/src/components/ui/navigation-menu.tsx +120 -0
- package/templates/default/src/components/ui/popover.tsx +33 -0
- package/templates/default/src/components/ui/progress.tsx +25 -0
- package/templates/default/src/components/ui/radio-group.tsx +44 -0
- package/templates/default/src/components/ui/scroll-area.tsx +40 -0
- package/templates/default/src/components/ui/select.tsx +144 -0
- package/templates/default/src/components/ui/separator.tsx +22 -0
- package/templates/default/src/components/ui/skeleton.tsx +7 -0
- package/templates/default/src/components/ui/table.tsx +76 -0
- package/templates/default/src/components/ui/tabs.tsx +55 -0
- package/templates/default/src/components/ui/textarea.tsx +21 -0
- package/templates/default/src/components/ui/toast.tsx +113 -0
- package/templates/default/src/components/ui/toaster.tsx +26 -0
- package/templates/default/src/components/ui/tooltip.tsx +32 -0
- package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -0
- package/templates/default/src/config/db-config.ts +10 -0
- package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -0
- package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -0
- package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -0
- package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -0
- package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -0
- package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -0
- package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -0
- package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -0
- package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -0
- package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -0
- package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -0
- package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -0
- package/templates/default/src/constants/form-opts/test-run-form-opts.ts +31 -0
- package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -0
- package/templates/default/src/hooks/use-toast.ts +187 -0
- package/templates/default/src/lib/bidirectional-sync.ts +432 -0
- package/templates/default/src/lib/database-sync.ts +531 -0
- package/templates/default/src/lib/environment-file-utils.ts +221 -0
- package/templates/default/src/lib/feature-file-generator.ts +411 -0
- package/templates/default/src/lib/gherkin-parser.ts +259 -0
- package/templates/default/src/lib/locator-group-file-utils.ts +370 -0
- package/templates/default/src/lib/metrics/metric-calculator.ts +613 -0
- package/templates/default/src/lib/module-hierarchy-builder.ts +205 -0
- package/templates/default/src/lib/path-helpers/module-path.ts +71 -0
- package/templates/default/src/lib/test-case-utils.ts +6 -0
- package/templates/default/src/lib/test-run/log-formatter.ts +83 -0
- package/templates/default/src/lib/test-run/process-manager.ts +191 -0
- package/templates/default/src/lib/test-run/report-parser.ts +316 -0
- package/templates/default/src/lib/test-run/test-run-executor.ts +144 -0
- package/templates/default/src/lib/test-run/winston-logger.ts +95 -0
- package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -0
- package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -0
- package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -0
- package/templates/default/src/lib/utils/node-param-validation.ts +81 -0
- package/templates/default/src/lib/utils/template-step-file-generator.ts +167 -0
- package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +723 -0
- package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -0
- package/templates/default/src/lib/utils.ts +31 -0
- package/templates/default/src/tests/config/environments/environments.json +14 -0
- package/templates/default/src/tests/config/executor/world.ts +41 -0
- package/templates/default/src/tests/executor.ts +80 -0
- package/templates/default/src/tests/hooks/hooks.ts +99 -0
- package/templates/default/src/tests/mapping/locator-map.json +1 -0
- package/templates/default/src/tests/steps/actions/click.step.ts +62 -0
- package/templates/default/src/tests/steps/actions/hover.step.ts +31 -0
- package/templates/default/src/tests/steps/actions/input.step.ts +149 -0
- package/templates/default/src/tests/steps/actions/navigation.step.ts +72 -0
- package/templates/default/src/tests/steps/actions/random_data.step.ts +146 -0
- package/templates/default/src/tests/steps/actions/store.step.ts +90 -0
- package/templates/default/src/tests/steps/actions/wait.step.ts +107 -0
- package/templates/default/src/tests/steps/validations/active_state_assertion.step.ts +34 -0
- package/templates/default/src/tests/steps/validations/navigation_assertion.step.ts +23 -0
- package/templates/default/src/tests/steps/validations/text_assertion.step.ts +111 -0
- package/templates/default/src/tests/steps/validations/visibility_assertion.step.ts +30 -0
- package/templates/default/src/tests/support/parameter-types.ts +12 -0
- package/templates/default/src/tests/utils/cache.util.ts +260 -0
- package/templates/default/src/tests/utils/cli.util.ts +177 -0
- package/templates/default/src/tests/utils/environment.util.ts +65 -0
- package/templates/default/src/tests/utils/locator.util.ts +248 -0
- package/templates/default/src/tests/utils/random-data.util.ts +45 -0
- package/templates/default/src/tests/utils/spawner.util.ts +617 -0
- package/templates/default/src/types/diagram/diagram.ts +34 -0
- package/templates/default/src/types/diagram/template-step.ts +11 -0
- package/templates/default/src/types/executor/browser.type.ts +1 -0
- package/templates/default/src/types/form/actionHandler.ts +6 -0
- package/templates/default/src/types/locator/locator.type.ts +11 -0
- package/templates/default/src/types/step/step.type.ts +1 -0
- package/templates/default/src/types/table/data-table.ts +6 -0
- package/templates/default/tailwind.config.ts +62 -0
- package/templates/default/tsconfig.json +28 -0
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Script to synchronize template steps from filesystem to database
|
|
5
|
+
* Scans step definition files to ensure all template steps exist in DB
|
|
6
|
+
* Filesystem is the source of truth - steps in DB but not in FS will be deleted
|
|
7
|
+
* Run this after merging changes to ensure template step sync
|
|
8
|
+
*
|
|
9
|
+
* Usage: npx tsx scripts/template-step-sync.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { promises as fs } from 'fs'
|
|
13
|
+
import { join } from 'path'
|
|
14
|
+
import { glob } from 'glob'
|
|
15
|
+
import { parse } from '@babel/parser'
|
|
16
|
+
import * as t from '@babel/types'
|
|
17
|
+
import _traverse from '@babel/traverse'
|
|
18
|
+
import type { NodePath } from '@babel/traverse'
|
|
19
|
+
|
|
20
|
+
// Handle both ESM and CJS exports
|
|
21
|
+
const traverse = (_traverse as { default?: typeof _traverse }).default ?? _traverse
|
|
22
|
+
import prisma from '../src/config/db-config'
|
|
23
|
+
import { TemplateStepGroupType, TemplateStepType, TemplateStepIcon, StepParameterType } from '@prisma/client'
|
|
24
|
+
|
|
25
|
+
interface StepGroupJSDoc {
|
|
26
|
+
name: string
|
|
27
|
+
description: string | null
|
|
28
|
+
type: TemplateStepGroupType
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface StepJSDoc {
|
|
32
|
+
name: string
|
|
33
|
+
description: string | null
|
|
34
|
+
icon: TemplateStepIcon
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface StepParameter {
|
|
38
|
+
name: string
|
|
39
|
+
type: StepParameterType
|
|
40
|
+
order: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ParsedStep {
|
|
44
|
+
jsdoc: StepJSDoc
|
|
45
|
+
signature: string
|
|
46
|
+
functionDefinition: string
|
|
47
|
+
parameters: StepParameter[]
|
|
48
|
+
keyword: 'When' | 'Then' | 'Given'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface StepData {
|
|
52
|
+
group: StepGroupJSDoc
|
|
53
|
+
steps: ParsedStep[]
|
|
54
|
+
filePath: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface SyncResult {
|
|
58
|
+
stepsScanned: number
|
|
59
|
+
stepsExisting: number
|
|
60
|
+
stepsCreated: number
|
|
61
|
+
stepsUpdated: number
|
|
62
|
+
stepsDeleted: number
|
|
63
|
+
errors: string[]
|
|
64
|
+
createdSteps: Array<{ name: string; signature: string; group: string }>
|
|
65
|
+
updatedSteps: Array<{ name: string; signature: string; group: string }>
|
|
66
|
+
deletedSteps: Array<{ name: string; signature: string; group: string }>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parses JSDoc comment to extract step group metadata
|
|
71
|
+
* Reused from template-step-group-sync.ts
|
|
72
|
+
*/
|
|
73
|
+
function parseGroupJSDoc(content: string): StepGroupJSDoc | null {
|
|
74
|
+
const lines = content.split('\n')
|
|
75
|
+
|
|
76
|
+
if (lines.length === 0) {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const firstLine = lines[0].trim()
|
|
81
|
+
if (!firstLine.startsWith('/**')) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let hasType = false
|
|
86
|
+
let endLine = -1
|
|
87
|
+
let name: string | null = null
|
|
88
|
+
let description: string | null = null
|
|
89
|
+
let type: string | null = null
|
|
90
|
+
|
|
91
|
+
const maxLines = Math.min(lines.length, 50)
|
|
92
|
+
for (let i = 0; i < maxLines; i++) {
|
|
93
|
+
const line = lines[i].trim()
|
|
94
|
+
|
|
95
|
+
if (line.includes('*/')) {
|
|
96
|
+
const beforeClose = line.split('*/')[0].trim()
|
|
97
|
+
|
|
98
|
+
if (beforeClose.startsWith('* @name') || beforeClose.startsWith('*@name')) {
|
|
99
|
+
const match = beforeClose.match(/@name\s+(.+)/)
|
|
100
|
+
if (match) {
|
|
101
|
+
name = match[1].trim()
|
|
102
|
+
}
|
|
103
|
+
} else if (beforeClose.startsWith('* @description') || beforeClose.startsWith('*@description')) {
|
|
104
|
+
const match = beforeClose.match(/@description\s+(.+)/)
|
|
105
|
+
if (match) {
|
|
106
|
+
description = match[1].trim() || null
|
|
107
|
+
}
|
|
108
|
+
} else if (beforeClose.startsWith('* @type') || beforeClose.startsWith('*@type')) {
|
|
109
|
+
hasType = true
|
|
110
|
+
const match = beforeClose.match(/@type\s+(.+)/)
|
|
111
|
+
if (match) {
|
|
112
|
+
type = match[1].trim()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
endLine = i
|
|
117
|
+
break
|
|
118
|
+
} else if (line.startsWith('* @name') || line.startsWith('*@name')) {
|
|
119
|
+
const match = line.match(/@name\s+(.+)/)
|
|
120
|
+
if (match) {
|
|
121
|
+
name = match[1].trim()
|
|
122
|
+
}
|
|
123
|
+
} else if (line.startsWith('* @description') || line.startsWith('*@description')) {
|
|
124
|
+
const match = line.match(/@description\s+(.+)/)
|
|
125
|
+
if (match) {
|
|
126
|
+
description = match[1].trim() || null
|
|
127
|
+
}
|
|
128
|
+
} else if (line.startsWith('* @type') || line.startsWith('*@type')) {
|
|
129
|
+
hasType = true
|
|
130
|
+
const match = line.match(/@type\s+(.+)/)
|
|
131
|
+
if (match) {
|
|
132
|
+
type = match[1].trim()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (hasType && endLine >= 0 && name && type) {
|
|
138
|
+
const normalizedType = type.toUpperCase()
|
|
139
|
+
if (normalizedType !== 'ACTION' && normalizedType !== 'VALIDATION') {
|
|
140
|
+
throw new Error(`Invalid @type value: ${type}. Must be ACTION or VALIDATION`)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
name: name.trim(),
|
|
145
|
+
description: description ? description.trim() : null,
|
|
146
|
+
type: normalizedType as TemplateStepGroupType,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Parses JSDoc comment to extract step metadata
|
|
155
|
+
*/
|
|
156
|
+
function parseStepJSDoc(content: string, startLine: number): StepJSDoc | null {
|
|
157
|
+
const lines = content.split('\n')
|
|
158
|
+
|
|
159
|
+
// Look backwards from startLine for JSDoc comment
|
|
160
|
+
// startLine is 0-based (line number - 1)
|
|
161
|
+
let jsdocStart = -1
|
|
162
|
+
for (let i = startLine - 1; i >= 0 && i >= startLine - 20; i--) {
|
|
163
|
+
const line = lines[i]?.trim()
|
|
164
|
+
// Check for end of JSDoc (could be on its own line or with content)
|
|
165
|
+
if (line?.includes('*/')) {
|
|
166
|
+
// Found end, now find start
|
|
167
|
+
jsdocStart = i
|
|
168
|
+
// Look backwards for the start
|
|
169
|
+
for (let j = i - 1; j >= 0 && j >= i - 10; j--) {
|
|
170
|
+
const prevLine = lines[j]?.trim()
|
|
171
|
+
if (prevLine?.startsWith('/**')) {
|
|
172
|
+
jsdocStart = j
|
|
173
|
+
break
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
break
|
|
177
|
+
} else if (line?.startsWith('/**')) {
|
|
178
|
+
jsdocStart = i
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (jsdocStart === -1) {
|
|
184
|
+
return null
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let name: string | null = null
|
|
188
|
+
let description: string | null = null
|
|
189
|
+
let icon: string | null = null
|
|
190
|
+
let foundJSDoc = false
|
|
191
|
+
|
|
192
|
+
for (let i = jsdocStart; i < Math.min(lines.length, jsdocStart + 20); i++) {
|
|
193
|
+
const line = lines[i]?.trim()
|
|
194
|
+
|
|
195
|
+
if (line?.startsWith('/**')) {
|
|
196
|
+
foundJSDoc = true
|
|
197
|
+
continue
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (line?.includes('*/')) {
|
|
201
|
+
const beforeClose = line.split('*/')[0].trim()
|
|
202
|
+
if (beforeClose.startsWith('* @name') || beforeClose.startsWith('*@name')) {
|
|
203
|
+
const match = beforeClose.match(/@name\s+(.+)/)
|
|
204
|
+
if (match) {
|
|
205
|
+
name = match[1].trim()
|
|
206
|
+
}
|
|
207
|
+
} else if (beforeClose.startsWith('* @description') || beforeClose.startsWith('*@description')) {
|
|
208
|
+
const match = beforeClose.match(/@description\s+(.+)/)
|
|
209
|
+
if (match) {
|
|
210
|
+
description = match[1].trim() || null
|
|
211
|
+
}
|
|
212
|
+
} else if (beforeClose.startsWith('* @icon') || beforeClose.startsWith('*@icon')) {
|
|
213
|
+
const match = beforeClose.match(/@icon\s+(.+)/)
|
|
214
|
+
if (match) {
|
|
215
|
+
icon = match[1].trim()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (foundJSDoc) {
|
|
222
|
+
if (line?.startsWith('* @name') || line?.startsWith('*@name')) {
|
|
223
|
+
const match = line.match(/@name\s+(.+)/)
|
|
224
|
+
if (match) {
|
|
225
|
+
name = match[1].trim()
|
|
226
|
+
}
|
|
227
|
+
} else if (line?.startsWith('* @description') || line?.startsWith('*@description')) {
|
|
228
|
+
const match = line.match(/@description\s+(.+)/)
|
|
229
|
+
if (match) {
|
|
230
|
+
description = match[1].trim() || null
|
|
231
|
+
}
|
|
232
|
+
} else if (line?.startsWith('* @icon') || line?.startsWith('*@icon')) {
|
|
233
|
+
const match = line.match(/@icon\s+(.+)/)
|
|
234
|
+
if (match) {
|
|
235
|
+
icon = match[1].trim()
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!name || !icon) {
|
|
242
|
+
return null
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Validate icon
|
|
246
|
+
const iconUpper = icon.toUpperCase()
|
|
247
|
+
const validIcons = Object.values(TemplateStepIcon)
|
|
248
|
+
if (!validIcons.includes(iconUpper as TemplateStepIcon)) {
|
|
249
|
+
throw new Error(`Invalid @icon value: ${icon}. Must be one of: ${validIcons.join(', ')}`)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
name: name.trim(),
|
|
254
|
+
description: description ? description.trim() : null,
|
|
255
|
+
icon: iconUpper as TemplateStepIcon,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Maps TypeScript type to StepParameterType
|
|
261
|
+
* Strict mode - throws error for unsupported types
|
|
262
|
+
*/
|
|
263
|
+
function mapTypeToParameterType(typeName: string): StepParameterType {
|
|
264
|
+
const normalized = typeName.trim()
|
|
265
|
+
|
|
266
|
+
if (normalized === 'SelectorName') {
|
|
267
|
+
return StepParameterType.LOCATOR
|
|
268
|
+
}
|
|
269
|
+
if (normalized === 'string') {
|
|
270
|
+
return StepParameterType.STRING
|
|
271
|
+
}
|
|
272
|
+
if (normalized === 'number' || normalized === 'int') {
|
|
273
|
+
return StepParameterType.NUMBER
|
|
274
|
+
}
|
|
275
|
+
if (normalized === 'boolean') {
|
|
276
|
+
return StepParameterType.BOOLEAN
|
|
277
|
+
}
|
|
278
|
+
if (normalized === 'Date') {
|
|
279
|
+
return StepParameterType.DATE
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
throw new Error(
|
|
283
|
+
`Unsupported parameter type: ${typeName}. Supported types: SelectorName, string, number, int, boolean, Date`,
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Extracts function definition code from AST node
|
|
289
|
+
* Returns the complete When/Then/Given call with function, without JSDoc
|
|
290
|
+
* Format: When('pattern', async function(this:CustomWorld, param: type){})
|
|
291
|
+
*/
|
|
292
|
+
function extractFunctionDefinition(callExpr: t.CallExpression, keyword: string, sourceCode: string): string {
|
|
293
|
+
// Get the source code for the entire call expression
|
|
294
|
+
const start = callExpr.start
|
|
295
|
+
const end = callExpr.end
|
|
296
|
+
|
|
297
|
+
if (start == null || end == null) {
|
|
298
|
+
throw new Error('Cannot extract function definition: missing position information')
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Extract the code - Babel's end position includes the closing parenthesis
|
|
302
|
+
// After the null check, TypeScript knows start and end are numbers
|
|
303
|
+
let code = sourceCode.slice(start, end).trim()
|
|
304
|
+
|
|
305
|
+
// Check if there's a semicolon immediately after (some files have it, some don't)
|
|
306
|
+
// Only add if it's clearly there in the source
|
|
307
|
+
const afterEnd = sourceCode.slice(end, end + 10).trim()
|
|
308
|
+
if (afterEnd.startsWith(';')) {
|
|
309
|
+
code += ';'
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return code
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Parses a step definition file to extract steps
|
|
317
|
+
*/
|
|
318
|
+
function parseStepFile(content: string, filePath: string): StepData | null {
|
|
319
|
+
// Parse group JSDoc
|
|
320
|
+
const group = parseGroupJSDoc(content)
|
|
321
|
+
if (!group) {
|
|
322
|
+
return null
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Parse TypeScript AST
|
|
326
|
+
let ast
|
|
327
|
+
try {
|
|
328
|
+
ast = parse(content, {
|
|
329
|
+
sourceType: 'module',
|
|
330
|
+
plugins: ['typescript', 'decorators-legacy'],
|
|
331
|
+
})
|
|
332
|
+
} catch (error) {
|
|
333
|
+
throw new Error(`Failed to parse TypeScript: ${error}`)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const steps: ParsedStep[] = []
|
|
337
|
+
|
|
338
|
+
// Traverse the AST to find When/Then/Given calls
|
|
339
|
+
traverse(ast, {
|
|
340
|
+
CallExpression(path: NodePath<t.CallExpression>) {
|
|
341
|
+
const node = path.node
|
|
342
|
+
const callee = node.callee
|
|
343
|
+
|
|
344
|
+
// Check if this is a When, Then, or Given call
|
|
345
|
+
let keyword: 'When' | 'Then' | 'Given' | null = null
|
|
346
|
+
if (t.isIdentifier(callee)) {
|
|
347
|
+
if (callee.name === 'When' || callee.name === 'Then' || callee.name === 'Given') {
|
|
348
|
+
keyword = callee.name as 'When' | 'Then' | 'Given'
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (!keyword || node.arguments.length < 2) {
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Extract Gherkin pattern (first argument - should be a string)
|
|
357
|
+
const patternArg = node.arguments[0]
|
|
358
|
+
if (!t.isStringLiteral(patternArg)) {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
const signature = patternArg.value
|
|
362
|
+
|
|
363
|
+
// Extract function (second argument)
|
|
364
|
+
const funcArg = node.arguments[1]
|
|
365
|
+
if (!t.isFunction(funcArg)) {
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Get line number for JSDoc lookup (Babel uses 1-based line numbers)
|
|
370
|
+
const lineNumber = node.loc?.start?.line
|
|
371
|
+
if (lineNumber === undefined) {
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Parse step JSDoc (convert to 0-based for array indexing)
|
|
376
|
+
const jsdoc = parseStepJSDoc(content, lineNumber - 1)
|
|
377
|
+
if (!jsdoc) {
|
|
378
|
+
return // Skip steps without JSDoc
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Extract parameters
|
|
382
|
+
const parameters: StepParameter[] = []
|
|
383
|
+
if (t.isFunction(funcArg) && funcArg.params) {
|
|
384
|
+
let order = 0
|
|
385
|
+
for (const param of funcArg.params) {
|
|
386
|
+
// Skip 'this: CustomWorld' parameter
|
|
387
|
+
if (t.isIdentifier(param) && param.name === 'this') {
|
|
388
|
+
continue
|
|
389
|
+
}
|
|
390
|
+
if (t.isObjectPattern(param) && param.properties.length === 1) {
|
|
391
|
+
const prop = param.properties[0]
|
|
392
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === 'this') {
|
|
393
|
+
continue
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let paramName: string | null = null
|
|
398
|
+
let paramType: string | null = null
|
|
399
|
+
|
|
400
|
+
if (t.isIdentifier(param)) {
|
|
401
|
+
paramName = param.name
|
|
402
|
+
if (param.typeAnnotation && t.isTSTypeAnnotation(param.typeAnnotation)) {
|
|
403
|
+
const typeAnnotation = param.typeAnnotation.typeAnnotation
|
|
404
|
+
if (t.isTSTypeReference(typeAnnotation) && t.isIdentifier(typeAnnotation.typeName)) {
|
|
405
|
+
paramType = typeAnnotation.typeName.name
|
|
406
|
+
} else if (t.isTSStringKeyword(typeAnnotation)) {
|
|
407
|
+
paramType = 'string'
|
|
408
|
+
} else if (t.isTSNumberKeyword(typeAnnotation)) {
|
|
409
|
+
paramType = 'number'
|
|
410
|
+
} else if (t.isTSBooleanKeyword(typeAnnotation)) {
|
|
411
|
+
paramType = 'boolean'
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} else if (t.isObjectPattern(param)) {
|
|
415
|
+
// Handle destructured parameters (unlikely but possible)
|
|
416
|
+
continue
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (paramName && paramType) {
|
|
420
|
+
try {
|
|
421
|
+
const mappedType = mapTypeToParameterType(paramType)
|
|
422
|
+
parameters.push({
|
|
423
|
+
name: paramName,
|
|
424
|
+
type: mappedType,
|
|
425
|
+
order: order++,
|
|
426
|
+
})
|
|
427
|
+
} catch (error) {
|
|
428
|
+
throw new Error(`Error mapping parameter type in step "${signature}": ${error}`)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Extract function definition
|
|
435
|
+
const functionDefinition = extractFunctionDefinition(node, keyword, content)
|
|
436
|
+
|
|
437
|
+
steps.push({
|
|
438
|
+
jsdoc,
|
|
439
|
+
signature,
|
|
440
|
+
functionDefinition,
|
|
441
|
+
parameters,
|
|
442
|
+
keyword,
|
|
443
|
+
})
|
|
444
|
+
},
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
group,
|
|
449
|
+
steps,
|
|
450
|
+
filePath,
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Scans step definition files
|
|
456
|
+
*/
|
|
457
|
+
async function scanStepFiles(baseDir: string): Promise<string[]> {
|
|
458
|
+
const patterns = ['src/tests/steps/actions/**/*.step.ts', 'src/tests/steps/validations/**/*.step.ts']
|
|
459
|
+
const stepFiles: string[] = []
|
|
460
|
+
|
|
461
|
+
for (const pattern of patterns) {
|
|
462
|
+
const files = await glob(pattern, {
|
|
463
|
+
cwd: baseDir,
|
|
464
|
+
})
|
|
465
|
+
stepFiles.push(...files)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return stepFiles
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Syncs template steps to database
|
|
473
|
+
*/
|
|
474
|
+
async function syncStepsToDatabase(
|
|
475
|
+
allSteps: Array<{ step: ParsedStep; groupName: string; filePath: string }>,
|
|
476
|
+
_baseDir: string,
|
|
477
|
+
): Promise<SyncResult> {
|
|
478
|
+
const result: SyncResult = {
|
|
479
|
+
stepsScanned: 0,
|
|
480
|
+
stepsExisting: 0,
|
|
481
|
+
stepsCreated: 0,
|
|
482
|
+
stepsUpdated: 0,
|
|
483
|
+
stepsDeleted: 0,
|
|
484
|
+
errors: [],
|
|
485
|
+
createdSteps: [],
|
|
486
|
+
updatedSteps: [],
|
|
487
|
+
deletedSteps: [],
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Track signatures from filesystem
|
|
491
|
+
const fsSignatures = new Set<string>()
|
|
492
|
+
|
|
493
|
+
// Process each step
|
|
494
|
+
for (const { step, groupName, filePath } of allSteps) {
|
|
495
|
+
try {
|
|
496
|
+
result.stepsScanned++
|
|
497
|
+
fsSignatures.add(step.signature)
|
|
498
|
+
|
|
499
|
+
// Find template step group
|
|
500
|
+
const stepGroup = await prisma.templateStepGroup.findFirst({
|
|
501
|
+
where: { name: groupName },
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
if (!stepGroup) {
|
|
505
|
+
const errorMsg = `Template step group '${groupName}' not found for step '${step.signature}' in ${filePath}`
|
|
506
|
+
result.errors.push(errorMsg)
|
|
507
|
+
console.error(` ā ${errorMsg}`)
|
|
508
|
+
continue
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Determine step type from group type
|
|
512
|
+
const stepType: TemplateStepType =
|
|
513
|
+
stepGroup.type === TemplateStepGroupType.ACTION ? TemplateStepType.ACTION : TemplateStepType.ASSERTION
|
|
514
|
+
|
|
515
|
+
// Check if step exists by signature
|
|
516
|
+
const existingStep = await prisma.templateStep.findFirst({
|
|
517
|
+
where: {
|
|
518
|
+
signature: step.signature,
|
|
519
|
+
},
|
|
520
|
+
include: {
|
|
521
|
+
templateStepGroup: true,
|
|
522
|
+
},
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
if (existingStep) {
|
|
526
|
+
// Check if update is needed
|
|
527
|
+
const needsUpdate =
|
|
528
|
+
existingStep.name !== step.jsdoc.name ||
|
|
529
|
+
existingStep.description !== (step.jsdoc.description || '') ||
|
|
530
|
+
existingStep.signature !== step.signature ||
|
|
531
|
+
existingStep.functionDefinition !== step.functionDefinition ||
|
|
532
|
+
existingStep.icon !== step.jsdoc.icon ||
|
|
533
|
+
existingStep.type !== stepType ||
|
|
534
|
+
existingStep.templateStepGroupId !== stepGroup.id
|
|
535
|
+
|
|
536
|
+
if (needsUpdate) {
|
|
537
|
+
// Update step and parameters
|
|
538
|
+
await prisma.templateStep.update({
|
|
539
|
+
where: { id: existingStep.id },
|
|
540
|
+
data: {
|
|
541
|
+
name: step.jsdoc.name,
|
|
542
|
+
description: step.jsdoc.description || '',
|
|
543
|
+
signature: step.signature,
|
|
544
|
+
functionDefinition: step.functionDefinition,
|
|
545
|
+
icon: step.jsdoc.icon,
|
|
546
|
+
type: stepType,
|
|
547
|
+
templateStepGroupId: stepGroup.id,
|
|
548
|
+
parameters: {
|
|
549
|
+
deleteMany: {},
|
|
550
|
+
create: step.parameters.map(param => ({
|
|
551
|
+
name: param.name,
|
|
552
|
+
type: param.type,
|
|
553
|
+
order: param.order,
|
|
554
|
+
})),
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
})
|
|
558
|
+
result.stepsUpdated++
|
|
559
|
+
result.updatedSteps.push({
|
|
560
|
+
name: step.jsdoc.name,
|
|
561
|
+
signature: step.signature,
|
|
562
|
+
group: groupName,
|
|
563
|
+
})
|
|
564
|
+
console.log(` š Updated step '${step.jsdoc.name}' (${step.signature})`)
|
|
565
|
+
} else {
|
|
566
|
+
result.stepsExisting++
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
// Create new step
|
|
570
|
+
await prisma.templateStep.create({
|
|
571
|
+
data: {
|
|
572
|
+
name: step.jsdoc.name,
|
|
573
|
+
description: step.jsdoc.description || '',
|
|
574
|
+
signature: step.signature,
|
|
575
|
+
functionDefinition: step.functionDefinition,
|
|
576
|
+
icon: step.jsdoc.icon,
|
|
577
|
+
type: stepType,
|
|
578
|
+
templateStepGroupId: stepGroup.id,
|
|
579
|
+
parameters: {
|
|
580
|
+
create: step.parameters.map(param => ({
|
|
581
|
+
name: param.name,
|
|
582
|
+
type: param.type,
|
|
583
|
+
order: param.order,
|
|
584
|
+
})),
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
})
|
|
588
|
+
result.stepsCreated++
|
|
589
|
+
result.createdSteps.push({
|
|
590
|
+
name: step.jsdoc.name,
|
|
591
|
+
signature: step.signature,
|
|
592
|
+
group: groupName,
|
|
593
|
+
})
|
|
594
|
+
console.log(` ā Created step '${step.jsdoc.name}' (${step.signature})`)
|
|
595
|
+
}
|
|
596
|
+
} catch (error) {
|
|
597
|
+
const errorMsg = `Error syncing step '${step.signature}' from ${filePath}: ${error}`
|
|
598
|
+
result.errors.push(errorMsg)
|
|
599
|
+
console.error(` ā ${errorMsg}`)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Delete steps that don't exist in filesystem
|
|
604
|
+
console.log('\nš Checking for orphaned template steps (not in filesystem)...')
|
|
605
|
+
const allDbSteps = await prisma.templateStep.findMany({
|
|
606
|
+
include: {
|
|
607
|
+
templateStepGroup: true,
|
|
608
|
+
},
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
for (const dbStep of allDbSteps) {
|
|
612
|
+
if (!fsSignatures.has(dbStep.signature)) {
|
|
613
|
+
try {
|
|
614
|
+
// Delete in order: child records first. TemplateTestCaseStepParameter and
|
|
615
|
+
// TestCaseStepParameter have no onDelete cascade, so they must be removed
|
|
616
|
+
// before TemplateTestCaseStep/TestCaseStep (which are cascade-deleted from TemplateStep).
|
|
617
|
+
await prisma.$transaction(async tx => {
|
|
618
|
+
await tx.templateTestCaseStepParameter.deleteMany({
|
|
619
|
+
where: { templateTestCaseStep: { templateStepId: dbStep.id } },
|
|
620
|
+
})
|
|
621
|
+
await tx.templateTestCaseStep.deleteMany({
|
|
622
|
+
where: { templateStepId: dbStep.id },
|
|
623
|
+
})
|
|
624
|
+
await tx.testCaseStepParameter.deleteMany({
|
|
625
|
+
where: { testCaseStep: { templateStepId: dbStep.id } },
|
|
626
|
+
})
|
|
627
|
+
await tx.testCaseStep.deleteMany({
|
|
628
|
+
where: { templateStepId: dbStep.id },
|
|
629
|
+
})
|
|
630
|
+
await tx.templateStepParameter.deleteMany({
|
|
631
|
+
where: { templateStepId: dbStep.id },
|
|
632
|
+
})
|
|
633
|
+
await tx.templateStep.delete({
|
|
634
|
+
where: { id: dbStep.id },
|
|
635
|
+
})
|
|
636
|
+
})
|
|
637
|
+
result.stepsDeleted++
|
|
638
|
+
result.deletedSteps.push({
|
|
639
|
+
name: dbStep.name,
|
|
640
|
+
signature: dbStep.signature,
|
|
641
|
+
group: dbStep.templateStepGroup.name,
|
|
642
|
+
})
|
|
643
|
+
console.log(` šļø Deleted step '${dbStep.name}' (${dbStep.signature})`)
|
|
644
|
+
} catch (error) {
|
|
645
|
+
const errorMsg = `Error deleting step '${dbStep.signature}': ${error}`
|
|
646
|
+
result.errors.push(errorMsg)
|
|
647
|
+
console.error(` ā ${errorMsg}`)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return result
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Generates and displays sync summary
|
|
657
|
+
*/
|
|
658
|
+
function generateSummary(result: SyncResult): void {
|
|
659
|
+
console.log('\nš Sync Summary:')
|
|
660
|
+
console.log(` š Steps scanned: ${result.stepsScanned}`)
|
|
661
|
+
console.log(` ā
Steps existing: ${result.stepsExisting}`)
|
|
662
|
+
console.log(` ā Steps created: ${result.stepsCreated}`)
|
|
663
|
+
console.log(` š Steps updated: ${result.stepsUpdated}`)
|
|
664
|
+
console.log(` šļø Steps deleted: ${result.stepsDeleted}`)
|
|
665
|
+
console.log(` ā Errors: ${result.errors.length}`)
|
|
666
|
+
|
|
667
|
+
if (result.createdSteps.length > 0) {
|
|
668
|
+
console.log('\n Created steps:')
|
|
669
|
+
result.createdSteps.forEach((step, index) => {
|
|
670
|
+
console.log(` ${index + 1}. ${step.name} (${step.signature}) [${step.group}]`)
|
|
671
|
+
})
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (result.updatedSteps.length > 0) {
|
|
675
|
+
console.log('\n Updated steps:')
|
|
676
|
+
result.updatedSteps.forEach((step, index) => {
|
|
677
|
+
console.log(` ${index + 1}. ${step.name} (${step.signature}) [${step.group}]`)
|
|
678
|
+
})
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (result.deletedSteps.length > 0) {
|
|
682
|
+
console.log('\n Deleted steps:')
|
|
683
|
+
result.deletedSteps.forEach((step, index) => {
|
|
684
|
+
console.log(` ${index + 1}. ${step.name} (${step.signature}) [${step.group}]`)
|
|
685
|
+
})
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (result.errors.length > 0) {
|
|
689
|
+
console.log('\n Errors:')
|
|
690
|
+
result.errors.forEach((error, index) => {
|
|
691
|
+
console.log(` ${index + 1}. ${error}`)
|
|
692
|
+
})
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Main function
|
|
698
|
+
*/
|
|
699
|
+
async function main() {
|
|
700
|
+
try {
|
|
701
|
+
console.log('š Starting template step sync...')
|
|
702
|
+
console.log('This will scan step definition files and sync template steps to database.')
|
|
703
|
+
console.log('Filesystem is the source of truth - steps in DB but not in FS will be deleted.\n')
|
|
704
|
+
|
|
705
|
+
const baseDir = process.cwd()
|
|
706
|
+
|
|
707
|
+
// Scan step files
|
|
708
|
+
console.log('š Scanning step definition files...')
|
|
709
|
+
const stepFiles = await scanStepFiles(baseDir)
|
|
710
|
+
console.log(` Found ${stepFiles.length} step file(s)`)
|
|
711
|
+
|
|
712
|
+
if (stepFiles.length === 0) {
|
|
713
|
+
console.log('\nā ļø No step files found. Nothing to sync.')
|
|
714
|
+
return
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Parse all files
|
|
718
|
+
console.log('\nš Parsing step files...')
|
|
719
|
+
const allSteps: Array<{ step: ParsedStep; groupName: string; filePath: string }> = []
|
|
720
|
+
const errors: string[] = []
|
|
721
|
+
|
|
722
|
+
for (const file of stepFiles) {
|
|
723
|
+
try {
|
|
724
|
+
const filePath = join(baseDir, file)
|
|
725
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
726
|
+
const stepData = parseStepFile(content, file)
|
|
727
|
+
|
|
728
|
+
if (!stepData) {
|
|
729
|
+
errors.push(`File '${file}' does not have a valid group JSDoc comment`)
|
|
730
|
+
console.log(` ā ļø Skipped '${file}' (no group JSDoc)`)
|
|
731
|
+
continue
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (stepData.steps.length === 0) {
|
|
735
|
+
console.log(` ā ļø No steps found in '${file}'`)
|
|
736
|
+
continue
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
console.log(` ā Parsed '${file}' (${stepData.steps.length} step(s))`)
|
|
740
|
+
|
|
741
|
+
for (const step of stepData.steps) {
|
|
742
|
+
allSteps.push({
|
|
743
|
+
step,
|
|
744
|
+
groupName: stepData.group.name,
|
|
745
|
+
filePath: file,
|
|
746
|
+
})
|
|
747
|
+
}
|
|
748
|
+
} catch (error) {
|
|
749
|
+
const errorMsg = `Error parsing file '${file}': ${error}`
|
|
750
|
+
errors.push(errorMsg)
|
|
751
|
+
console.error(` ā ${errorMsg}`)
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (errors.length > 0 && allSteps.length === 0) {
|
|
756
|
+
console.log('\nā ļø No valid steps found. Please check the errors above.')
|
|
757
|
+
process.exit(1)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Check for duplicate signatures
|
|
761
|
+
const signatureMap = new Map<string, string[]>()
|
|
762
|
+
for (const { step, filePath } of allSteps) {
|
|
763
|
+
if (!signatureMap.has(step.signature)) {
|
|
764
|
+
signatureMap.set(step.signature, [])
|
|
765
|
+
}
|
|
766
|
+
signatureMap.get(step.signature)!.push(filePath)
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
for (const [signature, files] of signatureMap.entries()) {
|
|
770
|
+
if (files.length > 1) {
|
|
771
|
+
const errorMsg = `Duplicate signature found: "${signature}" in files: ${files.join(', ')}`
|
|
772
|
+
errors.push(errorMsg)
|
|
773
|
+
console.error(` ā ${errorMsg}`)
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (errors.length > 0 && allSteps.length === 0) {
|
|
778
|
+
console.log('\nā ļø Cannot proceed due to errors. Please fix the issues above.')
|
|
779
|
+
process.exit(1)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Sync to database
|
|
783
|
+
console.log('\nā
Syncing template steps to database...')
|
|
784
|
+
const result = await syncStepsToDatabase(allSteps, baseDir)
|
|
785
|
+
|
|
786
|
+
// Add parsing errors to result
|
|
787
|
+
result.errors.push(...errors)
|
|
788
|
+
|
|
789
|
+
// Generate summary
|
|
790
|
+
generateSummary(result)
|
|
791
|
+
|
|
792
|
+
if (result.errors.length === 0) {
|
|
793
|
+
console.log('\nā
Sync completed successfully!')
|
|
794
|
+
} else {
|
|
795
|
+
console.log('\nā ļø Sync completed with errors. Please review the errors above.')
|
|
796
|
+
process.exit(1)
|
|
797
|
+
}
|
|
798
|
+
} catch (error) {
|
|
799
|
+
console.error('\nā Error during sync:', error)
|
|
800
|
+
process.exit(1)
|
|
801
|
+
} finally {
|
|
802
|
+
await prisma.$disconnect()
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
main()
|