@nextsparkjs/core 0.1.0-beta.184 → 0.1.0-beta.186
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.map +1 -1
- package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicFooter.js +2 -2
- package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicNavbar.js +2 -2
- package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
- package/dist/components/app/misc/ThemeToggle.js +4 -1
- package/dist/components/auth/DevKeyring.d.ts.map +1 -1
- package/dist/components/auth/DevKeyring.js +10 -2
- package/dist/components/auth/SessionCookieRefresher.d.ts +21 -0
- package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -0
- package/dist/components/auth/SessionCookieRefresher.js +11 -0
- package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
- package/dist/components/auth/forms/LoginForm.js +300 -6
- package/dist/components/auth/index.d.ts +1 -0
- package/dist/components/auth/index.d.ts.map +1 -1
- package/dist/components/auth/index.js +2 -0
- package/dist/components/billing/InvoicesTable.d.ts.map +1 -1
- package/dist/components/billing/InvoicesTable.js +4 -3
- package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/AuthGuard.js +2 -2
- package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/DashboardAuthLayout.js +2 -2
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +17 -0
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts.map +1 -0
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.js +52 -0
- package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/Sidebar.js +2 -3
- package/dist/components/dashboard/layouts/TopNavbar.js +2 -2
- package/dist/components/dashboard/layouts/index.d.ts +1 -0
- package/dist/components/dashboard/layouts/index.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/index.js +2 -0
- package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
- package/dist/components/entities/EntityFieldRenderer.js +3 -0
- package/dist/components/entities/EntityTable.d.ts.map +1 -1
- package/dist/components/entities/EntityTable.js +2 -1
- package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
- package/dist/components/entities/wrappers/EntityFormWrapper.js +7 -2
- package/dist/components/ui/combobox.d.ts +8 -1
- package/dist/components/ui/combobox.d.ts.map +1 -1
- package/dist/components/ui/combobox.js +37 -44
- package/dist/components/ui/country-select.d.ts +3 -1
- package/dist/components/ui/country-select.d.ts.map +1 -1
- package/dist/components/ui/country-select.js +3 -1
- package/dist/components/ui/currency-select.d.ts +3 -1
- package/dist/components/ui/currency-select.d.ts.map +1 -1
- package/dist/components/ui/currency-select.js +3 -1
- package/dist/components/ui/sheet.d.ts +5 -0
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +46 -26
- package/dist/components/ui/timezone-select.d.ts +3 -1
- package/dist/components/ui/timezone-select.d.ts.map +1 -1
- package/dist/components/ui/timezone-select.js +3 -1
- package/dist/contexts/TeamContext.d.ts +6 -0
- package/dist/contexts/TeamContext.d.ts.map +1 -1
- package/dist/contexts/TeamContext.js +8 -1
- package/dist/emails/otp-verification.d.ts.map +1 -1
- package/dist/emails/reset-password.d.ts.map +1 -1
- 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/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useAuth.d.ts +103 -25
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +24 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts +22 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
- package/dist/hooks/useEnsureUserMetadata.js +18 -5
- package/dist/hooks/useSessionCookieRefresh.d.ts +35 -0
- package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -0
- package/dist/hooks/useSessionCookieRefresh.js +49 -0
- package/dist/hooks/useValidationSchemas.d.ts +7 -7
- package/dist/lib/api/api-error.d.ts +3 -1
- package/dist/lib/api/api-error.d.ts.map +1 -1
- package/dist/lib/api/api-error.js +4 -2
- package/dist/lib/api/auth/dual-auth.d.ts +45 -3
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +42 -7
- package/dist/lib/api/auth/permissions.d.ts +7 -2
- package/dist/lib/api/auth/permissions.d.ts.map +1 -1
- package/dist/lib/api/auth/permissions.js +2 -2
- package/dist/lib/api/auth/scope-policy.d.ts +71 -0
- package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
- package/dist/lib/api/auth/scope-policy.js +53 -0
- package/dist/lib/api/auth.d.ts +35 -3
- package/dist/lib/api/auth.d.ts.map +1 -1
- package/dist/lib/api/auth.js +54 -23
- package/dist/lib/api/entity/audit-log.d.ts +35 -0
- package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
- package/dist/lib/api/entity/audit-log.js +35 -0
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +333 -155
- package/dist/lib/api/helpers.d.ts +29 -4
- package/dist/lib/api/helpers.d.ts.map +1 -1
- package/dist/lib/api/helpers.js +30 -6
- package/dist/lib/api/keys.d.ts +16 -0
- package/dist/lib/api/keys.d.ts.map +1 -1
- package/dist/lib/api/keys.js +19 -1
- package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
- package/dist/lib/api/routes/patterns-usages.js +3 -11
- package/dist/lib/auth/auth-methods.d.ts +52 -0
- package/dist/lib/auth/auth-methods.d.ts.map +1 -0
- package/dist/lib/auth/auth-methods.js +63 -0
- package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
- package/dist/lib/auth/registration-helpers.d.ts +9 -0
- package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
- package/dist/lib/auth/registration-helpers.js +8 -2
- package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
- package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
- package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
- package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
- package/dist/lib/auth/security-notifications/index.d.ts +20 -0
- package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/index.js +22 -0
- package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
- package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/registry.js +26 -0
- package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
- package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
- package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
- package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
- package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
- package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
- package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
- package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
- package/dist/lib/auth/security-notifications/types.d.ts +74 -0
- package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/types.js +0 -0
- package/dist/lib/auth/session-config.d.ts +37 -0
- package/dist/lib/auth/session-config.d.ts.map +1 -0
- package/dist/lib/auth/session-config.js +59 -0
- package/dist/lib/auth/session-refresh.d.ts +46 -0
- package/dist/lib/auth/session-refresh.d.ts.map +1 -0
- package/dist/lib/auth/session-refresh.js +11 -0
- package/dist/lib/auth-client.d.ts +1489 -3445
- package/dist/lib/auth-client.d.ts.map +1 -1
- package/dist/lib/auth-client.js +4 -0
- package/dist/lib/auth.d.ts +1 -3057
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +21 -8
- package/dist/lib/billing/gateways/factory.d.ts +8 -3
- package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
- package/dist/lib/billing/gateways/factory.js +37 -23
- package/dist/lib/billing/schema.d.ts +1 -1
- package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
- package/dist/lib/billing/stripe-webhook.js +2 -1
- package/dist/lib/config/app.config.d.ts.map +1 -1
- package/dist/lib/config/app.config.js +71 -0
- package/dist/lib/config/config-sync.d.ts +1 -4
- package/dist/lib/config/config-sync.d.ts.map +1 -1
- package/dist/lib/config/config-sync.js +6 -2
- package/dist/lib/config/types.d.ts +130 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/entities/entity-hooks.d.ts +9 -1
- package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
- package/dist/lib/entities/entity-hooks.js +77 -4
- package/dist/lib/entities/schema-generator.d.ts.map +1 -1
- package/dist/lib/entities/schema-generator.js +20 -4
- package/dist/lib/entities/serialization.d.ts +2 -2
- package/dist/lib/entities/serialization.d.ts.map +1 -1
- package/dist/lib/entities/serialization.js +3 -2
- package/dist/lib/entities/types.d.ts +8 -0
- package/dist/lib/entities/types.d.ts.map +1 -1
- package/dist/lib/locale.d.ts.map +1 -1
- package/dist/lib/locale.js +4 -1
- package/dist/lib/mcp/audit.d.ts +21 -0
- package/dist/lib/mcp/audit.d.ts.map +1 -0
- package/dist/lib/mcp/audit.js +48 -0
- package/dist/lib/mcp/engine.d.ts +28 -0
- package/dist/lib/mcp/engine.d.ts.map +1 -0
- package/dist/lib/mcp/engine.js +158 -0
- package/dist/lib/mcp/errors.d.ts +16 -0
- package/dist/lib/mcp/errors.d.ts.map +1 -0
- package/dist/lib/mcp/errors.js +66 -0
- package/dist/lib/mcp/executor.d.ts +15 -0
- package/dist/lib/mcp/executor.d.ts.map +1 -0
- package/dist/lib/mcp/executor.js +63 -0
- package/dist/lib/mcp/index.d.ts +9 -0
- package/dist/lib/mcp/index.d.ts.map +1 -0
- package/dist/lib/mcp/index.js +32 -0
- package/dist/lib/mcp/schema-builder.d.ts +45 -0
- package/dist/lib/mcp/schema-builder.d.ts.map +1 -0
- package/dist/lib/mcp/schema-builder.js +212 -0
- package/dist/lib/mcp/tool-generator.d.ts +31 -0
- package/dist/lib/mcp/tool-generator.d.ts.map +1 -0
- package/dist/lib/mcp/tool-generator.js +271 -0
- package/dist/lib/mcp/transport.d.ts +31 -0
- package/dist/lib/mcp/transport.d.ts.map +1 -0
- package/dist/lib/mcp/transport.js +58 -0
- package/dist/lib/mcp/types.d.ts +184 -0
- package/dist/lib/mcp/types.d.ts.map +1 -0
- package/dist/lib/mcp/types.js +11 -0
- package/dist/lib/plugins/hook-system.d.ts +13 -0
- package/dist/lib/plugins/hook-system.d.ts.map +1 -1
- package/dist/lib/plugins/hook-system.js +32 -2
- package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
- package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/handlers/index.js +3 -1
- package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
- package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
- package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
- package/dist/lib/selectors/core-selectors.d.ts +12 -0
- package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
- package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.js +14 -1
- package/dist/lib/selectors/selectors.d.ts +24 -0
- package/dist/lib/selectors/selectors.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.js +16 -32
- package/dist/lib/services/membership.service.d.ts +10 -2
- package/dist/lib/services/membership.service.d.ts.map +1 -1
- package/dist/lib/services/membership.service.js +37 -16
- package/dist/lib/services/subscription.service.d.ts.map +1 -1
- package/dist/lib/services/subscription.service.js +2 -1
- package/dist/lib/services/team-member.service.d.ts +15 -3
- package/dist/lib/services/team-member.service.d.ts.map +1 -1
- package/dist/lib/services/team-member.service.js +27 -6
- package/dist/lib/services/team.service.d.ts.map +1 -1
- package/dist/lib/teams/permissions.d.ts +3 -1
- package/dist/lib/teams/permissions.d.ts.map +1 -1
- package/dist/lib/teams/permissions.js +6 -0
- package/dist/lib/teams/schema.d.ts.map +1 -1
- package/dist/lib/teams/schema.js +3 -0
- package/dist/lib/theme/forced-theme.d.ts +34 -0
- package/dist/lib/theme/forced-theme.d.ts.map +1 -0
- package/dist/lib/theme/forced-theme.js +27 -0
- package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
- package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
- package/dist/lib/theme/get-default-theme-mode.js +5 -3
- package/dist/messages/de/auth.json +25 -2
- package/dist/messages/de/index.d.ts +81 -0
- 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/en/auth.json +25 -2
- package/dist/messages/en/index.d.ts +81 -0
- 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/es/auth.json +25 -2
- package/dist/messages/es/index.d.ts +81 -0
- 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/fr/auth.json +25 -2
- package/dist/messages/fr/index.d.ts +81 -0
- 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/it/auth.json +25 -2
- package/dist/messages/it/index.d.ts +81 -0
- 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/pt/auth.json +25 -2
- package/dist/messages/pt/index.d.ts +81 -0
- 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/migrations/005_api_audit_log.sql +2 -1
- package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
- package/dist/migrations/026_login_events.sql +91 -0
- package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
- package/dist/providers/DashboardProviders.d.ts +12 -3
- package/dist/providers/DashboardProviders.d.ts.map +1 -1
- package/dist/providers/DashboardProviders.js +6 -2
- package/dist/providers/theme-provider.d.ts +19 -2
- package/dist/providers/theme-provider.d.ts.map +1 -1
- package/dist/providers/theme-provider.js +6 -2
- package/dist/templates/app/(auth)/signup/page.tsx +8 -0
- package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
- package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
- package/dist/templates/app/api/health/route.ts +4 -2
- package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
- package/dist/templates/app/api/mcp/route.ts +176 -0
- package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
- package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
- package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
- package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
- package/dist/templates/app/api/v1/api-keys/route.ts +35 -45
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
- package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
- package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
- package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
- package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
- package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
- package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
- package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
- package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
- package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
- package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
- package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
- package/dist/templates/app/api/v1/media/route.ts +5 -9
- package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
- package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
- package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
- package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
- package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
- package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
- package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
- package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
- package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
- package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
- package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
- package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
- package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
- package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
- package/dist/templates/app/api/v1/teams/route.ts +33 -27
- package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
- package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
- package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
- package/dist/templates/app/api/v1/users/route.ts +21 -25
- package/dist/templates/app/dashboard/layout.tsx +2 -6
- package/dist/templates/app/layout.ppr.tsx +9 -14
- package/dist/templates/app/layout.tsx +39 -19
- package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
- package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
- package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
- package/dist/templates/proxy.ts +46 -10
- package/dist/templates/tsconfig.json +4 -0
- package/dist/types/theme.d.ts +9 -0
- package/dist/types/theme.d.ts.map +1 -1
- package/migrations/005_api_audit_log.sql +2 -1
- package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
- package/migrations/026_login_events.sql +91 -0
- package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
- package/package.json +20 -12
- package/scripts/build/registry/__tests__/mcp-registry.test.mjs +64 -0
- package/scripts/build/registry/__tests__/scope-registry.test.mjs +106 -0
- package/scripts/build/registry/discovery/mcp-overrides.mjs +72 -0
- package/scripts/build/registry/discovery/mcp-overrides.test.mjs +86 -0
- package/scripts/build/registry/generators/mcp-registry.mjs +61 -0
- package/scripts/build/registry/generators/scope-registry.mjs +140 -88
- package/scripts/build/registry.mjs +8 -4
- package/templates/app/(auth)/signup/page.tsx +8 -0
- package/templates/app/(public)/[...slug]/page.tsx +9 -14
- package/templates/app/api/auth/[...all]/route.ts +23 -1
- package/templates/app/api/health/route.ts +4 -2
- package/templates/app/api/internal/user-metadata/route.ts +5 -3
- package/templates/app/api/mcp/route.ts +176 -0
- package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
- package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
- package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
- package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
- package/templates/app/api/v1/api-keys/route.ts +35 -45
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
- package/templates/app/api/v1/billing/cancel/route.ts +7 -6
- package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
- package/templates/app/api/v1/billing/check-action/route.ts +6 -4
- package/templates/app/api/v1/billing/checkout/route.ts +7 -5
- package/templates/app/api/v1/billing/plans/route.ts +8 -12
- package/templates/app/api/v1/billing/portal/route.ts +7 -5
- package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
- package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
- package/templates/app/api/v1/devtools/features/route.ts +5 -4
- package/templates/app/api/v1/devtools/flows/route.ts +5 -4
- package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
- package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
- package/templates/app/api/v1/devtools/testing/route.ts +5 -4
- package/templates/app/api/v1/media/[id]/route.ts +13 -25
- package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
- package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
- package/templates/app/api/v1/media/route.ts +5 -9
- package/templates/app/api/v1/media/upload/route.ts +9 -21
- package/templates/app/api/v1/media-tags/route.ts +9 -13
- package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
- package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
- package/templates/app/api/v1/post-categories/route.ts +5 -4
- package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
- package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
- package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
- package/templates/app/api/v1/team-invitations/route.ts +9 -11
- package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
- package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
- package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
- package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
- package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
- package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
- package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
- package/templates/app/api/v1/teams/route.ts +33 -27
- package/templates/app/api/v1/teams/switch/route.ts +11 -13
- package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
- package/templates/app/api/v1/users/[id]/route.ts +35 -68
- package/templates/app/api/v1/users/route.ts +21 -25
- package/templates/app/dashboard/layout.tsx +2 -6
- package/templates/app/layout.ppr.tsx +9 -14
- package/templates/app/layout.tsx +39 -19
- package/templates/contents/themes/starter/config/app.config.ts +19 -0
- package/templates/contents/themes/starter/config/theme.config.ts +6 -0
- package/templates/contents/themes/starter/styles/globals.css +6 -2
- package/templates/proxy.ts +46 -10
- package/templates/tsconfig.json +4 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/scope-registry.ts +56 -6
- package/tests/jest/__mocks__/next-server.js +24 -0
- package/dist/styles/classes.json +0 -1093
- package/dist/styles/ui.css +0 -2
- package/scripts/build/build-ui-css.mjs +0 -351
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the scope-registry generator.
|
|
3
|
+
*
|
|
4
|
+
* The role→scope computation no longer happens at generation time (see the
|
|
5
|
+
* rationale comment at the top of `generators/scope-registry.mjs`) — it's
|
|
6
|
+
* emitted as TypeScript that computes `SCOPE_CONFIG.roles` at IMPORT time,
|
|
7
|
+
* from the sibling generated `entity-registry.ts`/`permissions-registry.ts`.
|
|
8
|
+
* So this generator-level test can only assert on the EMITTED source: the
|
|
9
|
+
* correct static imports, the defensive entity-registry guard, the
|
|
10
|
+
* role/permission-derived scope logic, and the untouched flag/restriction
|
|
11
|
+
* literals. The actual `SCOPE_CONFIG.roles` VALUES for a real role are
|
|
12
|
+
* covered by `packages/core/tests/jest/services/scope.service.test.ts`
|
|
13
|
+
* (against a realistic mock) and by manual inspection of the real generated
|
|
14
|
+
* output after `node packages/core/scripts/build/registry.mjs`.
|
|
15
|
+
*
|
|
16
|
+
* Run: node --test packages/core/scripts/build/registry/__tests__/scope-registry.test.mjs
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { test } from 'node:test'
|
|
20
|
+
import assert from 'node:assert/strict'
|
|
21
|
+
|
|
22
|
+
import { generateScopeRegistry } from '../generators/scope-registry.mjs'
|
|
23
|
+
|
|
24
|
+
test('imports ENTITY_REGISTRY and AVAILABLE_ROLES/PERMISSIONS_BY_ROLE from sibling generated registries', () => {
|
|
25
|
+
const out = generateScopeRegistry([], {})
|
|
26
|
+
assert.match(out, /import \{ ENTITY_REGISTRY \} from '\.\/entity-registry'/)
|
|
27
|
+
assert.match(out, /import \{ AVAILABLE_ROLES, PERMISSIONS_BY_ROLE \} from '\.\/permissions-registry'/)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('does not reintroduce the old generation-time entities/config computation', () => {
|
|
31
|
+
const out = generateScopeRegistry([{ name: 'ignored', api: { endpoints: { create: true } } }], { some: 'config' })
|
|
32
|
+
// The old bug read entity.api.endpoints/entity.features/entity.fields directly —
|
|
33
|
+
// none of those property paths should appear in the emitted output at all,
|
|
34
|
+
// since the real computation now reads ENTITY_REGISTRY's real EntityConfig.
|
|
35
|
+
assert.doesNotMatch(out, /entity\.api\.endpoints/)
|
|
36
|
+
assert.doesNotMatch(out, /entity\.features/)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('base scopes are empty — nothing granted irrespective of role', () => {
|
|
40
|
+
const out = generateScopeRegistry([], {})
|
|
41
|
+
assert.match(out, /base:\s*\[\]/)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('role scopes are computed from PERMISSIONS_BY_ROLE per AVAILABLE_ROLES, not hardcoded role names', () => {
|
|
45
|
+
const out = generateScopeRegistry([], {})
|
|
46
|
+
assert.match(out, /for \(const role of AVAILABLE_ROLES\)/)
|
|
47
|
+
assert.match(out, /const perms = PERMISSIONS_BY_ROLE\[role\]/)
|
|
48
|
+
// The old hardcoded roleScopes literal (superadmin/admin/manager/member keys,
|
|
49
|
+
// referencing a nonexistent "products" entity) must be gone.
|
|
50
|
+
assert.doesNotMatch(out, /'manager'/)
|
|
51
|
+
assert.doesNotMatch(out, /products:write/)
|
|
52
|
+
assert.doesNotMatch(out, /products:delete/)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('read/write/delete scopes are derived from list|read / create|update / delete permissions', () => {
|
|
56
|
+
const out = generateScopeRegistry([], {})
|
|
57
|
+
assert.match(out, /\$\{slug\}\.list.*Permission.*\|\|.*perms\?\.has\(`\$\{slug\}\.read`/)
|
|
58
|
+
assert.match(out, /\$\{slug\}\.create.*Permission.*\|\|.*perms\?\.has\(`\$\{slug\}\.update`/)
|
|
59
|
+
assert.match(out, /\$\{slug\}\.delete.*Permission/)
|
|
60
|
+
assert.match(out, /scopes\.push\(`\$\{slug\}:read`\)/)
|
|
61
|
+
assert.match(out, /scopes\.push\(`\$\{slug\}:write`\)/)
|
|
62
|
+
assert.match(out, /scopes\.push\(`\$\{slug\}:delete`\)/)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('guards against ChildEntityDefinition entries (no access/slug) in ENTITY_REGISTRY', () => {
|
|
66
|
+
const out = generateScopeRegistry([], {})
|
|
67
|
+
assert.match(out, /if \(!cfg\?\.access\?\.api \|\| !cfg\.slug\) return null/)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('flag scopes and restrictions are preserved unchanged', () => {
|
|
71
|
+
const out = generateScopeRegistry([], {})
|
|
72
|
+
assert.match(out, /beta_tester/)
|
|
73
|
+
assert.match(out, /vip:features/)
|
|
74
|
+
assert.match(out, /"remove": \[\s*"delete",\s*"admin"\s*\]/)
|
|
75
|
+
assert.match(out, /"allow_only": \[\s*"read",\s*"tasks:write"\s*\]/)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('superadmin is not hardcoded into SCOPE_CONFIG.roles — handled as a global-role bypass elsewhere', () => {
|
|
79
|
+
const out = generateScopeRegistry([], {})
|
|
80
|
+
assert.doesNotMatch(out, /'superadmin':\s*\[/)
|
|
81
|
+
assert.match(out, /Superadmin is a GLOBAL role/)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('is a pure function of its output shape — always emits both SCOPE_CONFIG and API_CONFIG exports', () => {
|
|
85
|
+
const out = generateScopeRegistry([], {})
|
|
86
|
+
assert.match(out, /export const SCOPE_CONFIG: ScopeConfig = \{/)
|
|
87
|
+
assert.match(out, /export const API_CONFIG: ApiConfig = \{/)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// Regression for the npm-mode bug: this generator hardcoded the two `@/core/*`
|
|
91
|
+
// type-only imports instead of routing them through convertCorePath() like
|
|
92
|
+
// every sibling generator does, so a scaffolded (non-monorepo) app's tsconfig
|
|
93
|
+
// — which maps `@/*` to its own `./*`, not to core — failed `tsc --noEmit`
|
|
94
|
+
// with TS2307 on both. See https://github.com/NextSpark-js/nextspark/issues/133
|
|
95
|
+
test('monorepo mode (isNpmMode: false or unset): @/core/* type imports are left as-is', () => {
|
|
96
|
+
const out = generateScopeRegistry([], { isNpmMode: false })
|
|
97
|
+
assert.match(out, /import type \{ Permission \} from '@\/core\/lib\/permissions\/types'/)
|
|
98
|
+
assert.match(out, /import type \{ EntityConfig \} from '@\/core\/lib\/entities\/types'/)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('npm mode: @/core/* type imports are converted to @nextsparkjs/core/*, like every sibling generator', () => {
|
|
102
|
+
const out = generateScopeRegistry([], { isNpmMode: true, outputDir: '/project/.nextspark/registries' })
|
|
103
|
+
assert.match(out, /import type \{ Permission \} from '@nextsparkjs\/core\/lib\/permissions\/types'/)
|
|
104
|
+
assert.match(out, /import type \{ EntityConfig \} from '@nextsparkjs\/core\/lib\/entities\/types'/)
|
|
105
|
+
assert.doesNotMatch(out, /from '@\/core\//)
|
|
106
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Override Discovery
|
|
3
|
+
*
|
|
4
|
+
* Discovers `entities/<slug>/mcp.ts` files for the active theme — the
|
|
5
|
+
* extension point a theme uses to customize how one entity is exposed by the
|
|
6
|
+
* MCP engine (@nextsparkjs/core/lib/mcp): exclude it, exclude specific
|
|
7
|
+
* operations, relax required fields, transform input/output, or add extra
|
|
8
|
+
* (non-CRUD) tools.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors discoverApiPresets's discoverEntityFolders (same
|
|
11
|
+
* `{theme}/entities/<slug>/` scan, same theme-only scope) with one
|
|
12
|
+
* difference: `mcp.ts` sits at the entity folder root (a sibling of `api/`,
|
|
13
|
+
* `migrations/`, `messages/`), not nested under `api/` like `presets.ts`.
|
|
14
|
+
*
|
|
15
|
+
* Unlike the presets discovery, this module does NOT parse file contents —
|
|
16
|
+
* an McpEntityOverride exports real functions (transformInput, extraTools),
|
|
17
|
+
* which can't be recovered by regex/brace-counting. The generator instead
|
|
18
|
+
* emits a literal `import` statement per discovered file (same approach as
|
|
19
|
+
* entity-registry.mjs), and the override object itself is resolved by the
|
|
20
|
+
* TypeScript compiler/bundler at build time, not by this script.
|
|
21
|
+
*
|
|
22
|
+
* @module core/scripts/build/registry/discovery/mcp-overrides
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { readdir } from 'fs/promises'
|
|
26
|
+
import { join } from 'path'
|
|
27
|
+
import { existsSync } from 'fs'
|
|
28
|
+
|
|
29
|
+
import { log, verbose } from '../../../utils/index.mjs'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Discover MCP entity overrides for the active theme.
|
|
33
|
+
* @param {object} config - Configuration object from getConfig()
|
|
34
|
+
* @returns {Promise<Array<{slug: string, importPath: string, themeName: string}>>}
|
|
35
|
+
*/
|
|
36
|
+
export async function discoverMcpOverrides(config) {
|
|
37
|
+
const overrides = []
|
|
38
|
+
const themeName = config.activeTheme
|
|
39
|
+
|
|
40
|
+
if (!themeName) {
|
|
41
|
+
verbose('[mcp-overrides] No active theme configured — skipping MCP override discovery')
|
|
42
|
+
return overrides
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const entitiesDir = join(config.themesDir, themeName, 'entities')
|
|
46
|
+
if (!existsSync(entitiesDir)) {
|
|
47
|
+
verbose(`[mcp-overrides] No entities directory found for theme "${themeName}"`)
|
|
48
|
+
return overrides
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const entries = await readdir(entitiesDir, { withFileTypes: true })
|
|
53
|
+
const entityDirs = entries.filter((entry) => entry.isDirectory())
|
|
54
|
+
|
|
55
|
+
for (const dir of entityDirs) {
|
|
56
|
+
const entityName = dir.name
|
|
57
|
+
const mcpPath = join(entitiesDir, entityName, 'mcp.ts')
|
|
58
|
+
if (!existsSync(mcpPath)) continue
|
|
59
|
+
|
|
60
|
+
overrides.push({
|
|
61
|
+
slug: entityName,
|
|
62
|
+
importPath: `@/contents/themes/${themeName}/entities/${entityName}/mcp`,
|
|
63
|
+
themeName,
|
|
64
|
+
})
|
|
65
|
+
verbose(`[mcp-overrides] MCP override discovered: ${entityName} -> ${mcpPath}`)
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
log(`[mcp-overrides] Error discovering MCP overrides: ${error.message}`, 'error')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return overrides
|
|
72
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for discoverMcpOverrides.
|
|
3
|
+
*
|
|
4
|
+
* Run: node --test packages/core/scripts/build/registry/discovery/mcp-overrides.test.mjs
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { test } from 'node:test'
|
|
8
|
+
import assert from 'node:assert/strict'
|
|
9
|
+
import { mkdtemp, mkdir, writeFile, rm } from 'fs/promises'
|
|
10
|
+
import { tmpdir } from 'os'
|
|
11
|
+
import { join } from 'path'
|
|
12
|
+
|
|
13
|
+
import { discoverMcpOverrides } from './mcp-overrides.mjs'
|
|
14
|
+
|
|
15
|
+
async function makeThemeFixture(entities) {
|
|
16
|
+
const root = await mkdtemp(join(tmpdir(), 'mcp-overrides-test-'))
|
|
17
|
+
const themesDir = join(root, 'themes')
|
|
18
|
+
const themeName = 'testtheme'
|
|
19
|
+
const entitiesDir = join(themesDir, themeName, 'entities')
|
|
20
|
+
|
|
21
|
+
for (const [slug, files] of Object.entries(entities)) {
|
|
22
|
+
const entityDir = join(entitiesDir, slug)
|
|
23
|
+
await mkdir(entityDir, { recursive: true })
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
await writeFile(join(entityDir, file), '// fixture\n')
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { root, themesDir, themeName }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('returns an empty array when there is no active theme', async () => {
|
|
33
|
+
const result = await discoverMcpOverrides({ activeTheme: undefined, themesDir: '/nonexistent' })
|
|
34
|
+
assert.deepEqual(result, [])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('returns an empty array when the theme has no entities directory', async () => {
|
|
38
|
+
const root = await mkdtemp(join(tmpdir(), 'mcp-overrides-test-'))
|
|
39
|
+
const result = await discoverMcpOverrides({ activeTheme: 'ghost', themesDir: join(root, 'themes') })
|
|
40
|
+
assert.deepEqual(result, [])
|
|
41
|
+
await rm(root, { recursive: true, force: true })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('finds mcp.ts at the entity folder root, not nested under api/', async () => {
|
|
45
|
+
const { root, themesDir, themeName } = await makeThemeFixture({
|
|
46
|
+
customers: ['customers.config.ts', 'mcp.ts'],
|
|
47
|
+
})
|
|
48
|
+
const result = await discoverMcpOverrides({ activeTheme: themeName, themesDir })
|
|
49
|
+
assert.equal(result.length, 1)
|
|
50
|
+
assert.equal(result[0].slug, 'customers')
|
|
51
|
+
assert.equal(result[0].importPath, `@/contents/themes/${themeName}/entities/customers/mcp`)
|
|
52
|
+
await rm(root, { recursive: true, force: true })
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('skips entities that have no mcp.ts', async () => {
|
|
56
|
+
const { root, themesDir, themeName } = await makeThemeFixture({
|
|
57
|
+
customers: ['customers.config.ts', 'mcp.ts'],
|
|
58
|
+
tasks: ['tasks.config.ts'],
|
|
59
|
+
})
|
|
60
|
+
const result = await discoverMcpOverrides({ activeTheme: themeName, themesDir })
|
|
61
|
+
assert.deepEqual(result.map((r) => r.slug), ['customers'])
|
|
62
|
+
await rm(root, { recursive: true, force: true })
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('does NOT match a preset-style entities/<slug>/api/mcp.ts (wrong location)', async () => {
|
|
66
|
+
const root = await mkdtemp(join(tmpdir(), 'mcp-overrides-test-'))
|
|
67
|
+
const themesDir = join(root, 'themes')
|
|
68
|
+
const themeName = 'testtheme'
|
|
69
|
+
await mkdir(join(themesDir, themeName, 'entities', 'customers', 'api'), { recursive: true })
|
|
70
|
+
await writeFile(join(themesDir, themeName, 'entities', 'customers', 'api', 'mcp.ts'), '// wrong spot\n')
|
|
71
|
+
|
|
72
|
+
const result = await discoverMcpOverrides({ activeTheme: themeName, themesDir })
|
|
73
|
+
assert.deepEqual(result, [])
|
|
74
|
+
await rm(root, { recursive: true, force: true })
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('discovers multiple entity overrides in the same theme', async () => {
|
|
78
|
+
const { root, themesDir, themeName } = await makeThemeFixture({
|
|
79
|
+
customers: ['mcp.ts'],
|
|
80
|
+
tasks: ['mcp.ts'],
|
|
81
|
+
posts: [],
|
|
82
|
+
})
|
|
83
|
+
const result = await discoverMcpOverrides({ activeTheme: themeName, themesDir })
|
|
84
|
+
assert.deepEqual(result.map((r) => r.slug).sort(), ['customers', 'tasks'])
|
|
85
|
+
await rm(root, { recursive: true, force: true })
|
|
86
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Override Registry Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates mcp-registry.ts: a literal-import map of discovered
|
|
5
|
+
* `entities/<slug>/mcp.ts` files (see discovery/mcp-overrides.mjs), consumed
|
|
6
|
+
* by the default `/api/mcp` route template to build the `overrides` option
|
|
7
|
+
* passed to `createMcpEngine`. Follows entity-registry.mjs's literal-import
|
|
8
|
+
* pattern (not the regex-parsing approach used for api-presets.ts) because
|
|
9
|
+
* an McpEntityOverride exports real functions — transformInput, extraTools —
|
|
10
|
+
* that can't be recovered from source text.
|
|
11
|
+
*
|
|
12
|
+
* @module core/scripts/build/registry/generators/mcp-registry
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { join } from 'path'
|
|
16
|
+
import { convertCorePath } from '../config.mjs'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate the MCP override registry file.
|
|
20
|
+
* @param {Array<{slug: string, importPath: string, themeName: string}>} mcpOverrides - Discovered overrides
|
|
21
|
+
* @param {object} config - Configuration object from getConfig()
|
|
22
|
+
* @returns {string} Generated TypeScript content
|
|
23
|
+
*/
|
|
24
|
+
export function generateMcpRegistry(mcpOverrides, config) {
|
|
25
|
+
const outputFilePath = join(config.outputDir, 'mcp-registry.ts')
|
|
26
|
+
|
|
27
|
+
const importName = (slug) => `${toPascalCase(slug)}McpOverride`
|
|
28
|
+
|
|
29
|
+
const imports = mcpOverrides
|
|
30
|
+
.map((entry) => `import ${importName(entry.slug)} from '${entry.importPath}'`)
|
|
31
|
+
.join('\n')
|
|
32
|
+
|
|
33
|
+
const entries = mcpOverrides
|
|
34
|
+
.map((entry) => ` '${entry.slug}': ${importName(entry.slug)},`)
|
|
35
|
+
.join('\n')
|
|
36
|
+
|
|
37
|
+
return `/**
|
|
38
|
+
* Auto-generated MCP Override Registry
|
|
39
|
+
*
|
|
40
|
+
* Generated at: ${new Date().toISOString()}
|
|
41
|
+
* MCP overrides discovered: ${mcpOverrides.length}
|
|
42
|
+
*
|
|
43
|
+
* DO NOT EDIT - This file is auto-generated by scripts/build/registry.mjs
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
${imports}
|
|
47
|
+
import type { McpEntityOverride } from '${convertCorePath('@/core/lib/mcp', outputFilePath, config)}'
|
|
48
|
+
|
|
49
|
+
export const MCP_OVERRIDES: Record<string, McpEntityOverride> = {
|
|
50
|
+
${entries}
|
|
51
|
+
}
|
|
52
|
+
`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toPascalCase(slug) {
|
|
56
|
+
return slug
|
|
57
|
+
.split(/[-_/]+/)
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
60
|
+
.join('')
|
|
61
|
+
}
|
|
@@ -2,113 +2,90 @@
|
|
|
2
2
|
* Scope Registry Generator
|
|
3
3
|
*
|
|
4
4
|
* Generates scope-registry.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
import { convertCorePath } from '../config.mjs'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT: unlike most registry generators, the role→scope computation does
|
|
13
|
+
* NOT happen here at generation time. It happens at IMPORT TIME inside the
|
|
14
|
+
* emitted TypeScript, by statically importing the sibling generated
|
|
15
|
+
* `entity-registry.ts` (for which entities are API-exposed, via
|
|
16
|
+
* `access.api`) and `permissions-registry.ts` (for which team roles can
|
|
17
|
+
* perform which action on each entity, via `PERMISSIONS_BY_ROLE`).
|
|
18
|
+
*
|
|
19
|
+
* Why: this generator previously computed scopes from the raw `entities`
|
|
20
|
+
* discovery array (filesystem-scan metadata only — `{name, exportName,
|
|
21
|
+
* configPath, ...}`), which never carried `.api`/`.features`/`.fields`
|
|
22
|
+
* properties. Those properties don't exist on that shape, nor on the real
|
|
23
|
+
* `EntityConfig` type, so the computation silently produced `base: []` and a
|
|
24
|
+
* hardcoded, wrong `roles` object every time. Reading the REAL entity config
|
|
25
|
+
* (`access.api`, `slug`, `fields`) and the REAL per-role permission matrix
|
|
26
|
+
* requires resolving imports, which can only happen once the generated files
|
|
27
|
+
* are imported by app code — not synchronously inside this generation
|
|
28
|
+
* script. Doing the computation at import time also guarantees scope
|
|
29
|
+
* minting and scope enforcement can never diverge again: both read the same
|
|
30
|
+
* `PERMISSIONS_BY_ROLE` source of truth `checkPermission()` uses at request
|
|
31
|
+
* time.
|
|
5
32
|
*
|
|
6
33
|
* @module core/scripts/build/registry/generators/scope-registry
|
|
7
34
|
*/
|
|
8
35
|
|
|
36
|
+
// Flag-based scopes and restriction rules are orthogonal to team roles/entity
|
|
37
|
+
// permissions (they're driven by user flags like `beta_tester`/`vip`, not by
|
|
38
|
+
// what a role can do to which entity) — kept as static literals, unrelated to
|
|
39
|
+
// the role/entity computation this generator used to get wrong.
|
|
40
|
+
const FLAG_SCOPES = {
|
|
41
|
+
'beta_tester': ['beta:features'],
|
|
42
|
+
'vip': ['vip:features', 'advanced:export'],
|
|
43
|
+
'early_adopter': ['early:features'],
|
|
44
|
+
'power_user': ['advanced:features', 'bulk:operations']
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const SCOPE_RESTRICTIONS = {
|
|
48
|
+
'restricted': {
|
|
49
|
+
remove: ['delete', 'admin']
|
|
50
|
+
},
|
|
51
|
+
'limited_access': {
|
|
52
|
+
allow_only: ['read', 'tasks:write']
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
9
56
|
/**
|
|
10
57
|
* Generate the scope registry file
|
|
11
58
|
*
|
|
12
|
-
* @param {Array} entities - Discovered entities
|
|
59
|
+
* @param {Array} entities - Discovered entities (unused for scope computation
|
|
60
|
+
* today — kept in the signature for call-site consistency with the other
|
|
61
|
+
* generators; the real per-entity data used below comes from the generated
|
|
62
|
+
* `entity-registry.ts` this file imports, not from this parameter)
|
|
13
63
|
* @param {object} config - Configuration object from getConfig()
|
|
14
64
|
* @returns {string} Generated TypeScript content
|
|
15
65
|
*/
|
|
16
66
|
export function generateScopeRegistry(entities, config) {
|
|
17
|
-
|
|
18
|
-
const baseScopes = []
|
|
19
|
-
const entityPaths = []
|
|
20
|
-
|
|
21
|
-
for (const entity of entities) {
|
|
22
|
-
if (entity.api && entity.features?.enabled) {
|
|
23
|
-
const entityName = entity.name
|
|
24
|
-
baseScopes.push(`${entityName}:read`)
|
|
25
|
-
|
|
26
|
-
// Add write scope if entity supports creation
|
|
27
|
-
if (entity.api.endpoints && entity.api.endpoints.create) {
|
|
28
|
-
baseScopes.push(`${entityName}:write`)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Add delete scope for entities that support deletion
|
|
32
|
-
if (entity.api.endpoints && entity.api.endpoints.delete) {
|
|
33
|
-
baseScopes.push(`${entityName}:delete`)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Collect entity paths for routing
|
|
37
|
-
if (entity.api.apiPath) {
|
|
38
|
-
entityPaths.push(entity.api.apiPath)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Define role-based scopes
|
|
44
|
-
const roleScopes = {
|
|
45
|
-
'superadmin': ['admin:users', 'tasks:delete', 'products:write', 'products:delete', ...baseScopes.filter(s => s.includes(':delete'))],
|
|
46
|
-
'admin': ['admin:users', 'tasks:delete', 'products:write', 'products:delete', ...baseScopes.filter(s => s.includes(':delete'))],
|
|
47
|
-
'manager': baseScopes.filter(s => s.includes(':write') || s.includes(':read')),
|
|
48
|
-
'member': baseScopes.filter(s => s.includes(':read') || s === 'tasks:write')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Define flag-based scopes
|
|
52
|
-
const flagScopes = {
|
|
53
|
-
'beta_tester': ['beta:features'],
|
|
54
|
-
'vip': ['vip:features', 'advanced:export'],
|
|
55
|
-
'early_adopter': ['early:features'],
|
|
56
|
-
'power_user': ['advanced:features', 'bulk:operations']
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Define allowed filters based on entities
|
|
60
|
-
const allowedFilters = ['status', 'role', 'completed', 'userId']
|
|
61
|
-
|
|
62
|
-
// Add entity-specific filters
|
|
63
|
-
for (const entity of entities) {
|
|
64
|
-
if (entity.api && entity.features?.enabled) {
|
|
65
|
-
allowedFilters.push(`${entity.name}Id`)
|
|
66
|
-
|
|
67
|
-
// Add common entity filters
|
|
68
|
-
if (entity.fields) {
|
|
69
|
-
const fields = Object.keys(entity.fields)
|
|
70
|
-
if (fields.includes('status')) allowedFilters.push(`${entity.name}Status`)
|
|
71
|
-
if (fields.includes('type')) allowedFilters.push(`${entity.name}Type`)
|
|
72
|
-
if (fields.includes('category')) allowedFilters.push(`${entity.name}Category`)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const scopeConfig = {
|
|
78
|
-
base: [...new Set(baseScopes)],
|
|
79
|
-
roles: roleScopes,
|
|
80
|
-
flags: flagScopes,
|
|
81
|
-
restrictions: {
|
|
82
|
-
'restricted': {
|
|
83
|
-
remove: ['delete', 'admin']
|
|
84
|
-
},
|
|
85
|
-
'limited_access': {
|
|
86
|
-
allow_only: ['read', 'tasks:write']
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const apiConfig = {
|
|
92
|
-
filters: {
|
|
93
|
-
allowed: [...new Set(allowedFilters)]
|
|
94
|
-
},
|
|
95
|
-
entityPaths: [...new Set(entityPaths)]
|
|
96
|
-
}
|
|
97
|
-
|
|
67
|
+
const outputFilePath = config.outputDir ? join(config.outputDir, 'scope-registry.ts') : 'scope-registry.ts'
|
|
98
68
|
return `/**
|
|
99
69
|
* Auto-generated API Scope Registry
|
|
100
70
|
*
|
|
101
71
|
* Generated at: ${new Date().toISOString()}
|
|
102
|
-
*
|
|
103
|
-
* Role
|
|
104
|
-
*
|
|
105
|
-
*
|
|
72
|
+
*
|
|
73
|
+
* Role→scope mapping is computed below at import time from
|
|
74
|
+
* \`entity-registry.ts\` (which entities are API-exposed) and
|
|
75
|
+
* \`permissions-registry.ts\` (which team roles can do what to each entity),
|
|
76
|
+
* so scope MINTING (this file) and scope ENFORCEMENT
|
|
77
|
+
* (\`checkPermission\`/\`hasRequiredScope\`) can never drift apart.
|
|
106
78
|
*
|
|
107
79
|
* This file replaces hardcoded scope configurations in helpers.ts
|
|
108
80
|
*
|
|
109
81
|
* DO NOT EDIT - This file is auto-generated by scripts/build-registry.mjs
|
|
110
82
|
*/
|
|
111
83
|
|
|
84
|
+
import { ENTITY_REGISTRY } from './entity-registry'
|
|
85
|
+
import { AVAILABLE_ROLES, PERMISSIONS_BY_ROLE } from './permissions-registry'
|
|
86
|
+
import type { Permission } from '${convertCorePath('@/core/lib/permissions/types', outputFilePath, config)}'
|
|
87
|
+
import type { EntityConfig } from '${convertCorePath('@/core/lib/entities/types', outputFilePath, config)}'
|
|
88
|
+
|
|
112
89
|
export interface ScopeConfig {
|
|
113
90
|
base: string[]
|
|
114
91
|
roles: Record<string, string[]>
|
|
@@ -123,9 +100,84 @@ export interface ApiConfig {
|
|
|
123
100
|
entityPaths: string[]
|
|
124
101
|
}
|
|
125
102
|
|
|
126
|
-
|
|
103
|
+
interface ScopeApiEntity {
|
|
104
|
+
slug: string
|
|
105
|
+
apiPath: string
|
|
106
|
+
fieldNames: string[]
|
|
107
|
+
}
|
|
127
108
|
|
|
128
|
-
|
|
109
|
+
// Entities exposed via the external API (access.api === true). ENTITY_REGISTRY
|
|
110
|
+
// entries can be a full EntityConfig or a ChildEntityDefinition (which has no
|
|
111
|
+
// \`access\`/\`slug\` at all) — guard defensively rather than assume every
|
|
112
|
+
// registry entry is a root entity.
|
|
113
|
+
const SCOPE_API_ENTITIES: ScopeApiEntity[] = Object.values(ENTITY_REGISTRY)
|
|
114
|
+
.map((entry) => {
|
|
115
|
+
const cfg = entry.config as Partial<EntityConfig>
|
|
116
|
+
if (!cfg?.access?.api || !cfg.slug) return null
|
|
117
|
+
return {
|
|
118
|
+
slug: cfg.slug,
|
|
119
|
+
apiPath: cfg.access.basePath || \`/api/v1/\${cfg.slug}\`,
|
|
120
|
+
fieldNames: Array.isArray(cfg.fields) ? cfg.fields.map((field) => field.name) : []
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
.filter((entity): entity is ScopeApiEntity => entity !== null)
|
|
124
|
+
|
|
125
|
+
// For each API-exposed entity, grant \`<slug>:read/write/delete\` to any team
|
|
126
|
+
// role whose PERMISSIONS_BY_ROLE set includes the corresponding
|
|
127
|
+
// \`<slug>.<action>\` permission — the SAME set checkAuthPermission() consults
|
|
128
|
+
// at request time. Superadmin is a GLOBAL role (never in AVAILABLE_ROLES) and
|
|
129
|
+
// is intentionally NOT represented here; it's handled as an explicit bypass
|
|
130
|
+
// in validateScopesForUser().
|
|
131
|
+
function computeRoleScopes(): Record<string, string[]> {
|
|
132
|
+
const roleScopes: Record<string, string[]> = {}
|
|
133
|
+
for (const role of AVAILABLE_ROLES) {
|
|
134
|
+
const perms = PERMISSIONS_BY_ROLE[role]
|
|
135
|
+
const scopes: string[] = []
|
|
136
|
+
for (const { slug } of SCOPE_API_ENTITIES) {
|
|
137
|
+
if (perms?.has(\`\${slug}.list\` as Permission) || perms?.has(\`\${slug}.read\` as Permission)) {
|
|
138
|
+
scopes.push(\`\${slug}:read\`)
|
|
139
|
+
}
|
|
140
|
+
if (perms?.has(\`\${slug}.create\` as Permission) || perms?.has(\`\${slug}.update\` as Permission)) {
|
|
141
|
+
scopes.push(\`\${slug}:write\`)
|
|
142
|
+
}
|
|
143
|
+
if (perms?.has(\`\${slug}.delete\` as Permission)) {
|
|
144
|
+
scopes.push(\`\${slug}:delete\`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
roleScopes[role] = [...new Set(scopes)]
|
|
148
|
+
}
|
|
149
|
+
return roleScopes
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function computeAllowedFilters(): string[] {
|
|
153
|
+
const allowedFilters = new Set<string>(['status', 'role', 'completed', 'userId'])
|
|
154
|
+
for (const { slug, fieldNames } of SCOPE_API_ENTITIES) {
|
|
155
|
+
allowedFilters.add(\`\${slug}Id\`)
|
|
156
|
+
if (fieldNames.includes('status')) allowedFilters.add(\`\${slug}Status\`)
|
|
157
|
+
if (fieldNames.includes('type')) allowedFilters.add(\`\${slug}Type\`)
|
|
158
|
+
if (fieldNames.includes('category')) allowedFilters.add(\`\${slug}Category\`)
|
|
159
|
+
}
|
|
160
|
+
return [...allowedFilters]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const SCOPE_CONFIG: ScopeConfig = {
|
|
164
|
+
// Nothing is granted irrespective of role — every entity scope flows
|
|
165
|
+
// through computeRoleScopes() above, sourced from the real permission
|
|
166
|
+
// matrix. Kept as an empty array (rather than removed) so a future,
|
|
167
|
+
// deliberate "these scopes belong to every authenticated key" need has a
|
|
168
|
+
// place to live without re-deriving this decision.
|
|
169
|
+
base: [],
|
|
170
|
+
roles: computeRoleScopes(),
|
|
171
|
+
flags: ${JSON.stringify(FLAG_SCOPES, null, 2).split('\n').join('\n ')},
|
|
172
|
+
restrictions: ${JSON.stringify(SCOPE_RESTRICTIONS, null, 2).split('\n').join('\n ')}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const API_CONFIG: ApiConfig = {
|
|
176
|
+
filters: {
|
|
177
|
+
allowed: computeAllowedFilters()
|
|
178
|
+
},
|
|
179
|
+
entityPaths: SCOPE_API_ENTITIES.map((entity) => entity.apiPath)
|
|
180
|
+
}
|
|
129
181
|
|
|
130
182
|
// ==================== Service Layer ====================
|
|
131
183
|
// Query functions have been moved to: @nextsparkjs/core/lib/services/scope.service
|
|
@@ -55,6 +55,7 @@ import { discoverEmails } from './registry/discovery/emails.mjs'
|
|
|
55
55
|
import { discoverBlocks } from './registry/discovery/blocks.mjs'
|
|
56
56
|
import { discoverCoreRoutes } from './registry/discovery/core-routes.mjs'
|
|
57
57
|
import { discoverApiPresets } from './registry/discovery/api-presets.mjs'
|
|
58
|
+
import { discoverMcpOverrides } from './registry/discovery/mcp-overrides.mjs'
|
|
58
59
|
import { validateEntityConfigurations } from './registry/validation/entity-validator.mjs'
|
|
59
60
|
import { generatePluginRegistry, generatePluginRegistryClient } from './registry/generators/plugin-registry.mjs'
|
|
60
61
|
import { generateEntityRegistry, generateEntityRegistryClient } from './registry/generators/entity-registry.mjs'
|
|
@@ -76,6 +77,7 @@ import { generatePermissionsRegistry } from './registry/generators/permissions-r
|
|
|
76
77
|
import { generateScheduledActionsRegistry } from './registry/generators/scheduled-actions-registry.mjs'
|
|
77
78
|
import { generateDocsRegistry } from './registry/generators/docs-registry.mjs'
|
|
78
79
|
import { generateApiPresetsRegistry, generateApiDocsRegistry } from './registry/generators/api-presets-registry.mjs'
|
|
80
|
+
import { generateMcpRegistry } from './registry/generators/mcp-registry.mjs'
|
|
79
81
|
import {
|
|
80
82
|
generateMissingPages,
|
|
81
83
|
displayTreeStructure,
|
|
@@ -89,7 +91,7 @@ import { syncAppGlobalsCss } from './theme.mjs'
|
|
|
89
91
|
|
|
90
92
|
// ==================== Registry File Generation ====================
|
|
91
93
|
|
|
92
|
-
async function generateRegistryFiles(CONFIG, plugins, entities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails) {
|
|
94
|
+
async function generateRegistryFiles(CONFIG, plugins, entities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails, mcpOverridesData) {
|
|
93
95
|
log('Generating registry files...', 'build')
|
|
94
96
|
|
|
95
97
|
try {
|
|
@@ -122,6 +124,7 @@ async function generateRegistryFiles(CONFIG, plugins, entities, themes, template
|
|
|
122
124
|
{ name: 'docs-registry.ts', content: generateDocsRegistry() },
|
|
123
125
|
{ name: 'api-presets-registry.ts', content: generateApiPresetsRegistry(apiPresetsData, CONFIG) },
|
|
124
126
|
{ name: 'api-docs-registry.ts', content: generateApiDocsRegistry(apiPresetsData, CONFIG) },
|
|
127
|
+
{ name: 'mcp-registry.ts', content: generateMcpRegistry(mcpOverridesData, CONFIG) },
|
|
125
128
|
{ name: 'index.ts', content: generateUnifiedRegistry(plugins, entities, themes, templates, middlewares, CONFIG) }
|
|
126
129
|
]
|
|
127
130
|
|
|
@@ -191,7 +194,7 @@ export async function buildRegistries(projectRoot = null) {
|
|
|
191
194
|
await discoverParentChildRelations(CONFIG)
|
|
192
195
|
|
|
193
196
|
// Discover all content types in parallel (pass CONFIG to each)
|
|
194
|
-
const [plugins, coreEntities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails] = await Promise.all([
|
|
197
|
+
const [plugins, coreEntities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails, mcpOverridesData] = await Promise.all([
|
|
195
198
|
discoverPlugins(CONFIG),
|
|
196
199
|
discoverCoreEntities(CONFIG),
|
|
197
200
|
discoverThemes(CONFIG),
|
|
@@ -201,7 +204,8 @@ export async function buildRegistries(projectRoot = null) {
|
|
|
201
204
|
discoverPermissionsConfig(CONFIG),
|
|
202
205
|
discoverCoreRoutes(CONFIG),
|
|
203
206
|
discoverApiPresets(CONFIG),
|
|
204
|
-
discoverEmails(CONFIG)
|
|
207
|
+
discoverEmails(CONFIG),
|
|
208
|
+
discoverMcpOverrides(CONFIG)
|
|
205
209
|
])
|
|
206
210
|
|
|
207
211
|
// Aggregate all entities with proper priority: plugin < core < theme
|
|
@@ -260,7 +264,7 @@ export async function buildRegistries(projectRoot = null) {
|
|
|
260
264
|
|
|
261
265
|
// Hoist plugin dependencies to root workspace for proper resolution
|
|
262
266
|
// Generate all registry files (use aggregated entities for entity registry + blocks)
|
|
263
|
-
await generateRegistryFiles(CONFIG, plugins, allEntities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails)
|
|
267
|
+
await generateRegistryFiles(CONFIG, plugins, allEntities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails, mcpOverridesData)
|
|
264
268
|
|
|
265
269
|
// Generate missing pages for templates that don't have core app pages
|
|
266
270
|
await generateMissingPages(templates, CONFIG)
|
|
@@ -4,6 +4,7 @@ import { redirect } from 'next/navigation'
|
|
|
4
4
|
import { SignupForm } from '@nextsparkjs/core/components/auth/forms/SignupForm'
|
|
5
5
|
import { getTemplateOrDefault, getMetadataOrDefault } from '@nextsparkjs/core/lib/template-resolver'
|
|
6
6
|
import { AUTH_CONFIG } from '@nextsparkjs/core/lib/config'
|
|
7
|
+
import { resolveAuthMethods } from '@nextsparkjs/core/lib/auth/auth-methods'
|
|
7
8
|
import { TeamService } from '@nextsparkjs/core/lib/services'
|
|
8
9
|
|
|
9
10
|
// This page reads DB state (TeamService.hasGlobal) and redirect()s during render,
|
|
@@ -23,6 +24,13 @@ export const metadata: Metadata = getMetadataOrDefault(
|
|
|
23
24
|
async function SignupPageContent() {
|
|
24
25
|
const registrationMode = AUTH_CONFIG?.registration?.mode ?? 'open'
|
|
25
26
|
|
|
27
|
+
// Passwordless preset (no 'email-password' in auth.methods): the account is
|
|
28
|
+
// created by the first one-time-code sign-in, so there is no password signup
|
|
29
|
+
// form to show — send people to /login instead.
|
|
30
|
+
if (!resolveAuthMethods(AUTH_CONFIG).includes('email-password')) {
|
|
31
|
+
redirect('/login')
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
// In invitation-only mode, allow the first user to register
|
|
27
35
|
// (when no global team exists yet). Subsequent users need invitations.
|
|
28
36
|
if (registrationMode === 'invitation-only') {
|