nebula-cms 0.1.3 → 0.1.5

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 (381) hide show
  1. package/README.md +3 -1
  2. package/dist/astro/index.d.ts +43 -0
  3. package/dist/astro/index.d.ts.map +1 -0
  4. package/dist/astro/index.js +223 -0
  5. package/{src → dist}/client/Admin.svelte +20 -11
  6. package/dist/client/Admin.svelte.d.ts +11 -0
  7. package/dist/client/Admin.svelte.d.ts.map +1 -0
  8. package/dist/client/components/BackendPicker.svelte.d.ts +4 -0
  9. package/dist/client/components/BackendPicker.svelte.d.ts.map +1 -0
  10. package/dist/client/components/DraftChip.svelte.d.ts +10 -0
  11. package/dist/client/components/DraftChip.svelte.d.ts.map +1 -0
  12. package/dist/client/components/MetadataForm.svelte.d.ts +12 -0
  13. package/dist/client/components/MetadataForm.svelte.d.ts.map +1 -0
  14. package/dist/client/components/ThemeToggle.svelte.d.ts +19 -0
  15. package/dist/client/components/ThemeToggle.svelte.d.ts.map +1 -0
  16. package/dist/client/components/dialogs/DeleteDraftDialog.svelte.d.ts +11 -0
  17. package/dist/client/components/dialogs/DeleteDraftDialog.svelte.d.ts.map +1 -0
  18. package/{src → dist}/client/components/dialogs/FilenameDialog.svelte +1 -2
  19. package/dist/client/components/dialogs/FilenameDialog.svelte.d.ts +13 -0
  20. package/dist/client/components/dialogs/FilenameDialog.svelte.d.ts.map +1 -0
  21. package/dist/client/components/editor/EditorPane.svelte.d.ts +4 -0
  22. package/dist/client/components/editor/EditorPane.svelte.d.ts.map +1 -0
  23. package/dist/client/components/editor/EditorTabs.svelte.d.ts +8 -0
  24. package/dist/client/components/editor/EditorTabs.svelte.d.ts.map +1 -0
  25. package/dist/client/components/editor/EditorToolbar.svelte.d.ts +4 -0
  26. package/dist/client/components/editor/EditorToolbar.svelte.d.ts.map +1 -0
  27. package/dist/client/components/editor/FormatSelector.svelte.d.ts +4 -0
  28. package/dist/client/components/editor/FormatSelector.svelte.d.ts.map +1 -0
  29. package/dist/client/components/editor/Toolbar.svelte.d.ts +19 -0
  30. package/dist/client/components/editor/Toolbar.svelte.d.ts.map +1 -0
  31. package/dist/client/components/fields/ArrayField.svelte.d.ts +15 -0
  32. package/dist/client/components/fields/ArrayField.svelte.d.ts.map +1 -0
  33. package/dist/client/components/fields/ArrayItem.svelte.d.ts +28 -0
  34. package/dist/client/components/fields/ArrayItem.svelte.d.ts.map +1 -0
  35. package/dist/client/components/fields/BooleanField.svelte.d.ts +16 -0
  36. package/dist/client/components/fields/BooleanField.svelte.d.ts.map +1 -0
  37. package/dist/client/components/fields/DateField.svelte.d.ts +16 -0
  38. package/dist/client/components/fields/DateField.svelte.d.ts.map +1 -0
  39. package/dist/client/components/fields/EnumField.svelte.d.ts +17 -0
  40. package/dist/client/components/fields/EnumField.svelte.d.ts.map +1 -0
  41. package/dist/client/components/fields/FieldWrapper.svelte.d.ts +18 -0
  42. package/dist/client/components/fields/FieldWrapper.svelte.d.ts.map +1 -0
  43. package/dist/client/components/fields/NumberField.svelte.d.ts +16 -0
  44. package/dist/client/components/fields/NumberField.svelte.d.ts.map +1 -0
  45. package/dist/client/components/fields/ObjectField.svelte.d.ts +16 -0
  46. package/dist/client/components/fields/ObjectField.svelte.d.ts.map +1 -0
  47. package/dist/client/components/fields/SchemaField.svelte.d.ts +16 -0
  48. package/dist/client/components/fields/SchemaField.svelte.d.ts.map +1 -0
  49. package/dist/client/components/fields/StringField.svelte.d.ts +16 -0
  50. package/dist/client/components/fields/StringField.svelte.d.ts.map +1 -0
  51. package/{src → dist}/client/components/sidebar/AdminSidebar.svelte +2 -4
  52. package/dist/client/components/sidebar/AdminSidebar.svelte.d.ts +19 -0
  53. package/dist/client/components/sidebar/AdminSidebar.svelte.d.ts.map +1 -0
  54. package/dist/client/components/sidebar/AdminSidebarSort.svelte.d.ts +12 -0
  55. package/dist/client/components/sidebar/AdminSidebarSort.svelte.d.ts.map +1 -0
  56. package/dist/client/css/icons.css +29 -0
  57. package/dist/client/index.d.ts +2 -0
  58. package/dist/client/index.d.ts.map +1 -0
  59. package/dist/client/js/drafts/merge.svelte.d.ts +24 -0
  60. package/dist/client/js/drafts/merge.svelte.d.ts.map +1 -0
  61. package/dist/client/js/drafts/merge.svelte.js +106 -0
  62. package/dist/client/js/drafts/ops.svelte.d.ts +31 -0
  63. package/dist/client/js/drafts/ops.svelte.d.ts.map +1 -0
  64. package/dist/client/js/drafts/ops.svelte.js +182 -0
  65. package/dist/client/js/drafts/storage.d.ts +45 -0
  66. package/dist/client/js/drafts/storage.d.ts.map +1 -0
  67. package/dist/client/js/drafts/storage.js +76 -0
  68. package/dist/client/js/drafts/workers/diff.d.ts +2 -0
  69. package/dist/client/js/drafts/workers/diff.d.ts.map +1 -0
  70. package/dist/client/js/drafts/workers/diff.js +20 -0
  71. package/dist/client/js/editor/editor.svelte.d.ts +124 -0
  72. package/dist/client/js/editor/editor.svelte.d.ts.map +1 -0
  73. package/dist/client/js/editor/editor.svelte.js +294 -0
  74. package/dist/client/js/editor/languages.d.ts +11 -0
  75. package/dist/client/js/editor/languages.d.ts.map +1 -0
  76. package/dist/client/js/editor/languages.js +93 -0
  77. package/dist/client/js/editor/link-wrap.d.ts +6 -0
  78. package/dist/client/js/editor/link-wrap.d.ts.map +1 -0
  79. package/{src/client/js/editor/link-wrap.ts → dist/client/js/editor/link-wrap.js} +17 -24
  80. package/dist/client/js/editor/markdown-shortcuts.d.ts +4 -0
  81. package/dist/client/js/editor/markdown-shortcuts.d.ts.map +1 -0
  82. package/dist/client/js/editor/markdown-shortcuts.js +219 -0
  83. package/dist/client/js/handlers/admin.d.ts +64 -0
  84. package/dist/client/js/handlers/admin.d.ts.map +1 -0
  85. package/dist/client/js/handlers/admin.js +186 -0
  86. package/dist/client/js/state/dialogs.svelte.d.ts +16 -0
  87. package/dist/client/js/state/dialogs.svelte.d.ts.map +1 -0
  88. package/dist/client/js/state/dialogs.svelte.js +28 -0
  89. package/dist/client/js/state/router.svelte.d.ts +44 -0
  90. package/dist/client/js/state/router.svelte.d.ts.map +1 -0
  91. package/dist/client/js/state/router.svelte.js +141 -0
  92. package/dist/client/js/state/schema.svelte.d.ts +51 -0
  93. package/dist/client/js/state/schema.svelte.d.ts.map +1 -0
  94. package/{src/client/js/state/schema.svelte.ts → dist/client/js/state/schema.svelte.js} +55 -70
  95. package/dist/client/js/state/state.svelte.d.ts +68 -0
  96. package/dist/client/js/state/state.svelte.d.ts.map +1 -0
  97. package/dist/client/js/state/state.svelte.js +300 -0
  98. package/dist/client/js/state/theme.svelte.d.ts +24 -0
  99. package/dist/client/js/state/theme.svelte.d.ts.map +1 -0
  100. package/{src/client/js/state/theme.svelte.ts → dist/client/js/state/theme.svelte.js} +54 -91
  101. package/dist/client/js/storage/adapter.d.ts +130 -0
  102. package/dist/client/js/storage/adapter.d.ts.map +1 -0
  103. package/dist/client/js/storage/adapter.js +5 -0
  104. package/dist/client/js/storage/client.d.ts +72 -0
  105. package/dist/client/js/storage/client.d.ts.map +1 -0
  106. package/dist/client/js/storage/client.js +121 -0
  107. package/dist/client/js/storage/db.d.ts +8 -0
  108. package/dist/client/js/storage/db.d.ts.map +1 -0
  109. package/dist/client/js/storage/db.js +35 -0
  110. package/dist/client/js/storage/fsa.d.ts +51 -0
  111. package/dist/client/js/storage/fsa.d.ts.map +1 -0
  112. package/dist/client/js/storage/fsa.js +91 -0
  113. package/dist/client/js/storage/github.d.ts +62 -0
  114. package/dist/client/js/storage/github.d.ts.map +1 -0
  115. package/dist/client/js/storage/github.js +216 -0
  116. package/dist/client/js/storage/storage.d.ts +32 -0
  117. package/dist/client/js/storage/storage.d.ts.map +1 -0
  118. package/dist/client/js/storage/storage.js +68 -0
  119. package/dist/client/js/storage/workers/frontmatter.d.ts +2 -0
  120. package/dist/client/js/storage/workers/frontmatter.d.ts.map +1 -0
  121. package/dist/client/js/storage/workers/frontmatter.js +253 -0
  122. package/dist/client/js/storage/workers/storage.d.ts +2 -0
  123. package/dist/client/js/storage/workers/storage.d.ts.map +1 -0
  124. package/dist/client/js/storage/workers/storage.js +167 -0
  125. package/dist/client/js/storage/workers/toml-parser.d.ts +2 -0
  126. package/dist/client/js/storage/workers/toml-parser.d.ts.map +1 -0
  127. package/dist/client/js/storage/workers/toml-parser.js +75 -0
  128. package/dist/client/js/storage/workers/yaml-parser.d.ts +2 -0
  129. package/dist/client/js/storage/workers/yaml-parser.d.ts.map +1 -0
  130. package/dist/client/js/storage/workers/yaml-parser.js +100 -0
  131. package/dist/client/js/utils/file-types.d.ts +58 -0
  132. package/dist/client/js/utils/file-types.d.ts.map +1 -0
  133. package/{src/client/js/utils/file-types.ts → dist/client/js/utils/file-types.js} +75 -107
  134. package/dist/client/js/utils/format.d.ts +8 -0
  135. package/dist/client/js/utils/format.d.ts.map +1 -0
  136. package/{src/client/js/utils/format.ts → dist/client/js/utils/format.js} +5 -6
  137. package/dist/client/js/utils/frontmatter.d.ts +12 -0
  138. package/dist/client/js/utils/frontmatter.d.ts.map +1 -0
  139. package/dist/client/js/utils/frontmatter.js +29 -0
  140. package/dist/client/js/utils/schema-utils.d.ts +110 -0
  141. package/dist/client/js/utils/schema-utils.d.ts.map +1 -0
  142. package/dist/client/js/utils/schema-utils.js +242 -0
  143. package/dist/client/js/utils/slug.d.ts +8 -0
  144. package/dist/client/js/utils/slug.d.ts.map +1 -0
  145. package/{src/client/js/utils/slug.ts → dist/client/js/utils/slug.js} +6 -7
  146. package/dist/client/js/utils/sort.d.ts +41 -0
  147. package/dist/client/js/utils/sort.d.ts.map +1 -0
  148. package/dist/client/js/utils/sort.js +65 -0
  149. package/dist/client/js/utils/stable-stringify.d.ts +8 -0
  150. package/dist/client/js/utils/stable-stringify.d.ts.map +1 -0
  151. package/dist/client/js/utils/stable-stringify.js +23 -0
  152. package/dist/client/js/utils/url-utils.d.ts +11 -0
  153. package/dist/client/js/utils/url-utils.d.ts.map +1 -0
  154. package/{src/client/js/utils/url-utils.ts → dist/client/js/utils/url-utils.js} +22 -23
  155. package/dist/client/types/browser-apis.d.ts +39 -0
  156. package/dist/types.d.ts +22 -0
  157. package/dist/types.d.ts.map +1 -0
  158. package/dist/types.js +1 -0
  159. package/package.json +13 -3
  160. package/.github/workflows/ci.yml +0 -27
  161. package/.github/workflows/publish.yml +0 -34
  162. package/.mcp.json +0 -12
  163. package/.prettierignore +0 -5
  164. package/.prettierrc.cjs +0 -22
  165. package/AGENTS.md +0 -183
  166. package/playground/astro.config.mjs +0 -7
  167. package/playground/node_modules/.bin/astro +0 -21
  168. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js +0 -85
  169. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js.map +0 -7
  170. package/playground/node_modules/.vite/deps/_metadata.json +0 -184
  171. package/playground/node_modules/.vite/deps/astro___aria-query.js +0 -6776
  172. package/playground/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
  173. package/playground/node_modules/.vite/deps/astro___axobject-query.js +0 -3754
  174. package/playground/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
  175. package/playground/node_modules/.vite/deps/astro___html-escaper.js +0 -34
  176. package/playground/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
  177. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js +0 -0
  178. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js.map +0 -7
  179. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js +0 -8
  180. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +0 -7
  181. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js +0 -21
  182. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js.map +0 -7
  183. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js +0 -223
  184. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js.map +0 -7
  185. package/playground/node_modules/.vite/deps/chunk-FPEUJ7DG.js +0 -27
  186. package/playground/node_modules/.vite/deps/chunk-FPEUJ7DG.js.map +0 -7
  187. package/playground/node_modules/.vite/deps/chunk-MHDZ3SK7.js +0 -1005
  188. package/playground/node_modules/.vite/deps/chunk-MHDZ3SK7.js.map +0 -7
  189. package/playground/node_modules/.vite/deps/chunk-RBDTDTPY.js +0 -204
  190. package/playground/node_modules/.vite/deps/chunk-RBDTDTPY.js.map +0 -7
  191. package/playground/node_modules/.vite/deps/chunk-RJGEXL5C.js +0 -688
  192. package/playground/node_modules/.vite/deps/chunk-RJGEXL5C.js.map +0 -7
  193. package/playground/node_modules/.vite/deps/chunk-YL4MIWGJ.js +0 -5099
  194. package/playground/node_modules/.vite/deps/chunk-YL4MIWGJ.js.map +0 -7
  195. package/playground/node_modules/.vite/deps/chunk-ZOV3DWEJ.js +0 -4376
  196. package/playground/node_modules/.vite/deps/chunk-ZOV3DWEJ.js.map +0 -7
  197. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js +0 -23
  198. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js.map +0 -7
  199. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js +0 -148
  200. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js.map +0 -7
  201. package/playground/node_modules/.vite/deps/package.json +0 -3
  202. package/playground/node_modules/.vite/deps/smol-toml.js +0 -843
  203. package/playground/node_modules/.vite/deps/smol-toml.js.map +0 -7
  204. package/playground/node_modules/.vite/deps/svelte.js +0 -55
  205. package/playground/node_modules/.vite/deps/svelte.js.map +0 -7
  206. package/playground/node_modules/.vite/deps/svelte___clsx.js +0 -9
  207. package/playground/node_modules/.vite/deps/svelte___clsx.js.map +0 -7
  208. package/playground/node_modules/.vite/deps/svelte_animate.js +0 -57
  209. package/playground/node_modules/.vite/deps/svelte_animate.js.map +0 -7
  210. package/playground/node_modules/.vite/deps/svelte_attachments.js +0 -15
  211. package/playground/node_modules/.vite/deps/svelte_attachments.js.map +0 -7
  212. package/playground/node_modules/.vite/deps/svelte_easing.js +0 -67
  213. package/playground/node_modules/.vite/deps/svelte_easing.js.map +0 -7
  214. package/playground/node_modules/.vite/deps/svelte_events.js +0 -11
  215. package/playground/node_modules/.vite/deps/svelte_events.js.map +0 -7
  216. package/playground/node_modules/.vite/deps/svelte_internal.js +0 -5
  217. package/playground/node_modules/.vite/deps/svelte_internal.js.map +0 -7
  218. package/playground/node_modules/.vite/deps/svelte_internal_client.js +0 -402
  219. package/playground/node_modules/.vite/deps/svelte_internal_client.js.map +0 -7
  220. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js +0 -10
  221. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js.map +0 -7
  222. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js +0 -8
  223. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js.map +0 -7
  224. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js +0 -8
  225. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js.map +0 -7
  226. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js +0 -8
  227. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js.map +0 -7
  228. package/playground/node_modules/.vite/deps/svelte_legacy.js +0 -35
  229. package/playground/node_modules/.vite/deps/svelte_legacy.js.map +0 -7
  230. package/playground/node_modules/.vite/deps/svelte_motion.js +0 -545
  231. package/playground/node_modules/.vite/deps/svelte_motion.js.map +0 -7
  232. package/playground/node_modules/.vite/deps/svelte_reactivity.js +0 -29
  233. package/playground/node_modules/.vite/deps/svelte_reactivity.js.map +0 -7
  234. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js +0 -127
  235. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js.map +0 -7
  236. package/playground/node_modules/.vite/deps/svelte_store.js +0 -103
  237. package/playground/node_modules/.vite/deps/svelte_store.js.map +0 -7
  238. package/playground/node_modules/.vite/deps/svelte_transition.js +0 -208
  239. package/playground/node_modules/.vite/deps/svelte_transition.js.map +0 -7
  240. package/playground/package.json +0 -16
  241. package/playground/pnpm-lock.yaml +0 -3167
  242. package/playground/src/content/authors/jane-doe.json +0 -8
  243. package/playground/src/content/config/build.toml +0 -2
  244. package/playground/src/content/courses/web-fundamentals.json +0 -29
  245. package/playground/src/content/docs/advanced.mdx +0 -6
  246. package/playground/src/content/docs/intro.md +0 -6
  247. package/playground/src/content/guides/getting-started.mdx +0 -6
  248. package/playground/src/content/posts/hello-world.md +0 -7
  249. package/playground/src/content/products/t-shirt.json +0 -16
  250. package/playground/src/content/recipes/pancakes.mdoc +0 -8
  251. package/playground/src/content/settings/site.yml +0 -2
  252. package/playground/src/content.config.ts +0 -198
  253. package/playground/src/env.d.ts +0 -1
  254. package/playground/src/pages/index.astro +0 -11
  255. package/playground/src/pages/nebula.astro +0 -14
  256. package/pnpm-workspace.yaml +0 -2
  257. package/scripts/subset-icons.mjs +0 -178
  258. package/src/astro/index.ts +0 -295
  259. package/src/client/js/drafts/merge.svelte.ts +0 -121
  260. package/src/client/js/drafts/ops.svelte.ts +0 -227
  261. package/src/client/js/drafts/storage.ts +0 -108
  262. package/src/client/js/drafts/workers/diff.ts +0 -40
  263. package/src/client/js/editor/editor.svelte.ts +0 -343
  264. package/src/client/js/editor/languages.ts +0 -98
  265. package/src/client/js/editor/markdown-shortcuts.ts +0 -261
  266. package/src/client/js/handlers/admin.ts +0 -246
  267. package/src/client/js/state/dialogs.svelte.ts +0 -35
  268. package/src/client/js/state/router.svelte.ts +0 -156
  269. package/src/client/js/state/state.svelte.ts +0 -334
  270. package/src/client/js/storage/adapter.ts +0 -102
  271. package/src/client/js/storage/client.ts +0 -150
  272. package/src/client/js/storage/db.ts +0 -36
  273. package/src/client/js/storage/fsa.ts +0 -110
  274. package/src/client/js/storage/github.ts +0 -297
  275. package/src/client/js/storage/storage.ts +0 -83
  276. package/src/client/js/storage/workers/frontmatter.ts +0 -320
  277. package/src/client/js/storage/workers/storage.ts +0 -177
  278. package/src/client/js/storage/workers/toml-parser.ts +0 -106
  279. package/src/client/js/storage/workers/yaml-parser.ts +0 -132
  280. package/src/client/js/utils/frontmatter.ts +0 -38
  281. package/src/client/js/utils/schema-utils.ts +0 -295
  282. package/src/client/js/utils/sort.ts +0 -84
  283. package/src/client/js/utils/stable-stringify.ts +0 -27
  284. package/src/types.ts +0 -25
  285. package/svelte.config.js +0 -4
  286. package/tests/astro/build.test.ts +0 -63
  287. package/tests/astro/index.test.ts +0 -689
  288. package/tests/client/components/Admin.test.ts +0 -446
  289. package/tests/client/components/BackendPicker.test.ts +0 -239
  290. package/tests/client/components/DraftChip.test.ts +0 -53
  291. package/tests/client/components/MetadataForm.test.ts +0 -164
  292. package/tests/client/components/dialogs/DeleteDraftDialog.test.ts +0 -91
  293. package/tests/client/components/dialogs/FilenameDialog.test.ts +0 -209
  294. package/tests/client/components/dialogs/dialog-stubs.ts +0 -19
  295. package/tests/client/components/editor/EditorPane.test.ts +0 -100
  296. package/tests/client/components/editor/EditorTabs.test.ts +0 -253
  297. package/tests/client/components/editor/EditorToolbar.test.ts +0 -252
  298. package/tests/client/components/editor/fixtures.ts +0 -31
  299. package/tests/client/components/fields/ArrayField.test.ts +0 -197
  300. package/tests/client/components/fields/BooleanField.test.ts +0 -206
  301. package/tests/client/components/fields/DateField.test.ts +0 -210
  302. package/tests/client/components/fields/EnumField.test.ts +0 -246
  303. package/tests/client/components/fields/NumberField.test.ts +0 -240
  304. package/tests/client/components/fields/ObjectField.test.ts +0 -157
  305. package/tests/client/components/fields/SchemaField.test.ts +0 -190
  306. package/tests/client/components/fields/StringField.test.ts +0 -223
  307. package/tests/client/components/sidebar/AdminSidebar.test.ts +0 -285
  308. package/tests/client/components/sidebar/AdminSidebarSort.test.ts +0 -135
  309. package/tests/client/components/sidebar/sort-mock.ts +0 -23
  310. package/tests/client/js/drafts/fixtures.ts +0 -22
  311. package/tests/client/js/drafts/merge.test.ts +0 -282
  312. package/tests/client/js/drafts/ops.test.ts +0 -658
  313. package/tests/client/js/drafts/storage.test.ts +0 -200
  314. package/tests/client/js/drafts/workers/diff.test.ts +0 -165
  315. package/tests/client/js/editor/editor.test.ts +0 -616
  316. package/tests/client/js/editor/link-wrap.test.ts +0 -225
  317. package/tests/client/js/editor/markdown-shortcuts.test.ts +0 -370
  318. package/tests/client/js/handlers/admin.test.ts +0 -467
  319. package/tests/client/js/state/router.test.ts +0 -619
  320. package/tests/client/js/state/schema.test.ts +0 -266
  321. package/tests/client/js/state/state.test.ts +0 -328
  322. package/tests/client/js/storage/adapter.test.ts +0 -115
  323. package/tests/client/js/storage/client.test.ts +0 -250
  324. package/tests/client/js/storage/db.test.ts +0 -59
  325. package/tests/client/js/storage/fsa.test.ts +0 -284
  326. package/tests/client/js/storage/github.test.ts +0 -349
  327. package/tests/client/js/storage/mock-port.ts +0 -95
  328. package/tests/client/js/storage/storage.test.ts +0 -77
  329. package/tests/client/js/storage/workers/frontmatter.test.ts +0 -479
  330. package/tests/client/js/storage/workers/storage.test.ts +0 -299
  331. package/tests/client/js/storage/workers/toml-parser.test.ts +0 -169
  332. package/tests/client/js/storage/workers/yaml-parser.test.ts +0 -168
  333. package/tests/client/js/utils/file-types.test.ts +0 -268
  334. package/tests/client/js/utils/frontmatter.test.ts +0 -87
  335. package/tests/client/js/utils/schema-utils.test.ts +0 -318
  336. package/tests/client/js/utils/slug.test.ts +0 -58
  337. package/tests/client/js/utils/sort.test.ts +0 -276
  338. package/tests/client/js/utils/stable-stringify.test.ts +0 -68
  339. package/tests/client/js/utils/url-utils.test.ts +0 -70
  340. package/tests/e2e/backend-connection.test.ts +0 -301
  341. package/tests/e2e/draft-lifecycle.test.ts +0 -388
  342. package/tests/e2e/editing.test.ts +0 -355
  343. package/tests/e2e/github-adapter.test.ts +0 -330
  344. package/tests/e2e/helpers/mock-adapter.ts +0 -166
  345. package/tests/e2e/helpers/test-app.ts +0 -155
  346. package/tests/e2e/navigation.test.ts +0 -358
  347. package/tests/e2e/publishing.test.ts +0 -345
  348. package/tests/e2e/unsaved-changes.test.ts +0 -317
  349. package/tests/setup.ts +0 -2
  350. package/tests/stubs/codemirror.ts +0 -197
  351. package/tsconfig.json +0 -19
  352. package/vitest.config.ts +0 -178
  353. /package/{src → dist}/client/components/BackendPicker.svelte +0 -0
  354. /package/{src → dist}/client/components/DraftChip.svelte +0 -0
  355. /package/{src → dist}/client/components/MetadataForm.svelte +0 -0
  356. /package/{src → dist}/client/components/ThemeToggle.svelte +0 -0
  357. /package/{src → dist}/client/components/dialogs/DeleteDraftDialog.svelte +0 -0
  358. /package/{src → dist}/client/components/editor/EditorPane.svelte +0 -0
  359. /package/{src → dist}/client/components/editor/EditorTabs.svelte +0 -0
  360. /package/{src → dist}/client/components/editor/EditorToolbar.svelte +0 -0
  361. /package/{src → dist}/client/components/editor/FormatSelector.svelte +0 -0
  362. /package/{src → dist}/client/components/editor/Toolbar.svelte +0 -0
  363. /package/{src → dist}/client/components/fields/ArrayField.svelte +0 -0
  364. /package/{src → dist}/client/components/fields/ArrayItem.svelte +0 -0
  365. /package/{src → dist}/client/components/fields/BooleanField.svelte +0 -0
  366. /package/{src → dist}/client/components/fields/DateField.svelte +0 -0
  367. /package/{src → dist}/client/components/fields/EnumField.svelte +0 -0
  368. /package/{src → dist}/client/components/fields/FieldWrapper.svelte +0 -0
  369. /package/{src → dist}/client/components/fields/NumberField.svelte +0 -0
  370. /package/{src → dist}/client/components/fields/ObjectField.svelte +0 -0
  371. /package/{src → dist}/client/components/fields/SchemaField.svelte +0 -0
  372. /package/{src → dist}/client/components/fields/StringField.svelte +0 -0
  373. /package/{src → dist}/client/components/sidebar/AdminSidebarSort.svelte +0 -0
  374. /package/{src → dist}/client/css/a11y.css +0 -0
  375. /package/{src → dist}/client/css/btn.css +0 -0
  376. /package/{src → dist}/client/css/dialog.css +0 -0
  377. /package/{src → dist}/client/css/field-input.css +0 -0
  378. /package/{src → dist}/client/css/reset.css +0 -0
  379. /package/{src → dist}/client/css/theme.css +0 -0
  380. /package/{src/client/index.ts → dist/client/index.js} +0 -0
  381. /package/{src → dist}/virtual.d.ts +0 -0
