gutterpress 0.11.20 → 0.11.21

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 (37) hide show
  1. package/README.md +1 -1
  2. package/dist/api/index.js +2 -2
  3. package/dist/{build-t49xka15.js → build-0neyk5h6.js} +4 -4
  4. package/dist/checks/asset/extensions.d.ts +9 -5
  5. package/dist/checks/asset/index.d.ts +0 -3
  6. package/dist/checks/pdf/index.d.ts +0 -1
  7. package/dist/checks/types.d.ts +8 -1
  8. package/dist/{cli-cyej3p9v.js → cli-b57adna7.js} +5 -2
  9. package/dist/{cli-stt6691c.js → cli-j0drzb6f.js} +268 -429
  10. package/dist/{cli-8gm4p42f.js → cli-j79451n6.js} +2 -2
  11. package/dist/{cli-nh16svye.js → cli-sqkccnm7.js} +1 -1
  12. package/dist/cli.js +12 -12
  13. package/dist/{doctor-53hq2v2n.js → doctor-fptq8tvn.js} +2 -2
  14. package/dist/{engine-5fw55dfb.js → engine-0b8bt05n.js} +2 -2
  15. package/dist/{engine-4rkhv5ga.js → engine-x07q927h.js} +1 -1
  16. package/dist/{ext-bvgfwtk7.js → ext-gp624t5c.js} +4 -4
  17. package/dist/{gutterpress-agent-yvd8w8my.js → gutterpress-agent-d29ry3ak.js} +5 -1
  18. package/dist/{index-crys9951.js → index-mr72vt8n.js} +2 -2
  19. package/dist/{index-379qafjq.js → index-mr7zs8mv.js} +442 -605
  20. package/dist/{index-yw16cfm8.js → index-t7r8g75c.js} +4 -1
  21. package/dist/index.js +3 -3
  22. package/dist/lib/asset-inline.d.ts +2 -0
  23. package/dist/lib/image-inspect.d.ts +0 -1
  24. package/dist/lib/presets.d.ts +0 -3
  25. package/dist/lib/validation-exec.d.ts +1 -1
  26. package/dist/{manifest.schema-c4wfpg6g.json → manifest.schema-n292sb77.json} +7 -10
  27. package/dist/{new-jzpneexb.js → new-dre6aw05.js} +4 -4
  28. package/dist/{preflight-c0f0s3y1.js → preflight-1k1zkg1p.js} +4 -4
  29. package/dist/{preview-x6wn3vdh.js → preview-dtrksvxf.js} +4 -4
  30. package/dist/{publish-rskr6x6b.js → publish-g1ytnrmr.js} +4 -4
  31. package/dist/schema/manifest.types.d.ts +10 -2
  32. package/dist/{validate-88w89ye7.js → validate-yrrq8639.js} +4 -4
  33. package/package.json +1 -1
  34. package/dist/checks/asset/image-color-space.d.ts +0 -3
  35. package/dist/checks/asset/image-resolution.d.ts +0 -3
  36. package/dist/checks/asset/image-tac.d.ts +0 -3
  37. package/dist/checks/pdf/rasterized-pages.d.ts +0 -3
