@jant/core 0.7.0 → 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 (158) hide show
  1. package/bin/commands/export.js +3 -1
  2. package/bin/commands/import-site.js +689 -216
  3. package/bin/commands/setup.js +136 -0
  4. package/bin/commands/site/export.js +71 -34
  5. package/bin/commands/site/pull-media.js +2 -6
  6. package/bin/commands/site/snapshot/export.js +18 -60
  7. package/bin/commands/site/snapshot/import.js +22 -23
  8. package/bin/lib/d1-query.js +87 -2
  9. package/bin/lib/hugo-markdown.js +4 -0
  10. package/bin/lib/site-pull-media.js +21 -28
  11. package/bin/lib/site-selection.js +10 -1
  12. package/bin/lib/site-snapshot.js +338 -3
  13. package/bin/lib/sql-export.js +68 -5
  14. package/bin/lib/wrangler-cli.js +9 -0
  15. package/bin/lib/zip-archive.js +187 -0
  16. package/dist/{app-K_Aa1MMn.js → app-ZcaI1kPN.js} +732 -217
  17. package/dist/client/.vite/manifest.json +20 -20
  18. package/dist/client/_assets/chunks/{create-editor-CD3FhrOB.js → create-editor-B-m7X7S5.js} +50 -46
  19. package/dist/client/_assets/chunks/{sortable-list-CgaL2jCs.js → sortable-list-BJyd-LXE.js} +1 -1
  20. package/dist/client/_assets/chunks/{unsafe-svg-0QCkP0vZ.js → unsafe-svg-BkscJx69.js} +2 -2
  21. package/dist/client/_assets/{client-GiYENVw8.css → client-DAhoqPdr.css} +1 -1
  22. package/dist/client/_assets/{client-auth-k8gJ6QJj.js → client-auth-CAMfTrKW.js} +1 -1
  23. package/dist/client/_assets/{client-compose-B_kDtWkW.js → client-compose-D6rExKVK.js} +1 -1
  24. package/dist/client/_assets/{client-VnFxJN7G.js → client-knSEyUJO.js} +1 -1
  25. package/dist/client/_assets/{client-manage-M90aSTOk.js → client-manage-sVOkqVbH.js} +1 -1
  26. package/dist/client/_assets/{client-settings-DfYs9n1F.js → client-settings-CGR_nZ78.js} +7 -7
  27. package/dist/{github-sync-BPAvT999.js → github-sync-Gw4orAHk.js} +799 -110
  28. package/dist/index.js +2 -2
  29. package/dist/node.js +9 -5
  30. package/package.json +13 -3
  31. package/src/__tests__/dev-scripts.test.ts +203 -0
  32. package/src/__tests__/export-collection-order.test.ts +276 -0
  33. package/src/__tests__/export-feed-ids.test.ts +184 -0
  34. package/src/__tests__/export-feed-order.test.ts +294 -0
  35. package/src/__tests__/export-hugo-build.test.ts +130 -0
  36. package/src/__tests__/export-import-roundtrip.test.ts +94 -0
  37. package/src/__tests__/export-service.test.ts +611 -28
  38. package/src/__tests__/export-smart-collection.test.ts +329 -0
  39. package/src/__tests__/helpers/hugo-site.ts +146 -0
  40. package/src/__tests__/import-site-command.test.ts +487 -1
  41. package/src/__tests__/mise-config.test.ts +75 -4
  42. package/src/__tests__/node-dev-tasks.test.ts +264 -0
  43. package/src/__tests__/site-export-canonical-import.test.ts +149 -0
  44. package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
  45. package/src/__tests__/snapshot-settings.test.ts +97 -0
  46. package/src/__tests__/snapshot-tables.test.ts +219 -0
  47. package/src/__tests__/sql-export.test.ts +173 -0
  48. package/src/__tests__/zip-archive.test.ts +106 -0
  49. package/src/app.tsx +15 -6
  50. package/src/client/components/__tests__/jant-settings-avatar.test.ts +1 -1
  51. package/src/client/components/__tests__/jant-settings-general.test.ts +21 -1
  52. package/src/client/components/jant-repo-picker-types.ts +6 -1
  53. package/src/client/components/jant-repo-picker.ts +4 -7
  54. package/src/client/components/jant-settings-general.ts +17 -12
  55. package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
  56. package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
  57. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
  58. package/src/client/tiptap/extensions.ts +0 -3
  59. package/src/client/tiptap/structural-keymap.ts +158 -47
  60. package/src/db/__tests__/d1-query.test.ts +56 -1
  61. package/src/i18n/locales/settings/en.po +8 -8
  62. package/src/i18n/locales/settings/en.ts +1 -1
  63. package/src/i18n/locales/settings/zh-Hans.po +8 -8
  64. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  65. package/src/i18n/locales/settings/zh-Hant.po +8 -8
  66. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  67. package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
  68. package/src/lib/__tests__/image.test.ts +27 -1
  69. package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
  70. package/src/lib/__tests__/markdown.test.ts +10 -0
  71. package/src/lib/__tests__/resolve-config.test.ts +67 -0
  72. package/src/lib/__tests__/schemas.test.ts +27 -1
  73. package/src/lib/__tests__/timeline.test.ts +87 -0
  74. package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
  75. package/src/lib/discover.ts +3 -1
  76. package/src/lib/github-sync-repo-name.ts +45 -0
  77. package/src/lib/hugo-markdown.ts +46 -0
  78. package/src/lib/image.ts +17 -4
  79. package/src/lib/markdown-manager.ts +392 -2
  80. package/src/lib/post-body-html.ts +11 -4
  81. package/src/lib/resolve-config.ts +58 -1
  82. package/src/lib/schemas.ts +50 -5
  83. package/src/lib/thread-fold.ts +3 -3
  84. package/src/lib/timeline.ts +1 -1
  85. package/src/lib/tiptap-to-markdown.ts +13 -7
  86. package/src/lib/url.ts +20 -0
  87. package/src/lib/view.ts +2 -2
  88. package/src/node/__tests__/cli-setup.test.ts +163 -0
  89. package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
  90. package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
  91. package/src/node/__tests__/runtime.test.ts +38 -0
  92. package/src/node/index.ts +2 -0
  93. package/src/node/request-handler.ts +3 -1
  94. package/src/routes/api/__tests__/posts.test.ts +24 -0
  95. package/src/routes/api/__tests__/upload.test.ts +34 -0
  96. package/src/routes/api/export.ts +3 -3
  97. package/src/routes/api/internal/sites.ts +0 -1
  98. package/src/routes/api/posts.ts +2 -0
  99. package/src/routes/api/public/posts.ts +21 -1
  100. package/src/routes/api/upload.ts +10 -3
  101. package/src/routes/compose.tsx +4 -0
  102. package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
  103. package/src/routes/dash/settings.tsx +212 -70
  104. package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
  105. package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
  106. package/src/routes/pages/archive.tsx +17 -11
  107. package/src/routes/pages/featured.tsx +11 -5
  108. package/src/routes/pages/page.tsx +53 -31
  109. package/src/routes/pages/search.tsx +4 -2
  110. package/src/runtime/__tests__/readiness.test.ts +23 -0
  111. package/src/runtime/node.ts +49 -0
  112. package/src/runtime/readiness.ts +15 -0
  113. package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
  114. package/src/services/__tests__/custom-url.test.ts +34 -0
  115. package/src/services/__tests__/github-app-installations.test.ts +153 -0
  116. package/src/services/__tests__/github-sync-push.test.ts +46 -0
  117. package/src/services/__tests__/media.test.ts +31 -0
  118. package/src/services/__tests__/path.test.ts +56 -0
  119. package/src/services/__tests__/post-timeline.test.ts +127 -0
  120. package/src/services/__tests__/post.test.ts +74 -0
  121. package/src/services/bootstrap.ts +263 -44
  122. package/src/services/custom-url.ts +2 -2
  123. package/src/services/export-theme/layouts/_default/alias.html +27 -1
  124. package/src/services/export-theme/layouts/_default/list.html +2 -63
  125. package/src/services/export-theme/layouts/_default/rss.xml +27 -38
  126. package/src/services/export-theme/layouts/collections/list.html +3 -3
  127. package/src/services/export-theme/layouts/featured/list.html +1 -4
  128. package/src/services/export-theme/layouts/index.html +40 -26
  129. package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
  130. package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
  131. package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
  132. package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
  133. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  134. package/src/services/export-theme/layouts/partials/head.html +1 -1
  135. package/src/services/export-theme/layouts/partials/header.html +5 -3
  136. package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
  137. package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
  138. package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
  139. package/src/services/export-theme/layouts/partials/thread-preview.html +1 -1
  140. package/src/services/export-theme/layouts/post/list.html +1 -1
  141. package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
  142. package/src/services/export-theme/styles/main.css +0 -1
  143. package/src/services/export-theme/theme.toml +1 -1
  144. package/src/services/export.ts +620 -71
  145. package/src/services/github-app-installations.ts +171 -4
  146. package/src/services/github-sync.ts +69 -15
  147. package/src/services/mcp.ts +19 -1
  148. package/src/services/media.ts +8 -1
  149. package/src/services/path.ts +34 -1
  150. package/src/services/post.ts +214 -39
  151. package/src/services/site-admin.ts +3 -7
  152. package/src/services/site.ts +83 -31
  153. package/src/styles/ui.css +7 -1
  154. package/src/types/app-context.ts +16 -0
  155. package/src/types/bindings.ts +5 -0
  156. package/src/types/operations.ts +10 -0
  157. package/src/ui/dash/settings/GeneralContent.tsx +10 -8
  158. package/src/client/tiptap/exitable-marks.ts +0 -73