@@ -1,284 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
-
3
- //////////////////////////////
4
- // FsaAdapter tests
5
- //
6
- // The File System Access API is not available in Node.js, so we build a
7
- // minimal in-memory mock of FileSystemDirectoryHandle / FileSystemFileHandle
8
- // that mirrors the FSA contract expected by FsaAdapter.
9
- //////////////////////////////
10
-
11
- import { FsaAdapter } from '../../../../src/client/js/storage/fsa';
12
-
13
- // ── Helpers ──────────────────────────────────────────────────────────────────
14
-
15
- /**
16
- * Writable mock that stores text and returns it via text().
17
- */
18
- interface MockFile {
19
- _content: string;
20
- text(): Promise<string>;
21
- }
22
-
23
- /**
24
- * Creates a minimal File mock.
25
- * @param {string} content - The file content
26
- * @return {MockFile} A mock File object
27
- */
28
- function makeFile(content: string): MockFile {
29
- return {
30
- _content: content,
31
- text: async () => content,
32
- };
33
- }
34
-
35
- /**
36
- * Creates a mock FileSystemWritableFileStream.
37
- * @param {MockFileHandle} owner - The handle that owns this stream
38
- * @return {object} A mock writable stream
39
- */
40
- function makeWritable(owner: MockFileHandle) {
41
- return {
42
- write: vi.fn(async (data: string) => {
43
- owner._content = data;
44
- }),
45
- close: vi.fn(async () => undefined),
46
- };
47
- }
48
-
49
- /**
50
- * Mock FileSystemFileHandle backed by an in-memory string.
51
- */
52
- class MockFileHandle {
53
- kind = 'file' as const;
54
- _content: string;
55
-
56
- /**
57
- * @param {string} content - Initial file content
58
- */
59
- constructor(content = '') {
60
- this._content = content;
61
- }
62
-
63
- /**
64
- * Returns the mock File for this handle.
65
- * @return {Promise<MockFile>} The file object
66
- */
67
- async getFile(): Promise<MockFile> {
68
- return makeFile(this._content);
69
- }
70
-
71
- /**
72
- * Returns a writable stream for this handle.
73
- * @return {Promise<ReturnType<typeof makeWritable>>} The writable stream
74
- */
75
- async createWritable() {
76
- return makeWritable(this);
77
- }
78
- }
79
-
80
- /**
81
- * Builds a mock FileSystemDirectoryHandle that holds a flat map of name → entry.
82
- * @param {Record<string, MockFileHandle | MockDirHandle>} children - The directory's children
83
- * @return {MockDirHandle} A mock directory handle
84
- */
85
- class MockDirHandle {
86
- kind = 'directory' as const;
87
- private children: Map<string, MockFileHandle | MockDirHandle>;
88
-
89
- /**
90
- * @param {Record<string, MockFileHandle | MockDirHandle>} entries - Initial children map
91
- */
92
- constructor(entries: Record<string, MockFileHandle | MockDirHandle> = {}) {
93
- this.children = new Map(Object.entries(entries));
94
- }
95
-
96
- /**
97
- * Returns a child directory handle by name.
98
- * @param {string} name - The directory name
99
- * @return {Promise<MockDirHandle>} The directory handle
100
- */
101
- async getDirectoryHandle(name: string): Promise<MockDirHandle> {
102
- const entry = this.children.get(name);
103
- if (!entry || entry.kind !== 'directory') {
104
- throw new DOMException(`${name} not found`, 'NotFoundError');
105
- }
106
- return entry as MockDirHandle;
107
- }
108
-
109
- /**
110
- * Returns a child file handle, optionally creating it.
111
- * @param {string} name - The file name
112
- * @param {{ create?: boolean }} options - Options object
113
- * @return {Promise<MockFileHandle>} The file handle
114
- */
115
- async getFileHandle(
116
- name: string,
117
- options?: { create?: boolean },
118
- ): Promise<MockFileHandle> {
119
- if (!this.children.has(name)) {
120
- if (options?.create) {
121
- const newHandle = new MockFileHandle('');
122
- this.children.set(name, newHandle);
123
- return newHandle;
124
- }
125
- throw new DOMException(`${name} not found`, 'NotFoundError');
126
- }
127
- return this.children.get(name) as MockFileHandle;
128
- }
129
-
130
- /**
131
- * Removes a child entry by name.
132
- * @param {string} name - The entry name to remove
133
- * @return {Promise<void>}
134
- */
135
- async removeEntry(name: string): Promise<void> {
136
- if (!this.children.has(name)) {
137
- throw new DOMException(`${name} not found`, 'NotFoundError');
138
- }
139
- this.children.delete(name);
140
- }
141
-
142
- /**
143
- * Async generator that yields [name, entry] pairs for all children.
144
- * @return {AsyncIterable<[string, MockFileHandle | MockDirHandle]>}
145
- */
146
- async *entries(): AsyncIterable<[string, MockFileHandle | MockDirHandle]> {
147
- for (const [name, entry] of this.children) {
148
- yield [name, entry];
149
- }
150
- }
151
- }
152
-
153
- // ── Fixtures ─────────────────────────────────────────────────────────────────
154
-
155
- /**
156
- * Builds a root handle pre-populated with src/content/posts containing
157
- * two .md files, one .mdx file, and a directory entry (to be skipped).
158
- * @return {MockDirHandle} The mock root handle
159
- */
160
- function makeRoot(): MockDirHandle {
161
- const postsDir = new MockDirHandle({
162
- 'hello.md': new MockFileHandle('---\ntitle: Hello\n---\n'),
163
- 'world.md': new MockFileHandle('---\ntitle: World\n---\n'),
164
- 'page.mdx': new MockFileHandle('---\ntitle: Page\n---\n'),
165
- subdir: new MockDirHandle(),
166
- });
167
- const contentDir = new MockDirHandle({ posts: postsDir });
168
- const srcDir = new MockDirHandle({ content: contentDir });
169
- return new MockDirHandle({ src: srcDir });
170
- }
171
-
172
- // ── Tests ─────────────────────────────────────────────────────────────────────
173
-
174
- describe('FsaAdapter', () => {
175
- let root: MockDirHandle;
176
- let adapter: FsaAdapter;
177
-
178
- beforeEach(() => {
179
- root = makeRoot();
180
- adapter = new FsaAdapter(root as unknown as FileSystemDirectoryHandle);
181
- });
182
-
183
- describe('listFiles', () => {
184
- it('returns all .md and .mdx files with their content', async () => {
185
- const files = await adapter.listFiles('posts', ['.md', '.mdx']);
186
- const names = files.map((f) => f.filename).sort();
187
- expect(names).toEqual(['hello.md', 'page.mdx', 'world.md']);
188
- });
189
-
190
- it('returns the correct content for each file', async () => {
191
- const files = await adapter.listFiles('posts', ['.md', '.mdx']);
192
- const hello = files.find((f) => f.filename === 'hello.md');
193
- expect(hello?.content).toBe('---\ntitle: Hello\n---\n');
194
- });
195
-
196
- it('skips directory entries', async () => {
197
- const files = await adapter.listFiles('posts', ['.md', '.mdx']);
198
- const names = files.map((f) => f.filename);
199
- expect(names).not.toContain('subdir');
200
- });
201
-
202
- it('returns an empty array for an empty collection directory', async () => {
203
- const emptyDir = new MockDirHandle();
204
- const contentDir = new MockDirHandle({ empty: emptyDir });
205
- const srcDir = new MockDirHandle({ content: contentDir });
206
- const emptyRoot = new MockDirHandle({ src: srcDir });
207
- const emptyAdapter = new FsaAdapter(
208
- emptyRoot as unknown as FileSystemDirectoryHandle,
209
- );
210
- const files = await emptyAdapter.listFiles('empty', ['.md', '.mdx']);
211
- expect(files).toEqual([]);
212
- });
213
-
214
- it('filters by the given extensions', async () => {
215
- const mdOnly = await adapter.listFiles('posts', ['.md']);
216
- const names = mdOnly.map((f) => f.filename).sort();
217
- expect(names).toEqual(['hello.md', 'world.md']);
218
- expect(names).not.toContain('page.mdx');
219
- });
220
-
221
- it('returns nothing when no files match the extensions', async () => {
222
- const files = await adapter.listFiles('posts', ['.yaml']);
223
- expect(files).toEqual([]);
224
- });
225
- });
226
-
227
- describe('readFile', () => {
228
- it('returns the file content for a known file', async () => {
229
- const content = await adapter.readFile('posts', 'hello.md');
230
- expect(content).toBe('---\ntitle: Hello\n---\n');
231
- });
232
-
233
- it('throws when the file does not exist', async () => {
234
- await expect(adapter.readFile('posts', 'missing.md')).rejects.toThrow();
235
- });
236
- });
237
-
238
- describe('writeFile', () => {
239
- it('writes content to an existing file', async () => {
240
- await adapter.writeFile('posts', 'hello.md', 'new content');
241
- const content = await adapter.readFile('posts', 'hello.md');
242
- expect(content).toBe('new content');
243
- });
244
-
245
- it('creates a new file when it does not exist', async () => {
246
- await adapter.writeFile('posts', 'brand-new.md', 'fresh content');
247
- const content = await adapter.readFile('posts', 'brand-new.md');
248
- expect(content).toBe('fresh content');
249
- });
250
- });
251
-
252
- describe('deleteFile', () => {
253
- it('removes an existing file from the collection', async () => {
254
- await adapter.deleteFile('posts', 'hello.md');
255
- const files = await adapter.listFiles('posts', ['.md', '.mdx']);
256
- const names = files.map((f) => f.filename);
257
- expect(names).not.toContain('hello.md');
258
- });
259
-
260
- it('throws when the file does not exist', async () => {
261
- await expect(
262
- adapter.deleteFile('posts', 'nonexistent.md'),
263
- ).rejects.toThrow();
264
- });
265
- });
266
-
267
- describe('writeFiles', () => {
268
- it('writes all files sequentially', async () => {
269
- await adapter.writeFiles([
270
- { collection: 'posts', filename: 'hello.md', content: 'updated hello' },
271
- { collection: 'posts', filename: 'world.md', content: 'updated world' },
272
- ]);
273
- const hello = await adapter.readFile('posts', 'hello.md');
274
- const world = await adapter.readFile('posts', 'world.md');
275
- expect(hello).toBe('updated hello');
276
- expect(world).toBe('updated world');
277
- });
278
-
279
- it('is a no-op for an empty array', async () => {
280
- // Should resolve without throwing
281
- await expect(adapter.writeFiles([])).resolves.toBeUndefined();
282
- });
283
- });
284
- });
@@ -1,349 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
-
3
- //////////////////////////////
4
- // GitHubAdapter tests
5
- //
6
- // All network calls are intercepted by replacing globalThis.fetch with a
7
- // vi.fn() that returns pre-built Response objects. Each test group
8
- // configures fetch to respond to the specific API endpoints exercised
9
- // by the method under test.
10
- //////////////////////////////
11
-
12
- import { GitHubAdapter } from '../../../../src/client/js/storage/github';
13
-
14
- // ── Helpers ───────────────────────────────────────────────────────────────────
15
-
16
- /**
17
- * Creates a minimal Response-like object for use in fetch mocks.
18
- * @param {unknown} body - The response body (will be JSON-serialized if object)
19
- * @param {number} status - HTTP status code
20
- * @param {{ text?: boolean }} opts - When text is true, body is treated as a raw string
21
- * @return {Response} A mock Response instance
22
- */
23
- function mockResponse(
24
- body: unknown,
25
- status = 200,
26
- opts: { text?: boolean } = {},
27
- ): Response {
28
- const bodyStr = opts.text ? (body as string) : JSON.stringify(body);
29
- return new Response(bodyStr, {
30
- status,
31
- headers: { 'Content-Type': opts.text ? 'text/plain' : 'application/json' },
32
- });
33
- }
34
-
35
- // ── Setup ─────────────────────────────────────────────────────────────────────
36
-
37
- let fetchMock: ReturnType<typeof vi.fn>;
38
-
39
- beforeEach(() => {
40
- fetchMock = vi.fn();
41
- globalThis.fetch = fetchMock;
42
- });
43
-
44
- afterEach(() => {
45
- vi.restoreAllMocks();
46
- });
47
-
48
- // ── Tests ─────────────────────────────────────────────────────────────────────
49
-
50
- describe('GitHubAdapter', () => {
51
- const TOKEN = 'ghp_test_token';
52
- const REPO = 'owner/my-repo';
53
-
54
- describe('validate', () => {
55
- it('stores the default branch returned by the repo endpoint', async () => {
56
- fetchMock.mockResolvedValueOnce(
57
- mockResponse({ default_branch: 'develop' }),
58
- );
59
- const adapter = new GitHubAdapter(TOKEN, REPO);
60
- await adapter.validate();
61
- // Confirm the branch is used in subsequent calls by inspecting listFiles
62
- fetchMock.mockResolvedValueOnce(mockResponse([]));
63
- await adapter.listFiles('posts', ['.md', '.mdx']);
64
- const listURL = fetchMock.mock.calls[1][0] as string;
65
- expect(listURL).toContain('ref=develop');
66
- });
67
-
68
- it('throws for a 401 response', async () => {
69
- fetchMock.mockResolvedValueOnce(mockResponse({}, 401));
70
- const adapter = new GitHubAdapter(TOKEN, REPO);
71
- await expect(adapter.validate()).rejects.toThrow('Invalid or expired');
72
- });
73
-
74
- it('throws for a 403 response', async () => {
75
- fetchMock.mockResolvedValueOnce(mockResponse({}, 403));
76
- const adapter = new GitHubAdapter(TOKEN, REPO);
77
- await expect(adapter.validate()).rejects.toThrow('lacks repository');
78
- });
79
-
80
- it('throws for a 404 response', async () => {
81
- fetchMock.mockResolvedValueOnce(mockResponse({}, 404));
82
- const adapter = new GitHubAdapter(TOKEN, REPO);
83
- await expect(adapter.validate()).rejects.toThrow('not found');
84
- });
85
-
86
- it('throws a generic error for other non-ok statuses', async () => {
87
- fetchMock.mockResolvedValueOnce(mockResponse({}, 500));
88
- const adapter = new GitHubAdapter(TOKEN, REPO);
89
- await expect(adapter.validate()).rejects.toThrow('GitHub API error: 500');
90
- });
91
- });
92
-
93
- describe('listFiles', () => {
94
- it('returns empty array when the collection path returns 404', async () => {
95
- fetchMock.mockResolvedValueOnce(mockResponse({}, 404));
96
- const adapter = new GitHubAdapter(TOKEN, REPO);
97
- const files = await adapter.listFiles('posts', ['.md', '.mdx']);
98
- expect(files).toEqual([]);
99
- });
100
-
101
- it('returns only files matching the given extensions', async () => {
102
- // Directory listing response
103
- fetchMock.mockResolvedValueOnce(
104
- mockResponse([
105
- {
106
- name: 'hello.md',
107
- download_url: 'https://raw.github.com/hello.md',
108
- },
109
- {
110
- name: 'world.mdx',
111
- download_url: 'https://raw.github.com/world.mdx',
112
- },
113
- {
114
- name: 'image.png',
115
- download_url: 'https://raw.github.com/image.png',
116
- },
117
- ]),
118
- );
119
- // readFile calls for hello.md and world.mdx
120
- fetchMock.mockResolvedValueOnce(
121
- mockResponse('# Hello', 200, { text: true }),
122
- );
123
- fetchMock.mockResolvedValueOnce(
124
- mockResponse('# World', 200, { text: true }),
125
- );
126
-
127
- const adapter = new GitHubAdapter(TOKEN, REPO);
128
- const files = await adapter.listFiles('posts', ['.md', '.mdx']);
129
- const names = files.map((f) => f.filename).sort();
130
- expect(names).toEqual(['hello.md', 'world.mdx']);
131
- });
132
-
133
- it('filters to only the requested extensions', async () => {
134
- fetchMock.mockResolvedValueOnce(
135
- mockResponse([
136
- {
137
- name: 'hello.md',
138
- download_url: 'https://raw.github.com/hello.md',
139
- },
140
- {
141
- name: 'world.mdx',
142
- download_url: 'https://raw.github.com/world.mdx',
143
- },
144
- {
145
- name: 'data.yaml',
146
- download_url: 'https://raw.github.com/data.yaml',
147
- },
148
- ]),
149
- );
150
- // Only .yaml file should be fetched
151
- fetchMock.mockResolvedValueOnce(
152
- mockResponse('key: value', 200, { text: true }),
153
- );
154
-
155
- const adapter = new GitHubAdapter(TOKEN, REPO);
156
- const files = await adapter.listFiles('posts', ['.yaml']);
157
- expect(files).toHaveLength(1);
158
- expect(files[0].filename).toBe('data.yaml');
159
- });
160
-
161
- it('throws when the listing request fails with a non-404 error', async () => {
162
- fetchMock.mockResolvedValueOnce(mockResponse({}, 500));
163
- const adapter = new GitHubAdapter(TOKEN, REPO);
164
- await expect(adapter.listFiles('posts', ['.md', '.mdx'])).rejects.toThrow(
165
- 'Failed to list files',
166
- );
167
- });
168
- });
169
-
170
- describe('deleteFile', () => {
171
- it('sends a DELETE request with the current SHA', async () => {
172
- // GET to retrieve the current SHA
173
- fetchMock.mockResolvedValueOnce(
174
- mockResponse({ sha: 'file-sha-123', name: 'old.md' }),
175
- );
176
- // DELETE succeeds
177
- fetchMock.mockResolvedValueOnce(mockResponse({ commit: {} }));
178
-
179
- const adapter = new GitHubAdapter(TOKEN, REPO);
180
- await adapter.deleteFile('posts', 'old.md');
181
-
182
- // Verify the DELETE call
183
- const deleteCall = fetchMock.mock.calls[1];
184
- expect(deleteCall[1].method).toBe('DELETE');
185
- const deleteBody = JSON.parse(deleteCall[1].body as string);
186
- expect(deleteBody.sha).toBe('file-sha-123');
187
- expect(deleteBody.message).toContain('old.md');
188
- });
189
-
190
- it('throws when the file does not exist', async () => {
191
- fetchMock.mockResolvedValueOnce(mockResponse({}, 404));
192
- const adapter = new GitHubAdapter(TOKEN, REPO);
193
- await expect(adapter.deleteFile('posts', 'missing.md')).rejects.toThrow(
194
- 'File not found for deletion',
195
- );
196
- });
197
-
198
- it('throws when the DELETE request fails', async () => {
199
- fetchMock.mockResolvedValueOnce(
200
- mockResponse({ sha: 'sha-abc', name: 'target.md' }),
201
- );
202
- fetchMock.mockResolvedValueOnce(mockResponse({}, 422));
203
- const adapter = new GitHubAdapter(TOKEN, REPO);
204
- await expect(adapter.deleteFile('posts', 'target.md')).rejects.toThrow(
205
- 'Failed to delete',
206
- );
207
- });
208
- });
209
-
210
- describe('readFile', () => {
211
- it('returns the raw file content', async () => {
212
- fetchMock.mockResolvedValueOnce(
213
- mockResponse('---\ntitle: Test\n---\n', 200, { text: true }),
214
- );
215
- const adapter = new GitHubAdapter(TOKEN, REPO);
216
- const content = await adapter.readFile('posts', 'test.md');
217
- expect(content).toBe('---\ntitle: Test\n---\n');
218
- });
219
-
220
- it('sends the raw+json Accept header', async () => {
221
- fetchMock.mockResolvedValueOnce(
222
- mockResponse('content', 200, { text: true }),
223
- );
224
- const adapter = new GitHubAdapter(TOKEN, REPO);
225
- await adapter.readFile('posts', 'test.md');
226
- const headers = fetchMock.mock.calls[0][1].headers as Record<
227
- string,
228
- string
229
- >;
230
- expect(headers['Accept']).toBe('application/vnd.github.raw+json');
231
- });
232
-
233
- it('throws when the file is not found', async () => {
234
- fetchMock.mockResolvedValueOnce(mockResponse({}, 404));
235
- const adapter = new GitHubAdapter(TOKEN, REPO);
236
- await expect(adapter.readFile('posts', 'missing.md')).rejects.toThrow(
237
- 'Failed to read',
238
- );
239
- });
240
- });
241
-
242
- describe('writeFile', () => {
243
- it('sends a PUT request without sha for a new file', async () => {
244
- // GET returns 404 (file doesn't exist yet)
245
- fetchMock.mockResolvedValueOnce(mockResponse({}, 404));
246
- // PUT succeeds
247
- fetchMock.mockResolvedValueOnce(mockResponse({ content: {} }));
248
-
249
- const adapter = new GitHubAdapter(TOKEN, REPO);
250
- await adapter.writeFile('posts', 'new.md', '# New');
251
-
252
- const putCall = fetchMock.mock.calls[1];
253
- const putBody = JSON.parse(putCall[1].body as string);
254
- expect(putBody.sha).toBeUndefined();
255
- expect(putBody.message).toContain('new.md');
256
- });
257
-
258
- it('sends a PUT request with sha for an existing file', async () => {
259
- // GET returns existing file with sha
260
- fetchMock.mockResolvedValueOnce(
261
- mockResponse({ sha: 'abc123', name: 'existing.md' }),
262
- );
263
- // PUT succeeds
264
- fetchMock.mockResolvedValueOnce(mockResponse({ content: {} }));
265
-
266
- const adapter = new GitHubAdapter(TOKEN, REPO);
267
- await adapter.writeFile('posts', 'existing.md', 'updated');
268
-
269
- const putBody = JSON.parse(fetchMock.mock.calls[1][1].body as string);
270
- expect(putBody.sha).toBe('abc123');
271
- });
272
-
273
- it('throws when the PUT request fails', async () => {
274
- fetchMock.mockResolvedValueOnce(mockResponse({}, 404));
275
- fetchMock.mockResolvedValueOnce(
276
- mockResponse('error text', 422, { text: true }),
277
- );
278
-
279
- const adapter = new GitHubAdapter(TOKEN, REPO);
280
- await expect(adapter.writeFile('posts', 'bad.md', 'x')).rejects.toThrow(
281
- 'Failed to write',
282
- );
283
- });
284
-
285
- it('base64-encodes the content', async () => {
286
- fetchMock.mockResolvedValueOnce(mockResponse({}, 404));
287
- fetchMock.mockResolvedValueOnce(mockResponse({ content: {} }));
288
-
289
- const adapter = new GitHubAdapter(TOKEN, REPO);
290
- await adapter.writeFile('posts', 'check.md', 'hello');
291
-
292
- const putBody = JSON.parse(fetchMock.mock.calls[1][1].body as string);
293
- // 'hello' base64 encodes to 'aGVsbG8='
294
- expect(putBody.content).toBe('aGVsbG8=');
295
- });
296
- });
297
-
298
- describe('writeFiles', () => {
299
- it('is a no-op for an empty array', async () => {
300
- const adapter = new GitHubAdapter(TOKEN, REPO);
301
- await expect(adapter.writeFiles([])).resolves.toBeUndefined();
302
- expect(fetchMock).not.toHaveBeenCalled();
303
- });
304
-
305
- it('delegates to writeFile for a single-file array', async () => {
306
- // GET (check sha) + PUT
307
- fetchMock.mockResolvedValueOnce(mockResponse({}, 404));
308
- fetchMock.mockResolvedValueOnce(mockResponse({ content: {} }));
309
-
310
- const adapter = new GitHubAdapter(TOKEN, REPO);
311
- await adapter.writeFiles([
312
- { collection: 'posts', filename: 'one.md', content: 'body' },
313
- ]);
314
- // Two calls: GET for sha check, PUT for write
315
- expect(fetchMock).toHaveBeenCalledTimes(2);
316
- });
317
-
318
- it('uses the Git Trees API for multiple files', async () => {
319
- // GET /git/ref/heads/main
320
- fetchMock.mockResolvedValueOnce(
321
- mockResponse({ object: { sha: 'commit-sha' } }),
322
- );
323
- // GET /git/commits/commit-sha
324
- fetchMock.mockResolvedValueOnce(
325
- mockResponse({ tree: { sha: 'tree-sha' } }),
326
- );
327
- // POST /git/trees
328
- fetchMock.mockResolvedValueOnce(mockResponse({ sha: 'new-tree-sha' }));
329
- // POST /git/commits
330
- fetchMock.mockResolvedValueOnce(mockResponse({ sha: 'new-commit-sha' }));
331
- // PATCH /git/refs/heads/main
332
- fetchMock.mockResolvedValueOnce(mockResponse({ ref: 'refs/heads/main' }));
333
-
334
- const adapter = new GitHubAdapter(TOKEN, REPO);
335
- await adapter.writeFiles([
336
- { collection: 'posts', filename: 'a.md', content: 'A' },
337
- { collection: 'posts', filename: 'b.md', content: 'B' },
338
- ]);
339
-
340
- // Should have made 5 API calls
341
- expect(fetchMock).toHaveBeenCalledTimes(5);
342
- // The tree POST should contain both file paths
343
- const treeBody = JSON.parse(fetchMock.mock.calls[2][1].body as string);
344
- const paths = treeBody.tree.map((t: { path: string }) => t.path);
345
- expect(paths).toContain('src/content/posts/a.md');
346
- expect(paths).toContain('src/content/posts/b.md');
347
- });
348
- });
349
- });