@nextsparkjs/core 0.1.0-beta.193 → 0.1.0-beta.195
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/README.md +2 -2
- 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/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/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/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/auth/permissions.d.ts +10 -0
- package/dist/lib/api/auth/permissions.d.ts.map +1 -1
- package/dist/lib/api/auth/permissions.js +7 -0
- 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 +54 -0
- package/dist/lib/api/cors-response.d.ts.map +1 -0
- package/dist/lib/api/cors-response.js +88 -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 -5
- 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 +27 -81
- 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/rate-limit.d.ts +4 -0
- package/dist/lib/api/rate-limit.d.ts.map +1 -1
- package/dist/lib/api/rate-limit.js +17 -38
- package/dist/lib/api/request-origin.d.ts +35 -0
- package/dist/lib/api/request-origin.d.ts.map +1 -0
- package/dist/lib/api/request-origin.js +69 -0
- 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 +2 -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 +15 -14
- 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/external-api-generator.d.ts.map +1 -1
- package/dist/lib/entities/external-api-generator.js +5 -5
- 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/services/subscription.service.d.ts.map +1 -1
- package/dist/lib/services/subscription.service.js +3 -0
- package/dist/lib/utils/cors.d.ts +7 -0
- package/dist/lib/utils/cors.d.ts.map +1 -1
- package/dist/lib/utils/cors.js +8 -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/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 +2 -0
- 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 +2 -0
- package/dist/routes/api/v1/media/upload/route.d.ts.map +1 -1
- package/dist/routes/api/v1/media/upload/route.js +2 -0
- 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.js +2 -2
- 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/users/[id]/meta/[key]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/users/[id]/meta/[key]/route.js +13 -0
- package/dist/routes/api/v1/users/[id]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/users/[id]/route.js +20 -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/devtools/layout.cc.d.ts.map +1 -1
- package/dist/routes/devtools/layout.cc.js +5 -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/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/blog/package.json +3 -3
- package/dist/templates/projects/crm/package.json +3 -3
- package/dist/templates/projects/productivity/package.json +3 -3
- package/dist/templates/proxy.ts +12 -1
- package/dist/templates/public/favicon.ico +0 -0
- package/package.json +8 -5
- package/scripts/build/auth-readiness.mjs +4 -0
- 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__/build-output.test.mjs +1 -1
- package/scripts/build/registry/__tests__/icons-discovery.test.mjs +3 -0
- package/scripts/build/registry/__tests__/project-mode-root-first.test.mjs +6 -6
- 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/__tests__/write-places.test.mjs +1 -1
- package/scripts/build/registry/contracts/__tests__/contracts-wire.test.mjs +1 -1
- package/scripts/build/registry/contracts/__tests__/contracts.test.mjs +1 -1
- package/scripts/build/registry/contracts/render.mjs +3 -3
- 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 +27 -3
- package/scripts/build/registry/host/__tests__/facade-emitter.test.mjs +9 -9
- 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-mode.test.mjs +1 -1
- package/scripts/build/registry/host/__tests__/host-plan.test.mjs +12 -2
- package/scripts/build/registry/host/__tests__/host-project-composition.test.mjs +1 -1
- package/scripts/build/registry/host/__tests__/host-project.test.mjs +2 -2
- package/scripts/build/registry/host/__tests__/host-robustness.test.mjs +65 -2
- package/scripts/build/registry/host/entity-routes.mjs +9 -3
- package/scripts/build/registry/host/facade-emitter.mjs +2 -2
- package/scripts/build/registry/host/generation.mjs +54 -0
- package/scripts/build/registry/host/next-route-exports.json +2 -2
- package/scripts/build/registry/host/prepare.mjs +3 -0
- package/scripts/build/registry/host/static-imports.mjs +4 -0
- package/scripts/build/routes-manifest.mjs +13 -1
- package/scripts/db/migration-time-limit.mjs +216 -34
- package/scripts/db/run-migrations.mjs +11 -179
- package/scripts/db/ssl-config.mjs +12 -1
- package/scripts/db/verify-theme-migrations.mjs +1 -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/blog/package.json +3 -3
- package/templates/projects/crm/package.json +3 -3
- package/templates/projects/productivity/package.json +3 -3
- package/templates/proxy.ts +12 -1
- package/templates/public/favicon.ico +0 -0
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
"@nextsparkjs/core": ">=0.1.0-0",
|
|
15
15
|
"@nextsparkjs/testing": ">=0.1.0-0",
|
|
16
16
|
"lucide-react": "^0.539.0",
|
|
17
|
-
"next": "
|
|
17
|
+
"next": "~16.3.6",
|
|
18
18
|
"next-intl": "^4.0.0",
|
|
19
|
-
"react": "^19.
|
|
20
|
-
"react-dom": "^19.
|
|
19
|
+
"react": "^19.2.0",
|
|
20
|
+
"react-dom": "^19.2.0",
|
|
21
21
|
"zod": "^4.0.0"
|
|
22
22
|
},
|
|
23
23
|
"nextspark": {
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"@nextsparkjs/core": ">=0.1.0-0",
|
|
14
14
|
"@nextsparkjs/testing": ">=0.1.0-0",
|
|
15
15
|
"lucide-react": "^0.539.0",
|
|
16
|
-
"next": "
|
|
16
|
+
"next": "~16.3.6",
|
|
17
17
|
"next-intl": "^4.0.0",
|
|
18
|
-
"react": "^19.
|
|
19
|
-
"react-dom": "^19.
|
|
18
|
+
"react": "^19.2.0",
|
|
19
|
+
"react-dom": "^19.2.0",
|
|
20
20
|
"zod": "^4.0.0"
|
|
21
21
|
},
|
|
22
22
|
"nextspark": {
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"@nextsparkjs/core": ">=0.1.0-0",
|
|
18
18
|
"@nextsparkjs/testing": ">=0.1.0-0",
|
|
19
19
|
"lucide-react": "^0.539.0",
|
|
20
|
-
"next": "
|
|
21
|
-
"react": "^19.
|
|
22
|
-
"react-dom": "^19.
|
|
20
|
+
"next": "~16.3.6",
|
|
21
|
+
"react": "^19.2.0",
|
|
22
|
+
"react-dom": "^19.2.0",
|
|
23
23
|
"zod": "^4.0.0"
|
|
24
24
|
},
|
|
25
25
|
"nextspark": {
|
package/dist/templates/proxy.ts
CHANGED
|
@@ -200,8 +200,19 @@ function getSession(request: NextRequest) {
|
|
|
200
200
|
* session. Gating the strip on protected prefixes is not enough: Next.js
|
|
201
201
|
* dispatches Server Actions by the `Next-Action` header, not by the URL, so an
|
|
202
202
|
* action can be POSTed to a public path with a forged `x-user-id`.
|
|
203
|
+
*
|
|
204
|
+
* The `x-api-*` names are never set here: core takes an API key's identity from
|
|
205
|
+
* validating the key, and nothing downstream may receive them from the client.
|
|
203
206
|
*/
|
|
204
|
-
const TRUSTED_IDENTITY_HEADERS = [
|
|
207
|
+
const TRUSTED_IDENTITY_HEADERS = [
|
|
208
|
+
'x-user-id',
|
|
209
|
+
'x-user-email',
|
|
210
|
+
'x-pathname',
|
|
211
|
+
'x-active-team-id',
|
|
212
|
+
'x-api-user-id',
|
|
213
|
+
'x-api-key-id',
|
|
214
|
+
'x-api-scopes',
|
|
215
|
+
] as const
|
|
205
216
|
|
|
206
217
|
/**
|
|
207
218
|
* Build the request headers forwarded to the app: inbound copy minus every
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.195",
|
|
4
4
|
"description": "NextSpark - The complete SaaS framework for Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "NextSpark <hello@nextspark.dev>",
|
|
@@ -390,9 +390,9 @@
|
|
|
390
390
|
"scripts"
|
|
391
391
|
],
|
|
392
392
|
"peerDependencies": {
|
|
393
|
-
"next": "~16.3.
|
|
394
|
-
"react": "
|
|
395
|
-
"react-dom": "
|
|
393
|
+
"next": "~16.3.6",
|
|
394
|
+
"react": "^19.2.0",
|
|
395
|
+
"react-dom": "^19.2.0"
|
|
396
396
|
},
|
|
397
397
|
"devDependencies": {
|
|
398
398
|
"@better-fetch/fetch": "1.3.1",
|
|
@@ -496,7 +496,10 @@
|
|
|
496
496
|
"use-intl": "^4.9.1",
|
|
497
497
|
"uuid": "^13.0.0",
|
|
498
498
|
"zod": "^4.3.6",
|
|
499
|
-
"@nextsparkjs/ui": "0.1.0-beta.
|
|
499
|
+
"@nextsparkjs/ui": "0.1.0-beta.195"
|
|
500
|
+
},
|
|
501
|
+
"engines": {
|
|
502
|
+
"node": ">=22.14.0"
|
|
500
503
|
},
|
|
501
504
|
"scripts": {
|
|
502
505
|
"postinstall": "node scripts/postinstall.mjs || true",
|
|
@@ -192,6 +192,10 @@ async function main() {
|
|
|
192
192
|
console.log(' RESEND_API_KEY, RESEND_FROM_EMAIL; Google: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET), or change auth.methods.')
|
|
193
193
|
console.log(' Credentials injected only at runtime: declare them with NEXTSPARK_AUTH_RUNTIME_ONLY=email,google;')
|
|
194
194
|
console.log(' they are validated again when the server starts and on every login request.')
|
|
195
|
+
if (result.diagnostics.some(({ code }) => code.endsWith('_PLACEHOLDER'))) {
|
|
196
|
+
console.log(' A placeholder value (re_..., your-google-client-id) is a value, not a missing one: NEXTSPARK_AUTH_RUNTIME_ONLY does not defer it.')
|
|
197
|
+
console.log(' Remove the placeholder lines from .env (and the build environment), or replace them with the real values.')
|
|
198
|
+
}
|
|
195
199
|
process.exit(1)
|
|
196
200
|
}
|
|
197
201
|
|
|
@@ -15,6 +15,9 @@ import test from 'node:test'
|
|
|
15
15
|
import { discoverApiPresets } from '../discovery/api-presets.mjs'
|
|
16
16
|
import { discoverRouteFiles } from '../discovery/plugins.mjs'
|
|
17
17
|
import { generateRouteHandlersRegistry } from '../generators/route-handlers.mjs'
|
|
18
|
+
import { silenceConsoleOutput } from './quiet-console.mjs'
|
|
19
|
+
|
|
20
|
+
silenceConsoleOutput()
|
|
18
21
|
|
|
19
22
|
const presets = endpoint => `export default defineApiEndpoint({\n endpoint: '${endpoint}',\n summary: 'S',\n presets: [{ id: 'a', title: 'A', method: 'GET' }]\n})\n`
|
|
20
23
|
|
|
@@ -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}`
|
|
@@ -29,7 +29,7 @@ const RAW_CONTROL = /[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u061c\u200e\u200f\
|
|
|
29
29
|
async function createProject(parent = tmpdir(), name = 'nextspark-build-output-test-') {
|
|
30
30
|
const root = await mkdtemp(join(parent, name))
|
|
31
31
|
await writeFile(join(root, 'nextspark.config.ts'), 'export default {}\n')
|
|
32
|
-
await writeFile(join(root, 'package.json'), '{"dependencies":{"next":"16.3.
|
|
32
|
+
await writeFile(join(root, 'package.json'), '{"dependencies":{"next":"16.3.6"}}')
|
|
33
33
|
await mkdir(join(root, 'src', 'app'), { recursive: true })
|
|
34
34
|
return root
|
|
35
35
|
}
|
|
@@ -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-'))
|
|
@@ -17,7 +17,7 @@ async function project(files = {}) {
|
|
|
17
17
|
|
|
18
18
|
test('the nearest nextspark.config.ts owns every project source path', async () => {
|
|
19
19
|
const root = await project({
|
|
20
|
-
'package.json': JSON.stringify({ dependencies: { next: '16.3.
|
|
20
|
+
'package.json': JSON.stringify({ dependencies: { next: '16.3.6' } }),
|
|
21
21
|
'nextspark.config.ts': 'export default {}\n',
|
|
22
22
|
})
|
|
23
23
|
const nested = join(root, 'entities', 'tasks')
|
|
@@ -40,7 +40,7 @@ test('the nearest nextspark.config.ts owns every project source path', async ()
|
|
|
40
40
|
|
|
41
41
|
test('a next.config file cannot select a project without nextspark.config.ts', async () => {
|
|
42
42
|
const root = await project({
|
|
43
|
-
'package.json': JSON.stringify({ dependencies: { next: '16.3.
|
|
43
|
+
'package.json': JSON.stringify({ dependencies: { next: '16.3.6' } }),
|
|
44
44
|
'next.config.mjs': 'export default {}\n',
|
|
45
45
|
})
|
|
46
46
|
try {
|
|
@@ -66,7 +66,7 @@ test('an enabled packaged plugin resolves through the project dependency tree',
|
|
|
66
66
|
const root = await project({
|
|
67
67
|
'package.json': JSON.stringify({
|
|
68
68
|
dependencies: {
|
|
69
|
-
next: '16.3.
|
|
69
|
+
next: '16.3.6',
|
|
70
70
|
'@example/plugin-search': '1.0.0',
|
|
71
71
|
},
|
|
72
72
|
}),
|
|
@@ -98,7 +98,7 @@ test('an enabled packaged plugin that is not installed fails with an actionable
|
|
|
98
98
|
const root = await project({
|
|
99
99
|
'package.json': JSON.stringify({
|
|
100
100
|
dependencies: {
|
|
101
|
-
next: '16.3.
|
|
101
|
+
next: '16.3.6',
|
|
102
102
|
'@example/plugin-search': '1.0.0',
|
|
103
103
|
},
|
|
104
104
|
}),
|
|
@@ -119,7 +119,7 @@ test('a packaged plugin cannot use a path segment as its logical name', async ()
|
|
|
119
119
|
const root = await project({
|
|
120
120
|
'package.json': JSON.stringify({
|
|
121
121
|
dependencies: {
|
|
122
|
-
next: '16.3.
|
|
122
|
+
next: '16.3.6',
|
|
123
123
|
'@example/plugin-search': '1.0.0',
|
|
124
124
|
},
|
|
125
125
|
}),
|
|
@@ -146,7 +146,7 @@ test('a local plugin shadows an installed packaged plugin with the same NextSpar
|
|
|
146
146
|
const root = await project({
|
|
147
147
|
'package.json': JSON.stringify({
|
|
148
148
|
dependencies: {
|
|
149
|
-
next: '16.3.
|
|
149
|
+
next: '16.3.6',
|
|
150
150
|
'@example/plugin-search': '1.0.0',
|
|
151
151
|
},
|
|
152
152
|
}),
|
|
@@ -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
|
|
@@ -269,7 +269,7 @@ async function snapshot(root) {
|
|
|
269
269
|
async function buildableProject() {
|
|
270
270
|
const project = await directory()
|
|
271
271
|
await writeIn(project.root, 'nextspark.config.ts', 'export default {}\n')
|
|
272
|
-
await writeIn(project.root, 'package.json', '{"dependencies":{"next":"16.3.
|
|
272
|
+
await writeIn(project.root, 'package.json', '{"dependencies":{"next":"16.3.6"}}')
|
|
273
273
|
await writeIn(project.root, 'templates/pricing/page.tsx', 'export default function Pricing() { return null }\n')
|
|
274
274
|
return project
|
|
275
275
|
}
|
|
@@ -22,7 +22,7 @@ async function planFor(files) {
|
|
|
22
22
|
const root = temp()
|
|
23
23
|
try {
|
|
24
24
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
25
|
-
write(root, 'package.json', JSON.stringify({ name: 'wire', dependencies: { next: '16.3.
|
|
25
|
+
write(root, 'package.json', JSON.stringify({ name: 'wire', dependencies: { next: '16.3.6' } }))
|
|
26
26
|
write(root, 'config/theme.config.ts', "export const wireThemeConfig = { name: 'wire' }\n")
|
|
27
27
|
for (const [path, content] of Object.entries(files)) write(root, path, content)
|
|
28
28
|
const entities = await discoverAllEntities(getConfig(root), { includeCore: false })
|
|
@@ -73,7 +73,7 @@ const NOTES_CONFIG = `export const noteEntityConfig = {
|
|
|
73
73
|
function project(extra = {}) {
|
|
74
74
|
const root = temp()
|
|
75
75
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
76
|
-
write(root, 'package.json', JSON.stringify({ name: 'contracts-project', dependencies: { next: '16.3.
|
|
76
|
+
write(root, 'package.json', JSON.stringify({ name: 'contracts-project', dependencies: { next: '16.3.6' } }))
|
|
77
77
|
write(root, 'config/theme.config.ts', "export const contractsThemeConfig = { name: 'contracts' }\n")
|
|
78
78
|
write(root, 'entities/tasks/tasks.config.ts', TASKS_CONFIG)
|
|
79
79
|
write(root, 'entities/tasks/tasks.fields.ts', FIELDS)
|
|
@@ -201,7 +201,7 @@ export function renderEntity(contract, { source, shape } = {}) {
|
|
|
201
201
|
const lines = [CONTRACTS_HEADER]
|
|
202
202
|
if (source) lines.push(`// Source: ${source}`)
|
|
203
203
|
for (const warning of contract.warnings) lines.push(`// Warning: ${warning}`)
|
|
204
|
-
lines.push('', "import
|
|
204
|
+
lines.push('', "import * as z from 'zod'", "import type { EntityConfig } from '../schema/types'", "import { generateEntitySchemas } from '../schema/schema-generator'", `import { ${usedFromShape.join(', ')} } from '../schema/response-shape'`)
|
|
205
205
|
if (shared.length > 0) lines.push(`import { ${shared.join(', ')} } from '../fields'`)
|
|
206
206
|
lines.push('', `export const ${names.apiPath} = ${quote(`/api/v1/${contract.slug}`)}`, '')
|
|
207
207
|
|
|
@@ -246,7 +246,7 @@ export function renderEntity(contract, { source, shape } = {}) {
|
|
|
246
246
|
export function renderEnvelope() {
|
|
247
247
|
return `${CONTRACTS_HEADER}
|
|
248
248
|
|
|
249
|
-
import
|
|
249
|
+
import * as z from 'zod'
|
|
250
250
|
|
|
251
251
|
/** \`info\` of every response: the timestamp, plus whatever the endpoint adds (pagination, \`created\`). */
|
|
252
252
|
export const apiInfoSchema = z.looseObject({ timestamp: z.string() })
|
|
@@ -307,7 +307,7 @@ export const apiErrorResponseSchema = z.object({
|
|
|
307
307
|
export function renderFields() {
|
|
308
308
|
return `${CONTRACTS_HEADER}
|
|
309
309
|
|
|
310
|
-
import
|
|
310
|
+
import * as z from 'zod'
|
|
311
311
|
import { addressSchema, fileObjectSchema } from './schema/schema-generator'
|
|
312
312
|
import { mediaRefSchema } from './schema/media-ref'
|
|
313
313
|
|
|
@@ -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
|
|
@@ -28,7 +28,7 @@ const { target, content } = await emitFacade({
|
|
|
28
28
|
Every problem is a `FacadeEmitError` carrying all diagnostics (`code`, `file`, `line`, `exportName`,
|
|
29
29
|
`message`); nothing is dropped silently.
|
|
30
30
|
|
|
31
|
-
## Export table (Next.js 16.3.
|
|
31
|
+
## Export table (Next.js 16.3.6)
|
|
32
32
|
|
|
33
33
|
Source of truth: `next-route-exports.json`. The tests fail when the installed Next.js version differs,
|
|
34
34
|
when Next's `AppSegmentConfigSchemaKeys` or `HTTP_METHODS` change, and when this rendering is stale.
|
|
@@ -71,7 +71,7 @@ export { listSlugs as generateStaticParams }
|
|
|
71
71
|
Metadata image kinds accept Next's one-digit variants (`icon1`, `apple-icon9`, `opengraph-image2`,
|
|
72
72
|
`twitter-image3`), mapped to the base kind.
|
|
73
73
|
|
|
74
|
-
The emitter resolves `next/package.json` once (project first, then core) and accepts only `~16.3.
|
|
74
|
+
The emitter resolves `next/package.json` once (project first, then core) and accepts only `~16.3.6`: the table's
|
|
75
75
|
`next` is the verified baseline, and any stable 16.3.x patch at or above it is accepted, while other minors/majors,
|
|
76
76
|
lower patches and prereleases are refused (`NS_HOST_UNSUPPORTED_NEXT_VERSION`). It loads Next's segment config
|
|
77
77
|
schema from that same package root.
|
|
@@ -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`.
|
|
@@ -595,14 +595,14 @@ test('an aliased magic export is forwarded under its source local name', async (
|
|
|
595
595
|
assert.match(content, /^export \{ listSlugs as generateStaticParams \}$/m)
|
|
596
596
|
})
|
|
597
597
|
|
|
598
|
-
// --- fix round 3: ~16.3.
|
|
598
|
+
// --- fix round 3: ~baseline version range (baseline 16.3.6 since 0.1.0-beta.194) ---
|
|
599
599
|
|
|
600
600
|
test('isSupportedNextVersion accepts ~baseline: same minor, stable patch >= baseline', () => {
|
|
601
|
-
assert.equal(ROUTE_EXPORT_TABLE.next, '16.3.
|
|
602
|
-
for (const version of ['16.3.
|
|
601
|
+
assert.equal(ROUTE_EXPORT_TABLE.next, '16.3.6')
|
|
602
|
+
for (const version of ['16.3.6', '16.3.7', '16.3.9', '16.3.10', '16.3.123', '16.3.6+build.1', '16.3.7+sha.abc-1']) assert.equal(isSupportedNextVersion(version), true, version)
|
|
603
603
|
for (const version of [
|
|
604
|
-
'16.3.4', '16.3.0', '16.4.0', '16.2.9', '17.0.0', '15.3.
|
|
605
|
-
'16.3.
|
|
604
|
+
'16.3.5', '16.3.4', '16.3.0', '16.4.0', '16.2.9', '17.0.0', '15.3.6', '16.3.7-canary.0', '16.3.6-rc.1', '16.3.7-canary.0+build.1',
|
|
605
|
+
'16.3.5+build.1', '16.3.6+', '16.3.6+bad..meta', '16.3', 'latest', '', undefined,
|
|
606
606
|
]) {
|
|
607
607
|
assert.equal(isSupportedNextVersion(version), false, String(version))
|
|
608
608
|
}
|
|
@@ -625,7 +625,7 @@ test('a later 16.3 patch is accepted and its own schema internal is used', async
|
|
|
625
625
|
}
|
|
626
626
|
})
|
|
627
627
|
|
|
628
|
-
for (const version of ['16.3.
|
|
628
|
+
for (const version of ['16.3.5', '16.4.0', '17.0.0', '16.3.6-canary.0']) {
|
|
629
629
|
test(`${DIAGNOSTICS.UNSUPPORTED_NEXT_VERSION}: next@${version} is refused`, async () => {
|
|
630
630
|
const dir = await fakeNextProject(version, "exports.AppSegmentConfigSchemaKeys = []\nexports.parseAppSegmentConfig = () => ({})\n")
|
|
631
631
|
try {
|
|
@@ -634,7 +634,7 @@ for (const version of ['16.3.4', '16.4.0', '17.0.0', '16.3.6-canary.0']) {
|
|
|
634
634
|
error => {
|
|
635
635
|
assert.deepEqual(error.diagnostics.map(d => d.code), [DIAGNOSTICS.UNSUPPORTED_NEXT_VERSION])
|
|
636
636
|
assert.match(error.message, new RegExp(`next@${version.replace(/\./g, '\\.')}`))
|
|
637
|
-
assert.match(error.message, /next@~16\.3\.
|
|
637
|
+
assert.match(error.message, /next@~16\.3\.6/)
|
|
638
638
|
return true
|
|
639
639
|
}
|
|
640
640
|
)
|
|
@@ -646,9 +646,9 @@ for (const version of ['16.3.4', '16.4.0', '17.0.0', '16.3.6-canary.0']) {
|
|
|
646
646
|
|
|
647
647
|
// --- fix round 4: build metadata ----------------------------------------------
|
|
648
648
|
|
|
649
|
-
test('a Next.js version with build metadata (16.3.
|
|
649
|
+
test('a Next.js version with build metadata (16.3.6+build.1) is accepted end to end', async () => {
|
|
650
650
|
const marker = "exports.AppSegmentConfigSchemaKeys = []\nexports.parseAppSegmentConfig = () => ({})\n"
|
|
651
|
-
const dir = await fakeNextProject('16.3.
|
|
651
|
+
const dir = await fakeNextProject('16.3.6+build.1', marker)
|
|
652
652
|
try {
|
|
653
653
|
assert.equal(loadNextSegmentConfig(dir).error, undefined)
|
|
654
654
|
const { content } = await emitFacade({ kind: 'page', target: 'a/page.tsx', specifier: '@/a', file: FILE, source: PAGE_WITH_CONFIG, projectRoot: dir })
|
|
@@ -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
|
// ---------------------------------------------------------------------------
|