gutterpress 0.9.0-alpha.1 → 0.9.0-alpha.2
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/dist/api/index.js +1 -1
- package/dist/{audit-k16v3n98.js → audit-nhn2pjz3.js} +3 -3
- package/dist/{build-8tqr4gbb.js → build-san7fv2z.js} +3 -3
- package/dist/{cli-rkqxbnkq.js → cli-e5zhb0xs.js} +1 -1
- package/dist/{cli-yp0d2hp8.js → cli-k4bd06sd.js} +377 -78
- package/dist/{cli-v2dze8ja.js → cli-najycadg.js} +1 -1
- package/dist/cli.js +15 -15
- package/dist/{doctor-57czxy3q.js → doctor-hrk0kxxz.js} +2 -2
- package/dist/{index-m3pa4wyf.js → index-bynn850m.js} +379 -80
- package/dist/index.js +1 -1
- package/dist/lib/markdown/assemble.d.ts +4 -6
- package/dist/lib/markdown/chapter-id.d.ts +1 -1
- package/dist/lib/markdown/index.d.ts +1 -1
- package/dist/{lint-4sq0dvf6.js → lint-96j9hrj4.js} +3 -3
- package/dist/{new-f2nmyr5b.js → new-8p38wavc.js} +3 -3
- package/dist/{pagedjs-interface-80g3xa8p.js → pagedjs-interface-qxvzgwd7.js} +92 -33
- package/dist/{plugin-xskq3dtx.js → plugin-ees6nhkc.js} +3 -3
- package/dist/{preflight-epcffags.js → preflight-1q6c2edh.js} +3 -3
- package/dist/preview/file-watcher.d.ts +39 -17
- package/dist/preview/http-server.d.ts +5 -2
- package/dist/preview/server-context.d.ts +2 -0
- package/dist/preview-shell-6dqexx1m.js +581 -0
- package/dist/{preview-xf5dktz2.js → preview-y5a2zen1.js} +3 -3
- package/dist/{publish-97vdwq31.js → publish-rm9yb3wh.js} +3 -3
- package/dist/render.js +10 -19
- package/dist/{repair-cz4z4hkg.js → repair-zgq7q2g6.js} +3 -3
- package/dist/server.d.ts +2 -0
- package/dist/{validate-swzejnd8.js → validate-nr0xa6sa.js} +3 -3
- package/package.json +1 -1
- package/dist/preview-shell-rghtfmc8.js +0 -155
|
@@ -1164,17 +1164,6 @@ async function assembleBookHtml(opts) {
|
|
|
1164
1164
|
throw new Error("No markdown files to render");
|
|
1165
1165
|
}
|
|
1166
1166
|
const md = createMarkdownRenderer(opts.plugins);
|
|
1167
|
-
let sourceChapter = null;
|
|
1168
|
-
if (opts.wrapChapters) {
|
|
1169
|
-
const renderToken = md.renderer.renderToken.bind(md.renderer);
|
|
1170
|
-
md.renderer.renderToken = (tokens, idx, options) => {
|
|
1171
|
-
const token = tokens[idx];
|
|
1172
|
-
if (sourceChapter !== null && token.level === 0 && token.map !== null && token.type.endsWith("_open")) {
|
|
1173
|
-
token.attrSet("data-chapter-src", sourceChapter);
|
|
1174
|
-
}
|
|
1175
|
-
return renderToken(tokens, idx, options);
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
1167
|
let bodyContent = "";
|
|
1179
1168
|
const imageRefs = new Set;
|
|
1180
1169
|
for (const file of files) {
|
|
@@ -1187,20 +1176,22 @@ async function assembleBookHtml(opts) {
|
|
|
1187
1176
|
throw new Error(`Failed to read file ${file}: ${errorMsg}`);
|
|
1188
1177
|
}
|
|
1189
1178
|
const env = {};
|
|
1190
|
-
|
|
1191
|
-
let rendered;
|
|
1192
|
-
try {
|
|
1193
|
-
rendered = md.render(content, env);
|
|
1194
|
-
} finally {
|
|
1195
|
-
sourceChapter = null;
|
|
1196
|
-
}
|
|
1179
|
+
const rendered = md.render(content, env);
|
|
1197
1180
|
if (env.layoutWarnings && env.layoutWarnings.length > 0) {
|
|
1198
1181
|
opts.onChapterWarnings?.(chapterId, env.layoutWarnings);
|
|
1199
1182
|
}
|
|
1200
1183
|
for (const ref of env.imageRefs ?? [])
|
|
1201
1184
|
imageRefs.add(ref);
|
|
1202
|
-
|
|
1185
|
+
if (opts.wrapChapters) {
|
|
1186
|
+
const safe = chapterId.replace(/&/g, "&").replace(/"/g, """);
|
|
1187
|
+
bodyContent += `<div class="gutterpress-chapter" data-chapter-src="${safe}">
|
|
1188
|
+
${rendered}
|
|
1189
|
+
</div>
|
|
1190
|
+
`;
|
|
1191
|
+
} else {
|
|
1192
|
+
bodyContent += rendered + `
|
|
1203
1193
|
`;
|
|
1194
|
+
}
|
|
1204
1195
|
}
|
|
1205
1196
|
if (opts.onImageRefs) {
|
|
1206
1197
|
for (const ref of collectHtmlImageRefs(bodyContent))
|
|
@@ -3387,7 +3378,7 @@ import git from "isomorphic-git";
|
|
|
3387
3378
|
// package.json
|
|
3388
3379
|
var package_default = {
|
|
3389
3380
|
name: "gutterpress",
|
|
3390
|
-
version: "0.9.0-alpha.
|
|
3381
|
+
version: "0.9.0-alpha.2",
|
|
3391
3382
|
description: "Markdown-to-PDF converter for professional print layout using Paged.js and Ghostscript.",
|
|
3392
3383
|
author: "itlackey",
|
|
3393
3384
|
license: "MPL-2.0",
|
|
@@ -3651,13 +3642,13 @@ var favicon_default = "./favicon-wkbm9cjn.ico";
|
|
|
3651
3642
|
var manifest_schema_default = "./manifest.schema-z61rzw44.json";
|
|
3652
3643
|
|
|
3653
3644
|
// src/assets/preview/scripts/pagedjs-interface.js
|
|
3654
|
-
var pagedjs_interface_default = "./pagedjs-interface-
|
|
3645
|
+
var pagedjs_interface_default = "./pagedjs-interface-qxvzgwd7.js";
|
|
3655
3646
|
|
|
3656
3647
|
// src/assets/preview/scripts/pagedjs-bridge.js
|
|
3657
3648
|
var pagedjs_bridge_default = "./pagedjs-bridge-vn4hk9fx.js";
|
|
3658
3649
|
|
|
3659
3650
|
// src/assets/preview/scripts/preview-shell.js
|
|
3660
|
-
var preview_shell_default = "./preview-shell-
|
|
3651
|
+
var preview_shell_default = "./preview-shell-6dqexx1m.js";
|
|
3661
3652
|
|
|
3662
3653
|
// src/assets/vendor/paged.polyfill.js
|
|
3663
3654
|
var paged_polyfill_default = "./paged.polyfill-n95pbxfn.js";
|
|
@@ -7668,12 +7659,17 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
7668
7659
|
}
|
|
7669
7660
|
});
|
|
7670
7661
|
}
|
|
7671
|
-
function injectPreviewScripts(html) {
|
|
7662
|
+
function injectPreviewScripts(html, pageIsolateChapters) {
|
|
7672
7663
|
const iface = `<script src="/preview/scripts/pagedjs-interface.js"></script>
|
|
7673
7664
|
` + `<script src="/preview/scripts/pagedjs-bridge.js"></script>
|
|
7674
7665
|
`;
|
|
7675
|
-
|
|
7666
|
+
let output = html.replace(pagedjsPolyfillTagRegex(), iface + BREAK_INSIDE_HANDLER + `
|
|
7676
7667
|
<script src="/vendor/paged.polyfill.js"></script>`);
|
|
7668
|
+
if (pageIsolateChapters && /<\/head>/i.test(output)) {
|
|
7669
|
+
output = output.replace(/<\/head>/i, `<style>.gutterpress-chapter{break-before:page}</style>
|
|
7670
|
+
</head>`);
|
|
7671
|
+
}
|
|
7672
|
+
return output;
|
|
7677
7673
|
}
|
|
7678
7674
|
async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
7679
7675
|
if (!inputPath) {
|
|
@@ -7682,9 +7678,10 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
|
7682
7678
|
return;
|
|
7683
7679
|
}
|
|
7684
7680
|
const nextAssets = new Map;
|
|
7681
|
+
const incremental = incrementalPreviewEnabled();
|
|
7685
7682
|
const html = await renderPreviewBook(inputPath, config, {
|
|
7686
7683
|
files: config.source?.files ?? null,
|
|
7687
|
-
wrapChapters:
|
|
7684
|
+
wrapChapters: incremental,
|
|
7688
7685
|
onCssAssets: (copies) => {
|
|
7689
7686
|
for (const copy of copies)
|
|
7690
7687
|
nextAssets.set(copy.to, copy.from);
|
|
@@ -7693,7 +7690,39 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
|
7693
7690
|
cssAssets.clear();
|
|
7694
7691
|
for (const [to, from] of nextAssets)
|
|
7695
7692
|
cssAssets.set(to, from);
|
|
7696
|
-
await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
|
|
7693
|
+
await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, incremental), "utf-8");
|
|
7694
|
+
}
|
|
7695
|
+
async function renderChapterPreviewHtml(inputPath, file, config) {
|
|
7696
|
+
const html = await renderPreviewBook(inputPath, config, {
|
|
7697
|
+
files: [canonicalChapterId(file)],
|
|
7698
|
+
wrapChapters: true
|
|
7699
|
+
});
|
|
7700
|
+
return injectPreviewScripts(html, true);
|
|
7701
|
+
}
|
|
7702
|
+
function describeChanges(changes, inputResolved) {
|
|
7703
|
+
const files = [];
|
|
7704
|
+
for (const [changedPath, event] of changes) {
|
|
7705
|
+
const relative2 = path10.relative(inputResolved, path10.resolve(changedPath));
|
|
7706
|
+
if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${path10.sep}`) || path10.isAbsolute(relative2))
|
|
7707
|
+
continue;
|
|
7708
|
+
files.push({
|
|
7709
|
+
relativePath: canonicalChapterId(relative2),
|
|
7710
|
+
ext: path10.extname(changedPath).toLowerCase(),
|
|
7711
|
+
event
|
|
7712
|
+
});
|
|
7713
|
+
}
|
|
7714
|
+
return files;
|
|
7715
|
+
}
|
|
7716
|
+
function decideBroadcast(files, changeCount, incremental) {
|
|
7717
|
+
const only = files.length === 1 ? files[0] : null;
|
|
7718
|
+
if (incremental && changeCount === 1 && only?.ext === ".md" && only.event !== "unlink" && only.event !== "unlinkDir") {
|
|
7719
|
+
return {
|
|
7720
|
+
kind: "chapter-splice",
|
|
7721
|
+
chapterId: canonicalChapterId(only.relativePath),
|
|
7722
|
+
relativePath: only.relativePath
|
|
7723
|
+
};
|
|
7724
|
+
}
|
|
7725
|
+
return { kind: "full-reload" };
|
|
7697
7726
|
}
|
|
7698
7727
|
function isDotPathUnderRoot(candidatePath, root) {
|
|
7699
7728
|
const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -7812,21 +7841,43 @@ function createFileWatcher(state) {
|
|
|
7812
7841
|
let closed = false;
|
|
7813
7842
|
const pendingChanges = new Map;
|
|
7814
7843
|
const suppressInitialExternalAdds = new Set;
|
|
7844
|
+
const settledWrites = new Map;
|
|
7845
|
+
let immediatePending = false;
|
|
7815
7846
|
function scheduleRebuild() {
|
|
7816
7847
|
if (closed)
|
|
7817
7848
|
return;
|
|
7818
7849
|
if (state.rebuildTimer)
|
|
7819
7850
|
clearTimeout(state.rebuildTimer);
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7851
|
+
const timer = setTimeout(() => {
|
|
7852
|
+
if (state.rebuildTimer === timer)
|
|
7853
|
+
state.rebuildTimer = null;
|
|
7854
|
+
runRebuild();
|
|
7855
|
+
}, DEBOUNCE.FILE_WATCH);
|
|
7856
|
+
state.rebuildTimer = timer;
|
|
7857
|
+
}
|
|
7858
|
+
async function recordChange(event, filePath2) {
|
|
7823
7859
|
if (closed)
|
|
7824
7860
|
return;
|
|
7825
|
-
|
|
7826
|
-
|
|
7861
|
+
const target = path10.resolve(filePath2);
|
|
7862
|
+
const settled = settledWrites.get(target);
|
|
7863
|
+
if (settled && event !== "unlink" && Date.now() <= settled.expiresAt) {
|
|
7864
|
+
const diskContent = await fsp4.readFile(target, "utf8").catch(() => null);
|
|
7865
|
+
if (closed)
|
|
7866
|
+
return;
|
|
7867
|
+
if (settledWrites.get(target) === settled && diskContent === settled.content) {
|
|
7868
|
+
debug(`Suppressed settled-write watcher echo: ${target}`);
|
|
7869
|
+
return;
|
|
7870
|
+
}
|
|
7871
|
+
}
|
|
7872
|
+
if (settledWrites.get(target) === settled)
|
|
7873
|
+
settledWrites.delete(target);
|
|
7874
|
+
debug(`File ${event}: ${target}`);
|
|
7875
|
+
pendingChanges.set(target, event);
|
|
7827
7876
|
scheduleRebuild();
|
|
7828
7877
|
}
|
|
7829
|
-
watcher.on("all",
|
|
7878
|
+
watcher.on("all", (event, filePath2) => {
|
|
7879
|
+
recordChange(event, filePath2);
|
|
7880
|
+
});
|
|
7830
7881
|
externalWatcher.on("all", (event, filePath2) => {
|
|
7831
7882
|
const target = path10.resolve(filePath2);
|
|
7832
7883
|
if (!desiredExternals.has(target))
|
|
@@ -7835,6 +7886,43 @@ function createFileWatcher(state) {
|
|
|
7835
7886
|
return;
|
|
7836
7887
|
recordChange(event, target);
|
|
7837
7888
|
});
|
|
7889
|
+
function isWatchedSource(target) {
|
|
7890
|
+
const rel = path10.relative(inputResolved, target);
|
|
7891
|
+
const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path10.sep}`) && !path10.isAbsolute(rel);
|
|
7892
|
+
if (inProject)
|
|
7893
|
+
return !isIgnoredWatchPath(target, inputResolved);
|
|
7894
|
+
return desiredExternals.has(target);
|
|
7895
|
+
}
|
|
7896
|
+
function acceptSettledWrite(target, writtenContent) {
|
|
7897
|
+
const settled = { content: writtenContent, expiresAt: Date.now() + 2000 };
|
|
7898
|
+
settledWrites.set(target, settled);
|
|
7899
|
+
const expiry = setTimeout(() => {
|
|
7900
|
+
if (settledWrites.get(target) === settled)
|
|
7901
|
+
settledWrites.delete(target);
|
|
7902
|
+
}, 2000);
|
|
7903
|
+
expiry.unref?.();
|
|
7904
|
+
pendingChanges.set(target, "change");
|
|
7905
|
+
immediatePending = true;
|
|
7906
|
+
if (state.rebuildTimer) {
|
|
7907
|
+
clearTimeout(state.rebuildTimer);
|
|
7908
|
+
state.rebuildTimer = null;
|
|
7909
|
+
}
|
|
7910
|
+
if (!state.isRebuilding)
|
|
7911
|
+
runRebuild();
|
|
7912
|
+
}
|
|
7913
|
+
state.notifySettledWrite = (filePath2, writtenContent) => {
|
|
7914
|
+
if (closed)
|
|
7915
|
+
return;
|
|
7916
|
+
const target = path10.resolve(filePath2);
|
|
7917
|
+
if (isWatchedSource(target)) {
|
|
7918
|
+
acceptSettledWrite(target, writtenContent);
|
|
7919
|
+
return;
|
|
7920
|
+
}
|
|
7921
|
+
syncQueue.then(() => {
|
|
7922
|
+
if (!closed && isWatchedSource(target))
|
|
7923
|
+
acceptSettledWrite(target, writtenContent);
|
|
7924
|
+
});
|
|
7925
|
+
};
|
|
7838
7926
|
let watchedExternalRoots = new Set;
|
|
7839
7927
|
let syncQueue = Promise.resolve();
|
|
7840
7928
|
function syncExternalWatches() {
|
|
@@ -7886,10 +7974,15 @@ function createFileWatcher(state) {
|
|
|
7886
7974
|
async function runRebuild() {
|
|
7887
7975
|
if (closed || state.isRebuilding)
|
|
7888
7976
|
return;
|
|
7977
|
+
if (state.rebuildTimer) {
|
|
7978
|
+
clearTimeout(state.rebuildTimer);
|
|
7979
|
+
state.rebuildTimer = null;
|
|
7980
|
+
}
|
|
7889
7981
|
const changes = [...pendingChanges.entries()];
|
|
7890
7982
|
pendingChanges.clear();
|
|
7891
7983
|
if (changes.length === 0)
|
|
7892
7984
|
return;
|
|
7985
|
+
immediatePending = false;
|
|
7893
7986
|
state.isRebuilding = true;
|
|
7894
7987
|
let resolveInFlight;
|
|
7895
7988
|
const inFlight = new Promise((resolve10) => {
|
|
@@ -7909,8 +8002,14 @@ function createFileWatcher(state) {
|
|
|
7909
8002
|
await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig, state.cssAssets);
|
|
7910
8003
|
if (closed)
|
|
7911
8004
|
return;
|
|
7912
|
-
|
|
7913
|
-
|
|
8005
|
+
const decision = decideBroadcast(describeChanges(changes, inputResolved), changes.length, incrementalPreviewEnabled());
|
|
8006
|
+
if (decision.kind === "chapter-splice") {
|
|
8007
|
+
state.previewServer?.broadcastContentUpdate(decision.chapterId);
|
|
8008
|
+
info(`Chapter updated: ${decision.relativePath}`);
|
|
8009
|
+
} else {
|
|
8010
|
+
state.previewServer?.broadcastReload();
|
|
8011
|
+
info(changes.length > 1 ? `Preview updated (${changes.length} files changed — full reload)` : "Preview updated");
|
|
8012
|
+
}
|
|
7914
8013
|
} catch (err) {
|
|
7915
8014
|
error("Failed to regenerate preview:", err);
|
|
7916
8015
|
} finally {
|
|
@@ -7918,13 +8017,19 @@ function createFileWatcher(state) {
|
|
|
7918
8017
|
resolveInFlight();
|
|
7919
8018
|
if (state.rebuildPromise === inFlight)
|
|
7920
8019
|
state.rebuildPromise = null;
|
|
7921
|
-
if (!closed && pendingChanges.size > 0)
|
|
7922
|
-
|
|
8020
|
+
if (!closed && pendingChanges.size > 0) {
|
|
8021
|
+
if (immediatePending)
|
|
8022
|
+
runRebuild();
|
|
8023
|
+
else
|
|
8024
|
+
scheduleRebuild();
|
|
8025
|
+
}
|
|
7923
8026
|
}
|
|
7924
8027
|
}
|
|
7925
8028
|
const closeBookWatcher = watcher.close.bind(watcher);
|
|
7926
8029
|
watcher.close = async () => {
|
|
7927
8030
|
closed = true;
|
|
8031
|
+
state.notifySettledWrite = null;
|
|
8032
|
+
settledWrites.clear();
|
|
7928
8033
|
await syncQueue.catch(() => {});
|
|
7929
8034
|
await state.rebuildPromise?.catch(() => {});
|
|
7930
8035
|
await externalWatcher.close();
|
|
@@ -8077,6 +8182,7 @@ function createServerState(inputPath, tempDir, config, options) {
|
|
|
8077
8182
|
rebuildTimer: null,
|
|
8078
8183
|
isRebuilding: false,
|
|
8079
8184
|
rebuildPromise: null,
|
|
8185
|
+
notifySettledWrite: null,
|
|
8080
8186
|
previewServer: null,
|
|
8081
8187
|
isShuttingDown: false,
|
|
8082
8188
|
tempDir,
|
|
@@ -8089,6 +8195,7 @@ function createServerState(inputPath, tempDir, config, options) {
|
|
|
8089
8195
|
// src/preview/http-server.ts
|
|
8090
8196
|
import http2 from "node:http";
|
|
8091
8197
|
import net from "node:net";
|
|
8198
|
+
import { randomUUID } from "node:crypto";
|
|
8092
8199
|
import { readFile as readFile21, stat as stat4 } from "node:fs/promises";
|
|
8093
8200
|
import path12 from "path";
|
|
8094
8201
|
import { WebSocket, WebSocketServer } from "ws";
|
|
@@ -8123,7 +8230,8 @@ function openPath(filePath2) {
|
|
|
8123
8230
|
|
|
8124
8231
|
// src/preview/http-server.ts
|
|
8125
8232
|
var HMR_PATH = "/__gutterpress-hmr";
|
|
8126
|
-
|
|
8233
|
+
function hmrClientSnippet(initialRevision, instanceId) {
|
|
8234
|
+
return `
|
|
8127
8235
|
<script>
|
|
8128
8236
|
(function () {
|
|
8129
8237
|
// When loaded by the preview SHELL (iframe double-buffer), the shell loads us
|
|
@@ -8133,6 +8241,10 @@ var HMR_CLIENT_SNIPPET = `
|
|
|
8133
8241
|
// for scroll-anchor and reload.
|
|
8134
8242
|
if (/[?&]gutterpressshell=1/.test(location.search)) return;
|
|
8135
8243
|
var ANCHOR_KEY = 'gutterpress-scroll-anchor';
|
|
8244
|
+
var appliedRevision = ${initialRevision};
|
|
8245
|
+
var appliedInstance = ${JSON.stringify(instanceId)};
|
|
8246
|
+
var readyToAcknowledge = false;
|
|
8247
|
+
var reloadRequested = false;
|
|
8136
8248
|
|
|
8137
8249
|
// Find the element nearest the top of the viewport that carries a source
|
|
8138
8250
|
// line (markdown-it-source-map emits data-source-line on block elements).
|
|
@@ -8183,7 +8295,13 @@ var HMR_CLIENT_SNIPPET = `
|
|
|
8183
8295
|
}
|
|
8184
8296
|
}
|
|
8185
8297
|
if (el) {
|
|
8186
|
-
window.scrollBy(
|
|
8298
|
+
window.scrollBy({
|
|
8299
|
+
top: el.getBoundingClientRect().top - a.offset,
|
|
8300
|
+
behavior: 'instant'
|
|
8301
|
+
});
|
|
8302
|
+
if (window.previewAPI && typeof window.previewAPI.refresh === 'function') {
|
|
8303
|
+
window.previewAPI.refresh();
|
|
8304
|
+
}
|
|
8187
8305
|
return;
|
|
8188
8306
|
}
|
|
8189
8307
|
if (tries++ < 240) setTimeout(attempt, 25); // wait for pagination
|
|
@@ -8191,44 +8309,100 @@ var HMR_CLIENT_SNIPPET = `
|
|
|
8191
8309
|
}
|
|
8192
8310
|
var restored = false;
|
|
8193
8311
|
function restoreOnce() { if (restored) return; restored = true; restoreAnchor(); }
|
|
8312
|
+
function finishInitialRender() {
|
|
8313
|
+
restoreOnce();
|
|
8314
|
+
readyToAcknowledge = true;
|
|
8315
|
+
acknowledge();
|
|
8316
|
+
}
|
|
8194
8317
|
// CRITICAL ordering: when the Paged.js polyfill is present it re-paginates on
|
|
8195
|
-
// load and its PagedConfig.after
|
|
8318
|
+
// load and its PagedConfig.after scrolls to the document start THEN fires
|
|
8196
8319
|
// 'renderingComplete'. So in engine mode we MUST wait for that event —
|
|
8197
|
-
// restoring earlier would be wiped by
|
|
8320
|
+
// restoring earlier would be wiped by that initial scroll. In static mode (no
|
|
8198
8321
|
// engine) the content is final immediately, so restore right after load.
|
|
8199
|
-
var hasEngine = !!document.querySelector('script[src*="paged.polyfill"]
|
|
8200
|
-
window.addEventListener('renderingComplete',
|
|
8322
|
+
var hasEngine = !!document.querySelector('script[src*="paged.polyfill"]');
|
|
8323
|
+
window.addEventListener('renderingComplete', finishInitialRender);
|
|
8201
8324
|
if (!hasEngine) {
|
|
8202
8325
|
if (document.readyState === 'loading') {
|
|
8203
|
-
document.addEventListener('DOMContentLoaded', function () { setTimeout(
|
|
8326
|
+
document.addEventListener('DOMContentLoaded', function () { setTimeout(finishInitialRender, 50); });
|
|
8204
8327
|
} else {
|
|
8205
|
-
setTimeout(
|
|
8328
|
+
setTimeout(finishInitialRender, 50);
|
|
8206
8329
|
}
|
|
8207
8330
|
}
|
|
8208
|
-
|
|
8331
|
+
var ws = null;
|
|
8332
|
+
var reconnectTimer = null;
|
|
8333
|
+
var reconnectDelay = 250;
|
|
8334
|
+
var stopped = false;
|
|
8209
8335
|
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8336
|
+
function acknowledge() {
|
|
8337
|
+
if (!readyToAcknowledge || !ws || ws.readyState !== 1) return;
|
|
8338
|
+
try {
|
|
8339
|
+
ws.send(JSON.stringify({
|
|
8340
|
+
type: 'reload-applied',
|
|
8341
|
+
instance: appliedInstance,
|
|
8342
|
+
revision: appliedRevision
|
|
8343
|
+
}));
|
|
8344
|
+
} catch (_) {}
|
|
8345
|
+
}
|
|
8346
|
+
|
|
8347
|
+
function connect() {
|
|
8348
|
+
if (stopped) return;
|
|
8349
|
+
ws = new WebSocket(location.origin.replace(/^http/, 'ws') + '${HMR_PATH}');
|
|
8350
|
+
ws.onopen = function () {
|
|
8351
|
+
reconnectDelay = 250;
|
|
8352
|
+
acknowledge();
|
|
8353
|
+
};
|
|
8354
|
+
ws.onmessage = function (e) {
|
|
8355
|
+
var msg;
|
|
8356
|
+
try { msg = JSON.parse(e.data); } catch (_) { return; }
|
|
8357
|
+
if (
|
|
8358
|
+
msg.type !== 'reload-state' &&
|
|
8359
|
+
msg.type !== 'full-reload' &&
|
|
8360
|
+
msg.type !== 'content-update'
|
|
8361
|
+
) return;
|
|
8362
|
+
var instance = typeof msg.instance === 'string' ? msg.instance : null;
|
|
8363
|
+
var revision = Number(msg.revision);
|
|
8364
|
+
if (!instance || !Number.isSafeInteger(revision) || revision < 0) return;
|
|
8365
|
+
if (instance === appliedInstance && revision <= appliedRevision) {
|
|
8366
|
+
acknowledge();
|
|
8367
|
+
return;
|
|
8368
|
+
}
|
|
8369
|
+
if (reloadRequested) return;
|
|
8370
|
+
reloadRequested = true;
|
|
8215
8371
|
try { var a = captureAnchor(); if (a) sessionStorage.setItem(ANCHOR_KEY, JSON.stringify(a)); } catch (_) {}
|
|
8216
8372
|
location.reload();
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8373
|
+
};
|
|
8374
|
+
ws.onclose = function () {
|
|
8375
|
+
ws = null;
|
|
8376
|
+
if (stopped || reconnectTimer !== null) return;
|
|
8377
|
+
reconnectTimer = setTimeout(function () {
|
|
8378
|
+
reconnectTimer = null;
|
|
8379
|
+
connect();
|
|
8380
|
+
}, reconnectDelay);
|
|
8381
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 5000);
|
|
8382
|
+
};
|
|
8383
|
+
ws.onerror = function () { try { ws.close(); } catch (_) {} };
|
|
8384
|
+
}
|
|
8385
|
+
|
|
8386
|
+
window.addEventListener('beforeunload', function () {
|
|
8387
|
+
stopped = true;
|
|
8388
|
+
if (reconnectTimer !== null) clearTimeout(reconnectTimer);
|
|
8389
|
+
});
|
|
8390
|
+
connect();
|
|
8220
8391
|
})();
|
|
8221
8392
|
</script>
|
|
8222
8393
|
`;
|
|
8223
|
-
|
|
8394
|
+
}
|
|
8395
|
+
function shellHtml(initialRevision, instanceId) {
|
|
8396
|
+
return `<!doctype html>
|
|
8224
8397
|
<html lang="en"><head><meta charset="utf-8"><title>gutterpress preview</title>
|
|
8225
8398
|
<style>html,body{margin:0;height:100%;background:#fff;overflow:hidden}
|
|
8226
8399
|
iframe{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}</style>
|
|
8227
8400
|
</head><body>
|
|
8228
8401
|
<iframe id="gutterpress-active" src="/book.html?gutterpressshell=1" title="preview"></iframe>
|
|
8229
|
-
<script>window.__GUTTERPRESS_HMR=${JSON.stringify(HMR_PATH)};</script>
|
|
8402
|
+
<script>window.__GUTTERPRESS_HMR=${JSON.stringify(HMR_PATH)};window.__GUTTERPRESS_INSTANCE=${JSON.stringify(instanceId)};window.__GUTTERPRESS_REVISION=${initialRevision};</script>
|
|
8230
8403
|
<script src="/preview/scripts/preview-shell.js"></script>
|
|
8231
8404
|
</body></html>`;
|
|
8405
|
+
}
|
|
8232
8406
|
function probePort(port, host) {
|
|
8233
8407
|
return new Promise((resolve10) => {
|
|
8234
8408
|
const srv = net.createServer();
|
|
@@ -8270,13 +8444,14 @@ async function findAvailablePort(startPort, host = "127.0.0.1") {
|
|
|
8270
8444
|
}
|
|
8271
8445
|
throw new BuildError(`Could not find an available port on ${host} from ${firstPort} to ${port - 1}; all are already in use. ` + "Stop another preview server or pass --port with an available port.", undefined, { cause: lastBusyError });
|
|
8272
8446
|
}
|
|
8273
|
-
function injectHmrClient(html) {
|
|
8447
|
+
function injectHmrClient(html, revision, instanceId) {
|
|
8448
|
+
const snippet = hmrClientSnippet(revision, instanceId);
|
|
8274
8449
|
const closingBody = html.lastIndexOf("</body>");
|
|
8275
8450
|
if (closingBody === -1)
|
|
8276
|
-
return html +
|
|
8277
|
-
return html.slice(0, closingBody) +
|
|
8451
|
+
return html + snippet;
|
|
8452
|
+
return html.slice(0, closingBody) + snippet + html.slice(closingBody);
|
|
8278
8453
|
}
|
|
8279
|
-
async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders = {}) {
|
|
8454
|
+
async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders = {}, hmrRevision = 0, hmrInstance = "") {
|
|
8280
8455
|
let filePath2 = absPath;
|
|
8281
8456
|
try {
|
|
8282
8457
|
const s = await stat4(filePath2);
|
|
@@ -8297,7 +8472,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
8297
8472
|
}
|
|
8298
8473
|
const isHtml = filePath2.endsWith(".html") || filePath2.endsWith(".htm");
|
|
8299
8474
|
if (isHtml) {
|
|
8300
|
-
const withHmr = injectHmrClient(data.toString("utf-8"));
|
|
8475
|
+
const withHmr = injectHmrClient(data.toString("utf-8"), hmrRevision, hmrInstance);
|
|
8301
8476
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
8302
8477
|
res.end(withHmr);
|
|
8303
8478
|
return;
|
|
@@ -8316,11 +8491,88 @@ function matchesEmbedded(urlPathname) {
|
|
|
8316
8491
|
return EMBEDDED_PREFIXES.some((p) => urlPathname.startsWith(p));
|
|
8317
8492
|
}
|
|
8318
8493
|
async function createPreviewServer(state, port) {
|
|
8319
|
-
const
|
|
8494
|
+
const ACK_RETRY_MS = 2000;
|
|
8495
|
+
const MAX_ACK_RETRIES = 95;
|
|
8496
|
+
const instanceId = randomUUID();
|
|
8497
|
+
let reloadRevision = 0;
|
|
8498
|
+
const clients = new Map;
|
|
8499
|
+
function sendRevision(ws, type, file) {
|
|
8500
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
8501
|
+
return;
|
|
8502
|
+
try {
|
|
8503
|
+
ws.send(JSON.stringify({
|
|
8504
|
+
type,
|
|
8505
|
+
instance: instanceId,
|
|
8506
|
+
revision: reloadRevision,
|
|
8507
|
+
...file ? { file } : {}
|
|
8508
|
+
}));
|
|
8509
|
+
} catch {}
|
|
8510
|
+
}
|
|
8511
|
+
function waitForAcknowledgement(ws) {
|
|
8512
|
+
const client = clients.get(ws);
|
|
8513
|
+
if (!client)
|
|
8514
|
+
return;
|
|
8515
|
+
if (client.acknowledgedRevision >= reloadRevision) {
|
|
8516
|
+
if (client.retryTimer !== null)
|
|
8517
|
+
clearTimeout(client.retryTimer);
|
|
8518
|
+
client.retryTimer = null;
|
|
8519
|
+
client.retryAttempts = 0;
|
|
8520
|
+
return;
|
|
8521
|
+
}
|
|
8522
|
+
if (client.retryTimer !== null)
|
|
8523
|
+
return;
|
|
8524
|
+
client.retryTimer = setTimeout(() => {
|
|
8525
|
+
const current = clients.get(ws);
|
|
8526
|
+
if (!current)
|
|
8527
|
+
return;
|
|
8528
|
+
current.retryTimer = null;
|
|
8529
|
+
if (current.retryAttempts >= MAX_ACK_RETRIES) {
|
|
8530
|
+
ws.terminate();
|
|
8531
|
+
return;
|
|
8532
|
+
}
|
|
8533
|
+
current.retryAttempts++;
|
|
8534
|
+
sendRevision(ws, "reload-state");
|
|
8535
|
+
waitForAcknowledgement(ws);
|
|
8536
|
+
}, ACK_RETRY_MS);
|
|
8537
|
+
client.retryTimer.unref?.();
|
|
8538
|
+
}
|
|
8539
|
+
function removeClient(ws) {
|
|
8540
|
+
const client = clients.get(ws);
|
|
8541
|
+
if (client && client.retryTimer !== null)
|
|
8542
|
+
clearTimeout(client.retryTimer);
|
|
8543
|
+
clients.delete(ws);
|
|
8544
|
+
}
|
|
8320
8545
|
const wss = new WebSocketServer({ noServer: true });
|
|
8321
8546
|
wss.on("connection", (ws) => {
|
|
8322
|
-
clients.
|
|
8323
|
-
ws.on("
|
|
8547
|
+
clients.set(ws, { acknowledgedRevision: -1, retryAttempts: 0, retryTimer: null });
|
|
8548
|
+
ws.on("message", (raw) => {
|
|
8549
|
+
let message;
|
|
8550
|
+
try {
|
|
8551
|
+
message = JSON.parse(raw.toString());
|
|
8552
|
+
} catch {
|
|
8553
|
+
return;
|
|
8554
|
+
}
|
|
8555
|
+
if (!message || typeof message !== "object")
|
|
8556
|
+
return;
|
|
8557
|
+
const payload = message;
|
|
8558
|
+
if (payload.type !== "reload-applied")
|
|
8559
|
+
return;
|
|
8560
|
+
const revision = Number(payload.revision);
|
|
8561
|
+
if (payload.instance !== instanceId || !Number.isSafeInteger(revision) || revision < 0 || revision > reloadRevision)
|
|
8562
|
+
return;
|
|
8563
|
+
const client = clients.get(ws);
|
|
8564
|
+
if (!client)
|
|
8565
|
+
return;
|
|
8566
|
+
client.acknowledgedRevision = Math.max(client.acknowledgedRevision, revision);
|
|
8567
|
+
waitForAcknowledgement(ws);
|
|
8568
|
+
});
|
|
8569
|
+
ws.on("close", () => removeClient(ws));
|
|
8570
|
+
ws.on("error", () => {
|
|
8571
|
+
removeClient(ws);
|
|
8572
|
+
ws.terminate();
|
|
8573
|
+
});
|
|
8574
|
+
sendRevision(ws, "reload-state");
|
|
8575
|
+
waitForAcknowledgement(ws);
|
|
8324
8576
|
});
|
|
8325
8577
|
const server = http2.createServer(async (req, res) => {
|
|
8326
8578
|
const url = new URL(req.url, "http://127.0.0.1");
|
|
@@ -8343,6 +8595,34 @@ async function createPreviewServer(state, port) {
|
|
|
8343
8595
|
res.end("Not Found");
|
|
8344
8596
|
return;
|
|
8345
8597
|
}
|
|
8598
|
+
if (url.pathname === "/__chapter" && req.method === "GET") {
|
|
8599
|
+
const rawFile = url.searchParams.get("file");
|
|
8600
|
+
const file = rawFile ? canonicalChapterId(rawFile) : "";
|
|
8601
|
+
const revision = Number(url.searchParams.get("revision"));
|
|
8602
|
+
const configuredFiles = state.config.source?.files;
|
|
8603
|
+
if (!file || !state.currentInputPath || !incrementalPreviewEnabled() || !Number.isSafeInteger(revision) || revision !== reloadRevision || path12.extname(file).toLowerCase() !== ".md" || hasDotSegment(file) || !resolveWithinRoot(file, state.currentInputPath) || Array.isArray(configuredFiles) && configuredFiles.length > 0 && !configuredFiles.some((configured) => canonicalChapterId(configured) === file)) {
|
|
8604
|
+
res.writeHead(400);
|
|
8605
|
+
res.end("Bad Request");
|
|
8606
|
+
return;
|
|
8607
|
+
}
|
|
8608
|
+
try {
|
|
8609
|
+
const html = await renderChapterPreviewHtml(state.currentInputPath, file, state.config);
|
|
8610
|
+
if (revision !== reloadRevision) {
|
|
8611
|
+
res.writeHead(409);
|
|
8612
|
+
res.end("Superseded");
|
|
8613
|
+
return;
|
|
8614
|
+
}
|
|
8615
|
+
res.writeHead(200, {
|
|
8616
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
8617
|
+
"Cache-Control": "no-store"
|
|
8618
|
+
});
|
|
8619
|
+
res.end(html);
|
|
8620
|
+
} catch (error2) {
|
|
8621
|
+
res.writeHead(500);
|
|
8622
|
+
res.end(error2 instanceof Error ? error2.message : String(error2));
|
|
8623
|
+
}
|
|
8624
|
+
return;
|
|
8625
|
+
}
|
|
8346
8626
|
if (matchesEmbedded(url.pathname)) {
|
|
8347
8627
|
if (req.headers["if-none-match"] === EMBEDDED_ETAG) {
|
|
8348
8628
|
res.writeHead(304, { ETag: EMBEDDED_ETAG, "Cache-Control": EMBEDDED_CACHE_CONTROL });
|
|
@@ -8361,7 +8641,7 @@ async function createPreviewServer(state, port) {
|
|
|
8361
8641
|
}
|
|
8362
8642
|
if (url.pathname === "/" && incrementalPreviewEnabled()) {
|
|
8363
8643
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
8364
|
-
res.end(
|
|
8644
|
+
res.end(shellHtml(reloadRevision, instanceId));
|
|
8365
8645
|
return;
|
|
8366
8646
|
}
|
|
8367
8647
|
const pathname = url.pathname === "/" ? "/book.html" : url.pathname;
|
|
@@ -8388,7 +8668,7 @@ async function createPreviewServer(state, port) {
|
|
|
8388
8668
|
res.end("Not Found");
|
|
8389
8669
|
return;
|
|
8390
8670
|
}
|
|
8391
|
-
await serveStatic(absPath, res).catch((err) => {
|
|
8671
|
+
await serveStatic(absPath, res, "no-store", {}, reloadRevision, instanceId).catch((err) => {
|
|
8392
8672
|
if (!res.headersSent) {
|
|
8393
8673
|
res.writeHead(500);
|
|
8394
8674
|
res.end(`Internal Server Error: ${err.message}`);
|
|
@@ -8415,14 +8695,21 @@ async function createPreviewServer(state, port) {
|
|
|
8415
8695
|
openPath(serverUrl).catch(() => {});
|
|
8416
8696
|
}
|
|
8417
8697
|
let stopped = false;
|
|
8418
|
-
const
|
|
8698
|
+
const broadcastUpdate = (type, file) => {
|
|
8419
8699
|
if (stopped)
|
|
8420
8700
|
return;
|
|
8421
|
-
|
|
8422
|
-
for (const ws of clients) {
|
|
8423
|
-
|
|
8424
|
-
|
|
8701
|
+
reloadRevision++;
|
|
8702
|
+
for (const [ws] of clients) {
|
|
8703
|
+
sendRevision(ws, type, file);
|
|
8704
|
+
waitForAcknowledgement(ws);
|
|
8705
|
+
}
|
|
8706
|
+
};
|
|
8707
|
+
const updateInFlight = () => {
|
|
8708
|
+
for (const client of clients.values()) {
|
|
8709
|
+
if (client.acknowledgedRevision < reloadRevision)
|
|
8710
|
+
return true;
|
|
8425
8711
|
}
|
|
8712
|
+
return false;
|
|
8426
8713
|
};
|
|
8427
8714
|
return {
|
|
8428
8715
|
port: boundPort,
|
|
@@ -8430,8 +8717,11 @@ async function createPreviewServer(state, port) {
|
|
|
8430
8717
|
if (stopped)
|
|
8431
8718
|
return;
|
|
8432
8719
|
stopped = true;
|
|
8433
|
-
for (const ws of clients)
|
|
8720
|
+
for (const [ws, client] of clients) {
|
|
8721
|
+
if (client.retryTimer !== null)
|
|
8722
|
+
clearTimeout(client.retryTimer);
|
|
8434
8723
|
ws.terminate();
|
|
8724
|
+
}
|
|
8435
8725
|
clients.clear();
|
|
8436
8726
|
wss.close();
|
|
8437
8727
|
await new Promise((resolve10) => {
|
|
@@ -8442,7 +8732,13 @@ async function createPreviewServer(state, port) {
|
|
|
8442
8732
|
});
|
|
8443
8733
|
},
|
|
8444
8734
|
broadcastReload() {
|
|
8445
|
-
|
|
8735
|
+
broadcastUpdate("full-reload");
|
|
8736
|
+
},
|
|
8737
|
+
broadcastContentUpdate(file) {
|
|
8738
|
+
if (updateInFlight())
|
|
8739
|
+
broadcastUpdate("full-reload");
|
|
8740
|
+
else
|
|
8741
|
+
broadcastUpdate("content-update", canonicalChapterId(file));
|
|
8446
8742
|
}
|
|
8447
8743
|
};
|
|
8448
8744
|
}
|
|
@@ -8495,7 +8791,10 @@ async function startPreviewServer(options) {
|
|
|
8495
8791
|
}
|
|
8496
8792
|
await shutdownServer(state);
|
|
8497
8793
|
},
|
|
8498
|
-
restart: restartPreview2
|
|
8794
|
+
restart: restartPreview2,
|
|
8795
|
+
notifySettledWrite: (filePath2, writtenContent) => {
|
|
8796
|
+
state.notifySettledWrite?.(filePath2, writtenContent);
|
|
8797
|
+
}
|
|
8499
8798
|
};
|
|
8500
8799
|
}
|
|
8501
8800
|
// src/lib/diagnostics.ts
|
|
@@ -8762,7 +9061,7 @@ import path15 from "node:path";
|
|
|
8762
9061
|
|
|
8763
9062
|
// src/lib/manifest-doc.ts
|
|
8764
9063
|
import { existsSync as existsSync12 } from "node:fs";
|
|
8765
|
-
import { randomUUID } from "node:crypto";
|
|
9064
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8766
9065
|
import { mkdir as mkdir7, open, readFile as readFile23, rename as rename3, rm as rm2 } from "node:fs/promises";
|
|
8767
9066
|
import path14 from "node:path";
|
|
8768
9067
|
import { parseDocument, isSeq, YAMLSeq } from "yaml";
|
|
@@ -8784,7 +9083,7 @@ async function loadManifestDoc(projectDir) {
|
|
|
8784
9083
|
async function writeManifestDoc(file, doc) {
|
|
8785
9084
|
const dir = path14.dirname(file);
|
|
8786
9085
|
await mkdir7(dir, { recursive: true });
|
|
8787
|
-
const temporary = path14.join(dir, `.${path14.basename(file)}.tmp-${process.pid}-${
|
|
9086
|
+
const temporary = path14.join(dir, `.${path14.basename(file)}.tmp-${process.pid}-${randomUUID2()}`);
|
|
8788
9087
|
try {
|
|
8789
9088
|
const handle = await open(temporary, "wx", 438);
|
|
8790
9089
|
try {
|
|
@@ -9439,13 +9738,13 @@ async function deleteSnippet(projectDir, fileName) {
|
|
|
9439
9738
|
await rm3(resolveSnippetPath(projectDir, fileName), { force: true });
|
|
9440
9739
|
}
|
|
9441
9740
|
// src/lib/plugin-manager.ts
|
|
9442
|
-
import { randomUUID as
|
|
9741
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
9443
9742
|
import { cp as cp4, lstat as lstat3, mkdir as mkdir12, rename as rename5, rm as rm5, stat as stat7 } from "node:fs/promises";
|
|
9444
9743
|
import path19 from "node:path";
|
|
9445
9744
|
import { isSeq as isSeq3, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar } from "yaml";
|
|
9446
9745
|
|
|
9447
9746
|
// src/lib/npm-plugin-installer.ts
|
|
9448
|
-
import { createHash as createHash4, randomUUID as
|
|
9747
|
+
import { createHash as createHash4, randomUUID as randomUUID3, timingSafeEqual } from "node:crypto";
|
|
9449
9748
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
9450
9749
|
import {
|
|
9451
9750
|
lstat as lstat2,
|
|
@@ -10121,7 +10420,7 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
|
10121
10420
|
}
|
|
10122
10421
|
try {
|
|
10123
10422
|
await lstat2(finalRoot);
|
|
10124
|
-
backupRoot = `${finalRoot}.backup-${
|
|
10423
|
+
backupRoot = `${finalRoot}.backup-${randomUUID3()}`;
|
|
10125
10424
|
await rename4(finalRoot, backupRoot);
|
|
10126
10425
|
} catch (error2) {
|
|
10127
10426
|
if (error2.code !== "ENOENT")
|
|
@@ -10400,8 +10699,8 @@ async function addLocalPluginUnlocked(projectDir, sourcePath) {
|
|
|
10400
10699
|
const destDir = path19.join(projectDir, PLUGINS_DIR);
|
|
10401
10700
|
await mkdir12(destDir, { recursive: true });
|
|
10402
10701
|
const dest = path19.join(destDir, base);
|
|
10403
|
-
const staging = path19.join(destDir, `.import-${
|
|
10404
|
-
const backup = path19.join(destDir, `.backup-${
|
|
10702
|
+
const staging = path19.join(destDir, `.import-${randomUUID4()}`);
|
|
10703
|
+
const backup = path19.join(destDir, `.backup-${randomUUID4()}`);
|
|
10405
10704
|
let hasBackup = false;
|
|
10406
10705
|
try {
|
|
10407
10706
|
if (info2.isDirectory()) {
|