gutterpress 0.11.17-alpha.3 → 0.11.17-alpha.4

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.
@@ -1,3 +1,6 @@
1
+ import {
2
+ blackTextToGray
3
+ } from "./index-ht1bcqdd.js";
1
4
  import {
2
5
  BuildError,
3
6
  EXIT_CODES,
@@ -5671,7 +5674,7 @@ import git from "isomorphic-git";
5671
5674
  // package.json
5672
5675
  var package_default = {
5673
5676
  name: "gutterpress",
5674
- version: "0.11.17-alpha.3",
5677
+ version: "0.11.17-alpha.4",
5675
5678
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5676
5679
  author: "itlackey",
5677
5680
  license: "MPL-2.0",
@@ -5938,417 +5941,6 @@ async function writeBuildFingerprint(input) {
5938
5941
  return outPath;
5939
5942
  }
5940
5943
 
5941
- // src/lib/black-text-gray.ts
5942
- var MAX_CHANNEL = 0.12;
5943
- var MAX_SPREAD = 0.04;
5944
- function isNearBlack([r, g, b]) {
5945
- const max = Math.max(r, g, b);
5946
- return max <= MAX_CHANNEL && max - Math.min(r, g, b) <= MAX_SPREAD;
5947
- }
5948
- var WS = new Set([0, 9, 10, 12, 13, 32]);
5949
- var DELIM = new Set("()<>[]{}/%".split("").map((c) => c.charCodeAt(0)));
5950
- function* tokenize(s) {
5951
- let i = 0;
5952
- const n = s.length;
5953
- while (i < n) {
5954
- const c = s.charCodeAt(i);
5955
- if (WS.has(c)) {
5956
- i++;
5957
- continue;
5958
- }
5959
- const start = i;
5960
- if (c === 37) {
5961
- while (i < n && s[i] !== `
5962
- ` && s[i] !== "\r")
5963
- i++;
5964
- continue;
5965
- }
5966
- if (c === 40) {
5967
- let depth = 0;
5968
- while (i < n) {
5969
- const d = s[i++];
5970
- if (d === "\\")
5971
- i++;
5972
- else if (d === "(")
5973
- depth++;
5974
- else if (d === ")" && --depth === 0)
5975
- break;
5976
- }
5977
- yield { text: s.slice(start, i), start, end: i, op: false };
5978
- continue;
5979
- }
5980
- if (c === 60 && s[i + 1] === "<") {
5981
- i += 2;
5982
- yield { text: "<<", start, end: i, op: false };
5983
- continue;
5984
- }
5985
- if (c === 62 && s[i + 1] === ">") {
5986
- i += 2;
5987
- yield { text: ">>", start, end: i, op: false };
5988
- continue;
5989
- }
5990
- if (c === 60) {
5991
- while (i < n && s[i] !== ">")
5992
- i++;
5993
- i++;
5994
- yield { text: s.slice(start, i), start, end: i, op: false };
5995
- continue;
5996
- }
5997
- if (c === 91 || c === 93 || c === 62 || c === 123 || c === 125 || c === 41) {
5998
- i++;
5999
- yield { text: s[start], start, end: i, op: false };
6000
- continue;
6001
- }
6002
- i++;
6003
- while (i < n && !WS.has(s.charCodeAt(i)) && !DELIM.has(s.charCodeAt(i)))
6004
- i++;
6005
- const text = s.slice(start, i);
6006
- const isOperand = c === 47 || /^[+-]?(\d+\.?\d*|\.\d+)$/.test(text);
6007
- yield { text, start, end: i, op: !isOperand };
6008
- if (text === "ID") {
6009
- const m = /\sEI(?=\s|$)/g;
6010
- m.lastIndex = i;
6011
- const hit = m.exec(s);
6012
- i = hit ? hit.index + hit[0].length : n;
6013
- }
6014
- }
6015
- }
6016
- var LARGE_TEXT_PT = 24;
6017
- var SIZE_EPS = 0.05;
6018
- var area = (m) => Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
6019
-
6020
- class TextScale {
6021
- ctm;
6022
- font = 0;
6023
- tm = 1;
6024
- stack = [];
6025
- constructor(ctm) {
6026
- this.ctm = ctm;
6027
- }
6028
- get unit() {
6029
- return this.ctm;
6030
- }
6031
- get size() {
6032
- return this.font * this.tm * this.ctm;
6033
- }
6034
- op(op, nums) {
6035
- switch (op) {
6036
- case "q":
6037
- this.stack.push([this.ctm, this.font]);
6038
- return false;
6039
- case "Q": {
6040
- const p = this.stack.pop();
6041
- if (p)
6042
- [this.ctm, this.font] = p;
6043
- return true;
6044
- }
6045
- case "cm":
6046
- if (nums.length === 6)
6047
- this.ctm *= area(nums);
6048
- return true;
6049
- case "BT":
6050
- this.tm = 1;
6051
- return true;
6052
- case "Tm":
6053
- if (nums.length === 6)
6054
- this.tm = area(nums);
6055
- return true;
6056
- case "Tf":
6057
- this.font = Math.abs(nums[nums.length - 1] ?? 0) || 0;
6058
- return true;
6059
- }
6060
- return false;
6061
- }
6062
- }
6063
- function rewriteBlackText(src, ctx = {}) {
6064
- let fill = null;
6065
- let stroke = null;
6066
- const stack = [];
6067
- const scale = new TextScale(ctx.baseScale ?? 1);
6068
- let inText = false;
6069
- let fillDirty = false;
6070
- let strokeDirty = false;
6071
- const spans = [];
6072
- let lastOp = "";
6073
- let rect = null;
6074
- const edits = [];
6075
- let operands = [];
6076
- const rgbOf = () => {
6077
- if (operands.length !== 3)
6078
- return null;
6079
- const v = operands.map((t) => Number(t.text));
6080
- return v.some((x) => Number.isNaN(x)) ? null : { rgb: v, raw: operands.map((t) => t.text).join(" ") };
6081
- };
6082
- const isSmall = () => scale.size <= LARGE_TEXT_PT + SIZE_EPS;
6083
- let fillIns = null;
6084
- let strokeIns = null;
6085
- const syncOne = (colour, dirty, ins, at, gray, op) => {
6086
- if (!colour || !isNearBlack(colour.rgb))
6087
- return [dirty, ins];
6088
- const small = isSmall();
6089
- if (small && !dirty) {
6090
- const e = { start: at, end: at, text: ` ${gray}` };
6091
- edits.push(e);
6092
- return [true, e];
6093
- }
6094
- if (!small && dirty) {
6095
- if (ins)
6096
- ins.text = "";
6097
- else
6098
- edits.push({ start: at, end: at, text: ` ${colour.raw} ${op}` });
6099
- return [false, null];
6100
- }
6101
- return [dirty, ins];
6102
- };
6103
- const sync = (at) => {
6104
- [fillDirty, fillIns] = syncOne(fill, fillDirty, fillIns, at, "0 g", "rg");
6105
- [strokeDirty, strokeIns] = syncOne(stroke, strokeDirty, strokeIns, at, "0 G", "RG");
6106
- };
6107
- const flush = (s) => {
6108
- for (const r of s.rects) {
6109
- if (r.thickness > 0.2 * s.maxSmall)
6110
- continue;
6111
- edits.push({ start: r.start, end: r.start, text: "q 0 g " }, { start: r.end, end: r.end, text: " Q" });
6112
- }
6113
- };
6114
- for (const t of tokenize(src)) {
6115
- if (!t.op) {
6116
- operands.push(t);
6117
- continue;
6118
- }
6119
- const op = t.text;
6120
- const nums = operands.map((o) => Number(o.text));
6121
- const sizeMayHaveChanged = scale.op(op, nums);
6122
- switch (op) {
6123
- case "q":
6124
- stack.push([fill, stroke]);
6125
- break;
6126
- case "Q": {
6127
- const p = stack.pop();
6128
- if (p)
6129
- [fill, stroke] = p;
6130
- break;
6131
- }
6132
- case "BT":
6133
- inText = true;
6134
- fillDirty = strokeDirty = false;
6135
- fillIns = strokeIns = null;
6136
- sync(t.end);
6137
- break;
6138
- case "Tf":
6139
- case "Tm":
6140
- if (inText && sizeMayHaveChanged)
6141
- sync(t.end);
6142
- break;
6143
- case "ET":
6144
- if (inText) {
6145
- let ins = "";
6146
- if (fillDirty && fill)
6147
- ins += `${fill.raw} rg `;
6148
- if (strokeDirty && stroke)
6149
- ins += `${stroke.raw} RG `;
6150
- if (ins)
6151
- edits.push({ start: t.start, end: t.start, text: ins });
6152
- }
6153
- inText = false;
6154
- fillIns = strokeIns = null;
6155
- break;
6156
- case "rg":
6157
- case "RG": {
6158
- const c = rgbOf();
6159
- const isFill = op === "rg";
6160
- if (isFill)
6161
- fill = c;
6162
- else
6163
- stroke = c;
6164
- if (inText && c && isNearBlack(c.rgb) && isSmall()) {
6165
- edits.push({ start: operands[0].start, end: t.end, text: isFill ? "0 g" : "0 G" });
6166
- if (isFill)
6167
- fillDirty = true;
6168
- else
6169
- strokeDirty = true;
6170
- } else if (inText) {
6171
- if (isFill)
6172
- fillDirty = false;
6173
- else
6174
- strokeDirty = false;
6175
- }
6176
- break;
6177
- }
6178
- case "g":
6179
- case "k":
6180
- case "sc":
6181
- case "scn":
6182
- case "cs":
6183
- fill = null;
6184
- if (inText)
6185
- fillDirty = false;
6186
- break;
6187
- case "G":
6188
- case "K":
6189
- case "SC":
6190
- case "SCN":
6191
- case "CS":
6192
- stroke = null;
6193
- if (inText)
6194
- strokeDirty = false;
6195
- break;
6196
- case "Do": {
6197
- const name = operands[operands.length - 1]?.text;
6198
- if (name?.startsWith("/"))
6199
- ctx.onDo?.(name.slice(1), scale.unit);
6200
- break;
6201
- }
6202
- case "BDC":
6203
- case "BMC":
6204
- spans.push({ hasSmall: false, maxSmall: 0, rects: [] });
6205
- break;
6206
- case "EMC": {
6207
- const s = spans.pop();
6208
- if (!s)
6209
- break;
6210
- const parent = spans[spans.length - 1];
6211
- if (s.hasSmall)
6212
- flush(s);
6213
- else
6214
- parent?.rects.push(...s.rects);
6215
- if (parent && s.hasSmall) {
6216
- parent.hasSmall = true;
6217
- parent.maxSmall = Math.max(parent.maxSmall, s.maxSmall);
6218
- }
6219
- break;
6220
- }
6221
- case "Tj":
6222
- case "TJ":
6223
- case "'":
6224
- case '"': {
6225
- fillIns = strokeIns = null;
6226
- const span = spans[spans.length - 1];
6227
- if (span && inText && isSmall()) {
6228
- span.hasSmall = true;
6229
- span.maxSmall = Math.max(span.maxSmall, scale.size);
6230
- }
6231
- break;
6232
- }
6233
- case "re":
6234
- rect = !inText && fill && isNearBlack(fill.rgb) && operands.length === 4 && nums.every((x) => !Number.isNaN(x)) ? { start: operands[0].start, thickness: Math.min(Math.abs(nums[2]), Math.abs(nums[3])) * scale.unit } : null;
6235
- break;
6236
- case "f":
6237
- case "F":
6238
- case "f*":
6239
- if (rect && lastOp === "re")
6240
- spans[spans.length - 1]?.rects.push({ ...rect, end: t.end });
6241
- break;
6242
- }
6243
- lastOp = op;
6244
- operands = [];
6245
- }
6246
- return applyEdits(src, edits);
6247
- }
6248
- function applyEdits(s, edits) {
6249
- if (!edits.length)
6250
- return s;
6251
- edits.sort((a, b) => a.start - b.start);
6252
- let out = "";
6253
- let pos = 0;
6254
- for (const e of edits) {
6255
- out += s.slice(pos, e.start) + e.text;
6256
- pos = e.end;
6257
- }
6258
- return out + s.slice(pos);
6259
- }
6260
- async function visitContent(doc, visit) {
6261
- const { PDFName, PDFRawStream, PDFArray, PDFRef, PDFDict, decodePDFRawStream } = await import("pdf-lib");
6262
- const ctx = doc.context;
6263
- const SKIP = ["Filter", "DecodeParms", "Length"].map((k) => PDFName.of(k));
6264
- let changed = false;
6265
- const decode = (s) => {
6266
- try {
6267
- return Buffer.from(decodePDFRawStream(s).decode()).toString("latin1");
6268
- } catch {
6269
- return null;
6270
- }
6271
- };
6272
- const replace = (ref, old, text) => {
6273
- const entries = {};
6274
- for (const [k, v] of old.dict.entries())
6275
- if (!SKIP.includes(k))
6276
- entries[k.decodeText()] = v;
6277
- ctx.assign(ref, ctx.flateStream(Buffer.from(text, "latin1"), entries));
6278
- changed = true;
6279
- };
6280
- const formScale = new Map;
6281
- const formMatrixScale = (form) => {
6282
- const m = form.dict.lookupMaybe(PDFName.of("Matrix"), PDFArray);
6283
- const nums = m?.asArray().map((n) => Number(n.toString()));
6284
- return nums?.length === 6 && !nums.some(Number.isNaN) ? area(nums) : 1;
6285
- };
6286
- const pages = doc.getPages();
6287
- for (let p = 0;p < pages.length; p++) {
6288
- const page = pages[p];
6289
- const contents = page.node.Contents();
6290
- const refs = [];
6291
- if (contents instanceof PDFArray) {
6292
- for (let i = 0;i < contents.size(); i++) {
6293
- const r = contents.get(i);
6294
- if (r instanceof PDFRef)
6295
- refs.push(r);
6296
- }
6297
- } else if (contents instanceof PDFRawStream) {
6298
- const r = page.node.get(PDFName.of("Contents"));
6299
- if (r instanceof PDFRef)
6300
- refs.push(r);
6301
- }
6302
- const streams = refs.map((r) => ctx.lookup(r));
6303
- if (!refs.length || !streams.every((s) => s instanceof PDFRawStream))
6304
- continue;
6305
- const parts = streams.map((s) => decode(s));
6306
- if (parts.some((q) => q === null))
6307
- continue;
6308
- const joined = parts.join(`
6309
- `);
6310
- const xobjects = page.node.Resources()?.lookupMaybe(PDFName.of("XObject"), PDFDict);
6311
- const next = visit(joined, {
6312
- baseScale: 1,
6313
- onDo: (name, scale) => {
6314
- const ref = xobjects?.get(PDFName.of(name));
6315
- const form = ref instanceof PDFRef ? ctx.lookup(ref) : undefined;
6316
- if (!(ref instanceof PDFRef) || !(form instanceof PDFRawStream))
6317
- return;
6318
- if (form.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
6319
- return;
6320
- const at = scale * formMatrixScale(form);
6321
- formScale.set(ref.tag, Math.min(formScale.get(ref.tag) ?? Infinity, at));
6322
- }
6323
- });
6324
- if (next === joined)
6325
- continue;
6326
- replace(refs[0], streams[0], next);
6327
- page.node.set(PDFName.of("Contents"), refs[0]);
6328
- }
6329
- for (const [ref, obj] of ctx.enumerateIndirectObjects()) {
6330
- if (!(obj instanceof PDFRawStream))
6331
- continue;
6332
- if (obj.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
6333
- continue;
6334
- const text = decode(obj);
6335
- if (text === null)
6336
- continue;
6337
- const next = visit(text, {
6338
- baseScale: formScale.get(ref.tag) ?? 0
6339
- });
6340
- if (next !== text)
6341
- replace(ref, obj, next);
6342
- }
6343
- return changed;
6344
- }
6345
- async function blackTextToGray(pdf) {
6346
- const { PDFDocument } = await import("pdf-lib");
6347
- const doc = await PDFDocument.load(pdf, { updateMetadata: false });
6348
- const changed = await visitContent(doc, (src, info2) => rewriteBlackText(src, info2));
6349
- return changed ? doc.save({ updateFieldAppearances: false }) : pdf;
6350
- }
6351
-
6352
5944
  // src/lib/validation-exec.ts
6353
5945
  import { existsSync as existsSync12 } from "node:fs";
6354
5946
  import { dirname as dirname4, join as join4, resolve as resolve8 } from "node:path";
@@ -7482,8 +7074,46 @@ var check8 = {
7482
7074
  };
7483
7075
  registerCheck(check8);
7484
7076
 
7485
- // src/checks/pdf/signature.ts
7077
+ // src/checks/pdf/black-text.ts
7078
+ import { readFile as readFile15 } from "node:fs/promises";
7486
7079
  var check9 = {
7080
+ id: "pdf.print.black-text",
7081
+ name: "Black Text",
7082
+ 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)",
7083
+ category: "pdf",
7084
+ phase: "post-build",
7085
+ enabledWhen: (config) => config.pdfx.blackText === "k-only",
7086
+ async run(ctx) {
7087
+ if (!ctx.pdfPath)
7088
+ return [];
7089
+ try {
7090
+ const { findRichBlackText, LARGE_TEXT_PT } = await import("./black-text-gray-7dz61456.js");
7091
+ const found = await findRichBlackText(await readFile15(ctx.pdfPath));
7092
+ if (found.runs === 0)
7093
+ return [];
7094
+ return [
7095
+ finding(check9.id, {
7096
+ severity: "warning",
7097
+ message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
7098
+ file: ctx.pdfPath,
7099
+ detail: "Small text printed with all four inks can look fuzzy if the plates are slightly out of register, which is why print vendors such as DriveThruRPG want small black text on the black plate alone. `pdfx.blackText: k-only` already does that for near-black text; this text is either a dark colour (for example a dark brown, which keeps its colour) or painted in a way the build cannot rewrite (an image or a pattern). To print it on the black plate, make the text black (#000). If the colour is intended, set `lint.rules: { pdf.print.black-text: off }` in manifest.yaml.",
7100
+ code: "rich-black-text",
7101
+ data: { runs: found.runs, pages: found.pages }
7102
+ })
7103
+ ];
7104
+ } catch {
7105
+ return [
7106
+ inspectionFailed(check9.id, "Could not inspect the PDF's text colours.", {
7107
+ file: ctx.pdfPath
7108
+ })
7109
+ ];
7110
+ }
7111
+ }
7112
+ };
7113
+ registerCheck(check9);
7114
+
7115
+ // src/checks/pdf/signature.ts
7116
+ var check10 = {
7487
7117
  id: "pdf.print.signature",
7488
7118
  name: "Signature",
7489
7119
  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)",
@@ -7495,7 +7125,7 @@ var check9 = {
7495
7125
  return [];
7496
7126
  const doc = await loadPdf(ctx.pdfPath);
7497
7127
  if (!doc) {
7498
- return [inspectionFailed(check9.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
7128
+ return [inspectionFailed(check10.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
7499
7129
  }
7500
7130
  const size = ctx.config.print.signature;
7501
7131
  const pages = doc.numPages;
@@ -7504,7 +7134,7 @@ var check9 = {
7504
7134
  const data = { pages, signature: size };
7505
7135
  if (pages % size !== 0) {
7506
7136
  return [
7507
- finding(check9.id, {
7137
+ finding(check10.id, {
7508
7138
  severity: "error",
7509
7139
  message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
7510
7140
  file: ctx.pdfPath,
@@ -7520,10 +7150,10 @@ var check9 = {
7520
7150
  if (!await pageHasMarks(doc, pages))
7521
7151
  return [];
7522
7152
  } catch {
7523
- return [inspectionFailed(check9.id, "Could not read the last page.", { file: ctx.pdfPath })];
7153
+ return [inspectionFailed(check10.id, "Could not read the last page.", { file: ctx.pdfPath })];
7524
7154
  }
7525
7155
  return [
7526
- finding(check9.id, {
7156
+ finding(check10.id, {
7527
7157
  severity: "error",
7528
7158
  message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
7529
7159
  file: ctx.pdfPath,
@@ -7534,10 +7164,10 @@ var check9 = {
7534
7164
  ];
7535
7165
  }
7536
7166
  };
7537
- registerCheck(check9);
7167
+ registerCheck(check10);
7538
7168
 
7539
7169
  // src/checks/pdf/bookmarks.ts
7540
- var check10 = {
7170
+ var check11 = {
7541
7171
  id: "pdf.nav.bookmarks",
7542
7172
  name: "Bookmarks",
7543
7173
  description: "Checks for PDF outline (bookmarks) tree",
@@ -7551,14 +7181,14 @@ var check10 = {
7551
7181
  const doc = await loadPdf(ctx.pdfPath);
7552
7182
  if (!doc) {
7553
7183
  return [
7554
- inspectionFailed(check10.id, "Could not inspect PDF for bookmarks.", {
7184
+ inspectionFailed(check11.id, "Could not inspect PDF for bookmarks.", {
7555
7185
  file: ctx.pdfPath
7556
7186
  })
7557
7187
  ];
7558
7188
  }
7559
7189
  if (await getOutlineCount(doc) === 0) {
7560
7190
  return [
7561
- finding(check10.id, {
7191
+ finding(check11.id, {
7562
7192
  severity: "warning",
7563
7193
  message: "PDF does not contain bookmarks (outline tree).",
7564
7194
  file: ctx.pdfPath
@@ -7568,10 +7198,10 @@ var check10 = {
7568
7198
  return [];
7569
7199
  }
7570
7200
  };
7571
- registerCheck(check10);
7201
+ registerCheck(check11);
7572
7202
 
7573
7203
  // src/checks/pdf/toc-links.ts
7574
- var check11 = {
7204
+ var check12 = {
7575
7205
  id: "pdf.nav.toc-links",
7576
7206
  name: "TOC Links",
7577
7207
  description: "Detects link annotations on TOC pages",
@@ -7586,7 +7216,7 @@ var check11 = {
7586
7216
  if (!doc) {
7587
7217
  return [
7588
7218
  {
7589
- checkId: check11.id,
7219
+ checkId: check12.id,
7590
7220
  severity: "warning",
7591
7221
  message: "Could not inspect PDF for TOC links.",
7592
7222
  file: ctx.pdfPath
@@ -7597,7 +7227,7 @@ var check11 = {
7597
7227
  if (!hasLinks) {
7598
7228
  return [
7599
7229
  {
7600
- checkId: check11.id,
7230
+ checkId: check12.id,
7601
7231
  severity: "warning",
7602
7232
  message: "No link annotations found in PDF (expected for TOC).",
7603
7233
  file: ctx.pdfPath
@@ -7607,10 +7237,10 @@ var check11 = {
7607
7237
  return [];
7608
7238
  }
7609
7239
  };
7610
- registerCheck(check11);
7240
+ registerCheck(check12);
7611
7241
 
7612
7242
  // src/checks/pdf/cross-refs.ts
7613
- var check12 = {
7243
+ var check13 = {
7614
7244
  id: "pdf.nav.cross-refs",
7615
7245
  name: "Cross References",
7616
7246
  description: "Counts and verifies internal link annotations",
@@ -7623,7 +7253,7 @@ var check12 = {
7623
7253
  if (!doc) {
7624
7254
  return [
7625
7255
  {
7626
- checkId: check12.id,
7256
+ checkId: check13.id,
7627
7257
  severity: "info",
7628
7258
  message: "Could not inspect PDF for cross-references.",
7629
7259
  file: ctx.pdfPath
@@ -7633,7 +7263,7 @@ var check12 = {
7633
7263
  const linkCount = await countLinkAnnotations(doc);
7634
7264
  return [
7635
7265
  {
7636
- checkId: check12.id,
7266
+ checkId: check13.id,
7637
7267
  severity: "info",
7638
7268
  message: `PDF contains ${linkCount} internal link annotation${linkCount !== 1 ? "s" : ""}.`,
7639
7269
  file: ctx.pdfPath
@@ -7641,10 +7271,10 @@ var check12 = {
7641
7271
  ];
7642
7272
  }
7643
7273
  };
7644
- registerCheck(check12);
7274
+ registerCheck(check13);
7645
7275
 
7646
7276
  // src/checks/pdf/page-labels.ts
7647
- var check13 = {
7277
+ var check14 = {
7648
7278
  id: "pdf.nav.page-labels",
7649
7279
  name: "Page Labels",
7650
7280
  description: "Verifies PDF page labels / folio numbering",
@@ -7659,7 +7289,7 @@ var check13 = {
7659
7289
  const labels = await getPageLabels(doc);
7660
7290
  return [
7661
7291
  {
7662
- checkId: check13.id,
7292
+ checkId: check14.id,
7663
7293
  severity: "info",
7664
7294
  message: labels && labels.length > 0 ? "PDF contains page labels." : "PDF does not contain explicit page labels.",
7665
7295
  file: ctx.pdfPath
@@ -7667,10 +7297,10 @@ var check13 = {
7667
7297
  ];
7668
7298
  }
7669
7299
  };
7670
- registerCheck(check13);
7300
+ registerCheck(check14);
7671
7301
 
7672
7302
  // src/checks/pdf/image-resolution.ts
7673
- var check14 = {
7303
+ var check15 = {
7674
7304
  id: "pdf.print.image-resolution",
7675
7305
  name: "Image Resolution",
7676
7306
  description: "Checks DPI of images embedded in the PDF",
@@ -7697,7 +7327,7 @@ var check14 = {
7697
7327
  lowResPages.sort((a, b) => a - b);
7698
7328
  return [
7699
7329
  {
7700
- checkId: check14.id,
7330
+ checkId: check15.id,
7701
7331
  severity: "warning",
7702
7332
  message: `Low-resolution images (below ${minDpi} DPI) found on pages: ${lowResPages.join(", ")}`,
7703
7333
  file: ctx.pdfPath
@@ -7707,10 +7337,10 @@ var check14 = {
7707
7337
  return [];
7708
7338
  }
7709
7339
  };
7710
- registerCheck(check14);
7340
+ registerCheck(check15);
7711
7341
 
7712
7342
  // src/checks/pdf/transparency.ts
7713
- var check15 = {
7343
+ var check16 = {
7714
7344
  id: "pdf.print.transparency",
7715
7345
  name: "Transparency",
7716
7346
  description: "Checks for transparency markers (/Transparency, /SMask, /BM) in the PDF",
@@ -7732,7 +7362,7 @@ var check15 = {
7732
7362
  if (found.length > 0) {
7733
7363
  return [
7734
7364
  {
7735
- checkId: check15.id,
7365
+ checkId: check16.id,
7736
7366
  severity: "warning",
7737
7367
  message: `Transparency detected in PDF: ${found.join(", ")}. This may cause issues with some printers.`,
7738
7368
  file: ctx.pdfPath
@@ -7742,7 +7372,7 @@ var check15 = {
7742
7372
  return [];
7743
7373
  }
7744
7374
  };
7745
- registerCheck(check15);
7375
+ registerCheck(check16);
7746
7376
 
7747
7377
  // src/checks/pdf/bleed.ts
7748
7378
  function findBox(bytes, box) {
@@ -7752,7 +7382,7 @@ function findBox(bytes, box) {
7752
7382
  return null;
7753
7383
  return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
7754
7384
  }
7755
- var check16 = {
7385
+ var check17 = {
7756
7386
  id: "pdf.print.bleed",
7757
7387
  name: "Bleed Box",
7758
7388
  description: "Compares MediaBox vs TrimBox/BleedBox to verify bleed area",
@@ -7766,7 +7396,7 @@ var check16 = {
7766
7396
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
7767
7397
  if (!bytes) {
7768
7398
  return [
7769
- inspectionFailed(check16.id, "Could not inspect PDF for bleed boxes.", {
7399
+ inspectionFailed(check17.id, "Could not inspect PDF for bleed boxes.", {
7770
7400
  file: ctx.pdfPath
7771
7401
  })
7772
7402
  ];
@@ -7776,14 +7406,14 @@ var check16 = {
7776
7406
  const bleed = findBox(bytes, "BleedBox");
7777
7407
  if (!media) {
7778
7408
  return [
7779
- inspectionFailed(check16.id, "Could not parse MediaBox from PDF.", {
7409
+ inspectionFailed(check17.id, "Could not parse MediaBox from PDF.", {
7780
7410
  file: ctx.pdfPath
7781
7411
  })
7782
7412
  ];
7783
7413
  }
7784
7414
  if (!trim && !bleed) {
7785
7415
  return [
7786
- finding(check16.id, {
7416
+ finding(check17.id, {
7787
7417
  severity: "warning",
7788
7418
  message: "No TrimBox or BleedBox found. Bleed area cannot be verified.",
7789
7419
  file: ctx.pdfPath
@@ -7796,7 +7426,7 @@ var check16 = {
7796
7426
  const bleedSize = ctx.config.validate.pdf.bleedSize;
7797
7427
  if (mediaW - trimW < bleedSize * 2 * 0.9) {
7798
7428
  return [
7799
- finding(check16.id, {
7429
+ finding(check17.id, {
7800
7430
  severity: "warning",
7801
7431
  message: `Bleed area appears insufficient. Expected at least ${bleedSize}pt on each side.`,
7802
7432
  file: ctx.pdfPath
@@ -7807,10 +7437,10 @@ var check16 = {
7807
7437
  return [];
7808
7438
  }
7809
7439
  };
7810
- registerCheck(check16);
7440
+ registerCheck(check17);
7811
7441
 
7812
7442
  // src/checks/pdf/pdfx-metadata.ts
7813
- var check17 = {
7443
+ var check18 = {
7814
7444
  id: "pdf.print.pdfx-metadata",
7815
7445
  name: "PDF/X Metadata",
7816
7446
  description: "Verifies PDF/X DOCINFO metadata via qpdf JSON",
@@ -7829,30 +7459,30 @@ var check17 = {
7829
7459
  const objects = parseQpdfObjectsJson(stdout);
7830
7460
  if (!objects) {
7831
7461
  return [
7832
- inspectionFailed(check17.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
7462
+ inspectionFailed(check18.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
7833
7463
  ];
7834
7464
  }
7835
- return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check17.id, {
7465
+ return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check18.id, {
7836
7466
  severity: "error",
7837
7467
  message,
7838
7468
  file: ctx.pdfPath
7839
7469
  }));
7840
7470
  } catch {
7841
7471
  return [
7842
- inspectionFailed(check17.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
7472
+ inspectionFailed(check18.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
7843
7473
  ];
7844
7474
  }
7845
7475
  }
7846
7476
  };
7847
- registerCheck(check17);
7477
+ registerCheck(check18);
7848
7478
 
7849
7479
  // src/checks/source/htmlhint.ts
7850
7480
  import { existsSync as existsSync9 } from "node:fs";
7851
- import { readFile as readFile15 } from "node:fs/promises";
7481
+ import { readFile as readFile16 } from "node:fs/promises";
7852
7482
  import htmlhint from "htmlhint";
7853
7483
  var { HTMLHint } = htmlhint;
7854
7484
  var CONFIG_NAMES2 = [".htmlhintrc"];
7855
- var check18 = {
7485
+ var check19 = {
7856
7486
  id: "source.htmlhint",
7857
7487
  name: "HTMLHint",
7858
7488
  description: "Runs htmlhint with project config to validate generated HTML",
@@ -7868,11 +7498,11 @@ var check18 = {
7868
7498
  const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
7869
7499
  if (!resolvedConfig && !configPath)
7870
7500
  return [];
7871
- const ruleset = resolvedConfig ? JSON.parse(await readFile15(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
7872
- const html = await readFile15(ctx.htmlPath, "utf8");
7501
+ const ruleset = resolvedConfig ? JSON.parse(await readFile16(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
7502
+ const html = await readFile16(ctx.htmlPath, "utf8");
7873
7503
  const messages = HTMLHint.verify(html, ruleset);
7874
7504
  return messages.map((m) => ({
7875
- checkId: check18.id,
7505
+ checkId: check19.id,
7876
7506
  severity: m.type === "error" ? "error" : "warning",
7877
7507
  message: `${m.rule.id}: ${m.message}`,
7878
7508
  file: ctx.htmlPath,
@@ -7881,10 +7511,10 @@ var check18 = {
7881
7511
  }));
7882
7512
  }
7883
7513
  };
7884
- registerCheck(check18);
7514
+ registerCheck(check19);
7885
7515
 
7886
7516
  // src/checks/source/stylelint.ts
7887
- import { readFile as readFile16 } from "node:fs/promises";
7517
+ import { readFile as readFile17 } from "node:fs/promises";
7888
7518
 
7889
7519
  // src/lib/printsafe.ts
7890
7520
  import postcss2 from "postcss";
@@ -8201,7 +7831,7 @@ function checkCssRaw(css, from) {
8201
7831
  }
8202
7832
 
8203
7833
  // src/checks/source/stylelint.ts
8204
- var check19 = {
7834
+ var check20 = {
8205
7835
  id: "source.stylelint",
8206
7836
  name: "Print-safety CSS",
8207
7837
  description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
@@ -8218,10 +7848,10 @@ var check19 = {
8218
7848
  for (const file of files) {
8219
7849
  let css;
8220
7850
  try {
8221
- css = await readFile16(file, "utf8");
7851
+ css = await readFile17(file, "utf8");
8222
7852
  } catch (err) {
8223
7853
  results.push({
8224
- checkId: check19.id,
7854
+ checkId: check20.id,
8225
7855
  severity: "error",
8226
7856
  message: `Cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`,
8227
7857
  file
@@ -8230,7 +7860,7 @@ var check19 = {
8230
7860
  }
8231
7861
  for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
8232
7862
  results.push({
8233
- checkId: check19.id,
7863
+ checkId: check20.id,
8234
7864
  severity: w.severity,
8235
7865
  message: `${w.rule}: ${w.message}`,
8236
7866
  file,
@@ -8242,15 +7872,15 @@ var check19 = {
8242
7872
  return results;
8243
7873
  }
8244
7874
  };
8245
- registerCheck(check19);
7875
+ registerCheck(check20);
8246
7876
 
8247
7877
  // src/checks/source/css-ownership.ts
8248
- import { readFile as readFile17 } from "node:fs/promises";
7878
+ import { readFile as readFile18 } from "node:fs/promises";
8249
7879
  import postcss3 from "postcss";
8250
7880
  import { parse as parseYaml3 } from "yaml";
8251
7881
  var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
8252
7882
  async function loadContract(configPath) {
8253
- const raw = await readFile17(configPath, "utf8");
7883
+ const raw = await readFile18(configPath, "utf8");
8254
7884
  const parsed = parseYaml3(raw);
8255
7885
  if (parsed && typeof parsed === "object")
8256
7886
  return parsed;
@@ -8363,7 +7993,7 @@ function checkOwnership(root, file, maps) {
8363
7993
  });
8364
7994
  return out;
8365
7995
  }
8366
- var check20 = {
7996
+ var check21 = {
8367
7997
  id: "source.css-ownership",
8368
7998
  name: "CSS ownership contract",
8369
7999
  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.",
@@ -8386,7 +8016,7 @@ var check20 = {
8386
8016
  } catch (err) {
8387
8017
  return [
8388
8018
  {
8389
- checkId: check20.id,
8019
+ checkId: check21.id,
8390
8020
  severity: "error",
8391
8021
  message: `Failed to parse CSS ownership contract "${configPath}": ${err instanceof Error ? err.message : String(err)}`,
8392
8022
  file: configPath
@@ -8401,7 +8031,7 @@ var check20 = {
8401
8031
  continue;
8402
8032
  let css;
8403
8033
  try {
8404
- css = await readFile17(absFile, "utf8");
8034
+ css = await readFile18(absFile, "utf8");
8405
8035
  } catch {
8406
8036
  continue;
8407
8037
  }
@@ -8428,7 +8058,7 @@ var check20 = {
8428
8058
  }
8429
8059
  for (const v of violations) {
8430
8060
  results.push({
8431
- checkId: check20.id,
8061
+ checkId: check21.id,
8432
8062
  severity: "warning",
8433
8063
  message: v.message,
8434
8064
  file: absFile,
@@ -8440,14 +8070,14 @@ var check20 = {
8440
8070
  return results;
8441
8071
  }
8442
8072
  };
8443
- registerCheck(check20);
8073
+ registerCheck(check21);
8444
8074
 
8445
8075
  // src/checks/source/local-refs.ts
8446
8076
  import { existsSync as existsSync10 } from "node:fs";
8447
8077
  import { dirname as dirname3, resolve as resolve6 } from "node:path";
8448
8078
 
8449
8079
  // src/checks/source/local-ref-parser.ts
8450
- import { readFile as readFile18 } from "node:fs/promises";
8080
+ import { readFile as readFile19 } from "node:fs/promises";
8451
8081
  function newlineOffsets(content) {
8452
8082
  const offsets = [];
8453
8083
  for (let i = 0;i < content.length; i++)
@@ -8516,7 +8146,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
8516
8146
  for (const file of files) {
8517
8147
  let refs;
8518
8148
  try {
8519
- refs = collectRenderedLocalRefs(await readFile18(file, "utf8"));
8149
+ refs = collectRenderedLocalRefs(await readFile19(file, "utf8"));
8520
8150
  } catch {
8521
8151
  results.push(inspectionFailed(checkId, `Could not read source file: ${file}`, { file }));
8522
8152
  continue;
@@ -8527,7 +8157,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
8527
8157
  }
8528
8158
 
8529
8159
  // src/checks/source/local-refs.ts
8530
- var check21 = {
8160
+ var check22 = {
8531
8161
  id: "source.links.local-refs",
8532
8162
  name: "Local Markdown References",
8533
8163
  description: "Checks local markdown links and image refs resolve on disk",
@@ -8535,12 +8165,12 @@ var check21 = {
8535
8165
  phase: "pre-build",
8536
8166
  async run(ctx) {
8537
8167
  const results = [];
8538
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check21.id, results)) {
8168
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check22.id, results)) {
8539
8169
  if (isNonFilesystemRef(ref))
8540
8170
  continue;
8541
8171
  const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
8542
8172
  if (escape) {
8543
- results.push(finding(check21.id, {
8173
+ results.push(finding(check22.id, {
8544
8174
  severity: "error",
8545
8175
  message: escape,
8546
8176
  file,
@@ -8550,7 +8180,7 @@ var check21 = {
8550
8180
  }
8551
8181
  if (localRefExists(ref, kind, file, ctx.inputDir))
8552
8182
  continue;
8553
- results.push(finding(check21.id, {
8183
+ results.push(finding(check22.id, {
8554
8184
  severity: kind === "image" ? "warning" : "error",
8555
8185
  code: kind === "image" ? "missing-image-placeholder" : undefined,
8556
8186
  message: kind === "image" ? `Local image not found; build will substitute a magenta placeholder: ${ref}` : `Local reference not found: ${ref}`,
@@ -8575,7 +8205,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
8575
8205
  return existsSync10(fileRelative);
8576
8206
  }
8577
8207
  }
8578
- registerCheck(check21);
8208
+ registerCheck(check22);
8579
8209
 
8580
8210
  // src/lib/build-staging.ts
8581
8211
  import path11 from "node:path";
@@ -8841,7 +8471,7 @@ async function createStageRoot() {
8841
8471
  }
8842
8472
 
8843
8473
  // src/checks/source/dangling-links.ts
8844
- var check22 = {
8474
+ var check23 = {
8845
8475
  id: "source.links.dangling",
8846
8476
  name: "Dangling Links",
8847
8477
  description: "Flags relative links the printed book cannot open (their href is dropped at print time)",
@@ -8849,10 +8479,10 @@ var check22 = {
8849
8479
  phase: "pre-build",
8850
8480
  async run(ctx) {
8851
8481
  const results = [];
8852
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check22.id, results)) {
8482
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
8853
8483
  if (kind !== "link" || isPrintResolvableHref(ref))
8854
8484
  continue;
8855
- results.push(finding(check22.id, {
8485
+ results.push(finding(check23.id, {
8856
8486
  severity: "warning",
8857
8487
  code: "dangling-link",
8858
8488
  data: { ref },
@@ -8864,11 +8494,11 @@ var check22 = {
8864
8494
  return results;
8865
8495
  }
8866
8496
  };
8867
- registerCheck(check22);
8497
+ registerCheck(check23);
8868
8498
 
8869
8499
  // src/checks/source/accessibility-alt-text.ts
8870
- import { readFile as readFile19 } from "node:fs/promises";
8871
- var check23 = {
8500
+ import { readFile as readFile20 } from "node:fs/promises";
8501
+ var check24 = {
8872
8502
  id: "source.accessibility.alt-text",
8873
8503
  name: "Image Alt Text",
8874
8504
  description: "Checks markdown images include non-empty alt text",
@@ -8881,7 +8511,7 @@ var check23 = {
8881
8511
  const results = [];
8882
8512
  for (const file of files) {
8883
8513
  try {
8884
- const content = await readFile19(file, "utf8");
8514
+ const content = await readFile20(file, "utf8");
8885
8515
  const lines = content.split(`
8886
8516
  `);
8887
8517
  let inFence = false;
@@ -8897,7 +8527,7 @@ var check23 = {
8897
8527
  if (alt.trim())
8898
8528
  continue;
8899
8529
  results.push({
8900
- checkId: check23.id,
8530
+ checkId: check24.id,
8901
8531
  severity: "warning",
8902
8532
  message: "Image is missing alt text",
8903
8533
  file,
@@ -8922,11 +8552,11 @@ function extractImageAlts(line) {
8922
8552
  }
8923
8553
  return alts;
8924
8554
  }
8925
- registerCheck(check23);
8555
+ registerCheck(check24);
8926
8556
 
8927
8557
  // src/checks/source/accessibility-heading-order.ts
8928
- import { readFile as readFile20 } from "node:fs/promises";
8929
- var check24 = {
8558
+ import { readFile as readFile21 } from "node:fs/promises";
8559
+ var check25 = {
8930
8560
  id: "source.accessibility.heading-order",
8931
8561
  name: "Heading Order",
8932
8562
  description: "Checks markdown heading levels do not jump by more than one",
@@ -8939,7 +8569,7 @@ var check24 = {
8939
8569
  const results = [];
8940
8570
  for (const file of files) {
8941
8571
  try {
8942
- const content = await readFile20(file, "utf8");
8572
+ const content = await readFile21(file, "utf8");
8943
8573
  const lines = content.split(`
8944
8574
  `);
8945
8575
  let inFence = false;
@@ -8954,7 +8584,7 @@ var check24 = {
8954
8584
  if (inFence)
8955
8585
  continue;
8956
8586
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
8957
- if (comment && parseDisableDirective(comment[1])?.includes(check24.id)) {
8587
+ if (comment && parseDisableDirective(comment[1])?.includes(check25.id)) {
8958
8588
  silencedLine = i + 1;
8959
8589
  continue;
8960
8590
  }
@@ -8964,7 +8594,7 @@ var check24 = {
8964
8594
  const level = match[1].length;
8965
8595
  if (prevLevel != null && level > prevLevel + 1 && i !== silencedLine) {
8966
8596
  results.push({
8967
- checkId: check24.id,
8597
+ checkId: check25.id,
8968
8598
  severity: "warning",
8969
8599
  message: `Heading level jump from h${prevLevel} to h${level}`,
8970
8600
  file,
@@ -8978,11 +8608,11 @@ var check24 = {
8978
8608
  return results;
8979
8609
  }
8980
8610
  };
8981
- registerCheck(check24);
8611
+ registerCheck(check25);
8982
8612
 
8983
8613
  // src/checks/source/layout-markers.ts
8984
- import { readFile as readFile21 } from "node:fs/promises";
8985
- var check25 = {
8614
+ import { readFile as readFile22 } from "node:fs/promises";
8615
+ var check26 = {
8986
8616
  id: "source.markdown.layout-markers",
8987
8617
  name: "Layout Markers",
8988
8618
  description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class",
@@ -8994,16 +8624,16 @@ var check25 = {
8994
8624
  return [];
8995
8625
  const results = [];
8996
8626
  const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
8997
- results.push(inspectionFailed(check25.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
8627
+ results.push(inspectionFailed(check26.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
8998
8628
  });
8999
8629
  const md = createMarkdownRenderer(plugins);
9000
8630
  for (const file of files) {
9001
8631
  try {
9002
- const content = await readFile21(file, "utf8");
8632
+ const content = await readFile22(file, "utf8");
9003
8633
  const env = {};
9004
8634
  md.render(content, env);
9005
8635
  for (const w of env.layoutWarnings ?? []) {
9006
- results.push(finding(check25.id, {
8636
+ results.push(finding(check26.id, {
9007
8637
  severity: w.severity ?? "warning",
9008
8638
  message: w.message,
9009
8639
  file,
@@ -9012,16 +8642,16 @@ var check25 = {
9012
8642
  }));
9013
8643
  }
9014
8644
  } catch (error2) {
9015
- results.push(inspectionFailed(check25.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
8645
+ results.push(inspectionFailed(check26.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
9016
8646
  }
9017
8647
  }
9018
8648
  return results;
9019
8649
  }
9020
8650
  };
9021
- registerCheck(check25);
8651
+ registerCheck(check26);
9022
8652
 
9023
8653
  // src/checks/source/merge-markers.ts
9024
- import { readFile as readFile22 } from "node:fs/promises";
8654
+ import { readFile as readFile23 } from "node:fs/promises";
9025
8655
  import path12 from "node:path";
9026
8656
 
9027
8657
  // src/checks/asset/extensions.ts
@@ -9051,7 +8681,7 @@ var CLOSE_SENTINEL = ">>>>>>> online version";
9051
8681
  function withoutOnlineTag(relPath) {
9052
8682
  return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
9053
8683
  }
9054
- var check26 = {
8684
+ var check27 = {
9055
8685
  id: "source.sync.merge-markers",
9056
8686
  name: "Combined Versions",
9057
8687
  description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
@@ -9063,9 +8693,9 @@ var check26 = {
9063
8693
  for (const file of textFiles) {
9064
8694
  let content;
9065
8695
  try {
9066
- content = await readFile22(file, "utf8");
8696
+ content = await readFile23(file, "utf8");
9067
8697
  } catch {
9068
- results.push(inspectionFailed(check26.id, `Could not read source file: ${file}`, { file }));
8698
+ results.push(inspectionFailed(check27.id, `Could not read source file: ${file}`, { file }));
9069
8699
  continue;
9070
8700
  }
9071
8701
  if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
@@ -9077,7 +8707,7 @@ var check26 = {
9077
8707
  const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
9078
8708
  if (line === OPEN_SENTINEL) {
9079
8709
  inBlock = true;
9080
- results.push(finding(check26.id, {
8710
+ results.push(finding(check27.id, {
9081
8711
  severity: "error",
9082
8712
  code: "two-versions-passage",
9083
8713
  message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
@@ -9086,7 +8716,7 @@ var check26 = {
9086
8716
  }));
9087
8717
  } else if (line === CLOSE_SENTINEL) {
9088
8718
  if (!inBlock) {
9089
- results.push(finding(check26.id, {
8719
+ results.push(finding(check27.id, {
9090
8720
  severity: "error",
9091
8721
  code: "leftover-version-marker",
9092
8722
  message: "This marker line is left over from combining two versions — delete it.",
@@ -9107,7 +8737,7 @@ var check26 = {
9107
8737
  });
9108
8738
  for (const sibling of siblings.sort()) {
9109
8739
  const rel = path12.relative(ctx.inputDir, sibling).split(path12.sep).join("/");
9110
- results.push(finding(check26.id, {
8740
+ results.push(finding(check27.id, {
9111
8741
  severity: "warning",
9112
8742
  code: "kept-both-versions",
9113
8743
  message: `Two versions of ${withoutOnlineTag(rel)} are in your book — keep the one you want, then delete the other.`,
@@ -9115,18 +8745,18 @@ var check26 = {
9115
8745
  }));
9116
8746
  }
9117
8747
  } catch (error2) {
9118
- results.push(inspectionFailed(check26.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
8748
+ results.push(inspectionFailed(check27.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
9119
8749
  }
9120
8750
  return results;
9121
8751
  }
9122
8752
  };
9123
- registerCheck(check26);
8753
+ registerCheck(check27);
9124
8754
 
9125
8755
  // src/checks/asset/image-file-size.ts
9126
8756
  import { stat as stat4 } from "node:fs/promises";
9127
8757
 
9128
8758
  // src/lib/image-inspect.ts
9129
- import { stat as stat3, readFile as readFile23 } from "node:fs/promises";
8759
+ import { stat as stat3, readFile as readFile24 } from "node:fs/promises";
9130
8760
  var DEFAULT_DPI = 72;
9131
8761
  var cache = new Map;
9132
8762
  async function inspectImage(path13) {
@@ -9141,7 +8771,7 @@ async function inspectImage(path13) {
9141
8771
  return hit.info;
9142
8772
  let info2 = null;
9143
8773
  try {
9144
- const buf = await readFile23(path13);
8774
+ const buf = await readFile24(path13);
9145
8775
  info2 = parseImage(buf);
9146
8776
  } catch {
9147
8777
  info2 = null;
@@ -9346,7 +8976,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
9346
8976
  }
9347
8977
 
9348
8978
  // src/checks/asset/image-file-size.ts
9349
- var check27 = {
8979
+ var check28 = {
9350
8980
  id: "asset.image.file-size",
9351
8981
  name: "Image File Size",
9352
8982
  description: "Checks that image files do not exceed the maximum size limit",
@@ -9367,14 +8997,14 @@ var check27 = {
9367
8997
  if (info2.size > maxSize) {
9368
8998
  const sizeMb = (info2.size / 1e6).toFixed(1);
9369
8999
  const maxMb = (maxSize / 1e6).toFixed(1);
9370
- results.push(finding(check27.id, {
9000
+ results.push(finding(check28.id, {
9371
9001
  severity: "warning",
9372
9002
  message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
9373
9003
  file
9374
9004
  }));
9375
9005
  }
9376
9006
  } catch {
9377
- results.push(inspectionFailed(check27.id, `Could not stat image file: ${file}`, {
9007
+ results.push(inspectionFailed(check28.id, `Could not stat image file: ${file}`, {
9378
9008
  file
9379
9009
  }));
9380
9010
  }
@@ -9382,10 +9012,10 @@ var check27 = {
9382
9012
  return results;
9383
9013
  }
9384
9014
  };
9385
- registerCheck(check27);
9015
+ registerCheck(check28);
9386
9016
 
9387
9017
  // src/checks/asset/image-resolution.ts
9388
- var check28 = {
9018
+ var check29 = {
9389
9019
  id: "asset.image.resolution",
9390
9020
  name: "Image Resolution",
9391
9021
  description: "Checks source image DPI from embedded density metadata",
@@ -9407,7 +9037,7 @@ var check28 = {
9407
9037
  const { xDpi, yDpi } = info2;
9408
9038
  if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
9409
9039
  results.push({
9410
- checkId: check28.id,
9040
+ checkId: check29.id,
9411
9041
  severity: "warning",
9412
9042
  message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
9413
9043
  file
@@ -9417,7 +9047,7 @@ var check28 = {
9417
9047
  return results;
9418
9048
  }
9419
9049
  };
9420
- registerCheck(check28);
9050
+ registerCheck(check29);
9421
9051
 
9422
9052
  // src/checks/asset/image-color-space.ts
9423
9053
  var LABEL = {
@@ -9425,7 +9055,7 @@ var LABEL = {
9425
9055
  gray: "Gray",
9426
9056
  cmyk: "CMYK"
9427
9057
  };
9428
- var check29 = {
9058
+ var check30 = {
9429
9059
  id: "asset.image.color-space",
9430
9060
  name: "Image Color Space",
9431
9061
  description: "Validates image color spaces against allowed list",
@@ -9446,7 +9076,7 @@ var check29 = {
9446
9076
  const cs = info2?.colorSpace;
9447
9077
  if (cs && !allowedLower.has(cs)) {
9448
9078
  results.push({
9449
- checkId: check29.id,
9079
+ checkId: check30.id,
9450
9080
  severity: "warning",
9451
9081
  message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
9452
9082
  file
@@ -9456,10 +9086,10 @@ var check29 = {
9456
9086
  return results;
9457
9087
  }
9458
9088
  };
9459
- registerCheck(check29);
9089
+ registerCheck(check30);
9460
9090
 
9461
9091
  // src/checks/asset/image-alpha.ts
9462
- var check30 = {
9092
+ var check31 = {
9463
9093
  id: "asset.image.alpha-channel",
9464
9094
  name: "Image Alpha Channel",
9465
9095
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -9477,7 +9107,7 @@ var check30 = {
9477
9107
  const info2 = await inspectImage(file);
9478
9108
  if (info2?.hasAlpha) {
9479
9109
  results.push({
9480
- checkId: check30.id,
9110
+ checkId: check31.id,
9481
9111
  severity: "warning",
9482
9112
  message: "Image contains alpha channel, which may cause print issues",
9483
9113
  file
@@ -9487,10 +9117,10 @@ var check30 = {
9487
9117
  return results;
9488
9118
  }
9489
9119
  };
9490
- registerCheck(check30);
9120
+ registerCheck(check31);
9491
9121
 
9492
9122
  // src/checks/asset/image-tac.ts
9493
- var check31 = {
9123
+ var check32 = {
9494
9124
  id: "asset.image.tac-raster",
9495
9125
  name: "Image TAC (Raster)",
9496
9126
  description: "Rasterizes and checks TAC per image using Ghostscript",
@@ -9509,7 +9139,7 @@ var check31 = {
9509
9139
  const ghostscript = await resolveGhostscript();
9510
9140
  if (!ghostscript) {
9511
9141
  return [
9512
- inspectionFailed(check31.id, "Could not inspect image ink coverage: Ghostscript executable not found")
9142
+ inspectionFailed(check32.id, "Could not inspect image ink coverage: Ghostscript executable not found")
9513
9143
  ];
9514
9144
  }
9515
9145
  for (const file of files) {
@@ -9527,7 +9157,7 @@ var check31 = {
9527
9157
  if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
9528
9158
  const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
9529
9159
  if (tac > maxTac) {
9530
- results.push(finding(check31.id, {
9160
+ results.push(finding(check32.id, {
9531
9161
  severity: "warning",
9532
9162
  message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
9533
9163
  file,
@@ -9539,16 +9169,16 @@ var check31 = {
9539
9169
  }
9540
9170
  }
9541
9171
  } catch {
9542
- results.push(inspectionFailed(check31.id, `Could not inspect image ink coverage: ${file}`, { file }));
9172
+ results.push(inspectionFailed(check32.id, `Could not inspect image ink coverage: ${file}`, { file }));
9543
9173
  }
9544
9174
  }
9545
9175
  return results;
9546
9176
  }
9547
9177
  };
9548
- registerCheck(check31);
9178
+ registerCheck(check32);
9549
9179
 
9550
9180
  // src/checks/asset/approved-fonts.ts
9551
- var check32 = {
9181
+ var check33 = {
9552
9182
  id: "asset.font.approved-files",
9553
9183
  name: "Approved Font Files",
9554
9184
  description: "Checks font files against the approved file patterns",
@@ -9587,7 +9217,7 @@ var check32 = {
9587
9217
  for (const font of allFonts) {
9588
9218
  if (!approvedFonts.has(font)) {
9589
9219
  results.push({
9590
- checkId: check32.id,
9220
+ checkId: check33.id,
9591
9221
  severity: "warning",
9592
9222
  message: "Font file not in approved list",
9593
9223
  file: font
@@ -9597,7 +9227,7 @@ var check32 = {
9597
9227
  return results;
9598
9228
  }
9599
9229
  };
9600
- registerCheck(check32);
9230
+ registerCheck(check33);
9601
9231
 
9602
9232
  // src/checks/asset/font-license.ts
9603
9233
  import { existsSync as existsSync11 } from "node:fs";
@@ -9612,7 +9242,7 @@ var LICENSE_NAMES = [
9612
9242
  "OFL-1.1.txt",
9613
9243
  "COPYING"
9614
9244
  ];
9615
- var check33 = {
9245
+ var check34 = {
9616
9246
  id: "asset.font.license",
9617
9247
  name: "Font License",
9618
9248
  description: "Checks for license files in font directories",
@@ -9639,7 +9269,7 @@ var check33 = {
9639
9269
  const hasLicense = LICENSE_NAMES.some((name) => existsSync11(resolve7(fontDir, name)));
9640
9270
  if (!hasLicense) {
9641
9271
  results.push({
9642
- checkId: check33.id,
9272
+ checkId: check34.id,
9643
9273
  severity: "warning",
9644
9274
  message: `No font license file found in directory`,
9645
9275
  file: fontDir
@@ -9649,10 +9279,10 @@ var check33 = {
9649
9279
  return results;
9650
9280
  }
9651
9281
  };
9652
- registerCheck(check33);
9282
+ registerCheck(check34);
9653
9283
 
9654
9284
  // src/checks/heuristic/text-density.ts
9655
- var check34 = {
9285
+ var check35 = {
9656
9286
  id: "heuristic.whitespace.text-density",
9657
9287
  name: "Text Density",
9658
9288
  description: "Checks characters-per-page ratio",
@@ -9681,7 +9311,7 @@ var check34 = {
9681
9311
  });
9682
9312
  if (lowPages.length > 0) {
9683
9313
  results.push({
9684
- checkId: check34.id,
9314
+ checkId: check35.id,
9685
9315
  severity: "info",
9686
9316
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
9687
9317
  file: ctx.pdfPath
@@ -9689,7 +9319,7 @@ var check34 = {
9689
9319
  }
9690
9320
  if (highPages.length > 0) {
9691
9321
  results.push({
9692
- checkId: check34.id,
9322
+ checkId: check35.id,
9693
9323
  severity: "info",
9694
9324
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
9695
9325
  file: ctx.pdfPath
@@ -9698,11 +9328,11 @@ var check34 = {
9698
9328
  return results;
9699
9329
  }
9700
9330
  };
9701
- registerCheck(check34);
9331
+ registerCheck(check35);
9702
9332
 
9703
9333
  // src/checks/heuristic/section-density.ts
9704
- import { readFile as readFile24 } from "node:fs/promises";
9705
- var check35 = {
9334
+ import { readFile as readFile25 } from "node:fs/promises";
9335
+ var check36 = {
9706
9336
  id: "heuristic.chunking.section-density",
9707
9337
  name: "Section Density",
9708
9338
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -9718,7 +9348,7 @@ var check35 = {
9718
9348
  const results = [];
9719
9349
  for (const file of files) {
9720
9350
  try {
9721
- const content = await readFile24(file, "utf8");
9351
+ const content = await readFile25(file, "utf8");
9722
9352
  const lines = content.split(`
9723
9353
  `);
9724
9354
  let currentSectionStart = 0;
@@ -9728,7 +9358,7 @@ var check35 = {
9728
9358
  const line = lines[i];
9729
9359
  if (/^#{1,6}\s/.test(line)) {
9730
9360
  if (paragraphCount > maxParas) {
9731
- results.push(finding(check35.id, {
9361
+ results.push(finding(check36.id, {
9732
9362
  severity: "info",
9733
9363
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
9734
9364
  file,
@@ -9748,7 +9378,7 @@ var check35 = {
9748
9378
  }
9749
9379
  }
9750
9380
  if (paragraphCount > maxParas) {
9751
- results.push(finding(check35.id, {
9381
+ results.push(finding(check36.id, {
9752
9382
  severity: "info",
9753
9383
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
9754
9384
  file,
@@ -9756,7 +9386,7 @@ var check35 = {
9756
9386
  }));
9757
9387
  }
9758
9388
  } catch {
9759
- results.push(inspectionFailed(check35.id, `Could not read source file: ${file}`, {
9389
+ results.push(inspectionFailed(check36.id, `Could not read source file: ${file}`, {
9760
9390
  file
9761
9391
  }));
9762
9392
  }
@@ -9764,10 +9394,10 @@ var check35 = {
9764
9394
  return results;
9765
9395
  }
9766
9396
  };
9767
- registerCheck(check35);
9397
+ registerCheck(check36);
9768
9398
 
9769
9399
  // src/checks/heuristic/layer-count.ts
9770
- var check36 = {
9400
+ var check37 = {
9771
9401
  id: "heuristic.decoration.layer-count",
9772
9402
  name: "Layer Count",
9773
9403
  description: "Counts image objects per page",
@@ -9792,7 +9422,7 @@ var check36 = {
9792
9422
  heavyPages.sort((a, b) => a - b);
9793
9423
  return [
9794
9424
  {
9795
- checkId: check36.id,
9425
+ checkId: check37.id,
9796
9426
  severity: "info",
9797
9427
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
9798
9428
  file: ctx.pdfPath
@@ -9802,10 +9432,10 @@ var check36 = {
9802
9432
  return [];
9803
9433
  }
9804
9434
  };
9805
- registerCheck(check36);
9435
+ registerCheck(check37);
9806
9436
 
9807
9437
  // src/checks/heuristic/placement-variance.ts
9808
- var check37 = {
9438
+ var check38 = {
9809
9439
  id: "heuristic.layout.placement-variance",
9810
9440
  name: "Placement Variance",
9811
9441
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -9823,7 +9453,7 @@ var check37 = {
9823
9453
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
9824
9454
  return [
9825
9455
  {
9826
- checkId: check37.id,
9456
+ checkId: check38.id,
9827
9457
  severity: "info",
9828
9458
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
9829
9459
  file: ctx.pdfPath
@@ -9831,7 +9461,7 @@ var check37 = {
9831
9461
  ];
9832
9462
  }
9833
9463
  };
9834
- registerCheck(check37);
9464
+ registerCheck(check38);
9835
9465
 
9836
9466
  // src/checks/runner.ts
9837
9467
  function getCheckSeverityOverride(checkId, config) {
@@ -9861,16 +9491,16 @@ async function runChecks(ctx, opts = {}) {
9861
9491
  }
9862
9492
  const release = retainPdfCache();
9863
9493
  try {
9864
- for (const check38 of checks2) {
9494
+ for (const check39 of checks2) {
9865
9495
  try {
9866
- let results = await check38.run(ctx);
9867
- const severityOverride = getCheckSeverityOverride(check38.id, ctx.config);
9496
+ let results = await check39.run(ctx);
9497
+ const severityOverride = getCheckSeverityOverride(check39.id, ctx.config);
9868
9498
  if (severityOverride) {
9869
9499
  for (const r of results) {
9870
9500
  r.severity = severityOverride;
9871
9501
  }
9872
9502
  }
9873
- const lintLevel = ruleLevel(ctx.config.lint.rules, check38.id);
9503
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check39.id);
9874
9504
  if (lintLevel === "off")
9875
9505
  results = [];
9876
9506
  else if (lintLevel) {
@@ -9878,15 +9508,15 @@ async function runChecks(ctx, opts = {}) {
9878
9508
  r.severity = lintLevel === "warn" ? "warning" : "error";
9879
9509
  }
9880
9510
  if (results.length === 0) {
9881
- passed.push(check38.id);
9511
+ passed.push(check39.id);
9882
9512
  } else {
9883
9513
  allResults.push(...results);
9884
9514
  }
9885
9515
  } catch (err) {
9886
9516
  allResults.push({
9887
- checkId: check38.id,
9517
+ checkId: check39.id,
9888
9518
  severity: "error",
9889
- message: `Check "${check38.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9519
+ message: `Check "${check39.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9890
9520
  });
9891
9521
  }
9892
9522
  }
@@ -9926,15 +9556,15 @@ function emptyReport() {
9926
9556
  async function checkToolAvailability(config, opts = {}) {
9927
9557
  const { checks: checks2 } = selectChecks(opts, config);
9928
9558
  const toolToChecks = new Map;
9929
- for (const check38 of checks2) {
9930
- if (!check38.requiredTools?.length)
9559
+ for (const check39 of checks2) {
9560
+ if (!check39.requiredTools?.length)
9931
9561
  continue;
9932
- for (const tool of check38.requiredTools) {
9562
+ for (const tool of check39.requiredTools) {
9933
9563
  const existing = toolToChecks.get(tool);
9934
9564
  if (existing) {
9935
- existing.push(check38.id);
9565
+ existing.push(check39.id);
9936
9566
  } else {
9937
- toolToChecks.set(tool, [check38.id]);
9567
+ toolToChecks.set(tool, [check39.id]);
9938
9568
  }
9939
9569
  }
9940
9570
  }
@@ -9950,11 +9580,11 @@ async function checkToolAvailability(config, opts = {}) {
9950
9580
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
9951
9581
  const missingSet = new Set(missing);
9952
9582
  const skippedChecks = [];
9953
- for (const check38 of checks2) {
9954
- if (!check38.requiredTools?.length)
9583
+ for (const check39 of checks2) {
9584
+ if (!check39.requiredTools?.length)
9955
9585
  continue;
9956
- if (check38.requiredTools.some((t) => missingSet.has(t))) {
9957
- skippedChecks.push(check38.id);
9586
+ if (check39.requiredTools.some((t) => missingSet.has(t))) {
9587
+ skippedChecks.push(check39.id);
9958
9588
  }
9959
9589
  }
9960
9590
  return { available, missing, skippedChecks, toolToChecks };
@@ -10227,7 +9857,7 @@ async function executeValidation(args) {
10227
9857
  };
10228
9858
  if (args.fix) {
10229
9859
  const selected = selectChecks(runnerOptions, config).checks;
10230
- if (selected.some((check38) => check38.id === MARKDOWNLINT_CHECK_ID)) {
9860
+ if (selected.some((check39) => check39.id === MARKDOWNLINT_CHECK_ID)) {
10231
9861
  await applyMarkdownlintFixes(context);
10232
9862
  } else {
10233
9863
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -10706,7 +10336,7 @@ async function runBuild(opts) {
10706
10336
  import path16 from "path";
10707
10337
  import { tmpdir } from "os";
10708
10338
  import { randomBytes as randomBytes2 } from "crypto";
10709
- import { readdir as readdir5, readFile as readFile25, writeFile as writeFile6 } from "node:fs/promises";
10339
+ import { readdir as readdir5, readFile as readFile26, writeFile as writeFile6 } from "node:fs/promises";
10710
10340
 
10711
10341
  // src/utils/file-utils.ts
10712
10342
  import { promises as fs3 } from "fs";
@@ -11255,7 +10885,7 @@ async function cleanupOrphanTempDirs() {
11255
10885
  const dir = path16.join(TEMP_DIR_BASE, entry);
11256
10886
  const pidFile = path16.join(dir, PID_FILE_NAME);
11257
10887
  try {
11258
- const raw = await readFile25(pidFile, "utf8");
10888
+ const raw = await readFile26(pidFile, "utf8");
11259
10889
  const pid = parseInt(raw.trim(), 10);
11260
10890
  if (!isProcessAlive(pid)) {
11261
10891
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -11362,7 +10992,7 @@ function createServerState(inputPath, tempDir, config, options) {
11362
10992
  import http from "node:http";
11363
10993
  import net from "node:net";
11364
10994
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
11365
- import { readFile as readFile26, stat as stat5 } from "node:fs/promises";
10995
+ import { readFile as readFile27, stat as stat5 } from "node:fs/promises";
11366
10996
  import path17 from "path";
11367
10997
  import { WebSocket, WebSocketServer } from "ws";
11368
10998
 
@@ -11461,7 +11091,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
11461
11091
  }
11462
11092
  let data;
11463
11093
  try {
11464
- data = await readFile26(filePath);
11094
+ data = await readFile27(filePath);
11465
11095
  } catch {
11466
11096
  res.writeHead(404);
11467
11097
  res.end("Not Found");
@@ -11510,7 +11140,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11510
11140
  let data;
11511
11141
  if (!validator || validator.stamp !== stamp) {
11512
11142
  try {
11513
- data = await readFile26(absPath);
11143
+ data = await readFile27(absPath);
11514
11144
  } catch {
11515
11145
  await serveStatic(absPath, res);
11516
11146
  return;
@@ -11822,7 +11452,7 @@ async function startPreviewServer(options) {
11822
11452
  import { platform, arch, release } from "node:os";
11823
11453
 
11824
11454
  // src/lib/remote-auth/token-store.ts
11825
- import { chmod, mkdir as mkdir7, readFile as readFile27, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
11455
+ import { chmod, mkdir as mkdir7, readFile as readFile28, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
11826
11456
  import os3 from "node:os";
11827
11457
  import path18 from "node:path";
11828
11458
  function credentialHostKey(hostOrUrl) {
@@ -11866,7 +11496,7 @@ class FileTokenStore {
11866
11496
  async read() {
11867
11497
  let raw;
11868
11498
  try {
11869
- raw = await readFile27(this.filePath, "utf8");
11499
+ raw = await readFile28(this.filePath, "utf8");
11870
11500
  } catch (e) {
11871
11501
  if (e?.code === "ENOENT") {
11872
11502
  return { version: 1, credentials: {} };
@@ -12066,13 +11696,13 @@ async function getSystemDiagnostics() {
12066
11696
  };
12067
11697
  }
12068
11698
  // src/lib/project-scaffold.ts
12069
- import { access, copyFile, cp as cp2, mkdir as mkdir9, readdir as readdir6, readFile as readFile29, stat as stat6, writeFile as writeFile9 } from "node:fs/promises";
11699
+ import { access, copyFile, cp as cp2, mkdir as mkdir9, readdir as readdir6, readFile as readFile30, stat as stat6, writeFile as writeFile9 } from "node:fs/promises";
12070
11700
  import { constants as FS, existsSync as existsSync14 } from "node:fs";
12071
11701
  import path20 from "node:path";
12072
11702
 
12073
11703
  // src/lib/page-size.ts
12074
11704
  import { existsSync as existsSync13 } from "node:fs";
12075
- import { mkdir as mkdir8, readFile as readFile28, writeFile as writeFile8 } from "node:fs/promises";
11705
+ import { mkdir as mkdir8, readFile as readFile29, writeFile as writeFile8 } from "node:fs/promises";
12076
11706
  import path19 from "node:path";
12077
11707
  import postcss4 from "postcss";
12078
11708
  import { Scalar as Scalar2 } from "yaml";
@@ -12177,7 +11807,7 @@ var relDisplay2 = (projectDir, abs) => path19.relative(projectDir, abs).split(pa
12177
11807
  async function findCssSize(projectDir, sheets) {
12178
11808
  let found = null;
12179
11809
  for (const file of sheets) {
12180
- const size = readPageSizeFromCss(await readFile28(file, "utf8"));
11810
+ const size = readPageSizeFromCss(await readFile29(file, "utf8"));
12181
11811
  if (size)
12182
11812
  found = { file, size };
12183
11813
  }
@@ -12204,7 +11834,7 @@ async function setPageSetup(projectDir, choice) {
12204
11834
  const found = await findCssSize(projectDir, sheets);
12205
11835
  const newSheet = path19.join(projectDir, "styles", "book.css");
12206
11836
  const target = found?.file ?? sheets[sheets.length - 1] ?? newSheet;
12207
- const before = existsSync13(target) ? await readFile28(target, "utf8") : "";
11837
+ const before = existsSync13(target) ? await readFile29(target, "utf8") : "";
12208
11838
  let after;
12209
11839
  try {
12210
11840
  after = setPageSizeInCss(before, size);
@@ -12494,7 +12124,7 @@ styles:
12494
12124
  async function firstSourceFile(manifestPath) {
12495
12125
  let text;
12496
12126
  try {
12497
- text = await readFile29(manifestPath, "utf8");
12127
+ text = await readFile30(manifestPath, "utf8");
12498
12128
  } catch {
12499
12129
  return;
12500
12130
  }
@@ -12506,14 +12136,14 @@ async function firstSourceFile(manifestPath) {
12506
12136
  return m ? m[1].trim() : undefined;
12507
12137
  }
12508
12138
  async function fillTemplateFile(filePath, substitutions) {
12509
- let text = await readFile29(filePath, "utf8");
12139
+ let text = await readFile30(filePath, "utf8");
12510
12140
  for (const [token, value] of Object.entries(substitutions)) {
12511
12141
  text = text.split(token).join(value);
12512
12142
  }
12513
12143
  await writeFile9(filePath, text, "utf8");
12514
12144
  }
12515
12145
  // src/lib/extension-scaffold.ts
12516
- import { access as access2, mkdir as mkdir10, readFile as readFile30, writeFile as writeFile10 } from "node:fs/promises";
12146
+ import { access as access2, mkdir as mkdir10, readFile as readFile31, writeFile as writeFile10 } from "node:fs/promises";
12517
12147
  import { constants as FS2 } from "node:fs";
12518
12148
  import path21 from "node:path";
12519
12149
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -12630,7 +12260,7 @@ async function scaffoldExtension(options) {
12630
12260
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
12631
12261
  const dest = path21.join(extensionDir, relDest);
12632
12262
  await mkdir10(path21.dirname(dest), { recursive: true });
12633
- const text = await readFile30(src, "utf8");
12263
+ const text = await readFile31(src, "utf8");
12634
12264
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
12635
12265
  await writeFile10(dest, substitute(text, table), "utf8");
12636
12266
  written.push(relDest);
@@ -12656,7 +12286,7 @@ function substitute(text, table) {
12656
12286
  return out;
12657
12287
  }
12658
12288
  // src/lib/project-templates.ts
12659
- import { cp as cp3, mkdir as mkdir11, readFile as readFile31, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
12289
+ import { cp as cp3, mkdir as mkdir11, readFile as readFile32, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
12660
12290
  import { constants as FS3, existsSync as existsSync15 } from "node:fs";
12661
12291
  import { access as access3 } from "node:fs/promises";
12662
12292
  import path22 from "node:path";
@@ -12668,7 +12298,7 @@ var BUILT_IN_TEMPLATE_IDS = [
12668
12298
  ];
12669
12299
  async function readTemplateChoices(manifestPath) {
12670
12300
  try {
12671
- const text = await readFile31(manifestPath, "utf8");
12301
+ const text = await readFile32(manifestPath, "utf8");
12672
12302
  const doc = parseDocument2(text);
12673
12303
  const out = {};
12674
12304
  const preset = doc.get("preset");
@@ -12850,7 +12480,7 @@ async function saveProjectAsTemplate(options) {
12850
12480
  async function retokeniseManifest(manifestPath) {
12851
12481
  let text;
12852
12482
  try {
12853
- text = await readFile31(manifestPath, "utf8");
12483
+ text = await readFile32(manifestPath, "utf8");
12854
12484
  } catch {
12855
12485
  return;
12856
12486
  }
@@ -12871,7 +12501,7 @@ async function listCustomTemplates(templatesRoot) {
12871
12501
  const dir = path22.join(templatesRoot, entry.name);
12872
12502
  let label = prettify(entry.name);
12873
12503
  try {
12874
- const meta = JSON.parse(await readFile31(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
12504
+ const meta = JSON.parse(await readFile32(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
12875
12505
  if (typeof meta.label === "string" && meta.label)
12876
12506
  label = meta.label;
12877
12507
  } catch {}
@@ -12897,7 +12527,7 @@ async function importTemplateFromFolder(options) {
12897
12527
  return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
12898
12528
  }
12899
12529
  // src/lib/snippets.ts
12900
- import { readdir as readdir8, readFile as readFile32, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
12530
+ import { readdir as readdir8, readFile as readFile33, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
12901
12531
  import path23 from "node:path";
12902
12532
  var SNIPPETS_DIR = "snippets";
12903
12533
  var LEVEL_RANK = { project: 0, extension: 1, core: 2 };
@@ -12938,7 +12568,7 @@ async function scanSnippetFiles(dir) {
12938
12568
  continue;
12939
12569
  let body = "";
12940
12570
  try {
12941
- body = await readFile32(path23.join(dir, fileName), "utf8");
12571
+ body = await readFile33(path23.join(dir, fileName), "utf8");
12942
12572
  } catch {
12943
12573
  continue;
12944
12574
  }
@@ -13024,7 +12654,7 @@ async function collectLibrary(projectDir) {
13024
12654
  listed.component = name;
13025
12655
  continue;
13026
12656
  }
13027
- const body = await readFile32(path23.join(entry.dir, rel), "utf8").catch(() => null);
12657
+ const body = await readFile33(path23.join(entry.dir, rel), "utf8").catch(() => null);
13028
12658
  if (body === null)
13029
12659
  continue;
13030
12660
  snippets2.push({
@@ -13074,7 +12704,7 @@ async function listMarkerComponents(projectDir) {
13074
12704
  }
13075
12705
  // src/lib/extension-import.ts
13076
12706
  import { existsSync as existsSync16 } from "node:fs";
13077
- import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile33, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
12707
+ import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile34, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
13078
12708
  import { tmpdir as tmpdir2 } from "node:os";
13079
12709
  import path24 from "node:path";
13080
12710
  import { unzipSync } from "fflate";
@@ -13147,7 +12777,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13147
12777
  if (!existsSync16(cssPath)) {
13148
12778
  throw new Error("A theme must contain a theme.css file.");
13149
12779
  }
13150
- const css = await readFile33(cssPath, "utf8");
12780
+ const css = await readFile34(cssPath, "utf8");
13151
12781
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
13152
12782
  if (reject) {
13153
12783
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -13175,7 +12805,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13175
12805
  for (let i = 0;i < extraSheets.length; i++) {
13176
12806
  const rel = extraSheets[i];
13177
12807
  const sheetPath = extraSheetPaths[i];
13178
- const sheetCss = await readFile33(sheetPath, "utf8");
12808
+ const sheetCss = await readFile34(sheetPath, "utf8");
13179
12809
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
13180
12810
  if (sheetReject) {
13181
12811
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -13208,7 +12838,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
13208
12838
  let pkg = {};
13209
12839
  if (metaExists) {
13210
12840
  try {
13211
- const parsed = JSON.parse(await readFile33(file, "utf8"));
12841
+ const parsed = JSON.parse(await readFile34(file, "utf8"));
13212
12842
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
13213
12843
  pkg = parsed;
13214
12844
  }
@@ -13310,11 +12940,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
13310
12940
  async function importExtensionFromFile(projectDir, filePath) {
13311
12941
  const ext = path24.extname(filePath).toLowerCase();
13312
12942
  if (ext === ".zip") {
13313
- const buf = await readFile33(filePath);
12943
+ const buf = await readFile34(filePath);
13314
12944
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
13315
12945
  }
13316
12946
  if (ext === ".css") {
13317
- const css = await readFile33(filePath, "utf8");
12947
+ const css = await readFile34(filePath, "utf8");
13318
12948
  return importExtensionFromCssText(projectDir, css, prettify(path24.basename(filePath, ".css")));
13319
12949
  }
13320
12950
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -15885,7 +15515,7 @@ var drivethrurpgProvider = {
15885
15515
  };
15886
15516
 
15887
15517
  // src/lib/publish/providers/gdrive.ts
15888
- import { mkdtemp as mkdtemp3, readFile as readFile34, readdir as readdir11, rm as rm9, stat as stat9, writeFile as writeFile16 } from "node:fs/promises";
15518
+ import { mkdtemp as mkdtemp3, readFile as readFile35, readdir as readdir11, rm as rm9, stat as stat9, writeFile as writeFile16 } from "node:fs/promises";
15889
15519
  import { tmpdir as tmpdir3 } from "node:os";
15890
15520
  import path30 from "node:path";
15891
15521
  import { Zip, ZipDeflate } from "fflate";
@@ -16277,12 +15907,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
16277
15907
  await collectZipEntries(root, full, out, onWarn);
16278
15908
  } else if (entry.isFile()) {
16279
15909
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16280
- out[rel] = await readFile34(full);
15910
+ out[rel] = await readFile35(full);
16281
15911
  } else if (entry.isSymbolicLink()) {
16282
15912
  const resolved = await stat9(full).catch(() => null);
16283
15913
  if (resolved?.isFile()) {
16284
15914
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16285
- out[rel] = await readFile34(full);
15915
+ out[rel] = await readFile35(full);
16286
15916
  } else if (resolved?.isDirectory()) {
16287
15917
  await collectZipEntries(root, full, out, onWarn);
16288
15918
  } else {
@@ -17148,7 +16778,7 @@ async function disconnectPublishCredential(key, deps, options = {}) {
17148
16778
  }
17149
16779
  }
17150
16780
  // src/lib/publish/selections.ts
17151
- import { chmod as chmod3, mkdir as mkdir21, readFile as readFile35, writeFile as writeFile18 } from "node:fs/promises";
16781
+ import { chmod as chmod3, mkdir as mkdir21, readFile as readFile36, writeFile as writeFile18 } from "node:fs/promises";
17152
16782
  import path34 from "node:path";
17153
16783
  class PublishSelectionsStore {
17154
16784
  filePath;
@@ -17158,7 +16788,7 @@ class PublishSelectionsStore {
17158
16788
  }
17159
16789
  async read() {
17160
16790
  try {
17161
- const raw = await readFile35(this.filePath, "utf8");
16791
+ const raw = await readFile36(this.filePath, "utf8");
17162
16792
  const parsed = JSON.parse(raw);
17163
16793
  if (parsed && typeof parsed === "object") {
17164
16794
  return {