@jant/core 0.6.16 → 0.7.1

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 (592) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/export.js +3 -1
  4. package/bin/commands/import-site.js +749 -220
  5. package/bin/commands/reset-password.js +21 -8
  6. package/bin/commands/setup.js +136 -0
  7. package/bin/commands/site/export.js +71 -34
  8. package/bin/commands/site/pull-media.js +2 -6
  9. package/bin/commands/site/snapshot/export.js +19 -61
  10. package/bin/commands/site/snapshot/import.js +23 -24
  11. package/bin/lib/d1-query.js +92 -18
  12. package/bin/lib/hugo-markdown.js +4 -0
  13. package/bin/lib/node-env.js +40 -4
  14. package/bin/lib/site-pull-media.js +21 -28
  15. package/bin/lib/site-selection.js +94 -29
  16. package/bin/lib/site-snapshot.js +338 -3
  17. package/bin/lib/sql-export.js +68 -5
  18. package/bin/lib/wrangler-cli.js +37 -2
  19. package/bin/lib/wrangler-json.js +35 -0
  20. package/bin/lib/zip-archive.js +187 -0
  21. package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
  22. package/dist/client/.vite/manifest.json +255 -90
  23. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  24. package/dist/client/_assets/chunks/create-editor-B-m7X7S5.js +261 -0
  25. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  26. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  27. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  28. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  29. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  30. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  31. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  32. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  33. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  34. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  35. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  36. package/dist/client/_assets/chunks/sortable-list-BJyd-LXE.js +2 -0
  37. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  38. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  39. package/dist/client/_assets/chunks/unsafe-svg-BkscJx69.js +2 -0
  40. package/dist/client/_assets/client-DAhoqPdr.css +2 -0
  41. package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
  42. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  43. package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
  44. package/dist/client/_assets/client-knSEyUJO.js +295 -0
  45. package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
  46. package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
  47. package/dist/github-sync-Gw4orAHk.js +8398 -0
  48. package/dist/index.js +2 -5
  49. package/dist/node.js +11 -9
  50. package/package.json +15 -5
  51. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  52. package/src/__tests__/basecoat-variants.test.ts +121 -0
  53. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  54. package/src/__tests__/dev-scripts.test.ts +203 -0
  55. package/src/__tests__/export-collection-order.test.ts +276 -0
  56. package/src/__tests__/export-feed-ids.test.ts +184 -0
  57. package/src/__tests__/export-feed-order.test.ts +294 -0
  58. package/src/__tests__/export-hugo-build.test.ts +390 -0
  59. package/src/__tests__/export-import-roundtrip.test.ts +94 -0
  60. package/src/__tests__/export-service.test.ts +842 -28
  61. package/src/__tests__/export-smart-collection.test.ts +329 -0
  62. package/src/__tests__/helpers/component-css.ts +91 -0
  63. package/src/__tests__/helpers/db.ts +33 -0
  64. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  65. package/src/__tests__/helpers/hugo-site.ts +146 -0
  66. package/src/__tests__/import-site-command.test.ts +487 -1
  67. package/src/__tests__/mise-config.test.ts +75 -4
  68. package/src/__tests__/node-dev-tasks.test.ts +264 -0
  69. package/src/__tests__/site-export-canonical-import.test.ts +149 -0
  70. package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
  71. package/src/__tests__/snapshot-settings.test.ts +97 -0
  72. package/src/__tests__/snapshot-tables.test.ts +219 -0
  73. package/src/__tests__/sql-export.test.ts +173 -0
  74. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  75. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  76. package/src/__tests__/zip-archive.test.ts +106 -0
  77. package/src/app.tsx +86 -22
  78. package/src/auth.ts +18 -1
  79. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  80. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  81. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  82. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  83. package/src/client/__tests__/compose-launch.test.ts +62 -1
  84. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  85. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  86. package/src/client/__tests__/copy-field.test.ts +162 -0
  87. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  88. package/src/client/__tests__/image-processor.test.ts +324 -2
  89. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  90. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  91. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  92. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  93. package/src/client/__tests__/toast.test.ts +23 -0
  94. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  95. package/src/client/__tests__/video-processor.test.ts +181 -0
  96. package/src/client/audio-processor.ts +24 -15
  97. package/src/client/audio-waveform.ts +49 -0
  98. package/src/client/collection-dialog-host.ts +72 -0
  99. package/src/client/collection-navigation.ts +51 -16
  100. package/src/client/collection-page-actions.ts +26 -0
  101. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  102. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  103. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  104. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  105. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  106. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  107. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  108. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  109. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  110. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  111. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  112. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  113. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  114. package/src/client/components/__tests__/jant-settings-general.test.ts +561 -172
  115. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  116. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  117. package/src/client/components/collection-dialog-types.ts +49 -0
  118. package/src/client/components/collection-manager-types.ts +32 -1
  119. package/src/client/components/collection-types.ts +1 -15
  120. package/src/client/components/compose-types.ts +75 -1
  121. package/src/client/components/jant-collection-dialog.ts +722 -0
  122. package/src/client/components/jant-collection-directory.ts +567 -182
  123. package/src/client/components/jant-collection-form.ts +53 -232
  124. package/src/client/components/jant-command-palette.ts +100 -49
  125. package/src/client/components/jant-compose-dialog.ts +1486 -707
  126. package/src/client/components/jant-compose-editor.ts +673 -336
  127. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  128. package/src/client/components/jant-config-editor.ts +65 -53
  129. package/src/client/components/jant-confirm-dialog.ts +13 -11
  130. package/src/client/components/jant-locale-picker.ts +418 -0
  131. package/src/client/components/jant-media-lightbox.ts +146 -136
  132. package/src/client/components/jant-nav-manager.ts +690 -366
  133. package/src/client/components/jant-post-menu.ts +1086 -282
  134. package/src/client/components/jant-post-picker.ts +324 -0
  135. package/src/client/components/jant-repo-picker-types.ts +6 -1
  136. package/src/client/components/jant-repo-picker.ts +137 -108
  137. package/src/client/components/jant-settings-avatar.ts +31 -27
  138. package/src/client/components/jant-settings-general.ts +622 -543
  139. package/src/client/components/jant-settings-language.ts +1087 -0
  140. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  141. package/src/client/components/jant-text-preview.ts +94 -86
  142. package/src/client/components/nav-manager-types.ts +62 -1
  143. package/src/client/components/settings-types.ts +14 -29
  144. package/src/client/components/smart-collection-conditions.ts +285 -0
  145. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  146. package/src/client/compose-bridge.ts +214 -141
  147. package/src/client/compose-launch.ts +76 -2
  148. package/src/client/compose-shortcuts.ts +6 -8
  149. package/src/client/compose-triggers.ts +50 -0
  150. package/src/client/copy-field.ts +108 -0
  151. package/src/client/custom-url-menu.ts +60 -4
  152. package/src/client/icons.ts +14 -0
  153. package/src/client/image-plan.ts +165 -0
  154. package/src/client/image-processor.ts +195 -117
  155. package/src/client/image-worker-client.ts +45 -0
  156. package/src/client/image-worker.ts +135 -0
  157. package/src/client/lazy-entries.ts +95 -0
  158. package/src/client/lazy-slugify.ts +2 -2
  159. package/src/client/media-scroll-hint.ts +43 -2
  160. package/src/client/mediabunny.ts +29 -0
  161. package/src/client/post-picker.ts +41 -0
  162. package/src/client/runtime-paths.ts +74 -1
  163. package/src/client/settings-bridge.ts +4 -10
  164. package/src/client/site-header-nav.js +18 -6
  165. package/src/client/smart-collection-dialog-host.ts +116 -0
  166. package/src/client/smart-collection-page-actions.ts +208 -0
  167. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  168. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  169. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  170. package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
  171. package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
  172. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
  173. package/src/client/tiptap/bubble-menu.ts +47 -16
  174. package/src/client/tiptap/extensions.ts +0 -3
  175. package/src/client/tiptap/floating-position.ts +216 -2
  176. package/src/client/tiptap/inline-image-upload.ts +7 -3
  177. package/src/client/tiptap/link-toolbar.ts +22 -9
  178. package/src/client/tiptap/structural-keymap.ts +158 -47
  179. package/src/client/toast.ts +13 -0
  180. package/src/client/types/vite-worker.d.ts +12 -0
  181. package/src/client/upload-with-metadata.ts +3 -13
  182. package/src/client/video-processor.ts +351 -107
  183. package/src/client-auth.ts +14 -31
  184. package/src/client-compose.ts +19 -0
  185. package/src/client-manage.ts +13 -0
  186. package/src/client-settings.ts +14 -0
  187. package/src/client-site.ts +10 -3
  188. package/src/client.ts +4 -0
  189. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  190. package/src/db/__tests__/d1-query.test.ts +56 -1
  191. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  192. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  193. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  194. package/src/db/migrations/0032_married_tomas.sql +3 -0
  195. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  196. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  197. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  198. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  199. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  200. package/src/db/migrations/meta/_journal.json +21 -0
  201. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  202. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  203. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  204. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  205. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  206. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  207. package/src/db/migrations/pg/meta/_journal.json +21 -0
  208. package/src/db/pg/schema.ts +195 -27
  209. package/src/db/post-visibility.ts +117 -0
  210. package/src/db/schema.ts +222 -27
  211. package/src/i18n/__tests__/detect.test.ts +26 -81
  212. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  213. package/src/i18n/context.tsx +12 -2
  214. package/src/i18n/detect.ts +7 -66
  215. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  216. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  217. package/src/i18n/locales/public/en.po +490 -158
  218. package/src/i18n/locales/public/en.ts +1 -1
  219. package/src/i18n/locales/public/zh-Hans.po +487 -155
  220. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  221. package/src/i18n/locales/public/zh-Hant.po +487 -155
  222. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  223. package/src/i18n/locales/settings/en.po +555 -73
  224. package/src/i18n/locales/settings/en.ts +1 -1
  225. package/src/i18n/locales/settings/zh-Hans.po +601 -119
  226. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  227. package/src/i18n/locales/settings/zh-Hant.po +614 -132
  228. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  229. package/src/i18n/locales.ts +94 -0
  230. package/src/i18n/middleware.ts +12 -2
  231. package/src/i18n/supported-locales.ts +80 -0
  232. package/src/lib/__tests__/api-settings.test.ts +11 -2
  233. package/src/lib/__tests__/asset-path.test.ts +92 -0
  234. package/src/lib/__tests__/deferred.test.ts +90 -0
  235. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  236. package/src/lib/__tests__/discover.test.ts +410 -0
  237. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  238. package/src/lib/__tests__/feed.test.ts +1526 -15
  239. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  240. package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
  241. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  242. package/src/lib/__tests__/http-cache.test.ts +162 -0
  243. package/src/lib/__tests__/image.test.ts +27 -1
  244. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  245. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  246. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  247. package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
  248. package/src/lib/__tests__/markdown.test.ts +10 -0
  249. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  250. package/src/lib/__tests__/navigation.test.ts +135 -1
  251. package/src/lib/__tests__/resolve-config.test.ts +131 -3
  252. package/src/lib/__tests__/schemas.test.ts +69 -5
  253. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  254. package/src/lib/__tests__/slug.test.ts +70 -0
  255. package/src/lib/__tests__/slugify.test.ts +85 -0
  256. package/src/lib/__tests__/storage.test.ts +32 -0
  257. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  258. package/src/lib/__tests__/timeline.test.ts +87 -0
  259. package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
  260. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  261. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  262. package/src/lib/__tests__/url.test.ts +78 -72
  263. package/src/lib/__tests__/view-language.test.ts +413 -0
  264. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  265. package/src/lib/address.ts +57 -0
  266. package/src/lib/api-posts.ts +12 -0
  267. package/src/lib/asset-path.ts +98 -1
  268. package/src/lib/build-env.ts +16 -0
  269. package/src/lib/collection-groups.ts +5 -1
  270. package/src/lib/collection-paths.ts +3 -10
  271. package/src/lib/constants.ts +28 -3
  272. package/src/lib/copy-field.ts +36 -0
  273. package/src/lib/crypto.ts +40 -0
  274. package/src/lib/csp-builder.ts +2 -1
  275. package/src/lib/deferred.ts +79 -0
  276. package/src/lib/discover-ping.ts +141 -0
  277. package/src/lib/discover.ts +439 -0
  278. package/src/lib/env.ts +134 -0
  279. package/src/lib/errors.ts +33 -0
  280. package/src/lib/feed-policy.ts +236 -2
  281. package/src/lib/feed.ts +926 -73
  282. package/src/lib/filter-dimensions.ts +1349 -0
  283. package/src/lib/github-sync-repo-name.ts +45 -0
  284. package/src/lib/github-sync-site-config.ts +3 -0
  285. package/src/lib/hosted-signin.ts +1 -1
  286. package/src/lib/http-cache.ts +148 -0
  287. package/src/lib/hugo-markdown.ts +62 -0
  288. package/src/lib/icons.ts +9 -2
  289. package/src/lib/ids.ts +3 -0
  290. package/src/lib/image.ts +17 -4
  291. package/src/lib/jant-docs.ts +42 -0
  292. package/src/lib/lang-detect.ts +308 -0
  293. package/src/lib/markdown-manager.ts +392 -2
  294. package/src/lib/media-helpers.ts +13 -1
  295. package/src/lib/navigation.ts +78 -11
  296. package/src/lib/pagination.ts +28 -24
  297. package/src/lib/per-language-surfaces.ts +67 -0
  298. package/src/lib/post-body-html.ts +11 -4
  299. package/src/lib/post-meta.ts +19 -0
  300. package/src/lib/render.tsx +55 -7
  301. package/src/lib/resolve-config.ts +111 -6
  302. package/src/lib/schemas.ts +251 -15
  303. package/src/lib/site-header-fragment.tsx +2 -1
  304. package/src/lib/site-unavailable-page.ts +47 -0
  305. package/src/lib/slug.ts +51 -7
  306. package/src/lib/slugify.ts +113 -0
  307. package/src/lib/startup-config.ts +4 -6
  308. package/src/lib/storage.ts +7 -0
  309. package/src/lib/summary.ts +50 -0
  310. package/src/lib/thread-fold.ts +161 -0
  311. package/src/lib/time.ts +32 -0
  312. package/src/lib/timeline.ts +43 -2
  313. package/src/lib/tiptap-render.ts +1 -2
  314. package/src/lib/tiptap-to-markdown.ts +13 -7
  315. package/src/lib/upload.ts +16 -13
  316. package/src/lib/url.ts +159 -86
  317. package/src/lib/version.ts +10 -8
  318. package/src/lib/view-language.ts +436 -0
  319. package/src/lib/view.ts +53 -25
  320. package/src/lib/viewer-context.ts +62 -0
  321. package/src/middleware/__tests__/auth.test.ts +70 -2
  322. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  323. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  324. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  325. package/src/middleware/__tests__/session.test.ts +181 -33
  326. package/src/middleware/auth.ts +50 -31
  327. package/src/middleware/cache-control.ts +75 -10
  328. package/src/middleware/config.ts +6 -6
  329. package/src/middleware/error-handler.ts +12 -0
  330. package/src/middleware/onboarding.ts +32 -10
  331. package/src/middleware/secure-headers.ts +1 -1
  332. package/src/middleware/session.ts +96 -8
  333. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  334. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  335. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  336. package/src/node/__tests__/cli-setup.test.ts +163 -0
  337. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  338. package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
  339. package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
  340. package/src/node/__tests__/runtime.test.ts +75 -0
  341. package/src/node/index.ts +3 -0
  342. package/src/node/request-handler.ts +3 -1
  343. package/src/preset.css +76 -22
  344. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  345. package/src/routes/api/__tests__/collections.test.ts +114 -0
  346. package/src/routes/api/__tests__/discover.test.ts +222 -0
  347. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  348. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  349. package/src/routes/api/__tests__/posts.test.ts +24 -0
  350. package/src/routes/api/__tests__/settings.test.ts +24 -3
  351. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  352. package/src/routes/api/__tests__/upload.test.ts +34 -0
  353. package/src/routes/api/collections.ts +49 -6
  354. package/src/routes/api/custom-urls.ts +0 -1
  355. package/src/routes/api/discover.ts +95 -0
  356. package/src/routes/api/export.ts +6 -3
  357. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  358. package/src/routes/api/internal/sites.ts +0 -1
  359. package/src/routes/api/nav-items.ts +55 -8
  360. package/src/routes/api/posts.ts +161 -0
  361. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  362. package/src/routes/api/public/archive.ts +77 -81
  363. package/src/routes/api/public/posts.ts +44 -7
  364. package/src/routes/api/smart-collections.ts +132 -0
  365. package/src/routes/api/telegram.ts +2 -40
  366. package/src/routes/api/upload.ts +10 -3
  367. package/src/routes/api/uploads.ts +1 -2
  368. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  369. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  370. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  371. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  372. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  373. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  374. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  375. package/src/routes/auth/setup.tsx +934 -186
  376. package/src/routes/auth/signin.tsx +10 -8
  377. package/src/routes/compose.tsx +9 -0
  378. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  379. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  380. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  381. package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
  382. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  383. package/src/routes/dash/custom-urls.tsx +1 -37
  384. package/src/routes/dash/settings.tsx +714 -111
  385. package/src/routes/discover-announce.ts +96 -0
  386. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  387. package/src/routes/feed/feed.ts +69 -40
  388. package/src/routes/feed/sitemap.ts +95 -11
  389. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  390. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  391. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  392. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  393. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  394. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  395. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  396. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  397. package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
  398. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  399. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  400. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  401. package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
  402. package/src/routes/pages/archive.tsx +487 -379
  403. package/src/routes/pages/collection.tsx +94 -96
  404. package/src/routes/pages/collections.tsx +38 -50
  405. package/src/routes/pages/featured.tsx +43 -18
  406. package/src/routes/pages/home.tsx +25 -6
  407. package/src/routes/pages/language.tsx +113 -0
  408. package/src/routes/pages/latest.tsx +26 -16
  409. package/src/routes/pages/new.tsx +3 -0
  410. package/src/routes/pages/page.tsx +228 -45
  411. package/src/routes/pages/partials.tsx +41 -0
  412. package/src/routes/pages/search.tsx +22 -5
  413. package/src/routes/pages/smart-collection.tsx +340 -0
  414. package/src/routes/pages/subscribe.tsx +182 -0
  415. package/src/runtime/__tests__/index.test.ts +19 -0
  416. package/src/runtime/__tests__/node.test.ts +129 -4
  417. package/src/runtime/__tests__/readiness.test.ts +23 -0
  418. package/src/runtime/cloudflare.ts +21 -2
  419. package/src/runtime/index.ts +27 -1
  420. package/src/runtime/node.ts +67 -1
  421. package/src/runtime/readiness.ts +15 -0
  422. package/src/runtime/site.ts +95 -24
  423. package/src/services/__tests__/auth.test.ts +116 -7
  424. package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
  425. package/src/services/__tests__/collection.test.ts +108 -4
  426. package/src/services/__tests__/custom-url.test.ts +34 -0
  427. package/src/services/__tests__/github-app-installations.test.ts +153 -0
  428. package/src/services/__tests__/github-sync-push.test.ts +46 -0
  429. package/src/services/__tests__/language.test.ts +495 -0
  430. package/src/services/__tests__/media.test.ts +31 -0
  431. package/src/services/__tests__/navigation.test.ts +213 -10
  432. package/src/services/__tests__/path.test.ts +236 -0
  433. package/src/services/__tests__/post-language.test.ts +830 -0
  434. package/src/services/__tests__/post-timeline.test.ts +127 -0
  435. package/src/services/__tests__/post.test.ts +120 -9
  436. package/src/services/__tests__/settings.test.ts +228 -17
  437. package/src/services/__tests__/site-admin.test.ts +5 -5
  438. package/src/services/__tests__/smart-collection.test.ts +651 -0
  439. package/src/services/about-page.ts +22 -1
  440. package/src/services/auth.ts +51 -49
  441. package/src/services/bootstrap.ts +408 -39
  442. package/src/services/collection-directory-position.ts +76 -0
  443. package/src/services/collection.ts +331 -46
  444. package/src/services/custom-url.ts +29 -24
  445. package/src/services/export-theme/assets/client-site.css +1 -1
  446. package/src/services/export-theme/assets/client-site.js +123 -123
  447. package/src/services/export-theme/layouts/_default/alias.html +27 -1
  448. package/src/services/export-theme/layouts/_default/list.html +2 -63
  449. package/src/services/export-theme/layouts/_default/rss.xml +103 -38
  450. package/src/services/export-theme/layouts/collections/list.html +3 -3
  451. package/src/services/export-theme/layouts/featured/list.html +1 -4
  452. package/src/services/export-theme/layouts/index.html +40 -26
  453. package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
  454. package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
  455. package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
  456. package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
  457. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  458. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  459. package/src/services/export-theme/layouts/partials/head.html +1 -1
  460. package/src/services/export-theme/layouts/partials/header.html +5 -3
  461. package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
  462. package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
  463. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  464. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  465. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  466. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  467. package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
  468. package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
  469. package/src/services/export-theme/layouts/post/list.html +1 -1
  470. package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
  471. package/src/services/export-theme/styles/main.css +13 -2
  472. package/src/services/export-theme/theme.toml +1 -1
  473. package/src/services/export.ts +880 -72
  474. package/src/services/github-app-installations.ts +171 -4
  475. package/src/services/github-sync.ts +69 -15
  476. package/src/services/index.ts +34 -3
  477. package/src/services/language.ts +412 -0
  478. package/src/services/mcp.ts +22 -2
  479. package/src/services/media.ts +22 -7
  480. package/src/services/navigation.ts +429 -67
  481. package/src/services/path.ts +254 -6
  482. package/src/services/post.ts +1234 -190
  483. package/src/services/search.ts +39 -2
  484. package/src/services/settings.ts +238 -43
  485. package/src/services/site-admin.ts +27 -14
  486. package/src/services/site.ts +160 -32
  487. package/src/services/smart-collection.ts +636 -0
  488. package/src/services/telegram.ts +20 -22
  489. package/src/services/upload-session.ts +1 -3
  490. package/src/style-author.css +27 -0
  491. package/src/styles/components-author.css +1622 -0
  492. package/src/styles/components.css +24 -1571
  493. package/src/styles/site-media.css +16 -2
  494. package/src/styles/tokens.css +82 -12
  495. package/src/styles/ui-author.css +7602 -0
  496. package/src/styles/ui.css +1090 -7216
  497. package/src/types/app-context.ts +23 -0
  498. package/src/types/bindings.ts +8 -0
  499. package/src/types/config.ts +94 -15
  500. package/src/types/constants.ts +188 -16
  501. package/src/types/entities.ts +87 -1
  502. package/src/types/operations.ts +74 -2
  503. package/src/types/props.ts +157 -31
  504. package/src/types/views.ts +55 -1
  505. package/src/ui/__tests__/font-themes.test.ts +52 -12
  506. package/src/ui/compose/ComposeDialog.tsx +201 -7
  507. package/src/ui/compose/ComposePrompt.tsx +1 -5
  508. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  509. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  510. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  511. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  512. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  513. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  514. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  515. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  516. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  517. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  518. package/src/ui/dash/settings/GeneralContent.tsx +277 -113
  519. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  520. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  521. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  522. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  523. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  524. package/src/ui/dash/settings/discover-copy.ts +116 -0
  525. package/src/ui/feed/LinkCard.tsx +55 -45
  526. package/src/ui/feed/NoteCard.tsx +9 -4
  527. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  528. package/src/ui/feed/QuoteCard.tsx +55 -30
  529. package/src/ui/feed/ThreadPreview.tsx +6 -2
  530. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  531. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  532. package/src/ui/feed/thread-preview-state.ts +20 -6
  533. package/src/ui/font-themes.ts +39 -29
  534. package/src/ui/layouts/BaseLayout.tsx +155 -10
  535. package/src/ui/layouts/SiteLayout.tsx +192 -34
  536. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  537. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  538. package/src/ui/pages/ArchivePage.tsx +282 -289
  539. package/src/ui/pages/CollectionPage.tsx +86 -76
  540. package/src/ui/pages/CollectionsPage.tsx +5 -0
  541. package/src/ui/pages/ComposePage.tsx +8 -0
  542. package/src/ui/pages/PostPage.tsx +47 -0
  543. package/src/ui/pages/SearchPage.tsx +3 -3
  544. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  545. package/src/ui/pages/SubscribePage.tsx +236 -0
  546. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  547. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  548. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  549. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  550. package/src/ui/shared/CollectionsManager.tsx +70 -17
  551. package/src/ui/shared/CopyField.tsx +127 -0
  552. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  553. package/src/ui/shared/MediaGallery.tsx +125 -46
  554. package/src/ui/shared/Pagination.tsx +20 -5
  555. package/src/ui/shared/PostFooter.tsx +20 -11
  556. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  557. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  558. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  559. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  560. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  561. package/src/ui/shared/archive-labels.ts +98 -0
  562. package/src/ui/shared/collection-management-labels.ts +94 -16
  563. package/src/ui/shared/navigation-labels.ts +45 -71
  564. package/src/ui/shared/post-article-attributes.ts +2 -0
  565. package/src/ui/shared/smart-collection-labels.ts +343 -0
  566. package/dist/app-C75Zc8dD.js +0 -6
  567. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  568. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  569. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  570. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  571. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  572. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  573. package/dist/env-BPYViDJJ.js +0 -281
  574. package/dist/export-Dl5KCiEs.js +0 -5562
  575. package/dist/github-api-BNF35Lkx.js +0 -176
  576. package/dist/github-app-DdiD-bC4.js +0 -275
  577. package/dist/github-sync-Bqg62IvV.js +0 -4
  578. package/dist/github-sync-HdK2EgvJ.js +0 -431
  579. package/dist/url-CbLAtlZe.js +0 -900
  580. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  581. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  582. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  583. package/src/client/collection-created-notice.ts +0 -66
  584. package/src/client/collection-form-bridge.ts +0 -191
  585. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  586. package/src/client/components/jant-post-form.ts +0 -422
  587. package/src/client/components/post-form-template.ts +0 -352
  588. package/src/client/components/post-form-types.ts +0 -96
  589. package/src/client/media-metadata.ts +0 -247
  590. package/src/client/post-form-bridge.ts +0 -193
  591. package/src/client/tiptap/exitable-marks.ts +0 -73
  592. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -1,8 +1,23 @@
