nebula-cms 0.1.3 → 0.1.4

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 (380) hide show
  1. package/README.md +2 -0
  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/dist/client/Admin.svelte.d.ts +11 -0
  6. package/dist/client/Admin.svelte.d.ts.map +1 -0
  7. package/dist/client/components/BackendPicker.svelte.d.ts +4 -0
  8. package/dist/client/components/BackendPicker.svelte.d.ts.map +1 -0
  9. package/dist/client/components/DraftChip.svelte.d.ts +10 -0
  10. package/dist/client/components/DraftChip.svelte.d.ts.map +1 -0
  11. package/dist/client/components/MetadataForm.svelte.d.ts +12 -0
  12. package/dist/client/components/MetadataForm.svelte.d.ts.map +1 -0
  13. package/dist/client/components/ThemeToggle.svelte.d.ts +19 -0
  14. package/dist/client/components/ThemeToggle.svelte.d.ts.map +1 -0
  15. package/dist/client/components/dialogs/DeleteDraftDialog.svelte.d.ts +11 -0
  16. package/dist/client/components/dialogs/DeleteDraftDialog.svelte.d.ts.map +1 -0
  17. package/dist/client/components/dialogs/FilenameDialog.svelte.d.ts +13 -0
  18. package/dist/client/components/dialogs/FilenameDialog.svelte.d.ts.map +1 -0
  19. package/dist/client/components/editor/EditorPane.svelte.d.ts +4 -0
  20. package/dist/client/components/editor/EditorPane.svelte.d.ts.map +1 -0
  21. package/dist/client/components/editor/EditorTabs.svelte.d.ts +8 -0
  22. package/dist/client/components/editor/EditorTabs.svelte.d.ts.map +1 -0
  23. package/dist/client/components/editor/EditorToolbar.svelte.d.ts +4 -0
  24. package/dist/client/components/editor/EditorToolbar.svelte.d.ts.map +1 -0
  25. package/dist/client/components/editor/FormatSelector.svelte.d.ts +4 -0
  26. package/dist/client/components/editor/FormatSelector.svelte.d.ts.map +1 -0
  27. package/dist/client/components/editor/Toolbar.svelte.d.ts +19 -0
  28. package/dist/client/components/editor/Toolbar.svelte.d.ts.map +1 -0
  29. package/dist/client/components/fields/ArrayField.svelte.d.ts +15 -0
  30. package/dist/client/components/fields/ArrayField.svelte.d.ts.map +1 -0
  31. package/dist/client/components/fields/ArrayItem.svelte.d.ts +28 -0
  32. package/dist/client/components/fields/ArrayItem.svelte.d.ts.map +1 -0
  33. package/dist/client/components/fields/BooleanField.svelte.d.ts +16 -0
  34. package/dist/client/components/fields/BooleanField.svelte.d.ts.map +1 -0
  35. package/dist/client/components/fields/DateField.svelte.d.ts +16 -0
  36. package/dist/client/components/fields/DateField.svelte.d.ts.map +1 -0
  37. package/dist/client/components/fields/EnumField.svelte.d.ts +17 -0
  38. package/dist/client/components/fields/EnumField.svelte.d.ts.map +1 -0
  39. package/dist/client/components/fields/FieldWrapper.svelte.d.ts +18 -0
  40. package/dist/client/components/fields/FieldWrapper.svelte.d.ts.map +1 -0
  41. package/dist/client/components/fields/NumberField.svelte.d.ts +16 -0
  42. package/dist/client/components/fields/NumberField.svelte.d.ts.map +1 -0
  43. package/dist/client/components/fields/ObjectField.svelte.d.ts +16 -0
  44. package/dist/client/components/fields/ObjectField.svelte.d.ts.map +1 -0
  45. package/dist/client/components/fields/SchemaField.svelte.d.ts +16 -0
  46. package/dist/client/components/fields/SchemaField.svelte.d.ts.map +1 -0
  47. package/dist/client/components/fields/StringField.svelte.d.ts +16 -0
  48. package/dist/client/components/fields/StringField.svelte.d.ts.map +1 -0
  49. package/dist/client/components/sidebar/AdminSidebar.svelte.d.ts +19 -0
  50. package/dist/client/components/sidebar/AdminSidebar.svelte.d.ts.map +1 -0
  51. package/dist/client/components/sidebar/AdminSidebarSort.svelte.d.ts +12 -0
  52. package/dist/client/components/sidebar/AdminSidebarSort.svelte.d.ts.map +1 -0
  53. package/dist/client/css/icons.css +29 -0
  54. package/dist/client/index.d.ts +2 -0
  55. package/dist/client/index.d.ts.map +1 -0
  56. package/dist/client/js/drafts/merge.svelte.d.ts +24 -0
  57. package/dist/client/js/drafts/merge.svelte.d.ts.map +1 -0
  58. package/dist/client/js/drafts/merge.svelte.js +106 -0
  59. package/dist/client/js/drafts/ops.svelte.d.ts +31 -0
  60. package/dist/client/js/drafts/ops.svelte.d.ts.map +1 -0
  61. package/dist/client/js/drafts/ops.svelte.js +182 -0
  62. package/dist/client/js/drafts/storage.d.ts +45 -0
  63. package/dist/client/js/drafts/storage.d.ts.map +1 -0
  64. package/dist/client/js/drafts/storage.js +76 -0
  65. package/dist/client/js/drafts/workers/diff.d.ts +2 -0
  66. package/dist/client/js/drafts/workers/diff.d.ts.map +1 -0
  67. package/dist/client/js/drafts/workers/diff.js +20 -0
  68. package/dist/client/js/editor/editor.svelte.d.ts +124 -0
  69. package/dist/client/js/editor/editor.svelte.d.ts.map +1 -0
  70. package/dist/client/js/editor/editor.svelte.js +294 -0
  71. package/dist/client/js/editor/languages.d.ts +11 -0
  72. package/dist/client/js/editor/languages.d.ts.map +1 -0
  73. package/dist/client/js/editor/languages.js +93 -0
  74. package/dist/client/js/editor/link-wrap.d.ts +6 -0
  75. package/dist/client/js/editor/link-wrap.d.ts.map +1 -0
  76. package/{src/client/js/editor/link-wrap.ts → dist/client/js/editor/link-wrap.js} +17 -24
  77. package/dist/client/js/editor/markdown-shortcuts.d.ts +4 -0
  78. package/dist/client/js/editor/markdown-shortcuts.d.ts.map +1 -0
  79. package/dist/client/js/editor/markdown-shortcuts.js +219 -0
  80. package/dist/client/js/handlers/admin.d.ts +64 -0
  81. package/dist/client/js/handlers/admin.d.ts.map +1 -0
  82. package/dist/client/js/handlers/admin.js +186 -0
  83. package/dist/client/js/state/dialogs.svelte.d.ts +16 -0
  84. package/dist/client/js/state/dialogs.svelte.d.ts.map +1 -0
  85. package/dist/client/js/state/dialogs.svelte.js +28 -0
  86. package/dist/client/js/state/router.svelte.d.ts +44 -0
  87. package/dist/client/js/state/router.svelte.d.ts.map +1 -0
  88. package/dist/client/js/state/router.svelte.js +135 -0
  89. package/dist/client/js/state/schema.svelte.d.ts +51 -0
  90. package/dist/client/js/state/schema.svelte.d.ts.map +1 -0
  91. package/{src/client/js/state/schema.svelte.ts → dist/client/js/state/schema.svelte.js} +55 -70
  92. package/dist/client/js/state/state.svelte.d.ts +68 -0
  93. package/dist/client/js/state/state.svelte.d.ts.map +1 -0
  94. package/dist/client/js/state/state.svelte.js +291 -0
  95. package/dist/client/js/state/theme.svelte.d.ts +24 -0
  96. package/dist/client/js/state/theme.svelte.d.ts.map +1 -0
  97. package/{src/client/js/state/theme.svelte.ts → dist/client/js/state/theme.svelte.js} +54 -91
  98. package/dist/client/js/storage/adapter.d.ts +130 -0
  99. package/dist/client/js/storage/adapter.d.ts.map +1 -0
  100. package/dist/client/js/storage/adapter.js +5 -0
  101. package/dist/client/js/storage/client.d.ts +72 -0
  102. package/dist/client/js/storage/client.d.ts.map +1 -0
  103. package/dist/client/js/storage/client.js +121 -0
  104. package/dist/client/js/storage/db.d.ts +8 -0
  105. package/dist/client/js/storage/db.d.ts.map +1 -0
  106. package/dist/client/js/storage/db.js +35 -0
  107. package/dist/client/js/storage/fsa.d.ts +51 -0
  108. package/dist/client/js/storage/fsa.d.ts.map +1 -0
  109. package/dist/client/js/storage/fsa.js +91 -0
  110. package/dist/client/js/storage/github.d.ts +62 -0
  111. package/dist/client/js/storage/github.d.ts.map +1 -0
  112. package/dist/client/js/storage/github.js +216 -0
  113. package/dist/client/js/storage/storage.d.ts +32 -0
  114. package/dist/client/js/storage/storage.d.ts.map +1 -0
  115. package/dist/client/js/storage/storage.js +68 -0
  116. package/dist/client/js/storage/workers/frontmatter.d.ts +2 -0
  117. package/dist/client/js/storage/workers/frontmatter.d.ts.map +1 -0
  118. package/dist/client/js/storage/workers/frontmatter.js +253 -0
  119. package/dist/client/js/storage/workers/storage.d.ts +2 -0
  120. package/dist/client/js/storage/workers/storage.d.ts.map +1 -0
  121. package/dist/client/js/storage/workers/storage.js +167 -0
  122. package/dist/client/js/storage/workers/toml-parser.d.ts +2 -0
  123. package/dist/client/js/storage/workers/toml-parser.d.ts.map +1 -0
  124. package/dist/client/js/storage/workers/toml-parser.js +75 -0
  125. package/dist/client/js/storage/workers/yaml-parser.d.ts +2 -0
  126. package/dist/client/js/storage/workers/yaml-parser.d.ts.map +1 -0
  127. package/dist/client/js/storage/workers/yaml-parser.js +100 -0
  128. package/dist/client/js/utils/file-types.d.ts +58 -0
  129. package/dist/client/js/utils/file-types.d.ts.map +1 -0
  130. package/{src/client/js/utils/file-types.ts → dist/client/js/utils/file-types.js} +75 -107
  131. package/dist/client/js/utils/format.d.ts +8 -0
  132. package/dist/client/js/utils/format.d.ts.map +1 -0
  133. package/{src/client/js/utils/format.ts → dist/client/js/utils/format.js} +5 -6
  134. package/dist/client/js/utils/frontmatter.d.ts +12 -0
  135. package/dist/client/js/utils/frontmatter.d.ts.map +1 -0
  136. package/dist/client/js/utils/frontmatter.js +29 -0
  137. package/dist/client/js/utils/schema-utils.d.ts +110 -0
  138. package/dist/client/js/utils/schema-utils.d.ts.map +1 -0
  139. package/dist/client/js/utils/schema-utils.js +242 -0
  140. package/dist/client/js/utils/slug.d.ts +8 -0
  141. package/dist/client/js/utils/slug.d.ts.map +1 -0
  142. package/{src/client/js/utils/slug.ts → dist/client/js/utils/slug.js} +6 -7
  143. package/dist/client/js/utils/sort.d.ts +41 -0
  144. package/dist/client/js/utils/sort.d.ts.map +1 -0
  145. package/dist/client/js/utils/sort.js +65 -0
  146. package/dist/client/js/utils/stable-stringify.d.ts +8 -0
  147. package/dist/client/js/utils/stable-stringify.d.ts.map +1 -0
  148. package/dist/client/js/utils/stable-stringify.js +23 -0
  149. package/dist/client/js/utils/url-utils.d.ts +11 -0
  150. package/dist/client/js/utils/url-utils.d.ts.map +1 -0
  151. package/{src/client/js/utils/url-utils.ts → dist/client/js/utils/url-utils.js} +22 -23
  152. package/dist/types.d.ts +22 -0
  153. package/dist/types.d.ts.map +1 -0
  154. package/dist/types.js +1 -0
  155. package/package.json +4 -1
  156. package/.github/workflows/ci.yml +0 -27
  157. package/.github/workflows/publish.yml +0 -34
  158. package/.mcp.json +0 -12
  159. package/.prettierignore +0 -5
  160. package/.prettierrc.cjs +0 -22
  161. package/AGENTS.md +0 -183
  162. package/playground/astro.config.mjs +0 -7
  163. package/playground/node_modules/.bin/astro +0 -21
  164. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js +0 -85
  165. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js.map +0 -7
  166. package/playground/node_modules/.vite/deps/_metadata.json +0 -184
  167. package/playground/node_modules/.vite/deps/astro___aria-query.js +0 -6776
  168. package/playground/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
  169. package/playground/node_modules/.vite/deps/astro___axobject-query.js +0 -3754
  170. package/playground/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
  171. package/playground/node_modules/.vite/deps/astro___html-escaper.js +0 -34
  172. package/playground/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
  173. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js +0 -0
  174. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js.map +0 -7
  175. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js +0 -8
  176. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +0 -7
  177. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js +0 -21
  178. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js.map +0 -7
  179. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js +0 -223
  180. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js.map +0 -7
  181. package/playground/node_modules/.vite/deps/chunk-FPEUJ7DG.js +0 -27
  182. package/playground/node_modules/.vite/deps/chunk-FPEUJ7DG.js.map +0 -7
  183. package/playground/node_modules/.vite/deps/chunk-MHDZ3SK7.js +0 -1005
  184. package/playground/node_modules/.vite/deps/chunk-MHDZ3SK7.js.map +0 -7
  185. package/playground/node_modules/.vite/deps/chunk-RBDTDTPY.js +0 -204
  186. package/playground/node_modules/.vite/deps/chunk-RBDTDTPY.js.map +0 -7
  187. package/playground/node_modules/.vite/deps/chunk-RJGEXL5C.js +0 -688
  188. package/playground/node_modules/.vite/deps/chunk-RJGEXL5C.js.map +0 -7
  189. package/playground/node_modules/.vite/deps/chunk-YL4MIWGJ.js +0 -5099
  190. package/playground/node_modules/.vite/deps/chunk-YL4MIWGJ.js.map +0 -7
  191. package/playground/node_modules/.vite/deps/chunk-ZOV3DWEJ.js +0 -4376
  192. package/playground/node_modules/.vite/deps/chunk-ZOV3DWEJ.js.map +0 -7
  193. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js +0 -23
  194. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js.map +0 -7
  195. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js +0 -148
  196. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js.map +0 -7
  197. package/playground/node_modules/.vite/deps/package.json +0 -3
  198. package/playground/node_modules/.vite/deps/smol-toml.js +0 -843
  199. package/playground/node_modules/.vite/deps/smol-toml.js.map +0 -7
  200. package/playground/node_modules/.vite/deps/svelte.js +0 -55
  201. package/playground/node_modules/.vite/deps/svelte.js.map +0 -7
  202. package/playground/node_modules/.vite/deps/svelte___clsx.js +0 -9
  203. package/playground/node_modules/.vite/deps/svelte___clsx.js.map +0 -7
  204. package/playground/node_modules/.vite/deps/svelte_animate.js +0 -57
  205. package/playground/node_modules/.vite/deps/svelte_animate.js.map +0 -7
  206. package/playground/node_modules/.vite/deps/svelte_attachments.js +0 -15
  207. package/playground/node_modules/.vite/deps/svelte_attachments.js.map +0 -7
  208. package/playground/node_modules/.vite/deps/svelte_easing.js +0 -67
  209. package/playground/node_modules/.vite/deps/svelte_easing.js.map +0 -7
  210. package/playground/node_modules/.vite/deps/svelte_events.js +0 -11
  211. package/playground/node_modules/.vite/deps/svelte_events.js.map +0 -7
  212. package/playground/node_modules/.vite/deps/svelte_internal.js +0 -5
  213. package/playground/node_modules/.vite/deps/svelte_internal.js.map +0 -7
  214. package/playground/node_modules/.vite/deps/svelte_internal_client.js +0 -402
  215. package/playground/node_modules/.vite/deps/svelte_internal_client.js.map +0 -7
  216. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js +0 -10
  217. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js.map +0 -7
  218. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js +0 -8
  219. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js.map +0 -7
  220. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js +0 -8
  221. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js.map +0 -7
  222. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js +0 -8
  223. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js.map +0 -7
  224. package/playground/node_modules/.vite/deps/svelte_legacy.js +0 -35
  225. package/playground/node_modules/.vite/deps/svelte_legacy.js.map +0 -7
  226. package/playground/node_modules/.vite/deps/svelte_motion.js +0 -545
  227. package/playground/node_modules/.vite/deps/svelte_motion.js.map +0 -7
  228. package/playground/node_modules/.vite/deps/svelte_reactivity.js +0 -29
  229. package/playground/node_modules/.vite/deps/svelte_reactivity.js.map +0 -7
  230. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js +0 -127
  231. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js.map +0 -7
  232. package/playground/node_modules/.vite/deps/svelte_store.js +0 -103
  233. package/playground/node_modules/.vite/deps/svelte_store.js.map +0 -7
  234. package/playground/node_modules/.vite/deps/svelte_transition.js +0 -208
  235. package/playground/node_modules/.vite/deps/svelte_transition.js.map +0 -7
  236. package/playground/package.json +0 -16
  237. package/playground/pnpm-lock.yaml +0 -3167
  238. package/playground/src/content/authors/jane-doe.json +0 -8
  239. package/playground/src/content/config/build.toml +0 -2
  240. package/playground/src/content/courses/web-fundamentals.json +0 -29
  241. package/playground/src/content/docs/advanced.mdx +0 -6
  242. package/playground/src/content/docs/intro.md +0 -6
  243. package/playground/src/content/guides/getting-started.mdx +0 -6
  244. package/playground/src/content/posts/hello-world.md +0 -7
  245. package/playground/src/content/products/t-shirt.json +0 -16
  246. package/playground/src/content/recipes/pancakes.mdoc +0 -8
  247. package/playground/src/content/settings/site.yml +0 -2
  248. package/playground/src/content.config.ts +0 -198
  249. package/playground/src/env.d.ts +0 -1
  250. package/playground/src/pages/index.astro +0 -11
  251. package/playground/src/pages/nebula.astro +0 -14
  252. package/pnpm-workspace.yaml +0 -2
  253. package/scripts/subset-icons.mjs +0 -178
  254. package/src/astro/index.ts +0 -295
  255. package/src/client/js/drafts/merge.svelte.ts +0 -121
  256. package/src/client/js/drafts/ops.svelte.ts +0 -227
  257. package/src/client/js/drafts/storage.ts +0 -108
  258. package/src/client/js/drafts/workers/diff.ts +0 -40
  259. package/src/client/js/editor/editor.svelte.ts +0 -343
  260. package/src/client/js/editor/languages.ts +0 -98
  261. package/src/client/js/editor/markdown-shortcuts.ts +0 -261
  262. package/src/client/js/handlers/admin.ts +0 -246
  263. package/src/client/js/state/dialogs.svelte.ts +0 -35
  264. package/src/client/js/state/router.svelte.ts +0 -156
  265. package/src/client/js/state/state.svelte.ts +0 -334
  266. package/src/client/js/storage/adapter.ts +0 -102
  267. package/src/client/js/storage/client.ts +0 -150
  268. package/src/client/js/storage/db.ts +0 -36
  269. package/src/client/js/storage/fsa.ts +0 -110
  270. package/src/client/js/storage/github.ts +0 -297
  271. package/src/client/js/storage/storage.ts +0 -83
  272. package/src/client/js/storage/workers/frontmatter.ts +0 -320
  273. package/src/client/js/storage/workers/storage.ts +0 -177
  274. package/src/client/js/storage/workers/toml-parser.ts +0 -106
  275. package/src/client/js/storage/workers/yaml-parser.ts +0 -132
  276. package/src/client/js/utils/frontmatter.ts +0 -38
  277. package/src/client/js/utils/schema-utils.ts +0 -295
  278. package/src/client/js/utils/sort.ts +0 -84
  279. package/src/client/js/utils/stable-stringify.ts +0 -27
  280. package/src/types.ts +0 -25
  281. package/svelte.config.js +0 -4
  282. package/tests/astro/build.test.ts +0 -63
  283. package/tests/astro/index.test.ts +0 -689
  284. package/tests/client/components/Admin.test.ts +0 -446
  285. package/tests/client/components/BackendPicker.test.ts +0 -239
  286. package/tests/client/components/DraftChip.test.ts +0 -53
  287. package/tests/client/components/MetadataForm.test.ts +0 -164
  288. package/tests/client/components/dialogs/DeleteDraftDialog.test.ts +0 -91
  289. package/tests/client/components/dialogs/FilenameDialog.test.ts +0 -209
  290. package/tests/client/components/dialogs/dialog-stubs.ts +0 -19
  291. package/tests/client/components/editor/EditorPane.test.ts +0 -100
  292. package/tests/client/components/editor/EditorTabs.test.ts +0 -253
  293. package/tests/client/components/editor/EditorToolbar.test.ts +0 -252
  294. package/tests/client/components/editor/fixtures.ts +0 -31
  295. package/tests/client/components/fields/ArrayField.test.ts +0 -197
  296. package/tests/client/components/fields/BooleanField.test.ts +0 -206
  297. package/tests/client/components/fields/DateField.test.ts +0 -210
  298. package/tests/client/components/fields/EnumField.test.ts +0 -246
  299. package/tests/client/components/fields/NumberField.test.ts +0 -240
  300. package/tests/client/components/fields/ObjectField.test.ts +0 -157
  301. package/tests/client/components/fields/SchemaField.test.ts +0 -190
  302. package/tests/client/components/fields/StringField.test.ts +0 -223
  303. package/tests/client/components/sidebar/AdminSidebar.test.ts +0 -285
  304. package/tests/client/components/sidebar/AdminSidebarSort.test.ts +0 -135
  305. package/tests/client/components/sidebar/sort-mock.ts +0 -23
  306. package/tests/client/js/drafts/fixtures.ts +0 -22
  307. package/tests/client/js/drafts/merge.test.ts +0 -282
  308. package/tests/client/js/drafts/ops.test.ts +0 -658
  309. package/tests/client/js/drafts/storage.test.ts +0 -200
  310. package/tests/client/js/drafts/workers/diff.test.ts +0 -165
  311. package/tests/client/js/editor/editor.test.ts +0 -616
  312. package/tests/client/js/editor/link-wrap.test.ts +0 -225
  313. package/tests/client/js/editor/markdown-shortcuts.test.ts +0 -370
  314. package/tests/client/js/handlers/admin.test.ts +0 -467
  315. package/tests/client/js/state/router.test.ts +0 -619
  316. package/tests/client/js/state/schema.test.ts +0 -266
  317. package/tests/client/js/state/state.test.ts +0 -328
  318. package/tests/client/js/storage/adapter.test.ts +0 -115
  319. package/tests/client/js/storage/client.test.ts +0 -250
  320. package/tests/client/js/storage/db.test.ts +0 -59
  321. package/tests/client/js/storage/fsa.test.ts +0 -284
  322. package/tests/client/js/storage/github.test.ts +0 -349
  323. package/tests/client/js/storage/mock-port.ts +0 -95
  324. package/tests/client/js/storage/storage.test.ts +0 -77
  325. package/tests/client/js/storage/workers/frontmatter.test.ts +0 -479
  326. package/tests/client/js/storage/workers/storage.test.ts +0 -299
  327. package/tests/client/js/storage/workers/toml-parser.test.ts +0 -169
  328. package/tests/client/js/storage/workers/yaml-parser.test.ts +0 -168
  329. package/tests/client/js/utils/file-types.test.ts +0 -268
  330. package/tests/client/js/utils/frontmatter.test.ts +0 -87
  331. package/tests/client/js/utils/schema-utils.test.ts +0 -318
  332. package/tests/client/js/utils/slug.test.ts +0 -58
  333. package/tests/client/js/utils/sort.test.ts +0 -276
  334. package/tests/client/js/utils/stable-stringify.test.ts +0 -68
  335. package/tests/client/js/utils/url-utils.test.ts +0 -70
  336. package/tests/e2e/backend-connection.test.ts +0 -301
  337. package/tests/e2e/draft-lifecycle.test.ts +0 -388
  338. package/tests/e2e/editing.test.ts +0 -355
  339. package/tests/e2e/github-adapter.test.ts +0 -330
  340. package/tests/e2e/helpers/mock-adapter.ts +0 -166
  341. package/tests/e2e/helpers/test-app.ts +0 -155
  342. package/tests/e2e/navigation.test.ts +0 -358
  343. package/tests/e2e/publishing.test.ts +0 -345
  344. package/tests/e2e/unsaved-changes.test.ts +0 -317
  345. package/tests/setup.ts +0 -2
  346. package/tests/stubs/codemirror.ts +0 -197
  347. package/tsconfig.json +0 -19
  348. package/vitest.config.ts +0 -178
  349. /package/{src → dist}/client/Admin.svelte +0 -0
  350. /package/{src → dist}/client/components/BackendPicker.svelte +0 -0
  351. /package/{src → dist}/client/components/DraftChip.svelte +0 -0
  352. /package/{src → dist}/client/components/MetadataForm.svelte +0 -0
  353. /package/{src → dist}/client/components/ThemeToggle.svelte +0 -0
  354. /package/{src → dist}/client/components/dialogs/DeleteDraftDialog.svelte +0 -0
  355. /package/{src → dist}/client/components/dialogs/FilenameDialog.svelte +0 -0
  356. /package/{src → dist}/client/components/editor/EditorPane.svelte +0 -0
  357. /package/{src → dist}/client/components/editor/EditorTabs.svelte +0 -0
  358. /package/{src → dist}/client/components/editor/EditorToolbar.svelte +0 -0
  359. /package/{src → dist}/client/components/editor/FormatSelector.svelte +0 -0
  360. /package/{src → dist}/client/components/editor/Toolbar.svelte +0 -0
  361. /package/{src → dist}/client/components/fields/ArrayField.svelte +0 -0
  362. /package/{src → dist}/client/components/fields/ArrayItem.svelte +0 -0
  363. /package/{src → dist}/client/components/fields/BooleanField.svelte +0 -0
  364. /package/{src → dist}/client/components/fields/DateField.svelte +0 -0
  365. /package/{src → dist}/client/components/fields/EnumField.svelte +0 -0
  366. /package/{src → dist}/client/components/fields/FieldWrapper.svelte +0 -0
  367. /package/{src → dist}/client/components/fields/NumberField.svelte +0 -0
  368. /package/{src → dist}/client/components/fields/ObjectField.svelte +0 -0
  369. /package/{src → dist}/client/components/fields/SchemaField.svelte +0 -0
  370. /package/{src → dist}/client/components/fields/StringField.svelte +0 -0
  371. /package/{src → dist}/client/components/sidebar/AdminSidebar.svelte +0 -0
  372. /package/{src → dist}/client/components/sidebar/AdminSidebarSort.svelte +0 -0
  373. /package/{src → dist}/client/css/a11y.css +0 -0
  374. /package/{src → dist}/client/css/btn.css +0 -0
  375. /package/{src → dist}/client/css/dialog.css +0 -0
  376. /package/{src → dist}/client/css/field-input.css +0 -0
  377. /package/{src → dist}/client/css/reset.css +0 -0
  378. /package/{src → dist}/client/css/theme.css +0 -0
  379. /package/{src/client/index.ts → dist/client/index.js} +0 -0
  380. /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
- });