emdash 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapters-Di31kZ28.d.mts → adapters-DoNJiveC.d.mts} +1 -1
- package/dist/{adapters-Di31kZ28.d.mts.map → adapters-DoNJiveC.d.mts.map} +1 -1
- package/dist/{apply-5uslYdUu.mjs → apply-BzltprvY.mjs} +90 -139
- package/dist/apply-BzltprvY.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +194 -17
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +6 -7
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +34 -57
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +17 -12
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +9 -6
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +301 -165
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +34 -10
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{base64-MBPo9ozB.mjs → base64-BRICGH2l.mjs} +1 -1
- package/dist/{base64-MBPo9ozB.mjs.map → base64-BRICGH2l.mjs.map} +1 -1
- package/dist/{byline-C4OVd8b3.mjs → byline-BSaNL1w7.mjs} +5 -5
- package/dist/byline-BSaNL1w7.mjs.map +1 -0
- package/dist/bylines-CvJ3PYz2.mjs +113 -0
- package/dist/bylines-CvJ3PYz2.mjs.map +1 -0
- package/dist/cache-C6N_hhN7.mjs +65 -0
- package/dist/cache-C6N_hhN7.mjs.map +1 -0
- package/dist/{chunks-HGz06Soa.mjs → chunks-NBQVDOci.mjs} +8 -2
- package/dist/{chunks-HGz06Soa.mjs.map → chunks-NBQVDOci.mjs.map} +1 -1
- package/dist/cli/index.mjs +229 -31
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +3 -3
- package/dist/client/index.mjs.map +1 -1
- package/dist/{config-BXwuX8Bx.mjs → config-BI0V3ICQ.mjs} +1 -1
- package/dist/{config-BXwuX8Bx.mjs.map → config-BI0V3ICQ.mjs.map} +1 -1
- package/dist/{content-D7J5y73J.mjs → content-8lOYF0pr.mjs} +43 -28
- package/dist/content-8lOYF0pr.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.d.mts.map +1 -1
- package/dist/db/libsql.mjs +7 -2
- package/dist/db/libsql.mjs.map +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.d.mts.map +1 -1
- package/dist/db/sqlite.mjs +8 -3
- package/dist/db/sqlite.mjs.map +1 -1
- package/dist/{db-errors-D0UT85nC.mjs → db-errors-WRezodiz.mjs} +1 -1
- package/dist/{db-errors-D0UT85nC.mjs.map → db-errors-WRezodiz.mjs.map} +1 -1
- package/dist/{default-CME5YdZ3.mjs → default-D8ksjWhO.mjs} +1 -1
- package/dist/{default-CME5YdZ3.mjs.map → default-D8ksjWhO.mjs.map} +1 -1
- package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
- package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
- package/dist/{error-CiYn9yDu.mjs → error-D_-tqP-I.mjs} +1 -1
- package/dist/error-D_-tqP-I.mjs.map +1 -0
- package/dist/{index-De6_Xv3v.d.mts → index-BFRaVcD6.d.mts} +243 -40
- package/dist/index-BFRaVcD6.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +29 -25
- package/dist/{load-CBcmDIot.mjs → load-DDqMMvZL.mjs} +2 -2
- package/dist/{load-CBcmDIot.mjs.map → load-DDqMMvZL.mjs.map} +1 -1
- package/dist/{loader-DeiBJEMe.mjs → loader-CKLbBnhK.mjs} +32 -10
- package/dist/loader-CKLbBnhK.mjs.map +1 -0
- package/dist/{manifest-schema-V30qsMft.mjs → manifest-schema-DqWNC3lM.mjs} +45 -3
- package/dist/manifest-schema-DqWNC3lM.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +3 -3
- package/dist/{media-DqHVh136.mjs → media-BW32b4gi.mjs} +4 -7
- package/dist/media-BW32b4gi.mjs.map +1 -0
- package/dist/{mode-CpNnGkPz.mjs → mode-ier8jbBk.mjs} +1 -1
- package/dist/mode-ier8jbBk.mjs.map +1 -0
- package/dist/options-BVp3UsTS.mjs +117 -0
- package/dist/options-BVp3UsTS.mjs.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{placeholder-tzpqGWII.d.mts → placeholder-BE4o_2dc.d.mts} +1 -1
- package/dist/{placeholder-tzpqGWII.d.mts.map → placeholder-BE4o_2dc.d.mts.map} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs → placeholder-CIJejMlK.mjs} +1 -1
- package/dist/placeholder-CIJejMlK.mjs.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/public-url-DByxYjUw.mjs +51 -0
- package/dist/public-url-DByxYjUw.mjs.map +1 -0
- package/dist/{query-g4Ug-9j9.mjs → query-Cg9ZKRQ0.mjs} +114 -16
- package/dist/query-Cg9ZKRQ0.mjs.map +1 -0
- package/dist/{redirect-CN0Rt9Ob.mjs → redirect-BhUBKRc1.mjs} +13 -8
- package/dist/redirect-BhUBKRc1.mjs.map +1 -0
- package/dist/{registry-Ci3WxVAr.mjs → registry-Dw70ChxB.mjs} +69 -11
- package/dist/registry-Dw70ChxB.mjs.map +1 -0
- package/dist/{request-cache-DiR961CV.mjs → request-cache-B-bmkipQ.mjs} +1 -1
- package/dist/request-cache-B-bmkipQ.mjs.map +1 -0
- package/dist/runner-Bnoj7vjK.d.mts +44 -0
- package/dist/runner-Bnoj7vjK.d.mts.map +1 -0
- package/dist/{runner-tQ7BJ4T7.mjs → runner-C7ADox5q.mjs} +185 -55
- package/dist/{runner-tQ7BJ4T7.mjs.map → runner-C7ADox5q.mjs.map} +1 -1
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +4 -4
- package/dist/{search-B0effn3j.mjs → search-dOGEccMa.mjs} +341 -152
- package/dist/search-dOGEccMa.mjs.map +1 -0
- package/dist/secrets-CW3reAnU.mjs +314 -0
- package/dist/secrets-CW3reAnU.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +15 -14
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.d.mts.map +1 -1
- package/dist/storage/s3.mjs +4 -4
- package/dist/storage/s3.mjs.map +1 -1
- package/dist/{taxonomies-K2z0Uhnj.mjs → taxonomies-ZlRtD6AG.mjs} +14 -7
- package/dist/taxonomies-ZlRtD6AG.mjs.map +1 -0
- package/dist/{tokens-BFPFx3CA.mjs → tokens-D7zMmWi2.mjs} +2 -2
- package/dist/{tokens-BFPFx3CA.mjs.map → tokens-D7zMmWi2.mjs.map} +1 -1
- package/dist/{transport-BykRfpyy.mjs → transport-BeMCmin1.mjs} +6 -5
- package/dist/{transport-BykRfpyy.mjs.map → transport-BeMCmin1.mjs.map} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts → transport-DNEfeMaU.d.mts} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts.map → transport-DNEfeMaU.d.mts.map} +1 -1
- package/dist/types-4fVtCIm0.mjs +68 -0
- package/dist/types-4fVtCIm0.mjs.map +1 -0
- package/dist/{types-CnZYHyLW.d.mts → types-BSyXeCFW.d.mts} +24 -2
- package/dist/{types-CnZYHyLW.d.mts.map → types-BSyXeCFW.d.mts.map} +1 -1
- package/dist/{types-DgrIP0tF.d.mts → types-BuBIptGk.d.mts} +80 -106
- package/dist/types-BuBIptGk.d.mts.map +1 -0
- package/dist/{types-BH2L167P.mjs → types-CDbKp7ND.mjs} +1 -1
- package/dist/{types-BH2L167P.mjs.map → types-CDbKp7ND.mjs.map} +1 -1
- package/dist/{types-DDS4MxsT.mjs → types-CIOg5AR8.mjs} +1 -1
- package/dist/{types-DDS4MxsT.mjs.map → types-CIOg5AR8.mjs.map} +1 -1
- package/dist/{types-6CUZRrZP.d.mts → types-CJsYGpco.d.mts} +24 -2
- package/dist/{types-6CUZRrZP.d.mts.map → types-CJsYGpco.d.mts.map} +1 -1
- package/dist/types-CRxNbK-Z.mjs +68 -0
- package/dist/types-CRxNbK-Z.mjs.map +1 -0
- package/dist/{types-C2v0c34j.d.mts → types-CrtWgIvl.d.mts} +1 -1
- package/dist/{types-C2v0c34j.d.mts.map → types-CrtWgIvl.d.mts.map} +1 -1
- package/dist/{types-CFWjXmus.d.mts → types-M78DQ1lx.d.mts} +1 -1
- package/dist/{types-CFWjXmus.d.mts.map → types-M78DQ1lx.d.mts.map} +1 -1
- package/dist/{validate-CqsNItbt.mjs → validate-Baqf0slj.mjs} +3 -3
- package/dist/{validate-CqsNItbt.mjs.map → validate-Baqf0slj.mjs.map} +1 -1
- package/dist/{validate-kM8Pjuf7.d.mts → validate-BfQh_C_y.d.mts} +4 -4
- package/dist/{validate-kM8Pjuf7.d.mts.map → validate-BfQh_C_y.d.mts.map} +1 -1
- package/dist/validation-BfEI7tNe.mjs +144 -0
- package/dist/validation-BfEI7tNe.mjs.map +1 -0
- package/dist/version-DoxrVdYf.mjs +7 -0
- package/dist/{version-BnTKdfam.mjs.map → version-DoxrVdYf.mjs.map} +1 -1
- package/dist/zod-generator-CC0xNe_K.mjs +132 -0
- package/dist/zod-generator-CC0xNe_K.mjs.map +1 -0
- package/locals.d.ts +1 -6
- package/package.json +21 -7
- package/src/api/auth-storage.ts +37 -0
- package/src/api/error.ts +6 -0
- package/src/api/errors.ts +8 -0
- package/src/api/handlers/comments.ts +19 -4
- package/src/api/handlers/content.ts +151 -4
- package/src/api/handlers/device-flow.ts +5 -0
- package/src/api/handlers/index.ts +2 -0
- package/src/api/handlers/marketplace.ts +11 -4
- package/src/api/handlers/media.ts +8 -1
- package/src/api/handlers/menus.ts +160 -21
- package/src/api/handlers/oauth-authorization.ts +72 -33
- package/src/api/handlers/redirects.ts +16 -3
- package/src/api/handlers/revision.ts +23 -14
- package/src/api/handlers/sections.ts +8 -1
- package/src/api/handlers/taxonomies.ts +131 -22
- package/src/api/handlers/validation.ts +212 -0
- package/src/api/openapi/document.ts +4 -1
- package/src/api/public-url.ts +54 -5
- package/src/api/route-utils.ts +14 -0
- package/src/api/schemas/comments.ts +2 -2
- package/src/api/schemas/common.ts +1 -1
- package/src/api/schemas/content.ts +17 -0
- package/src/api/schemas/sections.ts +3 -3
- package/src/api/schemas/setup.ts +8 -0
- package/src/api/schemas/users.ts +1 -1
- package/src/api/schemas/widgets.ts +12 -10
- package/src/api/setup-complete.ts +40 -0
- package/src/api/types.ts +5 -1
- package/src/astro/integration/index.ts +30 -2
- package/src/astro/integration/routes.ts +28 -0
- package/src/astro/integration/runtime.ts +49 -1
- package/src/astro/integration/virtual-modules.ts +73 -2
- package/src/astro/integration/vite-config.ts +49 -13
- package/src/astro/middleware/auth.ts +34 -6
- package/src/astro/middleware/redirect.ts +29 -16
- package/src/astro/middleware/request-context.ts +15 -5
- package/src/astro/middleware.ts +41 -10
- package/src/astro/routes/PluginRegistry.tsx +10 -1
- package/src/astro/routes/api/auth/invite/complete.ts +6 -1
- package/src/astro/routes/api/auth/mode.ts +57 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +23 -3
- package/src/astro/routes/api/auth/oauth/[provider].ts +10 -4
- package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
- package/src/astro/routes/api/auth/signup/complete.ts +6 -1
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
- package/src/astro/routes/api/content/[collection]/index.ts +1 -9
- package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
- package/src/astro/routes/api/import/wordpress/media.ts +2 -7
- package/src/astro/routes/api/import/wordpress/prepare.ts +9 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
- package/src/astro/routes/api/manifest.ts +62 -45
- package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
- package/src/astro/routes/api/openapi.json.ts +27 -10
- package/src/astro/routes/api/redirects/404s/index.ts +10 -4
- package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
- package/src/astro/routes/api/redirects/[id].ts +10 -4
- package/src/astro/routes/api/redirects/index.ts +7 -3
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
- package/src/astro/routes/api/schema/collections/index.ts +1 -1
- package/src/astro/routes/api/search/index.ts +10 -2
- package/src/astro/routes/api/sections/[slug].ts +10 -4
- package/src/astro/routes/api/sections/index.ts +7 -3
- package/src/astro/routes/api/settings/email.ts +4 -9
- package/src/astro/routes/api/setup/admin-verify.ts +6 -1
- package/src/astro/routes/api/setup/admin.ts +8 -2
- package/src/astro/routes/api/setup/index.ts +2 -2
- package/src/astro/routes/api/setup/status.ts +3 -1
- package/src/astro/routes/api/snapshot.ts +44 -18
- package/src/astro/routes/api/taxonomies/index.ts +0 -1
- package/src/astro/routes/api/themes/preview.ts +11 -5
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +4 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +4 -1
- package/src/astro/routes/api/widget-areas/[name].ts +4 -1
- package/src/astro/routes/api/widget-areas/index.ts +4 -1
- package/src/astro/types.ts +32 -3
- package/src/auth/allowed-origins.ts +168 -0
- package/src/auth/mode.ts +15 -3
- package/src/auth/passkey-config.ts +35 -13
- package/src/auth/providers/github-admin.tsx +29 -0
- package/src/auth/providers/github.ts +31 -0
- package/src/auth/providers/google-admin.tsx +44 -0
- package/src/auth/providers/google.ts +31 -0
- package/src/auth/types.ts +114 -4
- package/src/bylines/index.ts +37 -88
- package/src/cli/commands/auth.ts +28 -6
- package/src/cli/commands/bundle-utils.ts +11 -2
- package/src/cli/commands/bundle.ts +31 -9
- package/src/cli/commands/content.ts +13 -0
- package/src/cli/commands/login.ts +8 -1
- package/src/cli/commands/publish.ts +24 -0
- package/src/cli/commands/secrets.ts +183 -0
- package/src/cli/credentials.ts +1 -1
- package/src/cli/index.ts +5 -1
- package/src/client/index.ts +4 -4
- package/src/client/transport.ts +17 -7
- package/src/components/Break.astro +2 -2
- package/src/components/EmDashHead.astro +18 -13
- package/src/components/EmDashImage.astro +7 -6
- package/src/components/Embed.astro +1 -1
- package/src/components/Gallery.astro +6 -4
- package/src/components/Image.astro +9 -4
- package/src/components/InlinePortableTextEditor.tsx +106 -19
- package/src/components/LiveSearch.astro +5 -14
- package/src/config/secrets.ts +528 -0
- package/src/database/dialect-helpers.ts +50 -0
- package/src/database/migrations/034_published_at_index.ts +1 -1
- package/src/database/migrations/035_bounded_404_log.ts +56 -39
- package/src/database/migrations/runner.ts +156 -23
- package/src/database/repositories/audit.ts +6 -8
- package/src/database/repositories/byline.ts +6 -8
- package/src/database/repositories/comment.ts +12 -16
- package/src/database/repositories/content.ts +76 -52
- package/src/database/repositories/index.ts +1 -1
- package/src/database/repositories/media.ts +10 -13
- package/src/database/repositories/plugin-storage.ts +4 -6
- package/src/database/repositories/redirect.ts +26 -19
- package/src/database/repositories/taxonomy.ts +40 -3
- package/src/database/repositories/types.ts +57 -8
- package/src/database/repositories/user.ts +6 -8
- package/src/db/libsql.ts +1 -3
- package/src/db/sqlite.ts +2 -5
- package/src/emdash-runtime.ts +388 -247
- package/src/index.ts +14 -1
- package/src/loader.ts +30 -6
- package/src/mcp/server.ts +781 -141
- package/src/media/normalize.ts +1 -1
- package/src/media/url.ts +78 -0
- package/src/page/site-identity.ts +58 -0
- package/src/plugins/adapt-sandbox-entry.ts +22 -10
- package/src/plugins/context.ts +13 -10
- package/src/plugins/define-plugin.ts +40 -12
- package/src/plugins/email-console.ts +10 -3
- package/src/plugins/hooks.ts +34 -19
- package/src/plugins/index.ts +9 -0
- package/src/plugins/manifest-schema.ts +49 -2
- package/src/plugins/types.ts +174 -13
- package/src/preview/urls.ts +23 -3
- package/src/query.ts +149 -6
- package/src/redirects/cache.ts +38 -18
- package/src/request-cache.ts +3 -0
- package/src/schema/registry.ts +97 -5
- package/src/schema/zod-generator.ts +27 -5
- package/src/search/fts-manager.ts +0 -2
- package/src/search/query.ts +111 -26
- package/src/search/types.ts +8 -1
- package/src/sections/index.ts +7 -9
- package/src/seed/apply.ts +2 -0
- package/src/settings/index.ts +80 -6
- package/src/settings/types.ts +23 -1
- package/src/storage/s3.ts +12 -6
- package/src/taxonomies/index.ts +11 -1
- package/src/virtual-modules.d.ts +21 -1
- package/src/widgets/index.ts +1 -1
- package/dist/apply-5uslYdUu.mjs.map +0 -1
- package/dist/byline-C4OVd8b3.mjs.map +0 -1
- package/dist/bylines-hPTW79hw.mjs +0 -157
- package/dist/bylines-hPTW79hw.mjs.map +0 -1
- package/dist/cache-BkKBuIvS.mjs +0 -56
- package/dist/cache-BkKBuIvS.mjs.map +0 -1
- package/dist/chunk-ClPoSABd.mjs +0 -21
- package/dist/content-D7J5y73J.mjs.map +0 -1
- package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
- package/dist/error-CiYn9yDu.mjs.map +0 -1
- package/dist/index-De6_Xv3v.d.mts.map +0 -1
- package/dist/loader-DeiBJEMe.mjs.map +0 -1
- package/dist/manifest-schema-V30qsMft.mjs.map +0 -1
- package/dist/media-DqHVh136.mjs.map +0 -1
- package/dist/mode-CpNnGkPz.mjs.map +0 -1
- package/dist/placeholder-C-fk5hYI.mjs.map +0 -1
- package/dist/query-g4Ug-9j9.mjs.map +0 -1
- package/dist/redirect-CN0Rt9Ob.mjs.map +0 -1
- package/dist/registry-Ci3WxVAr.mjs.map +0 -1
- package/dist/request-cache-DiR961CV.mjs.map +0 -1
- package/dist/runner-BR2xKwhn.d.mts +0 -34
- package/dist/runner-BR2xKwhn.d.mts.map +0 -1
- package/dist/search-B0effn3j.mjs.map +0 -1
- package/dist/taxonomies-K2z0Uhnj.mjs.map +0 -1
- package/dist/types-CMMN0pNg.mjs +0 -31
- package/dist/types-CMMN0pNg.mjs.map +0 -1
- package/dist/types-DgrIP0tF.d.mts.map +0 -1
- package/dist/version-BnTKdfam.mjs +0 -7
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared setup completion logic.
|
|
3
|
+
*
|
|
4
|
+
* Called by OAuth callbacks and the passkey verify step when the first user
|
|
5
|
+
* is created during setup. Persists site title/tagline from setup state
|
|
6
|
+
* and marks setup as complete.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Kysely } from "kysely";
|
|
10
|
+
|
|
11
|
+
import { OptionsRepository } from "../database/repositories/options.js";
|
|
12
|
+
import type { Database } from "../database/types.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Finalize setup after the first admin user is created.
|
|
16
|
+
*
|
|
17
|
+
* Reads the setup_state option (written by the setup wizard's step 1),
|
|
18
|
+
* persists site_title and site_tagline, then marks setup complete.
|
|
19
|
+
*
|
|
20
|
+
* Safe to call multiple times — checks setup_complete first and no-ops
|
|
21
|
+
* if already done.
|
|
22
|
+
*/
|
|
23
|
+
export async function finalizeSetup(db: Kysely<Database>): Promise<void> {
|
|
24
|
+
const options = new OptionsRepository(db);
|
|
25
|
+
|
|
26
|
+
const setupComplete = await options.get("emdash:setup_complete");
|
|
27
|
+
if (setupComplete === true || setupComplete === "true") return;
|
|
28
|
+
|
|
29
|
+
// Persist site title/tagline from setup state (stored in step 1)
|
|
30
|
+
const setupState = await options.get<Record<string, unknown>>("emdash:setup_state");
|
|
31
|
+
if (setupState?.title && typeof setupState.title === "string") {
|
|
32
|
+
await options.set("emdash:site_title", setupState.title);
|
|
33
|
+
}
|
|
34
|
+
if (setupState?.tagline && typeof setupState.tagline === "string") {
|
|
35
|
+
await options.set("emdash:site_tagline", setupState.tagline);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await options.set("emdash:setup_complete", true);
|
|
39
|
+
await options.delete("emdash:setup_state");
|
|
40
|
+
}
|
package/src/api/types.ts
CHANGED
|
@@ -51,7 +51,11 @@ export interface FieldDescriptor {
|
|
|
51
51
|
kind: string;
|
|
52
52
|
label?: string;
|
|
53
53
|
required?: boolean;
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* For `select` / `multiSelect`: the list of enum choices.
|
|
56
|
+
* For `json` fields driven by a plugin `widget`: arbitrary widget config.
|
|
57
|
+
*/
|
|
58
|
+
options?: Array<{ value: string; label: string }> | Record<string, unknown>;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
/**
|
|
@@ -12,10 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
import type { AstroIntegration, AstroIntegrationLogger } from "astro";
|
|
14
14
|
|
|
15
|
+
import { validateAllowedOrigins, validateOriginShape } from "../../auth/allowed-origins.js";
|
|
15
16
|
import type { ResolvedPlugin } from "../../plugins/types.js";
|
|
16
17
|
import { local } from "../storage/adapters.js";
|
|
17
18
|
import { notoSans } from "./font-provider.js";
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
injectCoreRoutes,
|
|
21
|
+
injectBuiltinAuthRoutes,
|
|
22
|
+
injectAuthProviderRoutes,
|
|
23
|
+
injectMcpRoute,
|
|
24
|
+
} from "./routes.js";
|
|
19
25
|
import type { EmDashConfig, PluginDescriptor } from "./runtime.js";
|
|
20
26
|
import { createViteConfig } from "./vite-config.js";
|
|
21
27
|
|
|
@@ -112,6 +118,22 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
|
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
// Validate config.allowedOrigins shape at startup (per-entry rules: parseable,
|
|
122
|
+
// http(s), no trailing dots, no empty labels). The siteUrl-dependent rules
|
|
123
|
+
// (Rule A: requires siteUrl; Rule B: must be a subdomain of siteUrl) are
|
|
124
|
+
// deferred to runtime when config.siteUrl is absent — EMDASH_SITE_URL may
|
|
125
|
+
// supply it post-build, just like the env-var fallback for siteUrl above.
|
|
126
|
+
// When config.siteUrl IS present, run the full validator here for fail-fast.
|
|
127
|
+
if (resolvedConfig.allowedOrigins?.length) {
|
|
128
|
+
const tagged = resolvedConfig.allowedOrigins.map((origin) => ({
|
|
129
|
+
origin,
|
|
130
|
+
source: "config.allowedOrigins" as const,
|
|
131
|
+
}));
|
|
132
|
+
resolvedConfig.allowedOrigins = resolvedConfig.siteUrl
|
|
133
|
+
? validateAllowedOrigins(resolvedConfig.siteUrl, tagged)
|
|
134
|
+
: validateOriginShape(tagged);
|
|
135
|
+
}
|
|
136
|
+
|
|
115
137
|
// Plugin descriptors from config
|
|
116
138
|
const pluginDescriptors = resolvedConfig.plugins ?? [];
|
|
117
139
|
const sandboxedDescriptors = resolvedConfig.sandboxed ?? [];
|
|
@@ -157,6 +179,7 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
|
|
|
157
179
|
database: resolvedConfig.database,
|
|
158
180
|
storage: resolvedConfig.storage,
|
|
159
181
|
auth: resolvedConfig.auth,
|
|
182
|
+
authProviders: resolvedConfig.authProviders,
|
|
160
183
|
marketplace: resolvedConfig.marketplace,
|
|
161
184
|
siteUrl: resolvedConfig.siteUrl,
|
|
162
185
|
trustedProxyHeaders: resolvedConfig.trustedProxyHeaders,
|
|
@@ -267,7 +290,12 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
|
|
|
267
290
|
// Inject all core routes
|
|
268
291
|
injectCoreRoutes(injectRoute);
|
|
269
292
|
|
|
270
|
-
//
|
|
293
|
+
// Inject routes from pluggable auth providers (authProviders config)
|
|
294
|
+
if (resolvedConfig.authProviders?.length) {
|
|
295
|
+
injectAuthProviderRoutes(injectRoute, resolvedConfig.authProviders);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Inject passkey/oauth/magic-link routes unless transparent external auth is active
|
|
271
299
|
if (!useExternalAuth) {
|
|
272
300
|
injectBuiltinAuthRoutes(injectRoute);
|
|
273
301
|
}
|
|
@@ -46,6 +46,12 @@ export function injectCoreRoutes(injectRoute: InjectRoute): void {
|
|
|
46
46
|
entrypoint: resolveRoute("api/manifest.ts"),
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
// Auth mode endpoint (public — used by the login page to pick the right UI)
|
|
50
|
+
injectRoute({
|
|
51
|
+
pattern: "/_emdash/api/auth/mode",
|
|
52
|
+
entrypoint: resolveRoute("api/auth/mode.ts"),
|
|
53
|
+
});
|
|
54
|
+
|
|
49
55
|
injectRoute({
|
|
50
56
|
pattern: "/_emdash/api/dashboard",
|
|
51
57
|
entrypoint: resolveRoute("api/dashboard.ts"),
|
|
@@ -747,6 +753,28 @@ export function injectMcpRoute(injectRoute: InjectRoute): void {
|
|
|
747
753
|
});
|
|
748
754
|
}
|
|
749
755
|
|
|
756
|
+
/**
|
|
757
|
+
* Injects routes from pluggable auth providers.
|
|
758
|
+
*
|
|
759
|
+
* Each provider declares the routes it needs in its `AuthProviderDescriptor.routes` array.
|
|
760
|
+
* Routes are injected at build time so Vite can bundle them.
|
|
761
|
+
*/
|
|
762
|
+
export function injectAuthProviderRoutes(
|
|
763
|
+
injectRoute: InjectRoute,
|
|
764
|
+
providers: Array<{ routes?: Array<{ pattern: string; entrypoint: string }> }>,
|
|
765
|
+
): void {
|
|
766
|
+
for (const provider of providers) {
|
|
767
|
+
if (provider.routes) {
|
|
768
|
+
for (const route of provider.routes) {
|
|
769
|
+
injectRoute({
|
|
770
|
+
pattern: route.pattern,
|
|
771
|
+
entrypoint: route.entrypoint,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
750
778
|
/**
|
|
751
779
|
* Injects passkey/oauth/magic-link auth routes.
|
|
752
780
|
* Only used when NOT using external auth.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* DO NOT import Node.js-only modules here (fs, path, module, etc.)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { AuthDescriptor } from "../../auth/types.js";
|
|
10
|
+
import type { AuthDescriptor, AuthProviderDescriptor } from "../../auth/types.js";
|
|
11
11
|
import type { DatabaseDescriptor } from "../../db/adapters.js";
|
|
12
12
|
import type { MediaProviderDescriptor } from "../../media/types.js";
|
|
13
13
|
import type { ResolvedPlugin } from "../../plugins/types.js";
|
|
@@ -222,6 +222,24 @@ export interface EmDashConfig {
|
|
|
222
222
|
*/
|
|
223
223
|
auth?: AuthDescriptor;
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Pluggable auth providers (login methods on the login page).
|
|
227
|
+
*
|
|
228
|
+
* Auth providers appear as options alongside passkey on the login page
|
|
229
|
+
* and setup wizard. Any provider can be used to create the initial
|
|
230
|
+
* admin account. Passkey is built-in; providers listed here are additive.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* import { atproto } from "@emdash-cms/auth-atproto";
|
|
235
|
+
*
|
|
236
|
+
* emdash({
|
|
237
|
+
* authProviders: [atproto()],
|
|
238
|
+
* })
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
authProviders?: AuthProviderDescriptor[];
|
|
242
|
+
|
|
225
243
|
/**
|
|
226
244
|
* MCP (Model Context Protocol) server endpoint.
|
|
227
245
|
*
|
|
@@ -285,6 +303,36 @@ export interface EmDashConfig {
|
|
|
285
303
|
siteUrl?: string;
|
|
286
304
|
|
|
287
305
|
/**
|
|
306
|
+
* Additional origins accepted by passkey verification.
|
|
307
|
+
*
|
|
308
|
+
* When the same EmDash deployment is reachable under several hostnames sharing
|
|
309
|
+
* a registrable parent (e.g. `https://example.com` plus
|
|
310
|
+
* `https://preview.example.com`), the canonical `siteUrl` defines the `rpId`
|
|
311
|
+
* and the entries here are the *additional* origins from which assertions
|
|
312
|
+
* are accepted. Each entry must be the same hostname as `siteUrl` or a
|
|
313
|
+
* subdomain of it — WebAuthn requires `rpId` to be a registrable suffix of
|
|
314
|
+
* every origin.
|
|
315
|
+
*
|
|
316
|
+
* Merged at runtime with the `EMDASH_ALLOWED_ORIGINS` env var (comma-separated).
|
|
317
|
+
* Validation:
|
|
318
|
+
* - Config-declared entries are shape-checked at Astro startup.
|
|
319
|
+
* - Subdomain relationship to `siteUrl` is checked at startup when
|
|
320
|
+
* `siteUrl` is also config-declared, otherwise at first passkey
|
|
321
|
+
* verification (since `siteUrl` may come from `EMDASH_SITE_URL`).
|
|
322
|
+
*
|
|
323
|
+
* Mismatches throw with a source-attributed message naming
|
|
324
|
+
* `config.allowedOrigins` or `EMDASH_ALLOWED_ORIGINS`.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* emdash({
|
|
329
|
+
* siteUrl: "https://example.com",
|
|
330
|
+
* allowedOrigins: ["https://preview.example.com"],
|
|
331
|
+
* })
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
allowedOrigins?: string[];
|
|
335
|
+
/*
|
|
288
336
|
* Headers to trust for client IP resolution when running behind a reverse
|
|
289
337
|
* proxy. The first header in this list that is present on the request
|
|
290
338
|
* wins. Applies to rate limiting for auth endpoints and comment
|
|
@@ -10,6 +10,7 @@ import { readFileSync } from "node:fs";
|
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
12
|
|
|
13
|
+
import type { AuthProviderDescriptor } from "../../auth/types.js";
|
|
13
14
|
import type { MediaProviderDescriptor } from "../../media/types.js";
|
|
14
15
|
import { defaultSeed } from "../../seed/default.js";
|
|
15
16
|
import type { PluginDescriptor } from "./runtime.js";
|
|
@@ -47,6 +48,9 @@ export const RESOLVED_VIRTUAL_SANDBOXED_PLUGINS_ID = "\0" + VIRTUAL_SANDBOXED_PL
|
|
|
47
48
|
export const VIRTUAL_AUTH_ID = "virtual:emdash/auth";
|
|
48
49
|
export const RESOLVED_VIRTUAL_AUTH_ID = "\0" + VIRTUAL_AUTH_ID;
|
|
49
50
|
|
|
51
|
+
export const VIRTUAL_AUTH_PROVIDERS_ID = "virtual:emdash/auth-providers";
|
|
52
|
+
export const RESOLVED_VIRTUAL_AUTH_PROVIDERS_ID = "\0" + VIRTUAL_AUTH_PROVIDERS_ID;
|
|
53
|
+
|
|
50
54
|
export const VIRTUAL_MEDIA_PROVIDERS_ID = "virtual:emdash/media-providers";
|
|
51
55
|
export const RESOLVED_VIRTUAL_MEDIA_PROVIDERS_ID = "\0" + VIRTUAL_MEDIA_PROVIDERS_ID;
|
|
52
56
|
|
|
@@ -135,6 +139,43 @@ export const authenticate = _authenticate;
|
|
|
135
139
|
`;
|
|
136
140
|
}
|
|
137
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Generates the auth providers module.
|
|
144
|
+
*
|
|
145
|
+
* Statically imports each auth provider's `adminEntry` module and exports
|
|
146
|
+
* a registry keyed by provider ID. The admin UI uses this to render
|
|
147
|
+
* provider-specific login buttons/forms and setup steps.
|
|
148
|
+
*
|
|
149
|
+
* Follows the same pattern as `generateAdminRegistryModule()` for plugins.
|
|
150
|
+
*/
|
|
151
|
+
export function generateAuthProvidersModule(descriptors: AuthProviderDescriptor[]): string {
|
|
152
|
+
const withAdmin = descriptors.filter((d) => d.adminEntry);
|
|
153
|
+
|
|
154
|
+
if (withAdmin.length === 0) {
|
|
155
|
+
return `export const authProviders = {};`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const imports: string[] = [];
|
|
159
|
+
const entries: string[] = [];
|
|
160
|
+
|
|
161
|
+
withAdmin.forEach((descriptor, index) => {
|
|
162
|
+
const varName = `authProvider${index}`;
|
|
163
|
+
imports.push(`import * as ${varName} from ${JSON.stringify(descriptor.adminEntry)};`);
|
|
164
|
+
entries.push(
|
|
165
|
+
` ${JSON.stringify(descriptor.id)}: { ...${varName}, id: ${JSON.stringify(descriptor.id)}, label: ${JSON.stringify(descriptor.label)} },`,
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return `
|
|
170
|
+
// Auto-generated auth provider registry
|
|
171
|
+
${imports.join("\n")}
|
|
172
|
+
|
|
173
|
+
export const authProviders = {
|
|
174
|
+
${entries.join("\n")}
|
|
175
|
+
};
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
|
|
138
179
|
/**
|
|
139
180
|
* Generates the plugins module.
|
|
140
181
|
* Imports and instantiates all plugins at runtime.
|
|
@@ -360,9 +401,20 @@ export function generateWaitUntilModule(adapterName: string | undefined): string
|
|
|
360
401
|
* Reads the user's seed file at build time (in Node context) and embeds it,
|
|
361
402
|
* so the runtime doesn't need filesystem access (required for workerd).
|
|
362
403
|
*
|
|
404
|
+
* Search order:
|
|
405
|
+
* 1. `.emdash/seed.json`
|
|
406
|
+
* 2. `package.json` → `emdash.seed` reference
|
|
407
|
+
* 3. `seed/seed.json` (conventional template path)
|
|
408
|
+
*
|
|
363
409
|
* Exports `userSeed` (user's seed or null) and `seed` (user's seed or default).
|
|
410
|
+
*
|
|
411
|
+
* When no user seed is found, falls back to the built-in default seed and
|
|
412
|
+
* (if `warnOnFallback` is true) logs a warning so misconfiguration is visible
|
|
413
|
+
* during `astro dev`. Build/preview/sync stay silent so sites that
|
|
414
|
+
* intentionally use the default seed (e.g. the blank template) don't
|
|
415
|
+
* generate noisy logs.
|
|
364
416
|
*/
|
|
365
|
-
export function generateSeedModule(projectRoot: string): string {
|
|
417
|
+
export function generateSeedModule(projectRoot: string, warnOnFallback = false): string {
|
|
366
418
|
let userSeedJson: string | null = null;
|
|
367
419
|
|
|
368
420
|
// Try .emdash/seed.json
|
|
@@ -393,11 +445,30 @@ export function generateSeedModule(projectRoot: string): string {
|
|
|
393
445
|
}
|
|
394
446
|
}
|
|
395
447
|
|
|
448
|
+
// Try conventional seed/seed.json fallback
|
|
449
|
+
if (!userSeedJson) {
|
|
450
|
+
try {
|
|
451
|
+
const seedPath = resolve(projectRoot, "seed", "seed.json");
|
|
452
|
+
const content = readFileSync(seedPath, "utf-8");
|
|
453
|
+
JSON.parse(content); // validate
|
|
454
|
+
userSeedJson = content;
|
|
455
|
+
} catch {
|
|
456
|
+
// Not found
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
396
460
|
if (userSeedJson) {
|
|
397
461
|
return [`export const userSeed = ${userSeedJson};`, `export const seed = userSeed;`].join("\n");
|
|
398
462
|
}
|
|
399
463
|
|
|
400
|
-
// No user seed — inline the default
|
|
464
|
+
// No user seed — inline the default. Caller (the Vite plugin) gates this
|
|
465
|
+
// to dev-only so production builds stay quiet for sites that intentionally
|
|
466
|
+
// rely on the default seed.
|
|
467
|
+
if (warnOnFallback) {
|
|
468
|
+
console.warn(
|
|
469
|
+
"[emdash] No user seed found at .emdash/seed.json, package.json#emdash.seed, or seed/seed.json. Falling back to the built-in default seed; the setup wizard will not offer demo content for this site.",
|
|
470
|
+
);
|
|
471
|
+
}
|
|
401
472
|
return [
|
|
402
473
|
`export const userSeed = null;`,
|
|
403
474
|
`export const seed = ${JSON.stringify(defaultSeed)};`,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
|
-
import { dirname, resolve } from "node:path";
|
|
10
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
|
|
13
13
|
import type { AstroConfig } from "astro";
|
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
RESOLVED_VIRTUAL_SANDBOXED_PLUGINS_ID,
|
|
33
33
|
VIRTUAL_AUTH_ID,
|
|
34
34
|
RESOLVED_VIRTUAL_AUTH_ID,
|
|
35
|
+
VIRTUAL_AUTH_PROVIDERS_ID,
|
|
36
|
+
RESOLVED_VIRTUAL_AUTH_PROVIDERS_ID,
|
|
35
37
|
VIRTUAL_MEDIA_PROVIDERS_ID,
|
|
36
38
|
RESOLVED_VIRTUAL_MEDIA_PROVIDERS_ID,
|
|
37
39
|
VIRTUAL_BLOCK_COMPONENTS_ID,
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
generateDialectModule,
|
|
47
49
|
generateStorageModule,
|
|
48
50
|
generateAuthModule,
|
|
51
|
+
generateAuthProvidersModule,
|
|
49
52
|
generatePluginsModule,
|
|
50
53
|
generateAdminRegistryModule,
|
|
51
54
|
generateSandboxRunnerModule,
|
|
@@ -104,24 +107,34 @@ function resolveAdminDist(): string {
|
|
|
104
107
|
return dirname(adminPath);
|
|
105
108
|
}
|
|
106
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Check whether child is inside parent without relying on simple prefix checks.
|
|
112
|
+
*/
|
|
113
|
+
function isInside(parent: string, child: string): boolean {
|
|
114
|
+
const relativePath = relative(parent, child);
|
|
115
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
|
|
116
|
+
}
|
|
117
|
+
|
|
107
118
|
/**
|
|
108
119
|
* Resolve path to the admin package source directory.
|
|
109
|
-
* In dev mode, we alias @emdash-cms/admin to the source so
|
|
110
|
-
* directly — giving instant HMR instead of requiring a
|
|
120
|
+
* In dev mode inside this repo, we alias @emdash-cms/admin to the source so
|
|
121
|
+
* Vite processes it directly — giving instant HMR instead of requiring a
|
|
122
|
+
* rebuild + restart. External apps should use the built package surface.
|
|
111
123
|
*/
|
|
112
|
-
function resolveAdminSource(): string | undefined {
|
|
124
|
+
function resolveAdminSource(projectRoot: string): string | undefined {
|
|
113
125
|
const require = createRequire(import.meta.url);
|
|
114
126
|
const adminPath = require.resolve("@emdash-cms/admin");
|
|
115
127
|
// dist/index.js -> go up to package root, then into src/
|
|
116
128
|
const packageRoot = resolve(dirname(adminPath), "..");
|
|
129
|
+
const repoRoot = resolve(packageRoot, "..", "..");
|
|
117
130
|
const srcEntry = resolve(packageRoot, "src", "index.ts");
|
|
118
131
|
|
|
119
132
|
try {
|
|
120
|
-
if (existsSync(srcEntry)) {
|
|
133
|
+
if (existsSync(srcEntry) && isInside(repoRoot, projectRoot)) {
|
|
121
134
|
return resolve(packageRoot, "src");
|
|
122
135
|
}
|
|
123
136
|
} catch {
|
|
124
|
-
// Not in
|
|
137
|
+
// Not in local repo — fall back to dist
|
|
125
138
|
}
|
|
126
139
|
return undefined;
|
|
127
140
|
}
|
|
@@ -143,8 +156,13 @@ export interface VitePluginOptions {
|
|
|
143
156
|
export function createVirtualModulesPlugin(options: VitePluginOptions): Plugin {
|
|
144
157
|
const { serializableConfig, resolvedConfig, pluginDescriptors, astroConfig } = options;
|
|
145
158
|
|
|
159
|
+
let viteCommand: "build" | "serve" | undefined;
|
|
160
|
+
|
|
146
161
|
return {
|
|
147
162
|
name: "emdash-virtual-modules",
|
|
163
|
+
configResolved(config) {
|
|
164
|
+
viteCommand = config.command;
|
|
165
|
+
},
|
|
148
166
|
resolveId(id: string) {
|
|
149
167
|
if (id === VIRTUAL_CONFIG_ID) {
|
|
150
168
|
return RESOLVED_VIRTUAL_CONFIG_ID;
|
|
@@ -170,6 +188,9 @@ export function createVirtualModulesPlugin(options: VitePluginOptions): Plugin {
|
|
|
170
188
|
if (id === VIRTUAL_AUTH_ID) {
|
|
171
189
|
return RESOLVED_VIRTUAL_AUTH_ID;
|
|
172
190
|
}
|
|
191
|
+
if (id === VIRTUAL_AUTH_PROVIDERS_ID) {
|
|
192
|
+
return RESOLVED_VIRTUAL_AUTH_PROVIDERS_ID;
|
|
193
|
+
}
|
|
173
194
|
if (id === VIRTUAL_MEDIA_PROVIDERS_ID) {
|
|
174
195
|
return RESOLVED_VIRTUAL_MEDIA_PROVIDERS_ID;
|
|
175
196
|
}
|
|
@@ -228,6 +249,10 @@ export function createVirtualModulesPlugin(options: VitePluginOptions): Plugin {
|
|
|
228
249
|
}
|
|
229
250
|
return generateAuthModule(authDescriptor.entrypoint);
|
|
230
251
|
}
|
|
252
|
+
// Generate auth providers module (pluggable login methods)
|
|
253
|
+
if (id === RESOLVED_VIRTUAL_AUTH_PROVIDERS_ID) {
|
|
254
|
+
return generateAuthProvidersModule(resolvedConfig.authProviders ?? []);
|
|
255
|
+
}
|
|
231
256
|
// Generate media providers module
|
|
232
257
|
if (id === RESOLVED_VIRTUAL_MEDIA_PROVIDERS_ID) {
|
|
233
258
|
return generateMediaProvidersModule(resolvedConfig.mediaProviders ?? []);
|
|
@@ -239,7 +264,7 @@ export function createVirtualModulesPlugin(options: VitePluginOptions): Plugin {
|
|
|
239
264
|
// Generate seed module — embeds user seed or default at build time
|
|
240
265
|
if (id === RESOLVED_VIRTUAL_SEED_ID) {
|
|
241
266
|
const projectRoot = fileURLToPath(astroConfig.root);
|
|
242
|
-
return generateSeedModule(projectRoot);
|
|
267
|
+
return generateSeedModule(projectRoot, viteCommand === "serve");
|
|
243
268
|
}
|
|
244
269
|
// Generate wait-until module — re-exports cloudflare:workers'
|
|
245
270
|
// waitUntil under the Cloudflare adapter, undefined otherwise.
|
|
@@ -281,12 +306,9 @@ export function createViteConfig(
|
|
|
281
306
|
const adminDistPath = resolveAdminDist();
|
|
282
307
|
const cloudflare = isCloudflareAdapter(options.astroConfig);
|
|
283
308
|
const isDev = command === "dev";
|
|
309
|
+
const projectRoot = fileURLToPath(options.astroConfig.root);
|
|
284
310
|
|
|
285
|
-
|
|
286
|
-
// CSS always comes from dist/ (pre-compiled by @tailwindcss/cli) since Tailwind's
|
|
287
|
-
// Vite plugin has native deps that don't bundle well. Run `pnpm dev` in packages/admin
|
|
288
|
-
// alongside the demo server to get CSS watch-rebuilds too.
|
|
289
|
-
const adminSourcePath = isDev ? resolveAdminSource() : undefined;
|
|
311
|
+
const adminSourcePath = isDev ? resolveAdminSource(projectRoot) : undefined;
|
|
290
312
|
const useSource = adminSourcePath !== undefined;
|
|
291
313
|
|
|
292
314
|
return {
|
|
@@ -308,6 +330,20 @@ export function createViteConfig(
|
|
|
308
330
|
alias: [
|
|
309
331
|
{ find: "@emdash-cms/admin/styles.css", replacement: resolve(adminDistPath, "styles.css") },
|
|
310
332
|
{ find: "@emdash-cms/admin", replacement: useSource ? adminSourcePath : adminDistPath },
|
|
333
|
+
// `use-sync-external-store/shim` is a React <18 polyfill that ships
|
|
334
|
+
// only as CJS. It's pulled in transitively by `@tiptap/react`. With
|
|
335
|
+
// pnpm's virtual store the file lives under .pnpm/, where Vite's
|
|
336
|
+
// dep scanner can't reach it for pre-bundling — so the browser is
|
|
337
|
+
// served raw `module.exports` and hydration fails with
|
|
338
|
+
// `SyntaxError: ... does not provide an export named
|
|
339
|
+
// 'useSyncExternalStore'`. Redirect both shim entry points to the
|
|
340
|
+
// main `use-sync-external-store` package, which on React >=18
|
|
341
|
+
// (our peer-dep floor) delegates to React's built-in hook.
|
|
342
|
+
{
|
|
343
|
+
find: "use-sync-external-store/shim/index.js",
|
|
344
|
+
replacement: "use-sync-external-store",
|
|
345
|
+
},
|
|
346
|
+
{ find: "use-sync-external-store/shim", replacement: "use-sync-external-store" },
|
|
311
347
|
],
|
|
312
348
|
},
|
|
313
349
|
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Monorepo has both vite 6 (docs) and vite 7 (core). tsgo resolves correctly.
|
|
@@ -316,7 +352,7 @@ export function createViteConfig(
|
|
|
316
352
|
// In dev mode with source alias, compile Lingui macros on the fly
|
|
317
353
|
// and redirect locale .mjs imports to dist/.
|
|
318
354
|
// In production, macros are pre-compiled by tsdown in the admin package.
|
|
319
|
-
...(useSource ? [linguiMacroPlugin(adminSourcePath
|
|
355
|
+
...(useSource ? [linguiMacroPlugin(adminSourcePath, adminDistPath)] : []),
|
|
320
356
|
] as NonNullable<AstroConfig["vite"]>["plugins"],
|
|
321
357
|
// Handle native modules for SSR.
|
|
322
358
|
// On Node: external keeps native addons out of the SSR bundle.
|
|
@@ -17,6 +17,8 @@ import { ulid } from "ulidx";
|
|
|
17
17
|
// Import auth provider via virtual module (statically bundled)
|
|
18
18
|
// This avoids dynamic import issues in Cloudflare Workers
|
|
19
19
|
import { authenticate as virtualAuthenticate } from "virtual:emdash/auth";
|
|
20
|
+
// @ts-ignore - virtual module
|
|
21
|
+
import virtualConfig from "virtual:emdash/config";
|
|
20
22
|
|
|
21
23
|
import { checkPublicCsrf } from "../../api/csrf.js";
|
|
22
24
|
import { apiError } from "../../api/error.js";
|
|
@@ -30,7 +32,7 @@ import { resolveApiToken, resolveOAuthToken } from "../../api/handlers/api-token
|
|
|
30
32
|
import { hasScope } from "../../auth/api-tokens.js";
|
|
31
33
|
import { getAuthMode, type ExternalAuthMode } from "../../auth/mode.js";
|
|
32
34
|
import type { ExternalAuthConfig } from "../../auth/types.js";
|
|
33
|
-
import type { EmDashHandlers
|
|
35
|
+
import type { EmDashHandlers } from "../types.js";
|
|
34
36
|
import { buildEmDashCsp } from "./csp.js";
|
|
35
37
|
|
|
36
38
|
declare global {
|
|
@@ -40,7 +42,6 @@ declare global {
|
|
|
40
42
|
/** Token scopes when authenticated via API token or OAuth token. Undefined for session auth. */
|
|
41
43
|
tokenScopes?: string[];
|
|
42
44
|
emdash?: EmDashHandlers;
|
|
43
|
-
emdashManifest?: EmDashManifest;
|
|
44
45
|
}
|
|
45
46
|
interface SessionData {
|
|
46
47
|
user: { id: string };
|
|
@@ -111,6 +112,7 @@ const PUBLIC_API_PREFIXES = [
|
|
|
111
112
|
const PUBLIC_API_EXACT = new Set([
|
|
112
113
|
"/_emdash/api/auth/passkey/options",
|
|
113
114
|
"/_emdash/api/auth/passkey/verify",
|
|
115
|
+
"/_emdash/api/auth/mode",
|
|
114
116
|
"/_emdash/api/oauth/token",
|
|
115
117
|
"/_emdash/api/snapshot",
|
|
116
118
|
// Public site search — read-only. The query layer hardcodes status='published'
|
|
@@ -119,6 +121,22 @@ const PUBLIC_API_EXACT = new Set([
|
|
|
119
121
|
"/_emdash/api/search",
|
|
120
122
|
]);
|
|
121
123
|
|
|
124
|
+
// Build merged public routes at module load from auth provider descriptors.
|
|
125
|
+
// Routes ending with "/" are treated as prefixes; all others are exact matches.
|
|
126
|
+
const { exact: _providerExactRoutes, prefixes: _providerPrefixRoutes } = (() => {
|
|
127
|
+
const exact = new Set<string>();
|
|
128
|
+
const prefixes: string[] = [];
|
|
129
|
+
if (!virtualConfig?.authProviders) return { exact, prefixes };
|
|
130
|
+
for (const route of virtualConfig.authProviders.flatMap((p) => p.publicRoutes ?? [])) {
|
|
131
|
+
if (route.endsWith("/")) {
|
|
132
|
+
prefixes.push(route);
|
|
133
|
+
} else {
|
|
134
|
+
exact.add(route);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { exact, prefixes };
|
|
138
|
+
})();
|
|
139
|
+
|
|
122
140
|
/**
|
|
123
141
|
* OAuth protocol endpoints that are CSRF-exempt by design.
|
|
124
142
|
*
|
|
@@ -146,6 +164,8 @@ const CSRF_EXEMPT_PUBLIC_ROUTES = new Set([
|
|
|
146
164
|
function isPublicEmDashRoute(pathname: string): boolean {
|
|
147
165
|
if (PUBLIC_API_EXACT.has(pathname)) return true;
|
|
148
166
|
if (PUBLIC_API_PREFIXES.some((p) => pathname.startsWith(p))) return true;
|
|
167
|
+
if (_providerExactRoutes.has(pathname)) return true;
|
|
168
|
+
if (_providerPrefixRoutes.some((p) => pathname.startsWith(p))) return true;
|
|
149
169
|
if (import.meta.env.DEV && pathname === "/_emdash/api/typegen") return true;
|
|
150
170
|
return false;
|
|
151
171
|
}
|
|
@@ -702,10 +722,14 @@ const SCOPE_RULES: Array<[prefix: string, method: string, scope: string]> = [
|
|
|
702
722
|
["/_emdash/api/schema", "WRITE", "schema:write"],
|
|
703
723
|
|
|
704
724
|
// Taxonomy, menu, section, widget, revision — all content domain
|
|
725
|
+
// GET uses content:read (implicit from taxonomies:read / menus:read via role).
|
|
726
|
+
// WRITE uses the granular scope so tokens with only taxonomies:manage or
|
|
727
|
+
// menus:manage are not rejected. content:write implicitly grants these via
|
|
728
|
+
// IMPLICIT_SCOPE_GRANTS in @emdash-cms/auth.
|
|
705
729
|
["/_emdash/api/taxonomies", "GET", "content:read"],
|
|
706
|
-
["/_emdash/api/taxonomies", "WRITE", "
|
|
730
|
+
["/_emdash/api/taxonomies", "WRITE", "taxonomies:manage"],
|
|
707
731
|
["/_emdash/api/menus", "GET", "content:read"],
|
|
708
|
-
["/_emdash/api/menus", "WRITE", "
|
|
732
|
+
["/_emdash/api/menus", "WRITE", "menus:manage"],
|
|
709
733
|
["/_emdash/api/sections", "GET", "content:read"],
|
|
710
734
|
["/_emdash/api/sections", "WRITE", "content:write"],
|
|
711
735
|
["/_emdash/api/widget-areas", "GET", "content:read"],
|
|
@@ -717,12 +741,16 @@ const SCOPE_RULES: Array<[prefix: string, method: string, scope: string]> = [
|
|
|
717
741
|
["/_emdash/api/search", "GET", "content:read"],
|
|
718
742
|
["/_emdash/api/search", "WRITE", "admin"],
|
|
719
743
|
|
|
720
|
-
// Import, admin,
|
|
744
|
+
// Import, admin, plugins — all require admin scope
|
|
721
745
|
["/_emdash/api/import", "*", "admin"],
|
|
722
746
|
["/_emdash/api/admin", "*", "admin"],
|
|
723
|
-
["/_emdash/api/settings", "*", "admin"],
|
|
724
747
|
["/_emdash/api/plugins", "*", "admin"],
|
|
725
748
|
|
|
749
|
+
// Settings — use granular scopes so tokens with settings:read or
|
|
750
|
+
// settings:manage are not rejected at the middleware level.
|
|
751
|
+
["/_emdash/api/settings", "GET", "settings:read"],
|
|
752
|
+
["/_emdash/api/settings", "WRITE", "settings:manage"],
|
|
753
|
+
|
|
726
754
|
// MCP endpoint — scopes enforced per-tool inside mcp/server.ts
|
|
727
755
|
["/_emdash/api/mcp", "*", "content:read"],
|
|
728
756
|
];
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
import { defineMiddleware } from "astro:middleware";
|
|
18
18
|
|
|
19
19
|
import { RedirectRepository } from "../../database/repositories/redirect.js";
|
|
20
|
+
import { getDb } from "../../loader.js";
|
|
20
21
|
import {
|
|
21
|
-
|
|
22
|
+
getCachedRedirects,
|
|
22
23
|
matchCachedPatterns,
|
|
23
|
-
|
|
24
|
+
setCachedRedirects,
|
|
24
25
|
} from "../../redirects/cache.js";
|
|
25
26
|
|
|
26
27
|
/** Paths that should never be intercepted by redirects */
|
|
@@ -46,16 +47,34 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
46
47
|
return next();
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
// Public visitors hit the runtime's anonymous fast path, which intentionally
|
|
51
|
+
// omits `db` from `locals.emdash` to keep the public render boundary minimal
|
|
52
|
+
// (issue #808). Fall back to `getDb()`, which transparently returns the
|
|
53
|
+
// per-request scoped db (set in ALS by the runtime middleware) or the
|
|
54
|
+
// singleton — same path the loader and template helpers use.
|
|
55
|
+
let db = context.locals.emdash?.db;
|
|
56
|
+
if (!db) {
|
|
57
|
+
try {
|
|
58
|
+
db = await getDb();
|
|
59
|
+
} catch {
|
|
60
|
+
return next();
|
|
61
|
+
}
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
try {
|
|
55
|
-
const repo = new RedirectRepository(
|
|
65
|
+
const repo = new RedirectRepository(db);
|
|
66
|
+
|
|
67
|
+
// One query loads both exact and pattern rules into the cache; warm
|
|
68
|
+
// requests issue zero queries. Empty-redirect sites cache an empty
|
|
69
|
+
// Map + array, so the next request returns immediately without probing.
|
|
70
|
+
let cached = getCachedRedirects();
|
|
71
|
+
if (!cached) {
|
|
72
|
+
const all = await repo.findAllEnabled();
|
|
73
|
+
cached = setCachedRedirects(all);
|
|
74
|
+
}
|
|
56
75
|
|
|
57
|
-
// 1. Exact match (
|
|
58
|
-
const exact =
|
|
76
|
+
// 1. Exact match (O(1) Map lookup)
|
|
77
|
+
const exact = cached.exact.get(pathname);
|
|
59
78
|
if (exact) {
|
|
60
79
|
const dest = exact.destination;
|
|
61
80
|
if (dest.startsWith("//") || dest.startsWith("/\\")) return next();
|
|
@@ -64,14 +83,8 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
64
83
|
return context.redirect(dest, code);
|
|
65
84
|
}
|
|
66
85
|
|
|
67
|
-
// 2. Pattern match (
|
|
68
|
-
|
|
69
|
-
if (!rules) {
|
|
70
|
-
const patterns = await repo.findEnabledPatternRules();
|
|
71
|
-
rules = setCachedPatternRules(patterns);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const patternMatch = matchCachedPatterns(rules, pathname);
|
|
86
|
+
// 2. Pattern match (compile once, match every request)
|
|
87
|
+
const patternMatch = matchCachedPatterns(cached.patterns, pathname);
|
|
75
88
|
if (patternMatch) {
|
|
76
89
|
const { redirect, destination } = patternMatch;
|
|
77
90
|
if (destination.startsWith("//") || destination.startsWith("/\\")) return next();
|