@empline/preflight 1.1.11 → 1.1.13
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/dist/checks/consolidated/auth-storage-state.d.ts +3 -0
- package/dist/checks/consolidated/auth-storage-state.d.ts.map +1 -0
- package/dist/checks/consolidated/auth-storage-state.js +146 -0
- package/dist/checks/consolidated/auth-storage-state.js.map +1 -0
- package/dist/checks/consolidated/business.d.ts +50 -0
- package/dist/checks/consolidated/business.d.ts.map +1 -0
- package/dist/checks/consolidated/business.js +252 -0
- package/dist/checks/consolidated/business.js.map +1 -0
- package/dist/checks/consolidated/caching-strategy.d.ts +104 -0
- package/dist/checks/consolidated/caching-strategy.d.ts.map +1 -0
- package/dist/checks/consolidated/caching-strategy.js +725 -0
- package/dist/checks/consolidated/caching-strategy.js.map +1 -0
- package/dist/checks/consolidated/code-quality.d.ts +83 -0
- package/dist/checks/consolidated/code-quality.d.ts.map +1 -0
- package/dist/checks/consolidated/code-quality.js +445 -0
- package/dist/checks/consolidated/code-quality.js.map +1 -0
- package/dist/checks/consolidated/console-statements.d.ts +32 -0
- package/dist/checks/consolidated/console-statements.d.ts.map +1 -0
- package/dist/checks/consolidated/console-statements.js +304 -0
- package/dist/checks/consolidated/console-statements.js.map +1 -0
- package/dist/checks/consolidated/css-advanced-validation.d.ts +24 -0
- package/dist/checks/consolidated/css-advanced-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/css-advanced-validation.js +415 -0
- package/dist/checks/consolidated/css-advanced-validation.js.map +1 -0
- package/dist/checks/consolidated/css-organization.d.ts +14 -0
- package/dist/checks/consolidated/css-organization.d.ts.map +1 -0
- package/dist/checks/consolidated/css-organization.js +432 -0
- package/dist/checks/consolidated/css-organization.js.map +1 -0
- package/dist/checks/consolidated/css-runtime-validation.d.ts +22 -0
- package/dist/checks/consolidated/css-runtime-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/css-runtime-validation.js +330 -0
- package/dist/checks/consolidated/css-runtime-validation.js.map +1 -0
- package/dist/checks/consolidated/css-variable-validation.d.ts +17 -0
- package/dist/checks/consolidated/css-variable-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/css-variable-validation.js +412 -0
- package/dist/checks/consolidated/css-variable-validation.js.map +1 -0
- package/dist/checks/consolidated/dark-mode-consistency.d.ts +23 -0
- package/dist/checks/consolidated/dark-mode-consistency.d.ts.map +1 -0
- package/dist/checks/consolidated/dark-mode-consistency.js +291 -0
- package/dist/checks/consolidated/dark-mode-consistency.js.map +1 -0
- package/dist/checks/consolidated/database.d.ts +95 -0
- package/dist/checks/consolidated/database.d.ts.map +1 -0
- package/dist/checks/consolidated/database.js +427 -0
- package/dist/checks/consolidated/database.js.map +1 -0
- package/dist/checks/consolidated/e2e-checks.d.ts +52 -0
- package/dist/checks/consolidated/e2e-checks.d.ts.map +1 -0
- package/dist/checks/consolidated/e2e-checks.js +157 -0
- package/dist/checks/consolidated/e2e-checks.js.map +1 -0
- package/dist/checks/consolidated/e2e-regression-coverage.d.ts +14 -0
- package/dist/checks/consolidated/e2e-regression-coverage.d.ts.map +1 -0
- package/dist/checks/consolidated/e2e-regression-coverage.js +151 -0
- package/dist/checks/consolidated/e2e-regression-coverage.js.map +1 -0
- package/dist/checks/consolidated/e2e-validation.d.ts +137 -0
- package/dist/checks/consolidated/e2e-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/e2e-validation.js +1001 -0
- package/dist/checks/consolidated/e2e-validation.js.map +1 -0
- package/dist/checks/consolidated/enterprise-baseline.d.ts +9 -0
- package/dist/checks/consolidated/enterprise-baseline.d.ts.map +1 -0
- package/dist/checks/consolidated/enterprise-baseline.js +277 -0
- package/dist/checks/consolidated/enterprise-baseline.js.map +1 -0
- package/dist/checks/consolidated/generate-pageload-config.d.ts +6 -0
- package/dist/checks/consolidated/generate-pageload-config.d.ts.map +1 -0
- package/dist/checks/consolidated/generate-pageload-config.js +161 -0
- package/dist/checks/consolidated/generate-pageload-config.js.map +1 -0
- package/dist/checks/consolidated/hardened-checks.d.ts +276 -0
- package/dist/checks/consolidated/hardened-checks.d.ts.map +1 -0
- package/dist/checks/consolidated/hardened-checks.js +3056 -0
- package/dist/checks/consolidated/hardened-checks.js.map +1 -0
- package/dist/checks/consolidated/homepage-ux.d.ts +12 -0
- package/dist/checks/consolidated/homepage-ux.d.ts.map +1 -0
- package/dist/checks/consolidated/homepage-ux.js +242 -0
- package/dist/checks/consolidated/homepage-ux.js.map +1 -0
- package/dist/checks/consolidated/images.d.ts +76 -0
- package/dist/checks/consolidated/images.d.ts.map +1 -0
- package/dist/checks/consolidated/images.js +311 -0
- package/dist/checks/consolidated/images.js.map +1 -0
- package/dist/checks/consolidated/import-cycles.d.ts +63 -0
- package/dist/checks/consolidated/import-cycles.d.ts.map +1 -0
- package/dist/checks/consolidated/import-cycles.js +291 -0
- package/dist/checks/consolidated/import-cycles.js.map +1 -0
- package/dist/checks/consolidated/imports.d.ts +112 -0
- package/dist/checks/consolidated/imports.d.ts.map +1 -0
- package/dist/checks/consolidated/imports.js +977 -0
- package/dist/checks/consolidated/imports.js.map +1 -0
- package/dist/checks/consolidated/inline-style-conflicts.d.ts +21 -0
- package/dist/checks/consolidated/inline-style-conflicts.d.ts.map +1 -0
- package/dist/checks/consolidated/inline-style-conflicts.js +300 -0
- package/dist/checks/consolidated/inline-style-conflicts.js.map +1 -0
- package/dist/checks/consolidated/lib-organization.d.ts +12 -0
- package/dist/checks/consolidated/lib-organization.d.ts.map +1 -0
- package/dist/checks/consolidated/lib-organization.js +419 -0
- package/dist/checks/consolidated/lib-organization.js.map +1 -0
- package/dist/checks/consolidated/n-plus-one.d.ts +63 -0
- package/dist/checks/consolidated/n-plus-one.d.ts.map +1 -0
- package/dist/checks/consolidated/n-plus-one.js +331 -0
- package/dist/checks/consolidated/n-plus-one.js.map +1 -0
- package/dist/checks/consolidated/nextjs.d.ts +51 -0
- package/dist/checks/consolidated/nextjs.d.ts.map +1 -0
- package/dist/checks/consolidated/nextjs.js +205 -0
- package/dist/checks/consolidated/nextjs.js.map +1 -0
- package/dist/checks/consolidated/organization.d.ts +54 -0
- package/dist/checks/consolidated/organization.d.ts.map +1 -0
- package/dist/checks/consolidated/organization.js +158 -0
- package/dist/checks/consolidated/organization.js.map +1 -0
- package/dist/checks/consolidated/pageload.d.ts +12 -0
- package/dist/checks/consolidated/pageload.d.ts.map +1 -0
- package/dist/checks/consolidated/pageload.js +138 -0
- package/dist/checks/consolidated/pageload.js.map +1 -0
- package/dist/checks/consolidated/performance.d.ts +112 -0
- package/dist/checks/consolidated/performance.d.ts.map +1 -0
- package/dist/checks/consolidated/performance.js +1546 -0
- package/dist/checks/consolidated/performance.js.map +1 -0
- package/dist/checks/consolidated/quality.d.ts +52 -0
- package/dist/checks/consolidated/quality.d.ts.map +1 -0
- package/dist/checks/consolidated/quality.js +253 -0
- package/dist/checks/consolidated/quality.js.map +1 -0
- package/dist/checks/consolidated/react.d.ts +48 -0
- package/dist/checks/consolidated/react.d.ts.map +1 -0
- package/dist/checks/consolidated/react.js +203 -0
- package/dist/checks/consolidated/react.js.map +1 -0
- package/dist/checks/consolidated/regression-hygiene.d.ts +17 -0
- package/dist/checks/consolidated/regression-hygiene.d.ts.map +1 -0
- package/dist/checks/consolidated/regression-hygiene.js +242 -0
- package/dist/checks/consolidated/regression-hygiene.js.map +1 -0
- package/dist/checks/consolidated/regression.d.ts +20 -0
- package/dist/checks/consolidated/regression.d.ts.map +1 -0
- package/dist/checks/consolidated/regression.js +121 -0
- package/dist/checks/consolidated/regression.js.map +1 -0
- package/dist/checks/consolidated/runtime.d.ts +53 -0
- package/dist/checks/consolidated/runtime.d.ts.map +1 -0
- package/dist/checks/consolidated/runtime.js +160 -0
- package/dist/checks/consolidated/runtime.js.map +1 -0
- package/dist/checks/consolidated/script-performance.d.ts +17 -0
- package/dist/checks/consolidated/script-performance.d.ts.map +1 -0
- package/dist/checks/consolidated/script-performance.js +137 -0
- package/dist/checks/consolidated/script-performance.js.map +1 -0
- package/dist/checks/consolidated/security.d.ts +78 -0
- package/dist/checks/consolidated/security.d.ts.map +1 -0
- package/dist/checks/consolidated/security.js +404 -0
- package/dist/checks/consolidated/security.js.map +1 -0
- package/dist/checks/consolidated/seo.d.ts +31 -0
- package/dist/checks/consolidated/seo.d.ts.map +1 -0
- package/dist/checks/consolidated/seo.js +1438 -0
- package/dist/checks/consolidated/seo.js.map +1 -0
- package/dist/checks/consolidated/sx-prop-deprecation.d.ts +22 -0
- package/dist/checks/consolidated/sx-prop-deprecation.d.ts.map +1 -0
- package/dist/checks/consolidated/sx-prop-deprecation.js +280 -0
- package/dist/checks/consolidated/sx-prop-deprecation.js.map +1 -0
- package/dist/checks/consolidated/tailwind-class-validation.d.ts +25 -0
- package/dist/checks/consolidated/tailwind-class-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/tailwind-class-validation.js +533 -0
- package/dist/checks/consolidated/tailwind-class-validation.js.map +1 -0
- package/dist/checks/consolidated/testing.d.ts +54 -0
- package/dist/checks/consolidated/testing.d.ts.map +1 -0
- package/dist/checks/consolidated/testing.js +163 -0
- package/dist/checks/consolidated/testing.js.map +1 -0
- package/dist/checks/consolidated/typescript.d.ts +3 -0
- package/dist/checks/consolidated/typescript.d.ts.map +1 -0
- package/dist/checks/consolidated/typescript.js +31 -0
- package/dist/checks/consolidated/typescript.js.map +1 -0
- package/dist/checks/consolidated/ui-accessibility-advanced.d.ts +104 -0
- package/dist/checks/consolidated/ui-accessibility-advanced.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-accessibility-advanced.js +689 -0
- package/dist/checks/consolidated/ui-accessibility-advanced.js.map +1 -0
- package/dist/checks/consolidated/ui-accessibility.d.ts +121 -0
- package/dist/checks/consolidated/ui-accessibility.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-accessibility.js +776 -0
- package/dist/checks/consolidated/ui-accessibility.js.map +1 -0
- package/dist/checks/consolidated/ui-advanced-spacing.d.ts +142 -0
- package/dist/checks/consolidated/ui-advanced-spacing.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-advanced-spacing.js +1220 -0
- package/dist/checks/consolidated/ui-advanced-spacing.js.map +1 -0
- package/dist/checks/consolidated/ui-animation-duration.d.ts +108 -0
- package/dist/checks/consolidated/ui-animation-duration.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-animation-duration.js +531 -0
- package/dist/checks/consolidated/ui-animation-duration.js.map +1 -0
- package/dist/checks/consolidated/ui-border-radius.d.ts +90 -0
- package/dist/checks/consolidated/ui-border-radius.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-border-radius.js +519 -0
- package/dist/checks/consolidated/ui-border-radius.js.map +1 -0
- package/dist/checks/consolidated/ui-buttons.d.ts +32 -0
- package/dist/checks/consolidated/ui-buttons.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-buttons.js +481 -0
- package/dist/checks/consolidated/ui-buttons.js.map +1 -0
- package/dist/checks/consolidated/ui-cards.d.ts +29 -0
- package/dist/checks/consolidated/ui-cards.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-cards.js +504 -0
- package/dist/checks/consolidated/ui-cards.js.map +1 -0
- package/dist/checks/consolidated/ui-checks.d.ts +48 -0
- package/dist/checks/consolidated/ui-checks.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-checks.js +264 -0
- package/dist/checks/consolidated/ui-checks.js.map +1 -0
- package/dist/checks/consolidated/ui-cleanup.d.ts +81 -0
- package/dist/checks/consolidated/ui-cleanup.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-cleanup.js +650 -0
- package/dist/checks/consolidated/ui-cleanup.js.map +1 -0
- package/dist/checks/consolidated/ui-components.d.ts +255 -0
- package/dist/checks/consolidated/ui-components.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-components.js +2008 -0
- package/dist/checks/consolidated/ui-components.js.map +1 -0
- package/dist/checks/consolidated/ui-consistency-advanced.d.ts +130 -0
- package/dist/checks/consolidated/ui-consistency-advanced.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-consistency-advanced.js +982 -0
- package/dist/checks/consolidated/ui-consistency-advanced.js.map +1 -0
- package/dist/checks/consolidated/ui-consistency-comprehensive.d.ts +30 -0
- package/dist/checks/consolidated/ui-consistency-comprehensive.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-consistency-comprehensive.js +1018 -0
- package/dist/checks/consolidated/ui-consistency-comprehensive.js.map +1 -0
- package/dist/checks/consolidated/ui-consistency-extended.d.ts +26 -0
- package/dist/checks/consolidated/ui-consistency-extended.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-consistency-extended.js +606 -0
- package/dist/checks/consolidated/ui-consistency-extended.js.map +1 -0
- package/dist/checks/consolidated/ui-data-display.d.ts +103 -0
- package/dist/checks/consolidated/ui-data-display.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-data-display.js +740 -0
- package/dist/checks/consolidated/ui-data-display.js.map +1 -0
- package/dist/checks/consolidated/ui-deprecated.d.ts +22 -0
- package/dist/checks/consolidated/ui-deprecated.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-deprecated.js +336 -0
- package/dist/checks/consolidated/ui-deprecated.js.map +1 -0
- package/dist/checks/consolidated/ui-empty-null-states.d.ts +90 -0
- package/dist/checks/consolidated/ui-empty-null-states.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-empty-null-states.js +511 -0
- package/dist/checks/consolidated/ui-empty-null-states.js.map +1 -0
- package/dist/checks/consolidated/ui-error-states.d.ts +99 -0
- package/dist/checks/consolidated/ui-error-states.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-error-states.js +694 -0
- package/dist/checks/consolidated/ui-error-states.js.map +1 -0
- package/dist/checks/consolidated/ui-feedback-confirmations.d.ts +90 -0
- package/dist/checks/consolidated/ui-feedback-confirmations.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-feedback-confirmations.js +596 -0
- package/dist/checks/consolidated/ui-feedback-confirmations.js.map +1 -0
- package/dist/checks/consolidated/ui-forms.d.ts +32 -0
- package/dist/checks/consolidated/ui-forms.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-forms.js +568 -0
- package/dist/checks/consolidated/ui-forms.js.map +1 -0
- package/dist/checks/consolidated/ui-gradient-shadow.d.ts +90 -0
- package/dist/checks/consolidated/ui-gradient-shadow.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-gradient-shadow.js +568 -0
- package/dist/checks/consolidated/ui-gradient-shadow.js.map +1 -0
- package/dist/checks/consolidated/ui-grid-responsive.d.ts +27 -0
- package/dist/checks/consolidated/ui-grid-responsive.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-grid-responsive.js +441 -0
- package/dist/checks/consolidated/ui-grid-responsive.js.map +1 -0
- package/dist/checks/consolidated/ui-icon-size-tokens.d.ts +104 -0
- package/dist/checks/consolidated/ui-icon-size-tokens.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-icon-size-tokens.js +514 -0
- package/dist/checks/consolidated/ui-icon-size-tokens.js.map +1 -0
- package/dist/checks/consolidated/ui-iconography.d.ts +90 -0
- package/dist/checks/consolidated/ui-iconography.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-iconography.js +565 -0
- package/dist/checks/consolidated/ui-iconography.js.map +1 -0
- package/dist/checks/consolidated/ui-interactive-states.d.ts +240 -0
- package/dist/checks/consolidated/ui-interactive-states.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-interactive-states.js +2474 -0
- package/dist/checks/consolidated/ui-interactive-states.js.map +1 -0
- package/dist/checks/consolidated/ui-layout.d.ts +256 -0
- package/dist/checks/consolidated/ui-layout.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-layout.js +1371 -0
- package/dist/checks/consolidated/ui-layout.js.map +1 -0
- package/dist/checks/consolidated/ui-loading-skeletons.d.ts +11 -0
- package/dist/checks/consolidated/ui-loading-skeletons.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-loading-skeletons.js +145 -0
- package/dist/checks/consolidated/ui-loading-skeletons.js.map +1 -0
- package/dist/checks/consolidated/ui-loading-state-skeletons.d.ts +9 -0
- package/dist/checks/consolidated/ui-loading-state-skeletons.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-loading-state-skeletons.js +125 -0
- package/dist/checks/consolidated/ui-loading-state-skeletons.js.map +1 -0
- package/dist/checks/consolidated/ui-media.d.ts +74 -0
- package/dist/checks/consolidated/ui-media.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-media.js +408 -0
- package/dist/checks/consolidated/ui-media.js.map +1 -0
- package/dist/checks/consolidated/ui-micro-interactions.d.ts +107 -0
- package/dist/checks/consolidated/ui-micro-interactions.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-micro-interactions.js +825 -0
- package/dist/checks/consolidated/ui-micro-interactions.js.map +1 -0
- package/dist/checks/consolidated/ui-microcopy-consistency.d.ts +114 -0
- package/dist/checks/consolidated/ui-microcopy-consistency.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-microcopy-consistency.js +566 -0
- package/dist/checks/consolidated/ui-microcopy-consistency.js.map +1 -0
- package/dist/checks/consolidated/ui-mobile-ux.d.ts +251 -0
- package/dist/checks/consolidated/ui-mobile-ux.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-mobile-ux.js +2212 -0
- package/dist/checks/consolidated/ui-mobile-ux.js.map +1 -0
- package/dist/checks/consolidated/ui-motion-accessibility.d.ts +93 -0
- package/dist/checks/consolidated/ui-motion-accessibility.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-motion-accessibility.js +450 -0
- package/dist/checks/consolidated/ui-motion-accessibility.js.map +1 -0
- package/dist/checks/consolidated/ui-navigation.d.ts +85 -0
- package/dist/checks/consolidated/ui-navigation.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-navigation.js +673 -0
- package/dist/checks/consolidated/ui-navigation.js.map +1 -0
- package/dist/checks/consolidated/ui-patterns.d.ts +174 -0
- package/dist/checks/consolidated/ui-patterns.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-patterns.js +1532 -0
- package/dist/checks/consolidated/ui-patterns.js.map +1 -0
- package/dist/checks/consolidated/ui-responsive.d.ts +89 -0
- package/dist/checks/consolidated/ui-responsive.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-responsive.js +588 -0
- package/dist/checks/consolidated/ui-responsive.js.map +1 -0
- package/dist/checks/consolidated/ui-spacing-standards.d.ts +43 -0
- package/dist/checks/consolidated/ui-spacing-standards.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-spacing-standards.js +874 -0
- package/dist/checks/consolidated/ui-spacing-standards.js.map +1 -0
- package/dist/checks/consolidated/ui-spacing.d.ts +751 -0
- package/dist/checks/consolidated/ui-spacing.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-spacing.js +4996 -0
- package/dist/checks/consolidated/ui-spacing.js.map +1 -0
- package/dist/checks/consolidated/ui-standards-auto-fixer.d.ts +70 -0
- package/dist/checks/consolidated/ui-standards-auto-fixer.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-standards-auto-fixer.js +429 -0
- package/dist/checks/consolidated/ui-standards-auto-fixer.js.map +1 -0
- package/dist/checks/consolidated/ui-standards-enforcement.d.ts +100 -0
- package/dist/checks/consolidated/ui-standards-enforcement.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-standards-enforcement.js +935 -0
- package/dist/checks/consolidated/ui-standards-enforcement.js.map +1 -0
- package/dist/checks/consolidated/ui-state-consistency.d.ts +90 -0
- package/dist/checks/consolidated/ui-state-consistency.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-state-consistency.js +659 -0
- package/dist/checks/consolidated/ui-state-consistency.js.map +1 -0
- package/dist/checks/consolidated/ui-style-validation.d.ts +74 -0
- package/dist/checks/consolidated/ui-style-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-style-validation.js +403 -0
- package/dist/checks/consolidated/ui-style-validation.js.map +1 -0
- package/dist/checks/consolidated/ui-tokens.d.ts +110 -0
- package/dist/checks/consolidated/ui-tokens.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-tokens.js +990 -0
- package/dist/checks/consolidated/ui-tokens.js.map +1 -0
- package/dist/checks/consolidated/ui-typography.d.ts +77 -0
- package/dist/checks/consolidated/ui-typography.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-typography.js +416 -0
- package/dist/checks/consolidated/ui-typography.js.map +1 -0
- package/dist/checks/consolidated/ui-visual-hierarchy.d.ts +90 -0
- package/dist/checks/consolidated/ui-visual-hierarchy.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-visual-hierarchy.js +562 -0
- package/dist/checks/consolidated/ui-visual-hierarchy.js.map +1 -0
- package/dist/checks/consolidated/woocommerce.d.ts +50 -0
- package/dist/checks/consolidated/woocommerce.d.ts.map +1 -0
- package/dist/checks/consolidated/woocommerce.js +198 -0
- package/dist/checks/consolidated/woocommerce.js.map +1 -0
- package/dist/checks/core/api-route-protection.d.ts +2 -0
- package/dist/checks/core/api-route-protection.d.ts.map +1 -0
- package/dist/checks/core/api-route-protection.js +101 -0
- package/dist/checks/core/api-route-protection.js.map +1 -0
- package/dist/checks/core/critical.d.ts +8 -0
- package/dist/checks/core/critical.d.ts.map +1 -0
- package/dist/checks/core/critical.js +200 -0
- package/dist/checks/core/critical.js.map +1 -0
- package/dist/checks/core/database.d.ts +8 -0
- package/dist/checks/core/database.d.ts.map +1 -0
- package/dist/checks/core/database.js +699 -0
- package/dist/checks/core/database.js.map +1 -0
- package/dist/checks/core/development.d.ts +8 -0
- package/dist/checks/core/development.d.ts.map +1 -0
- package/dist/checks/core/development.js +417 -0
- package/dist/checks/core/development.js.map +1 -0
- package/dist/checks/core/hydration-mismatch-check.d.ts +38 -0
- package/dist/checks/core/hydration-mismatch-check.d.ts.map +1 -0
- package/dist/checks/core/hydration-mismatch-check.js +411 -0
- package/dist/checks/core/hydration-mismatch-check.js.map +1 -0
- package/dist/checks/core/performance.d.ts +8 -0
- package/dist/checks/core/performance.d.ts.map +1 -0
- package/dist/checks/core/performance.js +474 -0
- package/dist/checks/core/performance.js.map +1 -0
- package/dist/checks/core/security.d.ts +8 -0
- package/dist/checks/core/security.d.ts.map +1 -0
- package/dist/checks/core/security.js +275 -0
- package/dist/checks/core/security.js.map +1 -0
- package/dist/checks/core/standardized-error-handling.d.ts +43 -0
- package/dist/checks/core/standardized-error-handling.d.ts.map +1 -0
- package/dist/checks/core/standardized-error-handling.js +384 -0
- package/dist/checks/core/standardized-error-handling.js.map +1 -0
- package/dist/checks/core/supercatch.d.ts +8 -0
- package/dist/checks/core/supercatch.d.ts.map +1 -0
- package/dist/checks/core/supercatch.js +750 -0
- package/dist/checks/core/supercatch.js.map +1 -0
- package/dist/checks/core/suppression-check.d.ts +2 -0
- package/dist/checks/core/suppression-check.d.ts.map +1 -0
- package/dist/checks/core/suppression-check.js +129 -0
- package/dist/checks/core/suppression-check.js.map +1 -0
- package/dist/checks/core/ui-quality.d.ts +8 -0
- package/dist/checks/core/ui-quality.d.ts.map +1 -0
- package/dist/checks/core/ui-quality.js +1736 -0
- package/dist/checks/core/ui-quality.js.map +1 -0
- package/dist/checks/core/unused-assets-check.d.ts +2 -0
- package/dist/checks/core/unused-assets-check.d.ts.map +1 -0
- package/dist/checks/core/unused-assets-check.js +112 -0
- package/dist/checks/core/unused-assets-check.js.map +1 -0
- package/dist/checks/core/use-status-ssr-safety.d.ts +34 -0
- package/dist/checks/core/use-status-ssr-safety.d.ts.map +1 -0
- package/dist/checks/core/use-status-ssr-safety.js +283 -0
- package/dist/checks/core/use-status-ssr-safety.js.map +1 -0
- package/dist/checks/email/email-flow-validation.d.ts +23 -0
- package/dist/checks/email/email-flow-validation.d.ts.map +1 -0
- package/dist/checks/email/email-flow-validation.js +468 -0
- package/dist/checks/email/email-flow-validation.js.map +1 -0
- package/dist/checks/email/email-template-db-verification.d.ts +20 -0
- package/dist/checks/email/email-template-db-verification.d.ts.map +1 -0
- package/dist/checks/email/email-template-db-verification.js +46 -0
- package/dist/checks/email/email-template-db-verification.js.map +1 -0
- package/dist/checks/email/email-template-validation.d.ts +24 -0
- package/dist/checks/email/email-template-validation.d.ts.map +1 -0
- package/dist/checks/email/email-template-validation.js +688 -0
- package/dist/checks/email/email-template-validation.js.map +1 -0
- package/dist/checks/jsx/comment-placement.d.ts +45 -0
- package/dist/checks/jsx/comment-placement.d.ts.map +1 -0
- package/dist/checks/jsx/comment-placement.js +316 -0
- package/dist/checks/jsx/comment-placement.js.map +1 -0
- package/dist/checks/specialized/admin-layout-check.d.ts +19 -0
- package/dist/checks/specialized/admin-layout-check.d.ts.map +1 -0
- package/dist/checks/specialized/admin-layout-check.js +166 -0
- package/dist/checks/specialized/admin-layout-check.js.map +1 -0
- package/dist/checks/specialized/client-server-separation.d.ts +14 -0
- package/dist/checks/specialized/client-server-separation.d.ts.map +1 -0
- package/dist/checks/specialized/client-server-separation.js +197 -0
- package/dist/checks/specialized/client-server-separation.js.map +1 -0
- package/dist/checks/specialized/cost-optimization.d.ts +18 -0
- package/dist/checks/specialized/cost-optimization.d.ts.map +1 -0
- package/dist/checks/specialized/cost-optimization.js +78 -0
- package/dist/checks/specialized/cost-optimization.js.map +1 -0
- package/dist/checks/specialized/database-migration-sync.d.ts +21 -0
- package/dist/checks/specialized/database-migration-sync.d.ts.map +1 -0
- package/dist/checks/specialized/database-migration-sync.js +150 -0
- package/dist/checks/specialized/database-migration-sync.js.map +1 -0
- package/dist/checks/specialized/database-model-validation.d.ts +15 -0
- package/dist/checks/specialized/database-model-validation.d.ts.map +1 -0
- package/dist/checks/specialized/database-model-validation.js +35 -0
- package/dist/checks/specialized/database-model-validation.js.map +1 -0
- package/dist/checks/specialized/database-schema-migrations-diff.d.ts +27 -0
- package/dist/checks/specialized/database-schema-migrations-diff.d.ts.map +1 -0
- package/dist/checks/specialized/database-schema-migrations-diff.js +177 -0
- package/dist/checks/specialized/database-schema-migrations-diff.js.map +1 -0
- package/dist/checks/specialized/database-schema-sync.d.ts +23 -0
- package/dist/checks/specialized/database-schema-sync.d.ts.map +1 -0
- package/dist/checks/specialized/database-schema-sync.js +77 -0
- package/dist/checks/specialized/database-schema-sync.js.map +1 -0
- package/dist/checks/specialized/decimal-serialization.d.ts +24 -0
- package/dist/checks/specialized/decimal-serialization.d.ts.map +1 -0
- package/dist/checks/specialized/decimal-serialization.js +400 -0
- package/dist/checks/specialized/decimal-serialization.js.map +1 -0
- package/dist/checks/specialized/detect-router-issues.d.ts +14 -0
- package/dist/checks/specialized/detect-router-issues.d.ts.map +1 -0
- package/dist/checks/specialized/detect-router-issues.js +96 -0
- package/dist/checks/specialized/detect-router-issues.js.map +1 -0
- package/dist/checks/specialized/enum-validation.d.ts +15 -0
- package/dist/checks/specialized/enum-validation.d.ts.map +1 -0
- package/dist/checks/specialized/enum-validation.js +35 -0
- package/dist/checks/specialized/enum-validation.js.map +1 -0
- package/dist/checks/specialized/hash-collision.d.ts +18 -0
- package/dist/checks/specialized/hash-collision.d.ts.map +1 -0
- package/dist/checks/specialized/hash-collision.js +78 -0
- package/dist/checks/specialized/hash-collision.js.map +1 -0
- package/dist/checks/specialized/id-generation-enforcement.d.ts +16 -0
- package/dist/checks/specialized/id-generation-enforcement.d.ts.map +1 -0
- package/dist/checks/specialized/id-generation-enforcement.js +307 -0
- package/dist/checks/specialized/id-generation-enforcement.js.map +1 -0
- package/dist/checks/specialized/image-data-integrity.d.ts +15 -0
- package/dist/checks/specialized/image-data-integrity.d.ts.map +1 -0
- package/dist/checks/specialized/image-data-integrity.js +79 -0
- package/dist/checks/specialized/image-data-integrity.js.map +1 -0
- package/dist/checks/specialized/image-health.d.ts +14 -0
- package/dist/checks/specialized/image-health.d.ts.map +1 -0
- package/dist/checks/specialized/image-health.js +122 -0
- package/dist/checks/specialized/image-health.js.map +1 -0
- package/dist/checks/specialized/image-metadata-validation.d.ts +14 -0
- package/dist/checks/specialized/image-metadata-validation.d.ts.map +1 -0
- package/dist/checks/specialized/image-metadata-validation.js +95 -0
- package/dist/checks/specialized/image-metadata-validation.js.map +1 -0
- package/dist/checks/specialized/image-optimization.d.ts +16 -0
- package/dist/checks/specialized/image-optimization.d.ts.map +1 -0
- package/dist/checks/specialized/image-optimization.js +86 -0
- package/dist/checks/specialized/image-optimization.js.map +1 -0
- package/dist/checks/specialized/invalid-module-imports.d.ts +24 -0
- package/dist/checks/specialized/invalid-module-imports.d.ts.map +1 -0
- package/dist/checks/specialized/invalid-module-imports.js +209 -0
- package/dist/checks/specialized/invalid-module-imports.js.map +1 -0
- package/dist/checks/specialized/lint-validation.d.ts +26 -0
- package/dist/checks/specialized/lint-validation.d.ts.map +1 -0
- package/dist/checks/specialized/lint-validation.js +193 -0
- package/dist/checks/specialized/lint-validation.js.map +1 -0
- package/dist/checks/specialized/listing-workflow.d.ts +19 -0
- package/dist/checks/specialized/listing-workflow.d.ts.map +1 -0
- package/dist/checks/specialized/listing-workflow.js +89 -0
- package/dist/checks/specialized/listing-workflow.js.map +1 -0
- package/dist/checks/specialized/mui-imports-validation.d.ts +18 -0
- package/dist/checks/specialized/mui-imports-validation.d.ts.map +1 -0
- package/dist/checks/specialized/mui-imports-validation.js +134 -0
- package/dist/checks/specialized/mui-imports-validation.js.map +1 -0
- package/dist/checks/specialized/nextauth-v5-compliance.d.ts +16 -0
- package/dist/checks/specialized/nextauth-v5-compliance.d.ts.map +1 -0
- package/dist/checks/specialized/nextauth-v5-compliance.js +164 -0
- package/dist/checks/specialized/nextauth-v5-compliance.js.map +1 -0
- package/dist/checks/specialized/nextjs-params-check.d.ts +14 -0
- package/dist/checks/specialized/nextjs-params-check.d.ts.map +1 -0
- package/dist/checks/specialized/nextjs-params-check.js +140 -0
- package/dist/checks/specialized/nextjs-params-check.js.map +1 -0
- package/dist/checks/specialized/no-legacy-catalog-aliases-validation.d.ts +16 -0
- package/dist/checks/specialized/no-legacy-catalog-aliases-validation.d.ts.map +1 -0
- package/dist/checks/specialized/no-legacy-catalog-aliases-validation.js +36 -0
- package/dist/checks/specialized/no-legacy-catalog-aliases-validation.js.map +1 -0
- package/dist/checks/specialized/no-wata-cardgraded-validation.d.ts +22 -0
- package/dist/checks/specialized/no-wata-cardgraded-validation.d.ts.map +1 -0
- package/dist/checks/specialized/no-wata-cardgraded-validation.js +97 -0
- package/dist/checks/specialized/no-wata-cardgraded-validation.js.map +1 -0
- package/dist/checks/specialized/parameter-consistency-check.d.ts +20 -0
- package/dist/checks/specialized/parameter-consistency-check.d.ts.map +1 -0
- package/dist/checks/specialized/parameter-consistency-check.js +115 -0
- package/dist/checks/specialized/parameter-consistency-check.js.map +1 -0
- package/dist/checks/specialized/prisma-field-names-validation.d.ts +15 -0
- package/dist/checks/specialized/prisma-field-names-validation.d.ts.map +1 -0
- package/dist/checks/specialized/prisma-field-names-validation.js +35 -0
- package/dist/checks/specialized/prisma-field-names-validation.js.map +1 -0
- package/dist/checks/specialized/prisma-null-syntax.d.ts +34 -0
- package/dist/checks/specialized/prisma-null-syntax.d.ts.map +1 -0
- package/dist/checks/specialized/prisma-null-syntax.js +330 -0
- package/dist/checks/specialized/prisma-null-syntax.js.map +1 -0
- package/dist/checks/specialized/prisma-query-validation.d.ts +15 -0
- package/dist/checks/specialized/prisma-query-validation.d.ts.map +1 -0
- package/dist/checks/specialized/prisma-query-validation.js +35 -0
- package/dist/checks/specialized/prisma-query-validation.js.map +1 -0
- package/dist/checks/specialized/product-type-validation.d.ts +17 -0
- package/dist/checks/specialized/product-type-validation.d.ts.map +1 -0
- package/dist/checks/specialized/product-type-validation.js +129 -0
- package/dist/checks/specialized/product-type-validation.js.map +1 -0
- package/dist/checks/specialized/responsive-image-validation.d.ts +14 -0
- package/dist/checks/specialized/responsive-image-validation.d.ts.map +1 -0
- package/dist/checks/specialized/responsive-image-validation.js +101 -0
- package/dist/checks/specialized/responsive-image-validation.js.map +1 -0
- package/dist/checks/specialized/root-cleanliness.d.ts +21 -0
- package/dist/checks/specialized/root-cleanliness.d.ts.map +1 -0
- package/dist/checks/specialized/root-cleanliness.js +251 -0
- package/dist/checks/specialized/root-cleanliness.js.map +1 -0
- package/dist/checks/specialized/rotation-detection-validation.d.ts +16 -0
- package/dist/checks/specialized/rotation-detection-validation.d.ts.map +1 -0
- package/dist/checks/specialized/rotation-detection-validation.js +113 -0
- package/dist/checks/specialized/rotation-detection-validation.js.map +1 -0
- package/dist/checks/specialized/script-organization.d.ts +17 -0
- package/dist/checks/specialized/script-organization.d.ts.map +1 -0
- package/dist/checks/specialized/script-organization.js +487 -0
- package/dist/checks/specialized/script-organization.js.map +1 -0
- package/dist/checks/specialized/shared-components-migration.d.ts +137 -0
- package/dist/checks/specialized/shared-components-migration.d.ts.map +1 -0
- package/dist/checks/specialized/shared-components-migration.js +1288 -0
- package/dist/checks/specialized/shared-components-migration.js.map +1 -0
- package/dist/checks/specialized/store-specialties-normalization.d.ts +10 -0
- package/dist/checks/specialized/store-specialties-normalization.d.ts.map +1 -0
- package/dist/checks/specialized/store-specialties-normalization.js +126 -0
- package/dist/checks/specialized/store-specialties-normalization.js.map +1 -0
- package/dist/checks/specialized/two-stage-trim-validation.d.ts +16 -0
- package/dist/checks/specialized/two-stage-trim-validation.d.ts.map +1 -0
- package/dist/checks/specialized/two-stage-trim-validation.js +115 -0
- package/dist/checks/specialized/two-stage-trim-validation.js.map +1 -0
- package/dist/checks/specialized/underscore-variable-audit.d.ts +26 -0
- package/dist/checks/specialized/underscore-variable-audit.d.ts.map +1 -0
- package/dist/checks/specialized/underscore-variable-audit.js +219 -0
- package/dist/checks/specialized/underscore-variable-audit.js.map +1 -0
- package/dist/checks/specialized/unified-badge-consistency.d.ts +16 -0
- package/dist/checks/specialized/unified-badge-consistency.d.ts.map +1 -0
- package/dist/checks/specialized/unified-badge-consistency.js +284 -0
- package/dist/checks/specialized/unified-badge-consistency.js.map +1 -0
- package/dist/checks/specialized/validate-integration-enums.d.ts +15 -0
- package/dist/checks/specialized/validate-integration-enums.d.ts.map +1 -0
- package/dist/checks/specialized/validate-integration-enums.js +131 -0
- package/dist/checks/specialized/validate-integration-enums.js.map +1 -0
- package/dist/checks/testing/action-regression.d.ts +23 -0
- package/dist/checks/testing/action-regression.d.ts.map +1 -0
- package/dist/checks/testing/action-regression.js +192 -0
- package/dist/checks/testing/action-regression.js.map +1 -0
- package/dist/checks/testing/critical-api-coverage.d.ts +21 -0
- package/dist/checks/testing/critical-api-coverage.d.ts.map +1 -0
- package/dist/checks/testing/critical-api-coverage.js +158 -0
- package/dist/checks/testing/critical-api-coverage.js.map +1 -0
- package/dist/checks/testing/data-entry-regression-required.d.ts +24 -0
- package/dist/checks/testing/data-entry-regression-required.d.ts.map +1 -0
- package/dist/checks/testing/data-entry-regression-required.js +378 -0
- package/dist/checks/testing/data-entry-regression-required.js.map +1 -0
- package/dist/checks/testing/e2e-best-practices.d.ts +24 -0
- package/dist/checks/testing/e2e-best-practices.d.ts.map +1 -0
- package/dist/checks/testing/e2e-best-practices.js +791 -0
- package/dist/checks/testing/e2e-best-practices.js.map +1 -0
- package/dist/checks/testing/e2e-flake-patterns.d.ts +26 -0
- package/dist/checks/testing/e2e-flake-patterns.d.ts.map +1 -0
- package/dist/checks/testing/e2e-flake-patterns.js +305 -0
- package/dist/checks/testing/e2e-flake-patterns.js.map +1 -0
- package/dist/checks/testing/e2e-redundant-visibility-checks.d.ts +25 -0
- package/dist/checks/testing/e2e-redundant-visibility-checks.d.ts.map +1 -0
- package/dist/checks/testing/e2e-redundant-visibility-checks.js +613 -0
- package/dist/checks/testing/e2e-redundant-visibility-checks.js.map +1 -0
- package/dist/checks/testing/e2e-slow-tests.d.ts +9 -0
- package/dist/checks/testing/e2e-slow-tests.d.ts.map +1 -0
- package/dist/checks/testing/e2e-slow-tests.js +142 -0
- package/dist/checks/testing/e2e-slow-tests.js.map +1 -0
- package/dist/checks/testing/e2e-timeouts.d.ts +9 -0
- package/dist/checks/testing/e2e-timeouts.d.ts.map +1 -0
- package/dist/checks/testing/e2e-timeouts.js +82 -0
- package/dist/checks/testing/e2e-timeouts.js.map +1 -0
- package/dist/checks/testing/integration-e2e-depth.d.ts +20 -0
- package/dist/checks/testing/integration-e2e-depth.d.ts.map +1 -0
- package/dist/checks/testing/integration-e2e-depth.js +575 -0
- package/dist/checks/testing/integration-e2e-depth.js.map +1 -0
- package/dist/checks/testing/playwright-feature-coverage-gaps.d.ts +31 -0
- package/dist/checks/testing/playwright-feature-coverage-gaps.d.ts.map +1 -0
- package/dist/checks/testing/playwright-feature-coverage-gaps.js +1582 -0
- package/dist/checks/testing/playwright-feature-coverage-gaps.js.map +1 -0
- package/dist/checks/testing/playwright-mock-inventory.d.ts +24 -0
- package/dist/checks/testing/playwright-mock-inventory.d.ts.map +1 -0
- package/dist/checks/testing/playwright-mock-inventory.js +380 -0
- package/dist/checks/testing/playwright-mock-inventory.js.map +1 -0
- package/dist/checks/testing/test-coverage-threshold.d.ts +25 -0
- package/dist/checks/testing/test-coverage-threshold.d.ts.map +1 -0
- package/dist/checks/testing/test-coverage-threshold.js +166 -0
- package/dist/checks/testing/test-coverage-threshold.js.map +1 -0
- package/dist/checks/testing/test-flakiness-score.d.ts +27 -0
- package/dist/checks/testing/test-flakiness-score.d.ts.map +1 -0
- package/dist/checks/testing/test-flakiness-score.js +358 -0
- package/dist/checks/testing/test-flakiness-score.js.map +1 -0
- package/dist/checks/testing/test-patterns.d.ts +16 -0
- package/dist/checks/testing/test-patterns.d.ts.map +1 -0
- package/dist/checks/testing/test-patterns.js +156 -0
- package/dist/checks/testing/test-patterns.js.map +1 -0
- package/dist/checks/workflows/a-plus-rating-validation.d.ts +42 -0
- package/dist/checks/workflows/a-plus-rating-validation.d.ts.map +1 -0
- package/dist/checks/workflows/a-plus-rating-validation.js +527 -0
- package/dist/checks/workflows/a-plus-rating-validation.js.map +1 -0
- package/dist/checks/workflows/affected.d.ts +14 -0
- package/dist/checks/workflows/affected.d.ts.map +1 -0
- package/dist/checks/workflows/affected.js +126 -0
- package/dist/checks/workflows/affected.js.map +1 -0
- package/dist/checks/workflows/ai.d.ts +6 -0
- package/dist/checks/workflows/ai.d.ts.map +1 -0
- package/dist/checks/workflows/ai.js +42 -0
- package/dist/checks/workflows/ai.js.map +1 -0
- package/dist/checks/workflows/all.d.ts +31 -0
- package/dist/checks/workflows/all.d.ts.map +1 -0
- package/dist/checks/workflows/all.js +2688 -0
- package/dist/checks/workflows/all.js.map +1 -0
- package/dist/checks/workflows/commit.d.ts +19 -0
- package/dist/checks/workflows/commit.d.ts.map +1 -0
- package/dist/checks/workflows/commit.js +207 -0
- package/dist/checks/workflows/commit.js.map +1 -0
- package/dist/checks/workflows/critical.d.ts +9 -0
- package/dist/checks/workflows/critical.d.ts.map +1 -0
- package/dist/checks/workflows/critical.js +213 -0
- package/dist/checks/workflows/critical.js.map +1 -0
- package/dist/checks/workflows/database-id-validation.d.ts +9 -0
- package/dist/checks/workflows/database-id-validation.d.ts.map +1 -0
- package/dist/checks/workflows/database-id-validation.js +13 -0
- package/dist/checks/workflows/database-id-validation.js.map +1 -0
- package/dist/checks/workflows/deploy.d.ts +20 -0
- package/dist/checks/workflows/deploy.d.ts.map +1 -0
- package/dist/checks/workflows/deploy.js +107 -0
- package/dist/checks/workflows/deploy.js.map +1 -0
- package/dist/checks/workflows/deployment-readiness.d.ts +12 -0
- package/dist/checks/workflows/deployment-readiness.d.ts.map +1 -0
- package/dist/checks/workflows/deployment-readiness.js +403 -0
- package/dist/checks/workflows/deployment-readiness.js.map +1 -0
- package/dist/checks/workflows/dev.d.ts +19 -0
- package/dist/checks/workflows/dev.d.ts.map +1 -0
- package/dist/checks/workflows/dev.js +88 -0
- package/dist/checks/workflows/dev.js.map +1 -0
- package/dist/checks/workflows/development.d.ts +9 -0
- package/dist/checks/workflows/development.d.ts.map +1 -0
- package/dist/checks/workflows/development.js +65 -0
- package/dist/checks/workflows/development.js.map +1 -0
- package/dist/checks/workflows/enterprise.d.ts +10 -0
- package/dist/checks/workflows/enterprise.d.ts.map +1 -0
- package/dist/checks/workflows/enterprise.js +359 -0
- package/dist/checks/workflows/enterprise.js.map +1 -0
- package/dist/checks/workflows/images.d.ts +6 -0
- package/dist/checks/workflows/images.d.ts.map +1 -0
- package/dist/checks/workflows/images.js +58 -0
- package/dist/checks/workflows/images.js.map +1 -0
- package/dist/checks/workflows/naming.d.ts +19 -0
- package/dist/checks/workflows/naming.d.ts.map +1 -0
- package/dist/checks/workflows/naming.js +42 -0
- package/dist/checks/workflows/naming.js.map +1 -0
- package/dist/checks/workflows/performance.d.ts +8 -0
- package/dist/checks/workflows/performance.d.ts.map +1 -0
- package/dist/checks/workflows/performance.js +77 -0
- package/dist/checks/workflows/performance.js.map +1 -0
- package/dist/checks/workflows/pre-deploy.d.ts +6 -0
- package/dist/checks/workflows/pre-deploy.d.ts.map +1 -0
- package/dist/checks/workflows/pre-deploy.js +41 -0
- package/dist/checks/workflows/pre-deploy.js.map +1 -0
- package/dist/checks/workflows/security.d.ts +8 -0
- package/dist/checks/workflows/security.d.ts.map +1 -0
- package/dist/checks/workflows/security.js +71 -0
- package/dist/checks/workflows/security.js.map +1 -0
- package/dist/checks/workflows/supercatch.d.ts +8 -0
- package/dist/checks/workflows/supercatch.d.ts.map +1 -0
- package/dist/checks/workflows/supercatch.js +127 -0
- package/dist/checks/workflows/supercatch.js.map +1 -0
- package/dist/checks/workflows/ui-quality.d.ts +9 -0
- package/dist/checks/workflows/ui-quality.d.ts.map +1 -0
- package/dist/checks/workflows/ui-quality.js +264 -0
- package/dist/checks/workflows/ui-quality.js.map +1 -0
- package/dist/checks/workflows/ui-uniformity.d.ts +18 -0
- package/dist/checks/workflows/ui-uniformity.d.ts.map +1 -0
- package/dist/checks/workflows/ui-uniformity.js +265 -0
- package/dist/checks/workflows/ui-uniformity.js.map +1 -0
- package/dist/checks/workflows/vercel.d.ts +16 -0
- package/dist/checks/workflows/vercel.d.ts.map +1 -0
- package/dist/checks/workflows/vercel.js +173 -0
- package/dist/checks/workflows/vercel.js.map +1 -0
- package/dist/utils/validation-helpers.d.ts +43 -0
- package/dist/utils/validation-helpers.d.ts.map +1 -0
- package/dist/utils/validation-helpers.js +370 -0
- package/dist/utils/validation-helpers.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1582 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Playwright Feature Coverage Gap Detection Preflight (Proactive)
|
|
5
|
+
*
|
|
6
|
+
* PROACTIVELY discovers missing E2E test coverage by:
|
|
7
|
+
* 1. Scanning source code for features, API endpoints, UI components
|
|
8
|
+
* 2. Analyzing what Playwright tests actually exercise
|
|
9
|
+
* 3. Reporting gaps between implementation and test coverage
|
|
10
|
+
* 4. Tracking coverage trends over time
|
|
11
|
+
* 5. Generating test stubs for uncovered features
|
|
12
|
+
*
|
|
13
|
+
* This is NOT a checklist - it discovers gaps automatically.
|
|
14
|
+
*
|
|
15
|
+
* @blocking true
|
|
16
|
+
* @category testing
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* pnpm preflight:playwright-feature-gaps
|
|
20
|
+
* pnpm preflight:playwright-feature-gaps --verbose
|
|
21
|
+
* pnpm preflight:playwright-feature-gaps --report
|
|
22
|
+
* pnpm preflight:playwright-feature-gaps --generate-stubs
|
|
23
|
+
* pnpm preflight:playwright-feature-gaps --trend
|
|
24
|
+
*/
|
|
25
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
28
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
29
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
30
|
+
}
|
|
31
|
+
Object.defineProperty(o, k2, desc);
|
|
32
|
+
}) : (function(o, m, k, k2) {
|
|
33
|
+
if (k2 === undefined) k2 = k;
|
|
34
|
+
o[k2] = m[k];
|
|
35
|
+
}));
|
|
36
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
37
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
38
|
+
}) : function(o, v) {
|
|
39
|
+
o["default"] = v;
|
|
40
|
+
});
|
|
41
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
42
|
+
var ownKeys = function(o) {
|
|
43
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
44
|
+
var ar = [];
|
|
45
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
46
|
+
return ar;
|
|
47
|
+
};
|
|
48
|
+
return ownKeys(o);
|
|
49
|
+
};
|
|
50
|
+
return function (mod) {
|
|
51
|
+
if (mod && mod.__esModule) return mod;
|
|
52
|
+
var result = {};
|
|
53
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
54
|
+
__setModuleDefault(result, mod);
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
})();
|
|
58
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
59
|
+
exports.tags = exports.description = exports.blocking = exports.category = exports.name = exports.id = void 0;
|
|
60
|
+
exports.run = run;
|
|
61
|
+
const child_process_1 = require("child_process");
|
|
62
|
+
const fs = __importStar(require("fs"));
|
|
63
|
+
const path = __importStar(require("path"));
|
|
64
|
+
const console_chars_1 = require("../../utils/console-chars");
|
|
65
|
+
const universal_progress_reporter_1 = require("../system/universal-progress-reporter");
|
|
66
|
+
// Check metadata
|
|
67
|
+
exports.id = "testing/playwright-feature-coverage-gaps";
|
|
68
|
+
exports.name = "Playwright Feature Coverage Gaps";
|
|
69
|
+
exports.category = "testing";
|
|
70
|
+
exports.blocking = true;
|
|
71
|
+
exports.description = "Playwright Feature Coverage Gap Detection Preflight (Proactive)";
|
|
72
|
+
exports.tags = ["testing"];
|
|
73
|
+
const isVerbose = process.argv.includes("--verbose");
|
|
74
|
+
const generateReport = process.argv.includes("--fix") || process.argv.includes("--report");
|
|
75
|
+
const generateStubs = process.argv.includes("--generate-stubs");
|
|
76
|
+
const showTrend = process.argv.includes("--trend");
|
|
77
|
+
const warnOnly = process.argv.includes("--warn");
|
|
78
|
+
const TREND_FILE = ".playwright-coverage-trend.json";
|
|
79
|
+
// HELPER: Get recently modified files (last 30 days)
|
|
80
|
+
function getRecentlyModifiedFiles() {
|
|
81
|
+
const recentFiles = new Set();
|
|
82
|
+
try {
|
|
83
|
+
const thirtyDaysAgo = new Date();
|
|
84
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
85
|
+
const since = thirtyDaysAgo.toISOString().split("T")[0];
|
|
86
|
+
const output = (0, child_process_1.execSync)(`git log --since="${since}" --name-only --pretty=format: -- "*.ts" "*.tsx"`, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
|
|
87
|
+
output
|
|
88
|
+
.split("\n")
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.forEach((f) => recentFiles.add(f));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Git not available or not a git repo
|
|
94
|
+
}
|
|
95
|
+
return recentFiles;
|
|
96
|
+
}
|
|
97
|
+
// TREND TRACKING
|
|
98
|
+
function loadTrendHistory() {
|
|
99
|
+
try {
|
|
100
|
+
if (fs.existsSync(TREND_FILE)) {
|
|
101
|
+
return JSON.parse(fs.readFileSync(TREND_FILE, "utf-8"));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Ignore parse errors
|
|
106
|
+
}
|
|
107
|
+
return { runs: [] };
|
|
108
|
+
}
|
|
109
|
+
function saveTrendData(totalFeatures, coveredFeatures, byCategory) {
|
|
110
|
+
const history = loadTrendHistory();
|
|
111
|
+
const categoryStats = {};
|
|
112
|
+
for (const [cat, stats] of Object.entries(byCategory)) {
|
|
113
|
+
categoryStats[cat] = {
|
|
114
|
+
...stats,
|
|
115
|
+
percent: stats.total > 0 ? Math.round((stats.covered / stats.total) * 100) : 100,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const newEntry = {
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
totalFeatures,
|
|
121
|
+
coveredFeatures,
|
|
122
|
+
coveragePercent: totalFeatures > 0 ? Math.round((coveredFeatures / totalFeatures) * 100) : 100,
|
|
123
|
+
byCategory: categoryStats,
|
|
124
|
+
};
|
|
125
|
+
history.runs.push(newEntry);
|
|
126
|
+
// Keep last 100 runs
|
|
127
|
+
if (history.runs.length > 100) {
|
|
128
|
+
history.runs = history.runs.slice(-100);
|
|
129
|
+
}
|
|
130
|
+
fs.writeFileSync(TREND_FILE, JSON.stringify(history, null, 2));
|
|
131
|
+
}
|
|
132
|
+
function displayTrend() {
|
|
133
|
+
const history = loadTrendHistory();
|
|
134
|
+
if (history.runs.length === 0) {
|
|
135
|
+
console.log(`\n${console_chars_1.emoji.info} No trend data available yet. Run the preflight a few times to build history.`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
console.log(`\n${console_chars_1.emoji.chart} COVERAGE TREND (last ${Math.min(history.runs.length, 10)} runs)`);
|
|
139
|
+
console.log((0, console_chars_1.createDivider)(60, "light"));
|
|
140
|
+
const recentRuns = history.runs.slice(-10);
|
|
141
|
+
for (const run of recentRuns) {
|
|
142
|
+
const date = new Date(run.timestamp).toLocaleDateString();
|
|
143
|
+
const bar = "█".repeat(Math.floor(run.coveragePercent / 5)) +
|
|
144
|
+
"░".repeat(20 - Math.floor(run.coveragePercent / 5));
|
|
145
|
+
console.log(` ${date} ${bar} ${run.coveragePercent}% (${run.coveredFeatures}/${run.totalFeatures})`);
|
|
146
|
+
}
|
|
147
|
+
// Show trend direction
|
|
148
|
+
if (recentRuns.length >= 2) {
|
|
149
|
+
const latest = recentRuns[recentRuns.length - 1];
|
|
150
|
+
const previous = recentRuns[recentRuns.length - 2];
|
|
151
|
+
const diff = latest.coveragePercent - previous.coveragePercent;
|
|
152
|
+
if (diff > 0) {
|
|
153
|
+
console.log(`\n ${console_chars_1.emoji.success} Coverage improved by ${diff}% since last run`);
|
|
154
|
+
}
|
|
155
|
+
else if (diff < 0) {
|
|
156
|
+
console.log(`\n ${console_chars_1.emoji.warning} Coverage decreased by ${Math.abs(diff)}% since last run`);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.log(`\n ${console_chars_1.emoji.info} Coverage unchanged since last run`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Category breakdown for latest run
|
|
163
|
+
const latest = recentRuns[recentRuns.length - 1];
|
|
164
|
+
if (Object.keys(latest.byCategory).length > 0) {
|
|
165
|
+
console.log(`\n ${console_chars_1.emoji.folder} By Category:`);
|
|
166
|
+
for (const [cat, stats] of Object.entries(latest.byCategory)) {
|
|
167
|
+
const catBar = "█".repeat(Math.floor(stats.percent / 10)) +
|
|
168
|
+
"░".repeat(10 - Math.floor(stats.percent / 10));
|
|
169
|
+
console.log(` ${cat.padEnd(15)} ${catBar} ${stats.percent}%`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// TEST STUB GENERATION
|
|
174
|
+
function generateTestStub(gap) {
|
|
175
|
+
const { feature } = gap;
|
|
176
|
+
switch (feature.type) {
|
|
177
|
+
case "api-endpoint": {
|
|
178
|
+
const [method, endpoint] = feature.name.split(" ");
|
|
179
|
+
return `import { test, expect } from "@playwright/test";
|
|
180
|
+
|
|
181
|
+
test.describe("${endpoint}", () => {
|
|
182
|
+
test("${method} request returns expected response", async ({ request }) => {
|
|
183
|
+
// TODO: Implement test for ${feature.name}
|
|
184
|
+
// Source: ${feature.sourcePath}
|
|
185
|
+
|
|
186
|
+
const response = await request.${method.toLowerCase()}("${endpoint}", {
|
|
187
|
+
// Add request body/params as needed
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(response.ok()).toBeTruthy();
|
|
191
|
+
// Add more assertions based on expected response
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
196
|
+
case "page-action": {
|
|
197
|
+
return `import { test, expect } from "@playwright/test";
|
|
198
|
+
|
|
199
|
+
test.describe("${feature.name} action", () => {
|
|
200
|
+
test("executes successfully", async ({ page }) => {
|
|
201
|
+
// TODO: Implement test for server action: ${feature.name}
|
|
202
|
+
// Source: ${feature.sourcePath}${feature.line ? `:${feature.line}` : ""}
|
|
203
|
+
|
|
204
|
+
// Navigate to page that uses this action
|
|
205
|
+
await page.goto("/path-to-page");
|
|
206
|
+
|
|
207
|
+
// Trigger the action (e.g., form submission, button click)
|
|
208
|
+
// await page.getByRole("button", { name: "Submit" }).click();
|
|
209
|
+
|
|
210
|
+
// Assert expected outcome
|
|
211
|
+
// await expect(page.getByText("Success")).toBeVisible();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
case "page":
|
|
217
|
+
case "ui-component": {
|
|
218
|
+
const routePath = feature.name.replace("Page: ", "");
|
|
219
|
+
return `import { test, expect } from "@playwright/test";
|
|
220
|
+
|
|
221
|
+
test.describe("${routePath}", () => {
|
|
222
|
+
test("page loads successfully", async ({ page }) => {
|
|
223
|
+
// TODO: Implement test for ${feature.name}
|
|
224
|
+
// Source: ${feature.sourcePath}
|
|
225
|
+
|
|
226
|
+
await page.goto("${routePath}");
|
|
227
|
+
|
|
228
|
+
// Assert page loaded
|
|
229
|
+
await expect(page).toHaveURL(/${routePath.replace(/\//g, "\\/")}/);
|
|
230
|
+
|
|
231
|
+
// Add more specific assertions
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("key interactions work", async ({ page }) => {
|
|
235
|
+
await page.goto("${routePath}");
|
|
236
|
+
|
|
237
|
+
// TODO: Add interaction tests
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
`;
|
|
241
|
+
}
|
|
242
|
+
case "middleware": {
|
|
243
|
+
return `import { test, expect } from "@playwright/test";
|
|
244
|
+
|
|
245
|
+
test.describe("Middleware: ${feature.context || feature.name}", () => {
|
|
246
|
+
test("redirects unauthenticated users", async ({ page }) => {
|
|
247
|
+
// TODO: Implement middleware test
|
|
248
|
+
// Source: ${feature.sourcePath}
|
|
249
|
+
|
|
250
|
+
// Test unauthenticated access
|
|
251
|
+
await page.goto("/protected-route");
|
|
252
|
+
await expect(page).toHaveURL(/login/);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("allows authenticated users", async ({ page }) => {
|
|
256
|
+
// TODO: Set up authenticated state
|
|
257
|
+
// await page.goto("/protected-route");
|
|
258
|
+
// await expect(page).toHaveURL("/protected-route");
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
case "error-boundary": {
|
|
264
|
+
const isError = feature.context === "error";
|
|
265
|
+
return `import { test, expect } from "@playwright/test";
|
|
266
|
+
|
|
267
|
+
test.describe("${feature.name}", () => {
|
|
268
|
+
test("${isError ? "error" : "not-found"} boundary renders correctly", async ({ page }) => {
|
|
269
|
+
// TODO: Implement error boundary test
|
|
270
|
+
// Source: ${feature.sourcePath}
|
|
271
|
+
|
|
272
|
+
${isError
|
|
273
|
+
? `// Trigger an error condition
|
|
274
|
+
await page.route("**/api/**", (route) => route.abort());
|
|
275
|
+
await page.goto("/path-that-errors");
|
|
276
|
+
|
|
277
|
+
// Assert error UI is shown
|
|
278
|
+
await expect(page.getByText(/error|something went wrong/i)).toBeVisible();`
|
|
279
|
+
: `// Navigate to non-existent route
|
|
280
|
+
await page.goto("/non-existent-page-12345");
|
|
281
|
+
|
|
282
|
+
// Assert 404 UI is shown
|
|
283
|
+
await expect(page.getByText(/not found|404/i)).toBeVisible();`}
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
case "file-upload": {
|
|
289
|
+
return `import { test, expect } from "@playwright/test";
|
|
290
|
+
import path from "path";
|
|
291
|
+
import { createUniversalProgressReporter } from "../system/universal-progress-reporter";
|
|
292
|
+
|
|
293
|
+
test.describe("File upload: ${path.basename(feature.sourcePath)}", () => {
|
|
294
|
+
test("file upload works correctly", async ({ page }) => {
|
|
295
|
+
// TODO: Implement file upload test
|
|
296
|
+
// Source: ${feature.sourcePath}
|
|
297
|
+
|
|
298
|
+
await page.goto("/path-with-upload");
|
|
299
|
+
|
|
300
|
+
// Upload a file
|
|
301
|
+
const fileInput = page.locator('input[type="file"]');
|
|
302
|
+
await fileInput.setInputFiles(path.join(__dirname, "fixtures/test-file.png"));
|
|
303
|
+
|
|
304
|
+
// Assert upload success
|
|
305
|
+
// await expect(page.getByText("Upload complete")).toBeVisible();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
`;
|
|
309
|
+
}
|
|
310
|
+
case "modal-trigger": {
|
|
311
|
+
return `import { test, expect } from "@playwright/test";
|
|
312
|
+
|
|
313
|
+
test.describe("Modal: ${path.basename(feature.sourcePath)}", () => {
|
|
314
|
+
test("modal opens and closes correctly", async ({ page }) => {
|
|
315
|
+
// TODO: Implement modal test
|
|
316
|
+
// Source: ${feature.sourcePath}
|
|
317
|
+
|
|
318
|
+
await page.goto("/path-with-modal");
|
|
319
|
+
|
|
320
|
+
// Open modal
|
|
321
|
+
await page.getByRole("button", { name: /open/i }).click();
|
|
322
|
+
await expect(page.getByRole("dialog")).toBeVisible();
|
|
323
|
+
|
|
324
|
+
// Close modal
|
|
325
|
+
await page.getByRole("button", { name: /close|cancel/i }).click();
|
|
326
|
+
await expect(page.getByRole("dialog")).not.toBeVisible();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
default:
|
|
332
|
+
return `import { test, expect } from "@playwright/test";
|
|
333
|
+
|
|
334
|
+
test.describe("${feature.name}", () => {
|
|
335
|
+
test("TODO: implement test", async ({ page }) => {
|
|
336
|
+
// TODO: Implement test for ${feature.type}: ${feature.name}
|
|
337
|
+
// Source: ${feature.sourcePath}
|
|
338
|
+
|
|
339
|
+
// Add test implementation
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
`;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function writeTestStubs(gaps) {
|
|
346
|
+
const stubsDir = "tests/e2e/_generated-stubs";
|
|
347
|
+
if (!fs.existsSync(stubsDir)) {
|
|
348
|
+
fs.mkdirSync(stubsDir, { recursive: true });
|
|
349
|
+
}
|
|
350
|
+
let written = 0;
|
|
351
|
+
for (const gap of gaps) {
|
|
352
|
+
if (!gap.suggestedTestFile)
|
|
353
|
+
continue;
|
|
354
|
+
const stubPath = gap.suggestedTestFile.replace("tests/e2e/", `${stubsDir}/`);
|
|
355
|
+
const stubDir = path.dirname(stubPath);
|
|
356
|
+
if (!fs.existsSync(stubDir)) {
|
|
357
|
+
fs.mkdirSync(stubDir, { recursive: true });
|
|
358
|
+
}
|
|
359
|
+
// Don't overwrite existing stubs
|
|
360
|
+
if (fs.existsSync(stubPath))
|
|
361
|
+
continue;
|
|
362
|
+
const stub = generateTestStub(gap);
|
|
363
|
+
fs.writeFileSync(stubPath, stub);
|
|
364
|
+
written++;
|
|
365
|
+
}
|
|
366
|
+
return written;
|
|
367
|
+
}
|
|
368
|
+
// PHASE 1: DISCOVER FEATURES FROM SOURCE CODE
|
|
369
|
+
async function discoverApiEndpoints(recentFiles) {
|
|
370
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
371
|
+
const features = [];
|
|
372
|
+
const apiFiles = await glob("app/api/**/route.ts", { cwd: process.cwd() });
|
|
373
|
+
for (const file of apiFiles) {
|
|
374
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
375
|
+
const routePath = "/" + path.dirname(file).replace(/\\/g, "/").replace("app/", "");
|
|
376
|
+
const hasDynamicSegment = routePath.includes("[");
|
|
377
|
+
const recentlyModified = recentFiles.has(file);
|
|
378
|
+
const methods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
379
|
+
for (const method of methods) {
|
|
380
|
+
const methodRegex = new RegExp(`export\\s+(async\\s+)?function\\s+${method}\\b`, "i");
|
|
381
|
+
if (methodRegex.test(content)) {
|
|
382
|
+
const isCritical = routePath.includes("/store/") ||
|
|
383
|
+
routePath.includes("/admin/") ||
|
|
384
|
+
routePath.includes("/auth/") ||
|
|
385
|
+
routePath.includes("/checkout/") ||
|
|
386
|
+
routePath.includes("/orders/") ||
|
|
387
|
+
routePath.includes("/payments/") ||
|
|
388
|
+
method === "DELETE" ||
|
|
389
|
+
method === "POST";
|
|
390
|
+
features.push({
|
|
391
|
+
name: `${method} ${routePath}`,
|
|
392
|
+
type: "api-endpoint",
|
|
393
|
+
sourcePath: file,
|
|
394
|
+
context: method,
|
|
395
|
+
priority: isCritical ? "critical" : hasDynamicSegment ? "high" : "medium",
|
|
396
|
+
recentlyModified,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return features;
|
|
402
|
+
}
|
|
403
|
+
async function discoverPageActions(recentFiles) {
|
|
404
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
405
|
+
const features = [];
|
|
406
|
+
const actionFiles = await glob("app/**/actions.ts", { cwd: process.cwd() });
|
|
407
|
+
actionFiles.push(...(await glob("app/**/actions/*.ts", { cwd: process.cwd() })));
|
|
408
|
+
actionFiles.push(...(await glob("lib/actions/**/*.ts", { cwd: process.cwd() })));
|
|
409
|
+
for (const file of actionFiles) {
|
|
410
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
411
|
+
const recentlyModified = recentFiles.has(file);
|
|
412
|
+
const actionRegex = /export\s+async\s+function\s+(\w+)/g;
|
|
413
|
+
let match;
|
|
414
|
+
while ((match = actionRegex.exec(content)) !== null) {
|
|
415
|
+
const lineNum = content.substring(0, match.index).split("\n").length;
|
|
416
|
+
const actionName = match[1].toLowerCase();
|
|
417
|
+
const isCritical = actionName.includes("delete") ||
|
|
418
|
+
actionName.includes("create") ||
|
|
419
|
+
actionName.includes("update") ||
|
|
420
|
+
actionName.includes("submit") ||
|
|
421
|
+
actionName.includes("approve") ||
|
|
422
|
+
actionName.includes("reject") ||
|
|
423
|
+
actionName.includes("payment") ||
|
|
424
|
+
actionName.includes("order");
|
|
425
|
+
features.push({
|
|
426
|
+
name: match[1],
|
|
427
|
+
type: "page-action",
|
|
428
|
+
sourcePath: file,
|
|
429
|
+
line: lineNum,
|
|
430
|
+
priority: isCritical ? "critical" : "medium",
|
|
431
|
+
recentlyModified,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return features;
|
|
436
|
+
}
|
|
437
|
+
async function discoverFormFields(recentFiles) {
|
|
438
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
439
|
+
const features = [];
|
|
440
|
+
const componentFiles = await glob("{app,components}/**/*.tsx", { cwd: process.cwd() });
|
|
441
|
+
const formPatterns = [
|
|
442
|
+
{ regex: /<TextField[^>]*name=["']([^"']+)["']/g, type: "text-field" },
|
|
443
|
+
{ regex: /<Select[^>]*name=["']([^"']+)["']/g, type: "select" },
|
|
444
|
+
{ regex: /<input[^>]*name=["']([^"']+)["']/g, type: "input" },
|
|
445
|
+
{ regex: /register\(["']([^"']+)["']\)/g, type: "react-hook-form" },
|
|
446
|
+
{ regex: /<FormField[^>]*name=["']([^"']+)["']/g, type: "form-field" },
|
|
447
|
+
];
|
|
448
|
+
const criticalPaths = [
|
|
449
|
+
"app/store/",
|
|
450
|
+
"app/admin/",
|
|
451
|
+
"app/account/",
|
|
452
|
+
"app/(auth)/",
|
|
453
|
+
"components/forms/",
|
|
454
|
+
"components/seller/",
|
|
455
|
+
];
|
|
456
|
+
for (const file of componentFiles) {
|
|
457
|
+
const normalizedPath = file.replace(/\\/g, "/");
|
|
458
|
+
const isCritical = criticalPaths.some((p) => normalizedPath.includes(p));
|
|
459
|
+
if (!isCritical)
|
|
460
|
+
continue;
|
|
461
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
462
|
+
const recentlyModified = recentFiles.has(file);
|
|
463
|
+
for (const pattern of formPatterns) {
|
|
464
|
+
let match;
|
|
465
|
+
while ((match = pattern.regex.exec(content)) !== null) {
|
|
466
|
+
features.push({
|
|
467
|
+
name: match[1],
|
|
468
|
+
type: "form-field",
|
|
469
|
+
sourcePath: file,
|
|
470
|
+
context: pattern.type,
|
|
471
|
+
recentlyModified,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return features;
|
|
477
|
+
}
|
|
478
|
+
async function discoverCriticalButtons(recentFiles) {
|
|
479
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
480
|
+
const features = [];
|
|
481
|
+
const componentFiles = await glob("{app,components}/**/*.tsx", { cwd: process.cwd() });
|
|
482
|
+
const criticalPaths = ["app/store/", "app/admin/", "app/account/"];
|
|
483
|
+
for (const file of componentFiles) {
|
|
484
|
+
const normalizedPath = file.replace(/\\/g, "/");
|
|
485
|
+
const isCritical = criticalPaths.some((p) => normalizedPath.includes(p));
|
|
486
|
+
if (!isCritical)
|
|
487
|
+
continue;
|
|
488
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
489
|
+
const recentlyModified = recentFiles.has(file);
|
|
490
|
+
const deleteActions = content.match(/onClick=\{[^}]*(?:delete|remove|handleDelete)/gi) || [];
|
|
491
|
+
for (const _action of deleteActions) {
|
|
492
|
+
features.push({
|
|
493
|
+
name: `Delete action in ${path.basename(file)}`,
|
|
494
|
+
type: "ui-component",
|
|
495
|
+
sourcePath: file,
|
|
496
|
+
context: "destructive-action",
|
|
497
|
+
priority: "critical",
|
|
498
|
+
recentlyModified,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const submitButtons = content.match(/<Button[^>]*type=["']submit["'][^>]*>/gi) || [];
|
|
502
|
+
for (const _btn of submitButtons) {
|
|
503
|
+
features.push({
|
|
504
|
+
name: `Form submit in ${path.basename(file)}`,
|
|
505
|
+
type: "ui-component",
|
|
506
|
+
sourcePath: file,
|
|
507
|
+
context: "form-submit",
|
|
508
|
+
priority: "high",
|
|
509
|
+
recentlyModified,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const seen = new Set();
|
|
514
|
+
return features.filter((f) => {
|
|
515
|
+
const key = `${f.sourcePath}:${f.context}`;
|
|
516
|
+
if (seen.has(key))
|
|
517
|
+
return false;
|
|
518
|
+
seen.add(key);
|
|
519
|
+
return true;
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
// EXTENDED DISCOVERY: Middleware, Error Boundaries, Loading States, etc.
|
|
523
|
+
async function discoverMiddleware(recentFiles) {
|
|
524
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
525
|
+
const features = [];
|
|
526
|
+
const middlewareFiles = await glob("middleware.ts", { cwd: process.cwd() });
|
|
527
|
+
middlewareFiles.push(...(await glob("app/**/middleware.ts", { cwd: process.cwd() })));
|
|
528
|
+
for (const file of middlewareFiles) {
|
|
529
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
530
|
+
const recentlyModified = recentFiles.has(file);
|
|
531
|
+
const matcherRegex = /matcher:\s*\[([^\]]+)\]/g;
|
|
532
|
+
let match;
|
|
533
|
+
while ((match = matcherRegex.exec(content)) !== null) {
|
|
534
|
+
const patterns = match[1].split(",").map((p) => p.trim().replace(/["']/g, ""));
|
|
535
|
+
for (const pattern of patterns) {
|
|
536
|
+
features.push({
|
|
537
|
+
name: `Middleware: ${pattern}`,
|
|
538
|
+
type: "middleware",
|
|
539
|
+
sourcePath: file,
|
|
540
|
+
context: pattern,
|
|
541
|
+
priority: "high",
|
|
542
|
+
recentlyModified,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (content.includes("NextResponse.redirect") || content.includes("NextResponse.rewrite")) {
|
|
547
|
+
features.push({
|
|
548
|
+
name: `Middleware redirects in ${path.basename(file)}`,
|
|
549
|
+
type: "middleware",
|
|
550
|
+
sourcePath: file,
|
|
551
|
+
context: "redirect-logic",
|
|
552
|
+
priority: "high",
|
|
553
|
+
recentlyModified,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
if (content.includes("getToken") || content.includes("auth(") || content.includes("session")) {
|
|
557
|
+
features.push({
|
|
558
|
+
name: `Auth middleware in ${path.basename(file)}`,
|
|
559
|
+
type: "middleware",
|
|
560
|
+
sourcePath: file,
|
|
561
|
+
context: "auth-check",
|
|
562
|
+
priority: "critical",
|
|
563
|
+
recentlyModified,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return features;
|
|
568
|
+
}
|
|
569
|
+
async function discoverErrorBoundaries(recentFiles) {
|
|
570
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
571
|
+
const features = [];
|
|
572
|
+
const errorFiles = await glob("app/**/error.tsx", { cwd: process.cwd() });
|
|
573
|
+
const notFoundFiles = await glob("app/**/not-found.tsx", { cwd: process.cwd() });
|
|
574
|
+
for (const file of [...errorFiles, ...notFoundFiles]) {
|
|
575
|
+
const routePath = path.dirname(file).replace(/\\/g, "/").replace("app/", "/");
|
|
576
|
+
const isError = file.includes("error.tsx");
|
|
577
|
+
const recentlyModified = recentFiles.has(file);
|
|
578
|
+
features.push({
|
|
579
|
+
name: `${isError ? "Error" : "NotFound"} boundary: ${routePath}`,
|
|
580
|
+
type: "error-boundary",
|
|
581
|
+
sourcePath: file,
|
|
582
|
+
context: isError ? "error" : "not-found",
|
|
583
|
+
priority: "medium",
|
|
584
|
+
recentlyModified,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
return features;
|
|
588
|
+
}
|
|
589
|
+
async function discoverLoadingStates(recentFiles) {
|
|
590
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
591
|
+
const features = [];
|
|
592
|
+
const loadingFiles = await glob("app/**/loading.tsx", { cwd: process.cwd() });
|
|
593
|
+
for (const file of loadingFiles) {
|
|
594
|
+
const routePath = path.dirname(file).replace(/\\/g, "/").replace("app/", "/");
|
|
595
|
+
const recentlyModified = recentFiles.has(file);
|
|
596
|
+
features.push({
|
|
597
|
+
name: `Loading state: ${routePath}`,
|
|
598
|
+
type: "loading-state",
|
|
599
|
+
sourcePath: file,
|
|
600
|
+
priority: "low",
|
|
601
|
+
recentlyModified,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return features;
|
|
605
|
+
}
|
|
606
|
+
async function discoverDynamicRoutes(recentFiles) {
|
|
607
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
608
|
+
const features = [];
|
|
609
|
+
const pageFiles = await glob("app/**/\\[*\\]/**/page.tsx", { cwd: process.cwd() });
|
|
610
|
+
pageFiles.push(...(await glob("app/**/\\[*\\]/page.tsx", { cwd: process.cwd() })));
|
|
611
|
+
for (const file of pageFiles) {
|
|
612
|
+
const routePath = path.dirname(file).replace(/\\/g, "/").replace("app/", "/");
|
|
613
|
+
const recentlyModified = recentFiles.has(file);
|
|
614
|
+
const segments = routePath.match(/\[([^\]]+)\]/g) || [];
|
|
615
|
+
features.push({
|
|
616
|
+
name: `Dynamic route: ${routePath}`,
|
|
617
|
+
type: "dynamic-route",
|
|
618
|
+
sourcePath: file,
|
|
619
|
+
context: segments.join(", "),
|
|
620
|
+
priority: "high",
|
|
621
|
+
recentlyModified,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
return features;
|
|
625
|
+
}
|
|
626
|
+
async function discoverDataTestIds(recentFiles) {
|
|
627
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
628
|
+
const features = [];
|
|
629
|
+
const componentFiles = await glob("{app,components}/**/*.tsx", { cwd: process.cwd() });
|
|
630
|
+
for (const file of componentFiles) {
|
|
631
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
632
|
+
const recentlyModified = recentFiles.has(file);
|
|
633
|
+
const testIdRegex = /data-testid=["']([^"']+)["']/g;
|
|
634
|
+
let match;
|
|
635
|
+
while ((match = testIdRegex.exec(content)) !== null) {
|
|
636
|
+
features.push({
|
|
637
|
+
name: match[1],
|
|
638
|
+
type: "data-testid",
|
|
639
|
+
sourcePath: file,
|
|
640
|
+
priority: "medium",
|
|
641
|
+
recentlyModified,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return features;
|
|
646
|
+
}
|
|
647
|
+
async function discoverSideEffects(recentFiles) {
|
|
648
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
649
|
+
const features = [];
|
|
650
|
+
const componentFiles = await glob("{app,components}/**/*.tsx", { cwd: process.cwd() });
|
|
651
|
+
const criticalPaths = ["app/store/", "app/admin/", "app/account/", "app/checkout/"];
|
|
652
|
+
for (const file of componentFiles) {
|
|
653
|
+
const normalizedPath = file.replace(/\\/g, "/");
|
|
654
|
+
const isCritical = criticalPaths.some((p) => normalizedPath.includes(p));
|
|
655
|
+
if (!isCritical)
|
|
656
|
+
continue;
|
|
657
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
658
|
+
const recentlyModified = recentFiles.has(file);
|
|
659
|
+
const useEffectWithFetch = /useEffect\s*\(\s*(?:async\s*)?\(\)\s*=>\s*\{[^}]*(?:fetch|axios|api\.|\.get\(|\.post\()/gi;
|
|
660
|
+
if (useEffectWithFetch.test(content)) {
|
|
661
|
+
features.push({
|
|
662
|
+
name: `Side effect with API call in ${path.basename(file)}`,
|
|
663
|
+
type: "side-effect",
|
|
664
|
+
sourcePath: file,
|
|
665
|
+
context: "api-call",
|
|
666
|
+
priority: "high",
|
|
667
|
+
recentlyModified,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const seen = new Set();
|
|
672
|
+
return features.filter((f) => {
|
|
673
|
+
const key = `${f.sourcePath}:${f.type}`;
|
|
674
|
+
if (seen.has(key))
|
|
675
|
+
return false;
|
|
676
|
+
seen.add(key);
|
|
677
|
+
return true;
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
async function discoverModalTriggers(recentFiles) {
|
|
681
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
682
|
+
const features = [];
|
|
683
|
+
const componentFiles = await glob("{app,components}/**/*.tsx", { cwd: process.cwd() });
|
|
684
|
+
for (const file of componentFiles) {
|
|
685
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
686
|
+
const recentlyModified = recentFiles.has(file);
|
|
687
|
+
const modalPatterns = [
|
|
688
|
+
/<Dialog[^>]*open=/gi,
|
|
689
|
+
/<Modal[^>]*open=/gi,
|
|
690
|
+
/setOpen\(true\)/gi,
|
|
691
|
+
/setIsOpen\(true\)/gi,
|
|
692
|
+
/setShowModal\(true\)/gi,
|
|
693
|
+
/openModal\(/gi,
|
|
694
|
+
];
|
|
695
|
+
for (const pattern of modalPatterns) {
|
|
696
|
+
if (pattern.test(content)) {
|
|
697
|
+
features.push({
|
|
698
|
+
name: `Modal/Dialog in ${path.basename(file)}`,
|
|
699
|
+
type: "modal-trigger",
|
|
700
|
+
sourcePath: file,
|
|
701
|
+
priority: "medium",
|
|
702
|
+
recentlyModified,
|
|
703
|
+
});
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return features;
|
|
709
|
+
}
|
|
710
|
+
async function discoverToastTriggers(recentFiles) {
|
|
711
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
712
|
+
const features = [];
|
|
713
|
+
const componentFiles = await glob("{app,components}/**/*.tsx", { cwd: process.cwd() });
|
|
714
|
+
for (const file of componentFiles) {
|
|
715
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
716
|
+
const recentlyModified = recentFiles.has(file);
|
|
717
|
+
const toastPatterns = [
|
|
718
|
+
/toast\(/gi,
|
|
719
|
+
/toast\.success\(/gi,
|
|
720
|
+
/toast\.error\(/gi,
|
|
721
|
+
/showNotification\(/gi,
|
|
722
|
+
/enqueueSnackbar\(/gi,
|
|
723
|
+
/addToast\(/gi,
|
|
724
|
+
];
|
|
725
|
+
for (const pattern of toastPatterns) {
|
|
726
|
+
if (pattern.test(content)) {
|
|
727
|
+
features.push({
|
|
728
|
+
name: `Toast/Notification in ${path.basename(file)}`,
|
|
729
|
+
type: "toast-trigger",
|
|
730
|
+
sourcePath: file,
|
|
731
|
+
priority: "low",
|
|
732
|
+
recentlyModified,
|
|
733
|
+
});
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return features;
|
|
739
|
+
}
|
|
740
|
+
async function discoverFileUploads(recentFiles) {
|
|
741
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
742
|
+
const features = [];
|
|
743
|
+
const componentFiles = await glob("{app,components}/**/*.tsx", { cwd: process.cwd() });
|
|
744
|
+
for (const file of componentFiles) {
|
|
745
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
746
|
+
const recentlyModified = recentFiles.has(file);
|
|
747
|
+
const uploadPatterns = [
|
|
748
|
+
/<input[^>]*type=["']file["']/gi,
|
|
749
|
+
/useDropzone/gi,
|
|
750
|
+
/FileUpload/gi,
|
|
751
|
+
/UploadButton/gi,
|
|
752
|
+
/setInputFiles/gi,
|
|
753
|
+
/FormData.*append.*file/gi,
|
|
754
|
+
];
|
|
755
|
+
for (const pattern of uploadPatterns) {
|
|
756
|
+
if (pattern.test(content)) {
|
|
757
|
+
features.push({
|
|
758
|
+
name: `File upload in ${path.basename(file)}`,
|
|
759
|
+
type: "file-upload",
|
|
760
|
+
sourcePath: file,
|
|
761
|
+
priority: "high",
|
|
762
|
+
recentlyModified,
|
|
763
|
+
});
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return features;
|
|
769
|
+
}
|
|
770
|
+
async function discoverWebSockets(recentFiles) {
|
|
771
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
772
|
+
const features = [];
|
|
773
|
+
const files = await glob("{app,components,lib}/**/*.{ts,tsx}", { cwd: process.cwd() });
|
|
774
|
+
for (const file of files) {
|
|
775
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
776
|
+
const recentlyModified = recentFiles.has(file);
|
|
777
|
+
const wsPatterns = [
|
|
778
|
+
/new WebSocket\(/gi,
|
|
779
|
+
/useWebSocket/gi,
|
|
780
|
+
/socket\.io/gi,
|
|
781
|
+
/pusher/gi,
|
|
782
|
+
/ably/gi,
|
|
783
|
+
];
|
|
784
|
+
for (const pattern of wsPatterns) {
|
|
785
|
+
if (pattern.test(content)) {
|
|
786
|
+
features.push({
|
|
787
|
+
name: `WebSocket in ${path.basename(file)}`,
|
|
788
|
+
type: "websocket",
|
|
789
|
+
sourcePath: file,
|
|
790
|
+
priority: "high",
|
|
791
|
+
recentlyModified,
|
|
792
|
+
});
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return features;
|
|
798
|
+
}
|
|
799
|
+
async function discoverStorageUsage(recentFiles) {
|
|
800
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
801
|
+
const features = [];
|
|
802
|
+
const files = await glob("{app,components,lib}/**/*.{ts,tsx}", { cwd: process.cwd() });
|
|
803
|
+
for (const file of files) {
|
|
804
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
805
|
+
const recentlyModified = recentFiles.has(file);
|
|
806
|
+
if (/localStorage\./gi.test(content)) {
|
|
807
|
+
features.push({
|
|
808
|
+
name: `localStorage in ${path.basename(file)}`,
|
|
809
|
+
type: "storage-usage",
|
|
810
|
+
sourcePath: file,
|
|
811
|
+
context: "localStorage",
|
|
812
|
+
priority: "medium",
|
|
813
|
+
recentlyModified,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
if (/sessionStorage\./gi.test(content)) {
|
|
817
|
+
features.push({
|
|
818
|
+
name: `sessionStorage in ${path.basename(file)}`,
|
|
819
|
+
type: "storage-usage",
|
|
820
|
+
sourcePath: file,
|
|
821
|
+
context: "sessionStorage",
|
|
822
|
+
priority: "medium",
|
|
823
|
+
recentlyModified,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const seen = new Set();
|
|
828
|
+
return features.filter((f) => {
|
|
829
|
+
const key = `${f.sourcePath}:${f.context}`;
|
|
830
|
+
if (seen.has(key))
|
|
831
|
+
return false;
|
|
832
|
+
seen.add(key);
|
|
833
|
+
return true;
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
// PHASE 2: ANALYZE WHAT TESTS ACTUALLY COVER
|
|
837
|
+
async function analyzeTestCoverage() {
|
|
838
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
839
|
+
const coverageMap = new Map();
|
|
840
|
+
const testFiles = await glob("tests/e2e/**/*.spec.ts", { cwd: process.cwd() });
|
|
841
|
+
for (const testFile of testFiles) {
|
|
842
|
+
const content = fs.readFileSync(testFile, "utf-8");
|
|
843
|
+
const coverage = {
|
|
844
|
+
testPath: testFile,
|
|
845
|
+
coveredEndpoints: new Set(),
|
|
846
|
+
coveredActions: new Set(),
|
|
847
|
+
coveredComponents: new Set(),
|
|
848
|
+
mockedApis: new Set(),
|
|
849
|
+
interactions: new Set(),
|
|
850
|
+
testIds: new Set(),
|
|
851
|
+
dynamicParams: new Set(),
|
|
852
|
+
};
|
|
853
|
+
// Extract API endpoints being tested or mocked
|
|
854
|
+
const apiPatterns = [
|
|
855
|
+
/page\.route\(["']\*\*([^"']+)["']/g,
|
|
856
|
+
/request\.(?:get|post|put|patch|delete)\(["']([^"']+)["']/g,
|
|
857
|
+
/fetch\(["']([^"']+)["']/g,
|
|
858
|
+
/goto\([^,]*,\s*["']([^"']+)["']/g,
|
|
859
|
+
/page\.goto\(["']([^"']+)["']/g,
|
|
860
|
+
];
|
|
861
|
+
for (const pattern of apiPatterns) {
|
|
862
|
+
let match;
|
|
863
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
864
|
+
const endpoint = match[1].replace(/\*\*/g, "").replace(/\*/g, "");
|
|
865
|
+
if (endpoint.includes("/api/")) {
|
|
866
|
+
coverage.mockedApis.add(endpoint);
|
|
867
|
+
}
|
|
868
|
+
coverage.coveredEndpoints.add(endpoint);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
// Extract UI interactions
|
|
872
|
+
const interactionPatterns = [
|
|
873
|
+
/\.click\(\)/g,
|
|
874
|
+
/\.fill\(["']([^"']+)["']/g,
|
|
875
|
+
/\.type\(["']([^"']+)["']/g,
|
|
876
|
+
/\.check\(\)/g,
|
|
877
|
+
/\.selectOption\(/g,
|
|
878
|
+
/\.setInputFiles\(/g,
|
|
879
|
+
/getByRole\(["']([^"']+)["']/g,
|
|
880
|
+
/getByLabel\(["']([^"']+)["']/g,
|
|
881
|
+
/getByText\(["']([^"']+)["']/g,
|
|
882
|
+
/getByTestId\(["']([^"']+)["']/g,
|
|
883
|
+
];
|
|
884
|
+
for (const pattern of interactionPatterns) {
|
|
885
|
+
let match;
|
|
886
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
887
|
+
coverage.interactions.add(match[1] || match[0]);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
// Extract data-testid usage
|
|
891
|
+
const testIdPattern = /getByTestId\(["']([^"']+)["']\)/g;
|
|
892
|
+
let testIdMatch;
|
|
893
|
+
while ((testIdMatch = testIdPattern.exec(content)) !== null) {
|
|
894
|
+
coverage.testIds.add(testIdMatch[1]);
|
|
895
|
+
}
|
|
896
|
+
// Extract dynamic route params being tested
|
|
897
|
+
const dynamicParamPatterns = [
|
|
898
|
+
/\/\w+\/([a-f0-9-]{36})/gi, // UUIDs
|
|
899
|
+
/\/\w+\/(\d+)/g, // Numeric IDs
|
|
900
|
+
/params\.\w+/g, // params.id, params.slug
|
|
901
|
+
];
|
|
902
|
+
for (const pattern of dynamicParamPatterns) {
|
|
903
|
+
let match;
|
|
904
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
905
|
+
coverage.dynamicParams.add(match[1] || match[0]);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
// Extract action names being tested
|
|
909
|
+
const actionMentions = content.match(/["'](\w+Action|handle\w+|on\w+|submit\w+)["']/gi) || [];
|
|
910
|
+
for (const action of actionMentions) {
|
|
911
|
+
coverage.coveredActions.add(action.replace(/["']/g, ""));
|
|
912
|
+
}
|
|
913
|
+
coverageMap.set(testFile, coverage);
|
|
914
|
+
}
|
|
915
|
+
return coverageMap;
|
|
916
|
+
}
|
|
917
|
+
function aggregateCoverage(coverageMap) {
|
|
918
|
+
const result = {
|
|
919
|
+
allEndpoints: new Set(),
|
|
920
|
+
allMockedApis: new Set(),
|
|
921
|
+
allInteractions: new Set(),
|
|
922
|
+
allActions: new Set(),
|
|
923
|
+
allTestIds: new Set(),
|
|
924
|
+
allDynamicParams: new Set(),
|
|
925
|
+
testFilesByRoute: new Map(),
|
|
926
|
+
};
|
|
927
|
+
for (const [testPath, coverage] of coverageMap) {
|
|
928
|
+
coverage.coveredEndpoints.forEach((e) => {
|
|
929
|
+
result.allEndpoints.add(e);
|
|
930
|
+
const existing = result.testFilesByRoute.get(e) || [];
|
|
931
|
+
existing.push(testPath);
|
|
932
|
+
result.testFilesByRoute.set(e, existing);
|
|
933
|
+
});
|
|
934
|
+
coverage.mockedApis.forEach((e) => result.allMockedApis.add(e));
|
|
935
|
+
coverage.interactions.forEach((i) => result.allInteractions.add(i.toLowerCase()));
|
|
936
|
+
coverage.coveredActions.forEach((a) => result.allActions.add(a.toLowerCase()));
|
|
937
|
+
coverage.testIds.forEach((t) => result.allTestIds.add(t));
|
|
938
|
+
coverage.dynamicParams.forEach((p) => result.allDynamicParams.add(p));
|
|
939
|
+
}
|
|
940
|
+
return result;
|
|
941
|
+
}
|
|
942
|
+
// STRUCTURAL GAP DETECTION: Match source files to test files
|
|
943
|
+
async function findStructuralGaps(recentFiles) {
|
|
944
|
+
const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
|
|
945
|
+
const gaps = [];
|
|
946
|
+
const pageFiles = await glob("app/**/page.tsx", { cwd: process.cwd() });
|
|
947
|
+
const testFiles = await glob("tests/e2e/**/*.spec.ts", { cwd: process.cwd() });
|
|
948
|
+
// Build a map of test file coverage by route pattern
|
|
949
|
+
const testRoutePatterns = new Set();
|
|
950
|
+
const testFileContents = new Map();
|
|
951
|
+
for (const testFile of testFiles) {
|
|
952
|
+
const routePattern = testFile
|
|
953
|
+
.replace("tests/e2e/", "")
|
|
954
|
+
.replace(".spec.ts", "")
|
|
955
|
+
.replace(/\\/g, "/");
|
|
956
|
+
testRoutePatterns.add(routePattern);
|
|
957
|
+
testRoutePatterns.add(routePattern.replace(/-/g, "/"));
|
|
958
|
+
// Also read test file content to check for route references
|
|
959
|
+
try {
|
|
960
|
+
const content = fs.readFileSync(testFile, "utf-8");
|
|
961
|
+
testFileContents.set(testFile, content);
|
|
962
|
+
}
|
|
963
|
+
catch {
|
|
964
|
+
// Ignore read errors
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
// Build a set of all routes referenced in test files
|
|
968
|
+
const routesInTests = new Set();
|
|
969
|
+
for (const [_testFile, content] of testFileContents) {
|
|
970
|
+
// Extract goto URLs
|
|
971
|
+
const gotoMatches = content.matchAll(/goto\s*\(\s*["'`]([^"'`]+)["'`]/g);
|
|
972
|
+
for (const match of gotoMatches) {
|
|
973
|
+
const route = match[1].replace(/^\//, "").split("?")[0];
|
|
974
|
+
routesInTests.add(route);
|
|
975
|
+
// Also add parent routes
|
|
976
|
+
const parts = route.split("/");
|
|
977
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
978
|
+
routesInTests.add(parts.slice(0, i).join("/"));
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
// Extract route patterns from comments and test descriptions
|
|
982
|
+
const routeComments = content.matchAll(/\/([a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)*)/gi);
|
|
983
|
+
for (const match of routeComments) {
|
|
984
|
+
routesInTests.add(match[1]);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
for (const pageFile of pageFiles) {
|
|
988
|
+
const routePattern = path
|
|
989
|
+
.dirname(pageFile)
|
|
990
|
+
.replace(/\\/g, "/")
|
|
991
|
+
.replace("app/", "")
|
|
992
|
+
.replace(/\(.*?\)\//g, "") // Remove route groups like (auth)/
|
|
993
|
+
.replace(/\[.*?\]/g, "[param]"); // Normalize dynamic segments
|
|
994
|
+
// Check 1: Test file name matches route pattern
|
|
995
|
+
const hasTestFile = [...testRoutePatterns].some((testPattern) => {
|
|
996
|
+
return (testPattern.includes(routePattern) ||
|
|
997
|
+
routePattern.includes(testPattern) ||
|
|
998
|
+
testPattern.replace(/-/g, "").includes(routePattern.replace(/-/g, "")));
|
|
999
|
+
});
|
|
1000
|
+
// Check 2: Route is referenced in any test file content
|
|
1001
|
+
const routeWithoutParams = routePattern.replace(/\[param\]/g, "");
|
|
1002
|
+
const hasRouteReference = [...routesInTests].some((testedRoute) => {
|
|
1003
|
+
const normalizedTested = testedRoute.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1004
|
+
const normalizedRoute = routeWithoutParams.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1005
|
+
return (normalizedTested.includes(normalizedRoute) ||
|
|
1006
|
+
normalizedRoute.includes(normalizedTested) ||
|
|
1007
|
+
testedRoute.includes(routePattern.replace(/\[param\]/g, "")));
|
|
1008
|
+
});
|
|
1009
|
+
if (!hasTestFile && !hasRouteReference && !routePattern.includes("api/")) {
|
|
1010
|
+
const isCritical = routePattern.includes("store/") ||
|
|
1011
|
+
routePattern.includes("admin/") ||
|
|
1012
|
+
routePattern.includes("checkout/") ||
|
|
1013
|
+
routePattern.includes("account/");
|
|
1014
|
+
const recentlyModified = recentFiles.has(pageFile);
|
|
1015
|
+
gaps.push({
|
|
1016
|
+
feature: {
|
|
1017
|
+
name: `Page: /${routePattern}`,
|
|
1018
|
+
type: "page",
|
|
1019
|
+
sourcePath: pageFile,
|
|
1020
|
+
priority: isCritical ? "critical" : "medium",
|
|
1021
|
+
recentlyModified,
|
|
1022
|
+
},
|
|
1023
|
+
severity: isCritical ? "error" : "warning",
|
|
1024
|
+
reason: `No test file found matching route pattern: ${routePattern}${recentlyModified ? " (recently modified)" : ""}`,
|
|
1025
|
+
suggestedTestFile: `tests/e2e/${routePattern.replace(/\[param\]/g, "dynamic")}.spec.ts`,
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return gaps;
|
|
1030
|
+
}
|
|
1031
|
+
// PHASE 3: COMPARE AND FIND GAPS
|
|
1032
|
+
function findApiGaps(discoveredApis, testedEndpoints, mockedApis) {
|
|
1033
|
+
const gaps = [];
|
|
1034
|
+
for (const api of discoveredApis) {
|
|
1035
|
+
const endpoint = api.name.split(" ")[1];
|
|
1036
|
+
const _method = api.name.split(" ")[0];
|
|
1037
|
+
const isTested = [...testedEndpoints, ...mockedApis].some((tested) => tested.includes(endpoint) || endpoint.includes(tested.replace(/\*/g, "")));
|
|
1038
|
+
if (!isTested) {
|
|
1039
|
+
gaps.push({
|
|
1040
|
+
feature: api,
|
|
1041
|
+
severity: api.priority === "critical" ? "error" : "warning",
|
|
1042
|
+
reason: `API endpoint ${api.name} has no E2E test coverage${api.recentlyModified ? " (recently modified)" : ""}`,
|
|
1043
|
+
suggestedTestFile: `tests/e2e/api/${endpoint.replace("/api/", "").replace(/\//g, "-")}.spec.ts`,
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return gaps;
|
|
1048
|
+
}
|
|
1049
|
+
function findActionGaps(discoveredActions, testedActions, testedInteractions) {
|
|
1050
|
+
const gaps = [];
|
|
1051
|
+
for (const action of discoveredActions) {
|
|
1052
|
+
const actionName = action.name.toLowerCase();
|
|
1053
|
+
// Check if action name is directly referenced
|
|
1054
|
+
const isDirectlyTested = [...testedActions].some((tested) => tested.includes(actionName) || actionName.includes(tested));
|
|
1055
|
+
// Check if action functionality is tested through UI interactions
|
|
1056
|
+
// Extract key words from action name (e.g., "updateProfileVisibility" -> ["update", "profile", "visibility"])
|
|
1057
|
+
const actionWords = actionName
|
|
1058
|
+
.replace(/([A-Z])/g, " $1")
|
|
1059
|
+
.toLowerCase()
|
|
1060
|
+
.split(/\s+/)
|
|
1061
|
+
.filter((w) => w.length > 3); // Filter out short words like "get", "set"
|
|
1062
|
+
const isFunctionallyTested = actionWords.some((word) => [...testedInteractions].some((interaction) => interaction.includes(word) || word.includes(interaction)));
|
|
1063
|
+
// Also check if the action's source file path suggests a tested area
|
|
1064
|
+
const sourcePath = action.sourcePath.toLowerCase().replace(/\\/g, "/");
|
|
1065
|
+
const interactionsLower = [...testedInteractions].map((i) => i.toLowerCase());
|
|
1066
|
+
const isInTestedArea = (sourcePath.includes("profile") &&
|
|
1067
|
+
interactionsLower.some((i) => i.includes("profile") || i.includes("setting") || i.includes("visibility"))) ||
|
|
1068
|
+
(sourcePath.includes("cart") &&
|
|
1069
|
+
interactionsLower.some((i) => i.includes("cart") ||
|
|
1070
|
+
i.includes("quantity") ||
|
|
1071
|
+
i.includes("increase") ||
|
|
1072
|
+
i.includes("decrease"))) ||
|
|
1073
|
+
(sourcePath.includes("collection") &&
|
|
1074
|
+
interactionsLower.some((i) => i.includes("collection") || i.includes("quantity"))) ||
|
|
1075
|
+
// Wishlist actions - check for wishlist-related interactions
|
|
1076
|
+
(sourcePath.includes("wishlist") &&
|
|
1077
|
+
interactionsLower.some((i) => i.includes("wishlist") ||
|
|
1078
|
+
i.includes("save") ||
|
|
1079
|
+
i.includes("heart") ||
|
|
1080
|
+
i.includes("favorite") ||
|
|
1081
|
+
i.includes("remove"))) ||
|
|
1082
|
+
(actionName.includes("wishlist") &&
|
|
1083
|
+
interactionsLower.some((i) => i.includes("wishlist") ||
|
|
1084
|
+
i.includes("save") ||
|
|
1085
|
+
i.includes("heart") ||
|
|
1086
|
+
i.includes("favorite"))) ||
|
|
1087
|
+
// Social actions - check for social-related interactions
|
|
1088
|
+
(sourcePath.includes("social") &&
|
|
1089
|
+
interactionsLower.some((i) => i.includes("follow") ||
|
|
1090
|
+
i.includes("like") ||
|
|
1091
|
+
i.includes("feed") ||
|
|
1092
|
+
i.includes("social"))) ||
|
|
1093
|
+
(actionName.includes("follow") &&
|
|
1094
|
+
interactionsLower.some((i) => i.includes("follow") || i.includes("unfollow"))) ||
|
|
1095
|
+
(actionName.includes("like") &&
|
|
1096
|
+
interactionsLower.some((i) => i.includes("like") || i.includes("unlike") || i.includes("heart"))) ||
|
|
1097
|
+
(actionName.includes("feed") &&
|
|
1098
|
+
interactionsLower.some((i) => i.includes("feed") || i.includes("collection"))) ||
|
|
1099
|
+
// Price history actions
|
|
1100
|
+
(actionName.includes("pricehistory") &&
|
|
1101
|
+
interactionsLower.some((i) => i.includes("price") ||
|
|
1102
|
+
i.includes("history") ||
|
|
1103
|
+
i.includes("trend") ||
|
|
1104
|
+
i.includes("chart"))) ||
|
|
1105
|
+
(actionName.includes("price") &&
|
|
1106
|
+
actionName.includes("history") &&
|
|
1107
|
+
interactionsLower.some((i) => i.includes("price") || i.includes("drop"))) ||
|
|
1108
|
+
// Public collections actions
|
|
1109
|
+
(actionName.includes("publiccollection") &&
|
|
1110
|
+
interactionsLower.some((i) => i.includes("collection") || i.includes("public") || i.includes("explore"))) ||
|
|
1111
|
+
(actionName.includes("public") &&
|
|
1112
|
+
actionName.includes("collection") &&
|
|
1113
|
+
interactionsLower.some((i) => i.includes("collection") || i.includes("profile")));
|
|
1114
|
+
if (!isDirectlyTested && !isFunctionallyTested && !isInTestedArea) {
|
|
1115
|
+
gaps.push({
|
|
1116
|
+
feature: action,
|
|
1117
|
+
severity: action.priority === "critical" ? "error" : "warning",
|
|
1118
|
+
reason: `Server action "${action.name}" has no E2E test coverage${action.recentlyModified ? " (recently modified)" : ""}`,
|
|
1119
|
+
suggestedTestFile: `tests/e2e/actions/${action.name
|
|
1120
|
+
.replace(/([A-Z])/g, "-$1")
|
|
1121
|
+
.toLowerCase()
|
|
1122
|
+
.replace(/^-/, "")}.spec.ts`,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return gaps;
|
|
1127
|
+
}
|
|
1128
|
+
function findFormFieldGaps(discoveredFields, testedInteractions) {
|
|
1129
|
+
const gaps = [];
|
|
1130
|
+
const fieldsByFile = new Map();
|
|
1131
|
+
for (const field of discoveredFields) {
|
|
1132
|
+
const existing = fieldsByFile.get(field.sourcePath) || [];
|
|
1133
|
+
existing.push(field);
|
|
1134
|
+
fieldsByFile.set(field.sourcePath, existing);
|
|
1135
|
+
}
|
|
1136
|
+
for (const [file, fields] of fieldsByFile) {
|
|
1137
|
+
const untestedFields = fields.filter((f) => {
|
|
1138
|
+
const fieldName = f.name.toLowerCase();
|
|
1139
|
+
return ![...testedInteractions].some((tested) => tested.includes(fieldName) || fieldName.includes(tested));
|
|
1140
|
+
});
|
|
1141
|
+
if (untestedFields.length > 0 && untestedFields.length >= fields.length * 0.5) {
|
|
1142
|
+
const hasRecentlyModified = untestedFields.some((f) => f.recentlyModified);
|
|
1143
|
+
gaps.push({
|
|
1144
|
+
feature: {
|
|
1145
|
+
name: `${untestedFields.length} form fields`,
|
|
1146
|
+
type: "form-field",
|
|
1147
|
+
sourcePath: file,
|
|
1148
|
+
context: untestedFields
|
|
1149
|
+
.map((f) => f.name)
|
|
1150
|
+
.slice(0, 5)
|
|
1151
|
+
.join(", "),
|
|
1152
|
+
recentlyModified: hasRecentlyModified,
|
|
1153
|
+
},
|
|
1154
|
+
severity: "warning",
|
|
1155
|
+
reason: `Form in ${path.basename(file)} has ${untestedFields.length}/${fields.length} untested fields${hasRecentlyModified ? " (recently modified)" : ""}`,
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
return gaps;
|
|
1160
|
+
}
|
|
1161
|
+
function findDestructiveActionGaps(discoveredButtons, testedInteractions) {
|
|
1162
|
+
const gaps = [];
|
|
1163
|
+
const destructiveActions = discoveredButtons.filter((b) => b.context === "destructive-action");
|
|
1164
|
+
for (const action of destructiveActions) {
|
|
1165
|
+
const _fileName = path.basename(action.sourcePath).toLowerCase();
|
|
1166
|
+
const hasDeleteTest = [...testedInteractions].some((i) => i.includes("delete") || i.includes("remove") || i.includes("confirm"));
|
|
1167
|
+
if (!hasDeleteTest) {
|
|
1168
|
+
gaps.push({
|
|
1169
|
+
feature: action,
|
|
1170
|
+
severity: "error",
|
|
1171
|
+
reason: `Destructive action in ${path.basename(action.sourcePath)} has no E2E test${action.recentlyModified ? " (recently modified)" : ""}`,
|
|
1172
|
+
suggestedTestFile: `tests/e2e/destructive/${path.basename(action.sourcePath).replace(".tsx", "")}.spec.ts`,
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return gaps;
|
|
1177
|
+
}
|
|
1178
|
+
function findMiddlewareGaps(discoveredMiddleware, testedEndpoints) {
|
|
1179
|
+
const gaps = [];
|
|
1180
|
+
for (const mw of discoveredMiddleware) {
|
|
1181
|
+
// Check if middleware routes are tested
|
|
1182
|
+
const hasTest = [...testedEndpoints].some((e) => {
|
|
1183
|
+
if (mw.context && mw.context !== "auth-check" && mw.context !== "redirect-logic") {
|
|
1184
|
+
return e.includes(mw.context.replace(/\*/g, ""));
|
|
1185
|
+
}
|
|
1186
|
+
return false;
|
|
1187
|
+
});
|
|
1188
|
+
if (!hasTest) {
|
|
1189
|
+
if (mw.context === "auth-check") {
|
|
1190
|
+
gaps.push({
|
|
1191
|
+
feature: mw,
|
|
1192
|
+
severity: "error",
|
|
1193
|
+
reason: `Auth middleware needs tests for authenticated/unauthenticated states`,
|
|
1194
|
+
suggestedTestFile: "tests/e2e/auth/middleware-auth.spec.ts",
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
else if (mw.context === "redirect-logic") {
|
|
1198
|
+
gaps.push({
|
|
1199
|
+
feature: mw,
|
|
1200
|
+
severity: "warning",
|
|
1201
|
+
reason: `Middleware redirect logic should be tested`,
|
|
1202
|
+
suggestedTestFile: "tests/e2e/middleware/redirects.spec.ts",
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return gaps;
|
|
1208
|
+
}
|
|
1209
|
+
function findErrorBoundaryGaps(discoveredBoundaries, testedEndpoints, testedInteractions) {
|
|
1210
|
+
const gaps = [];
|
|
1211
|
+
// Check if there's a dedicated error boundary test file
|
|
1212
|
+
const hasErrorBoundaryTests = [...testedEndpoints].some((e) => e.includes("error") || e.includes("boundary") || e.includes("404") || e.includes("not-found"));
|
|
1213
|
+
// Check if error-related interactions are tested
|
|
1214
|
+
const hasErrorInteractions = [...testedInteractions].some((i) => i.includes("error") ||
|
|
1215
|
+
i.includes("retry") ||
|
|
1216
|
+
i.includes("reload") ||
|
|
1217
|
+
i.includes("try again") ||
|
|
1218
|
+
i.includes("failed"));
|
|
1219
|
+
// If we have error boundary tests, consider all boundaries covered
|
|
1220
|
+
if (hasErrorBoundaryTests || hasErrorInteractions) {
|
|
1221
|
+
return gaps;
|
|
1222
|
+
}
|
|
1223
|
+
for (const boundary of discoveredBoundaries) {
|
|
1224
|
+
const routePath = boundary.name
|
|
1225
|
+
.replace("Error boundary: ", "")
|
|
1226
|
+
.replace("NotFound boundary: ", "");
|
|
1227
|
+
const hasErrorTest = [...testedEndpoints].some((e) => e.includes(routePath) &&
|
|
1228
|
+
(e.includes("error") || e.includes("404") || e.includes("not-found")));
|
|
1229
|
+
if (!hasErrorTest) {
|
|
1230
|
+
gaps.push({
|
|
1231
|
+
feature: boundary,
|
|
1232
|
+
severity: "warning",
|
|
1233
|
+
reason: `${boundary.context === "error" ? "Error" : "NotFound"} boundary at ${routePath} has no E2E test`,
|
|
1234
|
+
suggestedTestFile: `tests/e2e/error-states/${routePath.replace(/\//g, "-").replace(/^-/, "")}-${boundary.context}.spec.ts`,
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return gaps;
|
|
1239
|
+
}
|
|
1240
|
+
function findFileUploadGaps(discoveredUploads, testedInteractions) {
|
|
1241
|
+
const gaps = [];
|
|
1242
|
+
const hasUploadTest = [...testedInteractions].some((i) => i.includes("file") || i.includes("upload") || i.includes("setinputfiles"));
|
|
1243
|
+
if (!hasUploadTest && discoveredUploads.length > 0) {
|
|
1244
|
+
for (const upload of discoveredUploads) {
|
|
1245
|
+
gaps.push({
|
|
1246
|
+
feature: upload,
|
|
1247
|
+
severity: "warning",
|
|
1248
|
+
reason: `File upload in ${path.basename(upload.sourcePath)} has no E2E test`,
|
|
1249
|
+
suggestedTestFile: `tests/e2e/uploads/${path.basename(upload.sourcePath).replace(".tsx", "")}.spec.ts`,
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return gaps;
|
|
1254
|
+
}
|
|
1255
|
+
function findModalGaps(discoveredModals, testedInteractions) {
|
|
1256
|
+
const gaps = [];
|
|
1257
|
+
const hasModalTest = [...testedInteractions].some((i) => i.includes("dialog") || i.includes("modal") || i.includes("close") || i.includes("cancel"));
|
|
1258
|
+
// Only flag critical path modals
|
|
1259
|
+
const criticalModals = discoveredModals.filter((m) => {
|
|
1260
|
+
const filePath = m.sourcePath.replace(/\\/g, "/");
|
|
1261
|
+
return (filePath.includes("store/") || filePath.includes("admin/") || filePath.includes("checkout/"));
|
|
1262
|
+
});
|
|
1263
|
+
if (!hasModalTest && criticalModals.length > 0) {
|
|
1264
|
+
// Group by file to reduce noise
|
|
1265
|
+
const byFile = new Map();
|
|
1266
|
+
for (const modal of criticalModals) {
|
|
1267
|
+
if (!byFile.has(modal.sourcePath)) {
|
|
1268
|
+
byFile.set(modal.sourcePath, modal);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
for (const modal of byFile.values()) {
|
|
1272
|
+
gaps.push({
|
|
1273
|
+
feature: modal,
|
|
1274
|
+
severity: "warning",
|
|
1275
|
+
reason: `Modal in critical path ${path.basename(modal.sourcePath)} has no E2E test`,
|
|
1276
|
+
suggestedTestFile: `tests/e2e/modals/${path.basename(modal.sourcePath).replace(".tsx", "")}.spec.ts`,
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return gaps;
|
|
1281
|
+
}
|
|
1282
|
+
function findDataTestIdGaps(discoveredTestIds, usedTestIds) {
|
|
1283
|
+
const gaps = [];
|
|
1284
|
+
// Only check critical path test IDs
|
|
1285
|
+
const criticalTestIds = discoveredTestIds.filter((t) => {
|
|
1286
|
+
const filePath = t.sourcePath.replace(/\\/g, "/");
|
|
1287
|
+
return (filePath.includes("store/") || filePath.includes("admin/") || filePath.includes("checkout/"));
|
|
1288
|
+
});
|
|
1289
|
+
for (const testId of criticalTestIds) {
|
|
1290
|
+
if (!usedTestIds.has(testId.name)) {
|
|
1291
|
+
gaps.push({
|
|
1292
|
+
feature: testId,
|
|
1293
|
+
severity: "warning",
|
|
1294
|
+
reason: `data-testid="${testId.name}" defined but never used in tests`,
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return gaps;
|
|
1299
|
+
}
|
|
1300
|
+
// MAIN EXECUTION
|
|
1301
|
+
async function run() {
|
|
1302
|
+
const reporter = (0, universal_progress_reporter_1.createUniversalProgressReporter)(exports.name);
|
|
1303
|
+
console.log(`\n${console_chars_1.emoji.testTube} PLAYWRIGHT FEATURE COVERAGE GAP DETECTION (Proactive)`);
|
|
1304
|
+
console.log((0, console_chars_1.createDivider)(60, "heavy"));
|
|
1305
|
+
// Show trend if requested
|
|
1306
|
+
if (showTrend) {
|
|
1307
|
+
displayTrend();
|
|
1308
|
+
process.exit(0);
|
|
1309
|
+
}
|
|
1310
|
+
console.log(`${console_chars_1.emoji.info} Scanning source code to discover features...`);
|
|
1311
|
+
// Get recently modified files for priority flagging
|
|
1312
|
+
const recentFiles = getRecentlyModifiedFiles();
|
|
1313
|
+
// Phase 1: Discover ALL features from source (core + extended)
|
|
1314
|
+
const [apiEndpoints, pageActions, formFields, criticalButtons, middleware, errorBoundaries, loadingStates, dynamicRoutes, dataTestIds, sideEffects, modalTriggers, toastTriggers, fileUploads, webSockets, storageUsage,] = await Promise.all([
|
|
1315
|
+
discoverApiEndpoints(recentFiles),
|
|
1316
|
+
discoverPageActions(recentFiles),
|
|
1317
|
+
discoverFormFields(recentFiles),
|
|
1318
|
+
discoverCriticalButtons(recentFiles),
|
|
1319
|
+
discoverMiddleware(recentFiles),
|
|
1320
|
+
discoverErrorBoundaries(recentFiles),
|
|
1321
|
+
discoverLoadingStates(recentFiles),
|
|
1322
|
+
discoverDynamicRoutes(recentFiles),
|
|
1323
|
+
discoverDataTestIds(recentFiles),
|
|
1324
|
+
discoverSideEffects(recentFiles),
|
|
1325
|
+
discoverModalTriggers(recentFiles),
|
|
1326
|
+
discoverToastTriggers(recentFiles),
|
|
1327
|
+
discoverFileUploads(recentFiles),
|
|
1328
|
+
discoverWebSockets(recentFiles),
|
|
1329
|
+
discoverStorageUsage(recentFiles),
|
|
1330
|
+
]);
|
|
1331
|
+
// Structural gaps (page.tsx without matching test files)
|
|
1332
|
+
const structuralGaps = await findStructuralGaps(recentFiles);
|
|
1333
|
+
if (isVerbose) {
|
|
1334
|
+
console.log(`\n${console_chars_1.emoji.search} Discovered:`);
|
|
1335
|
+
console.log(` ${console_chars_1.emoji.globe} ${apiEndpoints.length} API endpoints`);
|
|
1336
|
+
console.log(` ${console_chars_1.emoji.lightning} ${pageActions.length} server actions`);
|
|
1337
|
+
console.log(` ${console_chars_1.emoji.clipboard} ${formFields.length} form fields (critical paths)`);
|
|
1338
|
+
console.log(` ${console_chars_1.emoji.warning} ${criticalButtons.length} destructive actions`);
|
|
1339
|
+
console.log(` ${console_chars_1.emoji.lock} ${middleware.length} middleware rules`);
|
|
1340
|
+
console.log(` ${console_chars_1.emoji.error} ${errorBoundaries.length} error boundaries`);
|
|
1341
|
+
console.log(` ${console_chars_1.emoji.clock} ${loadingStates.length} loading states`);
|
|
1342
|
+
console.log(` ${console_chars_1.emoji.link} ${dynamicRoutes.length} dynamic routes`);
|
|
1343
|
+
console.log(` ${console_chars_1.emoji.target} ${dataTestIds.length} data-testid attributes`);
|
|
1344
|
+
console.log(` ${console_chars_1.emoji.refresh} ${sideEffects.length} side effects with API calls`);
|
|
1345
|
+
console.log(` ${console_chars_1.emoji.components} ${modalTriggers.length} modal/dialog triggers`);
|
|
1346
|
+
console.log(` ${console_chars_1.emoji.bell} ${toastTriggers.length} toast/notification triggers`);
|
|
1347
|
+
console.log(` ${console_chars_1.emoji.file} ${fileUploads.length} file upload components`);
|
|
1348
|
+
console.log(` ${console_chars_1.emoji.link} ${webSockets.length} WebSocket connections`);
|
|
1349
|
+
console.log(` ${console_chars_1.emoji.database} ${storageUsage.length} storage usage (local/session)`);
|
|
1350
|
+
console.log(` ${console_chars_1.emoji.folder} ${structuralGaps.length} pages without test files`);
|
|
1351
|
+
}
|
|
1352
|
+
// Phase 2: Analyze test coverage
|
|
1353
|
+
console.log(`\n${console_chars_1.emoji.search} Analyzing Playwright test coverage...`);
|
|
1354
|
+
const coverageMap = await analyzeTestCoverage();
|
|
1355
|
+
const aggregated = aggregateCoverage(coverageMap);
|
|
1356
|
+
if (isVerbose) {
|
|
1357
|
+
console.log(`\n${console_chars_1.emoji.chart} Test coverage found:`);
|
|
1358
|
+
console.log(` ${console_chars_1.emoji.check} ${aggregated.allEndpoints.size} endpoints referenced`);
|
|
1359
|
+
console.log(` ${console_chars_1.emoji.check} ${aggregated.allMockedApis.size} APIs mocked`);
|
|
1360
|
+
console.log(` ${console_chars_1.emoji.check} ${aggregated.allInteractions.size} unique interactions`);
|
|
1361
|
+
console.log(` ${console_chars_1.emoji.check} ${aggregated.allActions.size} actions referenced`);
|
|
1362
|
+
console.log(` ${console_chars_1.emoji.check} ${aggregated.allTestIds.size} test IDs used`);
|
|
1363
|
+
}
|
|
1364
|
+
// Phase 3: Find ALL gaps
|
|
1365
|
+
console.log(`\n${console_chars_1.emoji.magnifier} Comparing implementation vs tests...`);
|
|
1366
|
+
const apiGaps = findApiGaps(apiEndpoints, aggregated.allEndpoints, aggregated.allMockedApis);
|
|
1367
|
+
const actionGaps = findActionGaps(pageActions, aggregated.allActions, aggregated.allInteractions);
|
|
1368
|
+
const formGaps = findFormFieldGaps(formFields, aggregated.allInteractions);
|
|
1369
|
+
const destructiveGaps = findDestructiveActionGaps(criticalButtons, aggregated.allInteractions);
|
|
1370
|
+
const middlewareGaps = findMiddlewareGaps(middleware, aggregated.allEndpoints);
|
|
1371
|
+
const errorBoundaryGaps = findErrorBoundaryGaps(errorBoundaries, aggregated.allEndpoints, aggregated.allInteractions);
|
|
1372
|
+
const fileUploadGaps = findFileUploadGaps(fileUploads, aggregated.allInteractions);
|
|
1373
|
+
const modalGaps = findModalGaps(modalTriggers, aggregated.allInteractions);
|
|
1374
|
+
const testIdGaps = findDataTestIdGaps(dataTestIds, aggregated.allTestIds);
|
|
1375
|
+
const allGaps = [
|
|
1376
|
+
...apiGaps,
|
|
1377
|
+
...actionGaps,
|
|
1378
|
+
...formGaps,
|
|
1379
|
+
...destructiveGaps,
|
|
1380
|
+
...structuralGaps,
|
|
1381
|
+
...middlewareGaps,
|
|
1382
|
+
...errorBoundaryGaps,
|
|
1383
|
+
...fileUploadGaps,
|
|
1384
|
+
...modalGaps,
|
|
1385
|
+
...testIdGaps,
|
|
1386
|
+
];
|
|
1387
|
+
const errors = allGaps.filter((g) => g.severity === "error");
|
|
1388
|
+
const warnings = allGaps.filter((g) => g.severity === "warning");
|
|
1389
|
+
// Calculate coverage stats for trend tracking
|
|
1390
|
+
const totalFeatures = apiEndpoints.length +
|
|
1391
|
+
pageActions.length +
|
|
1392
|
+
criticalButtons.length +
|
|
1393
|
+
middleware.length +
|
|
1394
|
+
errorBoundaries.length +
|
|
1395
|
+
fileUploads.length;
|
|
1396
|
+
const coveredFeatures = totalFeatures -
|
|
1397
|
+
(apiGaps.length +
|
|
1398
|
+
actionGaps.length +
|
|
1399
|
+
destructiveGaps.length +
|
|
1400
|
+
middlewareGaps.length +
|
|
1401
|
+
errorBoundaryGaps.length +
|
|
1402
|
+
fileUploadGaps.length);
|
|
1403
|
+
const byCategory = {
|
|
1404
|
+
"api-endpoints": { total: apiEndpoints.length, covered: apiEndpoints.length - apiGaps.length },
|
|
1405
|
+
"server-actions": {
|
|
1406
|
+
total: pageActions.length,
|
|
1407
|
+
covered: pageActions.length - actionGaps.length,
|
|
1408
|
+
},
|
|
1409
|
+
"destructive-actions": {
|
|
1410
|
+
total: criticalButtons.length,
|
|
1411
|
+
covered: criticalButtons.length - destructiveGaps.length,
|
|
1412
|
+
},
|
|
1413
|
+
middleware: { total: middleware.length, covered: middleware.length - middlewareGaps.length },
|
|
1414
|
+
"error-boundaries": {
|
|
1415
|
+
total: errorBoundaries.length,
|
|
1416
|
+
covered: errorBoundaries.length - errorBoundaryGaps.length,
|
|
1417
|
+
},
|
|
1418
|
+
"file-uploads": {
|
|
1419
|
+
total: fileUploads.length,
|
|
1420
|
+
covered: fileUploads.length - fileUploadGaps.length,
|
|
1421
|
+
},
|
|
1422
|
+
};
|
|
1423
|
+
// Save trend data
|
|
1424
|
+
saveTrendData(totalFeatures, coveredFeatures, byCategory);
|
|
1425
|
+
// Report results
|
|
1426
|
+
console.log((0, console_chars_1.createDivider)(60, "light"));
|
|
1427
|
+
if (allGaps.length === 0) {
|
|
1428
|
+
console.log(`\n${console_chars_1.emoji.success} All discovered features have E2E test coverage!`);
|
|
1429
|
+
process.exit(0);
|
|
1430
|
+
}
|
|
1431
|
+
// Group gaps by type for cleaner output
|
|
1432
|
+
const gapsByType = new Map();
|
|
1433
|
+
for (const gap of allGaps) {
|
|
1434
|
+
const type = gap.feature.type;
|
|
1435
|
+
const existing = gapsByType.get(type) || [];
|
|
1436
|
+
existing.push(gap);
|
|
1437
|
+
gapsByType.set(type, existing);
|
|
1438
|
+
}
|
|
1439
|
+
const typeLabels = {
|
|
1440
|
+
"api-endpoint": "API Endpoints",
|
|
1441
|
+
"page-action": "Server Actions",
|
|
1442
|
+
"form-field": "Form Fields",
|
|
1443
|
+
"ui-component": "UI Components",
|
|
1444
|
+
"service-method": "Service Methods",
|
|
1445
|
+
middleware: "Middleware",
|
|
1446
|
+
"error-boundary": "Error Boundaries",
|
|
1447
|
+
"loading-state": "Loading States",
|
|
1448
|
+
"dynamic-route": "Dynamic Routes",
|
|
1449
|
+
"data-testid": "Test IDs",
|
|
1450
|
+
"side-effect": "Side Effects",
|
|
1451
|
+
"modal-trigger": "Modals/Dialogs",
|
|
1452
|
+
"toast-trigger": "Toasts/Notifications",
|
|
1453
|
+
"file-upload": "File Uploads",
|
|
1454
|
+
websocket: "WebSockets",
|
|
1455
|
+
"storage-usage": "Storage Usage",
|
|
1456
|
+
page: "Pages",
|
|
1457
|
+
};
|
|
1458
|
+
if (errors.length > 0) {
|
|
1459
|
+
console.log(`\n${console_chars_1.emoji.error} CRITICAL GAPS (${errors.length}):\n`);
|
|
1460
|
+
for (const [type, gaps] of gapsByType) {
|
|
1461
|
+
const typeErrors = gaps.filter((g) => g.severity === "error");
|
|
1462
|
+
if (typeErrors.length === 0)
|
|
1463
|
+
continue;
|
|
1464
|
+
console.log(` ${console_chars_1.emoji.folder} ${typeLabels[type] || type}:`);
|
|
1465
|
+
for (const gap of typeErrors.slice(0, 10)) {
|
|
1466
|
+
console.log(` ${console_chars_1.chars.cross} ${gap.feature.name}`);
|
|
1467
|
+
if (isVerbose) {
|
|
1468
|
+
console.log(` File: ${gap.feature.sourcePath}`);
|
|
1469
|
+
console.log(` ${gap.reason}`);
|
|
1470
|
+
if (gap.suggestedTestFile) {
|
|
1471
|
+
console.log(` Suggested: ${gap.suggestedTestFile}`);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
if (typeErrors.length > 10) {
|
|
1476
|
+
console.log(` ... and ${typeErrors.length - 10} more`);
|
|
1477
|
+
}
|
|
1478
|
+
console.log("");
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
if (warnings.length > 0 && isVerbose) {
|
|
1482
|
+
console.log(`\n${console_chars_1.emoji.warning} WARNINGS (${warnings.length}):\n`);
|
|
1483
|
+
for (const [type, gaps] of gapsByType) {
|
|
1484
|
+
const typeWarnings = gaps.filter((g) => g.severity === "warning");
|
|
1485
|
+
if (typeWarnings.length === 0)
|
|
1486
|
+
continue;
|
|
1487
|
+
console.log(` ${console_chars_1.emoji.folder} ${typeLabels[type] || type}: ${typeWarnings.length} gaps`);
|
|
1488
|
+
for (const gap of typeWarnings.slice(0, 5)) {
|
|
1489
|
+
console.log(` ${console_chars_1.emoji.warning} ${gap.feature.name}`);
|
|
1490
|
+
}
|
|
1491
|
+
if (typeWarnings.length > 5) {
|
|
1492
|
+
console.log(` ... and ${typeWarnings.length - 5} more`);
|
|
1493
|
+
}
|
|
1494
|
+
console.log("");
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
// Generate test stubs if requested
|
|
1498
|
+
if (generateStubs) {
|
|
1499
|
+
const gapsWithSuggestions = allGaps.filter((g) => g.suggestedTestFile);
|
|
1500
|
+
const stubsWritten = writeTestStubs(gapsWithSuggestions);
|
|
1501
|
+
console.log(`\n${console_chars_1.emoji.file} Generated ${stubsWritten} test stub(s) in tests/e2e/_generated-stubs/`);
|
|
1502
|
+
console.log(` Review and move stubs to appropriate locations, then implement tests.`);
|
|
1503
|
+
}
|
|
1504
|
+
// Generate report if requested
|
|
1505
|
+
if (generateReport) {
|
|
1506
|
+
const coveragePercent = totalFeatures > 0 ? Math.round((coveredFeatures / totalFeatures) * 100) : 100;
|
|
1507
|
+
const reportLines = [
|
|
1508
|
+
"# Playwright Coverage Gap Report (Auto-Discovered)",
|
|
1509
|
+
"",
|
|
1510
|
+
`Generated: ${new Date().toISOString()}`,
|
|
1511
|
+
"",
|
|
1512
|
+
"## Summary",
|
|
1513
|
+
"",
|
|
1514
|
+
`- **Total Features Discovered:** ${totalFeatures}`,
|
|
1515
|
+
`- **Features with Coverage:** ${coveredFeatures}`,
|
|
1516
|
+
`- **Coverage Percentage:** ${coveragePercent}%`,
|
|
1517
|
+
"",
|
|
1518
|
+
"### By Category",
|
|
1519
|
+
"",
|
|
1520
|
+
"| Category | Total | Covered | % |",
|
|
1521
|
+
"|----------|-------|---------|---|",
|
|
1522
|
+
];
|
|
1523
|
+
for (const [cat, stats] of Object.entries(byCategory)) {
|
|
1524
|
+
const pct = stats.total > 0 ? Math.round((stats.covered / stats.total) * 100) : 100;
|
|
1525
|
+
reportLines.push(`| ${cat} | ${stats.total} | ${stats.covered} | ${pct}% |`);
|
|
1526
|
+
}
|
|
1527
|
+
reportLines.push("");
|
|
1528
|
+
reportLines.push(`- **Critical Gaps:** ${errors.length}`);
|
|
1529
|
+
reportLines.push(`- **Warnings:** ${warnings.length}`);
|
|
1530
|
+
reportLines.push("");
|
|
1531
|
+
reportLines.push("## Critical Gaps");
|
|
1532
|
+
reportLines.push("");
|
|
1533
|
+
for (const gap of errors) {
|
|
1534
|
+
reportLines.push(`### ${gap.feature.name}`);
|
|
1535
|
+
reportLines.push(`- **Type:** ${gap.feature.type}`);
|
|
1536
|
+
reportLines.push(`- **File:** \`${gap.feature.sourcePath}\``);
|
|
1537
|
+
reportLines.push(`- **Issue:** ${gap.reason}`);
|
|
1538
|
+
if (gap.suggestedTestFile) {
|
|
1539
|
+
reportLines.push(`- **Suggested Test:** \`${gap.suggestedTestFile}\``);
|
|
1540
|
+
}
|
|
1541
|
+
reportLines.push("");
|
|
1542
|
+
}
|
|
1543
|
+
if (warnings.length > 0) {
|
|
1544
|
+
reportLines.push("## Warnings");
|
|
1545
|
+
reportLines.push("");
|
|
1546
|
+
for (const gap of warnings.slice(0, 20)) {
|
|
1547
|
+
reportLines.push(`- **${gap.feature.name}** (${gap.feature.type}): ${gap.reason}`);
|
|
1548
|
+
}
|
|
1549
|
+
if (warnings.length > 20) {
|
|
1550
|
+
reportLines.push(`- ... and ${warnings.length - 20} more warnings`);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
const reportPath = "reports/playwright-coverage-gaps.md";
|
|
1554
|
+
const reportDir = path.dirname(reportPath);
|
|
1555
|
+
if (!fs.existsSync(reportDir)) {
|
|
1556
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
1557
|
+
}
|
|
1558
|
+
fs.writeFileSync(reportPath, reportLines.join("\n"));
|
|
1559
|
+
console.log(`\n${console_chars_1.emoji.file} Report written to: ${reportPath}`);
|
|
1560
|
+
}
|
|
1561
|
+
// Summary
|
|
1562
|
+
console.log((0, console_chars_1.createDivider)(60, "heavy"));
|
|
1563
|
+
const coveragePercent = totalFeatures > 0 ? Math.round((coveredFeatures / totalFeatures) * 100) : 100;
|
|
1564
|
+
console.log(`\n${console_chars_1.emoji.chart} Coverage: ${coveragePercent}% (${coveredFeatures}/${totalFeatures} features)`);
|
|
1565
|
+
if (errors.length > 0) {
|
|
1566
|
+
if (warnOnly) {
|
|
1567
|
+
console.log(`\n${console_chars_1.emoji.warning} ${errors.length} critical gap(s), ${warnings.length} warning(s) (--warn mode, not blocking)`);
|
|
1568
|
+
console.log(`\nRun with --verbose for details, --report for markdown, --generate-stubs for test templates, --trend for history\n`);
|
|
1569
|
+
process.exit(0);
|
|
1570
|
+
}
|
|
1571
|
+
console.log(`\n${console_chars_1.emoji.error} FAILED: ${errors.length} critical gap(s), ${warnings.length} warning(s)`);
|
|
1572
|
+
console.log(`\nRun with --verbose for details, --report for markdown, --generate-stubs for test templates, --trend for history\n`);
|
|
1573
|
+
process.exit(1);
|
|
1574
|
+
}
|
|
1575
|
+
console.log(`\n${console_chars_1.emoji.success} PASSED with ${warnings.length} warning(s)`);
|
|
1576
|
+
process.exit(0);
|
|
1577
|
+
}
|
|
1578
|
+
// Allow direct execution
|
|
1579
|
+
if (require.main === module) {
|
|
1580
|
+
run().catch(console.error);
|
|
1581
|
+
}
|
|
1582
|
+
//# sourceMappingURL=playwright-feature-coverage-gaps.js.map
|