1
- import { runLocalWrangler } from "./wrangler-cli.js";
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { WRANGLER_MAX_BUFFER, runLocalWrangler } from "./wrangler-cli.js";
5
+ import { extractWranglerJson } from "./wrangler-json.js";
2
6
 
3
7
  const DEFAULT_RETRY_ATTEMPTS = 4;
4
8
  const DEFAULT_RETRY_DELAY_MS = 500;
5
9
 
10
+ /**
11
+ * Byte ceiling for a statement batch sent as one `--command=` argument.
12
+ *
13
+ * Linux caps a single argv entry at 128 KiB (`MAX_ARG_STRLEN`) and fails the
14
+ * spawn with `E2BIG`; macOS has no per-argument cap, so an oversized batch
15
+ * works on a developer's Mac and dies in CI and in the demo rebuild, which is
16
+ * exactly how it first surfaced — a snapshot import whose `db.sql` had grown
17
+ * past the limit. Anything at or above this goes through `--file` instead.
18
+ */
19
+ const MAX_INLINE_COMMAND_BYTES = 96 * 1024;
20
+
6
21
  function getD1Flag(runtime) {
7
22
  return runtime === "d1-remote" ? "--remote" : "--local";
8
23
  }
@@ -35,7 +50,7 @@ export function parseWranglerError(output) {
35
50
  }
