emdash 0.0.0-b → 0.0.2
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/README.md +87 -43
- package/dist/adapters-BLMa4JGD.d.mts +106 -0
- package/dist/adapters-BLMa4JGD.d.mts.map +1 -0
- package/dist/apply-Bjfq_b4-.mjs +1293 -0
- package/dist/apply-Bjfq_b4-.mjs.map +1 -0
- package/dist/astro/index.d.mts +51 -0
- package/dist/astro/index.d.mts.map +1 -0
- package/dist/astro/index.mjs +1336 -0
- package/dist/astro/index.mjs.map +1 -0
- package/dist/astro/middleware/auth.d.mts +31 -0
- package/dist/astro/middleware/auth.d.mts.map +1 -0
- package/dist/astro/middleware/auth.mjs +654 -0
- package/dist/astro/middleware/auth.mjs.map +1 -0
- package/dist/astro/middleware/redirect.d.mts +22 -0
- package/dist/astro/middleware/redirect.d.mts.map +1 -0
- package/dist/astro/middleware/redirect.mjs +63 -0
- package/dist/astro/middleware/redirect.mjs.map +1 -0
- package/dist/astro/middleware/request-context.d.mts +18 -0
- package/dist/astro/middleware/request-context.d.mts.map +1 -0
- package/dist/astro/middleware/request-context.mjs +1310 -0
- package/dist/astro/middleware/request-context.mjs.map +1 -0
- package/dist/astro/middleware/setup.d.mts +20 -0
- package/dist/astro/middleware/setup.d.mts.map +1 -0
- package/dist/astro/middleware/setup.mjs +47 -0
- package/dist/astro/middleware/setup.mjs.map +1 -0
- package/dist/astro/middleware.d.mts +13 -0
- package/dist/astro/middleware.d.mts.map +1 -0
- package/dist/astro/middleware.mjs +1613 -0
- package/dist/astro/middleware.mjs.map +1 -0
- package/dist/astro/types.d.mts +250 -0
- package/dist/astro/types.d.mts.map +1 -0
- package/dist/astro/types.mjs +1 -0
- package/dist/base64-MBPo9ozB.mjs +59 -0
- package/dist/base64-MBPo9ozB.mjs.map +1 -0
- package/dist/byline-CL847F26.mjs +213 -0
- package/dist/byline-CL847F26.mjs.map +1 -0
- package/dist/bylines-C2a-2TGt.mjs +136 -0
- package/dist/bylines-C2a-2TGt.mjs.map +1 -0
- package/dist/chunk-ClPoSABd.mjs +21 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3909 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/cf-access.d.mts +60 -0
- package/dist/client/cf-access.d.mts.map +1 -0
- package/dist/client/cf-access.mjs +179 -0
- package/dist/client/cf-access.mjs.map +1 -0
- package/dist/client/index.d.mts +398 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +346 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/config-CKE8p9xM.mjs +55 -0
- package/dist/config-CKE8p9xM.mjs.map +1 -0
- package/dist/connection-B4zVnQIa.mjs +40 -0
- package/dist/connection-B4zVnQIa.mjs.map +1 -0
- package/dist/content-D6C2WsZC.mjs +824 -0
- package/dist/content-D6C2WsZC.mjs.map +1 -0
- package/dist/db/index.d.mts +4 -0
- package/dist/db/index.mjs +62 -0
- package/dist/db/index.mjs.map +1 -0
- package/dist/db/libsql.d.mts +11 -0
- package/dist/db/libsql.d.mts.map +1 -0
- package/dist/db/libsql.mjs +17 -0
- package/dist/db/libsql.mjs.map +1 -0
- package/dist/db/postgres.d.mts +11 -0
- package/dist/db/postgres.d.mts.map +1 -0
- package/dist/db/postgres.mjs +30 -0
- package/dist/db/postgres.mjs.map +1 -0
- package/dist/db/sqlite.d.mts +11 -0
- package/dist/db/sqlite.d.mts.map +1 -0
- package/dist/db/sqlite.mjs +16 -0
- package/dist/db/sqlite.mjs.map +1 -0
- package/dist/default-Cyi4aAxu.mjs +81 -0
- package/dist/default-Cyi4aAxu.mjs.map +1 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs +90 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs.map +1 -0
- package/dist/error-Cxz0tQeO.mjs +27 -0
- package/dist/error-Cxz0tQeO.mjs.map +1 -0
- package/dist/index-C1xF3OGh.d.mts +4527 -0
- package/dist/index-C1xF3OGh.d.mts.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +30 -0
- package/dist/load-yOOlckBj.mjs +28 -0
- package/dist/load-yOOlckBj.mjs.map +1 -0
- package/dist/loader-fz8Q_3EO.mjs +447 -0
- package/dist/loader-fz8Q_3EO.mjs.map +1 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs +184 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs.map +1 -0
- package/dist/media/index.d.mts +26 -0
- package/dist/media/index.d.mts.map +1 -0
- package/dist/media/index.mjs +55 -0
- package/dist/media/index.mjs.map +1 -0
- package/dist/media/local-runtime.d.mts +39 -0
- package/dist/media/local-runtime.d.mts.map +1 -0
- package/dist/media/local-runtime.mjs +133 -0
- package/dist/media/local-runtime.mjs.map +1 -0
- package/dist/media-DqHVh136.mjs +200 -0
- package/dist/media-DqHVh136.mjs.map +1 -0
- package/dist/mode-C2EzN1uE.mjs +23 -0
- package/dist/mode-C2EzN1uE.mjs.map +1 -0
- package/dist/page/index.d.mts +140 -0
- package/dist/page/index.d.mts.map +1 -0
- package/dist/page/index.mjs +416 -0
- package/dist/page/index.mjs.map +1 -0
- package/dist/placeholder-CmGAmqeO.d.mts +276 -0
- package/dist/placeholder-CmGAmqeO.d.mts.map +1 -0
- package/dist/placeholder-SmpOx-_v.mjs +243 -0
- package/dist/placeholder-SmpOx-_v.mjs.map +1 -0
- package/dist/plugin-utils.d.mts +58 -0
- package/dist/plugin-utils.d.mts.map +1 -0
- package/dist/plugin-utils.mjs +78 -0
- package/dist/plugin-utils.mjs.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +22 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.mjs +113 -0
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -0
- package/dist/query-CS_iSj34.mjs +460 -0
- package/dist/query-CS_iSj34.mjs.map +1 -0
- package/dist/redirect-DIfIni3r.mjs +329 -0
- package/dist/redirect-DIfIni3r.mjs.map +1 -0
- package/dist/registry-D_w5HW4G.mjs +863 -0
- package/dist/registry-D_w5HW4G.mjs.map +1 -0
- package/dist/request-context.d.mts +49 -0
- package/dist/request-context.d.mts.map +1 -0
- package/dist/request-context.mjs +43 -0
- package/dist/request-context.mjs.map +1 -0
- package/dist/runner-C0hCbYnD.mjs +1412 -0
- package/dist/runner-C0hCbYnD.mjs.map +1 -0
- package/dist/runner-EAtf0ZIe.d.mts +27 -0
- package/dist/runner-EAtf0ZIe.d.mts.map +1 -0
- package/dist/runtime.d.mts +26 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +42 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/search-DG603UrT.mjs +9211 -0
- package/dist/search-DG603UrT.mjs.map +1 -0
- package/dist/seed/index.d.mts +3 -0
- package/dist/seed/index.mjs +15 -0
- package/dist/seo/index.d.mts +70 -0
- package/dist/seo/index.d.mts.map +1 -0
- package/dist/seo/index.mjs +70 -0
- package/dist/seo/index.mjs.map +1 -0
- package/dist/storage/local.d.mts +39 -0
- package/dist/storage/local.d.mts.map +1 -0
- package/dist/storage/local.mjs +166 -0
- package/dist/storage/local.mjs.map +1 -0
- package/dist/storage/s3.d.mts +32 -0
- package/dist/storage/s3.d.mts.map +1 -0
- package/dist/storage/s3.mjs +175 -0
- package/dist/storage/s3.mjs.map +1 -0
- package/dist/tokens-DpgrkrXK.mjs +171 -0
- package/dist/tokens-DpgrkrXK.mjs.map +1 -0
- package/dist/transport-BFGblqwG.d.mts +42 -0
- package/dist/transport-BFGblqwG.d.mts.map +1 -0
- package/dist/transport-yxiQsi8I.mjs +418 -0
- package/dist/transport-yxiQsi8I.mjs.map +1 -0
- package/dist/types-BRuPJGdV.d.mts +102 -0
- package/dist/types-BRuPJGdV.d.mts.map +1 -0
- package/dist/types-C4-fAxN3.d.mts +182 -0
- package/dist/types-C4-fAxN3.d.mts.map +1 -0
- package/dist/types-CMMN0pNg.mjs +31 -0
- package/dist/types-CMMN0pNg.mjs.map +1 -0
- package/dist/types-CUBbjgmP.mjs +16 -0
- package/dist/types-CUBbjgmP.mjs.map +1 -0
- package/dist/types-DRjfYOEv.d.mts +426 -0
- package/dist/types-DRjfYOEv.d.mts.map +1 -0
- package/dist/types-DY5zk5HN.mjs +73 -0
- package/dist/types-DY5zk5HN.mjs.map +1 -0
- package/dist/types-DaNLHo_T.d.mts +184 -0
- package/dist/types-DaNLHo_T.d.mts.map +1 -0
- package/dist/types-DvhsUmSJ.d.mts +1111 -0
- package/dist/types-DvhsUmSJ.d.mts.map +1 -0
- package/dist/validate-CpBtVMsD.d.mts +378 -0
- package/dist/validate-CpBtVMsD.d.mts.map +1 -0
- package/dist/validate-CqRJb_xU.mjs +97 -0
- package/dist/validate-CqRJb_xU.mjs.map +1 -0
- package/dist/validate-O7PWmlnq.mjs +328 -0
- package/dist/validate-O7PWmlnq.mjs.map +1 -0
- package/locals.d.ts +46 -0
- package/package.json +233 -19
- package/src/api/authorize.ts +63 -0
- package/src/api/csrf.ts +48 -0
- package/src/api/error.ts +99 -0
- package/src/api/errors.ts +445 -0
- package/src/api/escape.ts +9 -0
- package/src/api/handlers/api-tokens.ts +240 -0
- package/src/api/handlers/comments.ts +314 -0
- package/src/api/handlers/content.ts +1315 -0
- package/src/api/handlers/dashboard.ts +205 -0
- package/src/api/handlers/device-flow.ts +684 -0
- package/src/api/handlers/index.ts +163 -0
- package/src/api/handlers/manifest.ts +158 -0
- package/src/api/handlers/marketplace.ts +930 -0
- package/src/api/handlers/media.ts +207 -0
- package/src/api/handlers/menus.ts +493 -0
- package/src/api/handlers/oauth-authorization.ts +429 -0
- package/src/api/handlers/oauth-clients.ts +349 -0
- package/src/api/handlers/oauth-user-lookup.ts +39 -0
- package/src/api/handlers/plugins.ts +254 -0
- package/src/api/handlers/redirects.ts +360 -0
- package/src/api/handlers/revision.ts +145 -0
- package/src/api/handlers/schema.ts +534 -0
- package/src/api/handlers/sections.ts +289 -0
- package/src/api/handlers/seo.ts +115 -0
- package/src/api/handlers/settings.ts +49 -0
- package/src/api/handlers/snapshot.ts +350 -0
- package/src/api/handlers/taxonomies.ts +523 -0
- package/src/api/index.ts +6 -0
- package/src/api/openapi/document.ts +2368 -0
- package/src/api/openapi/index.ts +1 -0
- package/src/api/parse.ts +139 -0
- package/src/api/redirect.ts +14 -0
- package/src/api/rev.ts +67 -0
- package/src/api/schemas/auth.ts +112 -0
- package/src/api/schemas/bylines.ts +85 -0
- package/src/api/schemas/comments.ts +117 -0
- package/src/api/schemas/common.ts +89 -0
- package/src/api/schemas/content.ts +191 -0
- package/src/api/schemas/import.ts +52 -0
- package/src/api/schemas/index.ts +17 -0
- package/src/api/schemas/media.ts +116 -0
- package/src/api/schemas/menus.ts +111 -0
- package/src/api/schemas/redirects.ts +155 -0
- package/src/api/schemas/schema.ts +203 -0
- package/src/api/schemas/search.ts +63 -0
- package/src/api/schemas/sections.ts +67 -0
- package/src/api/schemas/settings.ts +63 -0
- package/src/api/schemas/setup.ts +37 -0
- package/src/api/schemas/taxonomies.ts +113 -0
- package/src/api/schemas/users.ts +96 -0
- package/src/api/schemas/widgets.ts +80 -0
- package/src/api/site-url.ts +25 -0
- package/src/api/types.ts +82 -0
- package/src/astro/index.ts +27 -0
- package/src/astro/integration/index.ts +303 -0
- package/src/astro/integration/routes.ts +834 -0
- package/src/astro/integration/runtime.ts +338 -0
- package/src/astro/integration/virtual-modules.ts +469 -0
- package/src/astro/integration/vite-config.ts +335 -0
- package/src/astro/middleware/auth.ts +743 -0
- package/src/astro/middleware/redirect.ts +89 -0
- package/src/astro/middleware/request-context.ts +129 -0
- package/src/astro/middleware/setup.ts +89 -0
- package/src/astro/middleware.ts +398 -0
- package/src/astro/routes/PluginRegistry.tsx +15 -0
- package/src/astro/routes/admin.astro +81 -0
- package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
- package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
- package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
- package/src/astro/routes/api/admin/bylines/index.ts +72 -0
- package/src/astro/routes/api/admin/comments/[id]/status.ts +116 -0
- package/src/astro/routes/api/admin/comments/[id].ts +64 -0
- package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
- package/src/astro/routes/api/admin/comments/counts.ts +30 -0
- package/src/astro/routes/api/admin/comments/index.ts +46 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
- package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
- package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
- package/src/astro/routes/api/admin/plugins/index.ts +32 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +61 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +62 -0
- package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +61 -0
- package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
- package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
- package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
- package/src/astro/routes/api/admin/users/index.ts +66 -0
- package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
- package/src/astro/routes/api/auth/invite/accept.ts +52 -0
- package/src/astro/routes/api/auth/invite/complete.ts +84 -0
- package/src/astro/routes/api/auth/invite/index.ts +99 -0
- package/src/astro/routes/api/auth/logout.ts +40 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
- package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
- package/src/astro/routes/api/auth/me.ts +60 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +219 -0
- package/src/astro/routes/api/auth/oauth/[provider].ts +119 -0
- package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
- package/src/astro/routes/api/auth/passkey/index.ts +54 -0
- package/src/astro/routes/api/auth/passkey/options.ts +82 -0
- package/src/astro/routes/api/auth/passkey/register/options.ts +86 -0
- package/src/astro/routes/api/auth/passkey/register/verify.ts +115 -0
- package/src/astro/routes/api/auth/passkey/verify.ts +66 -0
- package/src/astro/routes/api/auth/signup/complete.ts +85 -0
- package/src/astro/routes/api/auth/signup/request.ts +77 -0
- package/src/astro/routes/api/auth/signup/verify.ts +53 -0
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +312 -0
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +101 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
- package/src/astro/routes/api/content/[collection]/index.ts +59 -0
- package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
- package/src/astro/routes/api/dashboard.ts +32 -0
- package/src/astro/routes/api/dev/emails.ts +36 -0
- package/src/astro/routes/api/import/probe.ts +47 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +510 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +283 -0
- package/src/astro/routes/api/import/wordpress/media.ts +338 -0
- package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
- package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +347 -0
- package/src/astro/routes/api/manifest.ts +62 -0
- package/src/astro/routes/api/mcp.ts +124 -0
- package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
- package/src/astro/routes/api/media/[id].ts +145 -0
- package/src/astro/routes/api/media/file/[key].ts +79 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
- package/src/astro/routes/api/media/providers/index.ts +30 -0
- package/src/astro/routes/api/media/upload-url.ts +137 -0
- package/src/astro/routes/api/media.ts +190 -0
- package/src/astro/routes/api/menus/[name]/items.ts +87 -0
- package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
- package/src/astro/routes/api/menus/[name].ts +65 -0
- package/src/astro/routes/api/menus/index.ts +47 -0
- package/src/astro/routes/api/oauth/authorize.ts +412 -0
- package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
- package/src/astro/routes/api/oauth/device/code.ts +51 -0
- package/src/astro/routes/api/oauth/device/token.ts +69 -0
- package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
- package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
- package/src/astro/routes/api/oauth/token.ts +184 -0
- package/src/astro/routes/api/openapi.json.ts +32 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
- package/src/astro/routes/api/redirects/404s/index.ts +72 -0
- package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
- package/src/astro/routes/api/redirects/[id].ts +84 -0
- package/src/astro/routes/api/redirects/index.ts +52 -0
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
- package/src/astro/routes/api/revisions/[revisionId]/restore.ts +58 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
- package/src/astro/routes/api/schema/collections/index.ts +47 -0
- package/src/astro/routes/api/schema/index.ts +109 -0
- package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
- package/src/astro/routes/api/schema/orphans/index.ts +26 -0
- package/src/astro/routes/api/search/enable.ts +64 -0
- package/src/astro/routes/api/search/index.ts +55 -0
- package/src/astro/routes/api/search/rebuild.ts +72 -0
- package/src/astro/routes/api/search/stats.ts +35 -0
- package/src/astro/routes/api/search/suggest.ts +53 -0
- package/src/astro/routes/api/sections/[slug].ts +84 -0
- package/src/astro/routes/api/sections/index.ts +52 -0
- package/src/astro/routes/api/settings/email.ts +150 -0
- package/src/astro/routes/api/settings.ts +67 -0
- package/src/astro/routes/api/setup/admin-verify.ts +100 -0
- package/src/astro/routes/api/setup/admin.ts +94 -0
- package/src/astro/routes/api/setup/dev-bypass.ts +199 -0
- package/src/astro/routes/api/setup/dev-reset.ts +40 -0
- package/src/astro/routes/api/setup/index.ts +126 -0
- package/src/astro/routes/api/setup/status.ts +122 -0
- package/src/astro/routes/api/snapshot.ts +75 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
- package/src/astro/routes/api/taxonomies/index.ts +59 -0
- package/src/astro/routes/api/themes/preview.ts +77 -0
- package/src/astro/routes/api/typegen.ts +114 -0
- package/src/astro/routes/api/well-known/auth.ts +68 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +44 -0
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +37 -0
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +68 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
- package/src/astro/routes/api/widget-areas/[name].ts +87 -0
- package/src/astro/routes/api/widget-areas/index.ts +99 -0
- package/src/astro/routes/api/widget-components.ts +22 -0
- package/src/astro/routes/robots.txt.ts +77 -0
- package/src/astro/routes/sitemap.xml.ts +97 -0
- package/src/astro/storage/adapters.ts +74 -0
- package/src/astro/storage/index.ts +19 -0
- package/src/astro/storage/types.ts +60 -0
- package/src/astro/types.ts +346 -0
- package/src/auth/api-tokens.ts +25 -0
- package/src/auth/challenge-store.ts +80 -0
- package/src/auth/mode.ts +96 -0
- package/src/auth/oauth-state-store.ts +96 -0
- package/src/auth/passkey-config.ts +27 -0
- package/src/auth/rate-limit.ts +158 -0
- package/src/auth/scopes.ts +33 -0
- package/src/auth/types.ts +104 -0
- package/src/aws-sdk.d.ts +100 -0
- package/src/bylines/index.ts +237 -0
- package/src/cleanup.ts +153 -0
- package/src/cli/client-factory.ts +100 -0
- package/src/cli/commands/auth.ts +46 -0
- package/src/cli/commands/bundle-utils.ts +247 -0
- package/src/cli/commands/bundle.ts +609 -0
- package/src/cli/commands/content.ts +442 -0
- package/src/cli/commands/dev.ts +191 -0
- package/src/cli/commands/doctor.ts +211 -0
- package/src/cli/commands/export-seed.ts +630 -0
- package/src/cli/commands/import/wordpress.ts +1056 -0
- package/src/cli/commands/init.ts +192 -0
- package/src/cli/commands/login.ts +547 -0
- package/src/cli/commands/media.ts +165 -0
- package/src/cli/commands/menu.ts +67 -0
- package/src/cli/commands/plugin-init.ts +291 -0
- package/src/cli/commands/plugin-validate.ts +31 -0
- package/src/cli/commands/plugin.ts +33 -0
- package/src/cli/commands/publish.ts +697 -0
- package/src/cli/commands/schema.ts +233 -0
- package/src/cli/commands/search-cmd.ts +54 -0
- package/src/cli/commands/seed.ts +286 -0
- package/src/cli/commands/taxonomy.ts +128 -0
- package/src/cli/commands/types.ts +68 -0
- package/src/cli/credentials.ts +236 -0
- package/src/cli/index.ts +70 -0
- package/src/cli/output.ts +75 -0
- package/src/cli/wxr/parser.ts +969 -0
- package/src/client/cf-access.ts +193 -0
- package/src/client/index.ts +854 -0
- package/src/client/portable-text.ts +413 -0
- package/src/client/transport.ts +200 -0
- package/src/comments/moderator.ts +46 -0
- package/src/comments/notifications.ts +144 -0
- package/src/comments/query.ts +105 -0
- package/src/comments/service.ts +213 -0
- package/src/components/Break.astro +45 -0
- package/src/components/Button.astro +71 -0
- package/src/components/Buttons.astro +49 -0
- package/src/components/Code.astro +59 -0
- package/src/components/Columns.astro +59 -0
- package/src/components/CommentForm.astro +315 -0
- package/src/components/Comments.astro +232 -0
- package/src/components/Cover.astro +128 -0
- package/src/components/EmDashBodyEnd.astro +32 -0
- package/src/components/EmDashBodyStart.astro +32 -0
- package/src/components/EmDashHead.astro +53 -0
- package/src/components/EmDashImage.astro +178 -0
- package/src/components/EmDashMedia.astro +167 -0
- package/src/components/Embed.astro +128 -0
- package/src/components/File.astro +122 -0
- package/src/components/Gallery.astro +93 -0
- package/src/components/HtmlBlock.astro +33 -0
- package/src/components/Image.astro +178 -0
- package/src/components/InlineEditor.astro +27 -0
- package/src/components/InlinePortableTextEditor.tsx +1905 -0
- package/src/components/LiveSearch.astro +614 -0
- package/src/components/PortableText.astro +51 -0
- package/src/components/Pullquote.astro +51 -0
- package/src/components/Table.astro +108 -0
- package/src/components/WidgetArea.astro +22 -0
- package/src/components/WidgetRenderer.astro +72 -0
- package/src/components/index.ts +116 -0
- package/src/components/marks/Link.astro +31 -0
- package/src/components/marks/StrikeThrough.astro +7 -0
- package/src/components/marks/Subscript.astro +7 -0
- package/src/components/marks/Superscript.astro +7 -0
- package/src/components/marks/Underline.astro +7 -0
- package/src/components/widgets/Archives.astro +65 -0
- package/src/components/widgets/Categories.astro +35 -0
- package/src/components/widgets/RecentPosts.astro +51 -0
- package/src/components/widgets/Search.astro +18 -0
- package/src/components/widgets/Tags.astro +38 -0
- package/src/content/converters/index.ts +9 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +385 -0
- package/src/content/converters/prosemirror-to-portable-text.ts +413 -0
- package/src/content/converters/types.ts +120 -0
- package/src/content/index.ts +5 -0
- package/src/database/connection.ts +67 -0
- package/src/database/dialect-helpers.ts +138 -0
- package/src/database/index.ts +5 -0
- package/src/database/migrations/001_initial.ts +170 -0
- package/src/database/migrations/002_media_status.ts +26 -0
- package/src/database/migrations/003_schema_registry.ts +79 -0
- package/src/database/migrations/004_plugins.ts +62 -0
- package/src/database/migrations/005_menus.ts +67 -0
- package/src/database/migrations/006_taxonomy_defs.ts +51 -0
- package/src/database/migrations/007_widgets.ts +42 -0
- package/src/database/migrations/008_auth.ts +194 -0
- package/src/database/migrations/009_user_disabled.ts +27 -0
- package/src/database/migrations/011_sections.ts +65 -0
- package/src/database/migrations/012_search.ts +25 -0
- package/src/database/migrations/013_scheduled_publishing.ts +51 -0
- package/src/database/migrations/014_draft_revisions.ts +72 -0
- package/src/database/migrations/015_indexes.ts +82 -0
- package/src/database/migrations/016_api_tokens.ts +89 -0
- package/src/database/migrations/017_authorization_codes.ts +45 -0
- package/src/database/migrations/018_seo.ts +56 -0
- package/src/database/migrations/019_i18n.ts +618 -0
- package/src/database/migrations/020_collection_url_pattern.ts +23 -0
- package/src/database/migrations/021_remove_section_categories.ts +43 -0
- package/src/database/migrations/022_marketplace_plugin_state.ts +46 -0
- package/src/database/migrations/023_plugin_metadata.ts +33 -0
- package/src/database/migrations/024_media_placeholders.ts +32 -0
- package/src/database/migrations/025_oauth_clients.ts +28 -0
- package/src/database/migrations/026_cron_tasks.ts +49 -0
- package/src/database/migrations/027_comments.ts +87 -0
- package/src/database/migrations/028_drop_author_url.ts +9 -0
- package/src/database/migrations/029_redirects.ts +67 -0
- package/src/database/migrations/030_widen_scheduled_index.ts +48 -0
- package/src/database/migrations/031_bylines.ts +90 -0
- package/src/database/migrations/032_rate_limits.ts +39 -0
- package/src/database/migrations/runner.ts +170 -0
- package/src/database/repositories/audit.ts +294 -0
- package/src/database/repositories/byline.ts +387 -0
- package/src/database/repositories/comment.ts +458 -0
- package/src/database/repositories/content.ts +1144 -0
- package/src/database/repositories/index.ts +30 -0
- package/src/database/repositories/media.ts +347 -0
- package/src/database/repositories/options.ts +150 -0
- package/src/database/repositories/plugin-storage.ts +373 -0
- package/src/database/repositories/redirect.ts +480 -0
- package/src/database/repositories/revision.ts +200 -0
- package/src/database/repositories/seo.ts +176 -0
- package/src/database/repositories/taxonomy.ts +294 -0
- package/src/database/repositories/types.ts +132 -0
- package/src/database/repositories/user.ts +258 -0
- package/src/database/transaction.ts +54 -0
- package/src/database/types.ts +501 -0
- package/src/database/validate.ts +138 -0
- package/src/db/adapters.ts +125 -0
- package/src/db/index.ts +37 -0
- package/src/db/libsql.ts +23 -0
- package/src/db/postgres.ts +30 -0
- package/src/db/sqlite.ts +27 -0
- package/src/emdash-runtime.ts +2096 -0
- package/src/fields/boolean.ts +34 -0
- package/src/fields/datetime.ts +44 -0
- package/src/fields/file.ts +41 -0
- package/src/fields/image.ts +34 -0
- package/src/fields/index.ts +42 -0
- package/src/fields/integer.ts +50 -0
- package/src/fields/json.ts +37 -0
- package/src/fields/multiselect.ts +48 -0
- package/src/fields/number.ts +52 -0
- package/src/fields/portable-text.ts +33 -0
- package/src/fields/reference.ts +29 -0
- package/src/fields/richtext.ts +31 -0
- package/src/fields/select.ts +46 -0
- package/src/fields/slug.ts +38 -0
- package/src/fields/text.ts +55 -0
- package/src/fields/textarea.ts +52 -0
- package/src/fields/types.ts +64 -0
- package/src/i18n/config.ts +68 -0
- package/src/import/index.ts +90 -0
- package/src/import/menus.ts +436 -0
- package/src/import/registry.ts +111 -0
- package/src/import/sections.ts +103 -0
- package/src/import/settings.ts +281 -0
- package/src/import/sources/wordpress-plugin.ts +641 -0
- package/src/import/sources/wordpress-rest.ts +191 -0
- package/src/import/sources/wxr.ts +330 -0
- package/src/import/ssrf.ts +260 -0
- package/src/import/types.ts +418 -0
- package/src/import/utils.ts +412 -0
- package/src/index.ts +481 -0
- package/src/loader.ts +770 -0
- package/src/mcp/server.ts +1463 -0
- package/src/media/index.ts +32 -0
- package/src/media/local-runtime.ts +213 -0
- package/src/media/local.ts +46 -0
- package/src/media/normalize.ts +190 -0
- package/src/media/placeholder.ts +150 -0
- package/src/media/provider-loader.ts +78 -0
- package/src/media/types.ts +279 -0
- package/src/menus/index.ts +324 -0
- package/src/menus/types.ts +112 -0
- package/src/page/context.ts +93 -0
- package/src/page/fragments.ts +89 -0
- package/src/page/index.ts +58 -0
- package/src/page/jsonld.ts +94 -0
- package/src/page/metadata.ts +185 -0
- package/src/page/seo-contributions.ts +136 -0
- package/src/plugin-utils.ts +80 -0
- package/src/plugins/adapt-sandbox-entry.ts +207 -0
- package/src/plugins/context.ts +833 -0
- package/src/plugins/cron.ts +361 -0
- package/src/plugins/define-plugin.ts +259 -0
- package/src/plugins/email-console.ts +73 -0
- package/src/plugins/email.ts +209 -0
- package/src/plugins/hooks.ts +1273 -0
- package/src/plugins/index.ts +193 -0
- package/src/plugins/manager.ts +595 -0
- package/src/plugins/manifest-schema.ts +230 -0
- package/src/plugins/marketplace.ts +460 -0
- package/src/plugins/request-meta.ts +139 -0
- package/src/plugins/routes.ts +302 -0
- package/src/plugins/sandbox/index.ts +18 -0
- package/src/plugins/sandbox/noop.ts +76 -0
- package/src/plugins/sandbox/types.ts +173 -0
- package/src/plugins/scheduler/node.ts +122 -0
- package/src/plugins/scheduler/piggyback.ts +71 -0
- package/src/plugins/scheduler/types.ts +27 -0
- package/src/plugins/state.ts +208 -0
- package/src/plugins/storage-indexes.ts +326 -0
- package/src/plugins/storage-query.ts +240 -0
- package/src/plugins/types.ts +1284 -0
- package/src/preview/helpers.ts +27 -0
- package/src/preview/index.ts +40 -0
- package/src/preview/tokens.ts +279 -0
- package/src/preview/urls.ts +118 -0
- package/src/query.ts +674 -0
- package/src/redirects/patterns.ts +224 -0
- package/src/request-context.ts +67 -0
- package/src/runtime.ts +21 -0
- package/src/schema/index.ts +29 -0
- package/src/schema/query.ts +44 -0
- package/src/schema/registry.ts +965 -0
- package/src/schema/types.ts +276 -0
- package/src/schema/zod-generator.ts +413 -0
- package/src/search/fts-manager.ts +452 -0
- package/src/search/index.ts +26 -0
- package/src/search/query.ts +396 -0
- package/src/search/text-extraction.ts +162 -0
- package/src/search/types.ts +114 -0
- package/src/sections/index.ts +226 -0
- package/src/sections/types.ts +86 -0
- package/src/seed/apply.ts +1141 -0
- package/src/seed/default.ts +86 -0
- package/src/seed/index.ts +28 -0
- package/src/seed/load.ts +35 -0
- package/src/seed/types.ts +341 -0
- package/src/seed/validate.ts +642 -0
- package/src/seo/index.ts +179 -0
- package/src/settings/index.ts +203 -0
- package/src/settings/types.ts +58 -0
- package/src/storage/index.ts +28 -0
- package/src/storage/local.ts +249 -0
- package/src/storage/s3.ts +263 -0
- package/src/storage/types.ts +204 -0
- package/src/taxonomies/index.ts +309 -0
- package/src/taxonomies/types.ts +61 -0
- package/src/ui.ts +75 -0
- package/src/utils/base64.ts +73 -0
- package/src/utils/hash.ts +36 -0
- package/src/utils/sanitize.ts +20 -0
- package/src/utils/slugify.ts +29 -0
- package/src/utils/url.ts +48 -0
- package/src/virtual-modules.d.ts +111 -0
- package/src/visual-editing/editable.ts +108 -0
- package/src/visual-editing/toolbar.ts +1229 -0
- package/src/widgets/components.ts +105 -0
- package/src/widgets/index.ts +131 -0
- package/src/widgets/types.ts +81 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace plugin handlers
|
|
3
|
+
*
|
|
4
|
+
* Business logic for installing, updating, uninstalling, and checking
|
|
5
|
+
* updates for marketplace plugins. Routes are thin wrappers around these.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Kysely } from "kysely";
|
|
9
|
+
|
|
10
|
+
import type { Database } from "../../database/types.js";
|
|
11
|
+
import { validatePluginIdentifier } from "../../database/validate.js";
|
|
12
|
+
import { pluginManifestSchema } from "../../plugins/manifest-schema.js";
|
|
13
|
+
import { normalizeManifestRoute } from "../../plugins/manifest-schema.js";
|
|
14
|
+
import {
|
|
15
|
+
createMarketplaceClient,
|
|
16
|
+
MarketplaceError,
|
|
17
|
+
MarketplaceUnavailableError,
|
|
18
|
+
type MarketplaceClient,
|
|
19
|
+
type MarketplacePluginDetail,
|
|
20
|
+
type MarketplaceSearchOpts,
|
|
21
|
+
type MarketplaceThemeSearchOpts,
|
|
22
|
+
type MarketplaceVersionSummary,
|
|
23
|
+
type PluginBundle,
|
|
24
|
+
} from "../../plugins/marketplace.js";
|
|
25
|
+
import type { SandboxRunner } from "../../plugins/sandbox/types.js";
|
|
26
|
+
import { PluginStateRepository } from "../../plugins/state.js";
|
|
27
|
+
import type { PluginManifest } from "../../plugins/types.js";
|
|
28
|
+
import { EmDashStorageError } from "../../storage/types.js";
|
|
29
|
+
import type { Storage } from "../../storage/types.js";
|
|
30
|
+
import type { ApiResult } from "../types.js";
|
|
31
|
+
|
|
32
|
+
// ── Types ──────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export interface MarketplaceInstallResult {
|
|
35
|
+
pluginId: string;
|
|
36
|
+
version: string;
|
|
37
|
+
capabilities: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MarketplaceUpdateResult {
|
|
41
|
+
pluginId: string;
|
|
42
|
+
oldVersion: string;
|
|
43
|
+
newVersion: string;
|
|
44
|
+
capabilityChanges: {
|
|
45
|
+
added: string[];
|
|
46
|
+
removed: string[];
|
|
47
|
+
};
|
|
48
|
+
routeVisibilityChanges?: {
|
|
49
|
+
newlyPublic: string[];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface MarketplaceUpdateCheck {
|
|
54
|
+
pluginId: string;
|
|
55
|
+
installed: string;
|
|
56
|
+
latest: string;
|
|
57
|
+
hasUpdate: boolean;
|
|
58
|
+
hasCapabilityChanges: boolean;
|
|
59
|
+
capabilityChanges?: {
|
|
60
|
+
added: string[];
|
|
61
|
+
removed: string[];
|
|
62
|
+
};
|
|
63
|
+
hasRouteVisibilityChanges: boolean;
|
|
64
|
+
routeVisibilityChanges?: {
|
|
65
|
+
newlyPublic: string[];
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface MarketplaceUninstallResult {
|
|
70
|
+
pluginId: string;
|
|
71
|
+
dataDeleted: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/** Semver-like pattern: digits, dots, hyphens, plus signs (e.g. 1.0.0, 1.0.0-beta.1) */
|
|
77
|
+
const VERSION_PATTERN = /^[a-z0-9][a-z0-9._+-]*$/i;
|
|
78
|
+
|
|
79
|
+
function validateVersion(version: string): void {
|
|
80
|
+
if (version.includes("..")) throw new Error("Invalid version format");
|
|
81
|
+
if (!VERSION_PATTERN.test(version)) {
|
|
82
|
+
throw new Error("Invalid version format");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getClient(marketplaceUrl: string | undefined): MarketplaceClient | null {
|
|
87
|
+
if (!marketplaceUrl) return null;
|
|
88
|
+
return createMarketplaceClient(marketplaceUrl);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function diffCapabilities(
|
|
92
|
+
oldCaps: string[],
|
|
93
|
+
newCaps: string[],
|
|
94
|
+
): { added: string[]; removed: string[] } {
|
|
95
|
+
const oldSet = new Set(oldCaps);
|
|
96
|
+
const newSet = new Set(newCaps);
|
|
97
|
+
return {
|
|
98
|
+
added: newCaps.filter((c) => !oldSet.has(c)),
|
|
99
|
+
removed: oldCaps.filter((c) => !newSet.has(c)),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Diff route visibility between two manifests.
|
|
105
|
+
* Returns routes that changed from private to public (newly exposed).
|
|
106
|
+
*/
|
|
107
|
+
function diffRouteVisibility(
|
|
108
|
+
oldManifest: PluginManifest | undefined,
|
|
109
|
+
newManifest: PluginManifest,
|
|
110
|
+
): { newlyPublic: string[] } {
|
|
111
|
+
const oldPublicRoutes = new Set<string>();
|
|
112
|
+
if (oldManifest) {
|
|
113
|
+
for (const entry of oldManifest.routes) {
|
|
114
|
+
const normalized = normalizeManifestRoute(entry);
|
|
115
|
+
if (normalized.public === true) {
|
|
116
|
+
oldPublicRoutes.add(normalized.name);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const newlyPublic: string[] = [];
|
|
122
|
+
for (const entry of newManifest.routes) {
|
|
123
|
+
const normalized = normalizeManifestRoute(entry);
|
|
124
|
+
if (normalized.public === true && !oldPublicRoutes.has(normalized.name)) {
|
|
125
|
+
newlyPublic.push(normalized.name);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { newlyPublic };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function resolveVersionMetadata(
|
|
133
|
+
client: MarketplaceClient,
|
|
134
|
+
pluginId: string,
|
|
135
|
+
pluginDetail: MarketplacePluginDetail,
|
|
136
|
+
version: string,
|
|
137
|
+
): Promise<MarketplaceVersionSummary | null> {
|
|
138
|
+
if (pluginDetail.latestVersion?.version === version) {
|
|
139
|
+
return {
|
|
140
|
+
version: pluginDetail.latestVersion.version,
|
|
141
|
+
minEmDashVersion: pluginDetail.latestVersion.minEmDashVersion,
|
|
142
|
+
bundleSize: pluginDetail.latestVersion.bundleSize,
|
|
143
|
+
checksum: pluginDetail.latestVersion.checksum,
|
|
144
|
+
changelog: pluginDetail.latestVersion.changelog,
|
|
145
|
+
capabilities: pluginDetail.latestVersion.capabilities,
|
|
146
|
+
status: pluginDetail.latestVersion.status,
|
|
147
|
+
auditVerdict: pluginDetail.latestVersion.audit?.verdict ?? null,
|
|
148
|
+
imageAuditVerdict: pluginDetail.latestVersion.imageAudit?.verdict ?? null,
|
|
149
|
+
publishedAt: pluginDetail.latestVersion.publishedAt,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const versions = await client.getVersions(pluginId);
|
|
154
|
+
return versions.find((v) => v.version === version) ?? null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function validateBundleIdentity(
|
|
158
|
+
bundle: PluginBundle,
|
|
159
|
+
pluginId: string,
|
|
160
|
+
version: string,
|
|
161
|
+
): ApiResult<never> | null {
|
|
162
|
+
if (bundle.manifest.id !== pluginId) {
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
error: {
|
|
166
|
+
code: "MANIFEST_MISMATCH",
|
|
167
|
+
message: `Bundle manifest ID (${bundle.manifest.id}) does not match requested plugin (${pluginId})`,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (bundle.manifest.version !== version) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: {
|
|
176
|
+
code: "MANIFEST_VERSION_MISMATCH",
|
|
177
|
+
message: `Bundle manifest version (${bundle.manifest.version}) does not match requested version (${version})`,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Store a plugin bundle's files in site-local R2 storage */
|
|
186
|
+
async function storeBundleInR2(
|
|
187
|
+
storage: Storage,
|
|
188
|
+
pluginId: string,
|
|
189
|
+
version: string,
|
|
190
|
+
bundle: PluginBundle,
|
|
191
|
+
): Promise<void> {
|
|
192
|
+
validatePluginIdentifier(pluginId, "plugin ID");
|
|
193
|
+
validateVersion(version);
|
|
194
|
+
const prefix = `marketplace/${pluginId}/${version}`;
|
|
195
|
+
|
|
196
|
+
// Store manifest
|
|
197
|
+
await storage.upload({
|
|
198
|
+
key: `${prefix}/manifest.json`,
|
|
199
|
+
body: new TextEncoder().encode(JSON.stringify(bundle.manifest)),
|
|
200
|
+
contentType: "application/json",
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Store backend code
|
|
204
|
+
await storage.upload({
|
|
205
|
+
key: `${prefix}/backend.js`,
|
|
206
|
+
body: new TextEncoder().encode(bundle.backendCode),
|
|
207
|
+
contentType: "application/javascript",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Store admin code if present
|
|
211
|
+
if (bundle.adminCode) {
|
|
212
|
+
await storage.upload({
|
|
213
|
+
key: `${prefix}/admin.js`,
|
|
214
|
+
body: new TextEncoder().encode(bundle.adminCode),
|
|
215
|
+
contentType: "application/javascript",
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Read a ReadableStream to string */
|
|
221
|
+
async function streamToText(stream: ReadableStream<Uint8Array>): Promise<string> {
|
|
222
|
+
return new Response(stream).text();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Load a plugin bundle from site-local R2 storage */
|
|
226
|
+
export async function loadBundleFromR2(
|
|
227
|
+
storage: Storage,
|
|
228
|
+
pluginId: string,
|
|
229
|
+
version: string,
|
|
230
|
+
): Promise<{ manifest: PluginManifest; backendCode: string; adminCode?: string } | null> {
|
|
231
|
+
validatePluginIdentifier(pluginId, "plugin ID");
|
|
232
|
+
validateVersion(version);
|
|
233
|
+
const prefix = `marketplace/${pluginId}/${version}`;
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const manifestResult = await storage.download(`${prefix}/manifest.json`);
|
|
237
|
+
const backendResult = await storage.download(`${prefix}/backend.js`);
|
|
238
|
+
|
|
239
|
+
const manifestText = await streamToText(manifestResult.body);
|
|
240
|
+
const backendCode = await streamToText(backendResult.body);
|
|
241
|
+
const parsed: unknown = JSON.parse(manifestText);
|
|
242
|
+
const result = pluginManifestSchema.safeParse(parsed);
|
|
243
|
+
if (!result.success) return null;
|
|
244
|
+
// Elements are validated as unknown[] by Zod; cast to PluginManifest
|
|
245
|
+
// for the Element[] type (Block Kit validation happens at render time).
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Zod types elements as unknown[]; Element type validated at render time
|
|
247
|
+
const manifest = result.data as unknown as PluginManifest;
|
|
248
|
+
|
|
249
|
+
// Try to load admin code (optional)
|
|
250
|
+
let adminCode: string | undefined;
|
|
251
|
+
try {
|
|
252
|
+
const adminResult = await storage.download(`${prefix}/admin.js`);
|
|
253
|
+
adminCode = await streamToText(adminResult.body);
|
|
254
|
+
} catch {
|
|
255
|
+
// admin.js is optional
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { manifest, backendCode, adminCode };
|
|
259
|
+
} catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Delete a plugin bundle from site-local R2 storage */
|
|
265
|
+
async function deleteBundleFromR2(
|
|
266
|
+
storage: Storage,
|
|
267
|
+
pluginId: string,
|
|
268
|
+
version: string,
|
|
269
|
+
): Promise<void> {
|
|
270
|
+
validatePluginIdentifier(pluginId, "plugin ID");
|
|
271
|
+
validateVersion(version);
|
|
272
|
+
const prefix = `marketplace/${pluginId}/${version}`;
|
|
273
|
+
const files = ["manifest.json", "backend.js", "admin.js"];
|
|
274
|
+
|
|
275
|
+
for (const file of files) {
|
|
276
|
+
try {
|
|
277
|
+
await storage.delete(`${prefix}/${file}`);
|
|
278
|
+
} catch {
|
|
279
|
+
// Ignore missing files
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── Install ────────────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
export async function handleMarketplaceInstall(
|
|
287
|
+
db: Kysely<Database>,
|
|
288
|
+
storage: Storage | null,
|
|
289
|
+
sandboxRunner: SandboxRunner | null,
|
|
290
|
+
marketplaceUrl: string | undefined,
|
|
291
|
+
pluginId: string,
|
|
292
|
+
opts?: { version?: string; configuredPluginIds?: Set<string> },
|
|
293
|
+
): Promise<ApiResult<MarketplaceInstallResult>> {
|
|
294
|
+
const client = getClient(marketplaceUrl);
|
|
295
|
+
if (!client) {
|
|
296
|
+
return {
|
|
297
|
+
success: false,
|
|
298
|
+
error: {
|
|
299
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
300
|
+
message: "Marketplace is not configured",
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (!storage) {
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
error: {
|
|
309
|
+
code: "STORAGE_NOT_CONFIGURED",
|
|
310
|
+
message: "Storage is required for marketplace plugin installation",
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!sandboxRunner || !sandboxRunner.isAvailable()) {
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
error: {
|
|
319
|
+
code: "SANDBOX_NOT_AVAILABLE",
|
|
320
|
+
message: "Sandbox runner is required for marketplace plugins",
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
// Check if already installed
|
|
327
|
+
const stateRepo = new PluginStateRepository(db);
|
|
328
|
+
const existing = await stateRepo.get(pluginId);
|
|
329
|
+
if (existing && existing.source === "marketplace") {
|
|
330
|
+
return {
|
|
331
|
+
success: false,
|
|
332
|
+
error: {
|
|
333
|
+
code: "ALREADY_INSTALLED",
|
|
334
|
+
message: `Plugin ${pluginId} is already installed`,
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Block installation if a configured (trusted) plugin with the same ID exists.
|
|
340
|
+
// Without this check, the sandboxed plugin could shadow the trusted plugin's
|
|
341
|
+
// route handlers while auth decisions are made against the trusted plugin's metadata.
|
|
342
|
+
if (opts?.configuredPluginIds?.has(pluginId)) {
|
|
343
|
+
return {
|
|
344
|
+
success: false,
|
|
345
|
+
error: {
|
|
346
|
+
code: "PLUGIN_ID_CONFLICT",
|
|
347
|
+
message: `Cannot install marketplace plugin "${pluginId}" — a configured plugin with the same ID already exists`,
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Fetch plugin detail from marketplace
|
|
353
|
+
const pluginDetail = await client.getPlugin(pluginId);
|
|
354
|
+
const version = opts?.version ?? pluginDetail.latestVersion?.version;
|
|
355
|
+
if (!version) {
|
|
356
|
+
return {
|
|
357
|
+
success: false,
|
|
358
|
+
error: {
|
|
359
|
+
code: "NO_VERSION",
|
|
360
|
+
message: `No published versions found for plugin ${pluginId}`,
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const versionMetadata = await resolveVersionMetadata(client, pluginId, pluginDetail, version);
|
|
366
|
+
if (!versionMetadata) {
|
|
367
|
+
return {
|
|
368
|
+
success: false,
|
|
369
|
+
error: {
|
|
370
|
+
code: "NO_VERSION",
|
|
371
|
+
message: `Version ${version} was not found for plugin ${pluginId}`,
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Block installation of plugins that haven't passed audit.
|
|
377
|
+
// Both "fail" (explicitly malicious) and "warn" (audit error or
|
|
378
|
+
// inconclusive) are non-installable — only "pass" or null (no audit
|
|
379
|
+
// ran) are allowed through.
|
|
380
|
+
if (versionMetadata.auditVerdict === "fail" || versionMetadata.auditVerdict === "warn") {
|
|
381
|
+
return {
|
|
382
|
+
success: false,
|
|
383
|
+
error: {
|
|
384
|
+
code: "AUDIT_FAILED",
|
|
385
|
+
message:
|
|
386
|
+
versionMetadata.auditVerdict === "fail"
|
|
387
|
+
? "Plugin failed security audit and cannot be installed"
|
|
388
|
+
: "Plugin audit was inconclusive and cannot be installed until reviewed",
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Download and extract bundle
|
|
394
|
+
const bundle = await client.downloadBundle(pluginId, version);
|
|
395
|
+
|
|
396
|
+
// Verify checksum matches marketplace-published checksum
|
|
397
|
+
if (versionMetadata.checksum && bundle.checksum !== versionMetadata.checksum) {
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
error: {
|
|
401
|
+
code: "CHECKSUM_MISMATCH",
|
|
402
|
+
message: "Bundle checksum does not match marketplace record. Download may be corrupted.",
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const bundleIdentityError = validateBundleIdentity(bundle, pluginId, version);
|
|
408
|
+
if (bundleIdentityError) return bundleIdentityError;
|
|
409
|
+
|
|
410
|
+
// Store bundle in site-local R2
|
|
411
|
+
await storeBundleInR2(storage, pluginId, version, bundle);
|
|
412
|
+
|
|
413
|
+
// Write plugin state
|
|
414
|
+
await stateRepo.upsert(pluginId, version, "active", {
|
|
415
|
+
source: "marketplace",
|
|
416
|
+
marketplaceVersion: version,
|
|
417
|
+
displayName: pluginDetail.name,
|
|
418
|
+
description: pluginDetail.description ?? undefined,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Fire-and-forget install stat
|
|
422
|
+
client.reportInstall(pluginId, version).catch(() => {
|
|
423
|
+
// Intentional: never fails the install
|
|
424
|
+
});
|
|
425
|
+
return {
|
|
426
|
+
success: true,
|
|
427
|
+
data: {
|
|
428
|
+
pluginId,
|
|
429
|
+
version,
|
|
430
|
+
capabilities: bundle.manifest.capabilities,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
} catch (err) {
|
|
434
|
+
if (err instanceof MarketplaceUnavailableError) {
|
|
435
|
+
return {
|
|
436
|
+
success: false,
|
|
437
|
+
error: {
|
|
438
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
439
|
+
message: "Plugin marketplace is currently unavailable",
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
if (err instanceof MarketplaceError) {
|
|
444
|
+
return {
|
|
445
|
+
success: false,
|
|
446
|
+
error: {
|
|
447
|
+
code: err.code ?? "MARKETPLACE_ERROR",
|
|
448
|
+
message: err.message,
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
if (err instanceof EmDashStorageError) {
|
|
453
|
+
return {
|
|
454
|
+
success: false,
|
|
455
|
+
error: {
|
|
456
|
+
code: err.code ?? "STORAGE_ERROR",
|
|
457
|
+
message: "Storage error while installing plugin",
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
462
|
+
const code = (err as { code?: unknown }).code;
|
|
463
|
+
if (typeof code === "string" && code.trim()) {
|
|
464
|
+
return {
|
|
465
|
+
success: false,
|
|
466
|
+
error: {
|
|
467
|
+
code,
|
|
468
|
+
message: "Failed to install plugin from marketplace",
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
console.error("Failed to install marketplace plugin:", err);
|
|
474
|
+
return {
|
|
475
|
+
success: false,
|
|
476
|
+
error: {
|
|
477
|
+
code: "INSTALL_FAILED",
|
|
478
|
+
message: "Failed to install plugin from marketplace",
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ── Update ─────────────────────────────────────────────────────────
|
|
485
|
+
|
|
486
|
+
export async function handleMarketplaceUpdate(
|
|
487
|
+
db: Kysely<Database>,
|
|
488
|
+
storage: Storage | null,
|
|
489
|
+
sandboxRunner: SandboxRunner | null,
|
|
490
|
+
marketplaceUrl: string | undefined,
|
|
491
|
+
pluginId: string,
|
|
492
|
+
opts?: {
|
|
493
|
+
version?: string;
|
|
494
|
+
confirmCapabilityChanges?: boolean;
|
|
495
|
+
confirmRouteVisibilityChanges?: boolean;
|
|
496
|
+
},
|
|
497
|
+
): Promise<ApiResult<MarketplaceUpdateResult>> {
|
|
498
|
+
const client = getClient(marketplaceUrl);
|
|
499
|
+
if (!client) {
|
|
500
|
+
return {
|
|
501
|
+
success: false,
|
|
502
|
+
error: { code: "MARKETPLACE_NOT_CONFIGURED", message: "Marketplace is not configured" },
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
if (!storage) {
|
|
506
|
+
return {
|
|
507
|
+
success: false,
|
|
508
|
+
error: { code: "STORAGE_NOT_CONFIGURED", message: "Storage is required" },
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
if (!sandboxRunner || !sandboxRunner.isAvailable()) {
|
|
512
|
+
return {
|
|
513
|
+
success: false,
|
|
514
|
+
error: { code: "SANDBOX_NOT_AVAILABLE", message: "Sandbox runner is required" },
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
const stateRepo = new PluginStateRepository(db);
|
|
520
|
+
const existing = await stateRepo.get(pluginId);
|
|
521
|
+
if (!existing || existing.source !== "marketplace") {
|
|
522
|
+
return {
|
|
523
|
+
success: false,
|
|
524
|
+
error: {
|
|
525
|
+
code: "NOT_FOUND",
|
|
526
|
+
message: `No marketplace plugin found: ${pluginId}`,
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const oldVersion = existing.marketplaceVersion ?? existing.version;
|
|
532
|
+
|
|
533
|
+
// Get target version
|
|
534
|
+
const pluginDetail = await client.getPlugin(pluginId);
|
|
535
|
+
const newVersion = opts?.version ?? pluginDetail.latestVersion?.version;
|
|
536
|
+
if (!newVersion) {
|
|
537
|
+
return {
|
|
538
|
+
success: false,
|
|
539
|
+
error: { code: "NO_VERSION", message: "No newer version available" },
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (newVersion === oldVersion) {
|
|
544
|
+
return {
|
|
545
|
+
success: false,
|
|
546
|
+
error: { code: "ALREADY_UP_TO_DATE", message: "Plugin is already up to date" },
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const versionMetadata = await resolveVersionMetadata(
|
|
551
|
+
client,
|
|
552
|
+
pluginId,
|
|
553
|
+
pluginDetail,
|
|
554
|
+
newVersion,
|
|
555
|
+
);
|
|
556
|
+
if (!versionMetadata) {
|
|
557
|
+
return {
|
|
558
|
+
success: false,
|
|
559
|
+
error: {
|
|
560
|
+
code: "NO_VERSION",
|
|
561
|
+
message: `Version ${newVersion} was not found for plugin ${pluginId}`,
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Download new bundle
|
|
567
|
+
const bundle = await client.downloadBundle(pluginId, newVersion);
|
|
568
|
+
|
|
569
|
+
// Verify checksum matches marketplace-published checksum for this version
|
|
570
|
+
if (versionMetadata.checksum && bundle.checksum !== versionMetadata.checksum) {
|
|
571
|
+
return {
|
|
572
|
+
success: false,
|
|
573
|
+
error: {
|
|
574
|
+
code: "CHECKSUM_MISMATCH",
|
|
575
|
+
message: "Bundle checksum does not match marketplace record. Download may be corrupted.",
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const bundleIdentityError = validateBundleIdentity(bundle, pluginId, newVersion);
|
|
581
|
+
if (bundleIdentityError) return bundleIdentityError;
|
|
582
|
+
|
|
583
|
+
// Diff capabilities and route visibility against old version
|
|
584
|
+
const oldBundle = await loadBundleFromR2(storage, pluginId, oldVersion);
|
|
585
|
+
const oldCaps = oldBundle?.manifest.capabilities ?? [];
|
|
586
|
+
const capabilityChanges = diffCapabilities(oldCaps, bundle.manifest.capabilities);
|
|
587
|
+
const hasEscalation = capabilityChanges.added.length > 0;
|
|
588
|
+
|
|
589
|
+
// If capabilities escalated, require explicit confirmation
|
|
590
|
+
if (hasEscalation && !opts?.confirmCapabilityChanges) {
|
|
591
|
+
return {
|
|
592
|
+
success: false,
|
|
593
|
+
error: {
|
|
594
|
+
code: "CAPABILITY_ESCALATION",
|
|
595
|
+
message: "Plugin update requires new capabilities",
|
|
596
|
+
details: { capabilityChanges },
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Diff route visibility — routes going from private to public are a
|
|
602
|
+
// security-sensitive change that exposes unauthenticated endpoints.
|
|
603
|
+
const routeVisibilityChanges = diffRouteVisibility(oldBundle?.manifest, bundle.manifest);
|
|
604
|
+
const hasNewPublicRoutes = routeVisibilityChanges.newlyPublic.length > 0;
|
|
605
|
+
|
|
606
|
+
if (hasNewPublicRoutes && !opts?.confirmRouteVisibilityChanges) {
|
|
607
|
+
return {
|
|
608
|
+
success: false,
|
|
609
|
+
error: {
|
|
610
|
+
code: "ROUTE_VISIBILITY_ESCALATION",
|
|
611
|
+
message: "Plugin update exposes new public (unauthenticated) routes",
|
|
612
|
+
details: { routeVisibilityChanges, capabilityChanges },
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Store new bundle
|
|
618
|
+
await storeBundleInR2(storage, pluginId, newVersion, bundle);
|
|
619
|
+
|
|
620
|
+
// Update state
|
|
621
|
+
await stateRepo.upsert(pluginId, newVersion, "active", {
|
|
622
|
+
source: "marketplace",
|
|
623
|
+
marketplaceVersion: newVersion,
|
|
624
|
+
displayName: pluginDetail.name,
|
|
625
|
+
description: pluginDetail.description ?? undefined,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Clean up old bundle from R2 (best-effort)
|
|
629
|
+
deleteBundleFromR2(storage, pluginId, oldVersion).catch(() => {});
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
success: true,
|
|
633
|
+
data: {
|
|
634
|
+
pluginId,
|
|
635
|
+
oldVersion,
|
|
636
|
+
newVersion,
|
|
637
|
+
capabilityChanges,
|
|
638
|
+
routeVisibilityChanges: hasNewPublicRoutes ? routeVisibilityChanges : undefined,
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
} catch (err) {
|
|
642
|
+
if (err instanceof MarketplaceUnavailableError) {
|
|
643
|
+
return {
|
|
644
|
+
success: false,
|
|
645
|
+
error: { code: "MARKETPLACE_UNAVAILABLE", message: "Marketplace is unavailable" },
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (err instanceof MarketplaceError) {
|
|
649
|
+
return {
|
|
650
|
+
success: false,
|
|
651
|
+
error: { code: err.code ?? "MARKETPLACE_ERROR", message: err.message },
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
console.error("Failed to update marketplace plugin:", err);
|
|
655
|
+
return {
|
|
656
|
+
success: false,
|
|
657
|
+
error: { code: "UPDATE_FAILED", message: "Failed to update plugin" },
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// ── Uninstall ──────────────────────────────────────────────────────
|
|
663
|
+
|
|
664
|
+
export async function handleMarketplaceUninstall(
|
|
665
|
+
db: Kysely<Database>,
|
|
666
|
+
storage: Storage | null,
|
|
667
|
+
pluginId: string,
|
|
668
|
+
opts?: { deleteData?: boolean },
|
|
669
|
+
): Promise<ApiResult<MarketplaceUninstallResult>> {
|
|
670
|
+
try {
|
|
671
|
+
const stateRepo = new PluginStateRepository(db);
|
|
672
|
+
const existing = await stateRepo.get(pluginId);
|
|
673
|
+
if (!existing || existing.source !== "marketplace") {
|
|
674
|
+
return {
|
|
675
|
+
success: false,
|
|
676
|
+
error: {
|
|
677
|
+
code: "NOT_FOUND",
|
|
678
|
+
message: `No marketplace plugin found: ${pluginId}`,
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const version = existing.marketplaceVersion ?? existing.version;
|
|
684
|
+
|
|
685
|
+
// Delete bundle from site R2
|
|
686
|
+
if (storage) {
|
|
687
|
+
await deleteBundleFromR2(storage, pluginId, version);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Optionally delete plugin storage data
|
|
691
|
+
let dataDeleted = false;
|
|
692
|
+
if (opts?.deleteData) {
|
|
693
|
+
try {
|
|
694
|
+
await db.deleteFrom("_plugin_storage").where("plugin_id", "=", pluginId).execute();
|
|
695
|
+
dataDeleted = true;
|
|
696
|
+
} catch {
|
|
697
|
+
// Plugin storage table may not have data for this plugin
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Delete state row
|
|
702
|
+
await stateRepo.delete(pluginId);
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
success: true,
|
|
706
|
+
data: { pluginId, dataDeleted },
|
|
707
|
+
};
|
|
708
|
+
} catch (err) {
|
|
709
|
+
console.error("Failed to uninstall marketplace plugin:", err);
|
|
710
|
+
return {
|
|
711
|
+
success: false,
|
|
712
|
+
error: {
|
|
713
|
+
code: "UNINSTALL_FAILED",
|
|
714
|
+
message: "Failed to uninstall plugin",
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// ── Update check ───────────────────────────────────────────────────
|
|
721
|
+
|
|
722
|
+
export async function handleMarketplaceUpdateCheck(
|
|
723
|
+
db: Kysely<Database>,
|
|
724
|
+
marketplaceUrl: string | undefined,
|
|
725
|
+
): Promise<ApiResult<{ items: MarketplaceUpdateCheck[] }>> {
|
|
726
|
+
const client = getClient(marketplaceUrl);
|
|
727
|
+
if (!client) {
|
|
728
|
+
return {
|
|
729
|
+
success: false,
|
|
730
|
+
error: { code: "MARKETPLACE_NOT_CONFIGURED", message: "Marketplace is not configured" },
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
try {
|
|
735
|
+
const stateRepo = new PluginStateRepository(db);
|
|
736
|
+
const marketplacePlugins = await stateRepo.getMarketplacePlugins();
|
|
737
|
+
|
|
738
|
+
const items: MarketplaceUpdateCheck[] = [];
|
|
739
|
+
|
|
740
|
+
for (const plugin of marketplacePlugins) {
|
|
741
|
+
try {
|
|
742
|
+
const detail = await client.getPlugin(plugin.pluginId);
|
|
743
|
+
const latest = detail.latestVersion?.version;
|
|
744
|
+
const installed = plugin.marketplaceVersion ?? plugin.version;
|
|
745
|
+
|
|
746
|
+
if (!latest) continue;
|
|
747
|
+
|
|
748
|
+
const hasUpdate = latest !== installed;
|
|
749
|
+
let capabilityChanges: { added: string[]; removed: string[] } | undefined;
|
|
750
|
+
let hasCapabilityChanges = false;
|
|
751
|
+
|
|
752
|
+
if (hasUpdate && detail.latestVersion) {
|
|
753
|
+
const oldCaps = detail.capabilities ?? [];
|
|
754
|
+
const newCaps = detail.latestVersion.capabilities ?? [];
|
|
755
|
+
capabilityChanges = diffCapabilities(oldCaps, newCaps);
|
|
756
|
+
hasCapabilityChanges =
|
|
757
|
+
capabilityChanges.added.length > 0 || capabilityChanges.removed.length > 0;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
items.push({
|
|
761
|
+
pluginId: plugin.pluginId,
|
|
762
|
+
installed,
|
|
763
|
+
latest: latest ?? installed,
|
|
764
|
+
hasUpdate,
|
|
765
|
+
hasCapabilityChanges,
|
|
766
|
+
capabilityChanges: hasCapabilityChanges ? capabilityChanges : undefined,
|
|
767
|
+
// Route visibility changes require downloading both bundles to compare
|
|
768
|
+
// manifests, which is too expensive for a preview check. The actual
|
|
769
|
+
// enforcement happens at update time in handleMarketplaceUpdate.
|
|
770
|
+
hasRouteVisibilityChanges: false,
|
|
771
|
+
});
|
|
772
|
+
} catch (err) {
|
|
773
|
+
// Skip plugins that can't be checked (marketplace down, plugin delisted)
|
|
774
|
+
console.warn(`Failed to check updates for ${plugin.pluginId}:`, err);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return { success: true, data: { items } };
|
|
779
|
+
} catch (err) {
|
|
780
|
+
if (err instanceof MarketplaceUnavailableError) {
|
|
781
|
+
return {
|
|
782
|
+
success: false,
|
|
783
|
+
error: { code: "MARKETPLACE_UNAVAILABLE", message: "Marketplace is unavailable" },
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
console.error("Failed to check marketplace updates:", err);
|
|
787
|
+
return {
|
|
788
|
+
success: false,
|
|
789
|
+
error: { code: "UPDATE_CHECK_FAILED", message: "Failed to check for updates" },
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// ── Proxy ──────────────────────────────────────────────────────────
|
|
795
|
+
|
|
796
|
+
export async function handleMarketplaceSearch(
|
|
797
|
+
marketplaceUrl: string | undefined,
|
|
798
|
+
query?: string,
|
|
799
|
+
opts?: MarketplaceSearchOpts,
|
|
800
|
+
): Promise<ApiResult<unknown>> {
|
|
801
|
+
const client = getClient(marketplaceUrl);
|
|
802
|
+
if (!client) {
|
|
803
|
+
return {
|
|
804
|
+
success: false,
|
|
805
|
+
error: { code: "MARKETPLACE_NOT_CONFIGURED", message: "Marketplace is not configured" },
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
try {
|
|
810
|
+
const result = await client.search(query, opts);
|
|
811
|
+
return { success: true, data: result };
|
|
812
|
+
} catch (err) {
|
|
813
|
+
if (err instanceof MarketplaceUnavailableError) {
|
|
814
|
+
return {
|
|
815
|
+
success: false,
|
|
816
|
+
error: { code: "MARKETPLACE_UNAVAILABLE", message: "Marketplace is unavailable" },
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
console.error("Failed to search marketplace:", err);
|
|
820
|
+
return {
|
|
821
|
+
success: false,
|
|
822
|
+
error: { code: "SEARCH_FAILED", message: "Failed to search marketplace" },
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
export async function handleMarketplaceGetPlugin(
|
|
828
|
+
marketplaceUrl: string | undefined,
|
|
829
|
+
pluginId: string,
|
|
830
|
+
): Promise<ApiResult<unknown>> {
|
|
831
|
+
const client = getClient(marketplaceUrl);
|
|
832
|
+
if (!client) {
|
|
833
|
+
return {
|
|
834
|
+
success: false,
|
|
835
|
+
error: { code: "MARKETPLACE_NOT_CONFIGURED", message: "Marketplace is not configured" },
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
try {
|
|
840
|
+
const result = await client.getPlugin(pluginId);
|
|
841
|
+
return { success: true, data: result };
|
|
842
|
+
} catch (err) {
|
|
843
|
+
if (err instanceof MarketplaceError && err.status === 404) {
|
|
844
|
+
return {
|
|
845
|
+
success: false,
|
|
846
|
+
error: { code: "NOT_FOUND", message: `Plugin not found: ${pluginId}` },
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
if (err instanceof MarketplaceUnavailableError) {
|
|
850
|
+
return {
|
|
851
|
+
success: false,
|
|
852
|
+
error: { code: "MARKETPLACE_UNAVAILABLE", message: "Marketplace is unavailable" },
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
console.error("Failed to get marketplace plugin:", err);
|
|
856
|
+
return {
|
|
857
|
+
success: false,
|
|
858
|
+
error: { code: "GET_PLUGIN_FAILED", message: "Failed to get plugin details" },
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// ── Theme proxy handlers ──────────────────────────────────────────
|
|
864
|
+
|
|
865
|
+
export async function handleThemeSearch(
|
|
866
|
+
marketplaceUrl: string | undefined,
|
|
867
|
+
query?: string,
|
|
868
|
+
opts?: MarketplaceThemeSearchOpts,
|
|
869
|
+
): Promise<ApiResult<unknown>> {
|
|
870
|
+
const client = getClient(marketplaceUrl);
|
|
871
|
+
if (!client) {
|
|
872
|
+
return {
|
|
873
|
+
success: false,
|
|
874
|
+
error: { code: "MARKETPLACE_NOT_CONFIGURED", message: "Marketplace is not configured" },
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
try {
|
|
879
|
+
const result = await client.searchThemes(query, opts);
|
|
880
|
+
return { success: true, data: result };
|
|
881
|
+
} catch (err) {
|
|
882
|
+
if (err instanceof MarketplaceUnavailableError) {
|
|
883
|
+
return {
|
|
884
|
+
success: false,
|
|
885
|
+
error: { code: "MARKETPLACE_UNAVAILABLE", message: "Marketplace is unavailable" },
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
console.error("Failed to search themes:", err);
|
|
889
|
+
return {
|
|
890
|
+
success: false,
|
|
891
|
+
error: { code: "THEME_SEARCH_FAILED", message: "Failed to search themes" },
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
export async function handleThemeGetDetail(
|
|
897
|
+
marketplaceUrl: string | undefined,
|
|
898
|
+
themeId: string,
|
|
899
|
+
): Promise<ApiResult<unknown>> {
|
|
900
|
+
const client = getClient(marketplaceUrl);
|
|
901
|
+
if (!client) {
|
|
902
|
+
return {
|
|
903
|
+
success: false,
|
|
904
|
+
error: { code: "MARKETPLACE_NOT_CONFIGURED", message: "Marketplace is not configured" },
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
try {
|
|
909
|
+
const result = await client.getTheme(themeId);
|
|
910
|
+
return { success: true, data: result };
|
|
911
|
+
} catch (err) {
|
|
912
|
+
if (err instanceof MarketplaceError && err.status === 404) {
|
|
913
|
+
return {
|
|
914
|
+
success: false,
|
|
915
|
+
error: { code: "NOT_FOUND", message: `Theme not found: ${themeId}` },
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
if (err instanceof MarketplaceUnavailableError) {
|
|
919
|
+
return {
|
|
920
|
+
success: false,
|
|
921
|
+
error: { code: "MARKETPLACE_UNAVAILABLE", message: "Marketplace is unavailable" },
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
console.error("Failed to get marketplace theme:", err);
|
|
925
|
+
return {
|
|
926
|
+
success: false,
|
|
927
|
+
error: { code: "GET_THEME_FAILED", message: "Failed to get theme details" },
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
}
|