@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,162 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { CreateBucketCommand, DeleteObjectCommand, GetObjectCommand, HeadBucketCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3";
|
|
3
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
4
|
+
// Defaults match the minio service in docker-compose.yml (same treatment as
|
|
5
|
+
// POSTGRES_TEST_URL), so local dev works with zero required config. Point
|
|
6
|
+
// these at real S3/R2 via .env — see .env.example.
|
|
7
|
+
export function mediaStorageConfigFromEnv() {
|
|
8
|
+
return {
|
|
9
|
+
endpoint: process.env.S3_ENDPOINT ?? "http://localhost:9000",
|
|
10
|
+
region: process.env.S3_REGION ?? "us-east-1",
|
|
11
|
+
accessKeyId: process.env.S3_ACCESS_KEY_ID ?? "velora",
|
|
12
|
+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY ?? "velora-secret",
|
|
13
|
+
bucket: process.env.S3_BUCKET ?? "velora-media",
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export class MediaService {
|
|
17
|
+
prisma;
|
|
18
|
+
config;
|
|
19
|
+
dialect;
|
|
20
|
+
s3;
|
|
21
|
+
constructor(prisma, config,
|
|
22
|
+
// Which Prisma provider the injected client is actually querying against
|
|
23
|
+
// (postgresql | mysql | sqlite) — needed by list()'s search filter,
|
|
24
|
+
// see buildFilenameSearchFilter below. Passed in rather than
|
|
25
|
+
// rediscovered here so this class stays agnostic of how the caller
|
|
26
|
+
// resolved it (db/prisma.ts's VELORA_PRISMA_CLIENT_DIR-derived value in
|
|
27
|
+
// the real app, a literal in tests).
|
|
28
|
+
dialect) {
|
|
29
|
+
this.prisma = prisma;
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.dialect = dialect;
|
|
32
|
+
this.s3 = new S3Client({
|
|
33
|
+
endpoint: config.endpoint,
|
|
34
|
+
region: config.region,
|
|
35
|
+
credentials: {
|
|
36
|
+
accessKeyId: config.accessKeyId,
|
|
37
|
+
secretAccessKey: config.secretAccessKey,
|
|
38
|
+
},
|
|
39
|
+
// MinIO and R2 route by path, not by bucket subdomain — and a
|
|
40
|
+
// localhost endpoint can't do virtual-hosted buckets at all.
|
|
41
|
+
forcePathStyle: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Idempotent; safe to call on startup or from tests. Only creates the
|
|
45
|
+
// bucket when it doesn't exist yet.
|
|
46
|
+
async ensureBucket() {
|
|
47
|
+
try {
|
|
48
|
+
await this.s3.send(new HeadBucketCommand({ Bucket: this.config.bucket }));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
await this.s3.send(new CreateBucketCommand({ Bucket: this.config.bucket }));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async upload(input) {
|
|
55
|
+
const id = randomUUID();
|
|
56
|
+
const storageKey = `${id}/${sanitizeForKey(input.filename)}`;
|
|
57
|
+
await this.s3.send(new PutObjectCommand({
|
|
58
|
+
Bucket: this.config.bucket,
|
|
59
|
+
Key: storageKey,
|
|
60
|
+
Body: input.data,
|
|
61
|
+
ContentType: input.mimeType,
|
|
62
|
+
ContentLength: input.data.length,
|
|
63
|
+
}));
|
|
64
|
+
try {
|
|
65
|
+
const row = await this.prisma.mediaFile.create({
|
|
66
|
+
data: {
|
|
67
|
+
id,
|
|
68
|
+
filename: input.filename,
|
|
69
|
+
mimeType: input.mimeType,
|
|
70
|
+
size: input.data.length,
|
|
71
|
+
storageKey,
|
|
72
|
+
url: this.canonicalUrl(storageKey),
|
|
73
|
+
uploadedBy: input.uploadedBy,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
return toMediaFile(row);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
// Metadata is the source of truth — don't leave an object around that
|
|
80
|
+
// no metadata row points to.
|
|
81
|
+
await this.s3
|
|
82
|
+
.send(new DeleteObjectCommand({ Bucket: this.config.bucket, Key: storageKey }))
|
|
83
|
+
.catch(() => undefined);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async getById(id) {
|
|
88
|
+
const row = await this.prisma.mediaFile.findUnique({ where: { id } });
|
|
89
|
+
return row ? toMediaFile(row) : null;
|
|
90
|
+
}
|
|
91
|
+
// Newest-first, cursor-paginated (cursor = previous page's last id,
|
|
92
|
+
// same semantics as the content list endpoints). Prisma positions the
|
|
93
|
+
// cursor row within the compound order and skips it.
|
|
94
|
+
async list(options) {
|
|
95
|
+
const rows = await this.prisma.mediaFile.findMany({
|
|
96
|
+
where: options.search
|
|
97
|
+
? { filename: buildFilenameSearchFilter(options.search, this.dialect) }
|
|
98
|
+
: undefined,
|
|
99
|
+
orderBy: [{ uploadedAt: "desc" }, { id: "desc" }],
|
|
100
|
+
take: options.limit,
|
|
101
|
+
...(options.cursor ? { cursor: { id: options.cursor }, skip: 1 } : {}),
|
|
102
|
+
});
|
|
103
|
+
return rows.map(toMediaFile);
|
|
104
|
+
}
|
|
105
|
+
// Time-limited presigned GET — works against private buckets, which is
|
|
106
|
+
// why downloads go through this rather than the stored canonical url.
|
|
107
|
+
async getDownloadUrl(id, expiresInSeconds = 900) {
|
|
108
|
+
const row = await this.prisma.mediaFile.findUnique({ where: { id } });
|
|
109
|
+
if (!row) {
|
|
110
|
+
throw new Error(`Media file not found: ${id}`);
|
|
111
|
+
}
|
|
112
|
+
return getSignedUrl(this.s3, new GetObjectCommand({ Bucket: this.config.bucket, Key: row.storageKey }), { expiresIn: expiresInSeconds });
|
|
113
|
+
}
|
|
114
|
+
async delete(id) {
|
|
115
|
+
const row = await this.prisma.mediaFile.findUnique({ where: { id } });
|
|
116
|
+
if (!row) {
|
|
117
|
+
throw new Error(`Media file not found: ${id}`);
|
|
118
|
+
}
|
|
119
|
+
await this.s3.send(new DeleteObjectCommand({ Bucket: this.config.bucket, Key: row.storageKey }));
|
|
120
|
+
await this.prisma.mediaFile.delete({ where: { id } });
|
|
121
|
+
}
|
|
122
|
+
canonicalUrl(storageKey) {
|
|
123
|
+
const encodedKey = storageKey.split("/").map(encodeURIComponent).join("/");
|
|
124
|
+
return `${this.config.endpoint.replace(/\/$/, "")}/${this.config.bucket}/${encodedKey}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Case-insensitive filename search, built per the active Prisma provider's
|
|
128
|
+
// verified capabilities (Session 131 walkthrough, task-4-report.md step 13
|
|
129
|
+
// — SQLite 500'd here because `mode: "insensitive"` isn't a real argument
|
|
130
|
+
// outside the postgresql provider):
|
|
131
|
+
// - postgresql: bare `contains` compiles to a case-SENSITIVE LIKE, so
|
|
132
|
+
// Prisma's `mode: "insensitive"` is genuinely required there.
|
|
133
|
+
// - mysql: the generated client rejects `mode` (Prisma only accepts it
|
|
134
|
+
// for postgresql). Dropping `mode` assumes MySQL 8's default collation
|
|
135
|
+
// (utf8mb4_0900_ai_ci); case-sensitive collations silently degrade to
|
|
136
|
+
// case-sensitive search. Velora does not pin collation; the
|
|
137
|
+
// collation-independent fix (S104 LOWER()-both-sides) is backlogged.
|
|
138
|
+
// - sqlite: same rejection as mysql, and dropping `mode` is safe for the
|
|
139
|
+
// same reason — `contains` compiles to `LIKE`, which SQLite evaluates
|
|
140
|
+
// ASCII-case-insensitively by default.
|
|
141
|
+
// Verified empirically against real postgres/mysql/sqlite instances before
|
|
142
|
+
// writing this — don't "simplify" this back to one shape for every
|
|
143
|
+
// dialect; that's exactly what caused the SQLite 500.
|
|
144
|
+
export function buildFilenameSearchFilter(search, dialect) {
|
|
145
|
+
return dialect === "postgresql" ? { contains: search, mode: "insensitive" } : { contains: search };
|
|
146
|
+
}
|
|
147
|
+
// Original filename lives untouched in metadata; the object key only keeps
|
|
148
|
+
// characters that are safe in S3 keys and URLs.
|
|
149
|
+
function sanitizeForKey(filename) {
|
|
150
|
+
return filename.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
151
|
+
}
|
|
152
|
+
function toMediaFile(row) {
|
|
153
|
+
return {
|
|
154
|
+
id: row.id,
|
|
155
|
+
filename: row.filename,
|
|
156
|
+
mimeType: row.mimeType,
|
|
157
|
+
size: row.size,
|
|
158
|
+
url: row.url,
|
|
159
|
+
uploadedBy: row.uploadedBy,
|
|
160
|
+
uploadedAt: row.uploadedAt.toISOString(),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PrismaClient } from "@prisma/client";
|
|
2
|
+
import type { PluginRegistryService } from "./plugin-registry-service.js";
|
|
3
|
+
export interface BootLog {
|
|
4
|
+
info(msg: string): void;
|
|
5
|
+
warn(msg: string): void;
|
|
6
|
+
}
|
|
7
|
+
export interface BootPluginsReport {
|
|
8
|
+
attached: string[];
|
|
9
|
+
skipped: {
|
|
10
|
+
pluginId: string;
|
|
11
|
+
reason: string;
|
|
12
|
+
}[];
|
|
13
|
+
failed: {
|
|
14
|
+
pluginId: string;
|
|
15
|
+
reason: string;
|
|
16
|
+
}[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The registry-driven boot loader (Session 122) — the caller of the
|
|
20
|
+
* attachManifest() bridge Sessions 62-63 designed for exactly this.
|
|
21
|
+
* Runs once at boot, AFTER the registry exists and BEFORE the server
|
|
22
|
+
* answers requests: for every installed plugin row (enabled or not —
|
|
23
|
+
* attaching a disabled plugin's manifest is what lets activate() work
|
|
24
|
+
* after a restart without a reinstall), import the stored package's
|
|
25
|
+
* self-contained server/plugin.js (Session 121), re-validate, attach.
|
|
26
|
+
*
|
|
27
|
+
* Failure posture (the Session 91 precedent): one plugin failing NEVER
|
|
28
|
+
* blocks boot or any other plugin. A failed/missing package logs a
|
|
29
|
+
* warning and its routes keep the existing 503-with-clear-cause
|
|
30
|
+
* behavior until reinstall.
|
|
31
|
+
*/
|
|
32
|
+
export declare function bootInstalledPlugins(prisma: PrismaClient, registry: PluginRegistryService, log: BootLog): Promise<BootPluginsReport>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { validatePluginManifest } from "@velora-cms/plugin-sdk/manifest";
|
|
5
|
+
/**
|
|
6
|
+
* The registry-driven boot loader (Session 122) — the caller of the
|
|
7
|
+
* attachManifest() bridge Sessions 62-63 designed for exactly this.
|
|
8
|
+
* Runs once at boot, AFTER the registry exists and BEFORE the server
|
|
9
|
+
* answers requests: for every installed plugin row (enabled or not —
|
|
10
|
+
* attaching a disabled plugin's manifest is what lets activate() work
|
|
11
|
+
* after a restart without a reinstall), import the stored package's
|
|
12
|
+
* self-contained server/plugin.js (Session 121), re-validate, attach.
|
|
13
|
+
*
|
|
14
|
+
* Failure posture (the Session 91 precedent): one plugin failing NEVER
|
|
15
|
+
* blocks boot or any other plugin. A failed/missing package logs a
|
|
16
|
+
* warning and its routes keep the existing 503-with-clear-cause
|
|
17
|
+
* behavior until reinstall.
|
|
18
|
+
*/
|
|
19
|
+
export async function bootInstalledPlugins(prisma, registry, log) {
|
|
20
|
+
const report = { attached: [], skipped: [], failed: [] };
|
|
21
|
+
const rows = await prisma.pluginRegistry.findMany();
|
|
22
|
+
for (const row of rows) {
|
|
23
|
+
if (!row.packagePath) {
|
|
24
|
+
report.skipped.push({
|
|
25
|
+
pluginId: row.pluginId,
|
|
26
|
+
reason: "no stored package recorded (live-source plugin, or an install that never recorded its package)",
|
|
27
|
+
});
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const entryPath = join(row.packagePath, "server", "plugin.js");
|
|
32
|
+
if (!existsSync(entryPath)) {
|
|
33
|
+
report.failed.push({ pluginId: row.pluginId, reason: `package missing at ${entryPath}` });
|
|
34
|
+
log.warn(`[plugin-boot] ${row.pluginId}: package missing at ${entryPath} — routes will 503 until reinstall`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const mod = (await import(pathToFileURL(entryPath).href));
|
|
38
|
+
if (!mod.default) {
|
|
39
|
+
throw new Error("server/plugin.js has no default export");
|
|
40
|
+
}
|
|
41
|
+
const validated = validatePluginManifest(mod.default);
|
|
42
|
+
if (!validated.ok) {
|
|
43
|
+
throw new Error(`manifest failed validation: ${validated.errors.join("; ")}`);
|
|
44
|
+
}
|
|
45
|
+
await registry.attachManifest(validated.manifest);
|
|
46
|
+
report.attached.push(row.pluginId);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
50
|
+
report.failed.push({ pluginId: row.pluginId, reason });
|
|
51
|
+
log.warn(`[plugin-boot] ${row.pluginId}: failed to load — ${reason}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
log.info(`[plugin-boot] attached ${report.attached.length}, skipped ${report.skipped.length}, failed ${report.failed.length}` +
|
|
55
|
+
(report.attached.length ? ` (attached: ${report.attached.join(", ")})` : ""));
|
|
56
|
+
return report;
|
|
57
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ContentLifecycleEvent, ContentService } from "@velora-cms/content-service";
|
|
2
|
+
import type { DatabaseAdapter } from "@velora-cms/db-adapter";
|
|
3
|
+
import type { PluginRegistryService } from "./plugin-registry-service.js";
|
|
4
|
+
import type { StructuredStorageAPI } from "./structured-storage-api.js";
|
|
5
|
+
export declare function createHookDispatcher(deps: {
|
|
6
|
+
registry: PluginRegistryService;
|
|
7
|
+
structuredStorageAPI: StructuredStorageAPI;
|
|
8
|
+
contentService: ContentService;
|
|
9
|
+
adapter: DatabaseAdapter;
|
|
10
|
+
log: {
|
|
11
|
+
error: (obj: unknown, msg: string) => void;
|
|
12
|
+
};
|
|
13
|
+
}): (event: ContentLifecycleEvent) => void;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { buildContext } from "./plugin-context.js";
|
|
2
|
+
// S159: the hook engine's host half. Resolves every ACTIVE plugin's
|
|
3
|
+
// handler for the event and invokes each fire-and-forget with the same
|
|
4
|
+
// containment posture as plugin API routes: one plugin throwing is a log
|
|
5
|
+
// line, never a failed publish. The returned function is synchronous and
|
|
6
|
+
// void — ContentService's editorial path never awaits hook work.
|
|
7
|
+
export function createHookDispatcher(deps) {
|
|
8
|
+
return (event) => {
|
|
9
|
+
// listActiveHookContributions reads manifest.contributions off objects
|
|
10
|
+
// supplied by dynamically-imported plugin code, same as buildContext
|
|
11
|
+
// below — a throwing getter here is just as untrusted, and sits above
|
|
12
|
+
// the loop, so it needs its own boundary or it would silently abort
|
|
13
|
+
// dispatch for every plugin on every event.
|
|
14
|
+
let contributions;
|
|
15
|
+
try {
|
|
16
|
+
contributions = deps.registry.listActiveHookContributions(event.event);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
try {
|
|
20
|
+
deps.log.error({ err, event: event.event }, "plugin hook lookup failed");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Same untrusted-boundary swallow as below.
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const { pluginId, handler } of contributions) {
|
|
28
|
+
// The loop body itself — not just the deferred handler call below —
|
|
29
|
+
// must be per-plugin contained. buildContext and the synchronous
|
|
30
|
+
// prelude around it run on THIS plugin's turn through the loop; a
|
|
31
|
+
// throwing getter or similar from dynamically-imported plugin code
|
|
32
|
+
// here would otherwise abort dispatch for every plugin after this
|
|
33
|
+
// one, silently (nothing would log it).
|
|
34
|
+
try {
|
|
35
|
+
const context = buildContext(pluginId, deps.structuredStorageAPI, deps.contentService, deps.adapter);
|
|
36
|
+
// Each handler gets its own shallow copy of the event payload —
|
|
37
|
+
// every invocation below is a deferred microtask sharing what
|
|
38
|
+
// would otherwise be one object, so one plugin mutating its
|
|
39
|
+
// payload could otherwise leak that mutation into another
|
|
40
|
+
// plugin's handler.
|
|
41
|
+
const payload = { ...event };
|
|
42
|
+
void Promise.resolve()
|
|
43
|
+
.then(() => handler(payload, context))
|
|
44
|
+
.catch((err) => {
|
|
45
|
+
try {
|
|
46
|
+
deps.log.error({ err, pluginId, event: event.event }, "plugin hook handler threw");
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// A plugin can influence the logged `err` value at this
|
|
50
|
+
// untrusted boundary (its own thrown error) — if that value
|
|
51
|
+
// makes the logger itself throw (e.g. a hostile toJSON/
|
|
52
|
+
// getter), swallow it rather than leave an unhandled
|
|
53
|
+
// rejection from this .catch.
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
try {
|
|
59
|
+
deps.log.error({ err, pluginId, event: event.event }, "plugin hook dispatch failed");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Same untrusted-boundary swallow as above, for the synchronous
|
|
63
|
+
// path.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PrismaClient } from "@prisma/client";
|
|
2
|
+
import type { DatabaseAdapter } from "@velora-cms/db-adapter";
|
|
3
|
+
import { type ManifestRowFields, type RegisterLocalPluginResult } from "../sandbox/register-local-plugin.js";
|
|
4
|
+
import { PluginRegistryService } from "./plugin-registry-service.js";
|
|
5
|
+
import type { TrustTier } from "./verify-package.js";
|
|
6
|
+
/** Resolves the server-importable manifest entry a `velora-plugin
|
|
7
|
+
* build` package always produces at this fixed path. */
|
|
8
|
+
export declare function resolveBuiltManifestPath(packageDir: string): string;
|
|
9
|
+
export declare function installBuiltPackage(service: PluginRegistryService, packageDir: string, remoteUrl: string, setRemoteUrl: (pluginId: string, remoteUrl: string) => Promise<void>, syncManifestFields: (pluginId: string, fields: ManifestRowFields) => Promise<void>, setPackagePath?: (pluginId: string, packagePath: string) => Promise<void>, opts?: {
|
|
10
|
+
trustTier?: TrustTier;
|
|
11
|
+
seed?: {
|
|
12
|
+
prisma: PrismaClient;
|
|
13
|
+
adapter: DatabaseAdapter;
|
|
14
|
+
createdBy?: string;
|
|
15
|
+
};
|
|
16
|
+
}): Promise<RegisterLocalPluginResult>;
|
|
17
|
+
export interface InstallBuiltPackageCliArgs {
|
|
18
|
+
target: string;
|
|
19
|
+
remoteUrl: string;
|
|
20
|
+
sidecarPath?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Validates `main()`'s argv shape only — NOT whether "" is the right
|
|
23
|
+
* value for this particular plugin. A view-less plugin type (theme/
|
|
24
|
+
* template/bundle) has no remoteEntry.js at all — install-from-
|
|
25
|
+
* marketplace.ts computes remoteUrl as `""` for exactly that case ("Empty
|
|
26
|
+
* string = the loader's existing 'nothing to load' contract"), so an
|
|
27
|
+
* explicitly-passed empty string here is a legitimate value, not a missing
|
|
28
|
+
* argument, and this parser accepts it unconditionally. Only an actually
|
|
29
|
+
* omitted argv slot (undefined — the caller never supplied a second
|
|
30
|
+
* positional at all) is a usage error; `target` has no such legitimate-
|
|
31
|
+
* empty case, so it keeps the plain falsy check. This parser runs before
|
|
32
|
+
* any manifest is read, so it CANNOT tell a view-less "" apart from a
|
|
33
|
+
* view-bearing type's "" typo'd in by mistake — that type-aware check
|
|
34
|
+
* happens later, inside installBuiltPackage, once the manifest's real
|
|
35
|
+
* type is known. */
|
|
36
|
+
export declare function parseInstallBuiltPackageCliArgs(argv: string[]): InstallBuiltPackageCliArgs;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { hasClientBundle } from "@velora-cms/plugin-sdk/manifest";
|
|
5
|
+
import { registerLocalPlugin } from "../sandbox/register-local-plugin.js";
|
|
6
|
+
import { storePackage } from "./plugin-package-store.js";
|
|
7
|
+
import { PluginRegistryService } from "./plugin-registry-service.js";
|
|
8
|
+
import { seedTemplateSampleContent } from "./seed-template-content.js";
|
|
9
|
+
// The counterpart to ../sandbox/register-local-plugin.ts: that one loads
|
|
10
|
+
// a manifest from LIVE SOURCE (src/plugin.ts(x)) for velora-dev's
|
|
11
|
+
// same-filesystem dev sandbox; this one loads from a BUILT, distributable
|
|
12
|
+
// package's server/plugin.js (produced by `velora-plugin build`,
|
|
13
|
+
// Session 81) — proving a plugin installs from a package with no source
|
|
14
|
+
// tree alongside it at all. Both funnel into the same, unmodified
|
|
15
|
+
// registerLocalPlugin() — the manifest's origin doesn't matter to it.
|
|
16
|
+
//
|
|
17
|
+
// Since Session 121, server/plugin.js is a self-contained Vite SSR bundle
|
|
18
|
+
// (react, react/jsx-runtime, and @velora-cms/plugin-sdk all inlined; zero bare
|
|
19
|
+
// import specifiers) rather than a tsc-emitted file with live import
|
|
20
|
+
// specifiers — which is why no node_modules needs to exist anywhere near
|
|
21
|
+
// the extraction point for this to resolve.
|
|
22
|
+
//
|
|
23
|
+
// Since Session 122, install COPIES the package into the managed plugin
|
|
24
|
+
// store (plugin-package-store.ts) before importing anything, and imports
|
|
25
|
+
// from that STORED copy rather than the caller's (often tmpdir) extraction
|
|
26
|
+
// directory — the same copy a later boot loader will import on restart.
|
|
27
|
+
/** Resolves the server-importable manifest entry a `velora-plugin
|
|
28
|
+
* build` package always produces at this fixed path. */
|
|
29
|
+
export function resolveBuiltManifestPath(packageDir) {
|
|
30
|
+
const entryPath = join(packageDir, "server", "plugin.js");
|
|
31
|
+
if (!existsSync(entryPath)) {
|
|
32
|
+
throw new Error(`No built manifest found at ${packageDir} — expected server/plugin.js`);
|
|
33
|
+
}
|
|
34
|
+
return entryPath;
|
|
35
|
+
}
|
|
36
|
+
export async function installBuiltPackage(service, packageDir, remoteUrl, setRemoteUrl, syncManifestFields, setPackagePath, opts) {
|
|
37
|
+
resolveBuiltManifestPath(packageDir); // fail fast on a non-package before any copying
|
|
38
|
+
// manifest.json (written by `velora-plugin build`) supplies id+version
|
|
39
|
+
// WITHOUT importing code — the store path must be known before the
|
|
40
|
+
// import so the import runs against the STORED copy (what the boot
|
|
41
|
+
// loader will import later is byte-identical to what installs now).
|
|
42
|
+
const manifestJsonPath = join(packageDir, "manifest.json");
|
|
43
|
+
if (!existsSync(manifestJsonPath)) {
|
|
44
|
+
throw new Error(`No manifest.json found in ${packageDir} — not a velora-plugin built package`);
|
|
45
|
+
}
|
|
46
|
+
const summary = JSON.parse(readFileSync(manifestJsonPath, "utf8"));
|
|
47
|
+
if (!summary.id || !summary.version) {
|
|
48
|
+
throw new Error(`${manifestJsonPath} is missing id/version`);
|
|
49
|
+
}
|
|
50
|
+
// Path-safety gate: these two values become directory names BEFORE the
|
|
51
|
+
// manifest's own validation runs (validatePluginManifest fires later,
|
|
52
|
+
// inside registerLocalPlugin). Mirror the SDK manifest schema's id rule
|
|
53
|
+
// and a path-safe version shape so a hostile manifest.json can't
|
|
54
|
+
// traverse out of the store root.
|
|
55
|
+
if (!/^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(summary.id)) {
|
|
56
|
+
throw new Error(`manifest.json id "${summary.id}" is not a valid reverse-domain plugin id`);
|
|
57
|
+
}
|
|
58
|
+
// Must start and end with an alphanumeric — otherwise a version of "."
|
|
59
|
+
// or ".." (both match the bare character-class check on their own)
|
|
60
|
+
// resolves as a filesystem self-reference/parent-reference segment once
|
|
61
|
+
// joined into a path, not a literal directory name.
|
|
62
|
+
if (!/^[0-9a-z](?:[0-9a-z.+-]*[0-9a-z])?$/i.test(summary.version) ||
|
|
63
|
+
summary.version.includes("..")) {
|
|
64
|
+
throw new Error(`manifest.json version "${summary.version}" is not a path-safe version string`);
|
|
65
|
+
}
|
|
66
|
+
const storedDir = storePackage(packageDir, summary.id, summary.version);
|
|
67
|
+
const entryPath = resolveBuiltManifestPath(storedDir);
|
|
68
|
+
const mod = (await import(pathToFileURL(entryPath).href));
|
|
69
|
+
if (!mod.default) {
|
|
70
|
+
throw new Error(`${entryPath} has no default export`);
|
|
71
|
+
}
|
|
72
|
+
const manifestId = mod.default.id;
|
|
73
|
+
if (manifestId !== summary.id) {
|
|
74
|
+
throw new Error(`manifest.json id "${summary.id}" does not match server/plugin.js id "${manifestId}"`);
|
|
75
|
+
}
|
|
76
|
+
const manifestVersion = mod.default.version;
|
|
77
|
+
if (manifestVersion !== summary.version) {
|
|
78
|
+
throw new Error(`manifest.json version "${summary.version}" does not match server/plugin.js version "${manifestVersion}"`);
|
|
79
|
+
}
|
|
80
|
+
// SEC-2 (S172 audit): the declared type in manifest.json — the value the
|
|
81
|
+
// marketplace catalogs as `listing.type` and the human reviewer approves
|
|
82
|
+
// against — must equal the plugin's ACTUAL runtime type in server/plugin.js.
|
|
83
|
+
// The marketplace never inspects the (opaque, minified) server bundle, so a
|
|
84
|
+
// package could be listed/reviewed as a benign `datatype`/`theme` while its
|
|
85
|
+
// code runs as a `section` with public apiRoutes/hooks/scheduledTasks.
|
|
86
|
+
// Enforcing declared === runtime here makes any such spoof uninstallable
|
|
87
|
+
// everywhere, so the runtime type a plugin ships can never diverge from the
|
|
88
|
+
// type it was reviewed and listed as. (When manifest.json carries no type —
|
|
89
|
+
// an older sideload shape — there is nothing to contradict, so skip.)
|
|
90
|
+
const manifestType = mod.default.type;
|
|
91
|
+
if (summary.type && manifestType && summary.type !== manifestType) {
|
|
92
|
+
throw new Error(`manifest.json type "${summary.type}" does not match server/plugin.js type "${manifestType}" — the listed type must match the plugin's actual runtime type`);
|
|
93
|
+
}
|
|
94
|
+
// Type-aware remoteUrl gate. parseInstallBuiltPackageCliArgs (below)
|
|
95
|
+
// only enforces that the argv slot was supplied at all — it runs before
|
|
96
|
+
// any manifest is read, so it has no way to know the plugin's type and
|
|
97
|
+
// deliberately accepts an explicit "" unconditionally. install-from-
|
|
98
|
+
// marketplace.ts's own remoteUrl computation is the authority on when
|
|
99
|
+
// "" is actually correct: only for a view-less type (theme/template/
|
|
100
|
+
// bundle — see hasClientBundle), which ships no remoteEntry.js at all.
|
|
101
|
+
// A view-BEARING type (datatype/section/integration/utility) installed
|
|
102
|
+
// with an empty remoteUrl would register successfully but silently skip
|
|
103
|
+
// loading its views forever — boot-remote-plugins.ts's `if
|
|
104
|
+
// (!plugin.remoteUrl) continue` treats "" exactly like "nothing to
|
|
105
|
+
// load," with no error anywhere. Catch that here, now that the
|
|
106
|
+
// manifest's real type is known, rather than letting it install broken.
|
|
107
|
+
if (remoteUrl === "" && manifestType && hasClientBundle(manifestType)) {
|
|
108
|
+
throw new Error(`"${summary.id}" is type "${manifestType}", which ships a client bundle (remoteEntry.js) — an empty remoteUrl is only valid for view-less plugin types (theme/template/bundle). Pass the plugin's actual remoteUrl.`);
|
|
109
|
+
}
|
|
110
|
+
const outcome = await registerLocalPlugin(service, mod.default, remoteUrl, setRemoteUrl, syncManifestFields, opts);
|
|
111
|
+
await setPackagePath?.(summary.id, storedDir);
|
|
112
|
+
// D1: invoked AFTER registerLocalPlugin returns, never from inside
|
|
113
|
+
// PluginRegistryService.install() — the S88 documentTypes path stays
|
|
114
|
+
// untouched. Only a genuinely fresh install seeds — "activated"/
|
|
115
|
+
// "already-active" mean this call didn't create anything new, so
|
|
116
|
+
// there's nothing to seed against (and, for the already-active case,
|
|
117
|
+
// any real sample content would already exist anyway). storedDir is
|
|
118
|
+
// exactly the STORED package dir seedTemplateSampleContent reads
|
|
119
|
+
// sample-content.json from — the same copy the boot loader will import
|
|
120
|
+
// on restart. Wrapped in its own try/catch as a belt-and-braces guard
|
|
121
|
+
// on top of the seeder's own internal non-fatal handling — D2 requires
|
|
122
|
+
// this install to stand no matter what happens here.
|
|
123
|
+
if (outcome === "installed" && opts?.seed) {
|
|
124
|
+
try {
|
|
125
|
+
const seedResult = await seedTemplateSampleContent({ prisma: opts.seed.prisma, adapter: opts.seed.adapter }, summary.id, storedDir, opts.seed.createdBy);
|
|
126
|
+
if (typeof seedResult === "object") {
|
|
127
|
+
console.log(`[install-built-package] seeded ${seedResult.created} sample content node(s) for "${summary.id}"`);
|
|
128
|
+
}
|
|
129
|
+
else if (seedResult !== "none") {
|
|
130
|
+
console.log(`[install-built-package] sample content seeding for "${summary.id}": ${seedResult}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error(`[install-built-package] sample content seeding for "${summary.id}" failed — the install stands`, error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return outcome;
|
|
138
|
+
}
|
|
139
|
+
/** Validates `main()`'s argv shape only — NOT whether "" is the right
|
|
140
|
+
* value for this particular plugin. A view-less plugin type (theme/
|
|
141
|
+
* template/bundle) has no remoteEntry.js at all — install-from-
|
|
142
|
+
* marketplace.ts computes remoteUrl as `""` for exactly that case ("Empty
|
|
143
|
+
* string = the loader's existing 'nothing to load' contract"), so an
|
|
144
|
+
* explicitly-passed empty string here is a legitimate value, not a missing
|
|
145
|
+
* argument, and this parser accepts it unconditionally. Only an actually
|
|
146
|
+
* omitted argv slot (undefined — the caller never supplied a second
|
|
147
|
+
* positional at all) is a usage error; `target` has no such legitimate-
|
|
148
|
+
* empty case, so it keeps the plain falsy check. This parser runs before
|
|
149
|
+
* any manifest is read, so it CANNOT tell a view-less "" apart from a
|
|
150
|
+
* view-bearing type's "" typo'd in by mistake — that type-aware check
|
|
151
|
+
* happens later, inside installBuiltPackage, once the manifest's real
|
|
152
|
+
* type is known. */
|
|
153
|
+
export function parseInstallBuiltPackageCliArgs(argv) {
|
|
154
|
+
const [target, remoteUrl, sidecarPath] = argv;
|
|
155
|
+
if (!target || remoteUrl === undefined) {
|
|
156
|
+
throw new Error("Usage: tsx install-built-package.ts <packageDir|package.tar.gz> <remoteUrl> [sidecarPath]");
|
|
157
|
+
}
|
|
158
|
+
return { target, remoteUrl, sidecarPath };
|
|
159
|
+
}
|
|
160
|
+
async function main() {
|
|
161
|
+
const { target, remoteUrl, sidecarPath: sidecarFlag } = parseInstallBuiltPackageCliArgs(process.argv.slice(2));
|
|
162
|
+
// Since Session 122, the CLI connects through connectContentDb() rather
|
|
163
|
+
// than hardcoding a PostgreSQLAdapter — this makes the sqlite sandbox a
|
|
164
|
+
// first-class CLI target (Session 121's live proof needed a throwaway
|
|
165
|
+
// driver precisely because of the old hardcoded adapter).
|
|
166
|
+
const { prisma } = await import("../db/prisma.js");
|
|
167
|
+
const { connectContentDb } = await import("../db/content-db.js");
|
|
168
|
+
const contentDb = await connectContentDb();
|
|
169
|
+
const activeDialect = process.env.VELORA_PLUGIN_ACTIVE_DIALECT ?? contentDb.dialect;
|
|
170
|
+
const service = new PluginRegistryService(prisma, contentDb.adapter, { activeDialect });
|
|
171
|
+
const setRemoteUrl = async (pluginId, url) => {
|
|
172
|
+
await prisma.pluginRegistry.update({ where: { pluginId }, data: { remoteUrl: url } });
|
|
173
|
+
};
|
|
174
|
+
const syncManifestFields = async (pluginId, fields) => {
|
|
175
|
+
await prisma.pluginRegistry.update({ where: { pluginId }, data: fields });
|
|
176
|
+
};
|
|
177
|
+
const setPackagePath = async (pluginId, packagePath) => {
|
|
178
|
+
await prisma.pluginRegistry.update({ where: { pluginId }, data: { packagePath } });
|
|
179
|
+
};
|
|
180
|
+
// Session 127: a .tar.gz takes the verifying path; a directory takes the
|
|
181
|
+
// original one, unchanged — velora-dev and in-monorepo development have
|
|
182
|
+
// no tarball.
|
|
183
|
+
if (target.endsWith(".tar.gz")) {
|
|
184
|
+
const { installPackageTarball } = await import("./install-package-tarball.js");
|
|
185
|
+
const { outcome, verification } = await installPackageTarball(service, target, remoteUrl, setRemoteUrl, syncManifestFields, setPackagePath, sidecarFlag ? { sidecarPath: sidecarFlag } : {});
|
|
186
|
+
console.log(`[install-built-package] ${outcome} (${verification.status})`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const outcome = await installBuiltPackage(service, target, remoteUrl, setRemoteUrl, syncManifestFields, setPackagePath);
|
|
190
|
+
console.log(`[install-built-package] ${outcome}`);
|
|
191
|
+
}
|
|
192
|
+
await prisma.$disconnect();
|
|
193
|
+
}
|
|
194
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
195
|
+
main().catch((error) => {
|
|
196
|
+
console.error("[install-built-package] failed:", error);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PrismaClient } from "@prisma/client";
|
|
2
|
+
import type { TrustedKey, VerifyResult } from "@velora-cms/plugin-package";
|
|
3
|
+
import type { DatabaseAdapter } from "@velora-cms/db-adapter";
|
|
4
|
+
import type { ManifestRowFields, RegisterLocalPluginResult } from "../sandbox/register-local-plugin.js";
|
|
5
|
+
import type { PluginRegistryService } from "./plugin-registry-service.js";
|
|
6
|
+
export interface InstallPackageTarballOptions {
|
|
7
|
+
/** Defaults to `<tarballPath>.sig` when present; pass null to force the
|
|
8
|
+
* unsigned path. */
|
|
9
|
+
sidecarPath?: string | null;
|
|
10
|
+
/** Defaults to loadTrustedKeys(). */
|
|
11
|
+
trustedKeys?: TrustedKey[];
|
|
12
|
+
/** Forwarded verbatim to installBuiltPackage (Session 151): when
|
|
13
|
+
* supplied, a template plugin's sample-content.json is seeded after a
|
|
14
|
+
* fresh install — see installBuiltPackage's own opts.seed doc. Purely
|
|
15
|
+
* additive: every pre-existing caller omits it and behaves unchanged. */
|
|
16
|
+
seed?: {
|
|
17
|
+
prisma: PrismaClient;
|
|
18
|
+
adapter: DatabaseAdapter;
|
|
19
|
+
createdBy?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare function installPackageTarball(service: PluginRegistryService, tarballPath: string, remoteUrl: string, setRemoteUrl: (pluginId: string, remoteUrl: string) => Promise<void>, syncManifestFields: (pluginId: string, fields: ManifestRowFields) => Promise<void>, setPackagePath?: (pluginId: string, packagePath: string) => Promise<void>, options?: InstallPackageTarballOptions): Promise<{
|
|
23
|
+
outcome: RegisterLocalPluginResult;
|
|
24
|
+
verification: VerifyResult;
|
|
25
|
+
}>;
|