36
51
 
37
52
  try {
38
- const parsed = JSON.parse(output.trim());
53
+ const parsed = JSON.parse(extractWranglerJson(output).trim());
39
54
  const error = Array.isArray(parsed) ? parsed[0]?.error : parsed?.error;
40
55
  if (!error?.text) {
41
56
  return undefined;
@@ -61,6 +76,46 @@ function sleepSync(ms) {
61
76
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
62
77
  }
63
78
 
79
+ const WRANGLER_OUTPUT_EXCERPT_LENGTH = 2000;
80
+
81
+ /**
82
+ * Rows per read when dumping a D1 table: small enough that a page of long
83
+ * posts stays well inside {@link WRANGLER_MAX_BUFFER}.
84
+ */
85
+ export const D1_DUMP_PAGE_SIZE = 200;
86
+
87
+ /**
88
+ * Say why a Wrangler call failed without echoing its whole output: on a
89
+ * failed `d1 execute` that output can be a site's rows, hundreds of KB of it.
90
+ *
91
+ * @param {string[]} args - The Wrangler arguments
92
+ * @param {Error & { code?: string, stdout?: string, stderr?: string }} error
93
+ * @returns {string} A message naming the command and the cause
94
+ * @example
95
+ * describeWranglerFailure(["d1", "execute", "DB"], enobufsError);
96
+ * // "`wrangler d1 execute DB` printed more than 67108864 bytes, …"
97
+ */
98
+ export function describeWranglerFailure(args, error) {
99
+ const command = `wrangler ${args.slice(0, 3).join(" ")}`;
100
+ if (error?.code === "ENOBUFS") {
101
+ return `\`${command}\` printed more than ${WRANGLER_MAX_BUFFER} bytes, the most the CLI buffers.`;
102
+ }
103
+
104
+ const stderr = String(error?.stderr ?? "").trim();
105
+ if (stderr) return `\`${command}\` failed: ${stderr}`;
106
+
107
+ const stdout = String(error?.stdout ?? "").trim();
108
+ if (stdout) {
109
+ const excerpt =
110
+ stdout.length > WRANGLER_OUTPUT_EXCERPT_LENGTH
111
+ ? `${stdout.slice(0, WRANGLER_OUTPUT_EXCERPT_LENGTH)}… (${stdout.length} characters)`
112
+ : stdout;
113
+ return `\`${command}\` failed: ${excerpt}`;
114
+ }
115
+
116
+ return `\`${command}\` failed: ${error?.message ?? "unknown error"}`;
117
+ }
118
+
64
119
  export function isRetryableWranglerD1Failure(output, error) {
65
120
  const combined = `${output ?? ""}\n${error?.message ?? ""}`.toLowerCase();
66
121
  return [
@@ -75,18 +130,6 @@ export function isRetryableWranglerD1Failure(output, error) {
75
130
  ].some((fragment) => combined.includes(fragment));
76
131
  }
77
132
 
78
- /**
79
- * Strip non-JSON preamble that Wrangler sometimes writes to stdout
80
- * (e.g. "Proxy environment variables detected…") before the actual JSON payload.
81
- */
82
- function extractJson(raw) {
83
- const idx = raw.indexOf("[");
84
- const idx2 = raw.indexOf("{");
85
- if (idx === -1 && idx2 === -1) return raw;
86
- const start = idx === -1 ? idx2 : idx2 === -1 ? idx : Math.min(idx, idx2);
87
- return raw.slice(start);
88
- }
89
-
90
133
  function getWranglerExecutionOptions(options) {
91
134
  const executionOptions = { ...options };
92
135
  for (const key of [
@@ -134,12 +177,43 @@ function runWrangler(args, options = {}) {
134
177
  throw new Error(`Wrangler error: ${wranglerError}`);
135
178
  }
136
179
 
137
- throw new Error(output || error.message, { cause: error });
180
+ throw new Error(describeWranglerFailure(args, error), { cause: error });
138
181
  }
139
182
  }
140
183
  }
141
184
 
185
+ /**
186
+ * Whether a statement batch is too large to travel as one CLI argument.
187
+ *
188
+ * @param {string} sql - SQL batch about to be sent to Wrangler
189
+ * @returns {boolean} true when it has to go through a file instead
190
+ * @example
191
+ * ```js
192
+ * exceedsInlineCommandLimit("SELECT 1;"); // false
193
+ * ```
194
+ */
195
+ export function exceedsInlineCommandLimit(sql) {
196
+ return Buffer.byteLength(sql, "utf8") >= MAX_INLINE_COMMAND_BYTES;
197
+ }
198
+
199
+ function withTemporarySqlFile(sql, run) {
200
+ const directory = mkdtempSync(join(tmpdir(), "jant-d1-"));
201
+ try {
202
+ const filePath = join(directory, "batch.sql");
203
+ writeFileSync(filePath, sql);
204
+ return run(filePath);
205
+ } finally {
206
+ rmSync(directory, { recursive: true, force: true });
207
+ }
208
+ }
209
+
142
210
  export function executeD1(sql, runtime, options = {}) {
211
+ if (exceedsInlineCommandLimit(sql)) {
212
+ return withTemporarySqlFile(sql, (filePath) =>
213
+ executeD1File(filePath, runtime, options),
214
+ );
215
+ }
216
+
143
217
  const args = appendWranglerContext(
144
218
  [
145
219
  "d1",
@@ -153,7 +227,7 @@ export function executeD1(sql, runtime, options = {}) {
153
227
 
154
228
  if (options.quiet) {
155
229
  const output = runWrangler([...args, "--json"], options);
156
- const parsed = JSON.parse(extractJson(output));
230
+ const parsed = JSON.parse(extractWranglerJson(output));
157
231
  const statements = Array.isArray(parsed) ? parsed : [parsed];
158
232
 
159
233
  for (const statement of statements) {
@@ -183,7 +257,7 @@ export function queryD1(sql, runtime, options = {}) {
183
257
  ),
184
258
  options,
185
259
  );
186
- const parsed = JSON.parse(extractJson(output));
260
+ const parsed = JSON.parse(extractWranglerJson(output));
187
261
  const statement = Array.isArray(parsed) ? parsed[0] : parsed;
188
262
 
189
263
  if (statement?.error?.text) {
@@ -208,7 +282,7 @@ export function executeD1File(filePath, runtime, options = {}) {
208
282
 
209
283
  if (options.quiet) {
210
284
  const output = runWrangler([...args, "--json"], options);
211
- const parsed = JSON.parse(extractJson(output));
285
+ const parsed = JSON.parse(extractWranglerJson(output));
212
286
  const statements = Array.isArray(parsed) ? parsed : [parsed];
213
287
 
214
288
  for (const statement of statements) {
@@ -20,6 +20,7 @@ const FRONT_MATTER_KEY_ORDER = [
20
20
  "type",
21
21
  "draft",
22
22
  "aliases",
23
+ "feed_id",
23
24
  "build",
24
25
 
25
26
  // Post payload
@@ -27,12 +28,15 @@ const FRONT_MATTER_KEY_ORDER = [
27
28
  "status",
28
29
  "visibility",
29
30
  "summary_text",
31
+ "truncated",
30
32
  "link_url",
31
33
  "source_name",
32
34
  "source_url",
33
35
  "quote_text",
34
36
  "rating",
35
37
  "featured_at",
38
+ "featured_post_ids",
39
+ "featured_sort_at",
36
40
  "pinned_at",
37
41
 
38
42
  // Bookkeeping / attachments
@@ -22,13 +22,46 @@ function fileExists(envPath) {
22
22
  }
23
23
 
24
24
  /**
25
- * Locate `.env.node` for CLI auto-load. Searches, in order:
25
+ * Locate `.env.node` for CLI auto-load.
26
+ *
27
+ * `JANT_ENV_FILE` overrides the search whenever it is set: a path names the
28
+ * file to load, and an empty value means "load nothing". The empty form is
29
+ * what keeps a developer's `packages/core/.env.node` out of the test suite —
30
+ * see the `env` block in `vitest.config.ts`.
31
+ *
32
+ * Without the override, the search order is:
26
33
  * 1. `<cwd>/.env.node` — user's site directory
27
34
  * 2. `<bin>/../../.env.node` — `packages/core/.env.node` (in-repo dev)
28
35
  *
29
- * Returns the first existing path, or `null` if none is found.
36
+ * @param {string} [cwd] - Directory searched first, and the base a relative
37
+ * `JANT_ENV_FILE` resolves against. Defaults to `process.cwd()`.
38
+ * @param {Record<string, string | undefined>} [env] - Environment to read
39
+ * `JANT_ENV_FILE` from. Defaults to `process.env`.
40
+ * @returns {string | null} The env file to load, or `null` when there is none.
41
+ * @throws {Error} When `JANT_ENV_FILE` names a file that does not exist.
42
+ * @example
43
+ * findNodeEnvPath(process.cwd(), { JANT_ENV_FILE: "" }); // null
44
+ * findNodeEnvPath(process.cwd(), { JANT_ENV_FILE: "./staging.env" });
30
45
  */
31
- export function findNodeEnvPath(cwd = process.cwd()) {
46
+ export function findNodeEnvPath(cwd = process.cwd(), env = process.env) {
47
+ const override = env.JANT_ENV_FILE;
48
+ if (override !== undefined) {
49
+ const trimmed = override.trim();
50
+ if (trimmed === "") {
51
+ return null;
52
+ }
53
+
54
+ const overridePath = resolve(cwd, trimmed);
55
+ if (!fileExists(overridePath)) {
56
+ throw new Error(
57
+ `JANT_ENV_FILE points at "${override}", which does not exist. ` +
58
+ "Set it to a readable env file, or to an empty value to skip the env file.",
59
+ );
60
+ }
61
+
62
+ return overridePath;
63
+ }
64
+
32
65
  const candidates = [
33
66
  resolve(cwd, ".env.node"),
34
67
  resolve(dirname(fileURLToPath(import.meta.url)), "../../.env.node"),
@@ -90,9 +123,12 @@ export function loadNodeEnvFile(envPath, env = process.env) {
90
123
  * Always called before `resolveCliRuntime()`, so DATABASE_URL / DATA_DIR
91
124
  * defined in `.env.node` make `--node` (or auto-detect) work without
92
125
  * requiring the user to source the file manually.
126
+ *
127
+ * Honours `JANT_ENV_FILE` through {@link findNodeEnvPath}, so a caller that
128
+ * needs a fully controlled environment can opt out of the file entirely.
93
129
  */
94
130
  export function autoloadNodeEnv(env = process.env) {
95
- const envPath = findNodeEnvPath();
131
+ const envPath = findNodeEnvPath(process.cwd(), env);
96
132
  if (!envPath) {
97
133
  return { envPath: null, found: false, assignedKeys: [], skippedKeys: [] };
98
134
  }
@@ -12,7 +12,6 @@ import {
12
12
  } from "node:fs/promises";
13
13
  import { tmpdir } from "node:os";
14
14
  import { basename, dirname, extname, join, relative } from "node:path";
15
- import { unzipSync, zipSync } from "fflate";
16
15
  import { parse, stringify } from "smol-toml";
17
16
  import {
18
17
  collectMediaReferences as collectParsedMediaReferences,
@@ -20,6 +19,7 @@ import {
20
19
  rewriteMediaReferences,
21
20
  } from "./site-media-parser.js";
22
21
  import { formatFrontMatter, parseFrontMatter } from "./hugo-markdown.js";
22
+ import { extractZipFile, writeDirectoryToZip } from "./zip-archive.js";
23
23
 
24
24
  export function getSitePathPrefix(baseUrl) {
25
25
  if (typeof baseUrl !== "string" || baseUrl.trim() === "") {
@@ -477,29 +477,6 @@ async function resolveExistingPulledPath(
477
477
  return toPulledPublicPath(pathname, sitePathPrefix);
478
478
  }
479
479
 
480
- async function packDirectoryToZip(rootDir) {
481
- const files = {};
482
- const allFiles = await walkFiles(rootDir);
483
-
484
- for (const fullPath of allFiles) {
485
- const relPath = relative(rootDir, fullPath).replace(/\\/g, "/");
486
- files[relPath] = new Uint8Array(await readFile(fullPath));
487
- }
488
-
489
- return zipSync(files);
490
- }
491
-
492
- async function unpackZipToDirectory(zipBytes, rootDir) {
493
- const files = unzipSync(zipBytes);
494
- await Promise.all(
495
- Object.entries(files).map(async ([relPath, bytes]) => {
496
- const fullPath = join(rootDir, relPath);
497
- await mkdir(dirname(fullPath), { recursive: true });
498
- await writeFile(fullPath, bytes);
499
- }),
500
- );
501
- }
502
-
503
480
  export async function pullSiteExportDirectory(rootDir, options = {}) {
504
481
  const logger =
505
482
  typeof options.logger === "function" ? options.logger : () => {};
@@ -681,14 +658,30 @@ export async function pullSiteExportDirectory(rootDir, options = {}) {
681
658
  return stats;
682
659
  }
683
660
 
684
- export async function pullSiteExportZipBytes(zipBytes, options = {}) {
661
+ /**
662
+ * Pull media into an export ZIP: extract it to a temporary directory, pull
663
+ * there, and write the result as a new archive. Nothing is held in memory
664
+ * beyond one media file at a time, so the archive can be any size.
665
+ *
666
+ * @param {string} inputZipPath - Export archive to read
667
+ * @param {string} outputZipPath - Archive to write; may be the input
668
+ * @param {Parameters<typeof pullSiteExportDirectory>[1]} [options]
669
+ * @returns {Promise<Awaited<ReturnType<typeof pullSiteExportDirectory>>>} Pull stats
670
+ * @example
671
+ * await pullSiteExportZipFile("export.zip", "export.zip", { logger });
672
+ */
673
+ export async function pullSiteExportZipFile(
674
+ inputZipPath,
675
+ outputZipPath,
676
+ options = {},
677
+ ) {
685
678
  const tempDir = await mkdtemp(join(tmpdir(), "jant-site-pull-"));
686
679
 
687
680
  try {
688
- await unpackZipToDirectory(zipBytes, tempDir);
681
+ await extractZipFile(inputZipPath, tempDir);
689
682
  const stats = await pullSiteExportDirectory(tempDir, options);
690
- const pulledZip = await packDirectoryToZip(tempDir);
691
- return { zipBytes: pulledZip, stats };
683
+ await writeDirectoryToZip(tempDir, outputZipPath);
684
+ return stats;
692
685
  } finally {
693
686
  await rm(tempDir, { recursive: true, force: true });
694
687
  }
@@ -39,16 +39,45 @@ function normalizePathPrefix(value) {
39
39
  return normalized || null;
40
40
  }
41
41
 
42
- function resolveSelector(options = {}) {
43
- if (typeof options.site === "string" && options.site.trim()) {
44
- return {
45
- kind: "site",
46
- siteId: options.site.trim(),
47
- };
42
+ /**
43
+ * Printed when a host-based command names no site. Kept word for word with
44
+ * `resolveCliSite` in `src/runtime/site.ts`, which the Node path uses.
45
+ */
46
+ const HOST_BASED_SITE_REQUIRED_MESSAGE =
47
+ "host-based mode needs a target site. Pass --site <key|id>, --host <host>, or --url <url>.";
48
+
49
+ function getTrimmedOption(value) {
50
+ return typeof value === "string" && value.trim() ? value.trim() : null;
51
+ }
52
+
53
+ /**
54
+ * Turn the site flags a command parsed into the selector both resolvers take:
55
+ * this file's raw-SQL `resolveCliSite` and `createNodeCliRuntime`.
56
+ *
57
+ * @param {{ site?: string, host?: string, pathPrefix?: string, url?: string }} [options]
58
+ * The `--site`, `--host`, `--path-prefix` and `--url` values
59
+ * @returns {{ kind: "site", idOrKey: string } | { kind: "host", host: string, pathPrefix: string | null } | null}
60
+ * The selector, or null when no site flag was passed
61
+ * @throws {Error} When more than one of `--site`, `--host` and `--url` is set
62
+ * @example
63
+ * parseCliSiteSelector({ url: "https://example.com/blog/" });
64
+ * // { kind: "host", host: "example.com", pathPrefix: "/blog" }
65
+ */
66
+ export function parseCliSiteSelector(options = {}) {
67
+ const site = getTrimmedOption(options.site);
68
+ const host = getTrimmedOption(options.host);
69
+ const url = getTrimmedOption(options.url);
70
+
71
+ if ([site, host, url].filter(Boolean).length > 1) {
72
+ throw new Error("Choose only one of --site, --host, or --url.");
73
+ }
74
+
75
+ if (site) {
76
+ return { kind: "site", idOrKey: site };
48
77
  }
49
78
 
50
- if (typeof options.url === "string" && options.url.trim()) {
51
- const parsed = new URL(options.url.trim());
79
+ if (url) {
80
+ const parsed = new URL(url);
52
81
  return {
53
82
  kind: "host",
54
83
  host: parsed.host,
@@ -56,10 +85,10 @@ function resolveSelector(options = {}) {
56
85
  };
57
86
  }
58
87
 
59
- if (typeof options.host === "string" && options.host.trim()) {
88
+ if (host) {
60
89
  return {
61
90
  kind: "host",
62
- host: options.host.trim(),
91
+ host,
63
92
  pathPrefix: normalizePathPrefix(options.pathPrefix),
64
93
  };
65
94
  }
@@ -67,21 +96,26 @@ function resolveSelector(options = {}) {
67
96
  return null;
68
97
  }
69
98
 
70
- async function resolveSiteById(queryRunner, siteId) {
99
+ // Mirrors `SiteService.getByIdOrKey`: an id match wins over a key match.
100
+ async function resolveSiteByIdOrKey(queryRunner, idOrKey) {
101
+ const escaped = escapeSqlString(idOrKey);
71
102
  const rows = await queryRunner.query(`
72
103
  SELECT "id", "key", "status", "created_at", "updated_at"
73
104
  FROM "site"
74
- WHERE "id" = '${escapeSqlString(siteId)}'
75
- LIMIT 1
105
+ WHERE "id" = '${escaped}' OR "key" = '${escaped}'
106
+ LIMIT 2
76
107
  `);
77
108
 
78
- if (rows.length === 0) {
79
- throw new Error(`No site found for --site ${siteId}.`);
109
+ const row =
110
+ rows.find((candidate) => getOptionalString(candidate, "id") === idOrKey) ??
111
+ rows[0];
112
+ if (!row) {
113
+ throw new Error(`No site found for --site ${idOrKey}.`);
80
114
  }
81
115
 
82
116
  return {
83
117
  created: false,
84
- site: toSite(rows[0]),
118
+ site: toSite(row),
85
119
  };
86
120
  }
87
121
 
@@ -142,24 +176,63 @@ function formatSiteSummary(rows) {
142
176
  .join(", ");
143
177
  }
144
178
 
179
+ /**
180
+ * How the target instance maps requests to sites.
181
+ *
182
+ * @param {{ SITE_RESOLUTION_MODE?: unknown }} [env] The process environment,
183
+ * or runtime bindings built from it; defaults to `process.env`
184
+ * @returns {"host-based" | "single-site"} `host-based` only when set to it
185
+ * @example
186
+ * if (getCliSiteResolutionMode(process.env) === "host-based") { ... }
187
+ */
145
188
  export function getCliSiteResolutionMode(env = process.env) {
146
189
  return env.SITE_RESOLUTION_MODE === "host-based"
147
190
  ? "host-based"
148
191
  : "single-site";
149
192
  }
150
193
 
194
+ /**
195
+ * Resolve the site a command acts on with raw SQL, for runtimes that cannot
196
+ * run the site service (D1 through Wrangler, snapshot tooling).
197
+ *
198
+ * A site flag wins in either mode. Without one, single-site mode uses the
199
+ * instance's one site, and host-based mode fails before querying: a hosted
200
+ * database holds every tenant, so there is no default to fall back to.
201
+ *
202
+ * @param {{ query(sql: string): Promise<Record<string, unknown>[]>, execute?(sql: string): Promise<void> }} queryRunner
203
+ * Runs SQL against the target database
204
+ * @param {{ env?: { SITE_RESOLUTION_MODE?: unknown }, site?: string, host?: string, pathPrefix?: string, url?: string, createIfMissing?: boolean, bootstrapSite?: { id?: string, key?: string } }} [options]
205
+ * The environment, the command's site flags, and whether single-site mode
206
+ * may create the site shell when none exists
207
+ * @returns {Promise<{ created: boolean, site: { id: string, key: string, status: string, createdAt: number, updatedAt: number } }>}
208
+ * The site, and whether this call created it
209
+ * @throws {Error} When the flags match no site, when host-based mode has none,
210
+ * or when single-site mode finds zero sites it may not create or several
211
+ * @example
212
+ * const { site } = await resolveCliSite(context, {
213
+ * env: process.env,
214
+ * site: values.site,
215
+ * host: values.host,
216
+ * pathPrefix: values["path-prefix"],
217
+ * url: values.url,
218
+ * });
219
+ */
151
220
  export async function resolveCliSite(queryRunner, options = {}) {
152
221
  const resolutionMode = getCliSiteResolutionMode(options.env);
153
- const selector = resolveSelector(options);
222
+ const selector = parseCliSiteSelector(options);
154
223
 
155
224
  if (selector?.kind === "site") {
156
- return resolveSiteById(queryRunner, selector.siteId);
225
+ return resolveSiteByIdOrKey(queryRunner, selector.idOrKey);
157
226
  }
158
227
 
159
228
  if (selector?.kind === "host") {
160
229
  return resolveSiteByHost(queryRunner, selector.host, selector.pathPrefix);
161
230
  }
162
231
 
232
+ if (resolutionMode === "host-based") {
233
+ throw new Error(HOST_BASED_SITE_REQUIRED_MESSAGE);
234
+ }
235
+
163
236
  const rows = await queryRunner.query(`
164
237
  SELECT "id", "key", "status", "created_at", "updated_at"
165
238
  FROM "site"
@@ -168,12 +241,6 @@ export async function resolveCliSite(queryRunner, options = {}) {
168
241
  `);
169
242
 
170
243
  if (rows.length === 0) {
171
- if (resolutionMode !== "single-site") {
172
- throw new Error(
173
- "No site configured for this instance. Create a site before using host-based mode.",
174
- );
175
- }
176
-
177
244
  if (!options.createIfMissing || typeof queryRunner.execute !== "function") {
178
245
  throw new Error(
179
246
  "single-site mode requires an initialized site. Complete /setup first or run a command that can bootstrap the site shell.",
@@ -208,11 +275,9 @@ export async function resolveCliSite(queryRunner, options = {}) {
208
275
  }
209
276
 
210
277
  if (rows.length > 1) {
211
- const message =
212
- resolutionMode === "single-site"
213
- ? `single-site mode found multiple sites in the database: ${formatSiteSummary(rows)}. Restore SITE_RESOLUTION_MODE=host-based for this database, or remove the extra sites before restarting in single-site mode.`
214
- : "host-based mode requires --site, --host, or --url when the database contains multiple sites.";
215
- throw new Error(message);
278
+ throw new Error(
279
+ `single-site mode found multiple sites in the database: ${formatSiteSummary(rows)}. Restore SITE_RESOLUTION_MODE=host-based for this database, or remove the extra sites before restarting in single-site mode.`,
280
+ );
216
281
  }
217
282
 
218
283
  return {