gutterpress 0.11.16 → 0.11.17-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.
Files changed (30) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-8epzrj8g.js → build-1p999cmg.js} +4 -4
  3. package/dist/{cli-ev175hx9.js → cli-6z5zthzt.js} +138 -93
  4. package/dist/{cli-0prrt9e4.js → cli-70xgehq2.js} +1 -1
  5. package/dist/{cli-wqy8hnnh.js → cli-gymfg04q.js} +8 -6
  6. package/dist/{cli-182j48xc.js → cli-ppc0vz4b.js} +1 -1
  7. package/dist/cli.js +12 -12
  8. package/dist/{doctor-24mbx7y1.js → doctor-reqpnn3h.js} +2 -2
  9. package/dist/engine/shared/synthesis.d.ts +8 -0
  10. package/dist/{engine-j5h953pg.js → engine-078ggczp.js} +1 -1
  11. package/dist/{engine-w17dbt4f.js → engine-5jzhjjvv.js} +2 -2
  12. package/dist/{ext-p9gx5aac.js → ext-dsbfejt3.js} +4 -4
  13. package/dist/{gutterpress-viewer-swz6bwfa.js → gutterpress-viewer-zkv2stfy.js} +51 -6
  14. package/dist/{index-qep93v0z.js → index-5dcj1vts.js} +7 -5
  15. package/dist/{index-40achhs8.js → index-c275p061.js} +137 -90
  16. package/dist/{index-ee7wp8q3.js → index-eyaj6nkc.js} +2 -2
  17. package/dist/index.js +3 -3
  18. package/dist/lib/markdown/assemble.d.ts +10 -0
  19. package/dist/lib/markdown/index.d.ts +10 -0
  20. package/dist/lib/static-serve.d.ts +6 -0
  21. package/dist/{new-wsjsrm7w.js → new-m5y2wepr.js} +4 -4
  22. package/dist/{preflight-0fjjnk9e.js → preflight-bwpqwwny.js} +4 -4
  23. package/dist/preview/file-watcher.d.ts +5 -1
  24. package/dist/preview/server-context.d.ts +10 -0
  25. package/dist/{preview-azy06gw0.js → preview-62y7qhkp.js} +4 -4
  26. package/dist/{preview-interface-5wnzrvhf.js → preview-interface-c44qk17f.js} +7 -2
  27. package/dist/{publish-8db67m2h.js → publish-k2zyt3x6.js} +4 -4
  28. package/dist/render.js +2 -1
  29. package/dist/{validate-e7mw1xyd.js → validate-fgwwxtbn.js} +4 -4
  30. package/package.json +1 -1
@@ -4,7 +4,7 @@ import {
4
4
  MARGIN_BOX_IGNORED_PROPERTIES,
5
5
  getAssetPath,
6
6
  getAssetsDir
7
- } from "./index-ee7wp8q3.js";
7
+ } from "./index-eyaj6nkc.js";
8
8
  import {
9
9
  INSTALL_HINTS,
10
10
  execCapture,
@@ -2859,8 +2859,9 @@ ${projectCss.trim()}` : null
2859
2859
  `);
2860
2860
  const preloadTags = (opts.preloadImages ?? []).map((href) => `
2861
2861
  <link rel="preload" as="image" href="${href}">`).join("");
