gutterpress 0.11.20 → 0.11.21-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/dist/api/index.js +2 -2
  3. package/dist/{build-t49xka15.js → build-jd5ky1xh.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-8gm4p42f.js → cli-3zab895b.js} +2 -2
  9. package/dist/{cli-nh16svye.js → cli-a8azwpgj.js} +1 -1
  10. package/dist/{cli-cyej3p9v.js → cli-b57adna7.js} +5 -2
  11. package/dist/{cli-stt6691c.js → cli-xvtqsqy3.js} +268 -429
  12. package/dist/cli.js +12 -12
  13. package/dist/{doctor-53hq2v2n.js → doctor-0kgq1ns6.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-f90yv3dv.js} +4 -4
  17. package/dist/{gutterpress-agent-yvd8w8my.js → gutterpress-agent-d29ry3ak.js} +5 -1
  18. package/dist/{index-379qafjq.js → index-5qam734s.js} +442 -605
  19. package/dist/{index-crys9951.js → index-mr72vt8n.js} +2 -2
  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-fc7nwxea.js} +4 -4
  28. package/dist/{preflight-c0f0s3y1.js → preflight-hwvj3k3d.js} +4 -4
  29. package/dist/{preview-x6wn3vdh.js → preview-ktqjnf3d.js} +4 -4
  30. package/dist/{publish-rskr6x6b.js → publish-nf3d5cbv.js} +4 -4
  31. package/dist/schema/manifest.types.d.ts +10 -2
  32. package/dist/{validate-88w89ye7.js → validate-7aywy6m4.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
@@ -16,12 +16,12 @@ import {
16
16
  stripAnnotations,
17
17
  transparentPages,
18
18
  warn
19
- } from "./cli-8gm4p42f.js";
19
+ } from "./cli-3zab895b.js";
20
20
  import {
21
21
  DEBOUNCE,
22
22
  UsageError,
23
23
  resolvePort
24
- } from "./cli-nh16svye.js";
24
+ } from "./cli-a8azwpgj.js";
25
25
  import {
26
26
  blackTextToGray
27
27
  } from "./cli-ht1bcqdd.js";
