emdash 0.29.0 → 0.31.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-DPAkL6QF.d.mts → adapters-C0ONzkUP.d.mts} +10 -1
- package/dist/adapters-C0ONzkUP.d.mts.map +1 -0
- package/dist/{allowed-origins-FXB-FplB.mjs → allowed-origins-CCEi9bPI.mjs} +2 -2
- package/dist/{allowed-origins-FXB-FplB.mjs.map → allowed-origins-CCEi9bPI.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.d.mts.map +1 -1
- package/dist/api/route-utils.mjs +19 -19
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +5 -4
- package/dist/{api-DVbHNApo.mjs → api-EibxGP-V.mjs} +260 -160
- package/dist/api-EibxGP-V.mjs.map +1 -0
- package/dist/api-tokens-Cvmixds7.mjs +3 -0
- package/dist/{api-tokens-UYBkl17d.mjs → api-tokens-uPt8UDpx.mjs} +2 -2
- package/dist/{api-tokens-UYBkl17d.mjs.map → api-tokens-uPt8UDpx.mjs.map} +1 -1
- package/dist/{apply-D23iUV4q.mjs → apply-fGHCAwWN.mjs} +42 -44
- package/dist/apply-fGHCAwWN.mjs.map +1 -0
- package/dist/astro/index.d.mts +12 -13
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +90 -11
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +10 -11
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +56 -107
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.mjs +7 -7
- package/dist/astro/middleware/request-context.mjs +4 -4
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +47 -3
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +410 -123
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/PluginRegistry.d.mts +5 -1
- package/dist/astro/routes/PluginRegistry.d.mts.map +1 -1
- package/dist/astro/routes/PluginRegistry.mjs +2 -1
- package/dist/astro/routes/PluginRegistry.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +9 -8
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +9 -8
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +7 -7
- package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +10 -9
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +10 -9
- package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +10 -9
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +16 -15
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -22
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +20 -18
- package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +13 -12
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs +11 -10
- package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/index.mjs +11 -10
- package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
- 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/media-usage/repair.mjs +12 -10
- package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +41 -40
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +41 -40
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +46 -40
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.d.mts +8 -0
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +39 -0
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs.map +1 -0
- package/dist/astro/routes/api/admin/plugins/_id_/settings.d.mts +9 -0
- package/dist/astro/routes/api/admin/plugins/_id_/settings.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +78 -0
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -0
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +40 -39
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +43 -40
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +46 -40
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +40 -39
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +46 -41
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +40 -39
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +40 -39
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +44 -41
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +40 -39
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +45 -42
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +41 -40
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +40 -39
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +40 -39
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +4 -4
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +9 -8
- package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +5 -5
- package/dist/astro/routes/api/admin/users/index.mjs +9 -8
- package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/accept.mjs +4 -4
- package/dist/astro/routes/api/auth/invite/complete.mjs +13 -12
- package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/index.mjs +10 -9
- package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/register-options.mjs +12 -11
- package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/logout.mjs +4 -4
- package/dist/astro/routes/api/auth/magic-link/send.mjs +12 -11
- package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +5 -5
- package/dist/astro/routes/api/auth/me.mjs +9 -8
- package/dist/astro/routes/api/auth/me.mjs.map +1 -1
- package/dist/astro/routes/api/auth/mode.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/mode.mjs +6 -3
- package/dist/astro/routes/api/auth/mode.mjs.map +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +15 -5
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs.map +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +11 -5
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +8 -7
- package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/options.mjs +13 -12
- package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +11 -10
- package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +12 -11
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/verify.mjs +12 -11
- package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/complete.mjs +13 -12
- package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/request.mjs +12 -11
- package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/verify.mjs +4 -4
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -13
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +13 -12
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +5 -4
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +14 -13
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +10 -8
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +20 -19
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +18 -17
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -4
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +10 -8
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +19 -18
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +5 -4
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -9
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/index.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/index.mjs +12 -10
- package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/trash.mjs +10 -9
- package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
- package/dist/astro/routes/api/dashboard.mjs +8 -8
- package/dist/astro/routes/api/dev/emails.mjs +4 -4
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +17 -16
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -12
- package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/execute.mjs +13 -13
- package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -11
- package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +11 -10
- package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +11 -10
- package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -12
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +11 -10
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +17 -16
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -12
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +24 -23
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +7 -8
- package/dist/astro/routes/api/manifest.mjs.map +1 -1
- package/dist/astro/routes/api/mcp.d.mts.map +1 -1
- package/dist/astro/routes/api/mcp.mjs +149 -45
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +9 -8
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/usage.d.mts +8 -0
- package/dist/astro/routes/api/media/_id_/usage.d.mts.map +1 -0
- package/dist/astro/routes/api/media/_id_/usage.mjs +37 -0
- package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -0
- package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +26 -10
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +4 -3
- package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
- package/dist/astro/routes/api/media/providers/index.mjs +4 -4
- package/dist/astro/routes/api/media/upload-url.mjs +10 -9
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.d.mts.map +1 -1
- package/dist/astro/routes/api/media.mjs +31 -11
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +10 -9
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.d.mts.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +29 -16
- package/dist/astro/routes/api/oauth/authorize.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
- package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
- package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
- package/dist/astro/routes/api/oauth/register.mjs +4 -4
- package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
- package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
- package/dist/astro/routes/api/oauth/token.mjs +7 -7
- package/dist/astro/routes/api/openapi.json.mjs +52 -24
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +20 -32
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/index.mjs +12 -11
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -11
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +13 -12
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +13 -12
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/index.mjs +16 -15
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/translations.mjs +11 -11
- package/dist/astro/routes/api/relations/index.mjs +16 -15
- package/dist/astro/routes/api/relations/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -4
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +40 -39
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +40 -39
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +40 -39
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +40 -39
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +40 -39
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +9 -9
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +40 -39
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +40 -39
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +13 -12
- package/dist/astro/routes/api/search/enable.mjs.map +1 -1
- package/dist/astro/routes/api/search/index.mjs +12 -11
- package/dist/astro/routes/api/search/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/rebuild.mjs +13 -12
- package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
- package/dist/astro/routes/api/search/stats.mjs +8 -8
- package/dist/astro/routes/api/search/suggest.mjs +12 -11
- package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
- package/dist/astro/routes/api/sections/_slug_.mjs +12 -11
- package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/sections/index.mjs +12 -11
- package/dist/astro/routes/api/sections/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/backups/archives/_name_.d.mts +9 -0
- package/dist/astro/routes/api/settings/backups/archives/_name_.d.mts.map +1 -0
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +48 -0
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs.map +1 -0
- package/dist/astro/routes/api/settings/backups/archives/index.d.mts +8 -0
- package/dist/astro/routes/api/settings/backups/archives/index.d.mts.map +1 -0
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +23 -0
- package/dist/astro/routes/api/settings/backups/archives/index.mjs.map +1 -0
- package/dist/astro/routes/api/settings/backups/export.d.mts +8 -0
- package/dist/astro/routes/api/settings/backups/export.d.mts.map +1 -0
- package/dist/astro/routes/api/settings/backups/export.mjs +35 -0
- package/dist/astro/routes/api/settings/backups/export.mjs.map +1 -0
- package/dist/astro/routes/api/settings/backups/index.d.mts +9 -0
- package/dist/astro/routes/api/settings/backups/index.d.mts.map +1 -0
- package/dist/astro/routes/api/settings/backups/index.mjs +52 -0
- package/dist/astro/routes/api/settings/backups/index.mjs.map +1 -0
- package/dist/astro/routes/api/settings/email.mjs +5 -5
- package/dist/astro/routes/api/settings.mjs +14 -13
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +19 -15
- package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin.mjs +18 -14
- package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +26 -26
- package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
- package/dist/astro/routes/api/setup/index.mjs +28 -27
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +5 -5
- package/dist/astro/routes/api/snapshot.mjs +8 -215
- package/dist/astro/routes/api/snapshot.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +15 -14
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +15 -14
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +15 -14
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +15 -14
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +6 -6
- package/dist/astro/routes/api/typegen.mjs +6 -6
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.d.mts.map +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +4 -3
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs.map +1 -1
- package/dist/astro/routes/api/well-known/oauth-protected-resource.d.mts.map +1 -1
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +4 -3
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +9 -8
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +12 -11
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +12 -11
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +7 -7
- package/dist/astro/routes/api/widget-areas/index.mjs +12 -11
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +4 -4
- package/dist/astro/routes/robots.txt.mjs +7 -7
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +11 -11
- package/dist/astro/routes/sitemap.xml.mjs +8 -8
- package/dist/astro/types.d.mts +38 -17
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-BmCwQCib.mjs → authorize-6WPZa1xZ.mjs} +7 -4
- package/dist/authorize-6WPZa1xZ.mjs.map +1 -0
- package/dist/backup-Qr9PaGUo.mjs +241 -0
- package/dist/backup-Qr9PaGUo.mjs.map +1 -0
- package/dist/{base64-C74Msx8h.mjs → base64-B-PsqheR.mjs} +8 -4
- package/dist/base64-B-PsqheR.mjs.map +1 -0
- package/dist/{byline-BWb5H-65.mjs → byline-BTOxUlFF.mjs} +6 -6
- package/dist/{byline-BWb5H-65.mjs.map → byline-BTOxUlFF.mjs.map} +1 -1
- package/dist/{byline-fields-DO6bAYK8.mjs → byline-fields-CdU_LTF1.mjs} +1 -1
- package/dist/{byline-fields-DO6bAYK8.mjs.map → byline-fields-CdU_LTF1.mjs.map} +1 -1
- package/dist/{bylines-CHojDMpj.mjs → bylines-FyZu6TbJ.mjs} +7 -7
- package/dist/{bylines-CHojDMpj.mjs.map → bylines-FyZu6TbJ.mjs.map} +1 -1
- package/dist/{bylines-BGuKFaBy.mjs → bylines-oVN0iPwX.mjs} +12 -8
- package/dist/bylines-oVN0iPwX.mjs.map +1 -0
- package/dist/{cache-BeR7RfN8.mjs → cache-DDwy3JDW.mjs} +2 -2
- package/dist/{cache-BeR7RfN8.mjs.map → cache-DDwy3JDW.mjs.map} +1 -1
- package/dist/{challenge-store-B426wQ6O.mjs → challenge-store-BFzgFRog.mjs} +1 -1
- package/dist/{challenge-store-B426wQ6O.mjs.map → challenge-store-BFzgFRog.mjs.map} +1 -1
- package/dist/{chunks-BU5_nrAC.mjs → chunks-Bq-5DPrD.mjs} +2 -2
- package/dist/{chunks-BU5_nrAC.mjs.map → chunks-Bq-5DPrD.mjs.map} +1 -1
- package/dist/cli/index.mjs +139 -32
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +54 -3
- package/dist/client/index.d.mts.map +1 -1
- package/dist/client/index.mjs +16 -4
- package/dist/client/index.mjs.map +1 -1
- package/dist/{comment-SWh31kS0.mjs → comment-B9cUud8H.mjs} +2 -2
- package/dist/{comment-SWh31kS0.mjs.map → comment-B9cUud8H.mjs.map} +1 -1
- package/dist/{comment-reaction-DgcpziXI.mjs → comment-reaction-VUXO981F.mjs} +2 -2
- package/dist/{comment-reaction-DgcpziXI.mjs.map → comment-reaction-VUXO981F.mjs.map} +1 -1
- package/dist/{comments-kZYGppMS.mjs → comments-PXj1E9Si.mjs} +3 -3
- package/dist/{comments-kZYGppMS.mjs.map → comments-PXj1E9Si.mjs.map} +1 -1
- package/dist/{components-CliOkZAs.mjs → components-DZkL4Fx0.mjs} +1 -1
- package/dist/{components-CliOkZAs.mjs.map → components-DZkL4Fx0.mjs.map} +1 -1
- package/dist/{config-CVssduLe.mjs → config-B6b_BobR.mjs} +6 -2
- package/dist/config-B6b_BobR.mjs.map +1 -0
- package/dist/{content-BO61Sx4j.mjs → content-BC43pICM.mjs} +53 -19
- package/dist/content-BC43pICM.mjs.map +1 -0
- package/dist/{content-refresh-BkkApzeS.mjs → content-refresh-CPMSauD3.mjs} +96 -4
- package/dist/content-refresh-CPMSauD3.mjs.map +1 -0
- package/dist/{context-DtSFYFuS.mjs → context-DOurRIrb.mjs} +97 -74
- package/dist/context-DOurRIrb.mjs.map +1 -0
- package/dist/{cron-YFJ7uNVA.mjs → cron-BlKIMD_e.mjs} +1 -1
- package/dist/{cron-YFJ7uNVA.mjs.map → cron-BlKIMD_e.mjs.map} +1 -1
- package/dist/{dashboard-xo_gPs_D.mjs → dashboard-CBm4zhc3.mjs} +4 -4
- package/dist/{dashboard-xo_gPs_D.mjs.map → dashboard-CBm4zhc3.mjs.map} +1 -1
- package/dist/database/pg-migration-lock.d.mts.map +1 -1
- package/dist/database/pg-migration-lock.mjs +1 -4
- package/dist/database/pg-migration-lock.mjs.map +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-BKuXh_fi.mjs → db-errors-CcWLaRiR.mjs} +4 -2
- package/dist/db-errors-CcWLaRiR.mjs.map +1 -0
- package/dist/{default-DY5ovGUG.mjs → default-C4v9KP56.mjs} +1 -1
- package/dist/{default-DY5ovGUG.mjs.map → default-C4v9KP56.mjs.map} +1 -1
- package/dist/{device-flow-CPXQ_QfW.mjs → device-flow-BeA6lUS1.mjs} +5 -6
- package/dist/device-flow-BeA6lUS1.mjs.map +1 -0
- package/dist/{dialect-helpers-Cue7QQOR.mjs → dialect-helpers-BtOwo2us.mjs} +60 -14
- package/dist/dialect-helpers-BtOwo2us.mjs.map +1 -0
- package/dist/{email-console-CjkQc7oY.mjs → email-console-C-9Ng8DM.mjs} +1 -1
- package/dist/{email-console-CjkQc7oY.mjs.map → email-console-C-9Ng8DM.mjs.map} +1 -1
- package/dist/error-DJ98TRbQ.mjs +90 -0
- package/dist/error-DJ98TRbQ.mjs.map +1 -0
- package/dist/{error-CfJMLSkd.mjs → errors-DtEXIQQV.mjs} +15 -84
- package/dist/errors-DtEXIQQV.mjs.map +1 -0
- package/dist/{escape-r8_GbEU2.mjs → escape-CFedIB1C.mjs} +1 -1
- package/dist/{escape-r8_GbEU2.mjs.map → escape-CFedIB1C.mjs.map} +1 -1
- package/dist/{field-defs-cache-BVrNCgSj.mjs → field-defs-cache-KfNJ_qLQ.mjs} +2 -2
- package/dist/{field-defs-cache-BVrNCgSj.mjs.map → field-defs-cache-KfNJ_qLQ.mjs.map} +1 -1
- package/dist/{fts-manager-BD9s77qh.mjs → fts-manager-hyCU9OOp.mjs} +3 -3
- package/dist/{fts-manager-BD9s77qh.mjs.map → fts-manager-hyCU9OOp.mjs.map} +1 -1
- package/dist/{hreflang-CgIxhH4k.d.mts → hreflang-CIxH_LKz.d.mts} +2 -2
- package/dist/{hreflang-CgIxhH4k.d.mts.map → hreflang-CIxH_LKz.d.mts.map} +1 -1
- package/dist/{hreflang-pAWUiPd_.mjs → hreflang-CbYeCzl9.mjs} +7 -7
- package/dist/{hreflang-pAWUiPd_.mjs.map → hreflang-CbYeCzl9.mjs.map} +1 -1
- package/dist/{import-2jHtLzsO.mjs → import-oDYCgNYQ.mjs} +4 -4
- package/dist/{import-2jHtLzsO.mjs.map → import-oDYCgNYQ.mjs.map} +1 -1
- package/dist/{index-DxjWLKGT.d.mts → index-BEui-cRd.d.mts} +3 -3
- package/dist/{index-DxjWLKGT.d.mts.map → index-BEui-cRd.d.mts.map} +1 -1
- package/dist/{index-BXOCyLj0.d.mts → index-BcEH_FfS.d.mts} +812 -16
- package/dist/index-BcEH_FfS.d.mts.map +1 -0
- package/dist/index.d.mts +18 -19
- package/dist/index.mjs +61 -60
- package/dist/{load-DgoWMdRX.mjs → load-Dg5gtWma.mjs} +2 -2
- package/dist/{load-DgoWMdRX.mjs.map → load-Dg5gtWma.mjs.map} +1 -1
- package/dist/{loader-Rb8QlGZp.mjs → loader-D4rigvjR.mjs} +170 -6
- package/dist/loader-D4rigvjR.mjs.map +1 -0
- package/dist/{manifest-schema-DeGNma2O.mjs → manifest-schema-bCq54i7F.mjs} +16 -2
- package/dist/manifest-schema-bCq54i7F.mjs.map +1 -0
- package/dist/media/image-endpoint.d.mts +30 -2
- package/dist/media/image-endpoint.d.mts.map +1 -1
- package/dist/media/image-endpoint.mjs +29 -3
- package/dist/media/image-endpoint.mjs.map +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/local-runtime.d.mts +12 -13
- package/dist/media/local-runtime.d.mts.map +1 -1
- package/dist/media/local-runtime.mjs +6 -6
- package/dist/media-BJ6SRY3g.mjs +247 -0
- package/dist/media-BJ6SRY3g.mjs.map +1 -0
- package/dist/{media-allowlist-Dc05vKtt.mjs → media-allowlist-jUppPWFy.mjs} +1 -1
- package/dist/{media-allowlist-Dc05vKtt.mjs.map → media-allowlist-jUppPWFy.mjs.map} +1 -1
- package/dist/{media-D2ZFowpn.mjs → media-its1WLYv.mjs} +2 -2
- package/dist/{media-D2ZFowpn.mjs.map → media-its1WLYv.mjs.map} +1 -1
- package/dist/media-upload-DNu3QySc.mjs +145 -0
- package/dist/media-upload-DNu3QySc.mjs.map +1 -0
- package/dist/{media-url-DgIEYZr4.mjs → media-url-BCm5vBn6.mjs} +1 -1
- package/dist/{media-url-DgIEYZr4.mjs.map → media-url-BCm5vBn6.mjs.map} +1 -1
- package/dist/{media-usage-B__v9NMm.mjs → media-usage-DPQ3K4xB.mjs} +138 -5
- package/dist/{media-usage-B__v9NMm.mjs.map → media-usage-DPQ3K4xB.mjs.map} +1 -1
- package/dist/{menus-BpfevlJD.mjs → menus-BMGzWyWH.mjs} +15 -8
- package/dist/menus-BMGzWyWH.mjs.map +1 -0
- package/dist/{menus-BtvH7Z7t.mjs → menus-CPf_SW7u.mjs} +234 -13
- package/dist/menus-CPf_SW7u.mjs.map +1 -0
- package/dist/{mode-BVnhGZ2o.mjs → mode-fiXRMfeA.mjs} +1 -1
- package/dist/{mode-BVnhGZ2o.mjs.map → mode-fiXRMfeA.mjs.map} +1 -1
- package/dist/{oauth-authorization-BWqWTHBK.mjs → oauth-authorization-oqfbBuMH.mjs} +5 -6
- package/dist/oauth-authorization-oqfbBuMH.mjs.map +1 -0
- package/dist/{oauth-clients-43_XADoa.mjs → oauth-clients-BlrUHAsf.mjs} +1 -1
- package/dist/{oauth-clients-43_XADoa.mjs.map → oauth-clients-BlrUHAsf.mjs.map} +1 -1
- package/dist/{oauth-state-store-BRSUEkmx.mjs → oauth-state-store-DnvjRMy6.mjs} +2 -1
- package/dist/{oauth-state-store-BRSUEkmx.mjs.map → oauth-state-store-DnvjRMy6.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-Ctgemb0k.mjs → oauth-user-lookup-oUllxvAv.mjs} +1 -1
- package/dist/{oauth-user-lookup-Ctgemb0k.mjs.map → oauth-user-lookup-oUllxvAv.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{options-COXGTtqd.d.mts → options-BqMkWfYa.d.mts} +6 -6
- package/dist/{options-COXGTtqd.d.mts.map → options-BqMkWfYa.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/page/index.d.mts.map +1 -1
- package/dist/page/index.mjs.map +1 -1
- package/dist/{parse-GLksnn-B.mjs → parse-53zyxlpv.mjs} +4 -12
- package/dist/{parse-GLksnn-B.mjs.map → parse-53zyxlpv.mjs.map} +1 -1
- package/dist/{passkey-config-CjA1UbDj.mjs → passkey-config-C-SKpc0-.mjs} +1 -1
- package/dist/{passkey-config-CjA1UbDj.mjs.map → passkey-config-C-SKpc0-.mjs.map} +1 -1
- package/dist/{placeholder-DR-eeZr-.d.mts → placeholder-Dop47JMM.d.mts} +1 -1
- package/dist/{placeholder-DR-eeZr-.d.mts.map → placeholder-Dop47JMM.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +21 -2
- package/dist/plugin-types.d.mts.map +1 -1
- package/dist/plugin-utils.d.mts +18 -17
- package/dist/plugin-utils.d.mts.map +1 -1
- package/dist/plugin-utils.mjs +8 -6
- package/dist/plugin-utils.mjs.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +10 -11
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +15 -3
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/{portable-text-BAkgEKcv.mjs → portable-text-sDj92Kxv.mjs} +1 -1
- package/dist/{portable-text-BAkgEKcv.mjs.map → portable-text-sDj92Kxv.mjs.map} +1 -1
- package/dist/{preview-Rloa09Wp.mjs → preview-D4Jnbfx7.mjs} +2 -2
- package/dist/{preview-Rloa09Wp.mjs.map → preview-D4Jnbfx7.mjs.map} +1 -1
- package/dist/{public-url-C0cEvrRu.mjs → public-url-DSGTnJFw.mjs} +5 -3
- package/dist/public-url-DSGTnJFw.mjs.map +1 -0
- package/dist/{query-ZzvXyxWq.mjs → query-BbJQ-dGg.mjs} +15 -15
- package/dist/{query-ZzvXyxWq.mjs.map → query-BbJQ-dGg.mjs.map} +1 -1
- package/dist/{query-BxT95opY.mjs → query-DdFGHzQF.mjs} +3 -3
- package/dist/{query-BxT95opY.mjs.map → query-DdFGHzQF.mjs.map} +1 -1
- package/dist/{rate-limit-Cg4Rvc_-.mjs → rate-limit-D3IEiUi2.mjs} +2 -2
- package/dist/{rate-limit-Cg4Rvc_-.mjs.map → rate-limit-D3IEiUi2.mjs.map} +1 -1
- package/dist/{redirect-Ch3E98qZ.mjs → redirect-BoUac4NT.mjs} +10 -4
- package/dist/redirect-BoUac4NT.mjs.map +1 -0
- package/dist/{redirect-Ce_6Yb6z.mjs → redirect-Botom7X6.mjs} +1 -1
- package/dist/{redirect-Ce_6Yb6z.mjs.map → redirect-Botom7X6.mjs.map} +1 -1
- package/dist/{redirects-CXmVGRNZ.mjs → redirects-ChbpqKiS.mjs} +4 -4
- package/dist/{redirects-CXmVGRNZ.mjs.map → redirects-ChbpqKiS.mjs.map} +1 -1
- package/dist/{redirects-CeD7K7EX.mjs → redirects-D457qwt0.mjs} +10 -181
- package/dist/redirects-D457qwt0.mjs.map +1 -0
- package/dist/{registry-CyIXPi4E.mjs → registry-y21kjhfG.mjs} +97 -8
- package/dist/registry-y21kjhfG.mjs.map +1 -0
- package/dist/{relation-Ba68Tm4Z.mjs → relation-Du0r04E0.mjs} +3 -3
- package/dist/{relation-Ba68Tm4Z.mjs.map → relation-Du0r04E0.mjs.map} +1 -1
- package/dist/{relations-CbBpoOys.d.mts → relations-BKybBoOX.d.mts} +315 -9
- package/dist/relations-BKybBoOX.d.mts.map +1 -0
- package/dist/{relations-BmBmX4Eb.mjs → relations-CZMpBqEd.mjs} +3 -3
- package/dist/{relations-BmBmX4Eb.mjs.map → relations-CZMpBqEd.mjs.map} +1 -1
- package/dist/{relations-C8hVyEy0.mjs → relations-DtX1UMBH.mjs} +13 -7
- package/dist/relations-DtX1UMBH.mjs.map +1 -0
- package/dist/{request-meta-DCwktC5P.mjs → request-meta-DzXYYI-n.mjs} +2 -2
- package/dist/{request-meta-DCwktC5P.mjs.map → request-meta-DzXYYI-n.mjs.map} +1 -1
- package/dist/{resolve-Cd9dzclN.mjs → resolve-DFr4kq72.mjs} +2 -2
- package/dist/{resolve-Cd9dzclN.mjs.map → resolve-DFr4kq72.mjs.map} +1 -1
- package/dist/{runner-jt65bF7F.mjs → runner-BdeLEgad.mjs} +334 -200
- package/dist/runner-BdeLEgad.mjs.map +1 -0
- package/dist/{runner-CiNdIk3y.d.mts → runner-Dd8m8k9F.d.mts} +2 -2
- package/dist/{runner-CiNdIk3y.d.mts.map → runner-Dd8m8k9F.d.mts.map} +1 -1
- package/dist/runtime.d.mts +11 -12
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +4 -4
- package/dist/{schema-DwjufZ1S.mjs → schema-BH8P8vaC.mjs} +3 -3
- package/dist/{schema-DwjufZ1S.mjs.map → schema-BH8P8vaC.mjs.map} +1 -1
- package/dist/scopes-DTIkIaGA.mjs +27 -0
- package/dist/scopes-DTIkIaGA.mjs.map +1 -0
- package/dist/{search-Cl875CVE.mjs → search-D-Yhpmvs.mjs} +8 -7
- package/dist/search-D-Yhpmvs.mjs.map +1 -0
- package/dist/{secrets-CO_bEmII.mjs → secrets-CQ-69kF9.mjs} +25 -19
- package/dist/secrets-CQ-69kF9.mjs.map +1 -0
- package/dist/{sections-DTKzgoiS.mjs → sections-CzVBAKjw.mjs} +3 -3
- package/dist/{sections-DTKzgoiS.mjs.map → sections-CzVBAKjw.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +20 -20
- package/dist/seo/index.d.mts +14 -3
- package/dist/seo/index.d.mts.map +1 -1
- package/dist/seo/index.mjs +17 -17
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-BjrbXAd_.mjs → seo-C3GDfT0V.mjs} +1 -1
- package/dist/{seo-BjrbXAd_.mjs.map → seo-C3GDfT0V.mjs.map} +1 -1
- package/dist/{seo-BLm6SXzS.mjs → seo-kJzVAmmu.mjs} +2 -2
- package/dist/{seo-BLm6SXzS.mjs.map → seo-kJzVAmmu.mjs.map} +1 -1
- package/dist/{service-D6zDmKXy.mjs → service-CQnc276a.mjs} +3 -3
- package/dist/{service-D6zDmKXy.mjs.map → service-CQnc276a.mjs.map} +1 -1
- package/dist/{session-user-DoSs6YM8.mjs → session-user-DbHqKDKe.mjs} +1 -1
- package/dist/{session-user-DoSs6YM8.mjs.map → session-user-DbHqKDKe.mjs.map} +1 -1
- package/dist/{settings-BuQYvLfy.mjs → settings-C2PaBS49.mjs} +4 -4
- package/dist/{settings-BuQYvLfy.mjs.map → settings-C2PaBS49.mjs.map} +1 -1
- package/dist/{settings-CmS3TwXf.mjs → settings-Dtc0DU31.mjs} +3 -3
- package/dist/{settings-CmS3TwXf.mjs.map → settings-Dtc0DU31.mjs.map} +1 -1
- package/dist/{setup-complete-BPNgLHlU.mjs → setup-complete-CdIlqloh.mjs} +1 -1
- package/dist/{setup-complete-BPNgLHlU.mjs.map → setup-complete-CdIlqloh.mjs.map} +1 -1
- package/dist/{setup-nonce-DEPZX96T.mjs → setup-nonce-DqiIPr-J.mjs} +1 -1
- package/dist/{setup-nonce-DEPZX96T.mjs.map → setup-nonce-DqiIPr-J.mjs.map} +1 -1
- package/dist/{single-flight-cache-DEtW-OEG.mjs → single-flight-cache-C2exrGAi.mjs} +1 -1
- package/dist/{single-flight-cache-DEtW-OEG.mjs.map → single-flight-cache-C2exrGAi.mjs.map} +1 -1
- package/dist/{site-url-CcLy5D5s.mjs → site-url-NGJT6NTU.mjs} +1 -1
- package/dist/{site-url-CcLy5D5s.mjs.map → site-url-NGJT6NTU.mjs.map} +1 -1
- package/dist/snapshot-BdpUJKD-.mjs +216 -0
- package/dist/snapshot-BdpUJKD-.mjs.map +1 -0
- package/dist/{ssrf-TrKYJAy0.mjs → ssrf-CviKqWmq.mjs} +1 -1
- package/dist/{ssrf-TrKYJAy0.mjs.map → ssrf-CviKqWmq.mjs.map} +1 -1
- package/dist/state-xxv6ZTMv.mjs +128 -0
- package/dist/state-xxv6ZTMv.mjs.map +1 -0
- package/dist/{status-D3Gy1Zc6.mjs → status-COfycGIU.mjs} +1 -1
- package/dist/{status-D3Gy1Zc6.mjs.map → status-COfycGIU.mjs.map} +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.mjs +1 -1
- package/dist/{taxonomies-pUkuD6nj.mjs → taxonomies-HI9fnTdp.mjs} +8 -8
- package/dist/{taxonomies-pUkuD6nj.mjs.map → taxonomies-HI9fnTdp.mjs.map} +1 -1
- package/dist/{taxonomies-ClS4RKaZ.mjs → taxonomies-rMeBzOjX.mjs} +26 -17
- package/dist/taxonomies-rMeBzOjX.mjs.map +1 -0
- package/dist/{taxonomy-BkxbUMSw.mjs → taxonomy-0mCI25HN.mjs} +51 -13
- package/dist/taxonomy-0mCI25HN.mjs.map +1 -0
- package/dist/{tokens-BV6fgl5A.mjs → tokens-DVltpO2D.mjs} +2 -2
- package/dist/{tokens-BV6fgl5A.mjs.map → tokens-DVltpO2D.mjs.map} +1 -1
- package/dist/{transport-DTFhhPOg.mjs → transport-CKrlqSpD.mjs} +1 -1
- package/dist/{transport-DTFhhPOg.mjs.map → transport-CKrlqSpD.mjs.map} +1 -1
- package/dist/{transport-DmlmzD2U.d.mts → transport-Vd7VdqHv.d.mts} +1 -1
- package/dist/{transport-DmlmzD2U.d.mts.map → transport-Vd7VdqHv.d.mts.map} +1 -1
- package/dist/{trusted-proxy-CSgZVESw.mjs → trusted-proxy-CwjQj0YG.mjs} +1 -1
- package/dist/{trusted-proxy-CSgZVESw.mjs.map → trusted-proxy-CwjQj0YG.mjs.map} +1 -1
- package/dist/{types-B6-N9cBB.d.mts → types-B4vm81VH.d.mts} +1 -1
- package/dist/{types-B6-N9cBB.d.mts.map → types-B4vm81VH.d.mts.map} +1 -1
- package/dist/{types-Cm4Cz-8M.d.mts → types-B97jg3aW.d.mts} +6 -1
- package/dist/{types-Cm4Cz-8M.d.mts.map → types-B97jg3aW.d.mts.map} +1 -1
- package/dist/{types-BNZsPL9z.mjs → types-BD40g7zz.mjs} +1 -1
- package/dist/{types-BNZsPL9z.mjs.map → types-BD40g7zz.mjs.map} +1 -1
- package/dist/{types-QnlQIyPE.d.mts → types-BNfPHfay.d.mts} +1 -1
- package/dist/{types-QnlQIyPE.d.mts.map → types-BNfPHfay.d.mts.map} +1 -1
- package/dist/{types-DLIrNtv5.d.mts → types-BfYUPJdP.d.mts} +2 -2
- package/dist/{types-DLIrNtv5.d.mts.map → types-BfYUPJdP.d.mts.map} +1 -1
- package/dist/{types-CtDVagM4.d.mts → types-BvB7gDOD.d.mts} +38 -4
- package/dist/types-BvB7gDOD.d.mts.map +1 -0
- package/dist/{types-B_pmP7Yq.d.mts → types-C7U0HlEr.d.mts} +1 -1
- package/dist/{types-B_pmP7Yq.d.mts.map → types-C7U0HlEr.d.mts.map} +1 -1
- package/dist/{types-Ch8pFMEm.mjs → types-CrRUFdej.mjs} +3 -3
- package/dist/{types-Ch8pFMEm.mjs.map → types-CrRUFdej.mjs.map} +1 -1
- package/dist/{types-momGysZk.d.mts → types-DPKhI5ib.d.mts} +9 -1
- package/dist/types-DPKhI5ib.d.mts.map +1 -0
- package/dist/{types-BCVuUPrL.d.mts → types-Kmaxdarz.d.mts} +1 -1
- package/dist/{types-BCVuUPrL.d.mts.map → types-Kmaxdarz.d.mts.map} +1 -1
- package/dist/{types-qljd1hsL.d.mts → types-iVeZ5g6N.d.mts} +1 -1
- package/dist/{types-qljd1hsL.d.mts.map → types-iVeZ5g6N.d.mts.map} +1 -1
- package/dist/{user-DTxX_Fha.mjs → user-aTXqmXBz.mjs} +3 -3
- package/dist/{user-DTxX_Fha.mjs.map → user-aTXqmXBz.mjs.map} +1 -1
- package/dist/{utils-Cz8PVrl4.mjs → utils-BbUgmeZG.mjs} +1 -1
- package/dist/{utils-Cz8PVrl4.mjs.map → utils-BbUgmeZG.mjs.map} +1 -1
- package/dist/{validate-C3CpWlNt.mjs → validate-CDT1jl6_.mjs} +11 -6
- package/dist/validate-CDT1jl6_.mjs.map +1 -0
- package/dist/{validate-9jLuE301.d.mts → validate-DULfgpJP.d.mts} +5 -5
- package/dist/{validate-9jLuE301.d.mts.map → validate-DULfgpJP.d.mts.map} +1 -1
- package/dist/{validation-DM2K_NJC.mjs → validation-DlD0E35W.mjs} +10 -10
- package/dist/{validation-DM2K_NJC.mjs.map → validation-DlD0E35W.mjs.map} +1 -1
- package/dist/version-Ci6bw6YK.mjs +7 -0
- package/dist/{version-BlIJvNAZ.mjs.map → version-Ci6bw6YK.mjs.map} +1 -1
- package/dist/{widgets-B_X6_UXJ.mjs → widgets-5fipNkMT.mjs} +3 -3
- package/dist/{widgets-B_X6_UXJ.mjs.map → widgets-5fipNkMT.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-tADW4KTa.d.mts → wxr-taxonomies-B4tMuN5T.d.mts} +1 -1
- package/dist/{wxr-taxonomies-tADW4KTa.d.mts.map → wxr-taxonomies-B4tMuN5T.d.mts.map} +1 -1
- package/dist/{wxr-taxonomies-oNJB2S81.mjs → wxr-taxonomies-BmUnBNgz.mjs} +4 -4
- package/dist/{wxr-taxonomies-oNJB2S81.mjs.map → wxr-taxonomies-BmUnBNgz.mjs.map} +1 -1
- package/dist/{zod-generator-CrfV79De.mjs → zod-generator-CwkHWz4f.mjs} +2 -2
- package/dist/{zod-generator-CrfV79De.mjs.map → zod-generator-CwkHWz4f.mjs.map} +1 -1
- package/package.json +8 -8
- package/src/api/authorize.ts +10 -1
- package/src/api/error.ts +5 -5
- package/src/api/errors.ts +14 -0
- package/src/api/handlers/backup.ts +312 -0
- package/src/api/handlers/bylines.ts +5 -4
- package/src/api/handlers/content.ts +61 -9
- package/src/api/handlers/device-flow.ts +2 -3
- package/src/api/handlers/index.ts +16 -0
- package/src/api/handlers/marketplace.ts +38 -0
- package/src/api/handlers/media-upload.ts +210 -0
- package/src/api/handlers/media-usage.ts +211 -2
- package/src/api/handlers/menus.ts +11 -6
- package/src/api/handlers/oauth-authorization.ts +2 -6
- package/src/api/handlers/plugin-settings.ts +218 -0
- package/src/api/handlers/plugins.ts +41 -0
- package/src/api/handlers/registry.ts +39 -0
- package/src/api/handlers/relations.ts +7 -2
- package/src/api/handlers/snapshot.ts +21 -3
- package/src/api/handlers/taxonomies.ts +22 -14
- package/src/api/openapi/document.ts +48 -6
- package/src/api/parse.ts +1 -15
- package/src/api/public-url.ts +4 -2
- package/src/api/schemas/bylines.ts +4 -4
- package/src/api/schemas/common.ts +13 -8
- package/src/api/schemas/media-usage.ts +72 -0
- package/src/api/schemas/media.ts +32 -1
- package/src/api/schemas/menus.ts +2 -1
- package/src/api/schemas/relations.ts +3 -1
- package/src/api/schemas/taxonomies.ts +4 -2
- package/src/astro/integration/index.ts +38 -0
- package/src/astro/integration/routes.ts +35 -0
- package/src/astro/integration/runtime.ts +6 -0
- package/src/astro/integration/virtual-modules.ts +41 -7
- package/src/astro/integration/vite-config.ts +17 -1
- package/src/astro/middleware/auth.ts +70 -78
- package/src/astro/middleware/csp.ts +5 -2
- package/src/astro/middleware.ts +86 -31
- package/src/astro/public-plugin-api-routes.ts +2 -1
- package/src/astro/routes/PluginRegistry.tsx +5 -2
- package/src/astro/routes/admin.astro +56 -16
- package/src/astro/routes/api/admin/api-tokens/index.ts +2 -2
- package/src/astro/routes/api/admin/bylines/[id]/translations.ts +2 -5
- package/src/astro/routes/api/admin/bylines/index.ts +5 -8
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +6 -0
- package/src/astro/routes/api/admin/plugins/[id]/mcp.ts +42 -0
- package/src/astro/routes/api/admin/plugins/[id]/settings.ts +80 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +2 -0
- package/src/astro/routes/api/admin/plugins/index.ts +9 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +2 -0
- package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +2 -0
- package/src/astro/routes/api/admin/plugins/registry/install.ts +2 -0
- package/src/astro/routes/api/admin/plugins/updates.ts +2 -4
- package/src/astro/routes/api/auth/mode.ts +6 -14
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +21 -4
- package/src/astro/routes/api/auth/oauth/[provider].ts +24 -6
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +2 -3
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +2 -3
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/children.ts +4 -6
- package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/parents.ts +2 -3
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +4 -6
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id].ts +3 -4
- package/src/astro/routes/api/content/[collection]/authors.ts +2 -3
- package/src/astro/routes/api/content/[collection]/index.ts +6 -8
- package/src/astro/routes/api/content/[collection]/trash.ts +2 -3
- package/src/astro/routes/api/manifest.ts +2 -9
- package/src/astro/routes/api/mcp.ts +2 -1
- package/src/astro/routes/api/media/[id]/usage.ts +30 -0
- package/src/astro/routes/api/media/[id].ts +17 -6
- package/src/astro/routes/api/media/file/[...key].ts +8 -0
- package/src/astro/routes/api/media.ts +21 -2
- package/src/astro/routes/api/oauth/authorize.ts +43 -15
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +17 -3
- package/src/astro/routes/api/settings/backups/archives/[name].ts +78 -0
- package/src/astro/routes/api/settings/backups/archives/index.ts +33 -0
- package/src/astro/routes/api/settings/backups/export.ts +43 -0
- package/src/astro/routes/api/settings/backups/index.ts +76 -0
- package/src/astro/routes/api/setup/admin-verify.ts +8 -4
- package/src/astro/routes/api/setup/admin.ts +11 -4
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +4 -1
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +5 -1
- package/src/astro/types.ts +48 -2
- package/src/auth/api-tokens.ts +1 -0
- package/src/auth/oauth-state-store.ts +3 -0
- package/src/auth/providers/github-admin.tsx +5 -6
- package/src/auth/providers/google-admin.tsx +5 -6
- package/src/auth/scopes.ts +3 -9
- package/src/cli/client-factory.ts +0 -1
- package/src/cli/commands/bundle-utils.ts +50 -1
- package/src/cli/commands/bundle.ts +9 -2
- package/src/cli/commands/media.ts +127 -0
- package/src/client/index.ts +84 -2
- package/src/client/transport.ts +1 -1
- package/src/components/EmDashHead.astro +4 -8
- package/src/components/Gallery.astro +141 -26
- package/src/components/InlinePortableTextEditor.tsx +56 -35
- package/src/components/marks/Link.astro +5 -1
- package/src/config/secrets.ts +25 -19
- package/src/content/converters/gallery.ts +61 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +19 -0
- package/src/content/converters/prosemirror-to-portable-text.ts +18 -0
- package/src/content/converters/types.ts +36 -0
- package/src/database/dialect-helpers.ts +80 -7
- package/src/database/migrations/051_content_taxonomies_denorm.ts +120 -0
- package/src/database/migrations/052_media_usage_read_index.ts +26 -0
- package/src/database/migrations/053_plugin_mcp_tools.ts +21 -0
- package/src/database/migrations/runner.ts +6 -0
- package/src/database/pg-migration-lock.ts +1 -4
- package/src/database/repositories/audit.ts +2 -1
- package/src/database/repositories/content.ts +98 -16
- package/src/database/repositories/media-usage.ts +250 -1
- package/src/database/repositories/plugin-storage.ts +29 -32
- package/src/database/repositories/redirect.ts +14 -2
- package/src/database/repositories/taxonomy.ts +57 -8
- package/src/database/types.ts +23 -0
- package/src/db/adapters.ts +9 -0
- package/src/emdash-runtime.ts +365 -83
- package/src/i18n/config.ts +8 -0
- package/src/i18n/repair-locale-casing.ts +51 -0
- package/src/loader.ts +292 -1
- package/src/mcp/server.ts +222 -5
- package/src/media/image-endpoint.ts +38 -2
- package/src/page/metadata.ts +27 -0
- package/src/plugin-types.ts +19 -0
- package/src/plugin-utils.ts +8 -6
- package/src/plugins/adapt-sandbox-entry.ts +30 -1
- package/src/plugins/context.ts +7 -0
- package/src/plugins/define-plugin.ts +16 -0
- package/src/plugins/manifest-schema.ts +28 -1
- package/src/plugins/routes.ts +27 -1
- package/src/plugins/sandbox/runner-options.ts +16 -0
- package/src/plugins/sandbox/types.ts +6 -1
- package/src/plugins/state.ts +17 -1
- package/src/plugins/storage-query.ts +67 -33
- package/src/plugins/types.ts +41 -0
- package/src/schema/registry.ts +141 -5
- package/src/search/query.ts +3 -2
- package/src/seed/apply.ts +37 -36
- package/src/seed/validate.ts +15 -4
- package/src/seo/index.ts +19 -4
- package/src/storage/types.ts +6 -0
- package/src/utils/base64.ts +7 -2
- package/src/utils/db-errors.ts +3 -1
- package/src/virtual-modules.d.ts +13 -0
- package/src/visual-editing/toolbar.ts +1 -1
- package/dist/adapters-DPAkL6QF.d.mts.map +0 -1
- package/dist/api-DVbHNApo.mjs.map +0 -1
- package/dist/api-tokens-Dk2GrJMY.mjs +0 -3
- package/dist/apply-D23iUV4q.mjs.map +0 -1
- package/dist/authorize-BmCwQCib.mjs.map +0 -1
- package/dist/base64-C74Msx8h.mjs.map +0 -1
- package/dist/bylines-BGuKFaBy.mjs.map +0 -1
- package/dist/config-CVssduLe.mjs.map +0 -1
- package/dist/content-BO61Sx4j.mjs.map +0 -1
- package/dist/content-refresh-BkkApzeS.mjs.map +0 -1
- package/dist/context-DtSFYFuS.mjs.map +0 -1
- package/dist/db-errors-BKuXh_fi.mjs.map +0 -1
- package/dist/device-flow-CPXQ_QfW.mjs.map +0 -1
- package/dist/dialect-helpers-Cue7QQOR.mjs.map +0 -1
- package/dist/error-CfJMLSkd.mjs.map +0 -1
- package/dist/index-BXOCyLj0.d.mts.map +0 -1
- package/dist/loader-Rb8QlGZp.mjs.map +0 -1
- package/dist/manifest-schema-DeGNma2O.mjs.map +0 -1
- package/dist/menus-BpfevlJD.mjs.map +0 -1
- package/dist/menus-BtvH7Z7t.mjs.map +0 -1
- package/dist/oauth-authorization-BWqWTHBK.mjs.map +0 -1
- package/dist/public-url-C0cEvrRu.mjs.map +0 -1
- package/dist/redirect-Ch3E98qZ.mjs.map +0 -1
- package/dist/redirects-CeD7K7EX.mjs.map +0 -1
- package/dist/registry-CyIXPi4E.mjs.map +0 -1
- package/dist/relations-C8hVyEy0.mjs.map +0 -1
- package/dist/relations-CbBpoOys.d.mts.map +0 -1
- package/dist/runner-jt65bF7F.mjs.map +0 -1
- package/dist/scheduled-publish-DlJHGcuL.d.mts +0 -11
- package/dist/scheduled-publish-DlJHGcuL.d.mts.map +0 -1
- package/dist/search-Cl875CVE.mjs.map +0 -1
- package/dist/secrets-CO_bEmII.mjs.map +0 -1
- package/dist/taxonomies-ClS4RKaZ.mjs.map +0 -1
- package/dist/taxonomy-BkxbUMSw.mjs.map +0 -1
- package/dist/types-CtDVagM4.d.mts.map +0 -1
- package/dist/types-momGysZk.d.mts.map +0 -1
- package/dist/validate-C3CpWlNt.mjs.map +0 -1
- package/dist/version-BlIJvNAZ.mjs +0 -7
- /package/dist/{ssrf-B5DOv5E5.mjs → ssrf-BB__xfNM.mjs} +0 -0
- /package/dist/{types-DeCZBZxn.mjs → types-CK0LxPUQ.mjs} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as withTransaction } from "./transaction-D0FOsb3X.mjs";
|
|
2
|
-
import { a as hashApiToken,
|
|
3
|
-
import { c as validateRedirectUri, o as lookupOAuthClient, s as validateClientRedirectUri } from "./oauth-clients-
|
|
4
|
-
import { t as lookupUserRoleAndStatus } from "./oauth-user-lookup-
|
|
2
|
+
import { a as hashApiToken, o as isValidScope, r as generatePrefixedToken, t as TOKEN_PREFIXES } from "./api-tokens-Cvmixds7.mjs";
|
|
3
|
+
import { c as validateRedirectUri, o as lookupOAuthClient, s as validateClientRedirectUri } from "./oauth-clients-BlrUHAsf.mjs";
|
|
4
|
+
import { t as lookupUserRoleAndStatus } from "./oauth-user-lookup-oUllxvAv.mjs";
|
|
5
5
|
import { clampScopes, computeS256Challenge, secureCompare } from "@emdash-cms/auth";
|
|
6
6
|
import { generateCodeVerifier } from "arctic";
|
|
7
7
|
|
|
@@ -29,8 +29,7 @@ function expiresAt(seconds) {
|
|
|
29
29
|
*/
|
|
30
30
|
function normalizeScopes(requested) {
|
|
31
31
|
if (!requested) return [];
|
|
32
|
-
|
|
33
|
-
return requested.split(" ").filter(Boolean).filter((s) => validSet.has(s));
|
|
32
|
+
return requested.split(" ").filter(Boolean).filter(isValidScope);
|
|
34
33
|
}
|
|
35
34
|
/**
|
|
36
35
|
* Process an authorization request after the user approves consent.
|
|
@@ -272,4 +271,4 @@ function buildDeniedRedirect(redirectUri, state) {
|
|
|
272
271
|
|
|
273
272
|
//#endregion
|
|
274
273
|
export { handleAuthorizationApproval as n, handleAuthorizationCodeExchange as r, buildDeniedRedirect as t };
|
|
275
|
-
//# sourceMappingURL=oauth-authorization-
|
|
274
|
+
//# sourceMappingURL=oauth-authorization-oqfbBuMH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-authorization-oqfbBuMH.mjs","names":[],"sources":["../src/api/handlers/oauth-authorization.ts"],"sourcesContent":["/**\n * OAuth 2.1 Authorization Code + PKCE handlers.\n *\n * Implements the server side of the authorization code grant for MCP clients\n * (Claude Desktop, VS Code, etc.) per the MCP authorization spec (draft).\n *\n * Uses arctic for PKCE challenge generation and @emdash-cms/auth for token\n * utilities. Token infrastructure is shared with the device flow.\n */\n\nimport { clampScopes, computeS256Challenge, secureCompare } from \"@emdash-cms/auth\";\nimport type { RoleLevel } from \"@emdash-cms/auth\";\nimport { generateCodeVerifier } from \"arctic\";\nimport type { Kysely } from \"kysely\";\n\nimport {\n\tgeneratePrefixedToken,\n\thashApiToken,\n\tisValidScope,\n\tTOKEN_PREFIXES,\n} from \"../../auth/api-tokens.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { validateRedirectUri } from \"../oauth/redirect-uri.js\";\nimport type { ApiResult } from \"../types.js\";\nimport { lookupOAuthClient, validateClientRedirectUri } from \"./oauth-clients.js\";\nimport { lookupUserRoleAndStatus } from \"./oauth-user-lookup.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Authorization codes expire after 10 minutes (RFC 6749 §4.1.2 recommends short-lived) */\nconst AUTH_CODE_TTL_SECONDS = 10 * 60;\n\n/** Access token TTL: 1 hour */\nconst ACCESS_TOKEN_TTL_SECONDS = 60 * 60;\n\n/** Refresh token TTL: 90 days */\nconst REFRESH_TOKEN_TTL_SECONDS = 90 * 24 * 60 * 60;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AuthorizationParams {\n\tresponse_type: string;\n\tclient_id: string;\n\tredirect_uri: string;\n\tscope?: string;\n\tstate?: string;\n\tcode_challenge: string;\n\tcode_challenge_method: string;\n\tresource?: string;\n}\n\nexport interface TokenExchangeParams {\n\tgrant_type: string;\n\tcode: string;\n\tredirect_uri: string;\n\tclient_id: string;\n\tcode_verifier: string;\n\tresource?: string;\n}\n\nexport interface TokenResponse {\n\taccess_token: string;\n\trefresh_token: string;\n\ttoken_type: \"Bearer\";\n\texpires_in: number;\n\tscope: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction expiresAt(seconds: number): string {\n\treturn new Date(Date.now() + seconds * 1000).toISOString();\n}\n\nexport { validateRedirectUri };\n\n/**\n * Validate and normalize scopes. Returns validated scope list.\n */\nfunction normalizeScopes(requested?: string): string[] {\n\tif (!requested) return [];\n\n\tconst scopes = requested.split(\" \").filter(Boolean).filter(isValidScope);\n\n\treturn scopes;\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * Process an authorization request after the user approves consent.\n *\n * Generates an authorization code, stores it with the PKCE challenge,\n * and returns the redirect URL with the code appended.\n *\n * Scopes are clamped to the user's role to prevent scope escalation.\n */\nexport async function handleAuthorizationApproval(\n\tdb: Kysely<Database>,\n\tuserId: string,\n\tuserRole: RoleLevel,\n\tparams: AuthorizationParams,\n): Promise<ApiResult<{ redirect_url: string }>> {\n\ttry {\n\t\t// Validate response_type\n\t\tif (params.response_type !== \"code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"UNSUPPORTED_RESPONSE_TYPE\",\n\t\t\t\t\tmessage: \"Only response_type=code is supported\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Validate redirect_uri scheme/host (basic security check)\n\t\tconst uriError = validateRedirectUri(params.redirect_uri);\n\t\tif (uriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_REDIRECT_URI\", message: uriError },\n\t\t\t};\n\t\t}\n\n\t\t// Look up the registered OAuth client\n\t\tconst client = await lookupOAuthClient(db, params.client_id);\n\t\tif (!client) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_CLIENT\",\n\t\t\t\t\tmessage: \"Unknown client_id\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Validate redirect_uri against client's registered URIs\n\t\tconst clientUriError = validateClientRedirectUri(params.redirect_uri, client.redirectUris);\n\t\tif (clientUriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_REDIRECT_URI\", message: clientUriError },\n\t\t\t};\n\t\t}\n\n\t\t// Validate code_challenge_method\n\t\tif (params.code_challenge_method !== \"S256\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_REQUEST\",\n\t\t\t\t\tmessage: \"Only S256 code_challenge_method is supported\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Validate code_challenge is present\n\t\tif (!params.code_challenge) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_REQUEST\", message: \"code_challenge is required\" },\n\t\t\t};\n\t\t}\n\n\t\t// Validate scopes, then clamp to user's role\n\t\tconst userScopes = clampScopes(normalizeScopes(params.scope), userRole);\n\n\t\t// SEC-41: Intersect with client's registered scopes (if restricted).\n\t\t// A client registered with scopes: [\"content:read\"] should never receive\n\t\t// admin or schema:write, regardless of the approving user's role.\n\t\tconst clientScopes = client.scopes;\n\t\tconst scopes = clientScopes?.length\n\t\t\t? userScopes.filter((s: string) => clientScopes.includes(s))\n\t\t\t: userScopes;\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_SCOPE\", message: \"No valid scopes requested\" },\n\t\t\t};\n\t\t}\n\n\t\t// Generate authorization code (high entropy, base64url)\n\t\tconst code = generateCodeVerifier(); // 32 bytes random, base64url\n\t\tconst codeHash = hashApiToken(code);\n\n\t\t// Store the authorization code\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_authorization_codes\")\n\t\t\t.values({\n\t\t\t\tcode_hash: codeHash,\n\t\t\t\tclient_id: params.client_id,\n\t\t\t\tredirect_uri: params.redirect_uri,\n\t\t\t\tuser_id: userId,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tcode_challenge: params.code_challenge,\n\t\t\t\tcode_challenge_method: params.code_challenge_method,\n\t\t\t\tresource: params.resource ?? null,\n\t\t\t\texpires_at: expiresAt(AUTH_CODE_TTL_SECONDS),\n\t\t\t})\n\t\t\t.execute();\n\n\t\t// Build the redirect URL\n\t\tconst redirectUrl = new URL(params.redirect_uri);\n\t\tredirectUrl.searchParams.set(\"code\", code);\n\t\tif (params.state) {\n\t\t\tredirectUrl.searchParams.set(\"state\", params.state);\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { redirect_url: redirectUrl.toString() },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Authorization error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"AUTHORIZATION_ERROR\",\n\t\t\t\tmessage: \"Failed to process authorization\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Exchange an authorization code for access + refresh tokens.\n *\n * Validates the code, verifies PKCE, and issues tokens using the same\n * infrastructure as the device flow (ec_oat_*, ec_ort_*).\n */\nexport async function handleAuthorizationCodeExchange(\n\tdb: Kysely<Database>,\n\tparams: TokenExchangeParams,\n): Promise<ApiResult<TokenResponse>> {\n\ttry {\n\t\t// Validate grant_type\n\t\tif (params.grant_type !== \"authorization_code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"unsupported_grant_type\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\t// SEC-39: Atomically consume the authorization code using DELETE...RETURNING.\n\t\t// This prevents TOCTOU double-exchange: two concurrent requests with the\n\t\t// same code will race on the DELETE, and only one will get a row back.\n\t\tconst codeHash = hashApiToken(params.code);\n\n\t\tconst row = await db\n\t\t\t.deleteFrom(\"_emdash_authorization_codes\")\n\t\t\t.where(\"code_hash\", \"=\", codeHash)\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"Invalid authorization code\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < new Date()) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"Authorization code expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// Verify redirect_uri matches exactly\n\t\tif (row.redirect_uri !== params.redirect_uri) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"redirect_uri mismatch\" },\n\t\t\t};\n\t\t}\n\n\t\t// Verify client_id matches\n\t\tif (row.client_id !== params.client_id) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"client_id mismatch\" },\n\t\t\t};\n\t\t}\n\n\t\t// PKCE verification: SHA256(code_verifier) must match stored code_challenge\n\t\t// Use constant-time comparison to prevent timing side-channels\n\t\tconst derivedChallenge = computeS256Challenge(params.code_verifier);\n\t\tif (!secureCompare(derivedChallenge, row.code_challenge)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"PKCE verification failed\" },\n\t\t\t};\n\t\t}\n\n\t\t// Verify resource matches (if stored)\n\t\tif (row.resource && params.resource && row.resource !== params.resource) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"resource mismatch\" },\n\t\t\t};\n\t\t}\n\n\t\t// Revalidate user role before issuing tokens (same pattern as handleTokenRefresh).\n\t\t// The user's role may have changed since the authorization code was issued.\n\t\tconst userInfo = await lookupUserRoleAndStatus(db, row.user_id);\n\t\tif (!userInfo) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"User not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (userInfo.disabled) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"User account is disabled\" },\n\t\t\t};\n\t\t}\n\n\t\t// Re-clamp scopes against the user's current role\n\t\tconst storedScopes = JSON.parse(row.scopes) as string[];\n\t\tlet scopes = clampScopes(storedScopes, userInfo.role);\n\n\t\t// Intersect with client's registered scopes (if restricted)\n\t\tconst client = await lookupOAuthClient(db, row.client_id);\n\t\tif (client?.scopes?.length) {\n\t\t\tscopes = scopes.filter((s: string) => client.scopes!.includes(s));\n\t\t}\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"invalid_grant\",\n\t\t\t\t\tmessage: \"User role no longer supports any of the requested scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Issue tokens (same as device flow)\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\n\t\tconst refreshToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_REFRESH);\n\t\tconst refreshExpires = expiresAt(REFRESH_TOKEN_TTL_SECONDS);\n\n\t\t// Atomically store both tokens in a transaction\n\t\tawait withTransaction(db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\t\ttoken_type: \"access\",\n\t\t\t\t\tuser_id: row.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"mcp\",\n\t\t\t\t\texpires_at: accessExpires,\n\t\t\t\t\trefresh_token_hash: refreshToken.hash,\n\t\t\t\t\tclient_id: row.client_id,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: refreshToken.hash,\n\t\t\t\t\ttoken_type: \"refresh\",\n\t\t\t\t\tuser_id: row.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"mcp\",\n\t\t\t\t\texpires_at: refreshExpires,\n\t\t\t\t\trefresh_token_hash: null,\n\t\t\t\t\tclient_id: row.client_id,\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: refreshToken.raw,\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Token exchange error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_EXCHANGE_ERROR\",\n\t\t\t\tmessage: \"Failed to exchange authorization code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Build the authorization denied redirect URL.\n */\nexport function buildDeniedRedirect(redirectUri: string, state?: string): string {\n\tconst url = new URL(redirectUri);\n\turl.searchParams.set(\"error\", \"access_denied\");\n\turl.searchParams.set(\"error_description\", \"The user denied the authorization request\");\n\tif (state) {\n\t\turl.searchParams.set(\"state\", state);\n\t}\n\treturn url.toString();\n}\n\n/**\n * Clean up expired authorization codes.\n */\nexport async function cleanupExpiredAuthorizationCodes(db: Kysely<Database>): Promise<number> {\n\tconst result = await db\n\t\t.deleteFrom(\"_emdash_authorization_codes\")\n\t\t.where(\"expires_at\", \"<\", new Date().toISOString())\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numDeletedRows);\n}\n"],"mappings":"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"}
|
|
@@ -263,4 +263,4 @@ function validateClientRedirectUri(redirectUri, allowedUris) {
|
|
|
263
263
|
|
|
264
264
|
//#endregion
|
|
265
265
|
export { handleOAuthClientUpdate as a, validateRedirectUri as c, handleOAuthClientList as i, handleOAuthClientDelete as n, lookupOAuthClient as o, handleOAuthClientGet as r, validateClientRedirectUri as s, handleOAuthClientCreate as t };
|
|
266
|
-
//# sourceMappingURL=oauth-clients-
|
|
266
|
+
//# sourceMappingURL=oauth-clients-BlrUHAsf.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-clients-43_XADoa.mjs","names":[],"sources":["../src/api/oauth/redirect-uri.ts","../src/api/handlers/oauth-clients.ts"],"sourcesContent":["/**\n * Validate a redirect URI per OAuth 2.1 security requirements.\n *\n * Allows localhost / loopback redirect URIs over HTTP for native clients,\n * and any HTTPS URL for web-based flows.\n */\nexport function validateRedirectUri(uri: string): string | null {\n\ttry {\n\t\tconst url = new URL(uri);\n\n\t\t// Reject protocol-relative URLs\n\t\tif (uri.startsWith(\"//\")) {\n\t\t\treturn \"Protocol-relative redirect URIs are not allowed\";\n\t\t}\n\n\t\t// Allow localhost/loopback over HTTP (for desktop MCP clients)\n\t\tif (url.protocol === \"http:\") {\n\t\t\tconst host = url.hostname;\n\t\t\tif (host === \"127.0.0.1\" || host === \"localhost\" || host === \"[::1]\") {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn \"HTTP redirect URIs are only allowed for localhost\";\n\t\t}\n\n\t\t// Allow HTTPS\n\t\tif (url.protocol === \"https:\") {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn `Unsupported redirect URI scheme: ${url.protocol}`;\n\t} catch {\n\t\treturn \"Invalid redirect URI\";\n\t}\n}\n","/**\n * OAuth client management handlers.\n *\n * CRUD operations for registered OAuth clients. Each client has a set\n * of pre-registered redirect URIs. The authorization endpoint rejects\n * any redirect_uri not in the client's registered set.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validateRedirectUri } from \"../oauth/redirect-uri.js\";\nimport type { ApiResult } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Parse a JSON string column into a typed value. */\nfunction parseJsonColumn<T>(value: string): T {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns unknown, callers provide the expected shape\n\treturn JSON.parse(value) as T;\n}\n\nfunction validateRegisteredRedirectUris(redirectUris: string[]): string | null {\n\tfor (const redirectUri of redirectUris) {\n\t\tconst error = validateRedirectUri(redirectUri);\n\t\tif (error) {\n\t\t\treturn `Invalid redirect URI: ${error}`;\n\t\t}\n\t}\n\treturn null;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OAuthClientInfo {\n\tid: string;\n\tname: string;\n\tredirectUris: string[];\n\tscopes: string[] | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new OAuth client.\n */\nexport async function handleOAuthClientCreate(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tid: string;\n\t\tname: string;\n\t\tredirectUris: string[];\n\t\tscopes?: string[] | null;\n\t},\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tif (input.redirectUris.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"At least one redirect URI is required\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst redirectUriError = validateRegisteredRedirectUris(input.redirectUris);\n\t\tif (redirectUriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: redirectUriError,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Check for duplicate client ID\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"id\", \"=\", input.id)\n\t\t\t.executeTakeFirst();\n\n\t\tif (existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"CONFLICT\", message: \"OAuth client with this ID already exists\" },\n\t\t\t};\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_oauth_clients\")\n\t\t\t.values({\n\t\t\t\tid: input.id,\n\t\t\t\tname: input.name,\n\t\t\t\tredirect_uris: JSON.stringify(input.redirectUris),\n\t\t\t\tscopes: input.scopes && input.scopes.length > 0 ? JSON.stringify(input.scopes) : null,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: input.id,\n\t\t\t\tname: input.name,\n\t\t\t\tredirectUris: input.redirectUris,\n\t\t\t\tscopes: input.scopes && input.scopes.length > 0 ? input.scopes : null,\n\t\t\t\tcreatedAt: now,\n\t\t\t\tupdatedAt: now,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_CREATE_ERROR\",\n\t\t\t\tmessage: \"Failed to create OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * List all registered OAuth clients.\n */\nexport async function handleOAuthClientList(\n\tdb: Kysely<Database>,\n): Promise<ApiResult<{ items: OAuthClientInfo[] }>> {\n\ttry {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.execute();\n\n\t\tconst items: OAuthClientInfo[] = rows.map((row) => ({\n\t\t\tid: row.id,\n\t\t\tname: row.name,\n\t\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t\t\tcreatedAt: row.created_at,\n\t\t\tupdatedAt: row.updated_at,\n\t\t}));\n\n\t\treturn { success: true, data: { items } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_LIST_ERROR\",\n\t\t\t\tmessage: \"Failed to list OAuth clients\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Get a single OAuth client by ID.\n */\nexport async function handleOAuthClientGet(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: row.id,\n\t\t\t\tname: row.name,\n\t\t\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\t\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t\t\t\tcreatedAt: row.created_at,\n\t\t\t\tupdatedAt: row.updated_at,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_GET_ERROR\",\n\t\t\t\tmessage: \"Failed to get OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Update an OAuth client.\n */\nexport async function handleOAuthClientUpdate(\n\tdb: Kysely<Database>,\n\tclientId: string,\n\tinput: {\n\t\tname?: string;\n\t\tredirectUris?: string[];\n\t\tscopes?: string[] | null;\n\t},\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (input.redirectUris !== undefined && input.redirectUris.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"At least one redirect URI is required\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (input.redirectUris !== undefined) {\n\t\t\tconst redirectUriError = validateRegisteredRedirectUris(input.redirectUris);\n\t\t\tif (redirectUriError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\t\tmessage: redirectUriError,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst updates: Record<string, string | null> = {\n\t\t\tupdated_at: new Date().toISOString(),\n\t\t};\n\n\t\tif (input.name !== undefined) {\n\t\t\tupdates.name = input.name;\n\t\t}\n\t\tif (input.redirectUris !== undefined) {\n\t\t\tupdates.redirect_uris = JSON.stringify(input.redirectUris);\n\t\t}\n\t\tif (input.scopes !== undefined) {\n\t\t\tupdates.scopes =\n\t\t\t\tinput.scopes && input.scopes.length > 0 ? JSON.stringify(input.scopes) : null;\n\t\t}\n\n\t\tawait db.updateTable(\"_emdash_oauth_clients\").set(updates).where(\"id\", \"=\", clientId).execute();\n\n\t\t// Fetch the updated row\n\t\tconst updated = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!updated) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found after update\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: updated.id,\n\t\t\t\tname: updated.name,\n\t\t\t\tredirectUris: parseJsonColumn<string[]>(updated.redirect_uris),\n\t\t\t\tscopes: updated.scopes ? parseJsonColumn<string[]>(updated.scopes) : null,\n\t\t\t\tcreatedAt: updated.created_at,\n\t\t\t\tupdatedAt: updated.updated_at,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_UPDATE_ERROR\",\n\t\t\t\tmessage: \"Failed to update OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Delete an OAuth client.\n */\nexport async function handleOAuthClientDelete(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst result = await db\n\t\t\t.deleteFrom(\"_emdash_oauth_clients\")\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (result.numDeletedRows === 0n) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_DELETE_ERROR\",\n\t\t\t\tmessage: \"Failed to delete OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Lookup helpers (used by authorization handler)\n// ---------------------------------------------------------------------------\n\n/**\n * Look up a registered OAuth client by ID.\n * Returns the client's redirect URIs or null if the client is not registered.\n */\nexport async function lookupOAuthClient(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<{ redirectUris: string[]; scopes: string[] | null } | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t.select([\"redirect_uris\", \"scopes\"])\n\t\t.where(\"id\", \"=\", clientId)\n\t\t.executeTakeFirst();\n\n\tif (!row) return null;\n\n\treturn {\n\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t};\n}\n\n/**\n * Validate that a redirect URI is in the client's registered set.\n *\n * Comparison is exact string match (per RFC 6749 §3.1.2.3).\n * Returns null if valid, or an error message if not.\n */\nexport function validateClientRedirectUri(\n\tredirectUri: string,\n\tallowedUris: string[],\n): string | null {\n\tif (allowedUris.includes(redirectUri)) {\n\t\treturn null; // OK\n\t}\n\treturn \"redirect_uri is not registered for this client\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oauth-clients-BlrUHAsf.mjs","names":[],"sources":["../src/api/oauth/redirect-uri.ts","../src/api/handlers/oauth-clients.ts"],"sourcesContent":["/**\n * Validate a redirect URI per OAuth 2.1 security requirements.\n *\n * Allows localhost / loopback redirect URIs over HTTP for native clients,\n * and any HTTPS URL for web-based flows.\n */\nexport function validateRedirectUri(uri: string): string | null {\n\ttry {\n\t\tconst url = new URL(uri);\n\n\t\t// Reject protocol-relative URLs\n\t\tif (uri.startsWith(\"//\")) {\n\t\t\treturn \"Protocol-relative redirect URIs are not allowed\";\n\t\t}\n\n\t\t// Allow localhost/loopback over HTTP (for desktop MCP clients)\n\t\tif (url.protocol === \"http:\") {\n\t\t\tconst host = url.hostname;\n\t\t\tif (host === \"127.0.0.1\" || host === \"localhost\" || host === \"[::1]\") {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn \"HTTP redirect URIs are only allowed for localhost\";\n\t\t}\n\n\t\t// Allow HTTPS\n\t\tif (url.protocol === \"https:\") {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn `Unsupported redirect URI scheme: ${url.protocol}`;\n\t} catch {\n\t\treturn \"Invalid redirect URI\";\n\t}\n}\n","/**\n * OAuth client management handlers.\n *\n * CRUD operations for registered OAuth clients. Each client has a set\n * of pre-registered redirect URIs. The authorization endpoint rejects\n * any redirect_uri not in the client's registered set.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validateRedirectUri } from \"../oauth/redirect-uri.js\";\nimport type { ApiResult } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Parse a JSON string column into a typed value. */\nfunction parseJsonColumn<T>(value: string): T {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns unknown, callers provide the expected shape\n\treturn JSON.parse(value) as T;\n}\n\nfunction validateRegisteredRedirectUris(redirectUris: string[]): string | null {\n\tfor (const redirectUri of redirectUris) {\n\t\tconst error = validateRedirectUri(redirectUri);\n\t\tif (error) {\n\t\t\treturn `Invalid redirect URI: ${error}`;\n\t\t}\n\t}\n\treturn null;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OAuthClientInfo {\n\tid: string;\n\tname: string;\n\tredirectUris: string[];\n\tscopes: string[] | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new OAuth client.\n */\nexport async function handleOAuthClientCreate(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tid: string;\n\t\tname: string;\n\t\tredirectUris: string[];\n\t\tscopes?: string[] | null;\n\t},\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tif (input.redirectUris.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"At least one redirect URI is required\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst redirectUriError = validateRegisteredRedirectUris(input.redirectUris);\n\t\tif (redirectUriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: redirectUriError,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Check for duplicate client ID\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"id\", \"=\", input.id)\n\t\t\t.executeTakeFirst();\n\n\t\tif (existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"CONFLICT\", message: \"OAuth client with this ID already exists\" },\n\t\t\t};\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_oauth_clients\")\n\t\t\t.values({\n\t\t\t\tid: input.id,\n\t\t\t\tname: input.name,\n\t\t\t\tredirect_uris: JSON.stringify(input.redirectUris),\n\t\t\t\tscopes: input.scopes && input.scopes.length > 0 ? JSON.stringify(input.scopes) : null,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: input.id,\n\t\t\t\tname: input.name,\n\t\t\t\tredirectUris: input.redirectUris,\n\t\t\t\tscopes: input.scopes && input.scopes.length > 0 ? input.scopes : null,\n\t\t\t\tcreatedAt: now,\n\t\t\t\tupdatedAt: now,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_CREATE_ERROR\",\n\t\t\t\tmessage: \"Failed to create OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * List all registered OAuth clients.\n */\nexport async function handleOAuthClientList(\n\tdb: Kysely<Database>,\n): Promise<ApiResult<{ items: OAuthClientInfo[] }>> {\n\ttry {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.execute();\n\n\t\tconst items: OAuthClientInfo[] = rows.map((row) => ({\n\t\t\tid: row.id,\n\t\t\tname: row.name,\n\t\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t\t\tcreatedAt: row.created_at,\n\t\t\tupdatedAt: row.updated_at,\n\t\t}));\n\n\t\treturn { success: true, data: { items } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_LIST_ERROR\",\n\t\t\t\tmessage: \"Failed to list OAuth clients\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Get a single OAuth client by ID.\n */\nexport async function handleOAuthClientGet(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: row.id,\n\t\t\t\tname: row.name,\n\t\t\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\t\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t\t\t\tcreatedAt: row.created_at,\n\t\t\t\tupdatedAt: row.updated_at,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_GET_ERROR\",\n\t\t\t\tmessage: \"Failed to get OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Update an OAuth client.\n */\nexport async function handleOAuthClientUpdate(\n\tdb: Kysely<Database>,\n\tclientId: string,\n\tinput: {\n\t\tname?: string;\n\t\tredirectUris?: string[];\n\t\tscopes?: string[] | null;\n\t},\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (input.redirectUris !== undefined && input.redirectUris.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"At least one redirect URI is required\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (input.redirectUris !== undefined) {\n\t\t\tconst redirectUriError = validateRegisteredRedirectUris(input.redirectUris);\n\t\t\tif (redirectUriError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\t\tmessage: redirectUriError,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst updates: Record<string, string | null> = {\n\t\t\tupdated_at: new Date().toISOString(),\n\t\t};\n\n\t\tif (input.name !== undefined) {\n\t\t\tupdates.name = input.name;\n\t\t}\n\t\tif (input.redirectUris !== undefined) {\n\t\t\tupdates.redirect_uris = JSON.stringify(input.redirectUris);\n\t\t}\n\t\tif (input.scopes !== undefined) {\n\t\t\tupdates.scopes =\n\t\t\t\tinput.scopes && input.scopes.length > 0 ? JSON.stringify(input.scopes) : null;\n\t\t}\n\n\t\tawait db.updateTable(\"_emdash_oauth_clients\").set(updates).where(\"id\", \"=\", clientId).execute();\n\n\t\t// Fetch the updated row\n\t\tconst updated = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!updated) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found after update\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: updated.id,\n\t\t\t\tname: updated.name,\n\t\t\t\tredirectUris: parseJsonColumn<string[]>(updated.redirect_uris),\n\t\t\t\tscopes: updated.scopes ? parseJsonColumn<string[]>(updated.scopes) : null,\n\t\t\t\tcreatedAt: updated.created_at,\n\t\t\t\tupdatedAt: updated.updated_at,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_UPDATE_ERROR\",\n\t\t\t\tmessage: \"Failed to update OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Delete an OAuth client.\n */\nexport async function handleOAuthClientDelete(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst result = await db\n\t\t\t.deleteFrom(\"_emdash_oauth_clients\")\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (result.numDeletedRows === 0n) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_DELETE_ERROR\",\n\t\t\t\tmessage: \"Failed to delete OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Lookup helpers (used by authorization handler)\n// ---------------------------------------------------------------------------\n\n/**\n * Look up a registered OAuth client by ID.\n * Returns the client's redirect URIs or null if the client is not registered.\n */\nexport async function lookupOAuthClient(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<{ redirectUris: string[]; scopes: string[] | null } | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t.select([\"redirect_uris\", \"scopes\"])\n\t\t.where(\"id\", \"=\", clientId)\n\t\t.executeTakeFirst();\n\n\tif (!row) return null;\n\n\treturn {\n\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t};\n}\n\n/**\n * Validate that a redirect URI is in the client's registered set.\n *\n * Comparison is exact string match (per RFC 6749 §3.1.2.3).\n * Returns null if valid, or an error message if not.\n */\nexport function validateClientRedirectUri(\n\tredirectUri: string,\n\tallowedUris: string[],\n): string | null {\n\tif (allowedUris.includes(redirectUri)) {\n\t\treturn null; // OK\n\t}\n\treturn \"redirect_uri is not registered for this client\";\n}\n"],"mappings":"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"}
|
|
@@ -33,6 +33,7 @@ function createOAuthStateStore(db) {
|
|
|
33
33
|
};
|
|
34
34
|
if ("codeVerifier" in parsed && typeof parsed.codeVerifier === "string") oauthState.codeVerifier = parsed.codeVerifier;
|
|
35
35
|
if ("nonce" in parsed && typeof parsed.nonce === "string") oauthState.nonce = parsed.nonce;
|
|
36
|
+
if ("inviteToken" in parsed && typeof parsed.inviteToken === "string") oauthState.inviteToken = parsed.inviteToken;
|
|
36
37
|
return oauthState;
|
|
37
38
|
} catch {
|
|
38
39
|
return null;
|
|
@@ -46,4 +47,4 @@ function createOAuthStateStore(db) {
|
|
|
46
47
|
|
|
47
48
|
//#endregion
|
|
48
49
|
export { createOAuthStateStore as t };
|
|
49
|
-
//# sourceMappingURL=oauth-state-store-
|
|
50
|
+
//# sourceMappingURL=oauth-state-store-DnvjRMy6.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-state-store-
|
|
1
|
+
{"version":3,"file":"oauth-state-store-DnvjRMy6.mjs","names":[],"sources":["../src/auth/oauth-state-store.ts"],"sourcesContent":["/**\n * OAuth state store\n *\n * Stores OAuth state in the auth_challenges table with automatic expiration.\n * Uses the existing table but with type=\"oauth\" to distinguish from WebAuthn challenges.\n */\n\nimport type { StateStore, OAuthState } from \"@emdash-cms/auth\";\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../database/types.js\";\n\nconst OAUTH_STATE_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\nexport function createOAuthStateStore(db: Kysely<Database>): StateStore {\n\treturn {\n\t\tasync set(state: string, data: OAuthState): Promise<void> {\n\t\t\tconst expiresAt = new Date(Date.now() + OAUTH_STATE_TTL_MS).toISOString();\n\n\t\t\tawait db\n\t\t\t\t.insertInto(\"auth_challenges\")\n\t\t\t\t.values({\n\t\t\t\t\tchallenge: state,\n\t\t\t\t\ttype: \"oauth\",\n\t\t\t\t\tuser_id: null,\n\t\t\t\t\tdata: JSON.stringify(data),\n\t\t\t\t\texpires_at: expiresAt,\n\t\t\t\t})\n\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\toc.column(\"challenge\").doUpdateSet({\n\t\t\t\t\t\ttype: \"oauth\",\n\t\t\t\t\t\tdata: JSON.stringify(data),\n\t\t\t\t\t\texpires_at: expiresAt,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.execute();\n\t\t},\n\n\t\tasync get(state: string): Promise<OAuthState | null> {\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"auth_challenges\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"challenge\", \"=\", state)\n\t\t\t\t.where(\"type\", \"=\", \"oauth\")\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (!row) return null;\n\n\t\t\tconst expiresAt = new Date(row.expires_at).getTime();\n\n\t\t\t// Check expiration\n\t\t\tif (expiresAt < Date.now()) {\n\t\t\t\t// Expired, delete and return null\n\t\t\t\tawait this.delete(state);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (!row.data) return null;\n\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(row.data);\n\t\t\t\tif (\n\t\t\t\t\ttypeof parsed !== \"object\" ||\n\t\t\t\t\tparsed === null ||\n\t\t\t\t\t!(\"provider\" in parsed) ||\n\t\t\t\t\ttypeof parsed.provider !== \"string\" ||\n\t\t\t\t\t!(\"redirectUri\" in parsed) ||\n\t\t\t\t\ttypeof parsed.redirectUri !== \"string\"\n\t\t\t\t) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst oauthState: OAuthState = {\n\t\t\t\t\tprovider: parsed.provider,\n\t\t\t\t\tredirectUri: parsed.redirectUri,\n\t\t\t\t};\n\t\t\t\tif (\"codeVerifier\" in parsed && typeof parsed.codeVerifier === \"string\") {\n\t\t\t\t\toauthState.codeVerifier = parsed.codeVerifier;\n\t\t\t\t}\n\t\t\t\tif (\"nonce\" in parsed && typeof parsed.nonce === \"string\") {\n\t\t\t\t\toauthState.nonce = parsed.nonce;\n\t\t\t\t}\n\t\t\t\tif (\"inviteToken\" in parsed && typeof parsed.inviteToken === \"string\") {\n\t\t\t\t\toauthState.inviteToken = parsed.inviteToken;\n\t\t\t\t}\n\t\t\t\treturn oauthState;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync delete(state: string): Promise<void> {\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"auth_challenges\")\n\t\t\t\t.where(\"challenge\", \"=\", state)\n\t\t\t\t.where(\"type\", \"=\", \"oauth\")\n\t\t\t\t.execute();\n\t\t},\n\t};\n}\n"],"mappings":";AAYA,MAAM,qBAAqB,MAAU;AAErC,SAAgB,sBAAsB,IAAkC;AACvE,QAAO;EACN,MAAM,IAAI,OAAe,MAAiC;GACzD,MAAM,YAAY,IAAI,KAAK,KAAK,KAAK,GAAG,mBAAmB,CAAC,aAAa;AAEzE,SAAM,GACJ,WAAW,kBAAkB,CAC7B,OAAO;IACP,WAAW;IACX,MAAM;IACN,SAAS;IACT,MAAM,KAAK,UAAU,KAAK;IAC1B,YAAY;IACZ,CAAC,CACD,YAAY,OACZ,GAAG,OAAO,YAAY,CAAC,YAAY;IAClC,MAAM;IACN,MAAM,KAAK,UAAU,KAAK;IAC1B,YAAY;IACZ,CAAC,CACF,CACA,SAAS;;EAGZ,MAAM,IAAI,OAA2C;GACpD,MAAM,MAAM,MAAM,GAChB,WAAW,kBAAkB,CAC7B,WAAW,CACX,MAAM,aAAa,KAAK,MAAM,CAC9B,MAAM,QAAQ,KAAK,QAAQ,CAC3B,kBAAkB;AAEpB,OAAI,CAAC,IAAK,QAAO;AAKjB,OAHkB,IAAI,KAAK,IAAI,WAAW,CAAC,SAAS,GAGpC,KAAK,KAAK,EAAE;AAE3B,UAAM,KAAK,OAAO,MAAM;AACxB,WAAO;;AAGR,OAAI,CAAC,IAAI,KAAM,QAAO;AAEtB,OAAI;IACH,MAAM,SAAkB,KAAK,MAAM,IAAI,KAAK;AAC5C,QACC,OAAO,WAAW,YAClB,WAAW,QACX,EAAE,cAAc,WAChB,OAAO,OAAO,aAAa,YAC3B,EAAE,iBAAiB,WACnB,OAAO,OAAO,gBAAgB,SAE9B,QAAO;IAER,MAAM,aAAyB;KAC9B,UAAU,OAAO;KACjB,aAAa,OAAO;KACpB;AACD,QAAI,kBAAkB,UAAU,OAAO,OAAO,iBAAiB,SAC9D,YAAW,eAAe,OAAO;AAElC,QAAI,WAAW,UAAU,OAAO,OAAO,UAAU,SAChD,YAAW,QAAQ,OAAO;AAE3B,QAAI,iBAAiB,UAAU,OAAO,OAAO,gBAAgB,SAC5D,YAAW,cAAc,OAAO;AAEjC,WAAO;WACA;AACP,WAAO;;;EAIT,MAAM,OAAO,OAA8B;AAC1C,SAAM,GACJ,WAAW,kBAAkB,CAC7B,MAAM,aAAa,KAAK,MAAM,CAC9B,MAAM,QAAQ,KAAK,QAAQ,CAC3B,SAAS;;EAEZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-user-lookup-
|
|
1
|
+
{"version":3,"file":"oauth-user-lookup-oUllxvAv.mjs","names":[],"sources":["../src/api/handlers/oauth-user-lookup.ts"],"sourcesContent":["/**\n * Shared user lookup for OAuth token operations.\n *\n * Extracts user role and disabled status from the database. Used by\n * handleTokenRefresh() to revalidate scopes against the user's current\n * role and reject disabled users.\n */\n\nimport { toRoleLevel, type RoleLevel } from \"@emdash-cms/auth\";\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\n\nexport interface UserRoleAndStatus {\n\trole: RoleLevel;\n\tdisabled: boolean;\n}\n\n/**\n * Look up a user's current role and disabled status.\n * Returns null if the user doesn't exist.\n */\nexport async function lookupUserRoleAndStatus(\n\tdb: Kysely<Database>,\n\tuserId: string,\n): Promise<UserRoleAndStatus | null> {\n\tconst row = await db\n\t\t.selectFrom(\"users\")\n\t\t.select([\"role\", \"disabled\"])\n\t\t.where(\"id\", \"=\", userId)\n\t\t.executeTakeFirst();\n\n\tif (!row) return null;\n\n\treturn {\n\t\trole: toRoleLevel(row.role),\n\t\tdisabled: row.disabled === 1,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,eAAsB,wBACrB,IACA,QACoC;CACpC,MAAM,MAAM,MAAM,GAChB,WAAW,QAAQ,CACnB,OAAO,CAAC,QAAQ,WAAW,CAAC,CAC5B,MAAM,MAAM,KAAK,OAAO,CACxB,kBAAkB;AAEpB,KAAI,CAAC,IAAK,QAAO;AAEjB,QAAO;EACN,MAAM,YAAY,IAAI,KAAK;EAC3B,UAAU,IAAI,aAAa;EAC3B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as ContentItem } from "./types-
|
|
2
|
-
import { t as Database } from "./types-
|
|
1
|
+
import { i as ContentItem } from "./types-BfYUPJdP.mjs";
|
|
2
|
+
import { t as Database } from "./types-DPKhI5ib.mjs";
|
|
3
3
|
import { Kysely } from "kysely";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
@@ -94,14 +94,14 @@ interface ApiContext {
|
|
|
94
94
|
/**
|
|
95
95
|
* Create a standardized error response.
|
|
96
96
|
*
|
|
97
|
-
* Always returns `{ error: { code, message } }` with correct
|
|
98
|
-
* Use this for all error responses in API routes.
|
|
97
|
+
* Always returns `{ success: false, error: { code, message } }` with correct
|
|
98
|
+
* Content-Type. Use this for all error responses in API routes.
|
|
99
99
|
*/
|
|
100
100
|
declare function apiError(code: string, message: string, status: number, details?: Record<string, unknown>): Response;
|
|
101
101
|
/**
|
|
102
102
|
* Create a standardized success response.
|
|
103
103
|
*
|
|
104
|
-
* Always returns `{ data: T }` with correct status code.
|
|
104
|
+
* Always returns `{ success: true, data: T }` with correct status code.
|
|
105
105
|
* Use this for all success responses in API routes.
|
|
106
106
|
*/
|
|
107
107
|
declare function apiSuccess<T>(data: T, status?: number): Response;
|
|
@@ -204,4 +204,4 @@ declare class OptionsRepository {
|
|
|
204
204
|
}
|
|
205
205
|
//#endregion
|
|
206
206
|
export { parseQuery as a, handleError as c, ContentListResponse as d, ContentResponse as f, ManifestResponse as h, parseBody as i, ApiContext as l, ListResponse as m, ParseResult as n, apiError as o, FieldDescriptor as p, isParseError as r, apiSuccess as s, OptionsRepository as t, ApiResult as u };
|
|
207
|
-
//# sourceMappingURL=options-
|
|
207
|
+
//# sourceMappingURL=options-BqMkWfYa.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options-
|
|
1
|
+
{"version":3,"file":"options-BqMkWfYa.d.mts","names":[],"sources":["../src/api/types.ts","../src/api/error.ts","../src/api/parse.ts","../src/database/repositories/options.ts"],"mappings":";;;;;;;;AASA;UAAiB,YAAA;EAChB,KAAA,EAAO,CAAA;EACP,UAAA;EAF6B;;;;;EAQ7B,KAAA;AAAA;AAMD;;;AAAA,UAAiB,mBAAA,SAA4B,YAAA,CAAa,WAAA;AAAA,UAEzC,eAAA;EAChB,IAAA,EAAM,WAAA;;EAEN,IAAA;AAAA;;;;UAMgB,gBAAA;EAChB,OAAA;EACA,IAAA;EACA,WAAA,EAAa,MAAA;IAGX,KAAA;IACA,aAAA;IACA,QAAA;IACA,MAAA,EAAQ,MAAA,SAAe,eAAA;EAAA;EAGzB,OAAA,EAAS,MAAA;IAGP,UAAA,GAAa,KAAA;MAAQ,IAAA;MAAc,SAAA;IAAA;IACnC,OAAA;EAAA;AAAA;AAAA,UAKc,eAAA;EAChB,IAAA;EACA,KAAA;EACA,QAAA;EAZA;;;;EAiBA,OAAA,GAAU,KAAA;IAAQ,KAAA;IAAe,KAAA;EAAA,KAAmB,MAAA;AAAA;AARrD;;;;;;;;;;;;AAAA,KAuBY,SAAA;EACP,OAAA;EAAe,IAAA,EAAM,CAAA;AAAA;EAEvB,OAAA;EACA,KAAA;IAAS,IAAA,EAAM,CAAA;IAAG,OAAA;IAAiB,OAAA,GAAU,MAAA;EAAA;AAAA;;;;UAM/B,UAAA;EAChB,MAAA;EACA,QAAA;AAAA;;;;;;;;;iBC5De,QAAA,CACf,IAAA,UACA,OAAA,UACA,MAAA,UACA,OAAA,GAAU,MAAA,oBACR,QAAA;;;ADZH;;;;iBC2BgB,UAAA,GAAA,CAAc,IAAA,EAAM,CAAA,EAAG,MAAA,YAAe,QAAA;ADzBtD;;;;;;;;AAAA,iBCqCgB,WAAA,CACf,KAAA,WACA,eAAA,UACA,YAAA,WACE,QAAA;;;;;;;KChDS,WAAA,MAAiB,CAAA,GAAI,QAAA;;;;;;AFKjC;iBEGsB,SAAA,WAAoB,CAAA,CAAE,OAAA,CAAA,CAC3C,OAAA,EAAS,OAAA,EACT,MAAA,EAAQ,CAAA,GACN,OAAA,CAAQ,WAAA,CAAY,CAAA,CAAE,KAAA,CAAM,CAAA;;;;;;AFK/B;;iBEyDgB,UAAA,WAAqB,CAAA,CAAE,OAAA,CAAA,CAAS,GAAA,EAAK,GAAA,EAAK,MAAA,EAAQ,CAAA,GAAI,WAAA,CAAY,CAAA,CAAE,KAAA,CAAM,CAAA;;;;;iBA+B1E,YAAA,GAAA,CAAgB,MAAA,EAAQ,WAAA,CAAY,CAAA,IAAK,MAAA,IAAU,QAAA;;;;;;;AFjHnE;;cGKa,iBAAA;EAAA,QACQ,EAAA;cAAA,EAAA,EAAI,MAAA,CAAO,QAAA;EHL/B;;;EGUM,GAAA,aAAA,CAAiB,IAAA,WAAe,OAAA,CAAQ,CAAA;EHHzC;;AAMN;EGYO,YAAA,GAAA,CAAgB,IAAA,UAAc,YAAA,EAAc,CAAA,GAAI,OAAA,CAAQ,CAAA;;;;EAQxD,GAAA,aAAA,CAAiB,IAAA,UAAc,KAAA,EAAO,CAAA,GAAI,OAAA;EHlBjB;;;;;;;;EGwCzB,WAAA,aAAA,CAAyB,IAAA,UAAc,KAAA,EAAO,CAAA,GAAI,OAAA;EH/BxB;;;EGmD1B,MAAA,CAAO,IAAA,WAAe,OAAA;EHhDf;;;EGyDP,MAAA,CAAO,IAAA,WAAe,OAAA;EHhDb;;;EG6DT,OAAA,aAAA,CAAqB,KAAA,aAAkB,OAAA,CAAQ,GAAA,SAAY,CAAA;EHtEpD;;;EG0FP,OAAA,aAAA,CAAqB,OAAA,EAAS,MAAA,SAAe,CAAA,IAAK,OAAA;EHpFtD;;;EGgGI,MAAA,CAAA,GAAU,OAAA,CAAQ,GAAA;EH7Ff;;;EG0GH,WAAA,aAAA,CAAyB,MAAA,WAAiB,OAAA,CAAQ,GAAA,SAAY,CAAA;EHvG/B;;;EG0H/B,cAAA,CAAe,MAAA,WAAiB,OAAA;AAAA"}
|
package/dist/page/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { et as PageFragmentContribution, ot as PageMetadataLinkRel, rt as PageMetadataContribution, st as PagePlacement, t as BreadcrumbItem
|
|
2
|
-
import { n as SeoSettings } from "../types-
|
|
1
|
+
import { St as PublicPageContext, et as PageFragmentContribution, ot as PageMetadataLinkRel, rt as PageMetadataContribution, st as PagePlacement, t as BreadcrumbItem } from "../types-BvB7gDOD.mjs";
|
|
2
|
+
import { n as SeoSettings } from "../types-BNfPHfay.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/page/context.d.ts
|
|
5
5
|
/** Fields shared by both input forms */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/page/context.ts","../../src/page/metadata.ts","../../src/page/fragments.ts","../../src/page/seo-contributions.ts","../../src/page/jsonld.ts","../../src/page/index.ts"],"mappings":";;;;;UAUU,iBAAA;EACT,IAAA;EACA,QAAA;EACA,KAAA;EACA,SAAA;EACA,WAAA;EACA,SAAA;EACA,KAAA;EACA,OAAA;IAAY,UAAA;IAAoB,EAAA;IAAY,IAAA;EAAA;EAI3C;EAFD,GAAA;IACC,OAAA;IACA,aAAA;IACA,OAAA;IACA,MAAA;EAAA;EASD;EANA,WAAA;IACC,aAAA;IACA,YAAA;IACA,MAAA;EAAA;EAeQ;EAZT,QAAA;;;;;;EAMA,WAAA,GAAc,cAAA;EAOK;EALnB,OAAA;AAAA;AAKgC;AAAA,UADvB,UAAA,SAAmB,iBAAA;EAC5B,KAAA;IAAS,GAAA,EAAK,GAAA;IAAK,aAAA;EAAA;AAAA;;UAIV,QAAA,SAAiB,iBAAA;EAC1B,GAAA,EAAK,GAAA;EACL,MAAA;AAAA;AAAA,KAGW,4BAAA,GAA+B,UAAA,GAAa,QAAA;;;AASxD;iBAAgB,uBAAA,CAAwB,KAAA,EAAO,4BAAA,GAA+B,iBAAA;;;UCrD7D,oBAAA;EAChB,IAAA,EAAM,KAAA;IAAQ,IAAA;IAAc,OAAA;EAAA;EAC5B,UAAA,EAAY,KAAA;IAAQ,QAAA;IAAkB,OAAA;EAAA;EACtC,KAAA,EAAO,KAAA;IACN,GAAA,EAAK,mBAAA;IACL,IAAA;IACA,QAAA;EAAA;EAED,MAAA,EAAQ,KAAA;IAAQ,EAAA;IAAa,IAAA;EAAA;AAAA;AAAA,UAGpB,yBAAA;EACT,aAAA;AAAA;;iBAiBe,cAAA,CAAe,KAAA;;;;ADCvB;;;;;iBCuBQ,mBAAA,CAAoB,KAAA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/page/context.ts","../../src/page/metadata.ts","../../src/page/fragments.ts","../../src/page/seo-contributions.ts","../../src/page/jsonld.ts","../../src/page/index.ts"],"mappings":";;;;;UAUU,iBAAA;EACT,IAAA;EACA,QAAA;EACA,KAAA;EACA,SAAA;EACA,WAAA;EACA,SAAA;EACA,KAAA;EACA,OAAA;IAAY,UAAA;IAAoB,EAAA;IAAY,IAAA;EAAA;EAI3C;EAFD,GAAA;IACC,OAAA;IACA,aAAA;IACA,OAAA;IACA,MAAA;EAAA;EASD;EANA,WAAA;IACC,aAAA;IACA,YAAA;IACA,MAAA;EAAA;EAeQ;EAZT,QAAA;;;;;;EAMA,WAAA,GAAc,cAAA;EAOK;EALnB,OAAA;AAAA;AAKgC;AAAA,UADvB,UAAA,SAAmB,iBAAA;EAC5B,KAAA;IAAS,GAAA,EAAK,GAAA;IAAK,aAAA;EAAA;AAAA;;UAIV,QAAA,SAAiB,iBAAA;EAC1B,GAAA,EAAK,GAAA;EACL,MAAA;AAAA;AAAA,KAGW,4BAAA,GAA+B,UAAA,GAAa,QAAA;;;AASxD;iBAAgB,uBAAA,CAAwB,KAAA,EAAO,4BAAA,GAA+B,iBAAA;;;UCrD7D,oBAAA;EAChB,IAAA,EAAM,KAAA;IAAQ,IAAA;IAAc,OAAA;EAAA;EAC5B,UAAA,EAAY,KAAA;IAAQ,QAAA;IAAkB,OAAA;EAAA;EACtC,KAAA,EAAO,KAAA;IACN,GAAA,EAAK,mBAAA;IACL,IAAA;IACA,QAAA;EAAA;EAED,MAAA,EAAQ,KAAA;IAAQ,EAAA;IAAa,IAAA;EAAA;AAAA;AAAA,UAGpB,yBAAA;EACT,aAAA;AAAA;;iBAiBe,cAAA,CAAe,KAAA;;;;ADCvB;;;;;iBCuBQ,mBAAA,CAAoB,KAAA;;;ADTpC;;iBC4DgB,mBAAA,CACf,aAAA,EAAe,wBAAA,KACb,oBAAA;;iBAiFa,kBAAA,CACf,QAAA,EAAU,oBAAA,EACV,OAAA,GAAS,yBAAA;;;;;;;;iBCpLM,gBAAA,CACf,aAAA,EAAe,wBAAA,IACf,SAAA,EAAW,aAAA,GACT,wBAAA;;iBA2Da,eAAA,CACf,aAAA,EAAe,wBAAA,IACf,SAAA,EAAW,aAAA;;;;;;;;;;;;;iBCzDI,4BAAA,CACf,IAAA,EAAM,iBAAA,EACN,cAAA,mBACE,wBAAA;;;;;;;;iBA8Ha,4BAAA,CACf,WAAA,EAAa,WAAA,eACX,wBAAA;;;;;;;iBCjJa,WAAA,CAAY,GAAA,EAAK,MAAA,oBAA0B,MAAA;;;;;;;;;;iBAwB3C,sBAAA,CACf,IAAA,EAAM,iBAAA,EACN,cAAA,mBACE,MAAA;;;;;iBA2Ca,kBAAA,CAAmB,IAAA,EAAM,iBAAA,GAAoB,MAAA;;;;;;;UClD5C,iBAAA;EAChB,mBAAA,GAAsB,IAAA,EAAM,iBAAA,KAAsB,OAAA,CAAQ,wBAAA;EAC1D,oBAAA,GAAuB,IAAA,EAAM,iBAAA,KAAsB,OAAA,CAAQ,wBAAA;AAAA;;;;;iBAO5C,cAAA,CAAe,MAAA,EAAQ,MAAA,oBAA0B,iBAAA"}
|
package/dist/page/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/page/context.ts","../../src/page/metadata.ts","../../src/page/fragments.ts","../../src/page/jsonld.ts","../../src/page/seo-contributions.ts","../../src/page/index.ts"],"sourcesContent":["/**\n * Public page context builder\n *\n * Templates call this to describe the page being rendered.\n * The resulting context is passed to EmDashHead / EmDashBodyStart / EmDashBodyEnd.\n */\n\nimport type { BreadcrumbItem, PublicPageContext } from \"../plugins/types.js\";\n\n/** Fields shared by both input forms */\ninterface PageContextFields {\n\tkind: \"content\" | \"custom\";\n\tpageType?: string;\n\ttitle?: string | null;\n\tpageTitle?: string | null;\n\tdescription?: string | null;\n\tcanonical?: string | null;\n\timage?: string | null;\n\tcontent?: { collection: string; id: string; slug?: string | null };\n\t/** SEO overrides for OG/Twitter meta generation */\n\tseo?: {\n\t\togTitle?: string | null;\n\t\togDescription?: string | null;\n\t\togImage?: string | null;\n\t\trobots?: string | null;\n\t};\n\t/** Article metadata for Open Graph article: tags */\n\tarticleMeta?: {\n\t\tpublishedTime?: string | null;\n\t\tmodifiedTime?: string | null;\n\t\tauthor?: string | null;\n\t};\n\t/** Site name for structured data and og:site_name */\n\tsiteName?: string;\n\t/**\n\t * Breadcrumb trail for this page, root first. Pass an empty array\n\t * to explicitly opt out of breadcrumbs (e.g. homepage), or omit the\n\t * field to let consumers fall back to their own derivation.\n\t */\n\tbreadcrumbs?: BreadcrumbItem[];\n\t/** Public-facing site URL (origin) for structured data */\n\tsiteUrl?: string;\n}\n\n/** Input with Astro global -- used in .astro files */\ninterface AstroInput extends PageContextFields {\n\tAstro: { url: URL; currentLocale?: string };\n}\n\n/** Input with explicit URL -- used outside .astro files */\ninterface UrlInput extends PageContextFields {\n\turl: URL | string;\n\tlocale?: string;\n}\n\nexport type CreatePublicPageContextInput = AstroInput | UrlInput;\n\nfunction isAstroInput(input: CreatePublicPageContextInput): input is AstroInput {\n\treturn \"Astro\" in input;\n}\n\n/**\n * Build a PublicPageContext from template input.\n */\nexport function createPublicPageContext(input: CreatePublicPageContextInput): PublicPageContext {\n\tlet url: string;\n\tlet path: string;\n\tlet locale: string | null;\n\n\tif (isAstroInput(input)) {\n\t\turl = input.Astro.url.href;\n\t\tpath = input.Astro.url.pathname;\n\t\tlocale = input.Astro.currentLocale ?? null;\n\t} else {\n\t\tconst parsed = typeof input.url === \"string\" ? new URL(input.url) : input.url;\n\t\turl = parsed.href;\n\t\tpath = parsed.pathname;\n\t\tlocale = input.locale ?? null;\n\t}\n\n\treturn {\n\t\turl,\n\t\tpath,\n\t\tlocale,\n\t\tkind: input.kind,\n\t\tpageType: input.pageType ?? (input.kind === \"content\" ? \"article\" : \"website\"),\n\t\ttitle: input.title ?? null,\n\t\tpageTitle: input.pageTitle ?? null,\n\t\tdescription: input.description ?? null,\n\t\tcanonical: input.canonical ?? null,\n\t\timage: input.image ?? null,\n\t\tcontent: input.content\n\t\t\t? {\n\t\t\t\t\tcollection: input.content.collection,\n\t\t\t\t\tid: input.content.id,\n\t\t\t\t\tslug: input.content.slug ?? null,\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tseo: input.seo,\n\t\tarticleMeta: input.articleMeta,\n\t\tsiteName: input.siteName,\n\t\tbreadcrumbs: input.breadcrumbs,\n\t\tsiteUrl: input.siteUrl,\n\t};\n}\n","/**\n * Page metadata collection and rendering\n *\n * Collects typed metadata contributions from plugins via the page:metadata hook,\n * validates them, and resolves them into a deduplicated structure ready to render.\n */\n\nimport type { PageMetadataContribution, PageMetadataLinkRel } from \"../plugins/types.js\";\n\n// ── Resolved output ─────────────────────────────────────────────\n\nexport interface ResolvedPageMetadata {\n\tmeta: Array<{ name: string; content: string }>;\n\tproperties: Array<{ property: string; content: string }>;\n\tlinks: Array<{\n\t\trel: PageMetadataLinkRel;\n\t\thref: string;\n\t\threflang?: string;\n\t}>;\n\tjsonld: Array<{ id?: string; json: string }>;\n}\n\ninterface RenderPageMetadataOptions {\n\tincludeJsonLd?: boolean;\n}\n\n// ── Validation ──────────────────────────────────────────────────\n\n/** Schemes safe for use in link href attributes */\nconst SAFE_HREF_RE = /^(https?|at):\\/\\//i;\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n\t\"&\": \"&\",\n\t\"<\": \"<\",\n\t\">\": \">\",\n\t'\"': \""\",\n\t\"'\": \"'\",\n};\nconst HTML_ESCAPE_RE = /[&<>\"']/g;\n\n/** Escape a string for safe use in an HTML attribute value */\nexport function escapeHtmlAttr(value: string): string {\n\treturn value.replace(HTML_ESCAPE_RE, (ch) => HTML_ESCAPE_MAP[ch] ?? ch);\n}\n\n/** Validate that a URL uses a safe scheme (http, https, at) */\nfunction isSafeHref(url: string): boolean {\n\treturn SAFE_HREF_RE.test(url);\n}\n\n// ── JSON-LD serialization ───────────────────────────────────────\n\nconst JSONLD_LT_RE = /</g;\nconst JSONLD_GT_RE = />/g;\nconst JSONLD_U2028_RE = /\\u2028/g;\nconst JSONLD_U2029_RE = /\\u2029/g;\n\n/**\n * Safely serialize a value for embedding in a <script type=\"application/ld+json\"> tag.\n *\n * Plain JSON.stringify is not sufficient because:\n * - \"</script>\" in a nested string breaks out of the script tag\n * - \"<!--\" can open an HTML comment\n * - U+2028/U+2029 are line terminators in some JS engines\n */\nexport function safeJsonLdSerialize(value: unknown): string {\n\treturn JSON.stringify(value)\n\t\t.replace(JSONLD_LT_RE, \"\\\\u003c\")\n\t\t.replace(JSONLD_GT_RE, \"\\\\u003e\")\n\t\t.replace(JSONLD_U2028_RE, \"\\\\u2028\")\n\t\t.replace(JSONLD_U2029_RE, \"\\\\u2029\");\n}\n\nexport async function createSha256CspHash(value: string): Promise<string> {\n\tconst digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(value));\n\tconst bytes = new Uint8Array(digest);\n\tlet binary = \"\";\n\tfor (const byte of bytes) {\n\t\tbinary += String.fromCharCode(byte);\n\t}\n\treturn `sha256-${btoa(binary)}`;\n}\n\n// ── Merge / dedupe ──────────────────────────────────────────────\n\n/**\n * Resolve a flat list of contributions into deduplicated metadata.\n * First contribution wins for any given dedupe key.\n */\nexport function resolvePageMetadata(\n\tcontributions: PageMetadataContribution[],\n): ResolvedPageMetadata {\n\tconst result: ResolvedPageMetadata = {\n\t\tmeta: [],\n\t\tproperties: [],\n\t\tlinks: [],\n\t\tjsonld: [],\n\t};\n\n\tconst seenMeta = new Set<string>();\n\tconst seenProperties = new Set<string>();\n\tconst seenLinks = new Set<string>();\n\tconst seenJsonLd = new Set<string>();\n\n\tfor (const c of contributions) {\n\t\tswitch (c.kind) {\n\t\t\tcase \"meta\": {\n\t\t\t\tconst dedupeKey = c.key ?? c.name;\n\t\t\t\tif (seenMeta.has(dedupeKey)) continue;\n\t\t\t\tseenMeta.add(dedupeKey);\n\t\t\t\tresult.meta.push({ name: c.name, content: c.content });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"property\": {\n\t\t\t\tconst dedupeKey = c.key ?? c.property;\n\t\t\t\tif (seenProperties.has(dedupeKey)) continue;\n\t\t\t\tseenProperties.add(dedupeKey);\n\t\t\t\tresult.properties.push({\n\t\t\t\t\tproperty: c.property,\n\t\t\t\t\tcontent: c.content,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"link\": {\n\t\t\t\tif (!isSafeHref(c.href)) {\n\t\t\t\t\tif (import.meta.env?.DEV) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t`[page:metadata] Rejected link contribution with unsafe href scheme: ${c.href}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (c.rel === \"canonical\") {\n\t\t\t\t\tif (seenLinks.has(\"canonical\")) continue;\n\t\t\t\t\tseenLinks.add(\"canonical\");\n\t\t\t\t} else {\n\t\t\t\t\tconst dedupeKey = c.key ?? c.hreflang ?? c.href;\n\t\t\t\t\tif (seenLinks.has(dedupeKey)) continue;\n\t\t\t\t\tseenLinks.add(dedupeKey);\n\t\t\t\t}\n\t\t\t\tresult.links.push({\n\t\t\t\t\trel: c.rel,\n\t\t\t\t\thref: c.href,\n\t\t\t\t\t...(c.hreflang && { hreflang: c.hreflang }),\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"jsonld\": {\n\t\t\t\tif (c.id) {\n\t\t\t\t\tif (seenJsonLd.has(c.id)) continue;\n\t\t\t\t\tseenJsonLd.add(c.id);\n\t\t\t\t}\n\t\t\t\tresult.jsonld.push({\n\t\t\t\t\tid: c.id,\n\t\t\t\t\tjson: safeJsonLdSerialize(c.graph),\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// Unknown contribution kind -- skip silently at runtime.\n\t\t\t\t// TypeScript catches this at compile time for typed callers,\n\t\t\t\t// but sandboxed plugins may return unexpected shapes.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n// ── HTML rendering ──────────────────────────────────────────────\n\n/** Render resolved metadata to an HTML string for embedding in <head> */\nexport function renderPageMetadata(\n\tmetadata: ResolvedPageMetadata,\n\toptions: RenderPageMetadataOptions = {},\n): string {\n\tconst parts: string[] = [];\n\tconst includeJsonLd = options.includeJsonLd ?? true;\n\n\tfor (const m of metadata.meta) {\n\t\tparts.push(`<meta name=\"${escapeHtmlAttr(m.name)}\" content=\"${escapeHtmlAttr(m.content)}\">`);\n\t}\n\n\tfor (const p of metadata.properties) {\n\t\tparts.push(\n\t\t\t`<meta property=\"${escapeHtmlAttr(p.property)}\" content=\"${escapeHtmlAttr(p.content)}\">`,\n\t\t);\n\t}\n\n\tfor (const l of metadata.links) {\n\t\tlet tag = `<link rel=\"${escapeHtmlAttr(l.rel)}\" href=\"${escapeHtmlAttr(l.href)}\"`;\n\t\tif (l.hreflang) {\n\t\t\ttag += ` hreflang=\"${escapeHtmlAttr(l.hreflang)}\"`;\n\t\t}\n\t\ttag += \">\";\n\t\tparts.push(tag);\n\t}\n\n\tif (includeJsonLd) {\n\t\tfor (const j of metadata.jsonld) {\n\t\t\tparts.push(`<script type=\"application/ld+json\">${j.json}</script>`);\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\");\n}\n","/**\n * Page fragment collection and rendering\n *\n * Collects raw markup / script contributions from trusted plugins via\n * the page:fragments hook. Sandboxed plugins are never invoked.\n */\n\nimport type { PageFragmentContribution, PagePlacement } from \"../plugins/types.js\";\nimport { escapeHtmlAttr } from \"./metadata.js\";\n\n/** Escape sequences that would break out of a script tag */\nconst SCRIPT_CLOSE_RE = /<\\//g;\n\n// ── Dedupe and filter ───────────────────────────────────────────\n\n/**\n * Filter contributions to a specific placement and deduplicate.\n * - Contributions with the same `key + placement` are deduped (first wins).\n * - External scripts with the same `src + placement` are deduped.\n */\nexport function resolveFragments(\n\tcontributions: PageFragmentContribution[],\n\tplacement: PagePlacement,\n): PageFragmentContribution[] {\n\tconst filtered = contributions.filter((c) => c.placement === placement);\n\tconst seen = new Set<string>();\n\tconst result: PageFragmentContribution[] = [];\n\n\tfor (const c of filtered) {\n\t\t// Key-based dedupe\n\t\tif (c.key) {\n\t\t\tconst dedupeKey = `key:${c.key}`;\n\t\t\tif (seen.has(dedupeKey)) continue;\n\t\t\tseen.add(dedupeKey);\n\t\t} else if (c.kind === \"external-script\") {\n\t\t\tconst dedupeKey = `src:${c.src}`;\n\t\t\tif (seen.has(dedupeKey)) continue;\n\t\t\tseen.add(dedupeKey);\n\t\t}\n\n\t\tresult.push(c);\n\t}\n\n\treturn result;\n}\n\n// ── HTML rendering ──────────────────────────────────────────────\n\nconst EVENT_HANDLER_RE = /^on/i;\n\nfunction renderAttributes(attrs: Record<string, string>): string {\n\treturn Object.entries(attrs)\n\t\t.filter(([k]) => !EVENT_HANDLER_RE.test(k))\n\t\t.map(([k, v]) => ` ${escapeHtmlAttr(k)}=\"${escapeHtmlAttr(v)}\"`)\n\t\t.join(\"\");\n}\n\n/** Render a single fragment contribution to HTML */\nfunction renderFragment(c: PageFragmentContribution): string {\n\tswitch (c.kind) {\n\t\tcase \"external-script\": {\n\t\t\tlet tag = `<script src=\"${escapeHtmlAttr(c.src)}\"`;\n\t\t\tif (c.async) tag += \" async\";\n\t\t\tif (c.defer) tag += \" defer\";\n\t\t\tif (c.attributes) tag += renderAttributes(c.attributes);\n\t\t\ttag += \"></script>\";\n\t\t\treturn tag;\n\t\t}\n\t\tcase \"inline-script\": {\n\t\t\tlet tag = \"<script\";\n\t\t\tif (c.attributes) tag += renderAttributes(c.attributes);\n\t\t\t// Escape </ to <\\/ to prevent breaking out of the script tag.\n\t\t\t// This is valid JS and protects against code built from user data.\n\t\t\ttag += `>${c.code.replace(SCRIPT_CLOSE_RE, \"<\\\\/\")}</script>`;\n\t\t\treturn tag;\n\t\t}\n\t\tcase \"html\":\n\t\t\treturn c.html;\n\t}\n}\n\n/** Render a list of fragment contributions to an HTML string */\nexport function renderFragments(\n\tcontributions: PageFragmentContribution[],\n\tplacement: PagePlacement,\n): string {\n\tconst resolved = resolveFragments(contributions, placement);\n\treturn resolved.map(renderFragment).join(\"\\n\");\n}\n","/**\n * JSON-LD structured data builders\n *\n * Moved from template SEO.astro components into core so all JSON-LD\n * is serialized via safeJsonLdSerialize() and never hand-rolled in templates.\n */\n\nimport type { PublicPageContext } from \"../plugins/types.js\";\n\n/**\n * Remove null/undefined values from a JSON-LD object recursively.\n * JSON-LD validators prefer absent keys over null values.\n */\nexport function cleanJsonLd(obj: Record<string, unknown>): Record<string, unknown> {\n\tconst cleaned: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(obj)) {\n\t\tif (value !== undefined && value !== null) {\n\t\t\tif (typeof value === \"object\" && !Array.isArray(value)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- non-null, non-array object is safely treated as Record<string, unknown> for JSON-LD traversal\n\t\t\t\tcleaned[key] = cleanJsonLd(value as Record<string, unknown>);\n\t\t\t} else {\n\t\t\t\tcleaned[key] = value;\n\t\t\t}\n\t\t}\n\t}\n\treturn cleaned;\n}\n\n/**\n * Build a BlogPosting JSON-LD graph from page context.\n * Used for article-type content pages.\n *\n * @param page - Page context for the current request.\n * @param defaultOgImage - Optional site-wide fallback image URL, used when\n * the page has no own OG image. Matches the fallback applied to `og:image`\n * in `generateBaseSeoContributions`.\n */\nexport function buildBlogPostingJsonLd(\n\tpage: PublicPageContext,\n\tdefaultOgImage?: string | null,\n): Record<string, unknown> | null {\n\tif (page.pageType !== \"article\" || !page.canonical) return null;\n\n\tconst ogTitle = page.seo?.ogTitle ?? page.pageTitle ?? page.title;\n\tconst description = page.seo?.ogDescription || page.description;\n\tconst ogImage = page.seo?.ogImage || page.image || defaultOgImage || null;\n\tconst publishedTime = page.articleMeta?.publishedTime;\n\tconst modifiedTime = page.articleMeta?.modifiedTime;\n\tconst author = page.articleMeta?.author;\n\tconst siteName = page.siteName;\n\n\treturn cleanJsonLd({\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"BlogPosting\",\n\t\theadline: ogTitle,\n\t\tdescription,\n\t\timage: ogImage || undefined,\n\t\turl: page.canonical,\n\t\tdatePublished: publishedTime || undefined,\n\t\tdateModified: modifiedTime || publishedTime || undefined,\n\t\tauthor: author\n\t\t\t? {\n\t\t\t\t\t\"@type\": \"Person\",\n\t\t\t\t\tname: author,\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tpublisher: siteName\n\t\t\t? {\n\t\t\t\t\t\"@type\": \"Organization\",\n\t\t\t\t\tname: siteName,\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tmainEntityOfPage: {\n\t\t\t\"@type\": \"WebPage\",\n\t\t\t\"@id\": page.canonical,\n\t\t},\n\t});\n}\n\n/**\n * Build a WebSite JSON-LD graph from page context.\n * Used for non-article pages (homepage, listing pages, etc.)\n */\nexport function buildWebSiteJsonLd(page: PublicPageContext): Record<string, unknown> | null {\n\tconst siteName = page.siteName;\n\tif (!siteName) return null;\n\n\t// Use configured public origin, falling back to page URL origin\n\tlet siteUrl: string;\n\tif (page.siteUrl) {\n\t\tsiteUrl = page.siteUrl;\n\t} else {\n\t\ttry {\n\t\t\tsiteUrl = new URL(page.url).origin;\n\t\t} catch {\n\t\t\tsiteUrl = page.canonical || page.url;\n\t\t}\n\t}\n\n\treturn cleanJsonLd({\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"WebSite\",\n\t\tname: siteName,\n\t\turl: siteUrl,\n\t});\n}\n","/**\n * Generate base SEO metadata contributions from PublicPageContext.\n *\n * EmDashHead.astro composes the final contribution list as\n * `[...plugin, ...site, ...base]` and feeds it to `resolvePageMetadata()`,\n * which is first-wins. That ordering means plugin contributions override\n * site-level ones override base ones for any given key — base values are\n * the fallback, not the source of truth.\n *\n * This replaces the per-template SEO.astro components, eliminating\n * the class of XSS bugs where templates hand-rolled JSON-LD serialization.\n */\n\nimport type { PageMetadataContribution, PublicPageContext } from \"../plugins/types.js\";\nimport type { SeoSettings } from \"../settings/types.js\";\nimport { buildBlogPostingJsonLd, buildWebSiteJsonLd } from \"./jsonld.js\";\n\n/**\n * Generate base metadata contributions from a page context's SEO data.\n *\n * @param page - Page context produced by the runtime for the current request.\n * @param defaultOgImage - Optional site-wide fallback OG image URL, used when\n * the page has no own OG image (i.e., neither `seo.ogImage` nor `image`).\n * Sourced from `SiteSettings.seo.defaultOgImage` by `EmDashHead`.\n *\n * Returns an empty array if no SEO-relevant data is present.\n */\nexport function generateBaseSeoContributions(\n\tpage: PublicPageContext,\n\tdefaultOgImage?: string | null,\n): PageMetadataContribution[] {\n\tconst contributions: PageMetadataContribution[] = [];\n\n\tconst description = page.description;\n\tconst ogTitle = page.seo?.ogTitle ?? page.pageTitle ?? page.title;\n\tconst ogDescription = page.seo?.ogDescription || description;\n\tconst ogImage = page.seo?.ogImage || page.image || defaultOgImage || null;\n\tconst robots = page.seo?.robots;\n\tconst canonical = page.canonical;\n\tconst siteName = page.siteName;\n\n\t// -- Meta tags --\n\n\tif (description) {\n\t\tcontributions.push({ kind: \"meta\", name: \"description\", content: description });\n\t}\n\n\tif (robots) {\n\t\tcontributions.push({ kind: \"meta\", name: \"robots\", content: robots });\n\t}\n\n\t// -- Canonical link --\n\n\tif (canonical) {\n\t\tcontributions.push({ kind: \"link\", rel: \"canonical\", href: canonical });\n\t}\n\n\t// -- Open Graph --\n\n\tcontributions.push({\n\t\tkind: \"property\",\n\t\tproperty: \"og:type\",\n\t\tcontent: page.pageType === \"article\" ? \"article\" : \"website\",\n\t});\n\n\tif (ogTitle) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:title\", content: ogTitle });\n\t}\n\n\tif (ogDescription) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:description\", content: ogDescription });\n\t}\n\n\tif (ogImage) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:image\", content: ogImage });\n\t}\n\n\tif (canonical) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:url\", content: canonical });\n\t}\n\n\tif (siteName) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:site_name\", content: siteName });\n\t}\n\n\t// -- Twitter Card --\n\n\tcontributions.push({\n\t\tkind: \"meta\",\n\t\tname: \"twitter:card\",\n\t\tcontent: ogImage ? \"summary_large_image\" : \"summary\",\n\t});\n\n\tif (ogTitle) {\n\t\tcontributions.push({ kind: \"meta\", name: \"twitter:title\", content: ogTitle });\n\t}\n\n\tif (ogDescription) {\n\t\tcontributions.push({ kind: \"meta\", name: \"twitter:description\", content: ogDescription });\n\t}\n\n\tif (ogImage) {\n\t\tcontributions.push({ kind: \"meta\", name: \"twitter:image\", content: ogImage });\n\t}\n\n\t// -- Article metadata --\n\n\tif (page.pageType === \"article\" && page.articleMeta) {\n\t\tconst { publishedTime, modifiedTime, author } = page.articleMeta;\n\t\tif (publishedTime) {\n\t\t\tcontributions.push({\n\t\t\t\tkind: \"property\",\n\t\t\t\tproperty: \"article:published_time\",\n\t\t\t\tcontent: publishedTime,\n\t\t\t});\n\t\t}\n\t\tif (modifiedTime) {\n\t\t\tcontributions.push({\n\t\t\t\tkind: \"property\",\n\t\t\t\tproperty: \"article:modified_time\",\n\t\t\t\tcontent: modifiedTime,\n\t\t\t});\n\t\t}\n\t\tif (author) {\n\t\t\tcontributions.push({\n\t\t\t\tkind: \"property\",\n\t\t\t\tproperty: \"article:author\",\n\t\t\t\tcontent: author,\n\t\t\t});\n\t\t}\n\t}\n\n\t// -- JSON-LD --\n\n\tif (page.pageType === \"article\") {\n\t\tconst blogPosting = buildBlogPostingJsonLd(page, defaultOgImage ?? null);\n\t\tif (blogPosting) {\n\t\t\tcontributions.push({ kind: \"jsonld\", id: \"primary\", graph: blogPosting });\n\t\t}\n\t} else if (siteName) {\n\t\tconst webSite = buildWebSiteJsonLd(page);\n\t\tif (webSite) {\n\t\t\tcontributions.push({ kind: \"jsonld\", id: \"primary\", graph: webSite });\n\t\t}\n\t}\n\n\treturn contributions;\n}\n\n/**\n * Generate site-level SEO metadata contributions from SiteSettings.seo.\n *\n * These tags apply to every page (search engine ownership verification),\n * so they're sourced from site settings rather than per-page context.\n * Returns an empty array when no relevant settings are configured.\n */\nexport function generateSiteSeoContributions(\n\tseoSettings: SeoSettings | undefined,\n): PageMetadataContribution[] {\n\tconst contributions: PageMetadataContribution[] = [];\n\n\tif (!seoSettings) {\n\t\treturn contributions;\n\t}\n\n\tif (seoSettings.googleVerification) {\n\t\tcontributions.push({\n\t\t\tkind: \"meta\",\n\t\t\tname: \"google-site-verification\",\n\t\t\tcontent: seoSettings.googleVerification,\n\t\t});\n\t}\n\n\tif (seoSettings.bingVerification) {\n\t\tcontributions.push({\n\t\t\tkind: \"meta\",\n\t\t\tname: \"msvalidate.01\",\n\t\t\tcontent: seoSettings.bingVerification,\n\t\t});\n\t}\n\n\treturn contributions;\n}\n","/**\n * emdash/page — Public page contribution API\n *\n * Template integration points for plugin-driven head metadata\n * and trusted body fragments.\n */\n\nimport type {\n\tPublicPageContext,\n\tPageMetadataContribution,\n\tPageFragmentContribution,\n} from \"../plugins/types.js\";\n\nexport { createPublicPageContext } from \"./context.js\";\nexport type { CreatePublicPageContextInput } from \"./context.js\";\n\nexport {\n\tresolvePageMetadata,\n\trenderPageMetadata,\n\tsafeJsonLdSerialize,\n\tescapeHtmlAttr,\n} from \"./metadata.js\";\nexport type { ResolvedPageMetadata } from \"./metadata.js\";\n\nexport { resolveFragments, renderFragments } from \"./fragments.js\";\n\nexport { generateBaseSeoContributions, generateSiteSeoContributions } from \"./seo-contributions.js\";\nexport { cleanJsonLd, buildBlogPostingJsonLd, buildWebSiteJsonLd } from \"./jsonld.js\";\n\n/**\n * Shape of the EmDash runtime methods used by the render components.\n * Extracted here so all three components share a single type definition.\n */\nexport interface EmDashPageRuntime {\n\tcollectPageMetadata: (page: PublicPageContext) => Promise<PageMetadataContribution[]>;\n\tcollectPageFragments: (page: PublicPageContext) => Promise<PageFragmentContribution[]>;\n}\n\n/**\n * Get the page runtime from Astro locals. Returns undefined when\n * EmDash is not initialized (components render nothing in that case).\n */\nexport function getPageRuntime(locals: Record<string, unknown>): EmDashPageRuntime | undefined {\n\tconst emdash = locals.emdash;\n\tif (\n\t\temdash &&\n\t\ttypeof emdash === \"object\" &&\n\t\t\"collectPageMetadata\" in emdash &&\n\t\t\"collectPageFragments\" in emdash\n\t) {\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- structural check above confirms presence of required methods\n\t\treturn emdash as EmDashPageRuntime;\n\t}\n\treturn undefined;\n}\n\n// Astro render components are exported from \"emdash/ui\":\n// import { EmDashHead, EmDashBodyStart, EmDashBodyEnd } from \"emdash/ui\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/page/context.ts","../../src/page/metadata.ts","../../src/page/fragments.ts","../../src/page/jsonld.ts","../../src/page/seo-contributions.ts","../../src/page/index.ts"],"sourcesContent":["/**\n * Public page context builder\n *\n * Templates call this to describe the page being rendered.\n * The resulting context is passed to EmDashHead / EmDashBodyStart / EmDashBodyEnd.\n */\n\nimport type { BreadcrumbItem, PublicPageContext } from \"../plugins/types.js\";\n\n/** Fields shared by both input forms */\ninterface PageContextFields {\n\tkind: \"content\" | \"custom\";\n\tpageType?: string;\n\ttitle?: string | null;\n\tpageTitle?: string | null;\n\tdescription?: string | null;\n\tcanonical?: string | null;\n\timage?: string | null;\n\tcontent?: { collection: string; id: string; slug?: string | null };\n\t/** SEO overrides for OG/Twitter meta generation */\n\tseo?: {\n\t\togTitle?: string | null;\n\t\togDescription?: string | null;\n\t\togImage?: string | null;\n\t\trobots?: string | null;\n\t};\n\t/** Article metadata for Open Graph article: tags */\n\tarticleMeta?: {\n\t\tpublishedTime?: string | null;\n\t\tmodifiedTime?: string | null;\n\t\tauthor?: string | null;\n\t};\n\t/** Site name for structured data and og:site_name */\n\tsiteName?: string;\n\t/**\n\t * Breadcrumb trail for this page, root first. Pass an empty array\n\t * to explicitly opt out of breadcrumbs (e.g. homepage), or omit the\n\t * field to let consumers fall back to their own derivation.\n\t */\n\tbreadcrumbs?: BreadcrumbItem[];\n\t/** Public-facing site URL (origin) for structured data */\n\tsiteUrl?: string;\n}\n\n/** Input with Astro global -- used in .astro files */\ninterface AstroInput extends PageContextFields {\n\tAstro: { url: URL; currentLocale?: string };\n}\n\n/** Input with explicit URL -- used outside .astro files */\ninterface UrlInput extends PageContextFields {\n\turl: URL | string;\n\tlocale?: string;\n}\n\nexport type CreatePublicPageContextInput = AstroInput | UrlInput;\n\nfunction isAstroInput(input: CreatePublicPageContextInput): input is AstroInput {\n\treturn \"Astro\" in input;\n}\n\n/**\n * Build a PublicPageContext from template input.\n */\nexport function createPublicPageContext(input: CreatePublicPageContextInput): PublicPageContext {\n\tlet url: string;\n\tlet path: string;\n\tlet locale: string | null;\n\n\tif (isAstroInput(input)) {\n\t\turl = input.Astro.url.href;\n\t\tpath = input.Astro.url.pathname;\n\t\tlocale = input.Astro.currentLocale ?? null;\n\t} else {\n\t\tconst parsed = typeof input.url === \"string\" ? new URL(input.url) : input.url;\n\t\turl = parsed.href;\n\t\tpath = parsed.pathname;\n\t\tlocale = input.locale ?? null;\n\t}\n\n\treturn {\n\t\turl,\n\t\tpath,\n\t\tlocale,\n\t\tkind: input.kind,\n\t\tpageType: input.pageType ?? (input.kind === \"content\" ? \"article\" : \"website\"),\n\t\ttitle: input.title ?? null,\n\t\tpageTitle: input.pageTitle ?? null,\n\t\tdescription: input.description ?? null,\n\t\tcanonical: input.canonical ?? null,\n\t\timage: input.image ?? null,\n\t\tcontent: input.content\n\t\t\t? {\n\t\t\t\t\tcollection: input.content.collection,\n\t\t\t\t\tid: input.content.id,\n\t\t\t\t\tslug: input.content.slug ?? null,\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tseo: input.seo,\n\t\tarticleMeta: input.articleMeta,\n\t\tsiteName: input.siteName,\n\t\tbreadcrumbs: input.breadcrumbs,\n\t\tsiteUrl: input.siteUrl,\n\t};\n}\n","/**\n * Page metadata collection and rendering\n *\n * Collects typed metadata contributions from plugins via the page:metadata hook,\n * validates them, and resolves them into a deduplicated structure ready to render.\n */\n\nimport type { PageMetadataContribution, PageMetadataLinkRel } from \"../plugins/types.js\";\n\n// ── Resolved output ─────────────────────────────────────────────\n\nexport interface ResolvedPageMetadata {\n\tmeta: Array<{ name: string; content: string }>;\n\tproperties: Array<{ property: string; content: string }>;\n\tlinks: Array<{\n\t\trel: PageMetadataLinkRel;\n\t\thref: string;\n\t\threflang?: string;\n\t}>;\n\tjsonld: Array<{ id?: string; json: string }>;\n}\n\ninterface RenderPageMetadataOptions {\n\tincludeJsonLd?: boolean;\n}\n\n// ── Validation ──────────────────────────────────────────────────\n\n/** Schemes safe for use in link href attributes */\nconst SAFE_HREF_RE = /^(https?|at):\\/\\//i;\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n\t\"&\": \"&\",\n\t\"<\": \"<\",\n\t\">\": \">\",\n\t'\"': \""\",\n\t\"'\": \"'\",\n};\nconst HTML_ESCAPE_RE = /[&<>\"']/g;\n\n/** Escape a string for safe use in an HTML attribute value */\nexport function escapeHtmlAttr(value: string): string {\n\treturn value.replace(HTML_ESCAPE_RE, (ch) => HTML_ESCAPE_MAP[ch] ?? ch);\n}\n\n/** Validate that a URL uses a safe scheme (http, https, at) */\nfunction isSafeHref(url: string): boolean {\n\treturn SAFE_HREF_RE.test(url);\n}\n\n// ── JSON-LD serialization ───────────────────────────────────────\n\nconst JSONLD_LT_RE = /</g;\nconst JSONLD_GT_RE = />/g;\nconst JSONLD_U2028_RE = /\\u2028/g;\nconst JSONLD_U2029_RE = /\\u2029/g;\n\n/**\n * Safely serialize a value for embedding in a <script type=\"application/ld+json\"> tag.\n *\n * Plain JSON.stringify is not sufficient because:\n * - \"</script>\" in a nested string breaks out of the script tag\n * - \"<!--\" can open an HTML comment\n * - U+2028/U+2029 are line terminators in some JS engines\n */\nexport function safeJsonLdSerialize(value: unknown): string {\n\treturn JSON.stringify(value)\n\t\t.replace(JSONLD_LT_RE, \"\\\\u003c\")\n\t\t.replace(JSONLD_GT_RE, \"\\\\u003e\")\n\t\t.replace(JSONLD_U2028_RE, \"\\\\u2028\")\n\t\t.replace(JSONLD_U2029_RE, \"\\\\u2029\");\n}\n\nexport async function createSha256CspHash(value: string): Promise<string> {\n\tconst digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(value));\n\tconst bytes = new Uint8Array(digest);\n\tlet binary = \"\";\n\tfor (const byte of bytes) {\n\t\tbinary += String.fromCharCode(byte);\n\t}\n\treturn `sha256-${btoa(binary)}`;\n}\n\ninterface CspScriptHashSink {\n\tinsertScriptHash(hash: string): void;\n}\n\n/**\n * Register JSON-LD hashes with Astro's CSP runtime when CSP is enabled.\n *\n * The getter stays lazy because Astro warns when `Astro.csp` is accessed on a\n * project that has not enabled its built-in CSP support.\n */\nexport async function registerJsonLdCspHashes(\n\tenabled: boolean,\n\tgetCsp: () => CspScriptHashSink | undefined,\n\tscripts: ReadonlyArray<{ json: string }>,\n): Promise<void> {\n\tif (!enabled || scripts.length === 0) return;\n\n\tconst csp = getCsp();\n\tif (!csp) return;\n\n\tawait Promise.all(\n\t\tscripts.map(async ({ json }) => {\n\t\t\tcsp.insertScriptHash(await createSha256CspHash(json));\n\t\t}),\n\t);\n}\n\n// ── Merge / dedupe ──────────────────────────────────────────────\n\n/**\n * Resolve a flat list of contributions into deduplicated metadata.\n * First contribution wins for any given dedupe key.\n */\nexport function resolvePageMetadata(\n\tcontributions: PageMetadataContribution[],\n): ResolvedPageMetadata {\n\tconst result: ResolvedPageMetadata = {\n\t\tmeta: [],\n\t\tproperties: [],\n\t\tlinks: [],\n\t\tjsonld: [],\n\t};\n\n\tconst seenMeta = new Set<string>();\n\tconst seenProperties = new Set<string>();\n\tconst seenLinks = new Set<string>();\n\tconst seenJsonLd = new Set<string>();\n\n\tfor (const c of contributions) {\n\t\tswitch (c.kind) {\n\t\t\tcase \"meta\": {\n\t\t\t\tconst dedupeKey = c.key ?? c.name;\n\t\t\t\tif (seenMeta.has(dedupeKey)) continue;\n\t\t\t\tseenMeta.add(dedupeKey);\n\t\t\t\tresult.meta.push({ name: c.name, content: c.content });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"property\": {\n\t\t\t\tconst dedupeKey = c.key ?? c.property;\n\t\t\t\tif (seenProperties.has(dedupeKey)) continue;\n\t\t\t\tseenProperties.add(dedupeKey);\n\t\t\t\tresult.properties.push({\n\t\t\t\t\tproperty: c.property,\n\t\t\t\t\tcontent: c.content,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"link\": {\n\t\t\t\tif (!isSafeHref(c.href)) {\n\t\t\t\t\tif (import.meta.env?.DEV) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t`[page:metadata] Rejected link contribution with unsafe href scheme: ${c.href}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (c.rel === \"canonical\") {\n\t\t\t\t\tif (seenLinks.has(\"canonical\")) continue;\n\t\t\t\t\tseenLinks.add(\"canonical\");\n\t\t\t\t} else {\n\t\t\t\t\tconst dedupeKey = c.key ?? c.hreflang ?? c.href;\n\t\t\t\t\tif (seenLinks.has(dedupeKey)) continue;\n\t\t\t\t\tseenLinks.add(dedupeKey);\n\t\t\t\t}\n\t\t\t\tresult.links.push({\n\t\t\t\t\trel: c.rel,\n\t\t\t\t\thref: c.href,\n\t\t\t\t\t...(c.hreflang && { hreflang: c.hreflang }),\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"jsonld\": {\n\t\t\t\tif (c.id) {\n\t\t\t\t\tif (seenJsonLd.has(c.id)) continue;\n\t\t\t\t\tseenJsonLd.add(c.id);\n\t\t\t\t}\n\t\t\t\tresult.jsonld.push({\n\t\t\t\t\tid: c.id,\n\t\t\t\t\tjson: safeJsonLdSerialize(c.graph),\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// Unknown contribution kind -- skip silently at runtime.\n\t\t\t\t// TypeScript catches this at compile time for typed callers,\n\t\t\t\t// but sandboxed plugins may return unexpected shapes.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n// ── HTML rendering ──────────────────────────────────────────────\n\n/** Render resolved metadata to an HTML string for embedding in <head> */\nexport function renderPageMetadata(\n\tmetadata: ResolvedPageMetadata,\n\toptions: RenderPageMetadataOptions = {},\n): string {\n\tconst parts: string[] = [];\n\tconst includeJsonLd = options.includeJsonLd ?? true;\n\n\tfor (const m of metadata.meta) {\n\t\tparts.push(`<meta name=\"${escapeHtmlAttr(m.name)}\" content=\"${escapeHtmlAttr(m.content)}\">`);\n\t}\n\n\tfor (const p of metadata.properties) {\n\t\tparts.push(\n\t\t\t`<meta property=\"${escapeHtmlAttr(p.property)}\" content=\"${escapeHtmlAttr(p.content)}\">`,\n\t\t);\n\t}\n\n\tfor (const l of metadata.links) {\n\t\tlet tag = `<link rel=\"${escapeHtmlAttr(l.rel)}\" href=\"${escapeHtmlAttr(l.href)}\"`;\n\t\tif (l.hreflang) {\n\t\t\ttag += ` hreflang=\"${escapeHtmlAttr(l.hreflang)}\"`;\n\t\t}\n\t\ttag += \">\";\n\t\tparts.push(tag);\n\t}\n\n\tif (includeJsonLd) {\n\t\tfor (const j of metadata.jsonld) {\n\t\t\tparts.push(`<script type=\"application/ld+json\">${j.json}</script>`);\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\");\n}\n","/**\n * Page fragment collection and rendering\n *\n * Collects raw markup / script contributions from trusted plugins via\n * the page:fragments hook. Sandboxed plugins are never invoked.\n */\n\nimport type { PageFragmentContribution, PagePlacement } from \"../plugins/types.js\";\nimport { escapeHtmlAttr } from \"./metadata.js\";\n\n/** Escape sequences that would break out of a script tag */\nconst SCRIPT_CLOSE_RE = /<\\//g;\n\n// ── Dedupe and filter ───────────────────────────────────────────\n\n/**\n * Filter contributions to a specific placement and deduplicate.\n * - Contributions with the same `key + placement` are deduped (first wins).\n * - External scripts with the same `src + placement` are deduped.\n */\nexport function resolveFragments(\n\tcontributions: PageFragmentContribution[],\n\tplacement: PagePlacement,\n): PageFragmentContribution[] {\n\tconst filtered = contributions.filter((c) => c.placement === placement);\n\tconst seen = new Set<string>();\n\tconst result: PageFragmentContribution[] = [];\n\n\tfor (const c of filtered) {\n\t\t// Key-based dedupe\n\t\tif (c.key) {\n\t\t\tconst dedupeKey = `key:${c.key}`;\n\t\t\tif (seen.has(dedupeKey)) continue;\n\t\t\tseen.add(dedupeKey);\n\t\t} else if (c.kind === \"external-script\") {\n\t\t\tconst dedupeKey = `src:${c.src}`;\n\t\t\tif (seen.has(dedupeKey)) continue;\n\t\t\tseen.add(dedupeKey);\n\t\t}\n\n\t\tresult.push(c);\n\t}\n\n\treturn result;\n}\n\n// ── HTML rendering ──────────────────────────────────────────────\n\nconst EVENT_HANDLER_RE = /^on/i;\n\nfunction renderAttributes(attrs: Record<string, string>): string {\n\treturn Object.entries(attrs)\n\t\t.filter(([k]) => !EVENT_HANDLER_RE.test(k))\n\t\t.map(([k, v]) => ` ${escapeHtmlAttr(k)}=\"${escapeHtmlAttr(v)}\"`)\n\t\t.join(\"\");\n}\n\n/** Render a single fragment contribution to HTML */\nfunction renderFragment(c: PageFragmentContribution): string {\n\tswitch (c.kind) {\n\t\tcase \"external-script\": {\n\t\t\tlet tag = `<script src=\"${escapeHtmlAttr(c.src)}\"`;\n\t\t\tif (c.async) tag += \" async\";\n\t\t\tif (c.defer) tag += \" defer\";\n\t\t\tif (c.attributes) tag += renderAttributes(c.attributes);\n\t\t\ttag += \"></script>\";\n\t\t\treturn tag;\n\t\t}\n\t\tcase \"inline-script\": {\n\t\t\tlet tag = \"<script\";\n\t\t\tif (c.attributes) tag += renderAttributes(c.attributes);\n\t\t\t// Escape </ to <\\/ to prevent breaking out of the script tag.\n\t\t\t// This is valid JS and protects against code built from user data.\n\t\t\ttag += `>${c.code.replace(SCRIPT_CLOSE_RE, \"<\\\\/\")}</script>`;\n\t\t\treturn tag;\n\t\t}\n\t\tcase \"html\":\n\t\t\treturn c.html;\n\t}\n}\n\n/** Render a list of fragment contributions to an HTML string */\nexport function renderFragments(\n\tcontributions: PageFragmentContribution[],\n\tplacement: PagePlacement,\n): string {\n\tconst resolved = resolveFragments(contributions, placement);\n\treturn resolved.map(renderFragment).join(\"\\n\");\n}\n","/**\n * JSON-LD structured data builders\n *\n * Moved from template SEO.astro components into core so all JSON-LD\n * is serialized via safeJsonLdSerialize() and never hand-rolled in templates.\n */\n\nimport type { PublicPageContext } from \"../plugins/types.js\";\n\n/**\n * Remove null/undefined values from a JSON-LD object recursively.\n * JSON-LD validators prefer absent keys over null values.\n */\nexport function cleanJsonLd(obj: Record<string, unknown>): Record<string, unknown> {\n\tconst cleaned: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(obj)) {\n\t\tif (value !== undefined && value !== null) {\n\t\t\tif (typeof value === \"object\" && !Array.isArray(value)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- non-null, non-array object is safely treated as Record<string, unknown> for JSON-LD traversal\n\t\t\t\tcleaned[key] = cleanJsonLd(value as Record<string, unknown>);\n\t\t\t} else {\n\t\t\t\tcleaned[key] = value;\n\t\t\t}\n\t\t}\n\t}\n\treturn cleaned;\n}\n\n/**\n * Build a BlogPosting JSON-LD graph from page context.\n * Used for article-type content pages.\n *\n * @param page - Page context for the current request.\n * @param defaultOgImage - Optional site-wide fallback image URL, used when\n * the page has no own OG image. Matches the fallback applied to `og:image`\n * in `generateBaseSeoContributions`.\n */\nexport function buildBlogPostingJsonLd(\n\tpage: PublicPageContext,\n\tdefaultOgImage?: string | null,\n): Record<string, unknown> | null {\n\tif (page.pageType !== \"article\" || !page.canonical) return null;\n\n\tconst ogTitle = page.seo?.ogTitle ?? page.pageTitle ?? page.title;\n\tconst description = page.seo?.ogDescription || page.description;\n\tconst ogImage = page.seo?.ogImage || page.image || defaultOgImage || null;\n\tconst publishedTime = page.articleMeta?.publishedTime;\n\tconst modifiedTime = page.articleMeta?.modifiedTime;\n\tconst author = page.articleMeta?.author;\n\tconst siteName = page.siteName;\n\n\treturn cleanJsonLd({\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"BlogPosting\",\n\t\theadline: ogTitle,\n\t\tdescription,\n\t\timage: ogImage || undefined,\n\t\turl: page.canonical,\n\t\tdatePublished: publishedTime || undefined,\n\t\tdateModified: modifiedTime || publishedTime || undefined,\n\t\tauthor: author\n\t\t\t? {\n\t\t\t\t\t\"@type\": \"Person\",\n\t\t\t\t\tname: author,\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tpublisher: siteName\n\t\t\t? {\n\t\t\t\t\t\"@type\": \"Organization\",\n\t\t\t\t\tname: siteName,\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tmainEntityOfPage: {\n\t\t\t\"@type\": \"WebPage\",\n\t\t\t\"@id\": page.canonical,\n\t\t},\n\t});\n}\n\n/**\n * Build a WebSite JSON-LD graph from page context.\n * Used for non-article pages (homepage, listing pages, etc.)\n */\nexport function buildWebSiteJsonLd(page: PublicPageContext): Record<string, unknown> | null {\n\tconst siteName = page.siteName;\n\tif (!siteName) return null;\n\n\t// Use configured public origin, falling back to page URL origin\n\tlet siteUrl: string;\n\tif (page.siteUrl) {\n\t\tsiteUrl = page.siteUrl;\n\t} else {\n\t\ttry {\n\t\t\tsiteUrl = new URL(page.url).origin;\n\t\t} catch {\n\t\t\tsiteUrl = page.canonical || page.url;\n\t\t}\n\t}\n\n\treturn cleanJsonLd({\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"WebSite\",\n\t\tname: siteName,\n\t\turl: siteUrl,\n\t});\n}\n","/**\n * Generate base SEO metadata contributions from PublicPageContext.\n *\n * EmDashHead.astro composes the final contribution list as\n * `[...plugin, ...site, ...base]` and feeds it to `resolvePageMetadata()`,\n * which is first-wins. That ordering means plugin contributions override\n * site-level ones override base ones for any given key — base values are\n * the fallback, not the source of truth.\n *\n * This replaces the per-template SEO.astro components, eliminating\n * the class of XSS bugs where templates hand-rolled JSON-LD serialization.\n */\n\nimport type { PageMetadataContribution, PublicPageContext } from \"../plugins/types.js\";\nimport type { SeoSettings } from \"../settings/types.js\";\nimport { buildBlogPostingJsonLd, buildWebSiteJsonLd } from \"./jsonld.js\";\n\n/**\n * Generate base metadata contributions from a page context's SEO data.\n *\n * @param page - Page context produced by the runtime for the current request.\n * @param defaultOgImage - Optional site-wide fallback OG image URL, used when\n * the page has no own OG image (i.e., neither `seo.ogImage` nor `image`).\n * Sourced from `SiteSettings.seo.defaultOgImage` by `EmDashHead`.\n *\n * Returns an empty array if no SEO-relevant data is present.\n */\nexport function generateBaseSeoContributions(\n\tpage: PublicPageContext,\n\tdefaultOgImage?: string | null,\n): PageMetadataContribution[] {\n\tconst contributions: PageMetadataContribution[] = [];\n\n\tconst description = page.description;\n\tconst ogTitle = page.seo?.ogTitle ?? page.pageTitle ?? page.title;\n\tconst ogDescription = page.seo?.ogDescription || description;\n\tconst ogImage = page.seo?.ogImage || page.image || defaultOgImage || null;\n\tconst robots = page.seo?.robots;\n\tconst canonical = page.canonical;\n\tconst siteName = page.siteName;\n\n\t// -- Meta tags --\n\n\tif (description) {\n\t\tcontributions.push({ kind: \"meta\", name: \"description\", content: description });\n\t}\n\n\tif (robots) {\n\t\tcontributions.push({ kind: \"meta\", name: \"robots\", content: robots });\n\t}\n\n\t// -- Canonical link --\n\n\tif (canonical) {\n\t\tcontributions.push({ kind: \"link\", rel: \"canonical\", href: canonical });\n\t}\n\n\t// -- Open Graph --\n\n\tcontributions.push({\n\t\tkind: \"property\",\n\t\tproperty: \"og:type\",\n\t\tcontent: page.pageType === \"article\" ? \"article\" : \"website\",\n\t});\n\n\tif (ogTitle) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:title\", content: ogTitle });\n\t}\n\n\tif (ogDescription) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:description\", content: ogDescription });\n\t}\n\n\tif (ogImage) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:image\", content: ogImage });\n\t}\n\n\tif (canonical) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:url\", content: canonical });\n\t}\n\n\tif (siteName) {\n\t\tcontributions.push({ kind: \"property\", property: \"og:site_name\", content: siteName });\n\t}\n\n\t// -- Twitter Card --\n\n\tcontributions.push({\n\t\tkind: \"meta\",\n\t\tname: \"twitter:card\",\n\t\tcontent: ogImage ? \"summary_large_image\" : \"summary\",\n\t});\n\n\tif (ogTitle) {\n\t\tcontributions.push({ kind: \"meta\", name: \"twitter:title\", content: ogTitle });\n\t}\n\n\tif (ogDescription) {\n\t\tcontributions.push({ kind: \"meta\", name: \"twitter:description\", content: ogDescription });\n\t}\n\n\tif (ogImage) {\n\t\tcontributions.push({ kind: \"meta\", name: \"twitter:image\", content: ogImage });\n\t}\n\n\t// -- Article metadata --\n\n\tif (page.pageType === \"article\" && page.articleMeta) {\n\t\tconst { publishedTime, modifiedTime, author } = page.articleMeta;\n\t\tif (publishedTime) {\n\t\t\tcontributions.push({\n\t\t\t\tkind: \"property\",\n\t\t\t\tproperty: \"article:published_time\",\n\t\t\t\tcontent: publishedTime,\n\t\t\t});\n\t\t}\n\t\tif (modifiedTime) {\n\t\t\tcontributions.push({\n\t\t\t\tkind: \"property\",\n\t\t\t\tproperty: \"article:modified_time\",\n\t\t\t\tcontent: modifiedTime,\n\t\t\t});\n\t\t}\n\t\tif (author) {\n\t\t\tcontributions.push({\n\t\t\t\tkind: \"property\",\n\t\t\t\tproperty: \"article:author\",\n\t\t\t\tcontent: author,\n\t\t\t});\n\t\t}\n\t}\n\n\t// -- JSON-LD --\n\n\tif (page.pageType === \"article\") {\n\t\tconst blogPosting = buildBlogPostingJsonLd(page, defaultOgImage ?? null);\n\t\tif (blogPosting) {\n\t\t\tcontributions.push({ kind: \"jsonld\", id: \"primary\", graph: blogPosting });\n\t\t}\n\t} else if (siteName) {\n\t\tconst webSite = buildWebSiteJsonLd(page);\n\t\tif (webSite) {\n\t\t\tcontributions.push({ kind: \"jsonld\", id: \"primary\", graph: webSite });\n\t\t}\n\t}\n\n\treturn contributions;\n}\n\n/**\n * Generate site-level SEO metadata contributions from SiteSettings.seo.\n *\n * These tags apply to every page (search engine ownership verification),\n * so they're sourced from site settings rather than per-page context.\n * Returns an empty array when no relevant settings are configured.\n */\nexport function generateSiteSeoContributions(\n\tseoSettings: SeoSettings | undefined,\n): PageMetadataContribution[] {\n\tconst contributions: PageMetadataContribution[] = [];\n\n\tif (!seoSettings) {\n\t\treturn contributions;\n\t}\n\n\tif (seoSettings.googleVerification) {\n\t\tcontributions.push({\n\t\t\tkind: \"meta\",\n\t\t\tname: \"google-site-verification\",\n\t\t\tcontent: seoSettings.googleVerification,\n\t\t});\n\t}\n\n\tif (seoSettings.bingVerification) {\n\t\tcontributions.push({\n\t\t\tkind: \"meta\",\n\t\t\tname: \"msvalidate.01\",\n\t\t\tcontent: seoSettings.bingVerification,\n\t\t});\n\t}\n\n\treturn contributions;\n}\n","/**\n * emdash/page — Public page contribution API\n *\n * Template integration points for plugin-driven head metadata\n * and trusted body fragments.\n */\n\nimport type {\n\tPublicPageContext,\n\tPageMetadataContribution,\n\tPageFragmentContribution,\n} from \"../plugins/types.js\";\n\nexport { createPublicPageContext } from \"./context.js\";\nexport type { CreatePublicPageContextInput } from \"./context.js\";\n\nexport {\n\tresolvePageMetadata,\n\trenderPageMetadata,\n\tsafeJsonLdSerialize,\n\tescapeHtmlAttr,\n} from \"./metadata.js\";\nexport type { ResolvedPageMetadata } from \"./metadata.js\";\n\nexport { resolveFragments, renderFragments } from \"./fragments.js\";\n\nexport { generateBaseSeoContributions, generateSiteSeoContributions } from \"./seo-contributions.js\";\nexport { cleanJsonLd, buildBlogPostingJsonLd, buildWebSiteJsonLd } from \"./jsonld.js\";\n\n/**\n * Shape of the EmDash runtime methods used by the render components.\n * Extracted here so all three components share a single type definition.\n */\nexport interface EmDashPageRuntime {\n\tcollectPageMetadata: (page: PublicPageContext) => Promise<PageMetadataContribution[]>;\n\tcollectPageFragments: (page: PublicPageContext) => Promise<PageFragmentContribution[]>;\n}\n\n/**\n * Get the page runtime from Astro locals. Returns undefined when\n * EmDash is not initialized (components render nothing in that case).\n */\nexport function getPageRuntime(locals: Record<string, unknown>): EmDashPageRuntime | undefined {\n\tconst emdash = locals.emdash;\n\tif (\n\t\temdash &&\n\t\ttypeof emdash === \"object\" &&\n\t\t\"collectPageMetadata\" in emdash &&\n\t\t\"collectPageFragments\" in emdash\n\t) {\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- structural check above confirms presence of required methods\n\t\treturn emdash as EmDashPageRuntime;\n\t}\n\treturn undefined;\n}\n\n// Astro render components are exported from \"emdash/ui\":\n// import { EmDashHead, EmDashBodyStart, EmDashBodyEnd } from \"emdash/ui\";\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as apiError } from "./error-
|
|
1
|
+
import { t as apiError } from "./error-DJ98TRbQ.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/api/parse.ts
|
|
@@ -63,18 +63,10 @@ function parseQuery(url, schema) {
|
|
|
63
63
|
function validate(schema, data) {
|
|
64
64
|
const result = schema.safeParse(data);
|
|
65
65
|
if (result.success) return result.data;
|
|
66
|
-
|
|
66
|
+
return apiError("VALIDATION_ERROR", "Invalid request data", 400, { issues: result.error.issues.map((issue) => ({
|
|
67
67
|
path: issue.path.join("."),
|
|
68
68
|
message: issue.message
|
|
69
|
-
}));
|
|
70
|
-
return Response.json({ error: {
|
|
71
|
-
code: "VALIDATION_ERROR",
|
|
72
|
-
message: "Invalid request data",
|
|
73
|
-
details: { issues }
|
|
74
|
-
} }, {
|
|
75
|
-
status: 400,
|
|
76
|
-
headers: { "Cache-Control": "private, no-store" }
|
|
77
|
-
});
|
|
69
|
+
})) });
|
|
78
70
|
}
|
|
79
71
|
/**
|
|
80
72
|
* Type guard to check if a ParseResult is an error Response.
|
|
@@ -86,4 +78,4 @@ function isParseError(result) {
|
|
|
86
78
|
|
|
87
79
|
//#endregion
|
|
88
80
|
export { parseQuery as i, parseBody as n, parseOptionalBody as r, isParseError as t };
|
|
89
|
-
//# sourceMappingURL=parse-
|
|
81
|
+
//# sourceMappingURL=parse-53zyxlpv.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-
|
|
1
|
+
{"version":3,"file":"parse-53zyxlpv.mjs","names":[],"sources":["../src/api/parse.ts"],"sourcesContent":["/**\n * Request body and query parameter parsing with Zod validation.\n *\n * All API routes should use these utilities instead of `request.json() as T`\n * or raw `url.searchParams.get()` with manual coercion.\n */\n\nimport { z } from \"zod\";\n\nimport { apiError } from \"./error.js\";\n\n/** Maximum allowed JSON request body size (10 MB). */\nconst MAX_BODY_SIZE = 10 * 1024 * 1024;\n\n/**\n * Result of parsing: either the validated data or an error Response.\n * Routes should check `if (result instanceof Response) return result;`\n */\nexport type ParseResult<T> = T | Response;\n\n/**\n * Parse and validate a JSON request body against a Zod schema.\n *\n * Returns the validated data on success, or a 400 Response on failure.\n * Replaces all `(await request.json()) as T` casts.\n */\nexport async function parseBody<T extends z.ZodType>(\n\trequest: Request,\n\tschema: T,\n): Promise<ParseResult<z.infer<T>>> {\n\t// Best-effort size check via Content-Length (can be absent with chunked encoding)\n\tconst contentLength = request.headers.get(\"Content-Length\");\n\tif (contentLength && parseInt(contentLength, 10) > MAX_BODY_SIZE) {\n\t\treturn apiError(\"PAYLOAD_TOO_LARGE\", \"Request body too large\", 413);\n\t}\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = await request.json();\n\t} catch {\n\t\treturn apiError(\"INVALID_JSON\", \"Request body must be valid JSON\", 400);\n\t}\n\n\treturn validate(schema, raw);\n}\n\n/**\n * Parse and validate an optional JSON request body.\n *\n * Returns `defaultValue` if the body is empty, or the validated data if present.\n * For endpoints where the body is optional (e.g., preview-url, confirm).\n */\nexport async function parseOptionalBody<T extends z.ZodType>(\n\trequest: Request,\n\tschema: T,\n\tdefaultValue: z.infer<T>,\n): Promise<ParseResult<z.infer<T>>> {\n\t// Best-effort size check via Content-Length (can be absent with chunked encoding)\n\tconst contentLength = request.headers.get(\"Content-Length\");\n\tif (contentLength && parseInt(contentLength, 10) > MAX_BODY_SIZE) {\n\t\treturn apiError(\"PAYLOAD_TOO_LARGE\", \"Request body too large\", 413);\n\t}\n\n\tlet text: string;\n\ttry {\n\t\ttext = await request.text();\n\t} catch {\n\t\treturn defaultValue;\n\t}\n\n\tif (!text.trim()) {\n\t\treturn defaultValue;\n\t}\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(text);\n\t} catch {\n\t\treturn apiError(\"INVALID_JSON\", \"Request body must be valid JSON\", 400);\n\t}\n\n\treturn validate(schema, raw);\n}\n\n/**\n * Parse and validate URL search params against a Zod schema.\n *\n * Converts searchParams to a plain object before validation.\n * Zod coercion handles string -> number/boolean conversion.\n * Replaces manual `url.searchParams.get()` + `parseInt()` patterns.\n */\nexport function parseQuery<T extends z.ZodType>(url: URL, schema: T): ParseResult<z.infer<T>> {\n\tconst raw: Record<string, string> = {};\n\tfor (const [key, value] of url.searchParams) {\n\t\traw[key] = value;\n\t}\n\treturn validate(schema, raw);\n}\n\n/**\n * Validate raw data against a schema. Returns data or error Response.\n */\nfunction validate<T extends z.ZodType>(schema: T, data: unknown): ParseResult<z.infer<T>> {\n\tconst result = schema.safeParse(data);\n\n\tif (result.success) {\n\t\treturn result.data;\n\t}\n\n\t// Format Zod errors into a readable structure\n\tconst issues = result.error.issues.map((issue: z.ZodIssue) => ({\n\t\tpath: issue.path.join(\".\"),\n\t\tmessage: issue.message,\n\t}));\n\n\treturn apiError(\"VALIDATION_ERROR\", \"Invalid request data\", 400, { issues });\n}\n\n/**\n * Type guard to check if a ParseResult is an error Response.\n * Usage: `if (isParseError(result)) return result;`\n */\nexport function isParseError<T>(result: ParseResult<T>): result is Response {\n\treturn result instanceof Response;\n}\n"],"mappings":";;;;;AAYA,MAAM,gBAAgB,KAAK,OAAO;;;;;;;AAclC,eAAsB,UACrB,SACA,QACmC;CAEnC,MAAM,gBAAgB,QAAQ,QAAQ,IAAI,iBAAiB;AAC3D,KAAI,iBAAiB,SAAS,eAAe,GAAG,GAAG,cAClD,QAAO,SAAS,qBAAqB,0BAA0B,IAAI;CAGpE,IAAI;AACJ,KAAI;AACH,QAAM,MAAM,QAAQ,MAAM;SACnB;AACP,SAAO,SAAS,gBAAgB,mCAAmC,IAAI;;AAGxE,QAAO,SAAS,QAAQ,IAAI;;;;;;;;AAS7B,eAAsB,kBACrB,SACA,QACA,cACmC;CAEnC,MAAM,gBAAgB,QAAQ,QAAQ,IAAI,iBAAiB;AAC3D,KAAI,iBAAiB,SAAS,eAAe,GAAG,GAAG,cAClD,QAAO,SAAS,qBAAqB,0BAA0B,IAAI;CAGpE,IAAI;AACJ,KAAI;AACH,SAAO,MAAM,QAAQ,MAAM;SACpB;AACP,SAAO;;AAGR,KAAI,CAAC,KAAK,MAAM,CACf,QAAO;CAGR,IAAI;AACJ,KAAI;AACH,QAAM,KAAK,MAAM,KAAK;SACf;AACP,SAAO,SAAS,gBAAgB,mCAAmC,IAAI;;AAGxE,QAAO,SAAS,QAAQ,IAAI;;;;;;;;;AAU7B,SAAgB,WAAgC,KAAU,QAAoC;CAC7F,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,CAAC,KAAK,UAAU,IAAI,aAC9B,KAAI,OAAO;AAEZ,QAAO,SAAS,QAAQ,IAAI;;;;;AAM7B,SAAS,SAA8B,QAAW,MAAwC;CACzF,MAAM,SAAS,OAAO,UAAU,KAAK;AAErC,KAAI,OAAO,QACV,QAAO,OAAO;AASf,QAAO,SAAS,oBAAoB,wBAAwB,KAAK,EAAE,QALpD,OAAO,MAAM,OAAO,KAAK,WAAuB;EAC9D,MAAM,MAAM,KAAK,KAAK,IAAI;EAC1B,SAAS,MAAM;EACf,EAAE,EAEwE,CAAC;;;;;;AAO7E,SAAgB,aAAgB,QAA4C;AAC3E,QAAO,kBAAkB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-config-
|
|
1
|
+
{"version":3,"file":"passkey-config-C-SKpc0-.mjs","names":[],"sources":["../src/auth/passkey-config.ts"],"sourcesContent":["/**\n * Passkey configuration helper\n *\n * Extracts passkey configuration from the request URL.\n * This ensures the rpId and origin are correctly set for both\n * localhost development and production deployments.\n */\n\nexport interface PasskeyConfig {\n\trpName: string;\n\trpId: string;\n\t/**\n\t * Accepted client-data origins. First entry is the canonical/preferred origin;\n\t * additional entries support multi-origin deployments (e.g. apex + preview\n\t * subdomain sharing the same `rpId`). See `allowedOrigins` parameter.\n\t */\n\torigins: string[];\n}\n\n/**\n * Get passkey configuration from request URL\n *\n * @param url The request URL (typically `new URL(Astro.request.url)` or `new URL(request.url)`)\n * @param siteName Optional site name for rpName (defaults to hostname from `url` or public origin)\n * @param siteUrl Optional browser-facing origin (see `EmDashConfig.siteUrl`).\n * When set, the canonical **origin** and **rpId** are taken from this URL.\n * @param allowedOrigins Optional list of additional accepted origins for verification.\n * Each must share `rpId` with the canonical origin (WebAuthn requirement).\n * Typical use: apex + preview subdomain on the same registrable domain.\n * @throws If `siteUrl` is non-empty but not parseable by `new URL()`.\n */\nexport function getPasskeyConfig(\n\turl: URL,\n\tsiteName?: string,\n\tsiteUrl?: string,\n\tallowedOrigins?: string[],\n): PasskeyConfig {\n\tlet rpName: string;\n\tlet rpId: string;\n\tlet canonicalOrigin: string;\n\n\tif (siteUrl) {\n\t\tlet publicUrl: URL;\n\t\ttry {\n\t\t\tpublicUrl = new URL(siteUrl);\n\t\t} catch (e) {\n\t\t\tthrow new Error(`Invalid siteUrl: \"${siteUrl}\"`, { cause: e });\n\t\t}\n\t\trpName = siteName || publicUrl.hostname;\n\t\trpId = publicUrl.hostname;\n\t\tcanonicalOrigin = publicUrl.origin;\n\t} else {\n\t\trpName = siteName || url.hostname;\n\t\trpId = url.hostname;\n\t\tcanonicalOrigin = url.origin;\n\t}\n\n\tconst origins = [canonicalOrigin];\n\tif (allowedOrigins) {\n\t\tfor (const extra of allowedOrigins) {\n\t\t\tif (extra && !origins.includes(extra)) origins.push(extra);\n\t\t}\n\t}\n\n\treturn { rpName, rpId, origins };\n}\n"],"mappings":";;;;;;;;;;;;;AA+BA,SAAgB,iBACf,KACA,UACA,SACA,gBACgB;CAChB,IAAI;CACJ,IAAI;CACJ,IAAI;AAEJ,KAAI,SAAS;EACZ,IAAI;AACJ,MAAI;AACH,eAAY,IAAI,IAAI,QAAQ;WACpB,GAAG;AACX,SAAM,IAAI,MAAM,qBAAqB,QAAQ,IAAI,EAAE,OAAO,GAAG,CAAC;;AAE/D,WAAS,YAAY,UAAU;AAC/B,SAAO,UAAU;AACjB,oBAAkB,UAAU;QACtB;AACN,WAAS,YAAY,IAAI;AACzB,SAAO,IAAI;AACX,oBAAkB,IAAI;;CAGvB,MAAM,UAAU,CAAC,gBAAgB;AACjC,KAAI,gBACH;OAAK,MAAM,SAAS,eACnB,KAAI,SAAS,CAAC,QAAQ,SAAS,MAAM,CAAE,SAAQ,KAAK,MAAM;;AAI5D,QAAO;EAAE;EAAQ;EAAM;EAAS"}
|
|
@@ -297,4 +297,4 @@ declare function generatePlaceholder(buffer: Uint8Array, mimeType: string, dimen
|
|
|
297
297
|
}): Promise<PlaceholderData | null>;
|
|
298
298
|
//#endregion
|
|
299
299
|
export { MediaValue as _, ComponentEmbed as a, mediaItemToValue as b, EmbedResult as c, MediaListResult as d, MediaProvider as f, MediaUploadInput as g, MediaProviderItem as h, AudioEmbed as i, ImageEmbed as l, MediaProviderDescriptor as m, generatePlaceholder as n, CreateMediaProviderFn as o, MediaProviderCapabilities as p, normalizeMediaValue as r, EmbedOptions as s, PlaceholderData as t, MediaListOptions as u, ThumbnailOptions as v, VideoEmbed as y };
|
|
300
|
-
//# sourceMappingURL=placeholder-
|
|
300
|
+
//# sourceMappingURL=placeholder-Dop47JMM.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"placeholder-
|
|
1
|
+
{"version":3,"file":"placeholder-Dop47JMM.d.mts","names":[],"sources":["../src/media/types.ts","../src/media/normalize.ts","../src/media/placeholder.ts"],"mappings":"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"}
|