gutterpress 0.11.16-alpha.6 → 0.11.17-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-bk0rw638.js → build-jfjzam95.js} +4 -4
  3. package/dist/{cli-n9e1mzb7.js → cli-0754wskb.js} +1 -1
  4. package/dist/{cli-ac7zs8sp.js → cli-ew3gc4nt.js} +166 -100
  5. package/dist/{cli-wqy8hnnh.js → cli-xx5bryh6.js} +7 -5
  6. package/dist/{cli-nzzsyw1e.js → cli-zce1zcqc.js} +1 -1
  7. package/dist/cli.js +12 -12
  8. package/dist/{doctor-qmd7gjwt.js → doctor-h01f71ws.js} +2 -2
  9. package/dist/engine/shared/synthesis.d.ts +8 -0
  10. package/dist/{engine-w17dbt4f.js → engine-aaam2nz3.js} +2 -2
  11. package/dist/{engine-j5h953pg.js → engine-rhmwgk9k.js} +1 -1
  12. package/dist/{ext-j28pn3ng.js → ext-ja4hy1c2.js} +4 -4
  13. package/dist/{gutterpress-viewer-swz6bwfa.js → gutterpress-viewer-5a6h4evk.js} +46 -5
  14. package/dist/{index-qep93v0z.js → index-3nk5mnn6.js} +7 -5
  15. package/dist/{index-ee7wp8q3.js → index-dmm31ys3.js} +1 -1
  16. package/dist/{index-ss8dv3zf.js → index-r9t4acx5.js} +165 -97
  17. package/dist/index.js +3 -3
  18. package/dist/lib/markdown/assemble.d.ts +10 -0
  19. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  20. package/dist/lib/markdown/index.d.ts +10 -0
  21. package/dist/lib/static-serve.d.ts +6 -0
  22. package/dist/{new-rxkqh5t2.js → new-yzswp0ge.js} +4 -4
  23. package/dist/{preflight-wm3seg5z.js → preflight-0s1vw6y1.js} +4 -4
  24. package/dist/preview/file-watcher.d.ts +5 -1
  25. package/dist/preview/server-context.d.ts +10 -0
  26. package/dist/{preview-55jbgv9p.js → preview-tam7azsr.js} +4 -4
  27. package/dist/{publish-mv3kcwwn.js → publish-ra8kyf0z.js} +4 -4
  28. package/dist/render.js +30 -8
  29. package/dist/{validate-bhn2tjsw.js → validate-n9vae7p8.js} +4 -4
  30. package/package.json +1 -1
package/dist/api/index.js CHANGED
@@ -131,10 +131,10 @@ import {
131
131
  testRemoteAccess,
132
132
  updateExtensions,
133
133
  validateProjectExtensions
134
- } from "../index-ss8dv3zf.js";
134
+ } from "../index-r9t4acx5.js";
135
135
  import {
136
136
  BuildError
137
- } from "../index-ee7wp8q3.js";
137
+ } from "../index-dmm31ys3.js";
138
138
  import {
139
139
  DEFAULT_PRINT_OPTS,
140
140
  assertMilestone,
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  runBuild,
3
3
  splitOutPath
4
- } from "./cli-ac7zs8sp.js";
4
+ } from "./cli-ew3gc4nt.js";
5
5
  import {
6
6
  log
7
- } from "./cli-n9e1mzb7.js";
7
+ } from "./cli-0754wskb.js";
8
8
  import {
9
9
  UsageError,
10
10
  parseFormat,
11
11
  parsePdfxFlavor,
12
12
  rejectExtraPositionals,
13
13
  rejectUnknownFlags
14
- } from "./cli-nzzsyw1e.js";
15
- import"./cli-wqy8hnnh.js";
14
+ } from "./cli-zce1zcqc.js";
15
+ import"./cli-xx5bryh6.js";
16
16
  import {
17
17
  BuildError
18
18
  } from "./cli-46ycxe6r.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-nzzsyw1e.js";
3
+ } from "./cli-zce1zcqc.js";
4
4
  import {
5
5
  execCapture,
6
6
  findTool,
@@ -13,17 +13,17 @@ import {
13
13
  stampCreator,
14
14
  stripAnnotations,
15
15
  warn
16
- } from "./cli-n9e1mzb7.js";
16
+ } from "./cli-0754wskb.js";
17
17
  import {
18
18
  DEBOUNCE,
19
19
  UsageError,
20
20
  resolvePort
21
- } from "./cli-nzzsyw1e.js";
21
+ } from "./cli-zce1zcqc.js";
22
22
  import {
23
23
  MARGIN_BOX_IGNORED_PROPERTIES,
24
24
  getAssetPath,
25
25
  getAssetsDir
26
- } from "./cli-wqy8hnnh.js";
26
+ } from "./cli-xx5bryh6.js";
27
27
  import {
28
28
  BuildError,
29
29
  EXIT_CODES
@@ -1181,16 +1181,23 @@ function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
1181
1181
  }