2862
+ const printAttrs = (opts.print?.signature && opts.print.signature > 1 ? ` data-gp-signature="${opts.print.signature}"` : "") + (opts.print?.reserveLastPage ? " data-gp-reserve-last-page" : "");
2862
2863
  return `<!DOCTYPE html>
2863
- <html lang="en">
2864
+ <html lang="en"${printAttrs}>
2864
2865
  <head>
2865
2866
  <meta charset="UTF-8">
2866
2867
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -3319,6 +3320,7 @@ ${css.trim()}
3319
3320
  projectCss: inlined.css,
3320
3321
  preloadImages: cssAssetCopies.map((c) => c.to),
3321
3322
  title: opts.title,
3323
+ print: opts.print,
3322
3324
  plugins: opts.plugins,
3323
3325
  pluginCss,
3324
3326
  annotateSourceChapters: opts.annotateSourceChapters,
@@ -3330,6 +3332,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
3330
3332
  await mkdir(outDir, { recursive: true });
3331
3333
  const html = await renderChapters(inputDir, {
3332
3334
  title: opts.title,
3335
+ print: opts.print,
3333
3336
  styles: opts.styles,
3334
3337
  files: opts.files,
3335
3338
  plugins: opts.plugins,
@@ -5638,7 +5641,7 @@ import git from "isomorphic-git";
5638
5641
  // package.json
5639
5642
  var package_default = {
5640
5643
  name: "gutterpress",
5641
- version: "0.11.16",
5644
+ version: "0.11.17-alpha.2",
5642
5645
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5643
5646
  author: "itlackey",
5644
5647
  license: "MPL-2.0",
@@ -10106,6 +10109,7 @@ async function renderBook(ctx) {
10106
10109
  const cssAssets = [];
10107
10110
  const htmlFile = await renderChaptersToFile(renderDir, workDir, {
10108
10111
  title: config.title,
10112
+ print: config.print,
10109
10113
  styles: config.styles,
10110
10114
  files: config.source.files,
10111
10115
  plugins,
@@ -10257,7 +10261,7 @@ class PdfOutput {
10257
10261
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10258
10262
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10259
10263
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10260
- const { buildNativePdf } = await import("./engine-w17dbt4f.js");
10264
+ const { buildNativePdf } = await import("./engine-5jzhjjvv.js");
10261
10265
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10262
10266
  title: config.title,
10263
10267
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10359,7 +10363,7 @@ async function runBuild(opts) {
10359
10363
  }
10360
10364
  }
10361
10365
  // src/preview/lifecycle.ts
10362
- import path15 from "path";
10366
+ import path16 from "path";
10363
10367
  import { tmpdir } from "os";
10364
10368
  import { randomBytes as randomBytes2 } from "crypto";
10365
10369
  import { readdir as readdir5, readFile as readFile25, writeFile as writeFile6 } from "node:fs/promises";
@@ -10384,7 +10388,65 @@ async function mkdir6(dirPath) {
10384
10388
  // src/preview/file-watcher.ts
10385
10389
  import { watch } from "chokidar";
10386
10390
  import fsp3 from "node:fs/promises";
10387
- import path14 from "path";
10391
+ import path15 from "path";
10392
+
10393
+ // src/lib/static-serve.ts
10394
+ import path14 from "node:path";
10395
+ var STATIC_MIME = {
10396
+ ".html": "text/html; charset=utf-8",
10397
+ ".htm": "text/html; charset=utf-8",
10398
+ ".css": "text/css",
10399
+ ".js": "application/javascript",
10400
+ ".mjs": "application/javascript",
10401
+ ".json": "application/json",
10402
+ ".png": "image/png",
10403
+ ".jpg": "image/jpeg",
10404
+ ".jpeg": "image/jpeg",
10405
+ ".gif": "image/gif",
10406
+ ".svg": "image/svg+xml",
10407
+ ".webp": "image/webp",
10408
+ ".avif": "image/avif",
10409
+ ".ico": "image/x-icon",
10410
+ ".woff": "font/woff",
10411
+ ".woff2": "font/woff2",
10412
+ ".ttf": "font/ttf",
10413
+ ".otf": "font/otf"
10414
+ };
10415
+ function resolveWithinRoot(relPath, root) {
10416
+ const resolvedRoot = path14.resolve(root);
10417
+ const candidate = path14.resolve(resolvedRoot, "." + (relPath.startsWith("/") ? relPath : "/" + relPath));
10418
+ if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path14.sep)) {
10419
+ return null;
10420
+ }
10421
+ return candidate;
10422
+ }
10423
+ function hasDotSegment(urlPathname) {
10424
+ let decoded;
10425
+ try {
10426
+ decoded = decodeURIComponent(urlPathname);
10427
+ } catch {
10428
+ return true;
10429
+ }
10430
+ return decoded.split(/[/\\]/).some((segment) => segment.startsWith("."));
10431
+ }
10432
+ function resolveStaticPath(urlPathname, root) {
10433
+ let decoded;
10434
+ try {
10435
+ decoded = decodeURIComponent(urlPathname);
10436
+ } catch {
10437
+ return null;
10438
+ }
10439
+ return resolveWithinRoot(decoded, root);
10440
+ }
10441
+ function imageRefKey(ref) {
10442
+ let key = ref.split(/[?#]/)[0].replace(/^\.\//, "").replace(/^\/+/, "");
10443
+ try {
10444
+ key = decodeURIComponent(key);
10445
+ } catch {}
10446
+ return key;
10447
+ }
10448
+
10449
+ // src/preview/file-watcher.ts
10388
10450
  var EMPTY_BOOK_HTML = `<!doctype html>
