emdash 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapters-Di31kZ28.d.mts → adapters-DoNJiveC.d.mts} +1 -1
- package/dist/{adapters-Di31kZ28.d.mts.map → adapters-DoNJiveC.d.mts.map} +1 -1
- package/dist/{apply-5uslYdUu.mjs → apply-BzltprvY.mjs} +90 -139
- package/dist/apply-BzltprvY.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +194 -17
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +6 -7
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +34 -57
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +17 -12
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +9 -6
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +301 -165
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +34 -10
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{base64-MBPo9ozB.mjs → base64-BRICGH2l.mjs} +1 -1
- package/dist/{base64-MBPo9ozB.mjs.map → base64-BRICGH2l.mjs.map} +1 -1
- package/dist/{byline-C4OVd8b3.mjs → byline-BSaNL1w7.mjs} +5 -5
- package/dist/byline-BSaNL1w7.mjs.map +1 -0
- package/dist/bylines-CvJ3PYz2.mjs +113 -0
- package/dist/bylines-CvJ3PYz2.mjs.map +1 -0
- package/dist/cache-C6N_hhN7.mjs +65 -0
- package/dist/cache-C6N_hhN7.mjs.map +1 -0
- package/dist/{chunks-HGz06Soa.mjs → chunks-NBQVDOci.mjs} +8 -2
- package/dist/{chunks-HGz06Soa.mjs.map → chunks-NBQVDOci.mjs.map} +1 -1
- package/dist/cli/index.mjs +229 -31
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +3 -3
- package/dist/client/index.mjs.map +1 -1
- package/dist/{config-BXwuX8Bx.mjs → config-BI0V3ICQ.mjs} +1 -1
- package/dist/{config-BXwuX8Bx.mjs.map → config-BI0V3ICQ.mjs.map} +1 -1
- package/dist/{content-D7J5y73J.mjs → content-8lOYF0pr.mjs} +43 -28
- package/dist/content-8lOYF0pr.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.d.mts.map +1 -1
- package/dist/db/libsql.mjs +7 -2
- package/dist/db/libsql.mjs.map +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.d.mts.map +1 -1
- package/dist/db/sqlite.mjs +8 -3
- package/dist/db/sqlite.mjs.map +1 -1
- package/dist/{db-errors-D0UT85nC.mjs → db-errors-WRezodiz.mjs} +1 -1
- package/dist/{db-errors-D0UT85nC.mjs.map → db-errors-WRezodiz.mjs.map} +1 -1
- package/dist/{default-CME5YdZ3.mjs → default-D8ksjWhO.mjs} +1 -1
- package/dist/{default-CME5YdZ3.mjs.map → default-D8ksjWhO.mjs.map} +1 -1
- package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
- package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
- package/dist/{error-CiYn9yDu.mjs → error-D_-tqP-I.mjs} +1 -1
- package/dist/error-D_-tqP-I.mjs.map +1 -0
- package/dist/{index-De6_Xv3v.d.mts → index-BFRaVcD6.d.mts} +243 -40
- package/dist/index-BFRaVcD6.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +29 -25
- package/dist/{load-CBcmDIot.mjs → load-DDqMMvZL.mjs} +2 -2
- package/dist/{load-CBcmDIot.mjs.map → load-DDqMMvZL.mjs.map} +1 -1
- package/dist/{loader-DeiBJEMe.mjs → loader-CKLbBnhK.mjs} +32 -10
- package/dist/loader-CKLbBnhK.mjs.map +1 -0
- package/dist/{manifest-schema-V30qsMft.mjs → manifest-schema-DqWNC3lM.mjs} +45 -3
- package/dist/manifest-schema-DqWNC3lM.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +3 -3
- package/dist/{media-DqHVh136.mjs → media-BW32b4gi.mjs} +4 -7
- package/dist/media-BW32b4gi.mjs.map +1 -0
- package/dist/{mode-CpNnGkPz.mjs → mode-ier8jbBk.mjs} +1 -1
- package/dist/mode-ier8jbBk.mjs.map +1 -0
- package/dist/options-BVp3UsTS.mjs +117 -0
- package/dist/options-BVp3UsTS.mjs.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{placeholder-tzpqGWII.d.mts → placeholder-BE4o_2dc.d.mts} +1 -1
- package/dist/{placeholder-tzpqGWII.d.mts.map → placeholder-BE4o_2dc.d.mts.map} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs → placeholder-CIJejMlK.mjs} +1 -1
- package/dist/placeholder-CIJejMlK.mjs.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/public-url-DByxYjUw.mjs +51 -0
- package/dist/public-url-DByxYjUw.mjs.map +1 -0
- package/dist/{query-g4Ug-9j9.mjs → query-Cg9ZKRQ0.mjs} +114 -16
- package/dist/query-Cg9ZKRQ0.mjs.map +1 -0
- package/dist/{redirect-CN0Rt9Ob.mjs → redirect-BhUBKRc1.mjs} +13 -8
- package/dist/redirect-BhUBKRc1.mjs.map +1 -0
- package/dist/{registry-Ci3WxVAr.mjs → registry-Dw70ChxB.mjs} +69 -11
- package/dist/registry-Dw70ChxB.mjs.map +1 -0
- package/dist/{request-cache-DiR961CV.mjs → request-cache-B-bmkipQ.mjs} +1 -1
- package/dist/request-cache-B-bmkipQ.mjs.map +1 -0
- package/dist/runner-Bnoj7vjK.d.mts +44 -0
- package/dist/runner-Bnoj7vjK.d.mts.map +1 -0
- package/dist/{runner-tQ7BJ4T7.mjs → runner-C7ADox5q.mjs} +185 -55
- package/dist/{runner-tQ7BJ4T7.mjs.map → runner-C7ADox5q.mjs.map} +1 -1
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +4 -4
- package/dist/{search-B0effn3j.mjs → search-dOGEccMa.mjs} +341 -152
- package/dist/search-dOGEccMa.mjs.map +1 -0
- package/dist/secrets-CW3reAnU.mjs +314 -0
- package/dist/secrets-CW3reAnU.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +15 -14
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.d.mts.map +1 -1
- package/dist/storage/s3.mjs +4 -4
- package/dist/storage/s3.mjs.map +1 -1
- package/dist/{taxonomies-K2z0Uhnj.mjs → taxonomies-ZlRtD6AG.mjs} +14 -7
- package/dist/taxonomies-ZlRtD6AG.mjs.map +1 -0
- package/dist/{tokens-BFPFx3CA.mjs → tokens-D7zMmWi2.mjs} +2 -2
- package/dist/{tokens-BFPFx3CA.mjs.map → tokens-D7zMmWi2.mjs.map} +1 -1
- package/dist/{transport-BykRfpyy.mjs → transport-BeMCmin1.mjs} +6 -5
- package/dist/{transport-BykRfpyy.mjs.map → transport-BeMCmin1.mjs.map} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts → transport-DNEfeMaU.d.mts} +1 -1
- package/dist/{transport-H4Iwx7tC.d.mts.map → transport-DNEfeMaU.d.mts.map} +1 -1
- package/dist/types-4fVtCIm0.mjs +68 -0
- package/dist/types-4fVtCIm0.mjs.map +1 -0
- package/dist/{types-CnZYHyLW.d.mts → types-BSyXeCFW.d.mts} +24 -2
- package/dist/{types-CnZYHyLW.d.mts.map → types-BSyXeCFW.d.mts.map} +1 -1
- package/dist/{types-DgrIP0tF.d.mts → types-BuBIptGk.d.mts} +80 -106
- package/dist/types-BuBIptGk.d.mts.map +1 -0
- package/dist/{types-BH2L167P.mjs → types-CDbKp7ND.mjs} +1 -1
- package/dist/{types-BH2L167P.mjs.map → types-CDbKp7ND.mjs.map} +1 -1
- package/dist/{types-DDS4MxsT.mjs → types-CIOg5AR8.mjs} +1 -1
- package/dist/{types-DDS4MxsT.mjs.map → types-CIOg5AR8.mjs.map} +1 -1
- package/dist/{types-6CUZRrZP.d.mts → types-CJsYGpco.d.mts} +24 -2
- package/dist/{types-6CUZRrZP.d.mts.map → types-CJsYGpco.d.mts.map} +1 -1
- package/dist/types-CRxNbK-Z.mjs +68 -0
- package/dist/types-CRxNbK-Z.mjs.map +1 -0
- package/dist/{types-C2v0c34j.d.mts → types-CrtWgIvl.d.mts} +1 -1
- package/dist/{types-C2v0c34j.d.mts.map → types-CrtWgIvl.d.mts.map} +1 -1
- package/dist/{types-CFWjXmus.d.mts → types-M78DQ1lx.d.mts} +1 -1
- package/dist/{types-CFWjXmus.d.mts.map → types-M78DQ1lx.d.mts.map} +1 -1
- package/dist/{validate-CqsNItbt.mjs → validate-Baqf0slj.mjs} +3 -3
- package/dist/{validate-CqsNItbt.mjs.map → validate-Baqf0slj.mjs.map} +1 -1
- package/dist/{validate-kM8Pjuf7.d.mts → validate-BfQh_C_y.d.mts} +4 -4
- package/dist/{validate-kM8Pjuf7.d.mts.map → validate-BfQh_C_y.d.mts.map} +1 -1
- package/dist/validation-BfEI7tNe.mjs +144 -0
- package/dist/validation-BfEI7tNe.mjs.map +1 -0
- package/dist/version-DoxrVdYf.mjs +7 -0
- package/dist/{version-BnTKdfam.mjs.map → version-DoxrVdYf.mjs.map} +1 -1
- package/dist/zod-generator-CC0xNe_K.mjs +132 -0
- package/dist/zod-generator-CC0xNe_K.mjs.map +1 -0
- package/locals.d.ts +1 -6
- package/package.json +21 -7
- package/src/api/auth-storage.ts +37 -0
- package/src/api/error.ts +6 -0
- package/src/api/errors.ts +8 -0
- package/src/api/handlers/comments.ts +19 -4
- package/src/api/handlers/content.ts +151 -4
- package/src/api/handlers/device-flow.ts +5 -0
- package/src/api/handlers/index.ts +2 -0
- package/src/api/handlers/marketplace.ts +11 -4
- package/src/api/handlers/media.ts +8 -1
- package/src/api/handlers/menus.ts +160 -21
- package/src/api/handlers/oauth-authorization.ts +72 -33
- package/src/api/handlers/redirects.ts +16 -3
- package/src/api/handlers/revision.ts +23 -14
- package/src/api/handlers/sections.ts +8 -1
- package/src/api/handlers/taxonomies.ts +131 -22
- package/src/api/handlers/validation.ts +212 -0
- package/src/api/openapi/document.ts +4 -1
- package/src/api/public-url.ts +54 -5
- package/src/api/route-utils.ts +14 -0
- package/src/api/schemas/comments.ts +2 -2
- package/src/api/schemas/common.ts +1 -1
- package/src/api/schemas/content.ts +17 -0
- package/src/api/schemas/sections.ts +3 -3
- package/src/api/schemas/setup.ts +8 -0
- package/src/api/schemas/users.ts +1 -1
- package/src/api/schemas/widgets.ts +12 -10
- package/src/api/setup-complete.ts +40 -0
- package/src/api/types.ts +5 -1
- package/src/astro/integration/index.ts +30 -2
- package/src/astro/integration/routes.ts +28 -0
- package/src/astro/integration/runtime.ts +49 -1
- package/src/astro/integration/virtual-modules.ts +73 -2
- package/src/astro/integration/vite-config.ts +49 -13
- package/src/astro/middleware/auth.ts +34 -6
- package/src/astro/middleware/redirect.ts +29 -16
- package/src/astro/middleware/request-context.ts +15 -5
- package/src/astro/middleware.ts +41 -10
- package/src/astro/routes/PluginRegistry.tsx +10 -1
- package/src/astro/routes/api/auth/invite/complete.ts +6 -1
- package/src/astro/routes/api/auth/mode.ts +57 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +23 -3
- package/src/astro/routes/api/auth/oauth/[provider].ts +10 -4
- package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
- package/src/astro/routes/api/auth/signup/complete.ts +6 -1
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
- package/src/astro/routes/api/content/[collection]/index.ts +1 -9
- package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
- package/src/astro/routes/api/import/wordpress/media.ts +2 -7
- package/src/astro/routes/api/import/wordpress/prepare.ts +9 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
- package/src/astro/routes/api/manifest.ts +62 -45
- package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
- package/src/astro/routes/api/openapi.json.ts +27 -10
- package/src/astro/routes/api/redirects/404s/index.ts +10 -4
- package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
- package/src/astro/routes/api/redirects/[id].ts +10 -4
- package/src/astro/routes/api/redirects/index.ts +7 -3
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
- package/src/astro/routes/api/schema/collections/index.ts +1 -1
- package/src/astro/routes/api/search/index.ts +10 -2
- package/src/astro/routes/api/sections/[slug].ts +10 -4
- package/src/astro/routes/api/sections/index.ts +7 -3
- package/src/astro/routes/api/settings/email.ts +4 -9
- package/src/astro/routes/api/setup/admin-verify.ts +6 -1
- package/src/astro/routes/api/setup/admin.ts +8 -2
- package/src/astro/routes/api/setup/index.ts +2 -2
- package/src/astro/routes/api/setup/status.ts +3 -1
- package/src/astro/routes/api/snapshot.ts +44 -18
- package/src/astro/routes/api/taxonomies/index.ts +0 -1
- package/src/astro/routes/api/themes/preview.ts +11 -5
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +4 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +4 -1
- package/src/astro/routes/api/widget-areas/[name].ts +4 -1
- package/src/astro/routes/api/widget-areas/index.ts +4 -1
- package/src/astro/types.ts +32 -3
- package/src/auth/allowed-origins.ts +168 -0
- package/src/auth/mode.ts +15 -3
- package/src/auth/passkey-config.ts +35 -13
- package/src/auth/providers/github-admin.tsx +29 -0
- package/src/auth/providers/github.ts +31 -0
- package/src/auth/providers/google-admin.tsx +44 -0
- package/src/auth/providers/google.ts +31 -0
- package/src/auth/types.ts +114 -4
- package/src/bylines/index.ts +37 -88
- package/src/cli/commands/auth.ts +28 -6
- package/src/cli/commands/bundle-utils.ts +11 -2
- package/src/cli/commands/bundle.ts +31 -9
- package/src/cli/commands/content.ts +13 -0
- package/src/cli/commands/login.ts +8 -1
- package/src/cli/commands/publish.ts +24 -0
- package/src/cli/commands/secrets.ts +183 -0
- package/src/cli/credentials.ts +1 -1
- package/src/cli/index.ts +5 -1
- package/src/client/index.ts +4 -4
- package/src/client/transport.ts +17 -7
- package/src/components/Break.astro +2 -2
- package/src/components/EmDashHead.astro +18 -13
- package/src/components/EmDashImage.astro +7 -6
- package/src/components/Embed.astro +1 -1
- package/src/components/Gallery.astro +6 -4
- package/src/components/Image.astro +9 -4
- package/src/components/InlinePortableTextEditor.tsx +106 -19
- package/src/components/LiveSearch.astro +5 -14
- package/src/config/secrets.ts +528 -0
- package/src/database/dialect-helpers.ts +50 -0
- package/src/database/migrations/034_published_at_index.ts +1 -1
- package/src/database/migrations/035_bounded_404_log.ts +56 -39
- package/src/database/migrations/runner.ts +156 -23
- package/src/database/repositories/audit.ts +6 -8
- package/src/database/repositories/byline.ts +6 -8
- package/src/database/repositories/comment.ts +12 -16
- package/src/database/repositories/content.ts +76 -52
- package/src/database/repositories/index.ts +1 -1
- package/src/database/repositories/media.ts +10 -13
- package/src/database/repositories/plugin-storage.ts +4 -6
- package/src/database/repositories/redirect.ts +26 -19
- package/src/database/repositories/taxonomy.ts +40 -3
- package/src/database/repositories/types.ts +57 -8
- package/src/database/repositories/user.ts +6 -8
- package/src/db/libsql.ts +1 -3
- package/src/db/sqlite.ts +2 -5
- package/src/emdash-runtime.ts +388 -247
- package/src/index.ts +14 -1
- package/src/loader.ts +30 -6
- package/src/mcp/server.ts +781 -141
- package/src/media/normalize.ts +1 -1
- package/src/media/url.ts +78 -0
- package/src/page/site-identity.ts +58 -0
- package/src/plugins/adapt-sandbox-entry.ts +22 -10
- package/src/plugins/context.ts +13 -10
- package/src/plugins/define-plugin.ts +40 -12
- package/src/plugins/email-console.ts +10 -3
- package/src/plugins/hooks.ts +34 -19
- package/src/plugins/index.ts +9 -0
- package/src/plugins/manifest-schema.ts +49 -2
- package/src/plugins/types.ts +174 -13
- package/src/preview/urls.ts +23 -3
- package/src/query.ts +149 -6
- package/src/redirects/cache.ts +38 -18
- package/src/request-cache.ts +3 -0
- package/src/schema/registry.ts +97 -5
- package/src/schema/zod-generator.ts +27 -5
- package/src/search/fts-manager.ts +0 -2
- package/src/search/query.ts +111 -26
- package/src/search/types.ts +8 -1
- package/src/sections/index.ts +7 -9
- package/src/seed/apply.ts +2 -0
- package/src/settings/index.ts +80 -6
- package/src/settings/types.ts +23 -1
- package/src/storage/s3.ts +12 -6
- package/src/taxonomies/index.ts +11 -1
- package/src/virtual-modules.d.ts +21 -1
- package/src/widgets/index.ts +1 -1
- package/dist/apply-5uslYdUu.mjs.map +0 -1
- package/dist/byline-C4OVd8b3.mjs.map +0 -1
- package/dist/bylines-hPTW79hw.mjs +0 -157
- package/dist/bylines-hPTW79hw.mjs.map +0 -1
- package/dist/cache-BkKBuIvS.mjs +0 -56
- package/dist/cache-BkKBuIvS.mjs.map +0 -1
- package/dist/chunk-ClPoSABd.mjs +0 -21
- package/dist/content-D7J5y73J.mjs.map +0 -1
- package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
- package/dist/error-CiYn9yDu.mjs.map +0 -1
- package/dist/index-De6_Xv3v.d.mts.map +0 -1
- package/dist/loader-DeiBJEMe.mjs.map +0 -1
- package/dist/manifest-schema-V30qsMft.mjs.map +0 -1
- package/dist/media-DqHVh136.mjs.map +0 -1
- package/dist/mode-CpNnGkPz.mjs.map +0 -1
- package/dist/placeholder-C-fk5hYI.mjs.map +0 -1
- package/dist/query-g4Ug-9j9.mjs.map +0 -1
- package/dist/redirect-CN0Rt9Ob.mjs.map +0 -1
- package/dist/registry-Ci3WxVAr.mjs.map +0 -1
- package/dist/request-cache-DiR961CV.mjs.map +0 -1
- package/dist/runner-BR2xKwhn.d.mts +0 -34
- package/dist/runner-BR2xKwhn.d.mts.map +0 -1
- package/dist/search-B0effn3j.mjs.map +0 -1
- package/dist/taxonomies-K2z0Uhnj.mjs.map +0 -1
- package/dist/types-CMMN0pNg.mjs +0 -31
- package/dist/types-CMMN0pNg.mjs.map +0 -1
- package/dist/types-DgrIP0tF.d.mts.map +0 -1
- package/dist/version-BnTKdfam.mjs +0 -7
package/src/bylines/index.ts
CHANGED
|
@@ -12,7 +12,6 @@ import { BylineRepository } from "../database/repositories/byline.js";
|
|
|
12
12
|
import type { BylineSummary, ContentBylineCredit } from "../database/repositories/types.js";
|
|
13
13
|
import { validateIdentifier } from "../database/validate.js";
|
|
14
14
|
import { getDb } from "../loader.js";
|
|
15
|
-
import { chunks, SQL_BATCH_SIZE } from "../utils/chunks.js";
|
|
16
15
|
import { isMissingTableError } from "../utils/db-errors.js";
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -73,15 +72,11 @@ export async function getBylineBySlug(slug: string): Promise<BylineSummary | nul
|
|
|
73
72
|
* but the entry has an `authorId`, falls back to the user-linked byline
|
|
74
73
|
* (marked as source: "inferred").
|
|
75
74
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* for (const credit of bylines) {
|
|
82
|
-
* console.log(credit.byline.displayName, credit.roleLabel);
|
|
83
|
-
* }
|
|
84
|
-
* ```
|
|
75
|
+
* Internal: not re-exported from the `emdash` package entry point. Every
|
|
76
|
+
* entry returned by `getEmDashCollection` / `getEmDashEntry` already has
|
|
77
|
+
* `data.bylines` populated by `hydrateEntryBylines` (which uses the batch
|
|
78
|
+
* helper `getBylinesForEntries` directly). Site code should read those
|
|
79
|
+
* fields rather than calling this function.
|
|
85
80
|
*/
|
|
86
81
|
export async function getEntryBylines(
|
|
87
82
|
collection: string,
|
|
@@ -108,55 +103,55 @@ export async function getEntryBylines(
|
|
|
108
103
|
return [];
|
|
109
104
|
}
|
|
110
105
|
|
|
106
|
+
/**
|
|
107
|
+
* An entry reference for batch byline lookups.
|
|
108
|
+
*
|
|
109
|
+
* `authorId` is read directly from the row when computing the inferred-byline
|
|
110
|
+
* fallback — passing it in avoids a redundant `SELECT id, author_id` against
|
|
111
|
+
* the content table after every list/entry fetch.
|
|
112
|
+
*/
|
|
113
|
+
export interface BylineEntry {
|
|
114
|
+
id: string;
|
|
115
|
+
authorId: string | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
111
118
|
/**
|
|
112
119
|
* Batch-fetch byline credits for multiple content entries in a single query.
|
|
113
120
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
121
|
+
* Internal: consumed by `hydrateEntryBylines` in `query.ts` so that every
|
|
122
|
+
* entry returned from `getEmDashCollection` / `getEmDashEntry` already has
|
|
123
|
+
* `data.bylines` populated. Site code should rely on that eager hydration
|
|
124
|
+
* rather than calling this directly -- this function is not re-exported
|
|
125
|
+
* from the `emdash` package entry point.
|
|
116
126
|
*
|
|
117
127
|
* @param collection - The collection slug (e.g., "posts")
|
|
118
|
-
* @param
|
|
128
|
+
* @param entries - Entry id + authorId pairs (authorId is already on the row)
|
|
119
129
|
* @returns Map from entry ID to array of byline credits
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* ```ts
|
|
123
|
-
* import { getBylinesForEntries, getEmDashCollection } from "emdash";
|
|
124
|
-
*
|
|
125
|
-
* const { entries } = await getEmDashCollection("posts");
|
|
126
|
-
* const ids = entries.map(e => e.data.id);
|
|
127
|
-
* const bylinesMap = await getBylinesForEntries("posts", ids);
|
|
128
|
-
*
|
|
129
|
-
* for (const entry of entries) {
|
|
130
|
-
* const bylines = bylinesMap.get(entry.data.id) ?? [];
|
|
131
|
-
* // render bylines
|
|
132
|
-
* }
|
|
133
|
-
* ```
|
|
134
130
|
*/
|
|
135
131
|
export async function getBylinesForEntries(
|
|
136
132
|
collection: string,
|
|
137
|
-
|
|
133
|
+
entries: BylineEntry[],
|
|
138
134
|
): Promise<Map<string, ContentBylineCredit[]>> {
|
|
139
135
|
validateIdentifier(collection, "collection");
|
|
140
136
|
const result = new Map<string, ContentBylineCredit[]>();
|
|
141
137
|
|
|
142
|
-
|
|
143
|
-
for (const id of entryIds) {
|
|
138
|
+
for (const { id } of entries) {
|
|
144
139
|
result.set(id, []);
|
|
145
140
|
}
|
|
146
141
|
|
|
147
|
-
if (
|
|
142
|
+
if (entries.length === 0) {
|
|
148
143
|
return result;
|
|
149
144
|
}
|
|
150
145
|
|
|
151
146
|
const db = await getDb();
|
|
152
147
|
const repo = new BylineRepository(db);
|
|
148
|
+
const entryIds = entries.map((e) => e.id);
|
|
153
149
|
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// `isMissingTableError` catch below and return empty results.
|
|
150
|
+
// Sites with no bylines get an empty map back for one query — the previous
|
|
151
|
+
// "has any bylines" probe traded an extra round-trip on every request to
|
|
152
|
+
// save that one query on empty sites, which is exactly backwards for the
|
|
153
|
+
// common case. Pre-migration databases (bylines table missing) fall
|
|
154
|
+
// through to the `isMissingTableError` catch below and return empty.
|
|
160
155
|
let bylinesMap;
|
|
161
156
|
try {
|
|
162
157
|
bylinesMap = await repo.getContentBylinesMany(collection, entryIds);
|
|
@@ -165,32 +160,17 @@ export async function getBylinesForEntries(
|
|
|
165
160
|
throw error;
|
|
166
161
|
}
|
|
167
162
|
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
for (const id of entryIds) {
|
|
173
|
-
if (!bylinesMap.has(id)) {
|
|
174
|
-
// Need to check author_id for this entry — but we only have the IDs,
|
|
175
|
-
// so batch-fetch them from the content table
|
|
176
|
-
fallbackEntryIds.push(id);
|
|
163
|
+
const needsFallback = new Map<string, string>();
|
|
164
|
+
for (const { id, authorId } of entries) {
|
|
165
|
+
if (!bylinesMap.has(id) && authorId) {
|
|
166
|
+
needsFallback.set(id, authorId);
|
|
177
167
|
}
|
|
178
168
|
}
|
|
179
169
|
|
|
180
|
-
// Batch-fetch author_ids for entries that need fallback
|
|
181
|
-
if (fallbackEntryIds.length > 0) {
|
|
182
|
-
const authorMap = await getAuthorIds(db, collection, fallbackEntryIds);
|
|
183
|
-
for (const [entryId, authorId] of authorMap) {
|
|
184
|
-
needsFallback.set(entryId, authorId);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// 3. Batch fetch user-linked bylines for fallback
|
|
189
170
|
const uniqueAuthorIds = [...new Set(needsFallback.values())];
|
|
190
171
|
const authorBylineMap = await repo.findByUserIds(uniqueAuthorIds);
|
|
191
172
|
|
|
192
|
-
|
|
193
|
-
for (const id of entryIds) {
|
|
173
|
+
for (const { id } of entries) {
|
|
194
174
|
const explicit = bylinesMap.get(id);
|
|
195
175
|
if (explicit && explicit.length > 0) {
|
|
196
176
|
result.set(
|
|
@@ -205,11 +185,8 @@ export async function getBylinesForEntries(
|
|
|
205
185
|
const fallback = authorBylineMap.get(authorId);
|
|
206
186
|
if (fallback) {
|
|
207
187
|
result.set(id, [{ byline: fallback, sortOrder: 0, roleLabel: null, source: "inferred" }]);
|
|
208
|
-
continue;
|
|
209
188
|
}
|
|
210
189
|
}
|
|
211
|
-
|
|
212
|
-
// Already initialized with empty array
|
|
213
190
|
}
|
|
214
191
|
|
|
215
192
|
return result;
|
|
@@ -235,31 +212,3 @@ async function getAuthorId(
|
|
|
235
212
|
|
|
236
213
|
return result.rows[0]?.author_id ?? null;
|
|
237
214
|
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Batch-fetch author_ids for multiple content entries.
|
|
241
|
-
* Returns Map<entryId, authorId> (only entries with non-null author_id).
|
|
242
|
-
*/
|
|
243
|
-
async function getAuthorIds(
|
|
244
|
-
db: Awaited<ReturnType<typeof getDb>>,
|
|
245
|
-
collection: string,
|
|
246
|
-
entryIds: string[],
|
|
247
|
-
): Promise<Map<string, string>> {
|
|
248
|
-
validateIdentifier(collection, "collection");
|
|
249
|
-
const tableName = `ec_${collection}`;
|
|
250
|
-
|
|
251
|
-
const map = new Map<string, string>();
|
|
252
|
-
for (const chunk of chunks(entryIds, SQL_BATCH_SIZE)) {
|
|
253
|
-
const result = await sql<{ id: string; author_id: string | null }>`
|
|
254
|
-
SELECT id, author_id FROM ${sql.ref(tableName)}
|
|
255
|
-
WHERE id IN (${sql.join(chunk.map((id) => sql`${id}`))})
|
|
256
|
-
`.execute(db);
|
|
257
|
-
|
|
258
|
-
for (const row of result.rows) {
|
|
259
|
-
if (row.author_id) {
|
|
260
|
-
map.set(row.id, row.author_id);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return map;
|
|
265
|
-
}
|
package/src/cli/commands/auth.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth CLI commands
|
|
2
|
+
* Auth CLI commands (deprecated)
|
|
3
|
+
*
|
|
4
|
+
* Kept as a deprecated alias for backwards compatibility. The original
|
|
5
|
+
* `emdash auth secret` was documented in published docs and is plausibly
|
|
6
|
+
* scripted in user CI (e.g. `npx emdash auth secret >> .env`). Removing
|
|
7
|
+
* it outright would break those scripts on minor-version upgrade.
|
|
8
|
+
*
|
|
9
|
+
* The command still emits an `EMDASH_AUTH_SECRET=<32-byte-base64url>`
|
|
10
|
+
* line, unchanged. `EMDASH_AUTH_SECRET` itself is now legacy: it's only
|
|
11
|
+
* read as a fallback source for the commenter-IP hash salt so installs
|
|
12
|
+
* upgrading from a prior version keep stable IP hashes (and therefore
|
|
13
|
+
* stable rate-limit buckets). New installs don't need to set it.
|
|
14
|
+
*
|
|
15
|
+
* The deprecation note steers users toward `emdash secrets generate`
|
|
16
|
+
* (which emits a different, versioned `emdash_enc_v1_*` value for
|
|
17
|
+
* `EMDASH_ENCRYPTION_KEY` — used to encrypt plugin secrets at rest).
|
|
18
|
+
*
|
|
19
|
+
* Will be removed in a future minor.
|
|
3
20
|
*/
|
|
4
21
|
|
|
5
22
|
import { defineCommand } from "citty";
|
|
@@ -8,9 +25,6 @@ import pc from "picocolors";
|
|
|
8
25
|
|
|
9
26
|
import { encodeBase64url } from "../../utils/base64.js";
|
|
10
27
|
|
|
11
|
-
/**
|
|
12
|
-
* Generate a cryptographically secure auth secret
|
|
13
|
-
*/
|
|
14
28
|
function generateAuthSecret(): string {
|
|
15
29
|
const bytes = new Uint8Array(32);
|
|
16
30
|
crypto.getRandomValues(bytes);
|
|
@@ -20,11 +34,13 @@ function generateAuthSecret(): string {
|
|
|
20
34
|
const secretCommand = defineCommand({
|
|
21
35
|
meta: {
|
|
22
36
|
name: "secret",
|
|
23
|
-
description: "Generate a
|
|
37
|
+
description: "[DEPRECATED] Generate a value for legacy EMDASH_AUTH_SECRET",
|
|
24
38
|
},
|
|
25
39
|
run() {
|
|
26
40
|
const secret = generateAuthSecret();
|
|
27
41
|
|
|
42
|
+
// Match the original behavior verbatim: pretty-printed name=value
|
|
43
|
+
// on stdout (most scripts piped this to a file expecting that shape).
|
|
28
44
|
consola.log("");
|
|
29
45
|
consola.log(pc.bold("Generated auth secret:"));
|
|
30
46
|
consola.log("");
|
|
@@ -32,13 +48,19 @@ const secretCommand = defineCommand({
|
|
|
32
48
|
consola.log("");
|
|
33
49
|
consola.log(pc.dim("Add this to your environment variables."));
|
|
34
50
|
consola.log("");
|
|
51
|
+
// Deprecation note on stderr so it doesn't break stdout consumers.
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
`${pc.yellow("Note:")} ${pc.bold("emdash auth secret")} is deprecated and will be removed in a future minor. ` +
|
|
54
|
+
`${pc.cyan("EMDASH_AUTH_SECRET")} itself is now optional — it's only used as a legacy fallback for the commenter-IP hash salt. ` +
|
|
55
|
+
`For encrypting plugin secrets at rest, use ${pc.bold("emdash secrets generate")} (a different secret: ${pc.cyan("EMDASH_ENCRYPTION_KEY")}).\n`,
|
|
56
|
+
);
|
|
35
57
|
},
|
|
36
58
|
});
|
|
37
59
|
|
|
38
60
|
export const authCommand = defineCommand({
|
|
39
61
|
meta: {
|
|
40
62
|
name: "auth",
|
|
41
|
-
description: "Authentication utilities",
|
|
63
|
+
description: "[DEPRECATED] Authentication utilities (use `emdash secrets` for new flows)",
|
|
42
64
|
},
|
|
43
65
|
subCommands: {
|
|
44
66
|
secret: secretCommand,
|
|
@@ -30,8 +30,17 @@ export const ICON_SIZE = 256;
|
|
|
30
30
|
|
|
31
31
|
// ── Regex patterns (module-scope to avoid re-compilation) ────────────────────
|
|
32
32
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Matches Node.js built-in imports in bundled output:
|
|
35
|
+
* - require("node:xxx") / require("xxx")
|
|
36
|
+
* - import("node:xxx") / import("xxx")
|
|
37
|
+
* - import X from "node:xxx" / import { X } from "node:xxx"
|
|
38
|
+
* - import * as X from "node:xxx"
|
|
39
|
+
* - export { X } from "node:xxx"
|
|
40
|
+
* Captures the base module name (e.g. "fs" from "node:fs/promises").
|
|
41
|
+
*/
|
|
42
|
+
const NODE_BUILTIN_IMPORT_RE =
|
|
43
|
+
/(?:import|export|require)\s*(?:\(|[^(]*?\bfrom\s+)["'](?:node:)?([a-z_]+)(?:\/[^"']*)?\s*["']\)?/g;
|
|
35
44
|
const LEADING_DOT_SLASH_RE = /^\.\//;
|
|
36
45
|
const DIST_PREFIX_RE = /^dist\//;
|
|
37
46
|
const MJS_EXT_RE = /\.m?js$/;
|
|
@@ -20,6 +20,7 @@ import { resolve, join, extname, basename } from "node:path";
|
|
|
20
20
|
import { defineCommand } from "citty";
|
|
21
21
|
import consola from "consola";
|
|
22
22
|
|
|
23
|
+
import { CAPABILITY_RENAMES, isDeprecatedCapability } from "../../plugins/types.js";
|
|
23
24
|
import type { ResolvedPlugin } from "../../plugins/types.js";
|
|
24
25
|
import {
|
|
25
26
|
fileExists,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
ICON_SIZE,
|
|
39
40
|
} from "./bundle-utils.js";
|
|
40
41
|
|
|
41
|
-
const TS_EXT_RE = /\.tsx
|
|
42
|
+
const TS_EXT_RE = /\.(tsx?|[mc]?js)$/;
|
|
42
43
|
const SLASH_RE = /\//g;
|
|
43
44
|
const LEADING_AT_RE = /^@/;
|
|
44
45
|
const emdash_SCOPE_RE = /^@emdash-cms\//;
|
|
@@ -163,6 +164,8 @@ export const bundleCommand = defineCommand({
|
|
|
163
164
|
const tmpDir = join(pluginDir, ".emdash-bundle-tmp");
|
|
164
165
|
|
|
165
166
|
try {
|
|
167
|
+
// Clean up any stale temp directory from a previous failed run
|
|
168
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
166
169
|
await mkdir(tmpDir, { recursive: true });
|
|
167
170
|
|
|
168
171
|
// Build main entry to extract manifest.
|
|
@@ -522,20 +525,39 @@ export const bundleCommand = defineCommand({
|
|
|
522
525
|
}
|
|
523
526
|
}
|
|
524
527
|
|
|
525
|
-
// Check capabilities warnings
|
|
526
|
-
|
|
528
|
+
// Check capabilities warnings — use canonical names. Deprecated
|
|
529
|
+
// names are accepted (and warned about separately below) so we
|
|
530
|
+
// also check the legacy aliases here for the duration of the
|
|
531
|
+
// deprecation window.
|
|
532
|
+
const declaresUnrestricted =
|
|
533
|
+
manifest.capabilities.includes("network:request:unrestricted") ||
|
|
534
|
+
manifest.capabilities.includes("network:fetch:any");
|
|
535
|
+
const declaresHostRestricted =
|
|
536
|
+
manifest.capabilities.includes("network:request") ||
|
|
537
|
+
manifest.capabilities.includes("network:fetch");
|
|
538
|
+
if (declaresUnrestricted) {
|
|
527
539
|
consola.warn(
|
|
528
|
-
"Plugin declares unrestricted network access (network:
|
|
540
|
+
"Plugin declares unrestricted network access (network:request:unrestricted) — it can make requests to any host",
|
|
529
541
|
);
|
|
530
|
-
} else if (
|
|
531
|
-
manifest.capabilities.includes("network:fetch") &&
|
|
532
|
-
manifest.allowedHosts.length === 0
|
|
533
|
-
) {
|
|
542
|
+
} else if (declaresHostRestricted && manifest.allowedHosts.length === 0) {
|
|
534
543
|
consola.warn(
|
|
535
|
-
"Plugin declares network:
|
|
544
|
+
"Plugin declares network:request capability but no allowedHosts — all requests will be blocked",
|
|
536
545
|
);
|
|
537
546
|
}
|
|
538
547
|
|
|
548
|
+
// Warn for each deprecated capability used. The warning points
|
|
549
|
+
// to the new name so the fix is mechanical. We continue (not
|
|
550
|
+
// error) here — the hard fail lives in `publish` so authors
|
|
551
|
+
// can still build and test locally.
|
|
552
|
+
const deprecatedCaps = manifest.capabilities.filter(isDeprecatedCapability);
|
|
553
|
+
if (deprecatedCaps.length > 0) {
|
|
554
|
+
consola.warn("Plugin uses deprecated capability names. Rename them before publishing:");
|
|
555
|
+
for (const cap of deprecatedCaps) {
|
|
556
|
+
const replacement = CAPABILITY_RENAMES[cap];
|
|
557
|
+
consola.warn(` ${cap} → ${replacement}`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
539
561
|
// Check for features that won't work in sandboxed mode
|
|
540
562
|
if (
|
|
541
563
|
resolvedPlugin.admin?.portableTextBlocks &&
|
|
@@ -9,6 +9,7 @@ import { readFile } from "node:fs/promises";
|
|
|
9
9
|
import { defineCommand } from "citty";
|
|
10
10
|
import { consola } from "consola";
|
|
11
11
|
|
|
12
|
+
import { convertDataForRead } from "../../client/portable-text.js";
|
|
12
13
|
import { connectionArgs, createClientFromArgs } from "../client-factory.js";
|
|
13
14
|
import { configureOutputMode, output } from "../output.js";
|
|
14
15
|
|
|
@@ -144,6 +145,13 @@ const getCommand = defineCommand({
|
|
|
144
145
|
const comparison = await client.compare(args.collection, args.id);
|
|
145
146
|
if (comparison.hasChanges && comparison.draft) {
|
|
146
147
|
item.data = comparison.draft;
|
|
148
|
+
// The comparison endpoint returns raw PT data. Apply the same
|
|
149
|
+
// PT-to-markdown conversion that `client.get` does, unless --raw.
|
|
150
|
+
if (!args.raw && item.data) {
|
|
151
|
+
const col = await client.collection(args.collection);
|
|
152
|
+
const fields = col.fields.map((f) => ({ slug: f.slug, type: f.type }));
|
|
153
|
+
item.data = convertDataForRead(item.data, fields, false);
|
|
154
|
+
}
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
157
|
|
|
@@ -278,6 +286,7 @@ const deleteCommand = defineCommand({
|
|
|
278
286
|
try {
|
|
279
287
|
const client = createClientFromArgs(args);
|
|
280
288
|
await client.delete(args.collection, args.id);
|
|
289
|
+
output({ success: true }, args);
|
|
281
290
|
consola.success(`Deleted ${args.collection}/${args.id}`);
|
|
282
291
|
} catch (error) {
|
|
283
292
|
consola.error(error instanceof Error ? error.message : "Unknown error");
|
|
@@ -306,6 +315,7 @@ const publishCommand = defineCommand({
|
|
|
306
315
|
try {
|
|
307
316
|
const client = createClientFromArgs(args);
|
|
308
317
|
await client.publish(args.collection, args.id);
|
|
318
|
+
output({ success: true }, args);
|
|
309
319
|
consola.success(`Published ${args.collection}/${args.id}`);
|
|
310
320
|
} catch (error) {
|
|
311
321
|
consola.error(error instanceof Error ? error.message : "Unknown error");
|
|
@@ -334,6 +344,7 @@ const unpublishCommand = defineCommand({
|
|
|
334
344
|
try {
|
|
335
345
|
const client = createClientFromArgs(args);
|
|
336
346
|
await client.unpublish(args.collection, args.id);
|
|
347
|
+
output({ success: true }, args);
|
|
337
348
|
consola.success(`Unpublished ${args.collection}/${args.id}`);
|
|
338
349
|
} catch (error) {
|
|
339
350
|
consola.error(error instanceof Error ? error.message : "Unknown error");
|
|
@@ -367,6 +378,7 @@ const scheduleCommand = defineCommand({
|
|
|
367
378
|
try {
|
|
368
379
|
const client = createClientFromArgs(args);
|
|
369
380
|
await client.schedule(args.collection, args.id, { at: args.at });
|
|
381
|
+
output({ success: true }, args);
|
|
370
382
|
consola.success(`Scheduled ${args.collection}/${args.id} for ${args.at}`);
|
|
371
383
|
} catch (error) {
|
|
372
384
|
consola.error(error instanceof Error ? error.message : "Unknown error");
|
|
@@ -395,6 +407,7 @@ const restoreCommand = defineCommand({
|
|
|
395
407
|
try {
|
|
396
408
|
const client = createClientFromArgs(args);
|
|
397
409
|
await client.restore(args.collection, args.id);
|
|
410
|
+
output({ success: true }, args);
|
|
398
411
|
consola.success(`Restored ${args.collection}/${args.id}`);
|
|
399
412
|
} catch (error) {
|
|
400
413
|
consola.error(error instanceof Error ? error.message : "Unknown error");
|
|
@@ -459,7 +459,14 @@ export const whoamiCommand = defineCommand({
|
|
|
459
459
|
},
|
|
460
460
|
);
|
|
461
461
|
if (refreshRes.ok) {
|
|
462
|
-
const
|
|
462
|
+
const json = (await refreshRes.json()) as Record<string, unknown>;
|
|
463
|
+
// Token endpoint wraps response in { data: ... } via apiSuccess.
|
|
464
|
+
// Handle both wrapped and bare shapes for robustness.
|
|
465
|
+
const refreshed = (
|
|
466
|
+
json.data && typeof json.data === "object" && "access_token" in json.data
|
|
467
|
+
? json.data
|
|
468
|
+
: json
|
|
469
|
+
) as TokenResponse;
|
|
463
470
|
token = refreshed.access_token;
|
|
464
471
|
saveCredentials(baseUrl, {
|
|
465
472
|
...cred,
|
|
@@ -21,6 +21,7 @@ import { createGzipDecoder, unpackTar } from "modern-tar";
|
|
|
21
21
|
import pc from "picocolors";
|
|
22
22
|
|
|
23
23
|
import { pluginManifestSchema } from "../../plugins/manifest-schema.js";
|
|
24
|
+
import { CAPABILITY_RENAMES, isDeprecatedCapability } from "../../plugins/types.js";
|
|
24
25
|
import {
|
|
25
26
|
getMarketplaceCredential,
|
|
26
27
|
saveMarketplaceCredential,
|
|
@@ -440,6 +441,29 @@ export const publishCommand = defineCommand({
|
|
|
440
441
|
}
|
|
441
442
|
console.log();
|
|
442
443
|
|
|
444
|
+
// ── Step 2.5: Hard-fail on deprecated capability names ──
|
|
445
|
+
//
|
|
446
|
+
// Refusing to publish manifests that use deprecated capability names
|
|
447
|
+
// keeps the marketplace clean while the deprecation window is open.
|
|
448
|
+
// The fix is mechanical and entirely in the author's hands — they
|
|
449
|
+
// rename, re-bundle, and republish. Better to refuse 5 publishes
|
|
450
|
+
// than ship 500 deprecated manifests. We check before authentication
|
|
451
|
+
// so authors don't burn a device-flow login on a doomed publish.
|
|
452
|
+
const deprecatedCaps = manifest.capabilities.filter(isDeprecatedCapability);
|
|
453
|
+
if (deprecatedCaps.length > 0) {
|
|
454
|
+
consola.error(
|
|
455
|
+
"Plugin declares deprecated capability names. Rename them and re-bundle before publishing:",
|
|
456
|
+
);
|
|
457
|
+
for (const cap of deprecatedCaps) {
|
|
458
|
+
const replacement = CAPABILITY_RENAMES[cap];
|
|
459
|
+
consola.error(` ${cap} → ${replacement}`);
|
|
460
|
+
}
|
|
461
|
+
consola.error(
|
|
462
|
+
"See https://emdashcms.com/docs/plugins/overview#capabilities for the full rename table.",
|
|
463
|
+
);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
|
|
443
467
|
// ── Step 3: Authenticate ──
|
|
444
468
|
//
|
|
445
469
|
// Priority: EMDASH_MARKETPLACE_TOKEN env var > stored credential > interactive device flow.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets CLI commands
|
|
3
|
+
*
|
|
4
|
+
* Pure (no-DB) commands for working with EmDash secrets:
|
|
5
|
+
*
|
|
6
|
+
* - `emdash secrets generate` — emits a fresh `EMDASH_ENCRYPTION_KEY`.
|
|
7
|
+
* Optionally writes it to `.dev.vars` (Workers) or `.env` (Node).
|
|
8
|
+
* - `emdash secrets fingerprint <key>` — prints the kid for a key,
|
|
9
|
+
* useful in CI for verifying what's been deployed without exposing
|
|
10
|
+
* the raw value.
|
|
11
|
+
*
|
|
12
|
+
* DB-touching commands (`status`, `migrate`, `rotate`) live elsewhere:
|
|
13
|
+
* the CLI process can't open the production D1/Postgres binding from
|
|
14
|
+
* the operator's machine, so those operations ship as admin HTTP
|
|
15
|
+
* endpoints in a later PR. A thin `--site <url>` wrapper for those
|
|
16
|
+
* endpoints can land alongside.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { resolve } from "node:path";
|
|
21
|
+
|
|
22
|
+
import { defineCommand } from "citty";
|
|
23
|
+
import { consola } from "consola";
|
|
24
|
+
import pc from "picocolors";
|
|
25
|
+
|
|
26
|
+
import { EmDashSecretsError, fingerprintKey, generateEncryptionKey } from "../../config/secrets.js";
|
|
27
|
+
|
|
28
|
+
const KEY_VAR_NAME = "EMDASH_ENCRYPTION_KEY";
|
|
29
|
+
/** Matches a populated entry — `KEY=<at least one char>`. */
|
|
30
|
+
const POPULATED_KEY_LINE_PATTERN = /^EMDASH_ENCRYPTION_KEY=.+$/m;
|
|
31
|
+
/**
|
|
32
|
+
* Matches any line starting `KEY=` including `KEY=` with empty value.
|
|
33
|
+
* Used for in-place replacement when the entry exists but has no value.
|
|
34
|
+
*/
|
|
35
|
+
const ANY_KEY_LINE_PATTERN = /^EMDASH_ENCRYPTION_KEY=.*$/m;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Append (or replace) `EMDASH_ENCRYPTION_KEY` in a dotenv-style file.
|
|
39
|
+
*
|
|
40
|
+
* Idempotent: if the entry exists with a populated value, leaves it alone
|
|
41
|
+
* (returns `"skipped"`) unless `force` is set. An entry with an empty
|
|
42
|
+
* value (`EMDASH_ENCRYPTION_KEY=`) is treated as "not set" and gets
|
|
43
|
+
* replaced — placeholder lines aren't a reason to refuse.
|
|
44
|
+
*
|
|
45
|
+
* Always ends the resulting file with a trailing newline. Doesn't touch
|
|
46
|
+
* other variables.
|
|
47
|
+
*
|
|
48
|
+
* Exported for tests.
|
|
49
|
+
*/
|
|
50
|
+
export function writeEncryptionKeyToFile(
|
|
51
|
+
targetPath: string,
|
|
52
|
+
value: string,
|
|
53
|
+
force: boolean,
|
|
54
|
+
): "wrote" | "skipped" {
|
|
55
|
+
const exists = existsSync(targetPath);
|
|
56
|
+
const existing = exists ? readFileSync(targetPath, "utf-8") : "";
|
|
57
|
+
|
|
58
|
+
const hasPopulatedKey = POPULATED_KEY_LINE_PATTERN.test(existing);
|
|
59
|
+
if (hasPopulatedKey && !force) {
|
|
60
|
+
return "skipped";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const newLine = `${KEY_VAR_NAME}=${value}`;
|
|
64
|
+
let next: string;
|
|
65
|
+
if (ANY_KEY_LINE_PATTERN.test(existing)) {
|
|
66
|
+
// In-place replace handles both populated-and-forced and empty-value
|
|
67
|
+
// cases. Then ensure trailing newline.
|
|
68
|
+
next = existing.replace(ANY_KEY_LINE_PATTERN, newLine);
|
|
69
|
+
if (!next.endsWith("\n")) next += "\n";
|
|
70
|
+
} else {
|
|
71
|
+
// Append. Insert a separating newline only if the file has content
|
|
72
|
+
// not already ending in one.
|
|
73
|
+
const sep = existing.length === 0 ? "" : existing.endsWith("\n") ? "" : "\n";
|
|
74
|
+
next = `${existing}${sep}${newLine}\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
writeFileSync(targetPath, next);
|
|
78
|
+
return "wrote";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const generateCommand = defineCommand({
|
|
82
|
+
meta: {
|
|
83
|
+
name: "generate",
|
|
84
|
+
description: "Generate a new EmDash encryption key",
|
|
85
|
+
},
|
|
86
|
+
args: {
|
|
87
|
+
write: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description:
|
|
90
|
+
"Optional path to write the key to (e.g. .dev.vars or .env). " +
|
|
91
|
+
"Won't overwrite an existing entry without --force.",
|
|
92
|
+
},
|
|
93
|
+
force: {
|
|
94
|
+
type: "boolean",
|
|
95
|
+
description: "When used with --write, overwrite an existing entry",
|
|
96
|
+
default: false,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
run({ args }) {
|
|
100
|
+
const value = generateEncryptionKey();
|
|
101
|
+
|
|
102
|
+
if (args.write) {
|
|
103
|
+
const targetPath = resolve(process.cwd(), args.write);
|
|
104
|
+
const result = writeEncryptionKeyToFile(targetPath, value, args.force);
|
|
105
|
+
if (result === "skipped") {
|
|
106
|
+
// Idempotent no-op: entry already populated. Exit 0 so chained
|
|
107
|
+
// scripts (`emdash secrets generate --write && pnpm dev`) don't
|
|
108
|
+
// break. Pass --force to replace, with full awareness that
|
|
109
|
+
// existing encrypted secrets become unreadable.
|
|
110
|
+
consola.info(
|
|
111
|
+
`${KEY_VAR_NAME} already set in ${pc.cyan(args.write)}; leaving it alone. ` +
|
|
112
|
+
`Pass ${pc.bold("--force")} to replace it.`,
|
|
113
|
+
);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
consola.log("");
|
|
117
|
+
consola.log(`${pc.bold("Wrote")} ${pc.cyan(KEY_VAR_NAME)} to ${pc.cyan(args.write)}`);
|
|
118
|
+
consola.log("");
|
|
119
|
+
consola.log(
|
|
120
|
+
pc.yellow(
|
|
121
|
+
"Keep this file out of version control. Losing the key means losing every secret encrypted with it.",
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
consola.log("");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Print the key to stdout (one line, no decoration) so it can be
|
|
129
|
+
// piped into env files or secret-management tools without scraping.
|
|
130
|
+
// Explanatory text goes to stderr so it doesn't pollute the pipe.
|
|
131
|
+
process.stdout.write(`${value}\n`);
|
|
132
|
+
const guidance = [
|
|
133
|
+
"",
|
|
134
|
+
pc.bold("EmDash encryption key generated."),
|
|
135
|
+
"",
|
|
136
|
+
`Set ${pc.cyan(KEY_VAR_NAME)} in your environment.`,
|
|
137
|
+
"For Cloudflare deployments, push it to your Worker's secrets.",
|
|
138
|
+
"For Node deployments, add it to your process environment or .env file.",
|
|
139
|
+
"",
|
|
140
|
+
pc.yellow("Keep this value secret. Losing it means losing every secret encrypted with it."),
|
|
141
|
+
"",
|
|
142
|
+
].join("\n");
|
|
143
|
+
process.stderr.write(`${guidance}\n`);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const fingerprintCommand = defineCommand({
|
|
148
|
+
meta: {
|
|
149
|
+
name: "fingerprint",
|
|
150
|
+
description: "Print the kid (8-char fingerprint) for an encryption key",
|
|
151
|
+
},
|
|
152
|
+
args: {
|
|
153
|
+
key: {
|
|
154
|
+
type: "positional",
|
|
155
|
+
description: "The full key value (with the emdash_enc_v1_ prefix)",
|
|
156
|
+
required: true,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
async run({ args }) {
|
|
160
|
+
try {
|
|
161
|
+
const kid = await fingerprintKey(args.key);
|
|
162
|
+
// Newline-only on stdout so it pipes cleanly into env/CI logs
|
|
163
|
+
// without leaking the raw key.
|
|
164
|
+
process.stdout.write(`${kid}\n`);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
consola.error(
|
|
167
|
+
error instanceof EmDashSecretsError ? error.message : "Failed to fingerprint key",
|
|
168
|
+
);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
export const secretsCommand = defineCommand({
|
|
175
|
+
meta: {
|
|
176
|
+
name: "secrets",
|
|
177
|
+
description: "Manage EmDash secrets (generate, inspect)",
|
|
178
|
+
},
|
|
179
|
+
subCommands: {
|
|
180
|
+
generate: generateCommand,
|
|
181
|
+
fingerprint: fingerprintCommand,
|
|
182
|
+
},
|
|
183
|
+
});
|
package/src/cli/credentials.ts
CHANGED
|
@@ -130,7 +130,7 @@ function readStore(): CredentialStore {
|
|
|
130
130
|
|
|
131
131
|
function writeStore(store: CredentialStore): void {
|
|
132
132
|
const dir = getConfigDir();
|
|
133
|
-
mkdirSync(dir, { recursive: true });
|
|
133
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
134
134
|
|
|
135
135
|
const path = getCredentialPath();
|
|
136
136
|
writeFileSync(path, JSON.stringify(store, null, "\t"), {
|