@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,581 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { extractArtifact } from "@velora-cms/plugin-package";
|
|
6
|
+
import { hasClientBundle } from "@velora-cms/plugin-sdk/manifest";
|
|
7
|
+
import { installPackageTarball } from "../plugins/install-package-tarball.js";
|
|
8
|
+
import { removePluginPackages } from "../plugins/plugin-package-store.js";
|
|
9
|
+
import { PluginDependencyError, PluginIncompatibleError, PluginInstallError, } from "../plugins/plugin-registry-service.js";
|
|
10
|
+
import { seedTemplateSampleContent } from "../plugins/seed-template-content.js";
|
|
11
|
+
import { checkPackageIdentity, resolveSidecarPath, trustTierFor, verifyPackage } from "../plugins/verify-package.js";
|
|
12
|
+
import { loadTrustedKeys } from "../plugins/trusted-keys.js";
|
|
13
|
+
import { getSiteDomain } from "../config/site.js";
|
|
14
|
+
import { pluginAssetsRemoteUrl } from "../routes/plugin-assets.js";
|
|
15
|
+
import { MarketplaceNotFoundError, downloadPackage, downloadSignature, fetchListing, validateLicenseUpstream } from "./marketplace-client.js";
|
|
16
|
+
import { installBundleMembers } from "./install-bundle-members.js";
|
|
17
|
+
/** Duck-typed on `.code`, not `instanceof Prisma.PrismaClientKnownRequestError`
|
|
18
|
+
* — same reasoning as routes/users.ts's own duplicate-email handling:
|
|
19
|
+
* `prisma` (db/prisma.ts) is loaded dynamically per VELORA_PRISMA_CLIENT_DIR
|
|
20
|
+
* (multi-dialect client generation), so the error thrown by that runtime's
|
|
21
|
+
* client is a *different* module instance than any statically imported
|
|
22
|
+
* `@prisma/client`, and `instanceof` across that boundary always returns
|
|
23
|
+
* false. `code` is a stable string on the wire regardless of which client
|
|
24
|
+
* instance threw it. */
|
|
25
|
+
function isUniqueConstraintViolation(error) {
|
|
26
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "P2002";
|
|
27
|
+
}
|
|
28
|
+
/** Reads manifest.json's declared id/version out of a downloaded tarball
|
|
29
|
+
* WITHOUT running any part of the real install — used only to bind what
|
|
30
|
+
* was actually downloaded to what the caller REQUESTED, before
|
|
31
|
+
* installPackageTarball (or even downloadSignature) ever runs. See the
|
|
32
|
+
* call site's comment for why this matters. Extracts into its own
|
|
33
|
+
* disposable subdirectory via the same, already-reviewed extractArtifact()
|
|
34
|
+
* the real install path uses internally — never a hand-rolled read of an
|
|
35
|
+
* untrusted archive. Throws on a genuinely malformed/corrupt archive or a
|
|
36
|
+
* missing/malformed manifest.json, exactly mirroring
|
|
37
|
+
* installPackageTarball's own extraction-failure behavior, so both land on
|
|
38
|
+
* the same catch-all + cleanup + rethrow path below. */
|
|
39
|
+
async function peekPackageIdentity(tarballPath, peekDir) {
|
|
40
|
+
const extracted = await extractArtifact(tarballPath, peekDir);
|
|
41
|
+
if (!extracted.ok) {
|
|
42
|
+
throw new Error(`Package could not be extracted: ${extracted.reason}`);
|
|
43
|
+
}
|
|
44
|
+
const manifestPath = join(peekDir, "manifest.json");
|
|
45
|
+
if (!existsSync(manifestPath)) {
|
|
46
|
+
throw new Error("No manifest.json found in the downloaded package — not a velora-plugin built package");
|
|
47
|
+
}
|
|
48
|
+
const summary = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
49
|
+
if (!summary.id || !summary.version) {
|
|
50
|
+
throw new Error("manifest.json in the downloaded package is missing id/version");
|
|
51
|
+
}
|
|
52
|
+
// S152: the member list rides the signed artifact's manifest.json
|
|
53
|
+
// (write-manifest-json.ts passes bare string entries through verbatim).
|
|
54
|
+
// Read here — after the caller's trust gate has verified the tarball —
|
|
55
|
+
// so "resolve members before installing anything" is possible at all.
|
|
56
|
+
const bundledPlugins = Array.isArray(summary.contributions?.bundledPlugins)
|
|
57
|
+
? summary.contributions.bundledPlugins.filter((entry) => typeof entry === "string")
|
|
58
|
+
: undefined;
|
|
59
|
+
return { id: summary.id, version: summary.version, type: summary.type, bundledPlugins };
|
|
60
|
+
}
|
|
61
|
+
/** The same "(reason) — message — nothing was installed" wording for both
|
|
62
|
+
* verification-failure sites below (the pre-install gate and its
|
|
63
|
+
* post-install belt-and-braces twin) — factored out so the two can never
|
|
64
|
+
* drift into different phrasing for what is, by construction, the same
|
|
65
|
+
* verdict. */
|
|
66
|
+
function describeVerificationFailure(pluginId, version, verification) {
|
|
67
|
+
const reason = verification.status === "failed" ? verification.reason : verification.status;
|
|
68
|
+
const detail = verification.status === "failed" ? ` — ${verification.message}` : "";
|
|
69
|
+
return `Signature verification failed for "${pluginId}"@${version} (${reason})${detail} — nothing was installed`;
|
|
70
|
+
}
|
|
71
|
+
/** Best-effort teardown for a marketplace install that must not leave
|
|
72
|
+
* partial state behind. Shared by the post-install trust-tier
|
|
73
|
+
* belt-and-braces check and the catch-all below (an UNEXPECTED throw —
|
|
74
|
+
* e.g. a plugin's own onActivate() blowing up mid-install;
|
|
75
|
+
* PluginRegistryService's own activate() doc comment anticipates hooks
|
|
76
|
+
* throwing).
|
|
77
|
+
*
|
|
78
|
+
* `outcome` is installPackageTarball's own RegisterLocalPluginResult
|
|
79
|
+
* (undefined when it never returned at all — install() threw before
|
|
80
|
+
* reaching that point). This is BOTH the H2 fix and the M4 fix from the
|
|
81
|
+
* S138 hardening review, because they turn out to be the same missing
|
|
82
|
+
* piece of information:
|
|
83
|
+
*
|
|
84
|
+
* - H2: an "installed" outcome means THIS call's own
|
|
85
|
+
* PluginRegistryService.install() created the row — migrations applied,
|
|
86
|
+
* document types created, onInstall run, and (per registerLocalPlugin)
|
|
87
|
+
* activate() run too. The real inverse of all of that is
|
|
88
|
+
* PluginRegistryService.uninstall(), not a hand-rolled partial teardown
|
|
89
|
+
* — the old version of this function only ever deleted the registry
|
|
90
|
+
* row, leaving PluginMigrationRecord/PluginDocumentType rows (and the
|
|
91
|
+
* document types themselves) behind, which permanently 500'd every
|
|
92
|
+
* later reinstall attempt via a P2002 inside install()'s own migration
|
|
93
|
+
* loop. uninstall() rolls all of that back in the correct LIFO order.
|
|
94
|
+
* confirmStructuredDataCleanup: true because this is an orphan teardown
|
|
95
|
+
* of a moments-old failed install, not a user-facing uninstall with
|
|
96
|
+
* real accumulated content to protect behind a confirmation prompt.
|
|
97
|
+
* - M4: an "activated" or "already-active" outcome means a CONCURRENT
|
|
98
|
+
* request's install() created the row instead — registerLocalPlugin's
|
|
99
|
+
* own tolerant merge path, taken when this call's registration attempt
|
|
100
|
+
* observed an already-existing row for a pluginId step 1's own
|
|
101
|
+
* findUnique had, moments earlier, confirmed did not exist yet. None of
|
|
102
|
+
* the registry row, the in-process manifest attachment, or the
|
|
103
|
+
* package-store copy belongs to THIS request in that case — tearing any
|
|
104
|
+
* of it down would delete the winner's legitimate install, not undo
|
|
105
|
+
* this request's own (nonexistent) one.
|
|
106
|
+
* - undefined means install() threw before ever reaching its own
|
|
107
|
+
* create() call (PluginIncompatibleError, PluginDependencyError, a
|
|
108
|
+
* manifest-validation PluginInstallError, or install()'s own internal
|
|
109
|
+
* migration/onInstall rollback already ran) — no row exists for this
|
|
110
|
+
* pluginId under any of those, from this request or a concurrent one,
|
|
111
|
+
* so the manifest-detach + package-store cleanup below is always safe.
|
|
112
|
+
*
|
|
113
|
+
* Each step gets its OWN try/catch and only warns/logs on failure — a
|
|
114
|
+
* cleanup failure must never mask whatever real error is already
|
|
115
|
+
* propagating (or, on the trust-tier path, prevent the 422 response from
|
|
116
|
+
* being sent). */
|
|
117
|
+
async function cleanupOrphanedInstall(deps, pluginId, outcome) {
|
|
118
|
+
if (outcome === "activated" || outcome === "already-active") {
|
|
119
|
+
console.warn(`[install-from-marketplace] skipping cleanup for "${pluginId}" — a concurrent install already owns its registry row (outcome: "${outcome}"); nothing here belongs to this request`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (outcome === "installed") {
|
|
123
|
+
try {
|
|
124
|
+
await deps.registry.uninstall(pluginId, { confirmStructuredDataCleanup: true });
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.error(`[install-from-marketplace] failed to fully uninstall "${pluginId}" during cleanup — it may be left partially registered and need manual removal`, error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
try {
|
|
132
|
+
deps.registry.detachManifest(pluginId);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.warn(`[install-from-marketplace] failed to detach the in-process manifest for "${pluginId}" during cleanup`, error);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
await deps.prisma.pluginRegistry.deleteMany({ where: { pluginId } });
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error(`[install-from-marketplace] failed to delete the registry row for "${pluginId}" during cleanup — it may be orphaned and need manual removal`, error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Windows file-lock reality (per this module's own test suite): a sync
|
|
145
|
+
// rmSync immediately after the just-imported server/plugin.js's dynamic
|
|
146
|
+
// import can race a held handle, so this always runs last and never
|
|
147
|
+
// blocks the DB-side cleanup above on its own success.
|
|
148
|
+
try {
|
|
149
|
+
removePluginPackages(pluginId);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.warn(`[install-from-marketplace] failed to remove the stored package for "${pluginId}" during cleanup`, error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export async function installFromMarketplace(deps, input) {
|
|
156
|
+
const { prisma, registry, marketplaceBaseUrl, adapter } = deps;
|
|
157
|
+
const { pluginId, version, licenseKey, installedBy } = input;
|
|
158
|
+
// Step 1: already installed — a plain DB lookup, no network at all, so
|
|
159
|
+
// this is always the cheapest possible rejection. This check has an
|
|
160
|
+
// inherent TOCTOU gap against a second, concurrent install of the same
|
|
161
|
+
// pluginId (closed below, at the actual write, not here — see the
|
|
162
|
+
// isUniqueConstraintViolation branch).
|
|
163
|
+
const existingRow = await prisma.pluginRegistry.findUnique({ where: { pluginId } });
|
|
164
|
+
if (existingRow) {
|
|
165
|
+
return { ok: false, failure: { code: "already-installed", message: `Plugin "${pluginId}" is already installed` } };
|
|
166
|
+
}
|
|
167
|
+
// Step 2: the listing (and the requested version within it) must exist
|
|
168
|
+
// upstream before anything else is checked or downloaded.
|
|
169
|
+
let listing;
|
|
170
|
+
try {
|
|
171
|
+
listing = (await fetchListing(marketplaceBaseUrl, pluginId));
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof MarketplaceNotFoundError) {
|
|
175
|
+
return { ok: false, failure: { code: "not-found", message: error.message } };
|
|
176
|
+
}
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
const matchedVersion = (listing.versions ?? []).some((v) => v.version === version);
|
|
180
|
+
if (!matchedVersion) {
|
|
181
|
+
return {
|
|
182
|
+
ok: false,
|
|
183
|
+
failure: { code: "not-found", message: `No published version "${version}" for plugin "${pluginId}"` },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Step 3: paid plugins gate on a validated license BEFORE a single
|
|
187
|
+
// package byte is downloaded. siteDomain is hoisted to function scope
|
|
188
|
+
// so step 6 (the license row) reuses the exact value validated here
|
|
189
|
+
// rather than re-reading the env a second time.
|
|
190
|
+
let siteDomain;
|
|
191
|
+
if (listing.priceCents > 0) {
|
|
192
|
+
if (!licenseKey) {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
failure: { code: "license-required", message: `Plugin "${pluginId}" is a paid plugin — a license key is required` },
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
siteDomain = getSiteDomain();
|
|
199
|
+
if (!siteDomain) {
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
failure: {
|
|
203
|
+
code: "site-domain-missing",
|
|
204
|
+
message: "VELORA_SITE_DOMAIN must be configured before installing a paid plugin — the license binds to this instance's declared domain on first validation, and installing without it would burn that one-shot binding on a meaningless value",
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const verdict = await validateLicenseUpstream(marketplaceBaseUrl, { key: licenseKey, pluginId, domain: siteDomain });
|
|
209
|
+
if (verdict.status !== "valid") {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
failure: {
|
|
213
|
+
code: "license-rejected",
|
|
214
|
+
message: `License rejected for "${pluginId}" (${verdict.status}): ${verdict.reason ?? "no reason given"}`,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Steps 4-5: download into a disposable temp directory and hand the
|
|
220
|
+
// tarball to the existing verify+install pipeline. Cleaned in the
|
|
221
|
+
// finally below regardless of outcome. Tracks which RegisterLocalPluginResult
|
|
222
|
+
// (if any) installPackageTarball reports, so cleanupOrphanedInstall knows
|
|
223
|
+
// whether THIS request created the row it might need to tear down (see
|
|
224
|
+
// that function's own doc comment — this is the H2/M4 fix).
|
|
225
|
+
const tempDir = await mkdtemp(join(tmpdir(), "velora-install-"));
|
|
226
|
+
let installOutcome;
|
|
227
|
+
try {
|
|
228
|
+
const tarballPath = join(tempDir, "package.tar.gz");
|
|
229
|
+
// M1: a MarketplaceNotFoundError here (the listing named a version
|
|
230
|
+
// whose artifact route is missing — a real upstream data-consistency
|
|
231
|
+
// failure, not a client bug) must map to the same 404 the listing
|
|
232
|
+
// fetch above already gets. Nothing has been extracted, registered, or
|
|
233
|
+
// stored yet at this point, so there is nothing for cleanupOrphanedInstall
|
|
234
|
+
// to undo — a plain early return is enough.
|
|
235
|
+
try {
|
|
236
|
+
await downloadPackage(marketplaceBaseUrl, pluginId, version, tarballPath);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (error instanceof MarketplaceNotFoundError) {
|
|
240
|
+
return { ok: false, failure: { code: "not-found", message: error.message } };
|
|
241
|
+
}
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
// S152: declared here — in the OUTER try's body, above the inner try
|
|
245
|
+
// below — rather than inside that inner try, because the inner try's
|
|
246
|
+
// own catch (the P2002 / typed-refusal / rethrow handling further
|
|
247
|
+
// down) needs to read and call these too. A `let`/`const` declared
|
|
248
|
+
// inside a try block is not visible from that try's own catch block
|
|
249
|
+
// (they are sibling scopes, not parent/child) — only a declaration in
|
|
250
|
+
// an enclosing scope is visible from both.
|
|
251
|
+
let bundleMembers;
|
|
252
|
+
// S152: any failure AFTER the members are in must take them back out
|
|
253
|
+
// (the atomic-set invariant), including their ownership rows if the
|
|
254
|
+
// createMany below already ran.
|
|
255
|
+
const rollbackBundleMembers = async () => {
|
|
256
|
+
if (!bundleMembers || bundleMembers.installedIds.length === 0)
|
|
257
|
+
return;
|
|
258
|
+
for (const memberId of [...bundleMembers.installedIds].reverse()) {
|
|
259
|
+
await cleanupOrphanedInstall(deps, memberId, "installed");
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
await prisma.pluginBundleMember.deleteMany({ where: { bundlePluginId: pluginId } });
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
console.error(`[install-from-marketplace] failed to delete bundle ownership rows for "${pluginId}" during cleanup`, error);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
try {
|
|
269
|
+
// Identity binding, before anything is registered. installPackageTarball's
|
|
270
|
+
// own checkPackageIdentity only cross-checks the SIGNATURE ENVELOPE
|
|
271
|
+
// against the tarball's OWN manifest.json — it has no idea what
|
|
272
|
+
// pluginId/version THIS request actually asked for. A marketplace
|
|
273
|
+
// whose /packages/:pluginId/:version route serves the wrong artifact
|
|
274
|
+
// (a bug, or a compromised/misconfigured upstream) could otherwise
|
|
275
|
+
// have its own, internally self-consistent signature verify
|
|
276
|
+
// "verified" while installing a completely different plugin than
|
|
277
|
+
// what was requested — and since the registry row lands under the
|
|
278
|
+
// MANIFEST's id, not the requested one, a cleanup keyed only on the
|
|
279
|
+
// requested id would miss it entirely. Peeking the manifest here,
|
|
280
|
+
// before installPackageTarball (or even downloadSignature) ever
|
|
281
|
+
// runs, means a mismatch is refused before any registration side
|
|
282
|
+
// effect exists at all — nothing to clean up under either id,
|
|
283
|
+
// because nothing was ever installed under either id.
|
|
284
|
+
const actualIdentity = await peekPackageIdentity(tarballPath, join(tempDir, "identity-peek"));
|
|
285
|
+
if (actualIdentity.id !== pluginId || actualIdentity.version !== version) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
failure: {
|
|
289
|
+
code: "verification-failed",
|
|
290
|
+
message: `Requested "${pluginId}"@${version} but the downloaded package identifies itself as "${actualIdentity.id}"@${actualIdentity.version} — refusing to install a mismatched package`,
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const signatureText = await downloadSignature(marketplaceBaseUrl, pluginId, version);
|
|
295
|
+
const sidecarPath = resolveSidecarPath(tarballPath);
|
|
296
|
+
if (signatureText !== null) {
|
|
297
|
+
// resolveSidecarPath's convention: `<tarballPath>.sig`, right next
|
|
298
|
+
// to the artifact. installPackageTarball's own default sidecar
|
|
299
|
+
// resolution (when no options.sidecarPath is passed below) looks
|
|
300
|
+
// for exactly this path via existsSync, so writing here is all
|
|
301
|
+
// that's needed to hand it the signature.
|
|
302
|
+
await writeFile(sidecarPath, signatureText);
|
|
303
|
+
}
|
|
304
|
+
// C1 (S138 hardening review): the trust gate, computed and enforced
|
|
305
|
+
// HERE — before installPackageTarball, and therefore before a single
|
|
306
|
+
// line of the package's code has run. Previously this same verdict
|
|
307
|
+
// was only checked AFTER installPackageTarball had already copied
|
|
308
|
+
// the package into the managed store, `import()`ed its
|
|
309
|
+
// server/plugin.js (executing its top-level code), and run its
|
|
310
|
+
// migrations/onInstall/onActivate — a tampered or unsigned package
|
|
311
|
+
// was refused only once it had already executed once. That inverted
|
|
312
|
+
// this module's own documented spine ("download -> identity check ->
|
|
313
|
+
// verify -> register") and the spec's Decision 2 ("a marketplace
|
|
314
|
+
// install BLOCKS on failed verification").
|
|
315
|
+
//
|
|
316
|
+
// This reuses verifyPackage + checkPackageIdentity — the exact same
|
|
317
|
+
// functions installPackageTarball calls internally — against the
|
|
318
|
+
// exact same inputs (tarballPath, sidecarPath, loadTrustedKeys()) it
|
|
319
|
+
// will use a few lines below, plus actualIdentity from the identity
|
|
320
|
+
// peek above (already extracted, so this costs one extra
|
|
321
|
+
// hashTarball and nothing else — no second extraction). Because the
|
|
322
|
+
// inputs are identical and verifyPackage is a pure function of them,
|
|
323
|
+
// installPackageTarball's own internal recomputation is guaranteed
|
|
324
|
+
// to reach the same verdict; it is kept anyway as a second,
|
|
325
|
+
// belt-and-braces gate (see the comment where it's checked below)
|
|
326
|
+
// rather than trusted blindly.
|
|
327
|
+
const preVerification = checkPackageIdentity(await verifyPackage(tarballPath, existsSync(sidecarPath) ? sidecarPath : null, loadTrustedKeys()), actualIdentity.id, actualIdentity.version);
|
|
328
|
+
if (trustTierFor(preVerification) !== "verified") {
|
|
329
|
+
// Nothing has been imported, registered, or copied into the
|
|
330
|
+
// managed store yet — installPackageTarball has not been called —
|
|
331
|
+
// so there is nothing for cleanupOrphanedInstall to undo. This IS
|
|
332
|
+
// "refuse before execution," not "execute then refuse."
|
|
333
|
+
return {
|
|
334
|
+
ok: false,
|
|
335
|
+
failure: { code: "verification-failed", message: describeVerificationFailure(pluginId, version, preVerification) },
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
// S152: bundle fan-out — members first, bundle last. Runs only after
|
|
339
|
+
// the bundle's own artifact is downloaded, identity-bound, and
|
|
340
|
+
// signature-verified (the member list is READ from that verified
|
|
341
|
+
// artifact), and before the bundle itself is registered, so a bundle
|
|
342
|
+
// registry row existing always means the full set is in place.
|
|
343
|
+
// installBundleMembers guarantees that on any non-ok return or throw
|
|
344
|
+
// it has already rolled back every member it installed.
|
|
345
|
+
if (actualIdentity.type === "bundle") {
|
|
346
|
+
if (input.refuseBundleType) {
|
|
347
|
+
return {
|
|
348
|
+
ok: false,
|
|
349
|
+
failure: {
|
|
350
|
+
code: "install-refused",
|
|
351
|
+
message: `"${pluginId}" is itself a bundle — nested bundles are not supported as bundle members`,
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const result = await installBundleMembers({
|
|
356
|
+
fetchMemberListing: (memberId) => fetchListing(marketplaceBaseUrl, memberId),
|
|
357
|
+
installOne: (memberInput) => installFromMarketplace(deps, { ...memberInput, refuseBundleType: true }),
|
|
358
|
+
// A member's install ended fully registered AND active
|
|
359
|
+
// (registerLocalPlugin always activates), so its real inverse is
|
|
360
|
+
// the same "installed"-outcome cleanup the single-plugin path
|
|
361
|
+
// already uses: registry.uninstall with structured cleanup
|
|
362
|
+
// confirmed (moments-old data, nothing to protect) plus
|
|
363
|
+
// package-store removal.
|
|
364
|
+
rollbackOne: (memberId) => cleanupOrphanedInstall(deps, memberId, "installed"),
|
|
365
|
+
}, { bundleId: pluginId, memberIds: actualIdentity.bundledPlugins ?? [], installedBy });
|
|
366
|
+
if (!result.ok) {
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
bundleMembers = result;
|
|
370
|
+
}
|
|
371
|
+
const setRemoteUrl = async (id, url) => {
|
|
372
|
+
await prisma.pluginRegistry.update({ where: { pluginId: id }, data: { remoteUrl: url } });
|
|
373
|
+
};
|
|
374
|
+
const syncManifestFields = async (id, fields) => {
|
|
375
|
+
await prisma.pluginRegistry.update({ where: { pluginId: id }, data: fields });
|
|
376
|
+
};
|
|
377
|
+
const setPackagePath = async (id, packagePath) => {
|
|
378
|
+
await prisma.pluginRegistry.update({ where: { pluginId: id }, data: { packagePath } });
|
|
379
|
+
};
|
|
380
|
+
// remoteUrl: the S143 plugin-assets mount serves the stored copy of
|
|
381
|
+
// this exact package, so the row points there — server-relative (see
|
|
382
|
+
// pluginAssetsRemoteUrl's doc comment for why not absolute). Safe to
|
|
383
|
+
// compute from the REQUESTED id/version: the identity peek above
|
|
384
|
+
// already proved the package's manifest matches them, and
|
|
385
|
+
// installBuiltPackage stores under the manifest's own id/version.
|
|
386
|
+
// The RegisterLocalPluginResult half of the return ("installed" |
|
|
387
|
+
// "activated" | "already-active") IS captured here (into
|
|
388
|
+
// installOutcome, declared above the outer try) — cleanupOrphanedInstall
|
|
389
|
+
// needs it to tell "this request's own install()" apart from "a
|
|
390
|
+
// concurrent request's install() that won the race against step 1's
|
|
391
|
+
// own check," which registerLocalPlugin's tolerant `existing` merge
|
|
392
|
+
// path takes silently rather than throwing (see cleanupOrphanedInstall's
|
|
393
|
+
// doc comment for the full reasoning — this is the M4 fix).
|
|
394
|
+
// S144: only client-bearing types get a plugin-assets remoteUrl —
|
|
395
|
+
// a theme/template/bundle package has no remoteEntry.js, and a URL
|
|
396
|
+
// pointing at a 404 would make the admin loader log a load failure
|
|
397
|
+
// for every view-less plugin on every boot. Empty string = the
|
|
398
|
+
// loader's existing "nothing to load" contract. The peeked type is
|
|
399
|
+
// unvalidated here, which is fine: it only picks a URL; the full
|
|
400
|
+
// manifest validation still runs inside the install.
|
|
401
|
+
const remoteUrl = actualIdentity.type && !hasClientBundle(actualIdentity.type)
|
|
402
|
+
? ""
|
|
403
|
+
: pluginAssetsRemoteUrl(pluginId, version);
|
|
404
|
+
const { outcome, verification } = await installPackageTarball(registry, tarballPath, remoteUrl, setRemoteUrl, syncManifestFields, setPackagePath);
|
|
405
|
+
installOutcome = outcome;
|
|
406
|
+
// Belt-and-braces twin of the pre-install gate above, not the primary
|
|
407
|
+
// gate — C1's fix moved the REAL trust decision ahead of
|
|
408
|
+
// installPackageTarball, using the identical tarballPath/sidecarPath/
|
|
409
|
+
// trustedKeys inputs, so this recomputation (done internally by
|
|
410
|
+
// installPackageTarball) is expected to always agree and this branch
|
|
411
|
+
// should be unreachable in practice. It stays rather than being
|
|
412
|
+
// deleted: if a future change ever lets the two diverge (a trusted-key
|
|
413
|
+
// reload mid-request, a code path inserted between the two that
|
|
414
|
+
// touches the sidecar), this is what still stops an unverified
|
|
415
|
+
// package from being left installed instead of silently trusting the
|
|
416
|
+
// earlier check forever. installOutcome (just captured above) tells
|
|
417
|
+
// cleanupOrphanedInstall whether this request actually owns what
|
|
418
|
+
// needs undoing.
|
|
419
|
+
if (trustTierFor(verification) !== "verified") {
|
|
420
|
+
await cleanupOrphanedInstall(deps, pluginId, installOutcome);
|
|
421
|
+
await rollbackBundleMembers();
|
|
422
|
+
return {
|
|
423
|
+
ok: false,
|
|
424
|
+
failure: { code: "verification-failed", message: describeVerificationFailure(pluginId, version, verification) },
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
// Step 6: paid plugins get their license persisted only once the
|
|
428
|
+
// install has actually succeeded and verified.
|
|
429
|
+
const licensed = listing.priceCents > 0;
|
|
430
|
+
if (licensed) {
|
|
431
|
+
await prisma.pluginLicense.upsert({
|
|
432
|
+
where: { pluginId },
|
|
433
|
+
create: { pluginId, key: licenseKey, boundDomain: siteDomain, status: "valid", lastValidatedAt: new Date() },
|
|
434
|
+
update: { key: licenseKey, boundDomain: siteDomain, status: "valid", lastReason: null, lastValidatedAt: new Date() },
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
// Step 7: attaching the manifest again here would be redundant, not
|
|
438
|
+
// missing. registerLocalPlugin's `!existing` branch (always taken
|
|
439
|
+
// here, since step 1 already proved no row existed) calls
|
|
440
|
+
// PluginRegistryService.install(), which itself does
|
|
441
|
+
// `this.manifests.set(plugin.id, plugin)` before returning — the
|
|
442
|
+
// manifest is already attached in THIS process by the time
|
|
443
|
+
// installPackageTarball resolves. app.pluginRegistry.getManifest(id)
|
|
444
|
+
// answers immediately, no restart and no extra call needed.
|
|
445
|
+
// Step 7.5 (Session 149, Task 2): seed a template plugin's sample
|
|
446
|
+
// content, but ONLY when this request's own call actually created
|
|
447
|
+
// the row (installOutcome === "installed") — "activated"/
|
|
448
|
+
// "already-active" mean a concurrent request owns the install, same
|
|
449
|
+
// reasoning cleanupOrphanedInstall's doc comment already spells out
|
|
450
|
+
// for those two outcomes. D1's "never from inside
|
|
451
|
+
// PluginRegistryService.install()" is honored: this runs entirely
|
|
452
|
+
// outside it, after installPackageTarball has already returned.
|
|
453
|
+
// packagePath was just persisted by setPackagePath above (installBuiltPackage
|
|
454
|
+
// always calls it before returning), so a fresh read gets the exact
|
|
455
|
+
// stored directory seedTemplateSampleContent needs. Wrapped in its
|
|
456
|
+
// own try/catch as a belt-and-braces guard on top of the seeder's
|
|
457
|
+
// own internal non-fatal handling — D2 requires this install to
|
|
458
|
+
// stand no matter what happens here.
|
|
459
|
+
if (installOutcome === "installed") {
|
|
460
|
+
try {
|
|
461
|
+
const row = await prisma.pluginRegistry.findUnique({ where: { pluginId } });
|
|
462
|
+
if (row?.packagePath) {
|
|
463
|
+
const seedResult = await seedTemplateSampleContent({ prisma, adapter }, pluginId, row.packagePath, installedBy);
|
|
464
|
+
if (typeof seedResult === "object") {
|
|
465
|
+
console.log(`[install-from-marketplace] seeded ${seedResult.created} sample content node(s) for "${pluginId}"`);
|
|
466
|
+
}
|
|
467
|
+
else if (seedResult !== "none") {
|
|
468
|
+
console.log(`[install-from-marketplace] sample content seeding for "${pluginId}": ${seedResult}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
console.error(`[install-from-marketplace] sample content seeding for "${pluginId}" failed — the install stands`, error);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// S152: ownership rows LAST — written only once the bundle's own row
|
|
477
|
+
// exists and everything else has succeeded, so a rolled-back install
|
|
478
|
+
// can never leave ownership rows behind. Only members THIS operation
|
|
479
|
+
// installed get a row (skip-don't-claim); position preserves install
|
|
480
|
+
// order for the uninstall cascade's reverse walk.
|
|
481
|
+
if (bundleMembers && bundleMembers.installedIds.length > 0) {
|
|
482
|
+
await prisma.pluginBundleMember.createMany({
|
|
483
|
+
data: bundleMembers.installedIds.map((memberPluginId, index) => ({
|
|
484
|
+
bundlePluginId: pluginId,
|
|
485
|
+
memberPluginId,
|
|
486
|
+
position: index,
|
|
487
|
+
})),
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
ok: true,
|
|
492
|
+
pluginId,
|
|
493
|
+
version,
|
|
494
|
+
trustTier: "verified",
|
|
495
|
+
licensed,
|
|
496
|
+
...(bundleMembers ? { members: bundleMembers.members } : {}),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
// A second, concurrent install of the SAME never-before-installed
|
|
501
|
+
// plugin can both pass step 1's findUnique (neither sees the
|
|
502
|
+
// other's row yet) and then race PluginRegistryService.install()'s
|
|
503
|
+
// own prisma.pluginRegistry.create() — the loser's create() throws a
|
|
504
|
+
// unique-constraint violation on pluginId. That is not a partial
|
|
505
|
+
// failure to unwind: the loser's own install() never reached
|
|
506
|
+
// `this.manifests.set(...)` (it only runs AFTER create() succeeds),
|
|
507
|
+
// so nothing of THIS call's making was ever attached, activated, or
|
|
508
|
+
// registered. storePackage() did already run (installBuiltPackage
|
|
509
|
+
// copies into the managed store before calling install()), but that
|
|
510
|
+
// copy is byte-identical to the winner's — both installs are
|
|
511
|
+
// extracting the SAME downloaded tarball — so there is nothing of
|
|
512
|
+
// ours to undo, only the winner's legitimate install to leave
|
|
513
|
+
// completely alone. Report it exactly like the ordinary
|
|
514
|
+
// already-installed case.
|
|
515
|
+
//
|
|
516
|
+
// S152: this branch deliberately does NOT roll bundle members back.
|
|
517
|
+
// On P2002 a CONCURRENT request owns the bundle row, and that
|
|
518
|
+
// winner's own fan-out saw our already-installed members as
|
|
519
|
+
// pre-existing skips (skip-don't-claim) — tearing ours down here
|
|
520
|
+
// would break the winner's set out from under it. The members stay
|
|
521
|
+
// installed but unowned, exactly as if they pre-existed: a benign,
|
|
522
|
+
// documented residual mirroring cleanupOrphanedInstall's own M4 skip.
|
|
523
|
+
//
|
|
524
|
+
// The `installOutcome !== "installed"` guard matters because P2002
|
|
525
|
+
// is not exclusively the create-race described above: once THIS
|
|
526
|
+
// request's own registerLocalPlugin has already returned "installed",
|
|
527
|
+
// two LATER statements inside this same try can also throw P2002 —
|
|
528
|
+
// the pluginLicense.upsert (a concurrent license write) and the
|
|
529
|
+
// S152 pluginBundleMember.createMany (unique on [bundlePluginId,
|
|
530
|
+
// memberPluginId], which a stale ownership row can violate if a
|
|
531
|
+
// prior cleanup's own deleteMany failed and was swallowed). Neither
|
|
532
|
+
// of those is the concurrent-install race: this request DID create
|
|
533
|
+
// the registry row, so returning "already-installed" here would
|
|
534
|
+
// leave a fully-installed, active plugin/bundle behind with no
|
|
535
|
+
// ownership rows and no rollback. Only fall onto the early return
|
|
536
|
+
// when this request never won its own create — i.e. installOutcome
|
|
537
|
+
// is not "installed" — otherwise fall through to the cleanup path
|
|
538
|
+
// below, exactly like any other unexpected failure.
|
|
539
|
+
if (isUniqueConstraintViolation(error) && installOutcome !== "installed") {
|
|
540
|
+
return { ok: false, failure: { code: "already-installed", message: `Plugin "${pluginId}" is already installed` } };
|
|
541
|
+
}
|
|
542
|
+
// Anything else — a plugin's onActivate() throwing, a corrupt
|
|
543
|
+
// archive the identity peek didn't already catch, a DB error on the
|
|
544
|
+
// license upsert, or any other genuinely unexpected failure — may
|
|
545
|
+
// have left the plugin partially registered/attached/stored. Restore
|
|
546
|
+
// state before letting the error propagate; the route's own handler
|
|
547
|
+
// still turns an unmapped throw into a 500, but only AFTER this
|
|
548
|
+
// completes. installOutcome is undefined here whenever install()
|
|
549
|
+
// itself threw one of the three typed refusals mapped just below (all
|
|
550
|
+
// three fire from inside install(), before installPackageTarball ever
|
|
551
|
+
// returns) — cleanupOrphanedInstall treats that the same as "nothing
|
|
552
|
+
// exists to protect," which is correct for all three (see its own doc
|
|
553
|
+
// comment).
|
|
554
|
+
await cleanupOrphanedInstall(deps, pluginId, installOutcome);
|
|
555
|
+
await rollbackBundleMembers();
|
|
556
|
+
// M2 (S138 hardening review): PluginRegistryService.install()'s own
|
|
557
|
+
// typed refusals, previously unmapped and left to fall through to the
|
|
558
|
+
// generic rethrow below — which the route's handler turns into a bare
|
|
559
|
+
// 500, even though each of these is a well-understood, well-messaged
|
|
560
|
+
// refusal rather than a server bug. See the InstallFailure union's own
|
|
561
|
+
// comment for why "incompatible"/"install-refused" get 422 and
|
|
562
|
+
// "dependency-missing" gets 409. Checked most-specific-subclass-first
|
|
563
|
+
// since PluginIncompatibleError and PluginDependencyError both extend
|
|
564
|
+
// the base PluginInstallError.
|
|
565
|
+
if (error instanceof PluginIncompatibleError) {
|
|
566
|
+
return { ok: false, failure: { code: "incompatible", message: error.message } };
|
|
567
|
+
}
|
|
568
|
+
if (error instanceof PluginDependencyError) {
|
|
569
|
+
return { ok: false, failure: { code: "dependency-missing", message: error.message } };
|
|
570
|
+
}
|
|
571
|
+
if (error instanceof PluginInstallError) {
|
|
572
|
+
return { ok: false, failure: { code: "install-refused", message: error.message } };
|
|
573
|
+
}
|
|
574
|
+
throw error;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
finally {
|
|
578
|
+
// Step 8.
|
|
579
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type RevalidateDeps } from "./revalidate-license.js";
|
|
2
|
+
/** One sweep over every plugin that has a license row. A single plugin's
|
|
3
|
+
* failure is logged and never aborts the sweep (the Session 91 precedent:
|
|
4
|
+
* one plugin must never take the others down). */
|
|
5
|
+
export declare function runLicenseRevalidationPass(deps: RevalidateDeps): Promise<{
|
|
6
|
+
checked: number;
|
|
7
|
+
revoked: number;
|
|
8
|
+
failed: number;
|
|
9
|
+
}>;
|
|
10
|
+
/** Starts the first background timer in apps/server. Two things are
|
|
11
|
+
* non-negotiable: the handle is unref()'d so it can never hold the process
|
|
12
|
+
* open, and the caller MUST stop it on app close (app.ts registers an
|
|
13
|
+
* onClose hook) — otherwise every integration test that builds and closes
|
|
14
|
+
* an app leaves a live timer behind.
|
|
15
|
+
*
|
|
16
|
+
* Passes are intended never to overlap, but forward progress must never
|
|
17
|
+
* depend on a third-party hook actually resolving. What is ACTUALLY
|
|
18
|
+
* guaranteed (corrected from an earlier version of this comment, which
|
|
19
|
+
* claimed the wait was inherently bounded by the upstream 8s timeout
|
|
20
|
+
* alone — true of the license CHECK, but not of registry.deactivate(),
|
|
21
|
+
* which also awaits a plugin's onDeactivate hook with no timeout of its
|
|
22
|
+
* own):
|
|
23
|
+
* - stop() always resolves within ~passTimeoutMs of being called,
|
|
24
|
+
* regardless of whether the in-flight pass (or the hook it's stuck
|
|
25
|
+
* in) ever finishes. If it gives up, it logs clearly that it did.
|
|
26
|
+
* - A pass that's been "in flight" longer than passTimeoutMs is
|
|
27
|
+
* presumed wedged, and the overlap guard stops treating it as a
|
|
28
|
+
* reason to skip the next tick — so a hung onDeactivate hook can
|
|
29
|
+
* disable enforcement for at most one bad tick, not for the rest of
|
|
30
|
+
* the process's life.
|
|
31
|
+
* - Giving up on a wedged pass does not cancel it. It keeps running in
|
|
32
|
+
* the background and will still write whatever verdict it eventually
|
|
33
|
+
* reaches; it just can no longer block anyone else from making
|
|
34
|
+
* progress. */
|
|
35
|
+
export declare function startLicenseRevalidation(deps: RevalidateDeps, opts?: {
|
|
36
|
+
intervalMs?: number;
|
|
37
|
+
passTimeoutMs?: number;
|
|
38
|
+
}): () => Promise<void>;
|