@nextsparkjs/core 0.1.0-beta.189 → 0.1.0-beta.190
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/components/api/keys/CreateApiKeyDialog.d.ts.map +1 -1
- package/dist/components/api/keys/CreateApiKeyDialog.js +2 -1
- package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicFooter.js +2 -23
- package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
- package/dist/components/app/misc/ThemeToggle.js +2 -1
- package/dist/components/auth/forms/LoginForm.js +1 -1
- package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
- package/dist/components/auth/forms/SignupForm.js +2 -1
- package/dist/components/auth/pages/AuthErrorPage.d.ts.map +1 -1
- package/dist/components/auth/pages/AuthErrorPage.js +2 -1
- package/dist/components/billing/ManageBillingButton.d.ts.map +1 -1
- package/dist/components/billing/ManageBillingButton.js +2 -1
- package/dist/components/dashboard/block-editor/array-field.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/array-field.js +2 -1
- package/dist/components/dashboard/block-editor/block-picker.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/block-picker.js +2 -1
- package/dist/components/dashboard/block-editor/builder-editor-view.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/builder-editor-view.js +3 -2
- package/dist/components/dashboard/block-editor/config-panel.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/config-panel.js +3 -2
- package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/dynamic-form.js +2 -1
- package/dist/components/dashboard/block-editor/entity-fields-sidebar.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/entity-fields-sidebar.js +3 -2
- package/dist/components/dashboard/block-editor/pattern-reference-preview.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/pattern-reference-preview.js +2 -1
- package/dist/components/dashboard/layouts/TopNavbar.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/TopNavbar.js +4 -3
- package/dist/components/dashboard/misc/SearchDropdown.d.ts.map +1 -1
- package/dist/components/dashboard/misc/SearchDropdown.js +2 -1
- package/dist/components/dashboard/mobile/MobileTopBar.d.ts.map +1 -1
- package/dist/components/dashboard/mobile/MobileTopBar.js +2 -1
- package/dist/components/devtools/BlockDetailViewer.d.ts.map +1 -1
- package/dist/components/devtools/BlockDetailViewer.js +2 -1
- package/dist/components/devtools/BlocksViewer.d.ts.map +1 -1
- package/dist/components/devtools/BlocksViewer.js +2 -1
- package/dist/components/devtools/ConfigViewer.d.ts.map +1 -1
- package/dist/components/devtools/ConfigViewer.js +3 -2
- package/dist/components/devtools/TestCasesViewer.d.ts.map +1 -1
- package/dist/components/devtools/TestCasesViewer.js +3 -2
- package/dist/components/devtools/TestCoverageDashboard.d.ts.map +1 -1
- package/dist/components/devtools/TestCoverageDashboard.js +2 -1
- package/dist/components/devtools/api-explorer/ApiDocsModal.d.ts.map +1 -1
- package/dist/components/devtools/api-explorer/ApiDocsModal.js +0 -0
- package/dist/components/devtools/api-explorer/ApiExplorer.d.ts.map +1 -1
- package/dist/components/devtools/api-explorer/ApiExplorer.js +2 -1
- package/dist/components/devtools/scheduled-actions/actions-table.d.ts.map +1 -1
- package/dist/components/devtools/scheduled-actions/actions-table.js +3 -2
- package/dist/components/docs/docs-sidebar.d.ts.map +1 -1
- package/dist/components/docs/docs-sidebar.js +3 -2
- package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
- package/dist/components/entities/EntityFieldRenderer.js +2 -1
- package/dist/components/entities/EntityList.d.ts.map +1 -1
- package/dist/components/entities/EntityList.js +2 -1
- package/dist/components/entities/EntityTable.d.ts.map +1 -1
- package/dist/components/entities/EntityTable.js +3 -2
- package/dist/components/entities/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/entities/ErrorBoundary.js +2 -1
- package/dist/components/entities/wrappers/EntityDetailWrapper.d.ts.map +1 -1
- package/dist/components/entities/wrappers/EntityDetailWrapper.js +3 -2
- package/dist/components/media/MediaCard.d.ts.map +1 -1
- package/dist/components/media/MediaCard.js +2 -1
- package/dist/components/media/MediaDetailPanel.d.ts.map +1 -1
- package/dist/components/media/MediaDetailPanel.js +2 -1
- package/dist/components/media/MediaList.d.ts.map +1 -1
- package/dist/components/media/MediaList.js +2 -1
- package/dist/components/media/MediaSelector.d.ts.map +1 -1
- package/dist/components/media/MediaSelector.js +2 -1
- package/dist/components/media/MediaUploadZone.d.ts.map +1 -1
- package/dist/components/media/MediaUploadZone.js +2 -1
- package/dist/components/public/entities/PublicEntityDetail.d.ts.map +1 -1
- package/dist/components/public/entities/PublicEntityDetail.js +2 -1
- package/dist/components/teams/TeamPendingInvitations.d.ts.map +1 -1
- package/dist/components/teams/TeamPendingInvitations.js +3 -2
- package/dist/components/ui/accordion.js +1 -1
- package/dist/components/ui/audio-upload.d.ts.map +1 -1
- package/dist/components/ui/audio-upload.js +2 -1
- package/dist/components/ui/avatar.d.ts +17 -1
- package/dist/components/ui/avatar.d.ts.map +1 -1
- package/dist/components/ui/avatar.js +29 -2
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/breadcrumb.d.ts +13 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -1
- package/dist/components/ui/breadcrumb.js +23 -10
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/checkbox.js +1 -1
- package/dist/components/ui/file-upload.d.ts.map +1 -1
- package/dist/components/ui/file-upload.js +2 -1
- package/dist/components/ui/image-upload.d.ts.map +1 -1
- package/dist/components/ui/image-upload.js +4 -3
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/label.js +1 -1
- package/dist/components/ui/optimized-image.d.ts.map +1 -1
- package/dist/components/ui/optimized-image.js +5 -0
- package/dist/components/ui/pagination.d.ts +1 -1
- package/dist/components/ui/pagination.d.ts.map +1 -1
- package/dist/components/ui/pagination.js +3 -0
- package/dist/components/ui/progress.js +1 -1
- package/dist/components/ui/relation-display.d.ts.map +1 -1
- package/dist/components/ui/relation-display.js +3 -2
- package/dist/components/ui/separator.js +1 -1
- package/dist/components/ui/simple-relation-select.d.ts.map +1 -1
- package/dist/components/ui/simple-relation-select.js +7 -6
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/slider.js +1 -1
- package/dist/components/ui/switch.js +1 -1
- package/dist/components/ui/tabs.js +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/user-display.d.ts.map +1 -1
- package/dist/components/ui/user-display.js +2 -1
- package/dist/components/ui/user-select.d.ts.map +1 -1
- package/dist/components/ui/user-select.js +2 -1
- package/dist/components/ui/video-upload.d.ts.map +1 -1
- package/dist/components/ui/video-upload.js +3 -2
- package/dist/components/users/tables/UsersTable.d.ts.map +1 -1
- package/dist/components/users/tables/UsersTable.js +3 -2
- package/dist/contexts/SubscriptionContext.d.ts.map +1 -1
- package/dist/contexts/SubscriptionContext.js +4 -3
- package/dist/contexts/TeamContext.d.ts.map +1 -1
- package/dist/contexts/TeamContext.js +3 -2
- package/dist/hooks/useAccountPreferencesSync.d.ts.map +1 -1
- package/dist/hooks/useAccountPreferencesSync.js +2 -1
- package/dist/hooks/useApiExplorerNavigation.d.ts.map +1 -1
- package/dist/hooks/useApiExplorerNavigation.js +2 -1
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +3 -2
- package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
- package/dist/hooks/useEnsureUserMetadata.js +3 -2
- package/dist/hooks/useEntityWithMeta.d.ts.map +1 -1
- package/dist/hooks/useEntityWithMeta.js +3 -2
- package/dist/hooks/useInvoice.d.ts.map +1 -1
- package/dist/hooks/useInvoice.js +2 -1
- package/dist/hooks/useInvoices.d.ts.map +1 -1
- package/dist/hooks/useInvoices.js +2 -1
- package/dist/hooks/useMedia.d.ts.map +1 -1
- package/dist/hooks/useMedia.js +10 -9
- package/dist/hooks/useMediaUpload.d.ts.map +1 -1
- package/dist/hooks/useMediaUpload.js +2 -1
- package/dist/hooks/useMembership.d.ts.map +1 -1
- package/dist/hooks/useMembership.js +3 -2
- package/dist/hooks/usePatternUsages.d.ts.map +1 -1
- package/dist/hooks/usePatternUsages.js +3 -2
- package/dist/hooks/usePrefetch.d.ts.map +1 -1
- package/dist/hooks/usePrefetch.js +3 -2
- package/dist/hooks/useQuota.d.ts.map +1 -1
- package/dist/hooks/useQuota.js +2 -1
- package/dist/hooks/useTeamInvitations.d.ts.map +1 -1
- package/dist/hooks/useTeamInvitations.js +4 -3
- package/dist/hooks/useTeamMembers.d.ts.map +1 -1
- package/dist/hooks/useTeamMembers.js +5 -4
- package/dist/hooks/useTeamUpdate.d.ts.map +1 -1
- package/dist/hooks/useTeamUpdate.js +2 -1
- package/dist/hooks/useUserProfile.d.ts.map +1 -1
- package/dist/hooks/useUserProfile.js +3 -2
- package/dist/hooks/useUserSettings.d.ts.map +1 -1
- package/dist/hooks/useUserSettings.js +3 -2
- package/dist/lib/actions/entity.actions.d.ts.map +1 -1
- package/dist/lib/actions/entity.actions.js +2 -1
- package/dist/lib/api/entities.d.ts.map +1 -1
- package/dist/lib/api/entities.js +4 -3
- package/dist/lib/auth/verify-email-link.d.ts +10 -0
- package/dist/lib/auth/verify-email-link.d.ts.map +1 -0
- package/dist/lib/auth/verify-email-link.js +14 -0
- package/dist/lib/auth-client.d.ts +2 -0
- package/dist/lib/auth-client.d.ts.map +1 -1
- package/dist/lib/auth-client.js +4 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +10 -2
- package/dist/lib/base-path.d.ts +85 -0
- package/dist/lib/base-path.d.ts.map +1 -0
- package/dist/lib/base-path.js +79 -0
- package/dist/lib/billing/schema.d.ts +1 -1
- package/dist/lib/billing/schema.d.ts.map +1 -1
- package/dist/lib/billing/schema.js +1 -1
- package/dist/lib/blocks/sanitize-html.d.ts.map +1 -1
- package/dist/lib/blocks/sanitize-html.js +18 -2
- package/dist/lib/cache/revalidate-tag.d.ts +14 -0
- package/dist/lib/cache/revalidate-tag.d.ts.map +1 -0
- package/dist/lib/cache/revalidate-tag.js +8 -0
- package/dist/lib/config/dashboard-schema.d.ts +1 -1
- package/dist/lib/config/dashboard-schema.d.ts.map +1 -1
- package/dist/lib/config/dashboard-schema.js +1 -1
- package/dist/lib/config/types.d.ts +36 -16
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/docs/access.d.ts +28 -0
- package/dist/lib/docs/access.d.ts.map +1 -0
- package/dist/lib/docs/access.js +16 -0
- package/dist/lib/docs/parser.d.ts.map +1 -1
- package/dist/lib/docs/parser.js +2 -1
- package/dist/lib/docs/remark-doc-links.d.ts +14 -0
- package/dist/lib/docs/remark-doc-links.d.ts.map +1 -0
- package/dist/lib/docs/remark-doc-links.js +38 -0
- package/dist/lib/entities/api-generator.d.ts +1 -1
- package/dist/lib/entities/api-generator.d.ts.map +1 -1
- package/dist/lib/entities/api-generator.js +1 -1
- package/dist/lib/entities/schema-generator.d.ts +1 -1
- package/dist/lib/entities/schema-generator.d.ts.map +1 -1
- package/dist/lib/entities/schema-generator.js +1 -1
- package/dist/lib/mcp/engine.js +1 -1
- package/dist/lib/mcp/schema-builder.d.ts +1 -1
- package/dist/lib/mcp/schema-builder.d.ts.map +1 -1
- package/dist/lib/mcp/schema-builder.js +1 -1
- package/dist/lib/mcp/tool-generator.js +1 -1
- package/dist/lib/mcp/types.d.ts +1 -1
- package/dist/lib/mcp/types.d.ts.map +1 -1
- package/dist/lib/media/schemas.d.ts +1 -1
- package/dist/lib/media/schemas.d.ts.map +1 -1
- package/dist/lib/media/schemas.js +1 -1
- package/dist/lib/middleware/index.d.ts +5 -1
- package/dist/lib/middleware/index.d.ts.map +1 -1
- package/dist/lib/middleware/index.js +3 -2
- package/dist/lib/services/meta.service.d.ts +60 -0
- package/dist/lib/services/meta.service.d.ts.map +1 -1
- package/dist/lib/services/meta.service.js +132 -12
- package/dist/lib/services/middleware.service.d.ts.map +1 -1
- package/dist/lib/services/middleware.service.js +3 -2
- package/dist/lib/teams/schema.d.ts +1 -1
- package/dist/lib/teams/schema.d.ts.map +1 -1
- package/dist/lib/teams/schema.js +1 -1
- package/dist/lib/teams/teams.api.d.ts.map +1 -1
- package/dist/lib/teams/teams.api.js +4 -3
- package/dist/lib/user-data-client.d.ts.map +1 -1
- package/dist/lib/user-data-client.js +3 -2
- package/dist/lib/validation/invoices.d.ts +1 -1
- package/dist/lib/validation/invoices.d.ts.map +1 -1
- package/dist/lib/validation/invoices.js +1 -1
- package/dist/lib/validation-schemas.d.ts +1 -1
- package/dist/lib/validation-schemas.d.ts.map +1 -1
- package/dist/lib/validation-schemas.js +1 -1
- package/dist/lib/validation.d.ts +1 -1
- package/dist/lib/validation.d.ts.map +1 -1
- package/dist/lib/validation.js +1 -1
- package/dist/messages/de/footer.json +0 -1
- package/dist/messages/de/index.d.ts +0 -1
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/en/footer.json +0 -1
- package/dist/messages/en/index.d.ts +0 -1
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/es/footer.json +0 -1
- package/dist/messages/es/index.d.ts +0 -1
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/fr/footer.json +0 -1
- package/dist/messages/fr/index.d.ts +0 -1
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/it/footer.json +0 -1
- package/dist/messages/it/index.d.ts +0 -1
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/pt/footer.json +0 -1
- package/dist/messages/pt/index.d.ts +0 -1
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
- package/dist/templates/app/(auth)/verify-email/page.tsx +4 -3
- package/dist/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
- package/dist/templates/app/api/auth/[...all]/route.ts +15 -14
- package/dist/templates/app/api/internal/user-metadata/route.ts +6 -8
- package/dist/templates/app/api/user/plan-flags/route.ts +1 -1
- package/dist/templates/app/api/user/profile/route.ts +12 -17
- package/dist/templates/app/api/v1/api-keys/route.ts +1 -1
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
- package/dist/templates/app/api/v1/billing/cancel/route.ts +1 -1
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +1 -1
- package/dist/templates/app/api/v1/billing/check-action/route.ts +1 -1
- package/dist/templates/app/api/v1/billing/checkout/route.ts +1 -1
- package/dist/templates/app/api/v1/blocks/validate/route.ts +1 -1
- package/dist/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
- package/dist/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
- package/dist/templates/app/api/v1/post-categories/route.ts +1 -1
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
- package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
- package/dist/templates/app/api/v1/teams/docs.md +4 -9
- package/dist/templates/app/api/v1/teams/switch/route.ts +1 -1
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
- package/dist/templates/app/api/v1/users/[id]/route.ts +1 -1
- package/dist/templates/app/api/v1/users/route.ts +1 -1
- package/dist/templates/app/dashboard/(main)/media/page.tsx +3 -1
- package/dist/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
- package/dist/templates/app/dashboard/settings/profile/page.tsx +3 -2
- package/dist/templates/app/devtools/scheduled-actions/page.tsx +2 -1
- package/dist/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
- package/dist/templates/app/superadmin/subscriptions/page.tsx +3 -2
- package/dist/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
- package/dist/templates/app/superadmin/users/[userId]/page.tsx +2 -1
- package/dist/templates/app/superadmin/users/page.tsx +2 -1
- package/dist/templates/blocks/cta-section/component.tsx +3 -2
- package/dist/templates/blocks/cta-section/schema.ts +1 -1
- package/dist/templates/blocks/features-grid/component.tsx +2 -1
- package/dist/templates/blocks/features-grid/schema.ts +1 -1
- package/dist/templates/blocks/hero/component.tsx +3 -2
- package/dist/templates/blocks/hero/schema.ts +1 -1
- package/dist/templates/blocks/testimonials/component.tsx +2 -1
- package/dist/templates/blocks/testimonials/schema.ts +1 -1
- package/dist/templates/blocks/text-content/component.tsx +2 -1
- package/dist/templates/blocks/text-content/schema.ts +1 -1
- package/dist/templates/ci-workflows/README.md +1 -1
- package/dist/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/dist/templates/ci-workflows/github/cypress-smoke.yml +1 -1
- package/dist/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
- package/dist/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
- package/dist/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
- package/dist/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
- package/dist/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
- package/dist/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
- package/dist/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
- package/dist/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
- package/dist/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
- package/dist/templates/eslint.config.mjs +133 -9
- package/dist/templates/features/blog/blocks/post-content/component.tsx +2 -1
- package/dist/templates/features/blog/blocks/post-content/schema.ts +1 -1
- package/dist/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
- package/dist/templates/features/pages/blocks/hero/component.tsx +3 -2
- package/dist/templates/features/pages/blocks/hero/schema.ts +1 -1
- package/dist/templates/next.config.mjs +50 -29
- package/dist/templates/proxy.ts +100 -21
- package/dist/templates/public/sw.js +2 -2
- package/dist/templates/scripts/cy-run-prod.cjs +5 -5
- package/dist/types/blocks.d.ts +1 -1
- package/dist/types/blocks.d.ts.map +1 -1
- package/dist/types/blocks.js +1 -1
- package/package.json +9 -5
- package/scripts/build/docs-registry.mjs +7 -7
- package/scripts/build/registry/__tests__/billing-registry.test.mjs +43 -0
- package/scripts/build/registry/__tests__/build-output.test.mjs +209 -0
- package/scripts/build/registry/__tests__/copy-race-child.mjs +14 -0
- package/scripts/build/registry/__tests__/direct-fs-writes.mjs +203 -0
- package/scripts/build/registry/__tests__/globals-css-tag.test.mjs +66 -0
- package/scripts/build/registry/__tests__/icons-discovery.test.mjs +530 -29
- package/scripts/build/registry/__tests__/page-generator.test.mjs +211 -37
- package/scripts/build/registry/__tests__/published-signatures.test.mjs +239 -0
- package/scripts/build/registry/__tests__/route-file-rule.test.mjs +170 -0
- package/scripts/build/registry/__tests__/safe-fs.test.mjs +333 -0
- package/scripts/build/registry/__tests__/template-analysis.test.mjs +142 -0
- package/scripts/build/registry/__tests__/template-registry.test.mjs +279 -9
- package/scripts/build/registry/__tests__/templates-plan.test.mjs +128 -0
- package/scripts/build/registry/__tests__/templates-tree.test.mjs +367 -0
- package/scripts/build/registry/__tests__/write-places.test.mjs +425 -0
- package/scripts/build/registry/config.mjs +6 -65
- package/scripts/build/registry/discovery/icons.mjs +498 -43
- package/scripts/build/registry/discovery/templates.mjs +1 -1
- package/scripts/build/registry/generators/billing-registry.mjs +1 -1
- package/scripts/build/registry/generators/docs-registry.mjs +3 -4
- package/scripts/build/registry/generators/feature-registry.mjs +1 -1
- package/scripts/build/registry/generators/permissions-registry.mjs +2 -2
- package/scripts/build/registry/generators/template-registry.mjs +65 -49
- package/scripts/build/registry/index.mjs +1 -2
- package/scripts/build/registry/post-build/index.mjs +1 -1
- package/scripts/build/registry/post-build/own-gitignores.mjs +220 -0
- package/scripts/build/registry/post-build/page-generator.mjs +512 -268
- package/scripts/build/registry/post-build/route-cleanup.mjs +16 -13
- package/scripts/build/registry/post-build/test-fixtures.mjs +26 -8
- package/scripts/build/registry/project-mode.mjs +76 -0
- package/scripts/build/registry/shared/typescript-compiler.mjs +77 -0
- package/scripts/build/registry/validate-env.test.mjs +12 -8
- package/scripts/build/registry/watch.mjs +3 -53
- package/scripts/build/registry/write-places.mjs +228 -0
- package/scripts/build/registry.mjs +53 -30
- package/scripts/build/safe-fs.mjs +279 -0
- package/scripts/build/templates-plan.mjs +44 -0
- package/scripts/build/theme.mjs +46 -42
- package/scripts/build/update-tsconfig.mjs +16 -15
- package/scripts/db/cluster-changes.mjs +205 -0
- package/scripts/db/connection-time-limits.mjs +53 -0
- package/scripts/db/inspect-server.mjs +108 -0
- package/scripts/db/migration-time-limit.mjs +695 -0
- package/scripts/db/run-migrations.mjs +46 -41
- package/scripts/db/ssl-config.mjs +194 -0
- package/scripts/db/theme-location.mjs +69 -0
- package/scripts/db/verify-tables.mjs +3 -8
- package/scripts/db/verify-teams.mjs +2 -6
- package/scripts/db/verify-theme-migrations.mjs +169 -0
- package/scripts/maintenance/core-updater.mjs +1003 -0
- package/scripts/maintenance/step-guard.mjs +74 -0
- package/scripts/maintenance/update-core.mjs +14 -831
- package/scripts/utils/console-guard.mjs +13 -0
- package/scripts/utils/generated-tag.mjs +66 -0
- package/scripts/utils/index.mjs +9 -1
- package/scripts/utils/logging.mjs +140 -0
- package/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
- package/templates/app/(auth)/verify-email/page.tsx +4 -3
- package/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
- package/templates/app/api/auth/[...all]/route.ts +15 -14
- package/templates/app/api/internal/user-metadata/route.ts +6 -8
- package/templates/app/api/user/plan-flags/route.ts +1 -1
- package/templates/app/api/user/profile/route.ts +12 -17
- package/templates/app/api/v1/api-keys/route.ts +1 -1
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
- package/templates/app/api/v1/billing/cancel/route.ts +1 -1
- package/templates/app/api/v1/billing/change-plan/route.ts +1 -1
- package/templates/app/api/v1/billing/check-action/route.ts +1 -1
- package/templates/app/api/v1/billing/checkout/route.ts +1 -1
- package/templates/app/api/v1/blocks/validate/route.ts +1 -1
- package/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
- package/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
- package/templates/app/api/v1/post-categories/route.ts +1 -1
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
- package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
- package/templates/app/api/v1/teams/docs.md +4 -9
- package/templates/app/api/v1/teams/switch/route.ts +1 -1
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
- package/templates/app/api/v1/users/[id]/route.ts +1 -1
- package/templates/app/api/v1/users/route.ts +1 -1
- package/templates/app/dashboard/(main)/media/page.tsx +3 -1
- package/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
- package/templates/app/dashboard/settings/profile/page.tsx +3 -2
- package/templates/app/devtools/scheduled-actions/page.tsx +2 -1
- package/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
- package/templates/app/superadmin/subscriptions/page.tsx +3 -2
- package/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
- package/templates/app/superadmin/users/[userId]/page.tsx +2 -1
- package/templates/app/superadmin/users/page.tsx +2 -1
- package/templates/blocks/cta-section/component.tsx +3 -2
- package/templates/blocks/cta-section/schema.ts +1 -1
- package/templates/blocks/features-grid/component.tsx +2 -1
- package/templates/blocks/features-grid/schema.ts +1 -1
- package/templates/blocks/hero/component.tsx +3 -2
- package/templates/blocks/hero/schema.ts +1 -1
- package/templates/blocks/testimonials/component.tsx +2 -1
- package/templates/blocks/testimonials/schema.ts +1 -1
- package/templates/blocks/text-content/component.tsx +2 -1
- package/templates/blocks/text-content/schema.ts +1 -1
- package/templates/ci-workflows/README.md +1 -1
- package/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/templates/ci-workflows/github/cypress-smoke.yml +1 -1
- package/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
- package/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
- package/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
- package/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
- package/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
- package/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
- package/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
- package/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
- package/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
- package/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
- package/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
- package/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
- package/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
- package/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
- package/templates/eslint.config.mjs +133 -9
- package/templates/features/blog/blocks/post-content/component.tsx +2 -1
- package/templates/features/blog/blocks/post-content/schema.ts +1 -1
- package/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
- package/templates/features/pages/blocks/hero/component.tsx +3 -2
- package/templates/features/pages/blocks/hero/schema.ts +1 -1
- package/templates/next.config.mjs +50 -29
- package/templates/proxy.ts +100 -21
- package/templates/public/sw.js +2 -2
- package/templates/scripts/cy-run-prod.cjs +5 -5
- package/tests/jest/__mocks__/@nextsparkjs/registries/template-registry.client.ts +11 -0
- package/tests/jest/__mocks__/next-server.js +10 -0
- package/scripts/build/docs.mjs +0 -392
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for icon discovery.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
4
|
+
* The parts that can silently produce an empty or wrong registry — which
|
|
5
5
|
* degrades to every icon rendering as a Box, with no error anywhere — are the
|
|
6
|
-
* path
|
|
7
|
-
* directly; discoverIcons() itself only wires them to the filesystem.
|
|
6
|
+
* path matchers and the name extraction. Both are pure, so both are tested
|
|
7
|
+
* here directly; discoverIcons() itself only wires them to the filesystem.
|
|
8
8
|
*
|
|
9
9
|
* Run: node --test packages/core/scripts/build/registry/__tests__/icons-discovery.test.mjs
|
|
10
10
|
*/
|
|
@@ -12,7 +12,20 @@
|
|
|
12
12
|
import { test } from 'node:test'
|
|
13
13
|
import assert from 'node:assert/strict'
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
findUnresolvedIconRefs,
|
|
17
|
+
extractIconNames,
|
|
18
|
+
isIconSourcePath,
|
|
19
|
+
isIconCallSourcePath,
|
|
20
|
+
isTestFilePath,
|
|
21
|
+
extractLiteralIconCallNames
|
|
22
|
+
} from '../discovery/icons.mjs'
|
|
23
|
+
|
|
24
|
+
// extractLiteralIconCallNames matches a call site by its real import binding,
|
|
25
|
+
// so every snippet exercising a real DynamicIcon or resolveIcon call needs
|
|
26
|
+
// one of these in scope, the same way real theme and plugin source does.
|
|
27
|
+
const DYNAMIC_ICON_IMPORT = "import { DynamicIcon } from '@nextsparkjs/core/components/ui/dynamic-icon'\n"
|
|
28
|
+
const RESOLVE_ICON_IMPORT = "import { resolveIcon } from '@nextsparkjs/core/lib/icons'\n"
|
|
16
29
|
|
|
17
30
|
test('matches entity, block and app configs', () => {
|
|
18
31
|
assert.ok(isIconSourcePath('/p/contents/themes/default/entities/tasks/tasks.config.ts'))
|
|
@@ -26,9 +39,19 @@ test('matches the same paths with Windows separators', () => {
|
|
|
26
39
|
assert.ok(isIconSourcePath('C:\\p\\themes\\default\\config\\app.config.ts'))
|
|
27
40
|
})
|
|
28
41
|
|
|
29
|
-
test('
|
|
30
|
-
assert.
|
|
31
|
-
assert.
|
|
42
|
+
test('matches every config/*.config.ts of a theme or plugin, not only app.config.ts', () => {
|
|
43
|
+
assert.ok(isIconSourcePath('/p/themes/crm/config/dashboard.config.ts'))
|
|
44
|
+
assert.ok(isIconSourcePath('/p/themes/crm/config/features.config.ts'))
|
|
45
|
+
assert.ok(isIconSourcePath('/p/themes/crm/config/flows.config.ts'))
|
|
46
|
+
assert.ok(isIconSourcePath('/p/themes/default/config/theme.config.ts'))
|
|
47
|
+
assert.ok(isIconSourcePath('C:\\p\\themes\\crm\\config\\features.config.ts'))
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('ignores a config.ts nested deeper than a direct child of config/', () => {
|
|
51
|
+
assert.equal(isIconSourcePath('/p/themes/default/config/sub/nested.config.ts'), false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('ignores files outside entities, blocks and config directories', () => {
|
|
32
55
|
assert.equal(isIconSourcePath('/p/themes/default/entities/tasks/messages/en.ts'), false)
|
|
33
56
|
})
|
|
34
57
|
|
|
@@ -36,53 +59,140 @@ test('does not match a file that merely ends in config.ts', () => {
|
|
|
36
59
|
assert.equal(isIconSourcePath('/p/themes/default/blocks/hero/notconfig.ts'), false)
|
|
37
60
|
})
|
|
38
61
|
|
|
39
|
-
|
|
62
|
+
// --- isTestFilePath: test code is excluded from discovery entirely --------
|
|
63
|
+
|
|
64
|
+
test('flags .test. and .spec. files, ts and tsx', () => {
|
|
65
|
+
assert.ok(isTestFilePath('/p/themes/default/components/wallet-badge.test.tsx'))
|
|
66
|
+
assert.ok(isTestFilePath('/p/themes/default/components/wallet-badge.spec.ts'))
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('flags anything under __tests__, tests or cypress directories', () => {
|
|
70
|
+
assert.ok(isTestFilePath('/p/themes/default/__tests__/wallet-badge.tsx'))
|
|
71
|
+
assert.ok(isTestFilePath('/p/themes/default/tests/wallet-badge.tsx'))
|
|
72
|
+
assert.ok(isTestFilePath('/p/themes/default/tests/cypress/e2e/wallet.cy.ts'))
|
|
73
|
+
assert.ok(isTestFilePath('C:\\p\\themes\\default\\__tests__\\wallet-badge.tsx'))
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('does not flag ordinary component source', () => {
|
|
77
|
+
assert.equal(isTestFilePath('/p/themes/default/components/wallet-badge.tsx'), false)
|
|
78
|
+
assert.equal(isTestFilePath('/p/themes/default/entities/tasks/tasks.config.ts'), false)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('reads an icon under a quoted key', async () => {
|
|
40
82
|
const source = `
|
|
41
83
|
import { Users } from 'lucide-react'
|
|
42
84
|
export const config = { 'icon': Users }
|
|
43
85
|
`
|
|
44
|
-
assert.deepEqual(extractIconNames(source), ['Users'])
|
|
86
|
+
assert.deepEqual(await extractIconNames(source), ['Users'])
|
|
45
87
|
})
|
|
46
88
|
|
|
47
|
-
test('reads an icon declared as a lucide import', () => {
|
|
89
|
+
test('reads an icon declared as a lucide import', async () => {
|
|
48
90
|
const source = `
|
|
49
91
|
import { Users, CheckSquare } from 'lucide-react'
|
|
50
92
|
export const config = { icon: CheckSquare }
|
|
51
93
|
`
|
|
52
|
-
assert.deepEqual(extractIconNames(source), ['CheckSquare'])
|
|
94
|
+
assert.deepEqual(await extractIconNames(source), ['CheckSquare'])
|
|
53
95
|
})
|
|
54
96
|
|
|
55
|
-
test('resolves an aliased import back to the lucide export name', () => {
|
|
97
|
+
test('resolves an aliased import back to the lucide export name', async () => {
|
|
56
98
|
// `icon: HouseIcon` is lucide's `Home`; the registry is keyed by the export
|
|
57
99
|
const source = `
|
|
58
100
|
import { Home as HouseIcon } from 'lucide-react'
|
|
59
101
|
export const config = { icon: HouseIcon }
|
|
60
102
|
`
|
|
61
|
-
assert.deepEqual(extractIconNames(source), ['Home'])
|
|
103
|
+
assert.deepEqual(await extractIconNames(source), ['Home'])
|
|
62
104
|
})
|
|
63
105
|
|
|
64
|
-
test('ignores an identifier that did not come from lucide', () => {
|
|
106
|
+
test('ignores an identifier that did not come from lucide', async () => {
|
|
65
107
|
const source = `
|
|
66
108
|
import { MyOwnIcon } from '@/components/icons'
|
|
67
109
|
export const config = { icon: MyOwnIcon }
|
|
68
110
|
`
|
|
69
|
-
assert.deepEqual(extractIconNames(source), [])
|
|
111
|
+
assert.deepEqual(await extractIconNames(source), [])
|
|
70
112
|
})
|
|
71
113
|
|
|
72
|
-
test('reads a string icon name, kebab-case included', () => {
|
|
114
|
+
test('reads a string icon name, kebab-case included', async () => {
|
|
73
115
|
const source = `export const config = { icon: 'pie-chart' }`
|
|
74
|
-
assert.deepEqual(extractIconNames(source), ['pie-chart'])
|
|
116
|
+
assert.deepEqual(await extractIconNames(source), ['pie-chart'])
|
|
75
117
|
})
|
|
76
118
|
|
|
77
|
-
test('does not match a different key that ends in icon', () => {
|
|
119
|
+
test('does not match a different key that ends in icon', async () => {
|
|
78
120
|
const source = `
|
|
79
121
|
import { Users } from 'lucide-react'
|
|
80
122
|
export const config = { sidebarIcon: Users, my_icon: Users }
|
|
81
123
|
`
|
|
82
|
-
assert.deepEqual(extractIconNames(source), [])
|
|
124
|
+
assert.deepEqual(await extractIconNames(source), [])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('reads an icon from a shorthand property, where the name doubles as the value', async () => {
|
|
128
|
+
const source = `
|
|
129
|
+
import { Wallet as icon } from 'lucide-react'
|
|
130
|
+
export default { icon }
|
|
131
|
+
`
|
|
132
|
+
assert.deepEqual(await extractIconNames(source), ['Wallet'])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('reads an icon written under a computed property key', async () => {
|
|
136
|
+
const source = `export default { ['icon']: 'Wallet' }`
|
|
137
|
+
assert.deepEqual(await extractIconNames(source), ['Wallet'])
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('reads an icon under the iconName key, not only icon', async () => {
|
|
141
|
+
const source = `export default { iconName: 'Wallet' }`
|
|
142
|
+
assert.deepEqual(await extractIconNames(source), ['Wallet'])
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('reports a shorthand icon property that does not resolve, instead of staying silent', async () => {
|
|
146
|
+
assert.deepEqual(
|
|
147
|
+
await findUnresolvedIconRefs("import { icon } from './local-icons'\nexport default { icon }"),
|
|
148
|
+
['icon']
|
|
149
|
+
)
|
|
83
150
|
})
|
|
84
151
|
|
|
85
|
-
test('
|
|
152
|
+
test('reads an icon written as a get accessor, the same as a plain property', async () => {
|
|
153
|
+
const source = `export default { get icon() { return 'Wallet' } }`
|
|
154
|
+
assert.deepEqual(await extractIconNames(source), ['Wallet'])
|
|
155
|
+
assert.deepEqual(await findUnresolvedIconRefs(source), [])
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('reads a get accessor icon returning a lucide import', async () => {
|
|
159
|
+
const source = `
|
|
160
|
+
import { Wallet } from 'lucide-react'
|
|
161
|
+
export default { get icon() { return Wallet } }
|
|
162
|
+
`
|
|
163
|
+
assert.deepEqual(await extractIconNames(source), ['Wallet'])
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('reports a get accessor icon whose return value cannot be read statically, instead of staying silent', async () => {
|
|
167
|
+
const source = `export default { get icon() {\n const computed = someHelper()\n return computed\n} }`
|
|
168
|
+
assert.deepEqual(await extractIconNames(source), [])
|
|
169
|
+
assert.equal((await findUnresolvedIconRefs(source)).length, 1)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test('reports an icon written as a method, whose value is the function rather than a name', async () => {
|
|
173
|
+
const source = `export default { icon() { return 'Wallet' } }`
|
|
174
|
+
assert.deepEqual(await extractIconNames(source), [])
|
|
175
|
+
assert.deepEqual(await findUnresolvedIconRefs(source), ["icon() { return 'Wallet' }"])
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('reports a set accessor icon with no getter beside it, whose value reads as undefined', async () => {
|
|
179
|
+
const source = `export default { set icon(value) {} }`
|
|
180
|
+
assert.deepEqual(await extractIconNames(source), [])
|
|
181
|
+
assert.deepEqual(await findUnresolvedIconRefs(source), ['set icon(value) {}'])
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('says nothing about a set accessor icon when a getter beside it supplies the name', async () => {
|
|
185
|
+
const source = `export default { get icon() { return 'Wallet' }, set icon(value) {} }`
|
|
186
|
+
assert.deepEqual(await extractIconNames(source), ['Wallet'])
|
|
187
|
+
assert.deepEqual(await findUnresolvedIconRefs(source), [])
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('reads an icon written as a class field, and reports one that does not resolve', async () => {
|
|
191
|
+
assert.deepEqual(await extractIconNames(`export class Config { icon = 'Wallet' }`), ['Wallet'])
|
|
192
|
+
assert.deepEqual(await findUnresolvedIconRefs(`export class Config { icon = pickIcon() }`), ['pickIcon()'])
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('never yields anything but a bare name, so nothing can be injected', async () => {
|
|
86
196
|
// The generated registry is TypeScript built from these strings, and every
|
|
87
197
|
// name becomes a named import. Whatever a config holds, what comes out here
|
|
88
198
|
// is a bare name — and discoverIcons then drops any that lucide doesn't
|
|
@@ -94,7 +204,7 @@ test('never yields anything but a bare name, so nothing can be injected', () =>
|
|
|
94
204
|
]
|
|
95
205
|
|
|
96
206
|
for (const source of sources) {
|
|
97
|
-
for (const name of extractIconNames(source)) {
|
|
207
|
+
for (const name of await extractIconNames(source)) {
|
|
98
208
|
assert.match(name, /^[A-Za-z][\w$-]*$/, `unsafe name from: ${source}`)
|
|
99
209
|
}
|
|
100
210
|
}
|
|
@@ -102,29 +212,420 @@ test('never yields anything but a bare name, so nothing can be injected', () =>
|
|
|
102
212
|
|
|
103
213
|
// Only a direct lucide import or a string literal reaches the registry, so
|
|
104
214
|
// every other shape has to be reported rather than silently rendering a Box.
|
|
105
|
-
test('reports an icon reference the build cannot resolve', () => {
|
|
215
|
+
test('reports an icon reference the build cannot resolve', async () => {
|
|
106
216
|
assert.deepEqual(
|
|
107
|
-
findUnresolvedIconRefs("import * as I from 'lucide-react'\nexport default { icon: I.Users }"),
|
|
217
|
+
await findUnresolvedIconRefs("import * as I from 'lucide-react'\nexport default { icon: I.Users }"),
|
|
108
218
|
['I.Users']
|
|
109
219
|
)
|
|
110
220
|
assert.deepEqual(
|
|
111
|
-
findUnresolvedIconRefs("import { Users } from './icons'\nexport default { icon: Users }"),
|
|
221
|
+
await findUnresolvedIconRefs("import { Users } from './icons'\nexport default { icon: Users }"),
|
|
112
222
|
['Users']
|
|
113
223
|
)
|
|
114
224
|
assert.deepEqual(
|
|
115
|
-
findUnresolvedIconRefs("import { Users } from 'lucide-react'\nconst Mine = Users\nexport default { icon: Mine }"),
|
|
225
|
+
await findUnresolvedIconRefs("import { Users } from 'lucide-react'\nconst Mine = Users\nexport default { icon: Mine }"),
|
|
116
226
|
['Mine']
|
|
117
227
|
)
|
|
118
228
|
})
|
|
119
229
|
|
|
120
|
-
test('says nothing about a reference it can resolve', () => {
|
|
230
|
+
test('says nothing about a reference it can resolve', async () => {
|
|
121
231
|
assert.deepEqual(
|
|
122
|
-
findUnresolvedIconRefs("import { Users } from 'lucide-react'\nexport default { icon: Users }"),
|
|
232
|
+
await findUnresolvedIconRefs("import { Users } from 'lucide-react'\nexport default { icon: Users }"),
|
|
123
233
|
[]
|
|
124
234
|
)
|
|
125
235
|
assert.deepEqual(
|
|
126
|
-
findUnresolvedIconRefs("import { Home as HouseIcon } from 'lucide-react'\nexport default { icon: HouseIcon }"),
|
|
236
|
+
await findUnresolvedIconRefs("import { Home as HouseIcon } from 'lucide-react'\nexport default { icon: HouseIcon }"),
|
|
127
237
|
[]
|
|
128
238
|
)
|
|
129
|
-
assert.deepEqual(findUnresolvedIconRefs("export default { icon: 'pie-chart' }"), [])
|
|
239
|
+
assert.deepEqual(await findUnresolvedIconRefs("export default { icon: 'pie-chart' }"), [])
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
// --- Transparent TypeScript wrappers ---------------------------------------
|
|
243
|
+
//
|
|
244
|
+
// `as`, `<T>x`, `satisfies` and `!` change nothing about the value at
|
|
245
|
+
// runtime, so a config or call site written with one of them must resolve
|
|
246
|
+
// exactly like the bare form — and still be reported as unresolved when the
|
|
247
|
+
// wrapped value itself doesn't resolve, rather than disappearing silently.
|
|
248
|
+
|
|
249
|
+
test('reads a string icon name through `as const`', async () => {
|
|
250
|
+
const source = `export const config = { icon: 'Wallet' as const }`
|
|
251
|
+
assert.deepEqual(await extractIconNames(source), ['Wallet'])
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test('reads a lucide identifier through a non-null assertion', async () => {
|
|
255
|
+
const source = `
|
|
256
|
+
import { Wallet } from 'lucide-react'
|
|
257
|
+
export const config = { icon: Wallet! }
|
|
258
|
+
`
|
|
259
|
+
assert.deepEqual(await extractIconNames(source), ['Wallet'])
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test('reads a string icon name through `satisfies` and through parentheses', async () => {
|
|
263
|
+
assert.deepEqual(
|
|
264
|
+
await extractIconNames(`export const config = { icon: 'Wallet' satisfies string }`),
|
|
265
|
+
['Wallet']
|
|
266
|
+
)
|
|
267
|
+
assert.deepEqual(await extractIconNames(`export const config = { icon: ('Wallet') }`), ['Wallet'])
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test('reads a lucide identifier through an angle-bracket type assertion', async () => {
|
|
271
|
+
const source = `
|
|
272
|
+
import { Wallet } from 'lucide-react'
|
|
273
|
+
export const config = { icon: <any>Wallet }
|
|
274
|
+
`
|
|
275
|
+
assert.deepEqual(await extractIconNames(source), ['Wallet'])
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test('reports a wrapped reference that still does not resolve, instead of dropping it silently', async () => {
|
|
279
|
+
assert.deepEqual(
|
|
280
|
+
await findUnresolvedIconRefs("export default { icon: Wallet! }"),
|
|
281
|
+
['Wallet!']
|
|
282
|
+
)
|
|
283
|
+
assert.deepEqual(
|
|
284
|
+
await findUnresolvedIconRefs("export default { icon: Wallet as const }"),
|
|
285
|
+
['Wallet as const']
|
|
286
|
+
)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('reports a call result assigned to icon, instead of staying silent', async () => {
|
|
290
|
+
// Neither a string literal nor a lucide identifier - `chooseIcon()` cannot
|
|
291
|
+
// be resolved at build time, and unlike a bare identifier or a property
|
|
292
|
+
// access it must still be reported, not just left out of both lists.
|
|
293
|
+
assert.deepEqual(await extractIconNames('export default { icon: chooseIcon() as LucideIcon }'), [])
|
|
294
|
+
assert.deepEqual(
|
|
295
|
+
await findUnresolvedIconRefs('export default { icon: chooseIcon() as LucideIcon }'),
|
|
296
|
+
['chooseIcon() as LucideIcon']
|
|
297
|
+
)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('extracts a literal name through `satisfies` in a call argument', async () => {
|
|
301
|
+
const source = `${RESOLVE_ICON_IMPORT}resolveIcon('Wallet' satisfies string)`
|
|
302
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet'])
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
test('extracts a literal name through a non-null-asserted callee', async () => {
|
|
306
|
+
const source = `${RESOLVE_ICON_IMPORT}resolveIcon!('Wallet')`
|
|
307
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet'])
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
test('matches .ts and .tsx component files, with Windows separators too', () => {
|
|
311
|
+
assert.ok(isIconCallSourcePath('/p/themes/default/components/wallet-badge.tsx'))
|
|
312
|
+
assert.ok(isIconCallSourcePath('/p/themes/default/lib/format.ts'))
|
|
313
|
+
assert.ok(isIconCallSourcePath('C:\\p\\themes\\default\\components\\wallet-badge.tsx'))
|
|
314
|
+
assert.equal(isIconCallSourcePath('/p/themes/default/messages/en.json'), false)
|
|
315
|
+
assert.equal(isIconCallSourcePath('/p/themes/default/README.md'), false)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
test('extracts a literal name from DynamicIcon, either quoting style', async () => {
|
|
319
|
+
assert.deepEqual(
|
|
320
|
+
await extractLiteralIconCallNames(DYNAMIC_ICON_IMPORT + '<DynamicIcon name="Wallet" className="h-4 w-4" />'),
|
|
321
|
+
['Wallet']
|
|
322
|
+
)
|
|
323
|
+
assert.deepEqual(
|
|
324
|
+
await extractLiteralIconCallNames(DYNAMIC_ICON_IMPORT + "<DynamicIcon name={'pie-chart'} />"),
|
|
325
|
+
['pie-chart']
|
|
326
|
+
)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
test('extracts a literal name from resolveIcon, with or without a fallback argument', async () => {
|
|
330
|
+
assert.deepEqual(await extractLiteralIconCallNames(RESOLVE_ICON_IMPORT + "resolveIcon('receipt')"), ['receipt'])
|
|
331
|
+
assert.deepEqual(
|
|
332
|
+
await extractLiteralIconCallNames(RESOLVE_ICON_IMPORT + 'resolveIcon("receipt", Box)'),
|
|
333
|
+
['receipt']
|
|
334
|
+
)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test('ignores a runtime value handed to DynamicIcon or resolveIcon', async () => {
|
|
338
|
+
assert.deepEqual(await extractLiteralIconCallNames(DYNAMIC_ICON_IMPORT + '<DynamicIcon name={item.icon} />'), [])
|
|
339
|
+
assert.deepEqual(await extractLiteralIconCallNames(RESOLVE_ICON_IMPORT + 'resolveIcon(item.icon, Box)'), [])
|
|
340
|
+
assert.deepEqual(await extractLiteralIconCallNames(RESOLVE_ICON_IMPORT + 'resolveIcon(iconName)'), [])
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
test('never yields anything but a bare name from a call, so nothing can be injected', async () => {
|
|
344
|
+
const sources = [
|
|
345
|
+
DYNAMIC_ICON_IMPORT + '<DynamicIcon name="X"; process.exit(1); //" />',
|
|
346
|
+
RESOLVE_ICON_IMPORT + 'resolveIcon(\'Users } from "x"; import evil from "y"; //\')'
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
for (const source of sources) {
|
|
350
|
+
for (const name of await extractLiteralIconCallNames(source)) {
|
|
351
|
+
assert.match(name, /^[A-Za-z][\w$-]*$/, `unsafe name from: ${source}`)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
// --- Import-binding resolution --------------------------------------------
|
|
357
|
+
//
|
|
358
|
+
// A call site is matched through the file's own imports and lexical scope
|
|
359
|
+
// rather than by comparing the identifier's text, so aliasing and namespacing
|
|
360
|
+
// resolve, a same-named local that never came from core does not, and a
|
|
361
|
+
// closer declaration of the same name shadows the import for its scope.
|
|
362
|
+
|
|
363
|
+
test('resolves resolveIcon through an aliased import', async () => {
|
|
364
|
+
const source = "import { resolveIcon as ri } from '@nextsparkjs/core/lib/icons'\nri('Wallet')"
|
|
365
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet'])
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
test('resolves DynamicIcon through an aliased import', async () => {
|
|
369
|
+
const source = "import { DynamicIcon as DI } from '@nextsparkjs/core/components/ui/dynamic-icon'\n<DI name=\"Wallet\" />"
|
|
370
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet'])
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
test('resolves resolveIcon and DynamicIcon through a namespace import', async () => {
|
|
374
|
+
const source =
|
|
375
|
+
"import * as Core from '@nextsparkjs/core/lib/icons'\nimport * as CoreUI from '@nextsparkjs/core/components/ui/dynamic-icon'\nCore.resolveIcon('Wallet');\n<CoreUI.DynamicIcon name=\"Receipt\" />;"
|
|
376
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet', 'Receipt'])
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
test('ignores a same-named local that was never imported from core', async () => {
|
|
380
|
+
// A theme's own helper named resolveIcon (or a JSX component named
|
|
381
|
+
// DynamicIcon) is not core's, even though the text matches.
|
|
382
|
+
const source = "function resolveIcon(x) { return x }\nresolveIcon('Wallet')\nfunction DynamicIcon(props) { return null }\n<DynamicIcon name=\"Receipt\" />"
|
|
383
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
test('ignores resolveIcon imported from somewhere other than core', async () => {
|
|
387
|
+
const source = "import { resolveIcon } from './local-icons'\nresolveIcon('Wallet')"
|
|
388
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
test('ignores a call through a parameter that shadows the real core import', async () => {
|
|
392
|
+
// The module does import resolveIcon from core, but the call inside f()
|
|
393
|
+
// goes through f's own parameter of the same name, not the import.
|
|
394
|
+
const source = `${RESOLVE_ICON_IMPORT}function f(resolveIcon) { resolveIcon('Wallet') }`
|
|
395
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
test('still resolves the real import once the shadowing parameter goes out of scope', async () => {
|
|
399
|
+
const source = `${RESOLVE_ICON_IMPORT}function f(resolveIcon) { resolveIcon('Wallet') }\nresolveIcon('Receipt')`
|
|
400
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Receipt'])
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
// --- Function parameter initializer scope ----------------------------------
|
|
404
|
+
//
|
|
405
|
+
// Parameter defaults run before a function body is entered. Parameters and a
|
|
406
|
+
// named function expression shadow imports there, but body declarations do
|
|
407
|
+
// not; those declarations still shadow calls made by the body itself.
|
|
408
|
+
|
|
409
|
+
test('discovers a constructor default that a var in the constructor body does not shadow', async () => {
|
|
410
|
+
const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n constructor(icon = resolveIcon('Telescope')) {\n var resolveIcon = () => null\n }\n}`
|
|
411
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Telescope'])
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
test('discovers a constructor default despite a function declaration in its body', async () => {
|
|
415
|
+
const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n constructor(icon = resolveIcon('Satellite')) {\n function resolveIcon() {}\n }\n}`
|
|
416
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Satellite'])
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
test('discovers a method default despite a function declaration in its body', async () => {
|
|
420
|
+
const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n render(icon = resolveIcon('Map')) {\n function resolveIcon() {}\n }\n}`
|
|
421
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Map'])
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
test('discovers an arrow default despite a function declaration in its block body', async () => {
|
|
425
|
+
const source = `${RESOLVE_ICON_IMPORT}const render = (icon = resolveIcon('Rocket')) => {\n function resolveIcon() {}\n}`
|
|
426
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Rocket'])
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
test('discovers a destructured parameter default that a body var does not shadow', async () => {
|
|
430
|
+
const source = `${RESOLVE_ICON_IMPORT}const render = ({ icon = resolveIcon('Compass') } = {}) => {\n var resolveIcon = () => null\n}`
|
|
431
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Compass'])
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
test('discovers a computed destructured parameter key that a body var does not shadow', async () => {
|
|
435
|
+
const source = `${RESOLVE_ICON_IMPORT}const render = ({ [resolveIcon('Globe')]: icon } = {}) => {\n var resolveIcon = () => null\n}`
|
|
436
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Globe'])
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
test('keeps a body var shadowing calls made by the function body', async () => {
|
|
440
|
+
const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n constructor(a = 1) {\n var resolveIcon = f\n resolveIcon('Anchor')\n }\n}`
|
|
441
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
test("discovers a method's computed name, which its own parameters do not shadow", async () => {
|
|
445
|
+
const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n [resolveIcon('Flag')](resolveIcon) {\n var resolveIcon\n }\n}`
|
|
446
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Flag'])
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
test('keeps an earlier parameter shadowing a later parameter default', async () => {
|
|
450
|
+
const source = `${RESOLVE_ICON_IMPORT}const render = (resolveIcon, icon = resolveIcon('Kite')) => {}`
|
|
451
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
test('ignores a call imported from a same-prefixed package that is not core', async () => {
|
|
455
|
+
const source = "import { resolveIcon } from '@nextsparkjs/core-fake/lib/icons'\nresolveIcon('Wallet')"
|
|
456
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
// --- Block-wide shadowing ---------------------------------------------------
|
|
460
|
+
//
|
|
461
|
+
// A local declaration shadows an import for the rest of the block it is
|
|
462
|
+
// declared in, not merely for its own immediate subtree: a sibling statement
|
|
463
|
+
// later in the same block has to see the shadow too, the way it would at
|
|
464
|
+
// runtime.
|
|
465
|
+
|
|
466
|
+
test('ignores a call through a local variable that shadows the import later in the same block', async () => {
|
|
467
|
+
const source = `${RESOLVE_ICON_IMPORT}function f(local) {\n const resolveIcon = local\n resolveIcon('Wallet')\n}`
|
|
468
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
test('ignores a call through a local function declaration that shadows the import', async () => {
|
|
472
|
+
const source = `${RESOLVE_ICON_IMPORT}function outer() {\n function resolveIcon(x) { return x }\n resolveIcon('Wallet')\n}`
|
|
473
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
test('ignores a call inside a named function expression that shadows the import with its own name', async () => {
|
|
477
|
+
const source = `${RESOLVE_ICON_IMPORT}const wrapped = function resolveIcon(x) {\n resolveIcon('Wallet')\n return x\n}`
|
|
478
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
test('ignores a call through a local namespace that shadows the imported namespace', async () => {
|
|
482
|
+
const source = "import * as Core from '@nextsparkjs/core/lib/icons'\nfunction f() {\n const Core = { resolveIcon: () => {} }\n Core.resolveIcon('Wallet')\n}"
|
|
483
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
test('ignores a call through a var that shadows the import, hoisted out of the if that declares it', async () => {
|
|
487
|
+
// var is function-scoped, not block-scoped: the call after the if still
|
|
488
|
+
// sees the local declaration, not the import.
|
|
489
|
+
const source = `${RESOLVE_ICON_IMPORT}function f() {\n if (true) {\n var resolveIcon = (name) => name\n }\n return resolveIcon('Wallet')\n}`
|
|
490
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
test("ignores a call through a for loop's own initializer that shadows the import for the whole loop body", async () => {
|
|
494
|
+
const source = `${RESOLVE_ICON_IMPORT}function f() {\n for (const resolveIcon = (name) => name; false; ) {\n resolveIcon('Wallet')\n }\n}`
|
|
495
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
test('ignores a call through a let declared in one switch case that shadows the import in a later case', async () => {
|
|
499
|
+
// Every clause of a switch shares one lexical scope, unlike a block per case.
|
|
500
|
+
const source = `${RESOLVE_ICON_IMPORT}function f(x) {\n switch (x) {\n case 1:\n let resolveIcon = (name) => name\n break\n case 2:\n resolveIcon('Wallet')\n break\n }\n}`
|
|
501
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
test('ignores a call through a class declaration that shadows the import with its own name', async () => {
|
|
505
|
+
const source = `${RESOLVE_ICON_IMPORT}function f() {\n class resolveIcon {\n static call(name) { return name }\n }\n return resolveIcon('Wallet')\n}`
|
|
506
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
// --- Accessor, constructor, class and namespace scopes ----------------------
|
|
510
|
+
//
|
|
511
|
+
// Getters, setters and constructors are functions like any other: their
|
|
512
|
+
// parameters and every var hoisted in their bodies shadow the import inside
|
|
513
|
+
// them and nowhere else. A named class expression binds its own name only
|
|
514
|
+
// inside its body. A class `static { }` block and a TypeScript namespace are
|
|
515
|
+
// var scopes of their own, so a var declared in one neither leaks out to
|
|
516
|
+
// shadow the module's real import nor stays unseen inside.
|
|
517
|
+
|
|
518
|
+
test('ignores a call through a var hoisted out of an if inside a getter', async () => {
|
|
519
|
+
const source = `${RESOLVE_ICON_IMPORT}export const menu = {\n get icon() {\n if (true) {\n var resolveIcon = (name) => name\n }\n return resolveIcon('Wallet')\n }\n}`
|
|
520
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
test('ignores a call through a setter parameter that shadows the import', async () => {
|
|
524
|
+
const source = `${RESOLVE_ICON_IMPORT}export const menu = {\n set icon(resolveIcon) {\n resolveIcon('Wallet')\n }\n}`
|
|
525
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
test('ignores a call through a constructor parameter that shadows the import', async () => {
|
|
529
|
+
const source = `${RESOLVE_ICON_IMPORT}export class Menu {\n constructor(resolveIcon) {\n resolveIcon('Wallet')\n }\n}`
|
|
530
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
test("ignores a call through a named class expression's own name", async () => {
|
|
534
|
+
const source = `${RESOLVE_ICON_IMPORT}export const Menu = class resolveIcon {\n static render() {\n return resolveIcon('Wallet')\n }\n}`
|
|
535
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), [])
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
test('ignores a call through a var hoisted inside a getter, setter or constructor body, in a class too', async () => {
|
|
539
|
+
const hoisted = "if (true) { var resolveIcon = (name) => name }\n resolveIcon('Wallet')"
|
|
540
|
+
for (const member of [`get icon() {\n ${hoisted}\n }`, `set icon(value) {\n ${hoisted}\n }`, `constructor() {\n ${hoisted}\n }`]) {
|
|
541
|
+
assert.deepEqual(await extractLiteralIconCallNames(`${RESOLVE_ICON_IMPORT}export class Menu {\n ${member}\n}`), [], member)
|
|
542
|
+
}
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
test('still resolves the real import outside a setter, constructor or class expression that shadowed it', async () => {
|
|
546
|
+
const source = `${RESOLVE_ICON_IMPORT}const a = { set icon(resolveIcon) {} }\nclass B { constructor(resolveIcon) {} }\nconst C = class resolveIcon {}\nresolveIcon('Receipt')`
|
|
547
|
+
assert.deepEqual(await extractLiteralIconCallNames(source), ['Receipt'])
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
test('a var inside a class static block stays in that block, shadowing inside it and not after it', async () => {
|
|
551
|
+
const inside = `${RESOLVE_ICON_IMPORT}class Menu {\n static {\n if (true) { var resolveIcon = (name) => name }\n resolveIcon('Wallet')\n }\n}`
|
|
552
|
+
const after = `${RESOLVE_ICON_IMPORT}class Menu {\n static {\n var resolveIcon = (name) => name\n }\n}\nresolveIcon('Receipt')`
|
|
553
|
+
assert.deepEqual(await extractLiteralIconCallNames(inside), [])
|
|
554
|
+
assert.deepEqual(await extractLiteralIconCallNames(after), ['Receipt'])
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
test('a declaration inside a namespace shadows the import there and nowhere else', async () => {
|
|
558
|
+
const inside = `${RESOLVE_ICON_IMPORT}namespace Menu {\n const resolveIcon = (name: string) => name\n resolveIcon('Wallet')\n}`
|
|
559
|
+
const after = `${RESOLVE_ICON_IMPORT}namespace Menu {\n var resolveIcon = (name: string) => name\n}\nresolveIcon('Receipt')`
|
|
560
|
+
assert.deepEqual(await extractLiteralIconCallNames(inside, 'menu.ts'), [])
|
|
561
|
+
assert.deepEqual(await extractLiteralIconCallNames(after, 'menu.ts'), ['Receipt'])
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
// --- Decoy tests: a name in a non-code position must not reach the registry --
|
|
565
|
+
//
|
|
566
|
+
// A name written inside a comment, a docblock, or only in a test file is not
|
|
567
|
+
// something a real request can ever ask for by string, so discovery must not
|
|
568
|
+
// pick it up just because it looks the same as the code shapes above.
|
|
569
|
+
|
|
570
|
+
test('decoy: a line comment holding `icon: \'Ambulance\'` is not code', async () => {
|
|
571
|
+
const source = `
|
|
572
|
+
// icon: 'Ambulance'
|
|
573
|
+
export const config = {}
|
|
574
|
+
`
|
|
575
|
+
assert.deepEqual(await extractIconNames(source), [])
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
test('decoy: a block comment holding `icon: \'Bird\'` is not code', async () => {
|
|
579
|
+
const source = `
|
|
580
|
+
/* icon: 'Bird' */
|
|
581
|
+
export const config = {}
|
|
582
|
+
`
|
|
583
|
+
assert.deepEqual(await extractIconNames(source), [])
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
test('decoy: a docblock mentioning a lucide identifier is not code', async () => {
|
|
587
|
+
const source = `
|
|
588
|
+
import { Apple } from 'lucide-react'
|
|
589
|
+
/**
|
|
590
|
+
* Renders icon: Apple as an example of a config entry.
|
|
591
|
+
*/
|
|
592
|
+
export const config = {}
|
|
593
|
+
`
|
|
594
|
+
assert.deepEqual(await extractIconNames(source), [])
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
test('decoy: a `<DynamicIcon>` inside a line comment is not a call', async () => {
|
|
598
|
+
const source = `${DYNAMIC_ICON_IMPORT}
|
|
599
|
+
// <DynamicIcon name="Cherry" />
|
|
600
|
+
export function Widget() { return null }
|
|
601
|
+
`
|
|
602
|
+
assert.deepEqual(await extractLiteralIconCallNames(source, 'widget.tsx'), [])
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
test('decoy: a `<DynamicIcon>` inside a block comment is not a call', async () => {
|
|
606
|
+
const source = `${DYNAMIC_ICON_IMPORT}
|
|
607
|
+
/* <DynamicIcon name="Bug" /> */
|
|
608
|
+
export function Widget() { return null }
|
|
609
|
+
`
|
|
610
|
+
assert.deepEqual(await extractLiteralIconCallNames(source, 'widget.tsx'), [])
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
test('decoy: a `resolveIcon(...)` inside a block comment is not a call', async () => {
|
|
614
|
+
const source = `${RESOLVE_ICON_IMPORT}
|
|
615
|
+
/* resolveIcon('Fish') */
|
|
616
|
+
export function Widget() { return null }
|
|
617
|
+
`
|
|
618
|
+
assert.deepEqual(await extractLiteralIconCallNames(source, 'widget.tsx'), [])
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
test('decoy: `resolveIcon(...)` in a test file is excluded by path, not by content', async () => {
|
|
622
|
+
// extractLiteralIconCallNames has no notion of "test file" - it reads real
|
|
623
|
+
// code, and `resolveIcon('Citrus')` written directly is real code. The
|
|
624
|
+
// decoy is only harmless because discoverIcons never hands it this file:
|
|
625
|
+
// isIconCallSourcePath combined with isTestFilePath keeps *.test.tsx out of
|
|
626
|
+
// the call-source scan entirely.
|
|
627
|
+
const source = `${RESOLVE_ICON_IMPORT}resolveIcon('Citrus')`
|
|
628
|
+
assert.deepEqual(await extractLiteralIconCallNames(source, 'widget.tsx'), ['Citrus'])
|
|
629
|
+
assert.ok(isIconCallSourcePath('/p/themes/default/components/widget.test.tsx'))
|
|
630
|
+
assert.ok(isTestFilePath('/p/themes/default/components/widget.test.tsx'))
|
|
130
631
|
})
|