emdash 0.7.0 → 0.9.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/dist/{adapters-Di31kZ28.d.mts → adapters-DoNJiveC.d.mts} +1 -1
- package/dist/{adapters-Di31kZ28.d.mts.map → adapters-DoNJiveC.d.mts.map} +1 -1
- package/dist/{apply-5uslYdUu.mjs → apply-BzltprvY.mjs} +90 -139
- package/dist/apply-BzltprvY.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +194 -17
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +6 -7
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +34 -57
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +17 -12
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +9 -6
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +301 -165
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +34 -10
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{base64-MBPo9ozB.mjs → base64-BRICGH2l.mjs} +1 -1
- package/dist/{base64-MBPo9ozB.mjs.map → base64-BRICGH2l.mjs.map} +1 -1
- package/dist/{byline-C4OVd8b3.mjs → byline-BSaNL1w7.mjs} +5 -5
- package/dist/byline-BSaNL1w7.mjs.map +1 -0
- package/dist/bylines-CvJ3PYz2.mjs +113 -0
- package/dist/bylines-CvJ3PYz2.mjs.map +1 -0
- package/dist/cache-C6N_hhN7.mjs +65 -0
- package/dist/cache-C6N_hhN7.mjs.map +1 -0
- package/dist/{chunks-HGz06Soa.mjs → chunks-NBQVDOci.mjs} +8 -2
- package/dist/{chunks-HGz06Soa.mjs.map → chunks-NBQVDOci.mjs.map} +1 -1
- package/dist/cli/index.mjs +229 -31
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +3 -3
- package/dist/client/index.mjs.map +1 -1
- package/dist/{config-BXwuX8Bx.mjs → config-BI0V3ICQ.mjs} +1 -1
- package/dist/{config-BXwuX8Bx.mjs.map → config-BI0V3ICQ.mjs.map} +1 -1
- package/dist/{content-D7J5y73J.mjs → content-8lOYF0pr.mjs} +43 -28
- package/dist/content-8lOYF0pr.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.d.mts.map +1 -1
- package/dist/db/libsql.mjs +7 -2
- package/dist/db/libsql.mjs.map +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.d.mts.map +1 -1
- package/dist/db/sqlite.mjs +8 -3
- package/dist/db/sqlite.mjs.map +1 -1
- package/dist/{db-errors-D0UT85nC.mjs → db-errors-WRezodiz.mjs} +1 -1
- package/dist/{db-errors-D0UT85nC.mjs.map → db-errors-WRezodiz.mjs.map} +1 -1
- package/dist/{default-CME5YdZ3.mjs → default-D8ksjWhO.mjs} +1 -1
- package/dist/{default-CME5YdZ3.mjs.map → default-D8ksjWhO.mjs.map} +1 -1
- package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
- package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
- package/dist/{error-CiYn9yDu.mjs → error-D_-tqP-I.mjs} +1 -1
- package/dist/error-D_-tqP-I.mjs.map +1 -0
- package/dist/{index-De6_Xv3v.d.mts → index-BFRaVcD6.d.mts} +243 -40
- package/dist/index-BFRaVcD6.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +29 -25
- package/dist/{load-CBcmDIot.mjs → load-DDqMMvZL.mjs} +2 -2
- package/dist/{load-CBcmDIot.mjs.map → load-DDqMMvZL.mjs.map} +1 -1
- package/dist/{loader-DeiBJEMe.mjs → loader-CKLbBnhK.mjs} +32 -10
- package/dist/loader-CKLbBnhK.mjs.map +1 -0
- package/dist/{manifest-schema-V30qsMft.mjs → manifest-schema-DqWNC3lM.mjs} +45 -3
- package/dist/manifest-schema-DqWNC3lM.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +3 -3
- package/dist/{media-DqHVh136.mjs → media-BW32b4gi.mjs} +4 -7
- package/dist/media-BW32b4gi.mjs.map +1 -0
- package/dist/{mode-CpNnGkPz.mjs → mode-ier8jbBk.mjs} +1 -1
- package/dist/mode-ier8jbBk.mjs.map +1 -0
- package/dist/options-BVp3UsTS.mjs +117 -0
- package/dist/options-BVp3UsTS.mjs.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{placeholder-tzpqGWII.d.mts → placeholder-BE4o_2dc.d.mts} +1 -1
- package/dist/{placeholder-tzpqGWII.d.mts.map → placeholder-BE4o_2dc.d.mts.map} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs → placeholder-CIJejMlK.mjs} +1 -1
- package/dist/placeholder-CIJejMlK.mjs.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/public-url-DByxYjUw.mjs +51 -0
- package/dist/public-url-DByxYjUw.mjs.map +1 -0
- package/dist/{query-g4Ug-9j9.mjs → query-Cg9ZKRQ0.mjs} +114 -16
- package/dist/query-Cg9ZKRQ0.mjs.map +1 -0
- package/dist/{redirect-CN0Rt9Ob.mjs → redirect-BhUBKRc1.mjs} +13 -8
- package/dist/redirect-BhUBKRc1.mjs.map +1 -0
- package/dist/{registry-Ci3WxVAr.mjs → registry-Dw70ChxB.mjs} +69 -11
- package/dist/registry-Dw70ChxB.mjs.map +1 -0
- package/dist/{request-cache-DiR961CV.mjs → request-cache-B-bmkipQ.mjs} +1 -1
- package/dist/request-cache-B-bmkipQ.mjs.map +1 -0
- package/dist/runner-Bnoj7vjK.d.mts +44 -0
- package/dist/runner-Bnoj7vjK.d.mts.map +1 -0
- package/dist/{runner-tQ7BJ4T7.mjs → runner-C7ADox5q.mjs} +185 -55
- package/dist/{runner-tQ7BJ4T7.mjs.map → runner-C7ADox5q.mjs.map} +1 -1
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +4 -4
- package/dist/{search-B0effn3j.mjs → search-dOGEccMa.mjs} +341 -152
- package/dist/search-dOGEccMa.mjs.map +1 -0
- package/dist/secrets-CW3reAnU.mjs +314 -0
- package/dist/secrets-CW3reAnU.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +15 -14
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.d.mts.map +1 -1
- package/dist/storage/s3.mjs +4 -4
- package/dist/storage/s3.mjs.map +1 -1
- package/dist/{taxonomies-K2z0Uhnj.mjs → taxonomies-ZlRtD6AG.mjs} +14 -7
- package/dist/taxonomies-ZlRtD6AG.mjs.map +1 -0
- package/dist/{tokens-BFPFx3CA.mjs → tokens-D7zMmWi2.mjs} +2 -2
- package/dist/{tokens-BFPFx3CA.mjs.map → tokens-D7zMmWi2.mjs.map} +1 -1
- package/dist/{transport-BykRfpyy.mjs → transport-BeMCmin1.mjs} +6 -5
- package/dist/{transport-BykRfpyy.mjs.map → transport-BeMCmin1.mjs.map} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts → transport-DNEfeMaU.d.mts} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts.map → transport-DNEfeMaU.d.mts.map} +1 -1
- package/dist/types-4fVtCIm0.mjs +68 -0
- package/dist/types-4fVtCIm0.mjs.map +1 -0
- package/dist/{types-CnZYHyLW.d.mts → types-BSyXeCFW.d.mts} +24 -2
- package/dist/{types-CnZYHyLW.d.mts.map → types-BSyXeCFW.d.mts.map} +1 -1
- package/dist/{types-DgrIP0tF.d.mts → types-BuBIptGk.d.mts} +80 -106
- package/dist/types-BuBIptGk.d.mts.map +1 -0
- package/dist/{types-BH2L167P.mjs → types-CDbKp7ND.mjs} +1 -1
- package/dist/{types-BH2L167P.mjs.map → types-CDbKp7ND.mjs.map} +1 -1
- package/dist/{types-DDS4MxsT.mjs → types-CIOg5AR8.mjs} +1 -1
- package/dist/{types-DDS4MxsT.mjs.map → types-CIOg5AR8.mjs.map} +1 -1
- package/dist/{types-6CUZRrZP.d.mts → types-CJsYGpco.d.mts} +24 -2
- package/dist/{types-6CUZRrZP.d.mts.map → types-CJsYGpco.d.mts.map} +1 -1
- package/dist/types-CRxNbK-Z.mjs +68 -0
- package/dist/types-CRxNbK-Z.mjs.map +1 -0
- package/dist/{types-C2v0c34j.d.mts → types-CrtWgIvl.d.mts} +1 -1
- package/dist/{types-C2v0c34j.d.mts.map → types-CrtWgIvl.d.mts.map} +1 -1
- package/dist/{types-CFWjXmus.d.mts → types-M78DQ1lx.d.mts} +1 -1
- package/dist/{types-CFWjXmus.d.mts.map → types-M78DQ1lx.d.mts.map} +1 -1
- package/dist/{validate-CqsNItbt.mjs → validate-Baqf0slj.mjs} +3 -3
- package/dist/{validate-CqsNItbt.mjs.map → validate-Baqf0slj.mjs.map} +1 -1
- package/dist/{validate-kM8Pjuf7.d.mts → validate-BfQh_C_y.d.mts} +4 -4
- package/dist/{validate-kM8Pjuf7.d.mts.map → validate-BfQh_C_y.d.mts.map} +1 -1
- package/dist/validation-BfEI7tNe.mjs +144 -0
- package/dist/validation-BfEI7tNe.mjs.map +1 -0
- package/dist/version-DoxrVdYf.mjs +7 -0
- package/dist/{version-BnTKdfam.mjs.map → version-DoxrVdYf.mjs.map} +1 -1
- package/dist/zod-generator-CC0xNe_K.mjs +132 -0
- package/dist/zod-generator-CC0xNe_K.mjs.map +1 -0
- package/locals.d.ts +1 -6
- package/package.json +21 -7
- package/src/api/auth-storage.ts +37 -0
- package/src/api/error.ts +6 -0
- package/src/api/errors.ts +8 -0
- package/src/api/handlers/comments.ts +19 -4
- package/src/api/handlers/content.ts +151 -4
- package/src/api/handlers/device-flow.ts +5 -0
- package/src/api/handlers/index.ts +2 -0
- package/src/api/handlers/marketplace.ts +11 -4
- package/src/api/handlers/media.ts +8 -1
- package/src/api/handlers/menus.ts +160 -21
- package/src/api/handlers/oauth-authorization.ts +72 -33
- package/src/api/handlers/redirects.ts +16 -3
- package/src/api/handlers/revision.ts +23 -14
- package/src/api/handlers/sections.ts +8 -1
- package/src/api/handlers/taxonomies.ts +131 -22
- package/src/api/handlers/validation.ts +212 -0
- package/src/api/openapi/document.ts +4 -1
- package/src/api/public-url.ts +54 -5
- package/src/api/route-utils.ts +14 -0
- package/src/api/schemas/comments.ts +2 -2
- package/src/api/schemas/common.ts +1 -1
- package/src/api/schemas/content.ts +17 -0
- package/src/api/schemas/sections.ts +3 -3
- package/src/api/schemas/setup.ts +8 -0
- package/src/api/schemas/users.ts +1 -1
- package/src/api/schemas/widgets.ts +12 -10
- package/src/api/setup-complete.ts +40 -0
- package/src/api/types.ts +5 -1
- package/src/astro/integration/index.ts +30 -2
- package/src/astro/integration/routes.ts +28 -0
- package/src/astro/integration/runtime.ts +49 -1
- package/src/astro/integration/virtual-modules.ts +73 -2
- package/src/astro/integration/vite-config.ts +49 -13
- package/src/astro/middleware/auth.ts +34 -6
- package/src/astro/middleware/redirect.ts +29 -16
- package/src/astro/middleware/request-context.ts +15 -5
- package/src/astro/middleware.ts +41 -10
- package/src/astro/routes/PluginRegistry.tsx +10 -1
- package/src/astro/routes/api/auth/invite/complete.ts +6 -1
- package/src/astro/routes/api/auth/mode.ts +57 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +23 -3
- package/src/astro/routes/api/auth/oauth/[provider].ts +10 -4
- package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
- package/src/astro/routes/api/auth/signup/complete.ts +6 -1
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
- package/src/astro/routes/api/content/[collection]/index.ts +1 -9
- package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
- package/src/astro/routes/api/import/wordpress/media.ts +2 -7
- package/src/astro/routes/api/import/wordpress/prepare.ts +9 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
- package/src/astro/routes/api/manifest.ts +62 -45
- package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
- package/src/astro/routes/api/openapi.json.ts +27 -10
- package/src/astro/routes/api/redirects/404s/index.ts +10 -4
- package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
- package/src/astro/routes/api/redirects/[id].ts +10 -4
- package/src/astro/routes/api/redirects/index.ts +7 -3
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
- package/src/astro/routes/api/schema/collections/index.ts +1 -1
- package/src/astro/routes/api/search/index.ts +10 -2
- package/src/astro/routes/api/sections/[slug].ts +10 -4
- package/src/astro/routes/api/sections/index.ts +7 -3
- package/src/astro/routes/api/settings/email.ts +4 -9
- package/src/astro/routes/api/setup/admin-verify.ts +6 -1
- package/src/astro/routes/api/setup/admin.ts +8 -2
- package/src/astro/routes/api/setup/index.ts +2 -2
- package/src/astro/routes/api/setup/status.ts +3 -1
- package/src/astro/routes/api/snapshot.ts +44 -18
- package/src/astro/routes/api/taxonomies/index.ts +0 -1
- package/src/astro/routes/api/themes/preview.ts +11 -5
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +4 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +4 -1
- package/src/astro/routes/api/widget-areas/[name].ts +4 -1
- package/src/astro/routes/api/widget-areas/index.ts +4 -1
- package/src/astro/types.ts +32 -3
- package/src/auth/allowed-origins.ts +168 -0
- package/src/auth/mode.ts +15 -3
- package/src/auth/passkey-config.ts +35 -13
- package/src/auth/providers/github-admin.tsx +29 -0
- package/src/auth/providers/github.ts +31 -0
- package/src/auth/providers/google-admin.tsx +44 -0
- package/src/auth/providers/google.ts +31 -0
- package/src/auth/types.ts +114 -4
- package/src/bylines/index.ts +37 -88
- package/src/cli/commands/auth.ts +28 -6
- package/src/cli/commands/bundle-utils.ts +11 -2
- package/src/cli/commands/bundle.ts +31 -9
- package/src/cli/commands/content.ts +13 -0
- package/src/cli/commands/login.ts +8 -1
- package/src/cli/commands/publish.ts +24 -0
- package/src/cli/commands/secrets.ts +183 -0
- package/src/cli/credentials.ts +1 -1
- package/src/cli/index.ts +5 -1
- package/src/client/index.ts +4 -4
- package/src/client/transport.ts +17 -7
- package/src/components/Break.astro +2 -2
- package/src/components/EmDashHead.astro +18 -13
- package/src/components/EmDashImage.astro +7 -6
- package/src/components/Embed.astro +1 -1
- package/src/components/Gallery.astro +6 -4
- package/src/components/Image.astro +9 -4
- package/src/components/InlinePortableTextEditor.tsx +106 -19
- package/src/components/LiveSearch.astro +5 -14
- package/src/config/secrets.ts +528 -0
- package/src/database/dialect-helpers.ts +50 -0
- package/src/database/migrations/034_published_at_index.ts +1 -1
- package/src/database/migrations/035_bounded_404_log.ts +56 -39
- package/src/database/migrations/runner.ts +156 -23
- package/src/database/repositories/audit.ts +6 -8
- package/src/database/repositories/byline.ts +6 -8
- package/src/database/repositories/comment.ts +12 -16
- package/src/database/repositories/content.ts +76 -52
- package/src/database/repositories/index.ts +1 -1
- package/src/database/repositories/media.ts +10 -13
- package/src/database/repositories/plugin-storage.ts +4 -6
- package/src/database/repositories/redirect.ts +26 -19
- package/src/database/repositories/taxonomy.ts +40 -3
- package/src/database/repositories/types.ts +57 -8
- package/src/database/repositories/user.ts +6 -8
- package/src/db/libsql.ts +1 -3
- package/src/db/sqlite.ts +2 -5
- package/src/emdash-runtime.ts +388 -247
- package/src/index.ts +14 -1
- package/src/loader.ts +30 -6
- package/src/mcp/server.ts +781 -141
- package/src/media/normalize.ts +1 -1
- package/src/media/url.ts +78 -0
- package/src/page/site-identity.ts +58 -0
- package/src/plugins/adapt-sandbox-entry.ts +22 -10
- package/src/plugins/context.ts +13 -10
- package/src/plugins/define-plugin.ts +40 -12
- package/src/plugins/email-console.ts +10 -3
- package/src/plugins/hooks.ts +34 -19
- package/src/plugins/index.ts +9 -0
- package/src/plugins/manifest-schema.ts +49 -2
- package/src/plugins/types.ts +174 -13
- package/src/preview/urls.ts +23 -3
- package/src/query.ts +149 -6
- package/src/redirects/cache.ts +38 -18
- package/src/request-cache.ts +3 -0
- package/src/schema/registry.ts +97 -5
- package/src/schema/zod-generator.ts +27 -5
- package/src/search/fts-manager.ts +0 -2
- package/src/search/query.ts +111 -26
- package/src/search/types.ts +8 -1
- package/src/sections/index.ts +7 -9
- package/src/seed/apply.ts +2 -0
- package/src/settings/index.ts +80 -6
- package/src/settings/types.ts +23 -1
- package/src/storage/s3.ts +12 -6
- package/src/taxonomies/index.ts +11 -1
- package/src/virtual-modules.d.ts +21 -1
- package/src/widgets/index.ts +1 -1
- package/dist/apply-5uslYdUu.mjs.map +0 -1
- package/dist/byline-C4OVd8b3.mjs.map +0 -1
- package/dist/bylines-hPTW79hw.mjs +0 -157
- package/dist/bylines-hPTW79hw.mjs.map +0 -1
- package/dist/cache-BkKBuIvS.mjs +0 -56
- package/dist/cache-BkKBuIvS.mjs.map +0 -1
- package/dist/chunk-ClPoSABd.mjs +0 -21
- package/dist/content-D7J5y73J.mjs.map +0 -1
- package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
- package/dist/error-CiYn9yDu.mjs.map +0 -1
- package/dist/index-De6_Xv3v.d.mts.map +0 -1
- package/dist/loader-DeiBJEMe.mjs.map +0 -1
- package/dist/manifest-schema-V30qsMft.mjs.map +0 -1
- package/dist/media-DqHVh136.mjs.map +0 -1
- package/dist/mode-CpNnGkPz.mjs.map +0 -1
- package/dist/placeholder-C-fk5hYI.mjs.map +0 -1
- package/dist/query-g4Ug-9j9.mjs.map +0 -1
- package/dist/redirect-CN0Rt9Ob.mjs.map +0 -1
- package/dist/registry-Ci3WxVAr.mjs.map +0 -1
- package/dist/request-cache-DiR961CV.mjs.map +0 -1
- package/dist/runner-BR2xKwhn.d.mts +0 -34
- package/dist/runner-BR2xKwhn.d.mts.map +0 -1
- package/dist/search-B0effn3j.mjs.map +0 -1
- package/dist/taxonomies-K2z0Uhnj.mjs.map +0 -1
- package/dist/types-CMMN0pNg.mjs +0 -31
- package/dist/types-CMMN0pNg.mjs.map +0 -1
- package/dist/types-DgrIP0tF.d.mts.map +0 -1
- package/dist/version-BnTKdfam.mjs +0 -7
package/src/media/normalize.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type { MediaProvider, MediaProviderItem, MediaValue } from "./types.js";
|
|
13
13
|
|
|
14
|
-
const INTERNAL_MEDIA_PREFIX = "/_emdash/api/media/file/";
|
|
14
|
+
export const INTERNAL_MEDIA_PREFIX = "/_emdash/api/media/file/";
|
|
15
15
|
const URL_PATTERN = /^https?:\/\//;
|
|
16
16
|
|
|
17
17
|
/**
|
package/src/media/url.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public media URL resolution.
|
|
3
|
+
*
|
|
4
|
+
* Used at render time by the Image components to decide whether a storage
|
|
5
|
+
* key should be served from the configured `publicUrl` (R2 custom domain,
|
|
6
|
+
* S3 CDN) or through the internal `/_emdash/api/media/file/{key}` route.
|
|
7
|
+
*/
|
|
8
|
+
import type { Storage } from "../storage/types.js";
|
|
9
|
+
import { INTERNAL_MEDIA_PREFIX } from "./normalize.js";
|
|
10
|
+
|
|
11
|
+
// Keys accepted by the public-URL rewrite: the `{ulid}{ext}` shape produced by
|
|
12
|
+
// the upload pipeline, with letters, digits, dots, dashes, and underscores.
|
|
13
|
+
// Slashes, `?`, `#`, and `%` are rejected so attacker-controlled content in a
|
|
14
|
+
// portable-text `asset.url` cannot traverse or reroute on the CDN origin.
|
|
15
|
+
const SAFE_STORAGE_KEY = /^[A-Za-z0-9._-]+$/;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the public URL for a locally stored media key. Returns an empty
|
|
19
|
+
* string when no key is given. When a storage adapter is supplied, defers to
|
|
20
|
+
* `storage.getPublicUrl()`; otherwise returns the internal proxy route.
|
|
21
|
+
*/
|
|
22
|
+
export function resolvePublicMediaUrl(
|
|
23
|
+
storage: Storage | null | undefined,
|
|
24
|
+
storageKey: string,
|
|
25
|
+
): string {
|
|
26
|
+
if (!storageKey) return "";
|
|
27
|
+
if (storage) return storage.getPublicUrl(storageKey);
|
|
28
|
+
return `/_emdash/api/media/file/${storageKey}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build the `getPublicMediaUrl` closure attached to `Astro.locals.emdash`.
|
|
33
|
+
* Shared by the anonymous fast path and the full-runtime path in middleware.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function createPublicMediaUrlResolver(
|
|
38
|
+
storage: Storage | null | undefined,
|
|
39
|
+
): (key: string) => string {
|
|
40
|
+
return (key) => resolvePublicMediaUrl(storage, key);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Input shape for {@link buildRenderMediaUrl}. */
|
|
44
|
+
export interface RenderMediaRef {
|
|
45
|
+
/** Storage key with extension (the canonical shape from the upload pipeline). */
|
|
46
|
+
storageKey?: string;
|
|
47
|
+
/** Pre-baked URL (either an internal proxy URL or an external URL). */
|
|
48
|
+
url?: string;
|
|
49
|
+
/** Bare media id (ULID without extension); only the internal proxy can look this up. */
|
|
50
|
+
id?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build a render-time media URL. Prefers `storageKey`, then rewrites an
|
|
55
|
+
* internal `url` via `resolve`, then falls back to the internal proxy for a
|
|
56
|
+
* bare `id`. External URLs and non-matching internal-looking URLs pass
|
|
57
|
+
* through untouched. Returns `""` when nothing usable is present.
|
|
58
|
+
*
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export function buildRenderMediaUrl(
|
|
62
|
+
resolve: ((key: string) => string) | undefined,
|
|
63
|
+
ref: RenderMediaRef,
|
|
64
|
+
): string {
|
|
65
|
+
const { storageKey, url, id } = ref;
|
|
66
|
+
if (storageKey) {
|
|
67
|
+
return resolve ? resolve(storageKey) : `${INTERNAL_MEDIA_PREFIX}${storageKey}`;
|
|
68
|
+
}
|
|
69
|
+
if (url) {
|
|
70
|
+
if (resolve && url.startsWith(INTERNAL_MEDIA_PREFIX)) {
|
|
71
|
+
const key = url.slice(INTERNAL_MEDIA_PREFIX.length);
|
|
72
|
+
if (SAFE_STORAGE_KEY.test(key)) return resolve(key);
|
|
73
|
+
}
|
|
74
|
+
return url;
|
|
75
|
+
}
|
|
76
|
+
if (id) return `${INTERNAL_MEDIA_PREFIX}${id}`;
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site identity head injection.
|
|
3
|
+
*
|
|
4
|
+
* Emits first-party `<head>` tags sourced from the user-configured Site
|
|
5
|
+
* Identity. These are rendered alongside, but separate from, the plugin
|
|
6
|
+
* contribution pipeline (`page/metadata.ts`) because:
|
|
7
|
+
*
|
|
8
|
+
* - Site identity is first-party, not plugin-supplied. The contribution
|
|
9
|
+
* pipeline's `isSafeHref` allowlist rejects same-origin paths like
|
|
10
|
+
* `/_emdash/api/media/file/...` (which is correct for sandboxed plugin
|
|
11
|
+
* contributions, but blocks our own favicon URLs).
|
|
12
|
+
* - The data shape is fixed and small. Routing it through a generic
|
|
13
|
+
* deduper buys nothing.
|
|
14
|
+
*
|
|
15
|
+
* Currently emits only `<link rel="icon">`. Other site-identity tags
|
|
16
|
+
* (`apple-touch-icon`, `theme-color`, `application-name`) need their own
|
|
17
|
+
* configurable fields in `SiteSettings` before they ship; emitting them
|
|
18
|
+
* automatically from the favicon would produce broken icons on iOS for
|
|
19
|
+
* SVG favicons or blurry home-screen icons when the favicon is a small
|
|
20
|
+
* PNG. Tracked separately.
|
|
21
|
+
*
|
|
22
|
+
* Templates that previously emitted their own `<link rel="icon">` are
|
|
23
|
+
* getting their lines dropped in the same change that introduced this
|
|
24
|
+
* helper.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { MediaReference } from "../settings/types.js";
|
|
28
|
+
import { escapeHtmlAttr } from "./metadata.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Subset of site settings consumed by `renderSiteIdentity`. Kept narrow
|
|
32
|
+
* so callers don't have to fetch fields they don't use.
|
|
33
|
+
*/
|
|
34
|
+
export interface SiteIdentityInput {
|
|
35
|
+
favicon?: MediaReference;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build the `<head>` HTML for site identity tags. Returns an empty string
|
|
40
|
+
* when no identity fields are configured.
|
|
41
|
+
*/
|
|
42
|
+
export function renderSiteIdentity(input: SiteIdentityInput | undefined): string {
|
|
43
|
+
if (!input) return "";
|
|
44
|
+
|
|
45
|
+
const parts: string[] = [];
|
|
46
|
+
|
|
47
|
+
const favicon = input.favicon;
|
|
48
|
+
if (favicon?.url) {
|
|
49
|
+
let tag = `<link rel="icon" href="${escapeHtmlAttr(favicon.url)}"`;
|
|
50
|
+
if (favicon.contentType) {
|
|
51
|
+
tag += ` type="${escapeHtmlAttr(favicon.contentType)}"`;
|
|
52
|
+
}
|
|
53
|
+
tag += ">";
|
|
54
|
+
parts.push(tag);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return parts.join("\n");
|
|
58
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { PluginDescriptor } from "../astro/integration/runtime.js";
|
|
14
14
|
import { PLUGIN_CAPABILITIES, HOOK_NAMES } from "./manifest-schema.js";
|
|
15
|
+
import { normalizeCapabilities } from "./types.js";
|
|
15
16
|
import type {
|
|
16
17
|
StandardPluginDefinition,
|
|
17
18
|
StandardHookEntry,
|
|
@@ -147,7 +148,10 @@ export function adaptSandboxEntry(
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
// Build capabilities from descriptor.
|
|
150
|
-
// Validate against the known set (same as defineNativePlugin).
|
|
151
|
+
// Validate against the known set (same as defineNativePlugin). Both
|
|
152
|
+
// current and deprecated names are accepted; deprecated names are
|
|
153
|
+
// silently normalized to current names below so the runtime only ever
|
|
154
|
+
// sees the canonical form.
|
|
151
155
|
const rawCapabilities = descriptor.capabilities ?? [];
|
|
152
156
|
for (const cap of rawCapabilities) {
|
|
153
157
|
if (!VALID_CAPABILITIES_SET.has(cap)) {
|
|
@@ -157,20 +161,28 @@ export function adaptSandboxEntry(
|
|
|
157
161
|
);
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
|
|
165
|
+
// Silent normalization: rewrite deprecated names to current names.
|
|
166
|
+
// Safe assertion — `normalizeCapabilities` only emits validated input
|
|
167
|
+
// plus current names from the rename map, all of which are in the union.
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- validated above; normalizeCapabilities only returns capabilities from the union
|
|
169
|
+
const capabilities = normalizeCapabilities(rawCapabilities) as PluginCapability[];
|
|
162
170
|
const allowedHosts = descriptor.allowedHosts ?? [];
|
|
163
171
|
|
|
164
172
|
// Capability implications: broader capabilities imply narrower ones
|
|
165
|
-
// (mirrors the normalization in define-plugin.ts for native format)
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
// (mirrors the normalization in define-plugin.ts for native format).
|
|
174
|
+
// Operates on canonical names only.
|
|
175
|
+
if (capabilities.includes("content:write") && !capabilities.includes("content:read")) {
|
|
176
|
+
capabilities.push("content:read");
|
|
168
177
|
}
|
|
169
|
-
if (capabilities.includes("write
|
|
170
|
-
capabilities.push("read
|
|
178
|
+
if (capabilities.includes("media:write") && !capabilities.includes("media:read")) {
|
|
179
|
+
capabilities.push("media:read");
|
|
171
180
|
}
|
|
172
|
-
if (
|
|
173
|
-
capabilities.
|
|
181
|
+
if (
|
|
182
|
+
capabilities.includes("network:request:unrestricted") &&
|
|
183
|
+
!capabilities.includes("network:request")
|
|
184
|
+
) {
|
|
185
|
+
capabilities.push("network:request");
|
|
174
186
|
}
|
|
175
187
|
|
|
176
188
|
// Build storage config from descriptor.
|
package/src/plugins/context.ts
CHANGED
|
@@ -647,14 +647,14 @@ export function createUnrestrictedHttpAccess(pluginId: string): HttpAccess {
|
|
|
647
647
|
}
|
|
648
648
|
|
|
649
649
|
/**
|
|
650
|
-
* Create blocked HTTP access (for plugins without network:
|
|
650
|
+
* Create blocked HTTP access (for plugins without network:request capability)
|
|
651
651
|
*/
|
|
652
652
|
export function createBlockedHttpAccess(pluginId: string): HttpAccess {
|
|
653
653
|
return {
|
|
654
654
|
async fetch(): Promise<never> {
|
|
655
655
|
throw new Error(
|
|
656
|
-
`Plugin "${pluginId}" does not have the "network:
|
|
657
|
-
`Add "network:
|
|
656
|
+
`Plugin "${pluginId}" does not have the "network:request" capability. ` +
|
|
657
|
+
`Add "network:request" to the plugin's capabilities to enable HTTP requests.`,
|
|
658
658
|
);
|
|
659
659
|
},
|
|
660
660
|
};
|
|
@@ -902,32 +902,35 @@ export class PluginContextFactory {
|
|
|
902
902
|
const storage = createStorageAccess(this.db, plugin.id, plugin.storage);
|
|
903
903
|
|
|
904
904
|
// Capability-gated: content
|
|
905
|
+
// Note: capabilities reach this point already normalized to the
|
|
906
|
+
// canonical names by definePlugin / adaptSandboxEntry. Deprecated
|
|
907
|
+
// names ("read:content", "write:content") never appear here.
|
|
905
908
|
let content: ContentAccess | ContentAccessWithWrite | undefined;
|
|
906
|
-
if (capabilities.has("write
|
|
909
|
+
if (capabilities.has("content:write")) {
|
|
907
910
|
content = createContentAccessWithWrite(this.db);
|
|
908
|
-
} else if (capabilities.has("read
|
|
911
|
+
} else if (capabilities.has("content:read")) {
|
|
909
912
|
content = createContentAccess(this.db);
|
|
910
913
|
}
|
|
911
914
|
|
|
912
915
|
// Capability-gated: media
|
|
913
916
|
let media: MediaAccess | MediaAccessWithWrite | undefined;
|
|
914
|
-
if (capabilities.has("write
|
|
917
|
+
if (capabilities.has("media:write") && this.getUploadUrl) {
|
|
915
918
|
media = createMediaAccessWithWrite(this.db, this.getUploadUrl, this.storage);
|
|
916
|
-
} else if (capabilities.has("read
|
|
919
|
+
} else if (capabilities.has("media:read")) {
|
|
917
920
|
media = createMediaAccess(this.db);
|
|
918
921
|
}
|
|
919
922
|
|
|
920
923
|
// Capability-gated: http
|
|
921
924
|
let http: HttpAccess | undefined;
|
|
922
|
-
if (capabilities.has("network:
|
|
925
|
+
if (capabilities.has("network:request:unrestricted")) {
|
|
923
926
|
http = createUnrestrictedHttpAccess(plugin.id);
|
|
924
|
-
} else if (capabilities.has("network:
|
|
927
|
+
} else if (capabilities.has("network:request")) {
|
|
925
928
|
http = createHttpAccess(plugin.id, plugin.allowedHosts);
|
|
926
929
|
}
|
|
927
930
|
|
|
928
931
|
// Capability-gated: users
|
|
929
932
|
let users: UserAccess | undefined;
|
|
930
|
-
if (capabilities.has("read
|
|
933
|
+
if (capabilities.has("users:read")) {
|
|
931
934
|
users = createUserAccess(this.db);
|
|
932
935
|
}
|
|
933
936
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { normalizeCapabilities } from "./types.js";
|
|
16
17
|
import type {
|
|
17
18
|
PluginDefinition,
|
|
18
19
|
ResolvedPlugin,
|
|
@@ -20,6 +21,7 @@ import type {
|
|
|
20
21
|
ResolvedPluginHooks,
|
|
21
22
|
ResolvedHook,
|
|
22
23
|
HookConfig,
|
|
24
|
+
PluginCapability,
|
|
23
25
|
PluginStorageConfig,
|
|
24
26
|
StandardPluginDefinition,
|
|
25
27
|
} from "./types.js";
|
|
@@ -65,7 +67,7 @@ const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
|
|
|
65
67
|
* export default definePlugin({
|
|
66
68
|
* id: "my-plugin",
|
|
67
69
|
* version: "1.0.0",
|
|
68
|
-
* capabilities: ["read
|
|
70
|
+
* capabilities: ["content:read"],
|
|
69
71
|
* hooks: {
|
|
70
72
|
* "content:beforeSave": async (event, ctx) => {
|
|
71
73
|
* ctx.log.info("Saving content", { collection: event.collection });
|
|
@@ -143,8 +145,24 @@ function defineNativePlugin<TStorage extends PluginStorageConfig>(
|
|
|
143
145
|
throw new Error(`Invalid plugin version "${version}". Must be semver format (e.g., "1.0.0").`);
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
// Validate capabilities
|
|
147
|
-
|
|
148
|
+
// Validate capabilities. Both current names and deprecated aliases are
|
|
149
|
+
// accepted; aliases are silently rewritten to current names below so the
|
|
150
|
+
// runtime only ever sees the canonical form. Authors are warned at
|
|
151
|
+
// bundle/validate and hard-failed at publish.
|
|
152
|
+
const validCapabilities = new Set<string>([
|
|
153
|
+
// Current names
|
|
154
|
+
"network:request",
|
|
155
|
+
"network:request:unrestricted",
|
|
156
|
+
"content:read",
|
|
157
|
+
"content:write",
|
|
158
|
+
"media:read",
|
|
159
|
+
"media:write",
|
|
160
|
+
"users:read",
|
|
161
|
+
"email:send",
|
|
162
|
+
"hooks.email-transport:register",
|
|
163
|
+
"hooks.email-events:register",
|
|
164
|
+
"hooks.page-fragments:register",
|
|
165
|
+
// Deprecated aliases
|
|
148
166
|
"network:fetch",
|
|
149
167
|
"network:fetch:any",
|
|
150
168
|
"read:content",
|
|
@@ -152,7 +170,6 @@ function defineNativePlugin<TStorage extends PluginStorageConfig>(
|
|
|
152
170
|
"read:media",
|
|
153
171
|
"write:media",
|
|
154
172
|
"read:users",
|
|
155
|
-
"email:send",
|
|
156
173
|
"email:provide",
|
|
157
174
|
"email:intercept",
|
|
158
175
|
"page:inject",
|
|
@@ -163,16 +180,27 @@ function defineNativePlugin<TStorage extends PluginStorageConfig>(
|
|
|
163
180
|
}
|
|
164
181
|
}
|
|
165
182
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
183
|
+
// Silent normalization: rewrite deprecated names to current names. Done
|
|
184
|
+
// before the implication pass so implications work on canonical names.
|
|
185
|
+
// `as PluginCapability[]` is safe because `normalizeCapabilities` only
|
|
186
|
+
// returns strings from the validated input plus current names from the
|
|
187
|
+
// rename map, all of which are in the union.
|
|
188
|
+
const canonical = normalizeCapabilities(capabilities) as PluginCapability[];
|
|
189
|
+
|
|
190
|
+
// Capability implications: broader capabilities imply narrower ones.
|
|
191
|
+
// Operates on canonical names only.
|
|
192
|
+
const normalizedCapabilities: PluginCapability[] = [...canonical];
|
|
193
|
+
if (canonical.includes("content:write") && !canonical.includes("content:read")) {
|
|
194
|
+
normalizedCapabilities.push("content:read");
|
|
170
195
|
}
|
|
171
|
-
if (
|
|
172
|
-
normalizedCapabilities.push("read
|
|
196
|
+
if (canonical.includes("media:write") && !canonical.includes("media:read")) {
|
|
197
|
+
normalizedCapabilities.push("media:read");
|
|
173
198
|
}
|
|
174
|
-
if (
|
|
175
|
-
|
|
199
|
+
if (
|
|
200
|
+
canonical.includes("network:request:unrestricted") &&
|
|
201
|
+
!canonical.includes("network:request")
|
|
202
|
+
) {
|
|
203
|
+
normalizedCapabilities.push("network:request");
|
|
176
204
|
}
|
|
177
205
|
|
|
178
206
|
// Normalize hooks
|
|
@@ -30,9 +30,16 @@ export interface StoredEmail {
|
|
|
30
30
|
* instances (the runtime and the route handler may load separate copies
|
|
31
31
|
* of this module, but globalThis is always the same object).
|
|
32
32
|
*/
|
|
33
|
-
const GLOBAL_KEY = "
|
|
34
|
-
const
|
|
35
|
-
|
|
33
|
+
const GLOBAL_KEY = Symbol.for("emdash:dev-emails");
|
|
34
|
+
const g = globalThis as Record<symbol, unknown>;
|
|
35
|
+
const storedEmails: StoredEmail[] = (() => {
|
|
36
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- globalThis singleton pattern (see request-context.ts)
|
|
37
|
+
const existing = g[GLOBAL_KEY] as StoredEmail[] | undefined;
|
|
38
|
+
if (existing) return existing;
|
|
39
|
+
const fresh: StoredEmail[] = [];
|
|
40
|
+
g[GLOBAL_KEY] = fresh;
|
|
41
|
+
return fresh;
|
|
42
|
+
})();
|
|
36
43
|
|
|
37
44
|
/**
|
|
38
45
|
* Get all stored dev emails (most recent first).
|
package/src/plugins/hooks.ts
CHANGED
|
@@ -248,28 +248,32 @@ export class HookPipeline {
|
|
|
248
248
|
* capability will have that hook silently skipped at registration time.
|
|
249
249
|
*/
|
|
250
250
|
private static readonly HOOK_REQUIRED_CAPABILITY: ReadonlyMap<string, string> = new Map([
|
|
251
|
-
// Email
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
["
|
|
258
|
-
["
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
["content:
|
|
262
|
-
["content:
|
|
251
|
+
// Email — registering email:beforeSend/afterSend/deliver requires the
|
|
252
|
+
// matching `hooks.email-*:register` capability. These are distinct
|
|
253
|
+
// from `email:send` (which gates ctx.email) so that "this plugin
|
|
254
|
+
// reads/writes email events" is visible separately from "this
|
|
255
|
+
// plugin can send email".
|
|
256
|
+
["email:beforeSend", "hooks.email-events:register"],
|
|
257
|
+
["email:afterSend", "hooks.email-events:register"],
|
|
258
|
+
["email:deliver", "hooks.email-transport:register"],
|
|
259
|
+
// Content — beforeSave can mutate content, so requires content:write.
|
|
260
|
+
// afterSave is read-only notification, so content:read suffices.
|
|
261
|
+
["content:beforeSave", "content:write"],
|
|
262
|
+
["content:afterSave", "content:read"],
|
|
263
|
+
["content:beforeDelete", "content:read"],
|
|
264
|
+
["content:afterDelete", "content:read"],
|
|
265
|
+
["content:afterPublish", "content:read"],
|
|
266
|
+
["content:afterUnpublish", "content:read"],
|
|
263
267
|
// Media
|
|
264
|
-
["media:beforeUpload", "write
|
|
265
|
-
["media:afterUpload", "read
|
|
268
|
+
["media:beforeUpload", "media:write"],
|
|
269
|
+
["media:afterUpload", "media:read"],
|
|
266
270
|
// Comments — hooks expose author email, IP hash, user agent
|
|
267
|
-
["comment:beforeCreate", "read
|
|
268
|
-
["comment:moderate", "read
|
|
269
|
-
["comment:afterCreate", "read
|
|
270
|
-
["comment:afterModerate", "read
|
|
271
|
+
["comment:beforeCreate", "users:read"],
|
|
272
|
+
["comment:moderate", "users:read"],
|
|
273
|
+
["comment:afterCreate", "users:read"],
|
|
274
|
+
["comment:afterModerate", "users:read"],
|
|
271
275
|
// Page fragments — can inject arbitrary scripts into every public page
|
|
272
|
-
["page:fragments", "page:
|
|
276
|
+
["page:fragments", "hooks.page-fragments:register"],
|
|
273
277
|
]);
|
|
274
278
|
|
|
275
279
|
/**
|
|
@@ -1218,6 +1222,17 @@ export class HookPipeline {
|
|
|
1218
1222
|
return hooks.filter((h) => h.exclusive).map((h) => ({ pluginId: h.pluginId }));
|
|
1219
1223
|
}
|
|
1220
1224
|
|
|
1225
|
+
/**
|
|
1226
|
+
* Get all plugins that registered a non-exclusive handler for a given
|
|
1227
|
+
* hook (e.g. `email:beforeSend`, `email:afterSend`), preserving priority
|
|
1228
|
+
* order. Partitions with `getExclusiveHookProviders()`, which returns
|
|
1229
|
+
* plugins whose registration is marked `exclusive: true`.
|
|
1230
|
+
*/
|
|
1231
|
+
getHookProviders(hookName: string): Array<{ pluginId: string }> {
|
|
1232
|
+
const hooks = this.hooks.get(hookName as HookNameV2) ?? [];
|
|
1233
|
+
return hooks.filter((h) => !h.exclusive).map((h) => ({ pluginId: h.pluginId }));
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1221
1236
|
/**
|
|
1222
1237
|
* Invoke an exclusive hook — dispatch only to the selected provider.
|
|
1223
1238
|
* Returns null if no provider is selected or if the selected hook
|
package/src/plugins/index.ts
CHANGED
|
@@ -192,3 +192,12 @@ export type {
|
|
|
192
192
|
StandardRouteEntry,
|
|
193
193
|
} from "./types.js";
|
|
194
194
|
export { isStandardPluginDefinition } from "./types.js";
|
|
195
|
+
|
|
196
|
+
// Capability normalization (legacy → canonical alias layer)
|
|
197
|
+
export {
|
|
198
|
+
CAPABILITY_RENAMES,
|
|
199
|
+
isDeprecatedCapability,
|
|
200
|
+
normalizeCapability,
|
|
201
|
+
normalizeCapabilities,
|
|
202
|
+
} from "./types.js";
|
|
203
|
+
export type { CurrentPluginCapability, DeprecatedPluginCapability } from "./types.js";
|
|
@@ -12,7 +12,31 @@ import { z } from "zod";
|
|
|
12
12
|
|
|
13
13
|
// ── Enum values (must stay in sync with types.ts) ───────────────
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Current capability names — the ones authors should use going forward.
|
|
17
|
+
* See `PluginCapability` in `types.ts` for documentation of each.
|
|
18
|
+
*/
|
|
19
|
+
export const CURRENT_PLUGIN_CAPABILITIES = [
|
|
20
|
+
"network:request",
|
|
21
|
+
"network:request:unrestricted",
|
|
22
|
+
"content:read",
|
|
23
|
+
"content:write",
|
|
24
|
+
"media:read",
|
|
25
|
+
"media:write",
|
|
26
|
+
"users:read",
|
|
27
|
+
"email:send",
|
|
28
|
+
"hooks.email-transport:register",
|
|
29
|
+
"hooks.email-events:register",
|
|
30
|
+
"hooks.page-fragments:register",
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Legacy capability names accepted during the deprecation window.
|
|
35
|
+
* Normalized to current names via `normalizeCapability()` in types.ts
|
|
36
|
+
* before reaching the runtime. Plugin authors are warned at bundle/validate
|
|
37
|
+
* and hard-failed at publish.
|
|
38
|
+
*/
|
|
39
|
+
export const DEPRECATED_PLUGIN_CAPABILITIES = [
|
|
16
40
|
"network:fetch",
|
|
17
41
|
"network:fetch:any",
|
|
18
42
|
"read:content",
|
|
@@ -20,12 +44,23 @@ export const PLUGIN_CAPABILITIES = [
|
|
|
20
44
|
"read:media",
|
|
21
45
|
"write:media",
|
|
22
46
|
"read:users",
|
|
23
|
-
"email:send",
|
|
24
47
|
"email:provide",
|
|
25
48
|
"email:intercept",
|
|
26
49
|
"page:inject",
|
|
27
50
|
] as const;
|
|
28
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Full set of accepted capability strings — current + deprecated.
|
|
54
|
+
*
|
|
55
|
+
* The manifest schema accepts both during the transition. The runtime only
|
|
56
|
+
* ever sees current names because `normalizeCapability()` rewrites legacy
|
|
57
|
+
* names at every external boundary (definePlugin, adaptSandboxEntry).
|
|
58
|
+
*/
|
|
59
|
+
export const PLUGIN_CAPABILITIES = [
|
|
60
|
+
...CURRENT_PLUGIN_CAPABILITIES,
|
|
61
|
+
...DEPRECATED_PLUGIN_CAPABILITIES,
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
29
64
|
/** Must stay in sync with FieldType in schema/types.ts */
|
|
30
65
|
const FIELD_TYPES = [
|
|
31
66
|
"string",
|
|
@@ -131,6 +166,18 @@ const settingFieldSchema = z.discriminatedUnion("type", [
|
|
|
131
166
|
default: z.string().optional(),
|
|
132
167
|
}),
|
|
133
168
|
z.object({ ...baseSettingFields, type: z.literal("secret") }),
|
|
169
|
+
z.object({
|
|
170
|
+
...baseSettingFields,
|
|
171
|
+
type: z.literal("url"),
|
|
172
|
+
default: z.string().optional(),
|
|
173
|
+
placeholder: z.string().optional(),
|
|
174
|
+
}),
|
|
175
|
+
z.object({
|
|
176
|
+
...baseSettingFields,
|
|
177
|
+
type: z.literal("email"),
|
|
178
|
+
default: z.string().optional(),
|
|
179
|
+
placeholder: z.string().optional(),
|
|
180
|
+
}),
|
|
134
181
|
]);
|
|
135
182
|
|
|
136
183
|
const adminPageSchema = z.object({
|