@velora-cms/server 0.11.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/LICENSE +202 -0
- package/README.md +65 -0
- package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js_commonjs-proxy-Ng0fOpdU.js +1 -0
- package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js_commonjs-proxy-oc1i_iBU.js +9 -0
- package/dist/admin-bundle/assets/_virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DOKdWYyj.js +1 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare___mf_0_velora_mf_2_cms_mf_1_plugin_mf_2_sdk__loadShare__.js-C1-ZAxwO.js +2 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js-BuUBskgj.js +9 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DMaku-HC.js +9 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js-DYfqn12F.js +1 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-BPoj3DC9.js +25 -0
- package/dist/admin-bundle/assets/content-trash.schema-DfXHkC-w.js +72 -0
- package/dist/admin-bundle/assets/hostInit--C4xK6nj.js +1 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-CyU-ddYS.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-EA1o-u_Q.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-C4MQITzh.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-DPXcppbm.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-0pRdybE_.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-MJZ0liF5.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-Bo5KPYvw.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-CFZh1unN.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-BletK1Hp.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-Cd6jVIg7.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-Dk5eISD_.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-KrqB56Mw.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BeVLg08x.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BpLfq-63.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-Ck_W4G78.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-eJxPnL1P.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DT29Q8HW.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DjDAeNQD.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
- package/dist/admin-bundle/assets/index-BIAF0ShP.js +542 -0
- package/dist/admin-bundle/assets/index-DhMY-0sC.js +148 -0
- package/dist/admin-bundle/assets/index-DnZV_gsg.css +1 -0
- package/dist/admin-bundle/assets/index-Do71v6NZ.js +7 -0
- package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-italic-5yUt4G_9.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-normal-C5tfwALZ.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-cyrillic-opsz-italic-Bj0ndbyp.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-cyrillic-opsz-normal-DgmypKXX.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-ext-opsz-italic-DFpO5j-Z.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-ext-opsz-normal-DGm64vGR.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-opsz-italic-CHERNRFq.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-opsz-normal-DFglUmAd.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-ext-opsz-italic-4y8iAqqs.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-ext-opsz-normal-STiTPs0F.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-opsz-italic-CJ93D38p.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-opsz-normal-BHZsWIlc.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-vietnamese-opsz-italic-BdcrZMcO.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-vietnamese-opsz-normal-Gqw5JQlq.woff2 +0 -0
- package/dist/admin-bundle/assets/mf-entry-bootstrap-0-aef015cd.js +41 -0
- package/dist/admin-bundle/assets/remoteEntry-CNyN68bc.js +14 -0
- package/dist/admin-bundle/assets/virtualExposes-BDCSo5OS.js +1 -0
- package/dist/admin-bundle/assets/vite-preload-helper-Cgledu0r.js +1 -0
- package/dist/admin-bundle/index.html +16 -0
- package/dist/app.d.ts +8 -0
- package/dist/app.js +252 -0
- package/dist/auth/api-content-author.d.ts +2 -0
- package/dist/auth/api-content-author.js +27 -0
- package/dist/auth/api-keys.d.ts +7 -0
- package/dist/auth/api-keys.js +85 -0
- package/dist/auth/jwt-payload.d.ts +16 -0
- package/dist/auth/jwt-payload.js +1 -0
- package/dist/auth/preview-tokens.d.ts +7 -0
- package/dist/auth/preview-tokens.js +76 -0
- package/dist/auth/rate-limit.d.ts +16 -0
- package/dist/auth/rate-limit.js +283 -0
- package/dist/auth/require-auth.d.ts +4 -0
- package/dist/auth/require-auth.js +28 -0
- package/dist/auth/route-rate-limits.d.ts +31 -0
- package/dist/auth/route-rate-limits.js +155 -0
- package/dist/auth/tokens.d.ts +15 -0
- package/dist/auth/tokens.js +38 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +6 -0
- package/dist/boot/core-migration-gate.d.ts +12 -0
- package/dist/boot/core-migration-gate.js +52 -0
- package/dist/boot/resolve-boot-mode.d.ts +6 -0
- package/dist/boot/resolve-boot-mode.js +206 -0
- package/dist/bundled-templates/com.velora.blog/manifest.json +39 -0
- package/dist/bundled-templates/com.velora.blog/package.tar.gz +0 -0
- package/dist/bundled-templates/com.velora.blog/sample-content.json +14 -0
- package/dist/config/admin-ui.d.ts +1 -0
- package/dist/config/admin-ui.js +10 -0
- package/dist/config/cors.d.ts +7 -0
- package/dist/config/cors.js +53 -0
- package/dist/config/env-file-path.d.ts +2 -0
- package/dist/config/env-file-path.js +22 -0
- package/dist/config/graphql-limits.d.ts +2 -0
- package/dist/config/graphql-limits.js +26 -0
- package/dist/config/headless.d.ts +1 -0
- package/dist/config/headless.js +8 -0
- package/dist/config/license-revalidation.d.ts +2 -0
- package/dist/config/license-revalidation.js +16 -0
- package/dist/config/load-env.d.ts +1 -0
- package/dist/config/load-env.js +53 -0
- package/dist/config/marketplace.d.ts +11 -0
- package/dist/config/marketplace.js +19 -0
- package/dist/config/preview.d.ts +3 -0
- package/dist/config/preview.js +39 -0
- package/dist/config/public-docs.d.ts +1 -0
- package/dist/config/public-docs.js +9 -0
- package/dist/config/site.d.ts +7 -0
- package/dist/config/site.js +12 -0
- package/dist/config/trust-proxy.d.ts +2 -0
- package/dist/config/trust-proxy.js +42 -0
- package/dist/data-types/validate-data-type-settings.d.ts +1 -0
- package/dist/data-types/validate-data-type-settings.js +33 -0
- package/dist/db/content-db.d.ts +16 -0
- package/dist/db/content-db.js +109 -0
- package/dist/db/core-migrations/index.d.ts +14 -0
- package/dist/db/core-migrations/index.js +184 -0
- package/dist/db/core-migrations/steps.d.ts +4 -0
- package/dist/db/core-migrations/steps.js +66 -0
- package/dist/db/load-prisma-client.d.ts +2 -0
- package/dist/db/load-prisma-client.js +44 -0
- package/dist/db/prisma.d.ts +3 -0
- package/dist/db/prisma.js +67 -0
- package/dist/db/resolve-client-dir.d.ts +2 -0
- package/dist/db/resolve-client-dir.js +83 -0
- package/dist/docs/filter-public-api-spec.d.ts +2 -0
- package/dist/docs/filter-public-api-spec.js +52 -0
- package/dist/docs-bundle/404.html +41 -0
- package/dist/docs-bundle/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js +1 -0
- package/dist/docs-bundle/_astro/Search.astro_astro_type_script_index_0_lang.CavldbAs.js +1 -0
- package/dist/docs-bundle/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js +1 -0
- package/dist/docs-bundle/_astro/common.DTXA7sXM.css +3 -0
- package/dist/docs-bundle/_astro/ec.0vx5m.js +3 -0
- package/dist/docs-bundle/_astro/ec.w36nc.css +1 -0
- package/dist/docs-bundle/_astro/page.Dwipeu-R.js +1 -0
- package/dist/docs-bundle/_astro/print.ehPL0gv-.css +1 -0
- package/dist/docs-bundle/_astro/starlight-toc.CTCQXEge.js +1 -0
- package/dist/docs-bundle/_astro/ui-core.D-7Scnhs.js +2 -0
- package/dist/docs-bundle/favicon.svg +1 -0
- package/dist/docs-bundle/index.html +1 -0
- package/dist/docs-bundle/pagefind/fragment/en_18a36d7.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_1d9eb24.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_1ef320c.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_3099406.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_4a5b6fe.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_9aa652e.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_baccdf6.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_d81f3fe.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_d855550.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_e6d88d8.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_e9d3189.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/index/en_5feeed7.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_c6981f9.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_ce4f6bf.pf_index +0 -0
- package/dist/docs-bundle/pagefind/pagefind-component-ui.css +1509 -0
- package/dist/docs-bundle/pagefind/pagefind-component-ui.js +56 -0
- package/dist/docs-bundle/pagefind/pagefind-entry.json +1 -0
- package/dist/docs-bundle/pagefind/pagefind-highlight.js +1070 -0
- package/dist/docs-bundle/pagefind/pagefind-modular-ui.css +214 -0
- package/dist/docs-bundle/pagefind/pagefind-modular-ui.js +8 -0
- package/dist/docs-bundle/pagefind/pagefind-ui.css +1 -0
- package/dist/docs-bundle/pagefind/pagefind-ui.js +2 -0
- package/dist/docs-bundle/pagefind/pagefind-worker.js +6 -0
- package/dist/docs-bundle/pagefind/pagefind.en_1e94abd852.pf_meta +0 -0
- package/dist/docs-bundle/pagefind/pagefind.js +6 -0
- package/dist/docs-bundle/pagefind/wasm.en.pagefind +0 -0
- package/dist/docs-bundle/pagefind/wasm.unknown.pagefind +0 -0
- package/dist/docs-bundle/v1/index.html +86 -0
- package/dist/docs-bundle/v1/using-velora/build-a-headless-frontend/index.html +413 -0
- package/dist/docs-bundle/v1/using-velora/content-model/index.html +858 -0
- package/dist/docs-bundle/v1/using-velora/editing/index.html +748 -0
- package/dist/docs-bundle/v1/using-velora/getting-started/index.html +588 -0
- package/dist/docs-bundle/v1/using-velora/headless-api/index.html +1620 -0
- package/dist/docs-bundle/v1/using-velora/index.html +215 -0
- package/dist/docs-bundle/v1/using-velora/installation/index.html +886 -0
- package/dist/docs-bundle/v1/using-velora/installing-plugins/index.html +836 -0
- package/dist/docs-bundle/v1/using-velora/media/index.html +468 -0
- package/dist/docs-bundle/v1/using-velora/users-and-roles/index.html +561 -0
- package/dist/graphql/field-count-rule.d.ts +5 -0
- package/dist/graphql/field-count-rule.js +59 -0
- package/dist/graphql/schema.d.ts +12 -0
- package/dist/graphql/schema.js +330 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +180 -0
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +39 -0
- package/dist/marketplace/install-bundle-members.d.ts +33 -0
- package/dist/marketplace/install-bundle-members.js +137 -0
- package/dist/marketplace/install-from-marketplace.d.ts +60 -0
- package/dist/marketplace/install-from-marketplace.js +581 -0
- package/dist/marketplace/license-revalidation-job.d.ts +38 -0
- package/dist/marketplace/license-revalidation-job.js +139 -0
- package/dist/marketplace/marketplace-client.d.ts +43 -0
- package/dist/marketplace/marketplace-client.js +228 -0
- package/dist/marketplace/revalidate-license.d.ts +39 -0
- package/dist/marketplace/revalidate-license.js +103 -0
- package/dist/media/media-service.d.ts +36 -0
- package/dist/media/media-service.js +162 -0
- package/dist/plugins/boot-plugin-loader.d.ts +32 -0
- package/dist/plugins/boot-plugin-loader.js +57 -0
- package/dist/plugins/hook-dispatcher.d.ts +13 -0
- package/dist/plugins/hook-dispatcher.js +68 -0
- package/dist/plugins/install-built-package.d.ts +36 -0
- package/dist/plugins/install-built-package.js +199 -0
- package/dist/plugins/install-package-tarball.d.ts +25 -0
- package/dist/plugins/install-package-tarball.js +73 -0
- package/dist/plugins/plugin-context.d.ts +5 -0
- package/dist/plugins/plugin-context.js +26 -0
- package/dist/plugins/plugin-package-store.d.ts +31 -0
- package/dist/plugins/plugin-package-store.js +64 -0
- package/dist/plugins/plugin-registry-service.d.ts +227 -0
- package/dist/plugins/plugin-registry-service.js +636 -0
- package/dist/plugins/seed-template-content.d.ts +23 -0
- package/dist/plugins/seed-template-content.js +198 -0
- package/dist/plugins/server-serializers.d.ts +3 -0
- package/dist/plugins/server-serializers.js +16 -0
- package/dist/plugins/simple-storage-api.d.ts +9 -0
- package/dist/plugins/simple-storage-api.js +26 -0
- package/dist/plugins/snapshot-groups.d.ts +16 -0
- package/dist/plugins/snapshot-groups.js +81 -0
- package/dist/plugins/structured-query-builder.d.ts +21 -0
- package/dist/plugins/structured-query-builder.js +60 -0
- package/dist/plugins/structured-snapshot-service.d.ts +16 -0
- package/dist/plugins/structured-snapshot-service.js +286 -0
- package/dist/plugins/structured-storage-api.d.ts +22 -0
- package/dist/plugins/structured-storage-api.js +91 -0
- package/dist/plugins/structured-storage-transaction.d.ts +22 -0
- package/dist/plugins/structured-storage-transaction.js +63 -0
- package/dist/plugins/structured-table-naming.d.ts +1 -0
- package/dist/plugins/structured-table-naming.js +18 -0
- package/dist/plugins/trusted-keys.d.ts +11 -0
- package/dist/plugins/trusted-keys.js +67 -0
- package/dist/plugins/trusted-signing-keys.d.ts +27 -0
- package/dist/plugins/trusted-signing-keys.js +40 -0
- package/dist/plugins/uninstall-bundle.d.ts +29 -0
- package/dist/plugins/uninstall-bundle.js +150 -0
- package/dist/plugins/verify-package.d.ts +36 -0
- package/dist/plugins/verify-package.js +121 -0
- package/dist/routes/admin-static.d.ts +8 -0
- package/dist/routes/admin-static.js +196 -0
- package/dist/routes/api-keys.d.ts +2 -0
- package/dist/routes/api-keys.js +95 -0
- package/dist/routes/api-playground.d.ts +2 -0
- package/dist/routes/api-playground.js +21 -0
- package/dist/routes/auth.d.ts +2 -0
- package/dist/routes/auth.js +102 -0
- package/dist/routes/content.d.ts +3 -0
- package/dist/routes/content.js +471 -0
- package/dist/routes/data-types.d.ts +3 -0
- package/dist/routes/data-types.js +100 -0
- package/dist/routes/docs-static.d.ts +8 -0
- package/dist/routes/docs-static.js +137 -0
- package/dist/routes/document-types.d.ts +3 -0
- package/dist/routes/document-types.js +149 -0
- package/dist/routes/headless-bridge.d.ts +10 -0
- package/dist/routes/headless-bridge.js +207 -0
- package/dist/routes/health.d.ts +2 -0
- package/dist/routes/health.js +15 -0
- package/dist/routes/marketplace-proxy.d.ts +8 -0
- package/dist/routes/marketplace-proxy.js +135 -0
- package/dist/routes/media.d.ts +6 -0
- package/dist/routes/media.js +93 -0
- package/dist/routes/plugin-api-routes.d.ts +12 -0
- package/dist/routes/plugin-api-routes.js +224 -0
- package/dist/routes/plugin-assets.d.ts +8 -0
- package/dist/routes/plugin-assets.js +51 -0
- package/dist/routes/plugin-management.d.ts +7 -0
- package/dist/routes/plugin-management.js +525 -0
- package/dist/routes/plugin-storage.d.ts +8 -0
- package/dist/routes/plugin-storage.js +104 -0
- package/dist/routes/plugins.d.ts +2 -0
- package/dist/routes/plugins.js +36 -0
- package/dist/routes/public-api.d.ts +9 -0
- package/dist/routes/public-api.js +545 -0
- package/dist/routes/published-item.d.ts +57 -0
- package/dist/routes/published-item.js +236 -0
- package/dist/routes/setup.d.ts +9 -0
- package/dist/routes/setup.js +477 -0
- package/dist/routes/snapshots.d.ts +3 -0
- package/dist/routes/snapshots.js +65 -0
- package/dist/routes/templates.d.ts +3 -0
- package/dist/routes/templates.js +37 -0
- package/dist/routes/themes.d.ts +2 -0
- package/dist/routes/themes.js +30 -0
- package/dist/routes/users.d.ts +2 -0
- package/dist/routes/users.js +113 -0
- package/dist/sandbox/register-local-plugin.d.ts +36 -0
- package/dist/sandbox/register-local-plugin.js +108 -0
- package/dist/sandbox/seed-sandbox.d.ts +20 -0
- package/dist/sandbox/seed-sandbox.js +122 -0
- package/dist/scripts/copy-admin-bundle.d.ts +1 -0
- package/dist/scripts/copy-admin-bundle.js +24 -0
- package/dist/scripts/copy-docs-bundle.d.ts +1 -0
- package/dist/scripts/copy-docs-bundle.js +22 -0
- package/dist/scripts/create-api-key.d.ts +5 -0
- package/dist/scripts/create-api-key.js +56 -0
- package/dist/scripts/generate-prisma-clients.d.ts +6 -0
- package/dist/scripts/generate-prisma-clients.js +125 -0
- package/dist/scripts/pack-bundled-templates.d.ts +1 -0
- package/dist/scripts/pack-bundled-templates.js +87 -0
- package/dist/setup/build-connection-string.d.ts +19 -0
- package/dist/setup/build-connection-string.js +14 -0
- package/dist/setup/bundled-templates.d.ts +19 -0
- package/dist/setup/bundled-templates.js +121 -0
- package/dist/setup/ensure-prisma-client.d.ts +3 -0
- package/dist/setup/ensure-prisma-client.js +23 -0
- package/dist/setup/install-starter.d.ts +14 -0
- package/dist/setup/install-starter.js +76 -0
- package/dist/setup/normalize-database-url.d.ts +2 -0
- package/dist/setup/normalize-database-url.js +35 -0
- package/dist/setup/persist-env-config.d.ts +2 -0
- package/dist/setup/persist-env-config.js +63 -0
- package/dist/setup/provision-database.d.ts +2 -0
- package/dist/setup/provision-database.js +89 -0
- package/dist/setup-app.d.ts +4 -0
- package/dist/setup-app.js +43 -0
- package/package.json +96 -0
- package/prisma/schema.prisma +303 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ApiErrorSchema, MarketplaceInstallBodySchema, MarketplaceInstallResponseSchema, MarketplaceListQuerySchema, MarketplaceListResponseSchema, MarketplaceListingDetailSchema, MarketplacePluginIdParamsSchema, } from "@velora-cms/api-schemas";
|
|
2
|
+
import { requireAdmin } from "../auth/require-auth.js";
|
|
3
|
+
import { prisma } from "../db/prisma.js";
|
|
4
|
+
import { installFromMarketplace } from "../marketplace/install-from-marketplace.js";
|
|
5
|
+
import { MarketplaceNotFoundError, MarketplaceUnavailableError, fetchListing, fetchListings, } from "../marketplace/marketplace-client.js";
|
|
6
|
+
/** Compatibility is decided HERE, against this CMS's own dialect: the
|
|
7
|
+
* registry would refuse an incompatible install anyway (S64), so the
|
|
8
|
+
* browse screen says so up front rather than letting a user try. */
|
|
9
|
+
function annotate(listing, activeDialect) {
|
|
10
|
+
const dialects = listing.latestVersion?.supportedDialects ?? [];
|
|
11
|
+
return { ...listing, compatible: dialects.includes(activeDialect) };
|
|
12
|
+
}
|
|
13
|
+
export const marketplaceProxyRoutes = async (app, opts) => {
|
|
14
|
+
const { marketplaceBaseUrl, activeDialect, adapter } = opts;
|
|
15
|
+
app.get("/api/plugin-management/marketplace/listings", {
|
|
16
|
+
onRequest: requireAdmin,
|
|
17
|
+
schema: {
|
|
18
|
+
querystring: MarketplaceListQuerySchema,
|
|
19
|
+
response: { 200: MarketplaceListResponseSchema, 401: ApiErrorSchema, 403: ApiErrorSchema, 503: ApiErrorSchema },
|
|
20
|
+
},
|
|
21
|
+
}, async (request, reply) => {
|
|
22
|
+
try {
|
|
23
|
+
const body = (await fetchListings(marketplaceBaseUrl, request.query));
|
|
24
|
+
return {
|
|
25
|
+
items: body.items.map((item) => annotate(item, activeDialect)),
|
|
26
|
+
nextCursor: body.nextCursor ?? null,
|
|
27
|
+
activeDialect,
|
|
28
|
+
marketplaceBaseUrl,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof MarketplaceUnavailableError) {
|
|
33
|
+
return reply.code(503).send({ message: error.message });
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
app.get("/api/plugin-management/marketplace/listings/:pluginId", {
|
|
39
|
+
onRequest: requireAdmin,
|
|
40
|
+
schema: {
|
|
41
|
+
params: MarketplacePluginIdParamsSchema,
|
|
42
|
+
response: {
|
|
43
|
+
200: MarketplaceListingDetailSchema,
|
|
44
|
+
401: ApiErrorSchema,
|
|
45
|
+
403: ApiErrorSchema,
|
|
46
|
+
404: ApiErrorSchema,
|
|
47
|
+
503: ApiErrorSchema,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}, async (request, reply) => {
|
|
51
|
+
try {
|
|
52
|
+
const listing = (await fetchListing(marketplaceBaseUrl, request.params.pluginId));
|
|
53
|
+
return {
|
|
54
|
+
...annotate(listing, activeDialect),
|
|
55
|
+
versions: listing.versions ?? [],
|
|
56
|
+
activeDialect,
|
|
57
|
+
marketplaceBaseUrl,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error instanceof MarketplaceNotFoundError) {
|
|
62
|
+
return reply.code(404).send({ message: error.message });
|
|
63
|
+
}
|
|
64
|
+
if (error instanceof MarketplaceUnavailableError) {
|
|
65
|
+
return reply.code(503).send({ message: error.message });
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// In-CMS marketplace install (Session 138) — the ordered
|
|
71
|
+
// already-installed -> listing/version -> (paid) license -> download ->
|
|
72
|
+
// verify -> register spine lives in installFromMarketplace so it stays
|
|
73
|
+
// reviewable in one place; this handler just maps its outcome to a
|
|
74
|
+
// status code.
|
|
75
|
+
app.post("/api/plugin-management/marketplace/install", {
|
|
76
|
+
onRequest: requireAdmin,
|
|
77
|
+
schema: {
|
|
78
|
+
body: MarketplaceInstallBodySchema,
|
|
79
|
+
response: {
|
|
80
|
+
201: MarketplaceInstallResponseSchema,
|
|
81
|
+
400: ApiErrorSchema,
|
|
82
|
+
401: ApiErrorSchema,
|
|
83
|
+
403: ApiErrorSchema,
|
|
84
|
+
404: ApiErrorSchema,
|
|
85
|
+
409: ApiErrorSchema,
|
|
86
|
+
422: ApiErrorSchema,
|
|
87
|
+
503: ApiErrorSchema,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}, async (request, reply) => {
|
|
91
|
+
try {
|
|
92
|
+
const outcome = await installFromMarketplace({ prisma, registry: app.pluginRegistry, marketplaceBaseUrl, adapter },
|
|
93
|
+
// installedBy is server-derived from the verified JWT (requireAdmin
|
|
94
|
+
// already ran via onRequest), never taken from the request body —
|
|
95
|
+
// D2's createdBy rule for any sample content this install seeds.
|
|
96
|
+
{ ...request.body, installedBy: request.user.sub });
|
|
97
|
+
if (outcome.ok) {
|
|
98
|
+
return reply.code(201).send({
|
|
99
|
+
pluginId: outcome.pluginId,
|
|
100
|
+
version: outcome.version,
|
|
101
|
+
trustTier: outcome.trustTier,
|
|
102
|
+
licensed: outcome.licensed,
|
|
103
|
+
...(outcome.members ? { members: outcome.members } : {}),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const status = {
|
|
107
|
+
"already-installed": 409,
|
|
108
|
+
"not-found": 404,
|
|
109
|
+
"license-required": 400,
|
|
110
|
+
"site-domain-missing": 409,
|
|
111
|
+
"license-rejected": 403,
|
|
112
|
+
"verification-failed": 422,
|
|
113
|
+
// M2 (S138 hardening review) — see InstallFailure's own comment in
|
|
114
|
+
// install-from-marketplace.ts for why 422 vs 409 was chosen for
|
|
115
|
+
// each of these three.
|
|
116
|
+
incompatible: 422,
|
|
117
|
+
"dependency-missing": 409,
|
|
118
|
+
"install-refused": 422,
|
|
119
|
+
// S152: a member of the requested bundle failed — the bundle
|
|
120
|
+
// package itself may be fine, but the SET it names can't be
|
|
121
|
+
// processed on this server right now. Same 422 family as
|
|
122
|
+
// verification-failed/install-refused; the message names the
|
|
123
|
+
// offending member and its underlying reason.
|
|
124
|
+
"bundle-member-failed": 422,
|
|
125
|
+
};
|
|
126
|
+
return reply.code(status[outcome.failure.code]).send({ message: outcome.failure.message });
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (error instanceof MarketplaceUnavailableError) {
|
|
130
|
+
return reply.code(503).send({ message: error.message });
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
|
|
2
|
+
import type { MediaService } from "../media/media-service.js";
|
|
3
|
+
export interface MediaRoutesOptions {
|
|
4
|
+
mediaService: MediaService;
|
|
5
|
+
}
|
|
6
|
+
export declare const mediaRoutes: FastifyPluginAsyncZod<MediaRoutesOptions>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ApiErrorSchema, MediaDeleteResponseSchema, MediaFileResponseSchema, MediaListQuerySchema, MediaListResponseSchema, } from "@velora-cms/api-schemas";
|
|
3
|
+
import { requireAuth } from "../auth/require-auth.js";
|
|
4
|
+
const MediaParamsSchema = z.object({ id: z.string().uuid() });
|
|
5
|
+
export const mediaRoutes = async (app, opts) => {
|
|
6
|
+
const { mediaService } = opts;
|
|
7
|
+
async function toResponse(media) {
|
|
8
|
+
return { ...media, downloadUrl: await mediaService.getDownloadUrl(media.id) };
|
|
9
|
+
}
|
|
10
|
+
app.post("/api/media", {
|
|
11
|
+
onRequest: requireAuth,
|
|
12
|
+
schema: {
|
|
13
|
+
// Multipart body — file part validated by hand below; Zod can't
|
|
14
|
+
// describe a multipart stream, so validation here is response-side
|
|
15
|
+
// plus the explicit 400s.
|
|
16
|
+
consumes: ["multipart/form-data"],
|
|
17
|
+
response: {
|
|
18
|
+
201: MediaFileResponseSchema,
|
|
19
|
+
400: ApiErrorSchema,
|
|
20
|
+
401: ApiErrorSchema,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}, async (request, reply) => {
|
|
24
|
+
const file = await request.file();
|
|
25
|
+
if (!file || !file.filename) {
|
|
26
|
+
return reply.code(400).send({ message: "A multipart file field is required" });
|
|
27
|
+
}
|
|
28
|
+
const data = await file.toBuffer();
|
|
29
|
+
const media = await mediaService.upload({
|
|
30
|
+
filename: file.filename,
|
|
31
|
+
mimeType: file.mimetype,
|
|
32
|
+
data,
|
|
33
|
+
uploadedBy: request.user.sub,
|
|
34
|
+
});
|
|
35
|
+
return reply.code(201).send(await toResponse(media));
|
|
36
|
+
});
|
|
37
|
+
// Media library listing — admin-authed (SEC-6, S175 audit). This lists
|
|
38
|
+
// EVERY uploaded asset regardless of publish state, each with a working
|
|
39
|
+
// presigned downloadUrl, so it must not be anonymously enumerable;
|
|
40
|
+
// published/headless media has its own API-key-gated /api/v1/media
|
|
41
|
+
// surface. The admin UI reaches this through authFetch.
|
|
42
|
+
app.get("/api/media", {
|
|
43
|
+
onRequest: requireAuth,
|
|
44
|
+
schema: {
|
|
45
|
+
querystring: MediaListQuerySchema,
|
|
46
|
+
response: {
|
|
47
|
+
200: MediaListResponseSchema,
|
|
48
|
+
401: ApiErrorSchema,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}, async (request) => {
|
|
52
|
+
const { cursor, limit, search } = request.query;
|
|
53
|
+
const files = await mediaService.list({ cursor, limit, search });
|
|
54
|
+
const items = await Promise.all(files.map(toResponse));
|
|
55
|
+
const nextCursor = files.length === limit ? (files[files.length - 1]?.id ?? null) : null;
|
|
56
|
+
return { items, nextCursor };
|
|
57
|
+
});
|
|
58
|
+
app.get("/api/media/:id", {
|
|
59
|
+
onRequest: requireAuth,
|
|
60
|
+
schema: {
|
|
61
|
+
params: MediaParamsSchema,
|
|
62
|
+
response: {
|
|
63
|
+
200: MediaFileResponseSchema,
|
|
64
|
+
401: ApiErrorSchema,
|
|
65
|
+
404: ApiErrorSchema,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}, async (request, reply) => {
|
|
69
|
+
const media = await mediaService.getById(request.params.id);
|
|
70
|
+
if (!media) {
|
|
71
|
+
return reply.code(404).send({ message: `No media file found for id ${request.params.id}` });
|
|
72
|
+
}
|
|
73
|
+
return toResponse(media);
|
|
74
|
+
});
|
|
75
|
+
app.delete("/api/media/:id", {
|
|
76
|
+
onRequest: requireAuth,
|
|
77
|
+
schema: {
|
|
78
|
+
params: MediaParamsSchema,
|
|
79
|
+
response: {
|
|
80
|
+
200: MediaDeleteResponseSchema,
|
|
81
|
+
401: ApiErrorSchema,
|
|
82
|
+
404: ApiErrorSchema,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
}, async (request, reply) => {
|
|
86
|
+
const media = await mediaService.getById(request.params.id);
|
|
87
|
+
if (!media) {
|
|
88
|
+
return reply.code(404).send({ message: `No media file found for id ${request.params.id}` });
|
|
89
|
+
}
|
|
90
|
+
await mediaService.delete(media.id);
|
|
91
|
+
return { success: true };
|
|
92
|
+
});
|
|
93
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
|
|
2
|
+
import type { ContentService } from "@velora-cms/content-service";
|
|
3
|
+
import type { DatabaseAdapter } from "@velora-cms/db-adapter";
|
|
4
|
+
import type { PluginRegistryService } from "../plugins/plugin-registry-service.js";
|
|
5
|
+
import type { StructuredStorageAPI } from "../plugins/structured-storage-api.js";
|
|
6
|
+
export interface PluginApiRouteOptions {
|
|
7
|
+
registry: PluginRegistryService;
|
|
8
|
+
structuredStorageAPI: StructuredStorageAPI;
|
|
9
|
+
contentService: ContentService;
|
|
10
|
+
adapter: DatabaseAdapter;
|
|
11
|
+
}
|
|
12
|
+
export declare const pluginApiRoutes: FastifyPluginAsyncZod<PluginApiRouteOptions>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { ApiErrorSchema } from "@velora-cms/api-schemas";
|
|
2
|
+
import { PluginNotActiveError, PluginNotInstalledError } from "../plugins/plugin-registry-service.js";
|
|
3
|
+
import { buildContext } from "../plugins/plugin-context.js";
|
|
4
|
+
import { requireApiKey } from "../auth/api-keys.js";
|
|
5
|
+
import { rateLimitConfig } from "../auth/rate-limit.js";
|
|
6
|
+
import { requireAdmin } from "../auth/require-auth.js";
|
|
7
|
+
import { corsOptionsFromEnv } from "../config/cors.js";
|
|
8
|
+
function findDeclaredRoute(manifest, method, path) {
|
|
9
|
+
const declared = manifest.type === "section" ? (manifest.contributions.apiRoutes ?? []) : [];
|
|
10
|
+
return declared.find((route) => route.method === method && route.path === path);
|
|
11
|
+
}
|
|
12
|
+
// Real runtime mounting of the apiRoutes contribution (Month 8, Session
|
|
13
|
+
// 89) — previously manifest-validated only, never mounted anywhere.
|
|
14
|
+
// Fastify 5's router is sealed once the server starts listening, so a
|
|
15
|
+
// plugin's routes (only known once its manifest is installed/activated,
|
|
16
|
+
// possibly long after boot) can't be registered as literal Fastify
|
|
17
|
+
// routes. Instead, exactly 5 routes are registered here at boot — one
|
|
18
|
+
// per HTTP method contributions.apiRoutes supports — each a wildcard
|
|
19
|
+
// catch-all under /api/plugins/:pluginId/*, dispatching at request time
|
|
20
|
+
// to whichever route the target plugin's manifest actually declares.
|
|
21
|
+
// Mirrors plugin-storage.ts's exact same shape of problem (one fixed
|
|
22
|
+
// route, request-time dispatch via requireActivePlugin) generalized
|
|
23
|
+
// from a single fixed sub-path to an arbitrary plugin-declared set.
|
|
24
|
+
//
|
|
25
|
+
// Auth is enforced per-route as of Session 108: each declared route's
|
|
26
|
+
// `access` (Task 1's ApiRouteContribution.access) gates the dispatch,
|
|
27
|
+
// defaulting to "admin" when the manifest omits it — with "apiKey" as an
|
|
28
|
+
// explicit opt-in for headless consumers (e.g. Commerce's checkout).
|
|
29
|
+
// Session 160 adds a third tier, "public" — genuinely un-credentialed,
|
|
30
|
+
// for anonymous browser traffic like a contact-form submission, deferred
|
|
31
|
+
// back in Session 108 until a real consumer showed up. So a route CAN
|
|
32
|
+
// now be called with zero credentials, but only when its manifest opts
|
|
33
|
+
// in explicitly; every other value (including unrecognized ones) still
|
|
34
|
+
// falls back to "admin". Rate limiting for apiKey and public routes
|
|
35
|
+
// lands in this same session, as a separate, orthogonal concern (who may
|
|
36
|
+
// call vs. how often).
|
|
37
|
+
export const pluginApiRoutes = async (app, opts) => {
|
|
38
|
+
// Applies to every dispatch route registered in the prefixed child
|
|
39
|
+
// scope below — a parent context's hooks cascade to its children, so
|
|
40
|
+
// this doesn't need to move inside that scope to keep covering them.
|
|
41
|
+
await app.register(import("@fastify/rate-limit"), rateLimitConfig);
|
|
42
|
+
const { registry, structuredStorageAPI, contentService, adapter } = opts;
|
|
43
|
+
function dispatch(method) {
|
|
44
|
+
return async (request, reply) => {
|
|
45
|
+
const { pluginId } = request.params;
|
|
46
|
+
const wildcard = request.params["*"] ?? "";
|
|
47
|
+
const path = `/${wildcard}`;
|
|
48
|
+
try {
|
|
49
|
+
await registry.requireActivePlugin(pluginId);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (err instanceof PluginNotInstalledError) {
|
|
53
|
+
return reply.code(404).send({ message: err.message });
|
|
54
|
+
}
|
|
55
|
+
if (err instanceof PluginNotActiveError) {
|
|
56
|
+
return reply.code(403).send({ message: err.message });
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
const attached = registry.getManifest(pluginId);
|
|
61
|
+
if (!attached) {
|
|
62
|
+
// Transient, not permanent: the plugin IS installed and active
|
|
63
|
+
// (requireActivePlugin above already confirmed the DB row) — its
|
|
64
|
+
// manifest just isn't attached in THIS process (no boot-time
|
|
65
|
+
// plugin loader re-attaches every active plugin's manifest on
|
|
66
|
+
// restart yet). A 503, not a 404, so a headless consumer knows
|
|
67
|
+
// this is retryable rather than "this route doesn't exist."
|
|
68
|
+
return reply.code(503).send({
|
|
69
|
+
message: `Plugin "${pluginId}" is active but its manifest is not loaded in this process`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const route = findDeclaredRoute(attached, method, path);
|
|
73
|
+
if (!route) {
|
|
74
|
+
return reply.code(404).send({ message: `No route ${method} ${path} declared by plugin "${pluginId}"` });
|
|
75
|
+
}
|
|
76
|
+
// Enforce the route's declared access. Default (and any
|
|
77
|
+
// unrecognized value) is "admin" — secure by default, by
|
|
78
|
+
// construction. "public" is S160's genuinely un-credentialed tier
|
|
79
|
+
// for anonymous browser traffic (form submissions); such a route
|
|
80
|
+
// owns its own abuse controls, and the platform's contribution is
|
|
81
|
+
// the IP rate ceiling in rate-limit.ts plus the CORS allowlist.
|
|
82
|
+
// Runs here (not as a route-level onRequest hook) because the
|
|
83
|
+
// access level is only known once the declared route is resolved.
|
|
84
|
+
// requireApiKey/requireAdmin send their own 401/403 and we bail if
|
|
85
|
+
// they did.
|
|
86
|
+
const access = route.access ?? "admin";
|
|
87
|
+
if (access === "public") {
|
|
88
|
+
// No credential check by design.
|
|
89
|
+
}
|
|
90
|
+
else if (access === "apiKey") {
|
|
91
|
+
await requireApiKey(request, reply);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
await requireAdmin(request, reply);
|
|
95
|
+
}
|
|
96
|
+
if (reply.sent)
|
|
97
|
+
return reply;
|
|
98
|
+
// Re-bind to each schema's parsed output (not just validate-then-
|
|
99
|
+
// discard) — same behavior a normally-registered Fastify route gets
|
|
100
|
+
// from fastify-type-provider-zod, so a plugin's z.coerce/.default()/
|
|
101
|
+
// .transform() actually take effect for its handler, not just the
|
|
102
|
+
// raw wire value.
|
|
103
|
+
if (route.schema?.body) {
|
|
104
|
+
const result = route.schema.body.safeParse(request.body);
|
|
105
|
+
if (!result.success) {
|
|
106
|
+
return reply.code(400).send({ message: `Invalid request body: ${result.error.message}` });
|
|
107
|
+
}
|
|
108
|
+
request.body = result.data;
|
|
109
|
+
}
|
|
110
|
+
if (route.schema?.querystring) {
|
|
111
|
+
const result = route.schema.querystring.safeParse(request.query);
|
|
112
|
+
if (!result.success) {
|
|
113
|
+
return reply.code(400).send({ message: `Invalid query string: ${result.error.message}` });
|
|
114
|
+
}
|
|
115
|
+
request.query = result.data;
|
|
116
|
+
}
|
|
117
|
+
if (route.schema?.params) {
|
|
118
|
+
const result = route.schema.params.safeParse(request.params);
|
|
119
|
+
if (!result.success) {
|
|
120
|
+
return reply.code(400).send({ message: `Invalid params: ${result.error.message}` });
|
|
121
|
+
}
|
|
122
|
+
request.params = result.data;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
// route.handler's return is deliberately `unknown` at the SDK
|
|
126
|
+
// boundary (manifest.ts: "the handler's response is NOT
|
|
127
|
+
// schema-validated — trusted, same as SimpleStorageAPI's
|
|
128
|
+
// handlers today"). The response schema below only documents
|
|
129
|
+
// the error shapes (400/403/404/500), so fastify-type-provider-
|
|
130
|
+
// zod infers a strict `void | ApiError` return type for this
|
|
131
|
+
// handler — this cast is a compile-time-only acknowledgment of
|
|
132
|
+
// that intentionally-untyped success path, not a runtime lie:
|
|
133
|
+
// Fastify serializes whatever is returned regardless.
|
|
134
|
+
const context = buildContext(pluginId, structuredStorageAPI, contentService, adapter);
|
|
135
|
+
return (await route.handler(request, reply, context));
|
|
136
|
+
}
|
|
137
|
+
catch (cause) {
|
|
138
|
+
request.log.error({ err: cause, pluginId, path }, "plugin apiRoute handler threw");
|
|
139
|
+
return reply.code(500).send({ message: "Internal server error" });
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const responseSchema = {
|
|
144
|
+
400: ApiErrorSchema,
|
|
145
|
+
401: ApiErrorSchema,
|
|
146
|
+
403: ApiErrorSchema,
|
|
147
|
+
404: ApiErrorSchema,
|
|
148
|
+
500: ApiErrorSchema,
|
|
149
|
+
503: ApiErrorSchema,
|
|
150
|
+
};
|
|
151
|
+
// CORS and the dispatch routes are registered inside a PREFIXED child
|
|
152
|
+
// scope, not directly on `app`, and that prefix is what actually
|
|
153
|
+
// confines this to plugin routes. @fastify/cors is fastify-plugin-
|
|
154
|
+
// wrapped, so its onRequest HOOK is properly encapsulated (same as
|
|
155
|
+
// rate-limit above) — but it also registers a real ROUTE
|
|
156
|
+
// (`fastify.options('*')`), and Fastify's route table is server-wide:
|
|
157
|
+
// encapsulation confines hooks and decorators, it does NOT confine
|
|
158
|
+
// where a route's path matches. Registered directly on `app` (whose
|
|
159
|
+
// own routes below already carried full literal paths, i.e. no
|
|
160
|
+
// prefix), that `*` would have matched EVERY path on the server, not
|
|
161
|
+
// just this plugin's dispatch routes — a browser on an allowlisted
|
|
162
|
+
// origin could preflight and then send a non-simple cross-origin
|
|
163
|
+
// request (JSON body, Authorization header) to any core endpoint, not
|
|
164
|
+
// just a plugin's. Reads stayed blocked (the response hook is scoped,
|
|
165
|
+
// so no Access-Control-Allow-Origin ever came back on those), but
|
|
166
|
+
// blind cross-origin writes to core endpoints were live. Wrapping both
|
|
167
|
+
// in a `{ prefix: "/api/plugins" }` child scope confines the ROUTE to
|
|
168
|
+
// `/api/plugins/*` — routes below are written relative to that prefix
|
|
169
|
+
// accordingly. The child scope still inherits the rate-limit hook
|
|
170
|
+
// registered on `app` above.
|
|
171
|
+
//
|
|
172
|
+
// That confines the route, but not the blast radius to just THIS
|
|
173
|
+
// scope's own dispatch routes: `OPTIONS /api/plugins/*` also matches
|
|
174
|
+
// GET /api/plugins (routes/plugins.ts), plugin-storage.ts's GET/PUT, and
|
|
175
|
+
// plugin-management.ts's routes — none of which are registered in this
|
|
176
|
+
// scope, so none of them get the CORS response hook, so none of them
|
|
177
|
+
// ever return Access-Control-Allow-Origin on the actual (non-preflight)
|
|
178
|
+
// request. That gap is NOT exploitable, and is not being restructured
|
|
179
|
+
// here — two things contain it: corsOptionsFromEnv's `methods` allowlist
|
|
180
|
+
// (GET/POST/OPTIONS only) means a preflight for plugin-management's
|
|
181
|
+
// DELETE or plugin-storage's PUT is refused before any cross-origin
|
|
182
|
+
// write can be attempted, and `credentials: false` plus app.ts
|
|
183
|
+
// registering fastifyJwt with no `cookie` option means jwtVerify() only
|
|
184
|
+
// ever reads the Authorization header — never a cookie a browser would
|
|
185
|
+
// attach ambiently — so there is no ambient-credential CSRF surface for
|
|
186
|
+
// the wildcard preflight to ride in on.
|
|
187
|
+
//
|
|
188
|
+
// One real, harmless behaviour change this DOES cause: with
|
|
189
|
+
// strictPreflight left at its default (true), `OPTIONS
|
|
190
|
+
// /api/plugins/anything` now 400s (missing Origin or
|
|
191
|
+
// Access-Control-Request-Method) instead of 404ing as it did before this
|
|
192
|
+
// scope existed — including when VELORA_CORS_ORIGINS is unset, since
|
|
193
|
+
// @fastify/cors is registered unconditionally and only the response
|
|
194
|
+
// headers it adds are gated on that var, not the OPTIONS route/hook
|
|
195
|
+
// themselves.
|
|
196
|
+
await app.register(async (scope) => {
|
|
197
|
+
// strictPreflight (the plugin's default: true) is left as-is: an
|
|
198
|
+
// OPTIONS request that's missing Origin or Access-Control-Request-
|
|
199
|
+
// Method now 400s only under this prefix, not server-wide, and
|
|
200
|
+
// nothing in the dispatch verbs below is OPTIONS — a bare OPTIONS
|
|
201
|
+
// hitting a plugin route is either a real preflight (always sends
|
|
202
|
+
// both headers) or a malformed one worth rejecting, not a generic
|
|
203
|
+
// uptime probe (those target /api/health, not a plugin path).
|
|
204
|
+
await scope.register(import("@fastify/cors"), corsOptionsFromEnv((msg) => scope.log.warn(msg)));
|
|
205
|
+
// bodyLimit (S160 final-fix, finding 3): app.ts sets no bodyLimit, so
|
|
206
|
+
// Fastify's 1 MB default would otherwise apply here too — including
|
|
207
|
+
// to the "public" (zero-credential) tier, where the body is parsed,
|
|
208
|
+
// and for forms's /submit route re-serialized and hashed, BEFORE any
|
|
209
|
+
// plugin-level size check (e.g. forms-api.ts's MAX_SUBMISSION_BYTES,
|
|
210
|
+
// 20 KB) or rate-bucket slot is ever spent. 64 KB is comfortably
|
|
211
|
+
// above every real admin payload these dispatch routes carry today
|
|
212
|
+
// (form/redirect-rule/webhook-endpoint definitions, a Commerce
|
|
213
|
+
// checkout body) while cutting the untrusted-parse ceiling by ~16x
|
|
214
|
+
// for the public route. Checked before verified above: reviewed
|
|
215
|
+
// every current plugins/* POST/PUT/PATCH route to confirm none sends
|
|
216
|
+
// a body anywhere near this size.
|
|
217
|
+
const bodyLimit = 65536;
|
|
218
|
+
scope.get("/:pluginId/*", { schema: { response: responseSchema } }, dispatch("GET"));
|
|
219
|
+
scope.post("/:pluginId/*", { bodyLimit, schema: { response: responseSchema } }, dispatch("POST"));
|
|
220
|
+
scope.put("/:pluginId/*", { bodyLimit, schema: { response: responseSchema } }, dispatch("PUT"));
|
|
221
|
+
scope.patch("/:pluginId/*", { bodyLimit, schema: { response: responseSchema } }, dispatch("PATCH"));
|
|
222
|
+
scope.delete("/:pluginId/*", { bodyLimit, schema: { response: responseSchema } }, dispatch("DELETE"));
|
|
223
|
+
}, { prefix: "/api/plugins" });
|
|
224
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
export declare const PLUGIN_ASSETS_PREFIX = "/plugin-assets";
|
|
3
|
+
/** The server-relative remoteUrl a marketplace install stores (Task 2).
|
|
4
|
+
* Server-relative rather than absolute so a host/port/domain change never
|
|
5
|
+
* invalidates installed rows; the admin absolutizes against its own
|
|
6
|
+
* origin, which fronts this server for /api and /docs already. */
|
|
7
|
+
export declare function pluginAssetsRemoteUrl(pluginId: string, version: string): string;
|
|
8
|
+
export declare function pluginAssetsRoutes(app: FastifyInstance): Promise<void>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import fastifyStatic from "@fastify/static";
|
|
3
|
+
import { resolvePluginsDir } from "../plugins/plugin-package-store.js";
|
|
4
|
+
// S143: serves the managed plugin package store (plugin-package-store.ts,
|
|
5
|
+
// .velora/plugins/<pluginId>/<version>/) over HTTP so the admin's Module
|
|
6
|
+
// Federation loader can fetch a marketplace-installed plugin's
|
|
7
|
+
// remoteEntry.js — closing the S142 documented gap that left
|
|
8
|
+
// marketplace-installed plugins silently skipped by boot-remote-plugins
|
|
9
|
+
// (empty remoteUrl). The URL path IS the store path: no indirection.
|
|
10
|
+
//
|
|
11
|
+
// Unauthenticated by design, the same posture as GET /api/plugins (this is
|
|
12
|
+
// the same package bytes anyone can download from the public marketplace).
|
|
13
|
+
// Rate limiting joins the Month 14 pre-deploy backlog with the rest of the
|
|
14
|
+
// public surface. Serving server/plugin.js alongside the client assets is
|
|
15
|
+
// accepted: identical bytes ship in the public package artifact.
|
|
16
|
+
// Stored VERBATIM in pluginRegistry.remoteUrl rows at install time —
|
|
17
|
+
// changing this prefix strands every already-installed row on the old
|
|
18
|
+
// literal path (plugins silently stop loading) until a backfill or
|
|
19
|
+
// reinstall. Treat it as frozen, not a config knob.
|
|
20
|
+
export const PLUGIN_ASSETS_PREFIX = "/plugin-assets";
|
|
21
|
+
/** The server-relative remoteUrl a marketplace install stores (Task 2).
|
|
22
|
+
* Server-relative rather than absolute so a host/port/domain change never
|
|
23
|
+
* invalidates installed rows; the admin absolutizes against its own
|
|
24
|
+
* origin, which fronts this server for /api and /docs already. */
|
|
25
|
+
export function pluginAssetsRemoteUrl(pluginId, version) {
|
|
26
|
+
return `${PLUGIN_ASSETS_PREFIX}/${pluginId}/${version}/remoteEntry.js`;
|
|
27
|
+
}
|
|
28
|
+
export async function pluginAssetsRoutes(app) {
|
|
29
|
+
const root = resolvePluginsDir();
|
|
30
|
+
// @fastify/static@10 throws "root must be a directory" at registration
|
|
31
|
+
// (the docs-static.ts lesson) — on a fresh instance nothing is installed
|
|
32
|
+
// yet, so create the store empty. It is server-owned, so this is safe.
|
|
33
|
+
mkdirSync(root, { recursive: true });
|
|
34
|
+
// Static infra, not Zod-validated JSON — hidden from Swagger up front
|
|
35
|
+
// (the docs-static.ts treatment) rather than leaking bare path entries.
|
|
36
|
+
app.addHook("onRoute", (routeOptions) => {
|
|
37
|
+
if (routeOptions.url.startsWith(PLUGIN_ASSETS_PREFIX)) {
|
|
38
|
+
routeOptions.schema = { ...routeOptions.schema, hide: true };
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// Encapsulated child context with a trailing-slash prefix: confines
|
|
42
|
+
// setNotFoundHandler to this mount (docs-static.ts documents why — a
|
|
43
|
+
// bare prefix would hijack the app-wide 404, including /api/*).
|
|
44
|
+
await app.register(async (assetsApp) => {
|
|
45
|
+
await assetsApp.register(fastifyStatic, {
|
|
46
|
+
root,
|
|
47
|
+
decorateReply: false,
|
|
48
|
+
});
|
|
49
|
+
assetsApp.setNotFoundHandler((_req, reply) => reply.code(404).send({ message: "not found" }));
|
|
50
|
+
}, { prefix: `${PLUGIN_ASSETS_PREFIX}/` });
|
|
51
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
|
|
2
|
+
import type { DatabaseAdapter } from "@velora-cms/db-adapter";
|
|
3
|
+
import type { StructuredSnapshotService } from "../plugins/structured-snapshot-service.js";
|
|
4
|
+
export declare const pluginManagementRoutes: FastifyPluginAsyncZod<{
|
|
5
|
+
adapter: DatabaseAdapter;
|
|
6
|
+
structuredSnapshotService: StructuredSnapshotService;
|
|
7
|
+
}>;
|