@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.
- package/bin/commands/export.js +3 -1
- package/bin/commands/import-site.js +689 -216
- package/bin/commands/setup.js +136 -0
- package/bin/commands/site/export.js +71 -34
- package/bin/commands/site/pull-media.js +2 -6
- package/bin/commands/site/snapshot/export.js +18 -60
- package/bin/commands/site/snapshot/import.js +22 -23
- package/bin/lib/d1-query.js +87 -2
- package/bin/lib/hugo-markdown.js +4 -0
- package/bin/lib/site-pull-media.js +21 -28
- package/bin/lib/site-selection.js +10 -1
- package/bin/lib/site-snapshot.js +338 -3
- package/bin/lib/sql-export.js +68 -5
- package/bin/lib/wrangler-cli.js +9 -0
- package/bin/lib/zip-archive.js +187 -0
- package/dist/{app-K_Aa1MMn.js → app-ZcaI1kPN.js} +732 -217
- package/dist/client/.vite/manifest.json +20 -20
- package/dist/client/_assets/chunks/{create-editor-CD3FhrOB.js → create-editor-B-m7X7S5.js} +50 -46
- package/dist/client/_assets/chunks/{sortable-list-CgaL2jCs.js → sortable-list-BJyd-LXE.js} +1 -1
- package/dist/client/_assets/chunks/{unsafe-svg-0QCkP0vZ.js → unsafe-svg-BkscJx69.js} +2 -2
- package/dist/client/_assets/{client-GiYENVw8.css → client-DAhoqPdr.css} +1 -1
- package/dist/client/_assets/{client-auth-k8gJ6QJj.js → client-auth-CAMfTrKW.js} +1 -1
- package/dist/client/_assets/{client-compose-B_kDtWkW.js → client-compose-D6rExKVK.js} +1 -1
- package/dist/client/_assets/{client-VnFxJN7G.js → client-knSEyUJO.js} +1 -1
- package/dist/client/_assets/{client-manage-M90aSTOk.js → client-manage-sVOkqVbH.js} +1 -1
- package/dist/client/_assets/{client-settings-DfYs9n1F.js → client-settings-CGR_nZ78.js} +7 -7
- package/dist/{github-sync-BPAvT999.js → github-sync-Gw4orAHk.js} +799 -110
- package/dist/index.js +2 -2
- package/dist/node.js +9 -5
- package/package.json +13 -3
- package/src/__tests__/dev-scripts.test.ts +203 -0
- package/src/__tests__/export-collection-order.test.ts +276 -0
- package/src/__tests__/export-feed-ids.test.ts +184 -0
- package/src/__tests__/export-feed-order.test.ts +294 -0
- package/src/__tests__/export-hugo-build.test.ts +130 -0
- package/src/__tests__/export-import-roundtrip.test.ts +94 -0
- package/src/__tests__/export-service.test.ts +611 -28
- package/src/__tests__/export-smart-collection.test.ts +329 -0
- package/src/__tests__/helpers/hugo-site.ts +146 -0
- package/src/__tests__/import-site-command.test.ts +487 -1
- package/src/__tests__/mise-config.test.ts +75 -4
- package/src/__tests__/node-dev-tasks.test.ts +264 -0
- package/src/__tests__/site-export-canonical-import.test.ts +149 -0
- package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
- package/src/__tests__/snapshot-settings.test.ts +97 -0
- package/src/__tests__/snapshot-tables.test.ts +219 -0
- package/src/__tests__/sql-export.test.ts +173 -0
- package/src/__tests__/zip-archive.test.ts +106 -0
- package/src/app.tsx +15 -6
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +1 -1
- package/src/client/components/__tests__/jant-settings-general.test.ts +21 -1
- package/src/client/components/jant-repo-picker-types.ts +6 -1
- package/src/client/components/jant-repo-picker.ts +4 -7
- package/src/client/components/jant-settings-general.ts +17 -12
- package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
- package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
- package/src/client/tiptap/extensions.ts +0 -3
- package/src/client/tiptap/structural-keymap.ts +158 -47
- package/src/db/__tests__/d1-query.test.ts +56 -1
- package/src/i18n/locales/settings/en.po +8 -8
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +8 -8
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +8 -8
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
- package/src/lib/__tests__/image.test.ts +27 -1
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
- package/src/lib/__tests__/markdown.test.ts +10 -0
- package/src/lib/__tests__/resolve-config.test.ts +67 -0
- package/src/lib/__tests__/schemas.test.ts +27 -1
- package/src/lib/__tests__/timeline.test.ts +87 -0
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
- package/src/lib/discover.ts +3 -1
- package/src/lib/github-sync-repo-name.ts +45 -0
- package/src/lib/hugo-markdown.ts +46 -0
- package/src/lib/image.ts +17 -4
- package/src/lib/markdown-manager.ts +392 -2
- package/src/lib/post-body-html.ts +11 -4
- package/src/lib/resolve-config.ts +58 -1
- package/src/lib/schemas.ts +50 -5
- package/src/lib/thread-fold.ts +3 -3
- package/src/lib/timeline.ts +1 -1
- package/src/lib/tiptap-to-markdown.ts +13 -7
- package/src/lib/url.ts +20 -0
- package/src/lib/view.ts +2 -2
- package/src/node/__tests__/cli-setup.test.ts +163 -0
- package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
- package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
- package/src/node/__tests__/runtime.test.ts +38 -0
- package/src/node/index.ts +2 -0
- package/src/node/request-handler.ts +3 -1
- package/src/routes/api/__tests__/posts.test.ts +24 -0
- package/src/routes/api/__tests__/upload.test.ts +34 -0
- package/src/routes/api/export.ts +3 -3
- package/src/routes/api/internal/sites.ts +0 -1
- package/src/routes/api/posts.ts +2 -0
- package/src/routes/api/public/posts.ts +21 -1
- package/src/routes/api/upload.ts +10 -3
- package/src/routes/compose.tsx +4 -0
- package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
- package/src/routes/dash/settings.tsx +212 -70
- package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
- package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
- package/src/routes/pages/archive.tsx +17 -11
- package/src/routes/pages/featured.tsx +11 -5
- package/src/routes/pages/page.tsx +53 -31
- package/src/routes/pages/search.tsx +4 -2
- package/src/runtime/__tests__/readiness.test.ts +23 -0
- package/src/runtime/node.ts +49 -0
- package/src/runtime/readiness.ts +15 -0
- package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
- package/src/services/__tests__/custom-url.test.ts +34 -0
- package/src/services/__tests__/github-app-installations.test.ts +153 -0
- package/src/services/__tests__/github-sync-push.test.ts +46 -0
- package/src/services/__tests__/media.test.ts +31 -0
- package/src/services/__tests__/path.test.ts +56 -0
- package/src/services/__tests__/post-timeline.test.ts +127 -0
- package/src/services/__tests__/post.test.ts +74 -0
- package/src/services/bootstrap.ts +263 -44
- package/src/services/custom-url.ts +2 -2
- package/src/services/export-theme/layouts/_default/alias.html +27 -1
- package/src/services/export-theme/layouts/_default/list.html +2 -63
- package/src/services/export-theme/layouts/_default/rss.xml +27 -38
- package/src/services/export-theme/layouts/collections/list.html +3 -3
- package/src/services/export-theme/layouts/featured/list.html +1 -4
- package/src/services/export-theme/layouts/index.html +40 -26
- package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
- package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
- package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
- package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/head.html +1 -1
- package/src/services/export-theme/layouts/partials/header.html +5 -3
- package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
- package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
- package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
- package/src/services/export-theme/layouts/partials/thread-preview.html +1 -1
- package/src/services/export-theme/layouts/post/list.html +1 -1
- package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
- package/src/services/export-theme/styles/main.css +0 -1
- package/src/services/export-theme/theme.toml +1 -1
- package/src/services/export.ts +620 -71
- package/src/services/github-app-installations.ts +171 -4
- package/src/services/github-sync.ts +69 -15
- package/src/services/mcp.ts +19 -1
- package/src/services/media.ts +8 -1
- package/src/services/path.ts +34 -1
- package/src/services/post.ts +214 -39
- package/src/services/site-admin.ts +3 -7
- package/src/services/site.ts +83 -31
- package/src/styles/ui.css +7 -1
- package/src/types/app-context.ts +16 -0
- package/src/types/bindings.ts +5 -0
- package/src/types/operations.ts +10 -0
- package/src/ui/dash/settings/GeneralContent.tsx +10 -8
- 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
|
+
}
|