emdash 0.17.2 → 0.19.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/api/route-utils.d.mts +2 -2
- package/dist/api/route-utils.mjs +14 -14
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +3 -3
- package/dist/{api-B7GATEYo.mjs → api-BZ6bhjYs.mjs} +88 -16
- package/dist/api-BZ6bhjYs.mjs.map +1 -0
- package/dist/{apply-BrVqULFe.mjs → apply-hQkKKBCf.mjs} +23 -23
- package/dist/apply-hQkKKBCf.mjs.map +1 -0
- package/dist/astro/index.d.mts +8 -8
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +113 -23
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +7 -7
- package/dist/astro/middleware/auth.mjs +2 -2
- package/dist/astro/middleware/redirect.mjs +4 -4
- package/dist/astro/middleware/request-context.mjs +2 -2
- package/dist/astro/middleware.d.mts +26 -4
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +414 -215
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
- package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
- package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/index.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/updates.mjs +30 -30
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +30 -30
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +30 -30
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
- package/dist/astro/routes/api/admin/users/index.mjs +5 -5
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +6 -6
- package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/register-options.mjs +6 -6
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.mjs +6 -6
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +7 -7
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
- package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
- package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -8
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +12 -10
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -8
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.d.mts +8 -0
- package/dist/astro/routes/api/content/_collection_/authors.d.mts.map +1 -0
- package/dist/astro/routes/api/content/_collection_/authors.mjs +19 -0
- package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -0
- package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
- package/dist/astro/routes/api/dashboard.mjs +7 -7
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +2 -2
- package/dist/astro/routes/api/import/probe.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +7 -7
- package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/media.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
- package/dist/astro/routes/api/manifest.mjs +3 -3
- package/dist/astro/routes/api/mcp.mjs +28 -28
- package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
- package/dist/astro/routes/api/media/_id_.mjs +6 -6
- package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
- package/dist/astro/routes/api/media/providers/index.mjs +3 -3
- package/dist/astro/routes/api/media/upload-url.mjs +6 -6
- package/dist/astro/routes/api/media.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_.mjs +7 -7
- package/dist/astro/routes/api/menus/index.mjs +7 -7
- package/dist/astro/routes/api/oauth/authorize.mjs +1 -1
- package/dist/astro/routes/api/oauth/device/authorize.mjs +4 -4
- package/dist/astro/routes/api/oauth/device/code.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/token.mjs +5 -5
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +4 -4
- package/dist/astro/routes/api/oauth/token/revoke.mjs +4 -4
- package/dist/astro/routes/api/oauth/token.mjs +4 -4
- package/dist/astro/routes/api/openapi.json.mjs +17 -3
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
- package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
- package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
- package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
- package/dist/astro/routes/api/redirects/index.mjs +10 -10
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +30 -30
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +30 -30
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +30 -30
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +30 -30
- package/dist/astro/routes/api/schema/collections/index.mjs +30 -30
- package/dist/astro/routes/api/schema/index.mjs +6 -6
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +30 -30
- package/dist/astro/routes/api/schema/orphans/index.mjs +30 -30
- package/dist/astro/routes/api/search/enable.mjs +9 -9
- package/dist/astro/routes/api/search/index.mjs +8 -8
- package/dist/astro/routes/api/search/rebuild.mjs +9 -9
- package/dist/astro/routes/api/search/stats.mjs +6 -6
- package/dist/astro/routes/api/search/suggest.mjs +8 -8
- package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
- package/dist/astro/routes/api/sections/index.mjs +8 -8
- package/dist/astro/routes/api/settings/email.mjs +5 -5
- package/dist/astro/routes/api/settings.mjs +12 -12
- package/dist/astro/routes/api/setup/admin-verify.mjs +6 -6
- package/dist/astro/routes/api/setup/admin.mjs +6 -6
- package/dist/astro/routes/api/setup/dev-bypass.mjs +18 -18
- package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
- package/dist/astro/routes/api/setup/index.mjs +21 -21
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +5 -5
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
- package/dist/astro/routes/api/themes/preview.mjs +5 -5
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
- package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
- package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +6 -6
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +6 -6
- package/dist/astro/routes/sitemap.xml.mjs +6 -6
- package/dist/astro/types.d.mts +15 -8
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{authorize-CLTmOUyx.mjs → authorize-C_8t2KGa.mjs} +2 -2
- package/dist/{authorize-CLTmOUyx.mjs.map → authorize-C_8t2KGa.mjs.map} +1 -1
- package/dist/{byline-CAhk4FrG.mjs → byline-DUx48sJp.mjs} +6 -6
- package/dist/{byline-CAhk4FrG.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
- package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-51kg6Vuv.mjs} +3 -3
- package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-51kg6Vuv.mjs.map} +1 -1
- package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields-C_OsR-KF.mjs} +2 -2
- package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields-C_OsR-KF.mjs.map} +1 -1
- package/dist/{byline-fields-CR5hGLMw.d.mts → byline-fields-DYXKDuNX.d.mts} +53 -29
- package/dist/byline-fields-DYXKDuNX.d.mts.map +1 -0
- package/dist/{byline-registry-CxK5g559.mjs → byline-registry-CWP7I71B.mjs} +3 -3
- package/dist/{byline-registry-CxK5g559.mjs.map → byline-registry-CWP7I71B.mjs.map} +1 -1
- package/dist/{bylines-CbrD7STW.mjs → bylines-Cx5n-WqP.mjs} +3 -3
- package/dist/{bylines-CbrD7STW.mjs.map → bylines-Cx5n-WqP.mjs.map} +1 -1
- package/dist/{bylines-DCczH3AV.mjs → bylines-wurS258E.mjs} +50 -6
- package/dist/{bylines-DCczH3AV.mjs.map → bylines-wurS258E.mjs.map} +1 -1
- package/dist/{cache-DIHHyPkt.mjs → cache-B_HzASVT.mjs} +3 -3
- package/dist/{cache-DIHHyPkt.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
- package/dist/{chunks-DnnHlRG3.mjs → chunks-BerYVuve.mjs} +2 -2
- package/dist/{chunks-DnnHlRG3.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
- package/dist/cli/index.mjs +40 -27
- 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/{comment-DkAfGX9E.mjs → comment-sqQxNpN3.mjs} +2 -2
- package/dist/{comment-DkAfGX9E.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
- package/dist/{comments-DLFnXs7J.mjs → comments-CJ0RZsYR.mjs} +3 -3
- package/dist/{comments-DLFnXs7J.mjs.map → comments-CJ0RZsYR.mjs.map} +1 -1
- package/dist/{content-C7aJ7keg.mjs → content-BIlVx-RX.mjs} +132 -43
- package/dist/content-BIlVx-RX.mjs.map +1 -0
- package/dist/{context-Ca0HkaIh.mjs → context-GG52SPgh.mjs} +10 -10
- package/dist/{context-Ca0HkaIh.mjs.map → context-GG52SPgh.mjs.map} +1 -1
- package/dist/{cron-DZovZUnC.mjs → cron-BJ2ClIlj.mjs} +4 -3
- package/dist/cron-BJ2ClIlj.mjs.map +1 -0
- package/dist/{dashboard-BrfLIsX1.mjs → dashboard-2JgAMWxK.mjs} +4 -4
- package/dist/{dashboard-BrfLIsX1.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +1 -1
- package/dist/{device-flow-ptLrVINd.mjs → device-flow-s6_q3T7A.mjs} +2 -2
- package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-s6_q3T7A.mjs.map} +1 -1
- package/dist/{error-Bk9s3Ism.mjs → error-RwM4dD35.mjs} +2 -2
- package/dist/{error-Bk9s3Ism.mjs.map → error-RwM4dD35.mjs.map} +1 -1
- package/dist/{fts-manager-XpDfbIKo.mjs → fts-manager-1RgHmopc.mjs} +2 -2
- package/dist/{fts-manager-XpDfbIKo.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
- package/dist/{index-D60_SzHG.d.mts → index-BpYeJO1E.d.mts} +2 -2
- package/dist/{index-D60_SzHG.d.mts.map → index-BpYeJO1E.d.mts.map} +1 -1
- package/dist/{index-C8ciqSMJ.d.mts → index-FfiTQJq2.d.mts} +202 -20
- package/dist/index-FfiTQJq2.d.mts.map +1 -0
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +43 -43
- package/dist/{load-CF5oETkh.mjs → load-B84ohfBk.mjs} +2 -2
- package/dist/{load-CF5oETkh.mjs.map → load-B84ohfBk.mjs.map} +1 -1
- package/dist/{loader-BxyvbrZP.mjs → loader-CpZKpFz0.mjs} +32 -30
- package/dist/loader-CpZKpFz0.mjs.map +1 -0
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +6 -6
- package/dist/{media-Cyz5BhSN.mjs → media-JOf3pNkw.mjs} +2 -2
- package/dist/{media-Cyz5BhSN.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
- package/dist/{menus-PFp8FDuO.mjs → menus-DX4_E01q.mjs} +3 -3
- package/dist/{menus-PFp8FDuO.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
- package/dist/{menus-CIdZ_Q6U.mjs → menus-Dp9xporj.mjs} +112 -16
- package/dist/menus-Dp9xporj.mjs.map +1 -0
- package/dist/{normalize-DVV8nbrL.mjs → normalize-CK5o04zr.mjs} +2 -2
- package/dist/{normalize-DVV8nbrL.mjs.map → normalize-CK5o04zr.mjs.map} +1 -1
- package/dist/{oauth-authorization-DvBAL75d.mjs → oauth-authorization-1aPAYjiC.mjs} +2 -2
- package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-1aPAYjiC.mjs.map} +1 -1
- package/dist/{options-BL4X94qY.mjs → options-BPCVnesz.mjs} +1 -1
- package/dist/{options-BL4X94qY.mjs.map → options-BPCVnesz.mjs.map} +1 -1
- package/dist/{options-tb7DJROi.d.mts → options-D4MnavW_.d.mts} +3 -3
- package/dist/{options-tb7DJROi.d.mts.map → options-D4MnavW_.d.mts.map} +1 -1
- package/dist/{parse-B-K21lvm.mjs → parse-CrGndy1A.mjs} +2 -2
- package/dist/{parse-B-K21lvm.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
- package/dist/{patterns-CqG5Ya3i.mjs → patterns-p-RBdTbM.mjs} +1 -1
- package/dist/{patterns-CqG5Ya3i.mjs.map → patterns-p-RBdTbM.mjs.map} +1 -1
- package/dist/plugin-utils.d.mts +7 -7
- package/dist/plugins/adapt-sandbox-entry.d.mts +7 -7
- package/dist/{query-Cc649nDl.mjs → query-BFQ029Ts.mjs} +21 -15
- package/dist/query-BFQ029Ts.mjs.map +1 -0
- package/dist/{rate-limit-BI1OdpQH.mjs → rate-limit-ClFFUga6.mjs} +2 -2
- package/dist/{rate-limit-BI1OdpQH.mjs.map → rate-limit-ClFFUga6.mjs.map} +1 -1
- package/dist/{redirect-C-FeA4j9.mjs → redirect-CRWIt8Zj.mjs} +3 -3
- package/dist/{redirect-C-FeA4j9.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
- package/dist/{redirects-C0L9JUk4.mjs → redirects-DEygMrRO.mjs} +25 -3
- package/dist/redirects-DEygMrRO.mjs.map +1 -0
- package/dist/{redirects-C1UgU9E0.mjs → redirects-OIu6vQ2i.mjs} +5 -5
- package/dist/{redirects-C1UgU9E0.mjs.map → redirects-OIu6vQ2i.mjs.map} +1 -1
- package/dist/{registry-C-T_PWgp.mjs → registry-brYh-rAT.mjs} +6 -6
- package/dist/{registry-C-T_PWgp.mjs.map → registry-brYh-rAT.mjs.map} +1 -1
- package/dist/{request-cache-BYMs-BGX.mjs → request-cache-D32LpnmI.mjs} +1 -1
- package/dist/{request-cache-BYMs-BGX.mjs.map → request-cache-D32LpnmI.mjs.map} +1 -1
- package/dist/{runner-BiuUfx-V.mjs → runner--4wMWwKM.mjs} +224 -168
- package/dist/runner--4wMWwKM.mjs.map +1 -0
- package/dist/{runner-DM1yR5qd.d.mts → runner-BcRuXq_h.d.mts} +2 -2
- package/dist/{runner-DM1yR5qd.d.mts.map → runner-BcRuXq_h.d.mts.map} +1 -1
- package/dist/runtime.d.mts +7 -7
- package/dist/runtime.mjs +2 -2
- package/dist/{schema-BpCJh2lU.mjs → schema-CS7Eg5gh.mjs} +5 -5
- package/dist/{schema-BpCJh2lU.mjs.map → schema-CS7Eg5gh.mjs.map} +1 -1
- package/dist/{search-BrF7k0Ho.mjs → search-o-aQzHI1.mjs} +4 -4
- package/dist/{search-BrF7k0Ho.mjs.map → search-o-aQzHI1.mjs.map} +1 -1
- package/dist/{secrets-YYbTgB1w.mjs → secrets-C_ZtRos3.mjs} +2 -2
- package/dist/{secrets-YYbTgB1w.mjs.map → secrets-C_ZtRos3.mjs.map} +1 -1
- package/dist/{sections-8DEa-dWt.mjs → sections-DhsZ0ns9.mjs} +3 -3
- package/dist/{sections-8DEa-dWt.mjs.map → sections-DhsZ0ns9.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +16 -16
- package/dist/seo/index.d.mts +1 -1
- package/dist/{seo-CKr7pLfA.mjs → seo-B5e6y9Wk.mjs} +2 -2
- package/dist/{seo-CKr7pLfA.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
- package/dist/{service-9P2cdyR_.mjs → service-DAxg8RPR.mjs} +2 -2
- package/dist/{service-9P2cdyR_.mjs.map → service-DAxg8RPR.mjs.map} +1 -1
- package/dist/{settings-Jro4YcUb.mjs → settings-B1p-gPUK.mjs} +5 -5
- package/dist/{settings-Jro4YcUb.mjs.map → settings-B1p-gPUK.mjs.map} +1 -1
- package/dist/{settings-DYVzINdn.mjs → settings-DIsbHTRE.mjs} +3 -3
- package/dist/{settings-DYVzINdn.mjs.map → settings-DIsbHTRE.mjs.map} +1 -1
- package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-Yuv78yua.mjs} +2 -2
- package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-Yuv78yua.mjs.map} +1 -1
- package/dist/{site-url-Cm8-sJy7.mjs → site-url-mEVmwIFi.mjs} +2 -2
- package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-mEVmwIFi.mjs.map} +1 -1
- package/dist/{taxonomies-CGD6y79Q.mjs → taxonomies-BEW7S5AI.mjs} +10 -8
- package/dist/taxonomies-BEW7S5AI.mjs.map +1 -0
- package/dist/{taxonomies-C0bVme_m.mjs → taxonomies-UusDXv3C.mjs} +4 -4
- package/dist/{taxonomies-C0bVme_m.mjs.map → taxonomies-UusDXv3C.mjs.map} +1 -1
- package/dist/{taxonomy-Db5xwphL.mjs → taxonomy-CdllE4oq.mjs} +3 -3
- package/dist/{taxonomy-Db5xwphL.mjs.map → taxonomy-CdllE4oq.mjs.map} +1 -1
- package/dist/{transaction-NQj4VJ7Z.mjs → transaction-x2tJQ-A1.mjs} +1 -1
- package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-x2tJQ-A1.mjs.map} +1 -1
- package/dist/{transport-OnMNbsIA.d.mts → transport-BwQeeY2p.d.mts} +1 -1
- package/dist/{transport-OnMNbsIA.d.mts.map → transport-BwQeeY2p.d.mts.map} +1 -1
- package/dist/{types-CfyYQ7eY.mjs → types-BXSUSAjt.mjs} +16 -3
- package/dist/{types-CfyYQ7eY.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
- package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
- package/dist/{types-D8bhH891.mjs.map → types-DZk_y-MU.mjs.map} +1 -1
- package/dist/{types-DawhLFwy.d.mts → types-OT_Es5mp.d.mts} +26 -1
- package/dist/{types-DawhLFwy.d.mts.map → types-OT_Es5mp.d.mts.map} +1 -1
- package/dist/{types-i8_uzhMD.d.mts → types-WVmpZBJV.d.mts} +18 -3
- package/dist/types-WVmpZBJV.d.mts.map +1 -0
- package/dist/{user-tLdHUEXV.mjs → user-C0um7wrg.mjs} +18 -2
- package/dist/user-C0um7wrg.mjs.map +1 -0
- package/dist/{validate-Dy6nkNls.d.mts → validate-BPAHUSge.d.mts} +10 -2
- package/dist/validate-BPAHUSge.d.mts.map +1 -0
- package/dist/{validate-DWmnRg6E.mjs → validate-ZP9Dvg0P.mjs} +6 -3
- package/dist/validate-ZP9Dvg0P.mjs.map +1 -0
- package/dist/{validation-BQ_TP-On.mjs → validation-CE5i4q0c.mjs} +5 -5
- package/dist/{validation-BQ_TP-On.mjs.map → validation-CE5i4q0c.mjs.map} +1 -1
- package/dist/version-Dw0JXu45.mjs +7 -0
- package/dist/{version-CgcnMvqS.mjs.map → version-Dw0JXu45.mjs.map} +1 -1
- package/dist/{widgets-DzlINGI6.mjs → widgets-ClEnYQCH.mjs} +2 -2
- package/dist/{widgets-DzlINGI6.mjs.map → widgets-ClEnYQCH.mjs.map} +1 -1
- package/dist/{zod-generator-MMm56Prt.mjs → zod-generator-Djo_VHCt.mjs} +4 -3
- package/dist/zod-generator-Djo_VHCt.mjs.map +1 -0
- package/package.json +7 -7
- package/src/api/handlers/content.ts +107 -8
- package/src/api/handlers/index.ts +2 -0
- package/src/api/openapi/document.ts +25 -0
- package/src/api/schemas/content.ts +33 -0
- package/src/astro/integration/index.ts +98 -0
- package/src/astro/integration/routes.ts +6 -0
- package/src/astro/integration/virtual-modules.ts +39 -0
- package/src/astro/integration/vite-config.ts +12 -0
- package/src/astro/middleware/stream-end-metrics.ts +96 -0
- package/src/astro/middleware.ts +107 -31
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +8 -4
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id].ts +4 -2
- package/src/astro/routes/api/content/[collection]/authors.ts +34 -0
- package/src/astro/types.ts +8 -1
- package/src/bylines/index.ts +57 -0
- package/src/cli/commands/export-seed.ts +28 -12
- package/src/components/EmDashImage.astro +23 -4
- package/src/components/Image.astro +20 -3
- package/src/database/migrations/043_content_references.ts +121 -0
- package/src/database/migrations/runner.ts +9 -2
- package/src/database/repositories/content.ts +225 -67
- package/src/database/repositories/index.ts +7 -0
- package/src/database/repositories/relation.ts +467 -0
- package/src/database/repositories/types.ts +31 -0
- package/src/database/repositories/user.ts +18 -0
- package/src/database/types.ts +34 -0
- package/src/emdash-runtime.ts +318 -168
- package/src/index.ts +8 -1
- package/src/loader.ts +67 -34
- package/src/media/responsive.ts +125 -0
- package/src/menus/index.ts +27 -9
- package/src/plugins/cron.ts +3 -2
- package/src/plugins/hooks.ts +35 -6
- package/src/plugins/index.ts +5 -0
- package/src/plugins/manager.ts +1 -0
- package/src/plugins/scheduler/node.ts +9 -2
- package/src/query.ts +32 -5
- package/src/scheduled-publish.ts +153 -0
- package/src/schema/zod-generator.ts +6 -2
- package/src/seed/apply.ts +16 -6
- package/src/seed/types.ts +9 -0
- package/src/seed/validate.ts +15 -0
- package/src/taxonomies/index.ts +13 -8
- package/src/utils/init-lock.ts +143 -0
- package/src/virtual-modules.d.ts +11 -0
- package/dist/api-B7GATEYo.mjs.map +0 -1
- package/dist/apply-BrVqULFe.mjs.map +0 -1
- package/dist/byline-fields-CR5hGLMw.d.mts.map +0 -1
- package/dist/content-C7aJ7keg.mjs.map +0 -1
- package/dist/cron-DZovZUnC.mjs.map +0 -1
- package/dist/index-C8ciqSMJ.d.mts.map +0 -1
- package/dist/loader-BxyvbrZP.mjs.map +0 -1
- package/dist/menus-CIdZ_Q6U.mjs.map +0 -1
- package/dist/query-Cc649nDl.mjs.map +0 -1
- package/dist/redirects-C0L9JUk4.mjs.map +0 -1
- package/dist/runner-BiuUfx-V.mjs.map +0 -1
- package/dist/taxonomies-CGD6y79Q.mjs.map +0 -1
- package/dist/types-i8_uzhMD.d.mts.map +0 -1
- package/dist/user-tLdHUEXV.mjs.map +0 -1
- package/dist/validate-DWmnRg6E.mjs.map +0 -1
- package/dist/validate-Dy6nkNls.d.mts.map +0 -1
- package/dist/version-CgcnMvqS.mjs +0 -7
- package/dist/zod-generator-MMm56Prt.mjs.map +0 -1
- package/src/plugins/scheduler/piggyback.ts +0 -71
package/src/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ export type {
|
|
|
46
46
|
// API handlers
|
|
47
47
|
export {
|
|
48
48
|
handleContentList,
|
|
49
|
+
handleContentAuthors,
|
|
49
50
|
handleContentGet,
|
|
50
51
|
handleContentGetIncludingTrashed,
|
|
51
52
|
handleContentCreate,
|
|
@@ -201,6 +202,8 @@ export {
|
|
|
201
202
|
PluginManager,
|
|
202
203
|
createPluginManager,
|
|
203
204
|
PluginRouteError,
|
|
205
|
+
// Scheduler (Node timer heartbeat — used by virtual:emdash/scheduler)
|
|
206
|
+
NodeCronScheduler,
|
|
204
207
|
// Sandbox
|
|
205
208
|
NoopSandboxRunner,
|
|
206
209
|
SandboxNotAvailableError,
|
|
@@ -253,6 +256,10 @@ export type {
|
|
|
253
256
|
CollectionCommentSettings,
|
|
254
257
|
StoredComment,
|
|
255
258
|
|
|
259
|
+
// Scheduler types
|
|
260
|
+
CronScheduler,
|
|
261
|
+
SystemCleanupFn,
|
|
262
|
+
|
|
256
263
|
// Sandbox runtime types
|
|
257
264
|
SandboxRunner,
|
|
258
265
|
SandboxedPluginInstance,
|
|
@@ -406,7 +413,7 @@ export type {
|
|
|
406
413
|
} from "./menus/types.js";
|
|
407
414
|
|
|
408
415
|
// Bylines
|
|
409
|
-
export { getByline, getBylineBySlug } from "./bylines/index.js";
|
|
416
|
+
export { getByline, getBylineBySlug, getEntriesByByline } from "./bylines/index.js";
|
|
410
417
|
export type { BylineSummary, ContentBylineCredit } from "./database/repositories/types.js";
|
|
411
418
|
|
|
412
419
|
// Taxonomies
|
package/src/loader.ts
CHANGED
|
@@ -540,12 +540,16 @@ export interface CollectionFilter {
|
|
|
540
540
|
*/
|
|
541
541
|
cursor?: string;
|
|
542
542
|
/**
|
|
543
|
-
* Filter by field values, taxonomy terms, or ranges.
|
|
543
|
+
* Filter by field values, taxonomy terms, byline credits, or ranges.
|
|
544
544
|
*
|
|
545
545
|
* Taxonomy names are detected automatically and filtered via JOIN.
|
|
546
|
+
* The reserved `byline` key filters by byline credit (any credit, not
|
|
547
|
+
* just the primary one) via the `_emdash_content_bylines` junction
|
|
548
|
+
* table; its value is one or more byline translation groups.
|
|
546
549
|
* Other keys are treated as column filters on the content table.
|
|
547
550
|
*
|
|
548
551
|
* @example { category: 'news' } - taxonomy term
|
|
552
|
+
* @example { byline: '01HXYZ...' } - entries credited to a byline (any position)
|
|
549
553
|
* @example { series: 'main' } - exact match on a content field
|
|
550
554
|
* @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - date range
|
|
551
555
|
*/
|
|
@@ -673,13 +677,16 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
673
677
|
|
|
674
678
|
// Build cursor condition if cursor is provided
|
|
675
679
|
const cursorCondition = cursor ? buildCursorCondition(cursor, orderBy) : null;
|
|
676
|
-
const cursorConditionPrefixed = cursor
|
|
677
|
-
? buildCursorCondition(cursor, orderBy, tableName)
|
|
678
|
-
: null;
|
|
679
680
|
|
|
680
|
-
// Separate taxonomy filters from field filters
|
|
681
|
+
// Separate taxonomy / byline filters from field filters
|
|
681
682
|
let result: { rows: Record<string, unknown>[] };
|
|
682
683
|
let taxonomyFilter: { name: string; slugs: string[] } | null = null;
|
|
684
|
+
// A byline filter matches entries credited to any of the given
|
|
685
|
+
// byline translation groups via the `_emdash_content_bylines`
|
|
686
|
+
// junction table. `null` means no byline filter; an empty
|
|
687
|
+
// `groups` array means the filter was requested but matches
|
|
688
|
+
// nothing (short-circuited to an empty result below).
|
|
689
|
+
let bylineFilter: { groups: string[] } | null = null;
|
|
683
690
|
const fieldFilters: Record<string, WhereValue> = {};
|
|
684
691
|
|
|
685
692
|
if (where && Object.keys(where).length > 0) {
|
|
@@ -687,7 +694,16 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
687
694
|
|
|
688
695
|
for (const [key, value] of Object.entries(where)) {
|
|
689
696
|
if (value == null) continue;
|
|
690
|
-
if (
|
|
697
|
+
if (key === "byline") {
|
|
698
|
+
if (isWhereRange(value)) {
|
|
699
|
+
console.warn(
|
|
700
|
+
`[emdash] where filter: range operators are not supported on "byline", ignored`,
|
|
701
|
+
);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
const groups = Array.isArray(value) ? value : [value];
|
|
705
|
+
bylineFilter = { groups };
|
|
706
|
+
} else if (taxNames.has(key)) {
|
|
691
707
|
if (isWhereRange(value)) {
|
|
692
708
|
console.warn(
|
|
693
709
|
`[emdash] where filter: range operators are not supported on taxonomy "${key}", ignored`,
|
|
@@ -708,35 +724,27 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
708
724
|
}
|
|
709
725
|
}
|
|
710
726
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
fieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;
|
|
727
|
+
// A byline or taxonomy filter with no values matches nothing —
|
|
728
|
+
// short-circuit before building SQL (an empty `IN ()` is invalid
|
|
729
|
+
// SQL on both dialects).
|
|
730
|
+
if (
|
|
731
|
+
(bylineFilter && bylineFilter.groups.length === 0) ||
|
|
732
|
+
(taxonomyFilter && taxonomyFilter.slugs.length === 0)
|
|
733
|
+
) {
|
|
734
|
+
return { entries: [], cacheHint: { tags: [type] } };
|
|
735
|
+
}
|
|
721
736
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
AND t.name = ${taxonomyFilter.name}
|
|
734
|
-
AND t.slug IN (${sql.join(taxonomyFilter.slugs.map((s) => sql`${s}`))})
|
|
735
|
-
${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}
|
|
736
|
-
${orderByClause}
|
|
737
|
-
${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
|
|
738
|
-
`.execute(db);
|
|
739
|
-
} else {
|
|
737
|
+
{
|
|
738
|
+
// Taxonomy and byline filters are applied as correlated
|
|
739
|
+
// `EXISTS` semi-joins rather than `INNER JOIN ... DISTINCT`.
|
|
740
|
+
// A join fan-out would force `SELECT DISTINCT table.*`, and
|
|
741
|
+
// Postgres cannot apply DISTINCT to a row containing a `json`
|
|
742
|
+
// column (no equality operator), so the join approach throws
|
|
743
|
+
// there. EXISTS matches "credited/tagged at least once"
|
|
744
|
+
// without duplicating rows, needs no DISTINCT, and works on
|
|
745
|
+
// both SQLite and Postgres. The base query stays a single-
|
|
746
|
+
// table `SELECT *`, so all field/status/locale/cursor/order
|
|
747
|
+
// conditions reference unprefixed columns as before.
|
|
740
748
|
const orderByClause = buildOrderByClause(orderBy);
|
|
741
749
|
const statusCondition = buildStatusCondition(db, status);
|
|
742
750
|
const localeFilter = locale ? sql`AND locale = ${locale}` : sql``;
|
|
@@ -745,12 +753,37 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
745
753
|
const fieldCondsSQL =
|
|
746
754
|
fieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;
|
|
747
755
|
|
|
756
|
+
const taxonomyCond = taxonomyFilter
|
|
757
|
+
? sql`AND EXISTS (
|
|
758
|
+
SELECT 1 FROM content_taxonomies ct
|
|
759
|
+
INNER JOIN taxonomies t ON t.id = ct.taxonomy_id
|
|
760
|
+
WHERE ct.collection = ${type}
|
|
761
|
+
AND ct.entry_id = ${sql.ref(tableName)}.id
|
|
762
|
+
AND t.name = ${taxonomyFilter.name}
|
|
763
|
+
AND t.slug IN (${sql.join(taxonomyFilter.slugs.map((s) => sql`${s}`))})
|
|
764
|
+
)`
|
|
765
|
+
: sql``;
|
|
766
|
+
|
|
767
|
+
// `_emdash_content_bylines.byline_id` stores the byline's
|
|
768
|
+
// translation_group (migration 040), so a credit spans every
|
|
769
|
+
// locale variant of the byline and we match the group directly.
|
|
770
|
+
const bylineCond = bylineFilter
|
|
771
|
+
? sql`AND EXISTS (
|
|
772
|
+
SELECT 1 FROM _emdash_content_bylines cb
|
|
773
|
+
WHERE cb.collection_slug = ${type}
|
|
774
|
+
AND cb.content_id = ${sql.ref(tableName)}.id
|
|
775
|
+
AND cb.byline_id IN (${sql.join(bylineFilter.groups.map((g) => sql`${g}`))})
|
|
776
|
+
)`
|
|
777
|
+
: sql``;
|
|
778
|
+
|
|
748
779
|
result = await sql<Record<string, unknown>>`
|
|
749
780
|
SELECT * FROM ${sql.ref(tableName)}
|
|
750
781
|
WHERE deleted_at IS NULL
|
|
751
782
|
AND ${statusCondition}
|
|
752
783
|
${localeFilter}
|
|
753
784
|
${cursorCond}
|
|
785
|
+
${taxonomyCond}
|
|
786
|
+
${bylineCond}
|
|
754
787
|
${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}
|
|
755
788
|
${orderByClause}
|
|
756
789
|
${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive image helpers shared by the public Image components.
|
|
3
|
+
*
|
|
4
|
+
* These build a `srcset` for locally-stored / R2-stored media by delegating to
|
|
5
|
+
* Astro's configured image service (`astro:assets`). On Cloudflare that is the
|
|
6
|
+
* Images binding; on Node it is sharp; if neither is available it is a no-op
|
|
7
|
+
* passthrough. The calling `.astro` component passes Astro's `getImage` in so
|
|
8
|
+
* this module stays free of the `astro:assets` virtual import (which only
|
|
9
|
+
* resolves inside an Astro project, not in this precompiled package).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Standard responsive breakpoints. Matches CDN-provider srcset generation. */
|
|
13
|
+
export const RESPONSIVE_BREAKPOINTS = [640, 750, 828, 960, 1080, 1280, 1600, 1920];
|
|
14
|
+
|
|
15
|
+
/** Matches absolute http(s) URLs — the only shape Astro's image services optimize. */
|
|
16
|
+
const ABSOLUTE_HTTP_URL = /^https?:\/\//i;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Pick the srcset widths to generate for an image rendered at `maxWidth`.
|
|
20
|
+
* Includes breakpoints up to 2x (retina) plus the rendered width itself, so the
|
|
21
|
+
* browser always has an exact-fit candidate.
|
|
22
|
+
*/
|
|
23
|
+
export function responsiveWidths(maxWidth: number): number[] {
|
|
24
|
+
const cap = maxWidth * 2;
|
|
25
|
+
const widths = new Set(RESPONSIVE_BREAKPOINTS.filter((w) => w <= cap));
|
|
26
|
+
widths.add(maxWidth);
|
|
27
|
+
return [...widths].toSorted((a, b) => a - b);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Build the `sizes` attribute for an image with a known display width. */
|
|
31
|
+
export function responsiveSizes(width: number | undefined): string {
|
|
32
|
+
return width ? `(min-width: ${width}px) ${width}px, 100vw` : "100vw";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Make a same-origin media URL absolute so Astro's image service can optimize it.
|
|
37
|
+
*
|
|
38
|
+
* Astro only optimizes absolute http(s) URLs; a same-origin proxy path like
|
|
39
|
+
* `/_emdash/api/media/file/x.jpg` is otherwise treated as an unoptimizable
|
|
40
|
+
* public asset. Resolving it against the site's public origin (and authorizing
|
|
41
|
+
* that origin via `image.remotePatterns`) lets the service transform it.
|
|
42
|
+
*
|
|
43
|
+
* Only **same-origin** root-relative paths are resolved. Protocol-relative
|
|
44
|
+
* URLs (`//evil.com/x`) and backslash tricks (`/\evil.com`) also start with `/`
|
|
45
|
+
* but resolve to a different origin -- a classic SSRF vector once a
|
|
46
|
+
* remotePattern authorizes the media path -- so anything that escapes the
|
|
47
|
+
* origin is returned unchanged (and then skipped by `buildResponsiveImage`,
|
|
48
|
+
* which only accepts absolute http(s) URLs). Already-absolute URLs (CDN/public
|
|
49
|
+
* bucket) and non-path values (`data:`, `blob:`) are returned unchanged too.
|
|
50
|
+
*/
|
|
51
|
+
export function toAbsoluteMediaUrl(src: string, origin: string | undefined): string {
|
|
52
|
+
if (!src || !origin || !src.startsWith("/")) return src;
|
|
53
|
+
try {
|
|
54
|
+
const resolved = new URL(src, origin);
|
|
55
|
+
if (resolved.origin !== new URL(origin).origin) return src;
|
|
56
|
+
return resolved.href;
|
|
57
|
+
} catch {
|
|
58
|
+
return src;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Minimal structural subset of Astro's `getImage`. Astro's `ImageTransform`
|
|
64
|
+
* carries a `[key: string]: any` index signature, so the real `getImage` is
|
|
65
|
+
* assignable to this narrower type.
|
|
66
|
+
*/
|
|
67
|
+
export type GetImage = (options: {
|
|
68
|
+
src: string;
|
|
69
|
+
width?: number;
|
|
70
|
+
height?: number;
|
|
71
|
+
widths?: number[];
|
|
72
|
+
sizes?: string;
|
|
73
|
+
}) => Promise<{ src: string; srcSet?: { attribute?: string } | undefined }>;
|
|
74
|
+
|
|
75
|
+
export interface ResponsiveImage {
|
|
76
|
+
src: string;
|
|
77
|
+
srcset?: string;
|
|
78
|
+
sizes?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate a responsive `src`/`srcset`/`sizes` for a media URL via Astro's
|
|
83
|
+
* configured image service.
|
|
84
|
+
*
|
|
85
|
+
* Astro's image services (sharp, Cloudflare `/cdn-cgi/image`, and the default
|
|
86
|
+
* Cloudflare `cloudflare-binding` service) only optimize **absolute** URLs whose
|
|
87
|
+
* host is authorized via `image.domains` / `image.remotePatterns`. Anything else
|
|
88
|
+
* is passed through unchanged, which would yield a useless srcset (the same URL
|
|
89
|
+
* at every width descriptor). We therefore only attempt optimization for
|
|
90
|
+
* absolute http(s) URLs and verify the service actually rewrote the URL.
|
|
91
|
+
*
|
|
92
|
+
* Returns `null` so callers fall back to a plain `<img>` when:
|
|
93
|
+
* - dimensions are unknown (avoids an inferSize fetch on every render),
|
|
94
|
+
* - the URL is relative (a same-origin proxy/public asset Astro won't optimize),
|
|
95
|
+
* - the host isn't authorized (the service passed the URL through unchanged),
|
|
96
|
+
* - no image service is configured / `getImage` throws.
|
|
97
|
+
*/
|
|
98
|
+
export async function buildResponsiveImage(
|
|
99
|
+
getImage: GetImage,
|
|
100
|
+
opts: { src: string; width?: number; height?: number },
|
|
101
|
+
): Promise<ResponsiveImage | null> {
|
|
102
|
+
const { src, width, height } = opts;
|
|
103
|
+
if (!src || !width || !height) return null;
|
|
104
|
+
if (!ABSOLUTE_HTTP_URL.test(src)) return null;
|
|
105
|
+
try {
|
|
106
|
+
const sizes = responsiveSizes(width);
|
|
107
|
+
const result = await getImage({
|
|
108
|
+
src,
|
|
109
|
+
width,
|
|
110
|
+
height,
|
|
111
|
+
widths: responsiveWidths(width),
|
|
112
|
+
sizes,
|
|
113
|
+
});
|
|
114
|
+
// Passthrough: the service returned the source unchanged (unauthorized
|
|
115
|
+
// host or no optimization available). Don't emit a no-op srcset.
|
|
116
|
+
if (!result.src || result.src === src) return null;
|
|
117
|
+
return {
|
|
118
|
+
src: result.src,
|
|
119
|
+
srcset: result.srcSet?.attribute || undefined,
|
|
120
|
+
sizes,
|
|
121
|
+
};
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/menus/index.ts
CHANGED
|
@@ -136,15 +136,10 @@ async function buildMenuTree(
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
const urlPatterns =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.select(["slug", "url_pattern"])
|
|
144
|
-
.where("slug", "in", [...collectionSlugs])
|
|
145
|
-
.execute();
|
|
146
|
-
for (const row of rows) urlPatterns.set(row.slug, row.url_pattern);
|
|
147
|
-
}
|
|
139
|
+
const urlPatterns =
|
|
140
|
+
collectionSlugs.size > 0
|
|
141
|
+
? await getCollectionUrlPatterns(db, collectionSlugs)
|
|
142
|
+
: new Map<string, string | null>();
|
|
148
143
|
|
|
149
144
|
const resolvedItems = await Promise.all(
|
|
150
145
|
items.map((item) => resolveMenuItem(item, db, urlPatterns, locale)),
|
|
@@ -173,6 +168,29 @@ async function buildMenuTree(
|
|
|
173
168
|
return rootItems;
|
|
174
169
|
}
|
|
175
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Look up the `url_pattern` for a set of collection slugs, request-cached so
|
|
173
|
+
* a page rendering several menus (header, footer, ...) only pays for the
|
|
174
|
+
* lookup once per distinct slug set. Callers must treat the returned map as
|
|
175
|
+
* read-only — it is shared across cache hits within the request.
|
|
176
|
+
*/
|
|
177
|
+
function getCollectionUrlPatterns(
|
|
178
|
+
db: Kysely<Database>,
|
|
179
|
+
collectionSlugs: Set<string>,
|
|
180
|
+
): Promise<Map<string, string | null>> {
|
|
181
|
+
const key = `menu-collection-patterns:${[...collectionSlugs].toSorted().join(",")}`;
|
|
182
|
+
return requestCached(key, async () => {
|
|
183
|
+
const rows = await db
|
|
184
|
+
.selectFrom("_emdash_collections")
|
|
185
|
+
.select(["slug", "url_pattern"])
|
|
186
|
+
.where("slug", "in", [...collectionSlugs])
|
|
187
|
+
.execute();
|
|
188
|
+
const urlPatterns = new Map<string, string | null>();
|
|
189
|
+
for (const row of rows) urlPatterns.set(row.slug, row.url_pattern);
|
|
190
|
+
return urlPatterns;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
176
194
|
/**
|
|
177
195
|
* Resolve a single menu item's URL. `reference_id` is a translation_group
|
|
178
196
|
* (migration 036 remapped all existing references); we join it against
|
package/src/plugins/cron.ts
CHANGED
|
@@ -34,8 +34,9 @@ export type RescheduleFn = () => void;
|
|
|
34
34
|
/**
|
|
35
35
|
* Executes overdue cron tasks.
|
|
36
36
|
*
|
|
37
|
-
* Called by platform
|
|
38
|
-
*
|
|
37
|
+
* Called by the platform driver: the NodeCronScheduler timer on Node, or the
|
|
38
|
+
* Worker's `scheduled()` handler (via runScheduledTasks) on Cloudflare.
|
|
39
|
+
* Stateless — all state lives in the database.
|
|
39
40
|
*/
|
|
40
41
|
export class CronExecutor {
|
|
41
42
|
constructor(
|
package/src/plugins/hooks.ts
CHANGED
|
@@ -1297,6 +1297,13 @@ export interface ExclusiveHookResolutionOptions {
|
|
|
1297
1297
|
isActive: (pluginId: string) => boolean;
|
|
1298
1298
|
/** Read an option value from persistent storage. */
|
|
1299
1299
|
getOption: (key: string) => Promise<string | null>;
|
|
1300
|
+
/**
|
|
1301
|
+
* Batch-read option values for many keys in a single round trip.
|
|
1302
|
+
* When provided, resolution reads all current selections through this
|
|
1303
|
+
* instead of one getOption() call per hook. Keys absent from the
|
|
1304
|
+
* returned map are treated as unset.
|
|
1305
|
+
*/
|
|
1306
|
+
getOptions?: (keys: string[]) => Promise<ReadonlyMap<string, string>>;
|
|
1300
1307
|
/** Write an option value to persistent storage. */
|
|
1301
1308
|
setOption: (key: string, value: string) => Promise<void>;
|
|
1302
1309
|
/** Delete an option from persistent storage. */
|
|
@@ -1322,8 +1329,26 @@ const EXCLUSIVE_HOOK_KEY_PREFIX = "emdash:exclusive_hook:";
|
|
|
1322
1329
|
* 5. If multiple providers and no hint → leave unselected (admin must choose).
|
|
1323
1330
|
*/
|
|
1324
1331
|
export async function resolveExclusiveHooks(opts: ExclusiveHookResolutionOptions): Promise<void> {
|
|
1325
|
-
const { pipeline, isActive, getOption, setOption, deleteOption, preferredHints } =
|
|
1332
|
+
const { pipeline, isActive, getOption, getOptions, setOption, deleteOption, preferredHints } =
|
|
1333
|
+
opts;
|
|
1326
1334
|
const exclusiveHookNames = pipeline.getRegisteredExclusiveHooks();
|
|
1335
|
+
if (exclusiveHookNames.length === 0) return;
|
|
1336
|
+
|
|
1337
|
+
// Batch-read current selections in one round trip when the caller
|
|
1338
|
+
// provides a batch reader (1 query instead of N sequential gets).
|
|
1339
|
+
let batchedSelections: ReadonlyMap<string, string> | undefined;
|
|
1340
|
+
if (getOptions) {
|
|
1341
|
+
try {
|
|
1342
|
+
batchedSelections = await getOptions(
|
|
1343
|
+
exclusiveHookNames.map((hookName) => `${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`),
|
|
1344
|
+
);
|
|
1345
|
+
} catch {
|
|
1346
|
+
// Options table may not be ready. Matches the per-key tolerance
|
|
1347
|
+
// below: every hook's read would fail, so resolution is skipped
|
|
1348
|
+
// entirely without touching any selection.
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1327
1352
|
|
|
1328
1353
|
for (const hookName of exclusiveHookNames) {
|
|
1329
1354
|
const providers = pipeline.getExclusiveHookProviders(hookName);
|
|
@@ -1333,11 +1358,15 @@ export async function resolveExclusiveHooks(opts: ExclusiveHookResolutionOptions
|
|
|
1333
1358
|
|
|
1334
1359
|
const key = `${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`;
|
|
1335
1360
|
let currentSelection: string | null = null;
|
|
1336
|
-
|
|
1337
|
-
currentSelection =
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1361
|
+
if (batchedSelections) {
|
|
1362
|
+
currentSelection = batchedSelections.get(key) ?? null;
|
|
1363
|
+
} else {
|
|
1364
|
+
try {
|
|
1365
|
+
currentSelection = await getOption(key);
|
|
1366
|
+
} catch {
|
|
1367
|
+
// Options table may not be ready
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1341
1370
|
}
|
|
1342
1371
|
|
|
1343
1372
|
// If selection exists and the plugin is still active → keep it
|
package/src/plugins/index.ts
CHANGED
|
@@ -63,6 +63,11 @@ export type { RouteResult, InvokeRouteOptions } from "./routes.js";
|
|
|
63
63
|
export { PluginManager, createPluginManager } from "./manager.js";
|
|
64
64
|
export type { PluginManagerOptions, PluginState } from "./manager.js";
|
|
65
65
|
|
|
66
|
+
// Scheduler (Node timer-based heartbeat; consumed by the generated
|
|
67
|
+
// virtual:emdash/scheduler module on non-serverless adapters)
|
|
68
|
+
export { NodeCronScheduler } from "./scheduler/node.js";
|
|
69
|
+
export type { CronScheduler, SystemCleanupFn } from "./scheduler/types.js";
|
|
70
|
+
|
|
66
71
|
// Sandbox
|
|
67
72
|
export {
|
|
68
73
|
NoopSandboxRunner,
|
package/src/plugins/manager.ts
CHANGED
|
@@ -544,6 +544,7 @@ export class PluginManager {
|
|
|
544
544
|
pipeline: this.hookPipeline!,
|
|
545
545
|
isActive: (pluginId) => this.isActive(pluginId),
|
|
546
546
|
getOption: (key) => optionsRepo.get<string>(key),
|
|
547
|
+
getOptions: (keys) => optionsRepo.getMany<string>(keys),
|
|
547
548
|
setOption: (key, value) => optionsRepo.set(key, value),
|
|
548
549
|
deleteOption: async (key) => {
|
|
549
550
|
await optionsRepo.delete(key);
|
|
@@ -15,8 +15,15 @@ import type { CronScheduler, SystemCleanupFn } from "./types.js";
|
|
|
15
15
|
/** Minimum polling interval (ms) — prevents tight loops if next_run_at is in the past */
|
|
16
16
|
const MIN_INTERVAL_MS = 1000;
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Maximum polling interval (ms). Each wake runs the maintenance pass — stale
|
|
20
|
+
* lock recovery *and* the scheduled-publishing sweep + system cleanup. The cap
|
|
21
|
+
* is the worst-case latency for scheduled content when no plugin cron task is
|
|
22
|
+
* due sooner (`getNextDueTime()` only knows about cron tasks, not content
|
|
23
|
+
* `scheduled_at`). Held at 60s so Node publish latency matches the Cloudflare
|
|
24
|
+
* Cron Trigger cadence (`* * * * *`) rather than lagging up to five minutes.
|
|
25
|
+
*/
|
|
26
|
+
const MAX_INTERVAL_MS = 60 * 1000;
|
|
20
27
|
|
|
21
28
|
export class NodeCronScheduler implements CronScheduler {
|
|
22
29
|
private timer: ReturnType<typeof setTimeout> | null = null;
|
package/src/query.ts
CHANGED
|
@@ -101,13 +101,19 @@ export interface CollectionFilter {
|
|
|
101
101
|
*/
|
|
102
102
|
cursor?: string;
|
|
103
103
|
/**
|
|
104
|
-
* Filter by field values, taxonomy terms, or ranges.
|
|
104
|
+
* Filter by field values, taxonomy terms, byline credits, or ranges.
|
|
105
105
|
*
|
|
106
106
|
* Taxonomy names are detected automatically and filtered via JOIN.
|
|
107
|
+
* The reserved `byline` key filters by byline credit (any credit, not
|
|
108
|
+
* just the primary one) via the `_emdash_content_bylines` junction
|
|
109
|
+
* table; its value is one or more byline translation groups. This
|
|
110
|
+
* matches co-authored entries, which `primary_byline_id` alone misses.
|
|
107
111
|
* Other keys are treated as column filters on the content table.
|
|
108
112
|
*
|
|
109
113
|
* @example { category: 'news' } - Filter by taxonomy term
|
|
110
114
|
* @example { category: ['news', 'featured'] } - Filter by multiple terms (OR)
|
|
115
|
+
* @example { byline: '01HXYZ...' } - Entries credited to a byline (any position)
|
|
116
|
+
* @example { byline: ['01HXYZ...', '01HABC...'] } - Credited to any of these bylines (OR)
|
|
111
117
|
* @example { series: 'main' } - Exact match on a content field
|
|
112
118
|
* @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - Date range
|
|
113
119
|
*/
|
|
@@ -533,7 +539,9 @@ async function getEmDashCollectionUncached<T extends string, D = InferCollection
|
|
|
533
539
|
// round-trip cost on remote databases (D1 replicas, etc.).
|
|
534
540
|
await Promise.all([
|
|
535
541
|
hydrateEntryBylines(type, entriesWithEdit),
|
|
536
|
-
|
|
542
|
+
// Hydrate terms in the same locale the content rows were resolved to,
|
|
543
|
+
// otherwise localized entries get default-locale taxonomy terms (#1441).
|
|
544
|
+
hydrateEntryTerms(type, entriesWithEdit, resolvedLocale),
|
|
537
545
|
]);
|
|
538
546
|
|
|
539
547
|
return { entries: entriesWithEdit, nextCursor, cacheHint: cacheHint ?? {} };
|
|
@@ -611,7 +619,17 @@ export async function getEmDashEntry<T extends string, D = InferCollectionData<T
|
|
|
611
619
|
wrapped: ContentEntry<D>,
|
|
612
620
|
opts: { isPreview: boolean; fallbackLocale?: string; cacheHint: CacheHint },
|
|
613
621
|
): Promise<EntryResult<D>> {
|
|
614
|
-
|
|
622
|
+
// Hydrate terms in the entry's resolved locale (fallback-aware) so a
|
|
623
|
+
// localized entry never picks up default-locale taxonomy terms (#1441).
|
|
624
|
+
// When i18n is disabled we leave the locale unset to preserve the
|
|
625
|
+
// legacy "do not filter by locale" behaviour.
|
|
626
|
+
const termLocale = isI18nEnabled()
|
|
627
|
+
? dataStr(entryData(wrapped), "locale") || undefined
|
|
628
|
+
: undefined;
|
|
629
|
+
await Promise.all([
|
|
630
|
+
hydrateEntryBylines(type, [wrapped]),
|
|
631
|
+
hydrateEntryTerms(type, [wrapped], termLocale),
|
|
632
|
+
]);
|
|
615
633
|
return {
|
|
616
634
|
entry: wrapped,
|
|
617
635
|
isPreview: opts.isPreview,
|
|
@@ -788,9 +806,18 @@ async function hydrateEntryBylines<D>(type: string, entries: ContentEntry<D>[]):
|
|
|
788
806
|
* results and call getEntryTerms() per entry. With hydration, the list page
|
|
789
807
|
* stays at a single round-trip for term data.
|
|
790
808
|
*
|
|
809
|
+
* `locale` must be the locale the entries were resolved to. It is forwarded to
|
|
810
|
+
* `getAllTermsForEntries` so terms are returned in the entry's locale rather
|
|
811
|
+
* than falling back to the request-context / default locale (#1441). Pass
|
|
812
|
+
* `undefined` to keep the legacy "do not filter by locale" behaviour.
|
|
813
|
+
*
|
|
791
814
|
* Fails silently if the taxonomy tables don't exist yet (pre-migration).
|
|
792
815
|
*/
|
|
793
|
-
async function hydrateEntryTerms<D>(
|
|
816
|
+
async function hydrateEntryTerms<D>(
|
|
817
|
+
type: string,
|
|
818
|
+
entries: ContentEntry<D>[],
|
|
819
|
+
locale?: string,
|
|
820
|
+
): Promise<void> {
|
|
794
821
|
if (entries.length === 0) return;
|
|
795
822
|
|
|
796
823
|
try {
|
|
@@ -799,7 +826,7 @@ async function hydrateEntryTerms<D>(type: string, entries: ContentEntry<D>[]): P
|
|
|
799
826
|
const ids = entries.map((e) => dataStr(entryData(e), "id")).filter(Boolean);
|
|
800
827
|
if (ids.length === 0) return;
|
|
801
828
|
|
|
802
|
-
const termsMap = await getAllTermsForEntries(type, ids);
|
|
829
|
+
const termsMap = await getAllTermsForEntries(type, ids, { locale });
|
|
803
830
|
|
|
804
831
|
for (const entry of entries) {
|
|
805
832
|
const data = entryData(entry);
|