gazetta 0.7.0 → 0.8.0
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/admin-dist/assets/index-CBeq0rRb.js +693 -0
- package/admin-dist/assets/index-Dtg1dTZQ.css +1 -0
- package/admin-dist/assets/rolldown-runtime-BYbx6iT9.js +1 -0
- package/admin-dist/assets/{vendor-primevue-C0Q_YTCb.js → vendor-primevue-CBGHkaXv.js} +183 -39
- package/admin-dist/assets/{vendor-react-BipDVGow.js → vendor-react-BdW_kNCG.js} +2 -2
- package/admin-dist/assets/vendor-rjsf-lN2SztQt.js +33 -0
- package/admin-dist/assets/vendor-tiptap-C36yDquB.js +141 -0
- package/admin-dist/assets/vendor-vue-Bt5uR1VW.js +1 -0
- package/admin-dist/assets/workbox-window.prod.es5-DGMtIXHc.js +2 -0
- package/admin-dist/index.html +8 -8
- package/admin-dist/sw.js +1 -0
- package/dist/admin-api/archived-name-conflict.d.ts +31 -0
- package/dist/admin-api/archived-name-conflict.d.ts.map +1 -0
- package/dist/admin-api/archived-name-conflict.js +226 -0
- package/dist/admin-api/archived-name-conflict.js.map +1 -0
- package/dist/admin-api/cache-stats-logger.d.ts +83 -0
- package/dist/admin-api/cache-stats-logger.d.ts.map +1 -0
- package/dist/admin-api/cache-stats-logger.js +59 -0
- package/dist/admin-api/cache-stats-logger.js.map +1 -0
- package/dist/admin-api/hook-audit-emitter.d.ts +38 -0
- package/dist/admin-api/hook-audit-emitter.d.ts.map +1 -0
- package/dist/admin-api/hook-audit-emitter.js +21 -0
- package/dist/admin-api/hook-audit-emitter.js.map +1 -0
- package/dist/admin-api/index.d.ts +84 -0
- package/dist/admin-api/index.d.ts.map +1 -1
- package/dist/admin-api/index.js +254 -9
- package/dist/admin-api/index.js.map +1 -1
- package/dist/admin-api/middleware/audit.d.ts +25 -0
- package/dist/admin-api/middleware/audit.d.ts.map +1 -0
- package/dist/admin-api/middleware/audit.js +65 -0
- package/dist/admin-api/middleware/audit.js.map +1 -0
- package/dist/admin-api/middleware/capability.d.ts +8 -0
- package/dist/admin-api/middleware/capability.d.ts.map +1 -0
- package/dist/admin-api/middleware/capability.js +65 -0
- package/dist/admin-api/middleware/capability.js.map +1 -0
- package/dist/admin-api/middleware/principal.d.ts +18 -0
- package/dist/admin-api/middleware/principal.d.ts.map +1 -0
- package/dist/admin-api/middleware/principal.js +128 -0
- package/dist/admin-api/middleware/principal.js.map +1 -0
- package/dist/admin-api/routes/archive-review.d.ts +80 -0
- package/dist/admin-api/routes/archive-review.d.ts.map +1 -0
- package/dist/admin-api/routes/archive-review.js +70 -0
- package/dist/admin-api/routes/archive-review.js.map +1 -0
- package/dist/admin-api/routes/archive.d.ts +145 -0
- package/dist/admin-api/routes/archive.d.ts.map +1 -0
- package/dist/admin-api/routes/archive.js +540 -0
- package/dist/admin-api/routes/archive.js.map +1 -0
- package/dist/admin-api/routes/assets.d.ts +6 -1
- package/dist/admin-api/routes/assets.d.ts.map +1 -1
- package/dist/admin-api/routes/assets.js +167 -14
- package/dist/admin-api/routes/assets.js.map +1 -1
- package/dist/admin-api/routes/audit.d.ts +71 -0
- package/dist/admin-api/routes/audit.d.ts.map +1 -0
- package/dist/admin-api/routes/audit.js +178 -0
- package/dist/admin-api/routes/audit.js.map +1 -0
- package/dist/admin-api/routes/compare.d.ts.map +1 -1
- package/dist/admin-api/routes/compare.js +3 -2
- package/dist/admin-api/routes/compare.js.map +1 -1
- package/dist/admin-api/routes/fields.d.ts.map +1 -1
- package/dist/admin-api/routes/fields.js +2 -1
- package/dist/admin-api/routes/fields.js.map +1 -1
- package/dist/admin-api/routes/fragments.d.ts +13 -1
- package/dist/admin-api/routes/fragments.d.ts.map +1 -1
- package/dist/admin-api/routes/fragments.js +127 -92
- package/dist/admin-api/routes/fragments.js.map +1 -1
- package/dist/admin-api/routes/health.d.ts +60 -0
- package/dist/admin-api/routes/health.d.ts.map +1 -0
- package/dist/admin-api/routes/health.js +65 -0
- package/dist/admin-api/routes/health.js.map +1 -0
- package/dist/admin-api/routes/history.d.ts +2 -1
- package/dist/admin-api/routes/history.d.ts.map +1 -1
- package/dist/admin-api/routes/history.js +26 -4
- package/dist/admin-api/routes/history.js.map +1 -1
- package/dist/admin-api/routes/pages.d.ts +20 -1
- package/dist/admin-api/routes/pages.d.ts.map +1 -1
- package/dist/admin-api/routes/pages.js +157 -117
- package/dist/admin-api/routes/pages.js.map +1 -1
- package/dist/admin-api/routes/preview.d.ts.map +1 -1
- package/dist/admin-api/routes/preview.js +56 -17
- package/dist/admin-api/routes/preview.js.map +1 -1
- package/dist/admin-api/routes/publish.d.ts +19 -1
- package/dist/admin-api/routes/publish.d.ts.map +1 -1
- package/dist/admin-api/routes/publish.js +508 -92
- package/dist/admin-api/routes/publish.js.map +1 -1
- package/dist/admin-api/routes/rename.d.ts +62 -0
- package/dist/admin-api/routes/rename.d.ts.map +1 -0
- package/dist/admin-api/routes/rename.js +366 -0
- package/dist/admin-api/routes/rename.js.map +1 -0
- package/dist/admin-api/routes/site.d.ts.map +1 -1
- package/dist/admin-api/routes/site.js +6 -18
- package/dist/admin-api/routes/site.js.map +1 -1
- package/dist/admin-api/routes/system.d.ts +23 -0
- package/dist/admin-api/routes/system.d.ts.map +1 -0
- package/dist/admin-api/routes/system.js +115 -0
- package/dist/admin-api/routes/system.js.map +1 -0
- package/dist/admin-api/routes/templates.d.ts +11 -1
- package/dist/admin-api/routes/templates.d.ts.map +1 -1
- package/dist/admin-api/routes/templates.js +36 -3
- package/dist/admin-api/routes/templates.js.map +1 -1
- package/dist/admin-api/routes/validation.d.ts +47 -0
- package/dist/admin-api/routes/validation.d.ts.map +1 -0
- package/dist/admin-api/routes/validation.js +120 -0
- package/dist/admin-api/routes/validation.js.map +1 -0
- package/dist/admin-api/schemas/archive.d.ts +124 -0
- package/dist/admin-api/schemas/archive.d.ts.map +1 -0
- package/dist/admin-api/schemas/archive.js +93 -0
- package/dist/admin-api/schemas/archive.js.map +1 -0
- package/dist/admin-api/schemas/assets.d.ts +16 -0
- package/dist/admin-api/schemas/assets.d.ts.map +1 -1
- package/dist/admin-api/schemas/assets.js +15 -0
- package/dist/admin-api/schemas/assets.js.map +1 -1
- package/dist/admin-api/schemas/audit.d.ts +175 -0
- package/dist/admin-api/schemas/audit.d.ts.map +1 -0
- package/dist/admin-api/schemas/audit.js +91 -0
- package/dist/admin-api/schemas/audit.js.map +1 -0
- package/dist/admin-api/schemas/error.d.ts +94 -0
- package/dist/admin-api/schemas/error.d.ts.map +1 -0
- package/dist/admin-api/schemas/error.js +79 -0
- package/dist/admin-api/schemas/error.js.map +1 -0
- package/dist/admin-api/schemas/fragments.d.ts +2 -0
- package/dist/admin-api/schemas/fragments.d.ts.map +1 -1
- package/dist/admin-api/schemas/fragments.js +4 -0
- package/dist/admin-api/schemas/fragments.js.map +1 -1
- package/dist/admin-api/schemas/index.d.ts +8 -0
- package/dist/admin-api/schemas/index.d.ts.map +1 -1
- package/dist/admin-api/schemas/index.js +8 -0
- package/dist/admin-api/schemas/index.js.map +1 -1
- package/dist/admin-api/schemas/pages.d.ts +2 -0
- package/dist/admin-api/schemas/pages.d.ts.map +1 -1
- package/dist/admin-api/schemas/pages.js +11 -0
- package/dist/admin-api/schemas/pages.js.map +1 -1
- package/dist/admin-api/schemas/rename.d.ts +77 -0
- package/dist/admin-api/schemas/rename.d.ts.map +1 -0
- package/dist/admin-api/schemas/rename.js +75 -0
- package/dist/admin-api/schemas/rename.js.map +1 -0
- package/dist/admin-api/schemas/site.d.ts +3 -2
- package/dist/admin-api/schemas/site.d.ts.map +1 -1
- package/dist/admin-api/schemas/site.js +3 -2
- package/dist/admin-api/schemas/site.js.map +1 -1
- package/dist/admin-api/schemas/system.d.ts +28 -0
- package/dist/admin-api/schemas/system.d.ts.map +1 -0
- package/dist/admin-api/schemas/system.js +35 -0
- package/dist/admin-api/schemas/system.js.map +1 -0
- package/dist/admin-api/schemas/targets.d.ts +55 -0
- package/dist/admin-api/schemas/targets.d.ts.map +1 -1
- package/dist/admin-api/schemas/targets.js +46 -0
- package/dist/admin-api/schemas/targets.js.map +1 -1
- package/dist/admin-api/schemas/templates.d.ts +54 -0
- package/dist/admin-api/schemas/templates.d.ts.map +1 -1
- package/dist/admin-api/schemas/templates.js +21 -0
- package/dist/admin-api/schemas/templates.js.map +1 -1
- package/dist/admin-api/schemas/validation.d.ts +101 -0
- package/dist/admin-api/schemas/validation.d.ts.map +1 -0
- package/dist/admin-api/schemas/validation.js +57 -0
- package/dist/admin-api/schemas/validation.js.map +1 -0
- package/dist/admin-api/source-context.d.ts +66 -10
- package/dist/admin-api/source-context.d.ts.map +1 -1
- package/dist/admin-api/source-context.js +43 -5
- package/dist/admin-api/source-context.js.map +1 -1
- package/dist/ai/adapter-scaffold.d.ts +63 -0
- package/dist/ai/adapter-scaffold.d.ts.map +1 -0
- package/dist/ai/adapter-scaffold.js +89 -0
- package/dist/ai/adapter-scaffold.js.map +1 -0
- package/dist/ai/compose-prompt.d.ts +50 -0
- package/dist/ai/compose-prompt.d.ts.map +1 -0
- package/dist/ai/compose-prompt.js +49 -0
- package/dist/ai/compose-prompt.js.map +1 -0
- package/dist/ai/errors.d.ts +65 -0
- package/dist/ai/errors.d.ts.map +1 -0
- package/dist/ai/errors.js +59 -0
- package/dist/ai/errors.js.map +1 -0
- package/dist/ai/index.d.ts +17 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +16 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/provider.d.ts +76 -0
- package/dist/ai/provider.d.ts.map +1 -0
- package/dist/ai/provider.js +13 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/ai/refusal.d.ts +50 -0
- package/dist/ai/refusal.d.ts.map +1 -0
- package/dist/ai/refusal.js +100 -0
- package/dist/ai/refusal.js.map +1 -0
- package/dist/ai/vision-prep.d.ts +32 -0
- package/dist/ai/vision-prep.d.ts.map +1 -0
- package/dist/ai/vision-prep.js +113 -0
- package/dist/ai/vision-prep.js.map +1 -0
- package/dist/alt/adapter.d.ts +140 -0
- package/dist/alt/adapter.d.ts.map +1 -0
- package/dist/alt/adapter.js +7 -0
- package/dist/alt/adapter.js.map +1 -0
- package/dist/alt/anthropic.d.ts +63 -0
- package/dist/alt/anthropic.d.ts.map +1 -0
- package/dist/alt/anthropic.js +147 -0
- package/dist/alt/anthropic.js.map +1 -0
- package/dist/alt/config.d.ts +67 -0
- package/dist/alt/config.d.ts.map +1 -0
- package/dist/alt/config.js +41 -0
- package/dist/alt/config.js.map +1 -0
- package/dist/alt/factory.d.ts +19 -0
- package/dist/alt/factory.d.ts.map +1 -0
- package/dist/alt/factory.js +69 -0
- package/dist/alt/factory.js.map +1 -0
- package/dist/alt/null-adapter.d.ts +3 -0
- package/dist/alt/null-adapter.d.ts.map +1 -0
- package/dist/alt/null-adapter.js +43 -0
- package/dist/alt/null-adapter.js.map +1 -0
- package/dist/alt/ollama.d.ts +40 -0
- package/dist/alt/ollama.d.ts.map +1 -0
- package/dist/alt/ollama.js +139 -0
- package/dist/alt/ollama.js.map +1 -0
- package/dist/alt/openai.d.ts +46 -0
- package/dist/alt/openai.d.ts.map +1 -0
- package/dist/alt/openai.js +118 -0
- package/dist/alt/openai.js.map +1 -0
- package/dist/alt/prompt-policies.d.ts +79 -0
- package/dist/alt/prompt-policies.d.ts.map +1 -0
- package/dist/alt/prompt-policies.js +67 -0
- package/dist/alt/prompt-policies.js.map +1 -0
- package/dist/alt/route-handler.d.ts +56 -0
- package/dist/alt/route-handler.d.ts.map +1 -0
- package/dist/alt/route-handler.js +122 -0
- package/dist/alt/route-handler.js.map +1 -0
- package/dist/alt/suggester.d.ts +57 -0
- package/dist/alt/suggester.d.ts.map +1 -0
- package/dist/alt/suggester.js +133 -0
- package/dist/alt/suggester.js.map +1 -0
- package/dist/app.js +1 -1
- package/dist/app.js.map +1 -1
- package/dist/archive-aliases.d.ts +79 -0
- package/dist/archive-aliases.d.ts.map +1 -0
- package/dist/archive-aliases.js +60 -0
- package/dist/archive-aliases.js.map +1 -0
- package/dist/archive-helpers.d.ts +73 -0
- package/dist/archive-helpers.d.ts.map +1 -0
- package/dist/archive-helpers.js +94 -0
- package/dist/archive-helpers.js.map +1 -0
- package/dist/assets/find-refs.d.ts +1 -1
- package/dist/assets/find-refs.js +1 -1
- package/dist/assets/find-refs.js.map +1 -1
- package/dist/assets/rename.js +1 -1
- package/dist/assets/rename.js.map +1 -1
- package/dist/assets/replace.js +1 -1
- package/dist/assets/replace.js.map +1 -1
- package/dist/assets/resolve.js +4 -4
- package/dist/assets/resolve.js.map +1 -1
- package/dist/assets/serve-route.js +2 -2
- package/dist/assets/serve-route.js.map +1 -1
- package/dist/assets/validate.d.ts +1 -1
- package/dist/assets/validate.js +1 -1
- package/dist/audit/config.d.ts +75 -0
- package/dist/audit/config.d.ts.map +1 -0
- package/dist/audit/config.js +91 -0
- package/dist/audit/config.js.map +1 -0
- package/dist/audit/context.d.ts +98 -0
- package/dist/audit/context.d.ts.map +1 -0
- package/dist/audit/context.js +51 -0
- package/dist/audit/context.js.map +1 -0
- package/dist/audit/errors.d.ts +73 -0
- package/dist/audit/errors.d.ts.map +1 -0
- package/dist/audit/errors.js +78 -0
- package/dist/audit/errors.js.map +1 -0
- package/dist/audit/index.d.ts +16 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +10 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/provider.d.ts +73 -0
- package/dist/audit/provider.d.ts.map +1 -0
- package/dist/audit/provider.js +2 -0
- package/dist/audit/provider.js.map +1 -0
- package/dist/audit/providers/history.d.ts +66 -0
- package/dist/audit/providers/history.d.ts.map +1 -0
- package/dist/audit/providers/history.js +102 -0
- package/dist/audit/providers/history.js.map +1 -0
- package/dist/audit/pseudonymize.d.ts +26 -0
- package/dist/audit/pseudonymize.d.ts.map +1 -0
- package/dist/audit/pseudonymize.js +86 -0
- package/dist/audit/pseudonymize.js.map +1 -0
- package/dist/audit/recorder.d.ts +102 -0
- package/dist/audit/recorder.d.ts.map +1 -0
- package/dist/audit/recorder.js +55 -0
- package/dist/audit/recorder.js.map +1 -0
- package/dist/audit/retention.d.ts +83 -0
- package/dist/audit/retention.d.ts.map +1 -0
- package/dist/audit/retention.js +142 -0
- package/dist/audit/retention.js.map +1 -0
- package/dist/audit/source-ip.d.ts +32 -0
- package/dist/audit/source-ip.d.ts.map +1 -0
- package/dist/audit/source-ip.js +164 -0
- package/dist/audit/source-ip.js.map +1 -0
- package/dist/audit/types.d.ts +143 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/audit/types.js +33 -0
- package/dist/audit/types.js.map +1 -0
- package/dist/audit/user-agent.d.ts +28 -0
- package/dist/audit/user-agent.d.ts.map +1 -0
- package/dist/audit/user-agent.js +63 -0
- package/dist/audit/user-agent.js.map +1 -0
- package/dist/auth/capabilities.d.ts +28 -0
- package/dist/auth/capabilities.d.ts.map +1 -0
- package/dist/auth/capabilities.js +101 -0
- package/dist/auth/capabilities.js.map +1 -0
- package/dist/auth/config.d.ts +109 -0
- package/dist/auth/config.d.ts.map +1 -0
- package/dist/auth/config.js +221 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/errors.d.ts +72 -0
- package/dist/auth/errors.d.ts.map +1 -0
- package/dist/auth/errors.js +78 -0
- package/dist/auth/errors.js.map +1 -0
- package/dist/auth/factory.d.ts +43 -0
- package/dist/auth/factory.d.ts.map +1 -0
- package/dist/auth/factory.js +48 -0
- package/dist/auth/factory.js.map +1 -0
- package/dist/auth/index.d.ts +21 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +14 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/ip-match.d.ts +29 -0
- package/dist/auth/ip-match.d.ts.map +1 -0
- package/dist/auth/ip-match.js +162 -0
- package/dist/auth/ip-match.js.map +1 -0
- package/dist/auth/provider.d.ts +76 -0
- package/dist/auth/provider.d.ts.map +1 -0
- package/dist/auth/provider.js +2 -0
- package/dist/auth/provider.js.map +1 -0
- package/dist/auth/providers/aws-cognito.d.ts +55 -0
- package/dist/auth/providers/aws-cognito.d.ts.map +1 -0
- package/dist/auth/providers/aws-cognito.js +114 -0
- package/dist/auth/providers/aws-cognito.js.map +1 -0
- package/dist/auth/providers/azure-easy-auth.d.ts +7 -0
- package/dist/auth/providers/azure-easy-auth.d.ts.map +1 -0
- package/dist/auth/providers/azure-easy-auth.js +48 -0
- package/dist/auth/providers/azure-easy-auth.js.map +1 -0
- package/dist/auth/providers/cloudflare-access.d.ts +71 -0
- package/dist/auth/providers/cloudflare-access.d.ts.map +1 -0
- package/dist/auth/providers/cloudflare-access.js +120 -0
- package/dist/auth/providers/cloudflare-access.js.map +1 -0
- package/dist/auth/providers/forwarded-user.d.ts +31 -0
- package/dist/auth/providers/forwarded-user.d.ts.map +1 -0
- package/dist/auth/providers/forwarded-user.js +72 -0
- package/dist/auth/providers/forwarded-user.js.map +1 -0
- package/dist/auth/providers/none.d.ts +6 -0
- package/dist/auth/providers/none.d.ts.map +1 -0
- package/dist/auth/providers/none.js +19 -0
- package/dist/auth/providers/none.js.map +1 -0
- package/dist/auth/providers/tailscale.d.ts +7 -0
- package/dist/auth/providers/tailscale.d.ts.map +1 -0
- package/dist/auth/providers/tailscale.js +30 -0
- package/dist/auth/providers/tailscale.js.map +1 -0
- package/dist/auth/role-resolver.d.ts +38 -0
- package/dist/auth/role-resolver.d.ts.map +1 -0
- package/dist/auth/role-resolver.js +92 -0
- package/dist/auth/role-resolver.js.map +1 -0
- package/dist/auth/types.d.ts +150 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +60 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cache/errors.d.ts +41 -0
- package/dist/cache/errors.d.ts.map +1 -0
- package/dist/cache/errors.js +44 -0
- package/dist/cache/errors.js.map +1 -0
- package/dist/cache/factories.d.ts +17 -0
- package/dist/cache/factories.d.ts.map +1 -0
- package/dist/cache/factories.js +17 -0
- package/dist/cache/factories.js.map +1 -0
- package/dist/cache/keys.d.ts +63 -0
- package/dist/cache/keys.d.ts.map +1 -0
- package/dist/cache/keys.js +145 -0
- package/dist/cache/keys.js.map +1 -0
- package/dist/cache/memory.d.ts +51 -0
- package/dist/cache/memory.d.ts.map +1 -0
- package/dist/cache/memory.js +204 -0
- package/dist/cache/memory.js.map +1 -0
- package/dist/cache/per-site.d.ts +22 -0
- package/dist/cache/per-site.d.ts.map +1 -0
- package/dist/cache/per-site.js +114 -0
- package/dist/cache/per-site.js.map +1 -0
- package/dist/cache/types.d.ts +142 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +33 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/cli/archive.d.ts +44 -0
- package/dist/cli/archive.d.ts.map +1 -0
- package/dist/cli/archive.js +310 -0
- package/dist/cli/archive.js.map +1 -0
- package/dist/cli/bootstrap.d.ts +15 -8
- package/dist/cli/bootstrap.d.ts.map +1 -1
- package/dist/cli/bootstrap.js +59 -23
- package/dist/cli/bootstrap.js.map +1 -1
- package/dist/cli/dev-template-watcher.d.ts +29 -0
- package/dist/cli/dev-template-watcher.d.ts.map +1 -0
- package/dist/cli/dev-template-watcher.js +38 -0
- package/dist/cli/dev-template-watcher.js.map +1 -0
- package/dist/cli/history.d.ts.map +1 -1
- package/dist/cli/history.js +5 -3
- package/dist/cli/history.js.map +1 -1
- package/dist/cli/index.js +712 -395
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/validate-flags.d.ts +29 -0
- package/dist/cli/validate-flags.d.ts.map +1 -0
- package/dist/cli/validate-flags.js +49 -0
- package/dist/cli/validate-flags.js.map +1 -0
- package/dist/compare.d.ts +1 -1
- package/dist/compare.d.ts.map +1 -1
- package/dist/compare.js +25 -23
- package/dist/compare.js.map +1 -1
- package/dist/component-ids.d.ts +25 -0
- package/dist/component-ids.d.ts.map +1 -0
- package/dist/component-ids.js +83 -0
- package/dist/component-ids.js.map +1 -0
- package/dist/config/define.d.ts +61 -0
- package/dist/config/define.d.ts.map +1 -0
- package/dist/config/define.js +64 -0
- package/dist/config/define.js.map +1 -0
- package/dist/config/errors.d.ts +32 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +40 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/config/index.d.ts +13 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +20 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +105 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +265 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schemas.d.ts +89 -0
- package/dist/config/schemas.d.ts.map +1 -0
- package/dist/config/schemas.js +172 -0
- package/dist/config/schemas.js.map +1 -0
- package/dist/config/types.d.ts +32 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +15 -0
- package/dist/config/types.js.map +1 -0
- package/dist/deploy/cloudflare-workers.d.ts +46 -0
- package/dist/deploy/cloudflare-workers.d.ts.map +1 -0
- package/dist/deploy/cloudflare-workers.js +213 -0
- package/dist/deploy/cloudflare-workers.js.map +1 -0
- package/dist/deploy/errors.d.ts +66 -0
- package/dist/deploy/errors.d.ts.map +1 -0
- package/dist/deploy/errors.js +82 -0
- package/dist/deploy/errors.js.map +1 -0
- package/dist/deploy/index.d.ts +9 -0
- package/dist/deploy/index.d.ts.map +1 -0
- package/dist/deploy/index.js +3 -0
- package/dist/deploy/index.js.map +1 -0
- package/dist/deploy/types.d.ts +162 -0
- package/dist/deploy/types.d.ts.map +1 -0
- package/dist/deploy/types.js +2 -0
- package/dist/deploy/types.js.map +1 -0
- package/dist/fragments/create.d.ts +70 -0
- package/dist/fragments/create.d.ts.map +1 -0
- package/dist/fragments/create.js +93 -0
- package/dist/fragments/create.js.map +1 -0
- package/dist/fragments/publish.d.ts +37 -0
- package/dist/fragments/publish.d.ts.map +1 -0
- package/dist/fragments/publish.js +52 -0
- package/dist/fragments/publish.js.map +1 -0
- package/dist/fragments/save.d.ts +81 -0
- package/dist/fragments/save.d.ts.map +1 -0
- package/dist/fragments/save.js +105 -0
- package/dist/fragments/save.js.map +1 -0
- package/dist/history-recorder.d.ts +5 -5
- package/dist/history-recorder.d.ts.map +1 -1
- package/dist/history-recorder.js +4 -4
- package/dist/history-recorder.js.map +1 -1
- package/dist/history-restorer.js +2 -2
- package/dist/history-restorer.js.map +1 -1
- package/dist/history.d.ts +1 -1
- package/dist/hooks/audit-emitter.d.ts +73 -0
- package/dist/hooks/audit-emitter.d.ts.map +1 -0
- package/dist/hooks/audit-emitter.js +13 -0
- package/dist/hooks/audit-emitter.js.map +1 -0
- package/dist/hooks/context.d.ts +78 -0
- package/dist/hooks/context.d.ts.map +1 -0
- package/dist/hooks/context.js +56 -0
- package/dist/hooks/context.js.map +1 -0
- package/dist/hooks/contribution.d.ts +90 -0
- package/dist/hooks/contribution.d.ts.map +1 -0
- package/dist/hooks/contribution.js +2 -0
- package/dist/hooks/contribution.js.map +1 -0
- package/dist/hooks/dispatch.d.ts +30 -0
- package/dist/hooks/dispatch.d.ts.map +1 -0
- package/dist/hooks/dispatch.js +252 -0
- package/dist/hooks/dispatch.js.map +1 -0
- package/dist/hooks/errors.d.ts +100 -0
- package/dist/hooks/errors.d.ts.map +1 -0
- package/dist/hooks/errors.js +103 -0
- package/dist/hooks/errors.js.map +1 -0
- package/dist/hooks/index.d.ts +15 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/registry.d.ts +53 -0
- package/dist/hooks/registry.d.ts.map +1 -0
- package/dist/hooks/registry.js +139 -0
- package/dist/hooks/registry.js.map +1 -0
- package/dist/hooks/storage.d.ts +43 -0
- package/dist/hooks/storage.d.ts.map +1 -0
- package/dist/hooks/storage.js +2 -0
- package/dist/hooks/storage.js.map +1 -0
- package/dist/hooks/types.d.ts +324 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +2 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +26 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -5
- package/dist/index.js.map +1 -1
- package/dist/locale.d.ts +5 -1
- package/dist/locale.d.ts.map +1 -1
- package/dist/locale.js +6 -2
- package/dist/locale.js.map +1 -1
- package/dist/manifest-save.d.ts +255 -0
- package/dist/manifest-save.d.ts.map +1 -0
- package/dist/manifest-save.js +260 -0
- package/dist/manifest-save.js.map +1 -0
- package/dist/manifest.d.ts +1 -2
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +43 -44
- package/dist/manifest.js.map +1 -1
- package/dist/node-floor.d.ts +3 -0
- package/dist/node-floor.d.ts.map +1 -0
- package/dist/node-floor.js +3 -0
- package/dist/node-floor.js.map +1 -0
- package/dist/pages/create.d.ts +103 -0
- package/dist/pages/create.d.ts.map +1 -0
- package/dist/pages/create.js +117 -0
- package/dist/pages/create.js.map +1 -0
- package/dist/pages/publish.d.ts +59 -0
- package/dist/pages/publish.d.ts.map +1 -0
- package/dist/pages/publish.js +78 -0
- package/dist/pages/publish.js.map +1 -0
- package/dist/pages/save.d.ts +97 -0
- package/dist/pages/save.d.ts.map +1 -0
- package/dist/pages/save.js +138 -0
- package/dist/pages/save.js.map +1 -0
- package/dist/providers/factories.d.ts +65 -0
- package/dist/providers/factories.d.ts.map +1 -0
- package/dist/providers/factories.js +189 -0
- package/dist/providers/factories.js.map +1 -0
- package/dist/publish-item.d.ts +225 -0
- package/dist/publish-item.d.ts.map +1 -0
- package/dist/publish-item.js +210 -0
- package/dist/publish-item.js.map +1 -0
- package/dist/publish-rendered.d.ts.map +1 -1
- package/dist/publish-rendered.js +75 -6
- package/dist/publish-rendered.js.map +1 -1
- package/dist/publish-renderers.d.ts +132 -0
- package/dist/publish-renderers.d.ts.map +1 -0
- package/dist/publish-renderers.js +240 -0
- package/dist/publish-renderers.js.map +1 -0
- package/dist/publish-run.d.ts +223 -0
- package/dist/publish-run.d.ts.map +1 -0
- package/dist/publish-run.js +307 -0
- package/dist/publish-run.js.map +1 -0
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +1 -10
- package/dist/publish.js.map +1 -1
- package/dist/render-for-analysis.d.ts +24 -0
- package/dist/render-for-analysis.d.ts.map +1 -0
- package/dist/render-for-analysis.js +146 -0
- package/dist/render-for-analysis.js.map +1 -0
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +47 -23
- package/dist/resolver.js.map +1 -1
- package/dist/runtime/archive-marker.d.ts +62 -0
- package/dist/runtime/archive-marker.d.ts.map +1 -0
- package/dist/runtime/archive-marker.js +88 -0
- package/dist/runtime/archive-marker.js.map +1 -0
- package/dist/runtime/capability-gap-warnings.d.ts +42 -0
- package/dist/runtime/capability-gap-warnings.d.ts.map +1 -0
- package/dist/runtime/capability-gap-warnings.js +28 -0
- package/dist/runtime/capability-gap-warnings.js.map +1 -0
- package/dist/runtime/redirects-emit.d.ts +93 -0
- package/dist/runtime/redirects-emit.d.ts.map +1 -0
- package/dist/runtime/redirects-emit.js +89 -0
- package/dist/runtime/redirects-emit.js.map +1 -0
- package/dist/runtime/runtime-capabilities.d.ts +79 -0
- package/dist/runtime/runtime-capabilities.d.ts.map +1 -0
- package/dist/runtime/runtime-capabilities.js +60 -0
- package/dist/runtime/runtime-capabilities.js.map +1 -0
- package/dist/save-etag.d.ts +69 -0
- package/dist/save-etag.d.ts.map +1 -0
- package/dist/save-etag.js +118 -0
- package/dist/save-etag.js.map +1 -0
- package/dist/site-loader.d.ts +42 -4
- package/dist/site-loader.d.ts.map +1 -1
- package/dist/site-loader.js +27 -8
- package/dist/site-loader.js.map +1 -1
- package/dist/targets.d.ts +21 -12
- package/dist/targets.d.ts.map +1 -1
- package/dist/targets.js +27 -95
- package/dist/targets.js.map +1 -1
- package/dist/testing/admin-cache-contract.d.ts +52 -0
- package/dist/testing/admin-cache-contract.d.ts.map +1 -0
- package/dist/testing/admin-cache-contract.js +203 -0
- package/dist/testing/admin-cache-contract.js.map +1 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +11 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/transforms/factories.d.ts +16 -0
- package/dist/transforms/factories.d.ts.map +1 -0
- package/dist/transforms/factories.js +18 -0
- package/dist/transforms/factories.js.map +1 -0
- package/dist/transforms/index.d.ts +10 -17
- package/dist/transforms/index.d.ts.map +1 -1
- package/dist/transforms/index.js +4 -28
- package/dist/transforms/index.js.map +1 -1
- package/dist/transforms/sharp.d.ts +15 -1
- package/dist/transforms/sharp.d.ts.map +1 -1
- package/dist/transforms/sharp.js +34 -20
- package/dist/transforms/sharp.js.map +1 -1
- package/dist/types.d.ts +379 -52
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +20 -1
- package/dist/types.js.map +1 -1
- package/dist/validation/alt-required-walker.d.ts +27 -0
- package/dist/validation/alt-required-walker.d.ts.map +1 -0
- package/dist/validation/alt-required-walker.js +108 -0
- package/dist/validation/alt-required-walker.js.map +1 -0
- package/dist/validation/default-registry.d.ts +12 -0
- package/dist/validation/default-registry.d.ts.map +1 -0
- package/dist/validation/default-registry.js +55 -0
- package/dist/validation/default-registry.js.map +1 -0
- package/dist/validation/publish-audit.d.ts +44 -0
- package/dist/validation/publish-audit.d.ts.map +1 -0
- package/dist/validation/publish-audit.js +64 -0
- package/dist/validation/publish-audit.js.map +1 -0
- package/dist/validation/registry.d.ts +23 -0
- package/dist/validation/registry.d.ts.map +1 -0
- package/dist/validation/registry.js +15 -0
- package/dist/validation/registry.js.map +1 -0
- package/dist/validation/save-delta.d.ts +46 -0
- package/dist/validation/save-delta.d.ts.map +1 -0
- package/dist/validation/save-delta.js +57 -0
- package/dist/validation/save-delta.js.map +1 -0
- package/dist/validation/scanner.d.ts +91 -0
- package/dist/validation/scanner.d.ts.map +1 -0
- package/dist/validation/scanner.js +327 -0
- package/dist/validation/scanner.js.map +1 -0
- package/dist/validation/template-impact.d.ts +52 -0
- package/dist/validation/template-impact.d.ts.map +1 -0
- package/dist/validation/template-impact.js +53 -0
- package/dist/validation/template-impact.js.map +1 -0
- package/dist/validation/types.d.ts +123 -0
- package/dist/validation/types.d.ts.map +1 -0
- package/dist/validation/types.js +7 -0
- package/dist/validation/types.js.map +1 -0
- package/dist/validation/validators/accessibility.d.ts +3 -0
- package/dist/validation/validators/accessibility.d.ts.map +1 -0
- package/dist/validation/validators/accessibility.js +106 -0
- package/dist/validation/validators/accessibility.js.map +1 -0
- package/dist/validation/validators/aliasof-points-to-archived.d.ts +40 -0
- package/dist/validation/validators/aliasof-points-to-archived.d.ts.map +1 -0
- package/dist/validation/validators/aliasof-points-to-archived.js +34 -0
- package/dist/validation/validators/aliasof-points-to-archived.js.map +1 -0
- package/dist/validation/validators/alt-required.d.ts +3 -0
- package/dist/validation/validators/alt-required.d.ts.map +1 -0
- package/dist/validation/validators/alt-required.js +118 -0
- package/dist/validation/validators/alt-required.js.map +1 -0
- package/dist/validation/validators/archive-not-supported-on-target.d.ts +3 -0
- package/dist/validation/validators/archive-not-supported-on-target.d.ts.map +1 -0
- package/dist/validation/validators/archive-not-supported-on-target.js +38 -0
- package/dist/validation/validators/archive-not-supported-on-target.js.map +1 -0
- package/dist/validation/validators/broken-links.d.ts +3 -0
- package/dist/validation/validators/broken-links.d.ts.map +1 -0
- package/dist/validation/validators/broken-links.js +190 -0
- package/dist/validation/validators/broken-links.js.map +1 -0
- package/dist/validation/validators/circular-alias.d.ts +36 -0
- package/dist/validation/validators/circular-alias.d.ts.map +1 -0
- package/dist/validation/validators/circular-alias.js +63 -0
- package/dist/validation/validators/circular-alias.js.map +1 -0
- package/dist/validation/validators/circular-fragment.d.ts +15 -0
- package/dist/validation/validators/circular-fragment.d.ts.map +1 -0
- package/dist/validation/validators/circular-fragment.js +97 -0
- package/dist/validation/validators/circular-fragment.js.map +1 -0
- package/dist/validation/validators/dangling-alias.d.ts +38 -0
- package/dist/validation/validators/dangling-alias.d.ts.map +1 -0
- package/dist/validation/validators/dangling-alias.js +31 -0
- package/dist/validation/validators/dangling-alias.js.map +1 -0
- package/dist/validation/validators/deploy-target-type-supported.d.ts +3 -0
- package/dist/validation/validators/deploy-target-type-supported.d.ts.map +1 -0
- package/dist/validation/validators/deploy-target-type-supported.js +32 -0
- package/dist/validation/validators/deploy-target-type-supported.js.map +1 -0
- package/dist/validation/validators/dynamic-route-conflict.d.ts +18 -0
- package/dist/validation/validators/dynamic-route-conflict.d.ts.map +1 -0
- package/dist/validation/validators/dynamic-route-conflict.js +80 -0
- package/dist/validation/validators/dynamic-route-conflict.js.map +1 -0
- package/dist/validation/validators/html-validity.d.ts +3 -0
- package/dist/validation/validators/html-validity.d.ts.map +1 -0
- package/dist/validation/validators/html-validity.js +89 -0
- package/dist/validation/validators/html-validity.js.map +1 -0
- package/dist/validation/validators/orphaned-locale-file.d.ts +21 -0
- package/dist/validation/validators/orphaned-locale-file.d.ts.map +1 -0
- package/dist/validation/validators/orphaned-locale-file.js +84 -0
- package/dist/validation/validators/orphaned-locale-file.js.map +1 -0
- package/dist/validation/validators/referenced-archived-without-alias.d.ts +3 -0
- package/dist/validation/validators/referenced-archived-without-alias.d.ts.map +1 -0
- package/dist/validation/validators/referenced-archived-without-alias.js +65 -0
- package/dist/validation/validators/referenced-archived-without-alias.js.map +1 -0
- package/dist/validation/validators/referenced-asset-exists.d.ts +13 -0
- package/dist/validation/validators/referenced-asset-exists.d.ts.map +1 -0
- package/dist/validation/validators/referenced-asset-exists.js +80 -0
- package/dist/validation/validators/referenced-asset-exists.js.map +1 -0
- package/dist/validation/validators/referenced-fragment-exists.d.ts +9 -0
- package/dist/validation/validators/referenced-fragment-exists.d.ts.map +1 -0
- package/dist/validation/validators/referenced-fragment-exists.js +52 -0
- package/dist/validation/validators/referenced-fragment-exists.js.map +1 -0
- package/dist/validation/validators/referenced-template-exists.d.ts +10 -0
- package/dist/validation/validators/referenced-template-exists.d.ts.map +1 -0
- package/dist/validation/validators/referenced-template-exists.js +74 -0
- package/dist/validation/validators/referenced-template-exists.js.map +1 -0
- package/dist/validation/validators/schema-conformance.d.ts +17 -0
- package/dist/validation/validators/schema-conformance.d.ts.map +1 -0
- package/dist/validation/validators/schema-conformance.js +94 -0
- package/dist/validation/validators/schema-conformance.js.map +1 -0
- package/dist/validation/validators/target-deploy-coverage.d.ts +3 -0
- package/dist/validation/validators/target-deploy-coverage.d.ts.map +1 -0
- package/dist/validation/validators/target-deploy-coverage.js +37 -0
- package/dist/validation/validators/target-deploy-coverage.js.map +1 -0
- package/dist/validation/validators/unused-fragment.d.ts +16 -0
- package/dist/validation/validators/unused-fragment.d.ts.map +1 -0
- package/dist/validation/validators/unused-fragment.js +86 -0
- package/dist/validation/validators/unused-fragment.js.map +1 -0
- package/package.json +54 -31
- package/admin-dist/assets/index-BO9-CXmW.css +0 -1
- package/admin-dist/assets/index-Ufu8zZH_.js +0 -668
- package/admin-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
- package/admin-dist/assets/vendor-rjsf-HKBAjOmQ.js +0 -32
- package/admin-dist/assets/vendor-tiptap-IyO99U4R.js +0 -142
- package/admin-dist/assets/vendor-vue-D3wBSmDf.js +0 -1
- package/dist/publish-locale.d.ts +0 -44
- package/dist/publish-locale.d.ts.map +0 -1
- package/dist/publish-locale.js +0 -103
- package/dist/publish-locale.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -5,14 +5,16 @@ import { serve } from '@hono/node-server';
|
|
|
5
5
|
import { serveStatic } from '@hono/node-server/serve-static';
|
|
6
6
|
import { Hono } from 'hono';
|
|
7
7
|
import { streamSSE } from 'hono/streaming';
|
|
8
|
-
import yaml from 'js-yaml';
|
|
9
8
|
import { loadSite } from '../site-loader.js';
|
|
10
9
|
import { resolvePage } from '../resolver.js';
|
|
11
10
|
import { renderPage } from '../renderer.js';
|
|
12
11
|
import { createFilesystemProvider } from '../providers/filesystem.js';
|
|
13
12
|
import { invalidateTemplate, invalidateAllTemplates } from '../template-loader.js';
|
|
13
|
+
import { REQUIRED_NODE_FLOOR } from '../node-floor.js';
|
|
14
|
+
import { isTemplateEventRecent, TEMPLATE_RECENT_CHANGE_WINDOW_MS } from './dev-template-watcher.js';
|
|
14
15
|
import { getEnvironment, getType, isEditable } from '../types.js';
|
|
15
|
-
import { createAdminApp } from '../admin-api/index.js';
|
|
16
|
+
import { buildHooksRegistry, createAdminApp } from '../admin-api/index.js';
|
|
17
|
+
import { parseValidateFlags } from './validate-flags.js';
|
|
16
18
|
// ANSI color helpers — no dependency, suppressed when NO_COLOR or CI
|
|
17
19
|
const noColor = !!process.env.NO_COLOR || !process.stdout.isTTY;
|
|
18
20
|
const c = {
|
|
@@ -27,6 +29,39 @@ const c = {
|
|
|
27
29
|
};
|
|
28
30
|
const args = process.argv.slice(2);
|
|
29
31
|
const command = args[0];
|
|
32
|
+
/**
|
|
33
|
+
* Load a site manifest from `siteDir` via the TS config loader. Recognizes
|
|
34
|
+
* `site.config.ts`, `site.config.js`, and `site.config.mjs`.
|
|
35
|
+
*
|
|
36
|
+
* Returns null when no config file is found.
|
|
37
|
+
*/
|
|
38
|
+
async function loadSiteManifestForCli(siteDir) {
|
|
39
|
+
const tsConfigCandidates = ['site.config.ts', 'site.config.js', 'site.config.mjs'];
|
|
40
|
+
if (!tsConfigCandidates.some(f => existsSync(join(siteDir, f))))
|
|
41
|
+
return null;
|
|
42
|
+
const { loadSiteConfig, siteConfigToManifest } = await import('../config/loader.js');
|
|
43
|
+
const loaded = await loadSiteConfig(siteDir);
|
|
44
|
+
if (!loaded)
|
|
45
|
+
return null;
|
|
46
|
+
return siteConfigToManifest(loaded.config);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read `admin.hooks` factory contributions from a site manifest.
|
|
50
|
+
*
|
|
51
|
+
* The manifest types `admin?` as a loose record (`Record<string, unknown>`)
|
|
52
|
+
* to keep `SiteManifest` stable across foundation additions; each foundation
|
|
53
|
+
* narrow-types its own block at the consumption site. For hooks the runtime
|
|
54
|
+
* shape is `ReadonlyArray<HookContribution>` (per design-hooks.md
|
|
55
|
+
* "Registration"); we accept it as `unknown` here and cast at the boundary.
|
|
56
|
+
*
|
|
57
|
+
* Returns undefined when the field is absent or empty.
|
|
58
|
+
*/
|
|
59
|
+
function readHookContributions(manifest) {
|
|
60
|
+
const hooks = manifest?.admin?.hooks;
|
|
61
|
+
if (!Array.isArray(hooks) || hooks.length === 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
return hooks;
|
|
64
|
+
}
|
|
30
65
|
// Served to /admin/* requests during dev-server startup before Vite middleware
|
|
31
66
|
// is attached. Polls /admin/ping every 500ms and reloads when the admin becomes
|
|
32
67
|
// reachable. See #132 and cli/index.ts for why this is needed.
|
|
@@ -122,6 +157,84 @@ function detectProjectRoot(siteDir) {
|
|
|
122
157
|
// Fallback — use siteDir (templates/ may not exist yet)
|
|
123
158
|
return siteDir;
|
|
124
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Resolve the siteDir for any CLI command by extracting the site
|
|
162
|
+
* positional from the parsed args per the command's documented
|
|
163
|
+
* positional layout. Pure fs walk; no config eval — safe to call
|
|
164
|
+
* before env-load.
|
|
165
|
+
*
|
|
166
|
+
* Single dispatch table so env-load happens exactly once at the top
|
|
167
|
+
* of `main()`, before any config-eval can read process.env.
|
|
168
|
+
*/
|
|
169
|
+
async function resolveSiteDirForCommand(cmd, parsed) {
|
|
170
|
+
// Commands whose site positional sits at a fixed index.
|
|
171
|
+
if (cmd === 'build')
|
|
172
|
+
return resolveSiteDir(parsed.positional[0]);
|
|
173
|
+
if (cmd === 'dev' || cmd === 'validate' || cmd === 'admin')
|
|
174
|
+
return resolveSiteDir(parsed.positional[0]);
|
|
175
|
+
// `rollback <rev> [target|site] [site]`: site is positional[1] when
|
|
176
|
+
// site-shaped, else positional[2].
|
|
177
|
+
if (cmd === 'rollback') {
|
|
178
|
+
const second = parsed.positional[1];
|
|
179
|
+
const third = parsed.positional[2];
|
|
180
|
+
const secondIsSite = second && (second.includes('/') || hasSiteConfig(resolve(second)));
|
|
181
|
+
return resolveSiteDir(secondIsSite ? second : third);
|
|
182
|
+
}
|
|
183
|
+
// `publish|serve|deploy|history|undo [target|site] [site]`: site is
|
|
184
|
+
// positional[0] when site-shaped, else positional[1].
|
|
185
|
+
if (cmd === 'publish' || cmd === 'serve' || cmd === 'deploy' || cmd === 'history' || cmd === 'undo') {
|
|
186
|
+
const first = parsed.positional[0];
|
|
187
|
+
const firstIsSite = first && (first.includes('/') || hasSiteConfig(resolve(first)));
|
|
188
|
+
return resolveSiteDir(firstIsSite ? first : parsed.positional[1]);
|
|
189
|
+
}
|
|
190
|
+
// `translate <item> --to <locale> [target]` — no explicit site arg.
|
|
191
|
+
if (cmd === 'translate')
|
|
192
|
+
return resolveSiteDir(undefined);
|
|
193
|
+
// `assets <subcmd> [args...] [target] [site]`: layout differs by subcmd.
|
|
194
|
+
if (cmd === 'assets') {
|
|
195
|
+
const subcmd = parsed.positional[0];
|
|
196
|
+
const sitePositional = subcmd === 'info' ? parsed.positional[3] : parsed.positional[2];
|
|
197
|
+
return resolveSiteDir(sitePositional);
|
|
198
|
+
}
|
|
199
|
+
// `archive <subcmd> [args...] [target] [site]`: site at trailingStart+1.
|
|
200
|
+
if (cmd === 'archive') {
|
|
201
|
+
const subcmd = parsed.positional[0];
|
|
202
|
+
const requiredArgs = subcmd === 'rename' ? 2 : subcmd === 'purge' || subcmd === 'restore' ? 1 : 0;
|
|
203
|
+
return resolveSiteDir(parsed.positional[1 + requiredArgs + 1]);
|
|
204
|
+
}
|
|
205
|
+
// Unknown command → auto-detect (main's switch will reject with a
|
|
206
|
+
// clear "Unknown command" error after env-load runs).
|
|
207
|
+
return resolveSiteDir(undefined);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Load `.env` + `.env.local` from project root and site dir, in that
|
|
211
|
+
* order, so per-site secrets override project-wide defaults. Must run
|
|
212
|
+
* BEFORE the first config-eval (the site's storage / deploy / etc.
|
|
213
|
+
* factories read `process.env` synchronously at construction). Skipped
|
|
214
|
+
* in CI — CI runners inject env directly.
|
|
215
|
+
*
|
|
216
|
+
* Per Q1 of CLI env-loading discipline: site-local `.env` is honored
|
|
217
|
+
* (gazetta.studio has its own `.env` with R2/Cloudflare creds; this
|
|
218
|
+
* function ensures factories see them at config-eval time).
|
|
219
|
+
*/
|
|
220
|
+
function loadEnvFiles(siteDir) {
|
|
221
|
+
if (process.env.CI)
|
|
222
|
+
return;
|
|
223
|
+
const projectRoot = detectProjectRoot(siteDir);
|
|
224
|
+
const envDirs = projectRoot !== siteDir ? [projectRoot, siteDir] : [siteDir];
|
|
225
|
+
for (const dir of envDirs) {
|
|
226
|
+
for (const name of ['.env', '.env.local']) {
|
|
227
|
+
const envPath = join(dir, name);
|
|
228
|
+
if (!existsSync(envPath))
|
|
229
|
+
continue;
|
|
230
|
+
for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
|
|
231
|
+
const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/);
|
|
232
|
+
if (m && !(m[1] in process.env))
|
|
233
|
+
process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
125
238
|
function printHelp() {
|
|
126
239
|
console.log(`
|
|
127
240
|
gazetta - Stateless CMS for composable websites
|
|
@@ -147,6 +260,14 @@ function printHelp() {
|
|
|
147
260
|
Show full detail (variants, overrides, refs) for one asset
|
|
148
261
|
gazetta assets reindex [target] [site]
|
|
149
262
|
Rebuild the asset-refs sidecar index from manifests
|
|
263
|
+
gazetta archive list [--kind=page|fragment] [target] [site]
|
|
264
|
+
List archived items
|
|
265
|
+
gazetta archive purge <name> [--kind=...] [--force] [target] [site]
|
|
266
|
+
Permanently delete an archive
|
|
267
|
+
gazetta archive restore <name> [--kind=...] [target] [site]
|
|
268
|
+
Unarchive an item (back to live)
|
|
269
|
+
gazetta archive rename <oldname> <newname> [--kind=...] [--no-keep-alias] [target] [site]
|
|
270
|
+
Rename a live item; keeps the redirect by default
|
|
150
271
|
gazetta help Show this help message
|
|
151
272
|
|
|
152
273
|
Options:
|
|
@@ -160,7 +281,7 @@ function printHelp() {
|
|
|
160
281
|
Site is auto-detected from sites/ directory. If multiple sites exist,
|
|
161
282
|
you'll be prompted to choose (or pass it as an argument).
|
|
162
283
|
|
|
163
|
-
Target is auto-detected as the first target in site.
|
|
284
|
+
Target is auto-detected as the first target in site.config.ts. If multiple
|
|
164
285
|
targets exist, you'll be prompted to choose (or pass it as an argument).
|
|
165
286
|
|
|
166
287
|
Examples:
|
|
@@ -213,24 +334,30 @@ function parseArgs(input) {
|
|
|
213
334
|
* For commands like `publish` and `serve`, the first positional is the target
|
|
214
335
|
* and the second is the site.
|
|
215
336
|
*/
|
|
337
|
+
/** Returns true if the directory contains a Gazetta site config (`site.config.ts`/.js/.mjs). */
|
|
338
|
+
function hasSiteConfig(dir) {
|
|
339
|
+
return (existsSync(join(dir, 'site.config.ts')) ||
|
|
340
|
+
existsSync(join(dir, 'site.config.js')) ||
|
|
341
|
+
existsSync(join(dir, 'site.config.mjs')));
|
|
342
|
+
}
|
|
216
343
|
async function resolveSiteDir(positionalSite) {
|
|
217
344
|
// Explicit site dir provided
|
|
218
345
|
if (positionalSite) {
|
|
219
346
|
const dir = resolve(positionalSite);
|
|
220
|
-
if (
|
|
347
|
+
if (hasSiteConfig(dir))
|
|
221
348
|
return dir;
|
|
222
349
|
// Maybe it's a site name under sites/
|
|
223
350
|
const sitesSubdir = resolve('sites', positionalSite);
|
|
224
|
-
if (
|
|
351
|
+
if (hasSiteConfig(sitesSubdir))
|
|
225
352
|
return sitesSubdir;
|
|
226
353
|
// Maybe it's a project root with sites/
|
|
227
354
|
const mainSite = resolve(dir, 'sites/main');
|
|
228
|
-
if (
|
|
355
|
+
if (hasSiteConfig(mainSite))
|
|
229
356
|
return mainSite;
|
|
230
357
|
return dir; // let loadSite produce a clear error
|
|
231
358
|
}
|
|
232
359
|
// Auto-detect: check current dir first
|
|
233
|
-
if (
|
|
360
|
+
if (hasSiteConfig(resolve('.')))
|
|
234
361
|
return resolve('.');
|
|
235
362
|
// Check sites/ directory
|
|
236
363
|
const sitesDir = resolve('sites');
|
|
@@ -238,7 +365,7 @@ async function resolveSiteDir(positionalSite) {
|
|
|
238
365
|
const { readdirSync, statSync } = await import('node:fs');
|
|
239
366
|
const sites = readdirSync(sitesDir).filter(name => {
|
|
240
367
|
const dir = join(sitesDir, name);
|
|
241
|
-
return statSync(dir).isDirectory() &&
|
|
368
|
+
return statSync(dir).isDirectory() && hasSiteConfig(dir);
|
|
242
369
|
});
|
|
243
370
|
if (sites.length === 1)
|
|
244
371
|
return join(sitesDir, sites[0]);
|
|
@@ -270,11 +397,10 @@ async function resolveSiteDir(positionalSite) {
|
|
|
270
397
|
async function resolveTarget(positionalTarget, siteDir) {
|
|
271
398
|
if (positionalTarget)
|
|
272
399
|
return positionalTarget;
|
|
273
|
-
const
|
|
274
|
-
if (!
|
|
400
|
+
const manifest = await loadSiteManifestForCli(siteDir);
|
|
401
|
+
if (!manifest)
|
|
275
402
|
return undefined;
|
|
276
|
-
const
|
|
277
|
-
const targets = Object.keys(siteYaml.targets ?? {});
|
|
403
|
+
const targets = Object.keys(manifest.targets ?? {});
|
|
278
404
|
if (targets.length <= 1)
|
|
279
405
|
return targets[0]; // auto-select if 0 or 1
|
|
280
406
|
if (process.env.CI) {
|
|
@@ -293,13 +419,27 @@ async function resolveTarget(positionalTarget, siteDir) {
|
|
|
293
419
|
async function runInit(dir) {
|
|
294
420
|
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
295
421
|
const target = resolve(dir);
|
|
296
|
-
if (existsSync(join(target, 'sites')) || existsSync(join(target, 'site.
|
|
422
|
+
if (existsSync(join(target, 'sites')) || existsSync(join(target, 'site.config.ts'))) {
|
|
297
423
|
console.error(`\n Error: project already exists in ${target}\n`);
|
|
298
424
|
process.exit(1);
|
|
299
425
|
}
|
|
300
426
|
const name = target.split('/').pop() ?? 'my-site';
|
|
301
427
|
const files = {
|
|
302
|
-
'sites/main/site.
|
|
428
|
+
'sites/main/site.config.ts': `import { defineSite, filesystemStorage } from 'gazetta'
|
|
429
|
+
|
|
430
|
+
export default defineSite({
|
|
431
|
+
name: '${name}',
|
|
432
|
+
version: '1.0.0',
|
|
433
|
+
systemPages: ['404'],
|
|
434
|
+
targets: {
|
|
435
|
+
local: {
|
|
436
|
+
storage: filesystemStorage(),
|
|
437
|
+
// environment: 'local' (default); editable: true (default for local);
|
|
438
|
+
// filesystemStorage() defaults path to ./targets/local
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
})
|
|
442
|
+
`,
|
|
303
443
|
'templates/page-layout/index.ts': `import { z } from 'zod'
|
|
304
444
|
import type { TemplateFunction } from 'gazetta'
|
|
305
445
|
|
|
@@ -419,7 +559,7 @@ export default template
|
|
|
419
559
|
name,
|
|
420
560
|
private: true,
|
|
421
561
|
type: 'module',
|
|
422
|
-
engines: { node:
|
|
562
|
+
engines: { node: REQUIRED_NODE_FLOOR },
|
|
423
563
|
scripts: { dev: 'gazetta dev' },
|
|
424
564
|
dependencies: { gazetta: '*', react: '^19.0.0', 'react-dom': '^19.0.0', zod: '^4.0.0' },
|
|
425
565
|
}, null, 2) + '\n',
|
|
@@ -437,7 +577,7 @@ export default template
|
|
|
437
577
|
` ${c.dim('pages/home/')} ${c.dim('home page with hero + intro')}\n` +
|
|
438
578
|
` ${c.dim('pages/404/')} ${c.dim('error page')}\n` +
|
|
439
579
|
` ${c.dim('fragments/header/')} ${c.dim('shared header nav')}\n` +
|
|
440
|
-
` ${c.dim('site.
|
|
580
|
+
` ${c.dim('site.config.ts')} ${c.dim('site config + local target')}\n` +
|
|
441
581
|
`${c.bold('package.json')}`, `Created ${c.green(name)}/`);
|
|
442
582
|
// Run npm install
|
|
443
583
|
const s = spinner();
|
|
@@ -456,7 +596,7 @@ export default template
|
|
|
456
596
|
async function runPublish(siteDir, targetName, opts = {}) {
|
|
457
597
|
const projectRoot = detectProjectRoot(siteDir);
|
|
458
598
|
const templatesDir = join(projectRoot, 'templates');
|
|
459
|
-
// Source comes from the default editable target in site.
|
|
599
|
+
// Source comes from the default editable target in site.config.ts.
|
|
460
600
|
const { buildSourceContext } = await import('./bootstrap.js');
|
|
461
601
|
let source, manifest, targetConfigs;
|
|
462
602
|
try {
|
|
@@ -471,7 +611,7 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
471
611
|
const site = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest });
|
|
472
612
|
const siteYaml = manifest;
|
|
473
613
|
if (!siteYaml.targets || Object.keys(siteYaml.targets).length === 0) {
|
|
474
|
-
console.error(`\n Error: no targets configured in site.
|
|
614
|
+
console.error(`\n Error: no targets configured in site.config.ts`);
|
|
475
615
|
process.exit(1);
|
|
476
616
|
}
|
|
477
617
|
// Determine which targets to publish to
|
|
@@ -484,19 +624,23 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
484
624
|
}
|
|
485
625
|
// Initialize targets
|
|
486
626
|
const { createTargetRegistry } = await import('../targets.js');
|
|
487
|
-
const targets = await createTargetRegistry(Object.fromEntries(targetNames.map(n => [n, siteYaml.targets[n]]))
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
//
|
|
627
|
+
const targets = await createTargetRegistry(Object.fromEntries(targetNames.map(n => [n, siteYaml.targets[n]])));
|
|
628
|
+
// Per Cut 6 cutover: per-target render loop now goes through publishRun
|
|
629
|
+
// (imported below at use site). publishRun owns: per-item dispatch +
|
|
630
|
+
// asset publish + dep indices + site manifest. CLI keeps: incremental
|
|
631
|
+
// skip via compareTargets, locale fan-out into ItemRefs, console
|
|
632
|
+
// output via onProgress, sitemap+robots+redirects+purge post-loop.
|
|
633
|
+
const { scanTemplates, reportTemplateErrors } = await import('../templates-scan.js');
|
|
634
|
+
// Validate templates once for this publish run. publishRun consumes
|
|
635
|
+
// templateInfos directly and computes hashes internally (per Cut 5
|
|
636
|
+
// expansion); CLI only needs to scan + bail on invalid templates
|
|
637
|
+
// before launching the run.
|
|
493
638
|
const templateInfos = await scanTemplates(templatesDir, projectRoot);
|
|
494
639
|
const invalid = reportTemplateErrors(templateInfos);
|
|
495
640
|
if (invalid > 0) {
|
|
496
641
|
console.error(`\n ${c.red('✗')} Refusing to publish with invalid templates.`);
|
|
497
642
|
process.exit(1);
|
|
498
643
|
}
|
|
499
|
-
const templateHashes = templateHashesFrom(templateInfos);
|
|
500
644
|
console.log();
|
|
501
645
|
console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('publish')} ${c.dim(site.manifest.name)}`);
|
|
502
646
|
console.log();
|
|
@@ -504,6 +648,24 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
504
648
|
console.log(` ${c.dim('┃')} Fragments ${c.dim([...site.fragments.keys()].join(', '))}`);
|
|
505
649
|
console.log(` ${c.dim('┃')} Targets ${targetNames.join(', ')}`);
|
|
506
650
|
console.log();
|
|
651
|
+
// Cutover (Cut 6 of publish-pipeline-extraction): per-target render
|
|
652
|
+
// loop now delegates to `publishRun` for the per-target × per-item
|
|
653
|
+
// dispatch + asset publish + dep indices + site manifest. CLI keeps
|
|
654
|
+
// ownership of:
|
|
655
|
+
// - target registry init + per-target loop (so target-init failures
|
|
656
|
+
// can short-circuit just that target)
|
|
657
|
+
// - incremental skip via compareTargets (publishRun is filter-blind;
|
|
658
|
+
// CLI pre-filters ItemRefs[] based on per-locale unchanged sets)
|
|
659
|
+
// - locale fan-out (CLI expands page name → ItemRef[] including all
|
|
660
|
+
// locale variants supported by the target)
|
|
661
|
+
// - console output (onProgress callback formats human-readable lines)
|
|
662
|
+
// - sitemap / robots.txt / _redirects / cache purge (post-publishRun
|
|
663
|
+
// side effects on each target — kept inline)
|
|
664
|
+
//
|
|
665
|
+
// publishRun owns: per-item mode dispatch, archive short-circuit,
|
|
666
|
+
// sidecar emit, asset publish, dep indices, site manifest emit.
|
|
667
|
+
const { publishRun } = await import('../publish-run.js');
|
|
668
|
+
const { createContentRoot: _createContentRoot } = await import('../content-root.js');
|
|
507
669
|
for (const name of targetNames) {
|
|
508
670
|
const targetStorage = targets.get(name);
|
|
509
671
|
if (!targetStorage) {
|
|
@@ -513,7 +675,6 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
513
675
|
const targetConfig = siteYaml.targets[name];
|
|
514
676
|
const { getType } = await import('../types.js');
|
|
515
677
|
const targetType = targetConfig ? getType(targetConfig) : 'static';
|
|
516
|
-
const isStatic = targetType === 'static';
|
|
517
678
|
console.log(` ${c.bold(name)} ${c.dim(`(${targetType})`)}`);
|
|
518
679
|
let totalFiles = 0;
|
|
519
680
|
let totalRemoved = 0;
|
|
@@ -535,117 +696,102 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
535
696
|
unchanged.add(item);
|
|
536
697
|
}
|
|
537
698
|
let skipped = 0;
|
|
538
|
-
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (assetResult.copiedAssets > 0) {
|
|
556
|
-
console.log(` ${c.green('✓')} ${assetResult.copiedAssets} asset(s), ${assetResult.copiedFiles} file(s)`);
|
|
557
|
-
}
|
|
558
|
-
totalFiles += assetResult.copiedFiles;
|
|
559
|
-
}
|
|
560
|
-
// SEO context for this target — built once, shared across all page renders.
|
|
561
|
-
const seo = {
|
|
562
|
-
siteName: site.manifest.name,
|
|
563
|
-
siteUrl: targetConfig?.siteUrl,
|
|
564
|
-
locale: site.manifest.locale,
|
|
565
|
-
defaultOgImage: site.manifest.defaultOgImage,
|
|
566
|
-
};
|
|
567
|
-
if (isStatic) {
|
|
568
|
-
// Static mode — fully assembled HTML, no fragments needed separately.
|
|
569
|
-
// Page hash must include fragment hashes so a fragment change
|
|
570
|
-
// invalidates every page that bakes it in (compareTargets uses the
|
|
571
|
-
// same combination on the local side).
|
|
572
|
-
const fragmentHashes = new Map();
|
|
573
|
-
for (const [fragName, frag] of site.fragments) {
|
|
574
|
-
fragmentHashes.set(fragName, hashManifest(frag, { templateHashes }));
|
|
699
|
+
// Build the per-target ItemRef list. CLI does the locale fan-out
|
|
700
|
+
// explicitly here (vs publishRun, which treats the input list
|
|
701
|
+
// verbatim). Each (page|fragment, locale) cell becomes one ItemRef
|
|
702
|
+
// unless the unchanged set covers it.
|
|
703
|
+
const targetLocales = targetConfig?.locales;
|
|
704
|
+
const itemRefs = [];
|
|
705
|
+
const skippedNames = new Set(); // for console output (item entirely skipped)
|
|
706
|
+
// Pages — default + per-locale variants, filtered by target.locales when set.
|
|
707
|
+
for (const [pageName] of site.pages) {
|
|
708
|
+
const pageLocales = site.pageLocales.get(pageName);
|
|
709
|
+
const localesForPage = [undefined];
|
|
710
|
+
if (pageLocales) {
|
|
711
|
+
for (const loc of pageLocales.locales.keys()) {
|
|
712
|
+
if (targetLocales && !targetLocales.includes(loc))
|
|
713
|
+
continue;
|
|
714
|
+
localesForPage.push(loc);
|
|
715
|
+
}
|
|
575
716
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
717
|
+
let added = 0;
|
|
718
|
+
for (const loc of localesForPage) {
|
|
719
|
+
const key = loc ? `pages/${pageName}:${loc}` : `pages/${pageName}`;
|
|
720
|
+
if (unchanged.has(key))
|
|
579
721
|
continue;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
722
|
+
itemRefs.push({ kind: 'page', name: pageName, locale: loc });
|
|
723
|
+
added++;
|
|
724
|
+
}
|
|
725
|
+
if (added === 0 && localesForPage.length > 0) {
|
|
726
|
+
skippedNames.add(`pages/${pageName}`);
|
|
727
|
+
skipped++;
|
|
585
728
|
}
|
|
586
729
|
}
|
|
587
|
-
|
|
588
|
-
|
|
730
|
+
// Fragments — only emitted as separate items for non-static targets
|
|
731
|
+
// (static bakes them into pages). For static, the publishRun spine
|
|
732
|
+
// still needs them in the site for page rendering (already loaded).
|
|
733
|
+
if (targetType !== 'static') {
|
|
589
734
|
for (const [fragName] of site.fragments) {
|
|
590
|
-
// Build per-locale unchanged set: null = default, 'fr' = French
|
|
591
|
-
const fragUnchanged = new Set();
|
|
592
|
-
if (unchanged.has(`fragments/${fragName}`))
|
|
593
|
-
fragUnchanged.add(null);
|
|
594
735
|
const fragLocales = site.fragmentLocales.get(fragName);
|
|
736
|
+
const localesForFrag = [undefined];
|
|
595
737
|
if (fragLocales) {
|
|
596
738
|
for (const loc of fragLocales.locales.keys()) {
|
|
597
|
-
if (
|
|
598
|
-
|
|
739
|
+
if (targetLocales && !targetLocales.includes(loc))
|
|
740
|
+
continue;
|
|
741
|
+
localesForFrag.push(loc);
|
|
599
742
|
}
|
|
600
743
|
}
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
|
|
744
|
+
let added = 0;
|
|
745
|
+
for (const loc of localesForFrag) {
|
|
746
|
+
const key = loc ? `fragments/${fragName}:${loc}` : `fragments/${fragName}`;
|
|
747
|
+
if (unchanged.has(key))
|
|
748
|
+
continue;
|
|
749
|
+
itemRefs.push({ kind: 'fragment', name: fragName, locale: loc });
|
|
750
|
+
added++;
|
|
751
|
+
}
|
|
752
|
+
if (added === 0 && localesForFrag.length > 0) {
|
|
753
|
+
skippedNames.add(`fragments/${fragName}`);
|
|
604
754
|
skipped++;
|
|
605
|
-
continue;
|
|
606
755
|
}
|
|
607
|
-
const { files, removed } = await publishFragmentAllLocales(fragName, sourceRoot, targetStorage, site, { templateHashes }, { templatesDir, targetLocales: targetConfig?.locales, unchangedLocales: fragUnchanged });
|
|
608
|
-
totalFiles += files;
|
|
609
|
-
totalRemoved += removed;
|
|
610
|
-
const skippedCount = fragUnchanged.size > 0 ? ` (${fragUnchanged.size} locale${fragUnchanged.size > 1 ? 's' : ''} skipped)` : '';
|
|
611
|
-
console.log(` ${c.green('✓')} @${fragName}${skippedCount}`);
|
|
612
756
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
757
|
+
}
|
|
758
|
+
// Delegate the per-item × per-target render loop + asset publish +
|
|
759
|
+
// dep indices + site manifest to publishRun. Single-target call;
|
|
760
|
+
// CLI loops targets itself so per-target init failures (storage
|
|
761
|
+
// missing) short-circuit just that target.
|
|
762
|
+
const runResult = await publishRun({
|
|
763
|
+
items: itemRefs,
|
|
764
|
+
targets: [name],
|
|
765
|
+
site,
|
|
766
|
+
sourceRoot: source.contentRoot,
|
|
767
|
+
siteManifest: manifest,
|
|
768
|
+
targetStorages: new Map([[name, targetStorage]]),
|
|
769
|
+
templateInfos,
|
|
770
|
+
onProgress: ev => {
|
|
771
|
+
if (ev.kind === 'item-done') {
|
|
772
|
+
if (ev.result.ok) {
|
|
773
|
+
const label = ev.result.kind === 'fragment' ? `@${ev.result.name}` : ev.result.name;
|
|
774
|
+
const localeMark = ev.result.locale ? c.dim(` (${ev.result.locale})`) : '';
|
|
775
|
+
console.log(` ${c.green('✓')} ${label}${localeMark}`);
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
const label = ev.result.kind === 'fragment' ? `@${ev.result.name}` : ev.result.name;
|
|
779
|
+
console.error(` ${c.red('✗')} ${label}: ${ev.result.code}`);
|
|
623
780
|
}
|
|
624
781
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
targetLocales: targetConfig?.locales,
|
|
635
|
-
unchangedLocales: pageUnchanged,
|
|
636
|
-
});
|
|
637
|
-
totalFiles += files;
|
|
638
|
-
totalRemoved += removed;
|
|
639
|
-
const skippedCount = pageUnchanged.size > 0 ? ` (${pageUnchanged.size} locale${pageUnchanged.size > 1 ? 's' : ''} skipped)` : '';
|
|
640
|
-
console.log(` ${c.green('✓')} ${pageName}${skippedCount}`);
|
|
782
|
+
},
|
|
783
|
+
});
|
|
784
|
+
// Aggregate counts from publishRun's per-target result.
|
|
785
|
+
const tr = runResult.targets.find(t => t.name === name);
|
|
786
|
+
if (tr) {
|
|
787
|
+
totalFiles += tr.filesWritten;
|
|
788
|
+
totalRemoved += tr.filesRemoved;
|
|
789
|
+
if (tr.failed) {
|
|
790
|
+
console.error(` ${c.red('✗')} target failed: ${tr.failureReason ?? 'all items failed'}`);
|
|
641
791
|
}
|
|
642
792
|
}
|
|
643
793
|
if (skipped > 0)
|
|
644
794
|
console.log(` ${c.dim(`· ${skipped} unchanged (skipped)`)}`);
|
|
645
|
-
// Site manifest + dep-sidecar indices
|
|
646
|
-
await publishSiteManifest(sourceRoot, targetStorage, site);
|
|
647
|
-
await publishDepIndices(sourceRoot, targetStorage, site);
|
|
648
|
-
totalFiles += 1;
|
|
649
795
|
// Sitemap + robots.txt — generated from target sidecars
|
|
650
796
|
const siteUrl = targetConfig?.siteUrl;
|
|
651
797
|
if (siteUrl) {
|
|
@@ -755,6 +901,36 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
755
901
|
console.log(` ${c.dim('· robots.txt')}`);
|
|
756
902
|
}
|
|
757
903
|
}
|
|
904
|
+
// Host-format redirects file (e.g. _redirects for Cloudflare/Netlify).
|
|
905
|
+
// Per design-soft-delete.md Q10: plain-static targets without a worker
|
|
906
|
+
// can't read the per-page HTML marker; this file is the host-glue
|
|
907
|
+
// that makes archived-page redirects work for them. Independent of
|
|
908
|
+
// siteUrl — the format determines whether to emit at all.
|
|
909
|
+
const redirectsFormat = targetConfig?.redirects?.format;
|
|
910
|
+
if (redirectsFormat && redirectsFormat !== 'none') {
|
|
911
|
+
const { emitRedirects } = await import('../runtime/redirects-emit.js');
|
|
912
|
+
const { allPageEntries, deriveRoute } = await import('../site-loader.js');
|
|
913
|
+
const archives = [];
|
|
914
|
+
for (const entry of allPageEntries(site)) {
|
|
915
|
+
if (entry.page.archived !== true)
|
|
916
|
+
continue;
|
|
917
|
+
// Locale variants share the parent's archive state in v1; the
|
|
918
|
+
// default-locale row is the canonical entry. Per-locale archive
|
|
919
|
+
// is reserved for a future cut (per `design-soft-delete.md`
|
|
920
|
+
// future directions).
|
|
921
|
+
if (entry.locale)
|
|
922
|
+
continue;
|
|
923
|
+
const from = deriveRoute(entry.name);
|
|
924
|
+
const to = entry.page.aliasOf ? deriveRoute(entry.page.aliasOf) : undefined;
|
|
925
|
+
archives.push(to !== undefined ? { from, to } : { from });
|
|
926
|
+
}
|
|
927
|
+
const result = emitRedirects(redirectsFormat, archives);
|
|
928
|
+
if (result && result.body !== '') {
|
|
929
|
+
await targetStorage.writeFile(result.filename, result.body);
|
|
930
|
+
totalFiles++;
|
|
931
|
+
console.log(` ${c.dim(`· ${result.filename}`)}`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
758
934
|
const removedMsg = totalRemoved > 0 ? c.dim(` (${totalRemoved} old files cleaned)`) : '';
|
|
759
935
|
console.log(`\n ${c.green('✓')} ${c.bold(name)}: ${totalFiles} files published${removedMsg}\n`);
|
|
760
936
|
}
|
|
@@ -960,7 +1136,9 @@ async function runAdmin(siteDir, port) {
|
|
|
960
1136
|
app.get('/__reload', ctx => ctx.body(null, 204));
|
|
961
1137
|
const { buildSourceContext } = await import('./bootstrap.js');
|
|
962
1138
|
const { source, targetConfigs } = await buildSourceContext({ projectSiteDir: siteDir });
|
|
963
|
-
await
|
|
1139
|
+
const manifest = await loadSiteManifestForCli(siteDir);
|
|
1140
|
+
const hookContributions = readHookContributions(manifest);
|
|
1141
|
+
await setupProductionMode(app, source, siteDir, builtAdminDir, templatesDir, adminDir, targetConfigs, hookContributions, manifest);
|
|
964
1142
|
// SPA fallback for non-API admin routes
|
|
965
1143
|
app.get('*', ctx => {
|
|
966
1144
|
const indexPath = join(builtAdminDir, 'index.html');
|
|
@@ -968,10 +1146,10 @@ async function runAdmin(siteDir, port) {
|
|
|
968
1146
|
return ctx.html(readFileSync(indexPath, 'utf-8'));
|
|
969
1147
|
return ctx.notFound();
|
|
970
1148
|
});
|
|
971
|
-
const
|
|
1149
|
+
const siteManifest = (await loadSiteManifestForCli(siteDir)) ?? { name: 'gazetta' };
|
|
972
1150
|
const server = serve({ fetch: app.fetch, port }, () => {
|
|
973
1151
|
console.log();
|
|
974
|
-
console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('admin')} ${c.dim(
|
|
1152
|
+
console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('admin')} ${c.dim(siteManifest.name)}`);
|
|
975
1153
|
console.log();
|
|
976
1154
|
console.log(` ${c.dim('┃')} Admin ${c.cyan(`http://localhost:${port}/admin`)}`);
|
|
977
1155
|
console.log();
|
|
@@ -984,25 +1162,24 @@ async function runAdmin(siteDir, port) {
|
|
|
984
1162
|
}
|
|
985
1163
|
}
|
|
986
1164
|
async function runServe(siteDir, port, targetName) {
|
|
987
|
-
const
|
|
988
|
-
if (!
|
|
989
|
-
console.error(`\n Error: ${
|
|
1165
|
+
const siteYaml = await loadSiteManifestForCli(siteDir);
|
|
1166
|
+
if (!siteYaml) {
|
|
1167
|
+
console.error(`\n Error: no site config found in ${siteDir} (looked for site.config.ts)\n`);
|
|
990
1168
|
process.exit(1);
|
|
991
1169
|
}
|
|
992
|
-
const siteYaml = yaml.load(readFileSync(siteYamlPath, 'utf-8'));
|
|
993
1170
|
if (!siteYaml.targets || Object.keys(siteYaml.targets).length === 0) {
|
|
994
|
-
console.error('\n Error: no targets configured in site
|
|
1171
|
+
console.error('\n Error: no targets configured in site config\n');
|
|
995
1172
|
process.exit(1);
|
|
996
1173
|
}
|
|
997
1174
|
const name = targetName ?? Object.keys(siteYaml.targets)[0];
|
|
998
1175
|
const config = siteYaml.targets[name];
|
|
999
1176
|
if (!config) {
|
|
1000
|
-
console.error(`\n Error: target "${name}" not found in site
|
|
1177
|
+
console.error(`\n Error: target "${name}" not found in site config\n`);
|
|
1001
1178
|
process.exit(1);
|
|
1002
1179
|
}
|
|
1003
|
-
|
|
1004
|
-
//
|
|
1005
|
-
const storage =
|
|
1180
|
+
// Storage is already a constructed provider (Path X — operator-facing
|
|
1181
|
+
// factory ran at config-eval).
|
|
1182
|
+
const storage = config.storage;
|
|
1006
1183
|
const { getType } = await import('../types.js');
|
|
1007
1184
|
const { createServer } = await import('../serve.js');
|
|
1008
1185
|
const app = createServer({ storage, type: getType(config) });
|
|
@@ -1021,16 +1198,13 @@ async function runServe(siteDir, port, targetName) {
|
|
|
1021
1198
|
}
|
|
1022
1199
|
}
|
|
1023
1200
|
async function runDeploy(siteDir, targetName) {
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
if (!existsSync(siteYamlPath)) {
|
|
1028
|
-
console.error(`\n Error: No site.yaml found at ${siteDir}\n`);
|
|
1201
|
+
const siteYaml = await loadSiteManifestForCli(siteDir);
|
|
1202
|
+
if (!siteYaml) {
|
|
1203
|
+
console.error(`\n Error: no site config found at ${siteDir} (looked for site.config.ts)\n`);
|
|
1029
1204
|
process.exit(1);
|
|
1030
1205
|
}
|
|
1031
|
-
const siteYaml = yaml.load(readFileSync(siteYamlPath, 'utf-8'));
|
|
1032
1206
|
if (!siteYaml.targets) {
|
|
1033
|
-
console.error(`\n Error: No targets configured in site
|
|
1207
|
+
console.error(`\n Error: No targets configured in site config\n`);
|
|
1034
1208
|
process.exit(1);
|
|
1035
1209
|
}
|
|
1036
1210
|
if (!targetName) {
|
|
@@ -1042,130 +1216,193 @@ async function runDeploy(siteDir, targetName) {
|
|
|
1042
1216
|
console.error(`\n Error: Unknown target "${targetName}". Available: ${Object.keys(siteYaml.targets).join(', ')}\n`);
|
|
1043
1217
|
process.exit(1);
|
|
1044
1218
|
}
|
|
1045
|
-
if (!target.
|
|
1046
|
-
console.error(`\n Error: Target "${targetName}" has no
|
|
1219
|
+
if (!target.deploy) {
|
|
1220
|
+
console.error(`\n Error: Target "${targetName}" has no \`deploy:\` adapter configured. Add to site.config.ts:\n\n ` +
|
|
1221
|
+
`import { cloudflareWorkersDeploy } from 'gazetta'\n ` +
|
|
1222
|
+
`deploy: cloudflareWorkersDeploy({\n ` +
|
|
1223
|
+
` apiToken: process.env.CLOUDFLARE_API_TOKEN!,\n ` +
|
|
1224
|
+
` accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,\n ` +
|
|
1225
|
+
` name: '${targetName}',\n ` +
|
|
1226
|
+
` bucket: '${targetName}',\n })\n\n` +
|
|
1227
|
+
`For container hosts (Fly.io, Cloud Run, Railway, Render), use platform-native\n` +
|
|
1228
|
+
`deploy tooling; see docs/container-deployment.md.\n`);
|
|
1047
1229
|
process.exit(1);
|
|
1048
1230
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
await mkdir(tmpDir, { recursive: true });
|
|
1059
|
-
// Generate wrangler.toml
|
|
1060
|
-
let wranglerToml = `name = "${workerName}"\nmain = "index.ts"\ncompatibility_date = "2024-12-01"\nworkers_dev = true\n\n[[r2_buckets]]\nbinding = "SITE_BUCKET"\nbucket_name = "${bucketName}"\n`;
|
|
1061
|
-
// Add custom domain route if siteUrl is configured
|
|
1062
|
-
if (target.siteUrl) {
|
|
1063
|
-
const url = new URL(target.siteUrl);
|
|
1064
|
-
const hostname = url.hostname;
|
|
1065
|
-
wranglerToml += `\n[[routes]]\npattern = "${hostname}/*"\nzone_name = "${hostname}"\n`;
|
|
1066
|
-
}
|
|
1067
|
-
await writeFile(join(tmpDir, 'wrangler.toml'), wranglerToml);
|
|
1068
|
-
// Generate worker entry point
|
|
1069
|
-
const workerCode = `import { createWorker } from 'gazetta/workers/cloudflare-r2'\nexport default createWorker()\n`;
|
|
1070
|
-
await writeFile(join(tmpDir, 'index.ts'), workerCode);
|
|
1071
|
-
// Generate package.json for wrangler
|
|
1072
|
-
const pkgJson = JSON.stringify({
|
|
1073
|
-
type: 'module',
|
|
1074
|
-
dependencies: { gazetta: '*', hono: '*' },
|
|
1075
|
-
});
|
|
1076
|
-
await writeFile(join(tmpDir, 'package.json'), pkgJson);
|
|
1077
|
-
// Install deps and deploy
|
|
1078
|
-
console.log(` Deploying worker "${workerName}" to Cloudflare...`);
|
|
1231
|
+
const adapter = target.deploy;
|
|
1232
|
+
console.log(` ${c.cyan(`Deploying via ${adapter.name}...`)}`);
|
|
1233
|
+
// Build a no-op logger; v1 deploy doesn't require structured logging yet.
|
|
1234
|
+
const logger = {
|
|
1235
|
+
debug: (_o, _m) => { },
|
|
1236
|
+
info: (_o, _m) => { },
|
|
1237
|
+
warn: (o, m) => console.warn(` ${typeof o === 'string' ? o : (m ?? '')}`),
|
|
1238
|
+
error: (o, m) => console.error(` ${typeof o === 'string' ? o : (m ?? '')}`),
|
|
1239
|
+
};
|
|
1079
1240
|
try {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1241
|
+
const result = await adapter.execute({
|
|
1242
|
+
target,
|
|
1243
|
+
targetName,
|
|
1244
|
+
// outputDir not used by worker-deploy adapters (worker reads R2 at request).
|
|
1245
|
+
// Adapters that need bytes (static-host) read storage themselves.
|
|
1246
|
+
outputDir: join(siteDir, 'dist', targetName),
|
|
1247
|
+
storage: target.storage,
|
|
1248
|
+
env: process.env,
|
|
1249
|
+
logger,
|
|
1250
|
+
signal: new AbortController().signal,
|
|
1251
|
+
});
|
|
1252
|
+
if (result.url)
|
|
1253
|
+
console.log(` ${c.green('✓')} Deployed: ${result.url}`);
|
|
1254
|
+
else
|
|
1255
|
+
console.log(` ${c.green('✓')} Deployed.`);
|
|
1084
1256
|
if (target.siteUrl)
|
|
1085
1257
|
console.log(` Site: ${target.siteUrl}`);
|
|
1086
1258
|
}
|
|
1087
1259
|
catch (err) {
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1260
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1261
|
+
const adapterTag = err.adapter ?? adapter.name;
|
|
1262
|
+
console.error(`\n ${c.red(`Deploy failed (${adapterTag}):`)} ${message}\n`);
|
|
1090
1263
|
process.exit(1);
|
|
1091
1264
|
}
|
|
1092
|
-
|
|
1093
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
1094
|
-
}
|
|
1095
|
-
console.log(`\n ${c.green('✓')} Worker deployed. Now publish content:\n ${c.cyan(`gazetta publish ${targetName}`)}\n`);
|
|
1265
|
+
console.log(`\n ${c.green('✓')} Deploy complete. Publish content with:\n ${c.cyan(`gazetta publish ${targetName}`)}\n`);
|
|
1096
1266
|
}
|
|
1097
|
-
|
|
1267
|
+
const QUALITY_VALIDATORS = new Set(['accessibility', 'html-validity', 'broken-links']);
|
|
1268
|
+
async function runValidate(siteDir, rawArgs = []) {
|
|
1098
1269
|
const projectRoot = detectProjectRoot(siteDir);
|
|
1099
1270
|
const templatesDir = join(projectRoot, 'templates');
|
|
1271
|
+
const opts = parseValidateFlags(rawArgs);
|
|
1100
1272
|
console.log();
|
|
1101
1273
|
console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('validate')} ${c.dim(siteDir)}`);
|
|
1102
1274
|
console.log();
|
|
1103
|
-
// 1.
|
|
1275
|
+
// 1. Load site
|
|
1276
|
+
const configLabel = existsSync(join(siteDir, 'site.config.ts'))
|
|
1277
|
+
? 'site.config.ts'
|
|
1278
|
+
: existsSync(join(siteDir, 'site.config.js'))
|
|
1279
|
+
? 'site.config.js'
|
|
1280
|
+
: 'site.config.mjs';
|
|
1281
|
+
const { buildSourceContext } = await import('./bootstrap.js');
|
|
1104
1282
|
let site;
|
|
1283
|
+
let source;
|
|
1105
1284
|
try {
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
site = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest });
|
|
1109
|
-
console.log(` ${c.green('✓')}
|
|
1285
|
+
const built = await buildSourceContext({ projectSiteDir: siteDir });
|
|
1286
|
+
source = built.source;
|
|
1287
|
+
site = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest: built.manifest });
|
|
1288
|
+
console.log(` ${c.green('✓')} ${configLabel} ${c.dim(`— ${site.manifest.name}`)}`);
|
|
1110
1289
|
}
|
|
1111
1290
|
catch (err) {
|
|
1112
|
-
console.error(` ${c.red('✗')}
|
|
1291
|
+
console.error(` ${c.red('✗')} ${configLabel} ${c.dim(`— ${err.message}`)}`);
|
|
1113
1292
|
process.exit(1);
|
|
1114
1293
|
}
|
|
1115
|
-
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1294
|
+
// 2. Build the registry. Skip quality validators (a11y, html-validity)
|
|
1295
|
+
// unless `--include-quality` — they trigger rendering and add seconds
|
|
1296
|
+
// per page; default keeps the CLI snappy for CI ref-existence gating.
|
|
1297
|
+
const { defaultValidatorRegistry } = await import('../validation/default-registry.js');
|
|
1298
|
+
const { createValidatorRegistry } = await import('../validation/registry.js');
|
|
1299
|
+
const fullRegistry = defaultValidatorRegistry();
|
|
1300
|
+
const filtered = opts.includeQuality
|
|
1301
|
+
? fullRegistry
|
|
1302
|
+
: createValidatorRegistry(fullRegistry.all().filter(v => !QUALITY_VALIDATORS.has(v.name)));
|
|
1303
|
+
// 3. Run via the scanner — same orchestrator as the admin background scan,
|
|
1304
|
+
// so the CLI exercises identical code paths.
|
|
1305
|
+
const { createValidationScanner } = await import('../validation/scanner.js');
|
|
1306
|
+
const { createMemoryCache } = await import('../cache/memory.js');
|
|
1307
|
+
const scanner = createValidationScanner({
|
|
1308
|
+
storage: source.storage,
|
|
1309
|
+
contentRoot: source.contentRoot,
|
|
1310
|
+
registry: filtered,
|
|
1311
|
+
cache: createMemoryCache(),
|
|
1312
|
+
siteOptions: { templatesDir, manifest: site.manifest },
|
|
1313
|
+
loadSiteImpl: async () => site,
|
|
1314
|
+
});
|
|
1315
|
+
await scanner.scanAll();
|
|
1316
|
+
const allIssues = scanner.allIssues();
|
|
1317
|
+
// 4. Per-item summary. Pages first, then fragments — matches the existing
|
|
1318
|
+
// output ordering for unsurprising diff vs. the prior implementation.
|
|
1319
|
+
let errorCount = 0;
|
|
1320
|
+
let warnCount = 0;
|
|
1321
|
+
let infoCount = 0;
|
|
1322
|
+
const issuesByPath = new Map();
|
|
1323
|
+
for (const issue of allIssues) {
|
|
1324
|
+
const list = issuesByPath.get(issue.itemPath) ?? [];
|
|
1325
|
+
list.push(issue);
|
|
1326
|
+
issuesByPath.set(issue.itemPath, list);
|
|
1327
|
+
if (issue.severity === 'error')
|
|
1328
|
+
errorCount++;
|
|
1329
|
+
else if (issue.severity === 'warn')
|
|
1330
|
+
warnCount++;
|
|
1331
|
+
else
|
|
1332
|
+
infoCount++;
|
|
1333
|
+
}
|
|
1334
|
+
function summaryGlyph(issues) {
|
|
1335
|
+
if (issues.some(i => i.severity === 'error'))
|
|
1336
|
+
return { glyph: '✗', color: c.red };
|
|
1337
|
+
if (issues.some(i => i.severity === 'warn'))
|
|
1338
|
+
return { glyph: '⚠', color: c.yellow };
|
|
1339
|
+
if (issues.length > 0)
|
|
1340
|
+
return { glyph: 'ⓘ', color: c.cyan };
|
|
1341
|
+
return { glyph: '✓', color: c.green };
|
|
1342
|
+
}
|
|
1343
|
+
function shouldShow(severity) {
|
|
1344
|
+
if (opts.severity === 'all')
|
|
1345
|
+
return true;
|
|
1346
|
+
if (opts.severity === 'warn')
|
|
1347
|
+
return severity !== 'info';
|
|
1348
|
+
return severity === 'error';
|
|
1129
1349
|
}
|
|
1130
|
-
// 3. Validate all pages
|
|
1131
1350
|
for (const [pageName, page] of site.pages) {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1351
|
+
const path = `${page.dir}/page.json`;
|
|
1352
|
+
const issues = issuesByPath.get(path) ?? [];
|
|
1353
|
+
const visible = issues.filter(i => shouldShow(i.severity));
|
|
1354
|
+
const { glyph, color } = summaryGlyph(visible);
|
|
1355
|
+
const componentCount = page.components?.length ?? 0;
|
|
1356
|
+
console.log(` ${color(glyph)} ${pageName} ${c.dim(`(${componentCount} components)`)}`);
|
|
1357
|
+
if (opts.verbose) {
|
|
1358
|
+
for (const issue of visible) {
|
|
1359
|
+
console.log(` ${severityIcon(issue.severity)} ${issue.message}`);
|
|
1360
|
+
}
|
|
1137
1361
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1362
|
+
}
|
|
1363
|
+
for (const [fragName, frag] of site.fragments) {
|
|
1364
|
+
const path = `${frag.dir}/fragment.json`;
|
|
1365
|
+
const issues = issuesByPath.get(path) ?? [];
|
|
1366
|
+
const visible = issues.filter(i => shouldShow(i.severity));
|
|
1367
|
+
const { glyph, color } = summaryGlyph(visible);
|
|
1368
|
+
const childCount = frag.components?.length ?? 0;
|
|
1369
|
+
console.log(` ${color(glyph)} @${fragName} ${c.dim(`(${childCount} components)`)}`);
|
|
1370
|
+
if (opts.verbose) {
|
|
1371
|
+
for (const issue of visible) {
|
|
1372
|
+
console.log(` ${severityIcon(issue.severity)} ${issue.message}`);
|
|
1373
|
+
}
|
|
1141
1374
|
}
|
|
1142
1375
|
}
|
|
1143
|
-
//
|
|
1376
|
+
// 5. Project-structure checks (orphaned editors, missing custom fields).
|
|
1377
|
+
// These are project-layout concerns rather than per-item content rules,
|
|
1378
|
+
// so they don't fit the Validator interface — kept inline here.
|
|
1379
|
+
const adminDir = join(projectRoot, 'admin');
|
|
1144
1380
|
const projectStorage = createFilesystemProvider();
|
|
1145
1381
|
let templateNames = [];
|
|
1146
1382
|
try {
|
|
1147
1383
|
const entries = await projectStorage.readDir(templatesDir);
|
|
1148
|
-
templateNames = entries.filter(e => e.isDirectory).map(
|
|
1149
|
-
console.log(` ${c.green('✓')} ${c.dim(`${templateNames.length} templates`)}`);
|
|
1384
|
+
templateNames = entries.filter(e => e.isDirectory).map(e => e.name);
|
|
1150
1385
|
}
|
|
1151
1386
|
catch {
|
|
1152
|
-
|
|
1387
|
+
/* templates dir missing — site already errored above */
|
|
1153
1388
|
}
|
|
1154
|
-
//
|
|
1155
|
-
|
|
1389
|
+
// 5a. Orphaned editors: editor file exists but no matching template.
|
|
1390
|
+
// Always shown regardless of --severity since these are structural
|
|
1391
|
+
// issues operators need to know about even at the strictest filter.
|
|
1156
1392
|
const editorsDir = join(adminDir, 'editors');
|
|
1157
1393
|
if (existsSync(editorsDir)) {
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
.filter(f => f.endsWith('.ts') || f.endsWith('.tsx'));
|
|
1394
|
+
const fs = await import('node:fs');
|
|
1395
|
+
const editorFiles = fs.readdirSync(editorsDir).filter(f => f.endsWith('.ts') || f.endsWith('.tsx'));
|
|
1161
1396
|
for (const file of editorFiles) {
|
|
1162
1397
|
const editorName = file.replace(/\.(ts|tsx)$/, '');
|
|
1163
1398
|
if (!templateNames.includes(editorName)) {
|
|
1164
1399
|
console.log(` ${c.yellow('⚠')} orphaned editor: ${c.dim(`admin/editors/${file}`)} ${c.dim('— no matching template')}`);
|
|
1400
|
+
warnCount++;
|
|
1165
1401
|
}
|
|
1166
1402
|
}
|
|
1167
1403
|
}
|
|
1168
|
-
//
|
|
1404
|
+
// 5b. Missing custom fields: schema references field: 'name' but no
|
|
1405
|
+
// admin/fields/name.{ts,tsx}. Hard error — render fails without it.
|
|
1169
1406
|
const fieldsDir = join(adminDir, 'fields');
|
|
1170
1407
|
const fieldFiles = existsSync(fieldsDir)
|
|
1171
1408
|
? (await import('node:fs'))
|
|
@@ -1173,96 +1410,60 @@ async function runValidate(siteDir) {
|
|
|
1173
1410
|
.filter(f => f.endsWith('.ts') || f.endsWith('.tsx'))
|
|
1174
1411
|
.map(f => f.replace(/\.(ts|tsx)$/, ''))
|
|
1175
1412
|
: [];
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
const
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
}
|
|
1193
|
-
catch {
|
|
1194
|
-
/* template load errors already caught above */
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
// 7. Locale validation
|
|
1198
|
-
const { defaultLocaleFor } = await import('../locale.js');
|
|
1199
|
-
const defLoc = defaultLocaleFor(site.manifest);
|
|
1200
|
-
const hasI18n = !!site.manifest.locales?.supported?.length;
|
|
1201
|
-
// 7a. Warn about orphaned locale files when i18n is disabled
|
|
1202
|
-
if (!hasI18n && (site.pageLocales.size > 0 || site.fragmentLocales.size > 0)) {
|
|
1203
|
-
const orphanCount = site.pageLocales.size + site.fragmentLocales.size;
|
|
1204
|
-
console.log(` ${c.yellow('⚠')} ${orphanCount} locale file${orphanCount > 1 ? 's' : ''} found but i18n is disabled ${c.dim('— add locales.supported to site.yaml or remove *.locale.json files')}`);
|
|
1205
|
-
}
|
|
1206
|
-
// 7b. Warn about ambiguous page.en.json when en is default
|
|
1207
|
-
if (hasI18n) {
|
|
1208
|
-
for (const [name, entry] of site.pageLocales) {
|
|
1209
|
-
if (entry.locales.has(defLoc)) {
|
|
1210
|
-
console.log(` ${c.yellow('⚠')} page.${defLoc}.json in ${name} is ambiguous ${c.dim(`— "${defLoc}" is the default locale, use page.json instead`)}`);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
for (const [name, entry] of site.fragmentLocales) {
|
|
1214
|
-
if (entry.locales.has(defLoc)) {
|
|
1215
|
-
console.log(` ${c.yellow('⚠')} fragment.${defLoc}.json in ${name} is ambiguous ${c.dim(`— "${defLoc}" is the default locale, use fragment.json instead`)}`);
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
// 7c. Validate locale variant template/fragment refs
|
|
1220
|
-
if (hasI18n) {
|
|
1221
|
-
for (const [pageName, entry] of site.pageLocales) {
|
|
1222
|
-
for (const [locale] of entry.locales) {
|
|
1223
|
-
try {
|
|
1224
|
-
await resolvePage(pageName, site, locale);
|
|
1225
|
-
}
|
|
1226
|
-
catch (err) {
|
|
1227
|
-
console.error(` ${c.red('✗')} ${pageName} (${locale}) ${c.dim(`— ${err.message}`)}`);
|
|
1228
|
-
errors++;
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
// 8. Cross-domain hreflang bidirectional check
|
|
1234
|
-
// For per-domain targets (each with siteUrl + single locale), verify that
|
|
1235
|
-
// all targets serving the same page cross-link to each other.
|
|
1236
|
-
if (hasI18n && site.manifest.targets) {
|
|
1237
|
-
const targetsWithSiteUrl = Object.entries(site.manifest.targets).filter(([, cfg]) => cfg.siteUrl && cfg.locales?.length === 1);
|
|
1238
|
-
if (targetsWithSiteUrl.length > 1) {
|
|
1239
|
-
const localeToUrl = new Map();
|
|
1240
|
-
for (const [, cfg] of targetsWithSiteUrl) {
|
|
1241
|
-
localeToUrl.set(cfg.locales[0], cfg.siteUrl);
|
|
1242
|
-
}
|
|
1243
|
-
const missingPairs = [];
|
|
1244
|
-
for (const [locA, urlA] of localeToUrl) {
|
|
1245
|
-
for (const [locB, urlB] of localeToUrl) {
|
|
1246
|
-
if (locA === locB)
|
|
1247
|
-
continue;
|
|
1248
|
-
// Each target's sitemap should cross-link to the other
|
|
1249
|
-
// We can't check the actual sitemaps here (would need network),
|
|
1250
|
-
// but we can verify the config is consistent
|
|
1413
|
+
if (templateNames.length > 0) {
|
|
1414
|
+
const { loadTemplate } = await import('../template-loader.js');
|
|
1415
|
+
const zod = await import('zod');
|
|
1416
|
+
for (const tplName of templateNames) {
|
|
1417
|
+
try {
|
|
1418
|
+
const loaded = await loadTemplate(projectStorage, templatesDir, tplName);
|
|
1419
|
+
const jsonSchema = zod.z.toJSONSchema(loaded.schema);
|
|
1420
|
+
const props = jsonSchema.properties;
|
|
1421
|
+
if (!props)
|
|
1422
|
+
continue;
|
|
1423
|
+
for (const [propName, prop] of Object.entries(props)) {
|
|
1424
|
+
const fieldRef = prop.field;
|
|
1425
|
+
if (fieldRef && !fieldFiles.includes(fieldRef)) {
|
|
1426
|
+
console.error(` ${c.red('✗')} template ${tplName}.${propName} references field "${fieldRef}" ${c.dim('— not found in admin/fields/')}`);
|
|
1427
|
+
errorCount++;
|
|
1428
|
+
}
|
|
1251
1429
|
}
|
|
1252
1430
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1431
|
+
catch {
|
|
1432
|
+
/* template load errors surface via referenced-template-exists */
|
|
1255
1433
|
}
|
|
1256
1434
|
}
|
|
1257
1435
|
}
|
|
1436
|
+
// 6. Footer + exit code.
|
|
1258
1437
|
console.log();
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1438
|
+
const totalShown = opts.severity === 'all'
|
|
1439
|
+
? errorCount + warnCount + infoCount
|
|
1440
|
+
: opts.severity === 'warn'
|
|
1441
|
+
? errorCount + warnCount
|
|
1442
|
+
: errorCount;
|
|
1443
|
+
if (totalShown === 0) {
|
|
1444
|
+
console.log(` ${c.green('All good.')}\n`);
|
|
1445
|
+
return;
|
|
1265
1446
|
}
|
|
1447
|
+
const parts = [];
|
|
1448
|
+
if (errorCount > 0)
|
|
1449
|
+
parts.push(`${errorCount} error${errorCount > 1 ? 's' : ''}`);
|
|
1450
|
+
if (warnCount > 0 && opts.severity !== 'error')
|
|
1451
|
+
parts.push(`${warnCount} warning${warnCount > 1 ? 's' : ''}`);
|
|
1452
|
+
if (infoCount > 0 && opts.severity === 'all')
|
|
1453
|
+
parts.push(`${infoCount} info`);
|
|
1454
|
+
const summary = parts.join(', ');
|
|
1455
|
+
console.log(` ${summary}.\n`);
|
|
1456
|
+
// Exit non-zero if errors OR (warns AND warn-as-error is on).
|
|
1457
|
+
const fail = errorCount > 0 || (opts.warnAsError && warnCount > 0);
|
|
1458
|
+
if (fail)
|
|
1459
|
+
process.exit(1);
|
|
1460
|
+
}
|
|
1461
|
+
function severityIcon(severity) {
|
|
1462
|
+
if (severity === 'error')
|
|
1463
|
+
return c.red('✗');
|
|
1464
|
+
if (severity === 'warn')
|
|
1465
|
+
return c.yellow('⚠');
|
|
1466
|
+
return c.cyan('ⓘ');
|
|
1266
1467
|
}
|
|
1267
1468
|
function renderErrorOverlay(err) {
|
|
1268
1469
|
const message = err.message.replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -1313,7 +1514,7 @@ async function runDev(siteDir, port) {
|
|
|
1313
1514
|
const projectRoot = detectProjectRoot(siteDir);
|
|
1314
1515
|
const templatesDir = join(projectRoot, 'templates');
|
|
1315
1516
|
const adminDir = join(projectRoot, 'admin');
|
|
1316
|
-
// Build the source context from the default editable target in site.
|
|
1517
|
+
// Build the source context from the default editable target in site.config.ts.
|
|
1317
1518
|
// Cloud targets aren't init'd — admin API handles them lazily.
|
|
1318
1519
|
const { buildSourceContext } = await import('./bootstrap.js');
|
|
1319
1520
|
const { source, manifest, targetConfigs } = await buildSourceContext({ projectSiteDir: siteDir });
|
|
@@ -1368,6 +1569,7 @@ async function runDev(siteDir, port) {
|
|
|
1368
1569
|
app.route('/', assetServeRoutes(async () => source.storage));
|
|
1369
1570
|
// ---- Site page routes (default + locale variants) ----
|
|
1370
1571
|
const { allPageEntries } = await import('../site-loader.js');
|
|
1572
|
+
const { defaultLocaleFor: _devDefaultLocaleFor } = await import('../locale.js');
|
|
1371
1573
|
for (const { name: pageName, page, locale: pageLocale } of allPageEntries(site)) {
|
|
1372
1574
|
app.get(page.route, async (c) => {
|
|
1373
1575
|
try {
|
|
@@ -1382,7 +1584,7 @@ async function runDev(siteDir, port) {
|
|
|
1382
1584
|
route: freshPage?.route ?? page.route,
|
|
1383
1585
|
seo: {
|
|
1384
1586
|
siteName: freshSite.manifest.name,
|
|
1385
|
-
locale: pageLocale ?? freshSite.manifest
|
|
1587
|
+
locale: pageLocale ?? _devDefaultLocaleFor(freshSite.manifest),
|
|
1386
1588
|
defaultOgImage: freshSite.manifest.defaultOgImage,
|
|
1387
1589
|
},
|
|
1388
1590
|
});
|
|
@@ -1437,14 +1639,17 @@ async function runDev(siteDir, port) {
|
|
|
1437
1639
|
const isDevMode = cmsWebDir !== null;
|
|
1438
1640
|
// Admin Hono instance — captured so the template file watcher can
|
|
1439
1641
|
// invalidate its memoized template-scan cache on .ts/.tsx changes.
|
|
1642
|
+
// `rescanForTemplate` is optional: only the dev-mode setup decorates
|
|
1643
|
+
// it (production has no file watcher). Watcher uses optional-chaining.
|
|
1440
1644
|
let cmsApp = null;
|
|
1645
|
+
const hookContributions = readHookContributions(manifest);
|
|
1441
1646
|
if (isDevMode) {
|
|
1442
1647
|
// Dev mode: mount CMS API inline (same process = shared template cache)
|
|
1443
|
-
cmsApp = await setupCmsApi(app, source, siteDir, templatesDir, adminDir, targetConfigs);
|
|
1648
|
+
cmsApp = await setupCmsApi(app, source, siteDir, templatesDir, adminDir, targetConfigs, hookContributions, manifest);
|
|
1444
1649
|
}
|
|
1445
1650
|
else if (cmsStaticDir) {
|
|
1446
1651
|
// Production mode: inline CMS API + static files
|
|
1447
|
-
cmsApp = await setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs);
|
|
1652
|
+
cmsApp = await setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs, hookContributions, manifest);
|
|
1448
1653
|
}
|
|
1449
1654
|
// ---- 404 ----
|
|
1450
1655
|
app.notFound(c => {
|
|
@@ -1493,8 +1698,10 @@ async function runDev(siteDir, port) {
|
|
|
1493
1698
|
const env = getEnvironment(cfg);
|
|
1494
1699
|
const type = getType(cfg);
|
|
1495
1700
|
const ed = isEditable(cfg) ? 'editable ' : 'read-only';
|
|
1496
|
-
|
|
1497
|
-
|
|
1701
|
+
// Path X — the storage provider is opaque (operator constructed it via
|
|
1702
|
+
// factory at config-eval). Display target name + axes; the provider
|
|
1703
|
+
// identity is not introspectable from the StorageProvider interface.
|
|
1704
|
+
console.log(` ${c.dim('┃')} ${c.dim('•')} ${name.padEnd(14)} ${c.dim(env.padEnd(11))} ${c.dim(ed)} ${c.dim(type.padEnd(8))}`);
|
|
1498
1705
|
}
|
|
1499
1706
|
}
|
|
1500
1707
|
if (isDevMode && cmsWebDir) {
|
|
@@ -1580,7 +1787,15 @@ async function runDev(siteDir, port) {
|
|
|
1580
1787
|
// written into the source, so the scanner misses it. Include it
|
|
1581
1788
|
// explicitly to avoid a mid-session page reload when a TSX editor
|
|
1582
1789
|
// is first loaded (#122).
|
|
1583
|
-
|
|
1790
|
+
//
|
|
1791
|
+
// @formkit/drag-and-drop/vue is the ComponentTree's reorder lib
|
|
1792
|
+
// (#105). The package's main entry is CJS; the Vue subpath is
|
|
1793
|
+
// ESM. Vite's auto-scanner doesn't always pre-bundle subpath
|
|
1794
|
+
// ESM imports with mixed CJS-main packages on first load,
|
|
1795
|
+
// which triggers a runtime "Re-optimizing dependencies"
|
|
1796
|
+
// page reload mid-test on CI cold start. Explicit include
|
|
1797
|
+
// pins the subpath in the boot-time pre-bundle.
|
|
1798
|
+
include: ['react/jsx-dev-runtime', 'react/jsx-runtime', '@formkit/drag-and-drop/vue'],
|
|
1584
1799
|
},
|
|
1585
1800
|
server: {
|
|
1586
1801
|
middlewareMode: true,
|
|
@@ -1646,7 +1861,7 @@ async function runDev(siteDir, port) {
|
|
|
1646
1861
|
console.log();
|
|
1647
1862
|
});
|
|
1648
1863
|
// ---- File watching ----
|
|
1649
|
-
// Watch site dir for content changes (JSON manifests + site.
|
|
1864
|
+
// Watch site dir for content changes (JSON manifests + site.config.ts).
|
|
1650
1865
|
// Swallow FSWatcher 'error' events — Node's recursive watcher throws ENOENT
|
|
1651
1866
|
// when a watched subdir disappears (rm -rf during publish, git checkout).
|
|
1652
1867
|
// Letting it crash would take the whole dev server down; logging a warning
|
|
@@ -1665,6 +1880,12 @@ async function runDev(siteDir, port) {
|
|
|
1665
1880
|
if (filename.endsWith('.json') || filename.endsWith('.yaml')) {
|
|
1666
1881
|
console.log(` Manifest changed: ${filename}`);
|
|
1667
1882
|
invalidateAllTemplates();
|
|
1883
|
+
// Out-of-band manifest changes (git pull, manual edit, e2e
|
|
1884
|
+
// test wipes) bypass the admin-api save handler that would
|
|
1885
|
+
// otherwise invalidate the AdminCache. Drop content-summary
|
|
1886
|
+
// entries so the next /api/pages or /api/fragments rebuilds
|
|
1887
|
+
// from disk. Best-effort; the void promise is fire-and-forget.
|
|
1888
|
+
void cmsApp?.invalidateContentCache();
|
|
1668
1889
|
notifyReload();
|
|
1669
1890
|
}
|
|
1670
1891
|
});
|
|
@@ -1677,12 +1898,34 @@ async function runDev(siteDir, port) {
|
|
|
1677
1898
|
if (filename.endsWith('.ts') || filename.endsWith('.tsx')) {
|
|
1678
1899
|
const parts = filename.split('/');
|
|
1679
1900
|
if (parts.length >= 1) {
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
//
|
|
1683
|
-
//
|
|
1684
|
-
|
|
1685
|
-
|
|
1901
|
+
const templateName = parts[0];
|
|
1902
|
+
const fullPath = join(templatesDir, filename);
|
|
1903
|
+
// Suppress spurious events for files whose mtime predates the
|
|
1904
|
+
// watcher's recent window — these are kernel-delayed metadata
|
|
1905
|
+
// flushes for files written before the watcher began observing,
|
|
1906
|
+
// typical of the worker-scoped `cp -r` setup in the e2e fixture
|
|
1907
|
+
// (#286). Without the gate, the resulting notifyReload() closes
|
|
1908
|
+
// any open admin UI mid-interaction.
|
|
1909
|
+
void (async () => {
|
|
1910
|
+
if (!(await isTemplateEventRecent(fullPath, TEMPLATE_RECENT_CHANGE_WINDOW_MS))) {
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
console.log(` Template changed: ${templateName}`);
|
|
1914
|
+
invalidateTemplate(templateName);
|
|
1915
|
+
// Drop the admin-api's cached scan so next compare/publish
|
|
1916
|
+
// rehashes. Cheap (the scan is what's slow, not invalidation).
|
|
1917
|
+
cmsApp?.invalidateTemplatesCache();
|
|
1918
|
+
// Cut 6 — fire a validation rescan with the template-edit
|
|
1919
|
+
// cause so the scanner re-runs schema-conformance against
|
|
1920
|
+
// every page+fragment using this template. The ScanEvent
|
|
1921
|
+
// emits on the /__validation SSE channel; the admin's
|
|
1922
|
+
// TemplateChangedBanner consumes it to show the
|
|
1923
|
+
// template-developer's "did I break anything?" surface.
|
|
1924
|
+
// Fire-and-forget — the scan runs in the background and
|
|
1925
|
+
// the SSE event is the signal that it finished.
|
|
1926
|
+
void cmsApp?.rescanForTemplate?.(templateName);
|
|
1927
|
+
notifyReload();
|
|
1928
|
+
})();
|
|
1686
1929
|
}
|
|
1687
1930
|
}
|
|
1688
1931
|
});
|
|
@@ -1711,16 +1954,98 @@ function mountUserThemeRoute(cmsApp, adminDir) {
|
|
|
1711
1954
|
return c.body(readFileSync(themePath, 'utf-8'));
|
|
1712
1955
|
});
|
|
1713
1956
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1957
|
+
/**
|
|
1958
|
+
* Build the background validation scanner (validation Cut 2). The scanner
|
|
1959
|
+
* is constructed against the resolved source's storage + cache; the boot
|
|
1960
|
+
* path kicks off an initial full-site scan in the background so admin
|
|
1961
|
+
* responses don't block on it.
|
|
1962
|
+
*
|
|
1963
|
+
* Returns null when the manifest is unavailable (site lacks site.config.ts)
|
|
1964
|
+
* — the admin app degrades gracefully (route returns empty issues; SSE
|
|
1965
|
+
* channel idle).
|
|
1966
|
+
*/
|
|
1967
|
+
async function buildValidationScanner(opts) {
|
|
1968
|
+
if (!opts.manifest)
|
|
1969
|
+
return null;
|
|
1970
|
+
const { createValidationScanner } = await import('../validation/scanner.js');
|
|
1971
|
+
const { defaultValidatorRegistry } = await import('../validation/default-registry.js');
|
|
1972
|
+
const scanner = createValidationScanner({
|
|
1973
|
+
storage: opts.source.storage,
|
|
1974
|
+
contentRoot: opts.source.contentRoot,
|
|
1975
|
+
registry: defaultValidatorRegistry(),
|
|
1976
|
+
cache: opts.source.cache,
|
|
1977
|
+
siteOptions: { templatesDir: opts.templatesDir, manifest: opts.manifest },
|
|
1978
|
+
});
|
|
1979
|
+
// Boot warm: kick off the initial scan in the background. Errors are
|
|
1980
|
+
// logged but don't block boot — broken validators surface as info-issues
|
|
1981
|
+
// rather than failing the admin process.
|
|
1982
|
+
void scanner.scanAll().catch(err => {
|
|
1983
|
+
console.error('[validation] initial scan failed:', err);
|
|
1984
|
+
});
|
|
1985
|
+
return scanner;
|
|
1986
|
+
}
|
|
1987
|
+
async function setupCmsApi(app, source, siteDir, templatesDir, adminDir, targetConfigs, contributions, manifest) {
|
|
1988
|
+
// Build + seal the hook registry from `admin.hooks` factory
|
|
1989
|
+
// contributions before wiring the admin app. Hooks are an opt-in
|
|
1990
|
+
// extension surface; sites without `admin.hooks` get an empty
|
|
1991
|
+
// registry (no overhead).
|
|
1992
|
+
const hooks = await buildHooksRegistry({ contributions });
|
|
1993
|
+
const validationScanner = await buildValidationScanner({ source, templatesDir, manifest });
|
|
1994
|
+
// Mount SSE on the OUTER Hono app — matches `/__reload`'s placement so
|
|
1995
|
+
// the browser EventSource (which connects without an `/admin/` prefix)
|
|
1996
|
+
// bypasses Vite's middleware and reaches the route. Same in prod for
|
|
1997
|
+
// consistency: a save from one tab updates the badge in every other
|
|
1998
|
+
// tab without polling.
|
|
1999
|
+
const { mountValidationSse } = await import('../admin-api/routes/validation.js');
|
|
2000
|
+
mountValidationSse(app, validationScanner);
|
|
2001
|
+
const cmsApp = createAdminApp({
|
|
2002
|
+
source,
|
|
2003
|
+
siteDir,
|
|
2004
|
+
templatesDir,
|
|
2005
|
+
adminDir,
|
|
2006
|
+
targetConfigs,
|
|
2007
|
+
hooks,
|
|
2008
|
+
validationScanner,
|
|
2009
|
+
});
|
|
2010
|
+
// Decorate cmsApp with the template-rescan hook before returning. The
|
|
2011
|
+
// file watcher in startServer() invokes this on `.ts/.tsx` changes
|
|
2012
|
+
// under `templates/{name}/`. Failure is fail-open (logged + dropped)
|
|
2013
|
+
// — a scan failure shouldn't break dev-mode hot reload.
|
|
2014
|
+
const decoratedApp = cmsApp;
|
|
2015
|
+
decoratedApp.rescanForTemplate = async (name) => {
|
|
2016
|
+
if (!validationScanner)
|
|
2017
|
+
return;
|
|
2018
|
+
try {
|
|
2019
|
+
await validationScanner.rescan({ kind: 'template', name });
|
|
2020
|
+
}
|
|
2021
|
+
catch (err) {
|
|
2022
|
+
console.warn(` Validation scanner: template rescan failed for "${name}": ${err.message}`);
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
1716
2025
|
mountUserThemeRoute(cmsApp, adminDir);
|
|
1717
2026
|
app.route('/admin', cmsApp);
|
|
1718
|
-
return
|
|
2027
|
+
return decoratedApp;
|
|
1719
2028
|
}
|
|
1720
2029
|
// ---- Production mode: inline CMS API + static files from admin-dist/ ----
|
|
1721
|
-
async function setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs) {
|
|
2030
|
+
async function setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs, contributions, manifest) {
|
|
2031
|
+
// Same shape as dev mode — `gazetta serve` reads `admin.hooks`
|
|
2032
|
+
// factory contributions from the same site config.
|
|
2033
|
+
const hooks = await buildHooksRegistry({ contributions });
|
|
2034
|
+
const validationScanner = await buildValidationScanner({ source, templatesDir, manifest });
|
|
2035
|
+
// SSE channel at the outer app's root; see setupCmsApi for rationale.
|
|
2036
|
+
const { mountValidationSse } = await import('../admin-api/routes/validation.js');
|
|
2037
|
+
mountValidationSse(app, validationScanner);
|
|
1722
2038
|
// Mount CMS API inline at /admin (production mode — bundled editors/fields)
|
|
1723
|
-
const cmsApp = createAdminApp({
|
|
2039
|
+
const cmsApp = createAdminApp({
|
|
2040
|
+
source,
|
|
2041
|
+
siteDir,
|
|
2042
|
+
templatesDir,
|
|
2043
|
+
adminDir,
|
|
2044
|
+
production: true,
|
|
2045
|
+
targetConfigs,
|
|
2046
|
+
hooks,
|
|
2047
|
+
validationScanner,
|
|
2048
|
+
});
|
|
1724
2049
|
mountUserThemeRoute(cmsApp, adminDir);
|
|
1725
2050
|
app.route('/admin', cmsApp);
|
|
1726
2051
|
// Serve pre-built CMS static files (includes bundled editors/fields)
|
|
@@ -1785,77 +2110,76 @@ async function main() {
|
|
|
1785
2110
|
await runInit(parsed.positional[0] ?? '.');
|
|
1786
2111
|
return;
|
|
1787
2112
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
2113
|
+
// ── Resolve siteDir once (fs walk; no config eval) + load .env
|
|
2114
|
+
// ── BEFORE any config-eval. Storage / deploy / AI factories read
|
|
2115
|
+
// ── process.env synchronously at construction; resolveTarget()
|
|
2116
|
+
// ── triggers config-eval, so env must be in place first. Per-
|
|
2117
|
+
// ── command positional layouts determine which slot carries the
|
|
2118
|
+
// ── site path. Site-local `.env` is honored (e.g.,
|
|
2119
|
+
// ── sites/gazetta.studio/.env with R2 creds) per design-config.md.
|
|
2120
|
+
siteDir = await resolveSiteDirForCommand(command, parsed);
|
|
2121
|
+
loadEnvFiles(siteDir);
|
|
2122
|
+
if (command === 'build') {
|
|
1790
2123
|
await runBuild(siteDir);
|
|
1791
2124
|
return;
|
|
1792
2125
|
}
|
|
1793
2126
|
else if (command === 'rollback') {
|
|
1794
2127
|
// gazetta rollback <rev> [target] [site]
|
|
1795
|
-
const [rev, second
|
|
2128
|
+
const [rev, second] = parsed.positional;
|
|
1796
2129
|
if (!rev || !rev.startsWith('rev-')) {
|
|
1797
2130
|
console.error(`\n Error: rollback requires a revision id as the first argument (e.g. gazetta rollback rev-1776337441608 [target])\n`);
|
|
1798
2131
|
process.exit(1);
|
|
1799
2132
|
return;
|
|
1800
2133
|
}
|
|
1801
2134
|
rollbackRevisionId = rev;
|
|
1802
|
-
const secondIsSite = second && (second.includes('/') ||
|
|
1803
|
-
|
|
1804
|
-
siteDir = await resolveSiteDir(second);
|
|
1805
|
-
targetName = await resolveTarget(undefined, siteDir);
|
|
1806
|
-
}
|
|
1807
|
-
else {
|
|
1808
|
-
siteDir = await resolveSiteDir(third);
|
|
1809
|
-
targetName = await resolveTarget(second, siteDir);
|
|
1810
|
-
}
|
|
2135
|
+
const secondIsSite = second && (second.includes('/') || hasSiteConfig(resolve(second)));
|
|
2136
|
+
targetName = secondIsSite ? await resolveTarget(undefined, siteDir) : await resolveTarget(second, siteDir);
|
|
1811
2137
|
}
|
|
1812
2138
|
else if (targetFirstCommands.has(command)) {
|
|
1813
2139
|
// gazetta publish [target] [site]
|
|
1814
|
-
const [first
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
if (firstIsSite) {
|
|
1818
|
-
siteDir = await resolveSiteDir(first);
|
|
1819
|
-
targetName = await resolveTarget(undefined, siteDir);
|
|
1820
|
-
}
|
|
1821
|
-
else {
|
|
1822
|
-
siteDir = await resolveSiteDir(second);
|
|
1823
|
-
targetName = await resolveTarget(first, siteDir);
|
|
1824
|
-
}
|
|
2140
|
+
const [first] = parsed.positional;
|
|
2141
|
+
const firstIsSite = first && (first.includes('/') || hasSiteConfig(resolve(first)));
|
|
2142
|
+
targetName = firstIsSite ? await resolveTarget(undefined, siteDir) : await resolveTarget(first, siteDir);
|
|
1825
2143
|
}
|
|
1826
2144
|
else if (siteOnlyCommands.has(command)) {
|
|
1827
|
-
siteDir
|
|
2145
|
+
// siteDir set up front; nothing more needed.
|
|
1828
2146
|
}
|
|
1829
2147
|
else if (command === 'translate') {
|
|
1830
2148
|
// gazetta translate <item> --to <locale> [target]
|
|
1831
|
-
// positional args after the item are the optional target name
|
|
1832
|
-
siteDir = await resolveSiteDir(undefined);
|
|
1833
|
-
// Find the target arg — skip the item (pages/... or fragments/...) and --to/locale flags
|
|
1834
2149
|
const translatePositionals = parsed.positional.filter(p => !p.startsWith('pages/') && !p.startsWith('fragments/'));
|
|
1835
2150
|
if (translatePositionals.length > 0)
|
|
1836
2151
|
targetName = translatePositionals[0];
|
|
1837
2152
|
}
|
|
1838
2153
|
else if (command === 'assets') {
|
|
1839
2154
|
// gazetta assets <subcommand> [args...] [target] [site]
|
|
1840
|
-
//
|
|
1841
|
-
// Subcommand layouts:
|
|
1842
2155
|
// assets list [target] [site] → subcmd, target, site
|
|
1843
2156
|
// assets info <name> [target] [site] → subcmd, name, target, site
|
|
1844
2157
|
// assets reindex [target] [site] → subcmd, target, site
|
|
2158
|
+
const subcmd = parsed.positional[0];
|
|
2159
|
+
const targetArg = subcmd === 'info' ? parsed.positional[2] : parsed.positional[1];
|
|
2160
|
+
targetName = targetArg ? await resolveTarget(targetArg, siteDir) : undefined;
|
|
2161
|
+
}
|
|
2162
|
+
else if (command === 'archive') {
|
|
2163
|
+
// gazetta archive <subcommand> [args...]
|
|
1845
2164
|
//
|
|
1846
|
-
//
|
|
1847
|
-
//
|
|
1848
|
-
//
|
|
2165
|
+
// Site/target resolution: positional after the subcommand's
|
|
2166
|
+
// required args are interpreted as [target] [site]. The archive
|
|
2167
|
+
// module honors `--kind=` / `--force` / `--no-keep-alias` flags
|
|
2168
|
+
// separately (those don't reach the parsed.positional list per
|
|
2169
|
+
// global parseArgs).
|
|
2170
|
+
//
|
|
2171
|
+
// Subcommand positional shapes:
|
|
2172
|
+
// archive list → []
|
|
2173
|
+
// archive purge <name> → [name]
|
|
2174
|
+
// archive restore <name> → [name]
|
|
2175
|
+
// archive rename <oldname> <newname> → [oldname, newname]
|
|
2176
|
+
//
|
|
2177
|
+
// Remaining slots are [target] [site] in that order.
|
|
1849
2178
|
const subcmd = parsed.positional[0];
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
}
|
|
1855
|
-
else {
|
|
1856
|
-
siteDir = await resolveSiteDir(parsed.positional[2]);
|
|
1857
|
-
targetName = parsed.positional[1] ? await resolveTarget(parsed.positional[1], siteDir) : undefined;
|
|
1858
|
-
}
|
|
2179
|
+
const requiredArgs = subcmd === 'rename' ? 2 : subcmd === 'purge' || subcmd === 'restore' ? 1 : 0;
|
|
2180
|
+
const trailingStart = 1 + requiredArgs;
|
|
2181
|
+
const targetArg = parsed.positional[trailingStart];
|
|
2182
|
+
targetName = targetArg ? await resolveTarget(targetArg, siteDir) : undefined;
|
|
1859
2183
|
}
|
|
1860
2184
|
else {
|
|
1861
2185
|
console.error(` Unknown command: ${command}\n`);
|
|
@@ -1863,23 +2187,6 @@ async function main() {
|
|
|
1863
2187
|
process.exit(1);
|
|
1864
2188
|
return;
|
|
1865
2189
|
}
|
|
1866
|
-
// Load .env from project root and site dir (skipped in CI)
|
|
1867
|
-
if (!process.env.CI) {
|
|
1868
|
-
const projectRoot = detectProjectRoot(siteDir);
|
|
1869
|
-
const envDirs = projectRoot !== siteDir ? [projectRoot, siteDir] : [siteDir];
|
|
1870
|
-
for (const dir of envDirs) {
|
|
1871
|
-
for (const name of ['.env', '.env.local']) {
|
|
1872
|
-
const envPath = join(dir, name);
|
|
1873
|
-
if (existsSync(envPath)) {
|
|
1874
|
-
for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
|
|
1875
|
-
const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/);
|
|
1876
|
-
if (m && !(m[1] in process.env))
|
|
1877
|
-
process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
2190
|
switch (command) {
|
|
1884
2191
|
case 'publish':
|
|
1885
2192
|
await runPublish(siteDir, targetName, { force: parsed.force });
|
|
@@ -1891,7 +2198,7 @@ async function main() {
|
|
|
1891
2198
|
await runDeploy(siteDir, targetName);
|
|
1892
2199
|
break;
|
|
1893
2200
|
case 'validate':
|
|
1894
|
-
await runValidate(siteDir);
|
|
2201
|
+
await runValidate(siteDir, args.slice(1));
|
|
1895
2202
|
break;
|
|
1896
2203
|
case 'dev':
|
|
1897
2204
|
await runDev(siteDir, parsed.port ?? 3000);
|
|
@@ -1921,7 +2228,11 @@ async function main() {
|
|
|
1921
2228
|
}
|
|
1922
2229
|
// Resolve the content directory — translate operates on a target's filesystem.
|
|
1923
2230
|
// Uses the specified target or falls back to the first editable target.
|
|
1924
|
-
const siteYaml =
|
|
2231
|
+
const siteYaml = await loadSiteManifestForCli(siteDir);
|
|
2232
|
+
if (!siteYaml) {
|
|
2233
|
+
console.error(` Error: no site config found at ${siteDir}`);
|
|
2234
|
+
process.exit(1);
|
|
2235
|
+
}
|
|
1925
2236
|
const { isEditable } = await import('../types.js');
|
|
1926
2237
|
const resolvedTarget = targetName ?? Object.entries(siteYaml.targets ?? {}).find(([, cfg]) => isEditable(cfg))?.[0];
|
|
1927
2238
|
if (!resolvedTarget) {
|
|
@@ -1930,26 +2241,27 @@ async function main() {
|
|
|
1930
2241
|
}
|
|
1931
2242
|
const targetConfig = siteYaml.targets[resolvedTarget];
|
|
1932
2243
|
if (!targetConfig) {
|
|
1933
|
-
console.error(` Error: target "${resolvedTarget}" not found in site
|
|
2244
|
+
console.error(` Error: target "${resolvedTarget}" not found in site config`);
|
|
1934
2245
|
process.exit(1);
|
|
1935
2246
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
2247
|
+
// Translate goes through the storage provider so it works on any
|
|
2248
|
+
// storage backend (filesystem / R2 / S3 / Azure). Path X — the storage
|
|
2249
|
+
// provider was constructed by the operator's factory at config-eval.
|
|
2250
|
+
const storage = targetConfig.storage;
|
|
1938
2251
|
const baseName = isPage ? 'page' : 'fragment';
|
|
1939
|
-
const
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
if (!existsSync(sourceFile)) {
|
|
1944
|
-
console.error(` Error: ${sourceFile} not found`);
|
|
2252
|
+
const sourcePath = `${itemArg}/${baseName}.json`;
|
|
2253
|
+
const destPath = `${itemArg}/${localeFilename(baseName, locale)}`;
|
|
2254
|
+
if (!(await storage.exists(sourcePath))) {
|
|
2255
|
+
console.error(` Error: ${sourcePath} not found on target "${resolvedTarget}"`);
|
|
1945
2256
|
process.exit(1);
|
|
1946
2257
|
}
|
|
1947
|
-
if (
|
|
1948
|
-
console.error(` Error: ${
|
|
2258
|
+
if (await storage.exists(destPath)) {
|
|
2259
|
+
console.error(` Error: ${destPath} already exists on target "${resolvedTarget}"`);
|
|
1949
2260
|
process.exit(1);
|
|
1950
2261
|
}
|
|
1951
|
-
await
|
|
1952
|
-
|
|
2262
|
+
const sourceContent = await storage.readFile(sourcePath);
|
|
2263
|
+
await storage.writeFile(destPath, sourceContent);
|
|
2264
|
+
console.log(` ${c.green('✓')} Created ${destPath}`);
|
|
1953
2265
|
console.log(` Edit the file to translate the content.`);
|
|
1954
2266
|
break;
|
|
1955
2267
|
}
|
|
@@ -1971,12 +2283,17 @@ async function main() {
|
|
|
1971
2283
|
await runAssetsSubcommand({ args: args.slice(1), siteDir, targetName });
|
|
1972
2284
|
break;
|
|
1973
2285
|
}
|
|
2286
|
+
case 'archive': {
|
|
2287
|
+
const { runArchiveSubcommand } = await import('./archive.js');
|
|
2288
|
+
await runArchiveSubcommand({ args: args.slice(1), siteDir, targetName });
|
|
2289
|
+
break;
|
|
2290
|
+
}
|
|
1974
2291
|
}
|
|
1975
2292
|
}
|
|
1976
2293
|
/**
|
|
1977
2294
|
* Resolve site + target + config into the shape HistoryCommandContext
|
|
1978
2295
|
* expects. Lives here rather than in cli/history.ts so the target-
|
|
1979
|
-
* resolution logic (site
|
|
2296
|
+
* resolution logic (site config parsing, CI env handling) stays with
|
|
1980
2297
|
* the other CLI commands that already do it the same way.
|
|
1981
2298
|
*/
|
|
1982
2299
|
async function resolveHistoryContext(siteDir, targetName) {
|