@nextsparkjs/core 0.1.0-beta.187 → 0.1.0-beta.189
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/ApiKeyDisplay.js +19 -19
- package/dist/components/api/keys/CreateApiKeyDialog.js +21 -21
- package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicNavbar.js +34 -16
- package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
- package/dist/components/app/misc/ThemeToggle.js +3 -4
- package/dist/components/auth/SessionCookieRefresher.d.ts +5 -0
- package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -1
- package/dist/components/auth/SessionCookieRefresher.js +7 -1
- package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
- package/dist/components/auth/forms/LoginForm.js +49 -12
- package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
- package/dist/components/auth/forms/SignupForm.js +4 -3
- package/dist/components/dashboard/block-editor/block-settings-panel.js +1 -1
- package/dist/components/dashboard/block-editor/floating-block-toolbar.js +1 -1
- package/dist/components/dashboard/block-editor/sortable-block.js +1 -1
- package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/Sidebar.js +8 -6
- package/dist/components/dashboard/layouts/TopNavbar.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/TopNavbar.js +51 -54
- package/dist/components/dashboard/misc/NotificationsDropdown.d.ts.map +1 -1
- package/dist/components/dashboard/misc/NotificationsDropdown.js +17 -15
- package/dist/components/dashboard/misc/SearchDropdown.d.ts.map +1 -1
- package/dist/components/dashboard/misc/SearchDropdown.js +19 -22
- package/dist/components/dashboard/mobile/MobileBottomNav.js +1 -1
- package/dist/components/dashboard/mobile/MobileTopBar.d.ts +10 -1
- package/dist/components/dashboard/mobile/MobileTopBar.d.ts.map +1 -1
- package/dist/components/dashboard/mobile/MobileTopBar.js +4 -3
- package/dist/components/dashboard/navigation/DynamicNavigation.js +5 -5
- package/dist/components/devtools/BlockDetailViewer.js +1 -1
- package/dist/components/devtools/BlocksViewer.js +1 -1
- package/dist/components/entities/EntityChildManager.js +5 -5
- package/dist/components/entities/EntityDetailPage.d.ts.map +1 -1
- package/dist/components/entities/EntityDetailPage.js +16 -15
- package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
- package/dist/components/entities/EntityFieldRenderer.js +12 -7
- package/dist/components/entities/EntityPageHeader.js +5 -5
- package/dist/components/entities/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/entities/ErrorBoundary.js +20 -21
- package/dist/components/entities/wrappers/EntityDetailWrapper.js +5 -5
- package/dist/components/entities/wrappers/EntityFormWrapper.js +2 -2
- package/dist/components/entities/wrappers/EntityListWrapper.js +3 -3
- package/dist/components/permissions/NoPermission.js +10 -10
- package/dist/components/public/pageBuilder/PageRenderer.d.ts.map +1 -1
- package/dist/components/public/pageBuilder/PageRenderer.js +2 -1
- package/dist/components/settings/SettingsPageSkeleton.js +4 -4
- package/dist/components/settings/layouts/SettingsSidebar.js +6 -6
- package/dist/components/shared/MultiSelectFilter.d.ts +3 -1
- package/dist/components/shared/MultiSelectFilter.d.ts.map +1 -1
- package/dist/components/shared/MultiSelectFilter.js +7 -3
- package/dist/components/shared/SearchInput.d.ts +2 -0
- package/dist/components/shared/SearchInput.d.ts.map +1 -1
- package/dist/components/shared/SearchInput.js +64 -6
- package/dist/components/ui/address-input.js +18 -18
- package/dist/components/ui/audio-upload.js +9 -9
- package/dist/components/ui/combobox.d.ts.map +1 -1
- package/dist/components/ui/combobox.js +4 -4
- package/dist/components/ui/country-select.d.ts.map +1 -1
- package/dist/components/ui/country-select.js +3 -3
- package/dist/components/ui/currency-select.d.ts.map +1 -1
- package/dist/components/ui/currency-select.js +3 -3
- package/dist/components/ui/dynamic-icon.d.ts.map +1 -1
- package/dist/components/ui/dynamic-icon.js +3 -2
- package/dist/components/ui/file-upload.js +7 -7
- package/dist/components/ui/image-upload.js +8 -8
- package/dist/components/ui/multi-select.d.ts +2 -1
- package/dist/components/ui/multi-select.d.ts.map +1 -1
- package/dist/components/ui/multi-select.js +4 -3
- package/dist/components/ui/phone-input.d.ts.map +1 -1
- package/dist/components/ui/phone-input.js +3 -3
- package/dist/components/ui/relation-display.js +1 -1
- package/dist/components/ui/simple-relation-select.d.ts +2 -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/tags-input.d.ts.map +1 -1
- package/dist/components/ui/tags-input.js +1 -1
- package/dist/components/ui/timezone-select.d.ts.map +1 -1
- package/dist/components/ui/timezone-select.js +3 -3
- package/dist/components/ui/user-display.js +4 -4
- package/dist/components/ui/user-select.d.ts.map +1 -1
- package/dist/components/ui/user-select.js +9 -9
- package/dist/components/ui/video-upload.js +9 -9
- package/dist/contexts/SubscriptionContext.d.ts.map +1 -1
- package/dist/contexts/SubscriptionContext.js +5 -1
- package/dist/contexts/TeamContext.d.ts.map +1 -1
- package/dist/contexts/TeamContext.js +83 -26
- package/dist/emails/otp-verification.d.ts.map +1 -1
- package/dist/emails/otp-verification.js +3 -2
- package/dist/hooks/useAccountPreferencesSync.d.ts +13 -0
- package/dist/hooks/useAccountPreferencesSync.d.ts.map +1 -0
- package/dist/hooks/useAccountPreferencesSync.js +95 -0
- package/dist/hooks/useAuth.d.ts +202 -15
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +49 -13
- package/dist/hooks/useEntityConfig.js +2 -2
- package/dist/hooks/useEntitySearch.js +10 -10
- package/dist/hooks/useNotifications.js +12 -12
- package/dist/hooks/usePrefetch.d.ts.map +1 -1
- package/dist/hooks/usePrefetch.js +6 -2
- package/dist/hooks/useSearch.js +15 -15
- package/dist/hooks/useSessionCookieRefresh.d.ts +18 -1
- package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -1
- package/dist/hooks/useSessionCookieRefresh.js +17 -4
- package/dist/hooks/useSessionHint.d.ts +12 -0
- package/dist/hooks/useSessionHint.d.ts.map +1 -0
- package/dist/hooks/useSessionHint.js +15 -0
- package/dist/hooks/useTranslationPreload.js +6 -6
- package/dist/i18n.d.ts +13 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +2 -27
- package/dist/lib/actions/entity.actions.d.ts.map +1 -1
- package/dist/lib/actions/entity.actions.js +3 -2
- package/dist/lib/api/auth/dual-auth.d.ts +4 -1
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +5 -16
- package/dist/lib/api/entities.js +33 -33
- package/dist/lib/api/keys.d.ts +27 -27
- package/dist/lib/api/keys.js +27 -27
- package/dist/lib/auth/callback-url.d.ts +11 -0
- package/dist/lib/auth/callback-url.d.ts.map +1 -0
- package/dist/lib/auth/callback-url.js +14 -0
- package/dist/lib/auth/otp-config.d.ts +55 -0
- package/dist/lib/auth/otp-config.d.ts.map +1 -0
- package/dist/lib/auth/otp-config.js +48 -0
- package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
- package/dist/lib/auth/registration-helpers.js +3 -1
- package/dist/lib/auth/session-hint.d.ts +31 -0
- package/dist/lib/auth/session-hint.d.ts.map +1 -0
- package/dist/lib/auth/session-hint.js +37 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +6 -4
- package/dist/lib/blocks/loader.d.ts +3 -11
- package/dist/lib/blocks/loader.d.ts.map +1 -1
- package/dist/lib/blocks/loader.js +1 -9
- package/dist/lib/blocks/loader.server.d.ts +18 -0
- package/dist/lib/blocks/loader.server.d.ts.map +1 -0
- package/dist/lib/blocks/loader.server.js +11 -0
- package/dist/lib/blocks/sanitize-html.d.ts +31 -0
- package/dist/lib/blocks/sanitize-html.d.ts.map +1 -0
- package/dist/lib/blocks/sanitize-html.js +60 -0
- package/dist/lib/config/config-sync.d.ts +1 -0
- package/dist/lib/config/config-sync.d.ts.map +1 -1
- package/dist/lib/config/config-sync.js +4 -1
- package/dist/lib/config/types.d.ts +48 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/email/types.d.ts +6 -0
- package/dist/lib/email/types.d.ts.map +1 -1
- package/dist/lib/entities/serialization.d.ts.map +1 -1
- package/dist/lib/entities/serialization.js +3 -6
- package/dist/lib/icons.d.ts +12 -0
- package/dist/lib/icons.d.ts.map +1 -0
- package/dist/lib/icons.js +30 -0
- package/dist/lib/locale.d.ts +25 -1
- package/dist/lib/locale.d.ts.map +1 -1
- package/dist/lib/locale.js +49 -35
- package/dist/lib/mcp/engine.js +1 -1
- package/dist/lib/mcp/errors.js +17 -17
- package/dist/lib/mcp/schema-builder.js +16 -16
- package/dist/lib/mcp/tool-generator.js +18 -18
- package/dist/lib/search-highlighting.d.ts +7 -0
- package/dist/lib/search-highlighting.d.ts.map +1 -1
- package/dist/lib/search-highlighting.js +6 -2
- package/dist/lib/selectors/core-selectors.d.ts +2 -0
- package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.d.ts +1 -0
- package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.js +1 -0
- package/dist/lib/selectors/domains/entities.selectors.d.ts +2 -0
- package/dist/lib/selectors/domains/entities.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/entities.selectors.js +2 -0
- package/dist/lib/selectors/selectors.d.ts +4 -0
- package/dist/lib/selectors/selectors.d.ts.map +1 -1
- package/dist/lib/services/block.service.js +1 -1
- package/dist/lib/teams/active-team-cookie.d.ts +24 -0
- package/dist/lib/teams/active-team-cookie.d.ts.map +1 -0
- package/dist/lib/teams/active-team-cookie.js +17 -0
- package/dist/lib/teams/dashboard-team.d.ts +20 -0
- package/dist/lib/teams/dashboard-team.d.ts.map +1 -0
- package/dist/lib/teams/dashboard-team.js +38 -0
- package/dist/lib/teams/schema.d.ts.map +1 -1
- package/dist/lib/teams/schema.js +1 -1
- package/dist/lib/template-resolver.d.ts.map +1 -1
- package/dist/lib/template-resolver.js +0 -1
- package/dist/lib/theme/get-default-theme-mode.d.ts +7 -4
- package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
- package/dist/lib/theme/get-default-theme-mode.js +2 -44
- package/dist/messages/de/auth.json +4 -2
- package/dist/messages/de/common.json +83 -3
- package/dist/messages/de/email.json +1 -1
- package/dist/messages/de/index.d.ts +87 -0
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/de/settings.json +5 -0
- package/dist/messages/en/auth.json +4 -2
- package/dist/messages/en/common.json +83 -3
- package/dist/messages/en/email.json +1 -1
- package/dist/messages/en/index.d.ts +86 -0
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/en/settings.json +4 -0
- package/dist/messages/es/auth.json +4 -2
- package/dist/messages/es/common.json +83 -3
- package/dist/messages/es/email.json +1 -1
- package/dist/messages/es/index.d.ts +86 -0
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/es/settings.json +4 -0
- package/dist/messages/fr/auth.json +4 -2
- package/dist/messages/fr/common.json +83 -3
- package/dist/messages/fr/email.json +1 -1
- package/dist/messages/fr/index.d.ts +87 -0
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/fr/settings.json +5 -0
- package/dist/messages/it/auth.json +4 -2
- package/dist/messages/it/common.json +83 -3
- package/dist/messages/it/email.json +1 -1
- package/dist/messages/it/index.d.ts +87 -0
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/it/settings.json +5 -0
- package/dist/messages/pt/auth.json +4 -2
- package/dist/messages/pt/common.json +83 -3
- package/dist/messages/pt/email.json +1 -1
- package/dist/messages/pt/index.d.ts +87 -0
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/messages/pt/settings.json +5 -0
- package/dist/nextspark-registries.d.ts +54 -0
- package/dist/providers/DashboardProviders.d.ts +13 -10
- package/dist/providers/DashboardProviders.d.ts.map +1 -1
- package/dist/providers/DashboardProviders.js +7 -1
- package/dist/templates/app/(auth)/forgot-password/page.tsx +2 -2
- package/dist/templates/app/(auth)/reset-password/page.tsx +6 -6
- package/dist/templates/app/403/page.tsx +6 -6
- package/dist/templates/app/api/v1/teams/switch/route.ts +24 -5
- package/dist/templates/app/dashboard/(main)/[entity]/layout.tsx +9 -7
- package/dist/templates/app/dashboard/(main)/layout.tsx +4 -3
- package/dist/templates/app/dashboard/settings/api-keys/page.tsx +36 -34
- package/dist/templates/app/dashboard/settings/profile/page.tsx +7 -6
- package/dist/templates/app/dashboard/settings/security/page.tsx +5 -4
- package/dist/templates/app/layout.ppr.tsx +8 -8
- package/dist/templates/app/layout.tsx +12 -17
- package/dist/templates/blocks/cta-section/component.tsx +5 -2
- package/dist/templates/blocks/cta-section/config.ts +3 -2
- package/dist/templates/blocks/cta-section/fields.ts +2 -2
- package/dist/templates/blocks/cta-section/schema.ts +2 -2
- package/dist/templates/blocks/features-grid/component.tsx +10 -2
- package/dist/templates/blocks/features-grid/config.ts +3 -2
- package/dist/templates/blocks/features-grid/fields.ts +2 -2
- package/dist/templates/blocks/features-grid/schema.ts +2 -2
- package/dist/templates/blocks/hero/component.tsx +6 -3
- package/dist/templates/blocks/hero/config.ts +3 -2
- package/dist/templates/blocks/hero/fields.ts +2 -2
- package/dist/templates/blocks/hero/schema.ts +2 -2
- package/dist/templates/blocks/testimonials/component.tsx +6 -3
- package/dist/templates/blocks/testimonials/config.ts +3 -2
- package/dist/templates/blocks/testimonials/fields.ts +2 -2
- package/dist/templates/blocks/testimonials/schema.ts +2 -2
- package/dist/templates/blocks/text-content/component.tsx +7 -3
- package/dist/templates/blocks/text-content/config.ts +3 -2
- package/dist/templates/blocks/text-content/fields.ts +2 -2
- package/dist/templates/blocks/text-content/schema.ts +2 -2
- package/dist/templates/contents/themes/starter/blocks/cta-section/component.tsx +102 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/fields.ts +89 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/schema.ts +32 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/component.tsx +119 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/fields.ts +97 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/schema.ts +40 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/blocks/hero/component.tsx +105 -0
- package/dist/templates/contents/themes/starter/blocks/hero/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/hero/fields.ts +60 -0
- package/dist/templates/contents/themes/starter/blocks/hero/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/hero/schema.ts +33 -0
- package/dist/templates/contents/themes/starter/blocks/hero/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/component.tsx +116 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/fields.ts +105 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/schema.ts +41 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/component.tsx +100 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/fields.ts +88 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/schema.ts +30 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/config/app.config.ts +8 -0
- package/dist/templates/contents/themes/starter/entities/tasks/tasks.config.ts +7 -0
- package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/devtools/access.cy.ts +9 -1
- package/dist/templates/contents/themes/starter/tests/cypress/e2e/api/entities/tasks/TaskAPIController.js +2 -2
- package/dist/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +45 -38
- package/dist/templates/contents/themes/starter/tests/cypress/fixtures/entities.json +16 -0
- package/dist/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +3 -1
- package/dist/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +3 -3
- package/dist/templates/contents/themes/starter/tests/cypress/src/entities/PagesPOM.ts +137 -0
- package/dist/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +2 -0
- package/dist/templates/contents/themes/starter/tests/cypress/src/index.ts +1 -0
- package/dist/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +42 -11
- package/dist/templates/contents/themes/starter/tests/cypress/support/commands.ts +29 -18
- package/dist/templates/features/blog/blocks/post-content/component.tsx +5 -1
- package/dist/templates/features/blog/blocks/post-content/schema.ts +1 -1
- package/dist/templates/features/pages/blocks/hero/component.tsx +3 -0
- package/dist/templates/features/pages/blocks/hero/schema.ts +1 -1
- package/dist/templates/proxy.ts +138 -53
- package/dist/types/blocks.d.ts.map +1 -1
- package/dist/types/blocks.js +1 -1
- package/dist/types/task.types.d.ts +1 -1
- package/dist/types/task.types.js +7 -7
- package/dist/utils/dev/TranslationDebugger.js +2 -2
- package/package.json +14 -5
- package/scripts/build/registry/__tests__/block-registry.test.mjs +73 -0
- package/scripts/build/registry/__tests__/icons-discovery.test.mjs +130 -0
- package/scripts/build/registry/__tests__/identifier.test.mjs +65 -0
- package/scripts/build/registry/__tests__/page-generator.test.mjs +430 -0
- package/scripts/build/registry/__tests__/scheduled-actions-registry.test.mjs +41 -0
- package/scripts/build/registry/__tests__/template-registry.test.mjs +89 -0
- package/scripts/build/registry/discovery/icons.mjs +275 -0
- package/scripts/build/registry/discovery/index.mjs +1 -0
- package/scripts/build/registry/generators/block-registry.mjs +203 -55
- package/scripts/build/registry/generators/feature-registry.mjs +9 -2
- package/scripts/build/registry/generators/icon-registry.mjs +56 -0
- package/scripts/build/registry/generators/identifier.mjs +28 -0
- package/scripts/build/registry/generators/index.mjs +1 -0
- package/scripts/build/registry/generators/plugin-registry.mjs +4 -3
- package/scripts/build/registry/generators/scheduled-actions-registry.mjs +4 -2
- package/scripts/build/registry/generators/template-registry.mjs +49 -41
- package/scripts/build/registry/generators/theme-registry.mjs +6 -7
- package/scripts/build/registry/post-build/page-generator.mjs +380 -59
- package/scripts/build/registry.mjs +9 -1
- package/templates/app/(auth)/forgot-password/page.tsx +2 -2
- package/templates/app/(auth)/reset-password/page.tsx +6 -6
- package/templates/app/403/page.tsx +6 -6
- package/templates/app/api/v1/teams/switch/route.ts +24 -5
- package/templates/app/dashboard/(main)/[entity]/layout.tsx +9 -7
- package/templates/app/dashboard/(main)/layout.tsx +4 -3
- package/templates/app/dashboard/settings/api-keys/page.tsx +36 -34
- package/templates/app/dashboard/settings/profile/page.tsx +7 -6
- package/templates/app/dashboard/settings/security/page.tsx +5 -4
- package/templates/app/layout.ppr.tsx +8 -8
- package/templates/app/layout.tsx +12 -17
- package/templates/blocks/cta-section/component.tsx +5 -2
- package/templates/blocks/cta-section/config.ts +3 -2
- package/templates/blocks/cta-section/fields.ts +2 -2
- package/templates/blocks/cta-section/schema.ts +2 -2
- package/templates/blocks/features-grid/component.tsx +10 -2
- package/templates/blocks/features-grid/config.ts +3 -2
- package/templates/blocks/features-grid/fields.ts +2 -2
- package/templates/blocks/features-grid/schema.ts +2 -2
- package/templates/blocks/hero/component.tsx +6 -3
- package/templates/blocks/hero/config.ts +3 -2
- package/templates/blocks/hero/fields.ts +2 -2
- package/templates/blocks/hero/schema.ts +2 -2
- package/templates/blocks/testimonials/component.tsx +6 -3
- package/templates/blocks/testimonials/config.ts +3 -2
- package/templates/blocks/testimonials/fields.ts +2 -2
- package/templates/blocks/testimonials/schema.ts +2 -2
- package/templates/blocks/text-content/component.tsx +7 -3
- package/templates/blocks/text-content/config.ts +3 -2
- package/templates/blocks/text-content/fields.ts +2 -2
- package/templates/blocks/text-content/schema.ts +2 -2
- package/templates/contents/themes/starter/blocks/cta-section/component.tsx +102 -0
- package/templates/contents/themes/starter/blocks/cta-section/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/cta-section/fields.ts +89 -0
- package/templates/contents/themes/starter/blocks/cta-section/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/cta-section/schema.ts +32 -0
- package/templates/contents/themes/starter/blocks/cta-section/thumbnail.png +0 -0
- package/templates/contents/themes/starter/blocks/features-grid/component.tsx +119 -0
- package/templates/contents/themes/starter/blocks/features-grid/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/features-grid/fields.ts +97 -0
- package/templates/contents/themes/starter/blocks/features-grid/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/features-grid/schema.ts +40 -0
- package/templates/contents/themes/starter/blocks/features-grid/thumbnail.png +0 -0
- package/templates/contents/themes/starter/blocks/hero/component.tsx +105 -0
- package/templates/contents/themes/starter/blocks/hero/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/hero/fields.ts +60 -0
- package/templates/contents/themes/starter/blocks/hero/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/hero/schema.ts +33 -0
- package/templates/contents/themes/starter/blocks/hero/thumbnail.png +0 -0
- package/templates/contents/themes/starter/blocks/testimonials/component.tsx +116 -0
- package/templates/contents/themes/starter/blocks/testimonials/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/testimonials/fields.ts +105 -0
- package/templates/contents/themes/starter/blocks/testimonials/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/testimonials/schema.ts +41 -0
- package/templates/contents/themes/starter/blocks/testimonials/thumbnail.png +0 -0
- package/templates/contents/themes/starter/blocks/text-content/component.tsx +100 -0
- package/templates/contents/themes/starter/blocks/text-content/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/text-content/fields.ts +88 -0
- package/templates/contents/themes/starter/blocks/text-content/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/text-content/schema.ts +30 -0
- package/templates/contents/themes/starter/blocks/text-content/thumbnail.png +0 -0
- package/templates/contents/themes/starter/config/app.config.ts +8 -0
- package/templates/contents/themes/starter/entities/tasks/tasks.config.ts +7 -0
- package/templates/contents/themes/starter/tests/cypress/e2e/_utils/devtools/access.cy.ts +9 -1
- package/templates/contents/themes/starter/tests/cypress/e2e/api/entities/tasks/TaskAPIController.js +2 -2
- package/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +45 -38
- package/templates/contents/themes/starter/tests/cypress/fixtures/entities.json +16 -0
- package/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +3 -1
- package/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +3 -3
- package/templates/contents/themes/starter/tests/cypress/src/entities/PagesPOM.ts +137 -0
- package/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +2 -0
- package/templates/contents/themes/starter/tests/cypress/src/index.ts +1 -0
- package/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +42 -11
- package/templates/contents/themes/starter/tests/cypress/support/commands.ts +29 -18
- package/templates/features/blog/blocks/post-content/component.tsx +5 -1
- package/templates/features/blog/blocks/post-content/schema.ts +1 -1
- package/templates/features/pages/blocks/hero/component.tsx +3 -0
- package/templates/features/pages/blocks/hero/schema.ts +1 -1
- package/templates/proxy.ts +138 -53
- package/tests/jest/__mocks__/@nextsparkjs/registries/block-registry.client.ts +9 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/block-registry.lazy.ts +5 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/icon-registry.ts +42 -0
- package/tests/jest/__mocks__/next-intl-server.js +85 -5
- package/tests/jest/__mocks__/next-server.js +32 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for identifier safety in generated registries.
|
|
3
|
+
*
|
|
4
|
+
* The aliases the registries import under are built from user-chosen names, and
|
|
5
|
+
* an invalid one is not a local problem: every route reaches the app through
|
|
6
|
+
* these files, so a single `as 7startupsAppConfig` stops the whole project from
|
|
7
|
+
* parsing. Replacing hyphens — what this used to do — leaves that case open.
|
|
8
|
+
*
|
|
9
|
+
* Run: node --test packages/core/scripts/build/registry/__tests__/identifier.test.mjs
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { test } from 'node:test'
|
|
13
|
+
import assert from 'node:assert/strict'
|
|
14
|
+
|
|
15
|
+
import { toSafeIdentifier } from '../generators/identifier.mjs'
|
|
16
|
+
|
|
17
|
+
/** What the generated files are fed to: an ES module parser. */
|
|
18
|
+
function isValidIdentifier(value) {
|
|
19
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('leaves a name that is already an identifier alone', () => {
|
|
23
|
+
assert.equal(toSafeIdentifier('default'), 'default')
|
|
24
|
+
assert.equal(toSafeIdentifier('myTheme'), 'myTheme')
|
|
25
|
+
assert.equal(toSafeIdentifier('theme_2'), 'theme_2')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('replaces the characters a directory name allows and an identifier does not', () => {
|
|
29
|
+
assert.equal(toSafeIdentifier('my-theme'), 'my_theme')
|
|
30
|
+
assert.equal(toSafeIdentifier('my.theme'), 'my_theme')
|
|
31
|
+
assert.equal(toSafeIdentifier('my theme'), 'my_theme')
|
|
32
|
+
assert.equal(toSafeIdentifier('@scope/name'), '_scope_name')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('prefixes a leading digit', () => {
|
|
36
|
+
assert.equal(toSafeIdentifier('7startups'), '_7startups')
|
|
37
|
+
assert.equal(toSafeIdentifier('123-app'), '_123_app')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('never returns an empty identifier', () => {
|
|
41
|
+
assert.equal(toSafeIdentifier(''), '_')
|
|
42
|
+
assert.equal(toSafeIdentifier('---'), '___')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('every result is a valid identifier, suffixed or not', () => {
|
|
46
|
+
const names = [
|
|
47
|
+
'default', 'my-theme', '7startups', '123-app', 'a.b.c', '', '---',
|
|
48
|
+
'2026', 'über-theme', 'theme!', '9', '_private', '$dollar',
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
for (const name of names) {
|
|
52
|
+
const safe = toSafeIdentifier(name)
|
|
53
|
+
assert.ok(isValidIdentifier(safe), `${JSON.stringify(name)} produced ${JSON.stringify(safe)}`)
|
|
54
|
+
// The generators append a role to the alias, so that has to stay valid too
|
|
55
|
+
assert.ok(isValidIdentifier(`${safe}AppConfig`), `${safe}AppConfig is not an identifier`)
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('distinct names stay distinct where it matters', () => {
|
|
60
|
+
// Themes sit in one directory, so their names cannot collide after cleaning
|
|
61
|
+
// unless they differed only by a separator — which the filesystem allows.
|
|
62
|
+
// Documented rather than fixed: the alias is cosmetic, the registry is keyed
|
|
63
|
+
// by the real name.
|
|
64
|
+
assert.equal(toSafeIdentifier('my-theme'), toSafeIdentifier('my.theme'))
|
|
65
|
+
})
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the page-generator's route-level export handling.
|
|
3
|
+
*
|
|
4
|
+
* Next.js only reads route segment config (`revalidate`, `dynamic`, etc.)
|
|
5
|
+
* when it's a literal `export const` declared directly in the route file -
|
|
6
|
+
* a re-export is silently ignored and the default config is used instead
|
|
7
|
+
* (#191). The generator has to tell the two kinds of route export apart:
|
|
8
|
+
* segment config gets re-declared as a literal, everything else (metadata,
|
|
9
|
+
* generateMetadata, ...) can be forwarded with a plain re-export.
|
|
10
|
+
*
|
|
11
|
+
* Run: node --test packages/core/scripts/build/registry/__tests__/page-generator.test.mjs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { test } from 'node:test'
|
|
15
|
+
import assert from 'node:assert/strict'
|
|
16
|
+
import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises'
|
|
17
|
+
import { tmpdir } from 'node:os'
|
|
18
|
+
import { join, dirname } from 'node:path'
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
SEGMENT_CONFIG_EXPORTS,
|
|
22
|
+
MODULE_LEVEL_EXPORTS,
|
|
23
|
+
extractRouteExports,
|
|
24
|
+
generateTemplatePage,
|
|
25
|
+
generateMissingPages,
|
|
26
|
+
} from '../post-build/page-generator.mjs'
|
|
27
|
+
|
|
28
|
+
const FIXTURE_FILE = '/virtual/theme/templates/(public)/page.tsx'
|
|
29
|
+
const FIXTURE_FILE_PATTERN = FIXTURE_FILE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
30
|
+
|
|
31
|
+
// --- helpers for the content-generation tests ------------------------------
|
|
32
|
+
|
|
33
|
+
async function createProjectRoot() {
|
|
34
|
+
return mkdtemp(join(tmpdir(), 'nextspark-page-generator-test-'))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// generateTemplatePage resolves '@/...' template paths against the
|
|
38
|
+
// generator's module-level rootDir, which is only ever set as a side effect
|
|
39
|
+
// of generateMissingPages. Templates being empty makes it a no-op beyond
|
|
40
|
+
// that assignment (cleanupOrphanedTemplates returns early when app/(templates)
|
|
41
|
+
// doesn't exist yet under the given root).
|
|
42
|
+
async function useProjectRoot(root) {
|
|
43
|
+
await generateMissingPages([], { projectRoot: root })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function writeThemeTemplate(root, relativePath, content) {
|
|
47
|
+
const absolutePath = join(root, 'contents/themes/testtheme/templates', relativePath)
|
|
48
|
+
await mkdir(dirname(absolutePath), { recursive: true })
|
|
49
|
+
await writeFile(absolutePath, content, 'utf8')
|
|
50
|
+
return `@/contents/themes/testtheme/templates/${relativePath}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- the two exported export-name tables ------------------------------------
|
|
54
|
+
|
|
55
|
+
test('segment config and module-level export lists do not overlap', () => {
|
|
56
|
+
const overlap = SEGMENT_CONFIG_EXPORTS.filter(name => MODULE_LEVEL_EXPORTS.includes(name))
|
|
57
|
+
assert.deepEqual(overlap, [])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('experimental_ppr is one of the tracked segment config keys', () => {
|
|
61
|
+
assert.ok(SEGMENT_CONFIG_EXPORTS.includes('experimental_ppr'))
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// --- extractRouteExports: valid segment config literals ---------------------
|
|
65
|
+
|
|
66
|
+
test('a number literal is captured as segment config', async () => {
|
|
67
|
+
const { segmentConfig } = await extractRouteExports('export const maxDuration = 30\n', FIXTURE_FILE)
|
|
68
|
+
assert.equal(segmentConfig.maxDuration, 30)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('revalidate = false is captured as a literal, not treated as absent', async () => {
|
|
72
|
+
const { segmentConfig } = await extractRouteExports('export const revalidate = false\n', FIXTURE_FILE)
|
|
73
|
+
assert.equal(segmentConfig.revalidate, false)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('a valid enum string is captured', async () => {
|
|
77
|
+
const { segmentConfig } = await extractRouteExports("export const dynamic = 'force-static'\n", FIXTURE_FILE)
|
|
78
|
+
assert.equal(segmentConfig.dynamic, 'force-static')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('an array of strings is captured', async () => {
|
|
82
|
+
const { segmentConfig } = await extractRouteExports("export const preferredRegion = ['iad1', 'sfo1']\n", FIXTURE_FILE)
|
|
83
|
+
assert.deepEqual(segmentConfig.preferredRegion, ['iad1', 'sfo1'])
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('a satisfies-wrapped literal is unwrapped and captured', async () => {
|
|
87
|
+
const { segmentConfig } = await extractRouteExports(
|
|
88
|
+
"export const runtime = 'edge' satisfies 'edge' | 'nodejs'\n",
|
|
89
|
+
FIXTURE_FILE
|
|
90
|
+
)
|
|
91
|
+
assert.equal(segmentConfig.runtime, 'edge')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('an as-const-wrapped literal is unwrapped and captured', async () => {
|
|
95
|
+
const { segmentConfig } = await extractRouteExports('export const dynamicParams = false as const\n', FIXTURE_FILE)
|
|
96
|
+
assert.equal(segmentConfig.dynamicParams, false)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('a boolean literal is captured for experimental_ppr', async () => {
|
|
100
|
+
const { segmentConfig } = await extractRouteExports('export const experimental_ppr = true\n', FIXTURE_FILE)
|
|
101
|
+
assert.equal(segmentConfig.experimental_ppr, true)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// --- extractRouteExports: module-level exports -------------------------------
|
|
105
|
+
|
|
106
|
+
test('an exported function declaration is a module-level export', async () => {
|
|
107
|
+
const { moduleExports, segmentConfig } = await extractRouteExports(
|
|
108
|
+
'export async function generateMetadata() { return {} }\n',
|
|
109
|
+
FIXTURE_FILE
|
|
110
|
+
)
|
|
111
|
+
assert.deepEqual(moduleExports, ['generateMetadata'])
|
|
112
|
+
assert.deepEqual(segmentConfig, {})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('a locally re-exported function is a module-level export', async () => {
|
|
116
|
+
const source = `async function generateStaticParams() { return [] }\nexport { generateStaticParams }\n`
|
|
117
|
+
const { moduleExports } = await extractRouteExports(source, FIXTURE_FILE)
|
|
118
|
+
assert.deepEqual(moduleExports, ['generateStaticParams'])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('an exported const object (metadata) is a module-level export, value untouched', async () => {
|
|
122
|
+
const source = `export const metadata = { title: 'Blog' }\n`
|
|
123
|
+
const { moduleExports, segmentConfig } = await extractRouteExports(source, FIXTURE_FILE)
|
|
124
|
+
assert.deepEqual(moduleExports, ['metadata'])
|
|
125
|
+
assert.deepEqual(segmentConfig, {})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// --- extractRouteExports: what does NOT count as an export -------------------
|
|
129
|
+
|
|
130
|
+
test('a commented-out export is not detected', async () => {
|
|
131
|
+
const source = '// export const revalidate = 60\nexport default function Page() { return null }\n'
|
|
132
|
+
const { segmentConfig } = await extractRouteExports(source, FIXTURE_FILE)
|
|
133
|
+
assert.deepEqual(segmentConfig, {})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('export syntax inside a string literal is not detected', async () => {
|
|
137
|
+
const source = 'const code = "export const revalidate = 60"\nexport default function Page() { return null }\n'
|
|
138
|
+
const { segmentConfig } = await extractRouteExports(source, FIXTURE_FILE)
|
|
139
|
+
assert.deepEqual(segmentConfig, {})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// --- extractRouteExports: invalid segment config must throw ------------------
|
|
143
|
+
|
|
144
|
+
test('an expression value fails with an error naming the file and the key', async () => {
|
|
145
|
+
await assert.rejects(
|
|
146
|
+
() => extractRouteExports('export const revalidate = 60 * 60\n', FIXTURE_FILE),
|
|
147
|
+
error => {
|
|
148
|
+
assert.match(error.message, new RegExp(FIXTURE_FILE_PATTERN))
|
|
149
|
+
assert.match(error.message, /"revalidate"/)
|
|
150
|
+
assert.match(error.message, /literal/)
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('an identifier value fails', async () => {
|
|
157
|
+
const source = 'const HOUR = 3600\nexport const revalidate = HOUR\n'
|
|
158
|
+
await assert.rejects(() => extractRouteExports(source, FIXTURE_FILE), /"revalidate"/)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('re-exporting a segment config key from another module fails', async () => {
|
|
162
|
+
const source = "export { revalidate } from './shared-config'\n"
|
|
163
|
+
await assert.rejects(() => extractRouteExports(source, FIXTURE_FILE), /re-exported from another module/)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('a local export specifier (no inline const) fails for a segment config key', async () => {
|
|
167
|
+
const source = 'const revalidate = 60\nexport { revalidate }\n'
|
|
168
|
+
await assert.rejects(() => extractRouteExports(source, FIXTURE_FILE), /"revalidate"/)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('let/var for a segment config key fails', async () => {
|
|
172
|
+
const source = "export let dynamic = 'force-static'\n"
|
|
173
|
+
await assert.rejects(() => extractRouteExports(source, FIXTURE_FILE), /"let" or "var"/)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('a value outside the schema fails', async () => {
|
|
177
|
+
const source = "export const dynamic = 'nonsense'\n"
|
|
178
|
+
await assert.rejects(() => extractRouteExports(source, FIXTURE_FILE), /outside what Next\.js accepts/)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('a negative maxDuration fails schema validation', async () => {
|
|
182
|
+
const source = 'export const maxDuration = -5\n'
|
|
183
|
+
await assert.rejects(() => extractRouteExports(source, FIXTURE_FILE), /"maxDuration"/)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('an array with a non-string element fails schema validation', async () => {
|
|
187
|
+
const source = "export const preferredRegion = ['iad1', 42]\n"
|
|
188
|
+
await assert.rejects(() => extractRouteExports(source, FIXTURE_FILE), /"preferredRegion"/)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('the error names the line the bad export is declared on', async () => {
|
|
192
|
+
const source = "import { x } from 'y'\n\nexport const revalidate = computeRevalidate()\n"
|
|
193
|
+
await assert.rejects(() => extractRouteExports(source, FIXTURE_FILE), new RegExp(`${FIXTURE_FILE_PATTERN}:3`))
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// --- generated page content ---------------------------------------------------
|
|
197
|
+
|
|
198
|
+
test('the generated page re-declares segment config as a literal and forwards module exports separately', async () => {
|
|
199
|
+
const root = await createProjectRoot()
|
|
200
|
+
try {
|
|
201
|
+
await useProjectRoot(root)
|
|
202
|
+
const templatePath = await writeThemeTemplate(
|
|
203
|
+
root,
|
|
204
|
+
'(public)/blog/[slug]/page.tsx',
|
|
205
|
+
"export async function generateMetadata() { return {} }\nexport const revalidate = 3600\nexport default function Page() { return null }\n"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
const outputPath = join(root, 'app/(templates)/(public)/blog/[slug]/page.tsx')
|
|
209
|
+
await generateTemplatePage(
|
|
210
|
+
{ appPath: 'app/(public)/blog/[slug]/page.tsx', templateType: 'page', name: '(public)/blog/[slug]/page', templatePath },
|
|
211
|
+
outputPath
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
const generated = await readFile(outputPath, 'utf8')
|
|
215
|
+
assert.match(generated, /export const revalidate = 3600/)
|
|
216
|
+
assert.match(generated, /export \{ generateMetadata \} from '@\/contents\/themes\/testtheme\/templates\/\(public\)\/blog\/\[slug\]\/page'/)
|
|
217
|
+
|
|
218
|
+
const exportBlockLine = generated.split('\n').find(line => line.startsWith('export {'))
|
|
219
|
+
assert.ok(exportBlockLine, 'expected an export {} re-export line')
|
|
220
|
+
for (const key of SEGMENT_CONFIG_EXPORTS) {
|
|
221
|
+
assert.ok(!exportBlockLine.includes(key), `segment config key "${key}" must not appear in export {}`)
|
|
222
|
+
}
|
|
223
|
+
} finally {
|
|
224
|
+
await rm(root, { recursive: true, force: true })
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('a page with no route-level exports is generated unchanged (no forwarded-exports section)', async () => {
|
|
229
|
+
const root = await createProjectRoot()
|
|
230
|
+
try {
|
|
231
|
+
await useProjectRoot(root)
|
|
232
|
+
const templatePath = await writeThemeTemplate(root, 'plain/page.tsx', 'export default function Page() { return null }\n')
|
|
233
|
+
|
|
234
|
+
const outputPath = join(root, 'app/(templates)/plain/page.tsx')
|
|
235
|
+
await generateTemplatePage(
|
|
236
|
+
{ appPath: 'app/plain/page.tsx', templateType: 'page', name: 'plain/page', templatePath },
|
|
237
|
+
outputPath
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
const generated = await readFile(outputPath, 'utf8')
|
|
241
|
+
assert.doesNotMatch(generated, /export const/)
|
|
242
|
+
assert.doesNotMatch(generated, /export \{/)
|
|
243
|
+
assert.match(generated, /export default TemplateComponent\n$/)
|
|
244
|
+
} finally {
|
|
245
|
+
await rm(root, { recursive: true, force: true })
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test('an invalid segment config value fails page generation instead of being silently dropped', async () => {
|
|
250
|
+
const root = await createProjectRoot()
|
|
251
|
+
try {
|
|
252
|
+
await useProjectRoot(root)
|
|
253
|
+
const templatePath = await writeThemeTemplate(
|
|
254
|
+
root,
|
|
255
|
+
'broken/page.tsx',
|
|
256
|
+
'export const revalidate = process.env.REVALIDATE\nexport default function Page() { return null }\n'
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const outputPath = join(root, 'app/(templates)/broken/page.tsx')
|
|
260
|
+
await assert.rejects(
|
|
261
|
+
() =>
|
|
262
|
+
generateTemplatePage(
|
|
263
|
+
{ appPath: 'app/broken/page.tsx', templateType: 'page', name: 'broken/page', templatePath },
|
|
264
|
+
outputPath
|
|
265
|
+
),
|
|
266
|
+
/"revalidate"/
|
|
267
|
+
)
|
|
268
|
+
} finally {
|
|
269
|
+
await rm(root, { recursive: true, force: true })
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test('a layout with a default export and metadata forwards both, unlike the previous component-only behavior', async () => {
|
|
274
|
+
const root = await createProjectRoot()
|
|
275
|
+
try {
|
|
276
|
+
await useProjectRoot(root)
|
|
277
|
+
const templatePath = await writeThemeTemplate(
|
|
278
|
+
root,
|
|
279
|
+
'dashboard/layout.tsx',
|
|
280
|
+
"export const metadata = { title: 'Dashboard' }\nexport default function Layout({ children }) { return children }\n"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
const outputPath = join(root, 'app/(templates)/dashboard/layout.tsx')
|
|
284
|
+
await generateTemplatePage(
|
|
285
|
+
{ appPath: 'app/dashboard/layout.tsx', templateType: 'layout', name: 'dashboard/layout', templatePath },
|
|
286
|
+
outputPath
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
const generated = await readFile(outputPath, 'utf8')
|
|
290
|
+
assert.match(generated, /import TemplateComponent from/)
|
|
291
|
+
assert.match(generated, /export \{ metadata \} from/)
|
|
292
|
+
} finally {
|
|
293
|
+
await rm(root, { recursive: true, force: true })
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
test('a layout template with no exports at all still gets the plain pass-through wrapper', async () => {
|
|
298
|
+
const root = await createProjectRoot()
|
|
299
|
+
try {
|
|
300
|
+
await useProjectRoot(root)
|
|
301
|
+
const templatePath = await writeThemeTemplate(root, 'empty/layout.tsx', '')
|
|
302
|
+
|
|
303
|
+
const outputPath = join(root, 'app/(templates)/empty/layout.tsx')
|
|
304
|
+
await generateTemplatePage(
|
|
305
|
+
{ appPath: 'app/empty/layout.tsx', templateType: 'layout', name: 'empty/layout', templatePath },
|
|
306
|
+
outputPath
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
const generated = await readFile(outputPath, 'utf8')
|
|
310
|
+
assert.match(generated, /Layout template - empty template file/)
|
|
311
|
+
assert.doesNotMatch(generated, /export \{/)
|
|
312
|
+
assert.doesNotMatch(generated, /export const/)
|
|
313
|
+
} finally {
|
|
314
|
+
await rm(root, { recursive: true, force: true })
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
// --- default export detection (decides component import vs pass-through) ----
|
|
319
|
+
|
|
320
|
+
for (const [label, source, expected] of [
|
|
321
|
+
['export default function', 'export default function Layout({ children }) { return children }\n', true],
|
|
322
|
+
['export default of a declared identifier', 'const Layout = ({ children }) => children\nexport default Layout\n', true],
|
|
323
|
+
['a local export clause naming default', 'function Layout({ children }) { return children }\nexport { Layout as default }\n', true],
|
|
324
|
+
['a default re-exported from another module', "export { default } from './shell'\n", true],
|
|
325
|
+
['a comment that mentions export default', "// Intentionally no export default: metadata-only layout.\nexport const metadata = { title: 'Docs' }\n", false],
|
|
326
|
+
['a string that contains export default', "export const metadata = { title: 'export default' }\n", false],
|
|
327
|
+
['a type-only default', 'type Props = { title: string }\nexport type { Props as default }\n', false],
|
|
328
|
+
]) {
|
|
329
|
+
test(`hasDefaultExport is ${expected} for ${label}`, async () => {
|
|
330
|
+
const { hasDefaultExport } = await extractRouteExports(source, FIXTURE_FILE)
|
|
331
|
+
assert.equal(hasDefaultExport, expected)
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
test('a metadata-only layout whose comment mentions export default gets the pass-through, not an import of a missing default', async () => {
|
|
336
|
+
const root = await createProjectRoot()
|
|
337
|
+
try {
|
|
338
|
+
await useProjectRoot(root)
|
|
339
|
+
const templatePath = await writeThemeTemplate(
|
|
340
|
+
root,
|
|
341
|
+
'docs/layout.tsx',
|
|
342
|
+
"// Intentionally no export default: metadata-only layout.\nexport const metadata = { title: 'Docs' }\n"
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
const outputPath = join(root, 'app/(templates)/docs/layout.tsx')
|
|
346
|
+
await generateTemplatePage(
|
|
347
|
+
{ appPath: 'app/docs/layout.tsx', templateType: 'layout', name: 'docs/layout', templatePath },
|
|
348
|
+
outputPath
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
const generated = await readFile(outputPath, 'utf8')
|
|
352
|
+
assert.match(generated, /Layout template - metadata-only/)
|
|
353
|
+
assert.doesNotMatch(generated, /import TemplateComponent from/)
|
|
354
|
+
assert.match(generated, /export \{ metadata \} from/)
|
|
355
|
+
} finally {
|
|
356
|
+
await rm(root, { recursive: true, force: true })
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
test('a layout exported through `export { X as default }` imports the theme component instead of a pass-through', async () => {
|
|
361
|
+
const root = await createProjectRoot()
|
|
362
|
+
try {
|
|
363
|
+
await useProjectRoot(root)
|
|
364
|
+
const templatePath = await writeThemeTemplate(
|
|
365
|
+
root,
|
|
366
|
+
'dashboard/layout.tsx',
|
|
367
|
+
'function DashboardLayout({ children }) { return children }\nexport { DashboardLayout as default }\n'
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
const outputPath = join(root, 'app/(templates)/dashboard/layout.tsx')
|
|
371
|
+
await generateTemplatePage(
|
|
372
|
+
{ appPath: 'app/dashboard/layout.tsx', templateType: 'layout', name: 'dashboard/layout', templatePath },
|
|
373
|
+
outputPath
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
const generated = await readFile(outputPath, 'utf8')
|
|
377
|
+
assert.match(generated, /import TemplateComponent from/)
|
|
378
|
+
assert.doesNotMatch(generated, /Pass-through component|empty template file/)
|
|
379
|
+
} finally {
|
|
380
|
+
await rm(root, { recursive: true, force: true })
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
// --- parsing as the template's own kind of file ------------------------------
|
|
385
|
+
|
|
386
|
+
test('a .ts template is parsed as TypeScript, so syntax a TSX parse reads as JSX keeps its default export', async () => {
|
|
387
|
+
const source = 'type Props = { children: string }\nconst Layout = <T>({ children }: T & Props): string => children\nexport default Layout\n'
|
|
388
|
+
const { hasDefaultExport } = await extractRouteExports(source, '/virtual/theme/templates/(public)/layout.ts')
|
|
389
|
+
assert.equal(hasDefaultExport, true)
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
test('a .ts layout with TypeScript-only syntax imports the theme component instead of a pass-through', async () => {
|
|
393
|
+
const root = await createProjectRoot()
|
|
394
|
+
try {
|
|
395
|
+
await useProjectRoot(root)
|
|
396
|
+
const templatePath = await writeThemeTemplate(
|
|
397
|
+
root,
|
|
398
|
+
'reports/layout.ts',
|
|
399
|
+
"import type { ReactNode } from 'react'\nconst ReportsLayout = <T>({ children }: T & { children: ReactNode }) => children\nexport default ReportsLayout\n"
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
const outputPath = join(root, 'app/(templates)/reports/layout.ts')
|
|
403
|
+
await generateTemplatePage(
|
|
404
|
+
{ appPath: 'app/reports/layout.ts', templateType: 'layout', name: 'reports/layout', templatePath },
|
|
405
|
+
outputPath
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
const generated = await readFile(outputPath, 'utf8')
|
|
409
|
+
assert.match(generated, /import TemplateComponent from/)
|
|
410
|
+
} finally {
|
|
411
|
+
await rm(root, { recursive: true, force: true })
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
test('a template that does not parse fails with its file and line instead of being read in part', async () => {
|
|
416
|
+
const source = 'export const revalidate = 60\nexport default function Page( {\n'
|
|
417
|
+
await assert.rejects(
|
|
418
|
+
() => extractRouteExports(source, FIXTURE_FILE),
|
|
419
|
+
new RegExp(`${FIXTURE_FILE_PATTERN}:\\d+: the template does not parse`)
|
|
420
|
+
)
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
test('syntax Next.js parses but the installed TypeScript does not (a source phase import) is read instead of rejected', async () => {
|
|
424
|
+
const source = "import source wasm from './module.wasm'\nexport const runtime = 'edge'\nexport default wasm\n"
|
|
425
|
+
for (const file of ['/virtual/theme/templates/(public)/page.ts', FIXTURE_FILE]) {
|
|
426
|
+
const { segmentConfig, hasDefaultExport } = await extractRouteExports(source, file)
|
|
427
|
+
assert.deepEqual(segmentConfig, { runtime: 'edge' }, file)
|
|
428
|
+
assert.equal(hasDefaultExport, true, file)
|
|
429
|
+
}
|
|
430
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { generateScheduledActionsRegistry } from '../generators/scheduled-actions-registry.mjs'
|
|
5
|
+
|
|
6
|
+
const theme = (name) => ({
|
|
7
|
+
name,
|
|
8
|
+
hasScheduledActions: true,
|
|
9
|
+
scheduledActionsPath: `@/contents/themes/${name}/lib/scheduled-actions`,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('generates a registry for a theme with scheduled actions', () => {
|
|
13
|
+
const out = generateScheduledActionsRegistry([theme('default')], {})
|
|
14
|
+
|
|
15
|
+
assert.match(out, /import \* as defaultScheduledActions from '@\/contents\/themes\/default\/lib\/scheduled-actions'/)
|
|
16
|
+
assert.match(out, /'default': \{/)
|
|
17
|
+
assert.match(out, /registerAllHandlers: defaultScheduledActions\.registerAllHandlers/)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('a theme name that is not a valid identifier still produces one', () => {
|
|
21
|
+
const out = generateScheduledActionsRegistry([theme('7startups'), theme('my-theme')], {})
|
|
22
|
+
|
|
23
|
+
assert.match(out, /import \* as _7startupsScheduledActions from/)
|
|
24
|
+
assert.match(out, /import \* as my_themeScheduledActions from/)
|
|
25
|
+
assert.doesNotMatch(out, /import \* as 7startups/)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// The generated file is TypeScript shipped to the project: a build-script import
|
|
29
|
+
// leaking into it would not resolve there.
|
|
30
|
+
test('does not leak the generator\'s own imports into the generated file', () => {
|
|
31
|
+
const out = generateScheduledActionsRegistry([theme('default')], {})
|
|
32
|
+
|
|
33
|
+
assert.doesNotMatch(out, /identifier\.mjs/)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('a project with no scheduled actions still gets a valid registry', () => {
|
|
37
|
+
const out = generateScheduledActionsRegistry([{ name: 'starter', hasScheduledActions: false }], {})
|
|
38
|
+
|
|
39
|
+
assert.match(out, /SCHEDULED_ACTIONS_REGISTRY/)
|
|
40
|
+
assert.doesNotMatch(out, /starterScheduledActions/)
|
|
41
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the template-registry generator.
|
|
3
|
+
*
|
|
4
|
+
* What matters here is the BINDING the generator emits. A static
|
|
5
|
+
* `import Template_0 from '...'` per template makes every route that touches
|
|
6
|
+
* the registry pull in the whole template graph (#183), so both registries
|
|
7
|
+
* have to emit a deferred import instead — and the server one has to do it
|
|
8
|
+
* without a Suspense boundary, which would let a response commit a 200 before
|
|
9
|
+
* a template's own notFound() runs (#129).
|
|
10
|
+
*
|
|
11
|
+
* Run: node --test packages/core/scripts/build/registry/__tests__/template-registry.test.mjs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { test } from 'node:test'
|
|
15
|
+
import assert from 'node:assert/strict'
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
generateTemplateRegistry,
|
|
19
|
+
generateTemplateRegistryClient
|
|
20
|
+
} from '../generators/template-registry.mjs'
|
|
21
|
+
|
|
22
|
+
const pageTemplate = {
|
|
23
|
+
name: '(public)/page',
|
|
24
|
+
themeName: 'default',
|
|
25
|
+
templateType: 'page',
|
|
26
|
+
fileName: 'page.tsx',
|
|
27
|
+
relativePath: '(public)/page.tsx',
|
|
28
|
+
appPath: 'app/(public)/page.tsx',
|
|
29
|
+
templatePath: '@/contents/themes/default/templates/(public)/page.tsx',
|
|
30
|
+
priority: 10
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const protectedTemplate = {
|
|
34
|
+
...pageTemplate,
|
|
35
|
+
name: 'dashboard/layout',
|
|
36
|
+
templateType: 'layout',
|
|
37
|
+
fileName: 'layout.meta.ts',
|
|
38
|
+
relativePath: 'dashboard/layout.meta.ts',
|
|
39
|
+
appPath: 'app/dashboard/layout.tsx',
|
|
40
|
+
templatePath: '@/contents/themes/default/templates/dashboard/layout.meta.ts'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const config = { outputDir: '/tmp/registries', projectRoot: '/tmp/project' }
|
|
44
|
+
|
|
45
|
+
test('server registry defers the import instead of hoisting a static one', () => {
|
|
46
|
+
const out = generateTemplateRegistry([pageTemplate], config)
|
|
47
|
+
|
|
48
|
+
assert.match(
|
|
49
|
+
out,
|
|
50
|
+
/component: lazyTemplate\('app\/\(public\)\/page\.tsx', \(\) => import\('@\/contents\/themes\/default\/templates\/\(public\)\/page'\)\)/
|
|
51
|
+
)
|
|
52
|
+
assert.doesNotMatch(out, /^import Template_\d+ from/m)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('names the template in the error when it has no default export', () => {
|
|
56
|
+
const out = generateTemplateRegistry([pageTemplate], config)
|
|
57
|
+
|
|
58
|
+
// Without the module loaded up front, React would otherwise fail with a bare
|
|
59
|
+
// "invalid element type" and no clue which template is at fault
|
|
60
|
+
assert.match(out, /has no default export/)
|
|
61
|
+
assert.match(out, /if \(!templateModule\.default\)/)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('server registry loads templates without a Suspense boundary', () => {
|
|
65
|
+
const out = generateTemplateRegistry([pageTemplate], config)
|
|
66
|
+
|
|
67
|
+
// An async Server Component awaits inline; next/dynamic would wrap the
|
|
68
|
+
// template in Suspense and break notFound()'s hold on the status code.
|
|
69
|
+
assert.match(out, /return async function TemplateOverride/)
|
|
70
|
+
assert.doesNotMatch(out, /^import .* from 'next\/dynamic'/m)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('metadata-only templates still carry no component', () => {
|
|
74
|
+
const out = generateTemplateRegistry([protectedTemplate], config)
|
|
75
|
+
|
|
76
|
+
assert.match(out, /component: null/)
|
|
77
|
+
assert.doesNotMatch(out, /layout\.meta'\)\)/)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('client registry defers the import through next/dynamic', async () => {
|
|
81
|
+
const out = await generateTemplateRegistryClient([pageTemplate], config)
|
|
82
|
+
|
|
83
|
+
assert.match(out, /import dynamic from 'next\/dynamic'/)
|
|
84
|
+
assert.match(
|
|
85
|
+
out,
|
|
86
|
+
/'app\/\(public\)\/page\.tsx': dynamic\(\(\) => import\('@\/contents\/themes\/default\/templates\/\(public\)\/page'\)\)/
|
|
87
|
+
)
|
|
88
|
+
assert.doesNotMatch(out, /^import ClientTemplate_\d+ from/m)
|
|
89
|
+
})
|