gutterpress 0.0.0 → 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/README.md +21 -16
- package/dist/api/index.d.ts +6 -2
- package/dist/api/index.js +15 -3
- package/dist/{audit-1jrwwacz.js → audit-nhn2pjz3.js} +5 -5
- package/dist/{build-5s92kgpc.js → build-san7fv2z.js} +5 -5
- package/dist/checks/types.d.ts +6 -0
- package/dist/{cli-hvqjs342.js → cli-e5zhb0xs.js} +2 -2
- package/dist/{cli-7ykx7pp0.js → cli-k4bd06sd.js} +982 -331
- package/dist/{cli-h7phg3ka.js → cli-najycadg.js} +258 -258
- package/dist/{index-1tyj4bk3.js → cli-wchtvxvw.js} +4 -1
- package/dist/{cli-5bxx0c4y.js → cli-yzrh708h.js} +2 -2
- package/dist/cli.js +15 -15
- package/dist/{doctor-25zjt8dz.js → doctor-hrk0kxxz.js} +2 -2
- package/dist/{index-3h3fef7r.js → index-bynn850m.js} +1200 -410
- package/dist/{cli-1tyj4bk3.js → index-wchtvxvw.js} +4 -1
- package/dist/{index-5bxx0c4y.js → index-yzrh708h.js} +2 -2
- package/dist/index.js +15 -3
- package/dist/lib/asset-inline.d.ts +19 -0
- package/dist/lib/build-error.d.ts +0 -1
- package/dist/lib/build-fingerprint.d.ts +14 -0
- package/dist/lib/build-runner.d.ts +21 -1
- package/dist/lib/ghostscript.d.ts +0 -9
- package/dist/lib/manifest-config.d.ts +7 -0
- package/dist/lib/manifest.d.ts +8 -0
- 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 -2
- package/dist/lib/npm-plugin-installer.d.ts +1 -1
- package/dist/lib/output-paths.d.ts +0 -1
- package/dist/lib/pagination.d.ts +0 -20
- package/dist/lib/pdf-inspect.d.ts +2 -2
- package/dist/lib/pdf-parse.d.ts +2 -1
- package/dist/lib/plugin-manager.d.ts +0 -2
- package/dist/lib/presets.d.ts +36 -7
- package/dist/lib/project-scaffold.d.ts +39 -2
- package/dist/lib/project-source.d.ts +11 -9
- package/dist/lib/project-templates.d.ts +45 -7
- package/dist/lib/publish/providers/azure-swa.d.ts +0 -1
- package/dist/lib/publish/providers/itch.d.ts +0 -1
- package/dist/lib/publish/providers/shopify.d.ts +0 -1
- package/dist/lib/remote-auth/github-repos.d.ts +0 -2
- package/dist/lib/remote-auth/recovery/dispatch.d.ts +0 -5
- package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +3 -2
- package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +0 -8
- package/dist/lib/remote-auth/sync.d.ts +2 -10
- package/dist/lib/remote-auth/test-access.d.ts +0 -2
- package/dist/lib/remote-auth/test-support/git-http-server.d.ts +0 -37
- package/dist/lib/source-provider.d.ts +0 -14
- package/dist/lib/static-serve.d.ts +39 -0
- package/dist/lib/style-resolver.d.ts +10 -1
- package/dist/lib/targets.d.ts +84 -0
- package/dist/lib/theme-import.d.ts +0 -2
- package/dist/lib/theme-manager.d.ts +1 -1
- package/dist/lib/validation-exec.d.ts +4 -3
- package/dist/{lint-f63nxjfj.js → lint-96j9hrj4.js} +5 -5
- package/dist/{manifest.schema-rn1dpdpn.json → manifest.schema-z61rzw44.json} +12 -6
- package/dist/new-8p38wavc.js +185 -0
- package/dist/{pagedjs-interface-80g3xa8p.js → pagedjs-interface-qxvzgwd7.js} +92 -33
- package/dist/{plugin-wf2cbmwc.js → plugin-ees6nhkc.js} +5 -5
- package/dist/{preflight-tg4mpz1h.js → preflight-1q6c2edh.js} +24 -29
- package/dist/preview/file-watcher.d.ts +48 -39
- package/dist/preview/http-server.d.ts +5 -2
- package/dist/preview/server-context.d.ts +24 -0
- package/dist/preview-shell-6dqexx1m.js +581 -0
- package/dist/{preview-h2bwbb2x.js → preview-y5a2zen1.js} +5 -5
- package/dist/{project-source-0tg0r6vm.js → project-source-p0gn1wd5.js} +3 -3
- package/dist/{publish-ztaet53r.js → publish-rm9yb3wh.js} +5 -5
- package/dist/render.js +10 -19
- package/dist/{repair-jkp4n3j8.js → repair-zgq7q2g6.js} +5 -5
- package/dist/schema/manifest.types.d.ts +14 -3
- package/dist/server.d.ts +2 -0
- package/dist/{source-provider-hp96b5jf.js → source-provider-c1rjm2c0.js} +2 -8
- package/dist/types.d.ts +0 -1
- package/dist/{validate-k2drs19j.js → validate-nr0xa6sa.js} +8 -8
- package/package.json +2 -2
- package/dist/chapter-01-jj8ftn4c.md +0 -29
- package/dist/lib/validation-profile.d.ts +0 -18
- package/dist/manifest-ryyg71nf.yaml +0 -9
- package/dist/new-7b08a42h.js +0 -105
- package/dist/preview-shell-rghtfmc8.js +0 -155
- package/dist/theme-0yzvthe8.css +0 -65
- package/dist/theme-6a2fngc8.json +0 -6
|
@@ -20,13 +20,13 @@ import {
|
|
|
20
20
|
stampCreator,
|
|
21
21
|
stripAnnotations,
|
|
22
22
|
warn
|
|
23
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-najycadg.js";
|
|
24
24
|
import {
|
|
25
25
|
BuildError,
|
|
26
26
|
DEBOUNCE,
|
|
27
27
|
UsageError,
|
|
28
28
|
resolvePort
|
|
29
|
-
} from "./cli-
|
|
29
|
+
} from "./cli-e5zhb0xs.js";
|
|
30
30
|
import {
|
|
31
31
|
gitDirFor,
|
|
32
32
|
gitScopeFor,
|
|
@@ -40,11 +40,11 @@ import {
|
|
|
40
40
|
snapshotWorkingTreeUnlocked,
|
|
41
41
|
stageChanges,
|
|
42
42
|
withRepoLock
|
|
43
|
-
} from "./cli-
|
|
43
|
+
} from "./cli-yzrh708h.js";
|
|
44
44
|
import {
|
|
45
45
|
detectProjectSource,
|
|
46
46
|
syncRemoteFor
|
|
47
|
-
} from "./cli-
|
|
47
|
+
} from "./cli-wchtvxvw.js";
|
|
48
48
|
import {
|
|
49
49
|
defaultGitHttp
|
|
50
50
|
} from "./cli-yja077f6.js";
|
|
@@ -52,14 +52,10 @@ import {
|
|
|
52
52
|
__require
|
|
53
53
|
} from "./cli-37x76zdn.js";
|
|
54
54
|
|
|
55
|
-
// src/lib/manifest.ts
|
|
56
|
-
import { readFile } from "node:fs/promises";
|
|
57
|
-
import { existsSync, statSync } from "node:fs";
|
|
58
|
-
import { resolve, dirname } from "node:path";
|
|
59
|
-
import { parse as parseYaml, YAMLParseError } from "yaml";
|
|
60
|
-
|
|
61
55
|
// src/lib/presets.ts
|
|
56
|
+
var PRESET_IDS = ["dtrpg", "book", "custom"];
|
|
62
57
|
var DTRPG_PRESET = {
|
|
58
|
+
defaultTargets: ["dtrpg"],
|
|
63
59
|
plugins: [],
|
|
64
60
|
source: {
|
|
65
61
|
files: null
|
|
@@ -131,6 +127,7 @@ var DTRPG_PRESET = {
|
|
|
131
127
|
}
|
|
132
128
|
};
|
|
133
129
|
var BOOK_PRESET = {
|
|
130
|
+
defaultTargets: [],
|
|
134
131
|
plugins: [],
|
|
135
132
|
source: {
|
|
136
133
|
files: null
|
|
@@ -195,9 +192,15 @@ var BOOK_PRESET = {
|
|
|
195
192
|
}
|
|
196
193
|
}
|
|
197
194
|
};
|
|
195
|
+
var CUSTOM_PRESET = {
|
|
196
|
+
...BOOK_PRESET,
|
|
197
|
+
page: null,
|
|
198
|
+
defaultTargets: []
|
|
199
|
+
};
|
|
198
200
|
var PRESETS = {
|
|
199
201
|
dtrpg: DTRPG_PRESET,
|
|
200
|
-
book: BOOK_PRESET
|
|
202
|
+
book: BOOK_PRESET,
|
|
203
|
+
custom: CUSTOM_PRESET
|
|
201
204
|
};
|
|
202
205
|
var warnedIds = new Set;
|
|
203
206
|
function warnOnce(id, message, sink = defaultWarnSink) {
|
|
@@ -211,7 +214,7 @@ function defaultWarnSink(message) {
|
|
|
211
214
|
}
|
|
212
215
|
function resolvePreset(presetName) {
|
|
213
216
|
if (presetName === undefined) {
|
|
214
|
-
warnOnce("no-preset-set", '[gutterpress] No `preset`
|
|
217
|
+
warnOnce("no-preset-set", '[gutterpress] No `preset` in manifest.yaml — using "dtrpg" ' + "(DriveThruRPG print-ready defaults). Set `preset: dtrpg`, `book`, " + "or `custom` to choose explicitly.");
|
|
215
218
|
return DTRPG_PRESET;
|
|
216
219
|
}
|
|
217
220
|
const preset = PRESETS[presetName];
|
|
@@ -221,7 +224,126 @@ function resolvePreset(presetName) {
|
|
|
221
224
|
return preset;
|
|
222
225
|
}
|
|
223
226
|
|
|
227
|
+
// src/lib/targets.ts
|
|
228
|
+
var DTRPG_TARGET = {
|
|
229
|
+
id: "dtrpg",
|
|
230
|
+
label: "DriveThruRPG",
|
|
231
|
+
overlay: {
|
|
232
|
+
pdfx: { flavor: DTRPG_PRESET.pdfx.flavor },
|
|
233
|
+
ink: {
|
|
234
|
+
maxTac: DTRPG_PRESET.ink.maxTac,
|
|
235
|
+
tacTolerance: DTRPG_PRESET.ink.tacTolerance
|
|
236
|
+
},
|
|
237
|
+
validate: {
|
|
238
|
+
checks: {
|
|
239
|
+
"pdf.structure.qpdf": { enabled: true, severity: "error" },
|
|
240
|
+
"pdf.print.pdfx-markers": { enabled: true, severity: "error" },
|
|
241
|
+
"pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
|
|
242
|
+
"pdf.print.embedded-fonts": { enabled: true, severity: "error" }
|
|
243
|
+
},
|
|
244
|
+
assets: {
|
|
245
|
+
allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
|
|
246
|
+
},
|
|
247
|
+
pdf: {
|
|
248
|
+
forbidTransparency: DTRPG_PRESET.validate.pdf.forbidTransparency,
|
|
249
|
+
minImageResolution: DTRPG_PRESET.validate.pdf.minImageResolution
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
requiredChecks: [
|
|
254
|
+
"pdf.structure.qpdf",
|
|
255
|
+
"pdf.print.pdfx-markers",
|
|
256
|
+
"pdf.print.pdfx-metadata",
|
|
257
|
+
"pdf.print.embedded-fonts"
|
|
258
|
+
],
|
|
259
|
+
requiredTools: ["qpdf", "gs"]
|
|
260
|
+
};
|
|
261
|
+
var ITCH_TARGET = {
|
|
262
|
+
id: "itch",
|
|
263
|
+
label: "itch.io",
|
|
264
|
+
overlay: {
|
|
265
|
+
validate: {
|
|
266
|
+
checks: {
|
|
267
|
+
"pdf.structure.qpdf": { enabled: true, severity: "error" },
|
|
268
|
+
"pdf.print.embedded-fonts": { enabled: true, severity: "error" },
|
|
269
|
+
"pdf.print.pdfx-markers": false,
|
|
270
|
+
"pdf.print.pdfx-metadata": false,
|
|
271
|
+
"pdf.print.ink-coverage": false,
|
|
272
|
+
"asset.image.tac-raster": false
|
|
273
|
+
},
|
|
274
|
+
assets: {
|
|
275
|
+
allowedColorSpaces: ["RGB", "CMYK", "Grayscale"],
|
|
276
|
+
allowAlpha: true
|
|
277
|
+
},
|
|
278
|
+
pdf: {
|
|
279
|
+
forbidTransparency: false,
|
|
280
|
+
requireBleed: false
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
requiredChecks: ["pdf.structure.qpdf", "pdf.print.embedded-fonts"],
|
|
285
|
+
requiredTools: []
|
|
286
|
+
};
|
|
287
|
+
var TARGETS = {
|
|
288
|
+
dtrpg: DTRPG_TARGET,
|
|
289
|
+
itch: ITCH_TARGET
|
|
290
|
+
};
|
|
291
|
+
var TARGET_IDS = Object.keys(TARGETS);
|
|
292
|
+
function publishTargetFor(id) {
|
|
293
|
+
const target = TARGETS[id];
|
|
294
|
+
if (!target) {
|
|
295
|
+
throw new UsageError(`Unknown publish target "${id}". Known targets: ${TARGET_IDS.join(", ")}.`);
|
|
296
|
+
}
|
|
297
|
+
return target;
|
|
298
|
+
}
|
|
299
|
+
function resolveTargets(requested, fallback) {
|
|
300
|
+
const ids = requested ?? fallback;
|
|
301
|
+
const seen = new Set;
|
|
302
|
+
const out = [];
|
|
303
|
+
for (const id of ids) {
|
|
304
|
+
publishTargetFor(id);
|
|
305
|
+
if (!seen.has(id)) {
|
|
306
|
+
seen.add(id);
|
|
307
|
+
out.push(id);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
function overlayPreset(preset, target) {
|
|
313
|
+
const o = target.overlay;
|
|
314
|
+
return {
|
|
315
|
+
...preset,
|
|
316
|
+
pdfx: { ...preset.pdfx, ...o.pdfx },
|
|
317
|
+
ink: { ...preset.ink, ...o.ink },
|
|
318
|
+
validate: {
|
|
319
|
+
...preset.validate,
|
|
320
|
+
checks: { ...preset.validate.checks, ...o.validate?.checks },
|
|
321
|
+
assets: { ...preset.validate.assets, ...o.validate?.assets },
|
|
322
|
+
pdf: { ...preset.validate.pdf, ...o.validate?.pdf }
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
var DEFAULT_STRICT_PDF_CHECKS = [
|
|
327
|
+
"pdf.structure.qpdf",
|
|
328
|
+
"pdf.print.pdfx-markers",
|
|
329
|
+
"pdf.print.pdfx-metadata",
|
|
330
|
+
"pdf.print.embedded-fonts"
|
|
331
|
+
];
|
|
332
|
+
function applyDefaultPdfStrictChecks(config) {
|
|
333
|
+
const next = structuredClone(config);
|
|
334
|
+
for (const checkId of DEFAULT_STRICT_PDF_CHECKS) {
|
|
335
|
+
if (next.validate.checks[checkId] === undefined) {
|
|
336
|
+
next.validate.checks[checkId] = { enabled: true, severity: "error" };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return next;
|
|
340
|
+
}
|
|
341
|
+
|
|
224
342
|
// src/lib/manifest.ts
|
|
343
|
+
import { readFile } from "node:fs/promises";
|
|
344
|
+
import { existsSync, statSync } from "node:fs";
|
|
345
|
+
import { resolve, dirname } from "node:path";
|
|
346
|
+
import { parse as parseYaml, YAMLParseError } from "yaml";
|
|
225
347
|
var MANIFEST_FILENAMES = [
|
|
226
348
|
"manifest.yaml"
|
|
227
349
|
];
|
|
@@ -298,8 +420,34 @@ function mergeShape(cli, manifest, preset) {
|
|
|
298
420
|
return out;
|
|
299
421
|
}
|
|
300
422
|
function resolveConfig(cliOverrides, manifest) {
|
|
423
|
+
const presetName = cliOverrides.preset ?? manifest.preset;
|
|
424
|
+
return resolveWithPreset(cliOverrides, manifest, resolvePreset(presetName));
|
|
425
|
+
}
|
|
426
|
+
function resolveConfigForTarget(cliOverrides, manifest, targetId) {
|
|
301
427
|
const presetName = cliOverrides.preset ?? manifest.preset;
|
|
302
428
|
const preset = resolvePreset(presetName);
|
|
429
|
+
return resolveWithPreset(cliOverrides, manifest, overlayPreset(preset, publishTargetFor(targetId)));
|
|
430
|
+
}
|
|
431
|
+
function resolveCustomPage(c, m) {
|
|
432
|
+
const width = c.page?.width ?? m.page?.width;
|
|
433
|
+
const height = c.page?.height ?? m.page?.height;
|
|
434
|
+
const missing = [
|
|
435
|
+
width === undefined ? "page.width" : null,
|
|
436
|
+
height === undefined ? "page.height" : null
|
|
437
|
+
].filter((f) => f !== null);
|
|
438
|
+
if (missing.length > 0) {
|
|
439
|
+
throw new UsageError(`preset: custom requires ${missing.join(" and ")} in manifest.yaml ` + `(points; 72pt = 1in). Example for US Letter:
|
|
440
|
+
` + ` page:
|
|
441
|
+
width: 612
|
|
442
|
+
height: 792`);
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
width,
|
|
446
|
+
height,
|
|
447
|
+
tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
303
451
|
const m = manifest;
|
|
304
452
|
const c = cliOverrides;
|
|
305
453
|
const legacy = m;
|
|
@@ -321,9 +469,10 @@ function resolveConfig(cliOverrides, manifest) {
|
|
|
321
469
|
authors: c.authors ?? m.authors ?? [],
|
|
322
470
|
styles: c.styles ?? m.styles,
|
|
323
471
|
plugins,
|
|
472
|
+
targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
|
|
324
473
|
source: mergeShape(c.source, m.source, preset.source),
|
|
325
474
|
pdfx: mergeShape(c.pdfx, m.pdfx, preset.pdfx),
|
|
326
|
-
page: mergeShape(c.page, m.page, preset.page),
|
|
475
|
+
page: preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m),
|
|
327
476
|
ink: mergeShape(c.ink, m.ink, preset.ink),
|
|
328
477
|
lint: mergeShape(c.lint, m.lint, preset.lint),
|
|
329
478
|
validate: {
|
|
@@ -2575,6 +2724,22 @@ function decodeRef(ref) {
|
|
|
2575
2724
|
function toPosix(p) {
|
|
2576
2725
|
return p.split(path3.sep).join("/");
|
|
2577
2726
|
}
|
|
2727
|
+
function escapesProjectRoot(projectDir, absPath) {
|
|
2728
|
+
const rel = path3.relative(projectDir, absPath);
|
|
2729
|
+
return rel === ".." || rel.startsWith(`..${path3.sep}`) || path3.isAbsolute(rel);
|
|
2730
|
+
}
|
|
2731
|
+
function proseImageRefError(ref, projectDir) {
|
|
2732
|
+
const cleaned = stripUrlSuffix(decodeRef(ref));
|
|
2733
|
+
if (path3.isAbsolute(cleaned)) {
|
|
2734
|
+
return `Image reference must be relative to the project: ${ref}
|
|
2735
|
+
` + ` Copy the file into your project folder and reference it from there.`;
|
|
2736
|
+
}
|
|
2737
|
+
if (escapesProjectRoot(projectDir, path3.resolve(projectDir, cleaned))) {
|
|
2738
|
+
return `Image reference points outside the project: ${ref}
|
|
2739
|
+
` + ` Copy the file into your project folder and reference it from there.`;
|
|
2740
|
+
}
|
|
2741
|
+
return null;
|
|
2742
|
+
}
|
|
2578
2743
|
function mimeFor(ext) {
|
|
2579
2744
|
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
2580
2745
|
}
|
|
@@ -2677,7 +2842,7 @@ async function inlineOne(cssPath, projectDir, copies, warnings, seen) {
|
|
|
2677
2842
|
return dataUri(bytes, ext);
|
|
2678
2843
|
}
|
|
2679
2844
|
const projectRel = path3.relative(projectDir, absAsset);
|
|
2680
|
-
const dest = projectRel && !
|
|
2845
|
+
const dest = projectRel && !escapesProjectRoot(projectDir, absAsset) ? toPosix(projectRel) : `${HASHED_ASSET_DIR}/${contentHash(bytes)}${ext}`;
|
|
2681
2846
|
copies.set(dest, { from: absAsset, to: dest });
|
|
2682
2847
|
return dest;
|
|
2683
2848
|
});
|
|
@@ -2768,19 +2933,14 @@ async function planImageCopies(projectDir, refs) {
|
|
|
2768
2933
|
for (const ref of refs) {
|
|
2769
2934
|
if (isNonFileUrl(ref))
|
|
2770
2935
|
continue;
|
|
2771
|
-
const
|
|
2772
|
-
if (
|
|
2773
|
-
errors.push(
|
|
2774
|
-
` + ` Copy the file into your project folder and reference it from there.`);
|
|
2936
|
+
const refError = proseImageRefError(ref, projectDir);
|
|
2937
|
+
if (refError) {
|
|
2938
|
+
errors.push(refError);
|
|
2775
2939
|
continue;
|
|
2776
2940
|
}
|
|
2941
|
+
const cleaned = stripUrlSuffix(decodeRef(ref));
|
|
2777
2942
|
const abs = path3.resolve(projectDir, cleaned);
|
|
2778
2943
|
const rel = path3.relative(projectDir, abs);
|
|
2779
|
-
if (rel.startsWith("..")) {
|
|
2780
|
-
errors.push(`Image reference points outside the project: ${ref}
|
|
2781
|
-
` + ` Copy the file into your project folder and reference it from there.`);
|
|
2782
|
-
continue;
|
|
2783
|
-
}
|
|
2784
2944
|
const dest = toPosix(rel);
|
|
2785
2945
|
copies.set(dest, { from: abs, to: dest });
|
|
2786
2946
|
}
|
|
@@ -2814,6 +2974,16 @@ var check19 = {
|
|
|
2814
2974
|
if (inFence)
|
|
2815
2975
|
continue;
|
|
2816
2976
|
for (const { ref, kind } of extractLocalRefs(line)) {
|
|
2977
|
+
const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
|
|
2978
|
+
if (escape) {
|
|
2979
|
+
results.push(finding(check19.id, {
|
|
2980
|
+
severity: "error",
|
|
2981
|
+
message: escape,
|
|
2982
|
+
file,
|
|
2983
|
+
line: i + 1
|
|
2984
|
+
}));
|
|
2985
|
+
continue;
|
|
2986
|
+
}
|
|
2817
2987
|
if (localRefExists(ref, kind, file, ctx.inputDir))
|
|
2818
2988
|
continue;
|
|
2819
2989
|
results.push(finding(check19.id, {
|
|
@@ -3796,7 +3966,7 @@ function reportMissingTools(result) {
|
|
|
3796
3966
|
|
|
3797
3967
|
// src/lib/validation-exec.ts
|
|
3798
3968
|
import { existsSync as existsSync7 } from "node:fs";
|
|
3799
|
-
import { join as join2, resolve as resolve6 } from "node:path";
|
|
3969
|
+
import { dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
|
|
3800
3970
|
|
|
3801
3971
|
// src/lib/output-paths.ts
|
|
3802
3972
|
import path4 from "node:path";
|
|
@@ -4396,17 +4566,6 @@ async function assembleBookHtml(opts) {
|
|
|
4396
4566
|
throw new Error("No markdown files to render");
|
|
4397
4567
|
}
|
|
4398
4568
|
const md = createMarkdownRenderer(opts.plugins);
|
|
4399
|
-
let sourceChapter = null;
|
|
4400
|
-
if (opts.wrapChapters) {
|
|
4401
|
-
const renderToken = md.renderer.renderToken.bind(md.renderer);
|
|
4402
|
-
md.renderer.renderToken = (tokens, idx, options) => {
|
|
4403
|
-
const token = tokens[idx];
|
|
4404
|
-
if (sourceChapter !== null && token.level === 0 && token.map !== null && token.type.endsWith("_open")) {
|
|
4405
|
-
token.attrSet("data-chapter-src", sourceChapter);
|
|
4406
|
-
}
|
|
4407
|
-
return renderToken(tokens, idx, options);
|
|
4408
|
-
};
|
|
4409
|
-
}
|
|
4410
4569
|
let bodyContent = "";
|
|
4411
4570
|
const imageRefs = new Set;
|
|
4412
4571
|
for (const file of files) {
|
|
@@ -4419,20 +4578,22 @@ async function assembleBookHtml(opts) {
|
|
|
4419
4578
|
throw new Error(`Failed to read file ${file}: ${errorMsg}`);
|
|
4420
4579
|
}
|
|
4421
4580
|
const env = {};
|
|
4422
|
-
|
|
4423
|
-
let rendered;
|
|
4424
|
-
try {
|
|
4425
|
-
rendered = md.render(content, env);
|
|
4426
|
-
} finally {
|
|
4427
|
-
sourceChapter = null;
|
|
4428
|
-
}
|
|
4581
|
+
const rendered = md.render(content, env);
|
|
4429
4582
|
if (env.layoutWarnings && env.layoutWarnings.length > 0) {
|
|
4430
4583
|
opts.onChapterWarnings?.(chapterId, env.layoutWarnings);
|
|
4431
4584
|
}
|
|
4432
4585
|
for (const ref of env.imageRefs ?? [])
|
|
4433
4586
|
imageRefs.add(ref);
|
|
4434
|
-
|
|
4587
|
+
if (opts.wrapChapters) {
|
|
4588
|
+
const safe = chapterId.replace(/&/g, "&").replace(/"/g, """);
|
|
4589
|
+
bodyContent += `<div class="gutterpress-chapter" data-chapter-src="${safe}">
|
|
4590
|
+
${rendered}
|
|
4591
|
+
</div>
|
|
4592
|
+
`;
|
|
4593
|
+
} else {
|
|
4594
|
+
bodyContent += rendered + `
|
|
4435
4595
|
`;
|
|
4596
|
+
}
|
|
4436
4597
|
}
|
|
4437
4598
|
if (opts.onImageRefs) {
|
|
4438
4599
|
for (const ref of collectHtmlImageRefs(bodyContent))
|
|
@@ -4523,19 +4684,19 @@ function formatReport(report, format = "text") {
|
|
|
4523
4684
|
}
|
|
4524
4685
|
function formatText(report) {
|
|
4525
4686
|
for (const r of report.infos) {
|
|
4526
|
-
const loc = formatLocation(r.file, r.line, r.column);
|
|
4687
|
+
const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
|
|
4527
4688
|
log.info(`${loc}${r.message}`);
|
|
4528
4689
|
if (r.detail)
|
|
4529
4690
|
log.info(` ${r.detail}`);
|
|
4530
4691
|
}
|
|
4531
4692
|
for (const r of report.warnings) {
|
|
4532
|
-
const loc = formatLocation(r.file, r.line, r.column);
|
|
4693
|
+
const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
|
|
4533
4694
|
log.warn(`${loc}${r.message}`);
|
|
4534
4695
|
if (r.detail)
|
|
4535
4696
|
log.warn(` ${r.detail}`);
|
|
4536
4697
|
}
|
|
4537
4698
|
for (const r of report.errors) {
|
|
4538
|
-
const loc = formatLocation(r.file, r.line, r.column);
|
|
4699
|
+
const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
|
|
4539
4700
|
log.error(`${loc}${r.message}`);
|
|
4540
4701
|
if (r.detail)
|
|
4541
4702
|
log.error(` ${r.detail}`);
|
|
@@ -4549,6 +4710,9 @@ function formatText(report) {
|
|
|
4549
4710
|
log.success("VALIDATION PASSED");
|
|
4550
4711
|
}
|
|
4551
4712
|
}
|
|
4713
|
+
function targetPrefix(target) {
|
|
4714
|
+
return target ? `[${target}] ` : "";
|
|
4715
|
+
}
|
|
4552
4716
|
function formatJson(report) {
|
|
4553
4717
|
console.log(JSON.stringify({
|
|
4554
4718
|
results: report.results,
|
|
@@ -4650,58 +4814,46 @@ function emptyReport() {
|
|
|
4650
4814
|
};
|
|
4651
4815
|
}
|
|
4652
4816
|
|
|
4653
|
-
// src/lib/validation-
|
|
4654
|
-
var
|
|
4655
|
-
"
|
|
4656
|
-
"
|
|
4657
|
-
"
|
|
4658
|
-
"
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4817
|
+
// src/lib/validation-exec.ts
|
|
4818
|
+
var SHIPPED_ASSET_EXTS = new Set([
|
|
4819
|
+
".woff2",
|
|
4820
|
+
".woff",
|
|
4821
|
+
".ttf",
|
|
4822
|
+
".otf",
|
|
4823
|
+
".png",
|
|
4824
|
+
".jpg",
|
|
4825
|
+
".jpeg",
|
|
4826
|
+
".gif",
|
|
4827
|
+
".webp",
|
|
4828
|
+
".avif",
|
|
4829
|
+
".svg",
|
|
4830
|
+
".tif",
|
|
4831
|
+
".tiff"
|
|
4832
|
+
]);
|
|
4833
|
+
async function sharedAssetDirs(manifestDir, relStyles, inputDir) {
|
|
4834
|
+
let closure;
|
|
4835
|
+
try {
|
|
4836
|
+
closure = await collectStyleDependencies(manifestDir, relStyles);
|
|
4837
|
+
} catch {
|
|
4838
|
+
return [];
|
|
4668
4839
|
}
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
const
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
if (profile === "dtrpg") {
|
|
4679
|
-
next.pdfx.flavor = DTRPG_PRESET.pdfx.flavor;
|
|
4680
|
-
next.ink.maxTac = DTRPG_PRESET.ink.maxTac;
|
|
4681
|
-
next.ink.tacTolerance = DTRPG_PRESET.ink.tacTolerance;
|
|
4682
|
-
next.validate.pdf.forbidTransparency = DTRPG_PRESET.validate.pdf.forbidTransparency;
|
|
4683
|
-
next.validate.pdf.minImageResolution = DTRPG_PRESET.validate.pdf.minImageResolution;
|
|
4684
|
-
next.validate.assets.allowedColorSpaces = [
|
|
4685
|
-
...DTRPG_PRESET.validate.assets.allowedColorSpaces
|
|
4686
|
-
];
|
|
4687
|
-
enforceStrictPdfChecks(next, true);
|
|
4840
|
+
const projectRoot = resolve6(inputDir);
|
|
4841
|
+
const dirs = new Set;
|
|
4842
|
+
for (const file of closure) {
|
|
4843
|
+
const ext = file.slice(file.lastIndexOf(".")).toLowerCase();
|
|
4844
|
+
if (!SHIPPED_ASSET_EXTS.has(ext))
|
|
4845
|
+
continue;
|
|
4846
|
+
const dir = dirname3(resolve6(file));
|
|
4847
|
+
if (escapesProjectRoot(projectRoot, dir))
|
|
4848
|
+
dirs.add(dir);
|
|
4688
4849
|
}
|
|
4689
|
-
return
|
|
4850
|
+
return [...dirs];
|
|
4690
4851
|
}
|
|
4691
|
-
|
|
4692
|
-
// src/lib/validation-exec.ts
|
|
4693
4852
|
function parseCsv(value) {
|
|
4694
4853
|
if (!value)
|
|
4695
4854
|
return;
|
|
4696
4855
|
return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4697
4856
|
}
|
|
4698
|
-
function parseProfile(raw) {
|
|
4699
|
-
if (!raw)
|
|
4700
|
-
return;
|
|
4701
|
-
if (raw === "dtrpg")
|
|
4702
|
-
return raw;
|
|
4703
|
-
throw new Error(`Unsupported profile: ${raw}. Supported profiles: dtrpg`);
|
|
4704
|
-
}
|
|
4705
4857
|
function resolvePhaseArg(raw) {
|
|
4706
4858
|
const normalized = raw.trim().toLowerCase();
|
|
4707
4859
|
if (normalized === "pre" || normalized === "pre-build")
|
|
@@ -4724,20 +4876,16 @@ function resolveCategoryArg(raw) {
|
|
|
4724
4876
|
}
|
|
4725
4877
|
return parsed;
|
|
4726
4878
|
}
|
|
4727
|
-
function
|
|
4728
|
-
if (!profile)
|
|
4729
|
-
return report;
|
|
4730
|
-
const required = profile === "dtrpg" ? DTRPG_STRICT_PDF_CHECKS : [];
|
|
4731
|
-
if (required.length === 0)
|
|
4732
|
-
return report;
|
|
4879
|
+
function withTargetRequiredCheckErrors(report, tools, target) {
|
|
4733
4880
|
const skippedSet = new Set(tools.skippedChecks);
|
|
4734
|
-
const missingRequired =
|
|
4881
|
+
const missingRequired = target.requiredChecks.filter((checkId) => skippedSet.has(checkId));
|
|
4735
4882
|
if (missingRequired.length === 0)
|
|
4736
4883
|
return report;
|
|
4737
4884
|
const syntheticErrors = missingRequired.map((checkId) => ({
|
|
4738
4885
|
checkId,
|
|
4886
|
+
target: target.id,
|
|
4739
4887
|
severity: "error",
|
|
4740
|
-
message: `
|
|
4888
|
+
message: `Target ${target.id} requires check ${checkId}, but it was skipped because required tools are not available.`
|
|
4741
4889
|
}));
|
|
4742
4890
|
const results = [...report.results, ...syntheticErrors];
|
|
4743
4891
|
const errors = [...report.errors, ...syntheticErrors];
|
|
@@ -4751,19 +4899,115 @@ function withProfileRequiredCheckErrors(report, tools, profile) {
|
|
|
4751
4899
|
}
|
|
4752
4900
|
};
|
|
4753
4901
|
}
|
|
4902
|
+
var TARGET_INDEPENDENT_CATEGORIES = ["source", "heuristic"];
|
|
4903
|
+
var TARGET_DEPENDENT_CATEGORIES = ["asset", "pdf"];
|
|
4904
|
+
function tagReport(report, targetId) {
|
|
4905
|
+
const tag = (r) => ({ ...r, target: targetId });
|
|
4906
|
+
return {
|
|
4907
|
+
results: report.results.map(tag),
|
|
4908
|
+
errors: report.errors.map(tag),
|
|
4909
|
+
warnings: report.warnings.map(tag),
|
|
4910
|
+
infos: report.infos.map(tag),
|
|
4911
|
+
passed: report.passed,
|
|
4912
|
+
summary: report.summary
|
|
4913
|
+
};
|
|
4914
|
+
}
|
|
4915
|
+
function mergeReports(reports) {
|
|
4916
|
+
const merged = {
|
|
4917
|
+
results: [],
|
|
4918
|
+
errors: [],
|
|
4919
|
+
warnings: [],
|
|
4920
|
+
infos: [],
|
|
4921
|
+
passed: [],
|
|
4922
|
+
summary: { total: 0, errors: 0, warnings: 0, infos: 0, passed: 0 }
|
|
4923
|
+
};
|
|
4924
|
+
for (const r of reports) {
|
|
4925
|
+
merged.results.push(...r.results);
|
|
4926
|
+
merged.errors.push(...r.errors);
|
|
4927
|
+
merged.warnings.push(...r.warnings);
|
|
4928
|
+
merged.infos.push(...r.infos);
|
|
4929
|
+
merged.passed.push(...r.passed);
|
|
4930
|
+
merged.summary.total += r.summary.total;
|
|
4931
|
+
merged.summary.errors += r.summary.errors;
|
|
4932
|
+
merged.summary.warnings += r.summary.warnings;
|
|
4933
|
+
merged.summary.infos += r.summary.infos;
|
|
4934
|
+
merged.summary.passed += r.summary.passed;
|
|
4935
|
+
}
|
|
4936
|
+
return merged;
|
|
4937
|
+
}
|
|
4938
|
+
function mergeTools(results) {
|
|
4939
|
+
const available = new Set;
|
|
4940
|
+
const missing = new Set;
|
|
4941
|
+
const skippedChecks = new Set;
|
|
4942
|
+
const toolToChecks = new Map;
|
|
4943
|
+
for (const r of results) {
|
|
4944
|
+
for (const t of r.available)
|
|
4945
|
+
available.add(t);
|
|
4946
|
+
for (const t of r.missing)
|
|
4947
|
+
missing.add(t);
|
|
4948
|
+
for (const id of r.skippedChecks)
|
|
4949
|
+
skippedChecks.add(id);
|
|
4950
|
+
for (const [tool, ids] of r.toolToChecks) {
|
|
4951
|
+
const merged = new Set([...toolToChecks.get(tool) ?? [], ...ids]);
|
|
4952
|
+
toolToChecks.set(tool, [...merged]);
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
return {
|
|
4956
|
+
available: [...available],
|
|
4957
|
+
missing: [...missing],
|
|
4958
|
+
skippedChecks: [...skippedChecks],
|
|
4959
|
+
toolToChecks
|
|
4960
|
+
};
|
|
4961
|
+
}
|
|
4962
|
+
function planRuns(opts) {
|
|
4963
|
+
const { manifest, baseConfig, targetIds, pdfPath, categories, only } = opts;
|
|
4964
|
+
const finalize = (config) => pdfPath ? applyDefaultPdfStrictChecks(config) : config;
|
|
4965
|
+
if (targetIds.length === 0) {
|
|
4966
|
+
return [{ targetId: null, config: finalize(baseConfig), category: categories, only }];
|
|
4967
|
+
}
|
|
4968
|
+
const requested = categories ?? [...TARGET_INDEPENDENT_CATEGORIES, ...TARGET_DEPENDENT_CATEGORIES];
|
|
4969
|
+
const independentCats = requested.filter((c) => TARGET_INDEPENDENT_CATEGORIES.includes(c));
|
|
4970
|
+
const dependentCats = requested.filter((c) => TARGET_DEPENDENT_CATEGORIES.includes(c));
|
|
4971
|
+
let baseOnly;
|
|
4972
|
+
let dependentOnly;
|
|
4973
|
+
if (only && only.length > 0) {
|
|
4974
|
+
const { resolved, unmatched } = resolveCheckSelectors(only);
|
|
4975
|
+
const categoryById = new Map(getChecks({}).map((c) => [c.id, c.category]));
|
|
4976
|
+
const independentIds = resolved.filter((id) => TARGET_INDEPENDENT_CATEGORIES.includes(categoryById.get(id) ?? ""));
|
|
4977
|
+
const dependentIds = resolved.filter((id) => TARGET_DEPENDENT_CATEGORIES.includes(categoryById.get(id) ?? ""));
|
|
4978
|
+
baseOnly = [...independentIds, ...unmatched];
|
|
4979
|
+
dependentOnly = dependentIds;
|
|
4980
|
+
}
|
|
4981
|
+
const runs = [];
|
|
4982
|
+
const baseWanted = only ? (baseOnly?.length ?? 0) > 0 : independentCats.length > 0;
|
|
4983
|
+
if (baseWanted) {
|
|
4984
|
+
runs.push({
|
|
4985
|
+
targetId: null,
|
|
4986
|
+
config: baseConfig,
|
|
4987
|
+
category: only ? undefined : independentCats,
|
|
4988
|
+
only: baseOnly
|
|
4989
|
+
});
|
|
4990
|
+
}
|
|
4991
|
+
const dependentWanted = only ? (dependentOnly?.length ?? 0) > 0 : dependentCats.length > 0;
|
|
4992
|
+
if (dependentWanted) {
|
|
4993
|
+
for (const targetId of targetIds) {
|
|
4994
|
+
runs.push({
|
|
4995
|
+
targetId,
|
|
4996
|
+
config: finalize(resolveConfigForTarget({}, manifest, targetId)),
|
|
4997
|
+
category: only ? undefined : dependentCats,
|
|
4998
|
+
only: dependentOnly
|
|
4999
|
+
});
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
return runs;
|
|
5003
|
+
}
|
|
4754
5004
|
async function executeValidation(args) {
|
|
4755
5005
|
const manifestPath = typeof args.manifest === "string" ? args.manifest : undefined;
|
|
4756
5006
|
const { manifest, manifestDir } = await loadManifestWithPath(manifestPath ?? args.input ?? undefined, { explicit: manifestPath !== undefined });
|
|
4757
|
-
const
|
|
4758
|
-
|
|
5007
|
+
const config = resolveConfig({}, manifest);
|
|
5008
|
+
const targetIds = resolveTargets(parseCsv(typeof args.target === "string" ? args.target : undefined), config.targets);
|
|
4759
5009
|
const pdfPath = typeof args.pdf === "string" ? resolve6(args.pdf) : undefined;
|
|
4760
5010
|
const inputDir = typeof args.input === "string" ? resolve6(args.input) : undefined;
|
|
4761
|
-
if (pdfPath) {
|
|
4762
|
-
config = applyDtrpgPdfDefaults(config);
|
|
4763
|
-
}
|
|
4764
|
-
if (profile) {
|
|
4765
|
-
config = applyValidationProfile(config, profile);
|
|
4766
|
-
}
|
|
4767
5011
|
if (pdfPath && !existsSync7(pdfPath)) {
|
|
4768
5012
|
throw new Error(`File not found: ${pdfPath}`);
|
|
4769
5013
|
}
|
|
@@ -4792,11 +5036,11 @@ async function executeValidation(args) {
|
|
|
4792
5036
|
let htmlPath;
|
|
4793
5037
|
const outDir = resolveOutputDir(manifestDir, config.title);
|
|
4794
5038
|
if (inputDir) {
|
|
4795
|
-
const relMarkdown = await resolveActiveMarkdownFiles(
|
|
4796
|
-
markdownFiles = relMarkdown.map((f) => join2(
|
|
4797
|
-
const relStyles = await resolveActiveStyles(
|
|
4798
|
-
cssFiles = relStyles.map((rel) => resolve6(
|
|
4799
|
-
assetDirs = [inputDir];
|
|
5039
|
+
const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.source.files);
|
|
5040
|
+
markdownFiles = relMarkdown.map((f) => join2(manifestDir, canonicalChapterId(f)));
|
|
5041
|
+
const relStyles = await resolveActiveStyles(manifestDir, config.styles);
|
|
5042
|
+
cssFiles = relStyles.map((rel) => resolve6(manifestDir, rel)).filter((f) => !f.endsWith(".min.css"));
|
|
5043
|
+
assetDirs = [inputDir, ...await sharedAssetDirs(manifestDir, relStyles, inputDir)];
|
|
4800
5044
|
const possibleHtml = join2(outDir, BOOK_HTML_FILENAME);
|
|
4801
5045
|
if (existsSync7(possibleHtml)) {
|
|
4802
5046
|
htmlPath = possibleHtml;
|
|
@@ -4818,15 +5062,30 @@ async function executeValidation(args) {
|
|
|
4818
5062
|
only,
|
|
4819
5063
|
skip
|
|
4820
5064
|
};
|
|
4821
|
-
const
|
|
4822
|
-
const
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
5065
|
+
const runs = planRuns({ manifest, baseConfig: config, targetIds, pdfPath, categories, only });
|
|
5066
|
+
const reports = [];
|
|
5067
|
+
const toolResults = [];
|
|
5068
|
+
for (const run of runs) {
|
|
5069
|
+
const runOptions = {
|
|
5070
|
+
category: run.category,
|
|
5071
|
+
phase,
|
|
5072
|
+
only: run.only,
|
|
5073
|
+
skip
|
|
5074
|
+
};
|
|
5075
|
+
const runTools = await checkToolAvailability(run.config, runOptions);
|
|
5076
|
+
toolResults.push(runTools);
|
|
5077
|
+
let runReport = await runChecks({ ...context, config: run.config }, { ...runOptions, skipMissingTools: runTools.skippedChecks });
|
|
5078
|
+
if (run.targetId !== null) {
|
|
5079
|
+
runReport = withTargetRequiredCheckErrors(runReport, runTools, publishTargetFor(run.targetId));
|
|
5080
|
+
runReport = tagReport(runReport, run.targetId);
|
|
5081
|
+
}
|
|
5082
|
+
reports.push(runReport);
|
|
5083
|
+
}
|
|
5084
|
+
const tools = mergeTools(toolResults);
|
|
5085
|
+
const report = mergeReports(reports);
|
|
4827
5086
|
return {
|
|
4828
5087
|
config,
|
|
4829
|
-
|
|
5088
|
+
targets: targetIds,
|
|
4830
5089
|
context,
|
|
4831
5090
|
runnerOptions,
|
|
4832
5091
|
tools,
|
|
@@ -4887,7 +5146,7 @@ import { createHash as createHash3 } from "node:crypto";
|
|
|
4887
5146
|
import { existsSync as existsSync8, lstatSync, realpathSync, rmSync, statSync as statSync2, unlinkSync } from "node:fs";
|
|
4888
5147
|
import { cp, link, mkdir as mkdir2, mkdtemp, readFile as readFile16, rm, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
4889
5148
|
import { tmpdir } from "node:os";
|
|
4890
|
-
import { resolve as resolve7, join as join3, dirname as
|
|
5149
|
+
import { resolve as resolve7, join as join3, dirname as dirname4, basename, extname, relative, isAbsolute } from "node:path";
|
|
4891
5150
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4892
5151
|
import Module, { builtinModules, createRequire } from "node:module";
|
|
4893
5152
|
import { parse as parseJavaScript } from "acorn";
|
|
@@ -5256,7 +5515,7 @@ async function createIsolatedVendorTree(verified, address) {
|
|
|
5256
5515
|
for (let index = 0;index < sorted.length; index++) {
|
|
5257
5516
|
const source = sorted[index];
|
|
5258
5517
|
const destination = join3(loadRoot, "packages", String(index), "package");
|
|
5259
|
-
await mkdir2(
|
|
5518
|
+
await mkdir2(dirname4(destination), { recursive: true });
|
|
5260
5519
|
await cp(source.packageDir, destination, {
|
|
5261
5520
|
recursive: true,
|
|
5262
5521
|
filter: (candidate) => isolatedPackageCopyFilter(source.packageDir, candidate)
|
|
@@ -5315,7 +5574,7 @@ async function snapshotAndIsolateVendoredPlugin(baseDir, packageName, version) {
|
|
|
5315
5574
|
const snapshotProject = join3(container, "project");
|
|
5316
5575
|
const snapshotRoot = vendoredNpmPluginRoot(snapshotProject, packageName, version);
|
|
5317
5576
|
try {
|
|
5318
|
-
await mkdir2(
|
|
5577
|
+
await mkdir2(dirname4(snapshotRoot), { recursive: true });
|
|
5319
5578
|
await cp(sourceRoot, snapshotRoot, {
|
|
5320
5579
|
recursive: true,
|
|
5321
5580
|
dereference: false,
|
|
@@ -5453,7 +5712,7 @@ function shadowPathFor(pluginPath, mtimeMs) {
|
|
|
5453
5712
|
const ext = extname(pluginPath);
|
|
5454
5713
|
const stem = basename(pluginPath, ext);
|
|
5455
5714
|
const token = String(mtimeMs).replace(/\./g, "-");
|
|
5456
|
-
return join3(
|
|
5715
|
+
return join3(dirname4(pluginPath), `.${stem}.gutterpress-reload-${process.pid}-${token}${ext}`);
|
|
5457
5716
|
}
|
|
5458
5717
|
async function loadCachedPathPluginModule(pluginPath) {
|
|
5459
5718
|
const mtimeMs = statSync2(pluginPath).mtimeMs;
|
|
@@ -5731,7 +5990,7 @@ async function writeBuildFingerprint(input) {
|
|
|
5731
5990
|
iccPath: input.pdfx.iccPath,
|
|
5732
5991
|
stripAnnotations: input.pdfx.stripAnnotations
|
|
5733
5992
|
},
|
|
5734
|
-
outputDir
|
|
5993
|
+
outputDir: input.recordedOutputDir ? path5.resolve(input.recordedOutputDir) : outputDir
|
|
5735
5994
|
},
|
|
5736
5995
|
sourceRevision,
|
|
5737
5996
|
tools
|
|
@@ -5745,23 +6004,23 @@ async function writeBuildFingerprint(input) {
|
|
|
5745
6004
|
import { existsSync as existsSync9 } from "node:fs";
|
|
5746
6005
|
import { mkdtemp as mkdtemp2, mkdir as mkdir4, writeFile as writeFile4, readFile as readFile17 } from "node:fs/promises";
|
|
5747
6006
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
5748
|
-
import { dirname as
|
|
6007
|
+
import { dirname as dirname5, join as join4, resolve as resolve8 } from "node:path";
|
|
5749
6008
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5750
6009
|
|
|
5751
6010
|
// src/assets/favicon.ico
|
|
5752
6011
|
var favicon_default = "./favicon-wkbm9cjn.ico";
|
|
5753
6012
|
|
|
5754
6013
|
// src/assets/manifest.schema.json
|
|
5755
|
-
var manifest_schema_default = "./manifest.schema-
|
|
6014
|
+
var manifest_schema_default = "./manifest.schema-z61rzw44.json";
|
|
5756
6015
|
|
|
5757
6016
|
// src/assets/preview/scripts/pagedjs-interface.js
|
|
5758
|
-
var pagedjs_interface_default = "./pagedjs-interface-
|
|
6017
|
+
var pagedjs_interface_default = "./pagedjs-interface-qxvzgwd7.js";
|
|
5759
6018
|
|
|
5760
6019
|
// src/assets/preview/scripts/pagedjs-bridge.js
|
|
5761
6020
|
var pagedjs_bridge_default = "./pagedjs-bridge-vn4hk9fx.js";
|
|
5762
6021
|
|
|
5763
6022
|
// src/assets/preview/scripts/preview-shell.js
|
|
5764
|
-
var preview_shell_default = "./preview-shell-
|
|
6023
|
+
var preview_shell_default = "./preview-shell-6dqexx1m.js";
|
|
5765
6024
|
|
|
5766
6025
|
// src/assets/vendor/paged.polyfill.js
|
|
5767
6026
|
var paged_polyfill_default = "./paged.polyfill-n95pbxfn.js";
|
|
@@ -5775,23 +6034,17 @@ var manifest_default = "./manifest-n1gh3qw5.yaml";
|
|
|
5775
6034
|
// src/assets/templates/book/chapter-01.md
|
|
5776
6035
|
var chapter_01_default = "./chapter-01-rkpyvjna.md";
|
|
5777
6036
|
|
|
5778
|
-
// src/assets/templates/ttrpg/manifest.yaml
|
|
5779
|
-
var manifest_default2 = "./manifest-ryyg71nf.yaml";
|
|
5780
|
-
|
|
5781
|
-
// src/assets/templates/ttrpg/chapter-01.md
|
|
5782
|
-
var chapter_01_default2 = "./chapter-01-jj8ftn4c.md";
|
|
5783
|
-
|
|
5784
6037
|
// src/assets/templates/zine/manifest.yaml
|
|
5785
|
-
var
|
|
6038
|
+
var manifest_default2 = "./manifest-n1gh3qw5.yaml";
|
|
5786
6039
|
|
|
5787
6040
|
// src/assets/templates/zine/chapter-01.md
|
|
5788
|
-
var
|
|
6041
|
+
var chapter_01_default2 = "./chapter-01-7jwqg1ct.md";
|
|
5789
6042
|
|
|
5790
6043
|
// src/assets/templates/technical/manifest.yaml
|
|
5791
|
-
var
|
|
6044
|
+
var manifest_default3 = "./manifest-n1gh3qw5.yaml";
|
|
5792
6045
|
|
|
5793
6046
|
// src/assets/templates/technical/chapter-01.md
|
|
5794
|
-
var
|
|
6047
|
+
var chapter_01_default3 = "./chapter-01-6fn9pvd0.md";
|
|
5795
6048
|
|
|
5796
6049
|
// src/assets/themes/clean-book/theme.css
|
|
5797
6050
|
var theme_default = "./theme-570zmh2t.css";
|
|
@@ -5799,26 +6052,20 @@ var theme_default = "./theme-570zmh2t.css";
|
|
|
5799
6052
|
// src/assets/themes/clean-book/theme.json
|
|
5800
6053
|
var theme_default2 = "./theme-xt0hjb1b.json";
|
|
5801
6054
|
|
|
5802
|
-
// src/assets/themes/ttrpg-supplement/theme.css
|
|
5803
|
-
var theme_default3 = "./theme-0yzvthe8.css";
|
|
5804
|
-
|
|
5805
|
-
// src/assets/themes/ttrpg-supplement/theme.json
|
|
5806
|
-
var theme_default4 = "./theme-6a2fngc8.json";
|
|
5807
|
-
|
|
5808
6055
|
// src/assets/themes/zine/theme.css
|
|
5809
|
-
var
|
|
6056
|
+
var theme_default3 = "./theme-zz2ktzqs.css";
|
|
5810
6057
|
|
|
5811
6058
|
// src/assets/themes/zine/theme.json
|
|
5812
|
-
var
|
|
6059
|
+
var theme_default4 = "./theme-cd6efzjw.json";
|
|
5813
6060
|
|
|
5814
6061
|
// src/assets/themes/technical-doc/theme.css
|
|
5815
|
-
var
|
|
6062
|
+
var theme_default5 = "./theme-nya4nqh6.css";
|
|
5816
6063
|
|
|
5817
6064
|
// src/assets/themes/technical-doc/theme.json
|
|
5818
|
-
var
|
|
6065
|
+
var theme_default6 = "./theme-tqfypc1w.json";
|
|
5819
6066
|
|
|
5820
6067
|
// src/lib/embedded-assets.ts
|
|
5821
|
-
var __libdir =
|
|
6068
|
+
var __libdir = dirname5(fileURLToPath2(import.meta.url));
|
|
5822
6069
|
var abs = (p) => resolve8(__libdir, p);
|
|
5823
6070
|
var filePath = (v) => v;
|
|
5824
6071
|
var EMBEDDED_ASSETS = {
|
|
@@ -5831,27 +6078,23 @@ var EMBEDDED_ASSETS = {
|
|
|
5831
6078
|
"profiles/CGATS21_CRPC1.icc": abs(filePath(CGATS21_CRPC1_default)),
|
|
5832
6079
|
"templates/book/manifest.yaml": abs(filePath(manifest_default)),
|
|
5833
6080
|
"templates/book/chapter-01.md": abs(filePath(chapter_01_default)),
|
|
5834
|
-
"templates/
|
|
5835
|
-
"templates/
|
|
5836
|
-
"templates/
|
|
5837
|
-
"templates/
|
|
5838
|
-
"templates/technical/manifest.yaml": abs(filePath(manifest_default4)),
|
|
5839
|
-
"templates/technical/chapter-01.md": abs(filePath(chapter_01_default4)),
|
|
6081
|
+
"templates/zine/manifest.yaml": abs(filePath(manifest_default2)),
|
|
6082
|
+
"templates/zine/chapter-01.md": abs(filePath(chapter_01_default2)),
|
|
6083
|
+
"templates/technical/manifest.yaml": abs(filePath(manifest_default3)),
|
|
6084
|
+
"templates/technical/chapter-01.md": abs(filePath(chapter_01_default3)),
|
|
5840
6085
|
"themes/clean-book/theme.css": abs(filePath(theme_default)),
|
|
5841
6086
|
"themes/clean-book/theme.json": abs(filePath(theme_default2)),
|
|
5842
|
-
"themes/
|
|
5843
|
-
"themes/
|
|
5844
|
-
"themes/
|
|
5845
|
-
"themes/
|
|
5846
|
-
"themes/technical-doc/theme.css": abs(filePath(theme_default7)),
|
|
5847
|
-
"themes/technical-doc/theme.json": abs(filePath(theme_default8))
|
|
6087
|
+
"themes/zine/theme.css": abs(filePath(theme_default3)),
|
|
6088
|
+
"themes/zine/theme.json": abs(filePath(theme_default4)),
|
|
6089
|
+
"themes/technical-doc/theme.css": abs(filePath(theme_default5)),
|
|
6090
|
+
"themes/technical-doc/theme.json": abs(filePath(theme_default6))
|
|
5848
6091
|
};
|
|
5849
6092
|
var extractPromise = null;
|
|
5850
6093
|
async function extractAssets() {
|
|
5851
6094
|
const root = await mkdtemp2(join4(tmpdir2(), "gutterpress-assets-"));
|
|
5852
6095
|
for (const [relPath, srcPath] of Object.entries(EMBEDDED_ASSETS)) {
|
|
5853
6096
|
const dest = join4(root, relPath);
|
|
5854
|
-
await mkdir4(
|
|
6097
|
+
await mkdir4(dirname5(dest), { recursive: true });
|
|
5855
6098
|
const bytes = await readFile17(srcPath);
|
|
5856
6099
|
await writeFile4(dest, bytes);
|
|
5857
6100
|
}
|
|
@@ -5905,7 +6148,7 @@ ${m.installHint}`).join(`
|
|
|
5905
6148
|
|
|
5906
6149
|
${list}
|
|
5907
6150
|
|
|
5908
|
-
Install the missing tools and re-run, or set GHOSTSCRIPT_PATH, CHROMIUM_PATH, or system PATH so gutterpress can find them. See the User Guide Chapter
|
|
6151
|
+
Install the missing tools and re-run, or set GHOSTSCRIPT_PATH, CHROMIUM_PATH, or system PATH so gutterpress can find them. See the User Guide Chapter 7 (System Setup) at examples/gutterpress-user-guide/07-system-setup.md for the full per-feature matrix.`, 2);
|
|
5909
6152
|
}
|
|
5910
6153
|
function computeGates(format, opts, config) {
|
|
5911
6154
|
if (format === "html") {
|
|
@@ -5999,6 +6242,15 @@ function resolveWithinRoot(relPath, root) {
|
|
|
5999
6242
|
}
|
|
6000
6243
|
return candidate;
|
|
6001
6244
|
}
|
|
6245
|
+
function hasDotSegment(urlPathname) {
|
|
6246
|
+
let decoded;
|
|
6247
|
+
try {
|
|
6248
|
+
decoded = decodeURIComponent(urlPathname);
|
|
6249
|
+
} catch {
|
|
6250
|
+
return true;
|
|
6251
|
+
}
|
|
6252
|
+
return decoded.split(/[/\\]/).some((segment) => segment.startsWith("."));
|
|
6253
|
+
}
|
|
6002
6254
|
function resolveStaticPath(urlPathname, root) {
|
|
6003
6255
|
let decoded;
|
|
6004
6256
|
try {
|
|
@@ -6397,14 +6649,25 @@ async function resolveBuildContext(opts) {
|
|
|
6397
6649
|
const outDir = target.kind === "file" ? path9.dirname(target.file) : target.dir;
|
|
6398
6650
|
const gates = computeGates(format, opts, config);
|
|
6399
6651
|
const workDir = target.kind === "project" ? path9.join(path9.dirname(target.dir), `.${path9.basename(target.dir)}-build-${randomBytes(6).toString("hex")}`) : path9.join(os2.tmpdir(), `gutterpress-build-${randomBytes(6).toString("hex")}`);
|
|
6400
|
-
return {
|
|
6652
|
+
return {
|
|
6653
|
+
opts,
|
|
6654
|
+
format,
|
|
6655
|
+
inputDir,
|
|
6656
|
+
renderDir: manifestDir,
|
|
6657
|
+
outDir,
|
|
6658
|
+
workDir,
|
|
6659
|
+
target,
|
|
6660
|
+
manifestDir,
|
|
6661
|
+
config,
|
|
6662
|
+
gates
|
|
6663
|
+
};
|
|
6401
6664
|
}
|
|
6402
6665
|
async function runQualityGates(ctx) {
|
|
6403
|
-
const { gates, opts,
|
|
6666
|
+
const { gates, opts, renderDir } = ctx;
|
|
6404
6667
|
if (gates.lint) {
|
|
6405
6668
|
log.info("Lint: CSS print-safety");
|
|
6406
6669
|
const lintResult = await runLint({
|
|
6407
|
-
manifest: opts.manifestPath ??
|
|
6670
|
+
manifest: opts.manifestPath ?? renderDir
|
|
6408
6671
|
});
|
|
6409
6672
|
if (!lintResult.ok) {
|
|
6410
6673
|
throw new BuildError("CSS lint failed", 2);
|
|
@@ -6413,7 +6676,7 @@ async function runQualityGates(ctx) {
|
|
|
6413
6676
|
if (gates.preValidate) {
|
|
6414
6677
|
log.info("Pre-build validation");
|
|
6415
6678
|
const result = await executeAndReport({
|
|
6416
|
-
input:
|
|
6679
|
+
input: renderDir,
|
|
6417
6680
|
phase: "pre-build",
|
|
6418
6681
|
manifest: opts.manifestPath
|
|
6419
6682
|
}, "text");
|
|
@@ -6423,7 +6686,7 @@ async function runQualityGates(ctx) {
|
|
|
6423
6686
|
}
|
|
6424
6687
|
}
|
|
6425
6688
|
async function renderBook(ctx) {
|
|
6426
|
-
const { config,
|
|
6689
|
+
const { config, renderDir, workDir } = ctx;
|
|
6427
6690
|
if (config.source.files && config.source.files.length > 0) {
|
|
6428
6691
|
log.info(`Using specified files (${config.source.files.length} total)`);
|
|
6429
6692
|
} else {
|
|
@@ -6432,13 +6695,13 @@ async function renderBook(ctx) {
|
|
|
6432
6695
|
if (config.plugins.length > 0) {
|
|
6433
6696
|
log.info(`Loading ${config.plugins.length} plugin(s)...`);
|
|
6434
6697
|
}
|
|
6435
|
-
const { plugins, pluginCss } = await loadPluginsWithCss(config.plugins,
|
|
6698
|
+
const { plugins, pluginCss } = await loadPluginsWithCss(config.plugins, renderDir);
|
|
6436
6699
|
if (plugins && plugins.length > 0) {
|
|
6437
6700
|
log.success(`Loaded ${plugins.length} plugin(s)`);
|
|
6438
6701
|
}
|
|
6439
6702
|
const imageRefs = [];
|
|
6440
6703
|
const cssAssets = [];
|
|
6441
|
-
const htmlFile = await renderChaptersToFile(
|
|
6704
|
+
const htmlFile = await renderChaptersToFile(renderDir, workDir, {
|
|
6442
6705
|
title: config.title,
|
|
6443
6706
|
styles: config.styles,
|
|
6444
6707
|
files: config.source.files,
|
|
@@ -6457,7 +6720,7 @@ async function renderBook(ctx) {
|
|
|
6457
6720
|
}
|
|
6458
6721
|
});
|
|
6459
6722
|
log.success(`Wrote ${htmlFile}`);
|
|
6460
|
-
const { copies: imageCopies, errors } = await planImageCopies(
|
|
6723
|
+
const { copies: imageCopies, errors } = await planImageCopies(renderDir, imageRefs);
|
|
6461
6724
|
if (errors.length > 0) {
|
|
6462
6725
|
throw new BuildError(`Cannot resolve ${errors.length} image reference(s):
|
|
6463
6726
|
` + errors.map((e) => ` - ${e}`).join(`
|
|
@@ -6496,7 +6759,8 @@ async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
|
|
|
6496
6759
|
async function finalizeBuild(ctx, fingerprint, wroteMessage, paths, artifactName2 = null) {
|
|
6497
6760
|
const workFingerprint = await writeBuildFingerprint({
|
|
6498
6761
|
...fingerprint,
|
|
6499
|
-
outputDir: ctx.workDir
|
|
6762
|
+
outputDir: ctx.workDir,
|
|
6763
|
+
recordedOutputDir: fingerprint.outputDir
|
|
6500
6764
|
});
|
|
6501
6765
|
await publishBuild(ctx, artifactName2);
|
|
6502
6766
|
const delivered = ctx.target.kind !== "file";
|
|
@@ -6587,7 +6851,9 @@ class PdfOutput {
|
|
|
6587
6851
|
await renderHtmlToPdf(htmlFile, rawPdf, opts.pdfRenderer, staticHtmlRaw);
|
|
6588
6852
|
if (staticHtmlRaw && fs2.existsSync(staticHtmlRaw)) {
|
|
6589
6853
|
await finalizeStaticBook(await fsp3.readFile(staticHtmlRaw, "utf-8"), htmlFile, workDir);
|
|
6590
|
-
|
|
6854
|
+
if (ctx.target.kind !== "file") {
|
|
6855
|
+
log.success(`Wrote static desktop: ${path9.join(outDir, BOOK_HTML)}`);
|
|
6856
|
+
}
|
|
6591
6857
|
}
|
|
6592
6858
|
if (!pdfxMode) {
|
|
6593
6859
|
try {
|
|
@@ -6749,6 +7015,7 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
6749
7015
|
plugins,
|
|
6750
7016
|
pluginCss,
|
|
6751
7017
|
wrapChapters: opts.wrapChapters,
|
|
7018
|
+
...opts.onCssAssets ? { onCssAssets: opts.onCssAssets } : {},
|
|
6752
7019
|
onChapterWarnings: (file, warnings) => {
|
|
6753
7020
|
for (const w of warnings) {
|
|
6754
7021
|
warn(` ${file}, line ${w.line}: ${w.message}`);
|
|
@@ -6756,23 +7023,70 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
6756
7023
|
}
|
|
6757
7024
|
});
|
|
6758
7025
|
}
|
|
6759
|
-
function injectPreviewScripts(html) {
|
|
7026
|
+
function injectPreviewScripts(html, pageIsolateChapters) {
|
|
6760
7027
|
const iface = `<script src="/preview/scripts/pagedjs-interface.js"></script>
|
|
6761
7028
|
` + `<script src="/preview/scripts/pagedjs-bridge.js"></script>
|
|
6762
7029
|
`;
|
|
6763
|
-
|
|
7030
|
+
let output = html.replace(pagedjsPolyfillTagRegex(), iface + BREAK_INSIDE_HANDLER + `
|
|
6764
7031
|
<script src="/vendor/paged.polyfill.js"></script>`);
|
|
7032
|
+
if (pageIsolateChapters && /<\/head>/i.test(output)) {
|
|
7033
|
+
output = output.replace(/<\/head>/i, `<style>.gutterpress-chapter{break-before:page}</style>
|
|
7034
|
+
</head>`);
|
|
7035
|
+
}
|
|
7036
|
+
return output;
|
|
6765
7037
|
}
|
|
6766
|
-
async function generateAndWriteHtml(inputPath, tempDir, config) {
|
|
7038
|
+
async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
6767
7039
|
if (!inputPath) {
|
|
7040
|
+
cssAssets.clear();
|
|
6768
7041
|
await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
|
|
6769
7042
|
return;
|
|
6770
7043
|
}
|
|
7044
|
+
const nextAssets = new Map;
|
|
7045
|
+
const incremental = incrementalPreviewEnabled();
|
|
6771
7046
|
const html = await renderPreviewBook(inputPath, config, {
|
|
6772
7047
|
files: config.source?.files ?? null,
|
|
7048
|
+
wrapChapters: incremental,
|
|
7049
|
+
onCssAssets: (copies) => {
|
|
7050
|
+
for (const copy of copies)
|
|
7051
|
+
nextAssets.set(copy.to, copy.from);
|
|
7052
|
+
}
|
|
7053
|
+
});
|
|
7054
|
+
cssAssets.clear();
|
|
7055
|
+
for (const [to, from] of nextAssets)
|
|
7056
|
+
cssAssets.set(to, from);
|
|
7057
|
+
await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, incremental), "utf-8");
|
|
7058
|
+
}
|
|
7059
|
+
async function renderChapterPreviewHtml(inputPath, file, config) {
|
|
7060
|
+
const html = await renderPreviewBook(inputPath, config, {
|
|
7061
|
+
files: [canonicalChapterId(file)],
|
|
6773
7062
|
wrapChapters: true
|
|
6774
7063
|
});
|
|
6775
|
-
|
|
7064
|
+
return injectPreviewScripts(html, true);
|
|
7065
|
+
}
|
|
7066
|
+
function describeChanges(changes, inputResolved) {
|
|
7067
|
+
const files = [];
|
|
7068
|
+
for (const [changedPath, event] of changes) {
|
|
7069
|
+
const relative2 = path10.relative(inputResolved, path10.resolve(changedPath));
|
|
7070
|
+
if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${path10.sep}`) || path10.isAbsolute(relative2))
|
|
7071
|
+
continue;
|
|
7072
|
+
files.push({
|
|
7073
|
+
relativePath: canonicalChapterId(relative2),
|
|
7074
|
+
ext: path10.extname(changedPath).toLowerCase(),
|
|
7075
|
+
event
|
|
7076
|
+
});
|
|
7077
|
+
}
|
|
7078
|
+
return files;
|
|
7079
|
+
}
|
|
7080
|
+
function decideBroadcast(files, changeCount, incremental) {
|
|
7081
|
+
const only = files.length === 1 ? files[0] : null;
|
|
7082
|
+
if (incremental && changeCount === 1 && only?.ext === ".md" && only.event !== "unlink" && only.event !== "unlinkDir") {
|
|
7083
|
+
return {
|
|
7084
|
+
kind: "chapter-splice",
|
|
7085
|
+
chapterId: canonicalChapterId(only.relativePath),
|
|
7086
|
+
relativePath: only.relativePath
|
|
7087
|
+
};
|
|
7088
|
+
}
|
|
7089
|
+
return { kind: "full-reload" };
|
|
6776
7090
|
}
|
|
6777
7091
|
function isDotPathUnderRoot(candidatePath, root) {
|
|
6778
7092
|
const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -6789,11 +7103,24 @@ function isDotPathUnderRoot(candidatePath, root) {
|
|
|
6789
7103
|
return false;
|
|
6790
7104
|
return rel.split("/").some((segment) => segment.startsWith("."));
|
|
6791
7105
|
}
|
|
7106
|
+
function isGeneratedProjectPath(relative2) {
|
|
7107
|
+
const segments = relative2.split("/");
|
|
7108
|
+
for (let i = 0;i < segments.length; i++) {
|
|
7109
|
+
const segment = segments[i];
|
|
7110
|
+
if (segment === "dist" || segment === "node_modules")
|
|
7111
|
+
return true;
|
|
7112
|
+
if (segment === "plugins" && segments[i + 1] === "npm")
|
|
7113
|
+
return true;
|
|
7114
|
+
}
|
|
7115
|
+
return false;
|
|
7116
|
+
}
|
|
6792
7117
|
function isIgnoredWatchPath(candidatePath, projectRoot) {
|
|
6793
7118
|
const normalizedRoot = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
6794
7119
|
const normalizedPath = candidatePath.replace(/\\/g, "/");
|
|
6795
|
-
|
|
6796
|
-
|
|
7120
|
+
if (!normalizedPath.startsWith(normalizedRoot + "/"))
|
|
7121
|
+
return false;
|
|
7122
|
+
const relative2 = normalizedPath.slice(normalizedRoot.length + 1);
|
|
7123
|
+
return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
|
|
6797
7124
|
}
|
|
6798
7125
|
function isExternalWatchCandidate(candidatePath, targets) {
|
|
6799
7126
|
const candidate = path10.resolve(candidatePath);
|
|
@@ -6878,21 +7205,43 @@ function createFileWatcher(state) {
|
|
|
6878
7205
|
let closed = false;
|
|
6879
7206
|
const pendingChanges = new Map;
|
|
6880
7207
|
const suppressInitialExternalAdds = new Set;
|
|
7208
|
+
const settledWrites = new Map;
|
|
7209
|
+
let immediatePending = false;
|
|
6881
7210
|
function scheduleRebuild() {
|
|
6882
7211
|
if (closed)
|
|
6883
7212
|
return;
|
|
6884
7213
|
if (state.rebuildTimer)
|
|
6885
7214
|
clearTimeout(state.rebuildTimer);
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
7215
|
+
const timer = setTimeout(() => {
|
|
7216
|
+
if (state.rebuildTimer === timer)
|
|
7217
|
+
state.rebuildTimer = null;
|
|
7218
|
+
runRebuild();
|
|
7219
|
+
}, DEBOUNCE.FILE_WATCH);
|
|
7220
|
+
state.rebuildTimer = timer;
|
|
7221
|
+
}
|
|
7222
|
+
async function recordChange(event, filePath2) {
|
|
6889
7223
|
if (closed)
|
|
6890
7224
|
return;
|
|
6891
|
-
|
|
6892
|
-
|
|
7225
|
+
const target = path10.resolve(filePath2);
|
|
7226
|
+
const settled = settledWrites.get(target);
|
|
7227
|
+
if (settled && event !== "unlink" && Date.now() <= settled.expiresAt) {
|
|
7228
|
+
const diskContent = await fsp4.readFile(target, "utf8").catch(() => null);
|
|
7229
|
+
if (closed)
|
|
7230
|
+
return;
|
|
7231
|
+
if (settledWrites.get(target) === settled && diskContent === settled.content) {
|
|
7232
|
+
debug(`Suppressed settled-write watcher echo: ${target}`);
|
|
7233
|
+
return;
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
if (settledWrites.get(target) === settled)
|
|
7237
|
+
settledWrites.delete(target);
|
|
7238
|
+
debug(`File ${event}: ${target}`);
|
|
7239
|
+
pendingChanges.set(target, event);
|
|
6893
7240
|
scheduleRebuild();
|
|
6894
7241
|
}
|
|
6895
|
-
watcher.on("all",
|
|
7242
|
+
watcher.on("all", (event, filePath2) => {
|
|
7243
|
+
recordChange(event, filePath2);
|
|
7244
|
+
});
|
|
6896
7245
|
externalWatcher.on("all", (event, filePath2) => {
|
|
6897
7246
|
const target = path10.resolve(filePath2);
|
|
6898
7247
|
if (!desiredExternals.has(target))
|
|
@@ -6901,6 +7250,43 @@ function createFileWatcher(state) {
|
|
|
6901
7250
|
return;
|
|
6902
7251
|
recordChange(event, target);
|
|
6903
7252
|
});
|
|
7253
|
+
function isWatchedSource(target) {
|
|
7254
|
+
const rel = path10.relative(inputResolved, target);
|
|
7255
|
+
const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path10.sep}`) && !path10.isAbsolute(rel);
|
|
7256
|
+
if (inProject)
|
|
7257
|
+
return !isIgnoredWatchPath(target, inputResolved);
|
|
7258
|
+
return desiredExternals.has(target);
|
|
7259
|
+
}
|
|
7260
|
+
function acceptSettledWrite(target, writtenContent) {
|
|
7261
|
+
const settled = { content: writtenContent, expiresAt: Date.now() + 2000 };
|
|
7262
|
+
settledWrites.set(target, settled);
|
|
7263
|
+
const expiry = setTimeout(() => {
|
|
7264
|
+
if (settledWrites.get(target) === settled)
|
|
7265
|
+
settledWrites.delete(target);
|
|
7266
|
+
}, 2000);
|
|
7267
|
+
expiry.unref?.();
|
|
7268
|
+
pendingChanges.set(target, "change");
|
|
7269
|
+
immediatePending = true;
|
|
7270
|
+
if (state.rebuildTimer) {
|
|
7271
|
+
clearTimeout(state.rebuildTimer);
|
|
7272
|
+
state.rebuildTimer = null;
|
|
7273
|
+
}
|
|
7274
|
+
if (!state.isRebuilding)
|
|
7275
|
+
runRebuild();
|
|
7276
|
+
}
|
|
7277
|
+
state.notifySettledWrite = (filePath2, writtenContent) => {
|
|
7278
|
+
if (closed)
|
|
7279
|
+
return;
|
|
7280
|
+
const target = path10.resolve(filePath2);
|
|
7281
|
+
if (isWatchedSource(target)) {
|
|
7282
|
+
acceptSettledWrite(target, writtenContent);
|
|
7283
|
+
return;
|
|
7284
|
+
}
|
|
7285
|
+
syncQueue.then(() => {
|
|
7286
|
+
if (!closed && isWatchedSource(target))
|
|
7287
|
+
acceptSettledWrite(target, writtenContent);
|
|
7288
|
+
});
|
|
7289
|
+
};
|
|
6904
7290
|
let watchedExternalRoots = new Set;
|
|
6905
7291
|
let syncQueue = Promise.resolve();
|
|
6906
7292
|
function syncExternalWatches() {
|
|
@@ -6952,10 +7338,15 @@ function createFileWatcher(state) {
|
|
|
6952
7338
|
async function runRebuild() {
|
|
6953
7339
|
if (closed || state.isRebuilding)
|
|
6954
7340
|
return;
|
|
7341
|
+
if (state.rebuildTimer) {
|
|
7342
|
+
clearTimeout(state.rebuildTimer);
|
|
7343
|
+
state.rebuildTimer = null;
|
|
7344
|
+
}
|
|
6955
7345
|
const changes = [...pendingChanges.entries()];
|
|
6956
7346
|
pendingChanges.clear();
|
|
6957
7347
|
if (changes.length === 0)
|
|
6958
7348
|
return;
|
|
7349
|
+
immediatePending = false;
|
|
6959
7350
|
state.isRebuilding = true;
|
|
6960
7351
|
let resolveInFlight;
|
|
6961
7352
|
const inFlight = new Promise((resolve9) => {
|
|
@@ -6972,11 +7363,17 @@ function createFileWatcher(state) {
|
|
|
6972
7363
|
await syncExternalWatches();
|
|
6973
7364
|
if (closed)
|
|
6974
7365
|
return;
|
|
6975
|
-
await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig);
|
|
7366
|
+
await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig, state.cssAssets);
|
|
6976
7367
|
if (closed)
|
|
6977
7368
|
return;
|
|
6978
|
-
|
|
6979
|
-
|
|
7369
|
+
const decision = decideBroadcast(describeChanges(changes, inputResolved), changes.length, incrementalPreviewEnabled());
|
|
7370
|
+
if (decision.kind === "chapter-splice") {
|
|
7371
|
+
state.previewServer?.broadcastContentUpdate(decision.chapterId);
|
|
7372
|
+
info(`Chapter updated: ${decision.relativePath}`);
|
|
7373
|
+
} else {
|
|
7374
|
+
state.previewServer?.broadcastReload();
|
|
7375
|
+
info(changes.length > 1 ? `Preview updated (${changes.length} files changed — full reload)` : "Preview updated");
|
|
7376
|
+
}
|
|
6980
7377
|
} catch (err) {
|
|
6981
7378
|
error("Failed to regenerate preview:", err);
|
|
6982
7379
|
} finally {
|
|
@@ -6984,13 +7381,19 @@ function createFileWatcher(state) {
|
|
|
6984
7381
|
resolveInFlight();
|
|
6985
7382
|
if (state.rebuildPromise === inFlight)
|
|
6986
7383
|
state.rebuildPromise = null;
|
|
6987
|
-
if (!closed && pendingChanges.size > 0)
|
|
6988
|
-
|
|
7384
|
+
if (!closed && pendingChanges.size > 0) {
|
|
7385
|
+
if (immediatePending)
|
|
7386
|
+
runRebuild();
|
|
7387
|
+
else
|
|
7388
|
+
scheduleRebuild();
|
|
7389
|
+
}
|
|
6989
7390
|
}
|
|
6990
7391
|
}
|
|
6991
7392
|
const closeBookWatcher = watcher.close.bind(watcher);
|
|
6992
7393
|
watcher.close = async () => {
|
|
6993
7394
|
closed = true;
|
|
7395
|
+
state.notifySettledWrite = null;
|
|
7396
|
+
settledWrites.clear();
|
|
6994
7397
|
await syncQueue.catch(() => {});
|
|
6995
7398
|
await state.rebuildPromise?.catch(() => {});
|
|
6996
7399
|
await externalWatcher.close();
|
|
@@ -7089,7 +7492,7 @@ async function restartPreview(newInputPath, state) {
|
|
|
7089
7492
|
state.currentInputPath = newInputPath;
|
|
7090
7493
|
const manifest = await loadManifest(newInputPath);
|
|
7091
7494
|
state.config = resolveConfig({}, manifest);
|
|
7092
|
-
await generateAndWriteHtml(newInputPath, state.tempDir, state.config);
|
|
7495
|
+
await generateAndWriteHtml(newInputPath, state.tempDir, state.config, state.cssAssets);
|
|
7093
7496
|
startFileWatcher(state);
|
|
7094
7497
|
state.previewServer?.broadcastReload();
|
|
7095
7498
|
info("Preview restarted successfully");
|
|
@@ -7143,22 +7546,26 @@ function createServerState(inputPath, tempDir, config, options) {
|
|
|
7143
7546
|
rebuildTimer: null,
|
|
7144
7547
|
isRebuilding: false,
|
|
7145
7548
|
rebuildPromise: null,
|
|
7549
|
+
notifySettledWrite: null,
|
|
7146
7550
|
previewServer: null,
|
|
7147
7551
|
isShuttingDown: false,
|
|
7148
7552
|
tempDir,
|
|
7149
7553
|
config,
|
|
7150
|
-
options
|
|
7554
|
+
options,
|
|
7555
|
+
cssAssets: new Map
|
|
7151
7556
|
};
|
|
7152
7557
|
}
|
|
7153
7558
|
|
|
7154
7559
|
// src/preview/http-server.ts
|
|
7155
7560
|
import http2 from "node:http";
|
|
7156
7561
|
import net from "node:net";
|
|
7562
|
+
import { randomUUID } from "node:crypto";
|
|
7157
7563
|
import { readFile as readFile20, stat as stat4 } from "node:fs/promises";
|
|
7158
7564
|
import path12 from "path";
|
|
7159
7565
|
import { WebSocket, WebSocketServer } from "ws";
|
|
7160
7566
|
var HMR_PATH = "/__gutterpress-hmr";
|
|
7161
|
-
|
|
7567
|
+
function hmrClientSnippet(initialRevision, instanceId) {
|
|
7568
|
+
return `
|
|
7162
7569
|
<script>
|
|
7163
7570
|
(function () {
|
|
7164
7571
|
// When loaded by the preview SHELL (iframe double-buffer), the shell loads us
|
|
@@ -7168,6 +7575,10 @@ var HMR_CLIENT_SNIPPET = `
|
|
|
7168
7575
|
// for scroll-anchor and reload.
|
|
7169
7576
|
if (/[?&]gutterpressshell=1/.test(location.search)) return;
|
|
7170
7577
|
var ANCHOR_KEY = 'gutterpress-scroll-anchor';
|
|
7578
|
+
var appliedRevision = ${initialRevision};
|
|
7579
|
+
var appliedInstance = ${JSON.stringify(instanceId)};
|
|
7580
|
+
var readyToAcknowledge = false;
|
|
7581
|
+
var reloadRequested = false;
|
|
7171
7582
|
|
|
7172
7583
|
// Find the element nearest the top of the viewport that carries a source
|
|
7173
7584
|
// line (markdown-it-source-map emits data-source-line on block elements).
|
|
@@ -7218,7 +7629,13 @@ var HMR_CLIENT_SNIPPET = `
|
|
|
7218
7629
|
}
|
|
7219
7630
|
}
|
|
7220
7631
|
if (el) {
|
|
7221
|
-
window.scrollBy(
|
|
7632
|
+
window.scrollBy({
|
|
7633
|
+
top: el.getBoundingClientRect().top - a.offset,
|
|
7634
|
+
behavior: 'instant'
|
|
7635
|
+
});
|
|
7636
|
+
if (window.previewAPI && typeof window.previewAPI.refresh === 'function') {
|
|
7637
|
+
window.previewAPI.refresh();
|
|
7638
|
+
}
|
|
7222
7639
|
return;
|
|
7223
7640
|
}
|
|
7224
7641
|
if (tries++ < 240) setTimeout(attempt, 25); // wait for pagination
|
|
@@ -7226,44 +7643,100 @@ var HMR_CLIENT_SNIPPET = `
|
|
|
7226
7643
|
}
|
|
7227
7644
|
var restored = false;
|
|
7228
7645
|
function restoreOnce() { if (restored) return; restored = true; restoreAnchor(); }
|
|
7646
|
+
function finishInitialRender() {
|
|
7647
|
+
restoreOnce();
|
|
7648
|
+
readyToAcknowledge = true;
|
|
7649
|
+
acknowledge();
|
|
7650
|
+
}
|
|
7229
7651
|
// CRITICAL ordering: when the Paged.js polyfill is present it re-paginates on
|
|
7230
|
-
// load and its PagedConfig.after
|
|
7652
|
+
// load and its PagedConfig.after scrolls to the document start THEN fires
|
|
7231
7653
|
// 'renderingComplete'. So in engine mode we MUST wait for that event —
|
|
7232
|
-
// restoring earlier would be wiped by
|
|
7654
|
+
// restoring earlier would be wiped by that initial scroll. In static mode (no
|
|
7233
7655
|
// engine) the content is final immediately, so restore right after load.
|
|
7234
|
-
var hasEngine = !!document.querySelector('script[src*="paged.polyfill"]
|
|
7235
|
-
window.addEventListener('renderingComplete',
|
|
7656
|
+
var hasEngine = !!document.querySelector('script[src*="paged.polyfill"]');
|
|
7657
|
+
window.addEventListener('renderingComplete', finishInitialRender);
|
|
7236
7658
|
if (!hasEngine) {
|
|
7237
7659
|
if (document.readyState === 'loading') {
|
|
7238
|
-
document.addEventListener('DOMContentLoaded', function () { setTimeout(
|
|
7660
|
+
document.addEventListener('DOMContentLoaded', function () { setTimeout(finishInitialRender, 50); });
|
|
7239
7661
|
} else {
|
|
7240
|
-
setTimeout(
|
|
7662
|
+
setTimeout(finishInitialRender, 50);
|
|
7241
7663
|
}
|
|
7242
7664
|
}
|
|
7243
|
-
|
|
7665
|
+
var ws = null;
|
|
7666
|
+
var reconnectTimer = null;
|
|
7667
|
+
var reconnectDelay = 250;
|
|
7668
|
+
var stopped = false;
|
|
7669
|
+
|
|
7670
|
+
function acknowledge() {
|
|
7671
|
+
if (!readyToAcknowledge || !ws || ws.readyState !== 1) return;
|
|
7672
|
+
try {
|
|
7673
|
+
ws.send(JSON.stringify({
|
|
7674
|
+
type: 'reload-applied',
|
|
7675
|
+
instance: appliedInstance,
|
|
7676
|
+
revision: appliedRevision
|
|
7677
|
+
}));
|
|
7678
|
+
} catch (_) {}
|
|
7679
|
+
}
|
|
7244
7680
|
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7681
|
+
function connect() {
|
|
7682
|
+
if (stopped) return;
|
|
7683
|
+
ws = new WebSocket(location.origin.replace(/^http/, 'ws') + '${HMR_PATH}');
|
|
7684
|
+
ws.onopen = function () {
|
|
7685
|
+
reconnectDelay = 250;
|
|
7686
|
+
acknowledge();
|
|
7687
|
+
};
|
|
7688
|
+
ws.onmessage = function (e) {
|
|
7689
|
+
var msg;
|
|
7690
|
+
try { msg = JSON.parse(e.data); } catch (_) { return; }
|
|
7691
|
+
if (
|
|
7692
|
+
msg.type !== 'reload-state' &&
|
|
7693
|
+
msg.type !== 'full-reload' &&
|
|
7694
|
+
msg.type !== 'content-update'
|
|
7695
|
+
) return;
|
|
7696
|
+
var instance = typeof msg.instance === 'string' ? msg.instance : null;
|
|
7697
|
+
var revision = Number(msg.revision);
|
|
7698
|
+
if (!instance || !Number.isSafeInteger(revision) || revision < 0) return;
|
|
7699
|
+
if (instance === appliedInstance && revision <= appliedRevision) {
|
|
7700
|
+
acknowledge();
|
|
7701
|
+
return;
|
|
7702
|
+
}
|
|
7703
|
+
if (reloadRequested) return;
|
|
7704
|
+
reloadRequested = true;
|
|
7250
7705
|
try { var a = captureAnchor(); if (a) sessionStorage.setItem(ANCHOR_KEY, JSON.stringify(a)); } catch (_) {}
|
|
7251
7706
|
location.reload();
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7707
|
+
};
|
|
7708
|
+
ws.onclose = function () {
|
|
7709
|
+
ws = null;
|
|
7710
|
+
if (stopped || reconnectTimer !== null) return;
|
|
7711
|
+
reconnectTimer = setTimeout(function () {
|
|
7712
|
+
reconnectTimer = null;
|
|
7713
|
+
connect();
|
|
7714
|
+
}, reconnectDelay);
|
|
7715
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 5000);
|
|
7716
|
+
};
|
|
7717
|
+
ws.onerror = function () { try { ws.close(); } catch (_) {} };
|
|
7718
|
+
}
|
|
7719
|
+
|
|
7720
|
+
window.addEventListener('beforeunload', function () {
|
|
7721
|
+
stopped = true;
|
|
7722
|
+
if (reconnectTimer !== null) clearTimeout(reconnectTimer);
|
|
7723
|
+
});
|
|
7724
|
+
connect();
|
|
7255
7725
|
})();
|
|
7256
7726
|
</script>
|
|
7257
7727
|
`;
|
|
7258
|
-
|
|
7728
|
+
}
|
|
7729
|
+
function shellHtml(initialRevision, instanceId) {
|
|
7730
|
+
return `<!doctype html>
|
|
7259
7731
|
<html lang="en"><head><meta charset="utf-8"><title>gutterpress preview</title>
|
|
7260
7732
|
<style>html,body{margin:0;height:100%;background:#fff;overflow:hidden}
|
|
7261
7733
|
iframe{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}</style>
|
|
7262
7734
|
</head><body>
|
|
7263
7735
|
<iframe id="gutterpress-active" src="/book.html?gutterpressshell=1" title="preview"></iframe>
|
|
7264
|
-
<script>window.__GUTTERPRESS_HMR=${JSON.stringify(HMR_PATH)};</script>
|
|
7736
|
+
<script>window.__GUTTERPRESS_HMR=${JSON.stringify(HMR_PATH)};window.__GUTTERPRESS_INSTANCE=${JSON.stringify(instanceId)};window.__GUTTERPRESS_REVISION=${initialRevision};</script>
|
|
7265
7737
|
<script src="/preview/scripts/preview-shell.js"></script>
|
|
7266
7738
|
</body></html>`;
|
|
7739
|
+
}
|
|
7267
7740
|
function probePort(port, host) {
|
|
7268
7741
|
return new Promise((resolve9) => {
|
|
7269
7742
|
const srv = net.createServer();
|
|
@@ -7305,13 +7778,14 @@ async function findAvailablePort(startPort, host = "127.0.0.1") {
|
|
|
7305
7778
|
}
|
|
7306
7779
|
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 });
|
|
7307
7780
|
}
|
|
7308
|
-
function injectHmrClient(html) {
|
|
7781
|
+
function injectHmrClient(html, revision, instanceId) {
|
|
7782
|
+
const snippet = hmrClientSnippet(revision, instanceId);
|
|
7309
7783
|
const closingBody = html.lastIndexOf("</body>");
|
|
7310
7784
|
if (closingBody === -1)
|
|
7311
|
-
return html +
|
|
7312
|
-
return html.slice(0, closingBody) +
|
|
7785
|
+
return html + snippet;
|
|
7786
|
+
return html.slice(0, closingBody) + snippet + html.slice(closingBody);
|
|
7313
7787
|
}
|
|
7314
|
-
async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders = {}) {
|
|
7788
|
+
async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders = {}, hmrRevision = 0, hmrInstance = "") {
|
|
7315
7789
|
let filePath2 = absPath;
|
|
7316
7790
|
try {
|
|
7317
7791
|
const s = await stat4(filePath2);
|
|
@@ -7332,7 +7806,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
7332
7806
|
}
|
|
7333
7807
|
const isHtml = filePath2.endsWith(".html") || filePath2.endsWith(".htm");
|
|
7334
7808
|
if (isHtml) {
|
|
7335
|
-
const withHmr = injectHmrClient(data.toString("utf-8"));
|
|
7809
|
+
const withHmr = injectHmrClient(data.toString("utf-8"), hmrRevision, hmrInstance);
|
|
7336
7810
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
7337
7811
|
res.end(withHmr);
|
|
7338
7812
|
return;
|
|
@@ -7350,21 +7824,89 @@ function matchesEmbedded(urlPathname) {
|
|
|
7350
7824
|
return true;
|
|
7351
7825
|
return EMBEDDED_PREFIXES.some((p) => urlPathname.startsWith(p));
|
|
7352
7826
|
}
|
|
7353
|
-
function isDotfileRequest(urlPathname) {
|
|
7354
|
-
let decoded;
|
|
7355
|
-
try {
|
|
7356
|
-
decoded = decodeURIComponent(urlPathname);
|
|
7357
|
-
} catch {
|
|
7358
|
-
return true;
|
|
7359
|
-
}
|
|
7360
|
-
return decoded.split("/").some((segment) => segment.startsWith("."));
|
|
7361
|
-
}
|
|
7362
7827
|
async function createPreviewServer(state, port) {
|
|
7363
|
-
const
|
|
7828
|
+
const ACK_RETRY_MS = 2000;
|
|
7829
|
+
const MAX_ACK_RETRIES = 95;
|
|
7830
|
+
const instanceId = randomUUID();
|
|
7831
|
+
let reloadRevision = 0;
|
|
7832
|
+
const clients = new Map;
|
|
7833
|
+
function sendRevision(ws, type, file) {
|
|
7834
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
7835
|
+
return;
|
|
7836
|
+
try {
|
|
7837
|
+
ws.send(JSON.stringify({
|
|
7838
|
+
type,
|
|
7839
|
+
instance: instanceId,
|
|
7840
|
+
revision: reloadRevision,
|
|
7841
|
+
...file ? { file } : {}
|
|
7842
|
+
}));
|
|
7843
|
+
} catch {}
|
|
7844
|
+
}
|
|
7845
|
+
function waitForAcknowledgement(ws) {
|
|
7846
|
+
const client = clients.get(ws);
|
|
7847
|
+
if (!client)
|
|
7848
|
+
return;
|
|
7849
|
+
if (client.acknowledgedRevision >= reloadRevision) {
|
|
7850
|
+
if (client.retryTimer !== null)
|
|
7851
|
+
clearTimeout(client.retryTimer);
|
|
7852
|
+
client.retryTimer = null;
|
|
7853
|
+
client.retryAttempts = 0;
|
|
7854
|
+
return;
|
|
7855
|
+
}
|
|
7856
|
+
if (client.retryTimer !== null)
|
|
7857
|
+
return;
|
|
7858
|
+
client.retryTimer = setTimeout(() => {
|
|
7859
|
+
const current = clients.get(ws);
|
|
7860
|
+
if (!current)
|
|
7861
|
+
return;
|
|
7862
|
+
current.retryTimer = null;
|
|
7863
|
+
if (current.retryAttempts >= MAX_ACK_RETRIES) {
|
|
7864
|
+
ws.terminate();
|
|
7865
|
+
return;
|
|
7866
|
+
}
|
|
7867
|
+
current.retryAttempts++;
|
|
7868
|
+
sendRevision(ws, "reload-state");
|
|
7869
|
+
waitForAcknowledgement(ws);
|
|
7870
|
+
}, ACK_RETRY_MS);
|
|
7871
|
+
client.retryTimer.unref?.();
|
|
7872
|
+
}
|
|
7873
|
+
function removeClient(ws) {
|
|
7874
|
+
const client = clients.get(ws);
|
|
7875
|
+
if (client && client.retryTimer !== null)
|
|
7876
|
+
clearTimeout(client.retryTimer);
|
|
7877
|
+
clients.delete(ws);
|
|
7878
|
+
}
|
|
7364
7879
|
const wss = new WebSocketServer({ noServer: true });
|
|
7365
7880
|
wss.on("connection", (ws) => {
|
|
7366
|
-
clients.
|
|
7367
|
-
ws.on("
|
|
7881
|
+
clients.set(ws, { acknowledgedRevision: -1, retryAttempts: 0, retryTimer: null });
|
|
7882
|
+
ws.on("message", (raw) => {
|
|
7883
|
+
let message;
|
|
7884
|
+
try {
|
|
7885
|
+
message = JSON.parse(raw.toString());
|
|
7886
|
+
} catch {
|
|
7887
|
+
return;
|
|
7888
|
+
}
|
|
7889
|
+
if (!message || typeof message !== "object")
|
|
7890
|
+
return;
|
|
7891
|
+
const payload = message;
|
|
7892
|
+
if (payload.type !== "reload-applied")
|
|
7893
|
+
return;
|
|
7894
|
+
const revision = Number(payload.revision);
|
|
7895
|
+
if (payload.instance !== instanceId || !Number.isSafeInteger(revision) || revision < 0 || revision > reloadRevision)
|
|
7896
|
+
return;
|
|
7897
|
+
const client = clients.get(ws);
|
|
7898
|
+
if (!client)
|
|
7899
|
+
return;
|
|
7900
|
+
client.acknowledgedRevision = Math.max(client.acknowledgedRevision, revision);
|
|
7901
|
+
waitForAcknowledgement(ws);
|
|
7902
|
+
});
|
|
7903
|
+
ws.on("close", () => removeClient(ws));
|
|
7904
|
+
ws.on("error", () => {
|
|
7905
|
+
removeClient(ws);
|
|
7906
|
+
ws.terminate();
|
|
7907
|
+
});
|
|
7908
|
+
sendRevision(ws, "reload-state");
|
|
7909
|
+
waitForAcknowledgement(ws);
|
|
7368
7910
|
});
|
|
7369
7911
|
const server = http2.createServer(async (req, res) => {
|
|
7370
7912
|
const url = new URL(req.url, "http://127.0.0.1");
|
|
@@ -7387,6 +7929,34 @@ async function createPreviewServer(state, port) {
|
|
|
7387
7929
|
res.end("Not Found");
|
|
7388
7930
|
return;
|
|
7389
7931
|
}
|
|
7932
|
+
if (url.pathname === "/__chapter" && req.method === "GET") {
|
|
7933
|
+
const rawFile = url.searchParams.get("file");
|
|
7934
|
+
const file = rawFile ? canonicalChapterId(rawFile) : "";
|
|
7935
|
+
const revision = Number(url.searchParams.get("revision"));
|
|
7936
|
+
const configuredFiles = state.config.source?.files;
|
|
7937
|
+
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)) {
|
|
7938
|
+
res.writeHead(400);
|
|
7939
|
+
res.end("Bad Request");
|
|
7940
|
+
return;
|
|
7941
|
+
}
|
|
7942
|
+
try {
|
|
7943
|
+
const html = await renderChapterPreviewHtml(state.currentInputPath, file, state.config);
|
|
7944
|
+
if (revision !== reloadRevision) {
|
|
7945
|
+
res.writeHead(409);
|
|
7946
|
+
res.end("Superseded");
|
|
7947
|
+
return;
|
|
7948
|
+
}
|
|
7949
|
+
res.writeHead(200, {
|
|
7950
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
7951
|
+
"Cache-Control": "no-store"
|
|
7952
|
+
});
|
|
7953
|
+
res.end(html);
|
|
7954
|
+
} catch (error2) {
|
|
7955
|
+
res.writeHead(500);
|
|
7956
|
+
res.end(error2 instanceof Error ? error2.message : String(error2));
|
|
7957
|
+
}
|
|
7958
|
+
return;
|
|
7959
|
+
}
|
|
7390
7960
|
if (matchesEmbedded(url.pathname)) {
|
|
7391
7961
|
if (req.headers["if-none-match"] === EMBEDDED_ETAG) {
|
|
7392
7962
|
res.writeHead(304, { ETag: EMBEDDED_ETAG, "Cache-Control": EMBEDDED_CACHE_CONTROL });
|
|
@@ -7405,12 +7975,22 @@ async function createPreviewServer(state, port) {
|
|
|
7405
7975
|
}
|
|
7406
7976
|
if (url.pathname === "/" && incrementalPreviewEnabled()) {
|
|
7407
7977
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
7408
|
-
res.end(
|
|
7978
|
+
res.end(shellHtml(reloadRevision, instanceId));
|
|
7409
7979
|
return;
|
|
7410
7980
|
}
|
|
7411
7981
|
const pathname = url.pathname === "/" ? "/book.html" : url.pathname;
|
|
7982
|
+
const cssAssetSource = state.cssAssets.get(pathname.replace(/^\/+/, ""));
|
|
7983
|
+
if (cssAssetSource) {
|
|
7984
|
+
await serveStatic(cssAssetSource, res).catch((err) => {
|
|
7985
|
+
if (!res.headersSent) {
|
|
7986
|
+
res.writeHead(500);
|
|
7987
|
+
res.end(`Internal Server Error: ${err.message}`);
|
|
7988
|
+
}
|
|
7989
|
+
});
|
|
7990
|
+
return;
|
|
7991
|
+
}
|
|
7412
7992
|
const servingProjectRoot = pathname !== "/book.html";
|
|
7413
|
-
if (servingProjectRoot && (!state.currentInputPath ||
|
|
7993
|
+
if (servingProjectRoot && (!state.currentInputPath || hasDotSegment(pathname))) {
|
|
7414
7994
|
res.writeHead(404);
|
|
7415
7995
|
res.end("Not Found");
|
|
7416
7996
|
return;
|
|
@@ -7422,7 +8002,7 @@ async function createPreviewServer(state, port) {
|
|
|
7422
8002
|
res.end("Not Found");
|
|
7423
8003
|
return;
|
|
7424
8004
|
}
|
|
7425
|
-
await serveStatic(absPath, res).catch((err) => {
|
|
8005
|
+
await serveStatic(absPath, res, "no-store", {}, reloadRevision, instanceId).catch((err) => {
|
|
7426
8006
|
if (!res.headersSent) {
|
|
7427
8007
|
res.writeHead(500);
|
|
7428
8008
|
res.end(`Internal Server Error: ${err.message}`);
|
|
@@ -7449,23 +8029,33 @@ async function createPreviewServer(state, port) {
|
|
|
7449
8029
|
openPath(serverUrl).catch(() => {});
|
|
7450
8030
|
}
|
|
7451
8031
|
let stopped = false;
|
|
7452
|
-
const
|
|
8032
|
+
const broadcastUpdate = (type, file) => {
|
|
7453
8033
|
if (stopped)
|
|
7454
8034
|
return;
|
|
7455
|
-
|
|
7456
|
-
for (const ws of clients) {
|
|
7457
|
-
|
|
7458
|
-
|
|
8035
|
+
reloadRevision++;
|
|
8036
|
+
for (const [ws] of clients) {
|
|
8037
|
+
sendRevision(ws, type, file);
|
|
8038
|
+
waitForAcknowledgement(ws);
|
|
7459
8039
|
}
|
|
7460
8040
|
};
|
|
8041
|
+
const updateInFlight = () => {
|
|
8042
|
+
for (const client of clients.values()) {
|
|
8043
|
+
if (client.acknowledgedRevision < reloadRevision)
|
|
8044
|
+
return true;
|
|
8045
|
+
}
|
|
8046
|
+
return false;
|
|
8047
|
+
};
|
|
7461
8048
|
return {
|
|
7462
8049
|
port: boundPort,
|
|
7463
8050
|
async close() {
|
|
7464
8051
|
if (stopped)
|
|
7465
8052
|
return;
|
|
7466
8053
|
stopped = true;
|
|
7467
|
-
for (const ws of clients)
|
|
8054
|
+
for (const [ws, client] of clients) {
|
|
8055
|
+
if (client.retryTimer !== null)
|
|
8056
|
+
clearTimeout(client.retryTimer);
|
|
7468
8057
|
ws.terminate();
|
|
8058
|
+
}
|
|
7469
8059
|
clients.clear();
|
|
7470
8060
|
wss.close();
|
|
7471
8061
|
await new Promise((resolve9) => {
|
|
@@ -7476,7 +8066,13 @@ async function createPreviewServer(state, port) {
|
|
|
7476
8066
|
});
|
|
7477
8067
|
},
|
|
7478
8068
|
broadcastReload() {
|
|
7479
|
-
|
|
8069
|
+
broadcastUpdate("full-reload");
|
|
8070
|
+
},
|
|
8071
|
+
broadcastContentUpdate(file) {
|
|
8072
|
+
if (updateInFlight())
|
|
8073
|
+
broadcastUpdate("full-reload");
|
|
8074
|
+
else
|
|
8075
|
+
broadcastUpdate("content-update", canonicalChapterId(file));
|
|
7480
8076
|
}
|
|
7481
8077
|
};
|
|
7482
8078
|
}
|
|
@@ -7495,8 +8091,8 @@ async function startPreviewServer(options) {
|
|
|
7495
8091
|
}
|
|
7496
8092
|
const config = await initializeConfiguration(inputPath);
|
|
7497
8093
|
const tempDir = await initializePreviewDirectories();
|
|
7498
|
-
await generateAndWriteHtml(inputPath, tempDir, config);
|
|
7499
8094
|
const state = createServerState(inputPath, tempDir, config, options);
|
|
8095
|
+
await generateAndWriteHtml(inputPath, tempDir, config, state.cssAssets);
|
|
7500
8096
|
const restartPreview2 = async (newInputPath) => {
|
|
7501
8097
|
await restartPreview(newInputPath, state);
|
|
7502
8098
|
};
|
|
@@ -7529,17 +8125,74 @@ async function startPreviewServer(options) {
|
|
|
7529
8125
|
}
|
|
7530
8126
|
await shutdownServer(state);
|
|
7531
8127
|
},
|
|
7532
|
-
restart: restartPreview2
|
|
8128
|
+
restart: restartPreview2,
|
|
8129
|
+
notifySettledWrite: (filePath2, writtenContent) => {
|
|
8130
|
+
state.notifySettledWrite?.(filePath2, writtenContent);
|
|
8131
|
+
}
|
|
7533
8132
|
};
|
|
7534
8133
|
}
|
|
7535
8134
|
|
|
7536
8135
|
// src/lib/project-scaffold.ts
|
|
7537
|
-
import { access, copyFile, cp as cp2, mkdir as
|
|
7538
|
-
import { constants as FS, existsSync as
|
|
8136
|
+
import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile22, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
|
|
8137
|
+
import { constants as FS, existsSync as existsSync11 } from "node:fs";
|
|
8138
|
+
import path14 from "node:path";
|
|
8139
|
+
|
|
8140
|
+
// src/lib/manifest-doc.ts
|
|
8141
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
8142
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8143
|
+
import { mkdir as mkdir6, open, readFile as readFile21, rename, rm as rm2 } from "node:fs/promises";
|
|
7539
8144
|
import path13 from "node:path";
|
|
8145
|
+
import { parseDocument, isSeq, YAMLSeq } from "yaml";
|
|
8146
|
+
function resolveManifestPath(projectDir) {
|
|
8147
|
+
const existing = MANIFEST_FILENAMES.find((name) => existsSync10(path13.join(projectDir, name)));
|
|
8148
|
+
return path13.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
|
|
8149
|
+
}
|
|
8150
|
+
async function loadManifestDoc(projectDir) {
|
|
8151
|
+
const file = resolveManifestPath(projectDir);
|
|
8152
|
+
let text = "";
|
|
8153
|
+
try {
|
|
8154
|
+
text = await readFile21(file, "utf8");
|
|
8155
|
+
} catch (error2) {
|
|
8156
|
+
if (error2.code !== "ENOENT")
|
|
8157
|
+
throw error2;
|
|
8158
|
+
}
|
|
8159
|
+
return { doc: parseDocument(text), file };
|
|
8160
|
+
}
|
|
8161
|
+
async function writeManifestDoc(file, doc) {
|
|
8162
|
+
const dir = path13.dirname(file);
|
|
8163
|
+
await mkdir6(dir, { recursive: true });
|
|
8164
|
+
const temporary = path13.join(dir, `.${path13.basename(file)}.tmp-${process.pid}-${randomUUID2()}`);
|
|
8165
|
+
try {
|
|
8166
|
+
const handle = await open(temporary, "wx", 438);
|
|
8167
|
+
try {
|
|
8168
|
+
await handle.writeFile(doc.toString(), "utf8");
|
|
8169
|
+
await handle.sync();
|
|
8170
|
+
} finally {
|
|
8171
|
+
await handle.close();
|
|
8172
|
+
}
|
|
8173
|
+
await rename(temporary, file);
|
|
8174
|
+
const directory = await open(dir, "r").catch(() => null);
|
|
8175
|
+
if (directory) {
|
|
8176
|
+
await directory.sync().catch(() => {});
|
|
8177
|
+
await directory.close();
|
|
8178
|
+
}
|
|
8179
|
+
} finally {
|
|
8180
|
+
await rm2(temporary, { force: true });
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
function ensureSeq(doc, key) {
|
|
8184
|
+
let seq = doc.get(key, true);
|
|
8185
|
+
if (!isSeq(seq)) {
|
|
8186
|
+
const fresh = new YAMLSeq(doc.schema);
|
|
8187
|
+
doc.set(key, fresh);
|
|
8188
|
+
seq = fresh;
|
|
8189
|
+
}
|
|
8190
|
+
return seq;
|
|
8191
|
+
}
|
|
8192
|
+
|
|
8193
|
+
// src/lib/project-scaffold.ts
|
|
7540
8194
|
var STARTER_THEME_FOR_TEMPLATE = {
|
|
7541
8195
|
book: "clean-book",
|
|
7542
|
-
ttrpg: "ttrpg-supplement",
|
|
7543
8196
|
zine: "zine",
|
|
7544
8197
|
technical: "technical-doc"
|
|
7545
8198
|
};
|
|
@@ -7559,6 +8212,39 @@ function escapeYamlScalar(value) {
|
|
|
7559
8212
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
7560
8213
|
}
|
|
7561
8214
|
var DEFAULT_AUTHOR = "Anonymous";
|
|
8215
|
+
function isPositivePoints(value) {
|
|
8216
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
8217
|
+
}
|
|
8218
|
+
function requirePreset(preset, customPage) {
|
|
8219
|
+
if (!preset || !PRESET_IDS.includes(preset)) {
|
|
8220
|
+
const got = preset ? ` (got "${preset}")` : "";
|
|
8221
|
+
throw new CreateProjectErrorImpl("preset-required", `A preset is required to create a book${got}. Choose one of: ${PRESET_IDS.join(", ")}.`);
|
|
8222
|
+
}
|
|
8223
|
+
if (preset === "custom") {
|
|
8224
|
+
const missing = [];
|
|
8225
|
+
if (!customPage || !isPositivePoints(customPage.width))
|
|
8226
|
+
missing.push("page width");
|
|
8227
|
+
if (!customPage || !isPositivePoints(customPage.height))
|
|
8228
|
+
missing.push("page height");
|
|
8229
|
+
if (missing.length > 0) {
|
|
8230
|
+
throw new CreateProjectErrorImpl("custom-page-required", `The custom preset needs a trim size: ${missing.join(" and ")} in points ` + `(72pt = 1in — e.g. US Letter is 612 x 792).`);
|
|
8231
|
+
}
|
|
8232
|
+
} else if (customPage && (!isPositivePoints(customPage.width) || !isPositivePoints(customPage.height))) {
|
|
8233
|
+
throw new CreateProjectErrorImpl("custom-page-required", "Page overrides need a positive width and height in points (72pt = 1in).");
|
|
8234
|
+
}
|
|
8235
|
+
if (customPage?.tolerance !== undefined && !isPositivePoints(customPage.tolerance)) {
|
|
8236
|
+
throw new CreateProjectErrorImpl("custom-page-required", "Page tolerance must be a positive number of points.");
|
|
8237
|
+
}
|
|
8238
|
+
return preset;
|
|
8239
|
+
}
|
|
8240
|
+
function resolveScaffoldTargets(requested, preset) {
|
|
8241
|
+
const ids = requested ?? [...PRESETS[preset].defaultTargets];
|
|
8242
|
+
const unknown = ids.filter((id) => !TARGETS[id]);
|
|
8243
|
+
if (unknown.length > 0) {
|
|
8244
|
+
throw new CreateProjectErrorImpl("invalid-targets", `Unknown publish target${unknown.length > 1 ? "s" : ""} "${unknown.join('", "')}". ` + `Known targets: ${TARGET_IDS.join(", ")}.`);
|
|
8245
|
+
}
|
|
8246
|
+
return [...new Set(ids)];
|
|
8247
|
+
}
|
|
7562
8248
|
async function scaffoldProject(options) {
|
|
7563
8249
|
const name = (options.name ?? "").trim();
|
|
7564
8250
|
if (!name) {
|
|
@@ -7570,7 +8256,7 @@ async function scaffoldProject(options) {
|
|
|
7570
8256
|
throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
|
|
7571
8257
|
}
|
|
7572
8258
|
const parentDir = options.parentDir;
|
|
7573
|
-
if (!parentDir || !
|
|
8259
|
+
if (!parentDir || !path14.isAbsolute(parentDir)) {
|
|
7574
8260
|
throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
|
|
7575
8261
|
}
|
|
7576
8262
|
try {
|
|
@@ -7578,7 +8264,7 @@ async function scaffoldProject(options) {
|
|
|
7578
8264
|
} catch {
|
|
7579
8265
|
throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
7580
8266
|
}
|
|
7581
|
-
const projectDir =
|
|
8267
|
+
const projectDir = path14.join(parentDir, folderName);
|
|
7582
8268
|
let targetExists = true;
|
|
7583
8269
|
try {
|
|
7584
8270
|
await access(projectDir, FS.F_OK);
|
|
@@ -7590,26 +8276,28 @@ async function scaffoldProject(options) {
|
|
|
7590
8276
|
}
|
|
7591
8277
|
const template = options.template ?? "book";
|
|
7592
8278
|
const customTemplateDir = options.templateDir;
|
|
8279
|
+
const preset = customTemplateDir && options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
|
|
8280
|
+
const targets = preset ? resolveScaffoldTargets(options.targets, preset) : undefined;
|
|
7593
8281
|
try {
|
|
7594
|
-
await
|
|
8282
|
+
await mkdir7(projectDir, { recursive: true });
|
|
7595
8283
|
if (customTemplateDir) {
|
|
7596
8284
|
const entries = await readdir5(customTemplateDir, { withFileTypes: true });
|
|
7597
8285
|
for (const entry of entries) {
|
|
7598
8286
|
if (entry.name === ".gutterpress-template.json")
|
|
7599
8287
|
continue;
|
|
7600
|
-
await cp2(
|
|
8288
|
+
await cp2(path14.join(customTemplateDir, entry.name), path14.join(projectDir, entry.name), { recursive: true });
|
|
7601
8289
|
}
|
|
7602
|
-
await
|
|
8290
|
+
await mkdir7(path14.join(projectDir, "assets"), { recursive: true });
|
|
7603
8291
|
} else {
|
|
7604
8292
|
const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
|
|
7605
8293
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
7606
|
-
await
|
|
7607
|
-
await copyFile(tplManifest,
|
|
7608
|
-
await copyFile(tplChapter,
|
|
8294
|
+
await mkdir7(path14.join(projectDir, "assets"), { recursive: true });
|
|
8295
|
+
await copyFile(tplManifest, path14.join(projectDir, MANIFEST_FILENAMES[0]));
|
|
8296
|
+
await copyFile(tplChapter, path14.join(projectDir, "chapter-01.md"));
|
|
7609
8297
|
const starterThemeId = STARTER_THEME_FOR_TEMPLATE[template] ?? "clean-book";
|
|
7610
8298
|
const starterCss = await getAssetPath(`themes/${starterThemeId}/theme.css`);
|
|
7611
|
-
await
|
|
7612
|
-
await copyFile(starterCss,
|
|
8299
|
+
await mkdir7(path14.join(projectDir, "styles"), { recursive: true });
|
|
8300
|
+
await copyFile(starterCss, path14.join(projectDir, "styles", "book.css"));
|
|
7613
8301
|
}
|
|
7614
8302
|
} catch (e) {
|
|
7615
8303
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not create the project files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7619,18 +8307,34 @@ async function scaffoldProject(options) {
|
|
|
7619
8307
|
} catch (e) {
|
|
7620
8308
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
|
|
7621
8309
|
}
|
|
7622
|
-
const manifestPath = MANIFEST_FILENAMES.map((name2) =>
|
|
8310
|
+
const manifestPath = MANIFEST_FILENAMES.map((name2) => path14.join(projectDir, name2)).find(existsSync11) ?? path14.join(projectDir, MANIFEST_FILENAMES[0]);
|
|
7623
8311
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
7624
8312
|
const substitutions = {
|
|
7625
8313
|
"{{TITLE}}": escapeYamlScalar(name),
|
|
7626
8314
|
"{{AUTHOR}}": escapeYamlScalar(author)
|
|
7627
8315
|
};
|
|
7628
|
-
let openFile =
|
|
8316
|
+
let openFile = path14.join(projectDir, "chapter-01.md");
|
|
7629
8317
|
try {
|
|
7630
8318
|
await fillTemplateFile(manifestPath, substitutions);
|
|
8319
|
+
if (preset) {
|
|
8320
|
+
const { doc, file } = await loadManifestDoc(projectDir);
|
|
8321
|
+
doc.set("preset", preset);
|
|
8322
|
+
doc.set("targets", doc.createNode(targets ?? []));
|
|
8323
|
+
if (options.customPage) {
|
|
8324
|
+
const page = {
|
|
8325
|
+
width: options.customPage.width,
|
|
8326
|
+
height: options.customPage.height
|
|
8327
|
+
};
|
|
8328
|
+
if (options.customPage.tolerance !== undefined) {
|
|
8329
|
+
page.tolerance = options.customPage.tolerance;
|
|
8330
|
+
}
|
|
8331
|
+
doc.set("page", doc.createNode(page));
|
|
8332
|
+
}
|
|
8333
|
+
await writeManifestDoc(file, doc);
|
|
8334
|
+
}
|
|
7631
8335
|
const firstSource = await firstSourceFile(manifestPath);
|
|
7632
8336
|
if (firstSource)
|
|
7633
|
-
openFile =
|
|
8337
|
+
openFile = path14.join(projectDir, firstSource);
|
|
7634
8338
|
await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
|
|
7635
8339
|
} catch (e) {
|
|
7636
8340
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not finalise the project files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7640,7 +8344,7 @@ async function scaffoldProject(options) {
|
|
|
7640
8344
|
let versionHistoryError;
|
|
7641
8345
|
if (requested === "local-git") {
|
|
7642
8346
|
try {
|
|
7643
|
-
const { providerFor } = await import("./source-provider-
|
|
8347
|
+
const { providerFor } = await import("./source-provider-c1rjm2c0.js");
|
|
7644
8348
|
const provider = providerFor({ type: "local-folder", path: projectDir });
|
|
7645
8349
|
await provider.initVersionHistory({
|
|
7646
8350
|
projectDir,
|
|
@@ -7650,9 +8354,9 @@ async function scaffoldProject(options) {
|
|
|
7650
8354
|
versionHistory = "local-git";
|
|
7651
8355
|
} catch (e) {
|
|
7652
8356
|
versionHistory = "none";
|
|
7653
|
-
const { findEnclosingRepoDir } = await import("./project-source-
|
|
8357
|
+
const { findEnclosingRepoDir } = await import("./project-source-p0gn1wd5.js");
|
|
7654
8358
|
const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
|
|
7655
|
-
versionHistoryError = enclosingRepoDir ? `This book was added to ${
|
|
8359
|
+
versionHistoryError = enclosingRepoDir ? `This book was added to ${path14.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
|
|
7656
8360
|
}
|
|
7657
8361
|
}
|
|
7658
8362
|
const result = {
|
|
@@ -7667,10 +8371,10 @@ async function scaffoldProject(options) {
|
|
|
7667
8371
|
return result;
|
|
7668
8372
|
}
|
|
7669
8373
|
async function ensureGitignoreHasDist(projectDir) {
|
|
7670
|
-
const gitignorePath =
|
|
8374
|
+
const gitignorePath = path14.join(projectDir, ".gitignore");
|
|
7671
8375
|
let existing = null;
|
|
7672
8376
|
try {
|
|
7673
|
-
existing = await
|
|
8377
|
+
existing = await readFile22(gitignorePath, "utf8");
|
|
7674
8378
|
} catch {}
|
|
7675
8379
|
if (existing === null) {
|
|
7676
8380
|
await writeFile6(gitignorePath, `dist/
|
|
@@ -7690,7 +8394,7 @@ async function ensureGitignoreHasDist(projectDir) {
|
|
|
7690
8394
|
async function firstSourceFile(manifestPath) {
|
|
7691
8395
|
let text;
|
|
7692
8396
|
try {
|
|
7693
|
-
text = await
|
|
8397
|
+
text = await readFile22(manifestPath, "utf8");
|
|
7694
8398
|
} catch {
|
|
7695
8399
|
return;
|
|
7696
8400
|
}
|
|
@@ -7702,7 +8406,7 @@ async function firstSourceFile(manifestPath) {
|
|
|
7702
8406
|
return m ? m[1].trim() : undefined;
|
|
7703
8407
|
}
|
|
7704
8408
|
async function fillTemplateFile(filePath2, substitutions) {
|
|
7705
|
-
let text = await
|
|
8409
|
+
let text = await readFile22(filePath2, "utf8");
|
|
7706
8410
|
for (const [token, value] of Object.entries(substitutions)) {
|
|
7707
8411
|
text = text.split(token).join(value);
|
|
7708
8412
|
}
|
|
@@ -7710,9 +8414,9 @@ async function fillTemplateFile(filePath2, substitutions) {
|
|
|
7710
8414
|
}
|
|
7711
8415
|
|
|
7712
8416
|
// src/lib/project-templates.ts
|
|
8417
|
+
import { isMap, isScalar, isSeq as isSeq2, parseDocument as parseDocument2 } from "yaml";
|
|
7713
8418
|
var BUILT_IN_TEMPLATE_IDS = [
|
|
7714
8419
|
"book",
|
|
7715
|
-
"ttrpg",
|
|
7716
8420
|
"zine",
|
|
7717
8421
|
"technical"
|
|
7718
8422
|
];
|
|
@@ -7728,63 +8432,10 @@ var SKIP_ENTRIES = new Set([
|
|
|
7728
8432
|
|
|
7729
8433
|
// src/lib/plugin-manager.ts
|
|
7730
8434
|
import path16 from "node:path";
|
|
7731
|
-
import { isSeq as
|
|
7732
|
-
|
|
7733
|
-
// src/lib/manifest-doc.ts
|
|
7734
|
-
import { existsSync as existsSync11 } from "node:fs";
|
|
7735
|
-
import { randomUUID } from "node:crypto";
|
|
7736
|
-
import { mkdir as mkdir7, open, readFile as readFile22, rename, rm as rm2 } from "node:fs/promises";
|
|
7737
|
-
import path14 from "node:path";
|
|
7738
|
-
import { parseDocument, isSeq, YAMLSeq } from "yaml";
|
|
7739
|
-
function resolveManifestPath(projectDir) {
|
|
7740
|
-
const existing = MANIFEST_FILENAMES.find((name) => existsSync11(path14.join(projectDir, name)));
|
|
7741
|
-
return path14.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
|
|
7742
|
-
}
|
|
7743
|
-
async function loadManifestDoc(projectDir) {
|
|
7744
|
-
const file = resolveManifestPath(projectDir);
|
|
7745
|
-
let text = "";
|
|
7746
|
-
try {
|
|
7747
|
-
text = await readFile22(file, "utf8");
|
|
7748
|
-
} catch (error2) {
|
|
7749
|
-
if (error2.code !== "ENOENT")
|
|
7750
|
-
throw error2;
|
|
7751
|
-
}
|
|
7752
|
-
return { doc: parseDocument(text), file };
|
|
7753
|
-
}
|
|
7754
|
-
async function writeManifestDoc(file, doc) {
|
|
7755
|
-
const dir = path14.dirname(file);
|
|
7756
|
-
await mkdir7(dir, { recursive: true });
|
|
7757
|
-
const temporary = path14.join(dir, `.${path14.basename(file)}.tmp-${process.pid}-${randomUUID()}`);
|
|
7758
|
-
try {
|
|
7759
|
-
const handle = await open(temporary, "wx", 438);
|
|
7760
|
-
try {
|
|
7761
|
-
await handle.writeFile(doc.toString(), "utf8");
|
|
7762
|
-
await handle.sync();
|
|
7763
|
-
} finally {
|
|
7764
|
-
await handle.close();
|
|
7765
|
-
}
|
|
7766
|
-
await rename(temporary, file);
|
|
7767
|
-
const directory = await open(dir, "r").catch(() => null);
|
|
7768
|
-
if (directory) {
|
|
7769
|
-
await directory.sync().catch(() => {});
|
|
7770
|
-
await directory.close();
|
|
7771
|
-
}
|
|
7772
|
-
} finally {
|
|
7773
|
-
await rm2(temporary, { force: true });
|
|
7774
|
-
}
|
|
7775
|
-
}
|
|
7776
|
-
function ensureSeq(doc, key) {
|
|
7777
|
-
let seq = doc.get(key, true);
|
|
7778
|
-
if (!isSeq(seq)) {
|
|
7779
|
-
const fresh = new YAMLSeq(doc.schema);
|
|
7780
|
-
doc.set(key, fresh);
|
|
7781
|
-
seq = fresh;
|
|
7782
|
-
}
|
|
7783
|
-
return seq;
|
|
7784
|
-
}
|
|
8435
|
+
import { isSeq as isSeq3, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar } from "yaml";
|
|
7785
8436
|
|
|
7786
8437
|
// src/lib/npm-plugin-installer.ts
|
|
7787
|
-
import { createHash as createHash4, randomUUID as
|
|
8438
|
+
import { createHash as createHash4, randomUUID as randomUUID3, timingSafeEqual } from "node:crypto";
|
|
7788
8439
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
7789
8440
|
import {
|
|
7790
8441
|
lstat as lstat2,
|
|
@@ -8460,7 +9111,7 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
|
8460
9111
|
}
|
|
8461
9112
|
try {
|
|
8462
9113
|
await lstat2(finalRoot);
|
|
8463
|
-
backupRoot = `${finalRoot}.backup-${
|
|
9114
|
+
backupRoot = `${finalRoot}.backup-${randomUUID3()}`;
|
|
8464
9115
|
await rename2(finalRoot, backupRoot);
|
|
8465
9116
|
} catch (error2) {
|
|
8466
9117
|
if (error2.code !== "ENOENT")
|
|
@@ -8549,9 +9200,9 @@ function isLocalRef2(ref) {
|
|
|
8549
9200
|
return ref.startsWith("./") || ref.startsWith("../") || ref.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(ref) || /\.(m?js|cjs|ts)$/i.test(ref) || !ref.startsWith("@") && ref.includes("/");
|
|
8550
9201
|
}
|
|
8551
9202
|
function itemRef(item) {
|
|
8552
|
-
if (
|
|
9203
|
+
if (isScalar2(item))
|
|
8553
9204
|
return typeof item.value === "string" ? item.value : null;
|
|
8554
|
-
if (
|
|
9205
|
+
if (isMap2(item)) {
|
|
8555
9206
|
const p = item.get("path");
|
|
8556
9207
|
const n = item.get("name");
|
|
8557
9208
|
if (typeof p === "string")
|
|
@@ -8567,7 +9218,7 @@ function refKind(ref) {
|
|
|
8567
9218
|
async function listProjectPlugins(projectDir) {
|
|
8568
9219
|
const { doc } = await loadManifestDoc(projectDir);
|
|
8569
9220
|
const seq = doc.get("plugins", true);
|
|
8570
|
-
if (!
|
|
9221
|
+
if (!isSeq3(seq))
|
|
8571
9222
|
return [];
|
|
8572
9223
|
const out = [];
|
|
8573
9224
|
for (const item of seq.items) {
|
|
@@ -8578,7 +9229,7 @@ async function listProjectPlugins(projectDir) {
|
|
|
8578
9229
|
let kind;
|
|
8579
9230
|
let version;
|
|
8580
9231
|
let exportName;
|
|
8581
|
-
if (
|
|
9232
|
+
if (isMap2(item)) {
|
|
8582
9233
|
const e = item.get("enabled");
|
|
8583
9234
|
if (e === false)
|
|
8584
9235
|
enabled = false;
|
|
@@ -8623,7 +9274,7 @@ async function recordInstalledNpmPlugin(projectDir, name, version, exportName) {
|
|
|
8623
9274
|
const seq = ensureSeq(doc, "plugins");
|
|
8624
9275
|
const idx = indexOfRef(seq, name);
|
|
8625
9276
|
let map;
|
|
8626
|
-
if (idx >= 0 &&
|
|
9277
|
+
if (idx >= 0 && isMap2(seq.items[idx])) {
|
|
8627
9278
|
map = seq.items[idx];
|
|
8628
9279
|
} else {
|
|
8629
9280
|
map = new YAMLMap(doc.schema);
|
|
@@ -9955,8 +10606,8 @@ var drivethrurpgProvider = {
|
|
|
9955
10606
|
}
|
|
9956
10607
|
issues.push({
|
|
9957
10608
|
severity: "info",
|
|
9958
|
-
id: "drivethrurpg/validate-
|
|
9959
|
-
message: "Tip: run `gutterpress validate --
|
|
10609
|
+
id: "drivethrurpg/validate-target",
|
|
10610
|
+
message: "Tip: run `gutterpress validate --target dtrpg` for DriveThruRPG's print-compliance checks before uploading."
|
|
9960
10611
|
});
|
|
9961
10612
|
return issues;
|
|
9962
10613
|
},
|
|
@@ -10070,7 +10721,7 @@ var info4 = {
|
|
|
10070
10721
|
label: "itch.io",
|
|
10071
10722
|
kind: "api",
|
|
10072
10723
|
format: "pdf",
|
|
10073
|
-
description: "Upload the PDF to your itch.io project page (games,
|
|
10724
|
+
description: "Upload the PDF to your itch.io project page (games, zines, and other printable projects).",
|
|
10074
10725
|
configFields: [
|
|
10075
10726
|
{ key: "target", label: "Project (user/game)", placeholder: "you/your-book" },
|
|
10076
10727
|
{ key: "channel", label: "Channel", placeholder: "pdf" }
|
|
@@ -12253,7 +12904,7 @@ async function dispatchToHandler(kind, ctx, error2) {
|
|
|
12253
12904
|
return result;
|
|
12254
12905
|
}
|
|
12255
12906
|
// src/lib/theme-manager.ts
|
|
12256
|
-
import { isSeq as
|
|
12907
|
+
import { isSeq as isSeq4, Scalar as Scalar2 } from "yaml";
|
|
12257
12908
|
var THEMES_DIR = "themes";
|
|
12258
12909
|
var THEME_HREF_RE = new RegExp(`^${THEMES_DIR}/([^/]+)/theme\\.css$`);
|
|
12259
12910
|
// src/lib/theme-import.ts
|
|
@@ -12277,7 +12928,7 @@ var ALLOWED_ASSET_EXTS = new Set([
|
|
|
12277
12928
|
".css"
|
|
12278
12929
|
]);
|
|
12279
12930
|
// src/lib/manifest-config.ts
|
|
12280
|
-
import { isSeq as
|
|
12931
|
+
import { isSeq as isSeq5, isMap as isMap3, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
|
|
12281
12932
|
// src/lib/host-policy.ts
|
|
12282
12933
|
var AUTO_SNAPSHOT_MAX_MINUTES = 24 * 60;
|
|
12283
12934
|
var AUTO_SYNC_MAX_MINUTES = 24 * 60;
|
|
@@ -12374,5 +13025,5 @@ function normalizeProjectKey(projectDir) {
|
|
|
12374
13025
|
return projectDir.replace(/[\\/]+$/, "");
|
|
12375
13026
|
}
|
|
12376
13027
|
// src/lib/publish/manifest-publish.ts
|
|
12377
|
-
import { isMap as
|
|
12378
|
-
export { MANIFEST_FILENAMES, parseNpmPluginSpec, runLint, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, BUILT_IN_TEMPLATE_IDS, addNpmPlugin, isAppHeartbeatFresh, classifyFromHealth, classifyGitError, makeManualGuidance, inspectRepo, verifyRepoReadable, isUnbornRepo, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, runPublish, connectPublishProvider, buildRecoveryContext, recover16 as recover };
|
|
13028
|
+
import { isMap as isMap4 } from "yaml";
|
|
13029
|
+
export { PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, parseNpmPluginSpec, runLint, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, BUILT_IN_TEMPLATE_IDS, addNpmPlugin, isAppHeartbeatFresh, classifyFromHealth, classifyGitError, makeManualGuidance, inspectRepo, verifyRepoReadable, isUnbornRepo, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, runPublish, connectPublishProvider, buildRecoveryContext, recover16 as recover };
|