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.
Files changed (317) hide show
  1. package/dist/{adapters-BKSf3T9R.d.mts → adapters-BktHA7EO.d.mts} +1 -1
  2. package/dist/{adapters-BKSf3T9R.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
  3. package/dist/{apply-x0eMK1lX.mjs → apply-UsrFuO7l.mjs} +207 -355
  4. package/dist/apply-UsrFuO7l.mjs.map +1 -0
  5. package/dist/astro/index.d.mts +6 -6
  6. package/dist/astro/index.d.mts.map +1 -1
  7. package/dist/astro/index.mjs +118 -4
  8. package/dist/astro/index.mjs.map +1 -1
  9. package/dist/astro/middleware/auth.d.mts +6 -7
  10. package/dist/astro/middleware/auth.d.mts.map +1 -1
  11. package/dist/astro/middleware/auth.mjs +14 -57
  12. package/dist/astro/middleware/auth.mjs.map +1 -1
  13. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  14. package/dist/astro/middleware/redirect.mjs +15 -10
  15. package/dist/astro/middleware/redirect.mjs.map +1 -1
  16. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  17. package/dist/astro/middleware/request-context.mjs +8 -5
  18. package/dist/astro/middleware/request-context.mjs.map +1 -1
  19. package/dist/astro/middleware/setup.mjs +1 -1
  20. package/dist/astro/middleware.d.mts.map +1 -1
  21. package/dist/astro/middleware.mjs +70 -121
  22. package/dist/astro/middleware.mjs.map +1 -1
  23. package/dist/astro/types.d.mts +25 -10
  24. package/dist/astro/types.d.mts.map +1 -1
  25. package/dist/{byline-Chbr2GoP.mjs → byline-C3vnhIpU.mjs} +4 -4
  26. package/dist/{byline-Chbr2GoP.mjs.map → byline-C3vnhIpU.mjs.map} +1 -1
  27. package/dist/bylines-esI7ioa9.mjs +113 -0
  28. package/dist/bylines-esI7ioa9.mjs.map +1 -0
  29. package/dist/cache-fTzxgMFJ.mjs +65 -0
  30. package/dist/cache-fTzxgMFJ.mjs.map +1 -0
  31. package/dist/{chunks-HGz06Soa.mjs → chunks-Da2-b-oA.mjs} +8 -2
  32. package/dist/{chunks-HGz06Soa.mjs.map → chunks-Da2-b-oA.mjs.map} +1 -1
  33. package/dist/cli/index.mjs +456 -90
  34. package/dist/cli/index.mjs.map +1 -1
  35. package/dist/client/cf-access.d.mts +1 -1
  36. package/dist/client/index.d.mts +1 -1
  37. package/dist/client/index.mjs +3 -3
  38. package/dist/client/index.mjs.map +1 -1
  39. package/dist/{config-BXwuX8Bx.mjs → config-CVssduLe.mjs} +1 -1
  40. package/dist/{config-BXwuX8Bx.mjs.map → config-CVssduLe.mjs.map} +1 -1
  41. package/dist/{content-BcQPYxdV.mjs → content-C7G4QXkK.mjs} +42 -14
  42. package/dist/content-C7G4QXkK.mjs.map +1 -0
  43. package/dist/db/index.d.mts +3 -3
  44. package/dist/db/index.mjs +2 -2
  45. package/dist/db/libsql.d.mts +1 -1
  46. package/dist/db/libsql.d.mts.map +1 -1
  47. package/dist/db/libsql.mjs +7 -2
  48. package/dist/db/libsql.mjs.map +1 -1
  49. package/dist/db/postgres.d.mts +1 -1
  50. package/dist/db/sqlite.d.mts +1 -1
  51. package/dist/db/sqlite.d.mts.map +1 -1
  52. package/dist/db/sqlite.mjs +8 -3
  53. package/dist/db/sqlite.mjs.map +1 -1
  54. package/dist/{db-errors-l1Qh2RPR.mjs → db-errors-B7P2pSCn.mjs} +1 -1
  55. package/dist/{db-errors-l1Qh2RPR.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
  56. package/dist/{default-DCVqE5ib.mjs → default-pHuz9WF6.mjs} +1 -1
  57. package/dist/{default-DCVqE5ib.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
  58. package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
  59. package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
  60. package/dist/{error-zG5T1UGA.mjs → error-DqnRMM5z.mjs} +1 -1
  61. package/dist/{error-zG5T1UGA.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
  62. package/dist/{index-DIb-CzNx.d.mts → index-DjPMOfO0.d.mts} +162 -87
  63. package/dist/index-DjPMOfO0.d.mts.map +1 -0
  64. package/dist/index.d.mts +11 -11
  65. package/dist/index.mjs +27 -24
  66. package/dist/{load-CyEoextb.mjs → load-sXRuM7Us.mjs} +2 -2
  67. package/dist/{load-CyEoextb.mjs.map → load-sXRuM7Us.mjs.map} +1 -1
  68. package/dist/{loader-CndGj8kM.mjs → loader-Bx2_9-5e.mjs} +53 -8
  69. package/dist/loader-Bx2_9-5e.mjs.map +1 -0
  70. package/dist/{manifest-schema-DH9xhc6t.mjs → manifest-schema-CXAbd1vH.mjs} +33 -3
  71. package/dist/manifest-schema-CXAbd1vH.mjs.map +1 -0
  72. package/dist/media/index.d.mts +1 -1
  73. package/dist/media/index.mjs +1 -1
  74. package/dist/media/local-runtime.d.mts +7 -7
  75. package/dist/{mode-BnAOqItE.mjs → mode-YhqNVef_.mjs} +1 -1
  76. package/dist/{mode-BnAOqItE.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
  77. package/dist/options-nPxWnrya.mjs +117 -0
  78. package/dist/options-nPxWnrya.mjs.map +1 -0
  79. package/dist/page/index.d.mts +2 -2
  80. package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
  81. package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
  82. package/dist/{placeholder-D29tWZ7o.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
  83. package/dist/{placeholder-D29tWZ7o.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
  84. package/dist/{placeholder-C-fk5hYI.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
  85. package/dist/{placeholder-C-fk5hYI.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
  86. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  87. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  88. package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
  89. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  90. package/dist/public-url-B1AxbbbQ.mjs +51 -0
  91. package/dist/public-url-B1AxbbbQ.mjs.map +1 -0
  92. package/dist/{query-fqEdLFms.mjs → query-Bo-msrmu.mjs} +114 -16
  93. package/dist/query-Bo-msrmu.mjs.map +1 -0
  94. package/dist/{redirect-D_pshWdf.mjs → redirect-C5H7VGIX.mjs} +11 -6
  95. package/dist/redirect-C5H7VGIX.mjs.map +1 -0
  96. package/dist/{registry-C3Mr0ODu.mjs → registry-Beb7wxFc.mjs} +39 -5
  97. package/dist/registry-Beb7wxFc.mjs.map +1 -0
  98. package/dist/{request-cache-Ci7f5pBb.mjs → request-cache-C-tIpYIw.mjs} +1 -1
  99. package/dist/{request-cache-Ci7f5pBb.mjs.map → request-cache-C-tIpYIw.mjs.map} +1 -1
  100. package/dist/runner-Clwe4Mme.d.mts +44 -0
  101. package/dist/runner-Clwe4Mme.d.mts.map +1 -0
  102. package/dist/{runner-tQ7BJ4T7.mjs → runner-DMnlIkh4.mjs} +616 -191
  103. package/dist/runner-DMnlIkh4.mjs.map +1 -0
  104. package/dist/runtime.d.mts +6 -6
  105. package/dist/runtime.mjs +2 -2
  106. package/dist/{search-BoZYFuUk.mjs → search-DkN-BqsS.mjs} +270 -152
  107. package/dist/search-DkN-BqsS.mjs.map +1 -0
  108. package/dist/secrets-CZ8rxLX3.mjs +314 -0
  109. package/dist/secrets-CZ8rxLX3.mjs.map +1 -0
  110. package/dist/seed/index.d.mts +2 -2
  111. package/dist/seed/index.mjs +13 -11
  112. package/dist/seo/index.d.mts +1 -1
  113. package/dist/storage/local.d.mts +1 -1
  114. package/dist/storage/local.mjs +1 -1
  115. package/dist/storage/s3.d.mts +1 -1
  116. package/dist/storage/s3.mjs +1 -1
  117. package/dist/taxonomies-CTtewrSQ.mjs +407 -0
  118. package/dist/taxonomies-CTtewrSQ.mjs.map +1 -0
  119. package/dist/taxonomy-DSxx2K2L.mjs +218 -0
  120. package/dist/taxonomy-DSxx2K2L.mjs.map +1 -0
  121. package/dist/{tokens-D9vnZqYS.mjs → tokens-CyRDPVW2.mjs} +1 -1
  122. package/dist/{tokens-D9vnZqYS.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
  123. package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
  124. package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
  125. package/dist/{transport-CUnEL3Vs.d.mts → transport-DX_5rpsq.d.mts} +1 -1
  126. package/dist/{transport-CUnEL3Vs.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
  127. package/dist/{transport-C9ugt2Nr.mjs → transport-xpzIjCIB.mjs} +6 -5
  128. package/dist/{transport-C9ugt2Nr.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
  129. package/dist/{types-BrA0xf5I.d.mts → types-B_CXXnzh.d.mts} +1 -1
  130. package/dist/{types-BrA0xf5I.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
  131. package/dist/{types-DIMwPFub.d.mts → types-C-aFbqmA.d.mts} +1 -1
  132. package/dist/{types-DIMwPFub.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
  133. package/dist/types-CoO6mpV3.mjs +68 -0
  134. package/dist/types-CoO6mpV3.mjs.map +1 -0
  135. package/dist/{types-i36XcA_X.d.mts → types-D19uBYWn.d.mts} +83 -7
  136. package/dist/types-D19uBYWn.d.mts.map +1 -0
  137. package/dist/{types-BmPPSUEx.d.mts → types-Dl1fgFjn.d.mts} +24 -2
  138. package/dist/{types-BmPPSUEx.d.mts.map → types-Dl1fgFjn.d.mts.map} +1 -1
  139. package/dist/{types-CS8FIX7L.d.mts → types-Dtx1mSMX.d.mts} +9 -1
  140. package/dist/types-Dtx1mSMX.d.mts.map +1 -0
  141. package/dist/{types-Bm1dn-q3.mjs → types-Eg829jj9.mjs} +1 -1
  142. package/dist/{types-Bm1dn-q3.mjs.map → types-Eg829jj9.mjs.map} +1 -1
  143. package/dist/{types-CgqmmMJB.mjs → types-K-EkEQCI.mjs} +1 -1
  144. package/dist/{types-CgqmmMJB.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
  145. package/dist/{validate-CxVsLehf.mjs → validate-CBIbxM3L.mjs} +14 -10
  146. package/dist/validate-CBIbxM3L.mjs.map +1 -0
  147. package/dist/{validate-DHxmpFJt.d.mts → validate-DHGwADqO.d.mts} +18 -5
  148. package/dist/validate-DHGwADqO.d.mts.map +1 -0
  149. package/dist/{validation-C-ZpN2GI.mjs → validation-B1NYiEos.mjs} +6 -6
  150. package/dist/{validation-C-ZpN2GI.mjs.map → validation-B1NYiEos.mjs.map} +1 -1
  151. package/dist/version-CMD42IRC.mjs +7 -0
  152. package/dist/{version-Bbq8TCrz.mjs.map → version-CMD42IRC.mjs.map} +1 -1
  153. package/dist/{zod-generator-CpwccCIv.mjs → zod-generator-BNJDQBSZ.mjs} +11 -6
  154. package/dist/{zod-generator-CpwccCIv.mjs.map → zod-generator-BNJDQBSZ.mjs.map} +1 -1
  155. package/locals.d.ts +1 -6
  156. package/package.json +9 -8
  157. package/src/api/handlers/comments.ts +6 -4
  158. package/src/api/handlers/content.ts +40 -1
  159. package/src/api/handlers/dashboard.ts +29 -36
  160. package/src/api/handlers/device-flow.ts +5 -0
  161. package/src/api/handlers/marketplace.ts +11 -4
  162. package/src/api/handlers/menus.ts +256 -75
  163. package/src/api/handlers/oauth-authorization.ts +72 -33
  164. package/src/api/handlers/revision.ts +23 -14
  165. package/src/api/handlers/taxonomies.ts +273 -100
  166. package/src/api/public-url.ts +48 -2
  167. package/src/api/schemas/comments.ts +2 -2
  168. package/src/api/schemas/common.ts +7 -0
  169. package/src/api/schemas/content.ts +17 -0
  170. package/src/api/schemas/menus.ts +23 -0
  171. package/src/api/schemas/sections.ts +3 -3
  172. package/src/api/schemas/taxonomies.ts +39 -0
  173. package/src/api/schemas/users.ts +1 -1
  174. package/src/api/types.ts +5 -1
  175. package/src/astro/integration/index.ts +17 -0
  176. package/src/astro/integration/routes.ts +10 -0
  177. package/src/astro/integration/runtime.ts +30 -0
  178. package/src/astro/integration/virtual-modules.ts +32 -2
  179. package/src/astro/integration/vite-config.ts +6 -1
  180. package/src/astro/middleware/auth.ts +13 -6
  181. package/src/astro/middleware/redirect.ts +29 -16
  182. package/src/astro/middleware/request-context.ts +15 -5
  183. package/src/astro/middleware.ts +23 -9
  184. package/src/astro/routes/api/auth/invite/complete.ts +6 -1
  185. package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
  186. package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
  187. package/src/astro/routes/api/auth/signup/complete.ts +6 -1
  188. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
  189. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  190. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
  191. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
  192. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
  193. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
  194. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
  195. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
  196. package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
  197. package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
  198. package/src/astro/routes/api/import/wordpress/prepare.ts +7 -8
  199. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
  200. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
  201. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
  202. package/src/astro/routes/api/manifest.ts +62 -45
  203. package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
  204. package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
  205. package/src/astro/routes/api/menus/[name]/items.ts +16 -6
  206. package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
  207. package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
  208. package/src/astro/routes/api/menus/[name].ts +19 -10
  209. package/src/astro/routes/api/menus/index.ts +9 -6
  210. package/src/astro/routes/api/openapi.json.ts +27 -10
  211. package/src/astro/routes/api/redirects/404s/index.ts +10 -4
  212. package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
  213. package/src/astro/routes/api/redirects/[id].ts +10 -4
  214. package/src/astro/routes/api/redirects/index.ts +7 -3
  215. package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
  216. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
  217. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
  218. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
  219. package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
  220. package/src/astro/routes/api/schema/collections/index.ts +1 -1
  221. package/src/astro/routes/api/search/index.ts +10 -2
  222. package/src/astro/routes/api/sections/[slug].ts +10 -4
  223. package/src/astro/routes/api/sections/index.ts +7 -3
  224. package/src/astro/routes/api/setup/admin-verify.ts +6 -1
  225. package/src/astro/routes/api/snapshot.ts +44 -18
  226. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
  227. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
  228. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
  229. package/src/astro/routes/api/taxonomies/index.ts +9 -7
  230. package/src/astro/routes/api/themes/preview.ts +11 -5
  231. package/src/astro/types.ts +23 -3
  232. package/src/auth/allowed-origins.ts +168 -0
  233. package/src/auth/passkey-config.ts +35 -13
  234. package/src/bylines/index.ts +37 -88
  235. package/src/cli/commands/auth.ts +28 -6
  236. package/src/cli/commands/bundle-utils.ts +11 -2
  237. package/src/cli/commands/bundle.ts +28 -8
  238. package/src/cli/commands/content.ts +13 -0
  239. package/src/cli/commands/export-seed.ts +82 -21
  240. package/src/cli/commands/login.ts +8 -1
  241. package/src/cli/commands/plugin-init.ts +216 -90
  242. package/src/cli/commands/publish.ts +24 -0
  243. package/src/cli/commands/secrets.ts +183 -0
  244. package/src/cli/credentials.ts +1 -1
  245. package/src/cli/index.ts +5 -1
  246. package/src/client/index.ts +4 -4
  247. package/src/client/transport.ts +17 -7
  248. package/src/components/Break.astro +2 -2
  249. package/src/components/EmDashHead.astro +18 -13
  250. package/src/components/Embed.astro +1 -1
  251. package/src/components/Gallery.astro +1 -1
  252. package/src/components/Image.astro +1 -1
  253. package/src/components/InlinePortableTextEditor.tsx +104 -18
  254. package/src/config/secrets.ts +528 -0
  255. package/src/database/dialect-helpers.ts +50 -0
  256. package/src/database/migrations/034_published_at_index.ts +1 -1
  257. package/src/database/migrations/035_bounded_404_log.ts +56 -39
  258. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
  259. package/src/database/migrations/runner.ts +158 -23
  260. package/src/database/repositories/content.ts +47 -12
  261. package/src/database/repositories/redirect.ts +14 -3
  262. package/src/database/repositories/taxonomy.ts +212 -82
  263. package/src/database/types.ts +10 -2
  264. package/src/db/libsql.ts +1 -3
  265. package/src/db/sqlite.ts +2 -5
  266. package/src/emdash-runtime.ts +84 -159
  267. package/src/i18n/resolve.ts +37 -0
  268. package/src/index.ts +9 -0
  269. package/src/loader.ts +73 -3
  270. package/src/mcp/server.ts +180 -54
  271. package/src/menus/index.ts +143 -124
  272. package/src/menus/types.ts +15 -1
  273. package/src/page/site-identity.ts +58 -0
  274. package/src/plugins/adapt-sandbox-entry.ts +22 -10
  275. package/src/plugins/context.ts +13 -10
  276. package/src/plugins/define-plugin.ts +40 -12
  277. package/src/plugins/hooks.ts +23 -19
  278. package/src/plugins/index.ts +9 -0
  279. package/src/plugins/manifest-schema.ts +37 -2
  280. package/src/plugins/types.ts +151 -11
  281. package/src/preview/urls.ts +23 -3
  282. package/src/query.ts +148 -5
  283. package/src/redirects/cache.ts +38 -18
  284. package/src/schema/registry.ts +56 -0
  285. package/src/schema/zod-generator.ts +39 -7
  286. package/src/seed/apply.ts +142 -54
  287. package/src/seed/types.ts +14 -1
  288. package/src/seed/validate.ts +27 -13
  289. package/src/settings/index.ts +80 -6
  290. package/src/settings/types.ts +23 -1
  291. package/src/taxonomies/index.ts +237 -210
  292. package/src/taxonomies/types.ts +10 -0
  293. package/dist/apply-x0eMK1lX.mjs.map +0 -1
  294. package/dist/bylines-CRNsVG88.mjs +0 -157
  295. package/dist/bylines-CRNsVG88.mjs.map +0 -1
  296. package/dist/cache-BkKBuIvS.mjs +0 -56
  297. package/dist/cache-BkKBuIvS.mjs.map +0 -1
  298. package/dist/chunk-ClPoSABd.mjs +0 -21
  299. package/dist/content-BcQPYxdV.mjs.map +0 -1
  300. package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
  301. package/dist/index-DIb-CzNx.d.mts.map +0 -1
  302. package/dist/loader-CndGj8kM.mjs.map +0 -1
  303. package/dist/manifest-schema-DH9xhc6t.mjs.map +0 -1
  304. package/dist/query-fqEdLFms.mjs.map +0 -1
  305. package/dist/redirect-D_pshWdf.mjs.map +0 -1
  306. package/dist/registry-C3Mr0ODu.mjs.map +0 -1
  307. package/dist/runner-OURCaApa.d.mts +0 -34
  308. package/dist/runner-OURCaApa.d.mts.map +0 -1
  309. package/dist/runner-tQ7BJ4T7.mjs.map +0 -1
  310. package/dist/search-BoZYFuUk.mjs.map +0 -1
  311. package/dist/taxonomies-B4IAshV8.mjs +0 -308
  312. package/dist/taxonomies-B4IAshV8.mjs.map +0 -1
  313. package/dist/types-CS8FIX7L.d.mts.map +0 -1
  314. package/dist/types-i36XcA_X.d.mts.map +0 -1
  315. package/dist/validate-CxVsLehf.mjs.map +0 -1
  316. package/dist/validate-DHxmpFJt.d.mts.map +0 -1
  317. package/dist/version-Bbq8TCrz.mjs +0 -7
@@ -1,22 +1,26 @@
1
1
  #!/usr/bin/env node
2
- import { t as __exportAll } from "../chunk-ClPoSABd.mjs";
2
+ import { i as __exportAll, r as runMigrations, t as getMigrationStatus } from "../runner-DMnlIkh4.mjs";
3
3
  import { n as createDatabase } from "../connection-2igzM-AT.mjs";
4
- import { s as listTablesLike } from "../dialect-helpers-DhTzaUxP.mjs";
5
- import { r as runMigrations, t as getMigrationStatus } from "../runner-tQ7BJ4T7.mjs";
6
- import { t as ContentRepository } from "../content-BcQPYxdV.mjs";
4
+ import { c as listTablesLike } from "../dialect-helpers-BKCvISIQ.mjs";
5
+ import { r as isI18nEnabled } from "../config-CVssduLe.mjs";
6
+ import { t as ContentRepository } from "../content-C7G4QXkK.mjs";
7
7
  import { i as encodeBase64url } from "../base64-MBPo9ozB.mjs";
8
8
  import "../types-BIgulNsW.mjs";
9
9
  import { t as MediaRepository } from "../media-D8FbNsl0.mjs";
10
- import { m as TaxonomyRepository, p as OptionsRepository, t as applySeed } from "../apply-x0eMK1lX.mjs";
11
- import "../redirect-D_pshWdf.mjs";
12
- import "../byline-Chbr2GoP.mjs";
13
- import { r as isI18nEnabled } from "../config-BXwuX8Bx.mjs";
14
- import { n as SchemaRegistry } from "../registry-C3Mr0ODu.mjs";
15
- import "../loader-CndGj8kM.mjs";
16
- import "../request-cache-Ci7f5pBb.mjs";
17
- import { i as pluginManifestSchema } from "../manifest-schema-DH9xhc6t.mjs";
18
- import { t as validateSeed } from "../validate-CxVsLehf.mjs";
10
+ import { t as TaxonomyRepository } from "../taxonomy-DSxx2K2L.mjs";
11
+ import { t as OptionsRepository } from "../options-nPxWnrya.mjs";
12
+ import "../redirect-C5H7VGIX.mjs";
13
+ import "../byline-C3vnhIpU.mjs";
14
+ import { n as SchemaRegistry } from "../registry-Beb7wxFc.mjs";
15
+ import "../loader-Bx2_9-5e.mjs";
16
+ import "../request-cache-C-tIpYIw.mjs";
17
+ import { t as applySeed } from "../apply-UsrFuO7l.mjs";
18
+ import { i as pluginManifestSchema } from "../manifest-schema-CXAbd1vH.mjs";
19
+ import { n as isDeprecatedCapability, t as CAPABILITY_RENAMES } from "../types-CoO6mpV3.mjs";
20
+ import { t as validateSeed } from "../validate-CBIbxM3L.mjs";
21
+ import { n as fingerprintKey, r as generateEncryptionKey, t as EmDashSecretsError } from "../secrets-CZ8rxLX3.mjs";
19
22
  import { LocalStorage } from "../storage/local.mjs";
23
+ import { o as convertDataForRead } from "../transport-xpzIjCIB.mjs";
20
24
  import { createHeaderAwareFetch, customHeadersInterceptor, getCachedAccessToken, isAccessRedirect, resolveCustomHeaders, runCloudflaredLogin } from "../client/cf-access.mjs";
21
25
  import { EmDashClient } from "../client/index.mjs";
22
26
  import { imageSize } from "image-size";
@@ -35,10 +39,24 @@ import { packTar } from "modern-tar/fs";
35
39
 
36
40
  //#region src/cli/commands/auth.ts
37
41
  /**
38
- * Auth CLI commands
39
- */
40
- /**
41
- * Generate a cryptographically secure auth secret
42
+ * Auth CLI commands (deprecated)
43
+ *
44
+ * Kept as a deprecated alias for backwards compatibility. The original
45
+ * `emdash auth secret` was documented in published docs and is plausibly
46
+ * scripted in user CI (e.g. `npx emdash auth secret >> .env`). Removing
47
+ * it outright would break those scripts on minor-version upgrade.
48
+ *
49
+ * The command still emits an `EMDASH_AUTH_SECRET=<32-byte-base64url>`
50
+ * line, unchanged. `EMDASH_AUTH_SECRET` itself is now legacy: it's only
51
+ * read as a fallback source for the commenter-IP hash salt so installs
52
+ * upgrading from a prior version keep stable IP hashes (and therefore
53
+ * stable rate-limit buckets). New installs don't need to set it.
54
+ *
55
+ * The deprecation note steers users toward `emdash secrets generate`
56
+ * (which emits a different, versioned `emdash_enc_v1_*` value for
57
+ * `EMDASH_ENCRYPTION_KEY` — used to encrypt plugin secrets at rest).
58
+ *
59
+ * Will be removed in a future minor.
42
60
  */
43
61
  function generateAuthSecret() {
44
62
  const bytes = new Uint8Array(32);
@@ -48,7 +66,7 @@ function generateAuthSecret() {
48
66
  const secretCommand = defineCommand({
49
67
  meta: {
50
68
  name: "secret",
51
- description: "Generate a secure auth secret"
69
+ description: "[DEPRECATED] Generate a value for legacy EMDASH_AUTH_SECRET"
52
70
  },
53
71
  run() {
54
72
  const secret = generateAuthSecret();
@@ -59,12 +77,13 @@ const secretCommand = defineCommand({
59
77
  consola$1.log("");
60
78
  consola$1.log(pc.dim("Add this to your environment variables."));
61
79
  consola$1.log("");
80
+ process.stderr.write(`${pc.yellow("Note:")} ${pc.bold("emdash auth secret")} is deprecated and will be removed in a future minor. ${pc.cyan("EMDASH_AUTH_SECRET")} itself is now optional — it's only used as a legacy fallback for the commenter-IP hash salt. For encrypting plugin secrets at rest, use ${pc.bold("emdash secrets generate")} (a different secret: ${pc.cyan("EMDASH_ENCRYPTION_KEY")}).\n`);
62
81
  }
63
82
  });
64
83
  const authCommand = defineCommand({
65
84
  meta: {
66
85
  name: "auth",
67
- description: "Authentication utilities"
86
+ description: "[DEPRECATED] Authentication utilities (use `emdash secrets` for new flows)"
68
87
  },
69
88
  subCommands: { secret: secretCommand }
70
89
  });
@@ -134,7 +153,10 @@ function readStore() {
134
153
  return {};
135
154
  }
136
155
  function writeStore(store) {
137
- mkdirSync(getConfigDir(), { recursive: true });
156
+ mkdirSync(getConfigDir(), {
157
+ recursive: true,
158
+ mode: 448
159
+ });
138
160
  writeFileSync(getCredentialPath(), JSON.stringify(store, null, " "), {
139
161
  encoding: "utf-8",
140
162
  mode: 384
@@ -483,7 +505,16 @@ const getCommand$3 = defineCommand({
483
505
  });
484
506
  if (!args.published && item.draftRevisionId) {
485
507
  const comparison = await client.compare(args.collection, args.id);
486
- if (comparison.hasChanges && comparison.draft) item.data = comparison.draft;
508
+ if (comparison.hasChanges && comparison.draft) {
509
+ item.data = comparison.draft;
510
+ if (!args.raw && item.data) {
511
+ const fields = (await client.collection(args.collection)).fields.map((f) => ({
512
+ slug: f.slug,
513
+ type: f.type
514
+ }));
515
+ item.data = convertDataForRead(item.data, fields, false);
516
+ }
517
+ }
487
518
  }
488
519
  output(item, args);
489
520
  } catch (error) {
@@ -626,6 +657,7 @@ const deleteCommand$2 = defineCommand({
626
657
  configureOutputMode(args);
627
658
  try {
628
659
  await createClientFromArgs(args).delete(args.collection, args.id);
660
+ output({ success: true }, args);
629
661
  consola$1.success(`Deleted ${args.collection}/${args.id}`);
630
662
  } catch (error) {
631
663
  consola$1.error(error instanceof Error ? error.message : "Unknown error");
@@ -655,6 +687,7 @@ const publishCommand$1 = defineCommand({
655
687
  configureOutputMode(args);
656
688
  try {
657
689
  await createClientFromArgs(args).publish(args.collection, args.id);
690
+ output({ success: true }, args);
658
691
  consola$1.success(`Published ${args.collection}/${args.id}`);
659
692
  } catch (error) {
660
693
  consola$1.error(error instanceof Error ? error.message : "Unknown error");
@@ -684,6 +717,7 @@ const unpublishCommand = defineCommand({
684
717
  configureOutputMode(args);
685
718
  try {
686
719
  await createClientFromArgs(args).unpublish(args.collection, args.id);
720
+ output({ success: true }, args);
687
721
  consola$1.success(`Unpublished ${args.collection}/${args.id}`);
688
722
  } catch (error) {
689
723
  consola$1.error(error instanceof Error ? error.message : "Unknown error");
@@ -718,6 +752,7 @@ const scheduleCommand = defineCommand({
718
752
  configureOutputMode(args);
719
753
  try {
720
754
  await createClientFromArgs(args).schedule(args.collection, args.id, { at: args.at });
755
+ output({ success: true }, args);
721
756
  consola$1.success(`Scheduled ${args.collection}/${args.id} for ${args.at}`);
722
757
  } catch (error) {
723
758
  consola$1.error(error instanceof Error ? error.message : "Unknown error");
@@ -747,6 +782,7 @@ const restoreCommand = defineCommand({
747
782
  configureOutputMode(args);
748
783
  try {
749
784
  await createClientFromArgs(args).restore(args.collection, args.id);
785
+ output({ success: true }, args);
750
786
  consola$1.success(`Restored ${args.collection}/${args.id}`);
751
787
  } catch (error) {
752
788
  consola$1.error(error instanceof Error ? error.message : "Unknown error");
@@ -1213,49 +1249,88 @@ async function exportCollections(db) {
1213
1249
  * Export taxonomy definitions and terms
1214
1250
  */
1215
1251
  async function exportTaxonomies(db) {
1216
- const defs = await db.selectFrom("_emdash_taxonomy_defs").selectAll().execute();
1252
+ const i18nEnabled = isI18nEnabled();
1253
+ const defs = await db.selectFrom("_emdash_taxonomy_defs").selectAll().orderBy(["name", "locale"]).execute();
1217
1254
  const result = [];
1218
1255
  const termRepo = new TaxonomyRepository(db);
1256
+ const defGroupToSeedId = /* @__PURE__ */ new Map();
1219
1257
  for (const def of defs) {
1220
- const terms = await termRepo.findByName(def.name);
1221
- const seedTerms = [];
1258
+ const defSeedId = i18nEnabled && def.locale ? `tax:${def.name}:${def.locale}` : `tax:${def.name}`;
1259
+ const terms = await termRepo.findByName(def.name, { locale: def.locale });
1222
1260
  const idToSlug = /* @__PURE__ */ new Map();
1223
1261
  for (const term of terms) idToSlug.set(term.id, term.slug);
1262
+ const termGroupToSeedId = /* @__PURE__ */ new Map();
1263
+ const seedTerms = [];
1224
1264
  for (const term of terms) {
1265
+ const termSeedId = i18nEnabled && term.locale ? `term:${def.name}:${term.slug}:${term.locale}` : `term:${def.name}:${term.slug}`;
1225
1266
  const seedTerm = {
1267
+ id: termSeedId,
1226
1268
  slug: term.slug,
1227
1269
  label: term.label,
1228
1270
  description: typeof term.data?.description === "string" ? term.data.description : void 0
1229
1271
  };
1230
1272
  if (term.parentId) seedTerm.parent = idToSlug.get(term.parentId);
1273
+ if (i18nEnabled && term.locale) {
1274
+ seedTerm.locale = term.locale;
1275
+ if (term.translationGroup) {
1276
+ const anchor = termGroupToSeedId.get(term.translationGroup);
1277
+ if (anchor) seedTerm.translationOf = anchor;
1278
+ else termGroupToSeedId.set(term.translationGroup, termSeedId);
1279
+ }
1280
+ }
1231
1281
  seedTerms.push(seedTerm);
1232
1282
  }
1283
+ seedTerms.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
1233
1284
  const taxonomy = {
1285
+ id: defSeedId,
1234
1286
  name: def.name,
1235
1287
  label: def.label,
1236
1288
  labelSingular: def.label_singular || void 0,
1237
1289
  hierarchical: def.hierarchical === 1,
1238
1290
  collections: def.collections ? JSON.parse(def.collections) : []
1239
1291
  };
1292
+ if (i18nEnabled && def.locale) {
1293
+ taxonomy.locale = def.locale;
1294
+ if (def.translation_group) {
1295
+ const anchor = defGroupToSeedId.get(def.translation_group);
1296
+ if (anchor) taxonomy.translationOf = anchor;
1297
+ else defGroupToSeedId.set(def.translation_group, defSeedId);
1298
+ }
1299
+ }
1240
1300
  if (seedTerms.length > 0) taxonomy.terms = seedTerms;
1241
1301
  result.push(taxonomy);
1242
1302
  }
1303
+ result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
1243
1304
  return result;
1244
1305
  }
1245
1306
  /**
1246
1307
  * Export menus with their items
1247
1308
  */
1248
1309
  async function exportMenus(db) {
1249
- const menus = await db.selectFrom("_emdash_menus").selectAll().execute();
1310
+ const i18nEnabled = isI18nEnabled();
1311
+ const menus = await db.selectFrom("_emdash_menus").selectAll().orderBy(["name", "locale"]).execute();
1250
1312
  const result = [];
1313
+ const groupToSeedId = /* @__PURE__ */ new Map();
1251
1314
  for (const menu of menus) {
1315
+ const seedId = i18nEnabled && menu.locale ? `menu:${menu.name}:${menu.locale}` : `menu:${menu.name}`;
1252
1316
  const seedItems = buildMenuItemTree(await db.selectFrom("_emdash_menu_items").selectAll().where("menu_id", "=", menu.id).orderBy("sort_order", "asc").execute());
1253
- result.push({
1317
+ const seedMenu = {
1318
+ id: seedId,
1254
1319
  name: menu.name,
1255
1320
  label: menu.label,
1256
1321
  items: seedItems
1257
- });
1322
+ };
1323
+ if (i18nEnabled && menu.locale) {
1324
+ seedMenu.locale = menu.locale;
1325
+ if (menu.translation_group) {
1326
+ const anchor = groupToSeedId.get(menu.translation_group);
1327
+ if (anchor) seedMenu.translationOf = anchor;
1328
+ else groupToSeedId.set(menu.translation_group, seedId);
1329
+ }
1330
+ }
1331
+ result.push(seedMenu);
1258
1332
  }
1333
+ result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
1259
1334
  return result;
1260
1335
  }
1261
1336
  /** Type guard for valid widget types */
@@ -1841,7 +1916,8 @@ const whoamiCommand = defineCommand({
1841
1916
  })
1842
1917
  });
1843
1918
  if (refreshRes.ok) {
1844
- const refreshed = await refreshRes.json();
1919
+ const json = await refreshRes.json();
1920
+ const refreshed = json.data && typeof json.data === "object" && "access_token" in json.data ? json.data : json;
1845
1921
  token = refreshed.access_token;
1846
1922
  saveCredentials(baseUrl, {
1847
1923
  ...cred,
@@ -2125,8 +2201,16 @@ const MAX_SCREENSHOTS = 5;
2125
2201
  const MAX_SCREENSHOT_WIDTH = 1920;
2126
2202
  const MAX_SCREENSHOT_HEIGHT = 1080;
2127
2203
  const ICON_SIZE = 256;
2128
- /** Matches require("node:xxx") / require("xxx") / import("node:xxx") in bundled output */
2129
- const NODE_BUILTIN_IMPORT_RE = /(?:import|require)\s*\(?["'](?:node:)?([a-z_]+)["']\)?/g;
2204
+ /**
2205
+ * Matches Node.js built-in imports in bundled output:
2206
+ * - require("node:xxx") / require("xxx")
2207
+ * - import("node:xxx") / import("xxx")
2208
+ * - import X from "node:xxx" / import { X } from "node:xxx"
2209
+ * - import * as X from "node:xxx"
2210
+ * - export { X } from "node:xxx"
2211
+ * Captures the base module name (e.g. "fs" from "node:fs/promises").
2212
+ */
2213
+ const NODE_BUILTIN_IMPORT_RE = /(?:import|export|require)\s*(?:\(|[^(]*?\bfrom\s+)["'](?:node:)?([a-z_]+)(?:\/[^"']*)?\s*["']\)?/g;
2130
2214
  const LEADING_DOT_SLASH_RE = /^\.\//;
2131
2215
  const DIST_PREFIX_RE = /^dist\//;
2132
2216
  const MJS_EXT_RE = /\.m?js$/;
@@ -2619,8 +2703,18 @@ const bundleCommand = defineCommand({
2619
2703
  hasErrors = true;
2620
2704
  }
2621
2705
  }
2622
- if (manifest.capabilities.includes("network:fetch:any")) consola.warn("Plugin declares unrestricted network access (network:fetch:any) — it can make requests to any host");
2623
- else if (manifest.capabilities.includes("network:fetch") && manifest.allowedHosts.length === 0) consola.warn("Plugin declares network:fetch capability but no allowedHosts — all fetch requests will be blocked");
2706
+ const declaresUnrestricted = manifest.capabilities.includes("network:request:unrestricted") || manifest.capabilities.includes("network:fetch:any");
2707
+ const declaresHostRestricted = manifest.capabilities.includes("network:request") || manifest.capabilities.includes("network:fetch");
2708
+ if (declaresUnrestricted) consola.warn("Plugin declares unrestricted network access (network:request:unrestricted) — it can make requests to any host");
2709
+ else if (declaresHostRestricted && manifest.allowedHosts.length === 0) consola.warn("Plugin declares network:request capability but no allowedHosts — all requests will be blocked");
2710
+ const deprecatedCaps = manifest.capabilities.filter(isDeprecatedCapability);
2711
+ if (deprecatedCaps.length > 0) {
2712
+ consola.warn("Plugin uses deprecated capability names. Rename them before publishing:");
2713
+ for (const cap of deprecatedCaps) {
2714
+ const replacement = CAPABILITY_RENAMES[cap];
2715
+ consola.warn(` ${cap} → ${replacement}`);
2716
+ }
2717
+ }
2624
2718
  if (resolvedPlugin.admin?.portableTextBlocks && resolvedPlugin.admin.portableTextBlocks.length > 0) consola.warn("Plugin declares portableTextBlocks — these require trusted mode and will be ignored in sandboxed plugins");
2625
2719
  if (resolvedPlugin.admin?.entry) consola.warn("Plugin declares admin.entry — custom React components require trusted mode. Use Block Kit for sandboxed admin pages");
2626
2720
  if (resolvedPlugin.hooks["page:fragments"]) consola.warn("Plugin declares page:fragments hook — this is trusted-only and will not work in sandboxed mode");
@@ -2672,13 +2766,15 @@ const bundleCommand = defineCommand({
2672
2766
  /**
2673
2767
  * emdash plugin init
2674
2768
  *
2675
- * Scaffold a new EmDash plugin. Generates the standard-format boilerplate:
2769
+ * Scaffold a new EmDash plugin. Generates the sandboxed-format boilerplate:
2676
2770
  * src/index.ts -- descriptor factory
2677
2771
  * src/sandbox-entry.ts -- definePlugin({ hooks, routes })
2678
2772
  * package.json
2679
2773
  * tsconfig.json
2680
2774
  *
2681
- * Use --native to generate native-format boilerplate instead (createPlugin + React admin).
2775
+ * Use --format=native (or --native) to generate native-format boilerplate
2776
+ * instead (createPlugin + React admin). When neither is passed and stdout
2777
+ * is a TTY, the user is prompted to choose.
2682
2778
  *
2683
2779
  */
2684
2780
  const SLUG_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
@@ -2698,15 +2794,25 @@ const pluginInitCommand = defineCommand({
2698
2794
  type: "string",
2699
2795
  description: "Plugin name/id (e.g. my-plugin or @org/my-plugin)"
2700
2796
  },
2797
+ format: {
2798
+ type: "string",
2799
+ description: "Plugin format: sandboxed or native. Prompts when running interactively if not set.",
2800
+ valueHint: "sandboxed|native"
2801
+ },
2701
2802
  native: {
2702
2803
  type: "boolean",
2703
- description: "Generate native-format plugin (createPlugin + React admin)",
2804
+ description: "Shortcut for --format=native",
2704
2805
  default: false
2705
2806
  }
2706
2807
  },
2707
2808
  async run({ args }) {
2708
2809
  const targetDir = resolve(args.dir);
2709
- const isNative = args.native;
2810
+ const format = await resolveFormat(args.format, args.native);
2811
+ if (!format) {
2812
+ consola.info("Cancelled");
2813
+ return;
2814
+ }
2815
+ const isNative = format === "native";
2710
2816
  let pluginName = args.name || basename(targetDir);
2711
2817
  if (!pluginName || pluginName === ".") pluginName = basename(resolve("."));
2712
2818
  const slug = pluginName.replace(SCOPE_RE, "");
@@ -2719,69 +2825,162 @@ const pluginInitCommand = defineCommand({
2719
2825
  consola.error(`package.json already exists in ${targetDir}`);
2720
2826
  process.exit(1);
2721
2827
  }
2722
- consola.start(`Scaffolding ${isNative ? "native" : "standard"} plugin: ${pluginName}`);
2828
+ consola.start(`Scaffolding ${isNative ? "native" : "sandboxed"} plugin: ${pluginName}`);
2723
2829
  await mkdir(srcDir, { recursive: true });
2724
2830
  if (isNative) await scaffoldNative(targetDir, srcDir, pluginName, slug);
2725
2831
  else await scaffoldStandard(targetDir, srcDir, pluginName, slug);
2726
2832
  consola.success(`Plugin scaffolded in ${targetDir}`);
2727
2833
  consola.info("Next steps:");
2728
- if (args.dir !== ".") consola.info(` 1. cd ${args.dir}`);
2729
- consola.info(` ${args.dir !== "." ? "2" : "1"}. pnpm install`);
2730
- if (isNative) consola.info(` ${args.dir !== "." ? "3" : "2"}. Edit src/index.ts to add hooks and routes`);
2731
- else consola.info(` ${args.dir !== "." ? "3" : "2"}. Edit src/sandbox-entry.ts to add hooks and routes`);
2732
- consola.info(` ${args.dir !== "." ? "4" : "3"}. emdash plugin validate --dir .`);
2834
+ const steps = [];
2835
+ if (args.dir !== ".") steps.push(`cd ${args.dir}`);
2836
+ steps.push("pnpm install");
2837
+ steps.push(isNative ? "Edit src/index.ts to add hooks and routes" : "Edit src/sandbox-entry.ts to add hooks and routes");
2838
+ steps.push("pnpm build");
2839
+ if (!isNative) steps.push("emdash plugin validate --dir .");
2840
+ steps.forEach((step, i) => consola.info(` ${i + 1}. ${step}`));
2733
2841
  }
2734
2842
  });
2843
+ async function resolveFormat(formatArg, nativeFlag) {
2844
+ if (formatArg) {
2845
+ const normalized = formatArg.toLowerCase();
2846
+ let parsed;
2847
+ if (normalized === "native") parsed = "native";
2848
+ else if (normalized === "sandboxed" || normalized === "standard") parsed = "standard";
2849
+ else {
2850
+ consola.error(`Invalid --format "${formatArg}". Use "sandboxed" or "native".`);
2851
+ process.exit(1);
2852
+ }
2853
+ if (nativeFlag && parsed !== "native") {
2854
+ consola.error(`Conflicting flags: --native and --format=${formatArg}. Pass only one.`);
2855
+ process.exit(1);
2856
+ }
2857
+ return parsed;
2858
+ }
2859
+ if (nativeFlag) return "native";
2860
+ if (!process.stdout.isTTY) return "standard";
2861
+ const choice = await consola.prompt("Which plugin format?", {
2862
+ type: "select",
2863
+ initial: "standard",
2864
+ options: [{
2865
+ label: "Sandboxed",
2866
+ value: "standard",
2867
+ hint: "runs in an isolated sandbox; safe to install from the marketplace"
2868
+ }, {
2869
+ label: "Native",
2870
+ value: "native",
2871
+ hint: "full runtime access; install from npm"
2872
+ }],
2873
+ cancel: "null"
2874
+ });
2875
+ if (choice === null) return null;
2876
+ return choice;
2877
+ }
2878
+ function camelCase(slug) {
2879
+ return slug.split("-").map((s, i) => i === 0 ? s : s[0].toUpperCase() + s.slice(1)).join("");
2880
+ }
2881
+ function pascalCase(slug) {
2882
+ return slug.split("-").map((s) => s[0].toUpperCase() + s.slice(1)).join("");
2883
+ }
2884
+ const TSCONFIG = {
2885
+ compilerOptions: {
2886
+ target: "ES2022",
2887
+ module: "preserve",
2888
+ moduleResolution: "bundler",
2889
+ strict: true,
2890
+ esModuleInterop: true,
2891
+ declaration: true,
2892
+ outDir: "./dist",
2893
+ rootDir: "./src"
2894
+ },
2895
+ include: ["src/**/*"],
2896
+ exclude: ["node_modules", "dist"]
2897
+ };
2898
+ const TSDOWN_VERSION = "^0.20.0";
2899
+ const TYPESCRIPT_VERSION = "^5.9.0";
2735
2900
  async function scaffoldStandard(targetDir, srcDir, pluginName, slug) {
2736
- const fnName = slug.split("-").map((s, i) => i === 0 ? s : s[0].toUpperCase() + s.slice(1)).join("");
2901
+ const fnName = camelCase(slug);
2737
2902
  await writeFile(join(targetDir, "package.json"), JSON.stringify({
2738
2903
  name: pluginName,
2739
2904
  version: "0.1.0",
2740
2905
  type: "module",
2906
+ main: "./dist/index.mjs",
2741
2907
  exports: {
2742
- ".": "./src/index.ts",
2743
- "./sandbox": "./src/sandbox-entry.ts"
2908
+ ".": {
2909
+ types: "./dist/index.d.mts",
2910
+ import: "./dist/index.mjs"
2911
+ },
2912
+ "./sandbox": {
2913
+ types: "./dist/sandbox-entry.d.mts",
2914
+ import: "./dist/sandbox-entry.mjs"
2915
+ }
2744
2916
  },
2745
- files: ["src"],
2746
- peerDependencies: { emdash: "*" }
2747
- }, null, " ") + "\n");
2748
- await writeFile(join(targetDir, "tsconfig.json"), JSON.stringify({
2749
- compilerOptions: {
2750
- target: "ES2022",
2751
- module: "preserve",
2752
- moduleResolution: "bundler",
2753
- strict: true,
2754
- esModuleInterop: true,
2755
- declaration: true,
2756
- outDir: "./dist",
2757
- rootDir: "./src"
2758
- },
2759
- include: ["src/**/*"],
2760
- exclude: ["node_modules", "dist"]
2917
+ files: ["dist"],
2918
+ scripts: {
2919
+ build: "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --clean",
2920
+ dev: "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --watch",
2921
+ typecheck: "tsc --noEmit"
2922
+ },
2923
+ keywords: ["emdash", "emdash-plugin"],
2924
+ license: "MIT",
2925
+ peerDependencies: { emdash: "*" },
2926
+ devDependencies: {
2927
+ emdash: "*",
2928
+ tsdown: TSDOWN_VERSION,
2929
+ typescript: TYPESCRIPT_VERSION
2930
+ }
2761
2931
  }, null, " ") + "\n");
2932
+ await writeFile(join(targetDir, "tsconfig.json"), JSON.stringify(TSCONFIG, null, " ") + "\n");
2762
2933
  await writeFile(join(srcDir, "index.ts"), `import type { PluginDescriptor } from "emdash";
2763
2934
 
2764
2935
  export function ${fnName}Plugin(): PluginDescriptor {
2765
2936
  \treturn {
2766
- \t\tid: "${pluginName}",
2937
+ \t\tid: "${slug}",
2767
2938
  \t\tversion: "0.1.0",
2768
2939
  \t\tformat: "standard",
2769
2940
  \t\tentrypoint: "${pluginName}/sandbox",
2770
- \t\tcapabilities: [],
2941
+
2942
+ \t\tcapabilities: ["content:read"],
2943
+ \t\tstorage: {
2944
+ \t\t\tevents: { indexes: ["timestamp"] },
2945
+ \t\t},
2771
2946
  \t};
2772
2947
  }
2773
2948
  `);
2774
2949
  await writeFile(join(srcDir, "sandbox-entry.ts"), `import { definePlugin } from "emdash";
2775
2950
  import type { PluginContext } from "emdash";
2776
2951
 
2952
+ interface ContentSaveEvent {
2953
+ \tcollection: string;
2954
+ \tcontent: { id: string };
2955
+ \tisNew: boolean;
2956
+ }
2957
+
2777
2958
  export default definePlugin({
2778
2959
  \thooks: {
2779
2960
  \t\t"content:afterSave": {
2780
- \t\t\thandler: async (event: any, ctx: PluginContext) => {
2961
+ \t\t\thandler: async (event: ContentSaveEvent, ctx: PluginContext) => {
2781
2962
  \t\t\t\tctx.log.info("Content saved", {
2782
2963
  \t\t\t\t\tcollection: event.collection,
2783
2964
  \t\t\t\t\tid: event.content.id,
2784
2965
  \t\t\t\t});
2966
+
2967
+ \t\t\t\tawait ctx.storage.events.put(\`save-\${Date.now()}\`, {
2968
+ \t\t\t\t\ttimestamp: new Date().toISOString(),
2969
+ \t\t\t\t\tcollection: event.collection,
2970
+ \t\t\t\t\tcontentId: event.content.id,
2971
+ \t\t\t\t});
2972
+ \t\t\t},
2973
+ \t\t},
2974
+ \t},
2975
+
2976
+ \troutes: {
2977
+ \t\trecent: {
2978
+ \t\t\thandler: async (_routeCtx, ctx: PluginContext) => {
2979
+ \t\t\t\tconst result = await ctx.storage.events.query({
2980
+ \t\t\t\t\torderBy: { timestamp: "desc" },
2981
+ \t\t\t\t\tlimit: 10,
2982
+ \t\t\t\t});
2983
+ \t\t\t\treturn { events: result.items };
2785
2984
  \t\t\t},
2786
2985
  \t\t},
2787
2986
  \t},
@@ -2789,55 +2988,82 @@ export default definePlugin({
2789
2988
  `);
2790
2989
  }
2791
2990
  async function scaffoldNative(targetDir, srcDir, pluginName, slug) {
2792
- const fnName = slug.split("-").map((s, i) => i === 0 ? s : s[0].toUpperCase() + s.slice(1)).join("");
2991
+ const fnName = camelCase(slug);
2992
+ const typeName = pascalCase(slug);
2793
2993
  await writeFile(join(targetDir, "package.json"), JSON.stringify({
2794
2994
  name: pluginName,
2795
2995
  version: "0.1.0",
2796
2996
  type: "module",
2797
- exports: { ".": "./src/index.ts" },
2798
- files: ["src"],
2799
- peerDependencies: { emdash: "*" }
2800
- }, null, " ") + "\n");
2801
- await writeFile(join(targetDir, "tsconfig.json"), JSON.stringify({
2802
- compilerOptions: {
2803
- target: "ES2022",
2804
- module: "preserve",
2805
- moduleResolution: "bundler",
2806
- strict: true,
2807
- esModuleInterop: true,
2808
- declaration: true,
2809
- outDir: "./dist",
2810
- rootDir: "./src"
2811
- },
2812
- include: ["src/**/*"],
2813
- exclude: ["node_modules", "dist"]
2997
+ main: "./dist/index.mjs",
2998
+ exports: { ".": {
2999
+ types: "./dist/index.d.mts",
3000
+ import: "./dist/index.mjs"
3001
+ } },
3002
+ files: ["dist"],
3003
+ scripts: {
3004
+ build: "tsdown src/index.ts --format esm --dts --clean",
3005
+ dev: "tsdown src/index.ts --format esm --dts --watch",
3006
+ typecheck: "tsc --noEmit"
3007
+ },
3008
+ keywords: ["emdash", "emdash-plugin"],
3009
+ license: "MIT",
3010
+ peerDependencies: { emdash: "*" },
3011
+ devDependencies: {
3012
+ emdash: "*",
3013
+ tsdown: TSDOWN_VERSION,
3014
+ typescript: TYPESCRIPT_VERSION
3015
+ }
2814
3016
  }, null, " ") + "\n");
3017
+ await writeFile(join(targetDir, "tsconfig.json"), JSON.stringify(TSCONFIG, null, " ") + "\n");
2815
3018
  await writeFile(join(srcDir, "index.ts"), `import { definePlugin } from "emdash";
2816
3019
  import type { PluginDescriptor } from "emdash";
2817
3020
 
2818
- export function ${fnName}Plugin(): PluginDescriptor {
3021
+ export interface ${typeName}Options {
3022
+ \tenabled?: boolean;
3023
+ }
3024
+
3025
+ export function ${fnName}Plugin(options: ${typeName}Options = {}): PluginDescriptor<${typeName}Options> {
2819
3026
  \treturn {
2820
- \t\tid: "${pluginName}",
3027
+ \t\tid: "${slug}",
2821
3028
  \t\tversion: "0.1.0",
2822
3029
  \t\tformat: "native",
2823
3030
  \t\tentrypoint: "${pluginName}",
2824
- \t\toptions: {},
3031
+ \t\toptions,
2825
3032
  \t};
2826
3033
  }
2827
3034
 
2828
- export function createPlugin() {
3035
+ export function createPlugin(options: ${typeName}Options = {}) {
2829
3036
  \treturn definePlugin({
2830
- \t\tid: "${pluginName}",
3037
+ \t\tid: "${slug}",
2831
3038
  \t\tversion: "0.1.0",
2832
3039
 
3040
+ \t\tcapabilities: ["content:read"],
3041
+ \t\tstorage: {
3042
+ \t\t\tevents: { indexes: ["createdAt"] },
3043
+ \t\t},
3044
+
2833
3045
  \t\thooks: {
2834
3046
  \t\t\t"content:afterSave": async (event, ctx) => {
2835
- \t\t\t\tctx.log.info("Content saved", {
3047
+ \t\t\t\tif (options.enabled === false) return;
3048
+ \t\t\t\tawait ctx.storage.events.put(\`evt_\${Date.now()}\`, {
2836
3049
  \t\t\t\t\tcollection: event.collection,
2837
- \t\t\t\t\tid: event.content.id,
3050
+ \t\t\t\t\tcontentId: event.content.id,
3051
+ \t\t\t\t\tcreatedAt: new Date().toISOString(),
2838
3052
  \t\t\t\t});
2839
3053
  \t\t\t},
2840
3054
  \t\t},
3055
+
3056
+ \t\troutes: {
3057
+ \t\t\trecent: {
3058
+ \t\t\t\thandler: async (ctx) => {
3059
+ \t\t\t\t\tconst result = await ctx.storage.events.query({
3060
+ \t\t\t\t\t\torderBy: { createdAt: "desc" },
3061
+ \t\t\t\t\t\tlimit: 10,
3062
+ \t\t\t\t\t});
3063
+ \t\t\t\t\treturn { events: result.items };
3064
+ \t\t\t\t},
3065
+ \t\t\t},
3066
+ \t\t},
2841
3067
  \t});
2842
3068
  }
2843
3069
 
@@ -3115,6 +3341,16 @@ const publishCommand = defineCommand({
3115
3341
  if (manifest.capabilities.length > 0) consola.info(`Capabilities: ${manifest.capabilities.join(", ")}`);
3116
3342
  if (manifest.allowedHosts?.length) consola.info(`Allowed hosts: ${manifest.allowedHosts.join(", ")}`);
3117
3343
  console.log();
3344
+ const deprecatedCaps = manifest.capabilities.filter(isDeprecatedCapability);
3345
+ if (deprecatedCaps.length > 0) {
3346
+ consola.error("Plugin declares deprecated capability names. Rename them and re-bundle before publishing:");
3347
+ for (const cap of deprecatedCaps) {
3348
+ const replacement = CAPABILITY_RENAMES[cap];
3349
+ consola.error(` ${cap} → ${replacement}`);
3350
+ }
3351
+ consola.error("See https://emdashcms.com/docs/plugins/overview#capabilities for the full rename table.");
3352
+ process.exit(1);
3353
+ }
3118
3354
  let token;
3119
3355
  const envToken = process.env.EMDASH_MARKETPLACE_TOKEN;
3120
3356
  const stored = !envToken ? getMarketplaceCredential(registryUrl) : null;
@@ -3572,6 +3808,134 @@ const searchCommand = defineCommand({
3572
3808
  }
3573
3809
  });
3574
3810
 
3811
+ //#endregion
3812
+ //#region src/cli/commands/secrets.ts
3813
+ /**
3814
+ * Secrets CLI commands
3815
+ *
3816
+ * Pure (no-DB) commands for working with EmDash secrets:
3817
+ *
3818
+ * - `emdash secrets generate` — emits a fresh `EMDASH_ENCRYPTION_KEY`.
3819
+ * Optionally writes it to `.dev.vars` (Workers) or `.env` (Node).
3820
+ * - `emdash secrets fingerprint <key>` — prints the kid for a key,
3821
+ * useful in CI for verifying what's been deployed without exposing
3822
+ * the raw value.
3823
+ *
3824
+ * DB-touching commands (`status`, `migrate`, `rotate`) live elsewhere:
3825
+ * the CLI process can't open the production D1/Postgres binding from
3826
+ * the operator's machine, so those operations ship as admin HTTP
3827
+ * endpoints in a later PR. A thin `--site <url>` wrapper for those
3828
+ * endpoints can land alongside.
3829
+ */
3830
+ const KEY_VAR_NAME = "EMDASH_ENCRYPTION_KEY";
3831
+ /** Matches a populated entry — `KEY=<at least one char>`. */
3832
+ const POPULATED_KEY_LINE_PATTERN = /^EMDASH_ENCRYPTION_KEY=.+$/m;
3833
+ /**
3834
+ * Matches any line starting `KEY=` including `KEY=` with empty value.
3835
+ * Used for in-place replacement when the entry exists but has no value.
3836
+ */
3837
+ const ANY_KEY_LINE_PATTERN = /^EMDASH_ENCRYPTION_KEY=.*$/m;
3838
+ /**
3839
+ * Append (or replace) `EMDASH_ENCRYPTION_KEY` in a dotenv-style file.
3840
+ *
3841
+ * Idempotent: if the entry exists with a populated value, leaves it alone
3842
+ * (returns `"skipped"`) unless `force` is set. An entry with an empty
3843
+ * value (`EMDASH_ENCRYPTION_KEY=`) is treated as "not set" and gets
3844
+ * replaced — placeholder lines aren't a reason to refuse.
3845
+ *
3846
+ * Always ends the resulting file with a trailing newline. Doesn't touch
3847
+ * other variables.
3848
+ *
3849
+ * Exported for tests.
3850
+ */
3851
+ function writeEncryptionKeyToFile(targetPath, value, force) {
3852
+ const existing = existsSync(targetPath) ? readFileSync(targetPath, "utf-8") : "";
3853
+ if (POPULATED_KEY_LINE_PATTERN.test(existing) && !force) return "skipped";
3854
+ const newLine = `${KEY_VAR_NAME}=${value}`;
3855
+ let next;
3856
+ if (ANY_KEY_LINE_PATTERN.test(existing)) {
3857
+ next = existing.replace(ANY_KEY_LINE_PATTERN, newLine);
3858
+ if (!next.endsWith("\n")) next += "\n";
3859
+ } else next = `${existing}${existing.length === 0 ? "" : existing.endsWith("\n") ? "" : "\n"}${newLine}\n`;
3860
+ writeFileSync(targetPath, next);
3861
+ return "wrote";
3862
+ }
3863
+ const generateCommand = defineCommand({
3864
+ meta: {
3865
+ name: "generate",
3866
+ description: "Generate a new EmDash encryption key"
3867
+ },
3868
+ args: {
3869
+ write: {
3870
+ type: "string",
3871
+ description: "Optional path to write the key to (e.g. .dev.vars or .env). Won't overwrite an existing entry without --force."
3872
+ },
3873
+ force: {
3874
+ type: "boolean",
3875
+ description: "When used with --write, overwrite an existing entry",
3876
+ default: false
3877
+ }
3878
+ },
3879
+ run({ args }) {
3880
+ const value = generateEncryptionKey();
3881
+ if (args.write) {
3882
+ if (writeEncryptionKeyToFile(resolve(process.cwd(), args.write), value, args.force) === "skipped") {
3883
+ consola$1.info(`${KEY_VAR_NAME} already set in ${pc.cyan(args.write)}; leaving it alone. Pass ${pc.bold("--force")} to replace it.`);
3884
+ return;
3885
+ }
3886
+ consola$1.log("");
3887
+ consola$1.log(`${pc.bold("Wrote")} ${pc.cyan(KEY_VAR_NAME)} to ${pc.cyan(args.write)}`);
3888
+ consola$1.log("");
3889
+ consola$1.log(pc.yellow("Keep this file out of version control. Losing the key means losing every secret encrypted with it."));
3890
+ consola$1.log("");
3891
+ return;
3892
+ }
3893
+ process.stdout.write(`${value}\n`);
3894
+ const guidance = [
3895
+ "",
3896
+ pc.bold("EmDash encryption key generated."),
3897
+ "",
3898
+ `Set ${pc.cyan(KEY_VAR_NAME)} in your environment.`,
3899
+ "For Cloudflare deployments, push it to your Worker's secrets.",
3900
+ "For Node deployments, add it to your process environment or .env file.",
3901
+ "",
3902
+ pc.yellow("Keep this value secret. Losing it means losing every secret encrypted with it."),
3903
+ ""
3904
+ ].join("\n");
3905
+ process.stderr.write(`${guidance}\n`);
3906
+ }
3907
+ });
3908
+ const fingerprintCommand = defineCommand({
3909
+ meta: {
3910
+ name: "fingerprint",
3911
+ description: "Print the kid (8-char fingerprint) for an encryption key"
3912
+ },
3913
+ args: { key: {
3914
+ type: "positional",
3915
+ description: "The full key value (with the emdash_enc_v1_ prefix)",
3916
+ required: true
3917
+ } },
3918
+ async run({ args }) {
3919
+ try {
3920
+ const kid = await fingerprintKey(args.key);
3921
+ process.stdout.write(`${kid}\n`);
3922
+ } catch (error) {
3923
+ consola$1.error(error instanceof EmDashSecretsError ? error.message : "Failed to fingerprint key");
3924
+ process.exit(1);
3925
+ }
3926
+ }
3927
+ });
3928
+ const secretsCommand = defineCommand({
3929
+ meta: {
3930
+ name: "secrets",
3931
+ description: "Manage EmDash secrets (generate, inspect)"
3932
+ },
3933
+ subCommands: {
3934
+ generate: generateCommand,
3935
+ fingerprint: fingerprintCommand
3936
+ }
3937
+ });
3938
+
3575
3939
  //#endregion
3576
3940
  //#region src/cli/commands/seed.ts
3577
3941
  /**
@@ -3934,7 +4298,8 @@ const typesCommand = defineCommand({
3934
4298
  * - dev: Run dev server with local D1
3935
4299
  * - seed: Apply a seed file to the database
3936
4300
  * - export-seed: Export database schema and content as a seed file
3937
- * - auth: Authentication utilities (secret generation)
4301
+ * - secrets: Generate and inspect EmDash secrets (encryption keys, etc.)
4302
+ * - auth: [DEPRECATED] Generate auth secret (use `secrets` instead)
3938
4303
  * - login/logout/whoami: Session management
3939
4304
  * - content: Create, read, update, delete content
3940
4305
  * - schema: Manage collections and fields
@@ -3957,6 +4322,7 @@ runMain(defineCommand({
3957
4322
  doctor: doctorCommand,
3958
4323
  seed: seedCommand,
3959
4324
  "export-seed": exportSeedCommand,
4325
+ secrets: secretsCommand,
3960
4326
  auth: authCommand,
3961
4327
  login: loginCommand,
3962
4328
  logout: logoutCommand,