10389
10451
  <html lang="en">
10390
10452
  <head>
@@ -10405,12 +10467,14 @@ async function renderPreviewBook(inputPath, config, opts) {
10405
10467
  const { plugins, pluginStyles } = await loadPluginsWithCss(config.extensions, inputPath, (ref, err) => warn2(`Skipping plugin "${ref}" in preview — ${err.message}`));
10406
10468
  return renderChapters(inputPath, {
10407
10469
  title: config.title ?? "Document",
10470
+ print: config.print,
10408
10471
  styles: config.styles,
10409
10472
  files: opts.files,
10410
10473
  plugins,
10411
10474
  pluginStyles,
10412
10475
  annotateSourceChapters: true,
10413
10476
  ...opts.onCssAssets ? { onCssAssets: opts.onCssAssets } : {},
10477
+ ...opts.onImageRefs ? { onImageRefs: opts.onImageRefs } : {},
10414
10478
  onChapterWarnings: (file, warnings) => {
10415
10479
  for (const w of warnings) {
10416
10480
  warn2(` ${file}, line ${w.line}: ${w.message}`);
@@ -10425,24 +10489,35 @@ function injectPreviewScripts(html) {
10425
10489
  `;
10426
10490
  return /<\/head>/i.test(html) ? html.replace(/<\/head>/i, scripts + "</head>") : html + scripts;
10427
10491
  }
10428
- async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
10492
+ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, imageRefs) {
10429
10493
  if (!inputPath) {
10430
10494
  cssAssets.clear();
10431
- await fsp3.writeFile(path14.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
10495
+ imageRefs?.clear();
10496
+ await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
10432
10497
  return;
10433
10498
  }
10434
10499
  const nextAssets = new Map;
10500
+ const nextImageRefs = new Set;
10435
10501
  const html = await renderPreviewBook(inputPath, config, {
10436
10502
  files: config.source?.files ?? null,
10437
10503
  onCssAssets: (copies) => {
10438
10504
  for (const copy of copies)
10439
10505
  nextAssets.set(copy.to, copy.from);
10506
+ },
10507
+ onImageRefs: (refs) => {
10508
+ for (const ref of refs)
10509
+ nextImageRefs.add(imageRefKey(ref));
10440
10510
  }
10441
10511
  });
10442
10512
  cssAssets.clear();
10443
10513
  for (const [to, from] of nextAssets)
10444
10514
  cssAssets.set(to, from);
10445
- await fsp3.writeFile(path14.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
10515
+ if (imageRefs) {
10516
+ imageRefs.clear();
10517
+ for (const ref of nextImageRefs)
10518
+ imageRefs.add(ref);
10519
+ }
10520
+ await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
10446
10521
  }
10447
10522
  function isDotPathUnderRoot(candidatePath, root) {
10448
10523
  const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -10479,23 +10554,23 @@ function isIgnoredWatchPath(candidatePath, projectRoot) {
10479
10554
  return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
10480
10555
  }
10481
10556
  function isExternalWatchCandidate(candidatePath, targets) {
10482
- const candidate = path14.resolve(candidatePath);
10557
+ const candidate = path15.resolve(candidatePath);
10483
10558
  for (const rawTarget of targets) {
10484
- const target = path14.resolve(rawTarget);
10485
- const rel = path14.relative(candidate, target);
10486
- if (rel === "" || rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel))
10559
+ const target = path15.resolve(rawTarget);
10560
+ const rel = path15.relative(candidate, target);
10561
+ if (rel === "" || rel !== ".." && !rel.startsWith(`..${path15.sep}`) && !path15.isAbsolute(rel))
10487
10562
  return true;
10488
10563
  }
10489
10564
  return false;
10490
10565
  }
10491
10566
  async function nearestExistingDirectory(start) {
10492
- let current = path14.resolve(start);
10567
+ let current = path15.resolve(start);
10493
10568
  while (true) {
10494
10569
  try {
10495
10570
  if ((await fsp3.stat(current)).isDirectory())
10496
10571
  return current;
10497
10572
  } catch {}
10498
- const parent = path14.dirname(current);
10573
+ const parent = path15.dirname(current);
10499
10574
  if (parent === current)
10500
10575
  return current;
10501
10576
  current = parent;
@@ -10504,7 +10579,7 @@ async function nearestExistingDirectory(start) {
10504
10579
  async function externalWatchRoots(targets) {
10505
10580
  const roots = new Set;
10506
10581
  for (const target of targets) {
10507
- roots.add(await nearestExistingDirectory(path14.dirname(path14.resolve(target))));
10582
+ roots.add(await nearestExistingDirectory(path15.dirname(path15.resolve(target))));
10508
10583
  }
10509
10584
  return [...roots];
10510
10585
  }
@@ -10518,19 +10593,19 @@ async function extensionFolderFiles(dir) {
10518
10593
  const meta = await readExtensionMeta(dir);
10519
10594
  const entry = extensionEntry(meta);
10520
10595
  return [
10521
- path14.join(dir, EXTENSION_MANIFEST_FILENAME),
10522
- ...entry ? [path14.resolve(dir, entry)] : [],
10596
+ path15.join(dir, EXTENSION_MANIFEST_FILENAME),
10597
+ ...entry ? [path15.resolve(dir, entry)] : [],
10523
10598
  ...await collectStyleDependencies(dir, extensionStyleList(meta))
10524
10599
  ];
10525
10600
  }
10526
10601
  async function externalWatchTargets(projectDir, config) {
10527
- const root = path14.resolve(projectDir);
10602
+ const root = path15.resolve(projectDir);
10528
10603
  let canonicalRoot = root;
10529
10604
  try {
10530
10605
  canonicalRoot = await fsp3.realpath(root);
10531
10606
  } catch {}
10532
10607
  const styles = await resolveActiveStyles(root, config.styles);
10533
- const extensionPaths = (config.extensions ?? []).map((p) => p.path).filter((p) => !!p).map((p) => path14.resolve(root, p));
10608
+ const extensionPaths = (config.extensions ?? []).map((p) => p.path).filter((p) => !!p).map((p) => path15.resolve(root, p));
10534
10609
  const candidates = [
10535
10610
  ...await collectStyleDependencies(root, styles),
10536
10611
  ...extensionPaths,
@@ -10538,17 +10613,17 @@ async function externalWatchTargets(projectDir, config) {
10538
10613
  ];
10539
10614
  const external = new Set;
10540
10615
  for (const abs of candidates) {
10541
- const authoredPath = path14.resolve(abs);
10542
- const authoredRel = path14.relative(root, authoredPath);
10543
- if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${path14.sep}`) || path14.isAbsolute(authoredRel))) {
10616
+ const authoredPath = path15.resolve(abs);
10617
+ const authoredRel = path15.relative(root, authoredPath);
10618
+ if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${path15.sep}`) || path15.isAbsolute(authoredRel))) {
10544
10619
  external.add(authoredPath);
10545
10620
  }
10546
10621
  let canonicalPath = authoredPath;
10547
10622
  try {
10548
10623
  canonicalPath = await fsp3.realpath(canonicalPath);
10549
10624
  } catch {}
10550
- const rel = path14.relative(canonicalRoot, canonicalPath);
10551
- if (rel === "" || rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel))
10625
+ const rel = path15.relative(canonicalRoot, canonicalPath);
10626
+ if (rel === "" || rel !== ".." && !rel.startsWith(`..${path15.sep}`) && !path15.isAbsolute(rel))
10552
10627
  continue;
10553
10628
  external.add(canonicalPath);
10554
10629
  }
@@ -10575,7 +10650,7 @@ async function addAndAwaitWatch(watcher, roots) {
10575
10650
  }
10576
10651
  }
10577
10652
  function createFileWatcher(state) {
10578
- const inputResolved = path14.resolve(state.currentInputPath);
10653
+ const inputResolved = path15.resolve(state.currentInputPath);
10579
10654
  const watcher = watch(inputResolved, {
10580
10655
  persistent: true,
10581
10656
  ignoreInitial: true,
@@ -10615,7 +10690,7 @@ function createFileWatcher(state) {
10615
10690
  async function recordChange(event, filePath) {
10616
10691
  if (closed)
10617
10692
  return;
10618
- const target = path14.resolve(filePath);
10693
+ const target = path15.resolve(filePath);
10619
10694
  const settled = settledWrites.get(target);
10620
10695
  if (settled && event !== "unlink" && Date.now() <= settled.expiresAt) {
10621
10696
  const diskContent = await fsp3.readFile(target, "utf8").catch(() => null);
@@ -10636,7 +10711,7 @@ function createFileWatcher(state) {
10636
10711
  recordChange(event, filePath);
10637
10712
  });
10638
10713
  externalWatcher.on("all", (event, filePath) => {
10639
- const target = path14.resolve(filePath);
10714
+ const target = path15.resolve(filePath);
10640
10715
  if (!desiredExternals.has(target))
10641
10716
  return;
10642
10717
  if (event === "add" && suppressInitialExternalAdds.delete(target))
@@ -10644,8 +10719,8 @@ function createFileWatcher(state) {
10644
10719
  recordChange(event, target);
10645
10720
  });
10646
10721
  function isWatchedSource(target) {
10647
- const rel = path14.relative(inputResolved, target);
10648
- const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel);
10722
+ const rel = path15.relative(inputResolved, target);
10723
+ const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path15.sep}`) && !path15.isAbsolute(rel);
10649
10724
  if (inProject)
10650
10725
  return !isIgnoredWatchPath(target, inputResolved);
10651
10726
  return desiredExternals.has(target);
@@ -10670,7 +10745,7 @@ function createFileWatcher(state) {
10670
10745
  state.notifySettledWrite = (filePath, writtenContent) => {
10671
10746
  if (closed)
10672
10747
  return;
10673
- const target = path14.resolve(filePath);
10748
+ const target = path15.resolve(filePath);
10674
10749
  if (isWatchedSource(target)) {
10675
10750
  acceptSettledWrite(target, writtenContent);
10676
10751
  return;
@@ -10691,7 +10766,7 @@ function createFileWatcher(state) {
10691
10766
  return;
10692
10767
  let nextTargets;
10693
10768
  try {
10694
- nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) => path14.resolve(target)));
10769
+ nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) => path15.resolve(target)));
10695
10770
  } catch (err) {
10696
10771
  debug(`Could not resolve external watch targets: ${err}`);
10697
10772
  return;
@@ -10755,7 +10830,7 @@ function createFileWatcher(state) {
10755
10830
  await syncExternalWatches();
10756
10831
  if (closed)
10757
10832
  return;
10758
- await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig, state.cssAssets);
10833
+ await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig, state.cssAssets, state.imageRefs);
10759
10834
  if (closed)
10760
10835
  return;
10761
10836
  state.previewServer?.broadcastReload();
@@ -10817,7 +10892,7 @@ async function stopFileWatcher(state) {
10817
10892
  }
10818
10893
 
10819
10894
  // src/preview/lifecycle.ts
10820
- var TEMP_DIR_BASE = path15.join(tmpdir(), "gutterpress-preview");
10895
+ var TEMP_DIR_BASE = path16.join(tmpdir(), "gutterpress-preview");
10821
10896
  var PID_FILE_NAME = ".gutterpress.pid";
10822
10897
  function isProcessAlive(pid) {
10823
10898
  if (!Number.isFinite(pid) || pid <= 0)
@@ -10837,8 +10912,8 @@ async function cleanupOrphanTempDirs() {
10837
10912
  return;
10838
10913
  }
10839
10914
  for (const entry of entries) {
10840
- const dir = path15.join(TEMP_DIR_BASE, entry);
10841
- const pidFile = path15.join(dir, PID_FILE_NAME);
10915
+ const dir = path16.join(TEMP_DIR_BASE, entry);
10916
+ const pidFile = path16.join(dir, PID_FILE_NAME);
10842
10917
  try {
10843
10918
  const raw = await readFile25(pidFile, "utf8");
10844
10919
  const pid = parseInt(raw.trim(), 10);
@@ -10852,10 +10927,10 @@ async function cleanupOrphanTempDirs() {
10852
10927
  async function initializePreviewDirectories() {
10853
10928
  await cleanupOrphanTempDirs();
10854
10929
  const tempDirSuffix = randomBytes2(8).toString("hex");
10855
- const tempDir = path15.join(TEMP_DIR_BASE, tempDirSuffix);
10930
+ const tempDir = path16.join(TEMP_DIR_BASE, tempDirSuffix);
10856
10931
  await mkdir6(tempDir);
10857
10932
  debug(`Created temporary directory: ${tempDir}`);
10858
- await writeFile6(path15.join(tempDir, PID_FILE_NAME), `${process.pid}
10933
+ await writeFile6(path16.join(tempDir, PID_FILE_NAME), `${process.pid}
10859
10934
  `, "utf8");
10860
10935
  return tempDir;
10861
10936
  }
@@ -10878,7 +10953,7 @@ async function restartPreview(newInputPath, state) {
10878
10953
  state.currentInputPath = newInputPath;
10879
10954
  const manifest = await loadManifest(newInputPath);
10880
10955
  state.config = resolveConfig({}, manifest);
10881
- await generateAndWriteHtml(newInputPath, state.tempDir, state.config, state.cssAssets);
10956
+ await generateAndWriteHtml(newInputPath, state.tempDir, state.config, state.cssAssets, state.imageRefs);
10882
10957
  startFileWatcher(state);
10883
10958
  state.previewServer?.broadcastReload();
10884
10959
  info("Preview restarted successfully");
@@ -10938,7 +11013,8 @@ function createServerState(inputPath, tempDir, config, options) {
10938
11013
  tempDir,
10939
11014
  config,
10940
11015
  options,
10941
- cssAssets: new Map
11016
+ cssAssets: new Map,
11017
+ imageRefs: new Set
10942
11018
  };
10943
11019
  }
10944
11020
 
@@ -10978,55 +11054,6 @@ function openPath(filePath) {
10978
11054
  });
10979
11055
  }
10980
11056
 
10981
- // src/lib/static-serve.ts
10982
- import path16 from "node:path";
10983
- var STATIC_MIME = {
10984
- ".html": "text/html; charset=utf-8",
10985
- ".htm": "text/html; charset=utf-8",
10986
- ".css": "text/css",
10987
- ".js": "application/javascript",
10988
- ".mjs": "application/javascript",
10989
- ".json": "application/json",
10990
- ".png": "image/png",
10991
- ".jpg": "image/jpeg",
10992
- ".jpeg": "image/jpeg",
10993
- ".gif": "image/gif",
10994
- ".svg": "image/svg+xml",
10995
- ".webp": "image/webp",
10996
- ".avif": "image/avif",
10997
- ".ico": "image/x-icon",
10998
- ".woff": "font/woff",
10999
- ".woff2": "font/woff2",
11000
- ".ttf": "font/ttf",
11001
- ".otf": "font/otf"
11002
- };
11003
- function resolveWithinRoot(relPath, root) {
11004
- const resolvedRoot = path16.resolve(root);
11005
- const candidate = path16.resolve(resolvedRoot, "." + (relPath.startsWith("/") ? relPath : "/" + relPath));
11006
- if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path16.sep)) {
11007
- return null;
11008
- }
11009
- return candidate;
11010
- }
11011
- function hasDotSegment(urlPathname) {
11012
- let decoded;
11013
- try {
11014
- decoded = decodeURIComponent(urlPathname);
11015
- } catch {
11016
- return true;
11017
- }
11018
- return decoded.split(/[/\\]/).some((segment) => segment.startsWith("."));
11019
- }
11020
- function resolveStaticPath(urlPathname, root) {
11021
- let decoded;
11022
- try {
11023
- decoded = decodeURIComponent(urlPathname);
11024
- } catch {
11025
- return null;
11026
- }
11027
- return resolveWithinRoot(decoded, root);
11028
- }
11029
-
11030
11057
  // src/preview/http-server.ts
11031
11058
  var HMR_PATH = "/__gutterpress-hmr";
11032
11059
  function shellHtml(initialRevision, instanceId) {
@@ -11166,6 +11193,22 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11166
11193
  }
11167
11194
  await serveStatic(absPath, res, PROJECT_ASSET_CACHE_CONTROL, { ETag: validator.etag });
11168
11195
  }
11196
+ var missingImagePng;
11197
+ async function isMissingReferencedImage(pathname, absPath, refs) {
11198
+ if (!refs.has(imageRefKey(pathname)))
11199
+ return false;
11200
+ try {
11201
+ await stat5(absPath);
11202
+ return false;
11203
+ } catch (err) {
11204
+ return err?.code === "ENOENT";
11205
+ }
11206
+ }
11207
+ function serveMissingImage(res) {
11208
+ missingImagePng ??= Buffer.from(placeholderPng());
11209
+ res.writeHead(200, { "Content-Type": "image/png", "Cache-Control": "no-store" });
11210
+ res.end(missingImagePng);
11211
+ }
11169
11212
  function matchesEmbedded(urlPathname) {
11170
11213
  if (EMBEDDED_EXACT.has(urlPathname))
11171
11214
  return true;
@@ -11317,6 +11360,10 @@ async function createPreviewServer(state, port) {
11317
11360
  res.end("Not Found");
11318
11361
  return;
11319
11362
  }
11363
+ if (servingProjectRoot && await isMissingReferencedImage(pathname, absPath, state.imageRefs)) {
11364
+ serveMissingImage(res);
11365
+ return;
11366
+ }
11320
11367
  const serve = servingProjectRoot ? serveRevalidatedStatic(req, absPath, res, projectAssetValidators) : serveStatic(absPath, res);
11321
11368
  await serve.catch((err) => {
11322
11369
  if (!res.headersSent) {
@@ -11392,7 +11439,7 @@ async function startPreviewServer(options) {
11392
11439
  const config = await initializeConfiguration(inputPath);
11393
11440
  const tempDir = await initializePreviewDirectories();
11394
11441
  const state = createServerState(inputPath, tempDir, config, options);
11395
- await generateAndWriteHtml(inputPath, tempDir, config, state.cssAssets);
11442
+ await generateAndWriteHtml(inputPath, tempDir, config, state.cssAssets, state.imageRefs);
11396
11443
  const restartPreview2 = async (newInputPath) => {
11397
11444
  await restartPreview(newInputPath, state);
11398
11445
  };
@@ -29,7 +29,7 @@ var favicon_default = "./favicon-wkbm9cjn.ico";
29
29
  var manifest_schema_default = "./manifest.schema-40sfq49z.json";
30
30
 
31
31
  // src/assets/preview/scripts/preview-interface.js
32
- var preview_interface_default = "./preview-interface-5wnzrvhf.js";
32
+ var preview_interface_default = "./preview-interface-c44qk17f.js";
33
33
 
34
34
  // src/assets/preview/scripts/preview-bridge.js
35
35
  var preview_bridge_default = "./preview-bridge-8jhd1wpb.js";
@@ -41,7 +41,7 @@ var preview_shell_default = "./preview-shell-xwn84kqr.js";
41
41
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
42
42
 
43
43
  // src/assets/engine/gutterpress-viewer.js
44
- var gutterpress_viewer_default = "./gutterpress-viewer-swz6bwfa.js";
44
+ var gutterpress_viewer_default = "./gutterpress-viewer-zkv2stfy.js";
45
45
 
46
46
  // src/assets/engine/gutterpress-agent.js
47
47
  var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
package/dist/index.js CHANGED
@@ -148,13 +148,13 @@ import {
148
148
  testRemoteAccess,
149
149
  updateExtensions,
150
150
  validateProjectExtensions
151
- } from "./index-40achhs8.js";
151
+ } from "./index-c275p061.js";
152
152
  import {
153
153
  BUILD_DIAGNOSTIC_CODES
154
- } from "./index-qep93v0z.js";
154
+ } from "./index-5dcj1vts.js";
155
155
  import {
156
156
  BuildError
157
- } from "./index-ee7wp8q3.js";
157
+ } from "./index-eyaj6nkc.js";
158
158
  import {
159
159
  DEFAULT_PRINT_OPTS,
160
160
  assertMilestone,
@@ -116,6 +116,16 @@ export interface AssembleBookHtmlOptions {
116
116
  */
117
117
  preloadImages?: string[];
118
118
  title?: string;
119
+ /**
120
+ * The manifest's `print` padding, written on `<html>` as
121
+ * `data-gp-signature` / `data-gp-reserve-last-page` so the viewer shows the
122
+ * blank pages the PDF build appends (`signaturePadding` in
123
+ * engine/shared/synthesis.ts) and the two page counts agree.
124
+ */
125
+ print?: {
126
+ signature?: number;
127
+ reserveLastPage?: boolean;
128
+ };
119
129
  plugins?: LoadedPlugin[];
120
130
  pluginCss?: string;
121
131
  /** Add a layout-neutral source-file id to source-mapped preview blocks. */
@@ -30,6 +30,11 @@ export declare function resolveActiveMarkdownFiles(inputDir: string, configuredF
30
30
  */
31
31
  export declare function renderChapters(inputDir: string, opts?: {
32
32
  title?: string;
33
+ /** See {@link AssembleBookHtmlOptions.print}. */
34
+ print?: {
35
+ signature?: number;
36
+ reserveLastPage?: boolean;
37
+ };
33
38
  styles?: string[];
34
39
  files?: string[] | null;
35
40
  plugins?: LoadedPlugin[];
@@ -67,6 +72,11 @@ export declare function renderChapters(inputDir: string, opts?: {
67
72
  */
68
73
  export declare function renderChaptersToFile(inputDir: string, outDir: string, opts?: {
69
74
  title?: string;
75
+ /** See {@link AssembleBookHtmlOptions.print}. */
76
+ print?: {
77
+ signature?: number;
78
+ reserveLastPage?: boolean;
79
+ };
70
80
  styles?: string[];
71
81
  files?: string[] | null;
72
82
  plugins?: LoadedPlugin[];
@@ -85,3 +85,9 @@ export declare function resolveStaticPath(urlPathname: string, root: string): st
85
85
  * this helper.
86
86
  */
87
87
  export declare function serveFile(filePath: string, res: http.ServerResponse): Promise<void>;
88
+ /**
89
+ * The URL path (no leading slash, decoded, no `./`) an image reference is
90
+ * served at from book.html — the key `ServerState.imageRefs` is looked
91
+ * up by. A query or fragment is dropped, as the server's own path is.
92
+ */
93
+ export declare function imageRefKey(ref: string): string;
@@ -7,16 +7,16 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-ev175hx9.js";
10
+ } from "./cli-6z5zthzt.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-0prrt9e4.js";
13
+ } from "./cli-70xgehq2.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-182j48xc.js";
19
- import"./cli-wqy8hnnh.js";
18
+ } from "./cli-ppc0vz4b.js";
19
+ import"./cli-gymfg04q.js";
20
20
  import {
21
21
  EXIT_CODES
22
22
  } from "./cli-46ycxe6r.js";
@@ -2,16 +2,16 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-ev175hx9.js";
5
+ } from "./cli-6z5zthzt.js";
6
6
  import {
7
7
  log
8
- } from "./cli-0prrt9e4.js";
8
+ } from "./cli-70xgehq2.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-182j48xc.js";
14
- import"./cli-wqy8hnnh.js";
13
+ } from "./cli-ppc0vz4b.js";
14
+ import"./cli-gymfg04q.js";
15
15
  import {
16
16
  BuildError,
17
17
  EXIT_CODES
@@ -43,7 +43,11 @@ export declare function generateAndWriteHtml(inputPath: string, tempDir: string,
43
43
  files?: string[] | null;
44
44
  };
45
45
  extensions?: ResolvedExtensionConfig[];
46
- }, cssAssets: Map<string, string>): Promise<void>;
46
+ print?: {
47
+ signature?: number;
48
+ reserveLastPage?: boolean;
49
+ };
50
+ }, cssAssets: Map<string, string>, imageRefs?: Set<string>): Promise<void>;
47
51
  /**
48
52
  * Chokidar 5's `ignored` matcher (regex or function) is tested against the
49
53
  * fully-qualified, forward-slash-normalized ABSOLUTE path — never a path
@@ -53,6 +53,16 @@ export interface ServerState {
53
53
  * surface.
54
54
  */
55
55
  cssAssets: Map<string, string>;
56
+ /**
57
+ * Every image the current render references (`onImageRefs`, the same list
58
+ * the build plans its asset copies from), normalized to the URL path the
59
+ * preview serves it at. A request for one whose file does not exist gets
60
+ * the build's magenta placeholder (`placeholderPng`) instead of a 404, so
61
+ * the preview lays out the same 640×480 box the PDF does rather than a
62
+ * small broken-image icon — that difference moved every later page by one
63
+ * in a real book (#354). Rebuilt on every render; exact-match lookups only.
64
+ */
65
+ imageRefs: Set<string>;
56
66
  }
57
67
  /**
58
68
  * Create initial server state
@@ -7,10 +7,10 @@ import {
7
7
  runBuild,
8
8
  splitOutPath,
9
9
  startPreviewServer
10
- } from "./cli-ev175hx9.js";
10
+ } from "./cli-6z5zthzt.js";
11
11
  import {
12
12
  log
13
- } from "./cli-0prrt9e4.js";
13
+ } from "./cli-70xgehq2.js";
14
14
  import {
15
15
  UsageError,
16
16
  parseFormat,
@@ -18,8 +18,8 @@ import {
18
18
  rejectExtraPositionals,
19
19
  rejectUnknownFlags,
20
20
  resolvePort
21
- } from "./cli-182j48xc.js";
22
- import"./cli-wqy8hnnh.js";
21
+ } from "./cli-ppc0vz4b.js";
22
+ import"./cli-gymfg04q.js";
23
23
  import {
24
24
  BuildError
25
25
  } from "./cli-46ycxe6r.js";