@@ -30,7 +30,7 @@ import {
30
30
  getAssetPath,
31
31
  getAssetsDir,
32
32
  lastPageMode
33
- } from "./cli-cyej3p9v.js";
33
+ } from "./cli-b57adna7.js";
34
34
  import {
35
35
  BuildError,
36
36
  EXIT_CODES
@@ -191,8 +191,6 @@ var DTRPG_PRESET = {
191
191
  },
192
192
  assets: {
193
193
  maxImageSize: 1e7,
194
- minImageDpi: 300,
195
- allowedColorSpaces: ["CMYK", "Grayscale"],
196
194
  allowAlpha: false,
197
195
  approvedFontFiles: [],
198
196
  requireFontLicense: false
@@ -273,8 +271,6 @@ var BOOK_PRESET = {
273
271
  },
274
272
  assets: {
275
273
  maxImageSize: 1e7,
276
- minImageDpi: 300,
277
- allowedColorSpaces: ["RGB", "CMYK", "Grayscale"],
278
274
  allowAlpha: true,
279
275
  approvedFontFiles: [],
280
276
  requireFontLicense: false
@@ -345,9 +341,6 @@ var DTRPG_TARGET = {
345
341
  "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
346
342
  "pdf.print.safe-margins": { enabled: true, severity: "error" }
347
343
  },
348
- assets: {
349
- allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
350
- },
351
344
  pdf: {
352
345
  forbidTransparency: DTRPG_PRESET.validate.pdf.forbidTransparency,
353
346
  minImageResolution: DTRPG_PRESET.validate.pdf.minImageResolution
@@ -374,16 +367,16 @@ var ITCH_TARGET = {
374
367
  "pdf.print.pdfx-markers": false,
375
368
  "pdf.print.pdfx-metadata": false,
376
369
  "pdf.print.ink-coverage": false,
377
- "pdf.print.safe-margins": false,
378
- "asset.image.tac-raster": false
370
+ "pdf.print.safe-margins": false
379
371
  },
380
372
  assets: {
381
- allowedColorSpaces: ["RGB", "CMYK", "Grayscale"],
382
- allowAlpha: true
373
+ allowAlpha: true,
374
+ maxImageSize: 0
383
375
  },
384
376
  pdf: {
385
377
  forbidTransparency: false,
386
- requireBleed: false
378
+ requireBleed: false,
379
+ minImageResolution: 0
387
380
  }
388
381
  }
389
382
  },
@@ -613,6 +606,11 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
613
606
  if ((m.validate?.source?.allowedCallouts?.length ?? 0) > 0 || (c.validate?.source?.allowedCallouts?.length ?? 0) > 0) {
614
607
  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.");
615
608
  }
609
+ for (const key of ["minImageDpi", "allowedColorSpaces"]) {
610
+ if (m.validate?.assets?.[key] !== undefined || c.validate?.assets?.[key] !== undefined) {
611
+ 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.");
612
+ }
613
+ }
616
614
  const removed = m;
617
615
  if (removed.engine !== undefined) {
618
616
  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.");
@@ -5469,63 +5467,9 @@ var check7 = {
5469
5467
  };
5470
5468
  registerCheck(check7);
5471
5469
 
5472
- // src/checks/pdf/rasterized-pages.ts
5473
- var check8 = {
5474
- id: "pdf.print.rasterized-pages",
5475
- name: "Rasterized Pages",
5476
- description: "Detects pages that appear to be fully rasterized (CSS filters, blend modes, transparency)",
5477
- category: "pdf",
5478
- phase: "post-build",
5479
- async run(ctx) {
5480
- if (!ctx.pdfPath)
5481
- return [];
5482
- const doc = await loadPdf(ctx.pdfPath);
5483
- if (!doc)
5484
- return [];
5485
- const { imagesByPage } = await getOpPass(doc);
5486
- const { textByPage } = await getTextPass(doc);
5487
- const rasterizedPages = [];
5488
- for (const [pageNum, imgs] of imagesByPage) {
5489
- if (imgs.length !== 1)
5490
- continue;
5491
- let page;
5492
- try {
5493
- page = await doc.getPage(pageNum);
5494
- } catch {
5495
- continue;
5496
- }
5497
- const { w: pageW, h: pageH } = getPageSize(page);
5498
- const img = imgs[0];
5499
- const widthMatch = Math.abs(img.placedW - pageW) / pageW < 0.03;
5500
- const heightMatch = Math.abs(img.placedH - pageH) / pageH < 0.03;
5501
- if (!widthMatch || !heightMatch)
5502
- continue;
5503
- const text = (textByPage[pageNum - 1] ?? "").replace(/\s+/g, "");
5504
- if (text.length > 20 && text.length < 200) {
5505
- rasterizedPages.push(pageNum);
5506
- }
5507
- }
5508
- if (rasterizedPages.length === 0)
5509
- return [];
5510
- rasterizedPages.sort((a, b) => a - b);
5511
- return [
5512
- {
5513
- checkId: check8.id,
5514
- severity: "warning",
5515
- message: `Possible rasterized pages detected: ${rasterizedPages.join(", ")}`,
5516
- file: ctx.pdfPath,
5517
- 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.",
5518
- code: "rasterized-pages-detected",
5519
- data: { pages: rasterizedPages }
5520
- }
5521
- ];
5522
- }
5523
- };
5524
- registerCheck(check8);
5525
-
5526
5470
  // src/checks/pdf/black-text.ts
5527
5471
  import { readFile as readFile8 } from "node:fs/promises";
5528
- var check9 = {
5472
+ var check8 = {
5529
5473
  id: "pdf.print.black-text",
5530
5474
  name: "Black Text",
5531
5475
  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)",
@@ -5541,7 +5485,7 @@ var check9 = {
5541
5485
  if (found.runs === 0)
5542
5486
  return [];
5543
5487
  return [
5544
- finding(check9.id, {
5488
+ finding(check8.id, {
5545
5489
  severity: "warning",
5546
5490
  message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
5547
5491
  file: ctx.pdfPath,
@@ -5552,17 +5496,17 @@ var check9 = {
5552
5496
  ];
5553
5497
  } catch {
5554
5498
  return [
5555
- inspectionFailed(check9.id, "Could not inspect the PDF's text colours.", {
5499
+ inspectionFailed(check8.id, "Could not inspect the PDF's text colours.", {
5556
5500
  file: ctx.pdfPath
5557
5501
  })
5558
5502
  ];
5559
5503
  }
5560
5504
  }
5561
5505
  };
5562
- registerCheck(check9);
5506
+ registerCheck(check8);
5563
5507
 
5564
5508
  // src/checks/pdf/signature.ts
5565
- var check10 = {
5509
+ var check9 = {
5566
5510
  id: "pdf.print.signature",
5567
5511
  name: "Signature",
5568
5512
  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)",
@@ -5574,7 +5518,7 @@ var check10 = {
5574
5518
  return [];
5575
5519
  const doc = await loadPdf(ctx.pdfPath);
5576
5520
  if (!doc) {
5577
- return [inspectionFailed(check10.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
5521
+ return [inspectionFailed(check9.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
5578
5522
  }
5579
5523
  const size = ctx.config.print.signature;
5580
5524
  const pages = doc.numPages;
@@ -5583,7 +5527,7 @@ var check10 = {
5583
5527
  const data = { pages, signature: size };
5584
5528
  if (pages % size !== 0) {
5585
5529
  return [
5586
- finding(check10.id, {
5530
+ finding(check9.id, {
5587
5531
  severity: "error",
5588
5532
  message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
5589
5533
  file: ctx.pdfPath,
@@ -5599,10 +5543,10 @@ var check10 = {
5599
5543
  if (!await pageHasMarks(doc, pages))
5600
5544
  return [];
5601
5545
  } catch {
5602
- return [inspectionFailed(check10.id, "Could not read the last page.", { file: ctx.pdfPath })];
5546
+ return [inspectionFailed(check9.id, "Could not read the last page.", { file: ctx.pdfPath })];
5603
5547
  }
5604
5548
  return [
5605
- finding(check10.id, {
5549
+ finding(check9.id, {
5606
5550
  severity: "error",
5607
5551
  message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
5608
5552
  file: ctx.pdfPath,
@@ -5613,10 +5557,10 @@ var check10 = {
5613
5557
  ];
5614
5558
  }
5615
5559
  };
5616
- registerCheck(check10);
5560
+ registerCheck(check9);
5617
5561
 
5618
5562
  // src/checks/pdf/bookmarks.ts
5619
- var check11 = {
5563
+ var check10 = {
5620
5564
  id: "pdf.nav.bookmarks",
5621
5565
  name: "Bookmarks",
5622
5566
  description: "Checks for PDF outline (bookmarks) tree",
@@ -5630,14 +5574,14 @@ var check11 = {
5630
5574
  const doc = await loadPdf(ctx.pdfPath);
5631
5575
  if (!doc) {
5632
5576
  return [
5633
- inspectionFailed(check11.id, "Could not inspect PDF for bookmarks.", {
5577
+ inspectionFailed(check10.id, "Could not inspect PDF for bookmarks.", {
5634
5578
  file: ctx.pdfPath
5635
5579
  })
5636
5580
  ];
5637
5581
  }
5638
5582
  if (await getOutlineCount(doc) === 0) {
5639
5583
  return [
5640
- finding(check11.id, {
5584
+ finding(check10.id, {
5641
5585
  severity: "warning",
5642
5586
  message: "PDF does not contain bookmarks (outline tree).",
5643
5587
  file: ctx.pdfPath
@@ -5647,10 +5591,10 @@ var check11 = {
5647
5591
  return [];
5648
5592
  }
5649
5593
  };
5650
- registerCheck(check11);
5594
+ registerCheck(check10);
5651
5595
 
5652
5596
  // src/checks/pdf/toc-links.ts
5653
- var check12 = {
5597
+ var check11 = {
5654
5598
  id: "pdf.nav.toc-links",
5655
5599
  name: "TOC Links",
5656
5600
  description: "Detects link annotations on TOC pages",
@@ -5665,7 +5609,7 @@ var check12 = {
5665
5609
  if (!doc) {
5666
5610
  return [
5667
5611
  {
5668
- checkId: check12.id,
5612
+ checkId: check11.id,
5669
5613
  severity: "warning",
5670
5614
  message: "Could not inspect PDF for TOC links.",
5671
5615
  file: ctx.pdfPath
@@ -5676,7 +5620,7 @@ var check12 = {
5676
5620
  if (!hasLinks) {
5677
5621
  return [
5678
5622
  {
5679
- checkId: check12.id,
5623
+ checkId: check11.id,
5680
5624
  severity: "warning",
5681
5625
  message: "No link annotations found in PDF (expected for TOC).",
5682
5626
  file: ctx.pdfPath
@@ -5686,10 +5630,10 @@ var check12 = {
5686
5630
  return [];
5687
5631
  }
5688
5632
  };
5689
- registerCheck(check12);
5633
+ registerCheck(check11);
5690
5634
 
5691
5635
  // src/checks/pdf/cross-refs.ts
5692
- var check13 = {
5636
+ var check12 = {
5693
5637
  id: "pdf.nav.cross-refs",
5694
5638
  name: "Cross References",
5695
5639
  description: "Counts and verifies internal link annotations",
@@ -5702,7 +5646,7 @@ var check13 = {
5702
5646
  if (!doc) {
5703
5647
  return [
5704
5648
  {
5705
- checkId: check13.id,
5649
+ checkId: check12.id,
5706
5650
  severity: "info",
5707
5651
  message: "Could not inspect PDF for cross-references.",
5708
5652
  file: ctx.pdfPath
@@ -5712,7 +5656,7 @@ var check13 = {
5712
5656
  const linkCount = await countLinkAnnotations(doc);
5713
5657
  return [
5714
5658
  {
5715
- checkId: check13.id,
5659
+ checkId: check12.id,
5716
5660
  severity: "info",
5717
5661
  message: `PDF contains ${linkCount} internal link annotation${linkCount !== 1 ? "s" : ""}.`,
5718
5662
  file: ctx.pdfPath
@@ -5720,10 +5664,10 @@ var check13 = {
5720
5664
  ];
5721
5665
  }
5722
5666
  };
5723
- registerCheck(check13);
5667
+ registerCheck(check12);
5724
5668
 
5725
5669
  // src/checks/pdf/page-labels.ts
5726
- var check14 = {
5670
+ var check13 = {
5727
5671
  id: "pdf.nav.page-labels",
5728
5672
  name: "Page Labels",
5729
5673
  description: "Verifies PDF page labels / folio numbering",
@@ -5738,7 +5682,7 @@ var check14 = {
5738
5682
  const labels = await getPageLabels(doc);
5739
5683
  return [
5740
5684
  {
5741
- checkId: check14.id,
5685
+ checkId: check13.id,
5742
5686
  severity: "info",
5743
5687
  message: labels && labels.length > 0 ? "PDF contains page labels." : "PDF does not contain explicit page labels.",
5744
5688
  file: ctx.pdfPath
@@ -5746,10 +5690,10 @@ var check14 = {
5746
5690
  ];
5747
5691
  }
5748
5692
  };
5749
- registerCheck(check14);
5693
+ registerCheck(check13);
5750
5694
 
5751
5695
  // src/checks/pdf/image-resolution.ts
5752
- var check15 = {
5696
+ var check14 = {
5753
5697
  id: "pdf.print.image-resolution",
5754
5698
  name: "Image Resolution",
5755
5699
  description: "Checks DPI of images embedded in the PDF",
@@ -5776,7 +5720,7 @@ var check15 = {
5776
5720
  lowResPages.sort((a, b) => a - b);
5777
5721
  return [
5778
5722
  {
5779
- checkId: check15.id,
5723
+ checkId: check14.id,
5780
5724
  severity: "warning",
5781
5725
  message: `Low-resolution images (below ${minDpi} DPI) found on pages: ${lowResPages.join(", ")}`,
5782
5726
  file: ctx.pdfPath
@@ -5786,10 +5730,10 @@ var check15 = {
5786
5730
  return [];
5787
5731
  }
5788
5732
  };
5789
- registerCheck(check15);
5733
+ registerCheck(check14);
5790
5734
 
5791
5735
  // src/checks/pdf/transparency.ts
5792
- var check16 = {
5736
+ var check15 = {
5793
5737
  id: "pdf.print.transparency",
5794
5738
  name: "Transparency",
5795
5739
  description: "Checks for transparency markers (/Transparency, /SMask, /BM) in the PDF",
@@ -5811,7 +5755,7 @@ var check16 = {
5811
5755
  if (found.length > 0) {
5812
5756
  return [
5813
5757
  {
5814
- checkId: check16.id,
5758
+ checkId: check15.id,
5815
5759
  severity: "warning",
5816
5760
  message: `Transparency detected in PDF: ${found.join(", ")}. This may cause issues with some printers.`,
5817
5761
  file: ctx.pdfPath
@@ -5821,7 +5765,7 @@ var check16 = {
5821
5765
  return [];
5822
5766
  }
5823
5767
  };
5824
- registerCheck(check16);
5768
+ registerCheck(check15);
5825
5769
 
5826
5770
  // src/checks/pdf/bleed.ts
5827
5771
  function findBox(bytes, box) {
@@ -5831,7 +5775,7 @@ function findBox(bytes, box) {
5831
5775
  return null;
5832
5776
  return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
5833
5777
  }
5834
- var check17 = {
5778
+ var check16 = {
5835
5779
  id: "pdf.print.bleed",
5836
5780
  name: "Bleed Box",
5837
5781
  description: "Compares MediaBox vs TrimBox/BleedBox to verify bleed area",
@@ -5845,7 +5789,7 @@ var check17 = {
5845
5789
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
5846
5790
  if (!bytes) {
5847
5791
  return [
5848
- inspectionFailed(check17.id, "Could not inspect PDF for bleed boxes.", {
5792
+ inspectionFailed(check16.id, "Could not inspect PDF for bleed boxes.", {
5849
5793
  file: ctx.pdfPath
5850
5794
  })
5851
5795
  ];
@@ -5855,14 +5799,14 @@ var check17 = {
5855
5799
  const bleed = findBox(bytes, "BleedBox");
5856
5800
  if (!media) {
5857
5801
  return [
5858
- inspectionFailed(check17.id, "Could not parse MediaBox from PDF.", {
5802
+ inspectionFailed(check16.id, "Could not parse MediaBox from PDF.", {
5859
5803
  file: ctx.pdfPath
5860
5804
  })
5861
5805
  ];
5862
5806
  }
5863
5807
  if (!trim && !bleed) {
5864
5808
  return [
5865
- finding(check17.id, {
5809
+ finding(check16.id, {
5866
5810
  severity: "warning",
5867
5811
  message: "No TrimBox or BleedBox found. Bleed area cannot be verified.",
5868
5812
  file: ctx.pdfPath
@@ -5875,7 +5819,7 @@ var check17 = {
5875
5819
  const bleedSize = ctx.config.validate.pdf.bleedSize;
5876
5820
  if (mediaW - trimW < bleedSize * 2 * 0.9) {
5877
5821
  return [
5878
- finding(check17.id, {
5822
+ finding(check16.id, {
5879
5823
  severity: "warning",
5880
5824
  message: `Bleed area appears insufficient. Expected at least ${bleedSize}pt on each side.`,
5881
5825
  file: ctx.pdfPath
@@ -5886,10 +5830,10 @@ var check17 = {
5886
5830
  return [];
5887
5831
  }
5888
5832
  };
5889
- registerCheck(check17);
5833
+ registerCheck(check16);
5890
5834
 
5891
5835
  // src/checks/pdf/pdfx-metadata.ts
5892
- var check18 = {
5836
+ var check17 = {
5893
5837
  id: "pdf.print.pdfx-metadata",
5894
5838
  name: "PDF/X Metadata",
5895
5839
  description: "Verifies PDF/X DOCINFO metadata via qpdf JSON",
@@ -5908,22 +5852,22 @@ var check18 = {
5908
5852
  const objects = parseQpdfObjectsJson(stdout);
5909
5853
  if (!objects) {
5910
5854
  return [
5911
- inspectionFailed(check18.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
5855
+ inspectionFailed(check17.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
5912
5856
  ];
5913
5857
  }
5914
- return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check18.id, {
5858
+ return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check17.id, {
5915
5859
  severity: "error",
5916
5860
  message,
5917
5861
  file: ctx.pdfPath
5918
5862
  }));
5919
5863
  } catch {
5920
5864
  return [
5921
- inspectionFailed(check18.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
5865
+ inspectionFailed(check17.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
5922
5866
  ];
5923
5867
  }
5924
5868
  }
5925
5869
  };
5926
- registerCheck(check18);
5870
+ registerCheck(check17);
5927
5871
 
5928
5872
  // src/checks/source/markdownlint.ts
5929
5873
  import { readFile as readFile9 } from "node:fs/promises";
@@ -5978,7 +5922,7 @@ async function loadConfig(configPath) {
5978
5922
  return {};
5979
5923
  }
5980
5924
  var MARKDOWNLINT_CHECK_ID = "source.markdownlint";
5981
- var check19 = {
5925
+ var check18 = {
5982
5926
  id: MARKDOWNLINT_CHECK_ID,
5983
5927
  name: "Markdownlint",
5984
5928
  description: "Runs markdownlint with project config to validate Markdown files",
@@ -6003,7 +5947,7 @@ var check19 = {
6003
5947
  continue;
6004
5948
  for (const v of violations) {
6005
5949
  out.push({
6006
- checkId: check19.id,
5950
+ checkId: check18.id,
6007
5951
  severity: "warning",
6008
5952
  message: `${v.ruleDescription} (${v.ruleNames.join("/")})`,
6009
5953
  file,
@@ -6016,7 +5960,7 @@ var check19 = {
6016
5960
  return out;
6017
5961
  }
6018
5962
  };
6019
- registerCheck(check19);
5963
+ registerCheck(check18);
6020
5964
 
6021
5965
  // src/checks/source/htmlhint.ts
6022
5966
  import { existsSync as existsSync7 } from "node:fs";
@@ -6024,7 +5968,7 @@ import { readFile as readFile10 } from "node:fs/promises";
6024
5968
  import htmlhint from "htmlhint";
6025
5969
  var { HTMLHint } = htmlhint;
6026
5970
  var CONFIG_NAMES2 = [".htmlhintrc"];
6027
- var check20 = {
5971
+ var check19 = {
6028
5972
  id: "source.htmlhint",
6029
5973
  name: "HTMLHint",
6030
5974
  description: "Runs htmlhint with project config to validate generated HTML",
@@ -6044,7 +5988,7 @@ var check20 = {
6044
5988
  const html = await readFile10(ctx.htmlPath, "utf8");
6045
5989
  const messages = HTMLHint.verify(html, ruleset);
6046
5990
  return messages.map((m) => ({
6047
- checkId: check20.id,
5991
+ checkId: check19.id,
6048
5992
  severity: m.type === "error" ? "error" : "warning",
6049
5993
  message: `${m.rule.id}: ${m.message}`,
6050
5994
  file: ctx.htmlPath,
@@ -6053,7 +5997,7 @@ var check20 = {
6053
5997
  }));
6054
5998
  }
6055
5999
  };
6056
- registerCheck(check20);
6000
+ registerCheck(check19);
6057
6001
 
6058
6002
  // src/checks/source/stylelint.ts
6059
6003
  import { readFile as readFile11 } from "node:fs/promises";
@@ -6172,10 +6116,27 @@ function isUnpairedFitContentWidth(decl) {
6172
6116
  return true;
6173
6117
  }
6174
6118
  function targetsPageWrapper(selector) {
6175
- if (!/\.(page|spread)(?![\w-])/.test(selector))
6119
+ if (!/\.(page|spread)(?![\w-])/.test(subjectCompound(selector)))
6176
6120
  return false;
6177
6121
  return !/\bgp-(bleed|pin)\b/.test(selector);
6178
6122
  }
6123
+ function subjectCompound(selector) {
6124
+ let depth = 0;
6125
+ let start = 0;
6126
+ for (let i = 0;i < selector.length; i++) {
6127
+ const c = selector[i];
6128
+ if (c === "(" || c === "[")
6129
+ depth++;
6130
+ else if (c === ")" || c === "]")
6131
+ depth--;
6132
+ else if (depth === 0 && /[\s>+~]/.test(c))
6133
+ start = i + 1;
6134
+ }
6135
+ let subject = selector.slice(start);
6136
+ while (/\([^()]*\)/.test(subject))
6137
+ subject = subject.replace(/\([^()]*\)/g, "");
6138
+ return subject;
6139
+ }
6179
6140
  var cssWideResetKeywords = new Set(["initial", "unset", "revert", "revert-layer"]);
6180
6141
  var inertValues = {
6181
6142
  "z-index": new Set(["auto"]),
@@ -6422,7 +6383,7 @@ function checkCssRaw(css, from) {
6422
6383
  if (at === "page" || marginBoxAtRuleNames.has(at))
6423
6384
  return;
6424
6385
  }
6425
- if (!targetsPageWrapper(rule.selector))
6386
+ if (!rule.selectors.some(targetsPageWrapper))
6426
6387
  return;
6427
6388
  rule.walkDecls((decl) => {
6428
6389
  if (clipsDescendants(decl)) {
@@ -6446,7 +6407,7 @@ function checkCssRaw(css, from) {
6446
6407
  }
6447
6408
 
6448
6409
  // src/checks/source/stylelint.ts
6449
- var check21 = {
6410
+ var check20 = {
6450
6411
  id: "source.stylelint",
6451
6412
  name: "Print-safety CSS",
6452
6413
  description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
@@ -6466,7 +6427,7 @@ var check21 = {
6466
6427
  css = await readFile11(file, "utf8");
6467
6428
  } catch (err) {
6468
6429
  results.push({
6469
- checkId: check21.id,
6430
+ checkId: check20.id,
6470
6431
  severity: "error",
6471
6432
  message: `Cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`,
6472
6433
  file
@@ -6475,7 +6436,7 @@ var check21 = {
6475
6436
  }
6476
6437
  for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
6477
6438
  results.push({
6478
- checkId: check21.id,
6439
+ checkId: check20.id,
6479
6440
  severity: w.severity,
6480
6441
  message: `${w.rule}: ${w.message}`,
6481
6442
  file,
@@ -6487,7 +6448,7 @@ var check21 = {
6487
6448
  return results;
6488
6449
  }
6489
6450
  };
6490
- registerCheck(check21);
6451
+ registerCheck(check20);
6491
6452
 
6492
6453
  // src/checks/source/css-ownership.ts
6493
6454
  import { readFile as readFile12 } from "node:fs/promises";
@@ -6671,7 +6632,7 @@ function checkOwnership(root, file, maps) {
6671
6632
  });
6672
6633
  return out;
6673
6634
  }
6674
- var check22 = {
6635
+ var check21 = {
6675
6636
  id: "source.css-ownership",
6676
6637
  name: "CSS ownership contract",
6677
6638
  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.",
@@ -6694,7 +6655,7 @@ var check22 = {
6694
6655
  } catch (err) {
6695
6656
  return [
6696
6657
  {
6697
- checkId: check22.id,
6658
+ checkId: check21.id,
6698
6659
  severity: "error",
6699
6660
  message: `Failed to parse CSS ownership contract "${configPath}": ${err instanceof Error ? err.message : String(err)}`,
6700
6661
  file: configPath
@@ -6736,7 +6697,7 @@ var check22 = {
6736
6697
  }
6737
6698
  for (const v of violations) {
6738
6699
  results.push({
6739
- checkId: check22.id,
6700
+ checkId: check21.id,
6740
6701
  severity: "warning",
6741
6702
  message: v.message,
6742
6703
  file: absFile,
@@ -6748,7 +6709,7 @@ var check22 = {
6748
6709
  return results;
6749
6710
  }
6750
6711
  };
6751
- registerCheck(check22);
6712
+ registerCheck(check21);
6752
6713
 
6753
6714
  // src/checks/source/local-refs.ts
6754
6715
  import { existsSync as existsSync9 } from "node:fs";
@@ -7139,7 +7100,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
7139
7100
  }
7140
7101
 
7141
7102
  // src/checks/source/local-refs.ts
7142
- var check23 = {
7103
+ var check22 = {
7143
7104
  id: "source.links.local-refs",
7144
7105
  name: "Local Markdown References",
7145
7106
  description: "Checks local markdown links and image refs resolve on disk",
@@ -7147,12 +7108,12 @@ var check23 = {
7147
7108
  phase: "pre-build",
7148
7109
  async run(ctx) {
7149
7110
  const results = [];
7150
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
7111
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check22.id, results)) {
7151
7112
  if (isNonFilesystemRef(ref))
7152
7113
  continue;
7153
7114
  const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
7154
7115
  if (escape) {
7155
- results.push(finding(check23.id, {
7116
+ results.push(finding(check22.id, {
7156
7117
  severity: "error",
7157
7118
  message: escape,
7158
7119
  file,
@@ -7162,7 +7123,7 @@ var check23 = {
7162
7123
  }
7163
7124
  if (localRefExists(ref, kind, file, ctx.inputDir))
7164
7125
  continue;
7165
- results.push(finding(check23.id, {
7126
+ results.push(finding(check22.id, {
7166
7127
  severity: kind === "image" ? "warning" : "error",
7167
7128
  code: kind === "image" ? "missing-image-placeholder" : undefined,
7168
7129
  message: kind === "image" ? `Local image not found; build will substitute a magenta placeholder: ${ref}` : `Local reference not found: ${ref}`,
@@ -7187,7 +7148,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
7187
7148
  return existsSync9(fileRelative);
7188
7149
  }
7189
7150
  }
7190
- registerCheck(check23);
7151
+ registerCheck(check22);
7191
7152
 
7192
7153
  // src/lib/build-staging.ts
7193
7154
  import path8 from "node:path";
@@ -7453,7 +7414,7 @@ async function createStageRoot() {
7453
7414
  }
7454
7415
 
7455
7416
  // src/checks/source/dangling-links.ts
7456
- var check24 = {
7417
+ var check23 = {
7457
7418
  id: "source.links.dangling",
7458
7419
  name: "Dangling Links",
7459
7420
  description: "Flags relative links the printed book cannot open (their href is dropped at print time)",
@@ -7461,10 +7422,10 @@ var check24 = {
7461
7422
  phase: "pre-build",
7462
7423
  async run(ctx) {
7463
7424
  const results = [];
7464
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check24.id, results)) {
7425
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
7465
7426
  if (kind !== "link" || isPrintResolvableHref(ref))
7466
7427
  continue;
7467
- results.push(finding(check24.id, {
7428
+ results.push(finding(check23.id, {
7468
7429
  severity: "warning",
7469
7430
  code: "dangling-link",
7470
7431
  data: { ref },
@@ -7476,11 +7437,11 @@ var check24 = {
7476
7437
  return results;
7477
7438
  }
7478
7439
  };
7479
- registerCheck(check24);
7440
+ registerCheck(check23);
7480
7441
 
7481
7442
  // src/checks/source/accessibility-alt-text.ts
7482
7443
  import { readFile as readFile15 } from "node:fs/promises";
7483
- var check25 = {
7444
+ var check24 = {
7484
7445
  id: "source.accessibility.alt-text",
7485
7446
  name: "Image Alt Text",
7486
7447
  description: "Checks markdown images include non-empty alt text",
@@ -7509,7 +7470,7 @@ var check25 = {
7509
7470
  if (alt.trim())
7510
7471
  continue;
7511
7472
  results.push({
7512
- checkId: check25.id,
7473
+ checkId: check24.id,
7513
7474
  severity: "warning",
7514
7475
  message: "Image is missing alt text",
7515
7476
  file,
@@ -7534,11 +7495,11 @@ function extractImageAlts(line) {
7534
7495
  }
7535
7496
  return alts;
7536
7497
  }
7537
- registerCheck(check25);
7498
+ registerCheck(check24);
7538
7499
 
7539
7500
  // src/checks/source/accessibility-heading-order.ts
7540
7501
  import { readFile as readFile16 } from "node:fs/promises";
7541
- var check26 = {
7502
+ var check25 = {
7542
7503
  id: "source.accessibility.heading-order",
7543
7504
  name: "Heading Order",
7544
7505
  description: "Checks markdown heading levels do not jump by more than one",
@@ -7566,7 +7527,7 @@ var check26 = {
7566
7527
  if (inFence)
7567
7528
  continue;
7568
7529
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
7569
- if (comment && parseDisableDirective(comment[1])?.includes(check26.id)) {
7530
+ if (comment && parseDisableDirective(comment[1])?.includes(check25.id)) {
7570
7531
  silencedLine = i + 1;
7571
7532
  continue;
7572
7533
  }
@@ -7576,7 +7537,7 @@ var check26 = {
7576
7537
  const level = match[1].length;
7577
7538
  if (prevLevel != null && level > prevLevel + 1 && i !== silencedLine) {
7578
7539
  results.push({
7579
- checkId: check26.id,
7540
+ checkId: check25.id,
7580
7541
  severity: "warning",
7581
7542
  message: `Heading level jump from h${prevLevel} to h${level}`,
7582
7543
  file,
@@ -7590,7 +7551,7 @@ var check26 = {
7590
7551
  return results;
7591
7552
  }
7592
7553
  };
7593
- registerCheck(check26);
7554
+ registerCheck(check25);
7594
7555
 
7595
7556
  // src/checks/source/layout-markers.ts
7596
7557
  import { readFile as readFile17 } from "node:fs/promises";
@@ -7701,7 +7662,7 @@ async function collectKnownClasses(ctx, plugins) {
7701
7662
  }
7702
7663
  return names;
7703
7664
  }
7704
- var check27 = {
7665
+ var check26 = {
7705
7666
  id: "source.markdown.layout-markers",
7706
7667
  name: "Layout Markers",
7707
7668
  description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class and any other class no stylesheet mentions",
@@ -7713,7 +7674,7 @@ var check27 = {
7713
7674
  return [];
7714
7675
  const results = [];
7715
7676
  const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
7716
- results.push(inspectionFailed(check27.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
7677
+ results.push(inspectionFailed(check26.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
7717
7678
  });
7718
7679
  const md = createMarkdownRenderer(plugins);
7719
7680
  let known;
@@ -7732,7 +7693,7 @@ var check27 = {
7732
7693
  if (names.has(cls) || warnedLines.has(use.line))
7733
7694
  continue;
7734
7695
  const guess = nearestKnownClass(cls, names);
7735
- results.push(finding(check27.id, {
7696
+ results.push(finding(check26.id, {
7736
7697
  severity: "warning",
7737
7698
  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.` : ""),
7738
7699
  file,
@@ -7742,7 +7703,7 @@ var check27 = {
7742
7703
  }
7743
7704
  }
7744
7705
  for (const w of env.layoutWarnings ?? []) {
7745
- results.push(finding(check27.id, {
7706
+ results.push(finding(check26.id, {
7746
7707
  severity: w.severity ?? "warning",
7747
7708
  message: w.message,
7748
7709
  file,
@@ -7751,13 +7712,13 @@ var check27 = {
7751
7712
  }));
7752
7713
  }
7753
7714
  } catch (error2) {
7754
- results.push(inspectionFailed(check27.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
7715
+ results.push(inspectionFailed(check26.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
7755
7716
  }
7756
7717
  }
7757
7718
  return results;
7758
7719
  }
7759
7720
  };
7760
- registerCheck(check27);
7721
+ registerCheck(check26);
7761
7722
 
7762
7723
  // src/checks/source/merge-markers.ts
7763
7724
  import { readFile as readFile18 } from "node:fs/promises";
@@ -7777,6 +7738,9 @@ var ALL_IMAGE_EXTS = [
7777
7738
  "svg",
7778
7739
  "gif"
7779
7740
  ];
7741
+ function shippedImages(ctx, exts) {
7742
+ return (ctx.imageFiles ?? []).filter((file) => exts.includes(file.slice(file.lastIndexOf(".") + 1).toLowerCase()));
7743
+ }
7780
7744
  var FONT_EXTS2 = ["woff", "woff2", "otf", "ttf", "eot"];
7781
7745
  var ASSET_SCAN_IGNORE_GLOBS = [
7782
7746
  "**/node_modules/**",
@@ -7790,7 +7754,7 @@ var CLOSE_SENTINEL = ">>>>>>> online version";
7790
7754
  function withoutOnlineTag(relPath) {
7791
7755
  return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
7792
7756
  }
7793
- var check28 = {
7757
+ var check27 = {
7794
7758
  id: "source.sync.merge-markers",
7795
7759
  name: "Combined Versions",
7796
7760
  description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
@@ -7804,7 +7768,7 @@ var check28 = {
7804
7768
  try {
7805
7769
  content = await readFile18(file, "utf8");
7806
7770
  } catch {
7807
- results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
7771
+ results.push(inspectionFailed(check27.id, `Could not read source file: ${file}`, { file }));
7808
7772
  continue;
7809
7773
  }
7810
7774
  if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
@@ -7816,7 +7780,7 @@ var check28 = {
7816
7780
  const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
7817
7781
  if (line === OPEN_SENTINEL) {
7818
7782
  inBlock = true;
7819
- results.push(finding(check28.id, {
7783
+ results.push(finding(check27.id, {
7820
7784
  severity: "error",
7821
7785
  code: "two-versions-passage",
7822
7786
  message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
@@ -7825,7 +7789,7 @@ var check28 = {
7825
7789
  }));
7826
7790
  } else if (line === CLOSE_SENTINEL) {
7827
7791
  if (!inBlock) {
7828
- results.push(finding(check28.id, {
7792
+ results.push(finding(check27.id, {
7829
7793
  severity: "error",
7830
7794
  code: "leftover-version-marker",
7831
7795
  message: "This marker line is left over from combining two versions — delete it.",
@@ -7846,7 +7810,7 @@ var check28 = {
7846
7810
  });
7847
7811
  for (const sibling of siblings.sort()) {
7848
7812
  const rel = path9.relative(ctx.inputDir, sibling).split(path9.sep).join("/");
7849
- results.push(finding(check28.id, {
7813
+ results.push(finding(check27.id, {
7850
7814
  severity: "warning",
7851
7815
  code: "kept-both-versions",
7852
7816
  message: `Two versions of ${withoutOnlineTag(rel)} are in your book — keep the one you want, then delete the other.`,
@@ -7854,12 +7818,12 @@ var check28 = {
7854
7818
  }));
7855
7819
  }
7856
7820
  } catch (error2) {
7857
- results.push(inspectionFailed(check28.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
7821
+ results.push(inspectionFailed(check27.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
7858
7822
  }
7859
7823
  return results;
7860
7824
  }
7861
7825
  };
7862
- registerCheck(check28);
7826
+ registerCheck(check27);
7863
7827
 
7864
7828
  // src/checks/source/characters.ts
7865
7829
  import { readFile as readFile19 } from "node:fs/promises";
@@ -7974,7 +7938,7 @@ function codePointLabel(char) {
7974
7938
 
7975
7939
  // src/checks/source/characters.ts
7976
7940
  var PROBE = new RegExp(CHARACTER_PATTERN.source, "u");
7977
- var check29 = {
7941
+ var check28 = {
7978
7942
  id: "source.characters",
7979
7943
  name: "Unusual Characters",
7980
7944
  description: "Finds invisible and control characters, odd spaces, and pasted typography (em dashes, curly quotes, emoji) in markdown",
@@ -7987,7 +7951,7 @@ var check29 = {
7987
7951
  try {
7988
7952
  content = await readFile19(file, "utf8");
7989
7953
  } catch {
7990
- results.push(inspectionFailed(check29.id, `Could not read source file: ${file}`, { file }));
7954
+ results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
7991
7955
  continue;
7992
7956
  }
7993
7957
  if (!PROBE.test(content))
@@ -7998,7 +7962,7 @@ var check29 = {
7998
7962
  for (let i = 0;i < lines.length; i++) {
7999
7963
  const line = lines[i];
8000
7964
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
8001
- if (comment && parseDisableDirective(comment[1])?.includes(check29.id)) {
7965
+ if (comment && parseDisableDirective(comment[1])?.includes(check28.id)) {
8002
7966
  silencedLine = i + 1;
8003
7967
  continue;
8004
7968
  }
@@ -8007,7 +7971,7 @@ var check29 = {
8007
7971
  for (const match of line.matchAll(CHARACTER_PATTERN)) {
8008
7972
  const char = match[0];
8009
7973
  const { name, code, fix } = describeCharacter(char);
8010
- results.push(finding(check29.id, {
7974
+ results.push(finding(check28.id, {
8011
7975
  severity: code === "typographer" ? "info" : "warning",
8012
7976
  code,
8013
7977
  message: `${name} (${codePointLabel(char)}). ${fix}`,
@@ -8022,19 +7986,55 @@ var check29 = {
8022
7986
  return results;
8023
7987
  }
8024
7988
  };
8025
- registerCheck(check29);
7989
+ registerCheck(check28);
8026
7990
 
8027
7991
  // src/checks/asset/image-file-size.ts
8028
- import { stat as stat3 } from "node:fs/promises";
7992
+ import { stat as stat2 } from "node:fs/promises";
7993
+ var check29 = {
7994
+ id: "asset.image.file-size",
7995
+ name: "Image File Size",
7996
+ description: "Checks that image files do not exceed the maximum size limit",
7997
+ category: "asset",
7998
+ phase: "pre-build",
7999
+ async run(ctx) {
8000
+ const maxSize = ctx.config.validate.assets.maxImageSize;
8001
+ if (!maxSize)
8002
+ return [];
8003
+ const files = shippedImages(ctx, ALL_IMAGE_EXTS);
8004
+ if (files.length === 0)
8005
+ return [];
8006
+ const results = [];
8007
+ for (const file of files) {
8008
+ try {
8009
+ const info3 = await stat2(file);
8010
+ if (info3.size > maxSize) {
8011
+ const sizeMb = (info3.size / 1e6).toFixed(1);
8012
+ const maxMb = (maxSize / 1e6).toFixed(1);
8013
+ results.push(finding(check29.id, {
8014
+ severity: "warning",
8015
+ message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
8016
+ file
8017
+ }));
8018
+ }
8019
+ } catch {
8020
+ results.push(inspectionFailed(check29.id, `Could not stat image file: ${file}`, {
8021
+ file
8022
+ }));
8023
+ }
8024
+ }
8025
+ return results;
8026
+ }
8027
+ };
8028
+ registerCheck(check29);
8029
8029
 
8030
8030
  // src/lib/image-inspect.ts
8031
- import { stat as stat2, readFile as readFile20 } from "node:fs/promises";
8031
+ import { stat as stat3, readFile as readFile20 } from "node:fs/promises";
8032
8032
  var DEFAULT_DPI = 72;
8033
8033
  var cache = new Map;
8034
8034
  async function inspectImage(path10) {
8035
8035
  let st;
8036
8036
  try {
8037
- st = await stat2(path10);
8037
+ st = await stat3(path10);
8038
8038
  } catch {
8039
8039
  return null;
8040
8040
  }
@@ -8232,136 +8232,9 @@ function parseTiff(b) {
8232
8232
  colorSpace
8233
8233
  };
8234
8234
  }
8235
- async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS) {
8236
- const { glob } = await import("glob");
8237
- const pattern = exts.length === 1 ? `**/*.${exts[0]}` : `**/*.{${exts.join(",")}}`;
8238
- const files = [];
8239
- for (const dir of dirs) {
8240
- const matches = await glob(pattern, {
8241
- cwd: dir,
8242
- absolute: true,
8243
- ignore: [...ignore2]
8244
- });
8245
- files.push(...matches);
8246
- }
8247
- return files;
8248
- }
8249
-
8250
- // src/checks/asset/image-file-size.ts
8251
- var check30 = {
8252
- id: "asset.image.file-size",
8253
- name: "Image File Size",
8254
- description: "Checks that image files do not exceed the maximum size limit",
8255
- category: "asset",
8256
- phase: "pre-build",
8257
- async run(ctx) {
8258
- const maxSize = ctx.config.validate.assets.maxImageSize;
8259
- if (!maxSize)
8260
- return [];
8261
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
8262
- const files = await collectImageFiles(dirs, ALL_IMAGE_EXTS);
8263
- if (files.length === 0)
8264
- return [];
8265
- const results = [];
8266
- for (const file of files) {
8267
- try {
8268
- const info3 = await stat3(file);
8269
- if (info3.size > maxSize) {
8270
- const sizeMb = (info3.size / 1e6).toFixed(1);
8271
- const maxMb = (maxSize / 1e6).toFixed(1);
8272
- results.push(finding(check30.id, {
8273
- severity: "warning",
8274
- message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
8275
- file
8276
- }));
8277
- }
8278
- } catch {
8279
- results.push(inspectionFailed(check30.id, `Could not stat image file: ${file}`, {
8280
- file
8281
- }));
8282
- }
8283
- }
8284
- return results;
8285
- }
8286
- };
8287
- registerCheck(check30);
8288
-
8289
- // src/checks/asset/image-resolution.ts
8290
- var check31 = {
8291
- id: "asset.image.resolution",
8292
- name: "Image Resolution",
8293
- description: "Checks source image DPI from embedded density metadata",
8294
- category: "asset",
8295
- phase: "pre-build",
8296
- async run(ctx) {
8297
- const minDpi = ctx.config.validate.assets.minImageDpi;
8298
- if (!minDpi)
8299
- return [];
8300
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
8301
- const files = await collectImageFiles(dirs, RASTER_INSPECTABLE_EXTS);
8302
- if (files.length === 0)
8303
- return [];
8304
- const results = [];
8305
- for (const file of files) {
8306
- const info3 = await inspectImage(file);
8307
- if (!info3)
8308
- continue;
8309
- const { xDpi, yDpi } = info3;
8310
- if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
8311
- results.push({
8312
- checkId: check31.id,
8313
- severity: "warning",
8314
- message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
8315
- file
8316
- });
8317
- }
8318
- }
8319
- return results;
8320
- }
8321
- };
8322
- registerCheck(check31);
8323
-
8324
- // src/checks/asset/image-color-space.ts
8325
- var LABEL = {
8326
- srgb: "sRGB",
8327
- gray: "Gray",
8328
- cmyk: "CMYK"
8329
- };
8330
- var check32 = {
8331
- id: "asset.image.color-space",
8332
- name: "Image Color Space",
8333
- description: "Validates image color spaces against allowed list",
8334
- category: "asset",
8335
- phase: "pre-build",
8336
- async run(ctx) {
8337
- const allowed = ctx.config.validate.assets.allowedColorSpaces;
8338
- if (!allowed || allowed.length === 0)
8339
- return [];
8340
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
8341
- const files = await collectImageFiles(dirs, RASTER_INSPECTABLE_EXTS);
8342
- if (files.length === 0)
8343
- return [];
8344
- const allowedLower = new Set(allowed.map((s) => s.toLowerCase()));
8345
- const results = [];
8346
- for (const file of files) {
8347
- const info3 = await inspectImage(file);
8348
- const cs = info3?.colorSpace;
8349
- if (cs && !allowedLower.has(cs)) {
8350
- results.push({
8351
- checkId: check32.id,
8352
- severity: "warning",
8353
- message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
8354
- file
8355
- });
8356
- }
8357
- }
8358
- return results;
8359
- }
8360
- };
8361
- registerCheck(check32);
8362
8235
 
8363
8236
  // src/checks/asset/image-alpha.ts
8364
- var check33 = {
8237
+ var check30 = {
8365
8238
  id: "asset.image.alpha-channel",
8366
8239
  name: "Image Alpha Channel",
8367
8240
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -8370,8 +8243,9 @@ var check33 = {
8370
8243
  async run(ctx) {
8371
8244
  if (ctx.config.validate.assets.allowAlpha)
8372
8245
  return [];
8373
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
8374
- const files = await collectImageFiles(dirs, ["png", "tiff", "tif"]);
8246
+ if (ctx.config.pdfx.transparency === "preserve-text")
8247
+ return [];
8248
+ const files = shippedImages(ctx, ["png", "tiff", "tif"]);
8375
8249
  if (files.length === 0)
8376
8250
  return [];
8377
8251
  const results = [];
@@ -8379,7 +8253,7 @@ var check33 = {
8379
8253
  const info3 = await inspectImage(file);
8380
8254
  if (info3?.hasAlpha) {
8381
8255
  results.push({
8382
- checkId: check33.id,
8256
+ checkId: check30.id,
8383
8257
  severity: "warning",
8384
8258
  message: "Image contains alpha channel, which may cause print issues",
8385
8259
  file
@@ -8389,68 +8263,10 @@ var check33 = {
8389
8263
  return results;
8390
8264
  }
8391
8265
  };
8392
- registerCheck(check33);
8393
-
8394
- // src/checks/asset/image-tac.ts
8395
- var check34 = {
8396
- id: "asset.image.tac-raster",
8397
- name: "Image TAC (Raster)",
8398
- description: "Rasterizes and checks TAC per image using Ghostscript",
8399
- category: "asset",
8400
- phase: "pre-build",
8401
- requiredTools: ["gs"],
8402
- async run(ctx) {
8403
- const maxTac = ctx.config.ink.maxTac;
8404
- if (!maxTac)
8405
- return [];
8406
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
8407
- const files = await collectImageFiles(dirs, RASTER_INSPECTABLE_EXTS);
8408
- if (files.length === 0)
8409
- return [];
8410
- const results = [];
8411
- const ghostscript = await resolveGhostscript();
8412
- if (!ghostscript) {
8413
- return [
8414
- inspectionFailed(check34.id, "Could not inspect image ink coverage: Ghostscript executable not found")
8415
- ];
8416
- }
8417
- for (const file of files) {
8418
- try {
8419
- const { stdout } = await execCapture(ghostscript, [
8420
- "-q",
8421
- "-dBATCH",
8422
- "-dNOPAUSE",
8423
- "-sDEVICE=inkcov",
8424
- file
8425
- ]);
8426
- const lines = stdout.split(/\r?\n/).filter((l) => l.includes("CMYK"));
8427
- for (const line of lines) {
8428
- const nums = line.trim().split(/\s+/).slice(0, 4).map(Number);
8429
- if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
8430
- const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
8431
- if (tac > maxTac) {
8432
- results.push(finding(check34.id, {
8433
- severity: "warning",
8434
- message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
8435
- file,
8436
- code: "image-tac-exceeded",
8437
- data: { tac, limit: maxTac }
8438
- }));
8439
- break;
8440
- }
8441
- }
8442
- }
8443
- } catch {
8444
- results.push(inspectionFailed(check34.id, `Could not inspect image ink coverage: ${file}`, { file }));
8445
- }
8446
- }
8447
- return results;
8448
- }
8449
- };
8450
- registerCheck(check34);
8266
+ registerCheck(check30);
8451
8267
 
8452
8268
  // src/checks/asset/approved-fonts.ts
8453
- var check35 = {
8269
+ var check31 = {
8454
8270
  id: "asset.font.approved-files",
8455
8271
  name: "Approved Font Files",
8456
8272
  description: "Checks font files against the approved file patterns",
@@ -8489,7 +8305,7 @@ var check35 = {
8489
8305
  for (const font of allFonts) {
8490
8306
  if (!approvedFonts.has(font)) {
8491
8307
  results.push({
8492
- checkId: check35.id,
8308
+ checkId: check31.id,
8493
8309
  severity: "warning",
8494
8310
  message: "Font file not in approved list",
8495
8311
  file: font
@@ -8499,7 +8315,7 @@ var check35 = {
8499
8315
  return results;
8500
8316
  }
8501
8317
  };
8502
- registerCheck(check35);
8318
+ registerCheck(check31);
8503
8319
 
8504
8320
  // src/checks/asset/font-license.ts
8505
8321
  import { existsSync as existsSync10 } from "node:fs";
@@ -8514,7 +8330,7 @@ var LICENSE_NAMES = [
8514
8330
  "OFL-1.1.txt",
8515
8331
  "COPYING"
8516
8332
  ];
8517
- var check36 = {
8333
+ var check32 = {
8518
8334
  id: "asset.font.license",
8519
8335
  name: "Font License",
8520
8336
  description: "Checks for license files in font directories",
@@ -8541,7 +8357,7 @@ var check36 = {
8541
8357
  const hasLicense = LICENSE_NAMES.some((name) => existsSync10(resolve6(fontDir, name)));
8542
8358
  if (!hasLicense) {
8543
8359
  results.push({
8544
- checkId: check36.id,
8360
+ checkId: check32.id,
8545
8361
  severity: "warning",
8546
8362
  message: `No font license file found in directory`,
8547
8363
  file: fontDir
@@ -8551,10 +8367,10 @@ var check36 = {
8551
8367
  return results;
8552
8368
  }
8553
8369
  };
8554
- registerCheck(check36);
8370
+ registerCheck(check32);
8555
8371
 
8556
8372
  // src/checks/heuristic/text-density.ts
8557
- var check37 = {
8373
+ var check33 = {
8558
8374
  id: "heuristic.whitespace.text-density",
8559
8375
  name: "Text Density",
8560
8376
  description: "Checks characters-per-page ratio",
@@ -8583,7 +8399,7 @@ var check37 = {
8583
8399
  });
8584
8400
  if (lowPages.length > 0) {
8585
8401
  results.push({
8586
- checkId: check37.id,
8402
+ checkId: check33.id,
8587
8403
  severity: "info",
8588
8404
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
8589
8405
  file: ctx.pdfPath
@@ -8591,7 +8407,7 @@ var check37 = {
8591
8407
  }
8592
8408
  if (highPages.length > 0) {
8593
8409
  results.push({
8594
- checkId: check37.id,
8410
+ checkId: check33.id,
8595
8411
  severity: "info",
8596
8412
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
8597
8413
  file: ctx.pdfPath
@@ -8600,11 +8416,11 @@ var check37 = {
8600
8416
  return results;
8601
8417
  }
8602
8418
  };
8603
- registerCheck(check37);
8419
+ registerCheck(check33);
8604
8420
 
8605
8421
  // src/checks/heuristic/section-density.ts
8606
8422
  import { readFile as readFile21 } from "node:fs/promises";
8607
- var check38 = {
8423
+ var check34 = {
8608
8424
  id: "heuristic.chunking.section-density",
8609
8425
  name: "Section Density",
8610
8426
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -8630,7 +8446,7 @@ var check38 = {
8630
8446
  const line = lines[i];
8631
8447
  if (/^#{1,6}\s/.test(line)) {
8632
8448
  if (paragraphCount > maxParas) {
8633
- results.push(finding(check38.id, {
8449
+ results.push(finding(check34.id, {
8634
8450
  severity: "info",
8635
8451
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8636
8452
  file,
@@ -8650,7 +8466,7 @@ var check38 = {
8650
8466
  }
8651
8467
  }
8652
8468
  if (paragraphCount > maxParas) {
8653
- results.push(finding(check38.id, {
8469
+ results.push(finding(check34.id, {
8654
8470
  severity: "info",
8655
8471
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8656
8472
  file,
@@ -8658,7 +8474,7 @@ var check38 = {
8658
8474
  }));
8659
8475
  }
8660
8476
  } catch {
8661
- results.push(inspectionFailed(check38.id, `Could not read source file: ${file}`, {
8477
+ results.push(inspectionFailed(check34.id, `Could not read source file: ${file}`, {
8662
8478
  file
8663
8479
  }));
8664
8480
  }
@@ -8666,10 +8482,10 @@ var check38 = {
8666
8482
  return results;
8667
8483
  }
8668
8484
  };
8669
- registerCheck(check38);
8485
+ registerCheck(check34);
8670
8486
 
8671
8487
  // src/checks/heuristic/layer-count.ts
8672
- var check39 = {
8488
+ var check35 = {
8673
8489
  id: "heuristic.decoration.layer-count",
8674
8490
  name: "Layer Count",
8675
8491
  description: "Counts image objects per page",
@@ -8694,7 +8510,7 @@ var check39 = {
8694
8510
  heavyPages.sort((a, b) => a - b);
8695
8511
  return [
8696
8512
  {
8697
- checkId: check39.id,
8513
+ checkId: check35.id,
8698
8514
  severity: "info",
8699
8515
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
8700
8516
  file: ctx.pdfPath
@@ -8704,10 +8520,10 @@ var check39 = {
8704
8520
  return [];
8705
8521
  }
8706
8522
  };
8707
- registerCheck(check39);
8523
+ registerCheck(check35);
8708
8524
 
8709
8525
  // src/checks/heuristic/placement-variance.ts
8710
- var check40 = {
8526
+ var check36 = {
8711
8527
  id: "heuristic.layout.placement-variance",
8712
8528
  name: "Placement Variance",
8713
8529
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -8725,7 +8541,7 @@ var check40 = {
8725
8541
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
8726
8542
  return [
8727
8543
  {
8728
- checkId: check40.id,
8544
+ checkId: check36.id,
8729
8545
  severity: "info",
8730
8546
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
8731
8547
  file: ctx.pdfPath
@@ -8733,21 +8549,21 @@ var check40 = {
8733
8549
  ];
8734
8550
  }
8735
8551
  };
8736
- registerCheck(check40);
8552
+ registerCheck(check36);
8737
8553
 
8738
8554
  // src/checks/tool-check.ts
8739
8555
  async function checkToolAvailability(config, opts = {}) {
8740
8556
  const { checks: checks2 } = selectChecks(opts, config);
8741
8557
  const toolToChecks = new Map;
8742
- for (const check41 of checks2) {
8743
- if (!check41.requiredTools?.length)
8558
+ for (const check37 of checks2) {
8559
+ if (!check37.requiredTools?.length)
8744
8560
  continue;
8745
- for (const tool of check41.requiredTools) {
8561
+ for (const tool of check37.requiredTools) {
8746
8562
  const existing = toolToChecks.get(tool);
8747
8563
  if (existing) {
8748
- existing.push(check41.id);
8564
+ existing.push(check37.id);
8749
8565
  } else {
8750
- toolToChecks.set(tool, [check41.id]);
8566
+ toolToChecks.set(tool, [check37.id]);
8751
8567
  }
8752
8568
  }
8753
8569
  }
@@ -8763,11 +8579,11 @@ async function checkToolAvailability(config, opts = {}) {
8763
8579
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
8764
8580
  const missingSet = new Set(missing);
8765
8581
  const skippedChecks = [];
8766
- for (const check41 of checks2) {
8767
- if (!check41.requiredTools?.length)
8582
+ for (const check37 of checks2) {
8583
+ if (!check37.requiredTools?.length)
8768
8584
  continue;
8769
- if (check41.requiredTools.some((t) => missingSet.has(t))) {
8770
- skippedChecks.push(check41.id);
8585
+ if (check37.requiredTools.some((t) => missingSet.has(t))) {
8586
+ skippedChecks.push(check37.id);
8771
8587
  }
8772
8588
  }
8773
8589
  return { available, missing, skippedChecks, toolToChecks };
@@ -8786,6 +8602,7 @@ function reportMissingTools(result) {
8786
8602
 
8787
8603
  // src/lib/validation-exec.ts
8788
8604
  import { existsSync as existsSync11 } from "node:fs";
8605
+ import { readFile as readFile24 } from "node:fs/promises";
8789
8606
  import { dirname as dirname4, join as join3, resolve as resolve7 } from "node:path";
8790
8607
 
8791
8608
  // src/lib/output-paths.ts
@@ -9265,16 +9082,16 @@ async function runChecks(ctx, opts = {}) {
9265
9082
  }
9266
9083
  const release = retainPdfCache();
9267
9084
  try {
9268
- for (const check41 of checks2) {
9085
+ for (const check37 of checks2) {
9269
9086
  try {
9270
- let results = await check41.run(ctx);
9271
- const severityOverride = getCheckSeverityOverride(check41.id, ctx.config);
9087
+ let results = await check37.run(ctx);
9088
+ const severityOverride = getCheckSeverityOverride(check37.id, ctx.config);
9272
9089
  if (severityOverride) {
9273
9090
  for (const r of results) {
9274
9091
  r.severity = severityOverride;
9275
9092
  }
9276
9093
  }
9277
- const lintLevel = ruleLevel(ctx.config.lint.rules, check41.id);
9094
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check37.id);
9278
9095
  if (lintLevel === "off")
9279
9096
  results = [];
9280
9097
  else if (lintLevel) {
@@ -9282,15 +9099,15 @@ async function runChecks(ctx, opts = {}) {
9282
9099
  r.severity = lintLevel === "warn" ? "warning" : "error";
9283
9100
  }
9284
9101
  if (results.length === 0) {
9285
- passed.push(check41.id);
9102
+ passed.push(check37.id);
9286
9103
  } else {
9287
9104
  allResults.push(...results);
9288
9105
  }
9289
9106
  } catch (err) {
9290
9107
  allResults.push({
9291
- checkId: check41.id,
9108
+ checkId: check37.id,
9292
9109
  severity: "error",
9293
- message: `Check "${check41.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9110
+ message: `Check "${check37.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9294
9111
  });
9295
9112
  }
9296
9113
  }
@@ -9342,6 +9159,31 @@ var SHIPPED_ASSET_EXTS = new Set([
9342
9159
  ".tif",
9343
9160
  ".tiff"
9344
9161
  ]);
9162
+ async function shippedImageFiles(manifestDir, markdownFiles, stylePaths, extensions) {
9163
+ const isImage = (file) => ALL_IMAGE_EXTS.includes(file.slice(file.lastIndexOf(".") + 1).toLowerCase());
9164
+ const files = new Set;
9165
+ try {
9166
+ for (const file of await collectStyleDependencies(manifestDir, stylePaths)) {
9167
+ if (isImage(file))
9168
+ files.add(file);
9169
+ }
9170
+ } catch {}
9171
+ const collect = createRenderedLocalRefCollector(await loadPlugins(extensions, manifestDir, () => {}));
9172
+ for (const file of markdownFiles) {
9173
+ let refs;
9174
+ try {
9175
+ refs = collect(await readFile24(file, "utf8"));
9176
+ } catch {
9177
+ continue;
9178
+ }
9179
+ for (const { ref, kind } of refs) {
9180
+ if (kind !== "image" || isNonFilesystemRef(ref) || proseImageRefError(ref, manifestDir))
9181
+ continue;
9182
+ files.add(resolve7(manifestDir, stripUrlSuffix(decodeRef(ref))));
9183
+ }
9184
+ }
9185
+ return [...files].filter((file) => existsSync11(file)).sort();
9186
+ }
9345
9187
  async function sharedAssetDirs(manifestDir, relStyles, inputDir) {
9346
9188
  let closure;
9347
9189
  try {
@@ -9545,6 +9387,7 @@ async function executeValidation(args) {
9545
9387
  let markdownFiles;
9546
9388
  let cssFiles;
9547
9389
  let assetDirs;
9390
+ let imageFiles;
9548
9391
  let htmlPath;
9549
9392
  const outDir = resolveOutputDir(manifestDir, config.title);
9550
9393
  if (inputDir) {
@@ -9559,6 +9402,7 @@ async function executeValidation(args) {
9559
9402
  }
9560
9403
  cssFiles = [...projectCssFiles, ...pluginStylePaths].filter((f) => !f.endsWith(".min.css"));
9561
9404
  assetDirs = [inputDir, ...await sharedAssetDirs(manifestDir, relStyles, inputDir)];
9405
+ imageFiles = await shippedImageFiles(manifestDir, markdownFiles, [...relStyles, ...pluginStylePaths], config.extensions);
9562
9406
  const possibleHtml = join3(outDir, BOOK_HTML_FILENAME);
9563
9407
  if (existsSync11(possibleHtml)) {
9564
9408
  htmlPath = possibleHtml;
@@ -9572,7 +9416,8 @@ async function executeValidation(args) {
9572
9416
  htmlPath,
9573
9417
  markdownFiles,
9574
9418
  cssFiles,
9575
- assetDirs
9419
+ assetDirs,
9420
+ imageFiles
9576
9421
  };
9577
9422
  const runnerOptions = {
9578
9423
  category: categories,
@@ -9582,7 +9427,7 @@ async function executeValidation(args) {
9582
9427
  };
9583
9428
  if (args.fix) {
9584
9429
  const selected = selectChecks(runnerOptions, config).checks;
9585
- if (selected.some((check41) => check41.id === MARKDOWNLINT_CHECK_ID)) {
9430
+ if (selected.some((check37) => check37.id === MARKDOWNLINT_CHECK_ID)) {
9586
9431
  await applyMarkdownlintFixes(context);
9587
9432
  } else {
9588
9433
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -9651,12 +9496,6 @@ function buildPdfSummaryLines(results) {
9651
9496
  lines.push("Fonts: all embedded");
9652
9497
  }
9653
9498
  }
9654
- const rasterResults = results.filter((r) => r.checkId === "pdf.print.rasterized-pages");
9655
- if (rasterResults.length > 0) {
9656
- const rasterFinding = rasterResults.find((r) => r.code === "rasterized-pages-detected");
9657
- const pages = rasterFinding?.data?.pages;
9658
- lines.push(`Rasterized pages: ${Array.isArray(pages) ? pages.join(", ") : "none"}`);
9659
- }
9660
9499
  return lines;
9661
9500
  }
9662
9501
 
@@ -9669,7 +9508,7 @@ import { randomBytes } from "node:crypto";
9669
9508
 
9670
9509
  // src/lib/build-fingerprint.ts
9671
9510
  import * as fs from "node:fs";
9672
- import { mkdir as mkdir5, readFile as readFile24, writeFile as writeFile4 } from "node:fs/promises";
9511
+ import { mkdir as mkdir5, readFile as readFile25, writeFile as writeFile4 } from "node:fs/promises";
9673
9512
  import { createHash as createHash4 } from "node:crypto";
9674
9513
  import path12 from "node:path";
9675
9514
  import git from "isomorphic-git";
@@ -9756,8 +9595,8 @@ async function getEngineBundleHash() {
9756
9595
  getAssetPath("engine/gutterpress-agent.js")
9757
9596
  ]);
9758
9597
  const [viewerBytes, agentBytes] = await Promise.all([
9759
- readFile24(viewerPath),
9760
- readFile24(agentPath)
9598
+ readFile25(viewerPath),
9599
+ readFile25(agentPath)
9761
9600
  ]);
9762
9601
  const hash = createHash4("sha256");
9763
9602
  hash.update(viewerBytes);
@@ -10131,7 +9970,7 @@ class PdfOutput {
10131
9970
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10132
9971
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10133
9972
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10134
- const { buildNativePdf } = await import("./engine-4rkhv5ga.js");
9973
+ const { buildNativePdf } = await import("./engine-x07q927h.js");
10135
9974
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10136
9975
  title: config.title,
10137
9976
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10331,7 +10170,7 @@ function openPath(filePath) {
10331
10170
  import path16 from "path";
10332
10171
  import { tmpdir } from "os";
10333
10172
  import { randomBytes as randomBytes2 } from "crypto";
10334
- import { readdir as readdir4, readFile as readFile25, writeFile as writeFile5 } from "node:fs/promises";
10173
+ import { readdir as readdir5, readFile as readFile26, writeFile as writeFile5 } from "node:fs/promises";
10335
10174
 
10336
10175
  // src/utils/file-utils.ts
10337
10176
  import { promises as fs3 } from "fs";
@@ -10877,7 +10716,7 @@ function isProcessAlive(pid) {
10877
10716
  async function cleanupOrphanTempDirs() {
10878
10717
  let entries;
10879
10718
  try {
10880
- entries = await readdir4(TEMP_DIR_BASE);
10719
+ entries = await readdir5(TEMP_DIR_BASE);
10881
10720
  } catch {
10882
10721
  return;
10883
10722
  }
@@ -10885,7 +10724,7 @@ async function cleanupOrphanTempDirs() {
10885
10724
  const dir = path16.join(TEMP_DIR_BASE, entry);
10886
10725
  const pidFile = path16.join(dir, PID_FILE_NAME);
10887
10726
  try {
10888
- const raw = await readFile25(pidFile, "utf8");
10727
+ const raw = await readFile26(pidFile, "utf8");
10889
10728
  const pid = parseInt(raw.trim(), 10);
10890
10729
  if (!isProcessAlive(pid)) {
10891
10730
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -10992,7 +10831,7 @@ function createServerState(inputPath, tempDir, config, options) {
10992
10831
  import http from "node:http";
10993
10832
  import net from "node:net";
10994
10833
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
10995
- import { readFile as readFile26, stat as stat4 } from "node:fs/promises";
10834
+ import { readFile as readFile27, stat as stat4 } from "node:fs/promises";
10996
10835
  import path17 from "path";
10997
10836
  import { WebSocket, WebSocketServer } from "ws";
10998
10837
  var HMR_PATH = "/__gutterpress-hmr";
@@ -11061,7 +10900,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
11061
10900
  }
11062
10901
  let data;
11063
10902
  try {
11064
- data = await readFile26(filePath);
10903
+ data = await readFile27(filePath);
11065
10904
  } catch {
11066
10905
  res.writeHead(404);
11067
10906
  res.end("Not Found");
@@ -11110,7 +10949,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11110
10949
  let data;
11111
10950
  if (!validator || validator.stamp !== stamp) {
11112
10951
  try {
11113
- data = await readFile26(absPath);
10952
+ data = await readFile27(absPath);
11114
10953
  } catch {
11115
10954
  await serveStatic(absPath, res);
11116
10955
  return;
@@ -11420,7 +11259,7 @@ async function startPreviewServer(options) {
11420
11259
  }
11421
11260
 
11422
11261
  // src/lib/project-scaffold.ts
11423
- import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile27, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
11262
+ import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir6, readFile as readFile28, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
11424
11263
  import { constants as FS, existsSync as existsSync12 } from "node:fs";
11425
11264
  import path18 from "node:path";
11426
11265
 
@@ -11539,7 +11378,7 @@ async function scaffoldProject(options) {
11539
11378
  try {
11540
11379
  await mkdir7(projectDir, { recursive: true });
11541
11380
  if (customTemplateDir) {
11542
- const entries = await readdir5(customTemplateDir, { withFileTypes: true });
11381
+ const entries = await readdir6(customTemplateDir, { withFileTypes: true });
11543
11382
  for (const entry of entries) {
11544
11383
  if (entry.name === ".gutterpress-template.json")
11545
11384
  continue;
@@ -11621,7 +11460,7 @@ async function scaffoldProject(options) {
11621
11460
  async function firstSourceFile(manifestPath) {
11622
11461
  let text;
11623
11462
  try {
11624
- text = await readFile27(manifestPath, "utf8");
11463
+ text = await readFile28(manifestPath, "utf8");
11625
11464
  } catch {
11626
11465
  return;
11627
11466
  }
@@ -11633,7 +11472,7 @@ async function firstSourceFile(manifestPath) {
11633
11472
  return m ? m[1].trim() : undefined;
11634
11473
  }
11635
11474
  async function fillTemplateFile(filePath, substitutions) {
11636
- let text = await readFile27(filePath, "utf8");
11475
+ let text = await readFile28(filePath, "utf8");
11637
11476
  for (const [token, value] of Object.entries(substitutions)) {
11638
11477
  text = text.split(token).join(value);
11639
11478
  }
@@ -11641,7 +11480,7 @@ async function fillTemplateFile(filePath, substitutions) {
11641
11480
  }
11642
11481
 
11643
11482
  // src/lib/extension-scaffold.ts
11644
- import { access as access2, mkdir as mkdir8, readFile as readFile28, writeFile as writeFile7 } from "node:fs/promises";
11483
+ import { access as access2, mkdir as mkdir8, readFile as readFile29, writeFile as writeFile7 } from "node:fs/promises";
11645
11484
  import { constants as FS2 } from "node:fs";
11646
11485
  import path19 from "node:path";
11647
11486
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -11758,7 +11597,7 @@ async function scaffoldExtension(options) {
11758
11597
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
11759
11598
  const dest = path19.join(extensionDir, relDest);
11760
11599
  await mkdir8(path19.dirname(dest), { recursive: true });
11761
- const text = await readFile28(src, "utf8");
11600
+ const text = await readFile29(src, "utf8");
11762
11601
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
11763
11602
  await writeFile7(dest, substitute(text, table), "utf8");
11764
11603
  written.push(relDest);
@@ -11803,7 +11642,7 @@ var SKIP_ENTRIES = new Set([
11803
11642
 
11804
11643
  // src/lib/extension-import.ts
11805
11644
  import { existsSync as existsSync13 } from "node:fs";
11806
- import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile29, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11645
+ import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile30, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11807
11646
  import { tmpdir as tmpdir2 } from "node:os";
11808
11647
  import path20 from "node:path";
11809
11648
  import { unzipSync } from "fflate";
@@ -11876,7 +11715,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11876
11715
  if (!existsSync13(cssPath)) {
11877
11716
  throw new Error("A theme must contain a theme.css file.");
11878
11717
  }
11879
- const css = await readFile29(cssPath, "utf8");
11718
+ const css = await readFile30(cssPath, "utf8");
11880
11719
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
11881
11720
  if (reject) {
11882
11721
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -11904,7 +11743,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11904
11743
  for (let i = 0;i < extraSheets.length; i++) {
11905
11744
  const rel = extraSheets[i];
11906
11745
  const sheetPath = extraSheetPaths[i];
11907
- const sheetCss = await readFile29(sheetPath, "utf8");
11746
+ const sheetCss = await readFile30(sheetPath, "utf8");
11908
11747
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
11909
11748
  if (sheetReject) {
11910
11749
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -11937,7 +11776,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
11937
11776
  let pkg = {};
11938
11777
  if (metaExists) {
11939
11778
  try {
11940
- const parsed = JSON.parse(await readFile29(file, "utf8"));
11779
+ const parsed = JSON.parse(await readFile30(file, "utf8"));
11941
11780
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
11942
11781
  pkg = parsed;
11943
11782
  }
@@ -12039,11 +11878,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
12039
11878
  async function importExtensionFromFile(projectDir, filePath) {
12040
11879
  const ext = path20.extname(filePath).toLowerCase();
12041
11880
  if (ext === ".zip") {
12042
- const buf = await readFile29(filePath);
11881
+ const buf = await readFile30(filePath);
12043
11882
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
12044
11883
  }
12045
11884
  if (ext === ".css") {
12046
- const css = await readFile29(filePath, "utf8");
11885
+ const css = await readFile30(filePath, "utf8");
12047
11886
  return importExtensionFromCssText(projectDir, css, prettify(path20.basename(filePath, ".css")));
12048
11887
  }
12049
11888
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -12827,7 +12666,7 @@ var drivethrurpgProvider = {
12827
12666
  };
12828
12667
 
12829
12668
  // src/lib/publish/providers/gdrive.ts
12830
- import { mkdtemp as mkdtemp3, readFile as readFile30, readdir as readdir6, rm as rm5, stat as stat6, writeFile as writeFile10 } from "node:fs/promises";
12669
+ import { mkdtemp as mkdtemp3, readFile as readFile31, readdir as readdir7, rm as rm5, stat as stat6, writeFile as writeFile10 } from "node:fs/promises";
12831
12670
  import { tmpdir as tmpdir3 } from "node:os";
12832
12671
  import path22 from "node:path";
12833
12672
  import { Zip, ZipDeflate } from "fflate";
@@ -13212,19 +13051,19 @@ function zipEntriesNonBlocking(entries, opts) {
13212
13051
  });
13213
13052
  }
13214
13053
  async function collectZipEntries(root, dir, out, onWarn) {
13215
- const entries = await readdir6(dir, { withFileTypes: true });
13054
+ const entries = await readdir7(dir, { withFileTypes: true });
13216
13055
  for (const entry of entries) {
13217
13056
  const full = path22.join(dir, entry.name);
13218
13057
  if (entry.isDirectory()) {
13219
13058
  await collectZipEntries(root, full, out, onWarn);
13220
13059
  } else if (entry.isFile()) {
13221
13060
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13222
- out[rel] = await readFile30(full);
13061
+ out[rel] = await readFile31(full);
13223
13062
  } else if (entry.isSymbolicLink()) {
13224
13063
  const resolved = await stat6(full).catch(() => null);
13225
13064
  if (resolved?.isFile()) {
13226
13065
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13227
- out[rel] = await readFile30(full);
13066
+ out[rel] = await readFile31(full);
13228
13067
  } else if (resolved?.isDirectory()) {
13229
13068
  await collectZipEntries(root, full, out, onWarn);
13230
13069
  } else {
@@ -13885,7 +13724,7 @@ function publishProviderFor(id) {
13885
13724
  }
13886
13725
 
13887
13726
  // src/lib/publish/run-publish.ts
13888
- import { readdir as readdir7, stat as stat9 } from "node:fs/promises";
13727
+ import { readdir as readdir8, stat as stat9 } from "node:fs/promises";
13889
13728
  import path25 from "node:path";
13890
13729
  function resolvePublishFormat(info10, providerConfig) {
13891
13730
  if (info10.formats && info10.formats.length > 0) {
@@ -13981,7 +13820,7 @@ async function htmlDirIssues(dir) {
13981
13820
  message: `${dir} has no ${BOOK_HTML} — it isn't an HTML export. ${HTML_HINT}`
13982
13821
  });
13983
13822
  }
13984
- const entries = await readdir7(dir).catch(() => []);
13823
+ const entries = await readdir8(dir).catch(() => []);
13985
13824
  const extras = entries.filter((name) => name.toLowerCase().endsWith(".pdf") || name === BUILD_FINGERPRINT_FILENAME);
13986
13825
  if (extras.length > 0) {
13987
13826
  issues.push({
@@ -14211,7 +14050,7 @@ import git8 from "isomorphic-git";
14211
14050
  // src/lib/remote-auth/scorched-earth.ts
14212
14051
  import git9 from "isomorphic-git";
14213
14052
  // src/lib/publish/selections.ts
14214
- import { chmod as chmod2, mkdir as mkdir13, readFile as readFile31, writeFile as writeFile12 } from "node:fs/promises";
14053
+ import { chmod as chmod2, mkdir as mkdir13, readFile as readFile32, writeFile as writeFile12 } from "node:fs/promises";
14215
14054
  import path26 from "node:path";
14216
14055
  class PublishSelectionsStore {
14217
14056
  filePath;
@@ -14221,7 +14060,7 @@ class PublishSelectionsStore {
14221
14060
  }
14222
14061
  async read() {
14223
14062
  try {
14224
- const raw = await readFile31(this.filePath, "utf8");
14063
+ const raw = await readFile32(this.filePath, "utf8");
14225
14064
  const parsed = JSON.parse(raw);
14226
14065
  if (parsed && typeof parsed === "object") {
14227
14066
  return {