@@ -0,0 +1,187 @@
1
+ /**
2
+ * ZIP archives that stream to and from disk.
3
+ *
4
+ * Site exports and snapshots carry a site's media, and a real site's media
5
+ * runs to gigabytes. Building the archive in memory ran into Node's 2 GiB
6
+ * ceiling on one write or read, held three times the media in RAM, and
7
+ * fflate's writer has no ZIP64, so an archive past 4 GiB could not be
8
+ * written at all. yazl writes ZIP64 on its own when an archive needs it, and
9
+ * yauzl reads one entry at a time.
10
+ */
11
+
12
+ import { createWriteStream } from "node:fs";
13
+ import { mkdir, readdir, rename, rm } from "node:fs/promises";
14
+ import { dirname, extname, join, relative, resolve, sep } from "node:path";
15
+ import { pipeline } from "node:stream/promises";
16
+ import yauzl from "yauzl";
17
+ import yazl from "yazl";
18
+
19
+ /**
20
+ * Extensions stored as-is: they are compressed formats already, and deflating
21
+ * a gigabyte of video costs minutes to save nothing.
22
+ */
23
+ const STORED_EXTENSIONS = new Set([
24
+ ".avif",
25
+ ".gif",
26
+ ".heic",
27
+ ".jpeg",
28
+ ".jpg",
29
+ ".m4a",
30
+ ".mov",
31
+ ".mp3",
32
+ ".mp4",
33
+ ".ogg",
34
+ ".opus",
35
+ ".pdf",
36
+ ".png",
37
+ ".webm",
38
+ ".webp",
39
+ ".woff",
40
+ ".woff2",
41
+ ".zip",
42
+ ]);
43
+
44
+ async function listFiles(rootDir) {
45
+ const files = [];
46
+ for (const entry of await readdir(rootDir, { withFileTypes: true })) {
47
+ const fullPath = join(rootDir, entry.name);
48
+ if (entry.isDirectory()) {
49
+ files.push(...(await listFiles(fullPath)));
50
+ } else if (entry.isFile()) {
51
+ files.push(fullPath);
52
+ }
53
+ }
54
+ return files;
55
+ }
56
+
57
+ /**
58
+ * Write every file under a directory to a ZIP file.
59
+ *
60
+ * The archive goes to a temporary name next to the output and is renamed into
61
+ * place once complete, so a failure never leaves a truncated or empty archive
62
+ * where the reader expects a finished one.
63
+ *
64
+ * @param {string} sourceDir - Directory to archive
65
+ * @param {string} zipPath - Output archive path
66
+ * @returns {Promise<{ files: number }>} How many files went in
67
+ * @example
68
+ * await writeDirectoryToZip("./jant-site", "./jant-site-export.zip");
69
+ */
70
+ export async function writeDirectoryToZip(sourceDir, zipPath) {
71
+ const files = (await listFiles(sourceDir)).sort();
72
+ const zipFile = new yazl.ZipFile();
73
+ // yazl reports a file it can't read on the ZipFile, not on its output
74
+ // stream, which would otherwise wait for the rest of the archive forever.
75
+ const failed = new Promise((_resolve, reject) => {
76
+ zipFile.on("error", (error) => {
77
+ // Stop the output without re-emitting the error on it: yazl feeds it
78
+ // through `.pipe()`, which forwards no errors, so the copy would go
79
+ // unhandled. `failed` carries the cause to the caller.
80
+ zipFile.outputStream.destroy();
81
+ reject(error);
82
+ });
83
+ });
84
+ // It can fail before it is awaited below; an unhandled rejection in the
85
+ // meantime would take the process down instead of reaching the caller.
86
+ failed.catch(() => {});
87
+
88
+ for (const fullPath of files) {
89
+ const entryName = relative(sourceDir, fullPath).split(sep).join("/");
90
+ zipFile.addFile(fullPath, entryName, {
91
+ compress: !STORED_EXTENSIONS.has(extname(entryName).toLowerCase()),
92
+ });
93
+ }
94
+ zipFile.end();
95
+
96
+ await mkdir(dirname(zipPath), { recursive: true });
97
+ const partialPath = `${zipPath}.partial`;
98
+ const written = pipeline(
99
+ zipFile.outputStream,
100
+ createWriteStream(partialPath),
101
+ );
102
+ try {
103
+ await Promise.race([written, failed]);
104
+ await rename(partialPath, zipPath);
105
+ } catch (error) {
106
+ await written.catch(() => {});
107
+ await rm(partialPath, { force: true });
108
+ throw error;
109
+ }
110
+
111
+ return { files: files.length };
112
+ }
113
+
114
+ function openZip(open) {
115
+ return new Promise((resolvePromise, reject) => {
116
+ open((error, zipFile) => (error ? reject(error) : resolvePromise(zipFile)));
117
+ });
118
+ }
119
+
120
+ async function extractOpenZip(zipFile, targetDir) {
121
+ const root = resolve(targetDir);
122
+ let files = 0;
123
+
124
+ await new Promise((resolvePromise, reject) => {
125
+ const fail = (error) => {
126
+ zipFile.close();
127
+ reject(error);
128
+ };
129
+
130
+ zipFile.on("error", fail);
131
+ zipFile.on("end", () => resolvePromise());
132
+ zipFile.on("entry", (entry) => {
133
+ // yauzl already refuses absolute and `..` names; this holds the line
134
+ // whatever the library's defaults become.
135
+ const outputPath = resolve(root, entry.fileName);
136
+ if (outputPath !== root && !outputPath.startsWith(`${root}${sep}`)) {
137
+ fail(
138
+ new Error(
139
+ `Refusing ZIP entry outside the archive: ${entry.fileName}`,
140
+ ),
141
+ );
142
+ return;
143
+ }
144
+
145
+ if (entry.fileName.endsWith("/")) {
146
+ mkdir(outputPath, { recursive: true }).then(
147
+ () => zipFile.readEntry(),
148
+ fail,
149
+ );
150
+ return;
151
+ }
152
+
153
+ zipFile.openReadStream(entry, (error, readStream) => {
154
+ if (error) {
155
+ fail(error);
156
+ return;
157
+ }
158
+ mkdir(dirname(outputPath), { recursive: true })
159
+ .then(() => pipeline(readStream, createWriteStream(outputPath)))
160
+ .then(() => {
161
+ files += 1;
162
+ zipFile.readEntry();
163
+ }, fail);
164
+ });
165
+ });
166
+ zipFile.readEntry();
167
+ });
168
+
169
+ return { files };
170
+ }
171
+
172
+ /**
173
+ * Extract a ZIP file into a directory, one entry at a time.
174
+ *
175
+ * @param {string} zipPath - Archive to read; ZIP64 is fine
176
+ * @param {string} targetDir - Directory to write into
177
+ * @returns {Promise<{ files: number }>} How many files came out
178
+ * @throws {Error} When an entry names a path outside `targetDir`
179
+ * @example
180
+ * await extractZipFile("./jant-site-export.zip", tempDir);
181
+ */
182
+ export async function extractZipFile(zipPath, targetDir) {
183
+ const zipFile = await openZip((callback) =>
184
+ yauzl.open(zipPath, { lazyEntries: true }, callback),
185
+ );
186
+ return extractOpenZip(zipFile, targetDir);
187
+ }