@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,113 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ApiErrorSchema, CreateUserRequestSchema, CreateUserResponseSchema, UpdateUserRoleRequestSchema, UpdateUserRoleResponseSchema, UserListQuerySchema, UserListResponseSchema, } from "@velora-cms/api-schemas";
|
|
3
|
+
import { prisma } from "../db/prisma.js";
|
|
4
|
+
import { hashPassword } from "../auth/tokens.js";
|
|
5
|
+
import { requireAdmin } from "../auth/require-auth.js";
|
|
6
|
+
const UserParamsSchema = z.object({ id: z.string().uuid() });
|
|
7
|
+
function toSummary(user) {
|
|
8
|
+
// Deliberately reconstructed field by field — passwordHash must never
|
|
9
|
+
// ride along by spread.
|
|
10
|
+
return {
|
|
11
|
+
id: user.id,
|
|
12
|
+
email: user.email,
|
|
13
|
+
name: user.name,
|
|
14
|
+
role: user.role,
|
|
15
|
+
createdAt: user.createdAt.toISOString(),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Every route here is admin-only — user management is itself the "one
|
|
19
|
+
// real thing" the editor role is locked out of (403, enforced
|
|
20
|
+
// server-side, not just hidden in the UI).
|
|
21
|
+
export const usersRoutes = async (app) => {
|
|
22
|
+
app.get("/api/users", {
|
|
23
|
+
onRequest: requireAdmin,
|
|
24
|
+
schema: {
|
|
25
|
+
querystring: UserListQuerySchema,
|
|
26
|
+
response: {
|
|
27
|
+
200: UserListResponseSchema,
|
|
28
|
+
401: ApiErrorSchema,
|
|
29
|
+
403: ApiErrorSchema,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
}, async (request) => {
|
|
33
|
+
const { cursor, limit } = request.query;
|
|
34
|
+
const rows = await prisma.user.findMany({
|
|
35
|
+
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
|
|
36
|
+
take: limit,
|
|
37
|
+
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
|
|
38
|
+
});
|
|
39
|
+
const nextCursor = rows.length === limit ? (rows[rows.length - 1]?.id ?? null) : null;
|
|
40
|
+
return { items: rows.map(toSummary), nextCursor };
|
|
41
|
+
});
|
|
42
|
+
app.post("/api/users", {
|
|
43
|
+
onRequest: requireAdmin,
|
|
44
|
+
schema: {
|
|
45
|
+
body: CreateUserRequestSchema,
|
|
46
|
+
response: {
|
|
47
|
+
201: CreateUserResponseSchema,
|
|
48
|
+
401: ApiErrorSchema,
|
|
49
|
+
403: ApiErrorSchema,
|
|
50
|
+
409: ApiErrorSchema,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
}, async (request, reply) => {
|
|
54
|
+
const { email, name, password, role } = request.body;
|
|
55
|
+
try {
|
|
56
|
+
const user = await prisma.user.create({
|
|
57
|
+
data: {
|
|
58
|
+
email,
|
|
59
|
+
name: name ?? null,
|
|
60
|
+
passwordHash: await hashPassword(password),
|
|
61
|
+
role,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return reply.code(201).send({ user: toSummary(user) });
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
// Duck-typed on `.code`, not `instanceof Prisma.PrismaClientKnownRequestError`:
|
|
68
|
+
// `prisma` (db/prisma.ts) is loaded dynamically per VELORA_PRISMA_CLIENT_DIR
|
|
69
|
+
// (Session 94's multi-dialect client generation), so the error thrown by that
|
|
70
|
+
// runtime's client is a *different* module instance than the statically
|
|
71
|
+
// imported `@prisma/client` here — `instanceof` across that boundary always
|
|
72
|
+
// returns false, silently turning every duplicate-email conflict into a 500.
|
|
73
|
+
// `code` is a stable string on the wire regardless of which client instance
|
|
74
|
+
// threw it, so it survives the cross-module-instance gap `instanceof` doesn't.
|
|
75
|
+
if (error &&
|
|
76
|
+
typeof error === "object" &&
|
|
77
|
+
"code" in error &&
|
|
78
|
+
error.code === "P2002") {
|
|
79
|
+
return reply.code(409).send({ message: `A user with email ${email} already exists` });
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
app.patch("/api/users/:id/role", {
|
|
85
|
+
onRequest: requireAdmin,
|
|
86
|
+
schema: {
|
|
87
|
+
params: UserParamsSchema,
|
|
88
|
+
body: UpdateUserRoleRequestSchema,
|
|
89
|
+
response: {
|
|
90
|
+
200: UpdateUserRoleResponseSchema,
|
|
91
|
+
400: ApiErrorSchema,
|
|
92
|
+
401: ApiErrorSchema,
|
|
93
|
+
403: ApiErrorSchema,
|
|
94
|
+
404: ApiErrorSchema,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}, async (request, reply) => {
|
|
98
|
+
// Changing your own role is refused outright — the cheap, robust
|
|
99
|
+
// guard against demoting the last admin and locking everyone out.
|
|
100
|
+
if (request.params.id === request.user.sub) {
|
|
101
|
+
return reply.code(400).send({ message: "You cannot change your own role" });
|
|
102
|
+
}
|
|
103
|
+
const existing = await prisma.user.findUnique({ where: { id: request.params.id } });
|
|
104
|
+
if (!existing) {
|
|
105
|
+
return reply.code(404).send({ message: `No user found for id ${request.params.id}` });
|
|
106
|
+
}
|
|
107
|
+
const user = await prisma.user.update({
|
|
108
|
+
where: { id: existing.id },
|
|
109
|
+
data: { role: request.body.role },
|
|
110
|
+
});
|
|
111
|
+
return { user: toSummary(user) };
|
|
112
|
+
});
|
|
113
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PluginRegistryService } from "../plugins/plugin-registry-service.js";
|
|
2
|
+
import type { TrustTier } from "../plugins/verify-package.js";
|
|
3
|
+
export type RegisterLocalPluginResult = "installed" | "activated" | "already-active";
|
|
4
|
+
/** The manifest entry point convention every plugin (scaffolded or
|
|
5
|
+
* hand-built) shares: a default-exported CMSPlugin in src/plugin.ts(x). */
|
|
6
|
+
export declare function resolveManifestPath(pluginDir: string): string;
|
|
7
|
+
/** The registry-row columns derived from the manifest (as opposed to
|
|
8
|
+
* `remoteUrl`, which comes from where velora-dev happened to boot the
|
|
9
|
+
* plugin) — what syncManifestFields writes when a re-registered manifest
|
|
10
|
+
* has changed since the row was created. */
|
|
11
|
+
export interface ManifestRowFields {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
version: string;
|
|
15
|
+
storageType: string;
|
|
16
|
+
/** Session 127: re-synced on re-registration for the same reason the
|
|
17
|
+
* others are — a re-install of a tampered package must DOWNGRADE a
|
|
18
|
+
* previously-verified row rather than leave a stale "verified" behind. */
|
|
19
|
+
trustTier: TrustTier;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Idempotent: install() throws on an existing row, so this checks first.
|
|
23
|
+
* A restart (fresh process, empty in-memory manifests map) still needs
|
|
24
|
+
* attachManifest() before activate() can run — install() only attaches
|
|
25
|
+
* automatically on the FIRST call in a given process's lifetime.
|
|
26
|
+
* remoteUrl is set unconditionally (even on a no-op activation) since a
|
|
27
|
+
* fixed sandbox port never changes, but keeps the row honest either way.
|
|
28
|
+
* On the existing-row path, name/type/version/storageType are re-synced
|
|
29
|
+
* from the freshly-read manifest whenever they differ from the row —
|
|
30
|
+
* this tool's whole premise is iterating on a local plugin between
|
|
31
|
+
* sandbox restarts, so a manifest edit (bumped version, renamed plugin,
|
|
32
|
+
* changed storage tier) must not go stale in the registry forever.
|
|
33
|
+
*/
|
|
34
|
+
export declare function registerLocalPlugin(service: PluginRegistryService, rawManifest: unknown, remoteUrl: string, setRemoteUrl: (pluginId: string, remoteUrl: string) => Promise<void>, syncManifestFields: (pluginId: string, fields: ManifestRowFields) => Promise<void>, opts?: {
|
|
35
|
+
trustTier?: TrustTier;
|
|
36
|
+
}): Promise<RegisterLocalPluginResult>;
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
import { PluginInstallError, PluginRegistryService } from "../plugins/plugin-registry-service.js";
|
|
6
|
+
/** The manifest entry point convention every plugin (scaffolded or
|
|
7
|
+
* hand-built) shares: a default-exported CMSPlugin in src/plugin.ts(x). */
|
|
8
|
+
export function resolveManifestPath(pluginDir) {
|
|
9
|
+
const tsxPath = join(pluginDir, "src", "plugin.tsx");
|
|
10
|
+
if (existsSync(tsxPath))
|
|
11
|
+
return tsxPath;
|
|
12
|
+
const tsPath = join(pluginDir, "src", "plugin.ts");
|
|
13
|
+
if (existsSync(tsPath))
|
|
14
|
+
return tsPath;
|
|
15
|
+
throw new Error(`No plugin manifest found at ${pluginDir} — expected src/plugin.tsx or src/plugin.ts`);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Idempotent: install() throws on an existing row, so this checks first.
|
|
19
|
+
* A restart (fresh process, empty in-memory manifests map) still needs
|
|
20
|
+
* attachManifest() before activate() can run — install() only attaches
|
|
21
|
+
* automatically on the FIRST call in a given process's lifetime.
|
|
22
|
+
* remoteUrl is set unconditionally (even on a no-op activation) since a
|
|
23
|
+
* fixed sandbox port never changes, but keeps the row honest either way.
|
|
24
|
+
* On the existing-row path, name/type/version/storageType are re-synced
|
|
25
|
+
* from the freshly-read manifest whenever they differ from the row —
|
|
26
|
+
* this tool's whole premise is iterating on a local plugin between
|
|
27
|
+
* sandbox restarts, so a manifest edit (bumped version, renamed plugin,
|
|
28
|
+
* changed storage tier) must not go stale in the registry forever.
|
|
29
|
+
*/
|
|
30
|
+
export async function registerLocalPlugin(service, rawManifest, remoteUrl, setRemoteUrl, syncManifestFields, opts) {
|
|
31
|
+
const result = validatePluginManifest(rawManifest);
|
|
32
|
+
if (!result.ok) {
|
|
33
|
+
// PluginInstallError (not a plain Error), matching
|
|
34
|
+
// PluginRegistryService.install()'s own identical check verbatim —
|
|
35
|
+
// this call always runs first for the built-package install path
|
|
36
|
+
// (installBuiltPackage -> registerLocalPlugin -> service.install()),
|
|
37
|
+
// so a real manifest-validation failure is thrown HERE, not inside
|
|
38
|
+
// install(). Marketplace-install.ts's typed-error mapping
|
|
39
|
+
// (`instanceof PluginInstallError`) needs this to be the same class
|
|
40
|
+
// install() throws, or a marketplace install with a genuinely invalid
|
|
41
|
+
// manifest would fall through to an unmapped 500 instead of the
|
|
42
|
+
// intended 422.
|
|
43
|
+
throw new PluginInstallError(`Plugin manifest failed validation:\n${result.errors.map((e) => ` - ${e}`).join("\n")}`, result.errors);
|
|
44
|
+
}
|
|
45
|
+
const manifest = result.manifest;
|
|
46
|
+
const trustTier = opts?.trustTier ?? "unverified";
|
|
47
|
+
const existing = await service.getInstalledPlugin(manifest.id);
|
|
48
|
+
let outcome;
|
|
49
|
+
if (!existing) {
|
|
50
|
+
await service.install(manifest, { trustTier });
|
|
51
|
+
outcome = "installed";
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
await service.attachManifest(manifest);
|
|
55
|
+
outcome = existing.enabled ? "already-active" : "activated";
|
|
56
|
+
const fresh = {
|
|
57
|
+
name: manifest.name,
|
|
58
|
+
type: manifest.type,
|
|
59
|
+
version: manifest.version,
|
|
60
|
+
storageType: manifest.databaseCompatibility.storageType,
|
|
61
|
+
trustTier,
|
|
62
|
+
};
|
|
63
|
+
if (existing.name !== fresh.name ||
|
|
64
|
+
existing.type !== fresh.type ||
|
|
65
|
+
existing.version !== fresh.version ||
|
|
66
|
+
existing.storageType !== fresh.storageType ||
|
|
67
|
+
existing.trustTier !== fresh.trustTier) {
|
|
68
|
+
await syncManifestFields(manifest.id, fresh);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await service.activate(manifest.id);
|
|
72
|
+
await setRemoteUrl(manifest.id, remoteUrl);
|
|
73
|
+
return outcome;
|
|
74
|
+
}
|
|
75
|
+
// ---- CLI entry (spawned by velora-dev) ------------------------------------
|
|
76
|
+
async function main() {
|
|
77
|
+
const [pluginDir, remoteUrl] = process.argv.slice(2);
|
|
78
|
+
if (!pluginDir || !remoteUrl) {
|
|
79
|
+
throw new Error("Usage: tsx register-local-plugin.ts <pluginDir> <remoteUrl>");
|
|
80
|
+
}
|
|
81
|
+
const manifestPath = resolveManifestPath(pluginDir);
|
|
82
|
+
const mod = await import(pathToFileURL(manifestPath).href);
|
|
83
|
+
if (!mod.default) {
|
|
84
|
+
throw new Error(`${manifestPath} has no default export`);
|
|
85
|
+
}
|
|
86
|
+
// Since Session 122, the CLI connects through connectContentDb() rather
|
|
87
|
+
// than hardcoding a PostgreSQLAdapter — this makes the sqlite sandbox a
|
|
88
|
+
// first-class CLI target (Session 121's live proof needed a throwaway
|
|
89
|
+
// driver precisely because of the old hardcoded adapter).
|
|
90
|
+
const { prisma } = await import("../db/prisma.js");
|
|
91
|
+
const { connectContentDb } = await import("../db/content-db.js");
|
|
92
|
+
const contentDb = await connectContentDb();
|
|
93
|
+
const activeDialect = process.env.VELORA_PLUGIN_ACTIVE_DIALECT ?? contentDb.dialect;
|
|
94
|
+
const service = new PluginRegistryService(prisma, contentDb.adapter, { activeDialect });
|
|
95
|
+
const outcome = await registerLocalPlugin(service, mod.default, remoteUrl, async (pluginId, url) => {
|
|
96
|
+
await prisma.pluginRegistry.update({ where: { pluginId }, data: { remoteUrl: url } });
|
|
97
|
+
}, async (pluginId, fields) => {
|
|
98
|
+
await prisma.pluginRegistry.update({ where: { pluginId }, data: fields });
|
|
99
|
+
});
|
|
100
|
+
console.log(`[velora-dev register] ${mod.default.id}: ${outcome}`);
|
|
101
|
+
await prisma.$disconnect();
|
|
102
|
+
}
|
|
103
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
104
|
+
main().catch((error) => {
|
|
105
|
+
console.error("[velora-dev register] failed:", error);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ContentDb } from "../db/content-db.js";
|
|
2
|
+
export declare const PAGE_DOCUMENT_TYPE_ID = "d0c00000-0000-4000-8000-000000000001";
|
|
3
|
+
export declare const ARTICLE_DOCUMENT_TYPE_ID = "d0c00000-0000-4000-8000-000000000002";
|
|
4
|
+
export interface SeedUserStore {
|
|
5
|
+
findUnique(args: {
|
|
6
|
+
where: {
|
|
7
|
+
email: string;
|
|
8
|
+
};
|
|
9
|
+
}): Promise<unknown | null>;
|
|
10
|
+
create(args: {
|
|
11
|
+
data: {
|
|
12
|
+
email: string;
|
|
13
|
+
passwordHash: string;
|
|
14
|
+
name: string;
|
|
15
|
+
role: "admin";
|
|
16
|
+
};
|
|
17
|
+
}): Promise<unknown>;
|
|
18
|
+
}
|
|
19
|
+
export declare function seedAdminUser(users: SeedUserStore, email: string, password: string): Promise<"created" | "exists">;
|
|
20
|
+
export declare function seedContent(contentDb: ContentDb, createdBy: string): Promise<"created" | "exists">;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
import { hashPassword } from "../auth/tokens.js";
|
|
3
|
+
import { connectContentDb } from "../db/content-db.js";
|
|
4
|
+
// Sandbox seed (velora-dev Session 78): an admin user, two document
|
|
5
|
+
// types on the stable built-in Data Type config ids that migration 0006
|
|
6
|
+
// seeds, and a small published tree. Idempotent — each half checks its
|
|
7
|
+
// own marker and skips. Spawned by velora-dev via
|
|
8
|
+
// `pnpm --filter @velora-cms/server exec tsx src/sandbox/seed-sandbox.ts`
|
|
9
|
+
// with the sandbox env already set.
|
|
10
|
+
export const PAGE_DOCUMENT_TYPE_ID = "d0c00000-0000-4000-8000-000000000001";
|
|
11
|
+
export const ARTICLE_DOCUMENT_TYPE_ID = "d0c00000-0000-4000-8000-000000000002";
|
|
12
|
+
// Built-in Data Type configs (BUILT_IN_DATA_TYPE_CONFIGS in
|
|
13
|
+
// @velora-cms/api-schemas — seeded by content migration 0006, fixed ids).
|
|
14
|
+
const TEXT_CONFIG_ID = "b1e00000-0000-4000-8000-000000000001";
|
|
15
|
+
const RICHTEXT_CONFIG_ID = "b1e00000-0000-4000-8000-000000000006";
|
|
16
|
+
export async function seedAdminUser(users, email, password) {
|
|
17
|
+
if (await users.findUnique({ where: { email } })) {
|
|
18
|
+
return "exists";
|
|
19
|
+
}
|
|
20
|
+
await users.create({
|
|
21
|
+
data: { email, passwordHash: await hashPassword(password), name: "Sandbox Admin", role: "admin" },
|
|
22
|
+
});
|
|
23
|
+
return "created";
|
|
24
|
+
}
|
|
25
|
+
const PAGE_TYPE = {
|
|
26
|
+
id: PAGE_DOCUMENT_TYPE_ID,
|
|
27
|
+
name: "Page",
|
|
28
|
+
icon: "file-text",
|
|
29
|
+
kind: "page",
|
|
30
|
+
tabs: [],
|
|
31
|
+
fields: [
|
|
32
|
+
{ id: "title", name: "Title", datatypeId: TEXT_CONFIG_ID },
|
|
33
|
+
{ id: "body", name: "Body", datatypeId: RICHTEXT_CONFIG_ID },
|
|
34
|
+
],
|
|
35
|
+
treeRules: { allowedAtRoot: true, allowedParentTypes: [], allowedChildTypes: [] },
|
|
36
|
+
};
|
|
37
|
+
const ARTICLE_TYPE = {
|
|
38
|
+
id: ARTICLE_DOCUMENT_TYPE_ID,
|
|
39
|
+
name: "Article",
|
|
40
|
+
icon: "newspaper",
|
|
41
|
+
kind: "page",
|
|
42
|
+
tabs: [],
|
|
43
|
+
fields: [
|
|
44
|
+
{ id: "title", name: "Title", datatypeId: TEXT_CONFIG_ID },
|
|
45
|
+
{ id: "body", name: "Body", datatypeId: RICHTEXT_CONFIG_ID },
|
|
46
|
+
],
|
|
47
|
+
treeRules: { allowedAtRoot: false, allowedParentTypes: [PAGE_DOCUMENT_TYPE_ID], allowedChildTypes: [] },
|
|
48
|
+
};
|
|
49
|
+
const ARTICLES = [
|
|
50
|
+
{ slug: "welcome-to-velora", title: "Welcome to Velora" },
|
|
51
|
+
{ slug: "building-your-first-plugin", title: "Building your first plugin" },
|
|
52
|
+
{ slug: "how-content-versioning-works", title: "How content versioning works" },
|
|
53
|
+
];
|
|
54
|
+
export async function seedContent(contentDb, createdBy) {
|
|
55
|
+
const { adapter, contentService } = contentDb;
|
|
56
|
+
if (await adapter.getDocumentType(PAGE_DOCUMENT_TYPE_ID)) {
|
|
57
|
+
return "exists";
|
|
58
|
+
}
|
|
59
|
+
await adapter.createDocumentType(PAGE_TYPE);
|
|
60
|
+
await adapter.createDocumentType(ARTICLE_TYPE);
|
|
61
|
+
const home = await contentService.createContent({
|
|
62
|
+
documentTypeId: PAGE_DOCUMENT_TYPE_ID,
|
|
63
|
+
parentId: null,
|
|
64
|
+
path: "/home",
|
|
65
|
+
depth: 0,
|
|
66
|
+
sortOrder: 0,
|
|
67
|
+
locale: "en",
|
|
68
|
+
isDefaultLocale: true,
|
|
69
|
+
data: {
|
|
70
|
+
title: "Home",
|
|
71
|
+
body: "<p>Welcome to your Velora sandbox. Everything here is sample content — edit freely.</p>",
|
|
72
|
+
},
|
|
73
|
+
createdBy,
|
|
74
|
+
});
|
|
75
|
+
await contentService.publishContent(home.node.id);
|
|
76
|
+
let sortOrder = 0;
|
|
77
|
+
for (const article of ARTICLES) {
|
|
78
|
+
const created = await contentService.createContent({
|
|
79
|
+
documentTypeId: ARTICLE_DOCUMENT_TYPE_ID,
|
|
80
|
+
parentId: home.node.id,
|
|
81
|
+
path: `/home/${article.slug}`,
|
|
82
|
+
depth: 1,
|
|
83
|
+
sortOrder: sortOrder++,
|
|
84
|
+
locale: "en",
|
|
85
|
+
isDefaultLocale: true,
|
|
86
|
+
data: { title: article.title, body: `<p>${article.title} — sample article body.</p>` },
|
|
87
|
+
createdBy,
|
|
88
|
+
});
|
|
89
|
+
await contentService.publishContent(created.node.id);
|
|
90
|
+
}
|
|
91
|
+
return "created";
|
|
92
|
+
}
|
|
93
|
+
// ---- CLI entry (spawned by velora-dev) ------------------------------------
|
|
94
|
+
async function main() {
|
|
95
|
+
const email = process.env.VELORA_SANDBOX_ADMIN_EMAIL ?? "admin@velora.dev";
|
|
96
|
+
const password = process.env.VELORA_SANDBOX_ADMIN_PASSWORD ?? "velora";
|
|
97
|
+
const { prisma } = await import("../db/prisma.js");
|
|
98
|
+
const userResult = await seedAdminUser(prisma.user, email, password);
|
|
99
|
+
// ContentVersion.createdBy is serialized back out through the API
|
|
100
|
+
// response schema as a strict UUID (packages/api-schemas) — a
|
|
101
|
+
// human-readable placeholder like "velora-dev-seed" writes fine at the
|
|
102
|
+
// storage layer but 500s every later GET of this content. Use the real
|
|
103
|
+
// admin user's id, looked up fresh so this works whether this run just
|
|
104
|
+
// created the user or it already existed.
|
|
105
|
+
const adminUser = await prisma.user.findUnique({ where: { email } });
|
|
106
|
+
if (!adminUser) {
|
|
107
|
+
throw new Error(`[velora-dev seed] admin user "${email}" not found after seedAdminUser`);
|
|
108
|
+
}
|
|
109
|
+
const contentDb = await connectContentDb();
|
|
110
|
+
const contentResult = await seedContent(contentDb, adminUser.id);
|
|
111
|
+
await contentDb.adapter.disconnect();
|
|
112
|
+
await prisma.$disconnect();
|
|
113
|
+
console.log(`[velora-dev seed] admin user: ${userResult}, content: ${contentResult}`);
|
|
114
|
+
}
|
|
115
|
+
// Only run main() when executed directly (tsx src/sandbox/seed-sandbox.ts),
|
|
116
|
+
// never when imported by the integration test.
|
|
117
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
118
|
+
main().catch((error) => {
|
|
119
|
+
console.error("[velora-dev seed] failed:", error);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cpSync, existsSync, rmSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
// Build-time only. The devDependency on @velora-cms/admin-ui exists purely
|
|
5
|
+
// so turbo's ^build ordering guarantees dist-bundle exists before this
|
|
6
|
+
// runs — server CODE never imports from the admin app. Copying into the
|
|
7
|
+
// server's own dist/ is the docs-bundle pattern (S130), now for the back
|
|
8
|
+
// office (S184): a deployed/packed server must serve the admin with no
|
|
9
|
+
// sibling checkout. Missing bundle = hard build error: an API-only server
|
|
10
|
+
// build stopped being a valid product in S184.
|
|
11
|
+
const here = dirname(fileURLToPath(import.meta.url)); // <server>/dist/scripts
|
|
12
|
+
const serverDist = join(here, "..");
|
|
13
|
+
const repoRoot = join(serverDist, "..", "..", "..");
|
|
14
|
+
const source = join(repoRoot, "apps", "admin-ui", "dist-bundle");
|
|
15
|
+
const target = join(serverDist, "admin-bundle");
|
|
16
|
+
if (!existsSync(source)) {
|
|
17
|
+
// Loud failure: under turbo ordering the bundle must exist; its absence
|
|
18
|
+
// means the admin build broke or ordering regressed — not a skippable case.
|
|
19
|
+
console.error(`[copy-admin-bundle] missing ${source} — did the @velora-cms/admin-ui build run?`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
rmSync(target, { recursive: true, force: true });
|
|
23
|
+
cpSync(source, target, { recursive: true });
|
|
24
|
+
console.log(`[copy-admin-bundle] copied admin bundle -> ${target}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cpSync, existsSync, rmSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
// Build-time only. The devDependency on @velora-cms/docs exists purely so
|
|
5
|
+
// turbo's ^build ordering guarantees dist-bundle exists before this runs —
|
|
6
|
+
// server CODE never imports from the docs app. Copying into the server's
|
|
7
|
+
// own dist/ is the S121 lesson (a production server deployed without the
|
|
8
|
+
// monorepo must carry its docs inside its own artifact).
|
|
9
|
+
const here = dirname(fileURLToPath(import.meta.url)); // <server>/dist/scripts
|
|
10
|
+
const serverDist = join(here, "..");
|
|
11
|
+
const repoRoot = join(serverDist, "..", "..", "..");
|
|
12
|
+
const source = join(repoRoot, "apps", "docs", "dist-bundle");
|
|
13
|
+
const target = join(serverDist, "docs-bundle");
|
|
14
|
+
if (!existsSync(source)) {
|
|
15
|
+
// Loud failure: under turbo ordering the bundle must exist; its absence
|
|
16
|
+
// means the docs build broke or ordering regressed — not a skippable case.
|
|
17
|
+
console.error(`[copy-docs-bundle] missing ${source} — did the @velora-cms/docs build run?`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
rmSync(target, { recursive: true, force: true });
|
|
21
|
+
cpSync(source, target, { recursive: true });
|
|
22
|
+
console.log(`[copy-docs-bundle] copied docs bundle -> ${target}`);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Creates a public API key and prints the plaintext ONCE — only the hash
|
|
2
|
+
// is stored, so a lost key means creating a new one. The interim key
|
|
3
|
+
// path for Session 101; Session 102's Settings › API Keys UI becomes the
|
|
4
|
+
// primary way, and this stays as an ops fallback.
|
|
5
|
+
//
|
|
6
|
+
// Usage (from repo root, postgres-dev running):
|
|
7
|
+
// pnpm --filter @velora-cms/server exec dotenv -e ../../.env -- tsx src/scripts/create-api-key.ts "key name" [--scope read|write]
|
|
8
|
+
//
|
|
9
|
+
// --scope defaults to "read" (Session 182: keys are read-only unless
|
|
10
|
+
// explicitly opted into write access).
|
|
11
|
+
import { pathToFileURL } from "node:url";
|
|
12
|
+
import { ApiKeyScopeSchema } from "@velora-cms/api-schemas";
|
|
13
|
+
import { generateApiKey, hashApiKey } from "../auth/api-keys.js";
|
|
14
|
+
import { prisma } from "../db/prisma.js";
|
|
15
|
+
const USAGE = 'Usage: tsx src/scripts/create-api-key.ts "<key name>" [--scope read|write]';
|
|
16
|
+
export function parseArgs(argv) {
|
|
17
|
+
const args = argv.slice(2);
|
|
18
|
+
const positional = [];
|
|
19
|
+
let scopeArg;
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
if (args[i] === "--scope") {
|
|
22
|
+
scopeArg = args[++i];
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
positional.push(args[i]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const name = positional[0]?.trim();
|
|
29
|
+
if (!name) {
|
|
30
|
+
throw new Error(USAGE);
|
|
31
|
+
}
|
|
32
|
+
const parsedScope = ApiKeyScopeSchema.safeParse(scopeArg ?? "read");
|
|
33
|
+
if (!parsedScope.success) {
|
|
34
|
+
throw new Error(`${USAGE}\n--scope must be "read" or "write", got "${scopeArg}"`);
|
|
35
|
+
}
|
|
36
|
+
return { name, scope: parsedScope.data };
|
|
37
|
+
}
|
|
38
|
+
async function main() {
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = parseArgs(process.argv);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const { name, scope } = parsed;
|
|
48
|
+
const key = generateApiKey();
|
|
49
|
+
await prisma.apiKey.create({ data: { name, keyHash: hashApiKey(key), scope } });
|
|
50
|
+
console.log(`API key "${name}" created with scope "${scope}". The key is shown once — store it now:`);
|
|
51
|
+
console.log(key);
|
|
52
|
+
await prisma.$disconnect();
|
|
53
|
+
}
|
|
54
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
55
|
+
await main();
|
|
56
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Dialect } from "@velora-cms/prisma-variants";
|
|
2
|
+
export declare function clientDirFor(dialect: Dialect): string;
|
|
3
|
+
export declare function variantPathFor(dialect: Dialect): string;
|
|
4
|
+
export declare function writeVariantSchema(dialect: Dialect): string;
|
|
5
|
+
export declare function generatePrismaClients(dialects?: readonly Dialect[]): Promise<void>;
|
|
6
|
+
export declare function generateDefaultPrismaClient(): Promise<void>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { toMysqlSchema, toPostgresqlSchema, toSqliteSchema } from "@velora-cms/prisma-variants";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
// Ships one pre-generated Prisma client per Tier 1 dialect alongside the
|
|
9
|
+
// built server (apps/server/dist/prisma-clients/<dialect>), so a deployed
|
|
10
|
+
// instance never needs to run `prisma generate` at runtime — Session 96-97's
|
|
11
|
+
// setup wizard just picks which already-built client to load via the
|
|
12
|
+
// existing VELORA_PRISMA_CLIENT_DIR mechanism (apps/server/src/db/prisma.ts).
|
|
13
|
+
//
|
|
14
|
+
// Compiled location is dist/scripts/generate-prisma-clients.js, two levels
|
|
15
|
+
// under the package root (dist/scripts -> dist -> apps/server).
|
|
16
|
+
const SERVER_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
17
|
+
const SCHEMA_PATH = join(SERVER_DIR, "prisma", "schema.prisma");
|
|
18
|
+
const TRANSFORMS = {
|
|
19
|
+
postgresql: toPostgresqlSchema,
|
|
20
|
+
mysql: toMysqlSchema,
|
|
21
|
+
sqlite: toSqliteSchema,
|
|
22
|
+
};
|
|
23
|
+
// prisma generate never connects to a database, but it does validate that
|
|
24
|
+
// DATABASE_URL's scheme matches the schema's own provider. A placeholder
|
|
25
|
+
// per dialect keeps this build step reproducible regardless of whatever
|
|
26
|
+
// real DATABASE_URL the machine's .env happens to point at.
|
|
27
|
+
const PLACEHOLDER_URLS = {
|
|
28
|
+
postgresql: "postgresql://placeholder:placeholder@localhost:5432/placeholder",
|
|
29
|
+
mysql: "mysql://placeholder:placeholder@localhost:3306/placeholder",
|
|
30
|
+
sqlite: "file:./placeholder.db",
|
|
31
|
+
};
|
|
32
|
+
export function clientDirFor(dialect) {
|
|
33
|
+
return join(SERVER_DIR, "dist", "prisma-clients", dialect);
|
|
34
|
+
}
|
|
35
|
+
export function variantPathFor(dialect) {
|
|
36
|
+
return join(SERVER_DIR, "prisma", `.prisma-variant-${dialect}.schema.prisma`);
|
|
37
|
+
}
|
|
38
|
+
const ALL_DIALECTS = ["postgresql", "mysql", "sqlite"];
|
|
39
|
+
export function writeVariantSchema(dialect) {
|
|
40
|
+
const source = readFileSync(SCHEMA_PATH, "utf8");
|
|
41
|
+
const variantPath = variantPathFor(dialect);
|
|
42
|
+
writeFileSync(variantPath, TRANSFORMS[dialect](source, clientDirFor(dialect)));
|
|
43
|
+
return variantPath;
|
|
44
|
+
}
|
|
45
|
+
// npx is safe here (unlike packages/velora-dev/src/sandbox.ts, which uses
|
|
46
|
+
// `pnpm --filter @velora-cms/server exec prisma` instead): cwd is already
|
|
47
|
+
// apps/server, where prisma is a direct dependency (a runtime dependency,
|
|
48
|
+
// not a devDependency — the built server loads @prisma/client at runtime),
|
|
49
|
+
// so there's no repo-root walk-up for npx to trigger an unwanted auto-install.
|
|
50
|
+
// Was spawnSync + stdio: "inherit". A vitest worker's beforeAll chains four
|
|
51
|
+
// of these calls back-to-back with no await between them (one per dialect
|
|
52
|
+
// plus the default client); spawnSync blocks the whole event loop for each
|
|
53
|
+
// call's full wall-clock duration, so the beforeAll became one unbroken
|
|
54
|
+
// ~45-60s synchronous stretch — long enough to blow past vitest's
|
|
55
|
+
// "onTaskUpdate" RPC timeout and crash the worker with exit 1 even though
|
|
56
|
+
// every test passed. execFile (promisified) does the same work without
|
|
57
|
+
// blocking the loop: shell: true preserves the exact command-line quoting
|
|
58
|
+
// spawnSync relied on (the schema path still needs its own quotes because
|
|
59
|
+
// the shell, not execFile, is what re-parses the assembled command line),
|
|
60
|
+
// npx still resolves the same devDependency the same way, and the same
|
|
61
|
+
// "(exit N)" error text is thrown on failure. Losing stdio: "inherit"'s
|
|
62
|
+
// live streaming is deliberate: execFile has no stdio passthrough option
|
|
63
|
+
// (it always captures stdout/stderr to build its callback result), so
|
|
64
|
+
// output is written out in one shot after the process exits instead of
|
|
65
|
+
// line-by-line — captured, not silenced.
|
|
66
|
+
async function runPrismaGenerate(schemaPath, databaseUrl, label) {
|
|
67
|
+
console.log(`[generate-prisma-clients] generating ${label}…`);
|
|
68
|
+
try {
|
|
69
|
+
const { stdout, stderr } = await execFileAsync("npx", ["prisma", "generate", "--schema", `"${schemaPath}"`], {
|
|
70
|
+
cwd: SERVER_DIR,
|
|
71
|
+
shell: true,
|
|
72
|
+
// execFile buffers output (spawnSync with stdio:"inherit" did not);
|
|
73
|
+
// the 1 MiB default KILLS the child when exceeded, so give prisma
|
|
74
|
+
// generous headroom — a verbose engine error must fail loudly, not
|
|
75
|
+
// as a misleading SIGTERM.
|
|
76
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
77
|
+
env: { ...process.env, DATABASE_URL: databaseUrl },
|
|
78
|
+
});
|
|
79
|
+
if (stdout)
|
|
80
|
+
process.stdout.write(stdout);
|
|
81
|
+
if (stderr)
|
|
82
|
+
process.stderr.write(stderr);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const failed = error;
|
|
86
|
+
if (failed.stdout)
|
|
87
|
+
process.stdout.write(failed.stdout);
|
|
88
|
+
if (failed.stderr)
|
|
89
|
+
process.stderr.write(failed.stderr);
|
|
90
|
+
throw new Error(`generate-prisma-clients: prisma generate failed for ${label} (exit ${failed.code})`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export async function generatePrismaClients(dialects = ALL_DIALECTS) {
|
|
94
|
+
for (const dialect of dialects) {
|
|
95
|
+
const variantPath = writeVariantSchema(dialect);
|
|
96
|
+
await runPrismaGenerate(variantPath, PLACEHOLDER_URLS[dialect], dialect);
|
|
97
|
+
}
|
|
98
|
+
await generateDefaultPrismaClient();
|
|
99
|
+
}
|
|
100
|
+
// The FOURTH generate, and the one that makes a fresh deploy target boot
|
|
101
|
+
// without a manual step.
|
|
102
|
+
//
|
|
103
|
+
// db/load-prisma-client.ts's static `import { PrismaClient } from
|
|
104
|
+
// "@prisma/client"` means that package must be generated even when
|
|
105
|
+
// VELORA_PRISMA_CLIENT_DIR points at one of the three dialect variants
|
|
106
|
+
// above and the default client is never constructed. Session 155's
|
|
107
|
+
// out-of-monorepo deploy needed `prisma generate` by hand before first
|
|
108
|
+
// boot for exactly this reason.
|
|
109
|
+
//
|
|
110
|
+
// Deliberately NOT output-pinned, though the month plan's wording said so:
|
|
111
|
+
// pinning it somewhere other than Prisma's default location would leave
|
|
112
|
+
// that static import unresolved, i.e. would not fix the thing this exists
|
|
113
|
+
// to fix.
|
|
114
|
+
export async function generateDefaultPrismaClient() {
|
|
115
|
+
await runPrismaGenerate(SCHEMA_PATH, PLACEHOLDER_URLS.postgresql, "the default client");
|
|
116
|
+
}
|
|
117
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
118
|
+
// void-with-catch instead of a top-level await: TLA would make every
|
|
119
|
+
// importer of this module async for the sake of a CLI-only guard. The
|
|
120
|
+
// exit code on failure stays non-zero.
|
|
121
|
+
void generatePrismaClients().catch((error) => {
|
|
122
|
+
console.error(error);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|