@nextsparkjs/core 0.1.0-beta.194 → 0.1.0-beta.196
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/app/guards/DeveloperGuard.d.ts +8 -1
- package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
- package/dist/components/app/guards/DeveloperGuard.js +2 -1
- package/dist/components/app/guards/SuperAdminGuard.d.ts +8 -1
- package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
- package/dist/components/app/guards/SuperAdminGuard.js +2 -1
- package/dist/components/billing/InvoicesPagination.js +2 -2
- 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 +39 -23
- 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/AuthGuard.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/AuthGuard.js +3 -4
- package/dist/components/dashboard/layouts/AuthenticatedDashboardLayout.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/AuthenticatedDashboardLayout.js +9 -10
- package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/DashboardAuthLayout.js +3 -4
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +2 -2
- package/dist/components/dashboard/navigation/DynamicNavigation.js +2 -2
- package/dist/components/entities/wrappers/EntityDetailWrapper.d.ts.map +1 -1
- package/dist/components/entities/wrappers/EntityDetailWrapper.js +5 -2
- package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
- package/dist/components/entities/wrappers/EntityFormWrapper.js +5 -2
- package/dist/components/entities/wrappers/EntityListWrapper.d.ts.map +1 -1
- package/dist/components/entities/wrappers/EntityListWrapper.js +5 -2
- package/dist/components/settings/layouts/SettingsSidebar.d.ts.map +1 -1
- package/dist/components/settings/layouts/SettingsSidebar.js +0 -1
- package/dist/components/teams/CreateTeamButton.js +2 -2
- package/dist/components/teams/CreateTeamDialog.js +1 -1
- package/dist/components/teams/InviteMemberButton.js +3 -3
- package/dist/components/teams/InviteMemberDialog.js +1 -1
- package/dist/components/teams/TeamPendingInvitations.js +2 -2
- package/dist/components/ui/relation-display.d.ts.map +1 -1
- package/dist/components/ui/relation-display.js +2 -1
- package/dist/components/ui/user-display.d.ts.map +1 -1
- package/dist/components/ui/user-display.js +2 -1
- package/dist/emails/otp-verification.d.ts.map +1 -1
- package/dist/emails/otp-verification.js +5 -3
- package/dist/emails/reset-password.d.ts.map +1 -1
- package/dist/emails/reset-password.js +5 -3
- package/dist/emails/team-invitation.d.ts.map +1 -1
- package/dist/emails/team-invitation.js +3 -2
- package/dist/emails/verify-email.d.ts.map +1 -1
- package/dist/emails/verify-email.js +5 -3
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +21 -4
- package/dist/hooks/usePatternUsages.d.ts.map +1 -1
- package/dist/hooks/usePatternUsages.js +3 -2
- package/dist/lib/api/api-error.d.ts +2 -0
- package/dist/lib/api/api-error.d.ts.map +1 -1
- package/dist/lib/api/api-error.js +3 -1
- package/dist/lib/api/auth/dual-auth.d.ts +2 -1
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +22 -4
- package/dist/lib/api/auth/scope-policy.d.ts +6 -3
- package/dist/lib/api/auth/scope-policy.d.ts.map +1 -1
- package/dist/lib/api/auth.d.ts +2 -0
- package/dist/lib/api/auth.d.ts.map +1 -1
- package/dist/lib/api/auth.js +10 -10
- package/dist/lib/api/client-headers.d.ts +11 -0
- package/dist/lib/api/client-headers.d.ts.map +1 -0
- package/dist/lib/api/client-headers.js +12 -0
- package/dist/lib/api/client-ip.d.ts +59 -0
- package/dist/lib/api/client-ip.d.ts.map +1 -0
- package/dist/lib/api/client-ip.js +91 -0
- package/dist/lib/api/cors-response.d.ts +55 -0
- package/dist/lib/api/cors-response.d.ts.map +1 -0
- package/dist/lib/api/cors-response.js +90 -0
- package/dist/lib/api/entities.d.ts.map +1 -1
- package/dist/lib/api/entities.js +4 -3
- package/dist/lib/api/entity/audit-log.d.ts.map +1 -1
- package/dist/lib/api/entity/audit-log.js +2 -1
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +23 -8
- package/dist/lib/api/helpers.d.ts +8 -2
- package/dist/lib/api/helpers.d.ts.map +1 -1
- package/dist/lib/api/helpers.js +32 -103
- package/dist/lib/api/keys.d.ts +6 -0
- package/dist/lib/api/keys.d.ts.map +1 -1
- package/dist/lib/api/keys.js +7 -1
- package/dist/lib/api/permission-middleware.d.ts.map +1 -1
- package/dist/lib/api/permission-middleware.js +0 -2
- package/dist/lib/api/rate-limit.d.ts +12 -0
- package/dist/lib/api/rate-limit.d.ts.map +1 -1
- package/dist/lib/api/rate-limit.js +50 -43
- package/dist/lib/api/request-origin.d.ts +16 -7
- package/dist/lib/api/request-origin.d.ts.map +1 -1
- package/dist/lib/api/request-origin.js +43 -20
- package/dist/lib/auth/load-login-page.d.ts +10 -0
- package/dist/lib/auth/load-login-page.d.ts.map +1 -0
- package/dist/lib/auth/load-login-page.js +7 -0
- package/dist/lib/auth/runtime-readiness.d.ts.map +1 -1
- package/dist/lib/auth/runtime-readiness.js +4 -0
- package/dist/lib/auth/sample-accounts.d.ts +17 -0
- package/dist/lib/auth/sample-accounts.d.ts.map +1 -0
- package/dist/lib/auth/sample-accounts.js +22 -0
- package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +1 -8
- package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -1
- package/dist/lib/auth/security-notifications/device-fingerprint.js +2 -12
- package/dist/lib/auth-email-callbacks.js +3 -3
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +13 -5
- package/dist/lib/cache/public-entity-cache.d.ts +21 -0
- package/dist/lib/cache/public-entity-cache.d.ts.map +1 -0
- package/dist/lib/cache/public-entity-cache.js +25 -0
- package/dist/lib/config/config-client.d.ts.map +1 -1
- package/dist/lib/config/config-client.js +1 -1
- package/dist/lib/config/config-sync.d.ts.map +1 -1
- package/dist/lib/config/config-sync.js +1 -1
- package/dist/lib/config/public-config-client.d.ts +1 -0
- package/dist/lib/config/public-config-client.d.ts.map +1 -1
- package/dist/lib/config/public-config-client.js +3 -1
- package/dist/lib/config/types.d.ts +2 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/constants/reserved-slugs.d.ts +17 -8
- package/dist/lib/constants/reserved-slugs.d.ts.map +1 -1
- package/dist/lib/constants/reserved-slugs.js +16 -52
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +9 -3
- package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
- package/dist/lib/entities/entity-hooks.js +4 -0
- package/dist/lib/entities/external-api-generator.d.ts.map +1 -1
- package/dist/lib/entities/external-api-generator.js +2 -4
- package/dist/lib/entities/public-slug.d.ts +46 -0
- package/dist/lib/entities/public-slug.d.ts.map +1 -0
- package/dist/lib/entities/public-slug.js +39 -0
- package/dist/lib/mcp/errors.d.ts +2 -0
- package/dist/lib/mcp/errors.d.ts.map +1 -1
- package/dist/lib/mcp/errors.js +3 -1
- package/dist/lib/mcp/index.d.ts +1 -1
- package/dist/lib/mcp/index.d.ts.map +1 -1
- package/dist/lib/mcp/index.js +2 -1
- package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.js +2 -0
- package/dist/lib/teams/actions.d.ts.map +1 -1
- package/dist/lib/teams/actions.js +0 -1
- package/dist/messages/de/billing.json +12 -1
- package/dist/messages/de/common.json +2 -0
- package/dist/messages/de/docs.json +1 -0
- package/dist/messages/de/features.json +16 -4
- package/dist/messages/de/home.json +5 -0
- package/dist/messages/de/index.d.ts +68 -25
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/de/index.js +2 -0
- package/dist/messages/de/index.ts +2 -0
- package/dist/messages/de/settings.json +1 -0
- package/dist/messages/de/teams.json +3 -18
- package/dist/messages/en/billing.json +1 -1
- package/dist/messages/en/common.json +2 -0
- package/dist/messages/en/docs.json +1 -0
- package/dist/messages/en/features.json +16 -4
- package/dist/messages/en/home.json +5 -0
- package/dist/messages/en/index.d.ts +58 -25
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/en/index.js +2 -0
- package/dist/messages/en/index.ts +2 -0
- package/dist/messages/en/settings.json +1 -0
- package/dist/messages/en/teams.json +3 -18
- package/dist/messages/es/billing.json +1 -1
- package/dist/messages/es/common.json +2 -0
- package/dist/messages/es/docs.json +1 -0
- package/dist/messages/es/features.json +16 -4
- package/dist/messages/es/home.json +5 -0
- package/dist/messages/es/index.d.ts +41 -8
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/es/index.js +2 -0
- package/dist/messages/es/index.ts +2 -0
- package/dist/messages/es/settings.json +1 -0
- package/dist/messages/es/teams.json +3 -0
- package/dist/messages/fr/billing.json +12 -1
- package/dist/messages/fr/common.json +2 -0
- package/dist/messages/fr/docs.json +1 -0
- package/dist/messages/fr/features.json +16 -4
- package/dist/messages/fr/home.json +5 -0
- package/dist/messages/fr/index.d.ts +68 -25
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/fr/index.js +2 -0
- package/dist/messages/fr/index.ts +2 -0
- package/dist/messages/fr/settings.json +1 -0
- package/dist/messages/fr/teams.json +3 -18
- package/dist/messages/it/billing.json +12 -1
- package/dist/messages/it/common.json +2 -0
- package/dist/messages/it/docs.json +1 -0
- package/dist/messages/it/features.json +16 -4
- package/dist/messages/it/home.json +5 -0
- package/dist/messages/it/index.d.ts +68 -25
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/it/index.js +2 -0
- package/dist/messages/it/index.ts +2 -0
- package/dist/messages/it/settings.json +1 -0
- package/dist/messages/it/teams.json +3 -18
- package/dist/messages/pt/billing.json +12 -1
- package/dist/messages/pt/common.json +2 -0
- package/dist/messages/pt/docs.json +1 -0
- package/dist/messages/pt/features.json +16 -4
- package/dist/messages/pt/home.json +5 -0
- package/dist/messages/pt/index.d.ts +68 -25
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/messages/pt/index.js +2 -0
- package/dist/messages/pt/index.ts +2 -0
- package/dist/messages/pt/settings.json +1 -0
- package/dist/messages/pt/teams.json +3 -18
- package/dist/migrations/029_sample_accounts_outside_development.sql +55 -0
- package/dist/migrations/090_sample_data.sql +10 -0
- package/dist/routes/(auth)/signup/page.cc.d.ts +3 -8
- package/dist/routes/(auth)/signup/page.cc.d.ts.map +1 -1
- package/dist/routes/(auth)/signup/page.cc.js +7 -1
- package/dist/routes/(auth)/verify-email/page.d.ts.map +1 -1
- package/dist/routes/(auth)/verify-email/page.js +2 -1
- package/dist/routes/(public)/page.cc.d.ts +3 -0
- package/dist/routes/(public)/page.cc.d.ts.map +1 -0
- package/dist/routes/(public)/page.cc.js +3 -0
- package/dist/routes/_internal/area-access.cc.d.ts +21 -0
- package/dist/routes/_internal/area-access.cc.d.ts.map +1 -0
- package/dist/routes/_internal/area-access.cc.js +19 -0
- package/dist/routes/_internal/auth-layout.d.ts.map +1 -1
- package/dist/routes/_internal/auth-layout.js +3 -2
- package/dist/routes/_internal/dashboard-main-shared.d.ts +7 -0
- package/dist/routes/_internal/dashboard-main-shared.d.ts.map +1 -0
- package/dist/routes/_internal/dashboard-main-shared.js +49 -0
- package/dist/routes/_internal/default-auth-layout.d.ts.map +1 -1
- package/dist/routes/_internal/default-auth-layout.js +3 -2
- package/dist/routes/_internal/devtools-layout.cc.d.ts +3 -2
- package/dist/routes/_internal/devtools-layout.cc.d.ts.map +1 -1
- package/dist/routes/_internal/devtools-layout.cc.js +1 -1
- package/dist/routes/_internal/devtools-layout.d.ts +7 -2
- package/dist/routes/_internal/devtools-layout.d.ts.map +1 -1
- package/dist/routes/_internal/devtools-layout.js +4 -4
- package/dist/routes/_internal/entity-detail-route.cc.d.ts +8 -0
- package/dist/routes/_internal/entity-detail-route.cc.d.ts.map +1 -0
- package/dist/routes/_internal/entity-detail-route.cc.js +5 -0
- package/dist/routes/_internal/entity-edit-route.cc.d.ts +8 -0
- package/dist/routes/_internal/entity-edit-route.cc.d.ts.map +1 -0
- package/dist/routes/_internal/entity-edit-route.cc.js +5 -0
- package/dist/routes/_internal/entity-permission-layout.d.ts.map +1 -1
- package/dist/routes/_internal/entity-permission-layout.js +0 -7
- package/dist/routes/_internal/public-archive-route.d.ts +1 -1
- package/dist/routes/_internal/public-archive-route.d.ts.map +1 -1
- package/dist/routes/_internal/public-archive-route.js +8 -3
- package/dist/routes/_internal/public-item-route.cc.d.ts +5 -1
- package/dist/routes/_internal/public-item-route.cc.d.ts.map +1 -1
- package/dist/routes/_internal/public-item-route.cc.js +2 -1
- package/dist/routes/_internal/public-item-route.d.ts +7 -0
- package/dist/routes/_internal/public-item-route.d.ts.map +1 -1
- package/dist/routes/_internal/public-item-route.js +6 -1
- package/dist/routes/_internal/superadmin-layout.cc.d.ts +2 -1
- package/dist/routes/_internal/superadmin-layout.cc.d.ts.map +1 -1
- package/dist/routes/_internal/superadmin-layout.cc.js +1 -1
- package/dist/routes/_internal/superadmin-layout.d.ts +7 -2
- package/dist/routes/_internal/superadmin-layout.d.ts.map +1 -1
- package/dist/routes/_internal/superadmin-layout.js +6 -6
- package/dist/routes/_internal/suspended-route.d.ts +16 -0
- package/dist/routes/_internal/suspended-route.d.ts.map +1 -0
- package/dist/routes/_internal/suspended-route.js +7 -0
- package/dist/routes/api/auth/[...all]/route.d.ts.map +1 -1
- package/dist/routes/api/auth/[...all]/route.js +4 -12
- package/dist/routes/api/csp-report/route.d.ts.map +1 -1
- package/dist/routes/api/csp-report/route.js +2 -13
- package/dist/routes/api/devtools/config/entities/route.d.ts +2 -0
- package/dist/routes/api/devtools/config/entities/route.d.ts.map +1 -1
- package/dist/routes/api/devtools/config/entities/route.js +2 -0
- package/dist/routes/api/devtools/config/theme/route.d.ts +2 -0
- package/dist/routes/api/devtools/config/theme/route.d.ts.map +1 -1
- package/dist/routes/api/devtools/config/theme/route.js +2 -0
- package/dist/routes/api/devtools/tests/[...path]/route.d.ts +2 -0
- package/dist/routes/api/devtools/tests/[...path]/route.d.ts.map +1 -1
- package/dist/routes/api/devtools/tests/[...path]/route.js +2 -0
- package/dist/routes/api/devtools/tests/route.d.ts +2 -0
- package/dist/routes/api/devtools/tests/route.d.ts.map +1 -1
- package/dist/routes/api/devtools/tests/route.js +2 -0
- package/dist/routes/api/health/route.d.ts +2 -0
- package/dist/routes/api/health/route.d.ts.map +1 -1
- package/dist/routes/api/health/route.js +2 -0
- package/dist/routes/api/internal/user-metadata/route.d.ts +2 -0
- package/dist/routes/api/internal/user-metadata/route.d.ts.map +1 -1
- package/dist/routes/api/internal/user-metadata/route.js +2 -0
- package/dist/routes/api/mcp/route.d.ts +2 -0
- package/dist/routes/api/mcp/route.d.ts.map +1 -1
- package/dist/routes/api/mcp/route.js +9 -1
- package/dist/routes/api/superadmin/subscriptions/route.d.ts +2 -0
- package/dist/routes/api/superadmin/subscriptions/route.d.ts.map +1 -1
- package/dist/routes/api/superadmin/subscriptions/route.js +2 -0
- package/dist/routes/api/superadmin/teams/[teamId]/route.d.ts +2 -0
- package/dist/routes/api/superadmin/teams/[teamId]/route.d.ts.map +1 -1
- package/dist/routes/api/superadmin/teams/[teamId]/route.js +2 -0
- package/dist/routes/api/superadmin/teams/route.d.ts +2 -0
- package/dist/routes/api/superadmin/teams/route.d.ts.map +1 -1
- package/dist/routes/api/superadmin/teams/route.js +2 -0
- package/dist/routes/api/superadmin/users/[userId]/route.d.ts +2 -0
- package/dist/routes/api/superadmin/users/[userId]/route.d.ts.map +1 -1
- package/dist/routes/api/superadmin/users/[userId]/route.js +2 -0
- package/dist/routes/api/superadmin/users/route.d.ts +2 -0
- package/dist/routes/api/superadmin/users/route.d.ts.map +1 -1
- package/dist/routes/api/superadmin/users/route.js +2 -0
- package/dist/routes/api/user/delete-account/route.d.ts +2 -0
- package/dist/routes/api/user/delete-account/route.d.ts.map +1 -1
- package/dist/routes/api/user/delete-account/route.js +2 -0
- package/dist/routes/api/user/plan-flags/route.d.ts +2 -0
- package/dist/routes/api/user/plan-flags/route.d.ts.map +1 -1
- package/dist/routes/api/user/plan-flags/route.js +2 -0
- package/dist/routes/api/user/profile/route.d.ts +2 -0
- package/dist/routes/api/user/profile/route.d.ts.map +1 -1
- package/dist/routes/api/user/profile/route.js +4 -3
- package/dist/routes/api/v1/[entity]/[id]/child/[childType]/[childId]/route.d.ts +2 -0
- package/dist/routes/api/v1/[entity]/[id]/child/[childType]/[childId]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/[entity]/[id]/child/[childType]/[childId]/route.js +2 -0
- package/dist/routes/api/v1/[entity]/[id]/child/[childType]/route.d.ts +2 -0
- package/dist/routes/api/v1/[entity]/[id]/child/[childType]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/[entity]/[id]/child/[childType]/route.js +2 -0
- package/dist/routes/api/v1/billing/cancel/route.d.ts +2 -0
- package/dist/routes/api/v1/billing/cancel/route.d.ts.map +1 -1
- package/dist/routes/api/v1/billing/cancel/route.js +2 -0
- package/dist/routes/api/v1/billing/change-plan/route.d.ts +2 -0
- package/dist/routes/api/v1/billing/change-plan/route.d.ts.map +1 -1
- package/dist/routes/api/v1/billing/change-plan/route.js +2 -0
- package/dist/routes/api/v1/billing/check-action/route.d.ts +2 -0
- package/dist/routes/api/v1/billing/check-action/route.d.ts.map +1 -1
- package/dist/routes/api/v1/billing/check-action/route.js +2 -0
- package/dist/routes/api/v1/billing/checkout/route.d.ts +2 -0
- package/dist/routes/api/v1/billing/checkout/route.d.ts.map +1 -1
- package/dist/routes/api/v1/billing/checkout/route.js +2 -0
- package/dist/routes/api/v1/billing/plans/route.d.ts +2 -0
- package/dist/routes/api/v1/billing/plans/route.d.ts.map +1 -1
- package/dist/routes/api/v1/billing/plans/route.js +2 -0
- package/dist/routes/api/v1/billing/portal/route.d.ts +2 -0
- package/dist/routes/api/v1/billing/portal/route.d.ts.map +1 -1
- package/dist/routes/api/v1/billing/portal/route.js +2 -0
- package/dist/routes/api/v1/blocks/[slug]/route.d.ts +2 -0
- package/dist/routes/api/v1/blocks/[slug]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/blocks/[slug]/route.js +2 -0
- package/dist/routes/api/v1/blocks/route.d.ts +2 -0
- package/dist/routes/api/v1/blocks/route.d.ts.map +1 -1
- package/dist/routes/api/v1/blocks/route.js +2 -0
- package/dist/routes/api/v1/blocks/validate/route.d.ts +2 -0
- package/dist/routes/api/v1/blocks/validate/route.d.ts.map +1 -1
- package/dist/routes/api/v1/blocks/validate/route.js +2 -0
- package/dist/routes/api/v1/devtools/blocks/route.d.ts +2 -1
- package/dist/routes/api/v1/devtools/blocks/route.d.ts.map +1 -1
- package/dist/routes/api/v1/devtools/blocks/route.js +2 -9
- package/dist/routes/api/v1/devtools/docs/route.d.ts +2 -0
- package/dist/routes/api/v1/devtools/docs/route.d.ts.map +1 -1
- package/dist/routes/api/v1/devtools/docs/route.js +2 -0
- package/dist/routes/api/v1/devtools/features/route.d.ts +2 -1
- package/dist/routes/api/v1/devtools/features/route.d.ts.map +1 -1
- package/dist/routes/api/v1/devtools/features/route.js +2 -9
- package/dist/routes/api/v1/devtools/flows/route.d.ts +2 -1
- package/dist/routes/api/v1/devtools/flows/route.d.ts.map +1 -1
- package/dist/routes/api/v1/devtools/flows/route.js +2 -9
- package/dist/routes/api/v1/devtools/scheduled-actions/route.d.ts +2 -1
- package/dist/routes/api/v1/devtools/scheduled-actions/route.d.ts.map +1 -1
- package/dist/routes/api/v1/devtools/scheduled-actions/route.js +2 -9
- package/dist/routes/api/v1/devtools/scheduled-actions/run/route.d.ts +2 -1
- package/dist/routes/api/v1/devtools/scheduled-actions/run/route.d.ts.map +1 -1
- package/dist/routes/api/v1/devtools/scheduled-actions/run/route.js +2 -9
- package/dist/routes/api/v1/devtools/testing/route.d.ts +2 -1
- package/dist/routes/api/v1/devtools/testing/route.d.ts.map +1 -1
- package/dist/routes/api/v1/devtools/testing/route.js +2 -9
- package/dist/routes/api/v1/media/[id]/route.d.ts +2 -0
- package/dist/routes/api/v1/media/[id]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/media/[id]/route.js +2 -0
- package/dist/routes/api/v1/media/[id]/tags/route.d.ts +2 -0
- package/dist/routes/api/v1/media/[id]/tags/route.d.ts.map +1 -1
- package/dist/routes/api/v1/media/[id]/tags/route.js +2 -0
- package/dist/routes/api/v1/media/check-duplicates/route.d.ts +2 -0
- package/dist/routes/api/v1/media/check-duplicates/route.d.ts.map +1 -1
- package/dist/routes/api/v1/media/check-duplicates/route.js +2 -0
- package/dist/routes/api/v1/media/route.d.ts +2 -0
- package/dist/routes/api/v1/media/route.d.ts.map +1 -1
- package/dist/routes/api/v1/media/route.js +2 -0
- package/dist/routes/api/v1/media/upload/route.d.ts +4 -1
- package/dist/routes/api/v1/media/upload/route.d.ts.map +1 -1
- package/dist/routes/api/v1/media/upload/route.js +12 -2
- package/dist/routes/api/v1/media-tags/route.d.ts +2 -0
- package/dist/routes/api/v1/media-tags/route.d.ts.map +1 -1
- package/dist/routes/api/v1/media-tags/route.js +2 -0
- package/dist/routes/api/v1/post-categories/[id]/route.d.ts +2 -0
- package/dist/routes/api/v1/post-categories/[id]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/post-categories/[id]/route.js +2 -0
- package/dist/routes/api/v1/post-categories/route.d.ts +2 -0
- package/dist/routes/api/v1/post-categories/route.d.ts.map +1 -1
- package/dist/routes/api/v1/post-categories/route.js +2 -0
- package/dist/routes/api/v1/teams/[teamId]/members/route.d.ts.map +1 -1
- package/dist/routes/api/v1/teams/[teamId]/members/route.js +14 -12
- package/dist/routes/api/v1/teams/[teamId]/subscription/route.d.ts +2 -0
- package/dist/routes/api/v1/teams/[teamId]/subscription/route.d.ts.map +1 -1
- package/dist/routes/api/v1/teams/[teamId]/subscription/route.js +2 -0
- package/dist/routes/api/v1/teams/[teamId]/usage/[limitSlug]/route.d.ts +2 -0
- package/dist/routes/api/v1/teams/[teamId]/usage/[limitSlug]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/teams/[teamId]/usage/[limitSlug]/route.js +2 -0
- package/dist/routes/api/v1/teams/switch/route.d.ts.map +1 -1
- package/dist/routes/api/v1/teams/switch/route.js +4 -1
- package/dist/routes/api/v1/users/[id]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/users/[id]/route.js +3 -2
- package/dist/routes/api/v1/users/docs.md +34 -19
- package/dist/routes/api/v1/users/me/route.d.ts +6 -0
- package/dist/routes/api/v1/users/me/route.d.ts.map +1 -0
- package/dist/routes/api/v1/users/me/route.js +5 -0
- package/dist/routes/api/v1/users/presets.ts +2 -2
- package/dist/routes/dashboard/(main)/layout.cc.d.ts +4 -0
- package/dist/routes/dashboard/(main)/layout.cc.d.ts.map +1 -0
- package/dist/routes/dashboard/(main)/layout.cc.js +10 -0
- package/dist/routes/dashboard/(main)/layout.d.ts.map +1 -1
- package/dist/routes/dashboard/(main)/layout.js +3 -44
- package/dist/routes/dashboard/permission-denied/page.d.ts +1 -1
- package/dist/routes/dashboard/permission-denied/page.d.ts.map +1 -1
- package/dist/routes/dashboard/permission-denied/page.js +5 -1
- package/dist/routes/dashboard/settings/password/page.d.ts.map +1 -1
- package/dist/routes/dashboard/settings/password/page.js +10 -12
- package/dist/routes/devtools/config/page.js +1 -1
- package/dist/routes/devtools/layout.cc.d.ts.map +1 -1
- package/dist/routes/devtools/layout.cc.js +5 -1
- package/dist/routes/devtools/page.js +1 -1
- package/dist/routes/devtools/scheduled-actions/page.js +1 -1
- package/dist/routes/devtools/tests/[[...path]]/page.js +1 -1
- package/dist/routes/manifest.json +10 -0
- package/dist/routes/robots.d.ts +10 -0
- package/dist/routes/robots.d.ts.map +1 -0
- package/dist/routes/robots.js +10 -0
- package/dist/routes/superadmin/layout.cc.d.ts.map +1 -1
- package/dist/routes/superadmin/layout.cc.js +5 -1
- package/dist/routes/variants.json +12 -2
- package/dist/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/dist/templates/ci-workflows/github/cypress-smoke.yml +2 -1
- package/dist/templates/next.config.mjs +2 -29
- package/dist/templates/projects/blog/api/authors/[username]/route.ts +3 -0
- package/dist/templates/projects/blog/api/authors/route.ts +3 -0
- package/dist/templates/projects/blog/api/posts/public/route.ts +3 -0
- package/dist/templates/projects/starter/README.md +2 -0
- package/dist/templates/projects/starter/docs/public/01-overview/01-introduction.md +2 -0
- package/dist/templates/projects/starter/templates/dashboard/analytics/layout.tsx +14 -0
- package/dist/templates/proxy.ts +27 -9
- package/dist/templates/public/favicon.ico +0 -0
- package/dist/templates/public/uploads/README.md +5 -94
- package/migrations/029_sample_accounts_outside_development.sql +55 -0
- package/migrations/090_sample_data.sql +10 -0
- package/package.json +2 -2
- package/scripts/build/registry/__tests__/api-docs-colocated.test.mjs +3 -0
- package/scripts/build/registry/__tests__/billing-registry.test.mjs +50 -1
- package/scripts/build/registry/__tests__/block-registry.test.mjs +3 -0
- package/scripts/build/registry/__tests__/icons-discovery.test.mjs +3 -0
- package/scripts/build/registry/__tests__/quiet-console.mjs +17 -0
- package/scripts/build/registry/__tests__/safe-fs.test.mjs +3 -0
- package/scripts/build/registry/__tests__/team-roles.test.mjs +3 -0
- package/scripts/build/registry/discovery/permissions.test.mjs +3 -0
- package/scripts/build/registry/discovery/plugin-capabilities.mjs +2 -2
- package/scripts/build/registry/generators/billing-registry.mjs +7 -3
- package/scripts/build/registry/host/README.md +25 -1
- package/scripts/build/registry/host/__tests__/host-area-access.test.mjs +32 -0
- package/scripts/build/registry/host/__tests__/host-entities.test.mjs +32 -1
- package/scripts/build/registry/host/__tests__/host-plan.test.mjs +12 -2
- package/scripts/build/registry/host/__tests__/host-proxy-areas.test.mjs +72 -1
- package/scripts/build/registry/host/entity-routes.mjs +9 -3
- package/scripts/build/registry/host/proxy-areas.mjs +65 -2
- package/scripts/build/registry/host/static-imports.mjs +4 -0
- package/scripts/build/routes-manifest.mjs +13 -1
- package/scripts/db/run-migrations.mjs +30 -10
- package/scripts/db/sample-data.mjs +44 -0
- package/scripts/db/ssl-config.mjs +12 -1
- package/scripts/db/verify-theme-migrations.mjs +6 -0
- package/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/templates/ci-workflows/github/cypress-smoke.yml +2 -1
- package/templates/next.config.mjs +2 -29
- package/templates/projects/blog/api/authors/[username]/route.ts +3 -0
- package/templates/projects/blog/api/authors/route.ts +3 -0
- package/templates/projects/blog/api/posts/public/route.ts +3 -0
- package/templates/projects/starter/README.md +2 -0
- package/templates/projects/starter/docs/public/01-overview/01-introduction.md +2 -0
- package/templates/projects/starter/templates/dashboard/analytics/layout.tsx +14 -0
- package/templates/proxy.ts +27 -9
- package/templates/public/favicon.ico +0 -0
- package/templates/public/uploads/README.md +5 -94
- package/tests/jest/__mocks__/@nextsparkjs/registries/api-presets-registry.ts +2 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/mcp-registry.ts +2 -0
- package/dist/templates/projects/starter/public/brand/.gitkeep +0 -0
- package/dist/templates/public/uploads/.gitignore +0 -6
- package/dist/templates/public/uploads/temp/.gitkeep +0 -1
- package/templates/projects/starter/public/brand/.gitkeep +0 -0
- package/templates/public/uploads/.gitignore +0 -6
- package/templates/public/uploads/temp/.gitkeep +0 -1
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
import { test } from 'node:test'
|
|
10
10
|
import assert from 'node:assert/strict'
|
|
11
|
-
import { mkdtemp, readdir, rm } from 'node:fs/promises'
|
|
11
|
+
import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'
|
|
12
12
|
import { tmpdir } from 'node:os'
|
|
13
13
|
import { dirname, join } from 'node:path'
|
|
14
14
|
import { fileURLToPath } from 'node:url'
|
|
15
15
|
|
|
16
16
|
import { generateBillingRegistry } from '../generators/billing-registry.mjs'
|
|
17
|
+
import { silenceConsoleOutput } from './quiet-console.mjs'
|
|
18
|
+
|
|
19
|
+
silenceConsoleOutput()
|
|
17
20
|
|
|
18
21
|
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..', '..', '..')
|
|
19
22
|
|
|
@@ -45,3 +48,49 @@ test("generateBillingRegistry's jiti import writes nothing under TMPDIR, even wh
|
|
|
45
48
|
await cacheRoot.cleanup()
|
|
46
49
|
}
|
|
47
50
|
})
|
|
51
|
+
|
|
52
|
+
// #212: a billing config that exists but cannot load must fail the build, never become an empty registry
|
|
53
|
+
async function projectWith(billingSource) {
|
|
54
|
+
const dir = await directory('nextspark-billing-registry-cases-')
|
|
55
|
+
if (billingSource !== undefined) {
|
|
56
|
+
await mkdir(join(dir.root, 'config'), { recursive: true })
|
|
57
|
+
await writeFile(join(dir.root, 'config/billing.config.ts'), billingSource)
|
|
58
|
+
}
|
|
59
|
+
return { ...dir, config: { projectRoot: dir.root, projectSourceDir: dir.root, projectName: 'cases', outputDir: join(dir.root, 'out') } }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test('no billing config: the empty registry, the build keeps going', async () => {
|
|
63
|
+
const project = await projectWith(undefined)
|
|
64
|
+
try {
|
|
65
|
+
assert.match(await generateBillingRegistry(project.config), /No billing config found - empty registry generated/)
|
|
66
|
+
} finally { await project.cleanup() }
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('a valid billing config is read into the registry', async () => {
|
|
70
|
+
const project = await projectWith(`export const billingConfig = { provider: 'stripe', currency: 'usd', defaultPlan: 'free', features: { api: {} }, limits: { seats: {} }, plans: [{ slug: 'free', visibility: 'public', features: ['api'], limits: { seats: 3 } }] }\n`)
|
|
71
|
+
try {
|
|
72
|
+
const output = await generateBillingRegistry(project.config)
|
|
73
|
+
assert.match(output, /"totalPlans": 1/)
|
|
74
|
+
assert.match(output, /"seats": \{\s+"free": 3/)
|
|
75
|
+
} finally { await project.cleanup() }
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('a billing config that throws fails with its file name and the underlying error', async () => {
|
|
79
|
+
const project = await projectWith(`throw new Error('stripe price id is missing')\n`)
|
|
80
|
+
try {
|
|
81
|
+
await assert.rejects(generateBillingRegistry(project.config), (error) => {
|
|
82
|
+
assert.ok(error.message.includes(join(project.root, 'config/billing.config.ts')), error.message)
|
|
83
|
+
assert.ok(error.message.includes('stripe price id is missing'), error.message)
|
|
84
|
+
return true
|
|
85
|
+
})
|
|
86
|
+
} finally { await project.cleanup() }
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('a billing config with a syntax error or without a billingConfig export also fails', async () => {
|
|
90
|
+
for (const source of ['export const billingConfig = {', 'export const other = 1\n']) {
|
|
91
|
+
const project = await projectWith(source)
|
|
92
|
+
try {
|
|
93
|
+
await assert.rejects(generateBillingRegistry(project.config), /billing\.config\.ts/)
|
|
94
|
+
} finally { await project.cleanup() }
|
|
95
|
+
}
|
|
96
|
+
})
|
|
@@ -25,6 +25,9 @@ import {
|
|
|
25
25
|
import { readSchemaExport, thumbnailImport } from '../discovery/blocks.mjs'
|
|
26
26
|
import { validateGeneratedModule } from '../host/static-imports.mjs'
|
|
27
27
|
import { loadTypeScriptFor } from '../shared/typescript-compiler.mjs'
|
|
28
|
+
import { silenceConsoleOutput } from './quiet-console.mjs'
|
|
29
|
+
|
|
30
|
+
silenceConsoleOutput()
|
|
28
31
|
|
|
29
32
|
function block(slug, category) {
|
|
30
33
|
const base = `@/blocks/${slug}`
|
|
@@ -24,6 +24,9 @@ import {
|
|
|
24
24
|
isTestFilePath,
|
|
25
25
|
extractLiteralIconCallNames
|
|
26
26
|
} from '../discovery/icons.mjs'
|
|
27
|
+
import { silenceConsoleOutput } from './quiet-console.mjs'
|
|
28
|
+
|
|
29
|
+
silenceConsoleOutput()
|
|
27
30
|
|
|
28
31
|
test('discovers core entity icons through the resolution owner coreDir', async () => {
|
|
29
32
|
const root = await mkdtemp(join(tmpdir(), 'nextspark-icons-core-dir-'))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { after, before, mock } from 'node:test'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keeps a test file's in-process console.log/info output off the test child's stdout. The Node test runner
|
|
5
|
+
* reads that stdout for its own frames, and non-ASCII text on it (the build log's emoji) can make it fail with
|
|
6
|
+
* "Unable to deserialize cloned data" (nodejs/node#64061, fixed on 24+ only; #65934 for 22).
|
|
7
|
+
* Call once at the top of a file; a test that needs to read the output replaces console.log itself.
|
|
8
|
+
*/
|
|
9
|
+
export function silenceConsoleOutput() {
|
|
10
|
+
before(() => {
|
|
11
|
+
mock.method(console, 'log', () => {})
|
|
12
|
+
mock.method(console, 'info', () => {})
|
|
13
|
+
})
|
|
14
|
+
after(() => {
|
|
15
|
+
mock.restoreAll()
|
|
16
|
+
})
|
|
17
|
+
}
|
|
@@ -23,6 +23,9 @@ import { fileURLToPath } from 'node:url'
|
|
|
23
23
|
import { COPY_BLOCK_SIZE, isUnsafeWrite, projectFiles, UNSAFE_WRITE, unsafeWriteProblem } from '../../safe-fs.mjs'
|
|
24
24
|
import { generateTestBlocksJson, generateTestEntitiesJson } from '../post-build/test-fixtures.mjs'
|
|
25
25
|
import { directFsWrites, directFsWritesIn, generatorFiles } from './direct-fs-writes.mjs'
|
|
26
|
+
import { silenceConsoleOutput } from './quiet-console.mjs'
|
|
27
|
+
|
|
28
|
+
silenceConsoleOutput()
|
|
26
29
|
|
|
27
30
|
const CORE_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..')
|
|
28
31
|
const RUNS_AS_ROOT = process.getuid?.() === 0
|
|
@@ -18,6 +18,9 @@ import assert from 'node:assert/strict'
|
|
|
18
18
|
|
|
19
19
|
import { computeTeamRoleConfig, generatePermissionsRegistry } from '../generators/permissions-registry.mjs'
|
|
20
20
|
import { parseTeamRolesFromAppConfig } from '../discovery/permissions.mjs'
|
|
21
|
+
import { silenceConsoleOutput } from './quiet-console.mjs'
|
|
22
|
+
|
|
23
|
+
silenceConsoleOutput()
|
|
21
24
|
|
|
22
25
|
// ---------------------------------------------------------------------------
|
|
23
26
|
// computeTeamRoleConfig — the pure build-time computation
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import { describe, it } from 'node:test'
|
|
8
8
|
import assert from 'node:assert'
|
|
9
|
+
import { silenceConsoleOutput } from '../__tests__/quiet-console.mjs'
|
|
10
|
+
|
|
11
|
+
silenceConsoleOutput()
|
|
9
12
|
|
|
10
13
|
// Import the regex pattern directly since parseEntitiesFromConfig is not exported
|
|
11
14
|
// We'll test the regex patterns directly
|
|
@@ -329,11 +329,11 @@ const BUILTINS = new Set(builtinModules.filter(name => !name.startsWith('_')))
|
|
|
329
329
|
/** Bare specifiers that only run on a server (`next/server` is Next's request/response and `after`/`connection` APIs). */
|
|
330
330
|
const SERVER_ONLY_PACKAGES = new Set(['server-only', 'next/headers', 'next/server', 'pg', 'postgres', 'ioredis', 'nodemailer'])
|
|
331
331
|
/**
|
|
332
|
-
* The `lib/api` modules core's own 'use client' files import (the entity fetch helpers, `ApiError`, the scope catalog):
|
|
332
|
+
* The `lib/api` modules core's own 'use client' files import (the entity fetch helpers, `ApiError`, the scope catalog, the client request header names):
|
|
333
333
|
* they import no server module, so they are client-safe. Everything else under `lib/api` is server-only. A guard test
|
|
334
334
|
* checks that every `lib/api` module a core 'use client' file imports is in this list.
|
|
335
335
|
*/
|
|
336
|
-
export const CLIENT_SAFE_CORE_API = ['entities', 'api-error', 'keys']
|
|
336
|
+
export const CLIENT_SAFE_CORE_API = ['entities', 'api-error', 'keys', 'client-headers']
|
|
337
337
|
/** Core modules known to be server-only; the ones that import a marker above are also found by following the import. */
|
|
338
338
|
const SERVER_ONLY_CORE = [
|
|
339
339
|
/^@nextsparkjs\/core\/lib\/db(\/|$)/,
|
|
@@ -119,10 +119,14 @@ export async function generateBillingRegistry(configOrName, _legacyContentsDir,
|
|
|
119
119
|
// Use jiti to import .ts files at build time (Node.js can't import .ts natively)
|
|
120
120
|
const jiti = createJiti(import.meta.url, { interopDefault: true, fsCache: false })
|
|
121
121
|
const module = await jiti.import(absolutePath)
|
|
122
|
-
billingConfig = module.billingConfig
|
|
122
|
+
billingConfig = module.billingConfig
|
|
123
123
|
} catch (error) {
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
// A config that exists but cannot load must stop the build: an empty registry has no plans,
|
|
125
|
+
// and a project without plans skips every feature and quota check.
|
|
126
|
+
throw new Error(`Failed to load billing config ${absolutePath}: ${error.message}`, { cause: error })
|
|
127
|
+
}
|
|
128
|
+
if (!billingConfig || !Array.isArray(billingConfig.plans) || !billingConfig.features || !billingConfig.limits) {
|
|
129
|
+
throw new Error(`Billing config ${absolutePath} must export \`billingConfig\` with plans, features and limits`)
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
// Pre-compute everything at build time
|
|
@@ -217,7 +217,8 @@ spread and no expression. A host with another core (the conformance fixture) pas
|
|
|
217
217
|
`_internal/default-{auth,public}-layout`).
|
|
218
218
|
- **Role-gated areas** `/superadmin`, `/devtools`: every page, layout, template and default served under the area's URL (core's,
|
|
219
219
|
a plugin's or the project's, in any route group) is composed with `withSuperadminAccess` / `withDevtoolsAccess`
|
|
220
|
-
(`routes/_internal/area-access`
|
|
220
|
+
(`routes/_internal/area-access`; `area-access.cc` in a Cache Components host, the same check inside the segment's own Suspense
|
|
221
|
+
boundary), which checks the session's role on the server before the segment renders, and its
|
|
221
222
|
`generateMetadata` with `withSuperadminMetadata` / `withDevtoolsMetadata` (metadata resolves apart too); a Route Handler's
|
|
222
223
|
methods but `OPTIONS` with `withSuperadminRouteAccess` / `withDevtoolsRouteAccess` (401 / 403 JSON; `OPTIONS` is forwarded
|
|
223
224
|
unchecked, so it must not return data). An intercepting route
|
|
@@ -293,4 +294,27 @@ cookies or headers below no other boundary). The public item routes omit `revali
|
|
|
293
294
|
mode only imports `_internal/public-item-route.cc`, whose reads of the published item and of the patterns it references are `'use cache'`
|
|
294
295
|
functions (`cacheLife` of an hour for an item, tags `entity:<slug>` and `public-item:<slug>:<item slug>`).
|
|
295
296
|
|
|
297
|
+
**Instant navigation (dev server).** On a Cache Components host `next dev` runs Next.js's instant-navigation check on every page it
|
|
298
|
+
serves: it renders the route as a navigation into each segment and logs `Could not validate that a segment in your UI has instant
|
|
299
|
+
navigation` (a segment the server render never reached), `... uncached data during prerendering or a navigation` or `... runtime data
|
|
300
|
+
(`cookies()`, `headers()`, `params`, `searchParams`) accessed outside of <Suspense>`, and the dev badge shows an issue. The pages work; the check is
|
|
301
|
+
about what a navigation would wait for. What it found in the starter, and the fix, are in core, so no project file changes:
|
|
302
|
+
- The dashboard's client auth gate (`AuthenticatedDashboardLayout`) rendered a skeleton instead of `children` while the session loaded, so the
|
|
303
|
+
route's own segments were never rendered on the server ("dropped"). It renders the page while the session loads and keeps the signed-in side effects for
|
|
304
|
+
a known user.
|
|
305
|
+
- `dashboard/(main)/layout.cc` (one more `layout.cc.tsx` variant, for a layout whose permission check reads the request) renders the shell at once and runs the permission check (session and database) in a
|
|
306
|
+
Suspense boundary around the page; the ISR layout still checks before rendering anything, so a denied request keeps its real redirect.
|
|
307
|
+
- The pages that await `params` (the public item routes `public-item-route.cc`, the dashboard detail and edit routes `entity-detail-route.cc` /
|
|
308
|
+
`entity-edit-route.cc`, written for a host with `cacheComponents` on and nothing else) sit behind a boundary of their own
|
|
309
|
+
(`_internal/suspended-route`); the `/signup` variant and core's default home (`(public)/page.cc`) render the page that redirects behind one too, and the public archive and the permission-denied
|
|
310
|
+
page read `searchParams` behind one. The status a visitor gets does not change: the layouts already streamed these pages after their shell.
|
|
311
|
+
- A project without `public/favicon.ico` made the browser's own `GET /favicon.ico` hit the public `[slug]` page (an HTML "not found" with a 200);
|
|
312
|
+
new projects ship a favicon, and a request for a page that does not exist is now answered inside a boundary the check accepts.
|
|
313
|
+
- `/superadmin` and `/devtools` (#213): their client guards hid the page from the server render until the session loaded, and each segment awaited
|
|
314
|
+
its role check outside Suspense. In a Cache Components host the area layouts (`superadmin/layout.cc`, `devtools/layout.cc` and the composed
|
|
315
|
+
override through `withSuperadminGuard` / `withDevtoolsGuard`) pass `serverChecked` to their guard, which they render only after the role
|
|
316
|
+
check, and every segment under an area is composed from `_internal/area-access.cc`: the same check, inside the segment's own Suspense boundary,
|
|
317
|
+
the segment rendered only once it has passed. The ISR host keeps `_internal/area-access` and its guards' loading state.
|
|
318
|
+
Opting a segment out is `export const instant = false` in its source (a literal, as the facade rules require); nothing sets it today.
|
|
319
|
+
|
|
296
320
|
Tests: `node --test packages/core/scripts/build/registry/host/__tests__/*.test.mjs`.
|
|
@@ -276,3 +276,35 @@ test('a metadata file under an area cannot be guarded: a warning notice names it
|
|
|
276
276
|
w.cleanup()
|
|
277
277
|
}
|
|
278
278
|
})
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* #213: with core's own manifest, a Cache Components host composes every page and layout under the areas with
|
|
282
|
+
* area-access.cc (the check inside the segment's own Suspense boundary), a legacy ISR host with area-access as before.
|
|
283
|
+
* The facades stay inside the facade grammar either way.
|
|
284
|
+
*/
|
|
285
|
+
test("core's manifest: area segments import their check from area-access.cc in a Cache Components host only", async () => {
|
|
286
|
+
const facades = async cacheComponents => {
|
|
287
|
+
const { routes } = await loadCoreRouteManifest({ coreRoot: CORE_ROOT, cacheComponents })
|
|
288
|
+
const planned = planHost({ coreRoutes: routes })
|
|
289
|
+
const result = await render(planned.routes)
|
|
290
|
+
assert.deepEqual(result.diagnostics, [])
|
|
291
|
+
return result
|
|
292
|
+
}
|
|
293
|
+
const INTERNAL = '@nextsparkjs/core/routes/_internal/'
|
|
294
|
+
for (const [cacheComponents, module] of [[true, 'area-access.cc'], [false, 'area-access']]) {
|
|
295
|
+
const result = await facades(cacheComponents)
|
|
296
|
+
for (const [target, wrapper] of [
|
|
297
|
+
['superadmin/users/page.tsx', 'withSuperadminAccess'],
|
|
298
|
+
['superadmin/docs/[section]/[page]/page.tsx', 'withSuperadminAccess'],
|
|
299
|
+
['devtools/page.tsx', 'withDevtoolsAccess'],
|
|
300
|
+
['devtools/api/layout.tsx', 'withDevtoolsAccess'],
|
|
301
|
+
]) {
|
|
302
|
+
const content = contentOf(result, target)
|
|
303
|
+
assert.ok(content, target)
|
|
304
|
+
assert.ok(content.includes(`import { ${wrapper} } from "${INTERNAL}${module}"`), `${target} (cacheComponents ${cacheComponents}) imports ${wrapper} from ${module}:\n${content}`)
|
|
305
|
+
assert.deepEqual(validateGeneratedModule({ ts, source: content, file: `src/app/${target}`, grammar: 'composed-facade' }), [], target)
|
|
306
|
+
}
|
|
307
|
+
// A doc page's generateMetadata keeps area-access's check (re-exported by area-access.cc)
|
|
308
|
+
assert.match(contentOf(result, 'superadmin/docs/[section]/[page]/page.tsx'), new RegExp(`withSuperadminMetadata.*from "${INTERNAL}${module.replace('.', '\\.')}"`, 's'))
|
|
309
|
+
}
|
|
310
|
+
})
|
|
@@ -15,7 +15,7 @@ import { dirname, join } from 'node:path'
|
|
|
15
15
|
import { ENTITY_DIAGNOSTICS, ENTITY_MODULES, entityConfigFile, planEntityRoutes, readAllEntityFacts, readEntityFacts, routableEntities } from '../entity-routes.mjs'
|
|
16
16
|
import { PLAN_DIAGNOSTICS, PLAN_NOTICES, planHost, urlConflicts } from '../plan.mjs'
|
|
17
17
|
import { renderHost } from '../render.mjs'
|
|
18
|
-
import { validateGeneratedModule } from '../static-imports.mjs'
|
|
18
|
+
import { CORE_COMPOSITION_WRAPPERS, validateGeneratedModule } from '../static-imports.mjs'
|
|
19
19
|
import { loadTypeScriptFor } from '../../shared/typescript-compiler.mjs'
|
|
20
20
|
|
|
21
21
|
const CORE_ROOT = join(import.meta.dirname, '../../../../..')
|
|
@@ -29,6 +29,8 @@ const CORE_MODULES = {
|
|
|
29
29
|
[ENTITY_MODULES.detail]: ['core/entity-detail-route.tsx', factories('createEntityDetailRoute')],
|
|
30
30
|
[ENTITY_MODULES.create]: ['core/entity-create-route.tsx', factories('createEntityCreateRoute')],
|
|
31
31
|
[ENTITY_MODULES.edit]: ['core/entity-edit-route.tsx', factories('createEntityEditRoute')],
|
|
32
|
+
[ENTITY_MODULES.detailCc]: ['core/entity-detail-route.cc.tsx', factories('createEntityDetailRoute')],
|
|
33
|
+
[ENTITY_MODULES.editCc]: ['core/entity-edit-route.cc.tsx', factories('createEntityEditRoute')],
|
|
32
34
|
[ENTITY_MODULES.publicItem]: ['core/public-item-route.tsx', factories('createPublicItemRoute', 'createPublicItemMetadata')],
|
|
33
35
|
[ENTITY_MODULES.publicItemCc]: ['core/public-item-route.cc.tsx', factories('createPublicItemRoute', 'createPublicItemMetadata')],
|
|
34
36
|
[ENTITY_MODULES.publicArchive]: ['core/public-archive-route.tsx', factories('createPublicArchiveRoute', 'createPublicArchiveMetadata')],
|
|
@@ -690,6 +692,35 @@ test('a host that builds in Cache Components mode only imports the cached item s
|
|
|
690
692
|
}
|
|
691
693
|
})
|
|
692
694
|
|
|
695
|
+
test('a Cache Components host writes the detail and edit pages of an entity from the modules that put them behind Suspense (they await `params`)', async () => {
|
|
696
|
+
const w = world()
|
|
697
|
+
try {
|
|
698
|
+
const args = [w, [entity('tasks', 'taskEntityConfig')], { tasks: await factsOf("slug: 'tasks', enabled: true, ui: { dashboard: { showInMenu: true } }") }]
|
|
699
|
+
const moduleOf = async (target, factory, options) => at(await render((await plan(...args, options)).routes), target).match(new RegExp(`import \\{ ${factory} \\} from "([^"]+)"`))[1]
|
|
700
|
+
assert.equal(ENTITY_MODULES.detailCc, `${ENTITY_MODULES.detail}.cc`)
|
|
701
|
+
assert.equal(ENTITY_MODULES.editCc, `${ENTITY_MODULES.edit}.cc`)
|
|
702
|
+
const detail = options => moduleOf('dashboard/(main)/tasks/[id]/page.tsx', 'createEntityDetailRoute', options)
|
|
703
|
+
const edit = options => moduleOf('dashboard/(main)/tasks/[id]/edit/page.tsx', 'createEntityEditRoute', options)
|
|
704
|
+
assert.equal(await detail({ cacheComponents: true }), ENTITY_MODULES.detailCc)
|
|
705
|
+
assert.equal(await edit({ cacheComponents: true }), ENTITY_MODULES.editCc)
|
|
706
|
+
// An ISR host keeps the routes that redirect and answer notFound() before anything is sent; so does one that builds both, or does not know
|
|
707
|
+
for (const options of [{ cacheComponents: false }, {}, { cacheComponents: true, modes: ['isr', 'cc'] }]) {
|
|
708
|
+
assert.equal(await detail(options), ENTITY_MODULES.detail)
|
|
709
|
+
assert.equal(await edit(options), ENTITY_MODULES.edit)
|
|
710
|
+
}
|
|
711
|
+
} finally {
|
|
712
|
+
w.cleanup()
|
|
713
|
+
}
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
test('every factory module the entity routes import is a known composition module, with the factories it exports (a real host fails facade validation otherwise)', () => {
|
|
717
|
+
const FACTORY_MODULES = ['layout', 'list', 'detail', 'create', 'edit', 'detailCc', 'editCc', 'publicItem', 'publicItemCc', 'publicArchive']
|
|
718
|
+
for (const name of FACTORY_MODULES) {
|
|
719
|
+
const wrappers = CORE_COMPOSITION_WRAPPERS[ENTITY_MODULES[name]]
|
|
720
|
+
assert.ok(Array.isArray(wrappers) && wrappers.length > 0, `${name}: ${ENTITY_MODULES[name]} is missing from CORE_COMPOSITION_WRAPPERS`)
|
|
721
|
+
}
|
|
722
|
+
})
|
|
723
|
+
|
|
693
724
|
// ---------------------------------------------------------------------------
|
|
694
725
|
// "The template replaces": project templates at an entity's dashboard URL in another route group
|
|
695
726
|
// ---------------------------------------------------------------------------
|
|
@@ -53,6 +53,16 @@ test('core < plugin < project: a higher layer replaces a lower one at the same r
|
|
|
53
53
|
}
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
+
test('a project robots.ts replaces the robots route core ships (#214)', () => {
|
|
57
|
+
const { root, cleanup } = project({ 'templates/robots.ts': 'export default function robots() { return { rules: { userAgent: "*", disallow: "/" } } }\n' })
|
|
58
|
+
try {
|
|
59
|
+
const routes = resolveHostPlan({ coreRoutes: [core('robots.ts', 'robots')], plugins: [], project: { root } })
|
|
60
|
+
assert.deepEqual(summary(routes), ['robots.ts <- project:@/templates/robots (overrides core route @nextsparkjs/core/routes/robots)'])
|
|
61
|
+
} finally {
|
|
62
|
+
cleanup()
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
56
66
|
test('the extension does not decide the slot: templates/about/page.ts replaces core about/page.tsx', () => {
|
|
57
67
|
const { root, cleanup } = project({ 'templates/about/page.ts': PAGE })
|
|
58
68
|
try {
|
|
@@ -330,8 +340,8 @@ test('core\'s routes carry their protection level, so a composed layout can be t
|
|
|
330
340
|
assert.equal(withCc.routes.find(route => route.target === 'layout.tsx').compose.specifier, '@nextsparkjs/core/routes/_internal/root-layout.ppr')
|
|
331
341
|
assert.equal(withCc.routes.find(route => route.target === '(auth)/login/page.tsx').specifier, '@nextsparkjs/core/routes/(auth)/login/page.cc')
|
|
332
342
|
assert.deepEqual(withCc.variantsApplied.sort(), [
|
|
333
|
-
'(auth)/layout.tsx', '(auth)/login/page.tsx', '(auth)/signup/page.tsx', '(public)/docs/[section]/[page]/page.tsx', '(public)/layout.tsx',
|
|
334
|
-
'dashboard/layout.tsx', 'devtools/layout.tsx', 'layout.tsx', 'superadmin/docs/[section]/[page]/page.tsx', 'superadmin/layout.tsx',
|
|
343
|
+
'(auth)/layout.tsx', '(auth)/login/page.tsx', '(auth)/signup/page.tsx', '(public)/docs/[section]/[page]/page.tsx', '(public)/layout.tsx', '(public)/page.tsx',
|
|
344
|
+
'dashboard/(main)/layout.tsx', 'dashboard/layout.tsx', 'devtools/layout.tsx', 'layout.tsx', 'superadmin/docs/[section]/[page]/page.tsx', 'superadmin/layout.tsx',
|
|
335
345
|
])
|
|
336
346
|
// The layouts that load messages keep their wrapper (from the Cache Components module) and their protection level
|
|
337
347
|
const ccLevel = target => withCc.routes.find(route => route.target === target)
|
|
@@ -12,7 +12,7 @@ import { mkdtempSync, readFileSync, rmSync, symlinkSync } from 'node:fs'
|
|
|
12
12
|
import { tmpdir } from 'node:os'
|
|
13
13
|
import { join } from 'node:path'
|
|
14
14
|
|
|
15
|
-
import { PROXY_AREA_WARNING, missingProxyAreas, proxyAreaNotice, proxyAreaNotices } from '../proxy-areas.mjs'
|
|
15
|
+
import { PROXY_AREA_WARNING, PROXY_SESSION_WARNING, missingProxyAreas, withoutComments, proxyAreaNotice, proxyAreaNotices, proxySessionNotice } from '../proxy-areas.mjs'
|
|
16
16
|
import { predictHost } from '../prepare.mjs'
|
|
17
17
|
import { PAGE, tempHost, write } from './host-helpers.mjs'
|
|
18
18
|
|
|
@@ -30,6 +30,20 @@ test('an area counts only when the proxy names its path; a look-alike prefix or
|
|
|
30
30
|
assert.deepEqual(missingProxyAreas("const docs = '/superadmin-guide'; const tips = \"/devtools-tips\"").map(area => area.path), ['/superadmin', '/devtools'])
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
+
test("a proxy that re-exports core's template has the check; a re-export of anything else does not", () => {
|
|
34
|
+
assert.deepEqual(missingProxyAreas("export { proxy } from '../../../packages/core/templates/proxy'\nexport const config = { matcher: [] }"), [])
|
|
35
|
+
assert.deepEqual(missingProxyAreas("export { proxy } from '@nextsparkjs/core/templates/proxy'"), [])
|
|
36
|
+
assert.deepEqual(missingProxyAreas("export { proxy } from './my-proxy'").map(area => area.path), ['/superadmin', '/devtools'])
|
|
37
|
+
assert.deepEqual(missingProxyAreas("export { other } from '@nextsparkjs/core/templates/proxy'").map(area => area.path), ['/superadmin', '/devtools'])
|
|
38
|
+
const both = ['/superadmin', '/devtools']
|
|
39
|
+
assert.deepEqual(missingProxyAreas("export { proxy as coreProxy } from '@nextsparkjs/core/templates/proxy'\nexport function proxy() {}").map(a => a.path), both)
|
|
40
|
+
assert.deepEqual(missingProxyAreas("export { other as proxy } from '@nextsparkjs/core/templates/proxy'").map(a => a.path), both)
|
|
41
|
+
assert.deepEqual(missingProxyAreas("export { proxy } from './vendor/core/templates/proxy'").map(a => a.path), both)
|
|
42
|
+
assert.deepEqual(missingProxyAreas("// export { proxy } from '@nextsparkjs/core/templates/proxy'\nexport function proxy() {}").map(a => a.path), both)
|
|
43
|
+
assert.deepEqual(missingProxyAreas("/*\nexport { proxy } from '@nextsparkjs/core/templates/proxy'\n*/\nexport function proxy() {}").map(a => a.path), both)
|
|
44
|
+
assert.deepEqual(missingProxyAreas("export { config, proxy } from '@nextsparkjs/core/templates/proxy.ts'"), [])
|
|
45
|
+
})
|
|
46
|
+
|
|
33
47
|
test('the warning names the file, each missing area, its roles and where the check comes from', () => {
|
|
34
48
|
const notice = proxyAreaNotice('src/proxy.ts', "const PROTECTED = ['/superadmin']")
|
|
35
49
|
assert.equal(notice.code, PROXY_AREA_WARNING)
|
|
@@ -53,6 +67,63 @@ test('the proxy Next loads is the one checked; a project without one is not warn
|
|
|
53
67
|
}
|
|
54
68
|
})
|
|
55
69
|
|
|
70
|
+
// S80 (D1): the session check that fetched /api/auth/get-session from the request's origin, as every proxy.ts copied
|
|
71
|
+
// before 0.1.0-beta.196 does. Upgrading core never touches that copy, so prepare says what to change.
|
|
72
|
+
const HTTP_SESSION_CHECK = `function getSession(request: NextRequest) {
|
|
73
|
+
return betterFetch<Session>('/api/auth/get-session', {
|
|
74
|
+
baseURL: \`\${request.nextUrl.origin}\${request.nextUrl.basePath}\`,
|
|
75
|
+
headers: { cookie: request.headers.get('cookie') || '' },
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
const PROTECTED = ['/superadmin', '/devtools']
|
|
79
|
+
`
|
|
80
|
+
|
|
81
|
+
test('a proxy that asks its own /api/auth/get-session over HTTP is warned about, with the in-process call to use', () => {
|
|
82
|
+
const notice = proxySessionNotice('src/proxy.ts', HTTP_SESSION_CHECK)
|
|
83
|
+
assert.equal(notice.code, PROXY_SESSION_WARNING)
|
|
84
|
+
assert.match(notice.message, /^src\/proxy\.ts checks the session by fetching \/api\/auth\/get-session/)
|
|
85
|
+
assert.match(notice.message, /X-Forwarded-Proto: https/)
|
|
86
|
+
assert.match(notice.message, /auth\.api\.getSession\(\{ headers: new Headers\(\{ cookie: request\.headers\.get\('cookie'\) \|\| '' \}\), query: \{ disableRefresh: true \} \}\)/)
|
|
87
|
+
assert.equal(proxySessionNotice('src/proxy.ts', readFileSync(join(CORE_ROOT, 'templates/proxy.ts'), 'utf8')), null)
|
|
88
|
+
assert.equal(proxySessionNotice('src/proxy.ts', "// was: betterFetch('/api/auth/get-session')\nexport function proxy() {}"), null)
|
|
89
|
+
assert.equal(proxySessionNotice('src/proxy.ts', "export { proxy } from '@nextsparkjs/core/templates/proxy'"), null)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// The S80 review's measured edges: an end-of-line comment, and a '/*' / '*/' pair inside two string literals.
|
|
93
|
+
const COMMENT_EDGES = [
|
|
94
|
+
['an end-of-line comment after code', "const x = 1 // was '/api/auth/get-session'\nexport function proxy() {}", false],
|
|
95
|
+
['an end-of-line comment after code, no space', "const x = 1// was '/api/auth/get-session'", false],
|
|
96
|
+
["a '/*' and a '*/' in two strings around the call", "const a = '/api/*'\nbetterFetch('/api/auth/get-session')\nconst b = 'x*/'", true],
|
|
97
|
+
["a '//' inside a string before the call", "const u = 'https://example.com'; betterFetch('/api/auth/get-session')", true],
|
|
98
|
+
['a block comment', "/* betterFetch('/api/auth/get-session') */ export function proxy() {}", false],
|
|
99
|
+
['a quote escaped inside a string', "const q = 'it\\'s'; betterFetch('/api/auth/get-session')", true],
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
test('comments are stripped wherever they start, and never inside a string literal', () => {
|
|
103
|
+
for (const [label, source, warns] of COMMENT_EDGES) {
|
|
104
|
+
assert.equal(proxySessionNotice('src/proxy.ts', source) !== null, warns, label)
|
|
105
|
+
}
|
|
106
|
+
assert.equal(withoutComments("a // b\nc"), 'a \nc')
|
|
107
|
+
assert.equal(withoutComments("'//' /* x */ \"/*\""), "'//' \"/*\"")
|
|
108
|
+
// The repository template, old and new, reads the same with comments gone: every regex literal in it survives.
|
|
109
|
+
const template = readFileSync(join(CORE_ROOT, 'templates/proxy.ts'), 'utf8')
|
|
110
|
+
assert.match(withoutComments(template), /\.replace\(\/\\\\\/g, '\/'\)\.replace\(\/\\\/\\\/\+\/g, '\/'\)/)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test("the proxy Next loads gets the session warning next to the areas one; core's template gets neither", () => {
|
|
114
|
+
const root = mkdtempSync(join(tmpdir(), 'nextspark-proxy-session-'))
|
|
115
|
+
try {
|
|
116
|
+
write(root, 'src/proxy.ts', HTTP_SESSION_CHECK)
|
|
117
|
+
assert.deepEqual(proxyAreaNotices(root).map(notice => notice.code), [PROXY_SESSION_WARNING])
|
|
118
|
+
write(root, 'src/proxy.ts', `${HTTP_SESSION_CHECK.replace("['/superadmin', '/devtools']", '[]')}`)
|
|
119
|
+
assert.deepEqual(proxyAreaNotices(root).map(notice => notice.code), [PROXY_AREA_WARNING, PROXY_SESSION_WARNING])
|
|
120
|
+
write(root, 'src/proxy.ts', readFileSync(join(CORE_ROOT, 'templates/proxy.ts'), 'utf8'))
|
|
121
|
+
assert.deepEqual(proxyAreaNotices(root), [])
|
|
122
|
+
} finally {
|
|
123
|
+
rmSync(root, { recursive: true, force: true })
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
56
127
|
test("prepare's prediction carries the warning among its notices", async () => {
|
|
57
128
|
const host = tempHost({ 'templates/about/page.tsx': PAGE('About') })
|
|
58
129
|
const project = mkdtempSync(join(tmpdir(), 'nextspark-proxy-areas-project-'))
|
|
@@ -54,6 +54,9 @@ export const ENTITY_MODULES = Object.freeze({
|
|
|
54
54
|
detail: `${CORE_ROUTES_SPECIFIER}_internal/entity-detail-route`,
|
|
55
55
|
create: `${CORE_ROUTES_SPECIFIER}_internal/entity-create-route`,
|
|
56
56
|
edit: `${CORE_ROUTES_SPECIFIER}_internal/entity-edit-route`,
|
|
57
|
+
// The detail and edit pages of a host with `cacheComponents` on: the same routes behind a Suspense boundary (they await `params`).
|
|
58
|
+
detailCc: `${CORE_ROUTES_SPECIFIER}_internal/entity-detail-route.cc`,
|
|
59
|
+
editCc: `${CORE_ROUTES_SPECIFIER}_internal/entity-edit-route.cc`,
|
|
57
60
|
publicItem: `${CORE_ROUTES_SPECIFIER}_internal/public-item-route`,
|
|
58
61
|
// The item pages of a host with `cacheComponents` on: same factories, `'use cache'` reads instead of `revalidate`.
|
|
59
62
|
publicItemCc: `${CORE_ROUTES_SPECIFIER}_internal/public-item-route.cc`,
|
|
@@ -207,7 +210,10 @@ export function planEntityRoutes({ entities, facts, coreRoutes, resolveFile, cac
|
|
|
207
210
|
const revalidates = cacheComponents !== true && !modes.includes('cc')
|
|
208
211
|
// A host that builds in Cache Components mode only gets the cached item source; one that builds both (the
|
|
209
212
|
// conformance fixture) or does not know keeps the module that works in either.
|
|
210
|
-
const
|
|
213
|
+
const ccOnly = cacheComponents === true && modes.length === 0
|
|
214
|
+
const publicItemModule = ccOnly && modules.publicItemCc ? modules.publicItemCc : modules.publicItem
|
|
215
|
+
const detailModule = ccOnly && modules.detailCc ? modules.detailCc : modules.detail
|
|
216
|
+
const editModule = ccOnly && modules.editCc ? modules.editCc : modules.edit
|
|
211
217
|
|
|
212
218
|
const coreFacade = (kind, target, specifier) => ({
|
|
213
219
|
kind,
|
|
@@ -275,8 +281,8 @@ export function planEntityRoutes({ entities, facts, coreRoutes, resolveFile, cac
|
|
|
275
281
|
coreFacade('loading', `${base}/loading.tsx`, modules.loading),
|
|
276
282
|
composed('page', `${base}/page.tsx`, 'createEntityListRoute', modules.list, [config, arg.template()], { acceptsTemplate: true, reexport: [{ names: ['metadata'], specifier: modules.listMetadata }] }),
|
|
277
283
|
composed('page', `${base}/create/page.tsx`, 'createEntityCreateRoute', modules.create, [config, arg.template()], { acceptsTemplate: true }),
|
|
278
|
-
composed('page', `${base}/[id]/page.tsx`, 'createEntityDetailRoute',
|
|
279
|
-
composed('page', `${base}/[id]/edit/page.tsx`, 'createEntityEditRoute',
|
|
284
|
+
composed('page', `${base}/[id]/page.tsx`, 'createEntityDetailRoute', detailModule, [config, arg.literal(children), arg.template()], { acceptsTemplate: true, reexport: [{ names: ['metadata'], specifier: modules.detailMetadata }] }),
|
|
285
|
+
composed('page', `${base}/[id]/edit/page.tsx`, 'createEntityEditRoute', editModule, [config, arg.template()], { acceptsTemplate: true })
|
|
280
286
|
)
|
|
281
287
|
}
|
|
282
288
|
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* before the template gained the check, or written by hand, may not have it: `nextspark prepare` (and `migrate`, in
|
|
8
8
|
* the CLI) warn, naming each area the proxy never mentions.
|
|
9
9
|
*
|
|
10
|
+
* Same for the session check (S80): a proxy copied before 0.1.0-beta.196 fetches its own /api/auth/get-session from
|
|
11
|
+
* the request's origin, which behind a proxy that terminates TLS is https on a plain-HTTP port, so every signed-in
|
|
12
|
+
* user is sent to /login. That copy is never updated by an upgrade, so prepare and migrate say what to change.
|
|
13
|
+
*
|
|
10
14
|
* @module core/scripts/build/registry/host/proxy-areas
|
|
11
15
|
*/
|
|
12
16
|
|
|
@@ -24,8 +28,45 @@ export const PROTECTED_AREAS = Object.freeze([
|
|
|
24
28
|
/** Where Next loads a project's proxy from, next to the generated src/app. */
|
|
25
29
|
export const PROXY_FILES = Object.freeze(['src/proxy.ts', 'src/proxy.js', 'src/middleware.ts', 'src/middleware.js'])
|
|
26
30
|
|
|
31
|
+
/**
|
|
32
|
+
* A proxy that re-exports core's template under its own name (apps/dev does) has the check without naming the areas
|
|
33
|
+
* itself. Exact name (`proxy as x` is not the proxy Next loads), a concrete specifier (the package, or the repository's
|
|
34
|
+
* packages/core), and only code: a commented-out line does not count.
|
|
35
|
+
*/
|
|
36
|
+
const REEXPORTS_CORE_PROXY =
|
|
37
|
+
/^\s*export\s*\{\s*(?:[\w$]+\s*,\s*)*proxy\s*(?:,[^}]*)?\}\s*from\s*['"](?:@nextsparkjs\/core|(?:\.\.\/)+packages\/core)\/templates\/proxy(?:\.ts)?['"]/m
|
|
38
|
+
/**
|
|
39
|
+
* The source without its comments (`//` to the end of the line, wherever it starts, and block comments). String
|
|
40
|
+
* literals are kept whole, so a `/*` or `//` inside one is text. Not parsed: regular-expression literals (a quote in
|
|
41
|
+
* one can hide the code after it) and template-literal `${}` nesting; nor does the session detector follow a URL
|
|
42
|
+
* assembled from parts. No template that copied the HTTP session check needs either.
|
|
43
|
+
*/
|
|
44
|
+
export function withoutComments(source) {
|
|
45
|
+
let out = ''
|
|
46
|
+
let quote = null
|
|
47
|
+
for (let i = 0; i < source.length; i++) {
|
|
48
|
+
const c = source[i]
|
|
49
|
+
if (quote) {
|
|
50
|
+
out += c
|
|
51
|
+
if (c === '\\') out += source[++i] ?? ''
|
|
52
|
+
else if (c === quote) quote = null
|
|
53
|
+
} else if (c === '/' && source[i + 1] === '/') {
|
|
54
|
+
while (i + 1 < source.length && source[i + 1] !== '\n') i++
|
|
55
|
+
} else if (c === '/' && source[i + 1] === '*') {
|
|
56
|
+
const end = source.indexOf('*/', i + 2)
|
|
57
|
+
i = end < 0 ? source.length : end + 1
|
|
58
|
+
out += ' '
|
|
59
|
+
} else {
|
|
60
|
+
if (c === "'" || c === '"' || c === '`') quote = c
|
|
61
|
+
out += c
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out
|
|
65
|
+
}
|
|
66
|
+
|
|
27
67
|
/** The protected areas a proxy's source never names as a path (`'/superadmin'`, `"/devtools"`, `` `/devtools` ``). */
|
|
28
68
|
export function missingProxyAreas(source) {
|
|
69
|
+
if (REEXPORTS_CORE_PROXY.test(withoutComments(source))) return []
|
|
29
70
|
return PROTECTED_AREAS.filter(area => !new RegExp(`['"\`]${area.path}(?![\\w-])`).test(source))
|
|
30
71
|
}
|
|
31
72
|
|
|
@@ -46,11 +87,33 @@ export function proxyAreaNotice(file, source) {
|
|
|
46
87
|
}
|
|
47
88
|
}
|
|
48
89
|
|
|
90
|
+
export const PROXY_SESSION_WARNING = 'NS_PROXY_SESSION_OVER_HTTP'
|
|
91
|
+
|
|
92
|
+
/** What to write instead of the HTTP session check; migrate in the CLI says the same. */
|
|
93
|
+
export const PROXY_SESSION_FIX =
|
|
94
|
+
"import { auth } from '@nextsparkjs/core/lib/auth' and replace the betterFetch('/api/auth/get-session', ...) call with " +
|
|
95
|
+
"auth.api.getSession({ headers: new Headers({ cookie: request.headers.get('cookie') || '' }), query: { disableRefresh: true } }), " +
|
|
96
|
+
'which returns the session itself (or null), not { data }'
|
|
97
|
+
|
|
98
|
+
/** The warning for a proxy whose code (not a comment) asks /api/auth/get-session over HTTP, or null. */
|
|
99
|
+
export function proxySessionNotice(file, source) {
|
|
100
|
+
if (!withoutComments(source).includes('/api/auth/get-session')) return null
|
|
101
|
+
return {
|
|
102
|
+
code: PROXY_SESSION_WARNING,
|
|
103
|
+
target: file,
|
|
104
|
+
message:
|
|
105
|
+
`${file} checks the session by fetching /api/auth/get-session from the request's own origin. Behind a proxy that ` +
|
|
106
|
+
`terminates TLS (X-Forwarded-Proto: https) that origin is https on a port that speaks plain HTTP: the fetch fails ` +
|
|
107
|
+
`and every signed-in user is sent to /login. Read the session in process, as ` +
|
|
108
|
+
`node_modules/@nextsparkjs/core/templates/proxy.ts does: ${PROXY_SESSION_FIX}`,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
49
112
|
/** The warnings for the proxy Next would load in `projectRoot` (the first of PROXY_FILES that exists). */
|
|
50
113
|
export function proxyAreaNotices(projectRoot) {
|
|
51
114
|
if (!projectRoot) return []
|
|
52
115
|
const file = PROXY_FILES.find(candidate => existsSync(join(projectRoot, candidate)))
|
|
53
116
|
if (!file) return []
|
|
54
|
-
const
|
|
55
|
-
return
|
|
117
|
+
const source = readFileSync(join(projectRoot, file), 'utf8')
|
|
118
|
+
return [proxyAreaNotice(file, source), proxySessionNotice(file, source)].filter(Boolean)
|
|
56
119
|
}
|
|
@@ -80,11 +80,15 @@ export const CORE_COMPOSITION_WRAPPERS = Object.freeze({
|
|
|
80
80
|
[`${INTERNAL}devtools-layout.cc`]: ['withDevtoolsGuard'],
|
|
81
81
|
// Every page and layout under /superadmin and /devtools (the manifest's `access`).
|
|
82
82
|
[`${INTERNAL}area-access`]: ['withSuperadminAccess', 'withDevtoolsAccess', 'withSuperadminMetadata', 'withDevtoolsMetadata', 'withSuperadminRouteAccess', 'withDevtoolsRouteAccess'],
|
|
83
|
+
// The same, for a Cache Components host (each segment checks inside its own Suspense boundary).
|
|
84
|
+
[`${INTERNAL}area-access.cc`]: ['withSuperadminAccess', 'withDevtoolsAccess', 'withSuperadminMetadata', 'withDevtoolsMetadata', 'withSuperadminRouteAccess', 'withDevtoolsRouteAccess'],
|
|
83
85
|
[`${INTERNAL}entity-layout-route`]: ['createEntityLayoutRoute'],
|
|
84
86
|
[`${INTERNAL}entity-list-route`]: ['createEntityListRoute'],
|
|
85
87
|
[`${INTERNAL}entity-detail-route`]: ['createEntityDetailRoute'],
|
|
86
88
|
[`${INTERNAL}entity-create-route`]: ['createEntityCreateRoute'],
|
|
87
89
|
[`${INTERNAL}entity-edit-route`]: ['createEntityEditRoute'],
|
|
90
|
+
[`${INTERNAL}entity-detail-route.cc`]: ['createEntityDetailRoute'],
|
|
91
|
+
[`${INTERNAL}entity-edit-route.cc`]: ['createEntityEditRoute'],
|
|
88
92
|
[`${INTERNAL}public-item-route`]: ['createPublicItemRoute', 'createPublicItemMetadata'],
|
|
89
93
|
[`${INTERNAL}public-item-route.cc`]: ['createPublicItemRoute', 'createPublicItemMetadata'],
|
|
90
94
|
[`${INTERNAL}public-archive-route`]: ['createPublicArchiveRoute', 'createPublicArchiveMetadata'],
|
|
@@ -66,6 +66,8 @@ export const VARIANT_FILES = Object.freeze({
|
|
|
66
66
|
'superadmin/layout.cc.tsx': 'superadmin/layout.tsx',
|
|
67
67
|
'devtools/layout.cc.tsx': 'devtools/layout.tsx',
|
|
68
68
|
'dashboard/layout.cc.tsx': 'dashboard/layout.tsx',
|
|
69
|
+
'dashboard/(main)/layout.cc.tsx': 'dashboard/(main)/layout.tsx',
|
|
70
|
+
'(public)/page.cc.tsx': '(public)/page.tsx',
|
|
69
71
|
'(auth)/login/page.cc.tsx': '(auth)/login/page.tsx',
|
|
70
72
|
'(auth)/signup/page.cc.tsx': '(auth)/signup/page.tsx',
|
|
71
73
|
'(public)/docs/[section]/[page]/page.cc.tsx': '(public)/docs/[section]/[page]/page.tsx',
|
|
@@ -105,6 +107,15 @@ export const VARIANT_COMPOSE = Object.freeze({
|
|
|
105
107
|
'devtools/layout.cc.tsx': { wrapper: 'withDevtoolsGuard', specifier: `${ROUTES_SUBPATH}/_internal/devtools-layout.cc` },
|
|
106
108
|
})
|
|
107
109
|
|
|
110
|
+
/**
|
|
111
|
+
* The per-segment access check of a variant, when it differs from its base route's: in Cache Components mode every
|
|
112
|
+
* page and layout under an area checks the role inside its own Suspense boundary (area-access.cc).
|
|
113
|
+
*/
|
|
114
|
+
export const VARIANT_ACCESS = Object.freeze({
|
|
115
|
+
'superadmin/layout.cc.tsx': { ...ACCESS_ROUTES['superadmin/layout.tsx'], specifier: `${ROUTES_SUBPATH}/_internal/area-access.cc` },
|
|
116
|
+
'devtools/layout.cc.tsx': { ...ACCESS_ROUTES['devtools/layout.tsx'], specifier: `${ROUTES_SUBPATH}/_internal/area-access.cc` },
|
|
117
|
+
})
|
|
118
|
+
|
|
108
119
|
/**
|
|
109
120
|
* Files that live next to a route without being one: the API explorer's presets, read as text by the
|
|
110
121
|
* registry build (registry/discovery/api-presets.mjs), and its docs.md.
|
|
@@ -168,7 +179,8 @@ export function buildRoutesManifest(routesDir = ROUTES_DIR) {
|
|
|
168
179
|
const replaced = manifest.find(entry => entry.target === target)
|
|
169
180
|
if (!replaced) throw new Error(`Variant ${file} (${mode}) replaces ${target}, which is not a core route`)
|
|
170
181
|
const compose = VARIANT_COMPOSE[file] ?? replaced.compose
|
|
171
|
-
|
|
182
|
+
const access = VARIANT_ACCESS[file] ?? replaced.access
|
|
183
|
+
return { kind: replaced.kind, target, specifier: specifierForRouteFile(file), ...(compose ? { compose } : {}), ...(access ? { access } : {}) }
|
|
172
184
|
})
|
|
173
185
|
}
|
|
174
186
|
return { manifest, variants }
|