emdash 0.19.0 → 0.21.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-C5AWLJSD.d.mts → adapters-BxSmgtbF.d.mts} +1 -1
- package/dist/{adapters-C5AWLJSD.d.mts.map → adapters-BxSmgtbF.d.mts.map} +1 -1
- package/dist/{allowed-origins-CyYLEJkp.mjs → allowed-origins-BqC8cul8.mjs} +2 -2
- package/dist/{allowed-origins-CyYLEJkp.mjs.map → allowed-origins-BqC8cul8.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +13 -12
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +1 -1
- package/dist/api/schemas/index.mjs +3 -2
- package/dist/{api-BZ6bhjYs.mjs → api-DxjIV2o8.mjs} +46 -15
- package/dist/api-DxjIV2o8.mjs.map +1 -0
- package/dist/{api-tokens-VrXNiNvV.mjs → api-tokens-BFFkB0jB.mjs} +2 -2
- package/dist/{api-tokens-VrXNiNvV.mjs.map → api-tokens-BFFkB0jB.mjs.map} +1 -1
- package/dist/{apply-hQkKKBCf.mjs → apply-CLjxheyb.mjs} +12 -12
- package/dist/{apply-hQkKKBCf.mjs.map → apply-CLjxheyb.mjs.map} +1 -1
- package/dist/astro/index.d.mts +10 -10
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +50 -15
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +9 -9
- package/dist/astro/middleware/auth.mjs +5 -5
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +11 -2
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.mjs +3 -2
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +91 -137
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -4
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -4
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -7
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -7
- package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -7
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -12
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -12
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +14 -12
- package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +9 -8
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/counts.mjs +3 -3
- package/dist/astro/routes/api/admin/comments/index.mjs +7 -6
- package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +3 -3
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -26
- 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 +2 -2
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -26
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -4
- package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
- package/dist/astro/routes/api/admin/users/index.mjs +5 -4
- package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
- package/dist/astro/routes/api/auth/invite/complete.mjs +9 -8
- package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/index.mjs +6 -5
- package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -7
- package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -7
- package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.mjs +5 -4
- package/dist/astro/routes/api/auth/me.mjs.map +1 -1
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -4
- package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/options.mjs +10 -9
- package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -7
- package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -8
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -8
- package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/complete.mjs +9 -8
- package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/request.mjs +8 -7
- package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -9
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -8
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +10 -9
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/index.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
- package/dist/astro/routes/api/dashboard.mjs +3 -3
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +10 -9
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs +10 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +8 -7
- package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -8
- package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -7
- 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 +10 -9
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +14 -12
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +3 -3
- package/dist/astro/routes/api/mcp.mjs +20 -19
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -5
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +6 -5
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
- package/dist/astro/routes/api/media/providers/index.mjs +2 -2
- package/dist/astro/routes/api/media/upload-url.mjs +8 -7
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.mjs +10 -9
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +6 -5
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
- package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
- package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
- package/dist/astro/routes/api/oauth/token.mjs +5 -5
- package/dist/astro/routes/api/openapi.json.mjs +3 -2
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
- package/dist/astro/routes/api/redirects/404s/index.mjs +7 -6
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +7 -6
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +8 -7
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +8 -7
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -26
- 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 +28 -26
- 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 +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +8 -13
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -26
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +28 -26
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +9 -8
- package/dist/astro/routes/api/search/enable.mjs.map +1 -1
- package/dist/astro/routes/api/search/index.mjs +8 -7
- package/dist/astro/routes/api/search/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/rebuild.mjs +9 -8
- package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
- package/dist/astro/routes/api/search/stats.mjs +5 -5
- package/dist/astro/routes/api/search/suggest.mjs +8 -7
- package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
- package/dist/astro/routes/api/sections/_slug_.mjs +8 -7
- package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/sections/index.mjs +8 -7
- package/dist/astro/routes/api/sections/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/email.mjs +3 -3
- package/dist/astro/routes/api/settings.mjs +11 -9
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +10 -9
- package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin.mjs +9 -8
- package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +19 -18
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
- package/dist/astro/routes/api/setup/index.mjs +20 -18
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +5 -4
- package/dist/astro/routes/api/snapshot.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +5 -4
- package/dist/astro/routes/api/themes/preview.mjs.map +1 -1
- package/dist/astro/routes/api/typegen.mjs +4 -4
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -5
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -7
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -7
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -4
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +9 -7
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +5 -4
- package/dist/astro/routes/robots.txt.mjs.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +15 -7
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +6 -5
- package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
- package/dist/astro/types.d.mts +12 -12
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-C_8t2KGa.mjs → authorize-D5gfBVU5.mjs} +2 -2
- package/dist/{authorize-C_8t2KGa.mjs.map → authorize-D5gfBVU5.mjs.map} +1 -1
- package/dist/{byline-DUx48sJp.mjs → byline-V_Qp1Ziw.mjs} +27 -14
- package/dist/byline-V_Qp1Ziw.mjs.map +1 -0
- package/dist/{byline-fields-51kg6Vuv.mjs → byline-fields-B0NO1yUB.mjs} +3 -3
- package/dist/{byline-fields-51kg6Vuv.mjs.map → byline-fields-B0NO1yUB.mjs.map} +1 -1
- package/dist/{byline-fields-DYXKDuNX.d.mts → byline-fields-CQJRIQkn.d.mts} +36 -32
- package/dist/byline-fields-CQJRIQkn.d.mts.map +1 -0
- package/dist/{byline-fields-C_OsR-KF.mjs → byline-fields-nBVqK_Ff.mjs} +2 -2
- package/dist/{byline-fields-C_OsR-KF.mjs.map → byline-fields-nBVqK_Ff.mjs.map} +1 -1
- package/dist/{byline-registry-CWP7I71B.mjs → byline-registry-DedidtqC.mjs} +2 -2
- package/dist/{byline-registry-CWP7I71B.mjs.map → byline-registry-DedidtqC.mjs.map} +1 -1
- package/dist/{bylines-Cx5n-WqP.mjs → bylines-B2NWnIwS.mjs} +2 -2
- package/dist/{bylines-Cx5n-WqP.mjs.map → bylines-B2NWnIwS.mjs.map} +1 -1
- package/dist/{bylines-wurS258E.mjs → bylines-DfGDnred.mjs} +7 -7
- package/dist/{bylines-wurS258E.mjs.map → bylines-DfGDnred.mjs.map} +1 -1
- package/dist/{cache-B_HzASVT.mjs → cache-DTTHWD8n.mjs} +1 -1
- package/dist/{cache-B_HzASVT.mjs.map → cache-DTTHWD8n.mjs.map} +1 -1
- package/dist/{challenge-store-DGwuCc4R.mjs → challenge-store-woE0bbCf.mjs} +1 -1
- package/dist/{challenge-store-DGwuCc4R.mjs.map → challenge-store-woE0bbCf.mjs.map} +1 -1
- package/dist/cli/index.mjs +22 -20
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/{comments-CJ0RZsYR.mjs → comments-D2hNuxNa.mjs} +1 -1
- package/dist/{comments-CJ0RZsYR.mjs.map → comments-D2hNuxNa.mjs.map} +1 -1
- package/dist/{components-CTfpu3PZ.mjs → components-DYKp2gmo.mjs} +1 -1
- package/dist/{components-CTfpu3PZ.mjs.map → components-DYKp2gmo.mjs.map} +1 -1
- package/dist/{context-GG52SPgh.mjs → context-Cm4pt1Ws.mjs} +5 -5
- package/dist/{context-GG52SPgh.mjs.map → context-Cm4pt1Ws.mjs.map} +1 -1
- package/dist/{cron-BJ2ClIlj.mjs → cron-DdEVrQ2Y.mjs} +1 -1
- package/dist/{cron-BJ2ClIlj.mjs.map → cron-DdEVrQ2Y.mjs.map} +1 -1
- package/dist/{dashboard-2JgAMWxK.mjs → dashboard-C-UYpps0.mjs} +1 -1
- package/dist/{dashboard-2JgAMWxK.mjs.map → dashboard-C-UYpps0.mjs.map} +1 -1
- package/dist/database/instrumentation.d.mts +10 -1
- package/dist/database/instrumentation.d.mts.map +1 -1
- package/dist/database/instrumentation.mjs +13 -1
- package/dist/database/instrumentation.mjs.map +1 -1
- package/dist/db/index.d.mts +3 -3
- 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-CtzxKBxe.mjs → db-errors-BluWkwGI.mjs} +1 -1
- package/dist/{db-errors-CtzxKBxe.mjs.map → db-errors-BluWkwGI.mjs.map} +1 -1
- package/dist/{default-xLFNSsZ9.mjs → default-NHGuJzQ3.mjs} +1 -1
- package/dist/{default-xLFNSsZ9.mjs.map → default-NHGuJzQ3.mjs.map} +1 -1
- package/dist/{device-flow-s6_q3T7A.mjs → device-flow-BQApWgnW.mjs} +4 -4
- package/dist/{device-flow-s6_q3T7A.mjs.map → device-flow-BQApWgnW.mjs.map} +1 -1
- package/dist/{email-console-DHT2Fbpj.mjs → email-console-BbU3RbWv.mjs} +1 -1
- package/dist/{email-console-DHT2Fbpj.mjs.map → email-console-BbU3RbWv.mjs.map} +1 -1
- package/dist/{error-RwM4dD35.mjs → error-CNn_w7jf.mjs} +1 -1
- package/dist/{error-RwM4dD35.mjs.map → error-CNn_w7jf.mjs.map} +1 -1
- package/dist/{escape-bIyGoW5W.mjs → escape-DPgcxcpL.mjs} +1 -1
- package/dist/{escape-bIyGoW5W.mjs.map → escape-DPgcxcpL.mjs.map} +1 -1
- package/dist/{fts-manager-1RgHmopc.mjs → fts-manager-Cx5z8jdA.mjs} +1 -1
- package/dist/{fts-manager-1RgHmopc.mjs.map → fts-manager-Cx5z8jdA.mjs.map} +1 -1
- package/dist/{hash-9w3pd3-m.mjs → hash-DlvIFn0b.mjs} +1 -1
- package/dist/{hash-9w3pd3-m.mjs.map → hash-DlvIFn0b.mjs.map} +1 -1
- package/dist/{import-Dh8bWmyq.mjs → import-KyxT1Mbs.mjs} +3 -3
- package/dist/{import-Dh8bWmyq.mjs.map → import-KyxT1Mbs.mjs.map} +1 -1
- package/dist/{index-FfiTQJq2.d.mts → index-D2VAiumu.d.mts} +46 -15
- package/dist/{index-FfiTQJq2.d.mts.map → index-D2VAiumu.d.mts.map} +1 -1
- package/dist/{index-BpYeJO1E.d.mts → index-uT2yR66F.d.mts} +3 -3
- package/dist/{index-BpYeJO1E.d.mts.map → index-uT2yR66F.d.mts.map} +1 -1
- package/dist/index.d.mts +16 -16
- package/dist/index.mjs +48 -46
- package/dist/init-lock-DlBHjf9-.mjs +83 -0
- package/dist/init-lock-DlBHjf9-.mjs.map +1 -0
- package/dist/{load-B84ohfBk.mjs → load-Dq91b_DK.mjs} +1 -1
- package/dist/{load-B84ohfBk.mjs.map → load-Dq91b_DK.mjs.map} +1 -1
- package/dist/{loader-CpZKpFz0.mjs → loader-BqWjcH3h.mjs} +12 -15
- package/dist/loader-BqWjcH3h.mjs.map +1 -0
- package/dist/{manifest-schema-Cj-YrzrF.mjs → manifest-schema-DFPeqMAn.mjs} +55 -2
- package/dist/manifest-schema-DFPeqMAn.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +2 -2
- package/dist/media/local-runtime.d.mts +11 -11
- package/dist/media/local-runtime.mjs +4 -3
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-allowlist-CMcoYIjQ.mjs → media-allowlist-_A0SuDn4.mjs} +2 -2
- package/dist/{media-allowlist-CMcoYIjQ.mjs.map → media-allowlist-_A0SuDn4.mjs.map} +1 -1
- package/dist/media-url-CqLd69IO.mjs +26 -0
- package/dist/media-url-CqLd69IO.mjs.map +1 -0
- package/dist/{menus-Dp9xporj.mjs → menus-Ryk9L7fT.mjs} +10 -37
- package/dist/menus-Ryk9L7fT.mjs.map +1 -0
- package/dist/{mime-CCEzze7W.mjs → mime-YbtlEtvS.mjs} +1 -1
- package/dist/{mime-CCEzze7W.mjs.map → mime-YbtlEtvS.mjs.map} +1 -1
- package/dist/{mode-BjlXswIw.mjs → mode-CGXzIbD8.mjs} +1 -1
- package/dist/{mode-BjlXswIw.mjs.map → mode-CGXzIbD8.mjs.map} +1 -1
- package/dist/{normalize-CK5o04zr.mjs → normalize-DKsg36ty.mjs} +1 -1
- package/dist/{normalize-CK5o04zr.mjs.map → normalize-DKsg36ty.mjs.map} +1 -1
- package/dist/{oauth-authorization-1aPAYjiC.mjs → oauth-authorization-C2kVyjXI.mjs} +4 -4
- package/dist/{oauth-authorization-1aPAYjiC.mjs.map → oauth-authorization-C2kVyjXI.mjs.map} +1 -1
- package/dist/{oauth-clients-8mPDStMv.mjs → oauth-clients-BC873NCV.mjs} +1 -1
- package/dist/{oauth-clients-8mPDStMv.mjs.map → oauth-clients-BC873NCV.mjs.map} +1 -1
- package/dist/{oauth-state-store-BJ7YtrfD.mjs → oauth-state-store-Cd--TUaq.mjs} +1 -1
- package/dist/{oauth-state-store-BJ7YtrfD.mjs.map → oauth-state-store-Cd--TUaq.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-BdDSDvjF.mjs → oauth-user-lookup-e4wOvDud.mjs} +1 -1
- package/dist/{oauth-user-lookup-BdDSDvjF.mjs.map → oauth-user-lookup-e4wOvDud.mjs.map} +1 -1
- package/dist/{options-D4MnavW_.d.mts → options-9kLgkE8m.d.mts} +3 -3
- package/dist/{options-D4MnavW_.d.mts.map → options-9kLgkE8m.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-CrGndy1A.mjs → parse-DzSrk1t8.mjs} +2 -2
- package/dist/{parse-CrGndy1A.mjs.map → parse-DzSrk1t8.mjs.map} +1 -1
- package/dist/{passkey-config-BDVM86Tj.mjs → passkey-config-BpjbE_Uv.mjs} +1 -1
- package/dist/{passkey-config-BDVM86Tj.mjs.map → passkey-config-BpjbE_Uv.mjs.map} +1 -1
- package/dist/{placeholder-BZxr8W1j.mjs → placeholder-2xumZh4g.mjs} +1 -1
- package/dist/{placeholder-BZxr8W1j.mjs.map → placeholder-2xumZh4g.mjs.map} +1 -1
- package/dist/{placeholder-B9lUUEmj.d.mts → placeholder-BevVKfay.d.mts} +1 -1
- package/dist/{placeholder-B9lUUEmj.d.mts.map → placeholder-BevVKfay.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +1 -1
- package/dist/plugin-utils.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{preview-BfuRkVKW.mjs → preview-Dqv2hwXr.mjs} +2 -2
- package/dist/{preview-BfuRkVKW.mjs.map → preview-Dqv2hwXr.mjs.map} +1 -1
- package/dist/{public-url-egRHCy1m.mjs → public-url-D_zARuvZ.mjs} +1 -1
- package/dist/{public-url-egRHCy1m.mjs.map → public-url-D_zARuvZ.mjs.map} +1 -1
- package/dist/{query-BFQ029Ts.mjs → query-Crm038Mc.mjs} +21 -11
- package/dist/query-Crm038Mc.mjs.map +1 -0
- package/dist/{rate-limit-ClFFUga6.mjs → rate-limit-hRTBqmw1.mjs} +2 -2
- package/dist/{rate-limit-ClFFUga6.mjs.map → rate-limit-hRTBqmw1.mjs.map} +1 -1
- package/dist/{redirect-Cw3JTlmj.mjs → redirect-C-OOkyku.mjs} +1 -1
- package/dist/{redirect-Cw3JTlmj.mjs.map → redirect-C-OOkyku.mjs.map} +1 -1
- package/dist/{redirects-DEygMrRO.mjs → redirects-6Zg2SoYo.mjs} +11 -10
- package/dist/redirects-6Zg2SoYo.mjs.map +1 -0
- package/dist/{redirects-OIu6vQ2i.mjs → redirects-CP3TnTLO.mjs} +20 -14
- package/dist/redirects-CP3TnTLO.mjs.map +1 -0
- package/dist/{registry-brYh-rAT.mjs → registry-diMzD1Wf.mjs} +3 -3
- package/dist/{registry-brYh-rAT.mjs.map → registry-diMzD1Wf.mjs.map} +1 -1
- package/dist/{request-cache-D32LpnmI.mjs → request-cache-UwmBAiUK.mjs} +1 -1
- package/dist/{request-cache-D32LpnmI.mjs.map → request-cache-UwmBAiUK.mjs.map} +1 -1
- package/dist/request-context.d.mts +7 -0
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs +2 -1
- package/dist/request-context.mjs.map +1 -1
- package/dist/{request-meta-7ByVLxB-.mjs → request-meta-DPechd0W.mjs} +2 -2
- package/dist/{request-meta-7ByVLxB-.mjs.map → request-meta-DPechd0W.mjs.map} +1 -1
- package/dist/{resolve-BqYMVG0D.mjs → resolve-B3NUUtVY.mjs} +1 -1
- package/dist/{resolve-BqYMVG0D.mjs.map → resolve-B3NUUtVY.mjs.map} +1 -1
- package/dist/{runner-BcRuXq_h.d.mts → runner-C8vcbvCe.d.mts} +2 -2
- package/dist/{runner-BcRuXq_h.d.mts.map → runner-C8vcbvCe.d.mts.map} +1 -1
- package/dist/runtime.d.mts +10 -10
- package/dist/runtime.mjs +1 -1
- package/dist/{schema-CS7Eg5gh.mjs → schema-BDOkd3OU.mjs} +4 -4
- package/dist/{schema-CS7Eg5gh.mjs.map → schema-BDOkd3OU.mjs.map} +1 -1
- package/dist/{search-o-aQzHI1.mjs → search-Bs_J_EW-.mjs} +3 -3
- package/dist/{search-o-aQzHI1.mjs.map → search-Bs_J_EW-.mjs.map} +1 -1
- package/dist/{secrets-C_ZtRos3.mjs → secrets-C8xmE6mR.mjs} +21 -11
- package/dist/secrets-C8xmE6mR.mjs.map +1 -0
- package/dist/{sections-DhsZ0ns9.mjs → sections-P0zuBlyz.mjs} +2 -2
- package/dist/{sections-DhsZ0ns9.mjs.map → sections-P0zuBlyz.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +14 -13
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.d.mts.map +1 -1
- package/dist/seo/index.mjs +3 -12
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-DfjLvu8i.mjs → seo-CLhm-Fmb.mjs} +4 -3
- package/dist/seo-CLhm-Fmb.mjs.map +1 -0
- package/dist/{seo-B5e6y9Wk.mjs → seo-DpNgGQjF.mjs} +1 -1
- package/dist/{seo-B5e6y9Wk.mjs.map → seo-DpNgGQjF.mjs.map} +1 -1
- package/dist/{service-DAxg8RPR.mjs → service-CDQQnT8W.mjs} +2 -2
- package/dist/{service-DAxg8RPR.mjs.map → service-CDQQnT8W.mjs.map} +1 -1
- package/dist/{settings-B1p-gPUK.mjs → settings-BjBsmVAo.mjs} +32 -30
- package/dist/settings-BjBsmVAo.mjs.map +1 -0
- package/dist/{settings-DIsbHTRE.mjs → settings-sO0Fif4p.mjs} +2 -2
- package/dist/{settings-DIsbHTRE.mjs.map → settings-sO0Fif4p.mjs.map} +1 -1
- package/dist/{setup-complete-Yuv78yua.mjs → setup-complete-CMMr-oZU.mjs} +1 -1
- package/dist/{setup-complete-Yuv78yua.mjs.map → setup-complete-CMMr-oZU.mjs.map} +1 -1
- package/dist/{setup-nonce-Bm0uKqmf.mjs → setup-nonce-169xl4fV.mjs} +1 -1
- package/dist/{setup-nonce-Bm0uKqmf.mjs.map → setup-nonce-169xl4fV.mjs.map} +1 -1
- package/dist/single-flight-cache-C0UV1Npg.mjs +104 -0
- package/dist/single-flight-cache-C0UV1Npg.mjs.map +1 -0
- package/dist/{site-url-mEVmwIFi.mjs → site-url-vtsuOvSD.mjs} +1 -1
- package/dist/{site-url-mEVmwIFi.mjs.map → site-url-vtsuOvSD.mjs.map} +1 -1
- package/dist/{ssrf-BsVGIE0Z.mjs → ssrf-XO05Voq6.mjs} +1 -1
- package/dist/{ssrf-BsVGIE0Z.mjs.map → ssrf-XO05Voq6.mjs.map} +1 -1
- package/dist/status-2gZklYuj.mjs +30 -0
- package/dist/status-2gZklYuj.mjs.map +1 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +2 -2
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-BEW7S5AI.mjs → taxonomies-BBxYA38v.mjs} +49 -12
- package/dist/taxonomies-BBxYA38v.mjs.map +1 -0
- package/dist/{taxonomies-UusDXv3C.mjs → taxonomies-DuESHWKI.mjs} +2 -2
- package/dist/{taxonomies-UusDXv3C.mjs.map → taxonomies-DuESHWKI.mjs.map} +1 -1
- package/dist/{tokens-Bx2afeT-.mjs → tokens-DMkVjxrx.mjs} +1 -1
- package/dist/{tokens-Bx2afeT-.mjs.map → tokens-DMkVjxrx.mjs.map} +1 -1
- package/dist/{transport--Ck3RBin.mjs → transport-1cIrOb1Y.mjs} +1 -1
- package/dist/{transport--Ck3RBin.mjs.map → transport-1cIrOb1Y.mjs.map} +1 -1
- package/dist/{transport-BwQeeY2p.d.mts → transport-jdvsZEIt.d.mts} +1 -1
- package/dist/{transport-BwQeeY2p.d.mts.map → transport-jdvsZEIt.d.mts.map} +1 -1
- package/dist/{trusted-proxy-B4AfnoAp.mjs → trusted-proxy-CHp41Fjj.mjs} +1 -1
- package/dist/{trusted-proxy-B4AfnoAp.mjs.map → trusted-proxy-CHp41Fjj.mjs.map} +1 -1
- package/dist/{types-DWnN7weG.d.mts → types-BFgYtuKd.d.mts} +1 -1
- package/dist/{types-DWnN7weG.d.mts.map → types-BFgYtuKd.d.mts.map} +1 -1
- package/dist/{types-DZk_y-MU.mjs → types-BIduXPJk.mjs} +1 -1
- package/dist/types-BIduXPJk.mjs.map +1 -0
- package/dist/{types-WVmpZBJV.d.mts → types-BTnnBYVX.d.mts} +2 -2
- package/dist/{types-WVmpZBJV.d.mts.map → types-BTnnBYVX.d.mts.map} +1 -1
- package/dist/types-BoRm8-pp.mjs +3 -0
- package/dist/{types-DbCWhHet.d.mts → types-Bzfk2yC8.d.mts} +2 -2
- package/dist/types-Bzfk2yC8.d.mts.map +1 -0
- package/dist/{types-Qa7-HJJC.d.mts → types-CkEuk-Zr.d.mts} +1 -1
- package/dist/{types-Qa7-HJJC.d.mts.map → types-CkEuk-Zr.d.mts.map} +1 -1
- package/dist/{types-DMwSpvcw.d.mts → types-DO7whVYU.d.mts} +9 -3
- package/dist/{types-DMwSpvcw.d.mts.map → types-DO7whVYU.d.mts.map} +1 -1
- package/dist/{types-DX6v9KzJ.d.mts → types-DdkL6fyv.d.mts} +1 -1
- package/dist/{types-DX6v9KzJ.d.mts.map → types-DdkL6fyv.d.mts.map} +1 -1
- package/dist/{types-DpFmlNyB.mjs → types-DejCHqWT.mjs} +1 -1
- package/dist/{types-DpFmlNyB.mjs.map → types-DejCHqWT.mjs.map} +1 -1
- package/dist/{types-OT_Es5mp.d.mts → types-Del0VMij.d.mts} +1 -1
- package/dist/{types-OT_Es5mp.d.mts.map → types-Del0VMij.d.mts.map} +1 -1
- package/dist/{types-kwqCOUxj.d.mts → types-u_XxjbS8.d.mts} +1 -1
- package/dist/{types-kwqCOUxj.d.mts.map → types-u_XxjbS8.d.mts.map} +1 -1
- package/dist/{utils-C4Ih4DML.mjs → utils-C4M981Br.mjs} +1 -1
- package/dist/{utils-C4Ih4DML.mjs.map → utils-C4M981Br.mjs.map} +1 -1
- package/dist/{validate-ZP9Dvg0P.mjs → validate-DGhQPXzI.mjs} +2 -2
- package/dist/{validate-ZP9Dvg0P.mjs.map → validate-DGhQPXzI.mjs.map} +1 -1
- package/dist/{validate-BPAHUSge.d.mts → validate-cJOiOvT2.d.mts} +5 -5
- package/dist/{validate-BPAHUSge.d.mts.map → validate-cJOiOvT2.d.mts.map} +1 -1
- package/dist/{validation-CE5i4q0c.mjs → validation-DVHjPM1M.mjs} +5 -5
- package/dist/{validation-CE5i4q0c.mjs.map → validation-DVHjPM1M.mjs.map} +1 -1
- package/dist/version-BOjj_cfz.mjs +7 -0
- package/dist/{version-Dw0JXu45.mjs.map → version-BOjj_cfz.mjs.map} +1 -1
- package/dist/{widgets-ClEnYQCH.mjs → widgets-Ci6hLwfO.mjs} +47 -44
- package/dist/widgets-Ci6hLwfO.mjs.map +1 -0
- package/dist/{zod-generator-Djo_VHCt.mjs → zod-generator-CarzgPAu.mjs} +2 -2
- package/dist/{zod-generator-Djo_VHCt.mjs.map → zod-generator-CarzgPAu.mjs.map} +1 -1
- package/package.json +10 -10
- package/src/api/handlers/marketplace.ts +2 -5
- package/src/api/handlers/redirects.ts +24 -13
- package/src/api/handlers/registry.ts +70 -0
- package/src/api/handlers/seo.ts +9 -1
- package/src/api/schemas/redirects.ts +11 -4
- package/src/api/schemas/schema.ts +13 -1
- package/src/astro/integration/index.ts +44 -8
- package/src/astro/integration/routes.ts +46 -9
- package/src/astro/middleware/redirect.ts +12 -0
- package/src/astro/middleware.ts +20 -6
- package/src/astro/routes/api/schema/index.ts +7 -15
- package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
- package/src/bylines/field-defs-cache.ts +70 -20
- package/src/cli/commands/bundle-utils.ts +2 -0
- package/src/cli/commands/doctor.ts +1 -1
- package/src/cli/commands/secrets.ts +2 -2
- package/src/config/secrets.ts +28 -14
- package/src/database/instrumentation.ts +13 -0
- package/src/emdash-runtime.ts +31 -25
- package/src/loader.ts +24 -15
- package/src/plugins/manifest-schema.ts +75 -0
- package/src/plugins/marketplace.ts +2 -5
- package/src/plugins/types.ts +12 -0
- package/src/query.ts +13 -2
- package/src/redirects/status.ts +27 -0
- package/src/request-context.ts +8 -0
- package/src/schema/types.ts +11 -1
- package/src/seo/index.ts +2 -28
- package/src/seo/media-url.ts +32 -0
- package/src/settings/index.ts +32 -40
- package/src/taxonomies/index.ts +78 -12
- package/src/utils/single-flight-cache.ts +194 -0
- package/src/widgets/index.ts +57 -54
- package/dist/api-BZ6bhjYs.mjs.map +0 -1
- package/dist/byline-DUx48sJp.mjs.map +0 -1
- package/dist/byline-fields-DYXKDuNX.d.mts.map +0 -1
- package/dist/loader-CpZKpFz0.mjs.map +0 -1
- package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
- package/dist/menus-Dp9xporj.mjs.map +0 -1
- package/dist/query-BFQ029Ts.mjs.map +0 -1
- package/dist/redirects-DEygMrRO.mjs.map +0 -1
- package/dist/redirects-OIu6vQ2i.mjs.map +0 -1
- package/dist/secrets-C_ZtRos3.mjs.map +0 -1
- package/dist/seo-DfjLvu8i.mjs.map +0 -1
- package/dist/settings-B1p-gPUK.mjs.map +0 -1
- package/dist/taxonomies-BEW7S5AI.mjs.map +0 -1
- package/dist/types-Cj2S6FuC.mjs +0 -3
- package/dist/types-DZk_y-MU.mjs.map +0 -1
- package/dist/types-DbCWhHet.d.mts.map +0 -1
- package/dist/version-Dw0JXu45.mjs +0 -7
- package/dist/widgets-ClEnYQCH.mjs.map +0 -1
- /package/dist/{api-tokens-B6VgoE6M.mjs → api-tokens-C7ywRx7l.mjs} +0 -0
- /package/dist/{ssrf-BvgVcfNQ.mjs → ssrf-CRZGzjdL.mjs} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod-generator-Djo_VHCt.mjs","names":[],"sources":["../src/schema/zod-generator.ts"],"sourcesContent":["import { z, type ZodTypeAny } from \"zod\";\n\nimport { hashString } from \"../utils/hash.js\";\nimport type { Field, FieldType, CollectionWithFields } from \"./types.js\";\n\n/** Pattern to split on underscores, hyphens, and spaces for PascalCase conversion */\nconst PASCAL_CASE_SPLIT_PATTERN = /[_\\-\\s]+/;\n\n/**\n * Generate a Zod schema from a collection's field definitions\n *\n * This allows runtime validation of content based on dynamically\n * defined schemas stored in D1.\n */\nexport function generateZodSchema(\n\tcollection: CollectionWithFields,\n): z.ZodObject<Record<string, ZodTypeAny>> {\n\tconst shape: Record<string, ZodTypeAny> = {};\n\n\tfor (const field of collection.fields) {\n\t\tshape[field.slug] = generateFieldSchema(field);\n\t}\n\n\treturn z.object(shape);\n}\n\n/**\n * Generate Zod schema for a single field\n */\nexport function generateFieldSchema(field: Field): ZodTypeAny {\n\tlet schema = getBaseSchema(field.type, field);\n\n\t// Apply validation rules\n\tif (field.validation) {\n\t\tschema = applyValidation(schema, field);\n\t}\n\n\t// Apply required/optional. Non-required fields use `.nullish()` rather\n\t// than `.optional()` because the underlying SQLite columns are nullable\n\t// (see `SchemaRegistry.addFieldColumn` -- non-required fields are added\n\t// without `NOT NULL`). The admin re-sends what it loaded from the\n\t// server on autosave, so any field that's actually `null` in the DB\n\t// must round-trip cleanly through the validator. `.optional()` only\n\t// accepts `undefined`; `.nullish()` accepts both `undefined` and\n\t// `null`. (#867 — autosave failures on seeded entries.)\n\tif (!field.required) {\n\t\tschema = schema.nullish();\n\t}\n\n\t// Apply default value\n\tif (field.defaultValue !== undefined) {\n\t\tschema = schema.default(field.defaultValue);\n\t}\n\n\treturn schema;\n}\n\n/**\n * Get base Zod schema for a field type\n */\nfunction getBaseSchema(type: FieldType, field: Field): ZodTypeAny {\n\tswitch (type) {\n\t\tcase \"url\":\n\t\t\treturn z.string().url();\n\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\t\treturn z.string();\n\n\t\tcase \"number\":\n\t\t\treturn z.number();\n\n\t\tcase \"integer\":\n\t\t\treturn z.number().int();\n\n\t\tcase \"boolean\":\n\t\t\t// Boolean fields map to `INTEGER` columns (`FIELD_TYPE_TO_COLUMN`\n\t\t\t// in `schema/types.ts`) and `serializeValue` in\n\t\t\t// `database/repositories/content.ts` writes booleans as 0/1.\n\t\t\t// `deserializeValue` never converts them back, so reads return\n\t\t\t// numbers. Coerce the stored 0/1 shape here so a GET → POST\n\t\t\t// round-trip on a boolean field passes validation. Other inputs\n\t\t\t// (strings, other numbers) fall through to `z.boolean()` and\n\t\t\t// produce its standard rejection.\n\t\t\treturn z.preprocess((v) => (v === 0 || v === 1 ? Boolean(v) : v), z.boolean());\n\n\t\tcase \"datetime\":\n\t\t\treturn z.string().datetime().or(z.string().date());\n\n\t\tcase \"select\": {\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\tconst [first, ...rest] = options;\n\t\t\t\treturn z.enum([first, ...rest]);\n\t\t\t}\n\t\t\treturn z.string();\n\t\t}\n\n\t\tcase \"multiSelect\": {\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\tconst [first, ...rest] = multiOptions;\n\t\t\t\treturn z.array(z.enum([first, ...rest]));\n\t\t\t}\n\t\t\treturn z.array(z.string());\n\t\t}\n\n\t\tcase \"portableText\":\n\t\t\t// Portable Text is an array of blocks. We require `_type` because\n\t\t\t// renderers dispatch on it, but `_key` is intentionally optional:\n\t\t\t// it's a UI-layer concern that the editor regenerates on every\n\t\t\t// change (see `PortableTextEditor`), and the rest of this schema\n\t\t\t// uses `.passthrough()` for everything below the top level. Making\n\t\t\t// `_key` strictly required here was an accidentally tight invariant\n\t\t\t// that rejected any seed/import data not authored against the\n\t\t\t// editor (#867 — autosave failures on seeded template content).\n\t\t\treturn z.array(\n\t\t\t\tz\n\t\t\t\t\t.object({\n\t\t\t\t\t\t_type: z.string(),\n\t\t\t\t\t\t_key: z.string().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.passthrough(),\n\t\t\t);\n\n\t\tcase \"image\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\talt: z.string().optional(),\n\t\t\t\twidth: z.number().optional(),\n\t\t\t\theight: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"cloudflare-images\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Admin-side preview URL for external providers (not persisted by plugins) */\n\t\t\t\tpreviewUrl: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"file\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\tfilename: z.string().optional(),\n\t\t\t\tmimeType: z.string().optional(),\n\t\t\t\tsize: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"s3\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"reference\":\n\t\t\treturn z.string(); // Reference ID\n\n\t\tcase \"json\":\n\t\t\treturn z.unknown();\n\n\t\tdefault:\n\t\t\treturn z.unknown();\n\t}\n}\n\n/**\n * Apply validation rules to a schema\n */\nfunction applyValidation(schema: ZodTypeAny, field: Field): ZodTypeAny {\n\tconst validation = field.validation;\n\tif (!validation) return schema;\n\n\t// String validations\n\tif (schema instanceof z.ZodString) {\n\t\tlet strSchema = schema;\n\t\tif (validation.minLength !== undefined) {\n\t\t\tstrSchema = strSchema.min(validation.minLength);\n\t\t}\n\t\tif (validation.maxLength !== undefined) {\n\t\t\tstrSchema = strSchema.max(validation.maxLength);\n\t\t}\n\t\tif (validation.pattern) {\n\t\t\tstrSchema = strSchema.regex(new RegExp(validation.pattern));\n\t\t}\n\t\treturn strSchema;\n\t}\n\n\t// Number validations\n\tif (schema instanceof z.ZodNumber) {\n\t\tlet numSchema = schema;\n\t\tif (validation.min !== undefined) {\n\t\t\tnumSchema = numSchema.min(validation.min);\n\t\t}\n\t\tif (validation.max !== undefined) {\n\t\t\tnumSchema = numSchema.max(validation.max);\n\t\t}\n\t\treturn numSchema;\n\t}\n\n\treturn schema;\n}\n\n/**\n * Schema cache to avoid regenerating schemas on every request\n */\nconst schemaCache = new Map<string, { schema: z.ZodObject<any>; version: string }>();\n\n/**\n * Get or generate a cached schema for a collection\n */\nexport function getCachedSchema(\n\tcollection: CollectionWithFields,\n\tversion?: string,\n): z.ZodObject<any> {\n\tconst cacheKey = collection.slug;\n\tconst cached = schemaCache.get(cacheKey);\n\n\t// If version matches, return cached schema\n\tif (cached && (!version || cached.version === version)) {\n\t\treturn cached.schema;\n\t}\n\n\t// Generate new schema\n\tconst schema = generateZodSchema(collection);\n\n\t// Cache it\n\tschemaCache.set(cacheKey, {\n\t\tschema,\n\t\tversion: version || collection.updatedAt,\n\t});\n\n\treturn schema;\n}\n\n/**\n * Invalidate cached schema for a collection\n */\nexport function invalidateSchemaCache(slug: string): void {\n\tschemaCache.delete(slug);\n}\n\n/**\n * Clear all cached schemas\n */\nexport function clearSchemaCache(): void {\n\tschemaCache.clear();\n}\n\n/**\n * Validate data against a collection's schema\n */\nexport function validateContent(\n\tcollection: CollectionWithFields,\n\tdata: unknown,\n): { success: true; data: unknown } | { success: false; errors: z.ZodError } {\n\tconst schema = getCachedSchema(collection);\n\n\tconst result = schema.safeParse(data);\n\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\n\treturn { success: false, errors: result.error };\n}\n\n/**\n * Generate TypeScript interface from field definitions\n * Used by CLI `emdash types` to generate types\n */\nexport function generateTypeScript(collection: CollectionWithFields): string {\n\tconst interfaceName = getInterfaceName(collection);\n\tconst lines: string[] = [];\n\n\tlines.push(`export interface ${interfaceName} {`);\n\tlines.push(` id: string;`);\n\tlines.push(` slug: string | null;`);\n\tlines.push(` status: string;`);\n\n\tfor (const field of collection.fields) {\n\t\tconst tsType = fieldTypeToTypeScript(field);\n\t\tconst optional = field.required ? \"\" : \"?\";\n\t\tlines.push(` ${field.slug}${optional}: ${tsType};`);\n\t}\n\n\tlines.push(` createdAt: Date;`);\n\tlines.push(` updatedAt: Date;`);\n\tlines.push(` publishedAt: Date | null;`);\n\t// Bylines are eagerly loaded by getEmDashCollection/getEmDashEntry\n\tlines.push(` bylines?: ContentBylineCredit[];`);\n\t// Taxonomy terms are eagerly loaded by getEmDashCollection/getEmDashEntry,\n\t// keyed by taxonomy name (e.g. data.terms?.tag)\n\tlines.push(` terms?: Record<string, TaxonomyTerm[]>;`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate a complete types file with module augmentation\n * This produces emdash-env.d.ts content that provides typed query functions\n */\nexport function generateTypesFile(collections: CollectionWithFields[]): string {\n\tconst lines: string[] = [];\n\n\t// Header\n\tlines.push(`// Generated by EmDash on dev server start`);\n\tlines.push(`// Do not edit manually`);\n\tlines.push(``);\n\tlines.push(`/// <reference types=\"emdash/locals\" />`);\n\tlines.push(``);\n\n\t// Check if we need PortableTextBlock import\n\tconst needsPortableText = collections.some((c) =>\n\t\tc.fields.some((f) => f.type === \"portableText\"),\n\t);\n\n\t// Build imports - ContentBylineCredit and TaxonomyTerm are always needed\n\t// for the hydrated bylines/terms fields\n\tconst imports = [\"ContentBylineCredit\", \"TaxonomyTerm\"];\n\tif (needsPortableText) {\n\t\timports.push(\"PortableTextBlock\");\n\t}\n\tlines.push(`import type { ${imports.join(\", \")} } from \"emdash\";`);\n\tlines.push(``);\n\n\t// Generate individual interfaces\n\tfor (const collection of collections) {\n\t\tlines.push(generateTypeScript(collection));\n\t\tlines.push(``);\n\t}\n\n\t// Generate the Collections interface for module augmentation\n\tlines.push(`declare module \"emdash\" {`);\n\tlines.push(` interface EmDashCollections {`);\n\tfor (const collection of collections) {\n\t\tconst interfaceName = getInterfaceName(collection);\n\t\tlines.push(` ${collection.slug}: ${interfaceName};`);\n\t}\n\tlines.push(` }`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate schema hash for cache invalidation\n */\nexport async function generateSchemaHash(collections: CollectionWithFields[]): Promise<string> {\n\tconst str = JSON.stringify(\n\t\tcollections.map((c) => ({\n\t\t\tslug: c.slug,\n\t\t\tfields: c.fields.map((f) => ({\n\t\t\t\tslug: f.slug,\n\t\t\t\ttype: f.type,\n\t\t\t\trequired: f.required,\n\t\t\t\tvalidation: f.validation,\n\t\t\t})),\n\t\t})),\n\t);\n\treturn hashString(str);\n}\n\n/**\n * Map field type to TypeScript type\n */\nfunction fieldTypeToTypeScript(field: Field): string {\n\tswitch (field.type) {\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\tcase \"url\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\n\t\tcase \"number\":\n\t\tcase \"integer\":\n\t\t\treturn \"number\";\n\n\t\tcase \"boolean\":\n\t\t\treturn \"boolean\";\n\n\t\tcase \"select\":\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\treturn options.map((o) => `\"${o}\"`).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\n\t\tcase \"multiSelect\":\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\treturn `(${multiOptions.map((o) => `\"${o}\"`).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\n\t\tcase \"portableText\":\n\t\t\treturn \"PortableTextBlock[]\";\n\n\t\tcase \"image\":\n\t\t\treturn \"{ id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"file\":\n\t\t\treturn \"{ id: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"reference\":\n\t\t\t// Could be enhanced to include the referenced collection type\n\t\t\treturn \"string\";\n\n\t\tcase \"json\":\n\t\t\treturn \"unknown\";\n\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Convert string to PascalCase (handles slugs, spaces, etc.)\n */\nfunction pascalCase(str: string): string {\n\treturn str\n\t\t.split(PASCAL_CASE_SPLIT_PATTERN)\n\t\t.filter(Boolean)\n\t\t.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(\"\");\n}\n\n/**\n * Simple singularization - handles common cases\n */\nfunction singularize(str: string): string {\n\tif (str.endsWith(\"ies\")) {\n\t\treturn str.slice(0, -3) + \"y\";\n\t}\n\tif (\n\t\tstr.endsWith(\"es\") &&\n\t\t(str.endsWith(\"sses\") || str.endsWith(\"xes\") || str.endsWith(\"ches\") || str.endsWith(\"shes\"))\n\t) {\n\t\treturn str.slice(0, -2);\n\t}\n\tif (str.endsWith(\"s\") && !str.endsWith(\"ss\")) {\n\t\treturn str.slice(0, -1);\n\t}\n\treturn str;\n}\n\n/**\n * Get the interface name for a collection\n */\nfunction getInterfaceName(collection: CollectionWithFields): string {\n\treturn pascalCase(collection.labelSingular || singularize(collection.slug));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"zod-generator-CarzgPAu.mjs","names":[],"sources":["../src/schema/zod-generator.ts"],"sourcesContent":["import { z, type ZodTypeAny } from \"zod\";\n\nimport { hashString } from \"../utils/hash.js\";\nimport type { Field, FieldType, CollectionWithFields } from \"./types.js\";\n\n/** Pattern to split on underscores, hyphens, and spaces for PascalCase conversion */\nconst PASCAL_CASE_SPLIT_PATTERN = /[_\\-\\s]+/;\n\n/**\n * Generate a Zod schema from a collection's field definitions\n *\n * This allows runtime validation of content based on dynamically\n * defined schemas stored in D1.\n */\nexport function generateZodSchema(\n\tcollection: CollectionWithFields,\n): z.ZodObject<Record<string, ZodTypeAny>> {\n\tconst shape: Record<string, ZodTypeAny> = {};\n\n\tfor (const field of collection.fields) {\n\t\tshape[field.slug] = generateFieldSchema(field);\n\t}\n\n\treturn z.object(shape);\n}\n\n/**\n * Generate Zod schema for a single field\n */\nexport function generateFieldSchema(field: Field): ZodTypeAny {\n\tlet schema = getBaseSchema(field.type, field);\n\n\t// Apply validation rules\n\tif (field.validation) {\n\t\tschema = applyValidation(schema, field);\n\t}\n\n\t// Apply required/optional. Non-required fields use `.nullish()` rather\n\t// than `.optional()` because the underlying SQLite columns are nullable\n\t// (see `SchemaRegistry.addFieldColumn` -- non-required fields are added\n\t// without `NOT NULL`). The admin re-sends what it loaded from the\n\t// server on autosave, so any field that's actually `null` in the DB\n\t// must round-trip cleanly through the validator. `.optional()` only\n\t// accepts `undefined`; `.nullish()` accepts both `undefined` and\n\t// `null`. (#867 — autosave failures on seeded entries.)\n\tif (!field.required) {\n\t\tschema = schema.nullish();\n\t}\n\n\t// Apply default value\n\tif (field.defaultValue !== undefined) {\n\t\tschema = schema.default(field.defaultValue);\n\t}\n\n\treturn schema;\n}\n\n/**\n * Get base Zod schema for a field type\n */\nfunction getBaseSchema(type: FieldType, field: Field): ZodTypeAny {\n\tswitch (type) {\n\t\tcase \"url\":\n\t\t\treturn z.string().url();\n\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\t\treturn z.string();\n\n\t\tcase \"number\":\n\t\t\treturn z.number();\n\n\t\tcase \"integer\":\n\t\t\treturn z.number().int();\n\n\t\tcase \"boolean\":\n\t\t\t// Boolean fields map to `INTEGER` columns (`FIELD_TYPE_TO_COLUMN`\n\t\t\t// in `schema/types.ts`) and `serializeValue` in\n\t\t\t// `database/repositories/content.ts` writes booleans as 0/1.\n\t\t\t// `deserializeValue` never converts them back, so reads return\n\t\t\t// numbers. Coerce the stored 0/1 shape here so a GET → POST\n\t\t\t// round-trip on a boolean field passes validation. Other inputs\n\t\t\t// (strings, other numbers) fall through to `z.boolean()` and\n\t\t\t// produce its standard rejection.\n\t\t\treturn z.preprocess((v) => (v === 0 || v === 1 ? Boolean(v) : v), z.boolean());\n\n\t\tcase \"datetime\":\n\t\t\treturn z.string().datetime().or(z.string().date());\n\n\t\tcase \"select\": {\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\tconst [first, ...rest] = options;\n\t\t\t\treturn z.enum([first, ...rest]);\n\t\t\t}\n\t\t\treturn z.string();\n\t\t}\n\n\t\tcase \"multiSelect\": {\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\tconst [first, ...rest] = multiOptions;\n\t\t\t\treturn z.array(z.enum([first, ...rest]));\n\t\t\t}\n\t\t\treturn z.array(z.string());\n\t\t}\n\n\t\tcase \"portableText\":\n\t\t\t// Portable Text is an array of blocks. We require `_type` because\n\t\t\t// renderers dispatch on it, but `_key` is intentionally optional:\n\t\t\t// it's a UI-layer concern that the editor regenerates on every\n\t\t\t// change (see `PortableTextEditor`), and the rest of this schema\n\t\t\t// uses `.passthrough()` for everything below the top level. Making\n\t\t\t// `_key` strictly required here was an accidentally tight invariant\n\t\t\t// that rejected any seed/import data not authored against the\n\t\t\t// editor (#867 — autosave failures on seeded template content).\n\t\t\treturn z.array(\n\t\t\t\tz\n\t\t\t\t\t.object({\n\t\t\t\t\t\t_type: z.string(),\n\t\t\t\t\t\t_key: z.string().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.passthrough(),\n\t\t\t);\n\n\t\tcase \"image\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\talt: z.string().optional(),\n\t\t\t\twidth: z.number().optional(),\n\t\t\t\theight: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"cloudflare-images\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Admin-side preview URL for external providers (not persisted by plugins) */\n\t\t\t\tpreviewUrl: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"file\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\tfilename: z.string().optional(),\n\t\t\t\tmimeType: z.string().optional(),\n\t\t\t\tsize: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"s3\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"reference\":\n\t\t\treturn z.string(); // Reference ID\n\n\t\tcase \"json\":\n\t\t\treturn z.unknown();\n\n\t\tdefault:\n\t\t\treturn z.unknown();\n\t}\n}\n\n/**\n * Apply validation rules to a schema\n */\nfunction applyValidation(schema: ZodTypeAny, field: Field): ZodTypeAny {\n\tconst validation = field.validation;\n\tif (!validation) return schema;\n\n\t// String validations\n\tif (schema instanceof z.ZodString) {\n\t\tlet strSchema = schema;\n\t\tif (validation.minLength !== undefined) {\n\t\t\tstrSchema = strSchema.min(validation.minLength);\n\t\t}\n\t\tif (validation.maxLength !== undefined) {\n\t\t\tstrSchema = strSchema.max(validation.maxLength);\n\t\t}\n\t\tif (validation.pattern) {\n\t\t\tstrSchema = strSchema.regex(new RegExp(validation.pattern));\n\t\t}\n\t\treturn strSchema;\n\t}\n\n\t// Number validations\n\tif (schema instanceof z.ZodNumber) {\n\t\tlet numSchema = schema;\n\t\tif (validation.min !== undefined) {\n\t\t\tnumSchema = numSchema.min(validation.min);\n\t\t}\n\t\tif (validation.max !== undefined) {\n\t\t\tnumSchema = numSchema.max(validation.max);\n\t\t}\n\t\treturn numSchema;\n\t}\n\n\treturn schema;\n}\n\n/**\n * Schema cache to avoid regenerating schemas on every request\n */\nconst schemaCache = new Map<string, { schema: z.ZodObject<any>; version: string }>();\n\n/**\n * Get or generate a cached schema for a collection\n */\nexport function getCachedSchema(\n\tcollection: CollectionWithFields,\n\tversion?: string,\n): z.ZodObject<any> {\n\tconst cacheKey = collection.slug;\n\tconst cached = schemaCache.get(cacheKey);\n\n\t// If version matches, return cached schema\n\tif (cached && (!version || cached.version === version)) {\n\t\treturn cached.schema;\n\t}\n\n\t// Generate new schema\n\tconst schema = generateZodSchema(collection);\n\n\t// Cache it\n\tschemaCache.set(cacheKey, {\n\t\tschema,\n\t\tversion: version || collection.updatedAt,\n\t});\n\n\treturn schema;\n}\n\n/**\n * Invalidate cached schema for a collection\n */\nexport function invalidateSchemaCache(slug: string): void {\n\tschemaCache.delete(slug);\n}\n\n/**\n * Clear all cached schemas\n */\nexport function clearSchemaCache(): void {\n\tschemaCache.clear();\n}\n\n/**\n * Validate data against a collection's schema\n */\nexport function validateContent(\n\tcollection: CollectionWithFields,\n\tdata: unknown,\n): { success: true; data: unknown } | { success: false; errors: z.ZodError } {\n\tconst schema = getCachedSchema(collection);\n\n\tconst result = schema.safeParse(data);\n\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\n\treturn { success: false, errors: result.error };\n}\n\n/**\n * Generate TypeScript interface from field definitions\n * Used by CLI `emdash types` to generate types\n */\nexport function generateTypeScript(collection: CollectionWithFields): string {\n\tconst interfaceName = getInterfaceName(collection);\n\tconst lines: string[] = [];\n\n\tlines.push(`export interface ${interfaceName} {`);\n\tlines.push(` id: string;`);\n\tlines.push(` slug: string | null;`);\n\tlines.push(` status: string;`);\n\n\tfor (const field of collection.fields) {\n\t\tconst tsType = fieldTypeToTypeScript(field);\n\t\tconst optional = field.required ? \"\" : \"?\";\n\t\tlines.push(` ${field.slug}${optional}: ${tsType};`);\n\t}\n\n\tlines.push(` createdAt: Date;`);\n\tlines.push(` updatedAt: Date;`);\n\tlines.push(` publishedAt: Date | null;`);\n\t// Bylines are eagerly loaded by getEmDashCollection/getEmDashEntry\n\tlines.push(` bylines?: ContentBylineCredit[];`);\n\t// Taxonomy terms are eagerly loaded by getEmDashCollection/getEmDashEntry,\n\t// keyed by taxonomy name (e.g. data.terms?.tag)\n\tlines.push(` terms?: Record<string, TaxonomyTerm[]>;`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate a complete types file with module augmentation\n * This produces emdash-env.d.ts content that provides typed query functions\n */\nexport function generateTypesFile(collections: CollectionWithFields[]): string {\n\tconst lines: string[] = [];\n\n\t// Header\n\tlines.push(`// Generated by EmDash on dev server start`);\n\tlines.push(`// Do not edit manually`);\n\tlines.push(``);\n\tlines.push(`/// <reference types=\"emdash/locals\" />`);\n\tlines.push(``);\n\n\t// Check if we need PortableTextBlock import\n\tconst needsPortableText = collections.some((c) =>\n\t\tc.fields.some((f) => f.type === \"portableText\"),\n\t);\n\n\t// Build imports - ContentBylineCredit and TaxonomyTerm are always needed\n\t// for the hydrated bylines/terms fields\n\tconst imports = [\"ContentBylineCredit\", \"TaxonomyTerm\"];\n\tif (needsPortableText) {\n\t\timports.push(\"PortableTextBlock\");\n\t}\n\tlines.push(`import type { ${imports.join(\", \")} } from \"emdash\";`);\n\tlines.push(``);\n\n\t// Generate individual interfaces\n\tfor (const collection of collections) {\n\t\tlines.push(generateTypeScript(collection));\n\t\tlines.push(``);\n\t}\n\n\t// Generate the Collections interface for module augmentation\n\tlines.push(`declare module \"emdash\" {`);\n\tlines.push(` interface EmDashCollections {`);\n\tfor (const collection of collections) {\n\t\tconst interfaceName = getInterfaceName(collection);\n\t\tlines.push(` ${collection.slug}: ${interfaceName};`);\n\t}\n\tlines.push(` }`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate schema hash for cache invalidation\n */\nexport async function generateSchemaHash(collections: CollectionWithFields[]): Promise<string> {\n\tconst str = JSON.stringify(\n\t\tcollections.map((c) => ({\n\t\t\tslug: c.slug,\n\t\t\tfields: c.fields.map((f) => ({\n\t\t\t\tslug: f.slug,\n\t\t\t\ttype: f.type,\n\t\t\t\trequired: f.required,\n\t\t\t\tvalidation: f.validation,\n\t\t\t})),\n\t\t})),\n\t);\n\treturn hashString(str);\n}\n\n/**\n * Map field type to TypeScript type\n */\nfunction fieldTypeToTypeScript(field: Field): string {\n\tswitch (field.type) {\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\tcase \"url\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\n\t\tcase \"number\":\n\t\tcase \"integer\":\n\t\t\treturn \"number\";\n\n\t\tcase \"boolean\":\n\t\t\treturn \"boolean\";\n\n\t\tcase \"select\":\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\treturn options.map((o) => `\"${o}\"`).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\n\t\tcase \"multiSelect\":\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\treturn `(${multiOptions.map((o) => `\"${o}\"`).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\n\t\tcase \"portableText\":\n\t\t\treturn \"PortableTextBlock[]\";\n\n\t\tcase \"image\":\n\t\t\treturn \"{ id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"file\":\n\t\t\treturn \"{ id: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"reference\":\n\t\t\t// Could be enhanced to include the referenced collection type\n\t\t\treturn \"string\";\n\n\t\tcase \"json\":\n\t\t\treturn \"unknown\";\n\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Convert string to PascalCase (handles slugs, spaces, etc.)\n */\nfunction pascalCase(str: string): string {\n\treturn str\n\t\t.split(PASCAL_CASE_SPLIT_PATTERN)\n\t\t.filter(Boolean)\n\t\t.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(\"\");\n}\n\n/**\n * Simple singularization - handles common cases\n */\nfunction singularize(str: string): string {\n\tif (str.endsWith(\"ies\")) {\n\t\treturn str.slice(0, -3) + \"y\";\n\t}\n\tif (\n\t\tstr.endsWith(\"es\") &&\n\t\t(str.endsWith(\"sses\") || str.endsWith(\"xes\") || str.endsWith(\"ches\") || str.endsWith(\"shes\"))\n\t) {\n\t\treturn str.slice(0, -2);\n\t}\n\tif (str.endsWith(\"s\") && !str.endsWith(\"ss\")) {\n\t\treturn str.slice(0, -1);\n\t}\n\treturn str;\n}\n\n/**\n * Get the interface name for a collection\n */\nfunction getInterfaceName(collection: CollectionWithFields): string {\n\treturn pascalCase(collection.labelSingular || singularize(collection.slug));\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emdash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Astro-native CMS with WordPress migration support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
"#types": "./src/astro/types.js"
|
|
179
179
|
},
|
|
180
180
|
"dependencies": {
|
|
181
|
-
"@atcute/lexicons": "^
|
|
181
|
+
"@atcute/lexicons": "^2.0.0",
|
|
182
182
|
"@atcute/multibase": "^1.2.0",
|
|
183
183
|
"@floating-ui/react": "^0.27.16",
|
|
184
184
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
@@ -217,12 +217,12 @@
|
|
|
217
217
|
"ulidx": "^2.4.1",
|
|
218
218
|
"upng-js": "^2.1.0",
|
|
219
219
|
"zod": "^4.4.1",
|
|
220
|
-
"@atcute/client": "^
|
|
221
|
-
"@emdash-cms/admin": "0.
|
|
222
|
-
"@emdash-cms/
|
|
223
|
-
"@emdash-cms/
|
|
224
|
-
"@emdash-cms/
|
|
225
|
-
"@emdash-cms/
|
|
220
|
+
"@atcute/client": "^5.0.0",
|
|
221
|
+
"@emdash-cms/admin": "0.21.0",
|
|
222
|
+
"@emdash-cms/gutenberg-to-portable-text": "0.21.0",
|
|
223
|
+
"@emdash-cms/auth": "0.21.0",
|
|
224
|
+
"@emdash-cms/plugin-types": "0.1.0",
|
|
225
|
+
"@emdash-cms/registry-client": "0.3.2"
|
|
226
226
|
},
|
|
227
227
|
"optionalDependencies": {
|
|
228
228
|
"@libsql/kysely-libsql": "^0.4.0",
|
|
@@ -248,14 +248,14 @@
|
|
|
248
248
|
"@types/react": "19.2.14",
|
|
249
249
|
"@types/sanitize-html": "^2.16.0",
|
|
250
250
|
"@types/sax": "^1.2.7",
|
|
251
|
-
"@vitest/ui": "^4.1.
|
|
251
|
+
"@vitest/ui": "^4.1.8",
|
|
252
252
|
"publint": "0.3.17",
|
|
253
253
|
"tsdown": "0.20.3",
|
|
254
254
|
"typescript": "^6.0.3",
|
|
255
255
|
"vite": "^6.0.0",
|
|
256
256
|
"vitest": "^4.1.5",
|
|
257
257
|
"zod-openapi": "^5.4.6",
|
|
258
|
-
"@emdash-cms/blocks": "0.
|
|
258
|
+
"@emdash-cms/blocks": "0.21.0"
|
|
259
259
|
},
|
|
260
260
|
"repository": {
|
|
261
261
|
"type": "git",
|
|
@@ -9,7 +9,7 @@ import type { Kysely } from "kysely";
|
|
|
9
9
|
|
|
10
10
|
import type { Database } from "../../database/types.js";
|
|
11
11
|
import { validatePluginIdentifier } from "../../database/validate.js";
|
|
12
|
-
import { pluginManifestSchema } from "../../plugins/manifest-schema.js";
|
|
12
|
+
import { pluginManifestSchema, reconcileManifestAccess } from "../../plugins/manifest-schema.js";
|
|
13
13
|
import { normalizeManifestRoute } from "../../plugins/manifest-schema.js";
|
|
14
14
|
import {
|
|
15
15
|
createMarketplaceClient,
|
|
@@ -271,10 +271,7 @@ export async function loadBundleFromR2(
|
|
|
271
271
|
const parsed: unknown = JSON.parse(manifestText);
|
|
272
272
|
const result = pluginManifestSchema.safeParse(parsed);
|
|
273
273
|
if (!result.success) return null;
|
|
274
|
-
|
|
275
|
-
// for the Element[] type (Block Kit validation happens at render time).
|
|
276
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Zod types elements as unknown[]; Element type validated at render time
|
|
277
|
-
const manifest = result.data as unknown as PluginManifest;
|
|
274
|
+
const manifest = reconcileManifestAccess(result.data);
|
|
278
275
|
|
|
279
276
|
// Try to load admin code (optional)
|
|
280
277
|
let adminCode: string | undefined;
|
|
@@ -16,6 +16,7 @@ import type { FindManyResult } from "../../database/repositories/types.js";
|
|
|
16
16
|
import type { Database } from "../../database/types.js";
|
|
17
17
|
import { wouldCreateLoop, detectLoops, type RedirectEdge } from "../../redirects/loops.js";
|
|
18
18
|
import { validatePattern, validateDestinationParams, isPattern } from "../../redirects/patterns.js";
|
|
19
|
+
import { isTerminalStatus } from "../../redirects/status.js";
|
|
19
20
|
import type { ApiResult } from "../types.js";
|
|
20
21
|
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
@@ -70,7 +71,7 @@ export async function handleRedirectCreate(
|
|
|
70
71
|
db: Kysely<Database>,
|
|
71
72
|
input: {
|
|
72
73
|
source: string;
|
|
73
|
-
destination
|
|
74
|
+
destination?: string;
|
|
74
75
|
type?: number;
|
|
75
76
|
enabled?: boolean;
|
|
76
77
|
groupName?: string | null;
|
|
@@ -79,8 +80,14 @@ export async function handleRedirectCreate(
|
|
|
79
80
|
try {
|
|
80
81
|
const repo = new RedirectRepository(db);
|
|
81
82
|
|
|
83
|
+
const type = input.type ?? 301;
|
|
84
|
+
// Terminal statuses (410 Gone / 451) are served directly and have no
|
|
85
|
+
// destination — skip the destination/loop checks for them.
|
|
86
|
+
const terminal = isTerminalStatus(type);
|
|
87
|
+
const destination = terminal ? "" : (input.destination ?? "");
|
|
88
|
+
|
|
82
89
|
// Source and destination must differ
|
|
83
|
-
if (input.source ===
|
|
90
|
+
if (!terminal && input.source === destination) {
|
|
84
91
|
return {
|
|
85
92
|
success: false,
|
|
86
93
|
error: {
|
|
@@ -102,12 +109,15 @@ export async function handleRedirectCreate(
|
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
// Validate destination params reference valid source params
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
// (terminal rules have no destination to interpolate)
|
|
113
|
+
if (!terminal) {
|
|
114
|
+
const destError = validateDestinationParams(input.source, destination);
|
|
115
|
+
if (destError) {
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: { code: "VALIDATION_ERROR", message: destError },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
111
121
|
}
|
|
112
122
|
}
|
|
113
123
|
|
|
@@ -123,17 +133,18 @@ export async function handleRedirectCreate(
|
|
|
123
133
|
};
|
|
124
134
|
}
|
|
125
135
|
|
|
126
|
-
// Check for redirect loops (skip if creating as disabled
|
|
127
|
-
|
|
136
|
+
// Check for redirect loops (skip if creating as disabled, or terminal —
|
|
137
|
+
// a Gone rule has no destination, so it can't form a loop)
|
|
138
|
+
if (!terminal && input.enabled !== false) {
|
|
128
139
|
const edges = toEdges(await repo.findAllEnabled());
|
|
129
|
-
const loopPath = wouldCreateLoop(input.source,
|
|
140
|
+
const loopPath = wouldCreateLoop(input.source, destination, edges);
|
|
130
141
|
if (loopPath) return loopError(loopPath);
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
const redirect = await repo.create({
|
|
134
145
|
source: input.source,
|
|
135
|
-
destination
|
|
136
|
-
type
|
|
146
|
+
destination,
|
|
147
|
+
type,
|
|
137
148
|
isPattern: sourceIsPattern,
|
|
138
149
|
enabled: input.enabled ?? true,
|
|
139
150
|
groupName: input.groupName ?? null,
|
|
@@ -49,6 +49,8 @@ import { extractBundle } from "../../plugins/marketplace.js";
|
|
|
49
49
|
import type { PluginBundle } from "../../plugins/marketplace.js";
|
|
50
50
|
import type { SandboxRunner } from "../../plugins/sandbox/types.js";
|
|
51
51
|
import { PluginStateRepository } from "../../plugins/state.js";
|
|
52
|
+
import { declaredAccessToCapabilities } from "../../plugins/types.js";
|
|
53
|
+
import type { DeclaredAccess } from "../../plugins/types.js";
|
|
52
54
|
import {
|
|
53
55
|
canonicalCapabilitiesForDriftCheck,
|
|
54
56
|
coerceRegistryConfig,
|
|
@@ -70,6 +72,26 @@ import {
|
|
|
70
72
|
storeBundleInR2,
|
|
71
73
|
} from "./marketplace.js";
|
|
72
74
|
|
|
75
|
+
const RELEASE_EXTENSION_NSID = "com.emdashcms.experimental.package.releaseExtension";
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether two `declaredAccess` blocks grant exactly the same enforced access --
|
|
79
|
+
* the same capabilities AND the same host allow-list. Both are lowered through
|
|
80
|
+
* the canonical converter so that constraint content (`allowedHosts`), not just
|
|
81
|
+
* the capability set, is part of the comparison. The capability-set consent
|
|
82
|
+
* gate is blind to host scope; this is what keeps a bundle from being installed
|
|
83
|
+
* with a wider (or simply different) host allow-list than its published record
|
|
84
|
+
* advertised and the user consented to.
|
|
85
|
+
*/
|
|
86
|
+
export function enforcedAccessEqual(a: DeclaredAccess, b: DeclaredAccess): boolean {
|
|
87
|
+
const aa = declaredAccessToCapabilities(a);
|
|
88
|
+
const bb = declaredAccessToCapabilities(b);
|
|
89
|
+
return (
|
|
90
|
+
JSON.stringify(aa.capabilities.toSorted()) === JSON.stringify(bb.capabilities.toSorted()) &&
|
|
91
|
+
JSON.stringify(aa.allowedHosts.toSorted()) === JSON.stringify(bb.allowedHosts.toSorted())
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
// ── Types ──────────────────────────────────────────────────────────
|
|
74
96
|
|
|
75
97
|
export interface RegistryInstallInput {
|
|
@@ -999,6 +1021,31 @@ export async function handleRegistryInstall(
|
|
|
999
1021
|
// marketplace plugins that happen to share the publisher's slug.
|
|
1000
1022
|
bundle.manifest = { ...bundle.manifest, id: pluginId };
|
|
1001
1023
|
|
|
1024
|
+
// Integrity: the bundle that will run MUST declare exactly the access
|
|
1025
|
+
// the signed release record advertises. The consent dialog is driven
|
|
1026
|
+
// from the record's `declaredAccess`, so a bundle enforcing something
|
|
1027
|
+
// different -- a wider host allow-list, an extra capability -- would run
|
|
1028
|
+
// outside what the user reviewed. The capability-set consent gate below
|
|
1029
|
+
// is blind to constraint content (host scope), so compare the full
|
|
1030
|
+
// enforced access of record vs bundle here and refuse on any difference.
|
|
1031
|
+
const recordExt =
|
|
1032
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- extensions is the lexicon's open `unknown` map; narrow to read our own extension
|
|
1033
|
+
(release?.extensions as Record<string, { declaredAccess?: DeclaredAccess }> | undefined)?.[
|
|
1034
|
+
RELEASE_EXTENSION_NSID
|
|
1035
|
+
];
|
|
1036
|
+
if (
|
|
1037
|
+
!enforcedAccessEqual(recordExt?.declaredAccess ?? {}, bundle.manifest.declaredAccess ?? {})
|
|
1038
|
+
) {
|
|
1039
|
+
return {
|
|
1040
|
+
success: false,
|
|
1041
|
+
error: {
|
|
1042
|
+
code: "DECLARED_ACCESS_DRIFT",
|
|
1043
|
+
message:
|
|
1044
|
+
"The plugin bundle declares different permissions than its published record. Installation refused.",
|
|
1045
|
+
},
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1002
1049
|
// Capability consent gate: the admin MUST acknowledge the
|
|
1003
1050
|
// capabilities the bundle's manifest actually declares before we
|
|
1004
1051
|
// install it. The bundle manifest is the only source of truth
|
|
@@ -1488,6 +1535,29 @@ export async function handleRegistryUpdate(
|
|
|
1488
1535
|
// and R2 layout stay in sync across install and update.
|
|
1489
1536
|
bundle.manifest = { ...bundle.manifest, id: pluginId };
|
|
1490
1537
|
|
|
1538
|
+
// Integrity: same gate as install. The new bundle must declare exactly
|
|
1539
|
+
// the access its signed release record advertises. Without it, an update
|
|
1540
|
+
// that changes only the host scope (e.g. api.good.com -> evil.com) keeps
|
|
1541
|
+
// the capability set identical, sails through the escalation diff below,
|
|
1542
|
+
// and installs a bundle enforcing a scope the record never showed.
|
|
1543
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- extensions is the lexicon's open `unknown` map; narrow to read our own extension
|
|
1544
|
+
const updateRecordExtensions = signedRelease?.extensions as
|
|
1545
|
+
| Record<string, { declaredAccess?: DeclaredAccess }>
|
|
1546
|
+
| undefined;
|
|
1547
|
+
const recordExt = updateRecordExtensions?.[RELEASE_EXTENSION_NSID];
|
|
1548
|
+
if (
|
|
1549
|
+
!enforcedAccessEqual(recordExt?.declaredAccess ?? {}, bundle.manifest.declaredAccess ?? {})
|
|
1550
|
+
) {
|
|
1551
|
+
return {
|
|
1552
|
+
success: false,
|
|
1553
|
+
error: {
|
|
1554
|
+
code: "DECLARED_ACCESS_DRIFT",
|
|
1555
|
+
message:
|
|
1556
|
+
"The plugin bundle declares different permissions than its published record. Update refused.",
|
|
1557
|
+
},
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1491
1561
|
// Diff capabilities + route visibility against the currently
|
|
1492
1562
|
// installed bundle. Loading from R2 keeps us honest: the diff is
|
|
1493
1563
|
// against the bytes the sandbox is actually running, not whatever
|
package/src/api/handlers/seo.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface SitemapContentEntry {
|
|
|
29
29
|
* alternates between siblings.
|
|
30
30
|
*/
|
|
31
31
|
translationGroup: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Stored SEO image reference (`_emdash_seo.seo_image`), or null when
|
|
34
|
+
* the entry has no SEO image. The route resolves it to an absolute
|
|
35
|
+
* URL and emits it as an `<image:image>` sitemap entry.
|
|
36
|
+
*/
|
|
37
|
+
image: string | null;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
/** Per-collection sitemap data with entries and URL pattern */
|
|
@@ -106,8 +112,9 @@ export async function handleSitemapData(
|
|
|
106
112
|
updated_at: string;
|
|
107
113
|
locale: string;
|
|
108
114
|
translation_group: string | null;
|
|
115
|
+
seo_image: string | null;
|
|
109
116
|
}>`
|
|
110
|
-
SELECT c.slug, c.id, c.updated_at, c.locale, c.translation_group
|
|
117
|
+
SELECT c.slug, c.id, c.updated_at, c.locale, c.translation_group, s.seo_image
|
|
111
118
|
FROM ${sql.ref(tableName)} c
|
|
112
119
|
LEFT JOIN _emdash_seo s
|
|
113
120
|
ON s.collection = ${col.slug}
|
|
@@ -129,6 +136,7 @@ export async function handleSitemapData(
|
|
|
129
136
|
updatedAt: row.updated_at,
|
|
130
137
|
locale: row.locale,
|
|
131
138
|
translationGroup: row.translation_group,
|
|
139
|
+
image: row.seo_image ?? null,
|
|
132
140
|
});
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
import { isTerminalStatus, REDIRECT_RULE_STATUSES } from "../../redirects/status.js";
|
|
3
4
|
import { cursorPaginationQuery } from "./common.js";
|
|
4
5
|
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
@@ -9,8 +10,8 @@ import { cursorPaginationQuery } from "./common.js";
|
|
|
9
10
|
const redirectType = z.coerce
|
|
10
11
|
.number()
|
|
11
12
|
.int()
|
|
12
|
-
.refine((n) =>
|
|
13
|
-
message: "Redirect type must be 301, 302, 307, or
|
|
13
|
+
.refine((n) => REDIRECT_RULE_STATUSES.includes(n), {
|
|
14
|
+
message: "Redirect type must be 301, 302, 307, 308, 410, or 451",
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
/** Matches CR or LF characters */
|
|
@@ -40,17 +41,23 @@ const urlPath = z
|
|
|
40
41
|
export const createRedirectBody = z
|
|
41
42
|
.object({
|
|
42
43
|
source: urlPath,
|
|
43
|
-
|
|
44
|
+
// Optional: terminal statuses (410/451) have no destination. Redirect
|
|
45
|
+
// statuses require one — enforced by the refine below.
|
|
46
|
+
destination: z.union([urlPath, z.literal("")]).optional(),
|
|
44
47
|
type: redirectType.optional().default(301),
|
|
45
48
|
enabled: z.boolean().optional().default(true),
|
|
46
49
|
groupName: z.string().nullish(),
|
|
47
50
|
})
|
|
51
|
+
.refine((o) => isTerminalStatus(o.type ?? 301) || !!o.destination, {
|
|
52
|
+
message: "destination is required for redirect types (301, 302, 307, 308)",
|
|
53
|
+
path: ["destination"],
|
|
54
|
+
})
|
|
48
55
|
.meta({ id: "CreateRedirectBody" });
|
|
49
56
|
|
|
50
57
|
export const updateRedirectBody = z
|
|
51
58
|
.object({
|
|
52
59
|
source: urlPath.optional(),
|
|
53
|
-
destination: urlPath.optional(),
|
|
60
|
+
destination: z.union([urlPath, z.literal("")]).optional(),
|
|
54
61
|
type: redirectType.optional(),
|
|
55
62
|
enabled: z.boolean().optional(),
|
|
56
63
|
groupName: z.string().nullish(),
|
|
@@ -31,7 +31,19 @@ const fieldTypeValues = z.enum([
|
|
|
31
31
|
|
|
32
32
|
const repeaterSubFieldSchema = z.object({
|
|
33
33
|
slug: z.string().min(1).max(63).regex(slugPattern, "Invalid slug format"),
|
|
34
|
-
|
|
34
|
+
// Keep in sync with REPEATER_SUB_FIELD_TYPES in schema/types.ts.
|
|
35
|
+
// ("url" was already a documented sub-field type but missing here.)
|
|
36
|
+
type: z.enum([
|
|
37
|
+
"string",
|
|
38
|
+
"text",
|
|
39
|
+
"url",
|
|
40
|
+
"number",
|
|
41
|
+
"integer",
|
|
42
|
+
"boolean",
|
|
43
|
+
"datetime",
|
|
44
|
+
"select",
|
|
45
|
+
"image",
|
|
46
|
+
]),
|
|
35
47
|
label: z.string().min(1),
|
|
36
48
|
required: z.boolean().optional(),
|
|
37
49
|
options: z.array(z.string()).optional(),
|
|
@@ -17,6 +17,7 @@ import type { AstroIntegration, AstroIntegrationLogger } from "astro";
|
|
|
17
17
|
import { validateAllowedOrigins, validateOriginShape } from "../../auth/allowed-origins.js";
|
|
18
18
|
import { INTERNAL_MEDIA_PREFIX } from "../../media/normalize.js";
|
|
19
19
|
import type { ResolvedPlugin } from "../../plugins/types.js";
|
|
20
|
+
import { VERSION } from "../../version.js";
|
|
20
21
|
import { local } from "../storage/adapters.js";
|
|
21
22
|
import { notoSans } from "./font-provider.js";
|
|
22
23
|
import {
|
|
@@ -155,15 +156,24 @@ const cyan = (s: string) => `\x1b[36m${s}\x1b[39m`;
|
|
|
155
156
|
function printBanner(_logger: AstroIntegrationLogger): void {
|
|
156
157
|
const banner = `
|
|
157
158
|
|
|
158
|
-
${bold(cyan("— E M D A S H —"))}
|
|
159
|
+
${bold(cyan("— E M D A S H —"))} ${dim(`v${VERSION}`)}
|
|
159
160
|
`;
|
|
160
161
|
console.log(banner);
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Print dev-server route info with absolute (clickable) URLs, including the
|
|
166
|
+
* dev-bypass shortcut that skips passkey auth. Dev only -- the dev-bypass
|
|
167
|
+
* endpoint returns 403 in production.
|
|
168
|
+
*/
|
|
169
|
+
function printDevServerInfo(baseUrl: string, mcpEnabled: boolean): void {
|
|
170
|
+
const devBypassUrl = `${baseUrl}/_emdash/api/setup/dev-bypass?redirect=/_emdash/admin`;
|
|
171
|
+
console.log(`\n ${dim("›")} Admin UI ${cyan(`${baseUrl}/_emdash/admin`)}`);
|
|
172
|
+
if (mcpEnabled) {
|
|
173
|
+
console.log(` ${dim("›")} MCP server ${cyan(`${baseUrl}/_emdash/api/mcp`)}`);
|
|
174
|
+
}
|
|
175
|
+
console.log(` ${dim("›")} Dev bypass ${cyan(devBypassUrl)}`);
|
|
176
|
+
console.log(` ${dim("Skips passkey setup/auth and signs you in as a dev admin")}`);
|
|
167
177
|
console.log("");
|
|
168
178
|
}
|
|
169
179
|
|
|
@@ -298,6 +308,10 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
|
|
|
298
308
|
// Check if auth is an AuthDescriptor (has entrypoint) indicating external auth
|
|
299
309
|
const useExternalAuth = !!(resolvedConfig.auth && "entrypoint" in resolvedConfig.auth);
|
|
300
310
|
|
|
311
|
+
// Captured in astro:config:setup so the astro:server:setup hook can tell
|
|
312
|
+
// whether we're running `astro dev` (where the dev-bypass shortcut applies).
|
|
313
|
+
let astroCommand: "dev" | "build" | "preview" | "sync" | undefined;
|
|
314
|
+
|
|
301
315
|
return {
|
|
302
316
|
name: "emdash",
|
|
303
317
|
hooks: {
|
|
@@ -309,6 +323,7 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
|
|
|
309
323
|
config: astroConfig,
|
|
310
324
|
command,
|
|
311
325
|
}) => {
|
|
326
|
+
astroCommand = command;
|
|
312
327
|
printBanner(logger);
|
|
313
328
|
// Capture the host's Astro version so the runtime can expose it
|
|
314
329
|
// to the admin and the registry install gate for `env:astro`
|
|
@@ -345,7 +360,9 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
|
|
|
345
360
|
const securityConfig: Record<string, unknown> = {
|
|
346
361
|
checkOrigin: false,
|
|
347
362
|
...(resolvedConfig.siteUrl
|
|
348
|
-
? {
|
|
363
|
+
? {
|
|
364
|
+
allowedDomains: [{ hostname: new URL(resolvedConfig.siteUrl).hostname }],
|
|
365
|
+
}
|
|
349
366
|
: {}),
|
|
350
367
|
};
|
|
351
368
|
|
|
@@ -413,7 +430,7 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
|
|
|
413
430
|
});
|
|
414
431
|
|
|
415
432
|
// Inject all core routes
|
|
416
|
-
injectCoreRoutes(injectRoute);
|
|
433
|
+
injectCoreRoutes(injectRoute, { srcDir: astroConfig.srcDir });
|
|
417
434
|
|
|
418
435
|
// Inject routes from pluggable auth providers (authProviders config)
|
|
419
436
|
if (resolvedConfig.authProviders?.length) {
|
|
@@ -473,9 +490,28 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
|
|
|
473
490
|
order: "pre",
|
|
474
491
|
});
|
|
475
492
|
|
|
476
|
-
|
|
493
|
+
// Route info is printed with absolute, clickable URLs once the
|
|
494
|
+
// dev server is listening (see astro:server:setup), since the
|
|
495
|
+
// port isn't known yet here. Nothing useful to print for build.
|
|
477
496
|
},
|
|
478
497
|
"astro:server:setup": ({ server, logger }) => {
|
|
498
|
+
// Print route info with absolute, clickable URLs once the server
|
|
499
|
+
// is listening. Only in `astro dev` -- the dev-bypass shortcut is
|
|
500
|
+
// dev-only and the port is unknown until now.
|
|
501
|
+
if (astroCommand === "dev") {
|
|
502
|
+
server.httpServer?.once("listening", () => {
|
|
503
|
+
const address = server.httpServer?.address();
|
|
504
|
+
if (!address || typeof address === "string") return;
|
|
505
|
+
let host = address.address;
|
|
506
|
+
if (host === "::1" || host === "::" || host === "0.0.0.0") {
|
|
507
|
+
host = "localhost";
|
|
508
|
+
} else if (address.family === "IPv6") {
|
|
509
|
+
host = `[${host}]`;
|
|
510
|
+
}
|
|
511
|
+
printDevServerInfo(`http://${host}:${address.port}`, resolvedConfig.mcp !== false);
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
479
515
|
// Generate types once the server is listening.
|
|
480
516
|
// The endpoint returns the types content; we write the file here
|
|
481
517
|
// (in Node) because workerd has no real filesystem access.
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Defines and injects all EmDash routes into the Astro application.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
7
8
|
import { createRequire } from "node:module";
|
|
8
9
|
import { dirname, resolve } from "node:path";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -41,10 +42,42 @@ function resolveRoute(route: string): string {
|
|
|
41
42
|
/** Route injection function type */
|
|
42
43
|
type InjectRoute = (route: { pattern: string; entrypoint: string }) => void;
|
|
43
44
|
|
|
45
|
+
interface InjectCoreRoutesOptions {
|
|
46
|
+
srcDir?: URL;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ROUTE_OVERRIDE_EXTENSIONS = [
|
|
50
|
+
".astro",
|
|
51
|
+
".js",
|
|
52
|
+
".ts",
|
|
53
|
+
".jsx",
|
|
54
|
+
".tsx",
|
|
55
|
+
".mjs",
|
|
56
|
+
".mts",
|
|
57
|
+
".md",
|
|
58
|
+
".mdx",
|
|
59
|
+
".html",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Detect whether the host site defines its own root-level public route file.
|
|
64
|
+
*/
|
|
65
|
+
export function hasUserDefinedPublicRoute(srcDir: URL, basename: string): boolean {
|
|
66
|
+
const srcDirPath = fileURLToPath(srcDir);
|
|
67
|
+
return ROUTE_OVERRIDE_EXTENSIONS.some(
|
|
68
|
+
(extension) =>
|
|
69
|
+
existsSync(resolve(srcDirPath, "pages", `${basename}${extension}`)) ||
|
|
70
|
+
existsSync(resolve(srcDirPath, "pages", basename, `index${extension}`)),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
44
74
|
/**
|
|
45
75
|
* Injects all core EmDash routes.
|
|
46
76
|
*/
|
|
47
|
-
export function injectCoreRoutes(
|
|
77
|
+
export function injectCoreRoutes(
|
|
78
|
+
injectRoute: InjectRoute,
|
|
79
|
+
options: InjectCoreRoutesOptions = {},
|
|
80
|
+
): void {
|
|
48
81
|
// Inject admin shell route
|
|
49
82
|
injectRoute({
|
|
50
83
|
pattern: "/_emdash/admin/[...path]",
|
|
@@ -752,20 +785,24 @@ export function injectCoreRoutes(injectRoute: InjectRoute): void {
|
|
|
752
785
|
});
|
|
753
786
|
|
|
754
787
|
// SEO routes (public, at site root)
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
788
|
+
if (!options.srcDir || !hasUserDefinedPublicRoute(options.srcDir, "sitemap.xml")) {
|
|
789
|
+
injectRoute({
|
|
790
|
+
pattern: "/sitemap.xml",
|
|
791
|
+
entrypoint: resolveRoute("sitemap.xml.ts"),
|
|
792
|
+
});
|
|
793
|
+
}
|
|
759
794
|
|
|
760
795
|
injectRoute({
|
|
761
796
|
pattern: "/sitemap-[collection].xml",
|
|
762
797
|
entrypoint: resolveRoute("sitemap-[collection].xml.ts"),
|
|
763
798
|
});
|
|
764
799
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
800
|
+
if (!options.srcDir || !hasUserDefinedPublicRoute(options.srcDir, "robots.txt")) {
|
|
801
|
+
injectRoute({
|
|
802
|
+
pattern: "/robots.txt",
|
|
803
|
+
entrypoint: resolveRoute("robots.txt.ts"),
|
|
804
|
+
});
|
|
805
|
+
}
|
|
769
806
|
|
|
770
807
|
// Setup wizard API routes
|
|
771
808
|
injectRoute({
|