@@ -9,7 +9,7 @@ import {
9
9
  getAssetsDir,
10
10
  lastPageMode,
11
11
  parseSize
12
- } from "./index-crys9951.js";
12
+ } from "./index-mr72vt8n.js";
13
13
  import {
14
14
  countLinkAnnotations,
15
15
  getImageResolutions,
@@ -224,8 +224,6 @@ var DTRPG_PRESET = {
224
224
  },
225
225
  assets: {
226
226
  maxImageSize: 1e7,
227
- minImageDpi: 300,
228
- allowedColorSpaces: ["CMYK", "Grayscale"],
229
227
  allowAlpha: false,
230
228
  approvedFontFiles: [],
231
229
  requireFontLicense: false
@@ -306,8 +304,6 @@ var BOOK_PRESET = {
306
304
  },
307
305
  assets: {
308
306
  maxImageSize: 1e7,
309
- minImageDpi: 300,
310
- allowedColorSpaces: ["RGB", "CMYK", "Grayscale"],
311
307
  allowAlpha: true,
312
308
  approvedFontFiles: [],
313
309
  requireFontLicense: false
@@ -378,9 +374,6 @@ var DTRPG_TARGET = {
378
374
  "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
379
375
  "pdf.print.safe-margins": { enabled: true, severity: "error" }
380
376
  },
381
- assets: {
382
- allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
383
- },
384
377
  pdf: {
385
378
  forbidTransparency: DTRPG_PRESET.validate.pdf.forbidTransparency,
386
379
  minImageResolution: DTRPG_PRESET.validate.pdf.minImageResolution
@@ -407,16 +400,16 @@ var ITCH_TARGET = {
407
400
  "pdf.print.pdfx-markers": false,
408
401
  "pdf.print.pdfx-metadata": false,
409
402
  "pdf.print.ink-coverage": false,
410
- "pdf.print.safe-margins": false,
411
- "asset.image.tac-raster": false
403
+ "pdf.print.safe-margins": false
412
404
  },
413
405
  assets: {
414
- allowedColorSpaces: ["RGB", "CMYK", "Grayscale"],
415
- allowAlpha: true
406
+ allowAlpha: true,
407
+ maxImageSize: 0
416
408
  },
417
409
  pdf: {
418
410
  forbidTransparency: false,
419
- requireBleed: false
411
+ requireBleed: false,
412
+ minImageResolution: 0
420
413
  }
421
414
  }
422
415
  },
@@ -652,6 +645,11 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
652
645
  if ((m.validate?.source?.allowedCallouts?.length ?? 0) > 0 || (c.validate?.source?.allowedCallouts?.length ?? 0) > 0) {
653
646
  warnOnce("allowed-callouts-deprecated", "[gutterpress] manifest field `validate.source.allowedCallouts` is " + "deprecated and ignored. The `:::` container syntax it gated was " + "removed 2026-05-17. See docs/migrations/2026-05-removing-container-syntax.md.");
654
647
  }
648
+ for (const key of ["minImageDpi", "allowedColorSpaces"]) {
649
+ if (m.validate?.assets?.[key] !== undefined || c.validate?.assets?.[key] !== undefined) {
650
+ warnOnce(`assets-${key}-deprecated`, `[gutterpress] manifest field \`validate.assets.${key}\` is deprecated and ` + "ignored. The source-image check it configured was removed; the build " + "and the PDF checks measure what actually prints.");
651
+ }
652
+ }
655
653
  const removed = m;
656
654
  if (removed.engine !== undefined) {
657
655
  throw new UsageError("Manifest field `engine` was removed — delete it. The Gutterpress engine " + "(native Chromium pagination) is the only engine, so there is nothing to select.");
@@ -3825,7 +3823,7 @@ import { satisfies, validRange } from "semver";
3825
3823
  // package.json
3826
3824
  var package_default = {
3827
3825
  name: "gutterpress",
3828
- version: "0.11.20",
3826
+ version: "0.11.21",
3829
3827
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
3830
3828
  author: "itlackey",
3831
3829
  license: "MPL-2.0",
@@ -6495,6 +6493,7 @@ async function writeBuildFingerprint(input) {
6495
6493
 
6496
6494
  // src/lib/validation-exec.ts
6497
6495
  import { existsSync as existsSync12 } from "node:fs";
6496
+ import { readFile as readFile26 } from "node:fs/promises";
6498
6497
  import { dirname as dirname4, join as join4, resolve as resolve8 } from "node:path";
6499
6498
 
6500
6499
  // src/lib/markdownlint-fix.ts
@@ -6720,6 +6719,156 @@ async function applyMarkdownlintFixes(ctx) {
6720
6719
  return pending.map((p) => p.file);
6721
6720
  }
6722
6721
 
6722
+ // src/checks/source/local-ref-parser.ts
6723
+ import { readFile as readFile13 } from "node:fs/promises";
6724
+
6725
+ // src/checks/policy.ts
6726
+ var INSPECT_FAILED_CODE = "inspect-failed";
6727
+ function isCheckEnabled(check2, config) {
6728
+ const entry = config.validate.checks[check2.id];
6729
+ if (entry === false)
6730
+ return false;
6731
+ if (typeof entry === "object" && entry.enabled === false)
6732
+ return false;
6733
+ if (check2.enabledWhen && !check2.enabledWhen(config))
6734
+ return false;
6735
+ return true;
6736
+ }
6737
+ function selectChecks(opts, config) {
6738
+ const unmatched = [];
6739
+ let checks2;
6740
+ if (opts.only && opts.only.length > 0) {
6741
+ const resolved = resolveCheckSelectors(opts.only);
6742
+ checks2 = getChecks({ ids: resolved.resolved });
6743
+ unmatched.push(...resolved.unmatched);
6744
+ } else {
6745
+ checks2 = getChecks({ category: opts.category, phase: opts.phase });
6746
+ }
6747
+ if (opts.skip && opts.skip.length > 0) {
6748
+ const resolved = resolveCheckSelectors(opts.skip);
6749
+ const skipSet = new Set(resolved.resolved);
6750
+ checks2 = checks2.filter((c) => !skipSet.has(c.id));
6751
+ unmatched.push(...resolved.unmatched);
6752
+ }
6753
+ checks2 = checks2.filter((c) => isCheckEnabled(c, config));
6754
+ return { checks: checks2, unmatched };
6755
+ }
6756
+ function finding(checkId, init) {
6757
+ return { checkId, ...init };
6758
+ }
6759
+ function inspectionFailed(checkId, message, extra = {}) {
6760
+ return {
6761
+ checkId,
6762
+ severity: "warning",
6763
+ code: INSPECT_FAILED_CODE,
6764
+ message,
6765
+ ...extra
6766
+ };
6767
+ }
6768
+
6769
+ // src/checks/source/local-ref-parser.ts
6770
+ function newlineOffsets(content) {
6771
+ const offsets = [];
6772
+ for (let i = 0;i < content.length; i++)
6773
+ if (content.charCodeAt(i) === 10)
6774
+ offsets.push(i);
6775
+ return offsets;
6776
+ }
6777
+ function lineWithinInline(newlines, offset) {
6778
+ let low = 0;
6779
+ let high = newlines.length;
6780
+ while (low < high) {
6781
+ const middle = low + high >>> 1;
6782
+ if (newlines[middle] < offset)
6783
+ low = middle + 1;
6784
+ else
6785
+ high = middle;
6786
+ }
6787
+ return low;
6788
+ }
6789
+ function createRenderedLocalRefCollector(customPlugins) {
6790
+ const tokenOffsets = new WeakMap;
6791
+ const parser = createMarkdownRenderer(customPlugins);
6792
+ const BaseInlineState = parser.inline.State;
6793
+
6794
+ class PositionedInlineState extends BaseInlineState {
6795
+ push(...args) {
6796
+ const token = super.push(...args);
6797
+ tokenOffsets.set(token, this.pos);
6798
+ return token;
6799
+ }
6800
+ }
6801
+ parser.inline.State = PositionedInlineState;
6802
+ return (content) => {
6803
+ const tokens = parser.parse(content, {});
6804
+ const refs = [];
6805
+ for (const block of tokens) {
6806
+ if (block.type !== "inline" || !block.children || !block.map)
6807
+ continue;
6808
+ const newlines = newlineOffsets(block.content);
6809
+ for (const child of block.children) {
6810
+ const kind = child.type === "image" ? "image" : child.type === "link_open" ? "link" : undefined;
6811
+ if (!kind)
6812
+ continue;
6813
+ const ref = child.attrGet(kind === "image" ? "src" : "href");
6814
+ if (ref === null)
6815
+ continue;
6816
+ const offset = tokenOffsets.get(child);
6817
+ refs.push({
6818
+ ref,
6819
+ kind,
6820
+ line: block.map[0] + (offset === undefined ? 0 : lineWithinInline(newlines, offset)) + 1
6821
+ });
6822
+ }
6823
+ }
6824
+ return refs;
6825
+ };
6826
+ }
6827
+ async function* renderedLocalRefs(ctx, checkId, results) {
6828
+ const files = (ctx.markdownFiles ?? []).slice().sort();
6829
+ if (files.length === 0)
6830
+ return;
6831
+ const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
6832
+ results.push(inspectionFailed(checkId, `Plugin "${ref}" could not be loaded, so local references it defines were not checked: ${error2.message}`));
6833
+ });
6834
+ const collectRenderedLocalRefs = createRenderedLocalRefCollector(plugins);
6835
+ for (const file of files) {
6836
+ let refs;
6837
+ try {
6838
+ refs = collectRenderedLocalRefs(await readFile13(file, "utf8"));
6839
+ } catch {
6840
+ results.push(inspectionFailed(checkId, `Could not read source file: ${file}`, { file }));
6841
+ continue;
6842
+ }
6843
+ for (const ref of refs)
6844
+ yield { ...ref, file };
6845
+ }
6846
+ }
6847
+
6848
+ // src/checks/asset/extensions.ts
6849
+ var RASTER_INSPECTABLE_EXTS = [
6850
+ "png",
6851
+ "jpg",
6852
+ "jpeg",
6853
+ "tiff",
6854
+ "tif"
6855
+ ];
6856
+ var ALL_IMAGE_EXTS = [
6857
+ ...RASTER_INSPECTABLE_EXTS,
6858
+ "webp",
6859
+ "svg",
6860
+ "gif"
6861
+ ];
6862
+ function shippedImages(ctx, exts) {
6863
+ return (ctx.imageFiles ?? []).filter((file) => exts.includes(file.slice(file.lastIndexOf(".") + 1).toLowerCase()));
6864
+ }
6865
+ var FONT_EXTS2 = ["woff", "woff2", "otf", "ttf", "eot"];
6866
+ var ASSET_SCAN_IGNORE_GLOBS = [
6867
+ "**/node_modules/**",
6868
+ "**/.git/**",
6869
+ "**/dist/**"
6870
+ ];
6871
+
6723
6872
  // src/checks/formatter.ts
6724
6873
  function formatReport(report, format = "text") {
6725
6874
  if (format === "json") {
@@ -6779,50 +6928,6 @@ function formatLocation(file, line, column) {
6779
6928
  return `${loc}: `;
6780
6929
  }
6781
6930
 
6782
- // src/checks/policy.ts
6783
- var INSPECT_FAILED_CODE = "inspect-failed";
6784
- function isCheckEnabled(check2, config) {
6785
- const entry = config.validate.checks[check2.id];
6786
- if (entry === false)
6787
- return false;
6788
- if (typeof entry === "object" && entry.enabled === false)
6789
- return false;
6790
- if (check2.enabledWhen && !check2.enabledWhen(config))
6791
- return false;
6792
- return true;
6793
- }
6794
- function selectChecks(opts, config) {
6795
- const unmatched = [];
6796
- let checks2;
6797
- if (opts.only && opts.only.length > 0) {
6798
- const resolved = resolveCheckSelectors(opts.only);
6799
- checks2 = getChecks({ ids: resolved.resolved });
6800
- unmatched.push(...resolved.unmatched);
6801
- } else {
6802
- checks2 = getChecks({ category: opts.category, phase: opts.phase });
6803
- }
6804
- if (opts.skip && opts.skip.length > 0) {
6805
- const resolved = resolveCheckSelectors(opts.skip);
6806
- const skipSet = new Set(resolved.resolved);
6807
- checks2 = checks2.filter((c) => !skipSet.has(c.id));
6808
- unmatched.push(...resolved.unmatched);
6809
- }
6810
- checks2 = checks2.filter((c) => isCheckEnabled(c, config));
6811
- return { checks: checks2, unmatched };
6812
- }
6813
- function finding(checkId, init) {
6814
- return { checkId, ...init };
6815
- }
6816
- function inspectionFailed(checkId, message, extra = {}) {
6817
- return {
6818
- checkId,
6819
- severity: "warning",
6820
- code: INSPECT_FAILED_CODE,
6821
- message,
6822
- ...extra
6823
- };
6824
- }
6825
-
6826
6931
  // src/checks/pdf/qpdf-structure.ts
6827
6932
  var check2 = {
6828
6933
  id: "pdf.structure.qpdf",
@@ -7096,9 +7201,9 @@ var check5 = {
7096
7201
  registerCheck(check5);
7097
7202
 
7098
7203
  // src/lib/pdf-parse.ts
7099
- import { readFile as readFile13 } from "node:fs/promises";
7204
+ import { readFile as readFile14 } from "node:fs/promises";
7100
7205
  async function readPdfBytes(pdfPath) {
7101
- return readFile13(pdfPath, "latin1");
7206
+ return readFile14(pdfPath, "latin1");
7102
7207
  }
7103
7208
  function parseInkCov(out) {
7104
7209
  const lines = out.split(/\r?\n/).filter((l) => l.includes("CMYK"));
@@ -7304,63 +7409,9 @@ var check8 = {
7304
7409
  };
7305
7410
  registerCheck(check8);
7306
7411
 
7307
- // src/checks/pdf/rasterized-pages.ts
7308
- var check9 = {
7309
- id: "pdf.print.rasterized-pages",
7310
- name: "Rasterized Pages",
7311
- description: "Detects pages that appear to be fully rasterized (CSS filters, blend modes, transparency)",
7312
- category: "pdf",
7313
- phase: "post-build",
7314
- async run(ctx) {
7315
- if (!ctx.pdfPath)
7316
- return [];
7317
- const doc = await loadPdf(ctx.pdfPath);
7318
- if (!doc)
7319
- return [];
7320
- const { imagesByPage } = await getOpPass(doc);
7321
- const { textByPage } = await getTextPass(doc);
7322
- const rasterizedPages = [];
7323
- for (const [pageNum, imgs] of imagesByPage) {
7324
- if (imgs.length !== 1)
7325
- continue;
7326
- let page;
7327
- try {
7328
- page = await doc.getPage(pageNum);
7329
- } catch {
7330
- continue;
7331
- }
7332
- const { w: pageW, h: pageH } = getPageSize(page);
7333
- const img = imgs[0];
7334
- const widthMatch = Math.abs(img.placedW - pageW) / pageW < 0.03;
7335
- const heightMatch = Math.abs(img.placedH - pageH) / pageH < 0.03;
7336
- if (!widthMatch || !heightMatch)
7337
- continue;
7338
- const text = (textByPage[pageNum - 1] ?? "").replace(/\s+/g, "");
7339
- if (text.length > 20 && text.length < 200) {
7340
- rasterizedPages.push(pageNum);
7341
- }
7342
- }
7343
- if (rasterizedPages.length === 0)
7344
- return [];
7345
- rasterizedPages.sort((a, b) => a - b);
7346
- return [
7347
- {
7348
- checkId: check9.id,
7349
- severity: "warning",
7350
- message: `Possible rasterized pages detected: ${rasterizedPages.join(", ")}`,
7351
- file: ctx.pdfPath,
7352
- detail: "This may indicate CSS filters, blend modes, or transparency that forced flattening. " + "Text on these pages may not be selectable and quality may be reduced.",
7353
- code: "rasterized-pages-detected",
7354
- data: { pages: rasterizedPages }
7355
- }
7356
- ];
7357
- }
7358
- };
7359
- registerCheck(check9);
7360
-
7361
7412
  // src/checks/pdf/black-text.ts
7362
- import { readFile as readFile14 } from "node:fs/promises";
7363
- var check10 = {
7413
+ import { readFile as readFile15 } from "node:fs/promises";
7414
+ var check9 = {
7364
7415
  id: "pdf.print.black-text",
7365
7416
  name: "Black Text",
7366
7417
  description: "Warns when text at or below 24pt is painted in a dark four-colour mix (black, or a near-black colour such as a dark brown)",
@@ -7372,11 +7423,11 @@ var check10 = {
7372
7423
  return [];
7373
7424
  try {
7374
7425
  const { findRichBlackText, LARGE_TEXT_PT } = await import("./black-text-gray-7dz61456.js");
7375
- const found = await findRichBlackText(await readFile14(ctx.pdfPath));
7426
+ const found = await findRichBlackText(await readFile15(ctx.pdfPath));
7376
7427
  if (found.runs === 0)
7377
7428
  return [];
7378
7429
  return [
7379
- finding(check10.id, {
7430
+ finding(check9.id, {
7380
7431
  severity: "warning",
7381
7432
  message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
7382
7433
  file: ctx.pdfPath,
@@ -7387,17 +7438,17 @@ var check10 = {
7387
7438
  ];
7388
7439
  } catch {
7389
7440
  return [
7390
- inspectionFailed(check10.id, "Could not inspect the PDF's text colours.", {
7441
+ inspectionFailed(check9.id, "Could not inspect the PDF's text colours.", {
7391
7442
  file: ctx.pdfPath
7392
7443
  })
7393
7444
  ];
7394
7445
  }
7395
7446
  }
7396
7447
  };
7397
- registerCheck(check10);
7448
+ registerCheck(check9);
7398
7449
 
7399
7450
  // src/checks/pdf/signature.ts
7400
- var check11 = {
7451
+ var check10 = {
7401
7452
  id: "pdf.print.signature",
7402
7453
  name: "Signature",
7403
7454
  description: "Fails when the interior's page count does not fit the print signature (a multiple of it ending on a blank page, or one short of a multiple)",
@@ -7409,7 +7460,7 @@ var check11 = {
7409
7460
  return [];
7410
7461
  const doc = await loadPdf(ctx.pdfPath);
7411
7462
  if (!doc) {
7412
- return [inspectionFailed(check11.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
7463
+ return [inspectionFailed(check10.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
7413
7464
  }
7414
7465
  const size = ctx.config.print.signature;
7415
7466
  const pages = doc.numPages;
@@ -7418,7 +7469,7 @@ var check11 = {
7418
7469
  const data = { pages, signature: size };
7419
7470
  if (pages % size !== 0) {
7420
7471
  return [
7421
- finding(check11.id, {
7472
+ finding(check10.id, {
7422
7473
  severity: "error",
7423
7474
  message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
7424
7475
  file: ctx.pdfPath,
@@ -7434,10 +7485,10 @@ var check11 = {
7434
7485
  if (!await pageHasMarks(doc, pages))
7435
7486
  return [];
7436
7487
  } catch {
7437
- return [inspectionFailed(check11.id, "Could not read the last page.", { file: ctx.pdfPath })];
7488
+ return [inspectionFailed(check10.id, "Could not read the last page.", { file: ctx.pdfPath })];
7438
7489
  }
7439
7490
  return [
7440
- finding(check11.id, {
7491
+ finding(check10.id, {
7441
7492
  severity: "error",
7442
7493
  message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
7443
7494
  file: ctx.pdfPath,
@@ -7448,10 +7499,10 @@ var check11 = {
7448
7499
  ];
7449
7500
  }
7450
7501
  };
7451
- registerCheck(check11);
7502
+ registerCheck(check10);
7452
7503
 
7453
7504
  // src/checks/pdf/bookmarks.ts
7454
- var check12 = {
7505
+ var check11 = {
7455
7506
  id: "pdf.nav.bookmarks",
7456
7507
  name: "Bookmarks",
7457
7508
  description: "Checks for PDF outline (bookmarks) tree",
@@ -7465,14 +7516,14 @@ var check12 = {
7465
7516
  const doc = await loadPdf(ctx.pdfPath);
7466
7517
  if (!doc) {
7467
7518
  return [
7468
- inspectionFailed(check12.id, "Could not inspect PDF for bookmarks.", {
7519
+ inspectionFailed(check11.id, "Could not inspect PDF for bookmarks.", {
7469
7520
  file: ctx.pdfPath
7470
7521
  })
7471
7522
  ];
7472
7523
  }
7473
7524
  if (await getOutlineCount(doc) === 0) {
7474
7525
  return [
7475
- finding(check12.id, {
7526
+ finding(check11.id, {
7476
7527
  severity: "warning",
7477
7528
  message: "PDF does not contain bookmarks (outline tree).",
7478
7529
  file: ctx.pdfPath
@@ -7482,10 +7533,10 @@ var check12 = {
7482
7533
  return [];
7483
7534
  }
7484
7535
  };
7485
- registerCheck(check12);
7536
+ registerCheck(check11);
7486
7537
 
7487
7538
  // src/checks/pdf/toc-links.ts
7488
- var check13 = {
7539
+ var check12 = {
7489
7540
  id: "pdf.nav.toc-links",
7490
7541
  name: "TOC Links",
7491
7542
  description: "Detects link annotations on TOC pages",
@@ -7500,7 +7551,7 @@ var check13 = {
7500
7551
  if (!doc) {
7501
7552
  return [
7502
7553
  {
7503
- checkId: check13.id,
7554
+ checkId: check12.id,
7504
7555
  severity: "warning",
7505
7556
  message: "Could not inspect PDF for TOC links.",
7506
7557
  file: ctx.pdfPath
@@ -7511,7 +7562,7 @@ var check13 = {
7511
7562
  if (!hasLinks) {
7512
7563
  return [
7513
7564
  {
7514
- checkId: check13.id,
7565
+ checkId: check12.id,
7515
7566
  severity: "warning",
7516
7567
  message: "No link annotations found in PDF (expected for TOC).",
7517
7568
  file: ctx.pdfPath
@@ -7521,10 +7572,10 @@ var check13 = {
7521
7572
  return [];
7522
7573
  }
7523
7574
  };
7524
- registerCheck(check13);
7575
+ registerCheck(check12);
7525
7576
 
7526
7577
  // src/checks/pdf/cross-refs.ts
7527
- var check14 = {
7578
+ var check13 = {
7528
7579
  id: "pdf.nav.cross-refs",
7529
7580
  name: "Cross References",
7530
7581
  description: "Counts and verifies internal link annotations",
@@ -7537,7 +7588,7 @@ var check14 = {
7537
7588
  if (!doc) {
7538
7589
  return [
7539
7590
  {
7540
- checkId: check14.id,
7591
+ checkId: check13.id,
7541
7592
  severity: "info",
7542
7593
  message: "Could not inspect PDF for cross-references.",
7543
7594
  file: ctx.pdfPath
@@ -7547,7 +7598,7 @@ var check14 = {
7547
7598
  const linkCount = await countLinkAnnotations(doc);
7548
7599
  return [
7549
7600
  {
7550
- checkId: check14.id,
7601
+ checkId: check13.id,
7551
7602
  severity: "info",
7552
7603
  message: `PDF contains ${linkCount} internal link annotation${linkCount !== 1 ? "s" : ""}.`,
7553
7604
  file: ctx.pdfPath
@@ -7555,10 +7606,10 @@ var check14 = {
7555
7606
  ];
7556
7607
  }
7557
7608
  };
7558
- registerCheck(check14);
7609
+ registerCheck(check13);
7559
7610
 
7560
7611
  // src/checks/pdf/page-labels.ts
7561
- var check15 = {
7612
+ var check14 = {
7562
7613
  id: "pdf.nav.page-labels",
7563
7614
  name: "Page Labels",
7564
7615
  description: "Verifies PDF page labels / folio numbering",
@@ -7573,7 +7624,7 @@ var check15 = {
7573
7624
  const labels = await getPageLabels(doc);
7574
7625
  return [
7575
7626
  {
7576
- checkId: check15.id,
7627
+ checkId: check14.id,
7577
7628
  severity: "info",
7578
7629
  message: labels && labels.length > 0 ? "PDF contains page labels." : "PDF does not contain explicit page labels.",
7579
7630
  file: ctx.pdfPath
@@ -7581,10 +7632,10 @@ var check15 = {
7581
7632
  ];
7582
7633
  }
7583
7634
  };
7584
- registerCheck(check15);
7635
+ registerCheck(check14);
7585
7636
 
7586
7637
  // src/checks/pdf/image-resolution.ts
7587
- var check16 = {
7638
+ var check15 = {
7588
7639
  id: "pdf.print.image-resolution",
7589
7640
  name: "Image Resolution",
7590
7641
  description: "Checks DPI of images embedded in the PDF",
@@ -7611,7 +7662,7 @@ var check16 = {
7611
7662
  lowResPages.sort((a, b) => a - b);
7612
7663
  return [
7613
7664
  {
7614
- checkId: check16.id,
7665
+ checkId: check15.id,
7615
7666
  severity: "warning",
7616
7667
  message: `Low-resolution images (below ${minDpi} DPI) found on pages: ${lowResPages.join(", ")}`,
7617
7668
  file: ctx.pdfPath
@@ -7621,10 +7672,10 @@ var check16 = {
7621
7672
  return [];
7622
7673
  }
7623
7674
  };
7624
- registerCheck(check16);
7675
+ registerCheck(check15);
7625
7676
 
7626
7677
  // src/checks/pdf/transparency.ts
7627
- var check17 = {
7678
+ var check16 = {
7628
7679
  id: "pdf.print.transparency",
7629
7680
  name: "Transparency",
7630
7681
  description: "Checks for transparency markers (/Transparency, /SMask, /BM) in the PDF",
@@ -7646,7 +7697,7 @@ var check17 = {
7646
7697
  if (found.length > 0) {
7647
7698
  return [
7648
7699
  {
7649
- checkId: check17.id,
7700
+ checkId: check16.id,
7650
7701
  severity: "warning",
7651
7702
  message: `Transparency detected in PDF: ${found.join(", ")}. This may cause issues with some printers.`,
7652
7703
  file: ctx.pdfPath
@@ -7656,7 +7707,7 @@ var check17 = {
7656
7707
  return [];
7657
7708
  }
7658
7709
  };
7659
- registerCheck(check17);
7710
+ registerCheck(check16);
7660
7711
 
7661
7712
  // src/checks/pdf/bleed.ts
7662
7713
  function findBox(bytes, box) {
@@ -7666,7 +7717,7 @@ function findBox(bytes, box) {
7666
7717
  return null;
7667
7718
  return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
7668
7719
  }
7669
- var check18 = {
7720
+ var check17 = {
7670
7721
  id: "pdf.print.bleed",
7671
7722
  name: "Bleed Box",
7672
7723
  description: "Compares MediaBox vs TrimBox/BleedBox to verify bleed area",
@@ -7680,7 +7731,7 @@ var check18 = {
7680
7731
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
7681
7732
  if (!bytes) {
7682
7733
  return [
7683
- inspectionFailed(check18.id, "Could not inspect PDF for bleed boxes.", {
7734
+ inspectionFailed(check17.id, "Could not inspect PDF for bleed boxes.", {
7684
7735
  file: ctx.pdfPath
7685
7736
  })
7686
7737
  ];
@@ -7690,14 +7741,14 @@ var check18 = {
7690
7741
  const bleed = findBox(bytes, "BleedBox");
7691
7742
  if (!media) {
7692
7743
  return [
7693
- inspectionFailed(check18.id, "Could not parse MediaBox from PDF.", {
7744
+ inspectionFailed(check17.id, "Could not parse MediaBox from PDF.", {
7694
7745
  file: ctx.pdfPath
7695
7746
  })
7696
7747
  ];
7697
7748
  }
7698
7749
  if (!trim && !bleed) {
7699
7750
  return [
7700
- finding(check18.id, {
7751
+ finding(check17.id, {
7701
7752
  severity: "warning",
7702
7753
  message: "No TrimBox or BleedBox found. Bleed area cannot be verified.",
7703
7754
  file: ctx.pdfPath
@@ -7710,7 +7761,7 @@ var check18 = {
7710
7761
  const bleedSize = ctx.config.validate.pdf.bleedSize;
7711
7762
  if (mediaW - trimW < bleedSize * 2 * 0.9) {
7712
7763
  return [
7713
- finding(check18.id, {
7764
+ finding(check17.id, {
7714
7765
  severity: "warning",
7715
7766
  message: `Bleed area appears insufficient. Expected at least ${bleedSize}pt on each side.`,
7716
7767
  file: ctx.pdfPath
@@ -7721,10 +7772,10 @@ var check18 = {
7721
7772
  return [];
7722
7773
  }
7723
7774
  };
7724
- registerCheck(check18);
7775
+ registerCheck(check17);
7725
7776
 
7726
7777
  // src/checks/pdf/pdfx-metadata.ts
7727
- var check19 = {
7778
+ var check18 = {
7728
7779
  id: "pdf.print.pdfx-metadata",
7729
7780
  name: "PDF/X Metadata",
7730
7781
  description: "Verifies PDF/X DOCINFO metadata via qpdf JSON",
@@ -7743,30 +7794,30 @@ var check19 = {
7743
7794
  const objects = parseQpdfObjectsJson(stdout);
7744
7795
  if (!objects) {
7745
7796
  return [
7746
- inspectionFailed(check19.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
7797
+ inspectionFailed(check18.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
7747
7798
  ];
7748
7799
  }
7749
- return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check19.id, {
7800
+ return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check18.id, {
7750
7801
  severity: "error",
7751
7802
  message,
7752
7803
  file: ctx.pdfPath
7753
7804
  }));
7754
7805
  } catch {
7755
7806
  return [
7756
- inspectionFailed(check19.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
7807
+ inspectionFailed(check18.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
7757
7808
  ];
7758
7809
  }
7759
7810
  }
7760
7811
  };
7761
- registerCheck(check19);
7812
+ registerCheck(check18);
7762
7813
 
7763
7814
  // src/checks/source/htmlhint.ts
7764
7815
  import { existsSync as existsSync9 } from "node:fs";
7765
- import { readFile as readFile15 } from "node:fs/promises";
7816
+ import { readFile as readFile16 } from "node:fs/promises";
7766
7817
  import htmlhint from "htmlhint";
7767
7818
  var { HTMLHint } = htmlhint;
7768
7819
  var CONFIG_NAMES2 = [".htmlhintrc"];
7769
- var check20 = {
7820
+ var check19 = {
7770
7821
  id: "source.htmlhint",
7771
7822
  name: "HTMLHint",
7772
7823
  description: "Runs htmlhint with project config to validate generated HTML",
@@ -7782,11 +7833,11 @@ var check20 = {
7782
7833
  const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
7783
7834
  if (!resolvedConfig && !configPath)
7784
7835
  return [];
7785
- const ruleset = resolvedConfig ? JSON.parse(await readFile15(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
7786
- const html = await readFile15(ctx.htmlPath, "utf8");
7836
+ const ruleset = resolvedConfig ? JSON.parse(await readFile16(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
7837
+ const html = await readFile16(ctx.htmlPath, "utf8");
7787
7838
  const messages = HTMLHint.verify(html, ruleset);
7788
7839
  return messages.map((m) => ({
7789
- checkId: check20.id,
7840
+ checkId: check19.id,
7790
7841
  severity: m.type === "error" ? "error" : "warning",
7791
7842
  message: `${m.rule.id}: ${m.message}`,
7792
7843
  file: ctx.htmlPath,
@@ -7795,10 +7846,10 @@ var check20 = {
7795
7846
  }));
7796
7847
  }
7797
7848
  };
7798
- registerCheck(check20);
7849
+ registerCheck(check19);
7799
7850
 
7800
7851
  // src/checks/source/stylelint.ts
7801
- import { readFile as readFile16 } from "node:fs/promises";
7852
+ import { readFile as readFile17 } from "node:fs/promises";
7802
7853
 
7803
7854
  // src/lib/printsafe.ts
7804
7855
  import postcss2 from "postcss";
@@ -7914,10 +7965,27 @@ function isUnpairedFitContentWidth(decl) {
7914
7965
  return true;
7915
7966
  }
7916
7967
  function targetsPageWrapper(selector) {
7917
- if (!/\.(page|spread)(?![\w-])/.test(selector))
7968
+ if (!/\.(page|spread)(?![\w-])/.test(subjectCompound(selector)))
7918
7969
  return false;
7919
7970
  return !/\bgp-(bleed|pin)\b/.test(selector);
7920
7971
  }
7972
+ function subjectCompound(selector) {
7973
+ let depth = 0;
7974
+ let start = 0;
7975
+ for (let i = 0;i < selector.length; i++) {
7976
+ const c = selector[i];
7977
+ if (c === "(" || c === "[")
7978
+ depth++;
7979
+ else if (c === ")" || c === "]")
7980
+ depth--;
7981
+ else if (depth === 0 && /[\s>+~]/.test(c))
7982
+ start = i + 1;
7983
+ }
7984
+ let subject = selector.slice(start);
7985
+ while (/\([^()]*\)/.test(subject))
7986
+ subject = subject.replace(/\([^()]*\)/g, "");
7987
+ return subject;
7988
+ }
7921
7989
  var cssWideResetKeywords = new Set(["initial", "unset", "revert", "revert-layer"]);
7922
7990
  var inertValues = {
7923
7991
  "z-index": new Set(["auto"]),
@@ -8164,7 +8232,7 @@ function checkCssRaw(css, from) {
8164
8232
  if (at === "page" || marginBoxAtRuleNames.has(at))
8165
8233
  return;
8166
8234
  }
8167
- if (!targetsPageWrapper(rule.selector))
8235
+ if (!rule.selectors.some(targetsPageWrapper))
8168
8236
  return;
8169
8237
  rule.walkDecls((decl) => {
8170
8238
  if (clipsDescendants(decl)) {
@@ -8188,7 +8256,7 @@ function checkCssRaw(css, from) {
8188
8256
  }
8189
8257
 
8190
8258
  // src/checks/source/stylelint.ts
8191
- var check21 = {
8259
+ var check20 = {
8192
8260
  id: "source.stylelint",
8193
8261
  name: "Print-safety CSS",
8194
8262
  description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
@@ -8205,10 +8273,10 @@ var check21 = {
8205
8273
  for (const file of files) {
8206
8274
  let css;
8207
8275
  try {
8208
- css = await readFile16(file, "utf8");
8276
+ css = await readFile17(file, "utf8");
8209
8277
  } catch (err) {
8210
8278
  results.push({
8211
- checkId: check21.id,
8279
+ checkId: check20.id,
8212
8280
  severity: "error",
8213
8281
  message: `Cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`,
8214
8282
  file
@@ -8217,7 +8285,7 @@ var check21 = {
8217
8285
  }
8218
8286
  for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
8219
8287
  results.push({
8220
- checkId: check21.id,
8288
+ checkId: check20.id,
8221
8289
  severity: w.severity,
8222
8290
  message: `${w.rule}: ${w.message}`,
8223
8291
  file,
@@ -8229,15 +8297,15 @@ var check21 = {
8229
8297
  return results;
8230
8298
  }
8231
8299
  };
8232
- registerCheck(check21);
8300
+ registerCheck(check20);
8233
8301
 
8234
8302
  // src/checks/source/css-ownership.ts
8235
- import { readFile as readFile17 } from "node:fs/promises";
8303
+ import { readFile as readFile18 } from "node:fs/promises";
8236
8304
  import postcss3 from "postcss";
8237
8305
  import { parse as parseYaml3 } from "yaml";
8238
8306
  var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
8239
8307
  async function loadContract(configPath) {
8240
- const raw = await readFile17(configPath, "utf8");
8308
+ const raw = await readFile18(configPath, "utf8");
8241
8309
  const parsed = parseYaml3(raw);
8242
8310
  if (parsed && typeof parsed === "object")
8243
8311
  return parsed;
@@ -8350,7 +8418,7 @@ function checkOwnership(root, file, maps) {
8350
8418
  });
8351
8419
  return out;
8352
8420
  }
8353
- var check22 = {
8421
+ var check21 = {
8354
8422
  id: "source.css-ownership",
8355
8423
  name: "CSS ownership contract",
8356
8424
  description: "Checks an optional per-project CSS ownership contract (.gutterpress/css-contract.yaml): which file may declare which properties/at-rules, and which selectors must never appear unscoped.",
@@ -8373,7 +8441,7 @@ var check22 = {
8373
8441
  } catch (err) {
8374
8442
  return [
8375
8443
  {
8376
- checkId: check22.id,
8444
+ checkId: check21.id,
8377
8445
  severity: "error",
8378
8446
  message: `Failed to parse CSS ownership contract "${configPath}": ${err instanceof Error ? err.message : String(err)}`,
8379
8447
  file: configPath
@@ -8388,7 +8456,7 @@ var check22 = {
8388
8456
  continue;
8389
8457
  let css;
8390
8458
  try {
8391
- css = await readFile17(absFile, "utf8");
8459
+ css = await readFile18(absFile, "utf8");
8392
8460
  } catch {
8393
8461
  continue;
8394
8462
  }
@@ -8415,7 +8483,7 @@ var check22 = {
8415
8483
  }
8416
8484
  for (const v of violations) {
8417
8485
  results.push({
8418
- checkId: check22.id,
8486
+ checkId: check21.id,
8419
8487
  severity: "warning",
8420
8488
  message: v.message,
8421
8489
  file: absFile,
@@ -8427,94 +8495,12 @@ var check22 = {
8427
8495
  return results;
8428
8496
  }
8429
8497
  };
8430
- registerCheck(check22);
8498
+ registerCheck(check21);
8431
8499
 
8432
8500
  // src/checks/source/local-refs.ts
8433
8501
  import { existsSync as existsSync10 } from "node:fs";
8434
8502
  import { dirname as dirname3, resolve as resolve6 } from "node:path";
8435
-
8436
- // src/checks/source/local-ref-parser.ts
8437
- import { readFile as readFile18 } from "node:fs/promises";
8438
- function newlineOffsets(content) {
8439
- const offsets = [];
8440
- for (let i = 0;i < content.length; i++)
8441
- if (content.charCodeAt(i) === 10)
8442
- offsets.push(i);
8443
- return offsets;
8444
- }
8445
- function lineWithinInline(newlines, offset) {
8446
- let low = 0;
8447
- let high = newlines.length;
8448
- while (low < high) {
8449
- const middle = low + high >>> 1;
8450
- if (newlines[middle] < offset)
8451
- low = middle + 1;
8452
- else
8453
- high = middle;
8454
- }
8455
- return low;
8456
- }
8457
- function createRenderedLocalRefCollector(customPlugins) {
8458
- const tokenOffsets = new WeakMap;
8459
- const parser = createMarkdownRenderer(customPlugins);
8460
- const BaseInlineState = parser.inline.State;
8461
-
8462
- class PositionedInlineState extends BaseInlineState {
8463
- push(...args) {
8464
- const token = super.push(...args);
8465
- tokenOffsets.set(token, this.pos);
8466
- return token;
8467
- }
8468
- }
8469
- parser.inline.State = PositionedInlineState;
8470
- return (content) => {
8471
- const tokens = parser.parse(content, {});
8472
- const refs = [];
8473
- for (const block of tokens) {
8474
- if (block.type !== "inline" || !block.children || !block.map)
8475
- continue;
8476
- const newlines = newlineOffsets(block.content);
8477
- for (const child of block.children) {
8478
- const kind = child.type === "image" ? "image" : child.type === "link_open" ? "link" : undefined;
8479
- if (!kind)
8480
- continue;
8481
- const ref = child.attrGet(kind === "image" ? "src" : "href");
8482
- if (ref === null)
8483
- continue;
8484
- const offset = tokenOffsets.get(child);
8485
- refs.push({
8486
- ref,
8487
- kind,
8488
- line: block.map[0] + (offset === undefined ? 0 : lineWithinInline(newlines, offset)) + 1
8489
- });
8490
- }
8491
- }
8492
- return refs;
8493
- };
8494
- }
8495
- async function* renderedLocalRefs(ctx, checkId, results) {
8496
- const files = (ctx.markdownFiles ?? []).slice().sort();
8497
- if (files.length === 0)
8498
- return;
8499
- const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
8500
- results.push(inspectionFailed(checkId, `Plugin "${ref}" could not be loaded, so local references it defines were not checked: ${error2.message}`));
8501
- });
8502
- const collectRenderedLocalRefs = createRenderedLocalRefCollector(plugins);
8503
- for (const file of files) {
8504
- let refs;
8505
- try {
8506
- refs = collectRenderedLocalRefs(await readFile18(file, "utf8"));
8507
- } catch {
8508
- results.push(inspectionFailed(checkId, `Could not read source file: ${file}`, { file }));
8509
- continue;
8510
- }
8511
- for (const ref of refs)
8512
- yield { ...ref, file };
8513
- }
8514
- }
8515
-
8516
- // src/checks/source/local-refs.ts
8517
- var check23 = {
8503
+ var check22 = {
8518
8504
  id: "source.links.local-refs",
8519
8505
  name: "Local Markdown References",
8520
8506
  description: "Checks local markdown links and image refs resolve on disk",
@@ -8522,12 +8508,12 @@ var check23 = {
8522
8508
  phase: "pre-build",
8523
8509
  async run(ctx) {
8524
8510
  const results = [];
8525
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
8511
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check22.id, results)) {
8526
8512
  if (isNonFilesystemRef(ref))
8527
8513
  continue;
8528
8514
  const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
8529
8515
  if (escape) {
8530
- results.push(finding(check23.id, {
8516
+ results.push(finding(check22.id, {
8531
8517
  severity: "error",
8532
8518
  message: escape,
8533
8519
  file,
@@ -8537,7 +8523,7 @@ var check23 = {
8537
8523
  }
8538
8524
  if (localRefExists(ref, kind, file, ctx.inputDir))
8539
8525
  continue;
8540
- results.push(finding(check23.id, {
8526
+ results.push(finding(check22.id, {
8541
8527
  severity: kind === "image" ? "warning" : "error",
8542
8528
  code: kind === "image" ? "missing-image-placeholder" : undefined,
8543
8529
  message: kind === "image" ? `Local image not found; build will substitute a magenta placeholder: ${ref}` : `Local reference not found: ${ref}`,
@@ -8562,7 +8548,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
8562
8548
  return existsSync10(fileRelative);
8563
8549
  }
8564
8550
  }
8565
- registerCheck(check23);
8551
+ registerCheck(check22);
8566
8552
 
8567
8553
  // src/lib/build-staging.ts
8568
8554
  import path11 from "node:path";
@@ -8828,7 +8814,7 @@ async function createStageRoot() {
8828
8814
  }
8829
8815
 
8830
8816
  // src/checks/source/dangling-links.ts
8831
- var check24 = {
8817
+ var check23 = {
8832
8818
  id: "source.links.dangling",
8833
8819
  name: "Dangling Links",
8834
8820
  description: "Flags relative links the printed book cannot open (their href is dropped at print time)",
@@ -8836,10 +8822,10 @@ var check24 = {
8836
8822
  phase: "pre-build",
8837
8823
  async run(ctx) {
8838
8824
  const results = [];
8839
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check24.id, results)) {
8825
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
8840
8826
  if (kind !== "link" || isPrintResolvableHref(ref))
8841
8827
  continue;
8842
- results.push(finding(check24.id, {
8828
+ results.push(finding(check23.id, {
8843
8829
  severity: "warning",
8844
8830
  code: "dangling-link",
8845
8831
  data: { ref },
@@ -8851,11 +8837,11 @@ var check24 = {
8851
8837
  return results;
8852
8838
  }
8853
8839
  };
8854
- registerCheck(check24);
8840
+ registerCheck(check23);
8855
8841
 
8856
8842
  // src/checks/source/accessibility-alt-text.ts
8857
8843
  import { readFile as readFile19 } from "node:fs/promises";
8858
- var check25 = {
8844
+ var check24 = {
8859
8845
  id: "source.accessibility.alt-text",
8860
8846
  name: "Image Alt Text",
8861
8847
  description: "Checks markdown images include non-empty alt text",
@@ -8884,7 +8870,7 @@ var check25 = {
8884
8870
  if (alt.trim())
8885
8871
  continue;
8886
8872
  results.push({
8887
- checkId: check25.id,
8873
+ checkId: check24.id,
8888
8874
  severity: "warning",
8889
8875
  message: "Image is missing alt text",
8890
8876
  file,
@@ -8909,11 +8895,11 @@ function extractImageAlts(line) {
8909
8895
  }
8910
8896
  return alts;
8911
8897
  }
8912
- registerCheck(check25);
8898
+ registerCheck(check24);
8913
8899
 
8914
8900
  // src/checks/source/accessibility-heading-order.ts
8915
8901
  import { readFile as readFile20 } from "node:fs/promises";
8916
- var check26 = {
8902
+ var check25 = {
8917
8903
  id: "source.accessibility.heading-order",
8918
8904
  name: "Heading Order",
8919
8905
  description: "Checks markdown heading levels do not jump by more than one",
@@ -8941,7 +8927,7 @@ var check26 = {
8941
8927
  if (inFence)
8942
8928
  continue;
8943
8929
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
8944
- if (comment && parseDisableDirective(comment[1])?.includes(check26.id)) {
8930
+ if (comment && parseDisableDirective(comment[1])?.includes(check25.id)) {
8945
8931
  silencedLine = i + 1;
8946
8932
  continue;
8947
8933
  }
@@ -8951,7 +8937,7 @@ var check26 = {
8951
8937
  const level = match[1].length;
8952
8938
  if (prevLevel != null && level > prevLevel + 1 && i !== silencedLine) {
8953
8939
  results.push({
8954
- checkId: check26.id,
8940
+ checkId: check25.id,
8955
8941
  severity: "warning",
8956
8942
  message: `Heading level jump from h${prevLevel} to h${level}`,
8957
8943
  file,
@@ -8965,7 +8951,7 @@ var check26 = {
8965
8951
  return results;
8966
8952
  }
8967
8953
  };
8968
- registerCheck(check26);
8954
+ registerCheck(check25);
8969
8955
 
8970
8956
  // src/checks/source/layout-markers.ts
8971
8957
  import { readFile as readFile21 } from "node:fs/promises";
@@ -9076,7 +9062,7 @@ async function collectKnownClasses(ctx, plugins) {
9076
9062
  }
9077
9063
  return names;
9078
9064
  }
9079
- var check27 = {
9065
+ var check26 = {
9080
9066
  id: "source.markdown.layout-markers",
9081
9067
  name: "Layout Markers",
9082
9068
  description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class and any other class no stylesheet mentions",
@@ -9088,7 +9074,7 @@ var check27 = {
9088
9074
  return [];
9089
9075
  const results = [];
9090
9076
  const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
9091
- results.push(inspectionFailed(check27.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
9077
+ results.push(inspectionFailed(check26.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
9092
9078
  });
9093
9079
  const md = createMarkdownRenderer(plugins);
9094
9080
  let known;
@@ -9107,7 +9093,7 @@ var check27 = {
9107
9093
  if (names.has(cls) || warnedLines.has(use.line))
9108
9094
  continue;
9109
9095
  const guess = nearestKnownClass(cls, names);
9110
- results.push(finding(check27.id, {
9096
+ results.push(finding(check26.id, {
9111
9097
  severity: "warning",
9112
9098
  message: `Class ".${cls}" is not in any stylesheet (Gutterpress's own, the plugins', or this book's), so it styles nothing.` + (guess ? ` Did you mean ".${guess}"?` : "") + (use.count > 1 ? ` Used ${use.count} times in this file; the first is reported.` : ""),
9113
9099
  file,
@@ -9117,7 +9103,7 @@ var check27 = {
9117
9103
  }
9118
9104
  }
9119
9105
  for (const w of env.layoutWarnings ?? []) {
9120
- results.push(finding(check27.id, {
9106
+ results.push(finding(check26.id, {
9121
9107
  severity: w.severity ?? "warning",
9122
9108
  message: w.message,
9123
9109
  file,
@@ -9126,46 +9112,23 @@ var check27 = {
9126
9112
  }));
9127
9113
  }
9128
9114
  } catch (error2) {
9129
- results.push(inspectionFailed(check27.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
9115
+ results.push(inspectionFailed(check26.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
9130
9116
  }
9131
9117
  }
9132
9118
  return results;
9133
9119
  }
9134
9120
  };
9135
- registerCheck(check27);
9121
+ registerCheck(check26);
9136
9122
 
9137
9123
  // src/checks/source/merge-markers.ts
9138
9124
  import { readFile as readFile22 } from "node:fs/promises";
9139
9125
  import path12 from "node:path";
9140
-
9141
- // src/checks/asset/extensions.ts
9142
- var RASTER_INSPECTABLE_EXTS = [
9143
- "png",
9144
- "jpg",
9145
- "jpeg",
9146
- "tiff",
9147
- "tif"
9148
- ];
9149
- var ALL_IMAGE_EXTS = [
9150
- ...RASTER_INSPECTABLE_EXTS,
9151
- "webp",
9152
- "svg",
9153
- "gif"
9154
- ];
9155
- var FONT_EXTS2 = ["woff", "woff2", "otf", "ttf", "eot"];
9156
- var ASSET_SCAN_IGNORE_GLOBS = [
9157
- "**/node_modules/**",
9158
- "**/.git/**",
9159
- "**/dist/**"
9160
- ];
9161
-
9162
- // src/checks/source/merge-markers.ts
9163
9126
  var OPEN_SENTINEL = "<<<<<<< your version";
9164
9127
  var CLOSE_SENTINEL = ">>>>>>> online version";
9165
9128
  function withoutOnlineTag(relPath) {
9166
9129
  return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
9167
9130
  }
9168
- var check28 = {
9131
+ var check27 = {
9169
9132
  id: "source.sync.merge-markers",
9170
9133
  name: "Combined Versions",
9171
9134
  description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
@@ -9179,7 +9142,7 @@ var check28 = {
9179
9142
  try {
9180
9143
  content = await readFile22(file, "utf8");
9181
9144
  } catch {
9182
- results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
9145
+ results.push(inspectionFailed(check27.id, `Could not read source file: ${file}`, { file }));
9183
9146
  continue;
9184
9147
  }
9185
9148
  if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
@@ -9191,7 +9154,7 @@ var check28 = {
9191
9154
  const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
9192
9155
  if (line === OPEN_SENTINEL) {
9193
9156
  inBlock = true;
9194
- results.push(finding(check28.id, {
9157
+ results.push(finding(check27.id, {
9195
9158
  severity: "error",
9196
9159
  code: "two-versions-passage",
9197
9160
  message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
@@ -9200,7 +9163,7 @@ var check28 = {
9200
9163
  }));
9201
9164
  } else if (line === CLOSE_SENTINEL) {
9202
9165
  if (!inBlock) {
9203
- results.push(finding(check28.id, {
9166
+ results.push(finding(check27.id, {
9204
9167
  severity: "error",
9205
9168
  code: "leftover-version-marker",
9206
9169
  message: "This marker line is left over from combining two versions — delete it.",
@@ -9221,7 +9184,7 @@ var check28 = {
9221
9184
  });
9222
9185
  for (const sibling of siblings.sort()) {
9223
9186
  const rel = path12.relative(ctx.inputDir, sibling).split(path12.sep).join("/");
9224
- results.push(finding(check28.id, {
9187
+ results.push(finding(check27.id, {
9225
9188
  severity: "warning",
9226
9189
  code: "kept-both-versions",
9227
9190
  message: `Two versions of ${withoutOnlineTag(rel)} are in your book — keep the one you want, then delete the other.`,
@@ -9229,12 +9192,12 @@ var check28 = {
9229
9192
  }));
9230
9193
  }
9231
9194
  } catch (error2) {
9232
- results.push(inspectionFailed(check28.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
9195
+ results.push(inspectionFailed(check27.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
9233
9196
  }
9234
9197
  return results;
9235
9198
  }
9236
9199
  };
9237
- registerCheck(check28);
9200
+ registerCheck(check27);
9238
9201
 
9239
9202
  // src/checks/source/characters.ts
9240
9203
  import { readFile as readFile23 } from "node:fs/promises";
@@ -9349,7 +9312,7 @@ function codePointLabel(char) {
9349
9312
 
9350
9313
  // src/checks/source/characters.ts
9351
9314
  var PROBE = new RegExp(CHARACTER_PATTERN.source, "u");
9352
- var check29 = {
9315
+ var check28 = {
9353
9316
  id: "source.characters",
9354
9317
  name: "Unusual Characters",
9355
9318
  description: "Finds invisible and control characters, odd spaces, and pasted typography (em dashes, curly quotes, emoji) in markdown",
@@ -9362,7 +9325,7 @@ var check29 = {
9362
9325
  try {
9363
9326
  content = await readFile23(file, "utf8");
9364
9327
  } catch {
9365
- results.push(inspectionFailed(check29.id, `Could not read source file: ${file}`, { file }));
9328
+ results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
9366
9329
  continue;
9367
9330
  }
9368
9331
  if (!PROBE.test(content))
@@ -9373,7 +9336,7 @@ var check29 = {
9373
9336
  for (let i = 0;i < lines.length; i++) {
9374
9337
  const line = lines[i];
9375
9338
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
9376
- if (comment && parseDisableDirective(comment[1])?.includes(check29.id)) {
9339
+ if (comment && parseDisableDirective(comment[1])?.includes(check28.id)) {
9377
9340
  silencedLine = i + 1;
9378
9341
  continue;
9379
9342
  }
@@ -9382,7 +9345,7 @@ var check29 = {
9382
9345
  for (const match of line.matchAll(CHARACTER_PATTERN)) {
9383
9346
  const char = match[0];
9384
9347
  const { name, code, fix } = describeCharacter(char);
9385
- results.push(finding(check29.id, {
9348
+ results.push(finding(check28.id, {
9386
9349
  severity: code === "typographer" ? "info" : "warning",
9387
9350
  code,
9388
9351
  message: `${name} (${codePointLabel(char)}). ${fix}`,
@@ -9397,19 +9360,55 @@ var check29 = {
9397
9360
  return results;
9398
9361
  }
9399
9362
  };
9400
- registerCheck(check29);
9363
+ registerCheck(check28);
9401
9364
 
9402
9365
  // src/checks/asset/image-file-size.ts
9403
- import { stat as stat3 } from "node:fs/promises";
9366
+ import { stat as stat2 } from "node:fs/promises";
9367
+ var check29 = {
9368
+ id: "asset.image.file-size",
9369
+ name: "Image File Size",
9370
+ description: "Checks that image files do not exceed the maximum size limit",
9371
+ category: "asset",
9372
+ phase: "pre-build",
9373
+ async run(ctx) {
9374
+ const maxSize = ctx.config.validate.assets.maxImageSize;
9375
+ if (!maxSize)
9376
+ return [];
9377
+ const files = shippedImages(ctx, ALL_IMAGE_EXTS);
9378
+ if (files.length === 0)
9379
+ return [];
9380
+ const results = [];
9381
+ for (const file of files) {
9382
+ try {
9383
+ const info3 = await stat2(file);
9384
+ if (info3.size > maxSize) {
9385
+ const sizeMb = (info3.size / 1e6).toFixed(1);
9386
+ const maxMb = (maxSize / 1e6).toFixed(1);
9387
+ results.push(finding(check29.id, {
9388
+ severity: "warning",
9389
+ message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
9390
+ file
9391
+ }));
9392
+ }
9393
+ } catch {
9394
+ results.push(inspectionFailed(check29.id, `Could not stat image file: ${file}`, {
9395
+ file
9396
+ }));
9397
+ }
9398
+ }
9399
+ return results;
9400
+ }
9401
+ };
9402
+ registerCheck(check29);
9404
9403
 
9405
9404
  // src/lib/image-inspect.ts
9406
- import { stat as stat2, readFile as readFile24 } from "node:fs/promises";
9405
+ import { stat as stat3, readFile as readFile24 } from "node:fs/promises";
9407
9406
  var DEFAULT_DPI = 72;
9408
9407
  var cache = new Map;
9409
9408
  async function inspectImage(path13) {
9410
9409
  let st;
9411
9410
  try {
9412
- st = await stat2(path13);
9411
+ st = await stat3(path13);
9413
9412
  } catch {
9414
9413
  return null;
9415
9414
  }
@@ -9607,136 +9606,9 @@ function parseTiff(b) {
9607
9606
  colorSpace
9608
9607
  };
9609
9608
  }
9610
- async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS) {
9611
- const { glob } = await import("glob");
9612
- const pattern = exts.length === 1 ? `**/*.${exts[0]}` : `**/*.{${exts.join(",")}}`;
9613
- const files = [];
9614
- for (const dir of dirs) {
9615
- const matches = await glob(pattern, {
9616
- cwd: dir,
9617
- absolute: true,
9618
- ignore: [...ignore2]
9619
- });
9620
- files.push(...matches);
9621
- }
9622
- return files;
9623
- }
9624
-
9625
- // src/checks/asset/image-file-size.ts
9626
- var check30 = {
9627
- id: "asset.image.file-size",
9628
- name: "Image File Size",
9629
- description: "Checks that image files do not exceed the maximum size limit",
9630
- category: "asset",
9631
- phase: "pre-build",
9632
- async run(ctx) {
9633
- const maxSize = ctx.config.validate.assets.maxImageSize;
9634
- if (!maxSize)
9635
- return [];
9636
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
9637
- const files = await collectImageFiles(dirs, ALL_IMAGE_EXTS);
9638
- if (files.length === 0)
9639
- return [];
9640
- const results = [];
9641
- for (const file of files) {
9642
- try {
9643
- const info3 = await stat3(file);
9644
- if (info3.size > maxSize) {
9645
- const sizeMb = (info3.size / 1e6).toFixed(1);
9646
- const maxMb = (maxSize / 1e6).toFixed(1);
9647
- results.push(finding(check30.id, {
9648
- severity: "warning",
9649
- message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
9650
- file
9651
- }));
9652
- }
9653
- } catch {
9654
- results.push(inspectionFailed(check30.id, `Could not stat image file: ${file}`, {
9655
- file
9656
- }));
9657
- }
9658
- }
9659
- return results;
9660
- }
9661
- };
9662
- registerCheck(check30);
9663
-
9664
- // src/checks/asset/image-resolution.ts
9665
- var check31 = {
9666
- id: "asset.image.resolution",
9667
- name: "Image Resolution",
9668
- description: "Checks source image DPI from embedded density metadata",
9669
- category: "asset",
9670
- phase: "pre-build",
9671
- async run(ctx) {
9672
- const minDpi = ctx.config.validate.assets.minImageDpi;
9673
- if (!minDpi)
9674
- return [];
9675
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
9676
- const files = await collectImageFiles(dirs, RASTER_INSPECTABLE_EXTS);
9677
- if (files.length === 0)
9678
- return [];
9679
- const results = [];
9680
- for (const file of files) {
9681
- const info3 = await inspectImage(file);
9682
- if (!info3)
9683
- continue;
9684
- const { xDpi, yDpi } = info3;
9685
- if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
9686
- results.push({
9687
- checkId: check31.id,
9688
- severity: "warning",
9689
- message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
9690
- file
9691
- });
9692
- }
9693
- }
9694
- return results;
9695
- }
9696
- };
9697
- registerCheck(check31);
9698
-
9699
- // src/checks/asset/image-color-space.ts
9700
- var LABEL = {
9701
- srgb: "sRGB",
9702
- gray: "Gray",
9703
- cmyk: "CMYK"
9704
- };
9705
- var check32 = {
9706
- id: "asset.image.color-space",
9707
- name: "Image Color Space",
9708
- description: "Validates image color spaces against allowed list",
9709
- category: "asset",
9710
- phase: "pre-build",
9711
- async run(ctx) {
9712
- const allowed = ctx.config.validate.assets.allowedColorSpaces;
9713
- if (!allowed || allowed.length === 0)
9714
- return [];
9715
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
9716
- const files = await collectImageFiles(dirs, RASTER_INSPECTABLE_EXTS);
9717
- if (files.length === 0)
9718
- return [];
9719
- const allowedLower = new Set(allowed.map((s) => s.toLowerCase()));
9720
- const results = [];
9721
- for (const file of files) {
9722
- const info3 = await inspectImage(file);
9723
- const cs = info3?.colorSpace;
9724
- if (cs && !allowedLower.has(cs)) {
9725
- results.push({
9726
- checkId: check32.id,
9727
- severity: "warning",
9728
- message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
9729
- file
9730
- });
9731
- }
9732
- }
9733
- return results;
9734
- }
9735
- };
9736
- registerCheck(check32);
9737
9609
 
9738
9610
  // src/checks/asset/image-alpha.ts
9739
- var check33 = {
9611
+ var check30 = {
9740
9612
  id: "asset.image.alpha-channel",
9741
9613
  name: "Image Alpha Channel",
9742
9614
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -9745,8 +9617,9 @@ var check33 = {
9745
9617
  async run(ctx) {
9746
9618
  if (ctx.config.validate.assets.allowAlpha)
9747
9619
  return [];
9748
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
9749
- const files = await collectImageFiles(dirs, ["png", "tiff", "tif"]);
9620
+ if (ctx.config.pdfx.transparency === "preserve-text")
9621
+ return [];
9622
+ const files = shippedImages(ctx, ["png", "tiff", "tif"]);
9750
9623
  if (files.length === 0)
9751
9624
  return [];
9752
9625
  const results = [];
@@ -9754,7 +9627,7 @@ var check33 = {
9754
9627
  const info3 = await inspectImage(file);
9755
9628
  if (info3?.hasAlpha) {
9756
9629
  results.push({
9757
- checkId: check33.id,
9630
+ checkId: check30.id,
9758
9631
  severity: "warning",
9759
9632
  message: "Image contains alpha channel, which may cause print issues",
9760
9633
  file
@@ -9764,68 +9637,10 @@ var check33 = {
9764
9637
  return results;
9765
9638
  }
9766
9639
  };
9767
- registerCheck(check33);
9768
-
9769
- // src/checks/asset/image-tac.ts
9770
- var check34 = {
9771
- id: "asset.image.tac-raster",
9772
- name: "Image TAC (Raster)",
9773
- description: "Rasterizes and checks TAC per image using Ghostscript",
9774
- category: "asset",
9775
- phase: "pre-build",
9776
- requiredTools: ["gs"],
9777
- async run(ctx) {
9778
- const maxTac = ctx.config.ink.maxTac;
9779
- if (!maxTac)
9780
- return [];
9781
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
9782
- const files = await collectImageFiles(dirs, RASTER_INSPECTABLE_EXTS);
9783
- if (files.length === 0)
9784
- return [];
9785
- const results = [];
9786
- const ghostscript = await resolveGhostscript();
9787
- if (!ghostscript) {
9788
- return [
9789
- inspectionFailed(check34.id, "Could not inspect image ink coverage: Ghostscript executable not found")
9790
- ];
9791
- }
9792
- for (const file of files) {
9793
- try {
9794
- const { stdout } = await execCapture(ghostscript, [
9795
- "-q",
9796
- "-dBATCH",
9797
- "-dNOPAUSE",
9798
- "-sDEVICE=inkcov",
9799
- file
9800
- ]);
9801
- const lines = stdout.split(/\r?\n/).filter((l) => l.includes("CMYK"));
9802
- for (const line of lines) {
9803
- const nums = line.trim().split(/\s+/).slice(0, 4).map(Number);
9804
- if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
9805
- const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
9806
- if (tac > maxTac) {
9807
- results.push(finding(check34.id, {
9808
- severity: "warning",
9809
- message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
9810
- file,
9811
- code: "image-tac-exceeded",
9812
- data: { tac, limit: maxTac }
9813
- }));
9814
- break;
9815
- }
9816
- }
9817
- }
9818
- } catch {
9819
- results.push(inspectionFailed(check34.id, `Could not inspect image ink coverage: ${file}`, { file }));
9820
- }
9821
- }
9822
- return results;
9823
- }
9824
- };
9825
- registerCheck(check34);
9640
+ registerCheck(check30);
9826
9641
 
9827
9642
  // src/checks/asset/approved-fonts.ts
9828
- var check35 = {
9643
+ var check31 = {
9829
9644
  id: "asset.font.approved-files",
9830
9645
  name: "Approved Font Files",
9831
9646
  description: "Checks font files against the approved file patterns",
@@ -9864,7 +9679,7 @@ var check35 = {
9864
9679
  for (const font of allFonts) {
9865
9680
  if (!approvedFonts.has(font)) {
9866
9681
  results.push({
9867
- checkId: check35.id,
9682
+ checkId: check31.id,
9868
9683
  severity: "warning",
9869
9684
  message: "Font file not in approved list",
9870
9685
  file: font
@@ -9874,7 +9689,7 @@ var check35 = {
9874
9689
  return results;
9875
9690
  }
9876
9691
  };
9877
- registerCheck(check35);
9692
+ registerCheck(check31);
9878
9693
 
9879
9694
  // src/checks/asset/font-license.ts
9880
9695
  import { existsSync as existsSync11 } from "node:fs";
@@ -9889,7 +9704,7 @@ var LICENSE_NAMES = [
9889
9704
  "OFL-1.1.txt",
9890
9705
  "COPYING"
9891
9706
  ];
9892
- var check36 = {
9707
+ var check32 = {
9893
9708
  id: "asset.font.license",
9894
9709
  name: "Font License",
9895
9710
  description: "Checks for license files in font directories",
@@ -9916,7 +9731,7 @@ var check36 = {
9916
9731
  const hasLicense = LICENSE_NAMES.some((name) => existsSync11(resolve7(fontDir, name)));
9917
9732
  if (!hasLicense) {
9918
9733
  results.push({
9919
- checkId: check36.id,
9734
+ checkId: check32.id,
9920
9735
  severity: "warning",
9921
9736
  message: `No font license file found in directory`,
9922
9737
  file: fontDir
@@ -9926,10 +9741,10 @@ var check36 = {
9926
9741
  return results;
9927
9742
  }
9928
9743
  };
9929
- registerCheck(check36);
9744
+ registerCheck(check32);
9930
9745
 
9931
9746
  // src/checks/heuristic/text-density.ts
9932
- var check37 = {
9747
+ var check33 = {
9933
9748
  id: "heuristic.whitespace.text-density",
9934
9749
  name: "Text Density",
9935
9750
  description: "Checks characters-per-page ratio",
@@ -9958,7 +9773,7 @@ var check37 = {
9958
9773
  });
9959
9774
  if (lowPages.length > 0) {
9960
9775
  results.push({
9961
- checkId: check37.id,
9776
+ checkId: check33.id,
9962
9777
  severity: "info",
9963
9778
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
9964
9779
  file: ctx.pdfPath
@@ -9966,7 +9781,7 @@ var check37 = {
9966
9781
  }
9967
9782
  if (highPages.length > 0) {
9968
9783
  results.push({
9969
- checkId: check37.id,
9784
+ checkId: check33.id,
9970
9785
  severity: "info",
9971
9786
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
9972
9787
  file: ctx.pdfPath
@@ -9975,11 +9790,11 @@ var check37 = {
9975
9790
  return results;
9976
9791
  }
9977
9792
  };
9978
- registerCheck(check37);
9793
+ registerCheck(check33);
9979
9794
 
9980
9795
  // src/checks/heuristic/section-density.ts
9981
9796
  import { readFile as readFile25 } from "node:fs/promises";
9982
- var check38 = {
9797
+ var check34 = {
9983
9798
  id: "heuristic.chunking.section-density",
9984
9799
  name: "Section Density",
9985
9800
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -10005,7 +9820,7 @@ var check38 = {
10005
9820
  const line = lines[i];
10006
9821
  if (/^#{1,6}\s/.test(line)) {
10007
9822
  if (paragraphCount > maxParas) {
10008
- results.push(finding(check38.id, {
9823
+ results.push(finding(check34.id, {
10009
9824
  severity: "info",
10010
9825
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
10011
9826
  file,
@@ -10025,7 +9840,7 @@ var check38 = {
10025
9840
  }
10026
9841
  }
10027
9842
  if (paragraphCount > maxParas) {
10028
- results.push(finding(check38.id, {
9843
+ results.push(finding(check34.id, {
10029
9844
  severity: "info",
10030
9845
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
10031
9846
  file,
@@ -10033,7 +9848,7 @@ var check38 = {
10033
9848
  }));
10034
9849
  }
10035
9850
  } catch {
10036
- results.push(inspectionFailed(check38.id, `Could not read source file: ${file}`, {
9851
+ results.push(inspectionFailed(check34.id, `Could not read source file: ${file}`, {
10037
9852
  file
10038
9853
  }));
10039
9854
  }
@@ -10041,10 +9856,10 @@ var check38 = {
10041
9856
  return results;
10042
9857
  }
10043
9858
  };
10044
- registerCheck(check38);
9859
+ registerCheck(check34);
10045
9860
 
10046
9861
  // src/checks/heuristic/layer-count.ts
10047
- var check39 = {
9862
+ var check35 = {
10048
9863
  id: "heuristic.decoration.layer-count",
10049
9864
  name: "Layer Count",
10050
9865
  description: "Counts image objects per page",
@@ -10069,7 +9884,7 @@ var check39 = {
10069
9884
  heavyPages.sort((a, b) => a - b);
10070
9885
  return [
10071
9886
  {
10072
- checkId: check39.id,
9887
+ checkId: check35.id,
10073
9888
  severity: "info",
10074
9889
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
10075
9890
  file: ctx.pdfPath
@@ -10079,10 +9894,10 @@ var check39 = {
10079
9894
  return [];
10080
9895
  }
10081
9896
  };
10082
- registerCheck(check39);
9897
+ registerCheck(check35);
10083
9898
 
10084
9899
  // src/checks/heuristic/placement-variance.ts
10085
- var check40 = {
9900
+ var check36 = {
10086
9901
  id: "heuristic.layout.placement-variance",
10087
9902
  name: "Placement Variance",
10088
9903
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -10100,7 +9915,7 @@ var check40 = {
10100
9915
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
10101
9916
  return [
10102
9917
  {
10103
- checkId: check40.id,
9918
+ checkId: check36.id,
10104
9919
  severity: "info",
10105
9920
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
10106
9921
  file: ctx.pdfPath
@@ -10108,7 +9923,7 @@ var check40 = {
10108
9923
  ];
10109
9924
  }
10110
9925
  };
10111
- registerCheck(check40);
9926
+ registerCheck(check36);
10112
9927
 
10113
9928
  // src/checks/runner.ts
10114
9929
  function getCheckSeverityOverride(checkId, config) {
@@ -10138,16 +9953,16 @@ async function runChecks(ctx, opts = {}) {
10138
9953
  }
10139
9954
  const release = retainPdfCache();
10140
9955
  try {
10141
- for (const check41 of checks2) {
9956
+ for (const check37 of checks2) {
10142
9957
  try {
10143
- let results = await check41.run(ctx);
10144
- const severityOverride = getCheckSeverityOverride(check41.id, ctx.config);
9958
+ let results = await check37.run(ctx);
9959
+ const severityOverride = getCheckSeverityOverride(check37.id, ctx.config);
10145
9960
  if (severityOverride) {
10146
9961
  for (const r of results) {
10147
9962
  r.severity = severityOverride;
10148
9963
  }
10149
9964
  }
10150
- const lintLevel = ruleLevel(ctx.config.lint.rules, check41.id);
9965
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check37.id);
10151
9966
  if (lintLevel === "off")
10152
9967
  results = [];
10153
9968
  else if (lintLevel) {
@@ -10155,15 +9970,15 @@ async function runChecks(ctx, opts = {}) {
10155
9970
  r.severity = lintLevel === "warn" ? "warning" : "error";
10156
9971
  }
10157
9972
  if (results.length === 0) {
10158
- passed.push(check41.id);
9973
+ passed.push(check37.id);
10159
9974
  } else {
10160
9975
  allResults.push(...results);
10161
9976
  }
10162
9977
  } catch (err) {
10163
9978
  allResults.push({
10164
- checkId: check41.id,
9979
+ checkId: check37.id,
10165
9980
  severity: "error",
10166
- message: `Check "${check41.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9981
+ message: `Check "${check37.id}" threw: ${err instanceof Error ? err.message : String(err)}`
10167
9982
  });
10168
9983
  }
10169
9984
  }
@@ -10203,15 +10018,15 @@ function emptyReport() {
10203
10018
  async function checkToolAvailability(config, opts = {}) {
10204
10019
  const { checks: checks2 } = selectChecks(opts, config);
10205
10020
  const toolToChecks = new Map;
10206
- for (const check41 of checks2) {
10207
- if (!check41.requiredTools?.length)
10021
+ for (const check37 of checks2) {
10022
+ if (!check37.requiredTools?.length)
10208
10023
  continue;
10209
- for (const tool of check41.requiredTools) {
10024
+ for (const tool of check37.requiredTools) {
10210
10025
  const existing = toolToChecks.get(tool);
10211
10026
  if (existing) {
10212
- existing.push(check41.id);
10027
+ existing.push(check37.id);
10213
10028
  } else {
10214
- toolToChecks.set(tool, [check41.id]);
10029
+ toolToChecks.set(tool, [check37.id]);
10215
10030
  }
10216
10031
  }
10217
10032
  }
@@ -10227,11 +10042,11 @@ async function checkToolAvailability(config, opts = {}) {
10227
10042
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
10228
10043
  const missingSet = new Set(missing);
10229
10044
  const skippedChecks = [];
10230
- for (const check41 of checks2) {
10231
- if (!check41.requiredTools?.length)
10045
+ for (const check37 of checks2) {
10046
+ if (!check37.requiredTools?.length)
10232
10047
  continue;
10233
- if (check41.requiredTools.some((t) => missingSet.has(t))) {
10234
- skippedChecks.push(check41.id);
10048
+ if (check37.requiredTools.some((t) => missingSet.has(t))) {
10049
+ skippedChecks.push(check37.id);
10235
10050
  }
10236
10051
  }
10237
10052
  return { available, missing, skippedChecks, toolToChecks };
@@ -10264,6 +10079,31 @@ var SHIPPED_ASSET_EXTS = new Set([
10264
10079
  ".tif",
10265
10080
  ".tiff"
10266
10081
  ]);
10082
+ async function shippedImageFiles(manifestDir, markdownFiles, stylePaths, extensions) {
10083
+ const isImage = (file) => ALL_IMAGE_EXTS.includes(file.slice(file.lastIndexOf(".") + 1).toLowerCase());
10084
+ const files = new Set;
10085
+ try {
10086
+ for (const file of await collectStyleDependencies(manifestDir, stylePaths)) {
10087
+ if (isImage(file))
10088
+ files.add(file);
10089
+ }
10090
+ } catch {}
10091
+ const collect = createRenderedLocalRefCollector(await loadPlugins(extensions, manifestDir, () => {}));
10092
+ for (const file of markdownFiles) {
10093
+ let refs;
10094
+ try {
10095
+ refs = collect(await readFile26(file, "utf8"));
10096
+ } catch {
10097
+ continue;
10098
+ }
10099
+ for (const { ref, kind } of refs) {
10100
+ if (kind !== "image" || isNonFilesystemRef(ref) || proseImageRefError(ref, manifestDir))
10101
+ continue;
10102
+ files.add(resolve8(manifestDir, stripUrlSuffix(decodeRef(ref))));
10103
+ }
10104
+ }
10105
+ return [...files].filter((file) => existsSync12(file)).sort();
10106
+ }
10267
10107
  async function sharedAssetDirs(manifestDir, relStyles, inputDir) {
10268
10108
  let closure;
10269
10109
  try {
@@ -10467,6 +10307,7 @@ async function executeValidation(args) {
10467
10307
  let markdownFiles;
10468
10308
  let cssFiles;
10469
10309
  let assetDirs;
10310
+ let imageFiles;
10470
10311
  let htmlPath;
10471
10312
  const outDir = resolveOutputDir(manifestDir, config.title);
10472
10313
  if (inputDir) {
@@ -10481,6 +10322,7 @@ async function executeValidation(args) {
10481
10322
  }
10482
10323
  cssFiles = [...projectCssFiles, ...pluginStylePaths].filter((f) => !f.endsWith(".min.css"));
10483
10324
  assetDirs = [inputDir, ...await sharedAssetDirs(manifestDir, relStyles, inputDir)];
10325
+ imageFiles = await shippedImageFiles(manifestDir, markdownFiles, [...relStyles, ...pluginStylePaths], config.extensions);
10484
10326
  const possibleHtml = join4(outDir, BOOK_HTML_FILENAME);
10485
10327
  if (existsSync12(possibleHtml)) {
10486
10328
  htmlPath = possibleHtml;
@@ -10494,7 +10336,8 @@ async function executeValidation(args) {
10494
10336
  htmlPath,
10495
10337
  markdownFiles,
10496
10338
  cssFiles,
10497
- assetDirs
10339
+ assetDirs,
10340
+ imageFiles
10498
10341
  };
10499
10342
  const runnerOptions = {
10500
10343
  category: categories,
@@ -10504,7 +10347,7 @@ async function executeValidation(args) {
10504
10347
  };
10505
10348
  if (args.fix) {
10506
10349
  const selected = selectChecks(runnerOptions, config).checks;
10507
- if (selected.some((check41) => check41.id === MARKDOWNLINT_CHECK_ID)) {
10350
+ if (selected.some((check37) => check37.id === MARKDOWNLINT_CHECK_ID)) {
10508
10351
  await applyMarkdownlintFixes(context);
10509
10352
  } else {
10510
10353
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -10573,12 +10416,6 @@ function buildPdfSummaryLines(results) {
10573
10416
  lines.push("Fonts: all embedded");
10574
10417
  }
10575
10418
  }
10576
- const rasterResults = results.filter((r) => r.checkId === "pdf.print.rasterized-pages");
10577
- if (rasterResults.length > 0) {
10578
- const rasterFinding = rasterResults.find((r) => r.code === "rasterized-pages-detected");
10579
- const pages = rasterFinding?.data?.pages;
10580
- lines.push(`Rasterized pages: ${Array.isArray(pages) ? pages.join(", ") : "none"}`);
10581
- }
10582
10419
  return lines;
10583
10420
  }
10584
10421
 
@@ -10878,7 +10715,7 @@ class PdfOutput {
10878
10715
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10879
10716
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10880
10717
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10881
- const { buildNativePdf } = await import("./engine-5fw55dfb.js");
10718
+ const { buildNativePdf } = await import("./engine-0b8bt05n.js");
10882
10719
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10883
10720
  title: config.title,
10884
10721
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -11049,7 +10886,7 @@ function reportTransparentPlan({ split, artOnly, kept }, pdfxMode, dpi, pageList
11049
10886
  import path16 from "path";
11050
10887
  import { tmpdir } from "os";
11051
10888
  import { randomBytes as randomBytes2 } from "crypto";
11052
- import { readdir as readdir5, readFile as readFile26, writeFile as writeFile6 } from "node:fs/promises";
10889
+ import { readdir as readdir6, readFile as readFile27, writeFile as writeFile6 } from "node:fs/promises";
11053
10890
 
11054
10891
  // src/utils/file-utils.ts
11055
10892
  import { promises as fs3 } from "fs";
@@ -11595,7 +11432,7 @@ function isProcessAlive(pid) {
11595
11432
  async function cleanupOrphanTempDirs() {
11596
11433
  let entries;
11597
11434
  try {
11598
- entries = await readdir5(TEMP_DIR_BASE);
11435
+ entries = await readdir6(TEMP_DIR_BASE);
11599
11436
  } catch {
11600
11437
  return;
11601
11438
  }
@@ -11603,7 +11440,7 @@ async function cleanupOrphanTempDirs() {
11603
11440
  const dir = path16.join(TEMP_DIR_BASE, entry);
11604
11441
  const pidFile = path16.join(dir, PID_FILE_NAME);
11605
11442
  try {
11606
- const raw = await readFile26(pidFile, "utf8");
11443
+ const raw = await readFile27(pidFile, "utf8");
11607
11444
  const pid = parseInt(raw.trim(), 10);
11608
11445
  if (!isProcessAlive(pid)) {
11609
11446
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -11710,7 +11547,7 @@ function createServerState(inputPath, tempDir, config, options) {
11710
11547
  import http from "node:http";
11711
11548
  import net from "node:net";
11712
11549
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
11713
- import { readFile as readFile27, stat as stat4 } from "node:fs/promises";
11550
+ import { readFile as readFile28, stat as stat4 } from "node:fs/promises";
11714
11551
  import path17 from "path";
11715
11552
  import { WebSocket, WebSocketServer } from "ws";
11716
11553
 
@@ -11809,7 +11646,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
11809
11646
  }
11810
11647
  let data;
11811
11648
  try {
11812
- data = await readFile27(filePath);
11649
+ data = await readFile28(filePath);
11813
11650
  } catch {
11814
11651
  res.writeHead(404);
11815
11652
  res.end("Not Found");
@@ -11858,7 +11695,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11858
11695
  let data;
11859
11696
  if (!validator || validator.stamp !== stamp) {
11860
11697
  try {
11861
- data = await readFile27(absPath);
11698
+ data = await readFile28(absPath);
11862
11699
  } catch {
11863
11700
  await serveStatic(absPath, res);
11864
11701
  return;
@@ -12170,7 +12007,7 @@ async function startPreviewServer(options) {
12170
12007
  import { platform, arch, release } from "node:os";
12171
12008
 
12172
12009
  // src/lib/remote-auth/token-store.ts
12173
- import { chmod, mkdir as mkdir7, readFile as readFile28, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
12010
+ import { chmod, mkdir as mkdir7, readFile as readFile29, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
12174
12011
  import os3 from "node:os";
12175
12012
  import path18 from "node:path";
12176
12013
  function credentialHostKey(hostOrUrl) {
@@ -12214,7 +12051,7 @@ class FileTokenStore {
12214
12051
  async read() {
12215
12052
  let raw;
12216
12053
  try {
12217
- raw = await readFile28(this.filePath, "utf8");
12054
+ raw = await readFile29(this.filePath, "utf8");
12218
12055
  } catch (e) {
12219
12056
  if (e?.code === "ENOENT") {
12220
12057
  return { version: 1, credentials: {} };
@@ -12329,7 +12166,7 @@ var TOOLS_TO_PROBE = [
12329
12166
  hintKey: "gs",
12330
12167
  usedBy: [
12331
12168
  { feature: "PDF/X CMYK conversion (build --format pdfx)", severity: "required" },
12332
- { feature: "validate: pdf.print.ink-coverage, asset.image-tac", severity: "optional" }
12169
+ { feature: "validate: pdf.print.ink-coverage", severity: "optional" }
12333
12170
  ]
12334
12171
  },
12335
12172
  {
@@ -12414,13 +12251,13 @@ async function getSystemDiagnostics() {
12414
12251
  };
12415
12252
  }
12416
12253
  // src/lib/project-scaffold.ts
12417
- import { access, copyFile, cp as cp2, mkdir as mkdir9, readdir as readdir6, readFile as readFile30, stat as stat5, writeFile as writeFile9 } from "node:fs/promises";
12254
+ import { access, copyFile, cp as cp2, mkdir as mkdir9, readdir as readdir7, readFile as readFile31, stat as stat5, writeFile as writeFile9 } from "node:fs/promises";
12418
12255
  import { constants as FS, existsSync as existsSync14 } from "node:fs";
12419
12256
  import path20 from "node:path";
12420
12257
 
12421
12258
  // src/lib/page-size.ts
12422
12259
  import { existsSync as existsSync13 } from "node:fs";
12423
- import { mkdir as mkdir8, readFile as readFile29, writeFile as writeFile8 } from "node:fs/promises";
12260
+ import { mkdir as mkdir8, readFile as readFile30, writeFile as writeFile8 } from "node:fs/promises";
12424
12261
  import path19 from "node:path";
12425
12262
  import postcss5 from "postcss";
12426
12263
  import { isMap as isMap2, Scalar as Scalar2 } from "yaml";
@@ -12538,7 +12375,7 @@ var relDisplay2 = (projectDir, abs) => path19.relative(projectDir, abs).split(pa
12538
12375
  async function findCssSize(projectDir, sheets) {
12539
12376
  let found = null;
12540
12377
  for (const file of sheets) {
12541
- const size = readPageSizeFromCss(await readFile29(file, "utf8"));
12378
+ const size = readPageSizeFromCss(await readFile30(file, "utf8"));
12542
12379
  if (size)
12543
12380
  found = { file, size };
12544
12381
  }
@@ -12565,7 +12402,7 @@ async function setPageSetup(projectDir, choice) {
12565
12402
  const found = await findCssSize(projectDir, sheets);
12566
12403
  const newSheet = path19.join(projectDir, "styles", "book.css");
12567
12404
  const target = found?.file ?? sheets[sheets.length - 1] ?? newSheet;
12568
- const before = existsSync13(target) ? await readFile29(target, "utf8") : "";
12405
+ const before = existsSync13(target) ? await readFile30(target, "utf8") : "";
12569
12406
  let after;
12570
12407
  try {
12571
12408
  after = setPageSizeInCss(before, size);
@@ -12684,7 +12521,7 @@ async function scaffoldProject(options) {
12684
12521
  try {
12685
12522
  await mkdir9(projectDir, { recursive: true });
12686
12523
  if (customTemplateDir) {
12687
- const entries = await readdir6(customTemplateDir, { withFileTypes: true });
12524
+ const entries = await readdir7(customTemplateDir, { withFileTypes: true });
12688
12525
  for (const entry of entries) {
12689
12526
  if (entry.name === ".gutterpress-template.json")
12690
12527
  continue;
@@ -12789,7 +12626,7 @@ async function adoptFolder(options) {
12789
12626
  const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
12790
12627
  let mdFiles;
12791
12628
  try {
12792
- const entries = await readdir6(dir, { withFileTypes: true });
12629
+ const entries = await readdir7(dir, { withFileTypes: true });
12793
12630
  mdFiles = entries.filter((e) => e.isFile() && /\.(md|markdown)$/i.test(e.name)).map((e) => e.name).sort((a, b) => a.localeCompare(b));
12794
12631
  if (mdFiles.length === 0) {
12795
12632
  const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
@@ -12855,7 +12692,7 @@ styles:
12855
12692
  async function firstSourceFile(manifestPath) {
12856
12693
  let text;
12857
12694
  try {
12858
- text = await readFile30(manifestPath, "utf8");
12695
+ text = await readFile31(manifestPath, "utf8");
12859
12696
  } catch {
12860
12697
  return;
12861
12698
  }
@@ -12867,14 +12704,14 @@ async function firstSourceFile(manifestPath) {
12867
12704
  return m ? m[1].trim() : undefined;
12868
12705
  }
12869
12706
  async function fillTemplateFile(filePath, substitutions) {
12870
- let text = await readFile30(filePath, "utf8");
12707
+ let text = await readFile31(filePath, "utf8");
12871
12708
  for (const [token, value] of Object.entries(substitutions)) {
12872
12709
  text = text.split(token).join(value);
12873
12710
  }
12874
12711
  await writeFile9(filePath, text, "utf8");
12875
12712
  }
12876
12713
  // src/lib/extension-scaffold.ts
12877
- import { access as access2, mkdir as mkdir10, readFile as readFile31, writeFile as writeFile10 } from "node:fs/promises";
12714
+ import { access as access2, mkdir as mkdir10, readFile as readFile32, writeFile as writeFile10 } from "node:fs/promises";
12878
12715
  import { constants as FS2 } from "node:fs";
12879
12716
  import path21 from "node:path";
12880
12717
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -12991,7 +12828,7 @@ async function scaffoldExtension(options) {
12991
12828
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
12992
12829
  const dest = path21.join(extensionDir, relDest);
12993
12830
  await mkdir10(path21.dirname(dest), { recursive: true });
12994
- const text = await readFile31(src, "utf8");
12831
+ const text = await readFile32(src, "utf8");
12995
12832
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
12996
12833
  await writeFile10(dest, substitute(text, table), "utf8");
12997
12834
  written.push(relDest);
@@ -13017,7 +12854,7 @@ function substitute(text, table) {
13017
12854
  return out;
13018
12855
  }
13019
12856
  // src/lib/project-templates.ts
13020
- import { cp as cp3, mkdir as mkdir11, readFile as readFile32, readdir as readdir7, stat as stat6, writeFile as writeFile11 } from "node:fs/promises";
12857
+ import { cp as cp3, mkdir as mkdir11, readFile as readFile33, readdir as readdir8, stat as stat6, writeFile as writeFile11 } from "node:fs/promises";
13021
12858
  import { constants as FS3, existsSync as existsSync15 } from "node:fs";
13022
12859
  import { access as access3 } from "node:fs/promises";
13023
12860
  import path22 from "node:path";
@@ -13029,7 +12866,7 @@ var BUILT_IN_TEMPLATE_IDS = [
13029
12866
  ];
13030
12867
  async function readTemplateChoices(manifestPath) {
13031
12868
  try {
13032
- const text = await readFile32(manifestPath, "utf8");
12869
+ const text = await readFile33(manifestPath, "utf8");
13033
12870
  const doc = parseDocument2(text);
13034
12871
  const out = {};
13035
12872
  const preset = doc.get("preset");
@@ -13186,7 +13023,7 @@ async function saveProjectAsTemplate(options) {
13186
13023
  throw new Error(`A template named "${name}" already exists. Choose a different name.`);
13187
13024
  }
13188
13025
  await mkdir11(dir, { recursive: true });
13189
- const entries = await readdir7(projectDir, { withFileTypes: true });
13026
+ const entries = await readdir8(projectDir, { withFileTypes: true });
13190
13027
  for (const entry of entries) {
13191
13028
  if (SKIP_ENTRIES.has(entry.name))
13192
13029
  continue;
@@ -13211,7 +13048,7 @@ async function saveProjectAsTemplate(options) {
13211
13048
  async function retokeniseManifest(manifestPath) {
13212
13049
  let text;
13213
13050
  try {
13214
- text = await readFile32(manifestPath, "utf8");
13051
+ text = await readFile33(manifestPath, "utf8");
13215
13052
  } catch {
13216
13053
  return;
13217
13054
  }
@@ -13221,7 +13058,7 @@ async function retokeniseManifest(manifestPath) {
13221
13058
  async function listCustomTemplates(templatesRoot) {
13222
13059
  let entries;
13223
13060
  try {
13224
- entries = await readdir7(templatesRoot, { withFileTypes: true });
13061
+ entries = await readdir8(templatesRoot, { withFileTypes: true });
13225
13062
  } catch {
13226
13063
  return [];
13227
13064
  }
@@ -13232,7 +13069,7 @@ async function listCustomTemplates(templatesRoot) {
13232
13069
  const dir = path22.join(templatesRoot, entry.name);
13233
13070
  let label = prettify(entry.name);
13234
13071
  try {
13235
- const meta = JSON.parse(await readFile32(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
13072
+ const meta = JSON.parse(await readFile33(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
13236
13073
  if (typeof meta.label === "string" && meta.label)
13237
13074
  label = meta.label;
13238
13075
  } catch {}
@@ -13258,7 +13095,7 @@ async function importTemplateFromFolder(options) {
13258
13095
  return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
13259
13096
  }
13260
13097
  // src/lib/snippets.ts
13261
- import { readdir as readdir8, readFile as readFile33, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
13098
+ import { readdir as readdir9, readFile as readFile34, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
13262
13099
  import path23 from "node:path";
13263
13100
  var SNIPPETS_DIR = "snippets";
13264
13101
  var LEVEL_RANK = { project: 0, extension: 1, core: 2 };
@@ -13289,7 +13126,7 @@ function resolveSnippetPath(projectDir, fileName) {
13289
13126
  async function scanSnippetFiles(dir) {
13290
13127
  let names;
13291
13128
  try {
13292
- names = await readdir8(dir);
13129
+ names = await readdir9(dir);
13293
13130
  } catch {
13294
13131
  return [];
13295
13132
  }
@@ -13299,7 +13136,7 @@ async function scanSnippetFiles(dir) {
13299
13136
  continue;
13300
13137
  let body = "";
13301
13138
  try {
13302
- body = await readFile33(path23.join(dir, fileName), "utf8");
13139
+ body = await readFile34(path23.join(dir, fileName), "utf8");
13303
13140
  } catch {
13304
13141
  continue;
13305
13142
  }
@@ -13388,7 +13225,7 @@ async function collectLibrary(projectDir) {
13388
13225
  listed.component = name;
13389
13226
  continue;
13390
13227
  }
13391
- const body = await readFile33(path23.join(entry.dir, rel), "utf8").catch(() => null);
13228
+ const body = await readFile34(path23.join(entry.dir, rel), "utf8").catch(() => null);
13392
13229
  if (body === null)
13393
13230
  continue;
13394
13231
  snippets2.push({
@@ -13457,7 +13294,7 @@ async function listLocationVariants(projectDir) {
13457
13294
  }
13458
13295
  // src/lib/extension-import.ts
13459
13296
  import { existsSync as existsSync16 } from "node:fs";
13460
- import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile34, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
13297
+ import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile35, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
13461
13298
  import { tmpdir as tmpdir2 } from "node:os";
13462
13299
  import path24 from "node:path";
13463
13300
  import { unzipSync } from "fflate";
@@ -13530,7 +13367,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13530
13367
  if (!existsSync16(cssPath)) {
13531
13368
  throw new Error("A theme must contain a theme.css file.");
13532
13369
  }
13533
- const css = await readFile34(cssPath, "utf8");
13370
+ const css = await readFile35(cssPath, "utf8");
13534
13371
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
13535
13372
  if (reject) {
13536
13373
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -13558,7 +13395,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13558
13395
  for (let i = 0;i < extraSheets.length; i++) {
13559
13396
  const rel = extraSheets[i];
13560
13397
  const sheetPath = extraSheetPaths[i];
13561
- const sheetCss = await readFile34(sheetPath, "utf8");
13398
+ const sheetCss = await readFile35(sheetPath, "utf8");
13562
13399
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
13563
13400
  if (sheetReject) {
13564
13401
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -13591,7 +13428,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
13591
13428
  let pkg = {};
13592
13429
  if (metaExists) {
13593
13430
  try {
13594
- const parsed = JSON.parse(await readFile34(file, "utf8"));
13431
+ const parsed = JSON.parse(await readFile35(file, "utf8"));
13595
13432
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
13596
13433
  pkg = parsed;
13597
13434
  }
@@ -13693,11 +13530,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
13693
13530
  async function importExtensionFromFile(projectDir, filePath) {
13694
13531
  const ext = path24.extname(filePath).toLowerCase();
13695
13532
  if (ext === ".zip") {
13696
- const buf = await readFile34(filePath);
13533
+ const buf = await readFile35(filePath);
13697
13534
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
13698
13535
  }
13699
13536
  if (ext === ".css") {
13700
- const css = await readFile34(filePath, "utf8");
13537
+ const css = await readFile35(filePath, "utf8");
13701
13538
  return importExtensionFromCssText(projectDir, css, prettify(path24.basename(filePath, ".css")));
13702
13539
  }
13703
13540
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -15586,7 +15423,7 @@ async function setUpOnlineBackup(options) {
15586
15423
  }
15587
15424
  }
15588
15425
  // src/lib/remote-auth/clone.ts
15589
- import { mkdir as mkdir15, readdir as readdir9, rm as rm6 } from "node:fs/promises";
15426
+ import { mkdir as mkdir15, readdir as readdir10, rm as rm6 } from "node:fs/promises";
15590
15427
  import path26 from "node:path";
15591
15428
  import git6 from "isomorphic-git";
15592
15429
  function sanitizeCloneFolderName(name) {
@@ -15594,7 +15431,7 @@ function sanitizeCloneFolderName(name) {
15594
15431
  }
15595
15432
  async function assertCloneTarget(dir) {
15596
15433
  try {
15597
- const entries = await readdir9(dir);
15434
+ const entries = await readdir10(dir);
15598
15435
  if (entries.length > 0) {
15599
15436
  throw new Error("That folder already has files in it. Choose an empty folder for the book.");
15600
15437
  }
@@ -16016,16 +15853,16 @@ async function repairOnlineBackup(options) {
16016
15853
  }
16017
15854
  // src/lib/remote-auth/scorched-earth.ts
16018
15855
  import git9 from "isomorphic-git";
16019
- import { cp as cp6, mkdir as mkdir17, readdir as readdir10, rm as rm8 } from "node:fs/promises";
15856
+ import { cp as cp6, mkdir as mkdir17, readdir as readdir11, rm as rm8 } from "node:fs/promises";
16020
15857
  import path28 from "node:path";
16021
15858
  async function emptyDir(dir) {
16022
- for (const entry of await readdir10(dir)) {
15859
+ for (const entry of await readdir11(dir)) {
16023
15860
  await rm8(path28.join(dir, entry), { recursive: true, force: true });
16024
15861
  }
16025
15862
  }
16026
15863
  async function copyFilesBack(from, to) {
16027
15864
  const skip = path28.join(from, ".git");
16028
- for (const entry of await readdir10(from)) {
15865
+ for (const entry of await readdir11(from)) {
16029
15866
  const src = path28.join(from, entry);
16030
15867
  if (src === skip)
16031
15868
  continue;
@@ -16078,7 +15915,7 @@ async function scorchedEarth(options) {
16078
15915
  }
16079
15916
  }
16080
15917
  // src/lib/publish/run-publish.ts
16081
- import { readdir as readdir12, stat as stat11 } from "node:fs/promises";
15918
+ import { readdir as readdir13, stat as stat11 } from "node:fs/promises";
16082
15919
  import path33 from "node:path";
16083
15920
 
16084
15921
  // src/lib/publish/command-runner.ts
@@ -16309,7 +16146,7 @@ var drivethrurpgProvider = {
16309
16146
  };
16310
16147
 
16311
16148
  // src/lib/publish/providers/gdrive.ts
16312
- import { mkdtemp as mkdtemp3, readFile as readFile35, readdir as readdir11, rm as rm9, stat as stat8, writeFile as writeFile16 } from "node:fs/promises";
16149
+ import { mkdtemp as mkdtemp3, readFile as readFile36, readdir as readdir12, rm as rm9, stat as stat8, writeFile as writeFile16 } from "node:fs/promises";
16313
16150
  import { tmpdir as tmpdir3 } from "node:os";
16314
16151
  import path30 from "node:path";
16315
16152
  import { Zip, ZipDeflate } from "fflate";
@@ -16694,19 +16531,19 @@ function zipEntriesNonBlocking(entries, opts) {
16694
16531
  });
16695
16532
  }
16696
16533
  async function collectZipEntries(root, dir, out, onWarn) {
16697
- const entries = await readdir11(dir, { withFileTypes: true });
16534
+ const entries = await readdir12(dir, { withFileTypes: true });
16698
16535
  for (const entry of entries) {
16699
16536
  const full = path30.join(dir, entry.name);
16700
16537
  if (entry.isDirectory()) {
16701
16538
  await collectZipEntries(root, full, out, onWarn);
16702
16539
  } else if (entry.isFile()) {
16703
16540
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16704
- out[rel] = await readFile35(full);
16541
+ out[rel] = await readFile36(full);
16705
16542
  } else if (entry.isSymbolicLink()) {
16706
16543
  const resolved = await stat8(full).catch(() => null);
16707
16544
  if (resolved?.isFile()) {
16708
16545
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16709
- out[rel] = await readFile35(full);
16546
+ out[rel] = await readFile36(full);
16710
16547
  } else if (resolved?.isDirectory()) {
16711
16548
  await collectZipEntries(root, full, out, onWarn);
16712
16549
  } else {
@@ -17461,7 +17298,7 @@ async function htmlDirIssues(dir) {
17461
17298
  message: `${dir} has no ${BOOK_HTML} — it isn't an HTML export. ${HTML_HINT}`
17462
17299
  });
17463
17300
  }
17464
- const entries = await readdir12(dir).catch(() => []);
17301
+ const entries = await readdir13(dir).catch(() => []);
17465
17302
  const extras = entries.filter((name) => name.toLowerCase().endsWith(".pdf") || name === BUILD_FINGERPRINT_FILENAME);
17466
17303
  if (extras.length > 0) {
17467
17304
  issues.push({
@@ -17572,7 +17409,7 @@ async function disconnectPublishCredential(key, deps, options = {}) {
17572
17409
  }
17573
17410
  }
17574
17411
  // src/lib/publish/selections.ts
17575
- import { chmod as chmod3, mkdir as mkdir21, readFile as readFile36, writeFile as writeFile18 } from "node:fs/promises";
17412
+ import { chmod as chmod3, mkdir as mkdir21, readFile as readFile37, writeFile as writeFile18 } from "node:fs/promises";
17576
17413
  import path34 from "node:path";
17577
17414
  class PublishSelectionsStore {
17578
17415
  filePath;
@@ -17582,7 +17419,7 @@ class PublishSelectionsStore {
17582
17419
  }
17583
17420
  async read() {
17584
17421
  try {
17585
- const raw = await readFile36(this.filePath, "utf8");
17422
+ const raw = await readFile37(this.filePath, "utf8");
17586
17423
  const parsed = JSON.parse(raw);
17587
17424
  if (parsed && typeof parsed === "object") {
17588
17425
  return {