1182
1182
  return { name, baseKind: name, ...resolveContainerShape(name, pluginName, rawDecl) };
1183
1183
  }
1184
+ var CORE_MARKER_OWNER = "Gutterpress";
1185
+ var CORE_DECLARED_MARKERS = {
1186
+ columns: {
1187
+ class: "gp-columns",
1188
+ variants: { 1: "gp-columns-1", 2: "gp-columns-2", 3: "gp-columns-3", 4: "gp-columns-4", 5: "gp-columns-5" }
1189
+ }
1190
+ };
1184
1191
  function buildDeclaredMarkerRegistry(sources) {
1185
1192
  const rawRegistry = new Map;
1186
1193
  const originOf = new Map;
1187
- for (const { pluginName, markers } of sources) {
1194
+ for (const { pluginName, markers } of [{ pluginName: CORE_MARKER_OWNER, markers: CORE_DECLARED_MARKERS }, ...sources]) {
1188
1195
  if (!markers)
1189
1196
  continue;
1190
1197
  for (const [name, rawDecl] of Object.entries(markers)) {
1191
1198
  validateDeclaredMarkerName(name, pluginName);
1192
- if (KNOWN_KINDS.includes(name)) {
1193
- throw new Error(`Plugin "${pluginName}" declares marker "@${name}", which is a core Gutterpress marker name ` + `(${KNOWN_KINDS.map((k) => `@${k}`).join(", ")}). Core marker names cannot be shadowed — ` + `rename the plugin's marker.`);
1199
+ if (KNOWN_KINDS.includes(name) || pluginName !== CORE_MARKER_OWNER && name in CORE_DECLARED_MARKERS) {
1200
+ throw new Error(`Plugin "${pluginName}" declares marker "@${name}", which is a core Gutterpress marker name ` + `(${[...KNOWN_KINDS, ...Object.keys(CORE_DECLARED_MARKERS)].map((k) => `@${k}`).join(", ")}). ` + `Core marker names cannot be shadowed — ` + `rename the plugin's marker.`);
1194
1201
  }
1195
1202
  if (rawRegistry.has(name)) {
1196
1203
  throw new Error(`Marker "@${name}" is declared by both plugin "${originOf.get(name)}" and plugin "${pluginName}". ` + `Declared marker names must be unique across every loaded plugin — rename one of them.`);
@@ -1273,7 +1280,7 @@ function plugin(md, pluginOptions = {}) {
1273
1280
  preferPagesInSpreads: false,
1274
1281
  ...pluginOptions
1275
1282
  };
1276
- const declaredMarkers = options.declaredMarkers instanceof Map && options.declaredMarkers.size > 0 ? options.declaredMarkers : null;
1283
+ const declaredMarkers = options.declaredMarkers instanceof Map && options.declaredMarkers.size > 0 ? options.declaredMarkers : buildDeclaredMarkerRegistry([]);
1277
1284
  const declaredWords = declaredKindWords(declaredMarkers);
1278
1285
  function markerBlock(state, startLine, endLine, silent) {
1279
1286
  const pos = state.bMarks[startLine] + state.tShift[startLine];
@@ -1832,9 +1839,19 @@ var GUTTERPRESS_CSS = `
1832
1839
  NOT set, because the correct value depends on whether the run fragments
1833
1840
  across pages (auto packs each page's columns; the CSS initial balance is
1834
1841
  right for a run that fits on one page) and only the author knows which.
1835
- --gp-column-gap is author-settable. */
1836
- .gp-columns-2 { columns: 2; column-gap: var(--gp-column-gap, 1.5em); }
1837
- .gp-columns-3 { columns: 3; column-gap: var(--gp-column-gap, 1.5em); }
1842
+ --gp-column-gap is author-settable.
1843
+ .gp-columns is what the core @columns marker emits (markers.js); on its
1844
+ own it is two columns, and @columns 1 to 5 add the matching count, which
1845
+ wins because it comes later at the same specificity. */
1846
+ .gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5 {
1847
+ column-gap: var(--gp-column-gap, 1.5em);
1848
+ }
1849
+ .gp-columns { columns: 2; }
1850
+ .gp-columns-1 { columns: 1; }
1851
+ .gp-columns-2 { columns: 2; }
1852
+ .gp-columns-3 { columns: 3; }
1853
+ .gp-columns-4 { columns: 4; }
1854
+ .gp-columns-5 { columns: 5; }
1838
1855
 
1839
1856
  /* the per-shape decisions the paragraph above deliberately leaves to the
1840
1857
  author, named instead of left as raw CSS every book was reinventing
@@ -2035,8 +2052,12 @@ var GP_CLASSES = new Set([
2035
2052
  "gp-large",
2036
2053
  "gp-tight",
2037
2054
  "gp-loose",
2055
+ "gp-columns",
2056
+ "gp-columns-1",
2038
2057
  "gp-columns-2",
2039
2058
  "gp-columns-3",
2059
+ "gp-columns-4",
2060
+ "gp-columns-5",
2040
2061
  "gp-columns-all",
2041
2062
  "gp-columns-flow",
2042
2063
  "gp-columns-balanced",
@@ -8312,8 +8333,9 @@ ${projectCss.trim()}` : null
8312
8333
  `);
8313
8334
  const preloadTags = (opts.preloadImages ?? []).map((href) => `
8314
8335
  <link rel="preload" as="image" href="${href}">`).join("");
8336
+ const printAttrs = (opts.print?.signature && opts.print.signature > 1 ? ` data-gp-signature="${opts.print.signature}"` : "") + (opts.print?.reserveLastPage ? " data-gp-reserve-last-page" : "");
8315
8337
  return `<!DOCTYPE html>
8316
- <html lang="en">
8338
+ <html lang="en"${printAttrs}>
8317
8339
  <head>
8318
8340
  <meta charset="UTF-8">
8319
8341
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -8375,6 +8397,7 @@ ${css.trim()}
8375
8397
  projectCss: inlined.css,
8376
8398
  preloadImages: cssAssetCopies.map((c) => c.to),
8377
8399
  title: opts.title,
8400
+ print: opts.print,
8378
8401
  plugins: opts.plugins,
8379
8402
  pluginCss,
8380
8403
  annotateSourceChapters: opts.annotateSourceChapters,
@@ -8386,6 +8409,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
8386
8409
  await mkdir4(outDir, { recursive: true });
8387
8410
  const html = await renderChapters(inputDir, {
8388
8411
  title: opts.title,
8412
+ print: opts.print,
8389
8413
  styles: opts.styles,
8390
8414
  files: opts.files,
8391
8415
  plugins: opts.plugins,
@@ -9506,6 +9530,7 @@ async function renderBook(ctx) {
9506
9530
  const cssAssets = [];
9507
9531
  const htmlFile = await renderChaptersToFile(renderDir, workDir, {
9508
9532
  title: config.title,
9533
+ print: config.print,
9509
9534
  styles: config.styles,
9510
9535
  files: config.source.files,
9511
9536
  plugins,
@@ -9657,7 +9682,7 @@ class PdfOutput {
9657
9682
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
9658
9683
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
9659
9684
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
9660
- const { buildNativePdf } = await import("./engine-j5h953pg.js");
9685
+ const { buildNativePdf } = await import("./engine-rhmwgk9k.js");
9661
9686
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
9662
9687
  title: config.title,
9663
9688
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -9788,7 +9813,7 @@ function openPath(filePath) {
9788
9813
  }
9789
9814
 
9790
9815
  // src/preview/lifecycle.ts
9791
- import path15 from "path";
9816
+ import path16 from "path";
9792
9817
  import { tmpdir } from "os";
9793
9818
  import { randomBytes as randomBytes2 } from "crypto";
9794
9819
  import { readdir as readdir4, readFile as readFile24, writeFile as writeFile5 } from "node:fs/promises";
@@ -9813,7 +9838,65 @@ async function mkdir6(dirPath) {
9813
9838
  // src/preview/file-watcher.ts
9814
9839
  import { watch } from "chokidar";
9815
9840
  import fsp3 from "node:fs/promises";
9816
- import path14 from "path";
9841
+ import path15 from "path";
9842
+
9843
+ // src/lib/static-serve.ts
9844
+ import path14 from "node:path";
9845
+ var STATIC_MIME = {
9846
+ ".html": "text/html; charset=utf-8",
9847
+ ".htm": "text/html; charset=utf-8",
9848
+ ".css": "text/css",
9849
+ ".js": "application/javascript",
9850
+ ".mjs": "application/javascript",
9851
+ ".json": "application/json",
9852
+ ".png": "image/png",
9853
+ ".jpg": "image/jpeg",
9854
+ ".jpeg": "image/jpeg",
9855
+ ".gif": "image/gif",
9856
+ ".svg": "image/svg+xml",
9857
+ ".webp": "image/webp",
9858
+ ".avif": "image/avif",
9859
+ ".ico": "image/x-icon",
9860
+ ".woff": "font/woff",
9861
+ ".woff2": "font/woff2",
9862
+ ".ttf": "font/ttf",
9863
+ ".otf": "font/otf"
9864
+ };
9865
+ function resolveWithinRoot(relPath, root) {
9866
+ const resolvedRoot = path14.resolve(root);
9867
+ const candidate = path14.resolve(resolvedRoot, "." + (relPath.startsWith("/") ? relPath : "/" + relPath));
9868
+ if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path14.sep)) {
9869
+ return null;
9870
+ }
9871
+ return candidate;
9872
+ }
9873
+ function hasDotSegment(urlPathname) {
9874
+ let decoded;
9875
+ try {
9876
+ decoded = decodeURIComponent(urlPathname);
9877
+ } catch {
9878
+ return true;
9879
+ }
9880
+ return decoded.split(/[/\\]/).some((segment) => segment.startsWith("."));
9881
+ }
9882
+ function resolveStaticPath(urlPathname, root) {
9883
+ let decoded;
9884
+ try {
9885
+ decoded = decodeURIComponent(urlPathname);
9886
+ } catch {
9887
+ return null;
9888
+ }
9889
+ return resolveWithinRoot(decoded, root);
9890
+ }
9891
+ function imageRefKey(ref) {
9892
+ let key = ref.split(/[?#]/)[0].replace(/^\.\//, "").replace(/^\/+/, "");
9893
+ try {
9894
+ key = decodeURIComponent(key);
9895
+ } catch {}
9896
+ return key;
9897
+ }
9898
+
9899
+ // src/preview/file-watcher.ts
9817
9900
  var EMPTY_BOOK_HTML = `<!doctype html>
9818
9901
  <html lang="en">
9819
9902
  <head>
@@ -9834,12 +9917,14 @@ async function renderPreviewBook(inputPath, config, opts) {
9834
9917
  const { plugins, pluginStyles } = await loadPluginsWithCss(config.extensions, inputPath, (ref, err) => warn(`Skipping plugin "${ref}" in preview — ${err.message}`));
9835
9918
  return renderChapters(inputPath, {
9836
9919
  title: config.title ?? "Document",
9920
+ print: config.print,
9837
9921
  styles: config.styles,
9838
9922
  files: opts.files,
9839
9923
  plugins,
9840
9924
  pluginStyles,
9841
9925
  annotateSourceChapters: true,
9842
9926
  ...opts.onCssAssets ? { onCssAssets: opts.onCssAssets } : {},
9927
+ ...opts.onImageRefs ? { onImageRefs: opts.onImageRefs } : {},
9843
9928
  onChapterWarnings: (file, warnings) => {
9844
9929
  for (const w of warnings) {
9845
9930
  warn(` ${file}, line ${w.line}: ${w.message}`);
@@ -9854,24 +9939,35 @@ function injectPreviewScripts(html) {
9854
9939
  `;
9855
9940
  return /<\/head>/i.test(html) ? html.replace(/<\/head>/i, scripts + "</head>") : html + scripts;
9856
9941
  }
9857
- async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
9942
+ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, imageRefs) {
9858
9943
  if (!inputPath) {
9859
9944
  cssAssets.clear();
9860
- await fsp3.writeFile(path14.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
9945
+ imageRefs?.clear();
9946
+ await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
9861
9947
  return;
9862
9948
  }
9863
9949
  const nextAssets = new Map;
9950
+ const nextImageRefs = new Set;
9864
9951
  const html = await renderPreviewBook(inputPath, config, {
9865
9952
  files: config.source?.files ?? null,
9866
9953
  onCssAssets: (copies) => {
9867
9954
  for (const copy of copies)
9868
9955
  nextAssets.set(copy.to, copy.from);
9956
+ },
9957
+ onImageRefs: (refs) => {
9958
+ for (const ref of refs)
9959
+ nextImageRefs.add(imageRefKey(ref));
9869
9960
  }
9870
9961
  });
9871
9962
  cssAssets.clear();
9872
9963
  for (const [to, from] of nextAssets)
9873
9964
  cssAssets.set(to, from);
9874
- await fsp3.writeFile(path14.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
9965
+ if (imageRefs) {
9966
+ imageRefs.clear();
9967
+ for (const ref of nextImageRefs)
9968
+ imageRefs.add(ref);
9969
+ }
9970
+ await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
9875
9971
  }
9876
9972
  function isDotPathUnderRoot(candidatePath, root) {
9877
9973
  const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -9908,23 +10004,23 @@ function isIgnoredWatchPath(candidatePath, projectRoot) {
9908
10004
  return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
9909
10005
  }
9910
10006
  function isExternalWatchCandidate(candidatePath, targets) {
9911
- const candidate = path14.resolve(candidatePath);
10007
+ const candidate = path15.resolve(candidatePath);
9912
10008
  for (const rawTarget of targets) {
9913
- const target = path14.resolve(rawTarget);
9914
- const rel = path14.relative(candidate, target);
9915
- if (rel === "" || rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel))
10009
+ const target = path15.resolve(rawTarget);
10010
+ const rel = path15.relative(candidate, target);
10011
+ if (rel === "" || rel !== ".." && !rel.startsWith(`..${path15.sep}`) && !path15.isAbsolute(rel))
9916
10012
  return true;
9917
10013
  }
9918
10014
  return false;
9919
10015
  }
9920
10016
  async function nearestExistingDirectory(start) {
9921
- let current = path14.resolve(start);
10017
+ let current = path15.resolve(start);
9922
10018
  while (true) {
9923
10019
  try {
9924
10020
  if ((await fsp3.stat(current)).isDirectory())
9925
10021
  return current;
9926
10022
  } catch {}
9927
- const parent = path14.dirname(current);
10023
+ const parent = path15.dirname(current);
9928
10024
  if (parent === current)
9929
10025
  return current;
9930
10026
  current = parent;
@@ -9933,7 +10029,7 @@ async function nearestExistingDirectory(start) {
9933
10029
  async function externalWatchRoots(targets) {
9934
10030
  const roots = new Set;
9935
10031
  for (const target of targets) {
9936
- roots.add(await nearestExistingDirectory(path14.dirname(path14.resolve(target))));
10032
+ roots.add(await nearestExistingDirectory(path15.dirname(path15.resolve(target))));
9937
10033
  }
9938
10034
  return [...roots];
9939
10035
  }
@@ -9947,19 +10043,19 @@ async function extensionFolderFiles(dir) {
9947
10043
  const meta = await readExtensionMeta(dir);
9948
10044
  const entry = extensionEntry(meta);
9949
10045
  return [
9950
- path14.join(dir, EXTENSION_MANIFEST_FILENAME),
9951
- ...entry ? [path14.resolve(dir, entry)] : [],
10046
+ path15.join(dir, EXTENSION_MANIFEST_FILENAME),
10047
+ ...entry ? [path15.resolve(dir, entry)] : [],
9952
10048
  ...await collectStyleDependencies(dir, extensionStyleList(meta))
9953
10049
  ];
9954
10050
  }
9955
10051
  async function externalWatchTargets(projectDir, config) {
9956
- const root = path14.resolve(projectDir);
10052
+ const root = path15.resolve(projectDir);
9957
10053
  let canonicalRoot = root;
9958
10054
  try {
9959
10055
  canonicalRoot = await fsp3.realpath(root);
9960
10056
  } catch {}
9961
10057
  const styles = await resolveActiveStyles(root, config.styles);
9962
- const extensionPaths = (config.extensions ?? []).map((p) => p.path).filter((p) => !!p).map((p) => path14.resolve(root, p));
10058
+ const extensionPaths = (config.extensions ?? []).map((p) => p.path).filter((p) => !!p).map((p) => path15.resolve(root, p));
9963
10059
  const candidates = [
9964
10060
  ...await collectStyleDependencies(root, styles),
9965
10061
  ...extensionPaths,
@@ -9967,17 +10063,17 @@ async function externalWatchTargets(projectDir, config) {
9967
10063
  ];
9968
10064
  const external = new Set;
9969
10065
  for (const abs of candidates) {
9970
- const authoredPath = path14.resolve(abs);
9971
- const authoredRel = path14.relative(root, authoredPath);
9972
- if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${path14.sep}`) || path14.isAbsolute(authoredRel))) {
10066
+ const authoredPath = path15.resolve(abs);
10067
+ const authoredRel = path15.relative(root, authoredPath);
10068
+ if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${path15.sep}`) || path15.isAbsolute(authoredRel))) {
9973
10069
  external.add(authoredPath);
9974
10070
  }
9975
10071
  let canonicalPath = authoredPath;
9976
10072
  try {
9977
10073
  canonicalPath = await fsp3.realpath(canonicalPath);
9978
10074
  } catch {}
9979
- const rel = path14.relative(canonicalRoot, canonicalPath);
9980
- if (rel === "" || rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel))
10075
+ const rel = path15.relative(canonicalRoot, canonicalPath);
10076
+ if (rel === "" || rel !== ".." && !rel.startsWith(`..${path15.sep}`) && !path15.isAbsolute(rel))
9981
10077
  continue;
9982
10078
  external.add(canonicalPath);
9983
10079
  }
@@ -10004,7 +10100,7 @@ async function addAndAwaitWatch(watcher, roots) {
10004
10100
  }
10005
10101
  }
10006
10102
  function createFileWatcher(state) {
10007
- const inputResolved = path14.resolve(state.currentInputPath);
10103
+ const inputResolved = path15.resolve(state.currentInputPath);
10008
10104
  const watcher = watch(inputResolved, {
10009
10105
  persistent: true,
10010
10106
  ignoreInitial: true,
@@ -10044,7 +10140,7 @@ function createFileWatcher(state) {
10044
10140
  async function recordChange(event, filePath) {
10045
10141
  if (closed)
10046
10142
  return;
10047
- const target = path14.resolve(filePath);
10143
+ const target = path15.resolve(filePath);
10048
10144
  const settled = settledWrites.get(target);
10049
10145
  if (settled && event !== "unlink" && Date.now() <= settled.expiresAt) {
10050
10146
  const diskContent = await fsp3.readFile(target, "utf8").catch(() => null);
@@ -10065,7 +10161,7 @@ function createFileWatcher(state) {
10065
10161
  recordChange(event, filePath);
10066
10162
  });
10067
10163
  externalWatcher.on("all", (event, filePath) => {
10068
- const target = path14.resolve(filePath);
10164
+ const target = path15.resolve(filePath);
10069
10165
  if (!desiredExternals.has(target))
10070
10166
  return;
10071
10167
  if (event === "add" && suppressInitialExternalAdds.delete(target))
@@ -10073,8 +10169,8 @@ function createFileWatcher(state) {
10073
10169
  recordChange(event, target);
10074
10170
  });
10075
10171
  function isWatchedSource(target) {
10076
- const rel = path14.relative(inputResolved, target);
10077
- const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel);
10172
+ const rel = path15.relative(inputResolved, target);
10173
+ const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path15.sep}`) && !path15.isAbsolute(rel);
10078
10174
  if (inProject)
10079
10175
  return !isIgnoredWatchPath(target, inputResolved);
10080
10176
  return desiredExternals.has(target);
@@ -10099,7 +10195,7 @@ function createFileWatcher(state) {
10099
10195
  state.notifySettledWrite = (filePath, writtenContent) => {
10100
10196
  if (closed)
10101
10197
  return;
10102
- const target = path14.resolve(filePath);
10198
+ const target = path15.resolve(filePath);
10103
10199
  if (isWatchedSource(target)) {
10104
10200
  acceptSettledWrite(target, writtenContent);
10105
10201
  return;
@@ -10120,7 +10216,7 @@ function createFileWatcher(state) {
10120
10216
  return;
10121
10217
  let nextTargets;
10122
10218
  try {
10123
- nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) => path14.resolve(target)));
10219
+ nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) => path15.resolve(target)));
10124
10220
  } catch (err) {
10125
10221
  debug(`Could not resolve external watch targets: ${err}`);
10126
10222
  return;
@@ -10184,7 +10280,7 @@ function createFileWatcher(state) {
10184
10280
  await syncExternalWatches();
10185
10281
  if (closed)
10186
10282
  return;
10187
- await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig, state.cssAssets);
10283
+ await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig, state.cssAssets, state.imageRefs);
10188
10284
  if (closed)
10189
10285
  return;
10190
10286
  state.previewServer?.broadcastReload();
@@ -10246,7 +10342,7 @@ async function stopFileWatcher(state) {
10246
10342
  }
10247
10343
 
10248
10344
  // src/preview/lifecycle.ts
10249
- var TEMP_DIR_BASE = path15.join(tmpdir(), "gutterpress-preview");
10345
+ var TEMP_DIR_BASE = path16.join(tmpdir(), "gutterpress-preview");
10250
10346
  var PID_FILE_NAME = ".gutterpress.pid";
10251
10347
  function isProcessAlive(pid) {
10252
10348
  if (!Number.isFinite(pid) || pid <= 0)
@@ -10266,8 +10362,8 @@ async function cleanupOrphanTempDirs() {
10266
10362
  return;
10267
10363
  }
10268
10364
  for (const entry of entries) {
10269
- const dir = path15.join(TEMP_DIR_BASE, entry);
10270
- const pidFile = path15.join(dir, PID_FILE_NAME);
10365
+ const dir = path16.join(TEMP_DIR_BASE, entry);
10366
+ const pidFile = path16.join(dir, PID_FILE_NAME);
10271
10367
  try {
10272
10368
  const raw = await readFile24(pidFile, "utf8");
10273
10369
  const pid = parseInt(raw.trim(), 10);
@@ -10281,10 +10377,10 @@ async function cleanupOrphanTempDirs() {
10281
10377
  async function initializePreviewDirectories() {
10282
10378
  await cleanupOrphanTempDirs();
10283
10379
  const tempDirSuffix = randomBytes2(8).toString("hex");
10284
- const tempDir = path15.join(TEMP_DIR_BASE, tempDirSuffix);
10380
+ const tempDir = path16.join(TEMP_DIR_BASE, tempDirSuffix);
10285
10381
  await mkdir6(tempDir);
10286
10382
  debug(`Created temporary directory: ${tempDir}`);
10287
- await writeFile5(path15.join(tempDir, PID_FILE_NAME), `${process.pid}
10383
+ await writeFile5(path16.join(tempDir, PID_FILE_NAME), `${process.pid}
10288
10384
  `, "utf8");
10289
10385
  return tempDir;
10290
10386
  }
@@ -10307,7 +10403,7 @@ async function restartPreview(newInputPath, state) {
10307
10403
  state.currentInputPath = newInputPath;
10308
10404
  const manifest = await loadManifest(newInputPath);
10309
10405
  state.config = resolveConfig({}, manifest);
10310
- await generateAndWriteHtml(newInputPath, state.tempDir, state.config, state.cssAssets);
10406
+ await generateAndWriteHtml(newInputPath, state.tempDir, state.config, state.cssAssets, state.imageRefs);
10311
10407
  startFileWatcher(state);
10312
10408
  state.previewServer?.broadcastReload();
10313
10409
  info("Preview restarted successfully");
@@ -10367,7 +10463,8 @@ function createServerState(inputPath, tempDir, config, options) {
10367
10463
  tempDir,
10368
10464
  config,
10369
10465
  options,
10370
- cssAssets: new Map
10466
+ cssAssets: new Map,
10467
+ imageRefs: new Set
10371
10468
  };
10372
10469
  }
10373
10470
 
@@ -10378,57 +10475,6 @@ import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypt
10378
10475
  import { readFile as readFile25, stat as stat5 } from "node:fs/promises";
10379
10476
  import path17 from "path";
10380
10477
  import { WebSocket, WebSocketServer } from "ws";
10381
-
10382
- // src/lib/static-serve.ts
10383
- import path16 from "node:path";
10384
- var STATIC_MIME = {
10385
- ".html": "text/html; charset=utf-8",
10386
- ".htm": "text/html; charset=utf-8",
10387
- ".css": "text/css",
10388
- ".js": "application/javascript",
10389
- ".mjs": "application/javascript",
10390
- ".json": "application/json",
10391
- ".png": "image/png",
10392
- ".jpg": "image/jpeg",
10393
- ".jpeg": "image/jpeg",
10394
- ".gif": "image/gif",
10395
- ".svg": "image/svg+xml",
10396
- ".webp": "image/webp",
10397
- ".avif": "image/avif",
10398
- ".ico": "image/x-icon",
10399
- ".woff": "font/woff",
10400
- ".woff2": "font/woff2",
10401
- ".ttf": "font/ttf",
10402
- ".otf": "font/otf"
10403
- };
10404
- function resolveWithinRoot(relPath, root) {
10405
- const resolvedRoot = path16.resolve(root);
10406
- const candidate = path16.resolve(resolvedRoot, "." + (relPath.startsWith("/") ? relPath : "/" + relPath));
10407
- if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path16.sep)) {
10408
- return null;
10409
- }
10410
- return candidate;
10411
- }
10412
- function hasDotSegment(urlPathname) {
10413
- let decoded;
10414
- try {
10415
- decoded = decodeURIComponent(urlPathname);
10416
- } catch {
10417
- return true;
10418
- }
10419
- return decoded.split(/[/\\]/).some((segment) => segment.startsWith("."));
10420
- }
10421
- function resolveStaticPath(urlPathname, root) {
10422
- let decoded;
10423
- try {
10424
- decoded = decodeURIComponent(urlPathname);
10425
- } catch {
10426
- return null;
10427
- }
10428
- return resolveWithinRoot(decoded, root);
10429
- }
10430
-
10431
- // src/preview/http-server.ts
10432
10478
  var HMR_PATH = "/__gutterpress-hmr";
10433
10479
  function shellHtml(initialRevision, instanceId) {
10434
10480
  return `<!doctype html>
@@ -10567,6 +10613,22 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
10567
10613
  }
10568
10614
  await serveStatic(absPath, res, PROJECT_ASSET_CACHE_CONTROL, { ETag: validator.etag });
10569
10615
  }
10616
+ var missingImagePng;
10617
+ async function isMissingReferencedImage(pathname, absPath, refs) {
10618
+ if (!refs.has(imageRefKey(pathname)))
10619
+ return false;
10620
+ try {
10621
+ await stat5(absPath);
10622
+ return false;
10623
+ } catch (err) {
10624
+ return err?.code === "ENOENT";
10625
+ }
10626
+ }
10627
+ function serveMissingImage(res) {
10628
+ missingImagePng ??= Buffer.from(placeholderPng());
10629
+ res.writeHead(200, { "Content-Type": "image/png", "Cache-Control": "no-store" });
10630
+ res.end(missingImagePng);
10631
+ }
10570
10632
  function matchesEmbedded(urlPathname) {
10571
10633
  if (EMBEDDED_EXACT.has(urlPathname))
10572
10634
  return true;
@@ -10718,6 +10780,10 @@ async function createPreviewServer(state, port) {
10718
10780
  res.end("Not Found");
10719
10781
  return;
10720
10782
  }
10783
+ if (servingProjectRoot && await isMissingReferencedImage(pathname, absPath, state.imageRefs)) {
10784
+ serveMissingImage(res);
10785
+ return;
10786
+ }
10721
10787
  const serve = servingProjectRoot ? serveRevalidatedStatic(req, absPath, res, projectAssetValidators) : serveStatic(absPath, res);
10722
10788
  await serve.catch((err) => {
10723
10789
  if (!res.headersSent) {
@@ -10793,7 +10859,7 @@ async function startPreviewServer(options) {
10793
10859
  const config = await initializeConfiguration(inputPath);
10794
10860
  const tempDir = await initializePreviewDirectories();
10795
10861
  const state = createServerState(inputPath, tempDir, config, options);
10796
- await generateAndWriteHtml(inputPath, tempDir, config, state.cssAssets);
10862
+ await generateAndWriteHtml(inputPath, tempDir, config, state.cssAssets, state.imageRefs);
10797
10863
  const restartPreview2 = async (newInputPath) => {
10798
10864
  await restartPreview(newInputPath, state);
10799
10865
  };
@@ -681,6 +681,11 @@ function planRectoBlanks(sites) {
681
681
  return wrong;
682
682
  });
683
683
  }
684
+ function signaturePadding(pageCount, signature = 1, reserveLastPage = false) {
685
+ const size = signature > 1 ? signature : 1;
686
+ const padded = (size - pageCount % size) % size;
687
+ return padded === 0 && reserveLastPage ? size : padded;
688
+ }
684
689
  var WHICH_VALUES = new Set(["first", "start", "last", "first-except"]);
685
690
  function parseWhich(raw) {
686
691
  const w = (raw ?? "").trim();
@@ -1121,7 +1126,7 @@ var preview_shell_default = "./preview-shell-xwn84kqr.js";
1121
1126
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
1122
1127
 
1123
1128
  // src/assets/engine/gutterpress-viewer.js
1124
- var gutterpress_viewer_default = "./gutterpress-viewer-swz6bwfa.js";
1129
+ var gutterpress_viewer_default = "./gutterpress-viewer-5a6h4evk.js";
1125
1130
 
1126
1131
  // src/assets/engine/gutterpress-agent.js
1127
1132
  var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
@@ -1450,10 +1455,7 @@ async function postprocess(input, opts) {
1450
1455
  g.media.width - inset,
1451
1456
  g.media.height - inset
1452
1457
  ];
1453
- const signature = opts.signature && opts.signature > 1 ? opts.signature : 1;
1454
- let padded = (signature - doc.getPageCount() % signature) % signature;
1455
- if (padded === 0 && opts.reserveLastPage)
1456
- padded = signature;
1458
+ const padded = signaturePadding(doc.getPageCount(), opts.signature, opts.reserveLastPage);
1457
1459
  for (let i = 0;i < padded; i++)
1458
1460
  doc.addPage([g.media.width, g.media.height]);
1459
1461
  const wantMarks = opts.marks || g.marks.includes("crop") || g.marks.includes("cross");
@@ -4,7 +4,7 @@ import {
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "gutterpress",
7
- version: "0.11.16-alpha.6",
7
+ version: "0.11.17-alpha.1",
8
8
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
9
9
  author: "itlackey",
10
10
  license: "MPL-2.0",