gazetta 0.5.0 → 0.7.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-BO9-CXmW.css +1 -0
- package/admin-dist/assets/index-Ufu8zZH_.js +668 -0
- package/admin-dist/assets/{vendor-primevue-BnR1c_bQ.js → vendor-primevue-C0Q_YTCb.js} +330 -431
- package/admin-dist/assets/vendor-vue-D3wBSmDf.js +1 -0
- package/admin-dist/index.html +4 -4
- package/dist/admin-api/error-response.d.ts +21 -0
- package/dist/admin-api/error-response.d.ts.map +1 -0
- package/dist/admin-api/error-response.js +12 -0
- package/dist/admin-api/error-response.js.map +1 -0
- package/dist/admin-api/index.d.ts +19 -6
- package/dist/admin-api/index.d.ts.map +1 -1
- package/dist/admin-api/index.js +137 -21
- package/dist/admin-api/index.js.map +1 -1
- package/dist/admin-api/routes/assets.d.ts +16 -0
- package/dist/admin-api/routes/assets.d.ts.map +1 -0
- package/dist/admin-api/routes/assets.js +433 -0
- package/dist/admin-api/routes/assets.js.map +1 -0
- package/dist/admin-api/routes/compare.d.ts +2 -1
- package/dist/admin-api/routes/compare.d.ts.map +1 -1
- package/dist/admin-api/routes/compare.js +33 -24
- package/dist/admin-api/routes/compare.js.map +1 -1
- package/dist/admin-api/routes/fields.d.ts +2 -2
- package/dist/admin-api/routes/fields.d.ts.map +1 -1
- package/dist/admin-api/routes/fields.js +10 -3
- package/dist/admin-api/routes/fields.js.map +1 -1
- package/dist/admin-api/routes/fragments.d.ts +2 -3
- package/dist/admin-api/routes/fragments.d.ts.map +1 -1
- package/dist/admin-api/routes/fragments.js +120 -21
- package/dist/admin-api/routes/fragments.js.map +1 -1
- package/dist/admin-api/routes/history.d.ts +23 -0
- package/dist/admin-api/routes/history.d.ts.map +1 -0
- package/dist/admin-api/routes/history.js +143 -0
- package/dist/admin-api/routes/history.js.map +1 -0
- package/dist/admin-api/routes/pages.d.ts +2 -3
- package/dist/admin-api/routes/pages.d.ts.map +1 -1
- package/dist/admin-api/routes/pages.js +153 -22
- package/dist/admin-api/routes/pages.js.map +1 -1
- package/dist/admin-api/routes/preview.d.ts +2 -2
- package/dist/admin-api/routes/preview.d.ts.map +1 -1
- package/dist/admin-api/routes/preview.js +50 -15
- package/dist/admin-api/routes/preview.js.map +1 -1
- package/dist/admin-api/routes/publish.d.ts +2 -1
- package/dist/admin-api/routes/publish.d.ts.map +1 -1
- package/dist/admin-api/routes/publish.js +259 -79
- package/dist/admin-api/routes/publish.js.map +1 -1
- package/dist/admin-api/routes/site.d.ts +2 -2
- package/dist/admin-api/routes/site.d.ts.map +1 -1
- package/dist/admin-api/routes/site.js +27 -4
- package/dist/admin-api/routes/site.js.map +1 -1
- package/dist/admin-api/routes/templates.d.ts +2 -2
- package/dist/admin-api/routes/templates.d.ts.map +1 -1
- package/dist/admin-api/routes/templates.js +19 -9
- package/dist/admin-api/routes/templates.js.map +1 -1
- package/dist/admin-api/schemas/assets.d.ts +48 -0
- package/dist/admin-api/schemas/assets.d.ts.map +1 -0
- package/dist/admin-api/schemas/assets.js +44 -0
- package/dist/admin-api/schemas/assets.js.map +1 -0
- package/dist/admin-api/schemas/compare.d.ts +29 -0
- package/dist/admin-api/schemas/compare.d.ts.map +1 -0
- package/dist/admin-api/schemas/compare.js +30 -0
- package/dist/admin-api/schemas/compare.js.map +1 -0
- package/dist/admin-api/schemas/dependents.d.ts +15 -0
- package/dist/admin-api/schemas/dependents.d.ts.map +1 -0
- package/dist/admin-api/schemas/dependents.js +14 -0
- package/dist/admin-api/schemas/dependents.js.map +1 -0
- package/dist/admin-api/schemas/fetch.d.ts +12 -0
- package/dist/admin-api/schemas/fetch.d.ts.map +1 -0
- package/dist/admin-api/schemas/fetch.js +11 -0
- package/dist/admin-api/schemas/fetch.js.map +1 -0
- package/dist/admin-api/schemas/fields.d.ts +11 -0
- package/dist/admin-api/schemas/fields.d.ts.map +1 -0
- package/dist/admin-api/schemas/fields.js +11 -0
- package/dist/admin-api/schemas/fields.js.map +1 -0
- package/dist/admin-api/schemas/fragments.d.ts +27 -0
- package/dist/admin-api/schemas/fragments.d.ts.map +1 -0
- package/dist/admin-api/schemas/fragments.js +26 -0
- package/dist/admin-api/schemas/fragments.js.map +1 -0
- package/dist/admin-api/schemas/history.d.ts +73 -0
- package/dist/admin-api/schemas/history.d.ts.map +1 -0
- package/dist/admin-api/schemas/history.js +35 -0
- package/dist/admin-api/schemas/history.js.map +1 -0
- package/dist/admin-api/schemas/index.d.ts +34 -0
- package/dist/admin-api/schemas/index.d.ts.map +1 -0
- package/dist/admin-api/schemas/index.js +34 -0
- package/dist/admin-api/schemas/index.js.map +1 -0
- package/dist/admin-api/schemas/pages.d.ts +46 -0
- package/dist/admin-api/schemas/pages.d.ts.map +1 -0
- package/dist/admin-api/schemas/pages.js +47 -0
- package/dist/admin-api/schemas/pages.js.map +1 -0
- package/dist/admin-api/schemas/publish.d.ts +67 -0
- package/dist/admin-api/schemas/publish.d.ts.map +1 -0
- package/dist/admin-api/schemas/publish.js +60 -0
- package/dist/admin-api/schemas/publish.js.map +1 -0
- package/dist/admin-api/schemas/site.d.ts +28 -0
- package/dist/admin-api/schemas/site.d.ts.map +1 -0
- package/dist/admin-api/schemas/site.js +24 -0
- package/dist/admin-api/schemas/site.js.map +1 -0
- package/dist/admin-api/schemas/targets.d.ts +36 -0
- package/dist/admin-api/schemas/targets.d.ts.map +1 -0
- package/dist/admin-api/schemas/targets.js +19 -0
- package/dist/admin-api/schemas/targets.js.map +1 -0
- package/dist/admin-api/schemas/templates.d.ts +17 -0
- package/dist/admin-api/schemas/templates.d.ts.map +1 -0
- package/dist/admin-api/schemas/templates.js +16 -0
- package/dist/admin-api/schemas/templates.js.map +1 -0
- package/dist/admin-api/source-context.d.ts +158 -0
- package/dist/admin-api/source-context.d.ts.map +1 -0
- package/dist/admin-api/source-context.js +92 -0
- package/dist/admin-api/source-context.js.map +1 -0
- package/dist/app.js +1 -1
- package/dist/app.js.map +1 -1
- package/dist/assemble.d.ts.map +1 -1
- package/dist/assemble.js +4 -1
- package/dist/assemble.js.map +1 -1
- package/dist/assets/analyze-audio.d.ts +3 -0
- package/dist/assets/analyze-audio.d.ts.map +1 -0
- package/dist/assets/analyze-audio.js +80 -0
- package/dist/assets/analyze-audio.js.map +1 -0
- package/dist/assets/analyze-image.d.ts +19 -0
- package/dist/assets/analyze-image.d.ts.map +1 -0
- package/dist/assets/analyze-image.js +123 -0
- package/dist/assets/analyze-image.js.map +1 -0
- package/dist/assets/analyze.d.ts +94 -0
- package/dist/assets/analyze.d.ts.map +1 -0
- package/dist/assets/analyze.js +45 -0
- package/dist/assets/analyze.js.map +1 -0
- package/dist/assets/asset-deps.d.ts +30 -0
- package/dist/assets/asset-deps.d.ts.map +1 -0
- package/dist/assets/asset-deps.js +42 -0
- package/dist/assets/asset-deps.js.map +1 -0
- package/dist/assets/asset-paths.d.ts +155 -0
- package/dist/assets/asset-paths.d.ts.map +1 -0
- package/dist/assets/asset-paths.js +197 -0
- package/dist/assets/asset-paths.js.map +1 -0
- package/dist/assets/delete.d.ts +75 -0
- package/dist/assets/delete.d.ts.map +1 -0
- package/dist/assets/delete.js +82 -0
- package/dist/assets/delete.js.map +1 -0
- package/dist/assets/errors.d.ts +241 -0
- package/dist/assets/errors.d.ts.map +1 -0
- package/dist/assets/errors.js +300 -0
- package/dist/assets/errors.js.map +1 -0
- package/dist/assets/find-refs.d.ts +37 -0
- package/dist/assets/find-refs.d.ts.map +1 -0
- package/dist/assets/find-refs.js +35 -0
- package/dist/assets/find-refs.js.map +1 -0
- package/dist/assets/hash.d.ts +13 -0
- package/dist/assets/hash.d.ts.map +1 -0
- package/dist/assets/hash.js +43 -0
- package/dist/assets/hash.js.map +1 -0
- package/dist/assets/image-metadata.d.ts +11 -0
- package/dist/assets/image-metadata.d.ts.map +1 -0
- package/dist/assets/image-metadata.js +31 -0
- package/dist/assets/image-metadata.js.map +1 -0
- package/dist/assets/ingest-locale.d.ts +86 -0
- package/dist/assets/ingest-locale.d.ts.map +1 -0
- package/dist/assets/ingest-locale.js +209 -0
- package/dist/assets/ingest-locale.js.map +1 -0
- package/dist/assets/ingest.d.ts +96 -0
- package/dist/assets/ingest.d.ts.map +1 -0
- package/dist/assets/ingest.js +308 -0
- package/dist/assets/ingest.js.map +1 -0
- package/dist/assets/kind-compat.d.ts +34 -0
- package/dist/assets/kind-compat.d.ts.map +1 -0
- package/dist/assets/kind-compat.js +33 -0
- package/dist/assets/kind-compat.js.map +1 -0
- package/dist/assets/list.d.ts +46 -0
- package/dist/assets/list.d.ts.map +1 -0
- package/dist/assets/list.js +102 -0
- package/dist/assets/list.js.map +1 -0
- package/dist/assets/manifest-default.d.ts +56 -0
- package/dist/assets/manifest-default.d.ts.map +1 -0
- package/dist/assets/manifest-default.js +120 -0
- package/dist/assets/manifest-default.js.map +1 -0
- package/dist/assets/manifest-filename.d.ts +52 -0
- package/dist/assets/manifest-filename.d.ts.map +1 -0
- package/dist/assets/manifest-filename.js +104 -0
- package/dist/assets/manifest-filename.js.map +1 -0
- package/dist/assets/manifest-locale.d.ts +60 -0
- package/dist/assets/manifest-locale.d.ts.map +1 -0
- package/dist/assets/manifest-locale.js +206 -0
- package/dist/assets/manifest-locale.js.map +1 -0
- package/dist/assets/manifest-merge.d.ts +66 -0
- package/dist/assets/manifest-merge.d.ts.map +1 -0
- package/dist/assets/manifest-merge.js +82 -0
- package/dist/assets/manifest-merge.js.map +1 -0
- package/dist/assets/manifest.d.ts +83 -0
- package/dist/assets/manifest.d.ts.map +1 -0
- package/dist/assets/manifest.js +93 -0
- package/dist/assets/manifest.js.map +1 -0
- package/dist/assets/mime-sniff.d.ts +18 -0
- package/dist/assets/mime-sniff.d.ts.map +1 -0
- package/dist/assets/mime-sniff.js +84 -0
- package/dist/assets/mime-sniff.js.map +1 -0
- package/dist/assets/preprocess-svg.d.ts +3 -0
- package/dist/assets/preprocess-svg.d.ts.map +1 -0
- package/dist/assets/preprocess-svg.js +49 -0
- package/dist/assets/preprocess-svg.js.map +1 -0
- package/dist/assets/preprocess.d.ts +62 -0
- package/dist/assets/preprocess.d.ts.map +1 -0
- package/dist/assets/preprocess.js +86 -0
- package/dist/assets/preprocess.js.map +1 -0
- package/dist/assets/publish-plan.d.ts +41 -0
- package/dist/assets/publish-plan.d.ts.map +1 -0
- package/dist/assets/publish-plan.js +49 -0
- package/dist/assets/publish-plan.js.map +1 -0
- package/dist/assets/publish.d.ts +33 -0
- package/dist/assets/publish.d.ts.map +1 -0
- package/dist/assets/publish.js +81 -0
- package/dist/assets/publish.js.map +1 -0
- package/dist/assets/refs.d.ts +37 -0
- package/dist/assets/refs.d.ts.map +1 -0
- package/dist/assets/refs.js +33 -0
- package/dist/assets/refs.js.map +1 -0
- package/dist/assets/remove-override.d.ts +42 -0
- package/dist/assets/remove-override.d.ts.map +1 -0
- package/dist/assets/remove-override.js +53 -0
- package/dist/assets/remove-override.js.map +1 -0
- package/dist/assets/rename.d.ts +43 -0
- package/dist/assets/rename.d.ts.map +1 -0
- package/dist/assets/rename.js +271 -0
- package/dist/assets/rename.js.map +1 -0
- package/dist/assets/replace.d.ts +37 -0
- package/dist/assets/replace.d.ts.map +1 -0
- package/dist/assets/replace.js +195 -0
- package/dist/assets/replace.js.map +1 -0
- package/dist/assets/resolve.d.ts +141 -0
- package/dist/assets/resolve.d.ts.map +1 -0
- package/dist/assets/resolve.js +381 -0
- package/dist/assets/resolve.js.map +1 -0
- package/dist/assets/rewrite-manifest-asset-ref.d.ts +44 -0
- package/dist/assets/rewrite-manifest-asset-ref.d.ts.map +1 -0
- package/dist/assets/rewrite-manifest-asset-ref.js +51 -0
- package/dist/assets/rewrite-manifest-asset-ref.js.map +1 -0
- package/dist/assets/scan-manifest-for-asset.d.ts +63 -0
- package/dist/assets/scan-manifest-for-asset.d.ts.map +1 -0
- package/dist/assets/scan-manifest-for-asset.js +105 -0
- package/dist/assets/scan-manifest-for-asset.js.map +1 -0
- package/dist/assets/serve-route.d.ts +45 -0
- package/dist/assets/serve-route.d.ts.map +1 -0
- package/dist/assets/serve-route.js +123 -0
- package/dist/assets/serve-route.js.map +1 -0
- package/dist/assets/svg-sanitize.d.ts +38 -0
- package/dist/assets/svg-sanitize.d.ts.map +1 -0
- package/dist/assets/svg-sanitize.js +209 -0
- package/dist/assets/svg-sanitize.js.map +1 -0
- package/dist/assets/update-metadata.d.ts +61 -0
- package/dist/assets/update-metadata.d.ts.map +1 -0
- package/dist/assets/update-metadata.js +82 -0
- package/dist/assets/update-metadata.js.map +1 -0
- package/dist/assets/url.d.ts +82 -0
- package/dist/assets/url.d.ts.map +1 -0
- package/dist/assets/url.js +103 -0
- package/dist/assets/url.js.map +1 -0
- package/dist/assets/validate.d.ts +74 -0
- package/dist/assets/validate.d.ts.map +1 -0
- package/dist/assets/validate.js +136 -0
- package/dist/assets/validate.js.map +1 -0
- package/dist/assets/variants.d.ts +23 -0
- package/dist/assets/variants.d.ts.map +1 -0
- package/dist/assets/variants.js +74 -0
- package/dist/assets/variants.js.map +1 -0
- package/dist/cli/assets-cli.d.ts +58 -0
- package/dist/cli/assets-cli.d.ts.map +1 -0
- package/dist/cli/assets-cli.js +233 -0
- package/dist/cli/assets-cli.js.map +1 -0
- package/dist/cli/assets-display.d.ts +112 -0
- package/dist/cli/assets-display.d.ts.map +1 -0
- package/dist/cli/assets-display.js +106 -0
- package/dist/cli/assets-display.js.map +1 -0
- package/dist/cli/bootstrap.d.ts +46 -0
- package/dist/cli/bootstrap.d.ts.map +1 -0
- package/dist/cli/bootstrap.js +84 -0
- package/dist/cli/bootstrap.js.map +1 -0
- package/dist/cli/history.d.ts +45 -0
- package/dist/cli/history.d.ts.map +1 -0
- package/dist/cli/history.js +165 -0
- package/dist/cli/history.js.map +1 -0
- package/dist/cli/index.js +689 -128
- package/dist/cli/index.js.map +1 -1
- package/dist/compare.d.ts +8 -5
- package/dist/compare.d.ts.map +1 -1
- package/dist/compare.js +62 -20
- package/dist/compare.js.map +1 -1
- package/dist/content-root.d.ts +38 -0
- package/dist/content-root.d.ts.map +1 -0
- package/dist/content-root.js +29 -0
- package/dist/content-root.js.map +1 -0
- package/dist/dep-sidecars.d.ts +127 -0
- package/dist/dep-sidecars.d.ts.map +1 -0
- package/dist/dep-sidecars.js +122 -0
- package/dist/dep-sidecars.js.map +1 -0
- package/dist/editor/AssetEmbeddedWidget.d.ts +3 -0
- package/dist/editor/AssetEmbeddedWidget.d.ts.map +1 -0
- package/dist/editor/AssetEmbeddedWidget.js +146 -0
- package/dist/editor/AssetEmbeddedWidget.js.map +1 -0
- package/dist/editor/mount.d.ts +12 -1
- package/dist/editor/mount.d.ts.map +1 -1
- package/dist/editor/mount.js +96 -33
- package/dist/editor/mount.js.map +1 -1
- package/dist/format.d.ts +44 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +65 -0
- package/dist/format.js.map +1 -0
- package/dist/fragment-deps.d.ts +24 -0
- package/dist/fragment-deps.d.ts.map +1 -0
- package/dist/fragment-deps.js +20 -0
- package/dist/fragment-deps.js.map +1 -0
- package/dist/hash.d.ts +34 -9
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js +60 -21
- package/dist/hash.js.map +1 -1
- package/dist/history-provider.d.ts +49 -0
- package/dist/history-provider.d.ts.map +1 -0
- package/dist/history-provider.js +248 -0
- package/dist/history-provider.js.map +1 -0
- package/dist/history-recorder.d.ts +102 -0
- package/dist/history-recorder.d.ts.map +1 -0
- package/dist/history-recorder.js +168 -0
- package/dist/history-recorder.js.map +1 -0
- package/dist/history-restorer.d.ts +46 -0
- package/dist/history-restorer.d.ts.map +1 -0
- package/dist/history-restorer.js +137 -0
- package/dist/history-restorer.js.map +1 -0
- package/dist/history.d.ts +129 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +25 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +27 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/locale.d.ts +94 -0
- package/dist/locale.d.ts.map +1 -0
- package/dist/locale.js +188 -0
- package/dist/locale.js.map +1 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +16 -1
- package/dist/manifest.js.map +1 -1
- package/dist/providers/_atomic-write.d.ts +9 -0
- package/dist/providers/_atomic-write.d.ts.map +1 -0
- package/dist/providers/_atomic-write.js +72 -0
- package/dist/providers/_atomic-write.js.map +1 -0
- package/dist/providers/_rm-ignore-missing.d.ts +31 -0
- package/dist/providers/_rm-ignore-missing.d.ts.map +1 -0
- package/dist/providers/_rm-ignore-missing.js +12 -0
- package/dist/providers/_rm-ignore-missing.js.map +1 -0
- package/dist/providers/_stream-interop.d.ts +23 -0
- package/dist/providers/_stream-interop.d.ts.map +1 -0
- package/dist/providers/_stream-interop.js +21 -0
- package/dist/providers/_stream-interop.js.map +1 -0
- package/dist/providers/azure-blob.d.ts.map +1 -1
- package/dist/providers/azure-blob.js +60 -0
- package/dist/providers/azure-blob.js.map +1 -1
- package/dist/providers/filesystem.d.ts +4 -0
- package/dist/providers/filesystem.d.ts.map +1 -1
- package/dist/providers/filesystem.js +63 -2
- package/dist/providers/filesystem.js.map +1 -1
- package/dist/providers/s3.d.ts.map +1 -1
- package/dist/providers/s3.js +107 -16
- package/dist/providers/s3.js.map +1 -1
- package/dist/publish-locale.d.ts +44 -0
- package/dist/publish-locale.d.ts.map +1 -0
- package/dist/publish-locale.js +103 -0
- package/dist/publish-locale.js.map +1 -0
- package/dist/publish-rendered.d.ts +52 -21
- package/dist/publish-rendered.d.ts.map +1 -1
- package/dist/publish-rendered.js +144 -87
- package/dist/publish-rendered.js.map +1 -1
- package/dist/publish.d.ts +18 -19
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +41 -56
- package/dist/publish.js.map +1 -1
- package/dist/renderer.d.ts +14 -4
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +35 -23
- package/dist/renderer.js.map +1 -1
- package/dist/resolver.d.ts +17 -2
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +112 -16
- package/dist/resolver.js.map +1 -1
- package/dist/robots.d.ts +22 -0
- package/dist/robots.d.ts.map +1 -0
- package/dist/robots.js +25 -0
- package/dist/robots.js.map +1 -0
- package/dist/schema/dimensions.d.ts +78 -0
- package/dist/schema/dimensions.d.ts.map +1 -0
- package/dist/schema/dimensions.js +97 -0
- package/dist/schema/dimensions.js.map +1 -0
- package/dist/schema/helpers.d.ts +108 -0
- package/dist/schema/helpers.d.ts.map +1 -0
- package/dist/schema/helpers.js +133 -0
- package/dist/schema/helpers.js.map +1 -0
- package/dist/schema/index.d.ts +27 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +25 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/types.d.ts +390 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +25 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/selector-chain.d.ts +63 -0
- package/dist/selector-chain.d.ts.map +1 -0
- package/dist/selector-chain.js +58 -0
- package/dist/selector-chain.js.map +1 -0
- package/dist/seo.d.ts +56 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +72 -0
- package/dist/seo.js.map +1 -0
- package/dist/serve.d.ts +41 -3
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +206 -65
- package/dist/serve.js.map +1 -1
- package/dist/sidecars.d.ts +26 -21
- package/dist/sidecars.d.ts.map +1 -1
- package/dist/sidecars.js +143 -45
- package/dist/sidecars.js.map +1 -1
- package/dist/site-loader.d.ts +74 -6
- package/dist/site-loader.d.ts.map +1 -1
- package/dist/site-loader.js +138 -28
- package/dist/site-loader.js.map +1 -1
- package/dist/sitemap.d.ts +45 -0
- package/dist/sitemap.d.ts.map +1 -0
- package/dist/sitemap.js +67 -0
- package/dist/sitemap.js.map +1 -0
- package/dist/targets.d.ts +47 -1
- package/dist/targets.d.ts.map +1 -1
- package/dist/targets.js +83 -36
- package/dist/targets.js.map +1 -1
- package/dist/template-loader.d.ts +7 -3
- package/dist/template-loader.d.ts.map +1 -1
- package/dist/template-loader.js +27 -12
- package/dist/template-loader.js.map +1 -1
- package/dist/templates-scan-worker.js +1 -1
- package/dist/templates-scan-worker.js.map +1 -1
- package/dist/templates-scan.d.ts.map +1 -1
- package/dist/templates-scan.js +1 -1
- package/dist/templates-scan.js.map +1 -1
- package/dist/themes.d.ts +69 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +85 -0
- package/dist/themes.js.map +1 -0
- package/dist/transforms/adapter.d.ts +115 -0
- package/dist/transforms/adapter.d.ts.map +1 -0
- package/dist/transforms/adapter.js +2 -0
- package/dist/transforms/adapter.js.map +1 -0
- package/dist/transforms/cloudflare.d.ts +17 -0
- package/dist/transforms/cloudflare.d.ts.map +1 -0
- package/dist/transforms/cloudflare.js +110 -0
- package/dist/transforms/cloudflare.js.map +1 -0
- package/dist/transforms/index.d.ts +24 -0
- package/dist/transforms/index.d.ts.map +1 -0
- package/dist/transforms/index.js +30 -0
- package/dist/transforms/index.js.map +1 -0
- package/dist/transforms/sharp.d.ts +3 -0
- package/dist/transforms/sharp.d.ts.map +1 -0
- package/dist/transforms/sharp.js +43 -0
- package/dist/transforms/sharp.js.map +1 -0
- package/dist/types.d.ts +241 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +28 -5
- package/dist/types.js.map +1 -1
- package/dist/workers/cloudflare-r2.d.ts +11 -2
- package/dist/workers/cloudflare-r2.d.ts.map +1 -1
- package/dist/workers/cloudflare-r2.js +120 -55
- package/dist/workers/cloudflare-r2.js.map +1 -1
- package/package.json +30 -2
- package/admin-dist/assets/index-BZAFKsUp.js +0 -608
- package/admin-dist/assets/index-BpRotMuK.css +0 -1
- package/admin-dist/assets/vendor-vue-DSjyxCX6.js +0 -1
- package/dist/providers/r2.d.ts +0 -8
- package/dist/providers/r2.d.ts.map +0 -1
- package/dist/providers/r2.js +0 -83
- package/dist/providers/r2.js.map +0 -1
- package/dist/source-sidecars.d.ts +0 -13
- package/dist/source-sidecars.d.ts.map +0 -1
- package/dist/source-sidecars.js +0 -52
- package/dist/source-sidecars.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { resolve, join, dirname } from 'node:path';
|
|
2
|
+
import { resolve, join, dirname, relative } from 'node:path';
|
|
3
3
|
import { watch, existsSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { serve } from '@hono/node-server';
|
|
5
5
|
import { serveStatic } from '@hono/node-server/serve-static';
|
|
@@ -11,18 +11,19 @@ import { resolvePage } from '../resolver.js';
|
|
|
11
11
|
import { renderPage } from '../renderer.js';
|
|
12
12
|
import { createFilesystemProvider } from '../providers/filesystem.js';
|
|
13
13
|
import { invalidateTemplate, invalidateAllTemplates } from '../template-loader.js';
|
|
14
|
+
import { getEnvironment, getType, isEditable } from '../types.js';
|
|
14
15
|
import { createAdminApp } from '../admin-api/index.js';
|
|
15
16
|
// ANSI color helpers — no dependency, suppressed when NO_COLOR or CI
|
|
16
17
|
const noColor = !!process.env.NO_COLOR || !process.stdout.isTTY;
|
|
17
18
|
const c = {
|
|
18
|
-
bold: (s) => noColor ? s : `\x1b[1m${s}\x1b[22m
|
|
19
|
-
dim: (s) => noColor ? s : `\x1b[2m${s}\x1b[22m
|
|
20
|
-
cyan: (s) => noColor ? s : `\x1b[36m${s}\x1b[39m
|
|
21
|
-
green: (s) => noColor ? s : `\x1b[32m${s}\x1b[39m
|
|
22
|
-
yellow: (s) => noColor ? s : `\x1b[33m${s}\x1b[39m
|
|
23
|
-
red: (s) => noColor ? s : `\x1b[31m${s}\x1b[39m
|
|
24
|
-
magenta: (s) => noColor ? s : `\x1b[35m${s}\x1b[39m
|
|
25
|
-
bgGreen: (s) => noColor ? s : `\x1b[42m\x1b[30m${s}\x1b[39m\x1b[49m
|
|
19
|
+
bold: (s) => (noColor ? s : `\x1b[1m${s}\x1b[22m`),
|
|
20
|
+
dim: (s) => (noColor ? s : `\x1b[2m${s}\x1b[22m`),
|
|
21
|
+
cyan: (s) => (noColor ? s : `\x1b[36m${s}\x1b[39m`),
|
|
22
|
+
green: (s) => (noColor ? s : `\x1b[32m${s}\x1b[39m`),
|
|
23
|
+
yellow: (s) => (noColor ? s : `\x1b[33m${s}\x1b[39m`),
|
|
24
|
+
red: (s) => (noColor ? s : `\x1b[31m${s}\x1b[39m`),
|
|
25
|
+
magenta: (s) => (noColor ? s : `\x1b[35m${s}\x1b[39m`),
|
|
26
|
+
bgGreen: (s) => (noColor ? s : `\x1b[42m\x1b[30m${s}\x1b[39m\x1b[49m`),
|
|
26
27
|
};
|
|
27
28
|
const args = process.argv.slice(2);
|
|
28
29
|
const command = args[0];
|
|
@@ -134,11 +135,26 @@ function printHelp() {
|
|
|
134
135
|
gazetta serve [target] [site] Serve published pages from target storage
|
|
135
136
|
gazetta deploy [target] [site] Deploy worker to hosting (one-time setup)
|
|
136
137
|
gazetta validate [site] Check site for broken references
|
|
138
|
+
gazetta translate <item> --to <locale> [target]
|
|
139
|
+
Create a locale copy of a page or fragment
|
|
140
|
+
gazetta history [target] [site] List revisions on a target
|
|
141
|
+
gazetta undo [target] [site] Restore the previous revision (soft undo)
|
|
142
|
+
gazetta rollback <rev> [target] [site]
|
|
143
|
+
Restore an arbitrary revision by id
|
|
144
|
+
gazetta assets list [target] [site]
|
|
145
|
+
List assets on a target
|
|
146
|
+
gazetta assets info <name> [target] [site]
|
|
147
|
+
Show full detail (variants, overrides, refs) for one asset
|
|
148
|
+
gazetta assets reindex [target] [site]
|
|
149
|
+
Rebuild the asset-refs sidecar index from manifests
|
|
137
150
|
gazetta help Show this help message
|
|
138
151
|
|
|
139
152
|
Options:
|
|
140
153
|
--port, -p <port> Server port (default: 3000)
|
|
141
154
|
--force, -f Publish all items (skip unchanged check)
|
|
155
|
+
--yes, -y Skip confirmation prompt (required in CI
|
|
156
|
+
for undo/rollback on production targets)
|
|
157
|
+
--limit <n> Max revisions to list (default: 50)
|
|
142
158
|
|
|
143
159
|
Auto-detection:
|
|
144
160
|
Site is auto-detected from sites/ directory. If multiple sites exist,
|
|
@@ -155,12 +171,17 @@ function printHelp() {
|
|
|
155
171
|
gazetta publish production my-site # publish specific site to production
|
|
156
172
|
gazetta serve production -p 8080 # serve production on port 8080
|
|
157
173
|
gazetta validate # check site for errors
|
|
174
|
+
gazetta history # list revisions on default target
|
|
175
|
+
gazetta undo production --yes # undo last write on production (CI-safe)
|
|
176
|
+
gazetta rollback rev-1776337441608 # roll back to a specific revision
|
|
158
177
|
`);
|
|
159
178
|
}
|
|
160
179
|
function parseArgs(input) {
|
|
161
180
|
const positional = [];
|
|
162
181
|
let port;
|
|
163
182
|
let force = false;
|
|
183
|
+
let yes = false;
|
|
184
|
+
let limit;
|
|
164
185
|
for (let i = 0; i < input.length; i++) {
|
|
165
186
|
if (input[i] === '--port' || input[i] === '-p') {
|
|
166
187
|
port = parseInt(input[++i], 10);
|
|
@@ -168,11 +189,23 @@ function parseArgs(input) {
|
|
|
168
189
|
else if (input[i] === '--force' || input[i] === '-f') {
|
|
169
190
|
force = true;
|
|
170
191
|
}
|
|
192
|
+
else if (input[i] === '--yes' || input[i] === '-y') {
|
|
193
|
+
yes = true;
|
|
194
|
+
}
|
|
195
|
+
else if (input[i] === '--limit') {
|
|
196
|
+
limit = parseInt(input[++i], 10);
|
|
197
|
+
}
|
|
198
|
+
else if (input[i] === '--to') {
|
|
199
|
+
i++; // consume the locale value — translate command reads it from raw args
|
|
200
|
+
}
|
|
201
|
+
else if (input[i].startsWith('--to=')) {
|
|
202
|
+
// consumed by translate command directly
|
|
203
|
+
}
|
|
171
204
|
else if (!input[i].startsWith('-')) {
|
|
172
205
|
positional.push(input[i]);
|
|
173
206
|
}
|
|
174
207
|
}
|
|
175
|
-
return { positional, port, force };
|
|
208
|
+
return { positional, port, force, yes, limit };
|
|
176
209
|
}
|
|
177
210
|
/**
|
|
178
211
|
* Resolve the site directory from positional args or auto-detection.
|
|
@@ -203,8 +236,7 @@ async function resolveSiteDir(positionalSite) {
|
|
|
203
236
|
const sitesDir = resolve('sites');
|
|
204
237
|
if (existsSync(sitesDir)) {
|
|
205
238
|
const { readdirSync, statSync } = await import('node:fs');
|
|
206
|
-
const sites = readdirSync(sitesDir)
|
|
207
|
-
.filter(name => {
|
|
239
|
+
const sites = readdirSync(sitesDir).filter(name => {
|
|
208
240
|
const dir = join(sitesDir, name);
|
|
209
241
|
return statSync(dir).isDirectory() && existsSync(join(dir, 'site.yaml'));
|
|
210
242
|
});
|
|
@@ -267,7 +299,7 @@ async function runInit(dir) {
|
|
|
267
299
|
}
|
|
268
300
|
const name = target.split('/').pop() ?? 'my-site';
|
|
269
301
|
const files = {
|
|
270
|
-
'sites/main/site.yaml': `name: ${name}\nversion: 1.0.0\nsystemPages:\n - "404"\ntargets:\n local:\n storage:\n type: filesystem\n
|
|
302
|
+
'sites/main/site.yaml': `name: ${name}\nversion: 1.0.0\nsystemPages:\n - "404"\ntargets:\n local:\n storage:\n type: filesystem\n # environment=local, editable=true (defaults); path=./targets/local (default)\n`,
|
|
271
303
|
'templates/page-layout/index.ts': `import { z } from 'zod'
|
|
272
304
|
import type { TemplateFunction } from 'gazetta'
|
|
273
305
|
|
|
@@ -356,20 +388,28 @@ const template: TemplateFunction = ({ content = {} }) => {
|
|
|
356
388
|
|
|
357
389
|
export default template
|
|
358
390
|
`,
|
|
359
|
-
'sites/main/fragments/header/fragment.json': JSON.stringify({
|
|
391
|
+
'sites/main/targets/local/fragments/header/fragment.json': JSON.stringify({
|
|
360
392
|
template: 'nav',
|
|
361
393
|
content: { brand: name, links: [{ label: 'Home', href: '/' }] },
|
|
362
394
|
}, null, 2) + '\n',
|
|
363
|
-
'sites/main/pages/home/page.json': JSON.stringify({
|
|
395
|
+
'sites/main/targets/local/pages/home/page.json': JSON.stringify({
|
|
364
396
|
template: 'page-layout',
|
|
365
397
|
content: { title: name, description: 'A site built with Gazetta' },
|
|
366
398
|
components: [
|
|
367
399
|
'@header',
|
|
368
|
-
{
|
|
369
|
-
|
|
400
|
+
{
|
|
401
|
+
name: 'hero',
|
|
402
|
+
template: 'hero',
|
|
403
|
+
content: { title: `Welcome to ${name}`, subtitle: 'A site built with Gazetta' },
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: 'intro',
|
|
407
|
+
template: 'text-block',
|
|
408
|
+
content: { body: '<p>Edit this content in the CMS at <a href="/admin">/admin</a>.</p>' },
|
|
409
|
+
},
|
|
370
410
|
],
|
|
371
411
|
}, null, 2) + '\n',
|
|
372
|
-
'sites/main/pages/404/page.json': JSON.stringify({
|
|
412
|
+
'sites/main/targets/local/pages/404/page.json': JSON.stringify({
|
|
373
413
|
template: 'page-layout',
|
|
374
414
|
content: { title: 'Page Not Found', description: "The page you're looking for doesn't exist." },
|
|
375
415
|
}, null, 2) + '\n',
|
|
@@ -414,23 +454,24 @@ export default template
|
|
|
414
454
|
outro(`Done! Run: ${c.cyan(`${cdStep}npx gazetta dev`)}`);
|
|
415
455
|
}
|
|
416
456
|
async function runPublish(siteDir, targetName, opts = {}) {
|
|
417
|
-
const storage = createFilesystemProvider();
|
|
418
457
|
const projectRoot = detectProjectRoot(siteDir);
|
|
419
458
|
const templatesDir = join(projectRoot, 'templates');
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
459
|
+
// Source comes from the default editable target in site.yaml.
|
|
460
|
+
const { buildSourceContext } = await import('./bootstrap.js');
|
|
461
|
+
let source, manifest, targetConfigs;
|
|
462
|
+
try {
|
|
463
|
+
;
|
|
464
|
+
({ source, manifest, targetConfigs } = await buildSourceContext({ projectSiteDir: siteDir }));
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
console.error(`\n ${c.red('Error:')} ${err.message}\n`);
|
|
425
468
|
process.exit(1);
|
|
426
469
|
}
|
|
427
|
-
const
|
|
470
|
+
const storage = source.storage;
|
|
471
|
+
const site = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest });
|
|
472
|
+
const siteYaml = manifest;
|
|
428
473
|
if (!siteYaml.targets || Object.keys(siteYaml.targets).length === 0) {
|
|
429
|
-
console.error(`\n Error: no targets configured in
|
|
430
|
-
console.error(`\n Add a target to site.yaml:\n`);
|
|
431
|
-
console.error(` targets:`);
|
|
432
|
-
console.error(` staging:`);
|
|
433
|
-
console.error(` storage: { type: filesystem, path: ./dist/staging }\n`);
|
|
474
|
+
console.error(`\n Error: no targets configured in site.yaml`);
|
|
434
475
|
process.exit(1);
|
|
435
476
|
}
|
|
436
477
|
// Determine which targets to publish to
|
|
@@ -444,7 +485,8 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
444
485
|
// Initialize targets
|
|
445
486
|
const { createTargetRegistry } = await import('../targets.js');
|
|
446
487
|
const targets = await createTargetRegistry(Object.fromEntries(targetNames.map(n => [n, siteYaml.targets[n]])), siteDir);
|
|
447
|
-
const { publishPageRendered, publishPageStatic, publishFragmentRendered, publishSiteManifest,
|
|
488
|
+
const { publishPageRendered, publishPageStatic, publishFragmentRendered, publishSiteManifest, publishDepIndices } = await import('../publish-rendered.js');
|
|
489
|
+
const { publishPageAllLocales, publishFragmentAllLocales } = await import('../publish-locale.js');
|
|
448
490
|
const { scanTemplates, templateHashesFrom, reportTemplateErrors } = await import('../templates-scan.js');
|
|
449
491
|
const { hashManifest } = await import('../hash.js');
|
|
450
492
|
// Validate + hash templates once for this publish run
|
|
@@ -469,10 +511,10 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
469
511
|
continue;
|
|
470
512
|
}
|
|
471
513
|
const targetConfig = siteYaml.targets[name];
|
|
472
|
-
const {
|
|
473
|
-
const
|
|
474
|
-
const isStatic =
|
|
475
|
-
console.log(` ${c.bold(name)} ${c.dim(`(${
|
|
514
|
+
const { getType } = await import('../types.js');
|
|
515
|
+
const targetType = targetConfig ? getType(targetConfig) : 'static';
|
|
516
|
+
const isStatic = targetType === 'static';
|
|
517
|
+
console.log(` ${c.bold(name)} ${c.dim(`(${targetType})`)}`);
|
|
476
518
|
let totalFiles = 0;
|
|
477
519
|
let totalRemoved = 0;
|
|
478
520
|
// Incremental: compare source hashes against target sidecars, skip
|
|
@@ -481,18 +523,47 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
481
523
|
if (!opts.force) {
|
|
482
524
|
const { compareTargets } = await import('../compare.js');
|
|
483
525
|
const cmp = await compareTargets({
|
|
484
|
-
|
|
526
|
+
sourceRoot: source.contentRoot,
|
|
485
527
|
target: targetStorage,
|
|
486
|
-
siteDir,
|
|
487
528
|
templatesDir,
|
|
488
529
|
projectRoot,
|
|
489
|
-
|
|
530
|
+
type: targetType,
|
|
490
531
|
scanTemplates: async () => templateInfos,
|
|
532
|
+
manifest,
|
|
491
533
|
});
|
|
492
534
|
for (const item of cmp.unchanged)
|
|
493
535
|
unchanged.add(item);
|
|
494
536
|
}
|
|
495
537
|
let skipped = 0;
|
|
538
|
+
const sourceRoot = source.contentRoot;
|
|
539
|
+
// Asset publish — before any page render, so static-mode page HTML
|
|
540
|
+
// doesn't bake in URLs to bytes that aren't on the target yet. Skips
|
|
541
|
+
// assets that are already on target (content-addressed dedupe).
|
|
542
|
+
{
|
|
543
|
+
const { publishAssets } = await import('../assets/publish.js');
|
|
544
|
+
const { createContentRoot } = await import('../content-root.js');
|
|
545
|
+
const targetRoot = createContentRoot(targetStorage);
|
|
546
|
+
const itemNames = [
|
|
547
|
+
...[...site.pages.keys()].map(n => `pages/${n}`),
|
|
548
|
+
...[...site.fragments.keys()].map(n => `fragments/${n}`),
|
|
549
|
+
];
|
|
550
|
+
const assetResult = await publishAssets({ sourceRoot, targetRoot, itemNames });
|
|
551
|
+
if (!assetResult.ok) {
|
|
552
|
+
console.error(` ${c.red('✗')} Asset publish failed: source is missing — ${assetResult.missing.join(', ')}`);
|
|
553
|
+
process.exit(1);
|
|
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
|
+
};
|
|
496
567
|
if (isStatic) {
|
|
497
568
|
// Static mode — fully assembled HTML, no fragments needed separately.
|
|
498
569
|
// Page hash must include fragment hashes so a fragment change
|
|
@@ -508,42 +579,182 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
508
579
|
continue;
|
|
509
580
|
}
|
|
510
581
|
const manifestHash = hashManifest(page, { templateHashes, fragmentHashes });
|
|
511
|
-
const { files } = await publishPageStatic(pageName,
|
|
582
|
+
const { files } = await publishPageStatic(pageName, sourceRoot, targetStorage, templatesDir, manifestHash, site, seo);
|
|
512
583
|
totalFiles += files;
|
|
513
584
|
console.log(` ${c.green('✓')} ${pageName}`);
|
|
514
585
|
}
|
|
515
586
|
}
|
|
516
587
|
else {
|
|
517
588
|
// ESI mode — fragments separate, pages with placeholders
|
|
518
|
-
for (const [fragName
|
|
519
|
-
|
|
589
|
+
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
|
+
const fragLocales = site.fragmentLocales.get(fragName);
|
|
595
|
+
if (fragLocales) {
|
|
596
|
+
for (const loc of fragLocales.locales.keys()) {
|
|
597
|
+
if (unchanged.has(`fragments/${fragName}:${loc}`))
|
|
598
|
+
fragUnchanged.add(loc);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// Skip entirely if all locales unchanged
|
|
602
|
+
const totalFragLocales = 1 + (fragLocales?.locales.size ?? 0);
|
|
603
|
+
if (fragUnchanged.size >= totalFragLocales) {
|
|
520
604
|
skipped++;
|
|
521
605
|
continue;
|
|
522
606
|
}
|
|
523
|
-
const
|
|
524
|
-
const { files, removed } = await publishFragmentRendered(fragName, storage, siteDir, targetStorage, templatesDir, manifestHash, site);
|
|
607
|
+
const { files, removed } = await publishFragmentAllLocales(fragName, sourceRoot, targetStorage, site, { templateHashes }, { templatesDir, targetLocales: targetConfig?.locales, unchangedLocales: fragUnchanged });
|
|
525
608
|
totalFiles += files;
|
|
526
609
|
totalRemoved += removed;
|
|
527
|
-
|
|
610
|
+
const skippedCount = fragUnchanged.size > 0 ? ` (${fragUnchanged.size} locale${fragUnchanged.size > 1 ? 's' : ''} skipped)` : '';
|
|
611
|
+
console.log(` ${c.green('✓')} @${fragName}${skippedCount}`);
|
|
528
612
|
}
|
|
529
|
-
for (const [pageName
|
|
530
|
-
|
|
613
|
+
for (const [pageName] of site.pages) {
|
|
614
|
+
// Build per-locale unchanged set
|
|
615
|
+
const pageUnchanged = new Set();
|
|
616
|
+
if (unchanged.has(`pages/${pageName}`))
|
|
617
|
+
pageUnchanged.add(null);
|
|
618
|
+
const pageLocales = site.pageLocales.get(pageName);
|
|
619
|
+
if (pageLocales) {
|
|
620
|
+
for (const loc of pageLocales.locales.keys()) {
|
|
621
|
+
if (unchanged.has(`pages/${pageName}:${loc}`))
|
|
622
|
+
pageUnchanged.add(loc);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const totalPageLocales = 1 + (pageLocales?.locales.size ?? 0);
|
|
626
|
+
if (pageUnchanged.size >= totalPageLocales) {
|
|
531
627
|
skipped++;
|
|
532
628
|
continue;
|
|
533
629
|
}
|
|
534
|
-
const
|
|
535
|
-
|
|
630
|
+
const { files, removed } = await publishPageAllLocales(pageName, sourceRoot, targetStorage, site, { templateHashes }, {
|
|
631
|
+
cache: targetConfig?.cache,
|
|
632
|
+
templatesDir,
|
|
633
|
+
seo,
|
|
634
|
+
targetLocales: targetConfig?.locales,
|
|
635
|
+
unchangedLocales: pageUnchanged,
|
|
636
|
+
});
|
|
536
637
|
totalFiles += files;
|
|
537
638
|
totalRemoved += removed;
|
|
538
|
-
|
|
639
|
+
const skippedCount = pageUnchanged.size > 0 ? ` (${pageUnchanged.size} locale${pageUnchanged.size > 1 ? 's' : ''} skipped)` : '';
|
|
640
|
+
console.log(` ${c.green('✓')} ${pageName}${skippedCount}`);
|
|
539
641
|
}
|
|
540
642
|
}
|
|
541
643
|
if (skipped > 0)
|
|
542
644
|
console.log(` ${c.dim(`· ${skipped} unchanged (skipped)`)}`);
|
|
543
|
-
// Site manifest +
|
|
544
|
-
await publishSiteManifest(
|
|
545
|
-
await
|
|
546
|
-
totalFiles +=
|
|
645
|
+
// Site manifest + dep-sidecar indices
|
|
646
|
+
await publishSiteManifest(sourceRoot, targetStorage, site);
|
|
647
|
+
await publishDepIndices(sourceRoot, targetStorage, site);
|
|
648
|
+
totalFiles += 1;
|
|
649
|
+
// Sitemap + robots.txt — generated from target sidecars
|
|
650
|
+
const siteUrl = targetConfig?.siteUrl;
|
|
651
|
+
if (siteUrl) {
|
|
652
|
+
const { listSidecars } = await import('../sidecars.js');
|
|
653
|
+
const { generateSitemap } = await import('../sitemap.js');
|
|
654
|
+
const { generateRobotsTxt } = await import('../robots.js');
|
|
655
|
+
const targetPageSidecars = await listSidecars(targetStorage, 'pages');
|
|
656
|
+
// Merge source-side knowledge — listSidecars may miss just-written
|
|
657
|
+
// entries on R2 due to eventual list-after-write consistency. Every
|
|
658
|
+
// page we just published gets an entry even if the listing missed it.
|
|
659
|
+
const now = new Date().toISOString();
|
|
660
|
+
for (const [pageName, page] of site.pages) {
|
|
661
|
+
if (!targetPageSidecars.has(pageName)) {
|
|
662
|
+
targetPageSidecars.set(pageName, {
|
|
663
|
+
hash: '',
|
|
664
|
+
pub: { lastPublished: now, noindex: !!page.metadata?.robots?.includes('noindex') },
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
for (const [pageName, localeEntry] of site.pageLocales) {
|
|
669
|
+
for (const [loc, localePage] of localeEntry.locales) {
|
|
670
|
+
const key = `${pageName}:${loc}`;
|
|
671
|
+
if (!targetPageSidecars.has(key)) {
|
|
672
|
+
targetPageSidecars.set(key, {
|
|
673
|
+
hash: '',
|
|
674
|
+
pub: { lastPublished: now, noindex: !!localePage.metadata?.robots?.includes('noindex') },
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
const { resolveSiteLocales, defaultLocaleFor } = await import('../locale.js');
|
|
680
|
+
// Build hreflang groups — two strategies:
|
|
681
|
+
// 1. Subpath: same siteUrl, multiple locales → locale-prefixed routes
|
|
682
|
+
// 2. Cross-domain: other targets with different siteUrl → cross-link
|
|
683
|
+
const resolvedLoc = resolveSiteLocales(manifest);
|
|
684
|
+
const defLoc = defaultLocaleFor(manifest);
|
|
685
|
+
const hreflangGroups = new Map();
|
|
686
|
+
if (resolvedLoc) {
|
|
687
|
+
const { localeRoutePrefix } = await import('../locale.js');
|
|
688
|
+
const thisTargetLocales = targetConfig?.locales ?? resolvedLoc.supported;
|
|
689
|
+
const thisTargetDefault = targetConfig?.locale ?? defLoc;
|
|
690
|
+
for (const [pageName, page] of site.pages) {
|
|
691
|
+
if (pageName.includes('['))
|
|
692
|
+
continue;
|
|
693
|
+
const alternates = [];
|
|
694
|
+
// Subpath alternates on this target
|
|
695
|
+
if (thisTargetLocales.length > 1) {
|
|
696
|
+
for (const loc of thisTargetLocales) {
|
|
697
|
+
const prefix = localeRoutePrefix(loc, { ...resolvedLoc, default: thisTargetDefault });
|
|
698
|
+
const route = page.route === '/' ? prefix || '/' : `${prefix}${page.route}`;
|
|
699
|
+
alternates.push({ locale: loc, url: `${siteUrl}${route}` });
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
// Single-locale target — add self
|
|
704
|
+
alternates.push({ locale: thisTargetLocales[0] ?? defLoc, url: `${siteUrl}${page.route}` });
|
|
705
|
+
}
|
|
706
|
+
// Cross-domain alternates from other targets
|
|
707
|
+
for (const [otherName, otherConfig] of Object.entries(siteYaml.targets ?? {})) {
|
|
708
|
+
if (otherName === name)
|
|
709
|
+
continue; // skip self
|
|
710
|
+
if (!otherConfig.siteUrl)
|
|
711
|
+
continue;
|
|
712
|
+
const otherLocales = otherConfig.locales ?? resolvedLoc.supported;
|
|
713
|
+
const otherDefault = otherConfig.locale ?? defLoc;
|
|
714
|
+
for (const loc of otherLocales) {
|
|
715
|
+
// Skip locales already covered by this target
|
|
716
|
+
if (alternates.some(a => a.locale === loc))
|
|
717
|
+
continue;
|
|
718
|
+
const otherResolved = { ...resolvedLoc, default: otherDefault };
|
|
719
|
+
const prefix = localeRoutePrefix(loc, otherResolved);
|
|
720
|
+
const route = page.route === '/' ? prefix || '/' : `${prefix}${page.route}`;
|
|
721
|
+
alternates.push({ locale: loc, url: `${otherConfig.siteUrl}${route}` });
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (alternates.length > 1) {
|
|
725
|
+
hreflangGroups.set(pageName, alternates);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const sitemapXml = generateSitemap({
|
|
730
|
+
siteUrl,
|
|
731
|
+
pages: targetPageSidecars,
|
|
732
|
+
systemPages: site.manifest.systemPages,
|
|
733
|
+
hreflangGroups: hreflangGroups.size > 0 ? hreflangGroups : undefined,
|
|
734
|
+
defaultLocale: defLoc,
|
|
735
|
+
});
|
|
736
|
+
if (sitemapXml) {
|
|
737
|
+
await targetStorage.writeFile('sitemap.xml', sitemapXml);
|
|
738
|
+
totalFiles++;
|
|
739
|
+
console.log(` ${c.dim('· sitemap.xml')}`);
|
|
740
|
+
}
|
|
741
|
+
// robots.txt: only at the domain root — Google ignores robots.txt at
|
|
742
|
+
// subpaths. If siteUrl has a path component, the domain root belongs
|
|
743
|
+
// to someone else (host, reverse proxy, another app).
|
|
744
|
+
const isRootDeploy = !new URL(siteUrl).pathname.replace(/\/+$/, '');
|
|
745
|
+
if (isRootDeploy) {
|
|
746
|
+
let robotsTxt;
|
|
747
|
+
try {
|
|
748
|
+
robotsTxt = await source.contentRoot.storage.readFile(source.contentRoot.path('robots.txt'));
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
robotsTxt = generateRobotsTxt({ siteUrl });
|
|
752
|
+
}
|
|
753
|
+
await targetStorage.writeFile('robots.txt', robotsTxt);
|
|
754
|
+
totalFiles++;
|
|
755
|
+
console.log(` ${c.dim('· robots.txt')}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
547
758
|
const removedMsg = totalRemoved > 0 ? c.dim(` (${totalRemoved} old files cleaned)`) : '';
|
|
548
759
|
console.log(`\n ${c.green('✓')} ${c.bold(name)}: ${totalFiles} files published${removedMsg}\n`);
|
|
549
760
|
}
|
|
@@ -561,7 +772,8 @@ async function runPublish(siteDir, targetName, opts = {}) {
|
|
|
561
772
|
}
|
|
562
773
|
try {
|
|
563
774
|
const { lookupCloudflareZoneId } = await import('../publish-rendered.js');
|
|
564
|
-
const zoneId = resolveEnvVars(purge.zoneId) ??
|
|
775
|
+
const zoneId = resolveEnvVars(purge.zoneId) ??
|
|
776
|
+
(config.siteUrl ? await lookupCloudflareZoneId(config.siteUrl, apiToken) : null);
|
|
565
777
|
if (!zoneId) {
|
|
566
778
|
console.log(` ${name}: zone not found, set purge.zoneId or siteUrl`);
|
|
567
779
|
continue;
|
|
@@ -647,8 +859,10 @@ async function runBuild(siteDir) {
|
|
|
647
859
|
const editorsDir = join(adminDir, 'editors');
|
|
648
860
|
const fieldsDir = join(adminDir, 'fields');
|
|
649
861
|
const entryExtensions = ['.ts', '.tsx', '.jsx'];
|
|
650
|
-
const hasEditors = existsSync(editorsDir) &&
|
|
651
|
-
|
|
862
|
+
const hasEditors = existsSync(editorsDir) &&
|
|
863
|
+
(await import('node:fs')).readdirSync(editorsDir).some(f => entryExtensions.some(ext => f.endsWith(ext)));
|
|
864
|
+
const hasFields = existsSync(fieldsDir) &&
|
|
865
|
+
(await import('node:fs')).readdirSync(fieldsDir).some(f => entryExtensions.some(ext => f.endsWith(ext)));
|
|
652
866
|
if (hasEditors || hasFields) {
|
|
653
867
|
const { build: esbuild } = await import('esbuild');
|
|
654
868
|
const { writeFile: writeFileAsync, mkdir: mkdirAsync } = await import('node:fs/promises');
|
|
@@ -656,7 +870,7 @@ async function runBuild(siteDir) {
|
|
|
656
870
|
await mkdirAsync(sharedDir, { recursive: true });
|
|
657
871
|
// Build shared dependency bundles (one copy of React, etc.)
|
|
658
872
|
const sharedDeps = {
|
|
659
|
-
|
|
873
|
+
react: 'export * from "react"; import React from "react"; export default React;',
|
|
660
874
|
'react-dom/client': 'export * from "react-dom/client";',
|
|
661
875
|
'react/jsx-runtime': 'export * from "react/jsx-runtime";',
|
|
662
876
|
'gazetta/editor': 'export * from "gazetta/editor";',
|
|
@@ -682,14 +896,19 @@ async function runBuild(siteDir) {
|
|
|
682
896
|
});
|
|
683
897
|
importMap[specifier] = `/admin/_shared/${safeName}.js`;
|
|
684
898
|
}
|
|
685
|
-
catch {
|
|
899
|
+
catch {
|
|
900
|
+
/* skip — dep may not be installed */
|
|
901
|
+
}
|
|
686
902
|
await import('node:fs/promises').then(fs => fs.rm(stubFile, { force: true }));
|
|
687
903
|
}
|
|
688
904
|
console.log(` ${c.green('✓')} Shared deps: ${Object.keys(importMap).join(', ')}`);
|
|
689
905
|
// Bundle each custom editor/field with shared deps externalized
|
|
690
906
|
const externals = Object.keys(importMap);
|
|
691
907
|
let bundledCount = 0;
|
|
692
|
-
for (const [kind, srcDir] of [
|
|
908
|
+
for (const [kind, srcDir] of [
|
|
909
|
+
['editors', editorsDir],
|
|
910
|
+
['fields', fieldsDir],
|
|
911
|
+
]) {
|
|
693
912
|
if (!existsSync(srcDir))
|
|
694
913
|
continue;
|
|
695
914
|
const { readdirSync } = await import('node:fs');
|
|
@@ -738,11 +957,12 @@ async function runAdmin(siteDir, port) {
|
|
|
738
957
|
process.exit(1);
|
|
739
958
|
}
|
|
740
959
|
const app = new Hono();
|
|
741
|
-
app.get('/__reload',
|
|
742
|
-
const
|
|
743
|
-
|
|
960
|
+
app.get('/__reload', ctx => ctx.body(null, 204));
|
|
961
|
+
const { buildSourceContext } = await import('./bootstrap.js');
|
|
962
|
+
const { source, targetConfigs } = await buildSourceContext({ projectSiteDir: siteDir });
|
|
963
|
+
await setupProductionMode(app, source, siteDir, builtAdminDir, templatesDir, adminDir, targetConfigs);
|
|
744
964
|
// SPA fallback for non-API admin routes
|
|
745
|
-
app.get('*',
|
|
965
|
+
app.get('*', ctx => {
|
|
746
966
|
const indexPath = join(builtAdminDir, 'index.html');
|
|
747
967
|
if (existsSync(indexPath))
|
|
748
968
|
return ctx.html(readFileSync(indexPath, 'utf-8'));
|
|
@@ -757,7 +977,10 @@ async function runAdmin(siteDir, port) {
|
|
|
757
977
|
console.log();
|
|
758
978
|
});
|
|
759
979
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
760
|
-
process.on(signal, () => {
|
|
980
|
+
process.on(signal, () => {
|
|
981
|
+
console.log(`\n Shutting down...`);
|
|
982
|
+
server.close(() => process.exit(0));
|
|
983
|
+
});
|
|
761
984
|
}
|
|
762
985
|
}
|
|
763
986
|
async function runServe(siteDir, port, targetName) {
|
|
@@ -778,10 +1001,11 @@ async function runServe(siteDir, port, targetName) {
|
|
|
778
1001
|
process.exit(1);
|
|
779
1002
|
}
|
|
780
1003
|
const { createStorageProvider } = await import('../targets.js');
|
|
781
|
-
|
|
782
|
-
const
|
|
1004
|
+
// Pass targetName so filesystem path defaults to ./targets/<name> when unset.
|
|
1005
|
+
const storage = await createStorageProvider(config.storage, siteDir, name);
|
|
1006
|
+
const { getType } = await import('../types.js');
|
|
783
1007
|
const { createServer } = await import('../serve.js');
|
|
784
|
-
const app = createServer({ storage,
|
|
1008
|
+
const app = createServer({ storage, type: getType(config) });
|
|
785
1009
|
const server = serve({ fetch: app.fetch, port }, () => {
|
|
786
1010
|
console.log();
|
|
787
1011
|
console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('serve')} ${c.dim(siteYaml.name)} ${c.dim(`(${name})`)}`);
|
|
@@ -871,16 +1095,17 @@ async function runDeploy(siteDir, targetName) {
|
|
|
871
1095
|
console.log(`\n ${c.green('✓')} Worker deployed. Now publish content:\n ${c.cyan(`gazetta publish ${targetName}`)}\n`);
|
|
872
1096
|
}
|
|
873
1097
|
async function runValidate(siteDir) {
|
|
874
|
-
const storage = createFilesystemProvider();
|
|
875
1098
|
const projectRoot = detectProjectRoot(siteDir);
|
|
876
1099
|
const templatesDir = join(projectRoot, 'templates');
|
|
877
1100
|
console.log();
|
|
878
1101
|
console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('validate')} ${c.dim(siteDir)}`);
|
|
879
1102
|
console.log();
|
|
880
|
-
// 1. Check site.yaml
|
|
1103
|
+
// 1. Check site.yaml + load default editable target's content
|
|
881
1104
|
let site;
|
|
882
1105
|
try {
|
|
883
|
-
|
|
1106
|
+
const { buildSourceContext } = await import('./bootstrap.js');
|
|
1107
|
+
const { source, manifest } = await buildSourceContext({ projectSiteDir: siteDir });
|
|
1108
|
+
site = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest });
|
|
884
1109
|
console.log(` ${c.green('✓')} site.yaml ${c.dim(`— ${site.manifest.name}`)}`);
|
|
885
1110
|
}
|
|
886
1111
|
catch (err) {
|
|
@@ -915,11 +1140,12 @@ async function runValidate(siteDir) {
|
|
|
915
1140
|
errors++;
|
|
916
1141
|
}
|
|
917
1142
|
}
|
|
918
|
-
// 4. List templates
|
|
1143
|
+
// 4. List templates (project-level filesystem, not target content)
|
|
1144
|
+
const projectStorage = createFilesystemProvider();
|
|
919
1145
|
let templateNames = [];
|
|
920
1146
|
try {
|
|
921
|
-
const entries = await
|
|
922
|
-
templateNames = entries.filter(e => e.isDirectory).map(e => e.name);
|
|
1147
|
+
const entries = await projectStorage.readDir(templatesDir);
|
|
1148
|
+
templateNames = entries.filter(e => e.isDirectory).map((e) => e.name);
|
|
923
1149
|
console.log(` ${c.green('✓')} ${c.dim(`${templateNames.length} templates`)}`);
|
|
924
1150
|
}
|
|
925
1151
|
catch {
|
|
@@ -929,7 +1155,9 @@ async function runValidate(siteDir) {
|
|
|
929
1155
|
const adminDir = join(projectRoot, 'admin');
|
|
930
1156
|
const editorsDir = join(adminDir, 'editors');
|
|
931
1157
|
if (existsSync(editorsDir)) {
|
|
932
|
-
const editorFiles = (await import('node:fs'))
|
|
1158
|
+
const editorFiles = (await import('node:fs'))
|
|
1159
|
+
.readdirSync(editorsDir)
|
|
1160
|
+
.filter(f => f.endsWith('.ts') || f.endsWith('.tsx'));
|
|
933
1161
|
for (const file of editorFiles) {
|
|
934
1162
|
const editorName = file.replace(/\.(ts|tsx)$/, '');
|
|
935
1163
|
if (!templateNames.includes(editorName)) {
|
|
@@ -939,12 +1167,17 @@ async function runValidate(siteDir) {
|
|
|
939
1167
|
}
|
|
940
1168
|
// 6. Check for missing custom fields (schema references field but file doesn't exist)
|
|
941
1169
|
const fieldsDir = join(adminDir, 'fields');
|
|
942
|
-
const fieldFiles = existsSync(fieldsDir)
|
|
1170
|
+
const fieldFiles = existsSync(fieldsDir)
|
|
1171
|
+
? (await import('node:fs'))
|
|
1172
|
+
.readdirSync(fieldsDir)
|
|
1173
|
+
.filter(f => f.endsWith('.ts') || f.endsWith('.tsx'))
|
|
1174
|
+
.map(f => f.replace(/\.(ts|tsx)$/, ''))
|
|
1175
|
+
: [];
|
|
943
1176
|
const { loadTemplate } = await import('../template-loader.js');
|
|
944
1177
|
const zod = await import('zod');
|
|
945
1178
|
for (const tplName of templateNames) {
|
|
946
1179
|
try {
|
|
947
|
-
const loaded = await loadTemplate(
|
|
1180
|
+
const loaded = await loadTemplate(projectStorage, templatesDir, tplName);
|
|
948
1181
|
const jsonSchema = zod.z.toJSONSchema(loaded.schema);
|
|
949
1182
|
const props = jsonSchema.properties;
|
|
950
1183
|
if (!props)
|
|
@@ -957,7 +1190,70 @@ async function runValidate(siteDir) {
|
|
|
957
1190
|
}
|
|
958
1191
|
}
|
|
959
1192
|
}
|
|
960
|
-
catch {
|
|
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
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (localeToUrl.size > 1) {
|
|
1254
|
+
console.log(` ${c.green('✓')} cross-domain hreflang: ${[...localeToUrl.entries()].map(([l, u]) => `${l} → ${u}`).join(', ')}`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
961
1257
|
}
|
|
962
1258
|
console.log();
|
|
963
1259
|
if (errors > 0) {
|
|
@@ -1014,19 +1310,26 @@ function renderErrorOverlay(err) {
|
|
|
1014
1310
|
</html>`;
|
|
1015
1311
|
}
|
|
1016
1312
|
async function runDev(siteDir, port) {
|
|
1017
|
-
const storage = createFilesystemProvider();
|
|
1018
1313
|
const projectRoot = detectProjectRoot(siteDir);
|
|
1019
1314
|
const templatesDir = join(projectRoot, 'templates');
|
|
1020
1315
|
const adminDir = join(projectRoot, 'admin');
|
|
1021
|
-
|
|
1316
|
+
// Build the source context from the default editable target in site.yaml.
|
|
1317
|
+
// Cloud targets aren't init'd — admin API handles them lazily.
|
|
1318
|
+
const { buildSourceContext } = await import('./bootstrap.js');
|
|
1319
|
+
const { source, manifest, targetConfigs } = await buildSourceContext({ projectSiteDir: siteDir });
|
|
1320
|
+
const site = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest });
|
|
1321
|
+
const storage = source.storage;
|
|
1022
1322
|
const app = new Hono();
|
|
1023
1323
|
// ---- Live reload (SSE) ----
|
|
1024
1324
|
let reloadId = 0;
|
|
1025
1325
|
const reloadListeners = new Set();
|
|
1026
|
-
function notifyReload() {
|
|
1027
|
-
|
|
1326
|
+
function notifyReload() {
|
|
1327
|
+
reloadId++;
|
|
1328
|
+
for (const l of reloadListeners)
|
|
1329
|
+
l();
|
|
1330
|
+
}
|
|
1028
1331
|
const RELOAD_SCRIPT = `<script>new EventSource('/__reload').onmessage = () => location.reload()</script>`;
|
|
1029
|
-
app.get('/__reload',
|
|
1332
|
+
app.get('/__reload', c => {
|
|
1030
1333
|
return streamSSE(c, async (stream) => {
|
|
1031
1334
|
let lastId = reloadId;
|
|
1032
1335
|
const check = async () => {
|
|
@@ -1036,20 +1339,53 @@ async function runDev(siteDir, port) {
|
|
|
1036
1339
|
}
|
|
1037
1340
|
};
|
|
1038
1341
|
reloadListeners.add(check);
|
|
1039
|
-
stream.onAbort(() => {
|
|
1342
|
+
stream.onAbort(() => {
|
|
1343
|
+
reloadListeners.delete(check);
|
|
1344
|
+
});
|
|
1040
1345
|
while (true) {
|
|
1041
1346
|
await stream.sleep(500);
|
|
1042
1347
|
await check();
|
|
1043
1348
|
}
|
|
1044
1349
|
});
|
|
1045
1350
|
});
|
|
1046
|
-
// ----
|
|
1047
|
-
|
|
1351
|
+
// ---- Trailing slash normalization ----
|
|
1352
|
+
// Strip trailing slashes so /fr/ resolves as /fr and /fr/about/ as
|
|
1353
|
+
// /fr/about. Re-dispatches through the Hono router with the clean URL.
|
|
1354
|
+
// No redirect — preserves POST body and avoids round-trips.
|
|
1355
|
+
app.use(async (c, next) => {
|
|
1356
|
+
const url = new URL(c.req.url);
|
|
1357
|
+
if (url.pathname !== '/' && url.pathname.endsWith('/')) {
|
|
1358
|
+
url.pathname = url.pathname.slice(0, -1);
|
|
1359
|
+
return app.fetch(new Request(url, c.req.raw), c.env);
|
|
1360
|
+
}
|
|
1361
|
+
return next();
|
|
1362
|
+
});
|
|
1363
|
+
// ---- Asset serve route ----
|
|
1364
|
+
// Serves /assets/* from the active source target's storage. Matches the
|
|
1365
|
+
// URL pattern emitted by the asset resolver, so templates rendering
|
|
1366
|
+
// <img src="/assets/hero-a3b2c1d4.jpg"> load from here in dev.
|
|
1367
|
+
const { assetServeRoutes } = await import('../assets/serve-route.js');
|
|
1368
|
+
app.route('/', assetServeRoutes(async () => source.storage));
|
|
1369
|
+
// ---- Site page routes (default + locale variants) ----
|
|
1370
|
+
const { allPageEntries } = await import('../site-loader.js');
|
|
1371
|
+
for (const { name: pageName, page, locale: pageLocale } of allPageEntries(site)) {
|
|
1048
1372
|
app.get(page.route, async (c) => {
|
|
1049
1373
|
try {
|
|
1050
|
-
const freshSite = await loadSite({
|
|
1051
|
-
const resolved = await resolvePage(pageName, freshSite);
|
|
1052
|
-
const
|
|
1374
|
+
const freshSite = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest });
|
|
1375
|
+
const resolved = await resolvePage(pageName, freshSite, pageLocale);
|
|
1376
|
+
const freshPage = pageLocale
|
|
1377
|
+
? freshSite.pageLocales.get(pageName)?.locales.get(pageLocale)
|
|
1378
|
+
: freshSite.pages.get(pageName);
|
|
1379
|
+
const html = await renderPage(resolved, {
|
|
1380
|
+
routeParams: c.req.param(),
|
|
1381
|
+
metadata: freshPage?.metadata ?? page.metadata,
|
|
1382
|
+
route: freshPage?.route ?? page.route,
|
|
1383
|
+
seo: {
|
|
1384
|
+
siteName: freshSite.manifest.name,
|
|
1385
|
+
locale: pageLocale ?? freshSite.manifest.locale,
|
|
1386
|
+
defaultOgImage: freshSite.manifest.defaultOgImage,
|
|
1387
|
+
},
|
|
1388
|
+
});
|
|
1053
1389
|
return c.html(html.replace('</body>', `${RELOAD_SCRIPT}\n</body>`));
|
|
1054
1390
|
}
|
|
1055
1391
|
catch (err) {
|
|
@@ -1057,6 +1393,44 @@ async function runDev(siteDir, port) {
|
|
|
1057
1393
|
}
|
|
1058
1394
|
});
|
|
1059
1395
|
}
|
|
1396
|
+
// ---- Locale fallback routes for pages without locale variants ----
|
|
1397
|
+
// When a page exists in the default locale but has no page.fr.json,
|
|
1398
|
+
// register /fr{route} that renders the default content with FR locale context.
|
|
1399
|
+
// This prevents 404s on locale-prefixed URLs for untranslated pages.
|
|
1400
|
+
const { resolveSiteLocales } = await import('../locale.js');
|
|
1401
|
+
const resolvedLocales = resolveSiteLocales(manifest);
|
|
1402
|
+
if (resolvedLocales) {
|
|
1403
|
+
const nonDefaultLocales = resolvedLocales.supported.filter(l => l !== resolvedLocales.default);
|
|
1404
|
+
for (const loc of nonDefaultLocales) {
|
|
1405
|
+
for (const [pageName, page] of site.pages) {
|
|
1406
|
+
const hasLocaleVariant = site.pageLocales.get(pageName)?.locales.has(loc);
|
|
1407
|
+
if (hasLocaleVariant)
|
|
1408
|
+
continue; // already registered by allPageEntries
|
|
1409
|
+
const localeRoute = `/${loc}${page.route === '/' ? '' : page.route}`;
|
|
1410
|
+
app.get(localeRoute, async (c) => {
|
|
1411
|
+
try {
|
|
1412
|
+
const freshSite = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest });
|
|
1413
|
+
const resolved = await resolvePage(pageName, freshSite, loc);
|
|
1414
|
+
const freshPage = freshSite.pages.get(pageName);
|
|
1415
|
+
const html = await renderPage(resolved, {
|
|
1416
|
+
routeParams: c.req.param(),
|
|
1417
|
+
metadata: freshPage?.metadata,
|
|
1418
|
+
route: freshPage?.route,
|
|
1419
|
+
seo: {
|
|
1420
|
+
siteName: freshSite.manifest.name,
|
|
1421
|
+
locale: loc,
|
|
1422
|
+
defaultOgImage: freshSite.manifest.defaultOgImage,
|
|
1423
|
+
},
|
|
1424
|
+
});
|
|
1425
|
+
return c.html(html.replace('</body>', `${RELOAD_SCRIPT}\n</body>`));
|
|
1426
|
+
}
|
|
1427
|
+
catch (err) {
|
|
1428
|
+
return c.html(renderErrorOverlay(err), 500);
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1060
1434
|
// ---- Detect mode: dev (monorepo with apps/admin source) vs production (pre-built) ----
|
|
1061
1435
|
const cmsWebDir = findCmsDir();
|
|
1062
1436
|
const cmsStaticDir = findCmsStaticDir();
|
|
@@ -1066,14 +1440,14 @@ async function runDev(siteDir, port) {
|
|
|
1066
1440
|
let cmsApp = null;
|
|
1067
1441
|
if (isDevMode) {
|
|
1068
1442
|
// Dev mode: mount CMS API inline (same process = shared template cache)
|
|
1069
|
-
cmsApp = await setupCmsApi(app,
|
|
1443
|
+
cmsApp = await setupCmsApi(app, source, siteDir, templatesDir, adminDir, targetConfigs);
|
|
1070
1444
|
}
|
|
1071
1445
|
else if (cmsStaticDir) {
|
|
1072
1446
|
// Production mode: inline CMS API + static files
|
|
1073
|
-
cmsApp = await setupProductionMode(app,
|
|
1447
|
+
cmsApp = await setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs);
|
|
1074
1448
|
}
|
|
1075
1449
|
// ---- 404 ----
|
|
1076
|
-
app.notFound(
|
|
1450
|
+
app.notFound(c => {
|
|
1077
1451
|
const routes = [...site.pages.entries()].map(([n, p]) => ` ${p.route} → ${n}`).join('\n');
|
|
1078
1452
|
return c.html(`<pre style="padding:2rem">Page not found: ${c.req.path}\n\nAvailable:\n${routes}\n /admin → CMS editor</pre>`, 404);
|
|
1079
1453
|
});
|
|
@@ -1092,6 +1466,37 @@ async function runDev(siteDir, port) {
|
|
|
1092
1466
|
console.log();
|
|
1093
1467
|
console.log(` ${c.dim('┃')} Pages ${[...site.pages.entries()].map(([n, p]) => `${c.dim(p.route)} ${c.dim('→')} ${n}`).join(c.dim(', '))}`);
|
|
1094
1468
|
console.log(` ${c.dim('┃')} Frags ${c.dim([...site.fragments.keys()].join(', ') || '(none)')}`);
|
|
1469
|
+
// ---- Settings banner ----
|
|
1470
|
+
// Prints resolved configuration at startup so path / target / site
|
|
1471
|
+
// issues are diagnosed immediately instead of via empty API responses.
|
|
1472
|
+
// Opt-in via GAZETTA_QUIET=1 for scripted callers that don't want it.
|
|
1473
|
+
if (!process.env.GAZETTA_QUIET) {
|
|
1474
|
+
const relProject = relative(process.cwd(), projectRoot) || '.';
|
|
1475
|
+
const relSite = relative(projectRoot, siteDir) || '.';
|
|
1476
|
+
const relTemplates = relative(projectRoot, templatesDir) || '.';
|
|
1477
|
+
const sourceName = source.targetName ?? '(none)';
|
|
1478
|
+
const sourceCfg = targetConfigs[sourceName];
|
|
1479
|
+
const sourceEnv = sourceCfg ? getEnvironment(sourceCfg) : 'unknown';
|
|
1480
|
+
const sourceType = sourceCfg ? getType(sourceCfg) : 'unknown';
|
|
1481
|
+
const sourceEditable = sourceCfg ? isEditable(sourceCfg) : false;
|
|
1482
|
+
const sourceRoot = source.contentRoot.rootPath || '.';
|
|
1483
|
+
const targetsCount = Object.keys(targetConfigs).length;
|
|
1484
|
+
console.log();
|
|
1485
|
+
console.log(` ${c.dim('┃')} ${c.bold('Settings')}`);
|
|
1486
|
+
console.log(` ${c.dim('┃')} Project ${c.dim(relProject)}`);
|
|
1487
|
+
console.log(` ${c.dim('┃')} Site ${c.dim(relSite)}`);
|
|
1488
|
+
console.log(` ${c.dim('┃')} Templates ${c.dim(relTemplates)}`);
|
|
1489
|
+
console.log(` ${c.dim('┃')} Source ${sourceName} ${c.dim(`(${sourceEnv}, ${sourceEditable ? 'editable' : 'read-only'}, ${sourceType})`)}`);
|
|
1490
|
+
console.log(` ${c.dim('┃')} Content root ${c.dim(sourceRoot)}`);
|
|
1491
|
+
console.log(` ${c.dim('┃')} Targets (${targetsCount})`);
|
|
1492
|
+
for (const [name, cfg] of Object.entries(targetConfigs)) {
|
|
1493
|
+
const env = getEnvironment(cfg);
|
|
1494
|
+
const type = getType(cfg);
|
|
1495
|
+
const ed = isEditable(cfg) ? 'editable ' : 'read-only';
|
|
1496
|
+
const storagePath = cfg.storage?.type === 'filesystem' ? (cfg.storage.path ?? `targets/${name}`) : `${cfg.storage?.type ?? '?'}`;
|
|
1497
|
+
console.log(` ${c.dim('┃')} ${c.dim('•')} ${name.padEnd(14)} ${c.dim(env.padEnd(11))} ${c.dim(ed)} ${c.dim(type.padEnd(8))} ${c.dim('→ ' + storagePath)}`);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1095
1500
|
if (isDevMode && cmsWebDir) {
|
|
1096
1501
|
// While Vite is spinning up (compiling, scanning deps, attaching
|
|
1097
1502
|
// middleware), any /admin/* request falls through to the site's 404
|
|
@@ -1109,7 +1514,11 @@ async function runDev(siteDir, port) {
|
|
|
1109
1514
|
return true;
|
|
1110
1515
|
}
|
|
1111
1516
|
if (url === '/admin' || url.startsWith('/admin/') || url.startsWith('/@')) {
|
|
1112
|
-
res.writeHead(503, {
|
|
1517
|
+
res.writeHead(503, {
|
|
1518
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
1519
|
+
'Cache-Control': 'no-store',
|
|
1520
|
+
'Retry-After': '2',
|
|
1521
|
+
});
|
|
1113
1522
|
res.end(LOADER_HTML);
|
|
1114
1523
|
return true;
|
|
1115
1524
|
}
|
|
@@ -1122,6 +1531,7 @@ async function runDev(siteDir, port) {
|
|
|
1122
1531
|
if (loaderHandler(req, res))
|
|
1123
1532
|
return;
|
|
1124
1533
|
for (const l of originalListeners) {
|
|
1534
|
+
;
|
|
1125
1535
|
l(req, res);
|
|
1126
1536
|
}
|
|
1127
1537
|
});
|
|
@@ -1149,7 +1559,9 @@ async function runDev(siteDir, port) {
|
|
|
1149
1559
|
}
|
|
1150
1560
|
}
|
|
1151
1561
|
}
|
|
1152
|
-
catch {
|
|
1562
|
+
catch {
|
|
1563
|
+
/* ignore */
|
|
1564
|
+
}
|
|
1153
1565
|
}
|
|
1154
1566
|
}
|
|
1155
1567
|
const vite = await createViteServer({
|
|
@@ -1178,6 +1590,7 @@ async function runDev(siteDir, port) {
|
|
|
1178
1590
|
});
|
|
1179
1591
|
const honoHandler = (req, res) => {
|
|
1180
1592
|
for (const listener of originalListeners) {
|
|
1593
|
+
;
|
|
1181
1594
|
listener(req, res);
|
|
1182
1595
|
}
|
|
1183
1596
|
};
|
|
@@ -1191,7 +1604,10 @@ async function runDev(siteDir, port) {
|
|
|
1191
1604
|
res.end(JSON.stringify({ ready: true }));
|
|
1192
1605
|
return;
|
|
1193
1606
|
}
|
|
1194
|
-
if (url.startsWith('/admin/api') ||
|
|
1607
|
+
if (url.startsWith('/admin/api') ||
|
|
1608
|
+
url.startsWith('/admin/preview') ||
|
|
1609
|
+
url === '/admin/theme.css' ||
|
|
1610
|
+
url.startsWith('/admin/theme.css?')) {
|
|
1195
1611
|
honoHandler(req, res);
|
|
1196
1612
|
}
|
|
1197
1613
|
else if (url.startsWith('/admin') || url.startsWith('/@')) {
|
|
@@ -1201,6 +1617,26 @@ async function runDev(siteDir, port) {
|
|
|
1201
1617
|
honoHandler(req, res);
|
|
1202
1618
|
}
|
|
1203
1619
|
});
|
|
1620
|
+
// Force Vite to scan deps + complete initial optimization BEFORE we
|
|
1621
|
+
// mark the CMS ready. Without this, `cmsReady = true` fires the
|
|
1622
|
+
// moment Vite is created — loader page reloads, browser starts
|
|
1623
|
+
// fetching the SPA, and Vite's still building the dep bundle in the
|
|
1624
|
+
// background. The first round of imports arrives, Vite finds new
|
|
1625
|
+
// transitive deps, and fires `optimized dependencies changed.
|
|
1626
|
+
// reloading` mid-page-load. That reload cancels in-flight
|
|
1627
|
+
// `/admin/api/*` requests — silently breaking any component that
|
|
1628
|
+
// doesn't retry (FragmentBlastRadius, for one).
|
|
1629
|
+
//
|
|
1630
|
+
// Warm the SPA's main entry module (not index.html — Vite's
|
|
1631
|
+
// import-analysis plugin treats warmupRequest urls as JS modules
|
|
1632
|
+
// and chokes on HTML). The entry's transitive imports are exactly
|
|
1633
|
+
// what the browser will request on first load, so settling them
|
|
1634
|
+
// here means the browser gets a stable bundle. waitForRequestsIdle
|
|
1635
|
+
// blocks until Vite finishes processing the static-import chain,
|
|
1636
|
+
// which includes dep optimization.
|
|
1637
|
+
const ENTRY = '/src/client/main.ts';
|
|
1638
|
+
await vite.warmupRequest(ENTRY);
|
|
1639
|
+
await vite.waitForRequestsIdle(ENTRY);
|
|
1204
1640
|
cmsReady = true;
|
|
1205
1641
|
}
|
|
1206
1642
|
catch (err) {
|
|
@@ -1218,23 +1654,21 @@ async function runDev(siteDir, port) {
|
|
|
1218
1654
|
const siteWatcher = watch(siteDir, { recursive: true }, (_event, filename) => {
|
|
1219
1655
|
if (!filename)
|
|
1220
1656
|
return;
|
|
1657
|
+
// .gazetta/ is a reserved namespace (history, source-sidecars, etc.) that
|
|
1658
|
+
// the runtime never reads at request time. Writes there are extremely
|
|
1659
|
+
// frequent (one per save/publish × per-target) — treating them as
|
|
1660
|
+
// content changes would flood SSE reloads and reset preview iframe
|
|
1661
|
+
// scroll state mid-test. Filter them out at the watcher boundary.
|
|
1662
|
+
const norm = filename.replace(/\\/g, '/');
|
|
1663
|
+
if (norm.includes('/.gazetta/') || norm.startsWith('.gazetta/'))
|
|
1664
|
+
return;
|
|
1221
1665
|
if (filename.endsWith('.json') || filename.endsWith('.yaml')) {
|
|
1222
1666
|
console.log(` Manifest changed: ${filename}`);
|
|
1223
1667
|
invalidateAllTemplates();
|
|
1224
|
-
// Refresh source sidecars for external edits (git pull, direct file
|
|
1225
|
-
// edit). PUT routes already handle their own writes — this catches
|
|
1226
|
-
// everything outside the admin UI.
|
|
1227
|
-
const norm = filename.replace(/\\/g, '/');
|
|
1228
|
-
const pageMatch = /^pages\/(.+)\/page\.json$/.exec(norm);
|
|
1229
|
-
const fragMatch = /^fragments\/(.+)\/fragment\.json$/.exec(norm);
|
|
1230
|
-
if (pageMatch)
|
|
1231
|
-
cmsApp?.writeSourceSidecar('page', pageMatch[1]).catch(() => { });
|
|
1232
|
-
else if (fragMatch)
|
|
1233
|
-
cmsApp?.writeSourceSidecar('fragment', fragMatch[1]).catch(() => { });
|
|
1234
1668
|
notifyReload();
|
|
1235
1669
|
}
|
|
1236
1670
|
});
|
|
1237
|
-
siteWatcher.on('error',
|
|
1671
|
+
siteWatcher.on('error', err => console.warn(` File watcher warning (site): ${err.message}`));
|
|
1238
1672
|
// Watch templates dir for template source changes
|
|
1239
1673
|
if (existsSync(templatesDir)) {
|
|
1240
1674
|
const tplWatcher = watch(templatesDir, { recursive: true }, (_event, filename) => {
|
|
@@ -1248,12 +1682,11 @@ async function runDev(siteDir, port) {
|
|
|
1248
1682
|
// Drop the admin-api's cached scan so next compare/publish
|
|
1249
1683
|
// rehashes. Cheap (the scan is what's slow, not invalidation).
|
|
1250
1684
|
cmsApp?.invalidateTemplatesCache();
|
|
1251
|
-
cmsApp?.invalidateSourceSidecars();
|
|
1252
1685
|
notifyReload();
|
|
1253
1686
|
}
|
|
1254
1687
|
}
|
|
1255
1688
|
});
|
|
1256
|
-
tplWatcher.on('error',
|
|
1689
|
+
tplWatcher.on('error', err => console.warn(` File watcher warning (templates): ${err.message}`));
|
|
1257
1690
|
}
|
|
1258
1691
|
}
|
|
1259
1692
|
// ---- Mount CMS API on the main Hono app (shared process = shared template cache) ----
|
|
@@ -1265,7 +1698,7 @@ async function runDev(siteDir, port) {
|
|
|
1265
1698
|
* so user declarations win the cascade. See #134 and css-theming.md.
|
|
1266
1699
|
*/
|
|
1267
1700
|
function mountUserThemeRoute(cmsApp, adminDir) {
|
|
1268
|
-
cmsApp.get('/theme.css',
|
|
1701
|
+
cmsApp.get('/theme.css', c => {
|
|
1269
1702
|
const themePath = join(adminDir, 'theme.css');
|
|
1270
1703
|
c.header('Content-Type', 'text/css; charset=utf-8');
|
|
1271
1704
|
c.header('Cache-Control', 'no-cache');
|
|
@@ -1278,35 +1711,22 @@ function mountUserThemeRoute(cmsApp, adminDir) {
|
|
|
1278
1711
|
return c.body(readFileSync(themePath, 'utf-8'));
|
|
1279
1712
|
});
|
|
1280
1713
|
}
|
|
1281
|
-
async function setupCmsApi(app,
|
|
1282
|
-
const
|
|
1283
|
-
let targetConfigs;
|
|
1284
|
-
if (existsSync(siteYamlPath)) {
|
|
1285
|
-
const siteYaml = yaml.load(readFileSync(siteYamlPath, 'utf-8'));
|
|
1286
|
-
targetConfigs = siteYaml.targets;
|
|
1287
|
-
}
|
|
1288
|
-
const cmsApp = createAdminApp({ siteDir, storage, templatesDir, adminDir, targetConfigs });
|
|
1714
|
+
async function setupCmsApi(app, source, siteDir, templatesDir, adminDir, targetConfigs) {
|
|
1715
|
+
const cmsApp = createAdminApp({ source, siteDir, templatesDir, adminDir, targetConfigs });
|
|
1289
1716
|
mountUserThemeRoute(cmsApp, adminDir);
|
|
1290
1717
|
app.route('/admin', cmsApp);
|
|
1291
1718
|
return cmsApp;
|
|
1292
1719
|
}
|
|
1293
1720
|
// ---- Production mode: inline CMS API + static files from admin-dist/ ----
|
|
1294
|
-
async function setupProductionMode(app,
|
|
1295
|
-
// Read target configs from site.yaml — targets are initialized lazily on first publish/fetch
|
|
1296
|
-
const siteYamlPath = join(siteDir, 'site.yaml');
|
|
1297
|
-
let targetConfigs;
|
|
1298
|
-
if (existsSync(siteYamlPath)) {
|
|
1299
|
-
const siteYaml = yaml.load(readFileSync(siteYamlPath, 'utf-8'));
|
|
1300
|
-
targetConfigs = siteYaml.targets;
|
|
1301
|
-
}
|
|
1721
|
+
async function setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs) {
|
|
1302
1722
|
// Mount CMS API inline at /admin (production mode — bundled editors/fields)
|
|
1303
|
-
const cmsApp = createAdminApp({
|
|
1723
|
+
const cmsApp = createAdminApp({ source, siteDir, templatesDir, adminDir, production: true, targetConfigs });
|
|
1304
1724
|
mountUserThemeRoute(cmsApp, adminDir);
|
|
1305
1725
|
app.route('/admin', cmsApp);
|
|
1306
1726
|
// Serve pre-built CMS static files (includes bundled editors/fields)
|
|
1307
1727
|
app.use('/admin/*', serveStatic({
|
|
1308
1728
|
root: cmsStaticDir,
|
|
1309
|
-
rewriteRequestPath:
|
|
1729
|
+
rewriteRequestPath: path => path.replace(/^\/admin/, ''),
|
|
1310
1730
|
}));
|
|
1311
1731
|
// SPA fallback: serve index.html for /admin and unmatched /admin/* routes
|
|
1312
1732
|
const serveIndex = (c) => {
|
|
@@ -1352,11 +1772,15 @@ async function main() {
|
|
|
1352
1772
|
}
|
|
1353
1773
|
const parsed = parseArgs(args.slice(1));
|
|
1354
1774
|
// Commands that take [target] [site] positional args
|
|
1355
|
-
const targetFirstCommands = new Set(['publish', 'serve', 'deploy']);
|
|
1775
|
+
const targetFirstCommands = new Set(['publish', 'serve', 'deploy', 'history', 'undo']);
|
|
1356
1776
|
// Commands that take [site] positional arg
|
|
1357
1777
|
const siteOnlyCommands = new Set(['dev', 'validate', 'admin']);
|
|
1358
1778
|
let siteDir;
|
|
1359
1779
|
let targetName;
|
|
1780
|
+
// rollback: positional layout is `<rev> [target] [site]`. We stash
|
|
1781
|
+
// the revision id here because the shared positional parser uses
|
|
1782
|
+
// index 0 for target/site; rollback just consumes index 0 first.
|
|
1783
|
+
let rollbackRevisionId;
|
|
1360
1784
|
if (command === 'init') {
|
|
1361
1785
|
await runInit(parsed.positional[0] ?? '.');
|
|
1362
1786
|
return;
|
|
@@ -1366,6 +1790,25 @@ async function main() {
|
|
|
1366
1790
|
await runBuild(siteDir);
|
|
1367
1791
|
return;
|
|
1368
1792
|
}
|
|
1793
|
+
else if (command === 'rollback') {
|
|
1794
|
+
// gazetta rollback <rev> [target] [site]
|
|
1795
|
+
const [rev, second, third] = parsed.positional;
|
|
1796
|
+
if (!rev || !rev.startsWith('rev-')) {
|
|
1797
|
+
console.error(`\n Error: rollback requires a revision id as the first argument (e.g. gazetta rollback rev-1776337441608 [target])\n`);
|
|
1798
|
+
process.exit(1);
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
rollbackRevisionId = rev;
|
|
1802
|
+
const secondIsSite = second && (second.includes('/') || existsSync(join(resolve(second), 'site.yaml')));
|
|
1803
|
+
if (secondIsSite) {
|
|
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
|
+
}
|
|
1811
|
+
}
|
|
1369
1812
|
else if (targetFirstCommands.has(command)) {
|
|
1370
1813
|
// gazetta publish [target] [site]
|
|
1371
1814
|
const [first, second] = parsed.positional;
|
|
@@ -1383,6 +1826,37 @@ async function main() {
|
|
|
1383
1826
|
else if (siteOnlyCommands.has(command)) {
|
|
1384
1827
|
siteDir = await resolveSiteDir(parsed.positional[0]);
|
|
1385
1828
|
}
|
|
1829
|
+
else if (command === 'translate') {
|
|
1830
|
+
// 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
|
+
const translatePositionals = parsed.positional.filter(p => !p.startsWith('pages/') && !p.startsWith('fragments/'));
|
|
1835
|
+
if (translatePositionals.length > 0)
|
|
1836
|
+
targetName = translatePositionals[0];
|
|
1837
|
+
}
|
|
1838
|
+
else if (command === 'assets') {
|
|
1839
|
+
// gazetta assets <subcommand> [args...] [target] [site]
|
|
1840
|
+
//
|
|
1841
|
+
// Subcommand layouts:
|
|
1842
|
+
// assets list [target] [site] → subcmd, target, site
|
|
1843
|
+
// assets info <name> [target] [site] → subcmd, name, target, site
|
|
1844
|
+
// assets reindex [target] [site] → subcmd, target, site
|
|
1845
|
+
//
|
|
1846
|
+
// The dispatcher in the assets-cli module reads the asset name
|
|
1847
|
+
// from its `args` slice; here we resolve target/site by checking
|
|
1848
|
+
// the positional layout.
|
|
1849
|
+
const subcmd = parsed.positional[0];
|
|
1850
|
+
if (subcmd === 'info') {
|
|
1851
|
+
// info has an extra positional (the asset name) before target/site.
|
|
1852
|
+
siteDir = await resolveSiteDir(parsed.positional[3]);
|
|
1853
|
+
targetName = parsed.positional[2] ? await resolveTarget(parsed.positional[2], siteDir) : undefined;
|
|
1854
|
+
}
|
|
1855
|
+
else {
|
|
1856
|
+
siteDir = await resolveSiteDir(parsed.positional[2]);
|
|
1857
|
+
targetName = parsed.positional[1] ? await resolveTarget(parsed.positional[1], siteDir) : undefined;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1386
1860
|
else {
|
|
1387
1861
|
console.error(` Unknown command: ${command}\n`);
|
|
1388
1862
|
printHelp();
|
|
@@ -1425,9 +1899,96 @@ async function main() {
|
|
|
1425
1899
|
case 'admin':
|
|
1426
1900
|
await runAdmin(siteDir, parsed.port ?? 3000);
|
|
1427
1901
|
break;
|
|
1902
|
+
case 'translate': {
|
|
1903
|
+
const itemArg = args[1];
|
|
1904
|
+
const localeArg = args.find(a => a.startsWith('--to='))?.slice(5) ?? args[args.indexOf('--to') + 1];
|
|
1905
|
+
if (!itemArg || !localeArg) {
|
|
1906
|
+
console.error(' Usage: gazetta translate <pages/name|fragments/name> --to <locale>');
|
|
1907
|
+
console.error(' Example: gazetta translate pages/about --to fr');
|
|
1908
|
+
process.exit(1);
|
|
1909
|
+
}
|
|
1910
|
+
const { normalizeLocale, localeFilename, isValidLocale } = await import('../locale.js');
|
|
1911
|
+
if (!isValidLocale(localeArg)) {
|
|
1912
|
+
console.error(` Error: invalid locale code "${localeArg}". Use BCP 47 format (e.g. fr, en-gb, pt-br)`);
|
|
1913
|
+
process.exit(1);
|
|
1914
|
+
}
|
|
1915
|
+
const locale = normalizeLocale(localeArg);
|
|
1916
|
+
const isPage = itemArg.startsWith('pages/');
|
|
1917
|
+
const isFragment = itemArg.startsWith('fragments/');
|
|
1918
|
+
if (!isPage && !isFragment) {
|
|
1919
|
+
console.error(` Error: item must start with pages/ or fragments/ (got "${itemArg}")`);
|
|
1920
|
+
process.exit(1);
|
|
1921
|
+
}
|
|
1922
|
+
// Resolve the content directory — translate operates on a target's filesystem.
|
|
1923
|
+
// Uses the specified target or falls back to the first editable target.
|
|
1924
|
+
const siteYaml = yaml.load(readFileSync(join(siteDir, 'site.yaml'), 'utf-8'));
|
|
1925
|
+
const { isEditable } = await import('../types.js');
|
|
1926
|
+
const resolvedTarget = targetName ?? Object.entries(siteYaml.targets ?? {}).find(([, cfg]) => isEditable(cfg))?.[0];
|
|
1927
|
+
if (!resolvedTarget) {
|
|
1928
|
+
console.error(' Error: no editable target found');
|
|
1929
|
+
process.exit(1);
|
|
1930
|
+
}
|
|
1931
|
+
const targetConfig = siteYaml.targets[resolvedTarget];
|
|
1932
|
+
if (!targetConfig) {
|
|
1933
|
+
console.error(` Error: target "${resolvedTarget}" not found in site.yaml`);
|
|
1934
|
+
process.exit(1);
|
|
1935
|
+
}
|
|
1936
|
+
const storagePath = targetConfig.storage.path ?? join('targets', resolvedTarget);
|
|
1937
|
+
const contentDir = resolve(siteDir, storagePath);
|
|
1938
|
+
const baseName = isPage ? 'page' : 'fragment';
|
|
1939
|
+
const dir = join(contentDir, itemArg);
|
|
1940
|
+
const sourceFile = join(dir, `${baseName}.json`);
|
|
1941
|
+
const destFile = join(dir, localeFilename(baseName, locale));
|
|
1942
|
+
const fs = await import('node:fs/promises');
|
|
1943
|
+
if (!existsSync(sourceFile)) {
|
|
1944
|
+
console.error(` Error: ${sourceFile} not found`);
|
|
1945
|
+
process.exit(1);
|
|
1946
|
+
}
|
|
1947
|
+
if (existsSync(destFile)) {
|
|
1948
|
+
console.error(` Error: ${destFile} already exists`);
|
|
1949
|
+
process.exit(1);
|
|
1950
|
+
}
|
|
1951
|
+
await fs.copyFile(sourceFile, destFile);
|
|
1952
|
+
console.log(` ${c.green('✓')} Created ${relative(process.cwd(), destFile)}`);
|
|
1953
|
+
console.log(` Edit the file to translate the content.`);
|
|
1954
|
+
break;
|
|
1955
|
+
}
|
|
1956
|
+
case 'history':
|
|
1957
|
+
case 'undo':
|
|
1958
|
+
case 'rollback': {
|
|
1959
|
+
const { runHistoryList, runHistoryUndo, runHistoryRollback } = await import('./history.js');
|
|
1960
|
+
const ctx = await resolveHistoryContext(siteDir, targetName);
|
|
1961
|
+
if (command === 'history')
|
|
1962
|
+
await runHistoryList(ctx, { limit: parsed.limit });
|
|
1963
|
+
else if (command === 'undo')
|
|
1964
|
+
await runHistoryUndo(ctx, { yes: parsed.yes });
|
|
1965
|
+
else
|
|
1966
|
+
await runHistoryRollback(ctx, rollbackRevisionId, { yes: parsed.yes });
|
|
1967
|
+
break;
|
|
1968
|
+
}
|
|
1969
|
+
case 'assets': {
|
|
1970
|
+
const { runAssetsSubcommand } = await import('./assets-cli.js');
|
|
1971
|
+
await runAssetsSubcommand({ args: args.slice(1), siteDir, targetName });
|
|
1972
|
+
break;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
/**
|
|
1977
|
+
* Resolve site + target + config into the shape HistoryCommandContext
|
|
1978
|
+
* expects. Lives here rather than in cli/history.ts so the target-
|
|
1979
|
+
* resolution logic (site.yaml parsing, CI env handling) stays with
|
|
1980
|
+
* the other CLI commands that already do it the same way.
|
|
1981
|
+
*/
|
|
1982
|
+
async function resolveHistoryContext(siteDir, targetName) {
|
|
1983
|
+
const { bootstrapFromSiteYaml } = await import('./bootstrap.js');
|
|
1984
|
+
const { targetConfigs } = await bootstrapFromSiteYaml(siteDir);
|
|
1985
|
+
const config = targetConfigs[targetName];
|
|
1986
|
+
if (!config) {
|
|
1987
|
+
throw new Error(`Unknown target "${targetName}". Available: ${Object.keys(targetConfigs).join(', ')}`);
|
|
1428
1988
|
}
|
|
1989
|
+
return { siteDir, targetName, config };
|
|
1429
1990
|
}
|
|
1430
|
-
main().catch(
|
|
1991
|
+
main().catch(err => {
|
|
1431
1992
|
console.error(`\n Error: ${err.message}\n`);
|
|
1432
1993
|
process.exit(1);
|
|
1433
1994
|
});
|