@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
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the check of where the registry build writes, which the build runs
|
|
3
|
+
* before writing anything and `nextspark sync:app` and `nextspark dev` run from
|
|
4
|
+
* the installed core: a symlink, something of another kind or that can't be
|
|
5
|
+
* read, or a .gitignore of .nextspark/backups or .nextspark/registries that
|
|
6
|
+
* would not keep what is beside it out of git, is found, and a build that finds
|
|
7
|
+
* one writes nothing, in the project or through it. And the .gitignore the
|
|
8
|
+
* build keeps in .nextspark/registries leaves the registries out of git.
|
|
9
|
+
*
|
|
10
|
+
* Run: node --test packages/core/scripts/build/registry/__tests__/write-places.test.mjs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { test } from 'node:test'
|
|
14
|
+
import assert from 'node:assert/strict'
|
|
15
|
+
import { execFileSync, spawn, spawnSync } from 'node:child_process'
|
|
16
|
+
import { createHash } from 'node:crypto'
|
|
17
|
+
import { chmod, lstat, mkdir, mkdtemp, readdir, readFile, readlink, rm, symlink, writeFile } from 'node:fs/promises'
|
|
18
|
+
import { existsSync } from 'node:fs'
|
|
19
|
+
import { tmpdir } from 'node:os'
|
|
20
|
+
import { dirname, join } from 'node:path'
|
|
21
|
+
import { fileURLToPath } from 'node:url'
|
|
22
|
+
|
|
23
|
+
import { unsafeWritePlaces } from '../write-places.mjs'
|
|
24
|
+
import {
|
|
25
|
+
BACKUPS_GITIGNORE,
|
|
26
|
+
BACKUPS_GITIGNORE_CONTENT,
|
|
27
|
+
REGISTRIES_GITIGNORE,
|
|
28
|
+
REGISTRIES_GITIGNORE_CONTENT,
|
|
29
|
+
backupsGitignoreState,
|
|
30
|
+
ensureBackupsGitignore,
|
|
31
|
+
ensureRegistriesGitignore,
|
|
32
|
+
ownGitignoreState,
|
|
33
|
+
} from '../post-build/own-gitignores.mjs'
|
|
34
|
+
|
|
35
|
+
const CORE_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..')
|
|
36
|
+
const RUNS_AS_ROOT = process.getuid?.() === 0
|
|
37
|
+
|
|
38
|
+
async function directory() {
|
|
39
|
+
const root = await mkdtemp(join(tmpdir(), 'nextspark-write-places-test-'))
|
|
40
|
+
return { root, cleanup: () => rm(root, { recursive: true, force: true }) }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function writeIn(root, path, content = '') {
|
|
44
|
+
await mkdir(dirname(join(root, path)), { recursive: true })
|
|
45
|
+
await writeFile(join(root, path), content)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test('a symlink where the build or a caller writes, or something else in the way, is found without going through it', async () => {
|
|
49
|
+
const { root, cleanup } = await directory()
|
|
50
|
+
const outside = await directory()
|
|
51
|
+
try {
|
|
52
|
+
await writeIn(outside.root, 'dashboard/layout.tsx')
|
|
53
|
+
await mkdir(join(root, 'app'), { recursive: true })
|
|
54
|
+
await writeIn(root, '.nextspark/backups/2026-09-16T00-00-00-000Z-q1w2e3/i18n.ts')
|
|
55
|
+
|
|
56
|
+
assert.deepEqual(unsafeWritePlaces(root), [])
|
|
57
|
+
|
|
58
|
+
await symlink(outside.root, join(root, 'app/(templates)'))
|
|
59
|
+
assert.deepEqual(unsafeWritePlaces(root), [{ path: 'app/(templates)', problem: 'is a symlink' }])
|
|
60
|
+
|
|
61
|
+
await rm(join(root, 'app/(templates)'))
|
|
62
|
+
await mkdir(join(root, 'app/(templates)/(public)'), { recursive: true })
|
|
63
|
+
await symlink(join(outside.root, 'dashboard'), join(root, 'app/(templates)/(public)/dashboard'))
|
|
64
|
+
await symlink(join(outside.root, 'missing'), join(root, '.nextspark/sync-state.json'))
|
|
65
|
+
await symlink(join(outside.root, 'dashboard'), join(root, '.nextspark/registries'))
|
|
66
|
+
await symlink(join(outside.root, 'dashboard'), join(root, 'app/dashboard'))
|
|
67
|
+
await symlink(join(outside.root, 'globals.css'), join(root, 'app/globals.css'))
|
|
68
|
+
assert.deepEqual(unsafeWritePlaces(root, ['app/dashboard/page.tsx', 'app/layout.tsx', 'i18n.ts', '.nextspark/sync-state.json']), [
|
|
69
|
+
{ path: 'app/globals.css', problem: 'is a symlink' },
|
|
70
|
+
{ path: '.nextspark/registries', problem: 'is a symlink' },
|
|
71
|
+
{ path: 'app/dashboard', problem: 'is a symlink' },
|
|
72
|
+
{ path: '.nextspark/sync-state.json', problem: 'is a symlink' },
|
|
73
|
+
{ path: 'app/(templates)/(public)/dashboard', problem: 'is a symlink' },
|
|
74
|
+
])
|
|
75
|
+
assert.deepEqual(unsafeWritePlaces(root).map(({ path }) => path), [
|
|
76
|
+
'app/globals.css',
|
|
77
|
+
'.nextspark/registries',
|
|
78
|
+
'app/(templates)/(public)/dashboard',
|
|
79
|
+
], 'app/dashboard and the sync state count only for a caller that writes under them')
|
|
80
|
+
|
|
81
|
+
await rm(join(root, '.nextspark/registries'))
|
|
82
|
+
await mkdir(join(root, '.nextspark/registries'))
|
|
83
|
+
await symlink(join(outside.root, 'index.ts'), join(root, '.nextspark/registries/index.ts'))
|
|
84
|
+
assert.ok(unsafeWritePlaces(root).some(({ path, problem }) => path === '.nextspark/registries/index.ts' && problem === 'is a symlink'), 'a symlink in the registries is found')
|
|
85
|
+
|
|
86
|
+
await rm(join(root, '.nextspark/registries/index.ts'))
|
|
87
|
+
await mkdir(join(root, '.nextspark/registries/index.ts'))
|
|
88
|
+
await mkdir(join(root, 'app/(templates)/(public)/page.tsx'), { recursive: true })
|
|
89
|
+
await rm(join(root, 'app/dashboard'))
|
|
90
|
+
await writeFile(join(root, 'app/dashboard'), '')
|
|
91
|
+
await rm(join(root, 'app/globals.css'))
|
|
92
|
+
await mkdir(join(root, 'app/globals.css'))
|
|
93
|
+
await mkdir(join(root, 'i18n.ts'))
|
|
94
|
+
assert.deepEqual(unsafeWritePlaces(root, ['app/dashboard/page.tsx', 'app/layout.tsx', 'i18n.ts', '.nextspark/sync-state.json']), [
|
|
95
|
+
{ path: 'app/globals.css', problem: 'is not a file' },
|
|
96
|
+
{ path: 'app/dashboard', problem: 'is not a directory' },
|
|
97
|
+
{ path: 'i18n.ts', problem: 'is not a file' },
|
|
98
|
+
{ path: '.nextspark/sync-state.json', problem: 'is a symlink' },
|
|
99
|
+
{ path: 'app/(templates)/(public)/dashboard', problem: 'is a symlink' },
|
|
100
|
+
{ path: '.nextspark/registries/index.ts', problem: 'is not a file' },
|
|
101
|
+
], 'a directory in app/(templates) where a file goes is left to the registry build')
|
|
102
|
+
|
|
103
|
+
await rm(join(root, '.nextspark'), { recursive: true })
|
|
104
|
+
await writeFile(join(root, '.nextspark'), '')
|
|
105
|
+
await rm(join(root, 'app'), { recursive: true })
|
|
106
|
+
await symlink(outside.root, join(root, 'app'))
|
|
107
|
+
assert.deepEqual(unsafeWritePlaces(root), [
|
|
108
|
+
{ path: 'app', problem: 'is a symlink' },
|
|
109
|
+
{ path: '.nextspark', problem: 'is not a directory' },
|
|
110
|
+
])
|
|
111
|
+
} finally {
|
|
112
|
+
await outside.cleanup()
|
|
113
|
+
await cleanup()
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('a directory the build lists that cannot be read is found', { skip: RUNS_AS_ROOT }, async () => {
|
|
118
|
+
const { root, cleanup } = await directory()
|
|
119
|
+
try {
|
|
120
|
+
await mkdir(join(root, 'app/(templates)/dashboard'), { recursive: true })
|
|
121
|
+
await mkdir(join(root, '.nextspark/registries'), { recursive: true })
|
|
122
|
+
await mkdir(join(root, '.nextspark/backups'), { recursive: true })
|
|
123
|
+
await chmod(join(root, 'app/(templates)/dashboard'), 0)
|
|
124
|
+
await chmod(join(root, '.nextspark/registries'), 0)
|
|
125
|
+
await chmod(join(root, '.nextspark/backups'), 0)
|
|
126
|
+
assert.deepEqual(unsafeWritePlaces(root), [
|
|
127
|
+
{ path: '.nextspark/backups', problem: "can't be read" },
|
|
128
|
+
{ path: '.nextspark/registries', problem: "can't be read" },
|
|
129
|
+
{ path: 'app/(templates)/dashboard', problem: "can't be read" },
|
|
130
|
+
])
|
|
131
|
+
} finally {
|
|
132
|
+
await chmod(join(root, 'app/(templates)/dashboard'), 0o755).catch(() => {})
|
|
133
|
+
await chmod(join(root, '.nextspark/registries'), 0o755).catch(() => {})
|
|
134
|
+
await chmod(join(root, '.nextspark/backups'), 0o755).catch(() => {})
|
|
135
|
+
await cleanup()
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('a .nextspark/backups/.gitignore counts as in place only as a readable file with * as its one pattern, and anything else is found', async () => {
|
|
140
|
+
const contents = [
|
|
141
|
+
['*\n', 'in place'],
|
|
142
|
+
['\uFEFF*\r\n', 'in place'],
|
|
143
|
+
['# Our own words\r\n\n* \r\n', 'in place'],
|
|
144
|
+
['*\\ \n', 'other'],
|
|
145
|
+
['\\*\n', 'other'],
|
|
146
|
+
['/*\n', 'other'],
|
|
147
|
+
['*\n!*/\n', 'other'],
|
|
148
|
+
['*\n!manual-snapshots/\n', 'other'],
|
|
149
|
+
['# nothing\n', 'other'],
|
|
150
|
+
[' *\n', 'other'],
|
|
151
|
+
['*\t\n', 'other'],
|
|
152
|
+
]
|
|
153
|
+
for (const [content, state] of contents) {
|
|
154
|
+
const { root, cleanup } = await directory()
|
|
155
|
+
try {
|
|
156
|
+
await writeIn(root, BACKUPS_GITIGNORE, content)
|
|
157
|
+
assert.equal(backupsGitignoreState(root), state, JSON.stringify(content))
|
|
158
|
+
assert.equal(unsafeWritePlaces(root).length, state === 'in place' ? 0 : 1, JSON.stringify(content))
|
|
159
|
+
if (state === 'in place') {
|
|
160
|
+
assert.equal(await ensureBackupsGitignore(root), false, JSON.stringify(content))
|
|
161
|
+
} else {
|
|
162
|
+
await assert.rejects(ensureBackupsGitignore(root), /Nothing is backed up under \.nextspark\/backups/, JSON.stringify(content))
|
|
163
|
+
}
|
|
164
|
+
assert.equal(await readFile(join(root, BACKUPS_GITIGNORE), 'utf-8'), content, 'one already there is never written over')
|
|
165
|
+
} finally {
|
|
166
|
+
await cleanup()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { root, cleanup } = await directory()
|
|
171
|
+
const outside = await directory()
|
|
172
|
+
try {
|
|
173
|
+
assert.equal(backupsGitignoreState(root), 'missing')
|
|
174
|
+
assert.deepEqual(unsafeWritePlaces(root), [])
|
|
175
|
+
|
|
176
|
+
await mkdir(join(root, '.nextspark/backups'), { recursive: true })
|
|
177
|
+
await symlink(join(outside.root, 'rules'), join(root, BACKUPS_GITIGNORE))
|
|
178
|
+
assert.equal(backupsGitignoreState(root), 'symlink')
|
|
179
|
+
assert.deepEqual(unsafeWritePlaces(root).map(({ path }) => path), [BACKUPS_GITIGNORE])
|
|
180
|
+
await assert.rejects(ensureBackupsGitignore(root))
|
|
181
|
+
assert.equal(existsSync(join(outside.root, 'rules')), false, 'nothing is written through the symlink')
|
|
182
|
+
|
|
183
|
+
await rm(join(root, BACKUPS_GITIGNORE))
|
|
184
|
+
await mkdir(join(root, BACKUPS_GITIGNORE))
|
|
185
|
+
assert.equal(backupsGitignoreState(root), 'not a file')
|
|
186
|
+
assert.deepEqual(unsafeWritePlaces(root).map(({ path }) => path), [BACKUPS_GITIGNORE])
|
|
187
|
+
|
|
188
|
+
await rm(join(root, BACKUPS_GITIGNORE), { recursive: true })
|
|
189
|
+
assert.equal(await ensureBackupsGitignore(root), true)
|
|
190
|
+
assert.equal(await readFile(join(root, BACKUPS_GITIGNORE), 'utf-8'), BACKUPS_GITIGNORE_CONTENT)
|
|
191
|
+
|
|
192
|
+
assert.equal(ownGitignoreState(root, REGISTRIES_GITIGNORE), 'missing')
|
|
193
|
+
await writeIn(root, REGISTRIES_GITIGNORE, '*\n!index.ts\n')
|
|
194
|
+
assert.equal(ownGitignoreState(root, REGISTRIES_GITIGNORE), 'other')
|
|
195
|
+
assert.deepEqual(unsafeWritePlaces(root).map(({ path }) => path), [REGISTRIES_GITIGNORE], "the registries' .gitignore is read as the backups' is")
|
|
196
|
+
await assert.rejects(ensureRegistriesGitignore(root), /No registry is written under \.nextspark\/registries/)
|
|
197
|
+
await rm(join(root, REGISTRIES_GITIGNORE))
|
|
198
|
+
await symlink(join(outside.root, 'rules'), join(root, REGISTRIES_GITIGNORE))
|
|
199
|
+
assert.deepEqual(unsafeWritePlaces(root), [{ path: REGISTRIES_GITIGNORE, problem: 'is a symlink, which git does not read, so git would pick up what is beside it: make it a file with * as its only pattern, or remove it for nextspark to write it' }], 'found once, not again as a symlink in the registries')
|
|
200
|
+
await rm(join(root, REGISTRIES_GITIGNORE))
|
|
201
|
+
assert.equal(await ensureRegistriesGitignore(root), true)
|
|
202
|
+
assert.equal(await readFile(join(root, REGISTRIES_GITIGNORE), 'utf-8'), REGISTRIES_GITIGNORE_CONTENT)
|
|
203
|
+
} finally {
|
|
204
|
+
await outside.cleanup()
|
|
205
|
+
await cleanup()
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('a .nextspark/backups/.gitignore that cannot be read is found, not taken as missing', { skip: RUNS_AS_ROOT }, async () => {
|
|
210
|
+
const { root, cleanup } = await directory()
|
|
211
|
+
try {
|
|
212
|
+
await writeIn(root, BACKUPS_GITIGNORE, '*\n')
|
|
213
|
+
await chmod(join(root, BACKUPS_GITIGNORE), 0)
|
|
214
|
+
assert.equal(backupsGitignoreState(root), 'unreadable')
|
|
215
|
+
assert.deepEqual(unsafeWritePlaces(root).map(({ path }) => path), [BACKUPS_GITIGNORE])
|
|
216
|
+
await assert.rejects(ensureBackupsGitignore(root), /can't be read/)
|
|
217
|
+
} finally {
|
|
218
|
+
await chmod(join(root, BACKUPS_GITIGNORE), 0o644).catch(() => {})
|
|
219
|
+
await cleanup()
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('the .gitignore kept in .nextspark/backups ignores a backup whatever its directory is named and whatever the rules above take back', async () => {
|
|
224
|
+
const { root, cleanup } = await directory()
|
|
225
|
+
const gitIgnores = path => spawnSync('git', ['check-ignore', '-q', '--', path], { cwd: root }).status === 0
|
|
226
|
+
try {
|
|
227
|
+
execFileSync('git', ['init', '-q'], { cwd: root })
|
|
228
|
+
await writeFile(join(root, '.gitignore'), '!.nextspark/\n!.nextspark/backups/**\n')
|
|
229
|
+
await writeFile(join(root, '.git/info/exclude'), '!*\n')
|
|
230
|
+
await writeIn(root, '.nextspark/.gitignore', '!backups/\n!backups/**\n')
|
|
231
|
+
await writeIn(root, '.nextspark/backups/2026-09-16T00-00-00-000Z-r3g1st/.gitignore', '!*\n')
|
|
232
|
+
const backups = [
|
|
233
|
+
'.nextspark/backups/2026-09-16T00-00-00-000Z-Zz9Qa1/app/(templates)/(public)/page.tsx',
|
|
234
|
+
'.nextspark/backups/2026-09-16T00-00-00-000Z-r3g1st/i18n.ts',
|
|
235
|
+
'.nextspark/backups/named by hand/.env.local',
|
|
236
|
+
]
|
|
237
|
+
for (const file of backups) await writeIn(root, file, 'SECRET=1\n')
|
|
238
|
+
assert.deepEqual(backups.map(gitIgnores), [false, false, false])
|
|
239
|
+
|
|
240
|
+
assert.equal(await ensureBackupsGitignore(root), true)
|
|
241
|
+
assert.equal(await ensureBackupsGitignore(root), false, 'a second call leaves it as it is')
|
|
242
|
+
assert.equal(backupsGitignoreState(root), 'in place')
|
|
243
|
+
|
|
244
|
+
assert.deepEqual(backups.map(gitIgnores), [true, true, true])
|
|
245
|
+
assert.equal(
|
|
246
|
+
execFileSync('git', ['status', '--porcelain', '--untracked-files=all'], { cwd: root, encoding: 'utf-8' }),
|
|
247
|
+
'?? .gitignore\n?? .nextspark/.gitignore\n'
|
|
248
|
+
)
|
|
249
|
+
} finally {
|
|
250
|
+
await cleanup()
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
/** Every entry under `root`: a file by its content's hash, a symlink by where it points, a directory by its name. */
|
|
255
|
+
async function snapshot(root) {
|
|
256
|
+
const entries = {}
|
|
257
|
+
const walk = async relative => {
|
|
258
|
+
for (const entry of await readdir(join(root, relative), { withFileTypes: true })) {
|
|
259
|
+
const path = relative ? `${relative}/${entry.name}` : entry.name
|
|
260
|
+
const stats = await lstat(join(root, path))
|
|
261
|
+
if (stats.isSymbolicLink()) {
|
|
262
|
+
entries[path] = `-> ${await readlink(join(root, path))}`
|
|
263
|
+
} else if (stats.isDirectory()) {
|
|
264
|
+
entries[path] = 'dir'
|
|
265
|
+
if (stats.mode & 0o400) await walk(path)
|
|
266
|
+
} else if (stats.mode & 0o400) {
|
|
267
|
+
entries[path] = createHash('sha256').update(await readFile(join(root, path))).digest('hex')
|
|
268
|
+
} else {
|
|
269
|
+
entries[path] = 'unreadable file'
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
await walk('')
|
|
274
|
+
return entries
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** A project for core's real registry build, with a stale file in app/(templates) the build would back up and remove. */
|
|
278
|
+
async function buildableProject() {
|
|
279
|
+
const project = await directory()
|
|
280
|
+
await writeIn(project.root, '.env', 'NEXT_PUBLIC_ACTIVE_THEME=acme\n')
|
|
281
|
+
await writeIn(project.root, 'package.json', '{}')
|
|
282
|
+
await writeIn(project.root, 'app/layout.tsx', 'export default function Layout({ children }) { return children }\n')
|
|
283
|
+
await writeIn(project.root, 'app/(templates)/stale/page.tsx', 'export default function Stale() { return null }\n')
|
|
284
|
+
await writeIn(project.root, 'contents/themes/acme/templates/pricing/page.tsx', 'export default function Pricing() { return null }\n')
|
|
285
|
+
return project
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function runBuild(root) {
|
|
289
|
+
const result = spawnSync('node', ['scripts/build/registry.mjs'], {
|
|
290
|
+
cwd: CORE_DIR,
|
|
291
|
+
env: { ...process.env, NEXTSPARK_PROJECT_ROOT: root },
|
|
292
|
+
encoding: 'utf8',
|
|
293
|
+
})
|
|
294
|
+
return { status: result.status, output: `${result.stdout}${result.stderr}` }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
test("the registry build writes nothing, in the project or through it, when a place it writes under isn't safe", async () => {
|
|
298
|
+
const cases = [
|
|
299
|
+
['a registry that is a symlink to a file outside', async (root, outside) => {
|
|
300
|
+
await writeIn(outside, 'index.ts', '// outside\n')
|
|
301
|
+
await mkdir(join(root, '.nextspark/registries'), { recursive: true })
|
|
302
|
+
await symlink(join(outside, 'index.ts'), join(root, '.nextspark/registries/index.ts'))
|
|
303
|
+
}, '.nextspark/registries/index.ts is a symlink'],
|
|
304
|
+
['the registries directory a symlink to one outside', async (root, outside) => {
|
|
305
|
+
await mkdir(join(root, '.nextspark'), { recursive: true })
|
|
306
|
+
await symlink(outside, join(root, '.nextspark/registries'))
|
|
307
|
+
}, '.nextspark/registries is a symlink'],
|
|
308
|
+
['app/(templates) a symlink to a directory outside', async (root, outside) => {
|
|
309
|
+
await rm(join(root, 'app/(templates)'), { recursive: true })
|
|
310
|
+
await symlink(outside, join(root, 'app/(templates)'))
|
|
311
|
+
}, 'app/(templates) is a symlink'],
|
|
312
|
+
['app/globals.css a symlink to a file outside', async (root, outside) => {
|
|
313
|
+
await writeIn(outside, 'globals.css', '/* outside */\n')
|
|
314
|
+
await symlink(join(outside, 'globals.css'), join(root, 'app/globals.css'))
|
|
315
|
+
}, 'app/globals.css is a symlink'],
|
|
316
|
+
['a directory where a registry goes', async root => {
|
|
317
|
+
await writeIn(root, '.nextspark/registries/index.ts/stale.ts')
|
|
318
|
+
}, '.nextspark/registries/index.ts is not a file'],
|
|
319
|
+
['a backups .gitignore that takes the backups back', async root => {
|
|
320
|
+
await writeIn(root, BACKUPS_GITIGNORE, '*\n!*/\n')
|
|
321
|
+
}, `${BACKUPS_GITIGNORE} has patterns other than *`],
|
|
322
|
+
['a registries .gitignore that takes the registries back', async root => {
|
|
323
|
+
await writeIn(root, REGISTRIES_GITIGNORE, '*\n!*.ts\n')
|
|
324
|
+
}, `${REGISTRIES_GITIGNORE} has patterns other than *`],
|
|
325
|
+
['app/api a symlink to a directory outside holding an old generated plugin route and another file', async (root, outside) => {
|
|
326
|
+
await writeIn(outside, 'v1/plugin/legacy/route.ts', '// Auto-generated Plugin Route Proxy\n')
|
|
327
|
+
await writeIn(outside, 'v1/plugin/legacy/other.ts', 'export const other = 1\n')
|
|
328
|
+
await symlink(outside, join(root, 'app/api'))
|
|
329
|
+
}, 'app/api is a symlink'],
|
|
330
|
+
["the active theme's fixtures directory a symlink to one outside", async (root, outside) => {
|
|
331
|
+
await writeIn(outside, 'entities.json', '{"outside":true}\n')
|
|
332
|
+
await writeIn(outside, 'blocks.json', '{"outside":true}\n')
|
|
333
|
+
await mkdir(join(root, 'contents/themes/acme/tests/cypress'), { recursive: true })
|
|
334
|
+
await symlink(outside, join(root, 'contents/themes/acme/tests/cypress/fixtures'))
|
|
335
|
+
}, 'contents/themes/acme/tests/cypress/fixtures is a symlink'],
|
|
336
|
+
["the active theme's entities.json a symlink to a file outside", async (root, outside) => {
|
|
337
|
+
await writeIn(outside, 'entities.json', '{"outside":true}\n')
|
|
338
|
+
await mkdir(join(root, 'contents/themes/acme/tests/cypress/fixtures'), { recursive: true })
|
|
339
|
+
await symlink(join(outside, 'entities.json'), join(root, 'contents/themes/acme/tests/cypress/fixtures/entities.json'))
|
|
340
|
+
}, 'contents/themes/acme/tests/cypress/fixtures/entities.json is a symlink'],
|
|
341
|
+
['the active theme a symlink to a directory outside with fixtures', async (root, outside) => {
|
|
342
|
+
await writeIn(outside, 'templates/pricing/page.tsx', 'export default function Pricing() { return null }\n')
|
|
343
|
+
await writeIn(outside, 'tests/cypress/fixtures/entities.json', '{"outside":true}\n')
|
|
344
|
+
await writeIn(outside, 'tests/cypress/fixtures/blocks.json', '{"outside":true}\n')
|
|
345
|
+
await rm(join(root, 'contents/themes/acme'), { recursive: true })
|
|
346
|
+
await symlink(outside, join(root, 'contents/themes/acme'))
|
|
347
|
+
}, 'contents/themes/acme is a symlink'],
|
|
348
|
+
...(RUNS_AS_ROOT ? [] : [['a backups .gitignore that cannot be read', async root => {
|
|
349
|
+
await writeIn(root, BACKUPS_GITIGNORE, '*\n')
|
|
350
|
+
await chmod(join(root, BACKUPS_GITIGNORE), 0)
|
|
351
|
+
}, `${BACKUPS_GITIGNORE} can't be read`]]),
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
const wrong = []
|
|
355
|
+
for (const [label, setUp, named] of cases) {
|
|
356
|
+
const project = await buildableProject()
|
|
357
|
+
const outside = await directory()
|
|
358
|
+
try {
|
|
359
|
+
await setUp(project.root, outside.root)
|
|
360
|
+
const before = { project: await snapshot(project.root), outside: await snapshot(outside.root) }
|
|
361
|
+
const { status, output } = runBuild(project.root)
|
|
362
|
+
const after = { project: await snapshot(project.root), outside: await snapshot(outside.root) }
|
|
363
|
+
|
|
364
|
+
if (status !== 1) wrong.push(`${label}: exited ${status}`)
|
|
365
|
+
if (JSON.stringify(after.project) !== JSON.stringify(before.project)) wrong.push(`${label}: the project changed`)
|
|
366
|
+
if (JSON.stringify(after.outside) !== JSON.stringify(before.outside)) wrong.push(`${label}: what is outside changed`)
|
|
367
|
+
if (!output.split('\n').some(line => line.includes("Build failed before writing anything"))) wrong.push(`${label}: no line says the build stopped before writing`)
|
|
368
|
+
if (!output.split('\n').some(line => line.trim().startsWith(named))) wrong.push(`${label}: no line names ${named}`)
|
|
369
|
+
} finally {
|
|
370
|
+
await chmod(join(project.root, BACKUPS_GITIGNORE), 0o644).catch(() => {})
|
|
371
|
+
await outside.cleanup()
|
|
372
|
+
await project.cleanup()
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const control = await buildableProject()
|
|
377
|
+
try {
|
|
378
|
+
await writeIn(control.root, 'app/api/v1/plugin/legacy/route.ts', '// Auto-generated Plugin Route Proxy\n')
|
|
379
|
+
await mkdir(join(control.root, 'contents/themes/acme/tests/cypress/fixtures'), { recursive: true })
|
|
380
|
+
const { status } = runBuild(control.root)
|
|
381
|
+
if (status !== 0) wrong.push(`with nothing in the way, the build exited ${status}`)
|
|
382
|
+
if (!existsSync(join(control.root, '.nextspark/registries/index.ts'))) wrong.push('with nothing in the way, the build wrote no registry')
|
|
383
|
+
if (existsSync(join(control.root, 'app/(templates)/stale/page.tsx'))) wrong.push('with nothing in the way, the build left the stale file')
|
|
384
|
+
if (existsSync(join(control.root, 'app/api/v1/plugin/legacy'))) wrong.push('with nothing in the way, the build left the old generated plugin route')
|
|
385
|
+
// entities.json is written only for a theme discovery finds, which this one, with no theme.config.ts, is not
|
|
386
|
+
if (!existsSync(join(control.root, 'contents/themes/acme/tests/cypress/fixtures/blocks.json'))) wrong.push('with nothing in the way, the build wrote no blocks.json')
|
|
387
|
+
} finally {
|
|
388
|
+
await control.cleanup()
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
assert.deepEqual(wrong, [])
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
test("the registry build keeps .nextspark/registries out of git with a .gitignore of its own, while it runs and after, whatever the project's rules take back", async () => {
|
|
395
|
+
const project = await buildableProject()
|
|
396
|
+
const root = project.root
|
|
397
|
+
try {
|
|
398
|
+
execFileSync('git', ['init', '-q'], { cwd: root })
|
|
399
|
+
await writeFile(join(root, '.gitignore'), 'node_modules/\n.env\napp/(templates)/\n!.nextspark/\n!.nextspark/registries/**\n')
|
|
400
|
+
await writeIn(root, '.nextspark/.gitignore', '!registries/\n!registries/**\n')
|
|
401
|
+
await writeFile(join(root, '.git/info/exclude'), '!*\n')
|
|
402
|
+
const visible = () => execFileSync('git', ['status', '--porcelain', '--untracked-files=all'], { cwd: root, encoding: 'utf8' })
|
|
403
|
+
.split('\n').filter(line => line.includes('.nextspark/registries'))
|
|
404
|
+
|
|
405
|
+
const build = spawn('node', ['scripts/build/registry.mjs'], { cwd: CORE_DIR, env: { ...process.env, NEXTSPARK_PROJECT_ROOT: root }, stdio: 'ignore' })
|
|
406
|
+
let exited = null
|
|
407
|
+
build.on('exit', code => { exited = code })
|
|
408
|
+
const during = new Set()
|
|
409
|
+
let polls = 0
|
|
410
|
+
while (exited === null) {
|
|
411
|
+
for (const line of visible()) during.add(line)
|
|
412
|
+
polls++
|
|
413
|
+
await new Promise(resolve => setTimeout(resolve, 5))
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
assert.equal(exited, 0)
|
|
417
|
+
assert.ok(polls > 1, 'git was asked while the build ran')
|
|
418
|
+
assert.deepEqual([...during], [], 'git picks up no registry while the build runs')
|
|
419
|
+
assert.deepEqual(visible(), [], 'nor after')
|
|
420
|
+
assert.equal(await readFile(join(root, REGISTRIES_GITIGNORE), 'utf8'), REGISTRIES_GITIGNORE_CONTENT)
|
|
421
|
+
assert.ok(existsSync(join(root, '.nextspark/registries/index.ts')), 'the registries are written')
|
|
422
|
+
} finally {
|
|
423
|
+
await project.cleanup()
|
|
424
|
+
}
|
|
425
|
+
})
|
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
import { join, dirname } from 'path'
|
|
16
16
|
import { fileURLToPath } from 'url'
|
|
17
|
-
import { existsSync
|
|
17
|
+
import { existsSync } from 'fs'
|
|
18
18
|
import dotenv from 'dotenv'
|
|
19
19
|
import { loadNextSparkConfigSync } from '../config-loader.mjs'
|
|
20
|
+
import { detectMonorepoRoot, isInstalledAsPackage } from './project-mode.mjs'
|
|
21
|
+
import { shownPath } from '../../utils/logging.mjs'
|
|
20
22
|
|
|
21
23
|
// Load .env from the correct project root
|
|
22
24
|
// Priority: NEXTSPARK_PROJECT_ROOT env var > cwd
|
|
@@ -32,26 +34,7 @@ const __dirname = dirname(__filename)
|
|
|
32
34
|
// Root directory (5 levels up from packages/core/scripts/build/registry/)
|
|
33
35
|
export const rootDir = join(__dirname, '../../../../..')
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
* Detect monorepo root by searching for pnpm-workspace.yaml
|
|
37
|
-
* @param {string} startDir - Directory to start searching from
|
|
38
|
-
* @returns {string|null} Monorepo root path or null if not in monorepo
|
|
39
|
-
*/
|
|
40
|
-
export function detectMonorepoRoot(startDir = process.cwd()) {
|
|
41
|
-
let dir = startDir
|
|
42
|
-
const maxDepth = 10
|
|
43
|
-
let depth = 0
|
|
44
|
-
|
|
45
|
-
while (dir !== '/' && depth < maxDepth) {
|
|
46
|
-
if (existsSync(join(dir, 'pnpm-workspace.yaml'))) {
|
|
47
|
-
return dir
|
|
48
|
-
}
|
|
49
|
-
dir = dirname(dir)
|
|
50
|
-
depth++
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return null
|
|
54
|
-
}
|
|
37
|
+
export { detectMonorepoRoot, isInstalledAsPackage }
|
|
55
38
|
|
|
56
39
|
/**
|
|
57
40
|
* Detect project root by searching for nextspark.config.ts or apps/dev in monorepo
|
|
@@ -85,49 +68,6 @@ export function detectProjectRoot() {
|
|
|
85
68
|
return process.cwd()
|
|
86
69
|
}
|
|
87
70
|
|
|
88
|
-
/**
|
|
89
|
-
* Check if NextSpark is installed as a package (from npm registry)
|
|
90
|
-
*
|
|
91
|
-
* This handles both traditional npm/yarn AND pnpm:
|
|
92
|
-
* - npm/yarn: packages are copied to node_modules (not symlinks)
|
|
93
|
-
* - pnpm: ALL packages are symlinks, but npm packages point to .pnpm/ store
|
|
94
|
-
* - pnpm workspace: local packages symlink to the actual package path
|
|
95
|
-
*
|
|
96
|
-
* @param {string} root - Project root path
|
|
97
|
-
* @returns {boolean} True if installed from npm registry
|
|
98
|
-
*/
|
|
99
|
-
export function isInstalledAsPackage(root) {
|
|
100
|
-
const corePath = join(root, 'node_modules/@nextsparkjs/core')
|
|
101
|
-
if (!existsSync(corePath)) {
|
|
102
|
-
return false
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
const stat = lstatSync(corePath)
|
|
107
|
-
|
|
108
|
-
// If not a symlink, it's a traditional npm/yarn install
|
|
109
|
-
if (!stat.isSymbolicLink()) {
|
|
110
|
-
return true
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// For symlinks (pnpm), check WHERE it points to:
|
|
114
|
-
// - npm install: points to .pnpm/@nextsparkjs+core@version.../node_modules/@nextsparkjs/core
|
|
115
|
-
// - workspace: points to ../../packages/core or similar
|
|
116
|
-
const linkTarget = readlinkSync(corePath)
|
|
117
|
-
|
|
118
|
-
// If symlink points to .pnpm/ or .pnpmN/ directory, it's a real npm installation via pnpm
|
|
119
|
-
// The .pnpm/.pnpm2/etc directory is pnpm's content-addressable store
|
|
120
|
-
if (/\.pnpm\d?[/\\]/.test(linkTarget)) {
|
|
121
|
-
return true
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Otherwise it's a workspace symlink (monorepo development)
|
|
125
|
-
return false
|
|
126
|
-
} catch {
|
|
127
|
-
return false
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
71
|
/**
|
|
132
72
|
* Get dynamic configuration based on project root
|
|
133
73
|
* @param {string|null} projectRoot - Optional project root path
|
|
@@ -216,7 +156,8 @@ export function validateEnvironment(config) {
|
|
|
216
156
|
const hasEnvVarsFromProcess = !!process.env.NEXT_PUBLIC_ACTIVE_THEME
|
|
217
157
|
|
|
218
158
|
if (!existsSync(envFilePath) && !hasEnvVarsFromProcess) {
|
|
219
|
-
|
|
159
|
+
// The error spans lines, printed one per call: the path is shown on the one line it is on
|
|
160
|
+
let fix = `Create a .env file in your project root: ${shownPath(config.projectRoot)}`
|
|
220
161
|
if (existsSync(envExamplePath)) {
|
|
221
162
|
fix = `Copy the example file:\n cp .env.example .env\n Then update it with your configuration.`
|
|
222
163
|
}
|