@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,3056 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hardened Preflight Checks
|
|
4
|
+
*
|
|
5
|
+
* CRITICAL checks that block deployment to prevent production issues.
|
|
6
|
+
* These are the "iron-clad" rules that cannot be violated.
|
|
7
|
+
*
|
|
8
|
+
* Checks included:
|
|
9
|
+
* 1. DRAFT Status Enforcement - No direct ACTIVE listing creation
|
|
10
|
+
* 2. Async/Await Error Handling - All async code must have error handling
|
|
11
|
+
* 3. Console Cleanup - No console.log in production code
|
|
12
|
+
* 4. Secret Detection - No hardcoded API keys or secrets
|
|
13
|
+
* 5. React Hook Rules - Hooks must follow rules of hooks
|
|
14
|
+
* 6. Promise Handling - No floating promises
|
|
15
|
+
* 7. CVA Enforcement - Components with variants MUST use CVA
|
|
16
|
+
* 8. Type Safety - No unsafe type assertions in critical paths
|
|
17
|
+
* 9. ESLint Concurrency - ESLint commands must use multithreading
|
|
18
|
+
* 10. CardGraded Semantics - Prevent WATA in catalogNonSportsCards
|
|
19
|
+
* 11. Card Grade Field Consistency - prevent mixed grade fields
|
|
20
|
+
* 12. Serial Number Consistency - serialNumbered requires serialNumber
|
|
21
|
+
* 13. Autograph Consistency - auth flags require autographed
|
|
22
|
+
* 14. Video Game Grading Consistency - graded/grader/wata fields
|
|
23
|
+
* 15. Syndication Consistency - syndicated flags require platform IDs
|
|
24
|
+
* 16. Featured Consistency - featuredUntil requires featured
|
|
25
|
+
* 17. Flagging Consistency - flagged fields require isFlagged
|
|
26
|
+
* 18. Soft Delete Consistency - deletedBy requires deletedAt
|
|
27
|
+
* 19. Seller Approval Consistency - sellerApprovedAt requires sellerApproved
|
|
28
|
+
* 20. Recognition Metadata Consistency - recognitionConfidence/jobId require method
|
|
29
|
+
* 21. Autograph Grade Field Consistency - prevent mixed autograph grade systems
|
|
30
|
+
* 22. Autograph Grading Metadata Consistency - autographGraded required for autograph grades
|
|
31
|
+
*
|
|
32
|
+
* Usage:
|
|
33
|
+
* pnpm preflight:hardened # All hardened checks
|
|
34
|
+
* pnpm preflight:hardened draft # DRAFT status only
|
|
35
|
+
* pnpm preflight:hardened async # Async error handling only
|
|
36
|
+
* pnpm preflight:hardened console # Console cleanup only
|
|
37
|
+
* pnpm preflight:hardened secrets # Secret detection only
|
|
38
|
+
* pnpm preflight:hardened hooks # React hook rules only
|
|
39
|
+
* pnpm preflight:hardened promises # Promise handling only
|
|
40
|
+
* pnpm preflight:hardened cva # CVA enforcement only
|
|
41
|
+
* pnpm preflight:hardened types # Type safety only
|
|
42
|
+
* pnpm preflight:hardened eslint-concurrency # ESLint concurrency enforcement only
|
|
43
|
+
*
|
|
44
|
+
* All checks are BLOCKING by default.
|
|
45
|
+
*/
|
|
46
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
47
|
+
if (k2 === undefined) k2 = k;
|
|
48
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
49
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
50
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
51
|
+
}
|
|
52
|
+
Object.defineProperty(o, k2, desc);
|
|
53
|
+
}) : (function(o, m, k, k2) {
|
|
54
|
+
if (k2 === undefined) k2 = k;
|
|
55
|
+
o[k2] = m[k];
|
|
56
|
+
}));
|
|
57
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
58
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
59
|
+
}) : function(o, v) {
|
|
60
|
+
o["default"] = v;
|
|
61
|
+
});
|
|
62
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
63
|
+
var ownKeys = function(o) {
|
|
64
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
65
|
+
var ar = [];
|
|
66
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
67
|
+
return ar;
|
|
68
|
+
};
|
|
69
|
+
return ownKeys(o);
|
|
70
|
+
};
|
|
71
|
+
return function (mod) {
|
|
72
|
+
if (mod && mod.__esModule) return mod;
|
|
73
|
+
var result = {};
|
|
74
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
75
|
+
__setModuleDefault(result, mod);
|
|
76
|
+
return result;
|
|
77
|
+
};
|
|
78
|
+
})();
|
|
79
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
80
|
+
exports.HardenedPreflightModule = void 0;
|
|
81
|
+
const fs = __importStar(require("fs"));
|
|
82
|
+
const path = __importStar(require("path"));
|
|
83
|
+
const console_chars_1 = require("../../utils/console-chars");
|
|
84
|
+
const file_cache_1 = require("../../shared/file-cache");
|
|
85
|
+
const glob_patterns_1 = require("../../shared/glob-patterns");
|
|
86
|
+
const universal_progress_reporter_1 = require("../system/universal-progress-reporter");
|
|
87
|
+
const concurrency_config_1 = require("../../shared/concurrency-config");
|
|
88
|
+
// Use shared excludes + hardened-specific exclusions
|
|
89
|
+
const EXCLUDED = (0, glob_patterns_1.extendExcludes)(glob_patterns_1.STANDARD_EXCLUDES, [
|
|
90
|
+
"**/test-results/**",
|
|
91
|
+
"**/*.test.*",
|
|
92
|
+
"**/*.spec.*",
|
|
93
|
+
"**/*.stories.*",
|
|
94
|
+
"**/lib/examples/**",
|
|
95
|
+
"**/scripts/active/preflights/**",
|
|
96
|
+
"**/scripts/archived/**",
|
|
97
|
+
"**/__mocks__/**",
|
|
98
|
+
]);
|
|
99
|
+
// CACHED FILE LISTS - Scan once, use everywhere
|
|
100
|
+
let cachedAppLibFiles = null;
|
|
101
|
+
let cachedAppLibScriptsFiles = null;
|
|
102
|
+
let cachedApiFiles = null;
|
|
103
|
+
let cachedComponentFiles = null;
|
|
104
|
+
async function getAppLibFiles() {
|
|
105
|
+
if (!cachedAppLibFiles) {
|
|
106
|
+
cachedAppLibFiles = await file_cache_1.fileCache.getFiles("{app,lib}/**/*.{ts,tsx}", { ignore: EXCLUDED });
|
|
107
|
+
}
|
|
108
|
+
return cachedAppLibFiles;
|
|
109
|
+
}
|
|
110
|
+
async function getAppLibScriptsFiles() {
|
|
111
|
+
if (!cachedAppLibScriptsFiles) {
|
|
112
|
+
cachedAppLibScriptsFiles = await file_cache_1.fileCache.getFiles("{app,lib,scripts}/**/*.{ts,tsx,js,jsx}", {
|
|
113
|
+
ignore: EXCLUDED,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return cachedAppLibScriptsFiles;
|
|
117
|
+
}
|
|
118
|
+
async function getApiFiles() {
|
|
119
|
+
if (!cachedApiFiles) {
|
|
120
|
+
cachedApiFiles = await file_cache_1.fileCache.getFiles("app/api/**/*.{ts,tsx}", { ignore: EXCLUDED });
|
|
121
|
+
}
|
|
122
|
+
return cachedApiFiles;
|
|
123
|
+
}
|
|
124
|
+
async function getComponentHookFiles() {
|
|
125
|
+
if (!cachedComponentFiles) {
|
|
126
|
+
cachedComponentFiles = await file_cache_1.fileCache.getFiles("{app,components,hooks}/**/*.{tsx,jsx}", {
|
|
127
|
+
ignore: EXCLUDED,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return cachedComponentFiles;
|
|
131
|
+
}
|
|
132
|
+
// Get concurrency from shared config (respects PREFLIGHT_CONCURRENCY env var)
|
|
133
|
+
const concurrencyConfig = (0, concurrency_config_1.getConcurrencyConfig)();
|
|
134
|
+
class HardenedPreflightModule {
|
|
135
|
+
verbose;
|
|
136
|
+
parallel = false;
|
|
137
|
+
constructor(options = {}) {
|
|
138
|
+
this.verbose = options.verbose || false;
|
|
139
|
+
this.parallel = options.parallel || concurrencyConfig.parallel;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check 1: DRAFT Status Enforcement (BLOCKING)
|
|
143
|
+
*
|
|
144
|
+
* CRITICAL: Listings MUST be created in DRAFT or PENDING status.
|
|
145
|
+
* No direct ACTIVE creation allowed - saves $19.2K/year from fraud prevention.
|
|
146
|
+
*/
|
|
147
|
+
async checkDraftStatusEnforcement() {
|
|
148
|
+
const startTime = Date.now();
|
|
149
|
+
const issues = [];
|
|
150
|
+
const files = await getAppLibFiles();
|
|
151
|
+
// Files that are ALLOWED to set ACTIVE status (admin approval flows)
|
|
152
|
+
const allowedActivePatterns = [
|
|
153
|
+
"admin/listings/approve",
|
|
154
|
+
"admin/listings/[id]",
|
|
155
|
+
"admin/listing-approval",
|
|
156
|
+
"admin/listings/republish",
|
|
157
|
+
"admin/listings/bulk-approve",
|
|
158
|
+
"lib/actions/admin",
|
|
159
|
+
];
|
|
160
|
+
for (const file of files) {
|
|
161
|
+
const content = fs.readFileSync(file, "utf8");
|
|
162
|
+
const lines = content.split("\n");
|
|
163
|
+
// Normalize path for cross-platform matching
|
|
164
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
165
|
+
const isAllowedFile = allowedActivePatterns.some((allowed) => normalizedFile.includes(allowed));
|
|
166
|
+
lines.forEach((line, index) => {
|
|
167
|
+
// Skip comments
|
|
168
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*"))
|
|
169
|
+
return;
|
|
170
|
+
// Pattern 1: Direct ACTIVE creation in data objects
|
|
171
|
+
// Match: listingStatus: "ACTIVE" or listingStatus: 'ACTIVE'
|
|
172
|
+
// But exclude: where: { listingStatus: "ACTIVE" } (queries are OK)
|
|
173
|
+
if (/listingStatus:\s*["']ACTIVE["']/.test(line)) {
|
|
174
|
+
// Check if this is within a 'data:' block (creation) vs 'where:' (query)
|
|
175
|
+
const contextStart = Math.max(0, index - 10);
|
|
176
|
+
const context = lines.slice(contextStart, index + 1).join("\n");
|
|
177
|
+
// If there's a 'data:' or 'create(' or 'createMany(' before this, it's a creation
|
|
178
|
+
const isCreation = /(?:data\s*:|create\s*\(|createMany\s*\(|upsert\s*\()[^}]*listingStatus:\s*["']ACTIVE["']/s.test(context);
|
|
179
|
+
// Query patterns are OK
|
|
180
|
+
const isQuery = /(?:where\s*:|findFirst|findMany|findUnique)[^}]*listingStatus:\s*["']ACTIVE["']/s.test(context);
|
|
181
|
+
if (isCreation && !isQuery && !isAllowedFile) {
|
|
182
|
+
issues.push({
|
|
183
|
+
file,
|
|
184
|
+
line: index + 1,
|
|
185
|
+
type: "direct-active-creation",
|
|
186
|
+
severity: "error",
|
|
187
|
+
message: "CRITICAL: Direct ACTIVE listing creation detected",
|
|
188
|
+
suggestion: 'Create with listingStatus: "DRAFT" or "PENDING". Admin approval required for ACTIVE.',
|
|
189
|
+
snippet: line.trim().substring(0, 100),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Pattern 2: Status update to ACTIVE outside admin context
|
|
194
|
+
if (/update\s*\([^)]*listingStatus:\s*["']ACTIVE["']/.test(line) && !isAllowedFile) {
|
|
195
|
+
issues.push({
|
|
196
|
+
file,
|
|
197
|
+
line: index + 1,
|
|
198
|
+
type: "unauthorized-active-update",
|
|
199
|
+
severity: "error",
|
|
200
|
+
message: "Unauthorized ACTIVE status update detected",
|
|
201
|
+
suggestion: "Only admin approval routes can set ACTIVE status",
|
|
202
|
+
snippet: line.trim().substring(0, 100),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
208
|
+
return {
|
|
209
|
+
name: "DRAFT Status Enforcement",
|
|
210
|
+
passed: !hasErrors,
|
|
211
|
+
blocking: true,
|
|
212
|
+
issues,
|
|
213
|
+
duration: Date.now() - startTime,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Check 2: Async/Await Error Handling (BLOCKING)
|
|
218
|
+
*
|
|
219
|
+
* Unhandled promise rejections crash the server.
|
|
220
|
+
* All async functions in API routes must have try/catch.
|
|
221
|
+
*/
|
|
222
|
+
async checkAsyncErrorHandling() {
|
|
223
|
+
const startTime = Date.now();
|
|
224
|
+
const issues = [];
|
|
225
|
+
const files = await getApiFiles();
|
|
226
|
+
// Exclude simple test/health routes that don't need error handling
|
|
227
|
+
const simpleRoutePatterns = [
|
|
228
|
+
"test-vercel",
|
|
229
|
+
"test-route",
|
|
230
|
+
"auth\\test", // Windows path
|
|
231
|
+
"auth/test", // Unix path
|
|
232
|
+
"clear-cookies",
|
|
233
|
+
"health",
|
|
234
|
+
"ping",
|
|
235
|
+
"status",
|
|
236
|
+
];
|
|
237
|
+
for (const file of files) {
|
|
238
|
+
// Skip simple test routes
|
|
239
|
+
const isSimpleRoute = simpleRoutePatterns.some((pattern) => file.includes(pattern));
|
|
240
|
+
if (isSimpleRoute)
|
|
241
|
+
continue;
|
|
242
|
+
const content = fs.readFileSync(file, "utf8");
|
|
243
|
+
const lines = content.split("\n");
|
|
244
|
+
// Skip if this is a simple wrapper/delegator route (just calls another function)
|
|
245
|
+
const isDelegator = /return\s+\w+\s*\(request/.test(content) && lines.length < 30;
|
|
246
|
+
if (isDelegator)
|
|
247
|
+
continue;
|
|
248
|
+
// Check if file has async functions
|
|
249
|
+
const hasAsyncExport = /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)/i.test(content);
|
|
250
|
+
if (hasAsyncExport) {
|
|
251
|
+
// Check if there's a try/catch block
|
|
252
|
+
const hasTryCatch = /try\s*\{/.test(content);
|
|
253
|
+
// Check if using a wrapper that handles errors
|
|
254
|
+
const hasErrorWrapper = /withErrorHandler|handleApiError|apiHandler|catchAsync/.test(content);
|
|
255
|
+
if (!hasTryCatch && !hasErrorWrapper) {
|
|
256
|
+
// Find the line with the async export
|
|
257
|
+
lines.forEach((line, index) => {
|
|
258
|
+
if (/export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)/i.test(line)) {
|
|
259
|
+
issues.push({
|
|
260
|
+
file,
|
|
261
|
+
line: index + 1,
|
|
262
|
+
type: "missing-try-catch",
|
|
263
|
+
severity: "error",
|
|
264
|
+
message: "API route handler missing try/catch error handling",
|
|
265
|
+
suggestion: "Wrap handler body in try/catch or use withErrorHandler wrapper",
|
|
266
|
+
snippet: line.trim().substring(0, 100),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
274
|
+
return {
|
|
275
|
+
name: "Async Error Handling",
|
|
276
|
+
passed: !hasErrors,
|
|
277
|
+
blocking: true,
|
|
278
|
+
issues,
|
|
279
|
+
duration: Date.now() - startTime,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check 3: Console Cleanup (WARNING)
|
|
284
|
+
*
|
|
285
|
+
* console.log statements should not be in production code.
|
|
286
|
+
* Use proper logging with log levels instead.
|
|
287
|
+
*/
|
|
288
|
+
async checkConsoleCleanup() {
|
|
289
|
+
const startTime = Date.now();
|
|
290
|
+
const issues = [];
|
|
291
|
+
const files = await file_cache_1.fileCache.getFiles("{app,components,lib,hooks}/**/*.{ts,tsx}", {
|
|
292
|
+
ignore: EXCLUDED,
|
|
293
|
+
});
|
|
294
|
+
// Allowed console usage patterns
|
|
295
|
+
const allowedPatterns = [
|
|
296
|
+
/console\.error/, // Errors are OK
|
|
297
|
+
/console\.warn/, // Warnings are OK (for deprecation notices)
|
|
298
|
+
/\/\/.*console/, // Commented out
|
|
299
|
+
/['"]console/, // String containing 'console'
|
|
300
|
+
];
|
|
301
|
+
for (const file of files) {
|
|
302
|
+
const content = fs.readFileSync(file, "utf8");
|
|
303
|
+
const lines = content.split("\n");
|
|
304
|
+
lines.forEach((line, index) => {
|
|
305
|
+
// Skip if line is a comment
|
|
306
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*"))
|
|
307
|
+
return;
|
|
308
|
+
// Check for console.log, console.debug, console.info, console.table
|
|
309
|
+
if (/console\.(?:log|debug|info|table|dir|trace)\s*\(/.test(line)) {
|
|
310
|
+
// Check if it's an allowed pattern
|
|
311
|
+
const isAllowed = allowedPatterns.some((pattern) => pattern.test(line));
|
|
312
|
+
if (!isAllowed) {
|
|
313
|
+
issues.push({
|
|
314
|
+
file,
|
|
315
|
+
line: index + 1,
|
|
316
|
+
type: "console-log",
|
|
317
|
+
severity: "warning",
|
|
318
|
+
message: "console.log detected in production code",
|
|
319
|
+
suggestion: "Remove or use a proper logging utility with log levels",
|
|
320
|
+
snippet: line.trim().substring(0, 80),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
// Warning only - doesn't block deployment
|
|
327
|
+
return {
|
|
328
|
+
name: "Console Cleanup",
|
|
329
|
+
passed: true,
|
|
330
|
+
blocking: false,
|
|
331
|
+
issues,
|
|
332
|
+
duration: Date.now() - startTime,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Check 4: Secret Detection (BLOCKING)
|
|
337
|
+
*
|
|
338
|
+
* Hardcoded secrets in code are a security vulnerability.
|
|
339
|
+
*/
|
|
340
|
+
async checkSecretDetection() {
|
|
341
|
+
const startTime = Date.now();
|
|
342
|
+
const issues = [];
|
|
343
|
+
const files = await file_cache_1.fileCache.getFiles("{app,components,lib,hooks}/**/*.{ts,tsx,js,jsx}", {
|
|
344
|
+
ignore: EXCLUDED,
|
|
345
|
+
});
|
|
346
|
+
// Patterns that indicate hardcoded secrets
|
|
347
|
+
const secretPatterns = [
|
|
348
|
+
{
|
|
349
|
+
pattern: /["']sk-[a-zA-Z0-9]{20,}["']/,
|
|
350
|
+
type: "openai-key",
|
|
351
|
+
message: "Hardcoded OpenAI API key",
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
pattern: /["']pk_(?:live|test)_[a-zA-Z0-9]{20,}["']/,
|
|
355
|
+
type: "stripe-key",
|
|
356
|
+
message: "Hardcoded Stripe API key",
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
pattern: /["']ghp_[a-zA-Z0-9]{36}["']/,
|
|
360
|
+
type: "github-token",
|
|
361
|
+
message: "Hardcoded GitHub token",
|
|
362
|
+
},
|
|
363
|
+
{ pattern: /["']AKIA[A-Z0-9]{16}["']/, type: "aws-key", message: "Hardcoded AWS access key" },
|
|
364
|
+
{
|
|
365
|
+
pattern: /["'](?:password|passwd|pwd)\s*[:=]\s*["'][^"']{8,}["']/,
|
|
366
|
+
type: "password",
|
|
367
|
+
message: "Hardcoded password",
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
pattern: /["'](?:secret|api_?key)\s*[:=]\s*["'][^"']{10,}["']/i,
|
|
371
|
+
type: "generic-secret",
|
|
372
|
+
message: "Potential hardcoded secret",
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
pattern: /Authorization.*Bearer\s+[a-zA-Z0-9._-]{20,}/,
|
|
376
|
+
type: "bearer-token",
|
|
377
|
+
message: "Hardcoded bearer token",
|
|
378
|
+
},
|
|
379
|
+
];
|
|
380
|
+
// False positive patterns (environment variable references, etc.)
|
|
381
|
+
const falsePositivePatterns = [
|
|
382
|
+
/process\.env\./,
|
|
383
|
+
/env\(\)/,
|
|
384
|
+
/getEnv/,
|
|
385
|
+
/["']sk-\[/, // Placeholder
|
|
386
|
+
/["']sk-xxx/i, // Placeholder
|
|
387
|
+
/["']your[-_]?api[-_]?key/i,
|
|
388
|
+
];
|
|
389
|
+
for (const file of files) {
|
|
390
|
+
const content = fs.readFileSync(file, "utf8");
|
|
391
|
+
const lines = content.split("\n");
|
|
392
|
+
lines.forEach((line, index) => {
|
|
393
|
+
// Skip comments
|
|
394
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*"))
|
|
395
|
+
return;
|
|
396
|
+
// Skip if it's a false positive pattern
|
|
397
|
+
if (falsePositivePatterns.some((fp) => fp.test(line)))
|
|
398
|
+
return;
|
|
399
|
+
for (const { pattern, type, message } of secretPatterns) {
|
|
400
|
+
if (pattern.test(line)) {
|
|
401
|
+
issues.push({
|
|
402
|
+
file,
|
|
403
|
+
line: index + 1,
|
|
404
|
+
type,
|
|
405
|
+
severity: "error",
|
|
406
|
+
message,
|
|
407
|
+
suggestion: "Move to environment variables and use process.env",
|
|
408
|
+
snippet: line.trim().substring(0, 60) + "...[REDACTED]",
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
415
|
+
return {
|
|
416
|
+
name: "Secret Detection",
|
|
417
|
+
passed: !hasErrors,
|
|
418
|
+
blocking: true,
|
|
419
|
+
issues,
|
|
420
|
+
duration: Date.now() - startTime,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Check 5: React Hook Rules (BLOCKING)
|
|
425
|
+
*
|
|
426
|
+
* Hooks must follow the rules of hooks:
|
|
427
|
+
* - Only call hooks at the top level
|
|
428
|
+
* - Only call hooks from React functions
|
|
429
|
+
*
|
|
430
|
+
* This is a simplified heuristic check - use ESLint react-hooks plugin for comprehensive coverage.
|
|
431
|
+
* Only flags obvious violations: hooks directly inside if/else/switch/try/catch blocks.
|
|
432
|
+
*/
|
|
433
|
+
async checkReactHookRules() {
|
|
434
|
+
const startTime = Date.now();
|
|
435
|
+
const issues = [];
|
|
436
|
+
const files = await getComponentHookFiles();
|
|
437
|
+
// Known hook patterns
|
|
438
|
+
const hookPattern = /\b(use[A-Z]\w+|useState|useEffect|useCallback|useMemo|useRef|useContext|useReducer|useLayoutEffect|useImperativeHandle|useDebugValue)\s*\(/;
|
|
439
|
+
for (const file of files) {
|
|
440
|
+
const content = fs.readFileSync(file, "utf8");
|
|
441
|
+
const lines = content.split("\n");
|
|
442
|
+
lines.forEach((line, index) => {
|
|
443
|
+
// Skip comments
|
|
444
|
+
const trimmed = line.trim();
|
|
445
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*"))
|
|
446
|
+
return;
|
|
447
|
+
// Look for hooks that appear immediately after conditional keywords
|
|
448
|
+
// Pattern: if (...) { useState(...) } or if (...) useState(...)
|
|
449
|
+
// Check for hook on a line that starts with conditional pattern
|
|
450
|
+
const conditionalHookPattern = /^\s*(if|else|switch|case|default|try|catch|finally)\s*[\({].*use[A-Z]\w+\s*\(/;
|
|
451
|
+
if (conditionalHookPattern.test(line)) {
|
|
452
|
+
issues.push({
|
|
453
|
+
file,
|
|
454
|
+
line: index + 1,
|
|
455
|
+
type: "conditional-hook-inline",
|
|
456
|
+
severity: "error",
|
|
457
|
+
message: "Hook called in conditional statement - violates Rules of Hooks",
|
|
458
|
+
suggestion: "Move hook call to top level of component, before any conditionals",
|
|
459
|
+
snippet: trimmed.substring(0, 80),
|
|
460
|
+
});
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
// Check for hooks inside callback functions that might be called conditionally
|
|
464
|
+
// Example: .filter(x => useHook())
|
|
465
|
+
if (/\.\s*(filter|find|some|every|reduce|map|forEach)\s*\([^)]*use[A-Z]\w+/.test(line)) {
|
|
466
|
+
issues.push({
|
|
467
|
+
file,
|
|
468
|
+
line: index + 1,
|
|
469
|
+
type: "callback-hook",
|
|
470
|
+
severity: "error",
|
|
471
|
+
message: "Hook called inside array callback - violates Rules of Hooks",
|
|
472
|
+
suggestion: "Extract the hook to component level and pass values to callback",
|
|
473
|
+
snippet: trimmed.substring(0, 80),
|
|
474
|
+
});
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
// Check for ternary operators with hooks
|
|
478
|
+
// Example: condition ? useHookA() : useHookB()
|
|
479
|
+
if (/\?\s*use[A-Z]\w+\s*\(|\:\s*use[A-Z]\w+\s*\(/.test(line)) {
|
|
480
|
+
issues.push({
|
|
481
|
+
file,
|
|
482
|
+
line: index + 1,
|
|
483
|
+
type: "ternary-hook",
|
|
484
|
+
severity: "error",
|
|
485
|
+
message: "Hook called in ternary expression - violates Rules of Hooks",
|
|
486
|
+
suggestion: "Call both hooks unconditionally and choose result conditionally",
|
|
487
|
+
snippet: trimmed.substring(0, 80),
|
|
488
|
+
});
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
// Check for hooks in logical expressions
|
|
492
|
+
// Example: condition && useHook()
|
|
493
|
+
if (/\&\&\s*use[A-Z]\w+\s*\(|\|\|\s*use[A-Z]\w+\s*\(/.test(line)) {
|
|
494
|
+
issues.push({
|
|
495
|
+
file,
|
|
496
|
+
line: index + 1,
|
|
497
|
+
type: "logical-hook",
|
|
498
|
+
severity: "error",
|
|
499
|
+
message: "Hook called in logical expression - violates Rules of Hooks",
|
|
500
|
+
suggestion: "Call hooks unconditionally at component top level",
|
|
501
|
+
snippet: trimmed.substring(0, 80),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
507
|
+
return {
|
|
508
|
+
name: "React Hook Rules",
|
|
509
|
+
passed: !hasErrors,
|
|
510
|
+
blocking: true,
|
|
511
|
+
issues,
|
|
512
|
+
duration: Date.now() - startTime,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Check 9: Prisma Enum Literal Validation (BLOCKING)
|
|
517
|
+
*
|
|
518
|
+
* Prevents runtime PrismaClientValidationError caused by passing incorrect
|
|
519
|
+
* enum values (commonly lowercase strings) into Prisma queries/mutations.
|
|
520
|
+
*/
|
|
521
|
+
async checkPrismaEnumLiterals() {
|
|
522
|
+
const startTime = Date.now();
|
|
523
|
+
const issues = [];
|
|
524
|
+
const schemaPath = path.join(process.cwd(), "prisma", "schema.prisma");
|
|
525
|
+
if (!fs.existsSync(schemaPath)) {
|
|
526
|
+
return {
|
|
527
|
+
name: "Prisma Enum Literals",
|
|
528
|
+
passed: false,
|
|
529
|
+
blocking: true,
|
|
530
|
+
issues: [
|
|
531
|
+
{
|
|
532
|
+
file: schemaPath,
|
|
533
|
+
line: 0,
|
|
534
|
+
type: "missing-schema",
|
|
535
|
+
severity: "error",
|
|
536
|
+
message: "Missing prisma/schema.prisma - cannot validate enum literals",
|
|
537
|
+
},
|
|
538
|
+
],
|
|
539
|
+
duration: Date.now() - startTime,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
const schema = fs.readFileSync(schemaPath, "utf8");
|
|
543
|
+
// Parse enums: enum Name { VALUE ... }
|
|
544
|
+
const enumValues = new Map();
|
|
545
|
+
const enumValueByLower = new Map();
|
|
546
|
+
{
|
|
547
|
+
const enumRegex = /\benum\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
|
|
548
|
+
let match;
|
|
549
|
+
while ((match = enumRegex.exec(schema))) {
|
|
550
|
+
const enumName = match[1];
|
|
551
|
+
const body = match[2] ?? "";
|
|
552
|
+
const values = body
|
|
553
|
+
.split("\n")
|
|
554
|
+
.map((line) => line.replace(/\/\/.*$/, "").trim())
|
|
555
|
+
.filter((line) => line.length > 0)
|
|
556
|
+
.map((line) => line.split(/\s+/)[0] ?? "")
|
|
557
|
+
.filter((token) => token.length > 0 && token !== "}" && token !== "{");
|
|
558
|
+
if (values.length > 0) {
|
|
559
|
+
enumValues.set(enumName, values);
|
|
560
|
+
const lowerMap = new Map();
|
|
561
|
+
for (const value of values) {
|
|
562
|
+
lowerMap.set(value.toLowerCase(), value);
|
|
563
|
+
}
|
|
564
|
+
enumValueByLower.set(enumName, lowerMap);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (enumValues.size === 0) {
|
|
569
|
+
return {
|
|
570
|
+
name: "Prisma Enum Literals",
|
|
571
|
+
passed: true,
|
|
572
|
+
blocking: true,
|
|
573
|
+
issues: [],
|
|
574
|
+
duration: Date.now() - startTime,
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
// Parse model fields whose type is an enum.
|
|
578
|
+
// Map: fieldName -> set(enumName)
|
|
579
|
+
const enumFieldNames = new Map();
|
|
580
|
+
{
|
|
581
|
+
const modelRegex = /\bmodel\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
|
|
582
|
+
let match;
|
|
583
|
+
while ((match = modelRegex.exec(schema))) {
|
|
584
|
+
const body = match[2] ?? "";
|
|
585
|
+
for (const rawLine of body.split("\n")) {
|
|
586
|
+
const line = rawLine.replace(/\/\/.*$/, "").trim();
|
|
587
|
+
if (!line)
|
|
588
|
+
continue;
|
|
589
|
+
if (line.startsWith("@@"))
|
|
590
|
+
continue;
|
|
591
|
+
if (line.startsWith("@"))
|
|
592
|
+
continue;
|
|
593
|
+
const parts = line.split(/\s+/);
|
|
594
|
+
const fieldName = parts[0];
|
|
595
|
+
const typeTokenRaw = parts[1];
|
|
596
|
+
if (!fieldName || !typeTokenRaw)
|
|
597
|
+
continue;
|
|
598
|
+
const typeToken = typeTokenRaw.replace(/\?$/, "").replace(/\[\]$/, "");
|
|
599
|
+
if (!enumValues.has(typeToken))
|
|
600
|
+
continue;
|
|
601
|
+
const set = enumFieldNames.get(fieldName) ?? new Set();
|
|
602
|
+
set.add(typeToken);
|
|
603
|
+
enumFieldNames.set(fieldName, set);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (enumFieldNames.size === 0) {
|
|
608
|
+
return {
|
|
609
|
+
name: "Prisma Enum Literals",
|
|
610
|
+
passed: true,
|
|
611
|
+
blocking: true,
|
|
612
|
+
issues: [],
|
|
613
|
+
duration: Date.now() - startTime,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
const files = await file_cache_1.fileCache.getFiles("{app/api,lib}/**/*.{ts,tsx}", { ignore: EXCLUDED });
|
|
617
|
+
const keyValueRegex = /\b([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(['"])([A-Za-z_][A-Za-z0-9_]*)\2/g;
|
|
618
|
+
for (const file of files) {
|
|
619
|
+
const content = fs.readFileSync(file, "utf8");
|
|
620
|
+
if (!content.includes("prisma."))
|
|
621
|
+
continue;
|
|
622
|
+
const lines = content.split("\n");
|
|
623
|
+
const prismaLineIndexes = [];
|
|
624
|
+
for (let i = 0; i < lines.length; i++) {
|
|
625
|
+
if ((lines[i] ?? "").includes("prisma."))
|
|
626
|
+
prismaLineIndexes.push(i);
|
|
627
|
+
}
|
|
628
|
+
// Heuristic ignore: progress callback payloads often have `status: "processing"`
|
|
629
|
+
// and other literals that are NOT Prisma enum fields, but may be located near Prisma
|
|
630
|
+
// usage in the same file. This avoids false positives without weakening Prisma writes.
|
|
631
|
+
const ignoreEnumLiteralLine = new Array(lines.length).fill(false);
|
|
632
|
+
{
|
|
633
|
+
let inOnProgressObject = false;
|
|
634
|
+
let inInterfaceOrType = false;
|
|
635
|
+
let inResultsPush = false;
|
|
636
|
+
let braceDepth = 0;
|
|
637
|
+
for (let i = 0; i < lines.length; i++) {
|
|
638
|
+
const raw = lines[i] ?? "";
|
|
639
|
+
// Track interface/type definitions (not Prisma writes)
|
|
640
|
+
if (!inInterfaceOrType && /^\s*(?:interface|type)\s+\w+/.test(raw)) {
|
|
641
|
+
inInterfaceOrType = true;
|
|
642
|
+
braceDepth = 0;
|
|
643
|
+
}
|
|
644
|
+
if (inInterfaceOrType) {
|
|
645
|
+
braceDepth += (raw.match(/\{/g) || []).length;
|
|
646
|
+
braceDepth -= (raw.match(/\}/g) || []).length;
|
|
647
|
+
ignoreEnumLiteralLine[i] = true;
|
|
648
|
+
if (braceDepth <= 0 && raw.includes("}")) {
|
|
649
|
+
inInterfaceOrType = false;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// Track results.push({ ... }) patterns (local result objects, not Prisma)
|
|
653
|
+
if (!inResultsPush && /results\.push\s*\(\s*\{/.test(raw)) {
|
|
654
|
+
inResultsPush = true;
|
|
655
|
+
}
|
|
656
|
+
if (inResultsPush) {
|
|
657
|
+
ignoreEnumLiteralLine[i] = true;
|
|
658
|
+
if (/\}\s*\)\s*;/.test(raw) || raw.includes("});")) {
|
|
659
|
+
inResultsPush = false;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// Track onProgress callback payloads
|
|
663
|
+
if (!inOnProgressObject && /\bonProgress\?\.\s*\(\s*\{/.test(raw)) {
|
|
664
|
+
inOnProgressObject = true;
|
|
665
|
+
}
|
|
666
|
+
if (inOnProgressObject) {
|
|
667
|
+
ignoreEnumLiteralLine[i] = true;
|
|
668
|
+
if (/\}\s*\)\s*;/.test(raw) || raw.includes("});")) {
|
|
669
|
+
inOnProgressObject = false;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// Cheap guard: if no Prisma usage, skip
|
|
675
|
+
if (prismaLineIndexes.length === 0)
|
|
676
|
+
continue;
|
|
677
|
+
for (let index = 0; index < lines.length; index++) {
|
|
678
|
+
const rawLine = lines[index] ?? "";
|
|
679
|
+
const line = rawLine.trim();
|
|
680
|
+
if (ignoreEnumLiteralLine[index])
|
|
681
|
+
continue;
|
|
682
|
+
// Skip comment-only lines
|
|
683
|
+
if (line.startsWith("//") || line.startsWith("*"))
|
|
684
|
+
continue;
|
|
685
|
+
// Only consider lines near Prisma usage to reduce false positives.
|
|
686
|
+
const minDistance = prismaLineIndexes.reduce((min, prismaIndex) => {
|
|
687
|
+
const dist = Math.abs(prismaIndex - index);
|
|
688
|
+
return dist < min ? dist : min;
|
|
689
|
+
}, Number.POSITIVE_INFINITY);
|
|
690
|
+
if (minDistance > 50)
|
|
691
|
+
continue;
|
|
692
|
+
keyValueRegex.lastIndex = 0;
|
|
693
|
+
let m;
|
|
694
|
+
while ((m = keyValueRegex.exec(rawLine))) {
|
|
695
|
+
const fieldName = m[1];
|
|
696
|
+
const literal = m[3];
|
|
697
|
+
const enumCandidates = enumFieldNames.get(fieldName);
|
|
698
|
+
if (!enumCandidates)
|
|
699
|
+
continue;
|
|
700
|
+
const lower = literal.toLowerCase();
|
|
701
|
+
for (const enumName of enumCandidates) {
|
|
702
|
+
const lowerMap = enumValueByLower.get(enumName);
|
|
703
|
+
if (!lowerMap)
|
|
704
|
+
continue;
|
|
705
|
+
const expected = lowerMap.get(lower);
|
|
706
|
+
if (!expected)
|
|
707
|
+
continue;
|
|
708
|
+
// If the literal matches a known enum value case-insensitively, require exact match.
|
|
709
|
+
if (literal !== expected) {
|
|
710
|
+
issues.push({
|
|
711
|
+
file,
|
|
712
|
+
line: index + 1,
|
|
713
|
+
type: "prisma-enum-literal",
|
|
714
|
+
severity: "error",
|
|
715
|
+
message: `Prisma enum field "${fieldName}" uses "${literal}" but schema expects ${enumName}.${expected}`,
|
|
716
|
+
suggestion: `Use ${enumName}.${expected} (import from @prisma/client) or the exact string "${expected}"`,
|
|
717
|
+
snippet: rawLine.trim().substring(0, 140),
|
|
718
|
+
});
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
726
|
+
return {
|
|
727
|
+
name: "Prisma Enum Literals",
|
|
728
|
+
passed: !hasErrors,
|
|
729
|
+
blocking: true,
|
|
730
|
+
issues,
|
|
731
|
+
duration: Date.now() - startTime,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Check 6: Floating Promise Detection (BLOCKING)
|
|
736
|
+
*
|
|
737
|
+
* Promises that are not awaited or handled can cause silent failures.
|
|
738
|
+
*/
|
|
739
|
+
async checkFloatingPromises() {
|
|
740
|
+
const startTime = Date.now();
|
|
741
|
+
const issues = [];
|
|
742
|
+
const files = await getAppLibFiles();
|
|
743
|
+
for (const file of files) {
|
|
744
|
+
const content = fs.readFileSync(file, "utf8");
|
|
745
|
+
const lines = content.split("\n");
|
|
746
|
+
lines.forEach((line, index) => {
|
|
747
|
+
// Skip comments
|
|
748
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*"))
|
|
749
|
+
return;
|
|
750
|
+
// Pattern: Calling async function without await, .then(), or .catch()
|
|
751
|
+
// This is a simplified check - comprehensive check needs TypeScript analysis
|
|
752
|
+
// Check for Promise.all/Promise.race/Promise.allSettled without await
|
|
753
|
+
if (/(?<!await\s+)Promise\.(all|race|allSettled)\s*\(/.test(line) &&
|
|
754
|
+
!/\.then\(|\.catch\(|await/.test(line)) {
|
|
755
|
+
issues.push({
|
|
756
|
+
file,
|
|
757
|
+
line: index + 1,
|
|
758
|
+
type: "floating-promise",
|
|
759
|
+
severity: "warning",
|
|
760
|
+
message: "Promise.* call may not be awaited",
|
|
761
|
+
suggestion: "Add await or handle with .then()/.catch()",
|
|
762
|
+
snippet: line.trim().substring(0, 80),
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
// Check for fetch() without await
|
|
766
|
+
if (/(?<!await\s+)fetch\s*\(/.test(line) &&
|
|
767
|
+
!/\.then\(|\.catch\(|await|=\s*fetch/.test(line)) {
|
|
768
|
+
issues.push({
|
|
769
|
+
file,
|
|
770
|
+
line: index + 1,
|
|
771
|
+
type: "floating-fetch",
|
|
772
|
+
severity: "warning",
|
|
773
|
+
message: "fetch() call may not be awaited",
|
|
774
|
+
suggestion: "Add await or handle the promise",
|
|
775
|
+
snippet: line.trim().substring(0, 80),
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
// These are warnings, not blocking
|
|
781
|
+
return {
|
|
782
|
+
name: "Floating Promise Detection",
|
|
783
|
+
passed: true,
|
|
784
|
+
blocking: false,
|
|
785
|
+
issues,
|
|
786
|
+
duration: Date.now() - startTime,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Check 7: CVA Enforcement for Variant Components (WARNING ${chars.arrow} BLOCKING after migration)
|
|
791
|
+
*
|
|
792
|
+
* Components with variant/size props MUST use CVA for type-safe, consistent styling.
|
|
793
|
+
*/
|
|
794
|
+
async checkCvaEnforcement() {
|
|
795
|
+
const startTime = Date.now();
|
|
796
|
+
const issues = [];
|
|
797
|
+
const files = await file_cache_1.fileCache.getFiles("components/**/*.{tsx,jsx}", {
|
|
798
|
+
ignore: [...EXCLUDED, "**/ui/**"], // Skip UI package which should already use CVA
|
|
799
|
+
});
|
|
800
|
+
for (const file of files) {
|
|
801
|
+
const content = fs.readFileSync(file, "utf8");
|
|
802
|
+
const lines = content.split("\n");
|
|
803
|
+
// Check if component declares variant/size props
|
|
804
|
+
const hasVariantProp = /\bvariant\s*[?:]?\s*:\s*(?:string|['"][^'"]+['"]|VariantProps)/.test(content);
|
|
805
|
+
const hasSizeProp = /\bsize\s*[?:]?\s*:\s*(?:string|['"][^'"]+['"])/.test(content);
|
|
806
|
+
// Check if using CVA
|
|
807
|
+
const hasCva = /import.*\bcva\b|from\s+['"]class-variance-authority['"]/.test(content);
|
|
808
|
+
// Heuristic: only require CVA when variant/size appear to be used for runtime styling/branching.
|
|
809
|
+
// This avoids false positives for thin wrapper components that simply type and forward variant/size
|
|
810
|
+
// into a downstream CVA-backed UI component.
|
|
811
|
+
const usesVariantAtRuntime = lines.some((line) => {
|
|
812
|
+
const trimmed = line.trim();
|
|
813
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
814
|
+
return false;
|
|
815
|
+
// Only flag when variant/size drive style branching (not simple forwarding)
|
|
816
|
+
if (/\bvariant\b\s*===?/.test(trimmed))
|
|
817
|
+
return true;
|
|
818
|
+
if (/\bsize\b\s*===?/.test(trimmed))
|
|
819
|
+
return true;
|
|
820
|
+
if (/\bswitch\s*\(\s*variant\s*\)/.test(trimmed))
|
|
821
|
+
return true;
|
|
822
|
+
if (/\bswitch\s*\(\s*size\s*\)/.test(trimmed))
|
|
823
|
+
return true;
|
|
824
|
+
if (/\$\{\s*variant\s*\}/.test(trimmed))
|
|
825
|
+
return true;
|
|
826
|
+
if (/\$\{\s*size\s*\}/.test(trimmed))
|
|
827
|
+
return true;
|
|
828
|
+
return false;
|
|
829
|
+
});
|
|
830
|
+
if ((hasVariantProp || hasSizeProp) && usesVariantAtRuntime && !hasCva) {
|
|
831
|
+
// Find the first line that defines variant/size to point the developer at the API surface.
|
|
832
|
+
for (let index = 0; index < lines.length; index++) {
|
|
833
|
+
const line = lines[index] ?? "";
|
|
834
|
+
if (/\bvariant\s*[?:]?\s*:/.test(line) || /\bsize\s*[?:]?\s*:/.test(line)) {
|
|
835
|
+
issues.push({
|
|
836
|
+
file,
|
|
837
|
+
line: index + 1,
|
|
838
|
+
type: "missing-cva",
|
|
839
|
+
severity: "warning", // Will become 'error' after migration
|
|
840
|
+
message: "Component uses variant/size for styling but is missing CVA",
|
|
841
|
+
suggestion: 'Import cva from "class-variance-authority" and define variants',
|
|
842
|
+
snippet: line.trim().substring(0, 120),
|
|
843
|
+
});
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
// Check for manual variant handling
|
|
849
|
+
lines.forEach((line, index) => {
|
|
850
|
+
// Pattern: variant === "primary" ? "..." : "..."
|
|
851
|
+
if (/variant\s*===?\s*["'][^"']+["']\s*\?/.test(line)) {
|
|
852
|
+
if (!hasCva) {
|
|
853
|
+
issues.push({
|
|
854
|
+
file,
|
|
855
|
+
line: index + 1,
|
|
856
|
+
type: "manual-variant-handling",
|
|
857
|
+
severity: "warning",
|
|
858
|
+
message: "Manual variant ternary should use CVA variants",
|
|
859
|
+
suggestion: "Define variants with cva() for type-safe styling",
|
|
860
|
+
snippet: line.trim().substring(0, 80),
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
// Currently warning-only during migration
|
|
867
|
+
return {
|
|
868
|
+
name: "CVA Enforcement",
|
|
869
|
+
passed: true,
|
|
870
|
+
blocking: false, // Will become blocking after migration complete
|
|
871
|
+
issues,
|
|
872
|
+
duration: Date.now() - startTime,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Check 8: Unsafe Type Assertions in Critical Paths (WARNING)
|
|
877
|
+
*
|
|
878
|
+
* Excessive 'as any' or 'as unknown' can hide type errors.
|
|
879
|
+
*/
|
|
880
|
+
async checkTypeSafety() {
|
|
881
|
+
const startTime = Date.now();
|
|
882
|
+
const issues = [];
|
|
883
|
+
const files = await file_cache_1.fileCache.getFiles("{app/api,lib/api}/**/*.{ts,tsx}", { ignore: EXCLUDED });
|
|
884
|
+
for (const file of files) {
|
|
885
|
+
const content = fs.readFileSync(file, "utf8");
|
|
886
|
+
const lines = content.split("\n");
|
|
887
|
+
lines.forEach((line, index) => {
|
|
888
|
+
// Skip comments
|
|
889
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*"))
|
|
890
|
+
return;
|
|
891
|
+
// Check for dangerous patterns
|
|
892
|
+
if (/as\s+any\s*\)\./.test(line)) {
|
|
893
|
+
issues.push({
|
|
894
|
+
file,
|
|
895
|
+
line: index + 1,
|
|
896
|
+
type: "dangerous-any-chain",
|
|
897
|
+
severity: "warning",
|
|
898
|
+
message: '"as any" followed by property access - potential runtime error',
|
|
899
|
+
suggestion: "Use proper type narrowing or type guards",
|
|
900
|
+
snippet: line.trim().substring(0, 80),
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
// Check for 'as unknown as X' pattern (double casting)
|
|
904
|
+
if (/as\s+unknown\s+as\s+/.test(line)) {
|
|
905
|
+
issues.push({
|
|
906
|
+
file,
|
|
907
|
+
line: index + 1,
|
|
908
|
+
type: "double-cast",
|
|
909
|
+
severity: "warning",
|
|
910
|
+
message: "Double type assertion (as unknown as X) - indicates type mismatch",
|
|
911
|
+
suggestion: "Review types and fix the underlying type issue",
|
|
912
|
+
snippet: line.trim().substring(0, 80),
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
return {
|
|
918
|
+
name: "Type Safety",
|
|
919
|
+
passed: true,
|
|
920
|
+
blocking: false,
|
|
921
|
+
issues,
|
|
922
|
+
duration: Date.now() - startTime,
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Check 9: ESLint Concurrency Enforcement (BLOCKING)
|
|
927
|
+
*
|
|
928
|
+
* ESLint must run with multithreading enabled in our scripts/preflights.
|
|
929
|
+
* ESLint v9 defaults to `--concurrency off`, so we hard-fail if any
|
|
930
|
+
* maintained runner regresses and drops the flag.
|
|
931
|
+
*/
|
|
932
|
+
async checkEslintConcurrencyEnforcement() {
|
|
933
|
+
const startTime = Date.now();
|
|
934
|
+
const issues = [];
|
|
935
|
+
const requiredScriptKeys = [
|
|
936
|
+
{ key: "lint", suggestion: "Ensure lint script includes `--concurrency auto`." },
|
|
937
|
+
{ key: "lint:fix", suggestion: "Ensure lint:fix script includes `--concurrency auto`." },
|
|
938
|
+
];
|
|
939
|
+
const requiredFiles = [
|
|
940
|
+
{
|
|
941
|
+
file: "scripts/active/preflights/specialized/lint-validation.ts",
|
|
942
|
+
mustInclude: /--concurrency\s+auto/,
|
|
943
|
+
suggestion: "Add `--concurrency auto` to the ESLint command used by lint-validation preflight.",
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
file: "scripts/active/preflights/utils/eslint-integration.ts",
|
|
947
|
+
mustInclude: /--concurrency\s+auto/,
|
|
948
|
+
suggestion: "Ensure ESLintIntegration always adds `--concurrency auto` when invoking ESLint.",
|
|
949
|
+
},
|
|
950
|
+
];
|
|
951
|
+
const lineOfIndex = (content, index) => {
|
|
952
|
+
if (index <= 0)
|
|
953
|
+
return 1;
|
|
954
|
+
return content.slice(0, index).split("\n").length;
|
|
955
|
+
};
|
|
956
|
+
const readText = (relPath) => {
|
|
957
|
+
const abs = path.join(process.cwd(), relPath);
|
|
958
|
+
return { abs, content: fs.readFileSync(abs, "utf8") };
|
|
959
|
+
};
|
|
960
|
+
// 1) package.json scripts must include --concurrency (and not off)
|
|
961
|
+
try {
|
|
962
|
+
const { abs, content } = readText("package.json");
|
|
963
|
+
const parsed = JSON.parse(content);
|
|
964
|
+
const scripts = parsed?.scripts;
|
|
965
|
+
if (!scripts) {
|
|
966
|
+
issues.push({
|
|
967
|
+
file: abs,
|
|
968
|
+
line: 1,
|
|
969
|
+
type: "missing-scripts",
|
|
970
|
+
severity: "error",
|
|
971
|
+
message: "package.json missing scripts section",
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
else {
|
|
975
|
+
for (const { key, suggestion } of requiredScriptKeys) {
|
|
976
|
+
const value = scripts[key];
|
|
977
|
+
if (!value) {
|
|
978
|
+
issues.push({
|
|
979
|
+
file: abs,
|
|
980
|
+
line: 1,
|
|
981
|
+
type: "missing-script",
|
|
982
|
+
severity: "error",
|
|
983
|
+
message: `package.json scripts.${key} is missing`,
|
|
984
|
+
suggestion,
|
|
985
|
+
});
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
const hasConcurrency = /--concurrency\b/.test(value);
|
|
989
|
+
const explicitlyOff = /--concurrency\s+off\b/.test(value);
|
|
990
|
+
if (!hasConcurrency || explicitlyOff) {
|
|
991
|
+
const idx = content.indexOf(`\"${key}\"`);
|
|
992
|
+
issues.push({
|
|
993
|
+
file: abs,
|
|
994
|
+
line: lineOfIndex(content, idx),
|
|
995
|
+
type: "eslint-concurrency-missing",
|
|
996
|
+
severity: "error",
|
|
997
|
+
message: `package.json scripts.${key} must enable ESLint multithreading (missing/disabled --concurrency)`,
|
|
998
|
+
suggestion,
|
|
999
|
+
snippet: value.substring(0, 140),
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
issues.push({
|
|
1007
|
+
file: "package.json",
|
|
1008
|
+
line: 1,
|
|
1009
|
+
type: "package-json-read-failed",
|
|
1010
|
+
severity: "error",
|
|
1011
|
+
message: `Failed reading/parsing package.json: ${error?.message ?? String(error)}`,
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
// 2) Known maintained runners must include concurrency
|
|
1015
|
+
for (const entry of requiredFiles) {
|
|
1016
|
+
try {
|
|
1017
|
+
const { abs, content } = readText(entry.file);
|
|
1018
|
+
if (!entry.mustInclude.test(content)) {
|
|
1019
|
+
issues.push({
|
|
1020
|
+
file: abs,
|
|
1021
|
+
line: 1,
|
|
1022
|
+
type: "eslint-concurrency-missing",
|
|
1023
|
+
severity: "error",
|
|
1024
|
+
message: `Missing required ESLint concurrency flag in ${entry.file}`,
|
|
1025
|
+
suggestion: entry.suggestion,
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
catch (error) {
|
|
1030
|
+
issues.push({
|
|
1031
|
+
file: entry.file,
|
|
1032
|
+
line: 1,
|
|
1033
|
+
type: "file-read-failed",
|
|
1034
|
+
severity: "error",
|
|
1035
|
+
message: `Failed to read ${entry.file}: ${error?.message ?? String(error)}`,
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
// 3) Scan preflight scripts for direct execSync('...eslint...') usage without concurrency
|
|
1040
|
+
try {
|
|
1041
|
+
const preflightFiles = await file_cache_1.fileCache.getFiles("scripts/active/preflights/**/*.{ts,js,mjs,cjs}", { ignore: EXCLUDED });
|
|
1042
|
+
const literalRe = /execSync\(\s*([`"'])([\s\S]*?)\1\s*(?:,|\))/g;
|
|
1043
|
+
for (const file of preflightFiles) {
|
|
1044
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1045
|
+
let match;
|
|
1046
|
+
// eslint-disable-next-line no-cond-assign
|
|
1047
|
+
while ((match = literalRe.exec(content))) {
|
|
1048
|
+
const cmd = match[2] ?? "";
|
|
1049
|
+
if (!/\beslint\b/.test(cmd))
|
|
1050
|
+
continue;
|
|
1051
|
+
// Allow non-lint informational calls
|
|
1052
|
+
if (/\beslint\b\s+--(?:help|version)\b/.test(cmd))
|
|
1053
|
+
continue;
|
|
1054
|
+
const hasConcurrency = /--concurrency\b/.test(cmd);
|
|
1055
|
+
const explicitlyOff = /--concurrency\s+off\b/.test(cmd);
|
|
1056
|
+
if (!hasConcurrency || explicitlyOff) {
|
|
1057
|
+
issues.push({
|
|
1058
|
+
file,
|
|
1059
|
+
line: lineOfIndex(content, match.index),
|
|
1060
|
+
type: "eslint-concurrency-missing",
|
|
1061
|
+
severity: "error",
|
|
1062
|
+
message: "Preflight execSync ESLint command missing `--concurrency` (defaults to off)",
|
|
1063
|
+
suggestion: "Add `--concurrency auto` to the ESLint command string.",
|
|
1064
|
+
snippet: cmd.replace(/\s+/g, " ").trim().substring(0, 140),
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
catch (error) {
|
|
1071
|
+
issues.push({
|
|
1072
|
+
file: "scripts/active/preflights/**",
|
|
1073
|
+
line: 1,
|
|
1074
|
+
type: "preflight-scan-failed",
|
|
1075
|
+
severity: "warning",
|
|
1076
|
+
message: `Failed scanning preflight scripts for ESLint commands: ${error?.message ?? String(error)}`,
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1080
|
+
return {
|
|
1081
|
+
name: "ESLint Concurrency Enforcement",
|
|
1082
|
+
passed: !hasErrors,
|
|
1083
|
+
blocking: true,
|
|
1084
|
+
issues,
|
|
1085
|
+
duration: Date.now() - startTime,
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Check 10: CardGraded Semantics (BLOCKING)
|
|
1090
|
+
*
|
|
1091
|
+
* Guardrail: WATA is a video-game grader and must not appear as a `CardGraded`
|
|
1092
|
+
* value on catalogNonSportsCards writes (create/upsert/update).
|
|
1093
|
+
*/
|
|
1094
|
+
async checkCardGradedSemantics() {
|
|
1095
|
+
const startTime = Date.now();
|
|
1096
|
+
const issues = [];
|
|
1097
|
+
const files = await file_cache_1.fileCache.getFiles("{app,lib,scripts,components}/**/*.{ts,tsx,js,jsx}", {
|
|
1098
|
+
ignore: EXCLUDED,
|
|
1099
|
+
});
|
|
1100
|
+
const callPattern = /catalogNonSportsCards\.(?:create|createMany|upsert|update|updateMany)\s*\(/;
|
|
1101
|
+
const violationPattern = /catalogNonSportsCards\.(?:create|createMany|upsert|update|updateMany)\s*\([\s\S]{0,2500}?\bgraded\s*:\s*(?:CardGraded\.)?["']WATA["']/;
|
|
1102
|
+
for (const file of files) {
|
|
1103
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1104
|
+
if (!callPattern.test(content))
|
|
1105
|
+
continue;
|
|
1106
|
+
// Fast path: no WATA anywhere in the file
|
|
1107
|
+
if (!/\bWATA\b/.test(content))
|
|
1108
|
+
continue;
|
|
1109
|
+
const match = content.match(violationPattern);
|
|
1110
|
+
if (!match)
|
|
1111
|
+
continue;
|
|
1112
|
+
issues.push({
|
|
1113
|
+
file,
|
|
1114
|
+
line: 1,
|
|
1115
|
+
type: "cardgraded-wata-in-nonsports-write",
|
|
1116
|
+
severity: "error",
|
|
1117
|
+
message: "Invalid CardGraded usage: catalogNonSportsCards write sets graded to WATA",
|
|
1118
|
+
suggestion: 'Use graded: "UNGRADED" (or another card-appropriate value). Video-game grading belongs in catalogVideoGames fields (grader/wataGrade/wataSealGrade).',
|
|
1119
|
+
snippet: match[0].replace(/\s+/g, " ").trim().substring(0, 160),
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1123
|
+
return {
|
|
1124
|
+
name: "CardGraded Semantics (No WATA in catalogNonSportsCards)",
|
|
1125
|
+
passed: !hasErrors,
|
|
1126
|
+
blocking: true,
|
|
1127
|
+
issues,
|
|
1128
|
+
duration: Date.now() - startTime,
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Check 11: Card grade-field consistency (BLOCKING)
|
|
1133
|
+
*
|
|
1134
|
+
* Prevents semantically invalid grader/grade-field combinations:
|
|
1135
|
+
* - graded: "PSA" + bgsGrade set
|
|
1136
|
+
* - graded: "BGS" + psaGrade set
|
|
1137
|
+
* - graded: "CGC" + psaGrade/bgsGrade/sgcGrade set
|
|
1138
|
+
* - graded: "SGC" + psaGrade/bgsGrade/cgcGrade set
|
|
1139
|
+
* - graded: "UNGRADED" + any grade field set
|
|
1140
|
+
*/
|
|
1141
|
+
async checkCardGradeFieldConsistency() {
|
|
1142
|
+
const startTime = Date.now();
|
|
1143
|
+
const issues = [];
|
|
1144
|
+
const files = await getAppLibScriptsFiles();
|
|
1145
|
+
const writeCallPattern = /(?:catalog(?:SportsCards|NonSportsCards|TcgCards)|coreListings)\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
1146
|
+
const hasNonNullPsaGrade = /\bpsaGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
1147
|
+
const hasNonNullBgsGrade = /\bbgsGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
1148
|
+
const hasNonNullCgcGrade = /\bcgcGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
1149
|
+
const hasNonNullSgcGrade = /\bsgcGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
1150
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
1151
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
1152
|
+
if (firstBrace === -1)
|
|
1153
|
+
return null;
|
|
1154
|
+
let depth = 0;
|
|
1155
|
+
let inSingle = false;
|
|
1156
|
+
let inDouble = false;
|
|
1157
|
+
let inTemplate = false;
|
|
1158
|
+
let inLineComment = false;
|
|
1159
|
+
let inBlockComment = false;
|
|
1160
|
+
let escaped = false;
|
|
1161
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
1162
|
+
const ch = content[i];
|
|
1163
|
+
const next = content[i + 1];
|
|
1164
|
+
if (inLineComment) {
|
|
1165
|
+
if (ch === "\n")
|
|
1166
|
+
inLineComment = false;
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
if (inBlockComment) {
|
|
1170
|
+
if (ch === "*" && next === "/") {
|
|
1171
|
+
inBlockComment = false;
|
|
1172
|
+
i++;
|
|
1173
|
+
}
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
1177
|
+
if (ch === "/" && next === "/") {
|
|
1178
|
+
inLineComment = true;
|
|
1179
|
+
i++;
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (ch === "/" && next === "*") {
|
|
1183
|
+
inBlockComment = true;
|
|
1184
|
+
i++;
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
if (escaped) {
|
|
1189
|
+
escaped = false;
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
1193
|
+
escaped = true;
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
1197
|
+
inSingle = !inSingle;
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
1201
|
+
inDouble = !inDouble;
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
1205
|
+
inTemplate = !inTemplate;
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
if (inSingle || inDouble || inTemplate)
|
|
1209
|
+
continue;
|
|
1210
|
+
if (ch === "{") {
|
|
1211
|
+
depth++;
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (ch === "}") {
|
|
1215
|
+
depth--;
|
|
1216
|
+
if (depth === 0) {
|
|
1217
|
+
return content.slice(firstBrace, i + 1);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
return null;
|
|
1222
|
+
};
|
|
1223
|
+
for (const file of files) {
|
|
1224
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1225
|
+
if (!/(?:catalog(?:SportsCards|NonSportsCards|TcgCards)|coreListings)\./.test(content))
|
|
1226
|
+
continue;
|
|
1227
|
+
if (!/\bgraded\b|\bpsaGrade\b|\bbgsGrade\b|\bcgcGrade\b|\bsgcGrade\b/.test(content))
|
|
1228
|
+
continue;
|
|
1229
|
+
writeCallPattern.lastIndex = 0;
|
|
1230
|
+
let match;
|
|
1231
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
1232
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
1233
|
+
if (!window)
|
|
1234
|
+
continue;
|
|
1235
|
+
const isPSA = /\bgraded\s*:\s*(?:CardGraded\.)?["']PSA["']/.test(window);
|
|
1236
|
+
const isBGS = /\bgraded\s*:\s*(?:CardGraded\.)?["']BGS["']/.test(window);
|
|
1237
|
+
const isCGC = /\bgraded\s*:\s*(?:CardGraded\.)?['"]CGC['"]/.test(window);
|
|
1238
|
+
const isSGC = /\bgraded\s*:\s*(?:CardGraded\.)?['"]SGC['"]/.test(window);
|
|
1239
|
+
const isUngraded = /\bgraded\s*:\s*(?:CardGraded\.)?["']UNGRADED["']/.test(window);
|
|
1240
|
+
if (isPSA &&
|
|
1241
|
+
(hasNonNullBgsGrade.test(window) ||
|
|
1242
|
+
hasNonNullCgcGrade.test(window) ||
|
|
1243
|
+
hasNonNullSgcGrade.test(window))) {
|
|
1244
|
+
issues.push({
|
|
1245
|
+
file,
|
|
1246
|
+
line: 1,
|
|
1247
|
+
type: "grading-field-mismatch",
|
|
1248
|
+
severity: "error",
|
|
1249
|
+
message: "Invalid grading payload: graded is PSA but a non-PSA grade field is set",
|
|
1250
|
+
suggestion: "Clear bgsGrade/cgcGrade/sgcGrade (or change graded to match the grade field).",
|
|
1251
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
if (isBGS &&
|
|
1255
|
+
(hasNonNullPsaGrade.test(window) ||
|
|
1256
|
+
hasNonNullCgcGrade.test(window) ||
|
|
1257
|
+
hasNonNullSgcGrade.test(window))) {
|
|
1258
|
+
issues.push({
|
|
1259
|
+
file,
|
|
1260
|
+
line: 1,
|
|
1261
|
+
type: "grading-field-mismatch",
|
|
1262
|
+
severity: "error",
|
|
1263
|
+
message: "Invalid grading payload: graded is BGS but a non-BGS grade field is set",
|
|
1264
|
+
suggestion: "Clear psaGrade/cgcGrade/sgcGrade (or change graded to match the grade field).",
|
|
1265
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
if (isCGC &&
|
|
1269
|
+
(hasNonNullPsaGrade.test(window) ||
|
|
1270
|
+
hasNonNullBgsGrade.test(window) ||
|
|
1271
|
+
hasNonNullSgcGrade.test(window))) {
|
|
1272
|
+
issues.push({
|
|
1273
|
+
file,
|
|
1274
|
+
line: 1,
|
|
1275
|
+
type: "grading-field-mismatch",
|
|
1276
|
+
severity: "error",
|
|
1277
|
+
message: "Invalid grading payload: graded is CGC but a non-CGC grade field is set",
|
|
1278
|
+
suggestion: "Clear psaGrade/bgsGrade/sgcGrade (or change graded to match the grade field).",
|
|
1279
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
if (isSGC &&
|
|
1283
|
+
(hasNonNullPsaGrade.test(window) ||
|
|
1284
|
+
hasNonNullBgsGrade.test(window) ||
|
|
1285
|
+
hasNonNullCgcGrade.test(window))) {
|
|
1286
|
+
issues.push({
|
|
1287
|
+
file,
|
|
1288
|
+
line: 1,
|
|
1289
|
+
type: "grading-field-mismatch",
|
|
1290
|
+
severity: "error",
|
|
1291
|
+
message: "Invalid grading payload: graded is SGC but a non-SGC grade field is set",
|
|
1292
|
+
suggestion: "Clear psaGrade/bgsGrade/cgcGrade (or change graded to match the grade field).",
|
|
1293
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
if (isUngraded &&
|
|
1297
|
+
(hasNonNullPsaGrade.test(window) ||
|
|
1298
|
+
hasNonNullBgsGrade.test(window) ||
|
|
1299
|
+
hasNonNullCgcGrade.test(window) ||
|
|
1300
|
+
hasNonNullSgcGrade.test(window))) {
|
|
1301
|
+
issues.push({
|
|
1302
|
+
file,
|
|
1303
|
+
line: 1,
|
|
1304
|
+
type: "grading-field-mismatch",
|
|
1305
|
+
severity: "error",
|
|
1306
|
+
message: "Invalid grading payload: graded is UNGRADED but a grade field is set",
|
|
1307
|
+
suggestion: 'Either set graded to the correct grader (e.g., "PSA"/"BGS"/"CGC"/"SGC") or clear psaGrade/bgsGrade/cgcGrade/sgcGrade.',
|
|
1308
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1314
|
+
return {
|
|
1315
|
+
name: "Card Grade Field Consistency",
|
|
1316
|
+
passed: !hasErrors,
|
|
1317
|
+
blocking: true,
|
|
1318
|
+
issues,
|
|
1319
|
+
duration: Date.now() - startTime,
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Check 12: Serial number consistency (BLOCKING)
|
|
1324
|
+
*
|
|
1325
|
+
* Invariant:
|
|
1326
|
+
* - If serialNumbered is true, serialNumber must be present and non-empty.
|
|
1327
|
+
*
|
|
1328
|
+
* This catches common catalog/listing data-quality bugs in write payloads.
|
|
1329
|
+
*/
|
|
1330
|
+
async checkSerialNumberConsistency() {
|
|
1331
|
+
const startTime = Date.now();
|
|
1332
|
+
const issues = [];
|
|
1333
|
+
const files = await getAppLibScriptsFiles();
|
|
1334
|
+
const writeCallPattern = /(catalog(?:SportsCards|NonSportsCards|TcgCards)|coreListings)\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
1335
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
1336
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
1337
|
+
if (firstBrace === -1)
|
|
1338
|
+
return null;
|
|
1339
|
+
let depth = 0;
|
|
1340
|
+
let inSingle = false;
|
|
1341
|
+
let inDouble = false;
|
|
1342
|
+
let inTemplate = false;
|
|
1343
|
+
let inLineComment = false;
|
|
1344
|
+
let inBlockComment = false;
|
|
1345
|
+
let escaped = false;
|
|
1346
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
1347
|
+
const ch = content[i];
|
|
1348
|
+
const next = content[i + 1];
|
|
1349
|
+
if (inLineComment) {
|
|
1350
|
+
if (ch === "\n")
|
|
1351
|
+
inLineComment = false;
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
if (inBlockComment) {
|
|
1355
|
+
if (ch === "*" && next === "/") {
|
|
1356
|
+
inBlockComment = false;
|
|
1357
|
+
i++;
|
|
1358
|
+
}
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
1362
|
+
if (ch === "/" && next === "/") {
|
|
1363
|
+
inLineComment = true;
|
|
1364
|
+
i++;
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
if (ch === "/" && next === "*") {
|
|
1368
|
+
inBlockComment = true;
|
|
1369
|
+
i++;
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
if (escaped) {
|
|
1374
|
+
escaped = false;
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
1378
|
+
escaped = true;
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
1382
|
+
inSingle = !inSingle;
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
1386
|
+
inDouble = !inDouble;
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
1390
|
+
inTemplate = !inTemplate;
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
if (inSingle || inDouble || inTemplate)
|
|
1394
|
+
continue;
|
|
1395
|
+
if (ch === "{") {
|
|
1396
|
+
depth++;
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
if (ch === "}") {
|
|
1400
|
+
depth--;
|
|
1401
|
+
if (depth === 0) {
|
|
1402
|
+
return content.slice(firstBrace, i + 1);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
return null;
|
|
1407
|
+
};
|
|
1408
|
+
const serialFlagTrue = /\bserialNumbered\s*:\s*true\b/;
|
|
1409
|
+
const hasSerialField = /\bserialNumber\s*:/;
|
|
1410
|
+
const serialEmpty = /\bserialNumber\s*:\s*(?:''|""|null|undefined)\b/;
|
|
1411
|
+
for (const file of files) {
|
|
1412
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1413
|
+
if (!/serialNumbered|serialNumber/.test(content))
|
|
1414
|
+
continue;
|
|
1415
|
+
if (!/(catalog(?:SportsCards|NonSportsCards|TcgCards)|coreListings)\./.test(content))
|
|
1416
|
+
continue;
|
|
1417
|
+
writeCallPattern.lastIndex = 0;
|
|
1418
|
+
let match;
|
|
1419
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
1420
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
1421
|
+
if (!window)
|
|
1422
|
+
continue;
|
|
1423
|
+
if (!serialFlagTrue.test(window))
|
|
1424
|
+
continue;
|
|
1425
|
+
if (!hasSerialField.test(window) || serialEmpty.test(window)) {
|
|
1426
|
+
issues.push({
|
|
1427
|
+
file,
|
|
1428
|
+
line: 1,
|
|
1429
|
+
type: "serial-number-missing",
|
|
1430
|
+
severity: "error",
|
|
1431
|
+
message: "Invalid payload: serialNumbered is true but serialNumber is missing/empty",
|
|
1432
|
+
suggestion: "Provide a non-empty serialNumber when serialNumbered is true (or set serialNumbered to false).",
|
|
1433
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1439
|
+
return {
|
|
1440
|
+
name: "Serial Number Consistency",
|
|
1441
|
+
passed: !hasErrors,
|
|
1442
|
+
blocking: true,
|
|
1443
|
+
issues,
|
|
1444
|
+
duration: Date.now() - startTime,
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Check 13: Autograph consistency (BLOCKING)
|
|
1449
|
+
*
|
|
1450
|
+
* Invariants:
|
|
1451
|
+
* - If autographedAuthenticated/autographAuthenticated is true, autographed must be true.
|
|
1452
|
+
* - If autographed is explicitly false, autograph auth fields should not be populated.
|
|
1453
|
+
*/
|
|
1454
|
+
async checkAutographConsistency() {
|
|
1455
|
+
const startTime = Date.now();
|
|
1456
|
+
const issues = [];
|
|
1457
|
+
const files = await getAppLibScriptsFiles();
|
|
1458
|
+
const writeCallPattern = /(catalog(?:SportsCards|NonSportsCards|TcgCards)|coreListings)\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
1459
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
1460
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
1461
|
+
if (firstBrace === -1)
|
|
1462
|
+
return null;
|
|
1463
|
+
let depth = 0;
|
|
1464
|
+
let inSingle = false;
|
|
1465
|
+
let inDouble = false;
|
|
1466
|
+
let inTemplate = false;
|
|
1467
|
+
let inLineComment = false;
|
|
1468
|
+
let inBlockComment = false;
|
|
1469
|
+
let escaped = false;
|
|
1470
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
1471
|
+
const ch = content[i];
|
|
1472
|
+
const next = content[i + 1];
|
|
1473
|
+
if (inLineComment) {
|
|
1474
|
+
if (ch === "\n")
|
|
1475
|
+
inLineComment = false;
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
if (inBlockComment) {
|
|
1479
|
+
if (ch === "*" && next === "/") {
|
|
1480
|
+
inBlockComment = false;
|
|
1481
|
+
i++;
|
|
1482
|
+
}
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
1486
|
+
if (ch === "/" && next === "/") {
|
|
1487
|
+
inLineComment = true;
|
|
1488
|
+
i++;
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
if (ch === "/" && next === "*") {
|
|
1492
|
+
inBlockComment = true;
|
|
1493
|
+
i++;
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
if (escaped) {
|
|
1498
|
+
escaped = false;
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
1502
|
+
escaped = true;
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
1506
|
+
inSingle = !inSingle;
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
1510
|
+
inDouble = !inDouble;
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
1514
|
+
inTemplate = !inTemplate;
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
if (inSingle || inDouble || inTemplate)
|
|
1518
|
+
continue;
|
|
1519
|
+
if (ch === "{") {
|
|
1520
|
+
depth++;
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
if (ch === "}") {
|
|
1524
|
+
depth--;
|
|
1525
|
+
if (depth === 0) {
|
|
1526
|
+
return content.slice(firstBrace, i + 1);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
return null;
|
|
1531
|
+
};
|
|
1532
|
+
const autographedTrue = /\bautographed\s*:\s*true\b/;
|
|
1533
|
+
const autographedFalse = /\bautographed\s*:\s*false\b/;
|
|
1534
|
+
const authenticatedTrue = /\b(?:autographedAuthenticated|autographAuthenticated)\s*:\s*true\b/;
|
|
1535
|
+
const hasAuthenticationValue = /\bautographAuthentication\s*:\s*(?!null\b|undefined\b|''|""\b)/;
|
|
1536
|
+
const hasSigners = /\bautographSigners\s*:\s*\[/;
|
|
1537
|
+
const hasAutographGradedValue = /\bautographGraded\s*:\s*(?!null\b|undefined\b|''|""\b)/;
|
|
1538
|
+
const hasAutographGradeField = /\b(?:autographPsaGrade|autographBgsGrade|autographJsaGrade|autographSgcGrade)\s*:\s*(?!null\b|undefined\b)/;
|
|
1539
|
+
const hasAutographGradeMetadata = (w) => hasAutographGradedValue.test(w) || hasAutographGradeField.test(w);
|
|
1540
|
+
for (const file of files) {
|
|
1541
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1542
|
+
if (!/autograph|autographed/.test(content))
|
|
1543
|
+
continue;
|
|
1544
|
+
if (!/(catalog(?:SportsCards|NonSportsCards|TcgCards)|coreListings)\./.test(content))
|
|
1545
|
+
continue;
|
|
1546
|
+
writeCallPattern.lastIndex = 0;
|
|
1547
|
+
let match;
|
|
1548
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
1549
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
1550
|
+
if (!window)
|
|
1551
|
+
continue;
|
|
1552
|
+
const isAutographed = autographedTrue.test(window);
|
|
1553
|
+
if (authenticatedTrue.test(window) && !isAutographed) {
|
|
1554
|
+
issues.push({
|
|
1555
|
+
file,
|
|
1556
|
+
line: 1,
|
|
1557
|
+
type: "autograph-auth-without-autograph",
|
|
1558
|
+
severity: "error",
|
|
1559
|
+
message: "Invalid payload: autograph authenticated flag is true but autographed is not true",
|
|
1560
|
+
suggestion: "Set autographed: true when setting autographAuthenticated/autographedAuthenticated: true.",
|
|
1561
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
if (authenticatedTrue.test(window) && !hasAuthenticationValue.test(window)) {
|
|
1565
|
+
issues.push({
|
|
1566
|
+
file,
|
|
1567
|
+
line: 1,
|
|
1568
|
+
type: "autograph-auth-missing-authentication-method",
|
|
1569
|
+
severity: "error",
|
|
1570
|
+
message: "Invalid payload: autograph authenticated flag is true but autographAuthentication is missing/empty",
|
|
1571
|
+
suggestion: 'Provide autographAuthentication (e.g., "PSA/DNA", "BGS", "JSA") when setting autographAuthenticated/autographedAuthenticated: true.',
|
|
1572
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
if (hasAutographGradeMetadata(window) && !isAutographed) {
|
|
1576
|
+
issues.push({
|
|
1577
|
+
file,
|
|
1578
|
+
line: 1,
|
|
1579
|
+
type: "autograph-grade-without-autograph",
|
|
1580
|
+
severity: "error",
|
|
1581
|
+
message: "Invalid payload: autograph grading fields are populated but autographed is not true",
|
|
1582
|
+
suggestion: "Set autographed: true when setting autographGraded/autograph*Grade fields (or clear those fields).",
|
|
1583
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
if (autographedFalse.test(window) &&
|
|
1587
|
+
(hasAuthenticationValue.test(window) ||
|
|
1588
|
+
hasSigners.test(window) ||
|
|
1589
|
+
authenticatedTrue.test(window) ||
|
|
1590
|
+
hasAutographGradeMetadata(window))) {
|
|
1591
|
+
issues.push({
|
|
1592
|
+
file,
|
|
1593
|
+
line: 1,
|
|
1594
|
+
type: "autograph-fields-with-autographed-false",
|
|
1595
|
+
severity: "error",
|
|
1596
|
+
message: "Invalid payload: autographed is false but autograph fields/auth flags are populated",
|
|
1597
|
+
suggestion: "Either set autographed: true or clear autographAuthentication/autographSigners/auth flags and any autograph grading fields.",
|
|
1598
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1604
|
+
return {
|
|
1605
|
+
name: "Autograph Consistency",
|
|
1606
|
+
passed: !hasErrors,
|
|
1607
|
+
blocking: true,
|
|
1608
|
+
issues,
|
|
1609
|
+
duration: Date.now() - startTime,
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Check 14: Video game grading consistency (BLOCKING)
|
|
1614
|
+
*
|
|
1615
|
+
* Invariants (conservative / low false positives):
|
|
1616
|
+
* - If graded is explicitly true, grader must be present and not null/undefined.
|
|
1617
|
+
* - If graded is explicitly false, grader/wataGrade/wataSealGrade must not be populated.
|
|
1618
|
+
* - If wataSealGrade is set and grader is an explicit literal, it must be WATA.
|
|
1619
|
+
*/
|
|
1620
|
+
async checkVideoGameGradingConsistency() {
|
|
1621
|
+
const startTime = Date.now();
|
|
1622
|
+
const issues = [];
|
|
1623
|
+
const files = await getAppLibScriptsFiles();
|
|
1624
|
+
const writeCallPattern = /catalogVideoGames\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
1625
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
1626
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
1627
|
+
if (firstBrace === -1)
|
|
1628
|
+
return null;
|
|
1629
|
+
let depth = 0;
|
|
1630
|
+
let inSingle = false;
|
|
1631
|
+
let inDouble = false;
|
|
1632
|
+
let inTemplate = false;
|
|
1633
|
+
let inLineComment = false;
|
|
1634
|
+
let inBlockComment = false;
|
|
1635
|
+
let escaped = false;
|
|
1636
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
1637
|
+
const ch = content[i];
|
|
1638
|
+
const next = content[i + 1];
|
|
1639
|
+
if (inLineComment) {
|
|
1640
|
+
if (ch === "\n")
|
|
1641
|
+
inLineComment = false;
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (inBlockComment) {
|
|
1645
|
+
if (ch === "*" && next === "/") {
|
|
1646
|
+
inBlockComment = false;
|
|
1647
|
+
i++;
|
|
1648
|
+
}
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
1652
|
+
if (ch === "/" && next === "/") {
|
|
1653
|
+
inLineComment = true;
|
|
1654
|
+
i++;
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
if (ch === "/" && next === "*") {
|
|
1658
|
+
inBlockComment = true;
|
|
1659
|
+
i++;
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
if (escaped) {
|
|
1664
|
+
escaped = false;
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
1668
|
+
escaped = true;
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1671
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
1672
|
+
inSingle = !inSingle;
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
1676
|
+
inDouble = !inDouble;
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
1680
|
+
inTemplate = !inTemplate;
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
if (inSingle || inDouble || inTemplate)
|
|
1684
|
+
continue;
|
|
1685
|
+
if (ch === "{") {
|
|
1686
|
+
depth++;
|
|
1687
|
+
continue;
|
|
1688
|
+
}
|
|
1689
|
+
if (ch === "}") {
|
|
1690
|
+
depth--;
|
|
1691
|
+
if (depth === 0) {
|
|
1692
|
+
return content.slice(firstBrace, i + 1);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
return null;
|
|
1697
|
+
};
|
|
1698
|
+
const gradedTrue = /\bgraded\s*:\s*true\b/;
|
|
1699
|
+
const gradedFalse = /\bgraded\s*:\s*false\b/;
|
|
1700
|
+
const hasGraderField = /\bgrader\s*:/;
|
|
1701
|
+
const graderEmpty = /\bgrader\s*:\s*(?:null|undefined)\b/;
|
|
1702
|
+
const graderNonNull = /\bgrader\s*:\s*(?!null\b|undefined\b)/;
|
|
1703
|
+
const hasNonNullWataGrade = /\bwataGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
1704
|
+
const hasNonNullWataSealGrade = /\bwataSealGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
1705
|
+
const graderLiteralNotWata = /\bgrader\s*:\s*(?:GameGrader\.)?['"](?!WATA\b)[A-Z_]+['"]/;
|
|
1706
|
+
for (const file of files) {
|
|
1707
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1708
|
+
if (!/catalogVideoGames\./.test(content))
|
|
1709
|
+
continue;
|
|
1710
|
+
if (!/\bgraded\b|\bgrader\b|\bwataGrade\b|\bwataSealGrade\b/.test(content))
|
|
1711
|
+
continue;
|
|
1712
|
+
writeCallPattern.lastIndex = 0;
|
|
1713
|
+
let match;
|
|
1714
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
1715
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
1716
|
+
if (!window)
|
|
1717
|
+
continue;
|
|
1718
|
+
if (gradedTrue.test(window)) {
|
|
1719
|
+
if (!hasGraderField.test(window) || graderEmpty.test(window)) {
|
|
1720
|
+
issues.push({
|
|
1721
|
+
file,
|
|
1722
|
+
line: 1,
|
|
1723
|
+
type: "videogame-grading-missing-grader",
|
|
1724
|
+
severity: "error",
|
|
1725
|
+
message: "Invalid payload: catalogVideoGames graded is true but grader is missing/empty",
|
|
1726
|
+
suggestion: 'Set grader when graded: true (e.g., "WATA"/"VGA"/"CGC_GAMES") or set graded to false.',
|
|
1727
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
if (gradedFalse.test(window)) {
|
|
1732
|
+
if (graderNonNull.test(window) ||
|
|
1733
|
+
hasNonNullWataGrade.test(window) ||
|
|
1734
|
+
hasNonNullWataSealGrade.test(window)) {
|
|
1735
|
+
issues.push({
|
|
1736
|
+
file,
|
|
1737
|
+
line: 1,
|
|
1738
|
+
type: "videogame-grading-fields-while-ungraded",
|
|
1739
|
+
severity: "error",
|
|
1740
|
+
message: "Invalid payload: catalogVideoGames graded is false but grading fields are populated",
|
|
1741
|
+
suggestion: "Clear grader/wataGrade/wataSealGrade when graded is false (or set graded to true).",
|
|
1742
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
if (hasNonNullWataSealGrade.test(window) && graderLiteralNotWata.test(window)) {
|
|
1747
|
+
issues.push({
|
|
1748
|
+
file,
|
|
1749
|
+
line: 1,
|
|
1750
|
+
type: "videogame-watasealgrade-nonwata",
|
|
1751
|
+
severity: "error",
|
|
1752
|
+
message: "Invalid payload: wataSealGrade is set but grader is explicitly not WATA",
|
|
1753
|
+
suggestion: 'Only set wataSealGrade with grader: "WATA" (or clear wataSealGrade).',
|
|
1754
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1760
|
+
return {
|
|
1761
|
+
name: "Video Game Grading Consistency",
|
|
1762
|
+
passed: !hasErrors,
|
|
1763
|
+
blocking: true,
|
|
1764
|
+
issues,
|
|
1765
|
+
duration: Date.now() - startTime,
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Check 15: Syndication consistency (BLOCKING)
|
|
1770
|
+
*
|
|
1771
|
+
* Invariant:
|
|
1772
|
+
* - If syndicatedToEbay/Amazon/Tcgplayer/Preplo is true, the corresponding
|
|
1773
|
+
* platform listing id must be present and non-empty.
|
|
1774
|
+
*/
|
|
1775
|
+
async checkSyndicationConsistency() {
|
|
1776
|
+
const startTime = Date.now();
|
|
1777
|
+
const issues = [];
|
|
1778
|
+
const files = await getAppLibScriptsFiles();
|
|
1779
|
+
const writeCallPattern = /coreListings\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
1780
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
1781
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
1782
|
+
if (firstBrace === -1)
|
|
1783
|
+
return null;
|
|
1784
|
+
let depth = 0;
|
|
1785
|
+
let inSingle = false;
|
|
1786
|
+
let inDouble = false;
|
|
1787
|
+
let inTemplate = false;
|
|
1788
|
+
let inLineComment = false;
|
|
1789
|
+
let inBlockComment = false;
|
|
1790
|
+
let escaped = false;
|
|
1791
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
1792
|
+
const ch = content[i];
|
|
1793
|
+
const next = content[i + 1];
|
|
1794
|
+
if (inLineComment) {
|
|
1795
|
+
if (ch === "\n")
|
|
1796
|
+
inLineComment = false;
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
if (inBlockComment) {
|
|
1800
|
+
if (ch === "*" && next === "/") {
|
|
1801
|
+
inBlockComment = false;
|
|
1802
|
+
i++;
|
|
1803
|
+
}
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
1807
|
+
if (ch === "/" && next === "/") {
|
|
1808
|
+
inLineComment = true;
|
|
1809
|
+
i++;
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
if (ch === "/" && next === "*") {
|
|
1813
|
+
inBlockComment = true;
|
|
1814
|
+
i++;
|
|
1815
|
+
continue;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
if (escaped) {
|
|
1819
|
+
escaped = false;
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
1823
|
+
escaped = true;
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
1827
|
+
inSingle = !inSingle;
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
1831
|
+
inDouble = !inDouble;
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
1835
|
+
inTemplate = !inTemplate;
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
if (inSingle || inDouble || inTemplate)
|
|
1839
|
+
continue;
|
|
1840
|
+
if (ch === "{") {
|
|
1841
|
+
depth++;
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
if (ch === "}") {
|
|
1845
|
+
depth--;
|
|
1846
|
+
if (depth === 0) {
|
|
1847
|
+
return content.slice(firstBrace, i + 1);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
return null;
|
|
1852
|
+
};
|
|
1853
|
+
const makeFlagTrue = (flag) => new RegExp(`\\b${flag}\\s*:\\s*true\\b`);
|
|
1854
|
+
const makeIdField = (idField) => new RegExp(`\\b${idField}\\s*:`);
|
|
1855
|
+
const makeIdEmpty = (idField) => new RegExp(`\\b${idField}\\s*:\\s*(?:''|\"\"|null|undefined)\\b`);
|
|
1856
|
+
const rules = [
|
|
1857
|
+
{ flag: "syndicatedToEbay", id: "ebayListingId" },
|
|
1858
|
+
{ flag: "syndicatedToAmazon", id: "amazonListingId" },
|
|
1859
|
+
{ flag: "syndicatedToTcgplayer", id: "tcgPlayerListingId" },
|
|
1860
|
+
{ flag: "syndicatedToPreplo", id: "preploListingId" },
|
|
1861
|
+
];
|
|
1862
|
+
for (const file of files) {
|
|
1863
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1864
|
+
if (!/coreListings\./.test(content))
|
|
1865
|
+
continue;
|
|
1866
|
+
if (!/syndicatedTo(Ebay|Amazon|Tcgplayer|Preplo)|ListingId/.test(content))
|
|
1867
|
+
continue;
|
|
1868
|
+
writeCallPattern.lastIndex = 0;
|
|
1869
|
+
let match;
|
|
1870
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
1871
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
1872
|
+
if (!window)
|
|
1873
|
+
continue;
|
|
1874
|
+
for (const rule of rules) {
|
|
1875
|
+
const flagTrue = makeFlagTrue(rule.flag);
|
|
1876
|
+
if (!flagTrue.test(window))
|
|
1877
|
+
continue;
|
|
1878
|
+
const hasId = makeIdField(rule.id).test(window);
|
|
1879
|
+
const idEmpty = makeIdEmpty(rule.id).test(window);
|
|
1880
|
+
if (!hasId || idEmpty) {
|
|
1881
|
+
issues.push({
|
|
1882
|
+
file,
|
|
1883
|
+
line: 1,
|
|
1884
|
+
type: "syndication-missing-platform-id",
|
|
1885
|
+
severity: "error",
|
|
1886
|
+
message: `Invalid payload: ${rule.flag} is true but ${rule.id} is missing/empty`,
|
|
1887
|
+
suggestion: `Provide ${rule.id} when setting ${rule.flag}: true (or set ${rule.flag}: false).`,
|
|
1888
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1895
|
+
return {
|
|
1896
|
+
name: "Syndication Consistency",
|
|
1897
|
+
passed: !hasErrors,
|
|
1898
|
+
blocking: true,
|
|
1899
|
+
issues,
|
|
1900
|
+
duration: Date.now() - startTime,
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Check 16: Featured consistency (BLOCKING)
|
|
1905
|
+
*
|
|
1906
|
+
* Invariants (low false positives):
|
|
1907
|
+
* - If featuredUntil is set (non-null), featured must be true.
|
|
1908
|
+
* - If featured is explicitly false, featuredUntil must not be populated.
|
|
1909
|
+
*/
|
|
1910
|
+
async checkFeaturedConsistency() {
|
|
1911
|
+
const startTime = Date.now();
|
|
1912
|
+
const issues = [];
|
|
1913
|
+
const files = await getAppLibScriptsFiles();
|
|
1914
|
+
const writeCallPattern = /coreListings\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
1915
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
1916
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
1917
|
+
if (firstBrace === -1)
|
|
1918
|
+
return null;
|
|
1919
|
+
let depth = 0;
|
|
1920
|
+
let inSingle = false;
|
|
1921
|
+
let inDouble = false;
|
|
1922
|
+
let inTemplate = false;
|
|
1923
|
+
let inLineComment = false;
|
|
1924
|
+
let inBlockComment = false;
|
|
1925
|
+
let escaped = false;
|
|
1926
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
1927
|
+
const ch = content[i];
|
|
1928
|
+
const next = content[i + 1];
|
|
1929
|
+
if (inLineComment) {
|
|
1930
|
+
if (ch === "\n")
|
|
1931
|
+
inLineComment = false;
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
if (inBlockComment) {
|
|
1935
|
+
if (ch === "*" && next === "/") {
|
|
1936
|
+
inBlockComment = false;
|
|
1937
|
+
i++;
|
|
1938
|
+
}
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
1942
|
+
if (ch === "/" && next === "/") {
|
|
1943
|
+
inLineComment = true;
|
|
1944
|
+
i++;
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
if (ch === "/" && next === "*") {
|
|
1948
|
+
inBlockComment = true;
|
|
1949
|
+
i++;
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
if (escaped) {
|
|
1954
|
+
escaped = false;
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
1958
|
+
escaped = true;
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
1962
|
+
inSingle = !inSingle;
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
1966
|
+
inDouble = !inDouble;
|
|
1967
|
+
continue;
|
|
1968
|
+
}
|
|
1969
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
1970
|
+
inTemplate = !inTemplate;
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
if (inSingle || inDouble || inTemplate)
|
|
1974
|
+
continue;
|
|
1975
|
+
if (ch === "{") {
|
|
1976
|
+
depth++;
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
if (ch === "}") {
|
|
1980
|
+
depth--;
|
|
1981
|
+
if (depth === 0) {
|
|
1982
|
+
return content.slice(firstBrace, i + 1);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
return null;
|
|
1987
|
+
};
|
|
1988
|
+
const featuredTrue = /\bfeatured\s*:\s*true\b/;
|
|
1989
|
+
const featuredFalse = /\bfeatured\s*:\s*false\b/;
|
|
1990
|
+
const featuredUntilNonNull = /\bfeaturedUntil\s*:\s*(?!null\b|undefined\b)/;
|
|
1991
|
+
for (const file of files) {
|
|
1992
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1993
|
+
if (!/coreListings\./.test(content))
|
|
1994
|
+
continue;
|
|
1995
|
+
if (!/\bfeatured\b|\bfeaturedUntil\b/.test(content))
|
|
1996
|
+
continue;
|
|
1997
|
+
writeCallPattern.lastIndex = 0;
|
|
1998
|
+
let match;
|
|
1999
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
2000
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
2001
|
+
if (!window)
|
|
2002
|
+
continue;
|
|
2003
|
+
if (featuredUntilNonNull.test(window) && !featuredTrue.test(window)) {
|
|
2004
|
+
issues.push({
|
|
2005
|
+
file,
|
|
2006
|
+
line: 1,
|
|
2007
|
+
type: "featured-until-without-featured",
|
|
2008
|
+
severity: "error",
|
|
2009
|
+
message: "Invalid payload: featuredUntil is set but featured is not true",
|
|
2010
|
+
suggestion: "Set featured: true when setting featuredUntil (or clear featuredUntil).",
|
|
2011
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
if (featuredFalse.test(window) && featuredUntilNonNull.test(window)) {
|
|
2015
|
+
issues.push({
|
|
2016
|
+
file,
|
|
2017
|
+
line: 1,
|
|
2018
|
+
type: "featured-false-with-featureduntil",
|
|
2019
|
+
severity: "error",
|
|
2020
|
+
message: "Invalid payload: featured is false but featuredUntil is populated",
|
|
2021
|
+
suggestion: "Clear featuredUntil when featured is false (or set featured: true).",
|
|
2022
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
2028
|
+
return {
|
|
2029
|
+
name: "Featured Consistency",
|
|
2030
|
+
passed: !hasErrors,
|
|
2031
|
+
blocking: true,
|
|
2032
|
+
issues,
|
|
2033
|
+
duration: Date.now() - startTime,
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Check 17: Flagging consistency (BLOCKING)
|
|
2038
|
+
*
|
|
2039
|
+
* Invariants (low false positives):
|
|
2040
|
+
* - If isFlagged is explicitly false, flaggedAt/flaggedReason/flaggedBy must not be populated.
|
|
2041
|
+
* - If any flagged metadata is populated, isFlagged must be true.
|
|
2042
|
+
*/
|
|
2043
|
+
async checkFlaggingConsistency() {
|
|
2044
|
+
const startTime = Date.now();
|
|
2045
|
+
const issues = [];
|
|
2046
|
+
const files = await getAppLibScriptsFiles();
|
|
2047
|
+
const writeCallPattern = /coreListings\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
2048
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
2049
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
2050
|
+
if (firstBrace === -1)
|
|
2051
|
+
return null;
|
|
2052
|
+
let depth = 0;
|
|
2053
|
+
let inSingle = false;
|
|
2054
|
+
let inDouble = false;
|
|
2055
|
+
let inTemplate = false;
|
|
2056
|
+
let inLineComment = false;
|
|
2057
|
+
let inBlockComment = false;
|
|
2058
|
+
let escaped = false;
|
|
2059
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
2060
|
+
const ch = content[i];
|
|
2061
|
+
const next = content[i + 1];
|
|
2062
|
+
if (inLineComment) {
|
|
2063
|
+
if (ch === "\n")
|
|
2064
|
+
inLineComment = false;
|
|
2065
|
+
continue;
|
|
2066
|
+
}
|
|
2067
|
+
if (inBlockComment) {
|
|
2068
|
+
if (ch === "*" && next === "/") {
|
|
2069
|
+
inBlockComment = false;
|
|
2070
|
+
i++;
|
|
2071
|
+
}
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
2075
|
+
if (ch === "/" && next === "/") {
|
|
2076
|
+
inLineComment = true;
|
|
2077
|
+
i++;
|
|
2078
|
+
continue;
|
|
2079
|
+
}
|
|
2080
|
+
if (ch === "/" && next === "*") {
|
|
2081
|
+
inBlockComment = true;
|
|
2082
|
+
i++;
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
if (escaped) {
|
|
2087
|
+
escaped = false;
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
2091
|
+
escaped = true;
|
|
2092
|
+
continue;
|
|
2093
|
+
}
|
|
2094
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
2095
|
+
inSingle = !inSingle;
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
2099
|
+
inDouble = !inDouble;
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
2103
|
+
inTemplate = !inTemplate;
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
if (inSingle || inDouble || inTemplate)
|
|
2107
|
+
continue;
|
|
2108
|
+
if (ch === "{") {
|
|
2109
|
+
depth++;
|
|
2110
|
+
continue;
|
|
2111
|
+
}
|
|
2112
|
+
if (ch === "}") {
|
|
2113
|
+
depth--;
|
|
2114
|
+
if (depth === 0) {
|
|
2115
|
+
return content.slice(firstBrace, i + 1);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
return null;
|
|
2120
|
+
};
|
|
2121
|
+
const flaggedTrue = /\bisFlagged\s*:\s*true\b/;
|
|
2122
|
+
const flaggedFalse = /\bisFlagged\s*:\s*false\b/;
|
|
2123
|
+
const flaggedReasonNonNull = /\bflaggedReason\s*:\s*(?!null\b|undefined\b|''|""\b)/;
|
|
2124
|
+
const flaggedAtNonNull = /\bflaggedAt\s*:\s*(?!null\b|undefined\b)/;
|
|
2125
|
+
const flaggedByNonNull = /\bflaggedBy\s*:\s*(?!null\b|undefined\b|''|""\b)/;
|
|
2126
|
+
const anyFlagMetadata = (w) => flaggedReasonNonNull.test(w) || flaggedAtNonNull.test(w) || flaggedByNonNull.test(w);
|
|
2127
|
+
for (const file of files) {
|
|
2128
|
+
const content = fs.readFileSync(file, "utf8");
|
|
2129
|
+
if (!/coreListings\./.test(content))
|
|
2130
|
+
continue;
|
|
2131
|
+
if (!/isFlagged|flaggedReason|flaggedAt|flaggedBy/.test(content))
|
|
2132
|
+
continue;
|
|
2133
|
+
writeCallPattern.lastIndex = 0;
|
|
2134
|
+
let match;
|
|
2135
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
2136
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
2137
|
+
if (!window)
|
|
2138
|
+
continue;
|
|
2139
|
+
if (flaggedFalse.test(window) && anyFlagMetadata(window)) {
|
|
2140
|
+
issues.push({
|
|
2141
|
+
file,
|
|
2142
|
+
line: 1,
|
|
2143
|
+
type: "flag-metadata-while-unflagged",
|
|
2144
|
+
severity: "error",
|
|
2145
|
+
message: "Invalid payload: isFlagged is false but flagged metadata is populated",
|
|
2146
|
+
suggestion: "Clear flaggedReason/flaggedAt/flaggedBy when isFlagged is false (or set isFlagged: true).",
|
|
2147
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
if (anyFlagMetadata(window) && !flaggedTrue.test(window)) {
|
|
2151
|
+
issues.push({
|
|
2152
|
+
file,
|
|
2153
|
+
line: 1,
|
|
2154
|
+
type: "flag-metadata-without-flag",
|
|
2155
|
+
severity: "error",
|
|
2156
|
+
message: "Invalid payload: flagged metadata is populated but isFlagged is not true",
|
|
2157
|
+
suggestion: "Set isFlagged: true when setting flaggedReason/flaggedAt/flaggedBy (or clear the metadata).",
|
|
2158
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
2164
|
+
return {
|
|
2165
|
+
name: "Flagging Consistency",
|
|
2166
|
+
passed: !hasErrors,
|
|
2167
|
+
blocking: true,
|
|
2168
|
+
issues,
|
|
2169
|
+
duration: Date.now() - startTime,
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Check 18: Soft delete consistency (BLOCKING)
|
|
2174
|
+
*
|
|
2175
|
+
* Invariants (low false positives):
|
|
2176
|
+
* - If deletedBy is set (non-empty), deletedAt must be set (non-null).
|
|
2177
|
+
* - If deletedAt is explicitly null/undefined, deletedBy must not be populated.
|
|
2178
|
+
*/
|
|
2179
|
+
async checkSoftDeleteConsistency() {
|
|
2180
|
+
const startTime = Date.now();
|
|
2181
|
+
const issues = [];
|
|
2182
|
+
const files = await getAppLibScriptsFiles();
|
|
2183
|
+
const writeCallPattern = /coreListings\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
2184
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
2185
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
2186
|
+
if (firstBrace === -1)
|
|
2187
|
+
return null;
|
|
2188
|
+
let depth = 0;
|
|
2189
|
+
let inSingle = false;
|
|
2190
|
+
let inDouble = false;
|
|
2191
|
+
let inTemplate = false;
|
|
2192
|
+
let inLineComment = false;
|
|
2193
|
+
let inBlockComment = false;
|
|
2194
|
+
let escaped = false;
|
|
2195
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
2196
|
+
const ch = content[i];
|
|
2197
|
+
const next = content[i + 1];
|
|
2198
|
+
if (inLineComment) {
|
|
2199
|
+
if (ch === "\n")
|
|
2200
|
+
inLineComment = false;
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
if (inBlockComment) {
|
|
2204
|
+
if (ch === "*" && next === "/") {
|
|
2205
|
+
inBlockComment = false;
|
|
2206
|
+
i++;
|
|
2207
|
+
}
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
2211
|
+
if (ch === "/" && next === "/") {
|
|
2212
|
+
inLineComment = true;
|
|
2213
|
+
i++;
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2216
|
+
if (ch === "/" && next === "*") {
|
|
2217
|
+
inBlockComment = true;
|
|
2218
|
+
i++;
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
if (escaped) {
|
|
2223
|
+
escaped = false;
|
|
2224
|
+
continue;
|
|
2225
|
+
}
|
|
2226
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
2227
|
+
escaped = true;
|
|
2228
|
+
continue;
|
|
2229
|
+
}
|
|
2230
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
2231
|
+
inSingle = !inSingle;
|
|
2232
|
+
continue;
|
|
2233
|
+
}
|
|
2234
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
2235
|
+
inDouble = !inDouble;
|
|
2236
|
+
continue;
|
|
2237
|
+
}
|
|
2238
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
2239
|
+
inTemplate = !inTemplate;
|
|
2240
|
+
continue;
|
|
2241
|
+
}
|
|
2242
|
+
if (inSingle || inDouble || inTemplate)
|
|
2243
|
+
continue;
|
|
2244
|
+
if (ch === "{") {
|
|
2245
|
+
depth++;
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
2248
|
+
if (ch === "}") {
|
|
2249
|
+
depth--;
|
|
2250
|
+
if (depth === 0) {
|
|
2251
|
+
return content.slice(firstBrace, i + 1);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
return null;
|
|
2256
|
+
};
|
|
2257
|
+
const deletedByNonEmpty = /\bdeletedBy\s*:\s*(?!null\b|undefined\b|''|""\b)/;
|
|
2258
|
+
const deletedAtNonNull = /\bdeletedAt\s*:\s*(?!null\b|undefined\b)/;
|
|
2259
|
+
const deletedAtExplicitlyEmpty = /\bdeletedAt\s*:\s*(?:null|undefined)\b/;
|
|
2260
|
+
for (const file of files) {
|
|
2261
|
+
const content = fs.readFileSync(file, "utf8");
|
|
2262
|
+
if (!/coreListings\./.test(content))
|
|
2263
|
+
continue;
|
|
2264
|
+
if (!/deletedAt|deletedBy/.test(content))
|
|
2265
|
+
continue;
|
|
2266
|
+
writeCallPattern.lastIndex = 0;
|
|
2267
|
+
let match;
|
|
2268
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
2269
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
2270
|
+
if (!window)
|
|
2271
|
+
continue;
|
|
2272
|
+
if (deletedByNonEmpty.test(window) && !deletedAtNonNull.test(window)) {
|
|
2273
|
+
issues.push({
|
|
2274
|
+
file,
|
|
2275
|
+
line: 1,
|
|
2276
|
+
type: "deletedby-without-deletedat",
|
|
2277
|
+
severity: "error",
|
|
2278
|
+
message: "Invalid payload: deletedBy is set but deletedAt is not set",
|
|
2279
|
+
suggestion: "Set deletedAt when setting deletedBy (or clear deletedBy).",
|
|
2280
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
if (deletedAtExplicitlyEmpty.test(window) && deletedByNonEmpty.test(window)) {
|
|
2284
|
+
issues.push({
|
|
2285
|
+
file,
|
|
2286
|
+
line: 1,
|
|
2287
|
+
type: "deletedat-empty-with-deletedby",
|
|
2288
|
+
severity: "error",
|
|
2289
|
+
message: "Invalid payload: deletedAt is null/undefined but deletedBy is populated",
|
|
2290
|
+
suggestion: "Clear deletedBy when deletedAt is null/undefined (or set deletedAt to a timestamp).",
|
|
2291
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
2297
|
+
return {
|
|
2298
|
+
name: "Soft Delete Consistency",
|
|
2299
|
+
passed: !hasErrors,
|
|
2300
|
+
blocking: true,
|
|
2301
|
+
issues,
|
|
2302
|
+
duration: Date.now() - startTime,
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
/**
|
|
2306
|
+
* Check 19: Seller approval consistency (BLOCKING)
|
|
2307
|
+
*
|
|
2308
|
+
* Invariants (low false positives):
|
|
2309
|
+
* - If sellerApproved is true, sellerApprovedAt must be set (non-null).
|
|
2310
|
+
* - If sellerApprovedAt is set, sellerApproved must be true.
|
|
2311
|
+
* - If sellerApproved is explicitly false, sellerApprovedAt must not be populated.
|
|
2312
|
+
*/
|
|
2313
|
+
async checkSellerApprovalConsistency() {
|
|
2314
|
+
const startTime = Date.now();
|
|
2315
|
+
const issues = [];
|
|
2316
|
+
const files = await getAppLibScriptsFiles();
|
|
2317
|
+
const writeCallPattern = /coreListings\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
2318
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
2319
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
2320
|
+
if (firstBrace === -1)
|
|
2321
|
+
return null;
|
|
2322
|
+
let depth = 0;
|
|
2323
|
+
let inSingle = false;
|
|
2324
|
+
let inDouble = false;
|
|
2325
|
+
let inTemplate = false;
|
|
2326
|
+
let inLineComment = false;
|
|
2327
|
+
let inBlockComment = false;
|
|
2328
|
+
let escaped = false;
|
|
2329
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
2330
|
+
const ch = content[i];
|
|
2331
|
+
const next = content[i + 1];
|
|
2332
|
+
if (inLineComment) {
|
|
2333
|
+
if (ch === "\n")
|
|
2334
|
+
inLineComment = false;
|
|
2335
|
+
continue;
|
|
2336
|
+
}
|
|
2337
|
+
if (inBlockComment) {
|
|
2338
|
+
if (ch === "*" && next === "/") {
|
|
2339
|
+
inBlockComment = false;
|
|
2340
|
+
i++;
|
|
2341
|
+
}
|
|
2342
|
+
continue;
|
|
2343
|
+
}
|
|
2344
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
2345
|
+
if (ch === "/" && next === "/") {
|
|
2346
|
+
inLineComment = true;
|
|
2347
|
+
i++;
|
|
2348
|
+
continue;
|
|
2349
|
+
}
|
|
2350
|
+
if (ch === "/" && next === "*") {
|
|
2351
|
+
inBlockComment = true;
|
|
2352
|
+
i++;
|
|
2353
|
+
continue;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
if (escaped) {
|
|
2357
|
+
escaped = false;
|
|
2358
|
+
continue;
|
|
2359
|
+
}
|
|
2360
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
2361
|
+
escaped = true;
|
|
2362
|
+
continue;
|
|
2363
|
+
}
|
|
2364
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
2365
|
+
inSingle = !inSingle;
|
|
2366
|
+
continue;
|
|
2367
|
+
}
|
|
2368
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
2369
|
+
inDouble = !inDouble;
|
|
2370
|
+
continue;
|
|
2371
|
+
}
|
|
2372
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
2373
|
+
inTemplate = !inTemplate;
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
if (inSingle || inDouble || inTemplate)
|
|
2377
|
+
continue;
|
|
2378
|
+
if (ch === "{") {
|
|
2379
|
+
depth++;
|
|
2380
|
+
continue;
|
|
2381
|
+
}
|
|
2382
|
+
if (ch === "}") {
|
|
2383
|
+
depth--;
|
|
2384
|
+
if (depth === 0) {
|
|
2385
|
+
return content.slice(firstBrace, i + 1);
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
return null;
|
|
2390
|
+
};
|
|
2391
|
+
const approvedTrue = /\bsellerApproved\s*:\s*true\b/;
|
|
2392
|
+
const approvedFalse = /\bsellerApproved\s*:\s*false\b/;
|
|
2393
|
+
const approvedAtNonNull = /\bsellerApprovedAt\s*:\s*(?!null\b|undefined\b)/;
|
|
2394
|
+
for (const file of files) {
|
|
2395
|
+
const content = fs.readFileSync(file, "utf8");
|
|
2396
|
+
if (!/coreListings\./.test(content))
|
|
2397
|
+
continue;
|
|
2398
|
+
if (!/sellerApproved|sellerApprovedAt/.test(content))
|
|
2399
|
+
continue;
|
|
2400
|
+
writeCallPattern.lastIndex = 0;
|
|
2401
|
+
let match;
|
|
2402
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
2403
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
2404
|
+
if (!window)
|
|
2405
|
+
continue;
|
|
2406
|
+
if (approvedTrue.test(window) && !approvedAtNonNull.test(window)) {
|
|
2407
|
+
issues.push({
|
|
2408
|
+
file,
|
|
2409
|
+
line: 1,
|
|
2410
|
+
type: "seller-approved-missing-approvedat",
|
|
2411
|
+
severity: "error",
|
|
2412
|
+
message: "Invalid payload: sellerApproved is true but sellerApprovedAt is not set",
|
|
2413
|
+
suggestion: "Set sellerApprovedAt when setting sellerApproved: true (or set sellerApproved: false).",
|
|
2414
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
if (approvedAtNonNull.test(window) && !approvedTrue.test(window)) {
|
|
2418
|
+
issues.push({
|
|
2419
|
+
file,
|
|
2420
|
+
line: 1,
|
|
2421
|
+
type: "seller-approvedat-without-approved",
|
|
2422
|
+
severity: "error",
|
|
2423
|
+
message: "Invalid payload: sellerApprovedAt is set but sellerApproved is not true",
|
|
2424
|
+
suggestion: "Set sellerApproved: true when setting sellerApprovedAt (or clear sellerApprovedAt).",
|
|
2425
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
if (approvedFalse.test(window) && approvedAtNonNull.test(window)) {
|
|
2429
|
+
issues.push({
|
|
2430
|
+
file,
|
|
2431
|
+
line: 1,
|
|
2432
|
+
type: "seller-approved-false-with-approvedat",
|
|
2433
|
+
severity: "error",
|
|
2434
|
+
message: "Invalid payload: sellerApproved is false but sellerApprovedAt is populated",
|
|
2435
|
+
suggestion: "Clear sellerApprovedAt when sellerApproved is false (or set sellerApproved: true).",
|
|
2436
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
2442
|
+
return {
|
|
2443
|
+
name: "Seller Approval Consistency",
|
|
2444
|
+
passed: !hasErrors,
|
|
2445
|
+
blocking: true,
|
|
2446
|
+
issues,
|
|
2447
|
+
duration: Date.now() - startTime,
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* Check 20: AI recognition metadata consistency (BLOCKING)
|
|
2452
|
+
*
|
|
2453
|
+
* Invariants (low false positives):
|
|
2454
|
+
* - If recognitionConfidence is set, recognitionMethod must be set (non-empty).
|
|
2455
|
+
* - If recognitionJobId is set, recognitionMethod must be set (non-empty).
|
|
2456
|
+
*/
|
|
2457
|
+
async checkRecognitionMetadataConsistency() {
|
|
2458
|
+
const startTime = Date.now();
|
|
2459
|
+
const issues = [];
|
|
2460
|
+
const files = await getAppLibScriptsFiles();
|
|
2461
|
+
const writeCallPattern = /coreListings\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
2462
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
2463
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
2464
|
+
if (firstBrace === -1)
|
|
2465
|
+
return null;
|
|
2466
|
+
let depth = 0;
|
|
2467
|
+
let inSingle = false;
|
|
2468
|
+
let inDouble = false;
|
|
2469
|
+
let inTemplate = false;
|
|
2470
|
+
let inLineComment = false;
|
|
2471
|
+
let inBlockComment = false;
|
|
2472
|
+
let escaped = false;
|
|
2473
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
2474
|
+
const ch = content[i];
|
|
2475
|
+
const next = content[i + 1];
|
|
2476
|
+
if (inLineComment) {
|
|
2477
|
+
if (ch === "\n")
|
|
2478
|
+
inLineComment = false;
|
|
2479
|
+
continue;
|
|
2480
|
+
}
|
|
2481
|
+
if (inBlockComment) {
|
|
2482
|
+
if (ch === "*" && next === "/") {
|
|
2483
|
+
inBlockComment = false;
|
|
2484
|
+
i++;
|
|
2485
|
+
}
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
2489
|
+
if (ch === "/" && next === "/") {
|
|
2490
|
+
inLineComment = true;
|
|
2491
|
+
i++;
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
if (ch === "/" && next === "*") {
|
|
2495
|
+
inBlockComment = true;
|
|
2496
|
+
i++;
|
|
2497
|
+
continue;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
if (escaped) {
|
|
2501
|
+
escaped = false;
|
|
2502
|
+
continue;
|
|
2503
|
+
}
|
|
2504
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
2505
|
+
escaped = true;
|
|
2506
|
+
continue;
|
|
2507
|
+
}
|
|
2508
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
2509
|
+
inSingle = !inSingle;
|
|
2510
|
+
continue;
|
|
2511
|
+
}
|
|
2512
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
2513
|
+
inDouble = !inDouble;
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
2517
|
+
inTemplate = !inTemplate;
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
if (inSingle || inDouble || inTemplate)
|
|
2521
|
+
continue;
|
|
2522
|
+
if (ch === "{") {
|
|
2523
|
+
depth++;
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2526
|
+
if (ch === "}") {
|
|
2527
|
+
depth--;
|
|
2528
|
+
if (depth === 0) {
|
|
2529
|
+
return content.slice(firstBrace, i + 1);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
return null;
|
|
2534
|
+
};
|
|
2535
|
+
const confidenceNonNull = /\brecognitionConfidence\s*:\s*(?!null\b|undefined\b)/;
|
|
2536
|
+
const jobIdNonEmpty = /\brecognitionJobId\s*:\s*(?!null\b|undefined\b|''|""\b)/;
|
|
2537
|
+
const methodNonEmpty = /\brecognitionMethod\s*:\s*(?!null\b|undefined\b|''|""\b)/;
|
|
2538
|
+
for (const file of files) {
|
|
2539
|
+
const content = fs.readFileSync(file, "utf8");
|
|
2540
|
+
if (!/coreListings\./.test(content))
|
|
2541
|
+
continue;
|
|
2542
|
+
if (!/recognitionConfidence|recognitionJobId|recognitionMethod/.test(content))
|
|
2543
|
+
continue;
|
|
2544
|
+
writeCallPattern.lastIndex = 0;
|
|
2545
|
+
let match;
|
|
2546
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
2547
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
2548
|
+
if (!window)
|
|
2549
|
+
continue;
|
|
2550
|
+
const needsMethod = confidenceNonNull.test(window) || jobIdNonEmpty.test(window);
|
|
2551
|
+
if (needsMethod && !methodNonEmpty.test(window)) {
|
|
2552
|
+
issues.push({
|
|
2553
|
+
file,
|
|
2554
|
+
line: 1,
|
|
2555
|
+
type: "recognition-metadata-missing-method",
|
|
2556
|
+
severity: "error",
|
|
2557
|
+
message: "Invalid payload: recognitionConfidence/recognitionJobId is set but recognitionMethod is missing/empty",
|
|
2558
|
+
suggestion: "Set recognitionMethod when setting recognitionConfidence/recognitionJobId (or clear those fields).",
|
|
2559
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
2565
|
+
return {
|
|
2566
|
+
name: "Recognition Metadata Consistency",
|
|
2567
|
+
passed: !hasErrors,
|
|
2568
|
+
blocking: true,
|
|
2569
|
+
issues,
|
|
2570
|
+
duration: Date.now() - startTime,
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
/**
|
|
2574
|
+
* Check 21: Autograph grade-field consistency (BLOCKING)
|
|
2575
|
+
*
|
|
2576
|
+
* IMPORTANT: This is ONLY about autograph grading systems.
|
|
2577
|
+
* It is VALID to have a card grade + autograph grade at the same time:
|
|
2578
|
+
* - Example: psaGrade (card) + autographPsaGrade (PSA/DNA autograph) ${emoji.success}
|
|
2579
|
+
* - Example: bgsGrade (card) + autographBgsGrade (BGS autograph) ${emoji.success}
|
|
2580
|
+
*
|
|
2581
|
+
* Invariant:
|
|
2582
|
+
* - At most one of autographPsaGrade/autographBgsGrade/autographJsaGrade/autographSgcGrade
|
|
2583
|
+
* may be populated in a single coreListings write payload.
|
|
2584
|
+
*/
|
|
2585
|
+
async checkAutographGradeFieldConsistency() {
|
|
2586
|
+
const startTime = Date.now();
|
|
2587
|
+
const issues = [];
|
|
2588
|
+
const files = await getAppLibScriptsFiles();
|
|
2589
|
+
const writeCallPattern = /coreListings\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
2590
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
2591
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
2592
|
+
if (firstBrace === -1)
|
|
2593
|
+
return null;
|
|
2594
|
+
let depth = 0;
|
|
2595
|
+
let inSingle = false;
|
|
2596
|
+
let inDouble = false;
|
|
2597
|
+
let inTemplate = false;
|
|
2598
|
+
let inLineComment = false;
|
|
2599
|
+
let inBlockComment = false;
|
|
2600
|
+
let escaped = false;
|
|
2601
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
2602
|
+
const ch = content[i];
|
|
2603
|
+
const next = content[i + 1];
|
|
2604
|
+
if (inLineComment) {
|
|
2605
|
+
if (ch === "\n")
|
|
2606
|
+
inLineComment = false;
|
|
2607
|
+
continue;
|
|
2608
|
+
}
|
|
2609
|
+
if (inBlockComment) {
|
|
2610
|
+
if (ch === "*" && next === "/") {
|
|
2611
|
+
inBlockComment = false;
|
|
2612
|
+
i++;
|
|
2613
|
+
}
|
|
2614
|
+
continue;
|
|
2615
|
+
}
|
|
2616
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
2617
|
+
if (ch === "/" && next === "/") {
|
|
2618
|
+
inLineComment = true;
|
|
2619
|
+
i++;
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
if (ch === "/" && next === "*") {
|
|
2623
|
+
inBlockComment = true;
|
|
2624
|
+
i++;
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
if (escaped) {
|
|
2629
|
+
escaped = false;
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
2633
|
+
escaped = true;
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
2637
|
+
inSingle = !inSingle;
|
|
2638
|
+
continue;
|
|
2639
|
+
}
|
|
2640
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
2641
|
+
inDouble = !inDouble;
|
|
2642
|
+
continue;
|
|
2643
|
+
}
|
|
2644
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
2645
|
+
inTemplate = !inTemplate;
|
|
2646
|
+
continue;
|
|
2647
|
+
}
|
|
2648
|
+
if (inSingle || inDouble || inTemplate)
|
|
2649
|
+
continue;
|
|
2650
|
+
if (ch === "{") {
|
|
2651
|
+
depth++;
|
|
2652
|
+
continue;
|
|
2653
|
+
}
|
|
2654
|
+
if (ch === "}") {
|
|
2655
|
+
depth--;
|
|
2656
|
+
if (depth === 0) {
|
|
2657
|
+
return content.slice(firstBrace, i + 1);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
return null;
|
|
2662
|
+
};
|
|
2663
|
+
const hasNonNullAutographPsaGrade = /\bautographPsaGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
2664
|
+
const hasNonNullAutographBgsGrade = /\bautographBgsGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
2665
|
+
const hasNonNullAutographJsaGrade = /\bautographJsaGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
2666
|
+
const hasNonNullAutographSgcGrade = /\bautographSgcGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
2667
|
+
const countAutographGradeSystems = (w) => {
|
|
2668
|
+
let count = 0;
|
|
2669
|
+
if (hasNonNullAutographPsaGrade.test(w))
|
|
2670
|
+
count++;
|
|
2671
|
+
if (hasNonNullAutographBgsGrade.test(w))
|
|
2672
|
+
count++;
|
|
2673
|
+
if (hasNonNullAutographJsaGrade.test(w))
|
|
2674
|
+
count++;
|
|
2675
|
+
if (hasNonNullAutographSgcGrade.test(w))
|
|
2676
|
+
count++;
|
|
2677
|
+
return count;
|
|
2678
|
+
};
|
|
2679
|
+
for (const file of files) {
|
|
2680
|
+
const content = fs.readFileSync(file, "utf8");
|
|
2681
|
+
if (!/coreListings\./.test(content))
|
|
2682
|
+
continue;
|
|
2683
|
+
if (!/autographPsaGrade|autographBgsGrade|autographJsaGrade|autographSgcGrade/.test(content))
|
|
2684
|
+
continue;
|
|
2685
|
+
writeCallPattern.lastIndex = 0;
|
|
2686
|
+
let match;
|
|
2687
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
2688
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
2689
|
+
if (!window)
|
|
2690
|
+
continue;
|
|
2691
|
+
const gradeSystemsSet = countAutographGradeSystems(window);
|
|
2692
|
+
if (gradeSystemsSet <= 1)
|
|
2693
|
+
continue;
|
|
2694
|
+
issues.push({
|
|
2695
|
+
file,
|
|
2696
|
+
line: 1,
|
|
2697
|
+
type: "autograph-grade-field-mismatch",
|
|
2698
|
+
severity: "error",
|
|
2699
|
+
message: "Invalid payload: multiple autograph grade systems are populated in one write (PSA/BGS/JSA/SGC)",
|
|
2700
|
+
suggestion: "Only set one of autographPsaGrade/autographBgsGrade/autographJsaGrade/autographSgcGrade per listing. Card grade fields (psaGrade/bgsGrade/cgcGrade/sgcGrade) are separate and can coexist with a single autograph grade.",
|
|
2701
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
2706
|
+
return {
|
|
2707
|
+
name: "Autograph Grade Field Consistency",
|
|
2708
|
+
passed: !hasErrors,
|
|
2709
|
+
blocking: true,
|
|
2710
|
+
issues,
|
|
2711
|
+
duration: Date.now() - startTime,
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2715
|
+
* Check 22: Autograph grading metadata consistency (BLOCKING)
|
|
2716
|
+
*
|
|
2717
|
+
* Best-practice invariants (low false positives):
|
|
2718
|
+
* - If an autograph numeric grade is provided (autograph*Grade), `autographGraded` must also be set.
|
|
2719
|
+
* - If `autographGraded` is a string literal, it must match the grading system used.
|
|
2720
|
+
*
|
|
2721
|
+
* NOTE: This does NOT require the card itself to be graded. It's valid for:
|
|
2722
|
+
* - graded: UNGRADED + autographPsaGrade set ${emoji.success}
|
|
2723
|
+
*/
|
|
2724
|
+
async checkAutographGradingMetadataConsistency() {
|
|
2725
|
+
const startTime = Date.now();
|
|
2726
|
+
const issues = [];
|
|
2727
|
+
const files = await getAppLibScriptsFiles();
|
|
2728
|
+
const writeCallPattern = /coreListings\.(?:create|createMany|upsert|update|updateMany)\s*\(/g;
|
|
2729
|
+
const extractCallObjectLiteral = (content, callStartIndex) => {
|
|
2730
|
+
const firstBrace = content.indexOf("{", callStartIndex);
|
|
2731
|
+
if (firstBrace === -1)
|
|
2732
|
+
return null;
|
|
2733
|
+
let depth = 0;
|
|
2734
|
+
let inSingle = false;
|
|
2735
|
+
let inDouble = false;
|
|
2736
|
+
let inTemplate = false;
|
|
2737
|
+
let inLineComment = false;
|
|
2738
|
+
let inBlockComment = false;
|
|
2739
|
+
let escaped = false;
|
|
2740
|
+
for (let i = firstBrace; i < content.length; i++) {
|
|
2741
|
+
const ch = content[i];
|
|
2742
|
+
const next = content[i + 1];
|
|
2743
|
+
if (inLineComment) {
|
|
2744
|
+
if (ch === "\n")
|
|
2745
|
+
inLineComment = false;
|
|
2746
|
+
continue;
|
|
2747
|
+
}
|
|
2748
|
+
if (inBlockComment) {
|
|
2749
|
+
if (ch === "*" && next === "/") {
|
|
2750
|
+
inBlockComment = false;
|
|
2751
|
+
i++;
|
|
2752
|
+
}
|
|
2753
|
+
continue;
|
|
2754
|
+
}
|
|
2755
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
2756
|
+
if (ch === "/" && next === "/") {
|
|
2757
|
+
inLineComment = true;
|
|
2758
|
+
i++;
|
|
2759
|
+
continue;
|
|
2760
|
+
}
|
|
2761
|
+
if (ch === "/" && next === "*") {
|
|
2762
|
+
inBlockComment = true;
|
|
2763
|
+
i++;
|
|
2764
|
+
continue;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
if (escaped) {
|
|
2768
|
+
escaped = false;
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
2772
|
+
escaped = true;
|
|
2773
|
+
continue;
|
|
2774
|
+
}
|
|
2775
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
2776
|
+
inSingle = !inSingle;
|
|
2777
|
+
continue;
|
|
2778
|
+
}
|
|
2779
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
2780
|
+
inDouble = !inDouble;
|
|
2781
|
+
continue;
|
|
2782
|
+
}
|
|
2783
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
2784
|
+
inTemplate = !inTemplate;
|
|
2785
|
+
continue;
|
|
2786
|
+
}
|
|
2787
|
+
if (inSingle || inDouble || inTemplate)
|
|
2788
|
+
continue;
|
|
2789
|
+
if (ch === "{") {
|
|
2790
|
+
depth++;
|
|
2791
|
+
continue;
|
|
2792
|
+
}
|
|
2793
|
+
if (ch === "}") {
|
|
2794
|
+
depth--;
|
|
2795
|
+
if (depth === 0) {
|
|
2796
|
+
return content.slice(firstBrace, i + 1);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
return null;
|
|
2801
|
+
};
|
|
2802
|
+
const hasNonNullAutographPsaGrade = /\bautographPsaGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
2803
|
+
const hasNonNullAutographBgsGrade = /\bautographBgsGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
2804
|
+
const hasNonNullAutographJsaGrade = /\bautographJsaGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
2805
|
+
const hasNonNullAutographSgcGrade = /\bautographSgcGrade\s*:\s*(?!null\b|undefined\b)/;
|
|
2806
|
+
const anyAutographGrade = (w) => hasNonNullAutographPsaGrade.test(w) ||
|
|
2807
|
+
hasNonNullAutographBgsGrade.test(w) ||
|
|
2808
|
+
hasNonNullAutographJsaGrade.test(w) ||
|
|
2809
|
+
hasNonNullAutographSgcGrade.test(w);
|
|
2810
|
+
const autographGradedNonEmpty = /\bautographGraded\s*:\s*(?!null\b|undefined\b|''|""\b)/;
|
|
2811
|
+
const autographGradedLiteral = /\bautographGraded\s*:\s*['"]([^'\"]+)['"]/;
|
|
2812
|
+
for (const file of files) {
|
|
2813
|
+
const content = fs.readFileSync(file, "utf8");
|
|
2814
|
+
if (!/coreListings\./.test(content))
|
|
2815
|
+
continue;
|
|
2816
|
+
if (!/autographGraded|autographPsaGrade|autographBgsGrade|autographJsaGrade|autographSgcGrade/.test(content))
|
|
2817
|
+
continue;
|
|
2818
|
+
writeCallPattern.lastIndex = 0;
|
|
2819
|
+
let match;
|
|
2820
|
+
while ((match = writeCallPattern.exec(content)) !== null) {
|
|
2821
|
+
const window = extractCallObjectLiteral(content, match.index);
|
|
2822
|
+
if (!window)
|
|
2823
|
+
continue;
|
|
2824
|
+
if (!anyAutographGrade(window))
|
|
2825
|
+
continue;
|
|
2826
|
+
if (!autographGradedNonEmpty.test(window)) {
|
|
2827
|
+
issues.push({
|
|
2828
|
+
file,
|
|
2829
|
+
line: 1,
|
|
2830
|
+
type: "autograph-grade-missing-autographgraded",
|
|
2831
|
+
severity: "error",
|
|
2832
|
+
message: "Invalid payload: autograph grade is set but autographGraded is missing/empty",
|
|
2833
|
+
suggestion: 'Set autographGraded (e.g., "PSA/DNA", "BGS", "JSA", "SGC") when setting autographPsaGrade/autographBgsGrade/autographJsaGrade/autographSgcGrade.',
|
|
2834
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2835
|
+
});
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2838
|
+
const lit = window.match(autographGradedLiteral);
|
|
2839
|
+
if (!lit)
|
|
2840
|
+
continue;
|
|
2841
|
+
const value = (lit[1] ?? "").toLowerCase();
|
|
2842
|
+
const expectsPsa = hasNonNullAutographPsaGrade.test(window);
|
|
2843
|
+
const expectsBgs = hasNonNullAutographBgsGrade.test(window);
|
|
2844
|
+
const expectsJsa = hasNonNullAutographJsaGrade.test(window);
|
|
2845
|
+
const expectsSgc = hasNonNullAutographSgcGrade.test(window);
|
|
2846
|
+
const matchesPsa = /psa|dna/.test(value);
|
|
2847
|
+
const matchesBgs = /bgs|beckett/.test(value);
|
|
2848
|
+
const matchesJsa = /jsa/.test(value);
|
|
2849
|
+
const matchesSgc = /sgc/.test(value);
|
|
2850
|
+
const mismatch = (expectsPsa && !matchesPsa) ||
|
|
2851
|
+
(expectsBgs && !matchesBgs) ||
|
|
2852
|
+
(expectsJsa && !matchesJsa) ||
|
|
2853
|
+
(expectsSgc && !matchesSgc);
|
|
2854
|
+
if (mismatch) {
|
|
2855
|
+
issues.push({
|
|
2856
|
+
file,
|
|
2857
|
+
line: 1,
|
|
2858
|
+
type: "autograph-grade-autographgraded-mismatch",
|
|
2859
|
+
severity: "error",
|
|
2860
|
+
message: "Invalid payload: autograph grade system does not match autographGraded",
|
|
2861
|
+
suggestion: "Align autographGraded with the autograph grade field being set (PSA/DNA ↔ autographPsaGrade, BGS ↔ autographBgsGrade, JSA ↔ autographJsaGrade, SGC ↔ autographSgcGrade).",
|
|
2862
|
+
snippet: window.replace(/\s+/g, " ").trim().substring(0, 180),
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
2868
|
+
return {
|
|
2869
|
+
name: "Autograph Grading Metadata Consistency",
|
|
2870
|
+
passed: !hasErrors,
|
|
2871
|
+
blocking: true,
|
|
2872
|
+
issues,
|
|
2873
|
+
duration: Date.now() - startTime,
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Run all hardened checks
|
|
2878
|
+
*/
|
|
2879
|
+
async runAll() {
|
|
2880
|
+
const startTime = Date.now();
|
|
2881
|
+
console.log(`\n${console_chars_1.emoji.shield} HARDENED PREFLIGHT CHECKS`);
|
|
2882
|
+
console.log((0, console_chars_1.createDivider)(80, "heavy"));
|
|
2883
|
+
const checks = [];
|
|
2884
|
+
// BLOCKING CHECKS
|
|
2885
|
+
checks.push(await this.checkEslintConcurrencyEnforcement());
|
|
2886
|
+
checks.push(await this.checkDraftStatusEnforcement());
|
|
2887
|
+
checks.push(await this.checkAsyncErrorHandling());
|
|
2888
|
+
checks.push(await this.checkPrismaEnumLiterals());
|
|
2889
|
+
checks.push(await this.checkCardGradedSemantics());
|
|
2890
|
+
checks.push(await this.checkCardGradeFieldConsistency());
|
|
2891
|
+
checks.push(await this.checkSerialNumberConsistency());
|
|
2892
|
+
checks.push(await this.checkAutographConsistency());
|
|
2893
|
+
checks.push(await this.checkVideoGameGradingConsistency());
|
|
2894
|
+
checks.push(await this.checkSyndicationConsistency());
|
|
2895
|
+
checks.push(await this.checkFeaturedConsistency());
|
|
2896
|
+
checks.push(await this.checkFlaggingConsistency());
|
|
2897
|
+
checks.push(await this.checkSoftDeleteConsistency());
|
|
2898
|
+
checks.push(await this.checkSellerApprovalConsistency());
|
|
2899
|
+
checks.push(await this.checkRecognitionMetadataConsistency());
|
|
2900
|
+
checks.push(await this.checkAutographGradeFieldConsistency());
|
|
2901
|
+
checks.push(await this.checkAutographGradingMetadataConsistency());
|
|
2902
|
+
checks.push(await this.checkSecretDetection());
|
|
2903
|
+
checks.push(await this.checkReactHookRules());
|
|
2904
|
+
// WARNING CHECKS
|
|
2905
|
+
checks.push(await this.checkConsoleCleanup());
|
|
2906
|
+
checks.push(await this.checkFloatingPromises());
|
|
2907
|
+
checks.push(await this.checkCvaEnforcement());
|
|
2908
|
+
checks.push(await this.checkTypeSafety());
|
|
2909
|
+
const totalDuration = Date.now() - startTime;
|
|
2910
|
+
const allIssues = checks.flatMap((c) => c.issues);
|
|
2911
|
+
const hasBlockingFailure = checks.some((c) => !c.passed && c.blocking);
|
|
2912
|
+
const summary = {
|
|
2913
|
+
total: checks.length,
|
|
2914
|
+
passed: checks.filter((c) => c.passed).length,
|
|
2915
|
+
failed: checks.filter((c) => !c.passed).length,
|
|
2916
|
+
errors: allIssues.filter((i) => i.severity === "error").length,
|
|
2917
|
+
warnings: allIssues.filter((i) => i.severity === "warning").length,
|
|
2918
|
+
};
|
|
2919
|
+
this.printResults(checks, totalDuration, summary, hasBlockingFailure);
|
|
2920
|
+
return {
|
|
2921
|
+
module: "hardened",
|
|
2922
|
+
passed: !hasBlockingFailure,
|
|
2923
|
+
totalDuration,
|
|
2924
|
+
checks,
|
|
2925
|
+
summary,
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
printResults(checks, totalDuration, summary, hasBlockingFailure) {
|
|
2929
|
+
console.log("\n" + (0, console_chars_1.createDivider)(80, "heavy"));
|
|
2930
|
+
console.log(`${console_chars_1.emoji.chart} HARDENED CHECK RESULTS`);
|
|
2931
|
+
console.log((0, console_chars_1.createDivider)(80, "heavy"));
|
|
2932
|
+
checks.forEach((check) => {
|
|
2933
|
+
const icon = check.passed
|
|
2934
|
+
? `${console_chars_1.emoji.success}`
|
|
2935
|
+
: check.blocking
|
|
2936
|
+
? `${console_chars_1.emoji.error}`
|
|
2937
|
+
: `${console_chars_1.emoji.warning}`;
|
|
2938
|
+
const count = check.issues.length > 0 ? ` (${check.issues.length} issues)` : "";
|
|
2939
|
+
const blocking = check.blocking ? " [BLOCKING]" : "";
|
|
2940
|
+
console.log(`${icon} ${check.name.padEnd(35)} ${(check.duration / 1000).toFixed(1)}s${count}${blocking}`);
|
|
2941
|
+
// Show first few issues
|
|
2942
|
+
if (check.issues.length > 0 && (this.verbose || !check.passed)) {
|
|
2943
|
+
const showIssues = check.issues.slice(0, this.verbose ? 10 : 3);
|
|
2944
|
+
showIssues.forEach((issue) => {
|
|
2945
|
+
const loc = issue.line ? `:${issue.line}` : "";
|
|
2946
|
+
console.log(` ${issue.severity === "error" ? "${emoji.error}" : "${emoji.warning}"} ${issue.file}${loc}`);
|
|
2947
|
+
console.log(` ${issue.message}`);
|
|
2948
|
+
if (issue.suggestion) {
|
|
2949
|
+
console.log(` ${console_chars_1.emoji.hint} ${issue.suggestion}`);
|
|
2950
|
+
}
|
|
2951
|
+
});
|
|
2952
|
+
if (check.issues.length > showIssues.length) {
|
|
2953
|
+
console.log(` ... and ${check.issues.length - showIssues.length} more`);
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
});
|
|
2957
|
+
console.log((0, console_chars_1.createDivider)(80, "heavy"));
|
|
2958
|
+
console.log(`Total Checks: ${summary.total} | Passed: ${summary.passed} | Failed: ${summary.failed}`);
|
|
2959
|
+
console.log(`Errors: ${summary.errors} | Warnings: ${summary.warnings}`);
|
|
2960
|
+
console.log(`Time: ${(totalDuration / 1000).toFixed(1)}s`);
|
|
2961
|
+
console.log((0, console_chars_1.createDivider)(80, "heavy"));
|
|
2962
|
+
if (hasBlockingFailure) {
|
|
2963
|
+
console.log(`\n${console_chars_1.emoji.error} HARDENED CHECKS FAILED`);
|
|
2964
|
+
console.log(" Fix blocking errors before deployment\n");
|
|
2965
|
+
}
|
|
2966
|
+
else if (summary.warnings > 0) {
|
|
2967
|
+
console.log(`\n${console_chars_1.emoji.warning} HARDENED CHECKS PASSED (with warnings)\n`);
|
|
2968
|
+
}
|
|
2969
|
+
else {
|
|
2970
|
+
console.log(`\n${console_chars_1.emoji.success} HARDENED CHECKS PASSED\n`);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
exports.HardenedPreflightModule = HardenedPreflightModule;
|
|
2975
|
+
// CLI
|
|
2976
|
+
async function main() {
|
|
2977
|
+
const reporter = (0, universal_progress_reporter_1.createUniversalProgressReporter)(path.basename(__filename, ".ts"));
|
|
2978
|
+
const args = process.argv.slice(2);
|
|
2979
|
+
const mode = args[0] || "all";
|
|
2980
|
+
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
2981
|
+
const parallel = args.includes("--parallel") || args.includes("-p");
|
|
2982
|
+
const module = new HardenedPreflightModule({ verbose, parallel });
|
|
2983
|
+
const runSingle = async (name, fn) => {
|
|
2984
|
+
const result = await fn();
|
|
2985
|
+
const icon = result.passed
|
|
2986
|
+
? `${console_chars_1.emoji.success}`
|
|
2987
|
+
: result.blocking
|
|
2988
|
+
? `${console_chars_1.emoji.error}`
|
|
2989
|
+
: `${console_chars_1.emoji.warning}`;
|
|
2990
|
+
console.log(`\n${icon} ${result.name}: ${result.issues.length} issues`);
|
|
2991
|
+
if (result.issues.length > 0) {
|
|
2992
|
+
const showCount = verbose ? result.issues.length : 5;
|
|
2993
|
+
result.issues.slice(0, showCount).forEach((i) => {
|
|
2994
|
+
console.log(` ${i.file}${i.line ? `:${i.line}` : ""} - ${i.message}`);
|
|
2995
|
+
});
|
|
2996
|
+
if (!verbose && result.issues.length > 5) {
|
|
2997
|
+
console.log(` ... and ${result.issues.length - 5} more`);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
return {
|
|
3001
|
+
module: `hardened:${name}`,
|
|
3002
|
+
passed: result.passed || !result.blocking,
|
|
3003
|
+
totalDuration: result.duration,
|
|
3004
|
+
checks: [result],
|
|
3005
|
+
summary: {
|
|
3006
|
+
total: 1,
|
|
3007
|
+
passed: result.passed ? 1 : 0,
|
|
3008
|
+
failed: result.passed ? 0 : 1,
|
|
3009
|
+
errors: result.issues.filter((i) => i.severity === "error").length,
|
|
3010
|
+
warnings: result.issues.filter((i) => i.severity === "warning").length,
|
|
3011
|
+
},
|
|
3012
|
+
};
|
|
3013
|
+
};
|
|
3014
|
+
let result;
|
|
3015
|
+
switch (mode) {
|
|
3016
|
+
case "eslint-concurrency":
|
|
3017
|
+
result = await runSingle("eslint-concurrency", () => module.checkEslintConcurrencyEnforcement());
|
|
3018
|
+
break;
|
|
3019
|
+
case "draft":
|
|
3020
|
+
result = await runSingle("draft", () => module.checkDraftStatusEnforcement());
|
|
3021
|
+
break;
|
|
3022
|
+
case "async":
|
|
3023
|
+
result = await runSingle("async", () => module.checkAsyncErrorHandling());
|
|
3024
|
+
break;
|
|
3025
|
+
case "console":
|
|
3026
|
+
result = await runSingle("console", () => module.checkConsoleCleanup());
|
|
3027
|
+
break;
|
|
3028
|
+
case "secrets":
|
|
3029
|
+
result = await runSingle("secrets", () => module.checkSecretDetection());
|
|
3030
|
+
break;
|
|
3031
|
+
case "hooks":
|
|
3032
|
+
result = await runSingle("hooks", () => module.checkReactHookRules());
|
|
3033
|
+
break;
|
|
3034
|
+
case "enums":
|
|
3035
|
+
case "prisma-enums":
|
|
3036
|
+
result = await runSingle("prisma-enums", () => module.checkPrismaEnumLiterals());
|
|
3037
|
+
break;
|
|
3038
|
+
case "promises":
|
|
3039
|
+
result = await runSingle("promises", () => module.checkFloatingPromises());
|
|
3040
|
+
break;
|
|
3041
|
+
case "cva":
|
|
3042
|
+
result = await runSingle("cva", () => module.checkCvaEnforcement());
|
|
3043
|
+
break;
|
|
3044
|
+
case "types":
|
|
3045
|
+
result = await runSingle("types", () => module.checkTypeSafety());
|
|
3046
|
+
break;
|
|
3047
|
+
default:
|
|
3048
|
+
result = await module.runAll();
|
|
3049
|
+
break;
|
|
3050
|
+
}
|
|
3051
|
+
process.exit(result.passed ? 0 : 1);
|
|
3052
|
+
}
|
|
3053
|
+
if (require.main === module) {
|
|
3054
|
+
main();
|
|
3055
|
+
}
|
|
3056
|
+
//# sourceMappingURL=hardened-checks.js.map
|