emdash 0.9.0 → 0.11.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 (239) hide show
  1. package/dist/{adapters-DoNJiveC.d.mts → adapters-BktHA7EO.d.mts} +1 -1
  2. package/dist/{adapters-DoNJiveC.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
  3. package/dist/{apply-BzltprvY.mjs → apply-Ded_1vng.mjs} +167 -254
  4. package/dist/apply-Ded_1vng.mjs.map +1 -0
  5. package/dist/astro/index.d.mts +6 -6
  6. package/dist/astro/index.mjs +10 -2
  7. package/dist/astro/index.mjs.map +1 -1
  8. package/dist/astro/middleware/auth.d.mts +5 -5
  9. package/dist/astro/middleware/auth.mjs +5 -5
  10. package/dist/astro/middleware/redirect.mjs +5 -5
  11. package/dist/astro/middleware/request-context.mjs +4 -4
  12. package/dist/astro/middleware/setup.mjs +1 -1
  13. package/dist/astro/middleware.d.mts.map +1 -1
  14. package/dist/astro/middleware.mjs +94 -43
  15. package/dist/astro/middleware.mjs.map +1 -1
  16. package/dist/astro/types.d.mts +12 -11
  17. package/dist/astro/types.d.mts.map +1 -1
  18. package/dist/{base64-BRICGH2l.mjs → base64-MBPo9ozB.mjs} +1 -1
  19. package/dist/{base64-BRICGH2l.mjs.map → base64-MBPo9ozB.mjs.map} +1 -1
  20. package/dist/{byline-BSaNL1w7.mjs → byline-gFn1r0vA.mjs} +4 -4
  21. package/dist/{byline-BSaNL1w7.mjs.map → byline-gFn1r0vA.mjs.map} +1 -1
  22. package/dist/{bylines-CvJ3PYz2.mjs → bylines-DTFI8nDM.mjs} +5 -5
  23. package/dist/{bylines-CvJ3PYz2.mjs.map → bylines-DTFI8nDM.mjs.map} +1 -1
  24. package/dist/{cache-C6N_hhN7.mjs → cache-BAJbeoZ8.mjs} +3 -3
  25. package/dist/{cache-C6N_hhN7.mjs.map → cache-BAJbeoZ8.mjs.map} +1 -1
  26. package/dist/{chunks-NBQVDOci.mjs → chunks-BK1oZS-l.mjs} +2 -2
  27. package/dist/{chunks-NBQVDOci.mjs.map → chunks-BK1oZS-l.mjs.map} +1 -1
  28. package/dist/cli/index.mjs +342 -95
  29. package/dist/cli/index.mjs.map +1 -1
  30. package/dist/client/cf-access.d.mts +1 -1
  31. package/dist/client/index.d.mts +1 -1
  32. package/dist/client/index.mjs +1 -1
  33. package/dist/{config-BI0V3ICQ.mjs → config-CVssduLe.mjs} +1 -1
  34. package/dist/{config-BI0V3ICQ.mjs.map → config-CVssduLe.mjs.map} +1 -1
  35. package/dist/{content-8lOYF0pr.mjs → content-CERxPUN0.mjs} +14 -3
  36. package/dist/content-CERxPUN0.mjs.map +1 -0
  37. package/dist/database/instrumentation.d.mts +6 -4
  38. package/dist/database/instrumentation.d.mts.map +1 -1
  39. package/dist/database/instrumentation.mjs +19 -7
  40. package/dist/database/instrumentation.mjs.map +1 -1
  41. package/dist/db/index.d.mts +3 -3
  42. package/dist/db/index.mjs +1 -1
  43. package/dist/db/libsql.d.mts +1 -1
  44. package/dist/db/postgres.d.mts +1 -1
  45. package/dist/db/sqlite.d.mts +1 -1
  46. package/dist/{db-errors-WRezodiz.mjs → db-errors-B7P2pSCn.mjs} +1 -1
  47. package/dist/{db-errors-WRezodiz.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
  48. package/dist/{default-D8ksjWhO.mjs → default-pHuz9WF6.mjs} +1 -1
  49. package/dist/{default-D8ksjWhO.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
  50. package/dist/{error-D_-tqP-I.mjs → error-DqnRMM5z.mjs} +1 -1
  51. package/dist/{error-D_-tqP-I.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
  52. package/dist/{index-BFRaVcD6.d.mts → index-Cg-rC4Gj.d.mts} +110 -87
  53. package/dist/index-Cg-rC4Gj.d.mts.map +1 -0
  54. package/dist/index.d.mts +11 -11
  55. package/dist/index.mjs +29 -28
  56. package/dist/{load-DDqMMvZL.mjs → load-DR1VwFXR.mjs} +2 -2
  57. package/dist/{load-DDqMMvZL.mjs.map → load-DR1VwFXR.mjs.map} +1 -1
  58. package/dist/{loader-CKLbBnhK.mjs → loader-ou_PXAjg.mjs} +31 -6
  59. package/dist/loader-ou_PXAjg.mjs.map +1 -0
  60. package/dist/{manifest-schema-DqWNC3lM.mjs → manifest-schema-CXAbd1vH.mjs} +1 -1
  61. package/dist/{manifest-schema-DqWNC3lM.mjs.map → manifest-schema-CXAbd1vH.mjs.map} +1 -1
  62. package/dist/media/index.d.mts +1 -1
  63. package/dist/media/index.mjs +1 -1
  64. package/dist/media/local-runtime.d.mts +7 -7
  65. package/dist/media/local-runtime.mjs +3 -3
  66. package/dist/{media-BW32b4gi.mjs → media-1fFhub9c.mjs} +22 -10
  67. package/dist/media-1fFhub9c.mjs.map +1 -0
  68. package/dist/{mode-ier8jbBk.mjs → mode-YhqNVef_.mjs} +1 -1
  69. package/dist/{mode-ier8jbBk.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
  70. package/dist/{options-BVp3UsTS.mjs → options-nPxWnrya.mjs} +1 -1
  71. package/dist/{options-BVp3UsTS.mjs.map → options-nPxWnrya.mjs.map} +1 -1
  72. package/dist/page/index.d.mts +2 -2
  73. package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
  74. package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
  75. package/dist/{placeholder-BE4o_2dc.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
  76. package/dist/{placeholder-BE4o_2dc.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
  77. package/dist/{placeholder-CIJejMlK.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
  78. package/dist/{placeholder-CIJejMlK.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
  79. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  80. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  81. package/dist/{public-url-DByxYjUw.mjs → public-url-B1AxbbbQ.mjs} +1 -1
  82. package/dist/{public-url-DByxYjUw.mjs.map → public-url-B1AxbbbQ.mjs.map} +1 -1
  83. package/dist/{query-Cg9ZKRQ0.mjs → query-8c_meo_K.mjs} +13 -13
  84. package/dist/{query-Cg9ZKRQ0.mjs.map → query-8c_meo_K.mjs.map} +1 -1
  85. package/dist/{redirect-BhUBKRc1.mjs → redirect-C5H7VGIX.mjs} +3 -3
  86. package/dist/{redirect-BhUBKRc1.mjs.map → redirect-C5H7VGIX.mjs.map} +1 -1
  87. package/dist/{registry-Dw70ChxB.mjs → registry-Do34mz_P.mjs} +7 -6
  88. package/dist/registry-Do34mz_P.mjs.map +1 -0
  89. package/dist/{request-cache-B-bmkipQ.mjs → request-cache-D4I69LeL.mjs} +6 -2
  90. package/dist/request-cache-D4I69LeL.mjs.map +1 -0
  91. package/dist/request-context.d.mts +27 -1
  92. package/dist/request-context.d.mts.map +1 -1
  93. package/dist/request-context.mjs +16 -3
  94. package/dist/request-context.mjs.map +1 -1
  95. package/dist/{runner-C7ADox5q.mjs → runner-DIcU2UCC.mjs} +465 -148
  96. package/dist/runner-DIcU2UCC.mjs.map +1 -0
  97. package/dist/{runner-Bnoj7vjK.d.mts → runner-Iu3IZSDM.d.mts} +2 -2
  98. package/dist/{runner-Bnoj7vjK.d.mts.map → runner-Iu3IZSDM.d.mts.map} +1 -1
  99. package/dist/runtime.d.mts +6 -6
  100. package/dist/runtime.mjs +3 -3
  101. package/dist/{search-dOGEccMa.mjs → search-DuWhx4NG.mjs} +322 -108
  102. package/dist/search-DuWhx4NG.mjs.map +1 -0
  103. package/dist/{secrets-CW3reAnU.mjs → secrets-CZ8rxLX3.mjs} +3 -3
  104. package/dist/{secrets-CW3reAnU.mjs.map → secrets-CZ8rxLX3.mjs.map} +1 -1
  105. package/dist/seed/index.d.mts +2 -2
  106. package/dist/seed/index.mjs +15 -14
  107. package/dist/seo/index.d.mts +1 -1
  108. package/dist/storage/local.d.mts +1 -1
  109. package/dist/storage/local.mjs +1 -1
  110. package/dist/storage/s3.d.mts +1 -1
  111. package/dist/storage/s3.mjs +1 -1
  112. package/dist/taxonomies-Bw76xAxo.mjs +407 -0
  113. package/dist/taxonomies-Bw76xAxo.mjs.map +1 -0
  114. package/dist/taxonomy-D6NvlKo8.mjs +218 -0
  115. package/dist/taxonomy-D6NvlKo8.mjs.map +1 -0
  116. package/dist/{tokens-D7zMmWi2.mjs → tokens-CyRDPVW2.mjs} +2 -2
  117. package/dist/{tokens-D7zMmWi2.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
  118. package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
  119. package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
  120. package/dist/{transport-DNEfeMaU.d.mts → transport-DX_5rpsq.d.mts} +1 -1
  121. package/dist/{transport-DNEfeMaU.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
  122. package/dist/{transport-BeMCmin1.mjs → transport-xpzIjCIB.mjs} +1 -1
  123. package/dist/{transport-BeMCmin1.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
  124. package/dist/{types-CIOg5AR8.mjs → types-56BKbld_.mjs} +1 -1
  125. package/dist/types-56BKbld_.mjs.map +1 -0
  126. package/dist/{types-CRxNbK-Z.mjs → types-BIgulNsW.mjs} +2 -2
  127. package/dist/{types-CRxNbK-Z.mjs.map → types-BIgulNsW.mjs.map} +1 -1
  128. package/dist/{types-CrtWgIvl.d.mts → types-BQx6ZXpR.d.mts} +10 -1
  129. package/dist/types-BQx6ZXpR.d.mts.map +1 -0
  130. package/dist/{types-CJsYGpco.d.mts → types-B_CXXnzh.d.mts} +1 -1
  131. package/dist/{types-CJsYGpco.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
  132. package/dist/{types-M78DQ1lx.d.mts → types-C-aFbqmA.d.mts} +1 -1
  133. package/dist/{types-M78DQ1lx.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
  134. package/dist/types-DiI8NOG_.mjs +16 -0
  135. package/dist/types-DiI8NOG_.mjs.map +1 -0
  136. package/dist/{types-BuBIptGk.d.mts → types-IN5z_S3P.d.mts} +158 -92
  137. package/dist/types-IN5z_S3P.d.mts.map +1 -0
  138. package/dist/{types-BSyXeCFW.d.mts → types-IZSZfEwv.d.mts} +4 -3
  139. package/dist/types-IZSZfEwv.d.mts.map +1 -0
  140. package/dist/{types-CDbKp7ND.mjs → types-K-EkEQCI.mjs} +1 -1
  141. package/dist/{types-CDbKp7ND.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
  142. package/dist/{validate-BfQh_C_y.d.mts → validate-CO3JjFV5.d.mts} +22 -5
  143. package/dist/validate-CO3JjFV5.d.mts.map +1 -0
  144. package/dist/{validate-Baqf0slj.mjs → validate-UK4Ja1uo.mjs} +14 -10
  145. package/dist/validate-UK4Ja1uo.mjs.map +1 -0
  146. package/dist/{validation-BfEI7tNe.mjs → validation-Vc5DQkJa.mjs} +5 -5
  147. package/dist/{validation-BfEI7tNe.mjs.map → validation-Vc5DQkJa.mjs.map} +1 -1
  148. package/dist/version-Bg31I_Ff.mjs +7 -0
  149. package/dist/{version-DoxrVdYf.mjs.map → version-Bg31I_Ff.mjs.map} +1 -1
  150. package/dist/{zod-generator-CC0xNe_K.mjs → zod-generator-CHnJUP2l.mjs} +8 -3
  151. package/dist/zod-generator-CHnJUP2l.mjs.map +1 -0
  152. package/package.json +9 -8
  153. package/src/api/errors.ts +5 -0
  154. package/src/api/handlers/content.ts +20 -0
  155. package/src/api/handlers/dashboard.ts +29 -36
  156. package/src/api/handlers/media-allowlist.ts +40 -0
  157. package/src/api/handlers/media.ts +1 -1
  158. package/src/api/handlers/menus.ts +400 -89
  159. package/src/api/handlers/taxonomies.ts +273 -97
  160. package/src/api/handlers/validate-media-fields.ts +125 -0
  161. package/src/api/schemas/common.ts +7 -0
  162. package/src/api/schemas/media.ts +23 -3
  163. package/src/api/schemas/menus.ts +23 -0
  164. package/src/api/schemas/schema.ts +11 -2
  165. package/src/api/schemas/taxonomies.ts +39 -0
  166. package/src/astro/integration/routes.ts +10 -0
  167. package/src/astro/middleware.ts +46 -11
  168. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
  169. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
  170. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
  171. package/src/astro/routes/api/media/upload-url.ts +10 -4
  172. package/src/astro/routes/api/media.ts +12 -4
  173. package/src/astro/routes/api/menus/[name]/items.ts +16 -6
  174. package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
  175. package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
  176. package/src/astro/routes/api/menus/[name].ts +19 -10
  177. package/src/astro/routes/api/menus/index.ts +9 -6
  178. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
  179. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
  180. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
  181. package/src/astro/routes/api/taxonomies/index.ts +9 -6
  182. package/src/astro/types.ts +5 -1
  183. package/src/auth/rate-limit.ts +3 -3
  184. package/src/cli/commands/bundle-utils.ts +81 -6
  185. package/src/cli/commands/bundle.ts +18 -15
  186. package/src/cli/commands/export-seed.ts +139 -24
  187. package/src/cli/commands/plugin-init.ts +216 -90
  188. package/src/database/instrumentation.ts +22 -8
  189. package/src/database/migrations/016_api_tokens.ts +18 -3
  190. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
  191. package/src/database/migrations/037_credential_algorithm.ts +18 -0
  192. package/src/database/migrations/runner.ts +4 -0
  193. package/src/database/repositories/content.ts +11 -0
  194. package/src/database/repositories/media.ts +40 -10
  195. package/src/database/repositories/taxonomy.ts +193 -89
  196. package/src/database/types.ts +12 -3
  197. package/src/emdash-runtime.ts +16 -3
  198. package/src/fields/file.ts +7 -6
  199. package/src/fields/image.ts +12 -11
  200. package/src/fields/types.ts +3 -0
  201. package/src/i18n/resolve.ts +37 -0
  202. package/src/index.ts +1 -1
  203. package/src/loader.ts +49 -2
  204. package/src/mcp/server.ts +114 -26
  205. package/src/media/mime.ts +75 -0
  206. package/src/menus/index.ts +143 -124
  207. package/src/menus/types.ts +15 -1
  208. package/src/plugins/types.ts +81 -191
  209. package/src/request-cache.ts +6 -2
  210. package/src/request-context.ts +42 -2
  211. package/src/schema/registry.ts +5 -5
  212. package/src/schema/types.ts +3 -2
  213. package/src/schema/zod-generator.ts +12 -2
  214. package/src/seed/apply.ts +157 -54
  215. package/src/seed/types.ts +18 -1
  216. package/src/seed/validate.ts +27 -13
  217. package/src/taxonomies/index.ts +230 -213
  218. package/src/taxonomies/types.ts +10 -0
  219. package/dist/apply-BzltprvY.mjs.map +0 -1
  220. package/dist/content-8lOYF0pr.mjs.map +0 -1
  221. package/dist/index-BFRaVcD6.d.mts.map +0 -1
  222. package/dist/loader-CKLbBnhK.mjs.map +0 -1
  223. package/dist/media-BW32b4gi.mjs.map +0 -1
  224. package/dist/registry-Dw70ChxB.mjs.map +0 -1
  225. package/dist/request-cache-B-bmkipQ.mjs.map +0 -1
  226. package/dist/runner-C7ADox5q.mjs.map +0 -1
  227. package/dist/search-dOGEccMa.mjs.map +0 -1
  228. package/dist/taxonomies-ZlRtD6AG.mjs +0 -315
  229. package/dist/taxonomies-ZlRtD6AG.mjs.map +0 -1
  230. package/dist/types-4fVtCIm0.mjs +0 -68
  231. package/dist/types-4fVtCIm0.mjs.map +0 -1
  232. package/dist/types-BSyXeCFW.d.mts.map +0 -1
  233. package/dist/types-BuBIptGk.d.mts.map +0 -1
  234. package/dist/types-CIOg5AR8.mjs.map +0 -1
  235. package/dist/types-CrtWgIvl.d.mts.map +0 -1
  236. package/dist/validate-Baqf0slj.mjs.map +0 -1
  237. package/dist/validate-BfQh_C_y.d.mts.map +0 -1
  238. package/dist/version-DoxrVdYf.mjs +0 -7
  239. package/dist/zod-generator-CC0xNe_K.mjs.map +0 -1
@@ -1,25 +1,26 @@
1
1
  #!/usr/bin/env node
2
- import { i as __exportAll, r as runMigrations, t as getMigrationStatus } from "../runner-C7ADox5q.mjs";
2
+ import { i as __exportAll, r as runMigrations, t as getMigrationStatus } from "../runner-DIcU2UCC.mjs";
3
3
  import { n as createDatabase } from "../connection-2igzM-AT.mjs";
4
4
  import { c as listTablesLike } from "../dialect-helpers-BKCvISIQ.mjs";
5
- import { t as ContentRepository } from "../content-8lOYF0pr.mjs";
6
- import { i as encodeBase64url } from "../base64-BRICGH2l.mjs";
7
- import "../types-CRxNbK-Z.mjs";
8
- import { t as MediaRepository } from "../media-BW32b4gi.mjs";
9
- import { p as TaxonomyRepository, t as applySeed } from "../apply-BzltprvY.mjs";
10
- import { t as OptionsRepository } from "../options-BVp3UsTS.mjs";
11
- import "../redirect-BhUBKRc1.mjs";
12
- import "../byline-BSaNL1w7.mjs";
13
- import { r as isI18nEnabled } from "../config-BI0V3ICQ.mjs";
14
- import { n as SchemaRegistry } from "../registry-Dw70ChxB.mjs";
15
- import "../loader-CKLbBnhK.mjs";
16
- import "../request-cache-B-bmkipQ.mjs";
17
- import { i as pluginManifestSchema } from "../manifest-schema-DqWNC3lM.mjs";
18
- import { n as isDeprecatedCapability, t as CAPABILITY_RENAMES } from "../types-4fVtCIm0.mjs";
19
- import { t as validateSeed } from "../validate-Baqf0slj.mjs";
20
- import { n as fingerprintKey, r as generateEncryptionKey, t as EmDashSecretsError } from "../secrets-CW3reAnU.mjs";
5
+ import { r as isI18nEnabled } from "../config-CVssduLe.mjs";
6
+ import { a as slugify, t as ContentRepository } from "../content-CERxPUN0.mjs";
7
+ import { i as encodeBase64url } from "../base64-MBPo9ozB.mjs";
8
+ import "../types-BIgulNsW.mjs";
9
+ import { t as MediaRepository } from "../media-1fFhub9c.mjs";
10
+ import { t as TaxonomyRepository } from "../taxonomy-D6NvlKo8.mjs";
11
+ import { t as OptionsRepository } from "../options-nPxWnrya.mjs";
12
+ import "../redirect-C5H7VGIX.mjs";
13
+ import "../byline-gFn1r0vA.mjs";
14
+ import "../request-cache-D4I69LeL.mjs";
15
+ import { n as SchemaRegistry } from "../registry-Do34mz_P.mjs";
16
+ import "../loader-ou_PXAjg.mjs";
17
+ import { t as applySeed } from "../apply-Ded_1vng.mjs";
18
+ import { i as pluginManifestSchema } from "../manifest-schema-CXAbd1vH.mjs";
19
+ import { n as isDeprecatedCapability, t as CAPABILITY_RENAMES } from "../types-DiI8NOG_.mjs";
20
+ import { t as validateSeed } from "../validate-UK4Ja1uo.mjs";
21
+ import { n as fingerprintKey, r as generateEncryptionKey, t as EmDashSecretsError } from "../secrets-CZ8rxLX3.mjs";
21
22
  import { LocalStorage } from "../storage/local.mjs";
22
- import { o as convertDataForRead } from "../transport-BeMCmin1.mjs";
23
+ import { o as convertDataForRead } from "../transport-xpzIjCIB.mjs";
23
24
  import { createHeaderAwareFetch, customHeadersInterceptor, getCachedAccessToken, isAccessRedirect, resolveCustomHeaders, runCloudflaredLogin } from "../client/cf-access.mjs";
24
25
  import { EmDashClient } from "../client/index.mjs";
25
26
  import { imageSize } from "image-size";
@@ -1248,49 +1249,96 @@ async function exportCollections(db) {
1248
1249
  * Export taxonomy definitions and terms
1249
1250
  */
1250
1251
  async function exportTaxonomies(db) {
1251
- 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();
1252
1254
  const result = [];
1253
1255
  const termRepo = new TaxonomyRepository(db);
1256
+ const defGroupToSeedId = /* @__PURE__ */ new Map();
1254
1257
  for (const def of defs) {
1255
- const terms = await termRepo.findByName(def.name);
1256
- 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 });
1257
1260
  const idToSlug = /* @__PURE__ */ new Map();
1258
1261
  for (const term of terms) idToSlug.set(term.id, term.slug);
1262
+ const termGroupToSeedId = /* @__PURE__ */ new Map();
1263
+ const seedTerms = [];
1259
1264
  for (const term of terms) {
1265
+ const termSeedId = i18nEnabled && term.locale ? `term:${def.name}:${term.slug}:${term.locale}` : `term:${def.name}:${term.slug}`;
1260
1266
  const seedTerm = {
1267
+ id: termSeedId,
1261
1268
  slug: term.slug,
1262
1269
  label: term.label,
1263
1270
  description: typeof term.data?.description === "string" ? term.data.description : void 0
1264
1271
  };
1265
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
+ }
1266
1281
  seedTerms.push(seedTerm);
1267
1282
  }
1283
+ seedTerms.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
1268
1284
  const taxonomy = {
1285
+ id: defSeedId,
1269
1286
  name: def.name,
1270
1287
  label: def.label,
1271
1288
  labelSingular: def.label_singular || void 0,
1272
1289
  hierarchical: def.hierarchical === 1,
1273
1290
  collections: def.collections ? JSON.parse(def.collections) : []
1274
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
+ }
1275
1300
  if (seedTerms.length > 0) taxonomy.terms = seedTerms;
1276
1301
  result.push(taxonomy);
1277
1302
  }
1303
+ result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
1278
1304
  return result;
1279
1305
  }
1280
1306
  /**
1281
1307
  * Export menus with their items
1282
1308
  */
1283
1309
  async function exportMenus(db) {
1284
- 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();
1285
1312
  const result = [];
1313
+ const groupToSeedId = /* @__PURE__ */ new Map();
1314
+ const itemGroupToSeedId = /* @__PURE__ */ new Map();
1315
+ const usedItemSeedIds = /* @__PURE__ */ new Set();
1286
1316
  for (const menu of menus) {
1287
- const seedItems = buildMenuItemTree(await db.selectFrom("_emdash_menu_items").selectAll().where("menu_id", "=", menu.id).orderBy("sort_order", "asc").execute());
1288
- result.push({
1317
+ const seedId = i18nEnabled && menu.locale ? `menu:${menu.name}:${menu.locale}` : `menu:${menu.name}`;
1318
+ const seedItems = buildMenuItemTree(await db.selectFrom("_emdash_menu_items").selectAll().where("menu_id", "=", menu.id).orderBy("sort_order", "asc").execute(), {
1319
+ i18nEnabled,
1320
+ menuName: menu.name,
1321
+ menuLocale: menu.locale ?? null,
1322
+ itemGroupToSeedId,
1323
+ usedItemSeedIds
1324
+ });
1325
+ const seedMenu = {
1326
+ id: seedId,
1289
1327
  name: menu.name,
1290
1328
  label: menu.label,
1291
1329
  items: seedItems
1292
- });
1330
+ };
1331
+ if (i18nEnabled && menu.locale) {
1332
+ seedMenu.locale = menu.locale;
1333
+ if (menu.translation_group) {
1334
+ const anchor = groupToSeedId.get(menu.translation_group);
1335
+ if (anchor) seedMenu.translationOf = anchor;
1336
+ else groupToSeedId.set(menu.translation_group, seedId);
1337
+ }
1338
+ }
1339
+ result.push(seedMenu);
1293
1340
  }
1341
+ result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
1294
1342
  return result;
1295
1343
  }
1296
1344
  /** Type guard for valid widget types */
@@ -1300,13 +1348,25 @@ function isWidgetType(t) {
1300
1348
  /**
1301
1349
  * Build hierarchical menu item tree from flat array
1302
1350
  */
1303
- function buildMenuItemTree(items) {
1351
+ function buildMenuItemTree(items, i18nCtx) {
1304
1352
  const childMap = /* @__PURE__ */ new Map();
1305
1353
  for (const item of items) {
1306
1354
  const parentId = item.parent_id;
1307
1355
  if (!childMap.has(parentId)) childMap.set(parentId, []);
1308
1356
  childMap.get(parentId).push(item);
1309
1357
  }
1358
+ function makeSeedId(item) {
1359
+ const base = slugify(item.label || "") || item.id;
1360
+ const locale = i18nCtx.i18nEnabled ? item.locale ?? i18nCtx.menuLocale : null;
1361
+ const candidate = locale ? `item:${i18nCtx.menuName}:${base}:${locale}` : `item:${i18nCtx.menuName}:${base}`;
1362
+ if (!i18nCtx.usedItemSeedIds.has(candidate)) {
1363
+ i18nCtx.usedItemSeedIds.add(candidate);
1364
+ return candidate;
1365
+ }
1366
+ const fallback = locale ? `item:${i18nCtx.menuName}:${base}:${item.id}:${locale}` : `item:${i18nCtx.menuName}:${base}:${item.id}`;
1367
+ i18nCtx.usedItemSeedIds.add(fallback);
1368
+ return fallback;
1369
+ }
1310
1370
  function buildLevel(parentId) {
1311
1371
  return (childMap.get(parentId) || []).map((item) => {
1312
1372
  const seedItem = {
@@ -1321,6 +1381,17 @@ function buildMenuItemTree(items) {
1321
1381
  if (item.target === "_blank") seedItem.target = "_blank";
1322
1382
  if (item.title_attr) seedItem.titleAttr = item.title_attr;
1323
1383
  if (item.css_classes) seedItem.cssClasses = item.css_classes;
1384
+ if (i18nCtx.i18nEnabled) {
1385
+ const itemLocale = item.locale ?? i18nCtx.menuLocale;
1386
+ const seedId = makeSeedId(item);
1387
+ seedItem.id = seedId;
1388
+ if (itemLocale) seedItem.locale = itemLocale;
1389
+ if (item.translation_group) {
1390
+ const anchor = i18nCtx.itemGroupToSeedId.get(item.translation_group);
1391
+ if (anchor && anchor !== seedId) seedItem.translationOf = anchor;
1392
+ else if (!anchor) i18nCtx.itemGroupToSeedId.set(item.translation_group, seedId);
1393
+ }
1394
+ }
1324
1395
  const itemChildren = buildLevel(item.id);
1325
1396
  if (itemChildren.length > 0) seedItem.children = itemChildren;
1326
1397
  return seedItem;
@@ -2156,7 +2227,9 @@ const menuCommand = defineCommand({
2156
2227
  * Shared logic extracted from the bundle command so it can be tested
2157
2228
  * without the CLI harness and tsdown dependency.
2158
2229
  */
2159
- const MAX_BUNDLE_SIZE = 5 * 1024 * 1024;
2230
+ const MAX_BUNDLE_SIZE = 256 * 1024;
2231
+ const MAX_FILE_SIZE = 128 * 1024;
2232
+ const MAX_FILE_COUNT = 20;
2160
2233
  const MAX_SCREENSHOTS = 5;
2161
2234
  const MAX_SCREENSHOT_WIDTH = 1920;
2162
2235
  const MAX_SCREENSHOT_HEIGHT = 1080;
@@ -2329,21 +2402,66 @@ function findSourceExports(exports) {
2329
2402
  return issues;
2330
2403
  }
2331
2404
  /**
2332
- * Recursively calculate the total size of all files in a directory.
2405
+ * Recursively walk a staging directory and return a flat list of all files
2406
+ * with sizes. Names are relative to `dir` so they match what would appear
2407
+ * as the tarball entry name.
2333
2408
  */
2334
- async function calculateDirectorySize(dir) {
2335
- let total = 0;
2409
+ async function collectBundleEntries(dir) {
2410
+ const entries = [];
2411
+ await walkBundle(dir, "", entries);
2412
+ return entries;
2413
+ }
2414
+ async function walkBundle(dir, prefix, into) {
2336
2415
  const items = await readdir(dir, { withFileTypes: true });
2337
2416
  for (const item of items) {
2338
2417
  const fullPath = join(dir, item.name);
2418
+ const relPath = prefix ? `${prefix}/${item.name}` : item.name;
2339
2419
  if (item.isFile()) {
2340
2420
  const s = await stat(fullPath);
2341
- total += s.size;
2342
- } else if (item.isDirectory()) total += await calculateDirectorySize(fullPath);
2421
+ into.push({
2422
+ name: relPath,
2423
+ bytes: s.size
2424
+ });
2425
+ } else if (item.isDirectory()) await walkBundle(fullPath, relPath, into);
2343
2426
  }
2427
+ }
2428
+ /**
2429
+ * Sum the byte sizes of all entries.
2430
+ */
2431
+ function totalBundleBytes(entries) {
2432
+ let total = 0;
2433
+ for (const e of entries) total += e.bytes;
2344
2434
  return total;
2345
2435
  }
2346
2436
  /**
2437
+ * Check a bundle against the three size caps from RFC 0001:
2438
+ * - total decompressed ≤ MAX_BUNDLE_SIZE
2439
+ * - per-file decompressed ≤ MAX_FILE_SIZE
2440
+ * - file count ≤ MAX_FILE_COUNT
2441
+ *
2442
+ * Returns a list of violation messages (empty if the bundle is within all
2443
+ * caps). Messages are deterministic per input — the total/count violations
2444
+ * come first, then oversized files in alphabetical order — so the same
2445
+ * bundle always produces the same error text.
2446
+ */
2447
+ function validateBundleSize(entries) {
2448
+ const violations = [];
2449
+ const total = totalBundleBytes(entries);
2450
+ if (total > MAX_BUNDLE_SIZE) violations.push(`Bundle size ${formatBytes(total)} exceeds maximum of ${formatBytes(MAX_BUNDLE_SIZE)}.`);
2451
+ if (entries.length > MAX_FILE_COUNT) violations.push(`Bundle contains ${entries.length} files, exceeds maximum of ${MAX_FILE_COUNT}.`);
2452
+ const oversized = entries.filter((e) => e.bytes > MAX_FILE_SIZE).toSorted((a, b) => a.name.localeCompare(b.name));
2453
+ for (const e of oversized) violations.push(`File ${e.name} is ${formatBytes(e.bytes)}, exceeds per-file maximum of ${formatBytes(MAX_FILE_SIZE)}.`);
2454
+ return violations;
2455
+ }
2456
+ /**
2457
+ * Render a byte count as a human-friendly string (e.g. "256.0 KB").
2458
+ */
2459
+ function formatBytes(n) {
2460
+ if (n < 1024) return `${n} B`;
2461
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
2462
+ return `${(n / 1024 / 1024).toFixed(2)} MB`;
2463
+ }
2464
+ /**
2347
2465
  * Create a gzipped tarball from a directory.
2348
2466
  */
2349
2467
  async function createTarball(sourceDir, outputPath) {
@@ -2686,15 +2804,12 @@ const bundleCommand = defineCommand({
2686
2804
  hasErrors = true;
2687
2805
  }
2688
2806
  }
2689
- const totalSize = await calculateDirectorySize(bundleDir);
2690
- if (totalSize > MAX_BUNDLE_SIZE) {
2691
- const sizeMB = (totalSize / 1024 / 1024).toFixed(2);
2692
- consola.error(`Bundle size ${sizeMB}MB exceeds maximum of 5MB`);
2807
+ const bundleEntries = await collectBundleEntries(bundleDir);
2808
+ const sizeViolations = validateBundleSize(bundleEntries);
2809
+ if (sizeViolations.length > 0) {
2810
+ for (const v of sizeViolations) consola.error(v);
2693
2811
  hasErrors = true;
2694
- } else {
2695
- const sizeKB = (totalSize / 1024).toFixed(1);
2696
- consola.info(`Bundle size: ${sizeKB}KB`);
2697
- }
2812
+ } else consola.info(`Bundle size: ${formatBytes(totalBundleBytes(bundleEntries))} across ${bundleEntries.length} file${bundleEntries.length === 1 ? "" : "s"}`);
2698
2813
  if (hasErrors) {
2699
2814
  consola.error("Bundle validation failed");
2700
2815
  process.exit(1);
@@ -2726,13 +2841,15 @@ const bundleCommand = defineCommand({
2726
2841
  /**
2727
2842
  * emdash plugin init
2728
2843
  *
2729
- * Scaffold a new EmDash plugin. Generates the standard-format boilerplate:
2844
+ * Scaffold a new EmDash plugin. Generates the sandboxed-format boilerplate:
2730
2845
  * src/index.ts -- descriptor factory
2731
2846
  * src/sandbox-entry.ts -- definePlugin({ hooks, routes })
2732
2847
  * package.json
2733
2848
  * tsconfig.json
2734
2849
  *
2735
- * Use --native to generate native-format boilerplate instead (createPlugin + React admin).
2850
+ * Use --format=native (or --native) to generate native-format boilerplate
2851
+ * instead (createPlugin + React admin). When neither is passed and stdout
2852
+ * is a TTY, the user is prompted to choose.
2736
2853
  *
2737
2854
  */
2738
2855
  const SLUG_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
@@ -2752,15 +2869,25 @@ const pluginInitCommand = defineCommand({
2752
2869
  type: "string",
2753
2870
  description: "Plugin name/id (e.g. my-plugin or @org/my-plugin)"
2754
2871
  },
2872
+ format: {
2873
+ type: "string",
2874
+ description: "Plugin format: sandboxed or native. Prompts when running interactively if not set.",
2875
+ valueHint: "sandboxed|native"
2876
+ },
2755
2877
  native: {
2756
2878
  type: "boolean",
2757
- description: "Generate native-format plugin (createPlugin + React admin)",
2879
+ description: "Shortcut for --format=native",
2758
2880
  default: false
2759
2881
  }
2760
2882
  },
2761
2883
  async run({ args }) {
2762
2884
  const targetDir = resolve(args.dir);
2763
- const isNative = args.native;
2885
+ const format = await resolveFormat(args.format, args.native);
2886
+ if (!format) {
2887
+ consola.info("Cancelled");
2888
+ return;
2889
+ }
2890
+ const isNative = format === "native";
2764
2891
  let pluginName = args.name || basename(targetDir);
2765
2892
  if (!pluginName || pluginName === ".") pluginName = basename(resolve("."));
2766
2893
  const slug = pluginName.replace(SCOPE_RE, "");
@@ -2773,69 +2900,162 @@ const pluginInitCommand = defineCommand({
2773
2900
  consola.error(`package.json already exists in ${targetDir}`);
2774
2901
  process.exit(1);
2775
2902
  }
2776
- consola.start(`Scaffolding ${isNative ? "native" : "standard"} plugin: ${pluginName}`);
2903
+ consola.start(`Scaffolding ${isNative ? "native" : "sandboxed"} plugin: ${pluginName}`);
2777
2904
  await mkdir(srcDir, { recursive: true });
2778
2905
  if (isNative) await scaffoldNative(targetDir, srcDir, pluginName, slug);
2779
2906
  else await scaffoldStandard(targetDir, srcDir, pluginName, slug);
2780
2907
  consola.success(`Plugin scaffolded in ${targetDir}`);
2781
2908
  consola.info("Next steps:");
2782
- if (args.dir !== ".") consola.info(` 1. cd ${args.dir}`);
2783
- consola.info(` ${args.dir !== "." ? "2" : "1"}. pnpm install`);
2784
- if (isNative) consola.info(` ${args.dir !== "." ? "3" : "2"}. Edit src/index.ts to add hooks and routes`);
2785
- else consola.info(` ${args.dir !== "." ? "3" : "2"}. Edit src/sandbox-entry.ts to add hooks and routes`);
2786
- consola.info(` ${args.dir !== "." ? "4" : "3"}. emdash plugin validate --dir .`);
2909
+ const steps = [];
2910
+ if (args.dir !== ".") steps.push(`cd ${args.dir}`);
2911
+ steps.push("pnpm install");
2912
+ steps.push(isNative ? "Edit src/index.ts to add hooks and routes" : "Edit src/sandbox-entry.ts to add hooks and routes");
2913
+ steps.push("pnpm build");
2914
+ if (!isNative) steps.push("emdash plugin validate --dir .");
2915
+ steps.forEach((step, i) => consola.info(` ${i + 1}. ${step}`));
2787
2916
  }
2788
2917
  });
2918
+ async function resolveFormat(formatArg, nativeFlag) {
2919
+ if (formatArg) {
2920
+ const normalized = formatArg.toLowerCase();
2921
+ let parsed;
2922
+ if (normalized === "native") parsed = "native";
2923
+ else if (normalized === "sandboxed" || normalized === "standard") parsed = "standard";
2924
+ else {
2925
+ consola.error(`Invalid --format "${formatArg}". Use "sandboxed" or "native".`);
2926
+ process.exit(1);
2927
+ }
2928
+ if (nativeFlag && parsed !== "native") {
2929
+ consola.error(`Conflicting flags: --native and --format=${formatArg}. Pass only one.`);
2930
+ process.exit(1);
2931
+ }
2932
+ return parsed;
2933
+ }
2934
+ if (nativeFlag) return "native";
2935
+ if (!process.stdout.isTTY) return "standard";
2936
+ const choice = await consola.prompt("Which plugin format?", {
2937
+ type: "select",
2938
+ initial: "standard",
2939
+ options: [{
2940
+ label: "Sandboxed",
2941
+ value: "standard",
2942
+ hint: "runs in an isolated sandbox; safe to install from the marketplace"
2943
+ }, {
2944
+ label: "Native",
2945
+ value: "native",
2946
+ hint: "full runtime access; install from npm"
2947
+ }],
2948
+ cancel: "null"
2949
+ });
2950
+ if (choice === null) return null;
2951
+ return choice;
2952
+ }
2953
+ function camelCase(slug) {
2954
+ return slug.split("-").map((s, i) => i === 0 ? s : s[0].toUpperCase() + s.slice(1)).join("");
2955
+ }
2956
+ function pascalCase(slug) {
2957
+ return slug.split("-").map((s) => s[0].toUpperCase() + s.slice(1)).join("");
2958
+ }
2959
+ const TSCONFIG = {
2960
+ compilerOptions: {
2961
+ target: "ES2022",
2962
+ module: "preserve",
2963
+ moduleResolution: "bundler",
2964
+ strict: true,
2965
+ esModuleInterop: true,
2966
+ declaration: true,
2967
+ outDir: "./dist",
2968
+ rootDir: "./src"
2969
+ },
2970
+ include: ["src/**/*"],
2971
+ exclude: ["node_modules", "dist"]
2972
+ };
2973
+ const TSDOWN_VERSION = "^0.20.0";
2974
+ const TYPESCRIPT_VERSION = "^5.9.0";
2789
2975
  async function scaffoldStandard(targetDir, srcDir, pluginName, slug) {
2790
- const fnName = slug.split("-").map((s, i) => i === 0 ? s : s[0].toUpperCase() + s.slice(1)).join("");
2976
+ const fnName = camelCase(slug);
2791
2977
  await writeFile(join(targetDir, "package.json"), JSON.stringify({
2792
2978
  name: pluginName,
2793
2979
  version: "0.1.0",
2794
2980
  type: "module",
2981
+ main: "./dist/index.mjs",
2795
2982
  exports: {
2796
- ".": "./src/index.ts",
2797
- "./sandbox": "./src/sandbox-entry.ts"
2983
+ ".": {
2984
+ types: "./dist/index.d.mts",
2985
+ import: "./dist/index.mjs"
2986
+ },
2987
+ "./sandbox": {
2988
+ types: "./dist/sandbox-entry.d.mts",
2989
+ import: "./dist/sandbox-entry.mjs"
2990
+ }
2798
2991
  },
2799
- files: ["src"],
2800
- peerDependencies: { emdash: "*" }
2801
- }, null, " ") + "\n");
2802
- await writeFile(join(targetDir, "tsconfig.json"), JSON.stringify({
2803
- compilerOptions: {
2804
- target: "ES2022",
2805
- module: "preserve",
2806
- moduleResolution: "bundler",
2807
- strict: true,
2808
- esModuleInterop: true,
2809
- declaration: true,
2810
- outDir: "./dist",
2811
- rootDir: "./src"
2992
+ files: ["dist"],
2993
+ scripts: {
2994
+ build: "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --clean",
2995
+ dev: "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --watch",
2996
+ typecheck: "tsc --noEmit"
2812
2997
  },
2813
- include: ["src/**/*"],
2814
- exclude: ["node_modules", "dist"]
2998
+ keywords: ["emdash", "emdash-plugin"],
2999
+ license: "MIT",
3000
+ peerDependencies: { emdash: "*" },
3001
+ devDependencies: {
3002
+ emdash: "*",
3003
+ tsdown: TSDOWN_VERSION,
3004
+ typescript: TYPESCRIPT_VERSION
3005
+ }
2815
3006
  }, null, " ") + "\n");
3007
+ await writeFile(join(targetDir, "tsconfig.json"), JSON.stringify(TSCONFIG, null, " ") + "\n");
2816
3008
  await writeFile(join(srcDir, "index.ts"), `import type { PluginDescriptor } from "emdash";
2817
3009
 
2818
3010
  export function ${fnName}Plugin(): PluginDescriptor {
2819
3011
  \treturn {
2820
- \t\tid: "${pluginName}",
3012
+ \t\tid: "${slug}",
2821
3013
  \t\tversion: "0.1.0",
2822
3014
  \t\tformat: "standard",
2823
3015
  \t\tentrypoint: "${pluginName}/sandbox",
2824
- \t\tcapabilities: [],
3016
+
3017
+ \t\tcapabilities: ["content:read"],
3018
+ \t\tstorage: {
3019
+ \t\t\tevents: { indexes: ["timestamp"] },
3020
+ \t\t},
2825
3021
  \t};
2826
3022
  }
2827
3023
  `);
2828
3024
  await writeFile(join(srcDir, "sandbox-entry.ts"), `import { definePlugin } from "emdash";
2829
3025
  import type { PluginContext } from "emdash";
2830
3026
 
3027
+ interface ContentSaveEvent {
3028
+ \tcollection: string;
3029
+ \tcontent: { id: string };
3030
+ \tisNew: boolean;
3031
+ }
3032
+
2831
3033
  export default definePlugin({
2832
3034
  \thooks: {
2833
3035
  \t\t"content:afterSave": {
2834
- \t\t\thandler: async (event: any, ctx: PluginContext) => {
3036
+ \t\t\thandler: async (event: ContentSaveEvent, ctx: PluginContext) => {
2835
3037
  \t\t\t\tctx.log.info("Content saved", {
2836
3038
  \t\t\t\t\tcollection: event.collection,
2837
3039
  \t\t\t\t\tid: event.content.id,
2838
3040
  \t\t\t\t});
3041
+
3042
+ \t\t\t\tawait ctx.storage.events.put(\`save-\${Date.now()}\`, {
3043
+ \t\t\t\t\ttimestamp: new Date().toISOString(),
3044
+ \t\t\t\t\tcollection: event.collection,
3045
+ \t\t\t\t\tcontentId: event.content.id,
3046
+ \t\t\t\t});
3047
+ \t\t\t},
3048
+ \t\t},
3049
+ \t},
3050
+
3051
+ \troutes: {
3052
+ \t\trecent: {
3053
+ \t\t\thandler: async (_routeCtx, ctx: PluginContext) => {
3054
+ \t\t\t\tconst result = await ctx.storage.events.query({
3055
+ \t\t\t\t\torderBy: { timestamp: "desc" },
3056
+ \t\t\t\t\tlimit: 10,
3057
+ \t\t\t\t});
3058
+ \t\t\t\treturn { events: result.items };
2839
3059
  \t\t\t},
2840
3060
  \t\t},
2841
3061
  \t},
@@ -2843,55 +3063,82 @@ export default definePlugin({
2843
3063
  `);
2844
3064
  }
2845
3065
  async function scaffoldNative(targetDir, srcDir, pluginName, slug) {
2846
- const fnName = slug.split("-").map((s, i) => i === 0 ? s : s[0].toUpperCase() + s.slice(1)).join("");
3066
+ const fnName = camelCase(slug);
3067
+ const typeName = pascalCase(slug);
2847
3068
  await writeFile(join(targetDir, "package.json"), JSON.stringify({
2848
3069
  name: pluginName,
2849
3070
  version: "0.1.0",
2850
3071
  type: "module",
2851
- exports: { ".": "./src/index.ts" },
2852
- files: ["src"],
2853
- peerDependencies: { emdash: "*" }
2854
- }, null, " ") + "\n");
2855
- await writeFile(join(targetDir, "tsconfig.json"), JSON.stringify({
2856
- compilerOptions: {
2857
- target: "ES2022",
2858
- module: "preserve",
2859
- moduleResolution: "bundler",
2860
- strict: true,
2861
- esModuleInterop: true,
2862
- declaration: true,
2863
- outDir: "./dist",
2864
- rootDir: "./src"
3072
+ main: "./dist/index.mjs",
3073
+ exports: { ".": {
3074
+ types: "./dist/index.d.mts",
3075
+ import: "./dist/index.mjs"
3076
+ } },
3077
+ files: ["dist"],
3078
+ scripts: {
3079
+ build: "tsdown src/index.ts --format esm --dts --clean",
3080
+ dev: "tsdown src/index.ts --format esm --dts --watch",
3081
+ typecheck: "tsc --noEmit"
2865
3082
  },
2866
- include: ["src/**/*"],
2867
- exclude: ["node_modules", "dist"]
3083
+ keywords: ["emdash", "emdash-plugin"],
3084
+ license: "MIT",
3085
+ peerDependencies: { emdash: "*" },
3086
+ devDependencies: {
3087
+ emdash: "*",
3088
+ tsdown: TSDOWN_VERSION,
3089
+ typescript: TYPESCRIPT_VERSION
3090
+ }
2868
3091
  }, null, " ") + "\n");
3092
+ await writeFile(join(targetDir, "tsconfig.json"), JSON.stringify(TSCONFIG, null, " ") + "\n");
2869
3093
  await writeFile(join(srcDir, "index.ts"), `import { definePlugin } from "emdash";
2870
3094
  import type { PluginDescriptor } from "emdash";
2871
3095
 
2872
- export function ${fnName}Plugin(): PluginDescriptor {
3096
+ export interface ${typeName}Options {
3097
+ \tenabled?: boolean;
3098
+ }
3099
+
3100
+ export function ${fnName}Plugin(options: ${typeName}Options = {}): PluginDescriptor<${typeName}Options> {
2873
3101
  \treturn {
2874
- \t\tid: "${pluginName}",
3102
+ \t\tid: "${slug}",
2875
3103
  \t\tversion: "0.1.0",
2876
3104
  \t\tformat: "native",
2877
3105
  \t\tentrypoint: "${pluginName}",
2878
- \t\toptions: {},
3106
+ \t\toptions,
2879
3107
  \t};
2880
3108
  }
2881
3109
 
2882
- export function createPlugin() {
3110
+ export function createPlugin(options: ${typeName}Options = {}) {
2883
3111
  \treturn definePlugin({
2884
- \t\tid: "${pluginName}",
3112
+ \t\tid: "${slug}",
2885
3113
  \t\tversion: "0.1.0",
2886
3114
 
3115
+ \t\tcapabilities: ["content:read"],
3116
+ \t\tstorage: {
3117
+ \t\t\tevents: { indexes: ["createdAt"] },
3118
+ \t\t},
3119
+
2887
3120
  \t\thooks: {
2888
3121
  \t\t\t"content:afterSave": async (event, ctx) => {
2889
- \t\t\t\tctx.log.info("Content saved", {
3122
+ \t\t\t\tif (options.enabled === false) return;
3123
+ \t\t\t\tawait ctx.storage.events.put(\`evt_\${Date.now()}\`, {
2890
3124
  \t\t\t\t\tcollection: event.collection,
2891
- \t\t\t\t\tid: event.content.id,
3125
+ \t\t\t\t\tcontentId: event.content.id,
3126
+ \t\t\t\t\tcreatedAt: new Date().toISOString(),
2892
3127
  \t\t\t\t});
2893
3128
  \t\t\t},
2894
3129
  \t\t},
3130
+
3131
+ \t\troutes: {
3132
+ \t\t\trecent: {
3133
+ \t\t\t\thandler: async (ctx) => {
3134
+ \t\t\t\t\tconst result = await ctx.storage.events.query({
3135
+ \t\t\t\t\t\torderBy: { createdAt: "desc" },
3136
+ \t\t\t\t\t\tlimit: 10,
3137
+ \t\t\t\t\t});
3138
+ \t\t\t\t\treturn { events: result.items };
3139
+ \t\t\t\t},
3140
+ \t\t\t},
3141
+ \t\t},
2895
3142
  \t});
2896
3143
  }
2897
3144