emdash 0.8.0 → 0.10.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-BKSf3T9R.d.mts → adapters-BktHA7EO.d.mts} +1 -1
- package/dist/{adapters-BKSf3T9R.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
- package/dist/{apply-x0eMK1lX.mjs → apply-UsrFuO7l.mjs} +207 -355
- package/dist/apply-UsrFuO7l.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 +118 -4
- 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 +14 -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 +15 -10
- 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 +8 -5
- 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 +70 -121
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +25 -10
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{byline-Chbr2GoP.mjs → byline-C3vnhIpU.mjs} +4 -4
- package/dist/{byline-Chbr2GoP.mjs.map → byline-C3vnhIpU.mjs.map} +1 -1
- package/dist/bylines-esI7ioa9.mjs +113 -0
- package/dist/bylines-esI7ioa9.mjs.map +1 -0
- package/dist/cache-fTzxgMFJ.mjs +65 -0
- package/dist/cache-fTzxgMFJ.mjs.map +1 -0
- package/dist/{chunks-HGz06Soa.mjs → chunks-Da2-b-oA.mjs} +8 -2
- package/dist/{chunks-HGz06Soa.mjs.map → chunks-Da2-b-oA.mjs.map} +1 -1
- package/dist/cli/index.mjs +456 -90
- 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-CVssduLe.mjs} +1 -1
- package/dist/{config-BXwuX8Bx.mjs.map → config-CVssduLe.mjs.map} +1 -1
- package/dist/{content-BcQPYxdV.mjs → content-C7G4QXkK.mjs} +42 -14
- package/dist/content-C7G4QXkK.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-l1Qh2RPR.mjs → db-errors-B7P2pSCn.mjs} +1 -1
- package/dist/{db-errors-l1Qh2RPR.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
- package/dist/{default-DCVqE5ib.mjs → default-pHuz9WF6.mjs} +1 -1
- package/dist/{default-DCVqE5ib.mjs.map → default-pHuz9WF6.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-zG5T1UGA.mjs → error-DqnRMM5z.mjs} +1 -1
- package/dist/{error-zG5T1UGA.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
- package/dist/{index-DIb-CzNx.d.mts → index-DjPMOfO0.d.mts} +162 -87
- package/dist/index-DjPMOfO0.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +27 -24
- package/dist/{load-CyEoextb.mjs → load-sXRuM7Us.mjs} +2 -2
- package/dist/{load-CyEoextb.mjs.map → load-sXRuM7Us.mjs.map} +1 -1
- package/dist/{loader-CndGj8kM.mjs → loader-Bx2_9-5e.mjs} +53 -8
- package/dist/loader-Bx2_9-5e.mjs.map +1 -0
- package/dist/{manifest-schema-DH9xhc6t.mjs → manifest-schema-CXAbd1vH.mjs} +33 -3
- package/dist/manifest-schema-CXAbd1vH.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/{mode-BnAOqItE.mjs → mode-YhqNVef_.mjs} +1 -1
- package/dist/{mode-BnAOqItE.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
- package/dist/options-nPxWnrya.mjs +117 -0
- package/dist/options-nPxWnrya.mjs.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
- package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
- package/dist/{placeholder-D29tWZ7o.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
- package/dist/{placeholder-D29tWZ7o.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
- 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-B1AxbbbQ.mjs +51 -0
- package/dist/public-url-B1AxbbbQ.mjs.map +1 -0
- package/dist/{query-fqEdLFms.mjs → query-Bo-msrmu.mjs} +114 -16
- package/dist/query-Bo-msrmu.mjs.map +1 -0
- package/dist/{redirect-D_pshWdf.mjs → redirect-C5H7VGIX.mjs} +11 -6
- package/dist/redirect-C5H7VGIX.mjs.map +1 -0
- package/dist/{registry-C3Mr0ODu.mjs → registry-Beb7wxFc.mjs} +39 -5
- package/dist/registry-Beb7wxFc.mjs.map +1 -0
- package/dist/{request-cache-Ci7f5pBb.mjs → request-cache-C-tIpYIw.mjs} +1 -1
- package/dist/{request-cache-Ci7f5pBb.mjs.map → request-cache-C-tIpYIw.mjs.map} +1 -1
- package/dist/runner-Clwe4Mme.d.mts +44 -0
- package/dist/runner-Clwe4Mme.d.mts.map +1 -0
- package/dist/{runner-tQ7BJ4T7.mjs → runner-DMnlIkh4.mjs} +616 -191
- package/dist/runner-DMnlIkh4.mjs.map +1 -0
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +2 -2
- package/dist/{search-BoZYFuUk.mjs → search-DkN-BqsS.mjs} +270 -152
- package/dist/search-DkN-BqsS.mjs.map +1 -0
- package/dist/secrets-CZ8rxLX3.mjs +314 -0
- package/dist/secrets-CZ8rxLX3.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +13 -11
- 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.mjs +1 -1
- package/dist/taxonomies-CTtewrSQ.mjs +407 -0
- package/dist/taxonomies-CTtewrSQ.mjs.map +1 -0
- package/dist/taxonomy-DSxx2K2L.mjs +218 -0
- package/dist/taxonomy-DSxx2K2L.mjs.map +1 -0
- package/dist/{tokens-D9vnZqYS.mjs → tokens-CyRDPVW2.mjs} +1 -1
- package/dist/{tokens-D9vnZqYS.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
- package/dist/{transport-CUnEL3Vs.d.mts → transport-DX_5rpsq.d.mts} +1 -1
- package/dist/{transport-CUnEL3Vs.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
- package/dist/{transport-C9ugt2Nr.mjs → transport-xpzIjCIB.mjs} +6 -5
- package/dist/{transport-C9ugt2Nr.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
- package/dist/{types-BrA0xf5I.d.mts → types-B_CXXnzh.d.mts} +1 -1
- package/dist/{types-BrA0xf5I.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
- package/dist/{types-DIMwPFub.d.mts → types-C-aFbqmA.d.mts} +1 -1
- package/dist/{types-DIMwPFub.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
- package/dist/types-CoO6mpV3.mjs +68 -0
- package/dist/types-CoO6mpV3.mjs.map +1 -0
- package/dist/{types-i36XcA_X.d.mts → types-D19uBYWn.d.mts} +83 -7
- package/dist/types-D19uBYWn.d.mts.map +1 -0
- package/dist/{types-BmPPSUEx.d.mts → types-Dl1fgFjn.d.mts} +24 -2
- package/dist/{types-BmPPSUEx.d.mts.map → types-Dl1fgFjn.d.mts.map} +1 -1
- package/dist/{types-CS8FIX7L.d.mts → types-Dtx1mSMX.d.mts} +9 -1
- package/dist/types-Dtx1mSMX.d.mts.map +1 -0
- package/dist/{types-Bm1dn-q3.mjs → types-Eg829jj9.mjs} +1 -1
- package/dist/{types-Bm1dn-q3.mjs.map → types-Eg829jj9.mjs.map} +1 -1
- package/dist/{types-CgqmmMJB.mjs → types-K-EkEQCI.mjs} +1 -1
- package/dist/{types-CgqmmMJB.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
- package/dist/{validate-CxVsLehf.mjs → validate-CBIbxM3L.mjs} +14 -10
- package/dist/validate-CBIbxM3L.mjs.map +1 -0
- package/dist/{validate-DHxmpFJt.d.mts → validate-DHGwADqO.d.mts} +18 -5
- package/dist/validate-DHGwADqO.d.mts.map +1 -0
- package/dist/{validation-C-ZpN2GI.mjs → validation-B1NYiEos.mjs} +6 -6
- package/dist/{validation-C-ZpN2GI.mjs.map → validation-B1NYiEos.mjs.map} +1 -1
- package/dist/version-CMD42IRC.mjs +7 -0
- package/dist/{version-Bbq8TCrz.mjs.map → version-CMD42IRC.mjs.map} +1 -1
- package/dist/{zod-generator-CpwccCIv.mjs → zod-generator-BNJDQBSZ.mjs} +11 -6
- package/dist/{zod-generator-CpwccCIv.mjs.map → zod-generator-BNJDQBSZ.mjs.map} +1 -1
- package/locals.d.ts +1 -6
- package/package.json +9 -8
- package/src/api/handlers/comments.ts +6 -4
- package/src/api/handlers/content.ts +40 -1
- package/src/api/handlers/dashboard.ts +29 -36
- package/src/api/handlers/device-flow.ts +5 -0
- package/src/api/handlers/marketplace.ts +11 -4
- package/src/api/handlers/menus.ts +256 -75
- package/src/api/handlers/oauth-authorization.ts +72 -33
- package/src/api/handlers/revision.ts +23 -14
- package/src/api/handlers/taxonomies.ts +273 -100
- package/src/api/public-url.ts +48 -2
- package/src/api/schemas/comments.ts +2 -2
- package/src/api/schemas/common.ts +7 -0
- package/src/api/schemas/content.ts +17 -0
- package/src/api/schemas/menus.ts +23 -0
- package/src/api/schemas/sections.ts +3 -3
- package/src/api/schemas/taxonomies.ts +39 -0
- package/src/api/schemas/users.ts +1 -1
- package/src/api/types.ts +5 -1
- package/src/astro/integration/index.ts +17 -0
- package/src/astro/integration/routes.ts +10 -0
- package/src/astro/integration/runtime.ts +30 -0
- package/src/astro/integration/virtual-modules.ts +32 -2
- package/src/astro/integration/vite-config.ts +6 -1
- package/src/astro/middleware/auth.ts +13 -6
- package/src/astro/middleware/redirect.ts +29 -16
- package/src/astro/middleware/request-context.ts +15 -5
- package/src/astro/middleware.ts +23 -9
- package/src/astro/routes/api/auth/invite/complete.ts +6 -1
- 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]/permanent.ts +1 -1
- 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].ts +12 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
- package/src/astro/routes/api/import/wordpress/prepare.ts +7 -8
- package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
- 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/menus/[name]/items.ts +16 -6
- package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
- package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
- package/src/astro/routes/api/menus/[name].ts +19 -10
- package/src/astro/routes/api/menus/index.ts +9 -6
- 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/setup/admin-verify.ts +6 -1
- package/src/astro/routes/api/snapshot.ts +44 -18
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
- package/src/astro/routes/api/taxonomies/index.ts +9 -7
- package/src/astro/routes/api/themes/preview.ts +11 -5
- package/src/astro/types.ts +23 -3
- package/src/auth/allowed-origins.ts +168 -0
- package/src/auth/passkey-config.ts +35 -13
- 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 +28 -8
- package/src/cli/commands/content.ts +13 -0
- package/src/cli/commands/export-seed.ts +82 -21
- package/src/cli/commands/login.ts +8 -1
- package/src/cli/commands/plugin-init.ts +216 -90
- 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/Embed.astro +1 -1
- package/src/components/Gallery.astro +1 -1
- package/src/components/Image.astro +1 -1
- package/src/components/InlinePortableTextEditor.tsx +104 -18
- 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/036_i18n_menus_and_taxonomies.ts +477 -0
- package/src/database/migrations/runner.ts +158 -23
- package/src/database/repositories/content.ts +47 -12
- package/src/database/repositories/redirect.ts +14 -3
- package/src/database/repositories/taxonomy.ts +212 -82
- package/src/database/types.ts +10 -2
- package/src/db/libsql.ts +1 -3
- package/src/db/sqlite.ts +2 -5
- package/src/emdash-runtime.ts +84 -159
- package/src/i18n/resolve.ts +37 -0
- package/src/index.ts +9 -0
- package/src/loader.ts +73 -3
- package/src/mcp/server.ts +180 -54
- package/src/menus/index.ts +143 -124
- package/src/menus/types.ts +15 -1
- 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/hooks.ts +23 -19
- package/src/plugins/index.ts +9 -0
- package/src/plugins/manifest-schema.ts +37 -2
- package/src/plugins/types.ts +151 -11
- package/src/preview/urls.ts +23 -3
- package/src/query.ts +148 -5
- package/src/redirects/cache.ts +38 -18
- package/src/schema/registry.ts +56 -0
- package/src/schema/zod-generator.ts +39 -7
- package/src/seed/apply.ts +142 -54
- package/src/seed/types.ts +14 -1
- package/src/seed/validate.ts +27 -13
- package/src/settings/index.ts +80 -6
- package/src/settings/types.ts +23 -1
- package/src/taxonomies/index.ts +237 -210
- package/src/taxonomies/types.ts +10 -0
- package/dist/apply-x0eMK1lX.mjs.map +0 -1
- package/dist/bylines-CRNsVG88.mjs +0 -157
- package/dist/bylines-CRNsVG88.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-BcQPYxdV.mjs.map +0 -1
- package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
- package/dist/index-DIb-CzNx.d.mts.map +0 -1
- package/dist/loader-CndGj8kM.mjs.map +0 -1
- package/dist/manifest-schema-DH9xhc6t.mjs.map +0 -1
- package/dist/query-fqEdLFms.mjs.map +0 -1
- package/dist/redirect-D_pshWdf.mjs.map +0 -1
- package/dist/registry-C3Mr0ODu.mjs.map +0 -1
- package/dist/runner-OURCaApa.d.mts +0 -34
- package/dist/runner-OURCaApa.d.mts.map +0 -1
- package/dist/runner-tQ7BJ4T7.mjs.map +0 -1
- package/dist/search-BoZYFuUk.mjs.map +0 -1
- package/dist/taxonomies-B4IAshV8.mjs +0 -308
- package/dist/taxonomies-B4IAshV8.mjs.map +0 -1
- package/dist/types-CS8FIX7L.d.mts.map +0 -1
- package/dist/types-i36XcA_X.d.mts.map +0 -1
- package/dist/validate-CxVsLehf.mjs.map +0 -1
- package/dist/validate-DHxmpFJt.d.mts.map +0 -1
- package/dist/version-Bbq8TCrz.mjs +0 -7
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
export interface PasskeyConfig {
|
|
10
10
|
rpName: string;
|
|
11
11
|
rpId: string;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Accepted client-data origins. First entry is the canonical/preferred origin;
|
|
14
|
+
* additional entries support multi-origin deployments (e.g. apex + preview
|
|
15
|
+
* subdomain sharing the same `rpId`). See `allowedOrigins` parameter.
|
|
16
|
+
*/
|
|
17
|
+
origins: string[];
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -18,10 +23,22 @@ export interface PasskeyConfig {
|
|
|
18
23
|
* @param url The request URL (typically `new URL(Astro.request.url)` or `new URL(request.url)`)
|
|
19
24
|
* @param siteName Optional site name for rpName (defaults to hostname from `url` or public origin)
|
|
20
25
|
* @param siteUrl Optional browser-facing origin (see `EmDashConfig.siteUrl`).
|
|
21
|
-
* When set, **origin** and **rpId** are taken from this URL
|
|
26
|
+
* When set, the canonical **origin** and **rpId** are taken from this URL.
|
|
27
|
+
* @param allowedOrigins Optional list of additional accepted origins for verification.
|
|
28
|
+
* Each must share `rpId` with the canonical origin (WebAuthn requirement).
|
|
29
|
+
* Typical use: apex + preview subdomain on the same registrable domain.
|
|
22
30
|
* @throws If `siteUrl` is non-empty but not parseable by `new URL()`.
|
|
23
31
|
*/
|
|
24
|
-
export function getPasskeyConfig(
|
|
32
|
+
export function getPasskeyConfig(
|
|
33
|
+
url: URL,
|
|
34
|
+
siteName?: string,
|
|
35
|
+
siteUrl?: string,
|
|
36
|
+
allowedOrigins?: string[],
|
|
37
|
+
): PasskeyConfig {
|
|
38
|
+
let rpName: string;
|
|
39
|
+
let rpId: string;
|
|
40
|
+
let canonicalOrigin: string;
|
|
41
|
+
|
|
25
42
|
if (siteUrl) {
|
|
26
43
|
let publicUrl: URL;
|
|
27
44
|
try {
|
|
@@ -29,16 +46,21 @@ export function getPasskeyConfig(url: URL, siteName?: string, siteUrl?: string):
|
|
|
29
46
|
} catch (e) {
|
|
30
47
|
throw new Error(`Invalid siteUrl: "${siteUrl}"`, { cause: e });
|
|
31
48
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
rpName = siteName || publicUrl.hostname;
|
|
50
|
+
rpId = publicUrl.hostname;
|
|
51
|
+
canonicalOrigin = publicUrl.origin;
|
|
52
|
+
} else {
|
|
53
|
+
rpName = siteName || url.hostname;
|
|
54
|
+
rpId = url.hostname;
|
|
55
|
+
canonicalOrigin = url.origin;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const origins = [canonicalOrigin];
|
|
59
|
+
if (allowedOrigins) {
|
|
60
|
+
for (const extra of allowedOrigins) {
|
|
61
|
+
if (extra && !origins.includes(extra)) origins.push(extra);
|
|
62
|
+
}
|
|
37
63
|
}
|
|
38
64
|
|
|
39
|
-
return {
|
|
40
|
-
rpName: siteName || url.hostname,
|
|
41
|
-
rpId: url.hostname,
|
|
42
|
-
origin: url.origin,
|
|
43
|
-
};
|
|
65
|
+
return { rpName, rpId, origins };
|
|
44
66
|
}
|
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,
|
|
@@ -524,20 +525,39 @@ export const bundleCommand = defineCommand({
|
|
|
524
525
|
}
|
|
525
526
|
}
|
|
526
527
|
|
|
527
|
-
// Check capabilities warnings
|
|
528
|
-
|
|
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) {
|
|
529
539
|
consola.warn(
|
|
530
|
-
"Plugin declares unrestricted network access (network:
|
|
540
|
+
"Plugin declares unrestricted network access (network:request:unrestricted) — it can make requests to any host",
|
|
531
541
|
);
|
|
532
|
-
} else if (
|
|
533
|
-
manifest.capabilities.includes("network:fetch") &&
|
|
534
|
-
manifest.allowedHosts.length === 0
|
|
535
|
-
) {
|
|
542
|
+
} else if (declaresHostRestricted && manifest.allowedHosts.length === 0) {
|
|
536
543
|
consola.warn(
|
|
537
|
-
"Plugin declares network:
|
|
544
|
+
"Plugin declares network:request capability but no allowedHosts — all requests will be blocked",
|
|
538
545
|
);
|
|
539
546
|
}
|
|
540
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
|
+
|
|
541
561
|
// Check for features that won't work in sandboxed mode
|
|
542
562
|
if (
|
|
543
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");
|
|
@@ -212,41 +212,69 @@ async function exportCollections(db: Kysely<Database>): Promise<SeedCollection[]
|
|
|
212
212
|
* Export taxonomy definitions and terms
|
|
213
213
|
*/
|
|
214
214
|
async function exportTaxonomies(db: Kysely<Database>): Promise<SeedTaxonomy[]> {
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
const i18nEnabled = isI18nEnabled();
|
|
216
|
+
|
|
217
|
+
// Mirrors the content export pattern: one entry per (name, locale), stable
|
|
218
|
+
// seed-local id, translations linked via `translationOf` to the anchor's id.
|
|
219
|
+
const defs = await db
|
|
220
|
+
.selectFrom("_emdash_taxonomy_defs")
|
|
221
|
+
.selectAll()
|
|
222
|
+
.orderBy(["name", "locale"])
|
|
223
|
+
.execute();
|
|
217
224
|
|
|
218
225
|
const result: SeedTaxonomy[] = [];
|
|
219
226
|
const termRepo = new TaxonomyRepository(db);
|
|
220
227
|
|
|
228
|
+
// translation_group -> seed-local id of first def we emitted in that group.
|
|
229
|
+
const defGroupToSeedId = new Map<string, string>();
|
|
230
|
+
|
|
221
231
|
for (const def of defs) {
|
|
222
|
-
|
|
223
|
-
|
|
232
|
+
const defSeedId =
|
|
233
|
+
i18nEnabled && def.locale ? `tax:${def.name}:${def.locale}` : `tax:${def.name}`;
|
|
224
234
|
|
|
225
|
-
//
|
|
226
|
-
const
|
|
235
|
+
// Terms in this def's locale.
|
|
236
|
+
const terms = await termRepo.findByName(def.name, { locale: def.locale });
|
|
227
237
|
|
|
228
|
-
//
|
|
238
|
+
// id -> slug for parent resolution within this locale.
|
|
229
239
|
const idToSlug = new Map<string, string>();
|
|
230
|
-
for (const term of terms)
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
for (const term of terms) idToSlug.set(term.id, term.slug);
|
|
241
|
+
|
|
242
|
+
// translation_group -> seed id of the anchor term.
|
|
243
|
+
const termGroupToSeedId = new Map<string, string>();
|
|
233
244
|
|
|
245
|
+
const seedTerms: SeedTaxonomyTerm[] = [];
|
|
234
246
|
for (const term of terms) {
|
|
247
|
+
const termSeedId =
|
|
248
|
+
i18nEnabled && term.locale
|
|
249
|
+
? `term:${def.name}:${term.slug}:${term.locale}`
|
|
250
|
+
: `term:${def.name}:${term.slug}`;
|
|
251
|
+
|
|
235
252
|
const seedTerm: SeedTaxonomyTerm = {
|
|
253
|
+
id: termSeedId,
|
|
236
254
|
slug: term.slug,
|
|
237
255
|
label: term.label,
|
|
238
256
|
description: typeof term.data?.description === "string" ? term.data.description : undefined,
|
|
239
257
|
};
|
|
240
258
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
259
|
+
if (term.parentId) seedTerm.parent = idToSlug.get(term.parentId);
|
|
260
|
+
|
|
261
|
+
if (i18nEnabled && term.locale) {
|
|
262
|
+
seedTerm.locale = term.locale;
|
|
263
|
+
if (term.translationGroup) {
|
|
264
|
+
const anchor = termGroupToSeedId.get(term.translationGroup);
|
|
265
|
+
if (anchor) seedTerm.translationOf = anchor;
|
|
266
|
+
else termGroupToSeedId.set(term.translationGroup, termSeedId);
|
|
267
|
+
}
|
|
244
268
|
}
|
|
245
269
|
|
|
246
270
|
seedTerms.push(seedTerm);
|
|
247
271
|
}
|
|
248
272
|
|
|
273
|
+
// Anchors first so import can resolve `translationOf`.
|
|
274
|
+
seedTerms.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
|
|
275
|
+
|
|
249
276
|
const taxonomy: SeedTaxonomy = {
|
|
277
|
+
id: defSeedId,
|
|
250
278
|
name: def.name,
|
|
251
279
|
label: def.label,
|
|
252
280
|
labelSingular: def.label_singular || undefined,
|
|
@@ -254,13 +282,23 @@ async function exportTaxonomies(db: Kysely<Database>): Promise<SeedTaxonomy[]> {
|
|
|
254
282
|
collections: def.collections ? JSON.parse(def.collections) : [],
|
|
255
283
|
};
|
|
256
284
|
|
|
257
|
-
if (
|
|
258
|
-
taxonomy.
|
|
285
|
+
if (i18nEnabled && def.locale) {
|
|
286
|
+
taxonomy.locale = def.locale;
|
|
287
|
+
if (def.translation_group) {
|
|
288
|
+
const anchor = defGroupToSeedId.get(def.translation_group);
|
|
289
|
+
if (anchor) taxonomy.translationOf = anchor;
|
|
290
|
+
else defGroupToSeedId.set(def.translation_group, defSeedId);
|
|
291
|
+
}
|
|
259
292
|
}
|
|
260
293
|
|
|
294
|
+
if (seedTerms.length > 0) taxonomy.terms = seedTerms;
|
|
295
|
+
|
|
261
296
|
result.push(taxonomy);
|
|
262
297
|
}
|
|
263
298
|
|
|
299
|
+
// Anchors first at def level too.
|
|
300
|
+
result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
|
|
301
|
+
|
|
264
302
|
return result;
|
|
265
303
|
}
|
|
266
304
|
|
|
@@ -268,13 +306,22 @@ async function exportTaxonomies(db: Kysely<Database>): Promise<SeedTaxonomy[]> {
|
|
|
268
306
|
* Export menus with their items
|
|
269
307
|
*/
|
|
270
308
|
async function exportMenus(db: Kysely<Database>): Promise<SeedMenu[]> {
|
|
271
|
-
|
|
272
|
-
|
|
309
|
+
const i18nEnabled = isI18nEnabled();
|
|
310
|
+
|
|
311
|
+
const menus = await db
|
|
312
|
+
.selectFrom("_emdash_menus")
|
|
313
|
+
.selectAll()
|
|
314
|
+
.orderBy(["name", "locale"])
|
|
315
|
+
.execute();
|
|
273
316
|
|
|
274
317
|
const result: SeedMenu[] = [];
|
|
318
|
+
// translation_group -> seed-local id of the anchor menu in that group.
|
|
319
|
+
const groupToSeedId = new Map<string, string>();
|
|
275
320
|
|
|
276
321
|
for (const menu of menus) {
|
|
277
|
-
|
|
322
|
+
const seedId =
|
|
323
|
+
i18nEnabled && menu.locale ? `menu:${menu.name}:${menu.locale}` : `menu:${menu.name}`;
|
|
324
|
+
|
|
278
325
|
const items = await db
|
|
279
326
|
.selectFrom("_emdash_menu_items")
|
|
280
327
|
.selectAll()
|
|
@@ -282,16 +329,30 @@ async function exportMenus(db: Kysely<Database>): Promise<SeedMenu[]> {
|
|
|
282
329
|
.orderBy("sort_order", "asc")
|
|
283
330
|
.execute();
|
|
284
331
|
|
|
285
|
-
// Build item tree
|
|
286
332
|
const seedItems = buildMenuItemTree(items);
|
|
287
333
|
|
|
288
|
-
|
|
334
|
+
const seedMenu: SeedMenu = {
|
|
335
|
+
id: seedId,
|
|
289
336
|
name: menu.name,
|
|
290
337
|
label: menu.label,
|
|
291
338
|
items: seedItems,
|
|
292
|
-
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
if (i18nEnabled && menu.locale) {
|
|
342
|
+
seedMenu.locale = menu.locale;
|
|
343
|
+
if (menu.translation_group) {
|
|
344
|
+
const anchor = groupToSeedId.get(menu.translation_group);
|
|
345
|
+
if (anchor) seedMenu.translationOf = anchor;
|
|
346
|
+
else groupToSeedId.set(menu.translation_group, seedId);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
result.push(seedMenu);
|
|
293
351
|
}
|
|
294
352
|
|
|
353
|
+
// Anchors first so import can resolve `translationOf`.
|
|
354
|
+
result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
|
|
355
|
+
|
|
295
356
|
return result;
|
|
296
357
|
}
|
|
297
358
|
|
|
@@ -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,
|