@nextsparkjs/core 0.1.0-beta.189 → 0.1.0-beta.190
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/api/keys/CreateApiKeyDialog.d.ts.map +1 -1
- package/dist/components/api/keys/CreateApiKeyDialog.js +2 -1
- package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicFooter.js +2 -23
- package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
- package/dist/components/app/misc/ThemeToggle.js +2 -1
- package/dist/components/auth/forms/LoginForm.js +1 -1
- package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
- package/dist/components/auth/forms/SignupForm.js +2 -1
- package/dist/components/auth/pages/AuthErrorPage.d.ts.map +1 -1
- package/dist/components/auth/pages/AuthErrorPage.js +2 -1
- package/dist/components/billing/ManageBillingButton.d.ts.map +1 -1
- package/dist/components/billing/ManageBillingButton.js +2 -1
- package/dist/components/dashboard/block-editor/array-field.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/array-field.js +2 -1
- package/dist/components/dashboard/block-editor/block-picker.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/block-picker.js +2 -1
- package/dist/components/dashboard/block-editor/builder-editor-view.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/builder-editor-view.js +3 -2
- package/dist/components/dashboard/block-editor/config-panel.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/config-panel.js +3 -2
- package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/dynamic-form.js +2 -1
- package/dist/components/dashboard/block-editor/entity-fields-sidebar.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/entity-fields-sidebar.js +3 -2
- package/dist/components/dashboard/block-editor/pattern-reference-preview.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/pattern-reference-preview.js +2 -1
- package/dist/components/dashboard/layouts/TopNavbar.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/TopNavbar.js +4 -3
- package/dist/components/dashboard/misc/SearchDropdown.d.ts.map +1 -1
- package/dist/components/dashboard/misc/SearchDropdown.js +2 -1
- package/dist/components/dashboard/mobile/MobileTopBar.d.ts.map +1 -1
- package/dist/components/dashboard/mobile/MobileTopBar.js +2 -1
- package/dist/components/devtools/BlockDetailViewer.d.ts.map +1 -1
- package/dist/components/devtools/BlockDetailViewer.js +2 -1
- package/dist/components/devtools/BlocksViewer.d.ts.map +1 -1
- package/dist/components/devtools/BlocksViewer.js +2 -1
- package/dist/components/devtools/ConfigViewer.d.ts.map +1 -1
- package/dist/components/devtools/ConfigViewer.js +3 -2
- package/dist/components/devtools/TestCasesViewer.d.ts.map +1 -1
- package/dist/components/devtools/TestCasesViewer.js +3 -2
- package/dist/components/devtools/TestCoverageDashboard.d.ts.map +1 -1
- package/dist/components/devtools/TestCoverageDashboard.js +2 -1
- package/dist/components/devtools/api-explorer/ApiDocsModal.d.ts.map +1 -1
- package/dist/components/devtools/api-explorer/ApiDocsModal.js +0 -0
- package/dist/components/devtools/api-explorer/ApiExplorer.d.ts.map +1 -1
- package/dist/components/devtools/api-explorer/ApiExplorer.js +2 -1
- package/dist/components/devtools/scheduled-actions/actions-table.d.ts.map +1 -1
- package/dist/components/devtools/scheduled-actions/actions-table.js +3 -2
- package/dist/components/docs/docs-sidebar.d.ts.map +1 -1
- package/dist/components/docs/docs-sidebar.js +3 -2
- package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
- package/dist/components/entities/EntityFieldRenderer.js +2 -1
- package/dist/components/entities/EntityList.d.ts.map +1 -1
- package/dist/components/entities/EntityList.js +2 -1
- package/dist/components/entities/EntityTable.d.ts.map +1 -1
- package/dist/components/entities/EntityTable.js +3 -2
- package/dist/components/entities/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/entities/ErrorBoundary.js +2 -1
- package/dist/components/entities/wrappers/EntityDetailWrapper.d.ts.map +1 -1
- package/dist/components/entities/wrappers/EntityDetailWrapper.js +3 -2
- package/dist/components/media/MediaCard.d.ts.map +1 -1
- package/dist/components/media/MediaCard.js +2 -1
- package/dist/components/media/MediaDetailPanel.d.ts.map +1 -1
- package/dist/components/media/MediaDetailPanel.js +2 -1
- package/dist/components/media/MediaList.d.ts.map +1 -1
- package/dist/components/media/MediaList.js +2 -1
- package/dist/components/media/MediaSelector.d.ts.map +1 -1
- package/dist/components/media/MediaSelector.js +2 -1
- package/dist/components/media/MediaUploadZone.d.ts.map +1 -1
- package/dist/components/media/MediaUploadZone.js +2 -1
- package/dist/components/public/entities/PublicEntityDetail.d.ts.map +1 -1
- package/dist/components/public/entities/PublicEntityDetail.js +2 -1
- package/dist/components/teams/TeamPendingInvitations.d.ts.map +1 -1
- package/dist/components/teams/TeamPendingInvitations.js +3 -2
- package/dist/components/ui/accordion.js +1 -1
- package/dist/components/ui/audio-upload.d.ts.map +1 -1
- package/dist/components/ui/audio-upload.js +2 -1
- package/dist/components/ui/avatar.d.ts +17 -1
- package/dist/components/ui/avatar.d.ts.map +1 -1
- package/dist/components/ui/avatar.js +29 -2
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/breadcrumb.d.ts +13 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -1
- package/dist/components/ui/breadcrumb.js +23 -10
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/checkbox.js +1 -1
- package/dist/components/ui/file-upload.d.ts.map +1 -1
- package/dist/components/ui/file-upload.js +2 -1
- package/dist/components/ui/image-upload.d.ts.map +1 -1
- package/dist/components/ui/image-upload.js +4 -3
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/label.js +1 -1
- package/dist/components/ui/optimized-image.d.ts.map +1 -1
- package/dist/components/ui/optimized-image.js +5 -0
- package/dist/components/ui/pagination.d.ts +1 -1
- package/dist/components/ui/pagination.d.ts.map +1 -1
- package/dist/components/ui/pagination.js +3 -0
- package/dist/components/ui/progress.js +1 -1
- package/dist/components/ui/relation-display.d.ts.map +1 -1
- package/dist/components/ui/relation-display.js +3 -2
- package/dist/components/ui/separator.js +1 -1
- package/dist/components/ui/simple-relation-select.d.ts.map +1 -1
- package/dist/components/ui/simple-relation-select.js +7 -6
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/slider.js +1 -1
- package/dist/components/ui/switch.js +1 -1
- package/dist/components/ui/tabs.js +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/user-display.d.ts.map +1 -1
- package/dist/components/ui/user-display.js +2 -1
- package/dist/components/ui/user-select.d.ts.map +1 -1
- package/dist/components/ui/user-select.js +2 -1
- package/dist/components/ui/video-upload.d.ts.map +1 -1
- package/dist/components/ui/video-upload.js +3 -2
- package/dist/components/users/tables/UsersTable.d.ts.map +1 -1
- package/dist/components/users/tables/UsersTable.js +3 -2
- package/dist/contexts/SubscriptionContext.d.ts.map +1 -1
- package/dist/contexts/SubscriptionContext.js +4 -3
- package/dist/contexts/TeamContext.d.ts.map +1 -1
- package/dist/contexts/TeamContext.js +3 -2
- package/dist/hooks/useAccountPreferencesSync.d.ts.map +1 -1
- package/dist/hooks/useAccountPreferencesSync.js +2 -1
- package/dist/hooks/useApiExplorerNavigation.d.ts.map +1 -1
- package/dist/hooks/useApiExplorerNavigation.js +2 -1
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +3 -2
- package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
- package/dist/hooks/useEnsureUserMetadata.js +3 -2
- package/dist/hooks/useEntityWithMeta.d.ts.map +1 -1
- package/dist/hooks/useEntityWithMeta.js +3 -2
- package/dist/hooks/useInvoice.d.ts.map +1 -1
- package/dist/hooks/useInvoice.js +2 -1
- package/dist/hooks/useInvoices.d.ts.map +1 -1
- package/dist/hooks/useInvoices.js +2 -1
- package/dist/hooks/useMedia.d.ts.map +1 -1
- package/dist/hooks/useMedia.js +10 -9
- package/dist/hooks/useMediaUpload.d.ts.map +1 -1
- package/dist/hooks/useMediaUpload.js +2 -1
- package/dist/hooks/useMembership.d.ts.map +1 -1
- package/dist/hooks/useMembership.js +3 -2
- package/dist/hooks/usePatternUsages.d.ts.map +1 -1
- package/dist/hooks/usePatternUsages.js +3 -2
- package/dist/hooks/usePrefetch.d.ts.map +1 -1
- package/dist/hooks/usePrefetch.js +3 -2
- package/dist/hooks/useQuota.d.ts.map +1 -1
- package/dist/hooks/useQuota.js +2 -1
- package/dist/hooks/useTeamInvitations.d.ts.map +1 -1
- package/dist/hooks/useTeamInvitations.js +4 -3
- package/dist/hooks/useTeamMembers.d.ts.map +1 -1
- package/dist/hooks/useTeamMembers.js +5 -4
- package/dist/hooks/useTeamUpdate.d.ts.map +1 -1
- package/dist/hooks/useTeamUpdate.js +2 -1
- package/dist/hooks/useUserProfile.d.ts.map +1 -1
- package/dist/hooks/useUserProfile.js +3 -2
- package/dist/hooks/useUserSettings.d.ts.map +1 -1
- package/dist/hooks/useUserSettings.js +3 -2
- package/dist/lib/actions/entity.actions.d.ts.map +1 -1
- package/dist/lib/actions/entity.actions.js +2 -1
- package/dist/lib/api/entities.d.ts.map +1 -1
- package/dist/lib/api/entities.js +4 -3
- package/dist/lib/auth/verify-email-link.d.ts +10 -0
- package/dist/lib/auth/verify-email-link.d.ts.map +1 -0
- package/dist/lib/auth/verify-email-link.js +14 -0
- package/dist/lib/auth-client.d.ts +2 -0
- package/dist/lib/auth-client.d.ts.map +1 -1
- package/dist/lib/auth-client.js +4 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +10 -2
- package/dist/lib/base-path.d.ts +85 -0
- package/dist/lib/base-path.d.ts.map +1 -0
- package/dist/lib/base-path.js +79 -0
- package/dist/lib/billing/schema.d.ts +1 -1
- package/dist/lib/billing/schema.d.ts.map +1 -1
- package/dist/lib/billing/schema.js +1 -1
- package/dist/lib/blocks/sanitize-html.d.ts.map +1 -1
- package/dist/lib/blocks/sanitize-html.js +18 -2
- package/dist/lib/cache/revalidate-tag.d.ts +14 -0
- package/dist/lib/cache/revalidate-tag.d.ts.map +1 -0
- package/dist/lib/cache/revalidate-tag.js +8 -0
- package/dist/lib/config/dashboard-schema.d.ts +1 -1
- package/dist/lib/config/dashboard-schema.d.ts.map +1 -1
- package/dist/lib/config/dashboard-schema.js +1 -1
- package/dist/lib/config/types.d.ts +36 -16
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/docs/access.d.ts +28 -0
- package/dist/lib/docs/access.d.ts.map +1 -0
- package/dist/lib/docs/access.js +16 -0
- package/dist/lib/docs/parser.d.ts.map +1 -1
- package/dist/lib/docs/parser.js +2 -1
- package/dist/lib/docs/remark-doc-links.d.ts +14 -0
- package/dist/lib/docs/remark-doc-links.d.ts.map +1 -0
- package/dist/lib/docs/remark-doc-links.js +38 -0
- package/dist/lib/entities/api-generator.d.ts +1 -1
- package/dist/lib/entities/api-generator.d.ts.map +1 -1
- package/dist/lib/entities/api-generator.js +1 -1
- package/dist/lib/entities/schema-generator.d.ts +1 -1
- package/dist/lib/entities/schema-generator.d.ts.map +1 -1
- package/dist/lib/entities/schema-generator.js +1 -1
- package/dist/lib/mcp/engine.js +1 -1
- package/dist/lib/mcp/schema-builder.d.ts +1 -1
- package/dist/lib/mcp/schema-builder.d.ts.map +1 -1
- package/dist/lib/mcp/schema-builder.js +1 -1
- package/dist/lib/mcp/tool-generator.js +1 -1
- package/dist/lib/mcp/types.d.ts +1 -1
- package/dist/lib/mcp/types.d.ts.map +1 -1
- package/dist/lib/media/schemas.d.ts +1 -1
- package/dist/lib/media/schemas.d.ts.map +1 -1
- package/dist/lib/media/schemas.js +1 -1
- package/dist/lib/middleware/index.d.ts +5 -1
- package/dist/lib/middleware/index.d.ts.map +1 -1
- package/dist/lib/middleware/index.js +3 -2
- package/dist/lib/services/meta.service.d.ts +60 -0
- package/dist/lib/services/meta.service.d.ts.map +1 -1
- package/dist/lib/services/meta.service.js +132 -12
- package/dist/lib/services/middleware.service.d.ts.map +1 -1
- package/dist/lib/services/middleware.service.js +3 -2
- package/dist/lib/teams/schema.d.ts +1 -1
- package/dist/lib/teams/schema.d.ts.map +1 -1
- package/dist/lib/teams/schema.js +1 -1
- package/dist/lib/teams/teams.api.d.ts.map +1 -1
- package/dist/lib/teams/teams.api.js +4 -3
- package/dist/lib/user-data-client.d.ts.map +1 -1
- package/dist/lib/user-data-client.js +3 -2
- package/dist/lib/validation/invoices.d.ts +1 -1
- package/dist/lib/validation/invoices.d.ts.map +1 -1
- package/dist/lib/validation/invoices.js +1 -1
- package/dist/lib/validation-schemas.d.ts +1 -1
- package/dist/lib/validation-schemas.d.ts.map +1 -1
- package/dist/lib/validation-schemas.js +1 -1
- package/dist/lib/validation.d.ts +1 -1
- package/dist/lib/validation.d.ts.map +1 -1
- package/dist/lib/validation.js +1 -1
- package/dist/messages/de/footer.json +0 -1
- package/dist/messages/de/index.d.ts +0 -1
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/en/footer.json +0 -1
- package/dist/messages/en/index.d.ts +0 -1
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/es/footer.json +0 -1
- package/dist/messages/es/index.d.ts +0 -1
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/fr/footer.json +0 -1
- package/dist/messages/fr/index.d.ts +0 -1
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/it/footer.json +0 -1
- package/dist/messages/it/index.d.ts +0 -1
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/pt/footer.json +0 -1
- package/dist/messages/pt/index.d.ts +0 -1
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
- package/dist/templates/app/(auth)/verify-email/page.tsx +4 -3
- package/dist/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
- package/dist/templates/app/api/auth/[...all]/route.ts +15 -14
- package/dist/templates/app/api/internal/user-metadata/route.ts +6 -8
- package/dist/templates/app/api/user/plan-flags/route.ts +1 -1
- package/dist/templates/app/api/user/profile/route.ts +12 -17
- package/dist/templates/app/api/v1/api-keys/route.ts +1 -1
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
- package/dist/templates/app/api/v1/billing/cancel/route.ts +1 -1
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +1 -1
- package/dist/templates/app/api/v1/billing/check-action/route.ts +1 -1
- package/dist/templates/app/api/v1/billing/checkout/route.ts +1 -1
- package/dist/templates/app/api/v1/blocks/validate/route.ts +1 -1
- package/dist/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
- package/dist/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
- package/dist/templates/app/api/v1/post-categories/route.ts +1 -1
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
- package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
- package/dist/templates/app/api/v1/teams/docs.md +4 -9
- package/dist/templates/app/api/v1/teams/switch/route.ts +1 -1
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
- package/dist/templates/app/api/v1/users/[id]/route.ts +1 -1
- package/dist/templates/app/api/v1/users/route.ts +1 -1
- package/dist/templates/app/dashboard/(main)/media/page.tsx +3 -1
- package/dist/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
- package/dist/templates/app/dashboard/settings/profile/page.tsx +3 -2
- package/dist/templates/app/devtools/scheduled-actions/page.tsx +2 -1
- package/dist/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
- package/dist/templates/app/superadmin/subscriptions/page.tsx +3 -2
- package/dist/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
- package/dist/templates/app/superadmin/users/[userId]/page.tsx +2 -1
- package/dist/templates/app/superadmin/users/page.tsx +2 -1
- package/dist/templates/blocks/cta-section/component.tsx +3 -2
- package/dist/templates/blocks/cta-section/schema.ts +1 -1
- package/dist/templates/blocks/features-grid/component.tsx +2 -1
- package/dist/templates/blocks/features-grid/schema.ts +1 -1
- package/dist/templates/blocks/hero/component.tsx +3 -2
- package/dist/templates/blocks/hero/schema.ts +1 -1
- package/dist/templates/blocks/testimonials/component.tsx +2 -1
- package/dist/templates/blocks/testimonials/schema.ts +1 -1
- package/dist/templates/blocks/text-content/component.tsx +2 -1
- package/dist/templates/blocks/text-content/schema.ts +1 -1
- package/dist/templates/ci-workflows/README.md +1 -1
- package/dist/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/dist/templates/ci-workflows/github/cypress-smoke.yml +1 -1
- package/dist/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
- package/dist/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
- package/dist/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
- package/dist/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
- package/dist/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
- package/dist/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
- package/dist/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
- package/dist/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
- package/dist/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
- package/dist/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
- package/dist/templates/eslint.config.mjs +133 -9
- package/dist/templates/features/blog/blocks/post-content/component.tsx +2 -1
- package/dist/templates/features/blog/blocks/post-content/schema.ts +1 -1
- package/dist/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
- package/dist/templates/features/pages/blocks/hero/component.tsx +3 -2
- package/dist/templates/features/pages/blocks/hero/schema.ts +1 -1
- package/dist/templates/next.config.mjs +50 -29
- package/dist/templates/proxy.ts +100 -21
- package/dist/templates/public/sw.js +2 -2
- package/dist/templates/scripts/cy-run-prod.cjs +5 -5
- package/dist/types/blocks.d.ts +1 -1
- package/dist/types/blocks.d.ts.map +1 -1
- package/dist/types/blocks.js +1 -1
- package/package.json +9 -5
- package/scripts/build/docs-registry.mjs +7 -7
- package/scripts/build/registry/__tests__/billing-registry.test.mjs +43 -0
- package/scripts/build/registry/__tests__/build-output.test.mjs +209 -0
- package/scripts/build/registry/__tests__/copy-race-child.mjs +14 -0
- package/scripts/build/registry/__tests__/direct-fs-writes.mjs +203 -0
- package/scripts/build/registry/__tests__/globals-css-tag.test.mjs +66 -0
- package/scripts/build/registry/__tests__/icons-discovery.test.mjs +530 -29
- package/scripts/build/registry/__tests__/page-generator.test.mjs +211 -37
- package/scripts/build/registry/__tests__/published-signatures.test.mjs +239 -0
- package/scripts/build/registry/__tests__/route-file-rule.test.mjs +170 -0
- package/scripts/build/registry/__tests__/safe-fs.test.mjs +333 -0
- package/scripts/build/registry/__tests__/template-analysis.test.mjs +142 -0
- package/scripts/build/registry/__tests__/template-registry.test.mjs +279 -9
- package/scripts/build/registry/__tests__/templates-plan.test.mjs +128 -0
- package/scripts/build/registry/__tests__/templates-tree.test.mjs +367 -0
- package/scripts/build/registry/__tests__/write-places.test.mjs +425 -0
- package/scripts/build/registry/config.mjs +6 -65
- package/scripts/build/registry/discovery/icons.mjs +498 -43
- package/scripts/build/registry/discovery/templates.mjs +1 -1
- package/scripts/build/registry/generators/billing-registry.mjs +1 -1
- package/scripts/build/registry/generators/docs-registry.mjs +3 -4
- package/scripts/build/registry/generators/feature-registry.mjs +1 -1
- package/scripts/build/registry/generators/permissions-registry.mjs +2 -2
- package/scripts/build/registry/generators/template-registry.mjs +65 -49
- package/scripts/build/registry/index.mjs +1 -2
- package/scripts/build/registry/post-build/index.mjs +1 -1
- package/scripts/build/registry/post-build/own-gitignores.mjs +220 -0
- package/scripts/build/registry/post-build/page-generator.mjs +512 -268
- package/scripts/build/registry/post-build/route-cleanup.mjs +16 -13
- package/scripts/build/registry/post-build/test-fixtures.mjs +26 -8
- package/scripts/build/registry/project-mode.mjs +76 -0
- package/scripts/build/registry/shared/typescript-compiler.mjs +77 -0
- package/scripts/build/registry/validate-env.test.mjs +12 -8
- package/scripts/build/registry/watch.mjs +3 -53
- package/scripts/build/registry/write-places.mjs +228 -0
- package/scripts/build/registry.mjs +53 -30
- package/scripts/build/safe-fs.mjs +279 -0
- package/scripts/build/templates-plan.mjs +44 -0
- package/scripts/build/theme.mjs +46 -42
- package/scripts/build/update-tsconfig.mjs +16 -15
- package/scripts/db/cluster-changes.mjs +205 -0
- package/scripts/db/connection-time-limits.mjs +53 -0
- package/scripts/db/inspect-server.mjs +108 -0
- package/scripts/db/migration-time-limit.mjs +695 -0
- package/scripts/db/run-migrations.mjs +46 -41
- package/scripts/db/ssl-config.mjs +194 -0
- package/scripts/db/theme-location.mjs +69 -0
- package/scripts/db/verify-tables.mjs +3 -8
- package/scripts/db/verify-teams.mjs +2 -6
- package/scripts/db/verify-theme-migrations.mjs +169 -0
- package/scripts/maintenance/core-updater.mjs +1003 -0
- package/scripts/maintenance/step-guard.mjs +74 -0
- package/scripts/maintenance/update-core.mjs +14 -831
- package/scripts/utils/console-guard.mjs +13 -0
- package/scripts/utils/generated-tag.mjs +66 -0
- package/scripts/utils/index.mjs +9 -1
- package/scripts/utils/logging.mjs +140 -0
- package/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
- package/templates/app/(auth)/verify-email/page.tsx +4 -3
- package/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
- package/templates/app/api/auth/[...all]/route.ts +15 -14
- package/templates/app/api/internal/user-metadata/route.ts +6 -8
- package/templates/app/api/user/plan-flags/route.ts +1 -1
- package/templates/app/api/user/profile/route.ts +12 -17
- package/templates/app/api/v1/api-keys/route.ts +1 -1
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
- package/templates/app/api/v1/billing/cancel/route.ts +1 -1
- package/templates/app/api/v1/billing/change-plan/route.ts +1 -1
- package/templates/app/api/v1/billing/check-action/route.ts +1 -1
- package/templates/app/api/v1/billing/checkout/route.ts +1 -1
- package/templates/app/api/v1/blocks/validate/route.ts +1 -1
- package/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
- package/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
- package/templates/app/api/v1/post-categories/route.ts +1 -1
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
- package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
- package/templates/app/api/v1/teams/docs.md +4 -9
- package/templates/app/api/v1/teams/switch/route.ts +1 -1
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
- package/templates/app/api/v1/users/[id]/route.ts +1 -1
- package/templates/app/api/v1/users/route.ts +1 -1
- package/templates/app/dashboard/(main)/media/page.tsx +3 -1
- package/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
- package/templates/app/dashboard/settings/profile/page.tsx +3 -2
- package/templates/app/devtools/scheduled-actions/page.tsx +2 -1
- package/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
- package/templates/app/superadmin/subscriptions/page.tsx +3 -2
- package/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
- package/templates/app/superadmin/users/[userId]/page.tsx +2 -1
- package/templates/app/superadmin/users/page.tsx +2 -1
- package/templates/blocks/cta-section/component.tsx +3 -2
- package/templates/blocks/cta-section/schema.ts +1 -1
- package/templates/blocks/features-grid/component.tsx +2 -1
- package/templates/blocks/features-grid/schema.ts +1 -1
- package/templates/blocks/hero/component.tsx +3 -2
- package/templates/blocks/hero/schema.ts +1 -1
- package/templates/blocks/testimonials/component.tsx +2 -1
- package/templates/blocks/testimonials/schema.ts +1 -1
- package/templates/blocks/text-content/component.tsx +2 -1
- package/templates/blocks/text-content/schema.ts +1 -1
- package/templates/ci-workflows/README.md +1 -1
- package/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/templates/ci-workflows/github/cypress-smoke.yml +1 -1
- package/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
- package/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
- package/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
- package/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
- package/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
- package/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
- package/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
- package/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
- package/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
- package/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
- package/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
- package/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
- package/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
- package/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
- package/templates/eslint.config.mjs +133 -9
- package/templates/features/blog/blocks/post-content/component.tsx +2 -1
- package/templates/features/blog/blocks/post-content/schema.ts +1 -1
- package/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
- package/templates/features/pages/blocks/hero/component.tsx +3 -2
- package/templates/features/pages/blocks/hero/schema.ts +1 -1
- package/templates/next.config.mjs +50 -29
- package/templates/proxy.ts +100 -21
- package/templates/public/sw.js +2 -2
- package/templates/scripts/cy-run-prod.cjs +5 -5
- package/tests/jest/__mocks__/@nextsparkjs/registries/template-registry.client.ts +11 -0
- package/tests/jest/__mocks__/next-server.js +10 -0
- package/scripts/build/docs.mjs +0 -392
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import pg from "pg";
|
|
2
1
|
import fs from "fs";
|
|
3
2
|
import path from "path";
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
const { Client } = pg;
|
|
4
|
+
import { migrationTimeLimit, migrationClient, ignoredParametersNotice, runMigrationSql, recordMigration, migrationFailure } from './migration-time-limit.mjs';
|
|
7
5
|
|
|
8
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
7
|
const __dirname = path.dirname(__filename);
|
|
@@ -17,8 +15,11 @@ const packageRoot = path.join(__dirname, '..', '..'); // scripts/db/ -> core/
|
|
|
17
15
|
const isMonorepoMode = fs.existsSync(path.join(projectRoot, 'packages', 'core'));
|
|
18
16
|
const rootDir = projectRoot; // For backward compatibility with code below
|
|
19
17
|
|
|
20
|
-
// Read environment variables: prefer .env file if it exists, fallback to process.env (e.g. Vercel/CI)
|
|
18
|
+
// Read environment variables: prefer .env file if it exists, fallback to process.env (e.g. Vercel/CI).
|
|
19
|
+
// `--no-env-file` skips the .env file, so the connection and theme come only from the
|
|
20
|
+
// environment this process was started with (db:verify-theme runs it that way).
|
|
21
21
|
const envPath = path.join(projectRoot, '.env');
|
|
22
|
+
const readEnvFile = !process.argv.includes('--no-env-file');
|
|
22
23
|
|
|
23
24
|
let DATABASE_URL = process.env.DATABASE_URL ?? null;
|
|
24
25
|
// Migrations and seeds run as the table OWNER. After the runtime cutover the app
|
|
@@ -27,8 +28,9 @@ let DATABASE_URL = process.env.DATABASE_URL ?? null;
|
|
|
27
28
|
// Falls back to DATABASE_URL when unset (pre-cutover: same owner connection).
|
|
28
29
|
let MIGRATE_DATABASE_URL = process.env.MIGRATE_DATABASE_URL ?? null;
|
|
29
30
|
let ACTIVE_THEME = process.env.NEXT_PUBLIC_ACTIVE_THEME ?? null;
|
|
31
|
+
let MIGRATION_TIMEOUT_SECONDS = process.env.MIGRATION_TIMEOUT_SECONDS;
|
|
30
32
|
|
|
31
|
-
if (fs.existsSync(envPath)) {
|
|
33
|
+
if (readEnvFile && fs.existsSync(envPath)) {
|
|
32
34
|
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
33
35
|
const envLines = envContent.split('\n');
|
|
34
36
|
|
|
@@ -46,6 +48,9 @@ if (fs.existsSync(envPath)) {
|
|
|
46
48
|
if (key?.trim() === 'NEXT_PUBLIC_ACTIVE_THEME' && valueParts.length > 0) {
|
|
47
49
|
ACTIVE_THEME = value;
|
|
48
50
|
}
|
|
51
|
+
if (key?.trim() === 'MIGRATION_TIMEOUT_SECONDS' && valueParts.length > 0) {
|
|
52
|
+
MIGRATION_TIMEOUT_SECONDS = value;
|
|
53
|
+
}
|
|
49
54
|
}
|
|
50
55
|
});
|
|
51
56
|
}
|
|
@@ -63,15 +68,21 @@ if (!ACTIVE_THEME) {
|
|
|
63
68
|
process.exit(1);
|
|
64
69
|
}
|
|
65
70
|
|
|
71
|
+
// How long each migration may run, when MIGRATION_TIMEOUT_SECONDS asks for a limit
|
|
72
|
+
// (see migration-time-limit.mjs). Without it, a migration runs for as long as it takes.
|
|
73
|
+
let TIME_LIMIT;
|
|
74
|
+
try {
|
|
75
|
+
TIME_LIMIT = migrationTimeLimit({ MIGRATION_TIMEOUT_SECONDS });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(`❌ ${error.message}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const ignoredParameters = ignoredParametersNotice(MIGRATION_URL, TIME_LIMIT);
|
|
82
|
+
if (ignoredParameters) console.log(`⚠️ ${ignoredParameters}\n`);
|
|
83
|
+
|
|
66
84
|
async function runMigrations() {
|
|
67
|
-
const client =
|
|
68
|
-
connectionString: MIGRATION_URL,
|
|
69
|
-
ssl: {
|
|
70
|
-
rejectUnauthorized: false,
|
|
71
|
-
require: true
|
|
72
|
-
},
|
|
73
|
-
connectionTimeoutMillis: 10000
|
|
74
|
-
});
|
|
85
|
+
const client = migrationClient(MIGRATION_URL, TIME_LIMIT);
|
|
75
86
|
|
|
76
87
|
try {
|
|
77
88
|
await client.connect();
|
|
@@ -113,17 +124,14 @@ async function runMigrations() {
|
|
|
113
124
|
const sql = fs.readFileSync(path.join(migrationsDir, file), 'utf8');
|
|
114
125
|
|
|
115
126
|
try {
|
|
116
|
-
await client
|
|
127
|
+
await runMigrationSql(client, { sql, limit: TIME_LIMIT, connectionString: MIGRATION_URL });
|
|
117
128
|
|
|
118
129
|
// Record successful migration
|
|
119
|
-
await client
|
|
120
|
-
'INSERT INTO "_migrations" (filename) VALUES ($1)',
|
|
121
|
-
[file]
|
|
122
|
-
);
|
|
130
|
+
await recordMigration(client, { file, table: '_migrations', row: { filename: file }, limit: TIME_LIMIT });
|
|
123
131
|
|
|
124
132
|
console.log(`✅ Successfully executed ${file}\n`);
|
|
125
133
|
} catch (error) {
|
|
126
|
-
console.error(`❌
|
|
134
|
+
console.error(`❌ ${migrationFailure(file, error)}`);
|
|
127
135
|
throw error;
|
|
128
136
|
}
|
|
129
137
|
}
|
|
@@ -328,11 +336,13 @@ async function executeContentMigration(client, migration) {
|
|
|
328
336
|
console.log(` 🔄 ${filename}...`);
|
|
329
337
|
const sql = fs.readFileSync(fullPath, 'utf8');
|
|
330
338
|
|
|
331
|
-
await client
|
|
332
|
-
await client
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
339
|
+
await runMigrationSql(client, { sql, limit: TIME_LIMIT, connectionString: MIGRATION_URL });
|
|
340
|
+
await recordMigration(client, {
|
|
341
|
+
file: filename,
|
|
342
|
+
table: '_content_migrations',
|
|
343
|
+
row: { source_type: sourceType, source_name: sourceName, filename },
|
|
344
|
+
limit: TIME_LIMIT,
|
|
345
|
+
});
|
|
336
346
|
|
|
337
347
|
console.log(` ✅ ${filename} executed successfully`);
|
|
338
348
|
return true;
|
|
@@ -357,11 +367,13 @@ async function executeEntityMigration(client, migration) {
|
|
|
357
367
|
console.log(`${indent}🔄 ${filename}...`);
|
|
358
368
|
const sql = fs.readFileSync(fullPath, 'utf8');
|
|
359
369
|
|
|
360
|
-
await client
|
|
361
|
-
await client
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
370
|
+
await runMigrationSql(client, { sql, limit: TIME_LIMIT, connectionString: MIGRATION_URL });
|
|
371
|
+
await recordMigration(client, {
|
|
372
|
+
file: filename,
|
|
373
|
+
table: '_entity_migrations',
|
|
374
|
+
row: { entity_name: entityName, source_type: sourceType, source_name: sourceName, filename },
|
|
375
|
+
limit: TIME_LIMIT,
|
|
376
|
+
});
|
|
365
377
|
|
|
366
378
|
console.log(`${indent}✅ ${filename} executed successfully`);
|
|
367
379
|
return true;
|
|
@@ -369,14 +381,7 @@ async function executeEntityMigration(client, migration) {
|
|
|
369
381
|
|
|
370
382
|
// Entity migrations runner - WordPress-like architecture with sample_data deferred execution
|
|
371
383
|
async function runEntityMigrations() {
|
|
372
|
-
const client =
|
|
373
|
-
connectionString: MIGRATION_URL,
|
|
374
|
-
ssl: {
|
|
375
|
-
rejectUnauthorized: false,
|
|
376
|
-
require: true
|
|
377
|
-
},
|
|
378
|
-
connectionTimeoutMillis: 10000
|
|
379
|
-
});
|
|
384
|
+
const client = migrationClient(MIGRATION_URL, TIME_LIMIT);
|
|
380
385
|
|
|
381
386
|
try {
|
|
382
387
|
await client.connect();
|
|
@@ -545,7 +550,7 @@ async function runEntityMigrations() {
|
|
|
545
550
|
const executed = await executeContentMigration(client, migration);
|
|
546
551
|
if (executed) totalContentMigrations++;
|
|
547
552
|
} catch (error) {
|
|
548
|
-
console.error(` ❌
|
|
553
|
+
console.error(` ❌ ${migrationFailure(migration.filename, error)}`);
|
|
549
554
|
throw error;
|
|
550
555
|
}
|
|
551
556
|
}
|
|
@@ -566,7 +571,7 @@ async function runEntityMigrations() {
|
|
|
566
571
|
const executed = await executeEntityMigration(client, migration);
|
|
567
572
|
if (executed) totalEntityMigrations++;
|
|
568
573
|
} catch (error) {
|
|
569
|
-
console.error(` ❌
|
|
574
|
+
console.error(` ❌ ${migrationFailure(migration.filename, error)}`);
|
|
570
575
|
throw error;
|
|
571
576
|
}
|
|
572
577
|
}
|
|
@@ -586,7 +591,7 @@ async function runEntityMigrations() {
|
|
|
586
591
|
const executed = await executeContentMigration(client, migration);
|
|
587
592
|
if (executed) totalContentMigrations++;
|
|
588
593
|
} catch (error) {
|
|
589
|
-
console.error(` ❌
|
|
594
|
+
console.error(` ❌ ${migrationFailure(migration.filename, error)}`);
|
|
590
595
|
throw error;
|
|
591
596
|
}
|
|
592
597
|
}
|
|
@@ -607,7 +612,7 @@ async function runEntityMigrations() {
|
|
|
607
612
|
const executed = await executeEntityMigration(client, migration);
|
|
608
613
|
if (executed) totalEntityMigrations++;
|
|
609
614
|
} catch (error) {
|
|
610
|
-
console.error(` ❌
|
|
615
|
+
console.error(` ❌ ${migrationFailure(migration.filename, error)}`);
|
|
611
616
|
throw error;
|
|
612
617
|
}
|
|
613
618
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import pg from 'pg'
|
|
2
|
+
import { EventEmitter } from 'node:events'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SSL handling for the database maintenance scripts.
|
|
6
|
+
*
|
|
7
|
+
* This deliberately mirrors `src/lib/db.ts` without importing application
|
|
8
|
+
* runtime code: scripts run as standalone Node ESM programs while the runtime
|
|
9
|
+
* remains bundled separately until its next release.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Strip SSL-related parameters so pg cannot override the explicit `ssl` option.
|
|
14
|
+
* pg v8+ otherwise resolves `sslmode=require` as certificate validation.
|
|
15
|
+
*/
|
|
16
|
+
export function stripSSLParams(databaseUrl) {
|
|
17
|
+
if (!databaseUrl) return databaseUrl
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(databaseUrl)
|
|
20
|
+
url.searchParams.delete('sslmode')
|
|
21
|
+
url.searchParams.delete('uselibpqcompat')
|
|
22
|
+
return url.toString()
|
|
23
|
+
} catch {
|
|
24
|
+
return databaseUrl
|
|
25
|
+
.replace(/[?&]sslmode=[^&]*/g, '')
|
|
26
|
+
.replace(/[?&]uselibpqcompat=[^&]*/g, '')
|
|
27
|
+
.replace(/\?&/, '?')
|
|
28
|
+
.replace(/\?$/, '')
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse the SSL policy used by database scripts.
|
|
34
|
+
*
|
|
35
|
+
* Explicit sslmode takes precedence. With no recognized mode, only production
|
|
36
|
+
* uses validated TLS; all other environments use no TLS.
|
|
37
|
+
*/
|
|
38
|
+
export function parseSSLConfig(databaseUrl) {
|
|
39
|
+
const isProduction = process.env.NODE_ENV === 'production'
|
|
40
|
+
|
|
41
|
+
if (!databaseUrl) return isProduction ? { rejectUnauthorized: true } : false
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const url = new URL(databaseUrl)
|
|
45
|
+
const sslmode = url.searchParams.get('sslmode')?.trim().toLowerCase()
|
|
46
|
+
|
|
47
|
+
if (sslmode) {
|
|
48
|
+
switch (sslmode) {
|
|
49
|
+
case 'disable':
|
|
50
|
+
if (isProduction) console.warn('[DB] WARNING: SSL disabled in production environment. This is insecure!')
|
|
51
|
+
return false
|
|
52
|
+
case 'require':
|
|
53
|
+
case 'prefer':
|
|
54
|
+
case 'allow':
|
|
55
|
+
return { rejectUnauthorized: false }
|
|
56
|
+
case 'verify-ca':
|
|
57
|
+
case 'verify-full':
|
|
58
|
+
return { rejectUnauthorized: true }
|
|
59
|
+
default:
|
|
60
|
+
console.warn(`[DB] Unknown sslmode value: "${sslmode}". Using environment defaults.`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return isProduction ? { rejectUnauthorized: true } : false
|
|
65
|
+
} catch {
|
|
66
|
+
if (databaseUrl.includes('sslmode=disable')) return false
|
|
67
|
+
return isProduction ? { rejectUnauthorized: true } : false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True only for a syntactically valid URL which does not state an sslmode. */
|
|
72
|
+
export function prefersSSL(databaseUrl) {
|
|
73
|
+
if (!databaseUrl) return false
|
|
74
|
+
try {
|
|
75
|
+
return !new URL(databaseUrl).searchParams.has('sslmode')
|
|
76
|
+
} catch {
|
|
77
|
+
// Keep malformed URLs on the runtime's conservative, documented policy.
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The initial configuration for a script connection.
|
|
84
|
+
*
|
|
85
|
+
* A URL without sslmode has libpq's `prefer` semantics: first ask for TLS
|
|
86
|
+
* without validating the certificate, and fall back only when Postgres says it
|
|
87
|
+
* has no TLS support. `parseSSLConfig` intentionally remains the runtime's
|
|
88
|
+
* policy; the distinct initial value here is what makes the scripts differ on
|
|
89
|
+
* that one, documented case.
|
|
90
|
+
*/
|
|
91
|
+
export function scriptConnectionOptions(databaseUrl, options = {}) {
|
|
92
|
+
return {
|
|
93
|
+
...options,
|
|
94
|
+
connectionString: stripSSLParams(databaseUrl),
|
|
95
|
+
ssl: prefersSSL(databaseUrl) ? { rejectUnauthorized: false } : parseSSLConfig(databaseUrl),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const NO_SSL_SUPPORT = 'The server does not support SSL connections'
|
|
100
|
+
|
|
101
|
+
function isNoSSLSupport(error) {
|
|
102
|
+
return error?.message === NO_SSL_SUPPORT
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A small Client facade for the only case where scripts deliberately differ
|
|
107
|
+
* from the runtime: a URL with no sslmode. It preserves pg's Client surface
|
|
108
|
+
* while swapping the first TLS-only connection for one without TLS only after
|
|
109
|
+
* pg's precise "server does not support SSL" error. Every other failure is
|
|
110
|
+
* returned unchanged.
|
|
111
|
+
*/
|
|
112
|
+
class SSLPreferClient extends EventEmitter {
|
|
113
|
+
constructor(options) {
|
|
114
|
+
super()
|
|
115
|
+
this.options = options
|
|
116
|
+
this.client = new pg.Client(options)
|
|
117
|
+
this.eventsForwarded = false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get connection() { return this.client.connection }
|
|
121
|
+
get connectionParameters() { return this.client.connectionParameters }
|
|
122
|
+
get database() { return this.client.database }
|
|
123
|
+
set database(value) { this.client.database = value }
|
|
124
|
+
get processID() { return this.client.processID }
|
|
125
|
+
get _queryable() { return this.client._queryable }
|
|
126
|
+
get _ending() { return this.client._ending }
|
|
127
|
+
|
|
128
|
+
forwardEvents() {
|
|
129
|
+
if (this.eventsForwarded) return
|
|
130
|
+
this.eventsForwarded = true
|
|
131
|
+
// Pool relies on these two; the scripts use the underlying connection for
|
|
132
|
+
// protocol events. Forwarding also keeps the normal pg error/end contract.
|
|
133
|
+
for (const event of ['error', 'end', 'notice', 'notification']) {
|
|
134
|
+
this.client.on(event, (...args) => this.emit(event, ...args))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async connectPromise() {
|
|
139
|
+
const startedAt = performance.now()
|
|
140
|
+
try {
|
|
141
|
+
await this.client.connect()
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (!isNoSSLSupport(error)) throw error
|
|
144
|
+
|
|
145
|
+
// A retry shares, rather than resets, the connection time budget. This
|
|
146
|
+
// matters for timeLimitedClient(), whose connectMs is a hard bound.
|
|
147
|
+
const timeout = Number(this.options.connectionTimeoutMillis) || 0
|
|
148
|
+
const elapsed = performance.now() - startedAt
|
|
149
|
+
if (timeout > 0 && elapsed >= timeout) throw new Error('timeout expired')
|
|
150
|
+
|
|
151
|
+
// The facade exposes pg's live connection parameters. Callers such as
|
|
152
|
+
// timeLimitedClient() deliberately update those after construction so a
|
|
153
|
+
// connection string cannot disable their limits. Build the plaintext
|
|
154
|
+
// retry from that live, resolved state (rather than re-parsing the URL),
|
|
155
|
+
// or it would silently discard every such update.
|
|
156
|
+
const { connectionString: _connectionString, ...options } = this.options
|
|
157
|
+
this.client = new pg.Client({
|
|
158
|
+
...options,
|
|
159
|
+
...this.client.connectionParameters,
|
|
160
|
+
password: this.client.password,
|
|
161
|
+
ssl: false,
|
|
162
|
+
...(timeout > 0 ? { connectionTimeoutMillis: Math.max(1, Math.ceil(timeout - elapsed)) } : {}),
|
|
163
|
+
})
|
|
164
|
+
await this.client.connect()
|
|
165
|
+
}
|
|
166
|
+
this.forwardEvents()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
connect(callback) {
|
|
170
|
+
const promise = this.connectPromise()
|
|
171
|
+
if (!callback) return promise
|
|
172
|
+
promise.then(() => callback(null, this), callback)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
query(...args) { return this.client.query(...args) }
|
|
176
|
+
end(...args) { return this.client.end(...args) }
|
|
177
|
+
ref() { return this.client.ref() }
|
|
178
|
+
unref() { return this.client.unref() }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** A Client with explicit modes unchanged, or libpq-style `prefer` otherwise. */
|
|
182
|
+
export function scriptClient(databaseUrl, options = {}) {
|
|
183
|
+
const config = scriptConnectionOptions(databaseUrl, options)
|
|
184
|
+
return prefersSSL(databaseUrl) ? new SSLPreferClient(config) : new pg.Client(config)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** A Pool whose clients have the same `prefer` behavior as direct clients. */
|
|
188
|
+
export function scriptPool(databaseUrl, options = {}) {
|
|
189
|
+
const config = scriptConnectionOptions(databaseUrl, options)
|
|
190
|
+
return new pg.Pool({
|
|
191
|
+
...config,
|
|
192
|
+
...(prefersSSL(databaseUrl) ? { Client: SSLPreferClient } : {}),
|
|
193
|
+
})
|
|
194
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Where db:verify-theme finds a theme, and the directory run-migrations.mjs has
|
|
2
|
+
// to run in to migrate it.
|
|
3
|
+
//
|
|
4
|
+
// run-migrations.mjs reads the active theme from `<cwd>/contents/themes/<theme>`.
|
|
5
|
+
// The monorepo's themes reach it through apps/dev/contents, which links to
|
|
6
|
+
// themes/. The starter a generated project ships is not among them: it lives in
|
|
7
|
+
// packages/core/templates/contents/themes/starter, and themes/starter holds only
|
|
8
|
+
// its tests. packages/core/templates has the same contents/ layout a generated
|
|
9
|
+
// project has, so the runner reads the starter from there.
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
|
|
14
|
+
/** The directories the runner can run in, each with its own contents/themes. */
|
|
15
|
+
export const THEME_PROJECTS = ['apps/dev', 'packages/core/templates'];
|
|
16
|
+
|
|
17
|
+
// The runner reads a theme's plugins from one of these; a directory with
|
|
18
|
+
// neither is not a theme, and migrating it migrates core alone.
|
|
19
|
+
const THEME_CONFIGS = ['config/theme.config.ts', 'theme.config.ts'];
|
|
20
|
+
|
|
21
|
+
const SKIP_DIRS = new Set(['node_modules', '.next', 'dist']);
|
|
22
|
+
|
|
23
|
+
/** Every .sql file inside a `migrations` directory under `dir`, at any depth. */
|
|
24
|
+
export function migrationFiles(dir, insideMigrations = false, found = []) {
|
|
25
|
+
if (!fs.existsSync(dir)) return found;
|
|
26
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
27
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
28
|
+
const entryPath = path.join(dir, entry.name);
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
migrationFiles(entryPath, insideMigrations || entry.name === 'migrations', found);
|
|
31
|
+
} else if (insideMigrations && entry.name.endsWith('.sql')) {
|
|
32
|
+
found.push(entryPath);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return found;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The theme called `theme`: its directory, the directory to run the runner in,
|
|
40
|
+
* and the migration files it holds. `{ error }` when there is no such theme,
|
|
41
|
+
* when the name is a theme in more than one place, or when it has no
|
|
42
|
+
* migrations, since a run over none of them proves nothing about it.
|
|
43
|
+
*/
|
|
44
|
+
export function findTheme(repoRoot, theme) {
|
|
45
|
+
const candidates = THEME_PROJECTS.map(project => ({
|
|
46
|
+
projectDir: path.join(repoRoot, project),
|
|
47
|
+
themeDir: path.join(repoRoot, project, 'contents', 'themes', theme),
|
|
48
|
+
}));
|
|
49
|
+
// A theme reached through a link is shown where it really lives: themes/blog, not apps/dev/contents/themes/blog.
|
|
50
|
+
const shown = dir =>
|
|
51
|
+
fs.existsSync(dir) ? path.relative(fs.realpathSync(repoRoot), fs.realpathSync(dir)) : path.relative(repoRoot, dir);
|
|
52
|
+
const found = candidates.filter(({ themeDir }) => THEME_CONFIGS.some(config => fs.existsSync(path.join(themeDir, config))));
|
|
53
|
+
|
|
54
|
+
if (found.length === 0) {
|
|
55
|
+
return {
|
|
56
|
+
error: `no theme "${theme}": none of ${candidates.map(({ themeDir }) => shown(themeDir)).join(', ')} has a theme config`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (found.length > 1) {
|
|
60
|
+
return { error: `"${theme}" is a theme in more than one place: ${found.map(({ themeDir }) => shown(themeDir)).join(', ')}` };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const [{ projectDir, themeDir }] = found;
|
|
64
|
+
const migrations = migrationFiles(themeDir);
|
|
65
|
+
if (migrations.length === 0) {
|
|
66
|
+
return { error: `theme "${theme}" at ${shown(themeDir)} has no migrations to verify` };
|
|
67
|
+
}
|
|
68
|
+
return { projectDir, themeDir, shownDir: shown(themeDir), migrations };
|
|
69
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import pg from "pg";
|
|
2
1
|
import fs from "fs";
|
|
3
2
|
import path from "path";
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
const { Client } = pg;
|
|
4
|
+
import { scriptClient } from './ssl-config.mjs';
|
|
7
5
|
|
|
8
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
7
|
const __dirname = path.dirname(__filename);
|
|
@@ -24,10 +22,7 @@ envLines.forEach(line => {
|
|
|
24
22
|
});
|
|
25
23
|
|
|
26
24
|
async function verifyTables() {
|
|
27
|
-
const client =
|
|
28
|
-
connectionString: DATABASE_URL,
|
|
29
|
-
ssl: { rejectUnauthorized: false }
|
|
30
|
-
});
|
|
25
|
+
const client = scriptClient(DATABASE_URL);
|
|
31
26
|
|
|
32
27
|
try {
|
|
33
28
|
await client.connect();
|
|
@@ -139,4 +134,4 @@ async function verifyTables() {
|
|
|
139
134
|
}
|
|
140
135
|
}
|
|
141
136
|
|
|
142
|
-
verifyTables();
|
|
137
|
+
verifyTables();
|
|
@@ -5,17 +5,13 @@
|
|
|
5
5
|
* Run with: node core/scripts/db/verify-teams.mjs
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import pg from 'pg'
|
|
9
8
|
import dotenv from 'dotenv'
|
|
9
|
+
import { scriptPool } from './ssl-config.mjs'
|
|
10
10
|
|
|
11
11
|
dotenv.config()
|
|
12
12
|
|
|
13
|
-
const { Pool } = pg
|
|
14
|
-
|
|
15
13
|
async function verifyTeamsSetup() {
|
|
16
|
-
const pool =
|
|
17
|
-
connectionString: process.env.DATABASE_URL,
|
|
18
|
-
})
|
|
14
|
+
const pool = scriptPool(process.env.DATABASE_URL)
|
|
19
15
|
|
|
20
16
|
try {
|
|
21
17
|
console.log('🔍 Verifying Teams Core Setup...\n')
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Runs a single theme's migrations end to end against an explicitly-named
|
|
2
|
+
// database, to catch problems like #193 (sample data referencing a row no
|
|
3
|
+
// migration creates) before they ship. Refuses to run against a database that
|
|
4
|
+
// already holds anything, since running a theme's migrations there would mix
|
|
5
|
+
// that theme's schema and sample data into whatever is already there.
|
|
6
|
+
//
|
|
7
|
+
// Usage: VERIFY_THEME_DATABASE_URL=<database-url> pnpm db:verify-theme <theme>
|
|
8
|
+
//
|
|
9
|
+
// The theme is looked up among the monorepo's themes and the starter core
|
|
10
|
+
// ships to generated projects (see theme-location.mjs). A name that is not a
|
|
11
|
+
// theme, or a theme with no migrations, is refused: a run over core's
|
|
12
|
+
// migrations alone would pass without saying anything about the theme.
|
|
13
|
+
//
|
|
14
|
+
// The run reaches past that database: the core migrations create and alter
|
|
15
|
+
// cluster-wide roles (see cluster-changes.mjs), which every database on the
|
|
16
|
+
// same Postgres server shares. The output names those roles before the
|
|
17
|
+
// migrations run, and the run is refused when the server shows signs of being
|
|
18
|
+
// in use — one of those roles already exists, another database lives there, or
|
|
19
|
+
// the server will not say, or not in time, what is on it.
|
|
20
|
+
// VERIFY_THEME_ALLOW_CLUSTER_CHANGES=1 says the server is yours to change and
|
|
21
|
+
// runs anyway. A disposable server, one per run, needs neither.
|
|
22
|
+
//
|
|
23
|
+
// The database URL comes from the environment, never from an argument:
|
|
24
|
+
// arguments show up in pnpm's command echo and in the process list, and the
|
|
25
|
+
// URL carries the password. The output names only the host and the database.
|
|
26
|
+
//
|
|
27
|
+
// The migrations run through run-migrations.mjs, the same script
|
|
28
|
+
// `pnpm db:migrate` uses, with `--no-env-file` and the theme and database in
|
|
29
|
+
// its environment. No file is read or written for that: no .env is touched,
|
|
30
|
+
// even if this process is killed mid-run.
|
|
31
|
+
//
|
|
32
|
+
// MIGRATE_DATABASE_URL is removed from the child's environment because
|
|
33
|
+
// run-migrations.mjs connects to it in preference to DATABASE_URL, which
|
|
34
|
+
// would migrate a database this script never checked.
|
|
35
|
+
//
|
|
36
|
+
// Every migration runs under a time limit, MIGRATION_TIMEOUT_SECONDS (30 s
|
|
37
|
+
// unless set; see migration-time-limit.mjs). A migration that runs past it
|
|
38
|
+
// fails the run with the migration's name, and its session on the server is
|
|
39
|
+
// ended, instead of leaving the command waiting.
|
|
40
|
+
|
|
41
|
+
import path from 'path';
|
|
42
|
+
import { fileURLToPath } from 'url';
|
|
43
|
+
import { spawnSync } from 'child_process';
|
|
44
|
+
import { GLOBAL_OBJECTS, inspectCluster } from './cluster-changes.mjs';
|
|
45
|
+
import { inspectTarget, inspectMaintenanceDatabase } from './inspect-server.mjs';
|
|
46
|
+
import { findTheme } from './theme-location.mjs';
|
|
47
|
+
import { TIME_LIMIT_VARIABLE, migrationTimeLimit } from './migration-time-limit.mjs';
|
|
48
|
+
|
|
49
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
50
|
+
const __dirname = path.dirname(__filename);
|
|
51
|
+
const repoRoot = path.join(__dirname, '..', '..', '..', '..'); // scripts/db/ -> core/ -> packages/ -> repo root
|
|
52
|
+
const runnerPath = path.join(repoRoot, 'packages', 'core', 'scripts', 'db', 'run-migrations.mjs');
|
|
53
|
+
|
|
54
|
+
const USAGE = 'Usage: VERIFY_THEME_DATABASE_URL=<database-url> pnpm db:verify-theme <theme>';
|
|
55
|
+
|
|
56
|
+
const ACKNOWLEDGEMENT_VAR = 'VERIFY_THEME_ALLOW_CLUSTER_CHANGES';
|
|
57
|
+
const acknowledgesClusterChanges = process.env[ACKNOWLEDGEMENT_VAR] === '1';
|
|
58
|
+
|
|
59
|
+
const args = process.argv.slice(2);
|
|
60
|
+
const theme = args[0];
|
|
61
|
+
const databaseUrl = process.env.VERIFY_THEME_DATABASE_URL;
|
|
62
|
+
|
|
63
|
+
// An extra argument, or a theme that looks like a URL, is most likely the
|
|
64
|
+
// database URL passed the old way: reject it without printing it.
|
|
65
|
+
if (!theme || args.length > 1 || theme.includes('://')) {
|
|
66
|
+
if (theme) console.error('❌ Pass the database URL in VERIFY_THEME_DATABASE_URL, not as an argument.');
|
|
67
|
+
console.error(USAGE);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!databaseUrl) {
|
|
72
|
+
console.error(`❌ VERIFY_THEME_DATABASE_URL is not set.\n${USAGE}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let target;
|
|
77
|
+
try {
|
|
78
|
+
const url = new URL(databaseUrl);
|
|
79
|
+
target = `${url.host}${url.pathname}`;
|
|
80
|
+
} catch {
|
|
81
|
+
console.error('❌ VERIFY_THEME_DATABASE_URL is not a valid URL.');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const located = findTheme(repoRoot, theme);
|
|
86
|
+
if (located.error) {
|
|
87
|
+
console.error(`❌ ${located.error}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Against an empty database no migration a theme ships takes a second; the
|
|
92
|
+
// default leaves a slow runner room many times over, and a migration that needs
|
|
93
|
+
// more can be given it.
|
|
94
|
+
const DEFAULT_TIME_LIMIT_SECONDS = '30';
|
|
95
|
+
const timeLimitSetting = process.env[TIME_LIMIT_VARIABLE] || DEFAULT_TIME_LIMIT_SECONDS;
|
|
96
|
+
let timeLimit;
|
|
97
|
+
try {
|
|
98
|
+
timeLimit = migrationTimeLimit({ [TIME_LIMIT_VARIABLE]: timeLimitSetting });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(`❌ ${error.message}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function main() {
|
|
105
|
+
console.log(`🔎 Verifying theme "${theme}" (${located.shownDir}, ${located.migrations.length} migration files) against ${target}...\n`);
|
|
106
|
+
|
|
107
|
+
let inspection;
|
|
108
|
+
try {
|
|
109
|
+
inspection = await inspectTarget(databaseUrl);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(`❌ Could not inspect ${target}: ${error.message}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (inspection.existingObjects.length > 0) {
|
|
116
|
+
const examples = inspection.existingObjects.map(({ kind, name }) => `${name} (${kind})`).join(', ');
|
|
117
|
+
console.error(
|
|
118
|
+
`❌ Refusing to run: ${target} already holds objects, for example ${examples}.\n` +
|
|
119
|
+
' This script only runs against a fresh, empty database, to avoid mixing\n' +
|
|
120
|
+
' this theme\'s schema and sample data into whatever is already there.'
|
|
121
|
+
);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log('This run also changes these cluster-wide roles, which every database on this server shares:');
|
|
126
|
+
for (const { role, change } of GLOBAL_OBJECTS) console.log(` ${role}: ${change}`);
|
|
127
|
+
console.log('');
|
|
128
|
+
|
|
129
|
+
const maintenance = await inspectMaintenanceDatabase(databaseUrl);
|
|
130
|
+
const cluster = inspectCluster({ ...inspection, maintenance });
|
|
131
|
+
if (!cluster.disposable && !acknowledgesClusterChanges) {
|
|
132
|
+
console.error(
|
|
133
|
+
`❌ Refusing to run: ${target} is on a Postgres server that is not this run's to change.\n` +
|
|
134
|
+
cluster.reasons.map(reason => ` - ${reason}\n`).join('') +
|
|
135
|
+
' The roles above are cluster-wide: altering them here reaches every database\n' +
|
|
136
|
+
' on this server. Point the run at a disposable Postgres server, or, if this\n' +
|
|
137
|
+
` server is yours to change, re-run it with ${ACKNOWLEDGEMENT_VAR}=1.`
|
|
138
|
+
);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
if (!cluster.disposable) {
|
|
142
|
+
console.log(`⚠️ ${ACKNOWLEDGEMENT_VAR}=1: changing those roles on a server in use (${cluster.reasons.join('; ')}).\n`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(`Each migration may run for ${timeLimit.seconds} s; ${TIME_LIMIT_VARIABLE} changes that.\n`);
|
|
146
|
+
|
|
147
|
+
const { MIGRATE_DATABASE_URL: _migrateUrl, VERIFY_THEME_DATABASE_URL: _verifyUrl, ...inheritedEnv } = process.env;
|
|
148
|
+
|
|
149
|
+
const result = spawnSync(process.execPath, [runnerPath, '--no-env-file'], {
|
|
150
|
+
cwd: located.projectDir,
|
|
151
|
+
stdio: 'inherit',
|
|
152
|
+
env: {
|
|
153
|
+
...inheritedEnv,
|
|
154
|
+
DATABASE_URL: databaseUrl,
|
|
155
|
+
NEXT_PUBLIC_ACTIVE_THEME: theme,
|
|
156
|
+
[TIME_LIMIT_VARIABLE]: timeLimitSetting,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (result.status !== 0) {
|
|
161
|
+
const reason = result.signal ? `signal ${result.signal}` : `exit code ${result.status}`;
|
|
162
|
+
console.error(`\n❌ Theme "${theme}" migrations failed (${reason}).`);
|
|
163
|
+
process.exit(result.status ?? 1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(`\n✅ Theme "${theme}" migrations ran cleanly against ${target}.`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
main();
|