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,349 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Script to synchronize module hierarchy from filesystem to database
|
|
5
|
+
* Scans locators and features directories to ensure all modules exist in DB
|
|
6
|
+
* Filesystem is the source of truth - modules in DB but not in FS will be deleted
|
|
7
|
+
* Run this after merging changes to ensure module sync
|
|
8
|
+
*
|
|
9
|
+
* Usage: npx tsx scripts/sync-modules.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { buildModuleHierarchy, findModuleByPath, getAllModulesWithPaths } from '../src/lib/module-hierarchy-builder'
|
|
13
|
+
import { join } from 'path'
|
|
14
|
+
import { glob } from 'glob'
|
|
15
|
+
import prisma from '../src/config/db-config'
|
|
16
|
+
|
|
17
|
+
interface SyncResult {
|
|
18
|
+
modulesScanned: number
|
|
19
|
+
modulesExisting: number
|
|
20
|
+
modulesCreated: number
|
|
21
|
+
modulesDeleted: number
|
|
22
|
+
errors: string[]
|
|
23
|
+
createdModules: string[]
|
|
24
|
+
existingModules: string[]
|
|
25
|
+
deletedModules: string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Scans locator directories and extracts module paths
|
|
30
|
+
*/
|
|
31
|
+
async function scanLocatorDirectories(baseDir: string): Promise<string[]> {
|
|
32
|
+
const modulePaths = new Set<string>()
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Get all JSON files in locators directory
|
|
36
|
+
const pattern = 'src/tests/locators/**/*.json'
|
|
37
|
+
const files = await glob(pattern, {
|
|
38
|
+
cwd: baseDir,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
const filePath = join(baseDir, file)
|
|
43
|
+
const modulePath = extractModulePathFromLocatorFile(filePath, baseDir)
|
|
44
|
+
if (modulePath) {
|
|
45
|
+
modulePaths.add(modulePath)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('Error scanning locator directories:', error)
|
|
50
|
+
throw error
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Array.from(modulePaths)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Scans feature directories and extracts module paths
|
|
58
|
+
*/
|
|
59
|
+
async function scanFeatureDirectories(baseDir: string): Promise<string[]> {
|
|
60
|
+
const modulePaths = new Set<string>()
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// Get all feature files
|
|
64
|
+
const pattern = 'src/tests/features/**/*.feature'
|
|
65
|
+
const files = await glob(pattern, {
|
|
66
|
+
cwd: baseDir,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
const filePath = join(baseDir, file)
|
|
71
|
+
const modulePath = extractModulePathFromFeatureFile(filePath, baseDir)
|
|
72
|
+
if (modulePath) {
|
|
73
|
+
modulePaths.add(modulePath)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Error scanning feature directories:', error)
|
|
78
|
+
throw error
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Array.from(modulePaths)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Extracts module path from locator file path
|
|
86
|
+
* Example: src/tests/locators/home/home.json -> /home
|
|
87
|
+
*/
|
|
88
|
+
function extractModulePathFromLocatorFile(filePath: string, baseDir: string): string {
|
|
89
|
+
const testsDir = join(baseDir, 'src', 'tests')
|
|
90
|
+
const relativePath = filePath.replace(testsDir, '').replace(/\\/g, '/')
|
|
91
|
+
const pathParts = relativePath.split('/').filter(p => p && p !== 'locators')
|
|
92
|
+
const moduleParts = pathParts.slice(0, -1) // Remove filename
|
|
93
|
+
return moduleParts.length > 0 ? '/' + moduleParts.join('/') : '/'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Extracts module path from feature file path
|
|
98
|
+
* Example: src/tests/features/login/demo.feature -> /login
|
|
99
|
+
*/
|
|
100
|
+
function extractModulePathFromFeatureFile(filePath: string, baseDir: string): string {
|
|
101
|
+
const featuresBaseDir = join(baseDir, 'src', 'tests', 'features')
|
|
102
|
+
const relativePath = filePath.replace(featuresBaseDir, '').replace(/\\/g, '/')
|
|
103
|
+
const pathParts = relativePath.split('/').filter(part => part && part !== '')
|
|
104
|
+
const moduleParts = pathParts.slice(0, -1) // Remove filename
|
|
105
|
+
return moduleParts.length > 0 ? '/' + moduleParts.join('/') : '/'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Builds a module tree from discovered paths
|
|
110
|
+
* Returns a map of module paths to their parent paths
|
|
111
|
+
*/
|
|
112
|
+
function buildModuleTree(modulePaths: string[]): Map<string, string | null> {
|
|
113
|
+
const tree = new Map<string, string | null>()
|
|
114
|
+
|
|
115
|
+
// Add all paths and their parent paths
|
|
116
|
+
for (const modulePath of modulePaths) {
|
|
117
|
+
if (modulePath === '/') {
|
|
118
|
+
tree.set('/', null)
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const pathParts = modulePath.split('/').filter(p => p)
|
|
123
|
+
let currentPath = ''
|
|
124
|
+
|
|
125
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
126
|
+
currentPath += '/' + pathParts[i]
|
|
127
|
+
|
|
128
|
+
if (i === 0) {
|
|
129
|
+
tree.set(currentPath, null)
|
|
130
|
+
} else {
|
|
131
|
+
const parentPath = '/' + pathParts.slice(0, i).join('/')
|
|
132
|
+
tree.set(currentPath, parentPath)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return tree
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Syncs modules to database (creates missing modules)
|
|
142
|
+
*/
|
|
143
|
+
async function syncModulesToDatabase(moduleTree: Map<string, string | null>): Promise<SyncResult> {
|
|
144
|
+
const result: SyncResult = {
|
|
145
|
+
modulesScanned: moduleTree.size,
|
|
146
|
+
modulesExisting: 0,
|
|
147
|
+
modulesCreated: 0,
|
|
148
|
+
modulesDeleted: 0,
|
|
149
|
+
errors: [],
|
|
150
|
+
createdModules: [],
|
|
151
|
+
existingModules: [],
|
|
152
|
+
deletedModules: [],
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Sort paths by depth (shallowest first) to ensure parents are created before children
|
|
156
|
+
const sortedPaths = Array.from(moduleTree.keys()).sort((a, b) => {
|
|
157
|
+
const depthA = a.split('/').filter(p => p).length
|
|
158
|
+
const depthB = b.split('/').filter(p => p).length
|
|
159
|
+
return depthA - depthB
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
for (const modulePath of sortedPaths) {
|
|
163
|
+
try {
|
|
164
|
+
// Check if module already exists
|
|
165
|
+
const existingModuleId = await findModuleByPath(modulePath)
|
|
166
|
+
|
|
167
|
+
if (existingModuleId) {
|
|
168
|
+
result.modulesExisting++
|
|
169
|
+
result.existingModules.push(modulePath)
|
|
170
|
+
console.log(` ā Module '${modulePath}' already exists`)
|
|
171
|
+
} else {
|
|
172
|
+
// Build the hierarchy for this path (which will create it if needed)
|
|
173
|
+
await buildModuleHierarchy(modulePath)
|
|
174
|
+
result.modulesCreated++
|
|
175
|
+
result.createdModules.push(modulePath)
|
|
176
|
+
console.log(` ā Created module '${modulePath}'`)
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const errorMsg = `Error syncing module '${modulePath}': ${error}`
|
|
180
|
+
result.errors.push(errorMsg)
|
|
181
|
+
console.error(` ā ${errorMsg}`)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Deletes orphaned modules (modules in DB but not in FS)
|
|
190
|
+
*/
|
|
191
|
+
async function deleteOrphanedModules(fsModulePaths: Set<string>, result: SyncResult): Promise<void> {
|
|
192
|
+
console.log('\nš Checking for orphaned modules (not in filesystem)...')
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// Get all modules from database with their paths
|
|
196
|
+
const dbModules = await getAllModulesWithPaths()
|
|
197
|
+
|
|
198
|
+
// Sort modules by reverse depth (deepest first) to avoid foreign key constraint issues
|
|
199
|
+
const sortedDbModules = dbModules.sort((a, b) => {
|
|
200
|
+
const depthA = a.path.split('/').filter(p => p).length
|
|
201
|
+
const depthB = b.path.split('/').filter(p => p).length
|
|
202
|
+
return depthB - depthA // Reverse order
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
for (const dbModule of sortedDbModules) {
|
|
206
|
+
// Skip the default root module (preserve it)
|
|
207
|
+
if (dbModule.name === 'root' && dbModule.parentId === null) {
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check if module path exists in FS
|
|
212
|
+
if (!fsModulePaths.has(dbModule.path)) {
|
|
213
|
+
try {
|
|
214
|
+
// Check if module has dependencies (for logging)
|
|
215
|
+
const moduleWithDeps = await prisma.module.findUnique({
|
|
216
|
+
where: { id: dbModule.id },
|
|
217
|
+
include: {
|
|
218
|
+
locatorGroups: { select: { id: true } },
|
|
219
|
+
testSuites: { select: { id: true } },
|
|
220
|
+
},
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
if (moduleWithDeps) {
|
|
224
|
+
const hasLocatorGroups = moduleWithDeps.locatorGroups.length > 0
|
|
225
|
+
const hasTestSuites = moduleWithDeps.testSuites.length > 0
|
|
226
|
+
|
|
227
|
+
if (hasLocatorGroups || hasTestSuites) {
|
|
228
|
+
const deps = []
|
|
229
|
+
if (hasLocatorGroups) deps.push(`${moduleWithDeps.locatorGroups.length} locator group(s)`)
|
|
230
|
+
if (hasTestSuites) deps.push(`${moduleWithDeps.testSuites.length} test suite(s)`)
|
|
231
|
+
console.log(
|
|
232
|
+
` ā ļø Module '${dbModule.path}' has dependencies (${deps.join(', ')}) - will be cascade deleted`,
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Delete the module (Prisma cascade will handle children, locatorGroups, and testSuites)
|
|
237
|
+
await prisma.module.delete({
|
|
238
|
+
where: { id: dbModule.id },
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
result.modulesDeleted++
|
|
242
|
+
result.deletedModules.push(dbModule.path)
|
|
243
|
+
console.log(` šļø Deleted module '${dbModule.path}' (not in filesystem)`)
|
|
244
|
+
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
const errorMsg = `Error deleting module '${dbModule.path}': ${error}`
|
|
247
|
+
result.errors.push(errorMsg)
|
|
248
|
+
console.error(` ā ${errorMsg}`)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
const errorMsg = `Error checking for orphaned modules: ${error}`
|
|
254
|
+
result.errors.push(errorMsg)
|
|
255
|
+
console.error(` ā ${errorMsg}`)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Generates and displays sync summary
|
|
261
|
+
*/
|
|
262
|
+
function generateSummary(result: SyncResult): void {
|
|
263
|
+
console.log('\nš Sync Summary:')
|
|
264
|
+
console.log(` š Modules scanned: ${result.modulesScanned}`)
|
|
265
|
+
console.log(` ā
Modules existing: ${result.modulesExisting}`)
|
|
266
|
+
console.log(` ā Modules created: ${result.modulesCreated}`)
|
|
267
|
+
console.log(` šļø Modules deleted: ${result.modulesDeleted}`)
|
|
268
|
+
console.log(` ā Errors: ${result.errors.length}`)
|
|
269
|
+
|
|
270
|
+
if (result.createdModules.length > 0) {
|
|
271
|
+
console.log('\n Created modules:')
|
|
272
|
+
result.createdModules.forEach((path, index) => {
|
|
273
|
+
console.log(` ${index + 1}. ${path}`)
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (result.deletedModules.length > 0) {
|
|
278
|
+
console.log('\n Deleted modules:')
|
|
279
|
+
result.deletedModules.forEach((path, index) => {
|
|
280
|
+
console.log(` ${index + 1}. ${path}`)
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (result.errors.length > 0) {
|
|
285
|
+
console.log('\n Errors:')
|
|
286
|
+
result.errors.forEach((error, index) => {
|
|
287
|
+
console.log(` ${index + 1}. ${error}`)
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Main function
|
|
294
|
+
*/
|
|
295
|
+
async function main() {
|
|
296
|
+
try {
|
|
297
|
+
console.log('š Starting modules sync...')
|
|
298
|
+
console.log('This will scan filesystem directories and sync module hierarchy to database.')
|
|
299
|
+
console.log('Filesystem is the source of truth - modules in DB but not in FS will be deleted.\n')
|
|
300
|
+
|
|
301
|
+
const baseDir = process.cwd()
|
|
302
|
+
|
|
303
|
+
// Scan directories
|
|
304
|
+
console.log('š Scanning src/tests/locators...')
|
|
305
|
+
const locatorModulePaths = await scanLocatorDirectories(baseDir)
|
|
306
|
+
console.log(` Found ${locatorModulePaths.length} module path(s): ${locatorModulePaths.join(', ') || 'none'}`)
|
|
307
|
+
|
|
308
|
+
console.log('\nš Scanning src/tests/features...')
|
|
309
|
+
const featureModulePaths = await scanFeatureDirectories(baseDir)
|
|
310
|
+
console.log(` Found ${featureModulePaths.length} module path(s): ${featureModulePaths.join(', ') || 'none'}`)
|
|
311
|
+
|
|
312
|
+
// Combine and deduplicate
|
|
313
|
+
const allModulePaths = Array.from(new Set([...locatorModulePaths, ...featureModulePaths]))
|
|
314
|
+
console.log(`\nš Building module hierarchy from ${allModulePaths.length} unique module path(s)...`)
|
|
315
|
+
|
|
316
|
+
// Build module tree
|
|
317
|
+
const moduleTree = buildModuleTree(allModulePaths)
|
|
318
|
+
|
|
319
|
+
// Sync to database (create missing modules)
|
|
320
|
+
console.log('\nā
Syncing modules to database...')
|
|
321
|
+
const result = await syncModulesToDatabase(moduleTree)
|
|
322
|
+
|
|
323
|
+
// Delete orphaned modules (modules in DB but not in FS)
|
|
324
|
+
const fsModulePathsSet = new Set(allModulePaths)
|
|
325
|
+
// Also add all parent paths from the tree
|
|
326
|
+
for (const path of moduleTree.keys()) {
|
|
327
|
+
fsModulePathsSet.add(path)
|
|
328
|
+
}
|
|
329
|
+
await deleteOrphanedModules(fsModulePathsSet, result)
|
|
330
|
+
|
|
331
|
+
// Generate summary
|
|
332
|
+
generateSummary(result)
|
|
333
|
+
|
|
334
|
+
if (result.errors.length === 0) {
|
|
335
|
+
console.log('\nā
Sync completed successfully!')
|
|
336
|
+
} else {
|
|
337
|
+
console.log('\nā ļø Sync completed with errors. Please review the errors above.')
|
|
338
|
+
process.exit(1)
|
|
339
|
+
}
|
|
340
|
+
} catch (error) {
|
|
341
|
+
console.error('\nā Error during sync:', error)
|
|
342
|
+
process.exit(1)
|
|
343
|
+
} finally {
|
|
344
|
+
await prisma.$disconnect()
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
main()
|
|
349
|
+
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Script to synchronize tags from feature files to database
|
|
5
|
+
* Scans feature files to ensure all tags exist in DB
|
|
6
|
+
* Filesystem is the source of truth - tags in DB but not in FS will be deleted
|
|
7
|
+
* Run this after merging changes to ensure tag sync
|
|
8
|
+
*
|
|
9
|
+
* Usage: npx tsx scripts/sync-tags.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { join } from 'path'
|
|
13
|
+
import prisma from '../src/config/db-config'
|
|
14
|
+
import { scanFeatureFiles, ParsedFeature } from '../src/lib/gherkin-parser'
|
|
15
|
+
import { TagType } from '@prisma/client'
|
|
16
|
+
|
|
17
|
+
interface TagData {
|
|
18
|
+
name: string // Without @ prefix, for DB storage
|
|
19
|
+
tagExpression: string // With @ prefix, for tagExpression field
|
|
20
|
+
type: TagType // IDENTIFIER or FILTER
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SyncResult {
|
|
24
|
+
tagsScanned: number
|
|
25
|
+
tagsExisting: number
|
|
26
|
+
tagsCreated: number
|
|
27
|
+
tagsDeleted: number
|
|
28
|
+
errors: string[]
|
|
29
|
+
createdTags: string[]
|
|
30
|
+
deletedTags: string[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Splits a tag line that may contain multiple tags separated by spaces
|
|
35
|
+
* Example: "@smoke @demo" -> ["@smoke", "@demo"]
|
|
36
|
+
*/
|
|
37
|
+
function splitTagLine(tagLine: string): string[] {
|
|
38
|
+
// Split by spaces and filter for strings that start with @
|
|
39
|
+
return tagLine
|
|
40
|
+
.split(/\s+/)
|
|
41
|
+
.filter(tag => tag.trim().startsWith('@'))
|
|
42
|
+
.map(tag => tag.trim())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extracts unique tags from parsed feature files
|
|
47
|
+
* Combines feature-level and scenario-level tags
|
|
48
|
+
* Handles tags on the same line separated by spaces
|
|
49
|
+
*/
|
|
50
|
+
function extractUniqueTags(parsedFeatures: ParsedFeature[]): Set<string> {
|
|
51
|
+
const uniqueTags = new Set<string>()
|
|
52
|
+
|
|
53
|
+
for (const feature of parsedFeatures) {
|
|
54
|
+
// Add feature-level tags
|
|
55
|
+
for (const tagLine of feature.tags) {
|
|
56
|
+
if (tagLine.startsWith('@')) {
|
|
57
|
+
// Split tags that might be on the same line
|
|
58
|
+
const tags = splitTagLine(tagLine)
|
|
59
|
+
for (const tag of tags) {
|
|
60
|
+
uniqueTags.add(tag)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add scenario-level tags
|
|
66
|
+
for (const scenario of feature.scenarios) {
|
|
67
|
+
for (const tagLine of scenario.tags) {
|
|
68
|
+
if (tagLine.startsWith('@')) {
|
|
69
|
+
// Split tags that might be on the same line
|
|
70
|
+
const tags = splitTagLine(tagLine)
|
|
71
|
+
for (const tag of tags) {
|
|
72
|
+
uniqueTags.add(tag)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return uniqueTags
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Builds Tag objects from tag expressions
|
|
84
|
+
* Maps tag expressions to Prisma Tag model structure
|
|
85
|
+
*/
|
|
86
|
+
function buildTagObjects(tagExpressions: Set<string>): TagData[] {
|
|
87
|
+
const tagObjects: TagData[] = []
|
|
88
|
+
|
|
89
|
+
for (const tagExpression of tagExpressions) {
|
|
90
|
+
// Strip @ prefix for name field
|
|
91
|
+
const name = tagExpression.startsWith('@') ? tagExpression.substring(1) : tagExpression
|
|
92
|
+
|
|
93
|
+
// Determine type: IDENTIFIER if starts with tc_, otherwise FILTER
|
|
94
|
+
const type = name.startsWith('tc_') ? TagType.IDENTIFIER : TagType.FILTER
|
|
95
|
+
|
|
96
|
+
tagObjects.push({
|
|
97
|
+
name,
|
|
98
|
+
tagExpression, // Keep @ prefix for tagExpression
|
|
99
|
+
type,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return tagObjects
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Syncs tags to database
|
|
108
|
+
* Creates missing tags and deletes orphaned tags (FS is source of truth)
|
|
109
|
+
*/
|
|
110
|
+
async function syncTagsToDatabase(tagObjects: TagData[]): Promise<SyncResult> {
|
|
111
|
+
const result: SyncResult = {
|
|
112
|
+
tagsScanned: tagObjects.length,
|
|
113
|
+
tagsExisting: 0,
|
|
114
|
+
tagsCreated: 0,
|
|
115
|
+
tagsDeleted: 0,
|
|
116
|
+
errors: [],
|
|
117
|
+
createdTags: [],
|
|
118
|
+
deletedTags: [],
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get set of tag names from feature files (FS source of truth)
|
|
122
|
+
const fsTagNames = new Set(tagObjects.map(tag => tag.name))
|
|
123
|
+
|
|
124
|
+
// Get all tags from database
|
|
125
|
+
const allDbTags = await prisma.tag.findMany({
|
|
126
|
+
select: { id: true, name: true },
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Delete tags from DB that are not in FS (FS is source of truth)
|
|
130
|
+
console.log('\nš Checking for orphaned tags (not in feature files)...')
|
|
131
|
+
for (const dbTag of allDbTags) {
|
|
132
|
+
if (!fsTagNames.has(dbTag.name)) {
|
|
133
|
+
try {
|
|
134
|
+
await prisma.tag.delete({
|
|
135
|
+
where: { id: dbTag.id },
|
|
136
|
+
})
|
|
137
|
+
result.tagsDeleted++
|
|
138
|
+
result.deletedTags.push(dbTag.name)
|
|
139
|
+
console.log(` šļø Deleted tag '${dbTag.name}' (not in feature files)`)
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const errorMsg = `Error deleting tag '${dbTag.name}': ${error}`
|
|
142
|
+
result.errors.push(errorMsg)
|
|
143
|
+
console.error(` ā ${errorMsg}`)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Create or check tags from FS
|
|
149
|
+
console.log('\nā
Syncing tags from feature files to database...')
|
|
150
|
+
for (const tagData of tagObjects) {
|
|
151
|
+
try {
|
|
152
|
+
// Check if tag already exists by name (names are unique per user requirement)
|
|
153
|
+
const existing = await prisma.tag.findFirst({
|
|
154
|
+
where: { name: tagData.name },
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
if (existing) {
|
|
158
|
+
// If tag exists but has wrong type, update it
|
|
159
|
+
// This fixes tags that were created with wrong type (e.g., via UI or old code)
|
|
160
|
+
if (existing.type !== tagData.type) {
|
|
161
|
+
await prisma.tag.update({
|
|
162
|
+
where: { id: existing.id },
|
|
163
|
+
data: { type: tagData.type },
|
|
164
|
+
})
|
|
165
|
+
result.tagsCreated++ // Count as created since we're fixing it
|
|
166
|
+
result.createdTags.push(tagData.name)
|
|
167
|
+
console.log(` š Updated tag '${tagData.name}' type from ${existing.type} to ${tagData.type}`)
|
|
168
|
+
} else {
|
|
169
|
+
result.tagsExisting++
|
|
170
|
+
console.log(` ā Tag '${tagData.name}' already exists`)
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
// Create the tag
|
|
174
|
+
await prisma.tag.create({
|
|
175
|
+
data: {
|
|
176
|
+
name: tagData.name,
|
|
177
|
+
tagExpression: tagData.tagExpression,
|
|
178
|
+
type: tagData.type,
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
result.tagsCreated++
|
|
182
|
+
result.createdTags.push(tagData.name)
|
|
183
|
+
console.log(` ā Created tag '${tagData.name}' (type: ${tagData.type})`)
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
const errorMsg = `Error syncing tag '${tagData.name}': ${error}`
|
|
187
|
+
result.errors.push(errorMsg)
|
|
188
|
+
console.error(` ā ${errorMsg}`)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return result
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generates and displays sync summary
|
|
197
|
+
*/
|
|
198
|
+
function generateSummary(result: SyncResult): void {
|
|
199
|
+
console.log('\nš Sync Summary:')
|
|
200
|
+
console.log(` š Tags scanned: ${result.tagsScanned}`)
|
|
201
|
+
console.log(` ā
Tags existing: ${result.tagsExisting}`)
|
|
202
|
+
console.log(` ā Tags created: ${result.tagsCreated}`)
|
|
203
|
+
console.log(` šļø Tags deleted: ${result.tagsDeleted}`)
|
|
204
|
+
console.log(` ā Errors: ${result.errors.length}`)
|
|
205
|
+
|
|
206
|
+
if (result.createdTags.length > 0) {
|
|
207
|
+
console.log('\n Created tags:')
|
|
208
|
+
result.createdTags.forEach((name, index) => {
|
|
209
|
+
console.log(` ${index + 1}. ${name}`)
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (result.deletedTags.length > 0) {
|
|
214
|
+
console.log('\n Deleted tags:')
|
|
215
|
+
result.deletedTags.forEach((name, index) => {
|
|
216
|
+
console.log(` ${index + 1}. ${name}`)
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (result.errors.length > 0) {
|
|
221
|
+
console.log('\n Errors:')
|
|
222
|
+
result.errors.forEach((error, index) => {
|
|
223
|
+
console.log(` ${index + 1}. ${error}`)
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Main function
|
|
230
|
+
*/
|
|
231
|
+
async function main() {
|
|
232
|
+
try {
|
|
233
|
+
console.log('š Starting tags sync...')
|
|
234
|
+
console.log('This will scan feature files and sync tags to database.')
|
|
235
|
+
console.log('Filesystem is the source of truth - tags in DB but not in FS will be deleted.\n')
|
|
236
|
+
|
|
237
|
+
const baseDir = process.cwd()
|
|
238
|
+
const featuresDir = join(baseDir, 'src', 'tests', 'features')
|
|
239
|
+
|
|
240
|
+
// Scan feature files
|
|
241
|
+
console.log('š Scanning feature files in src/tests/features...')
|
|
242
|
+
const parsedFeatures = await scanFeatureFiles(featuresDir)
|
|
243
|
+
console.log(` Found ${parsedFeatures.length} feature file(s)`)
|
|
244
|
+
|
|
245
|
+
if (parsedFeatures.length === 0) {
|
|
246
|
+
console.log('\nā ļø No feature files found. Nothing to sync.')
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Extract unique tags
|
|
251
|
+
console.log('\nš Extracting unique tags from feature files...')
|
|
252
|
+
const uniqueTagExpressions = extractUniqueTags(parsedFeatures)
|
|
253
|
+
console.log(` Found ${uniqueTagExpressions.size} unique tag(s)`)
|
|
254
|
+
|
|
255
|
+
if (uniqueTagExpressions.size === 0) {
|
|
256
|
+
console.log('\nā ļø No tags found in feature files. Nothing to sync.')
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Build tag objects
|
|
261
|
+
console.log('\nšØ Building tag objects...')
|
|
262
|
+
const tagObjects = buildTagObjects(uniqueTagExpressions)
|
|
263
|
+
console.log(` Built ${tagObjects.length} tag object(s)`)
|
|
264
|
+
|
|
265
|
+
// Log tag details
|
|
266
|
+
console.log('\n Tag details:')
|
|
267
|
+
for (const tag of tagObjects) {
|
|
268
|
+
console.log(` - ${tag.tagExpression} ā name: '${tag.name}', type: ${tag.type}`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Sync to database
|
|
272
|
+
const result = await syncTagsToDatabase(tagObjects)
|
|
273
|
+
|
|
274
|
+
// Generate summary
|
|
275
|
+
generateSummary(result)
|
|
276
|
+
|
|
277
|
+
if (result.errors.length === 0) {
|
|
278
|
+
console.log('\nā
Sync completed successfully!')
|
|
279
|
+
} else {
|
|
280
|
+
console.log('\nā ļø Sync completed with errors. Please review the errors above.')
|
|
281
|
+
process.exit(1)
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error('\nā Error during sync:', error)
|
|
285
|
+
process.exit(1)
|
|
286
|
+
} finally {
|
|
287
|
+
await prisma.$disconnect()
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
main()
|
|
292
|
+
|