gutterpress 0.11.19-alpha.1 → 0.11.19-alpha.3

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.
@@ -5984,7 +5984,7 @@ import git from "isomorphic-git";
5984
5984
  // package.json
5985
5985
  var package_default = {
5986
5986
  name: "gutterpress",
5987
- version: "0.11.19-alpha.1",
5987
+ version: "0.11.19-alpha.3",
5988
5988
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5989
5989
  author: "itlackey",
5990
5990
  license: "MPL-2.0",
@@ -9262,11 +9262,174 @@ var check27 = {
9262
9262
  };
9263
9263
  registerCheck(check27);
9264
9264
 
9265
+ // src/checks/source/characters.ts
9266
+ import { readFile as readFile24 } from "node:fs/promises";
9267
+
9268
+ // src/checks/source/character-table.ts
9269
+ var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\u2019\\u201B-\\u201D\\u201F\\u2022\\u2026" + "\\u2028-\\u202F\\u2032\\u2033\\u205F\\u2060\\u2066-\\u2069\\u2190\\u2192" + "\\u2194\\u21D2\\u2212\\u25AA\\u25E6\\u2605\\u2713\\u2714\\u2717\\u3000" + "\\uE000-\\uF8FF\\uFEFF\\uFF02\\uFF07\\uFFFD]" + "|\\p{Emoji_Presentation}|\\p{Extended_Pictographic}(?=\\uFE0F)", "gu");
9270
+ var NAMED = {
9271
+ 8212: ["Em dash", "typographer", "---"],
9272
+ 8211: ["En dash", "typographer", "--"],
9273
+ 8220: ["Opening curly quote", "typographer", '"'],
9274
+ 8221: ["Closing curly quote", "typographer", '"'],
9275
+ 8216: ["Opening curly single quote", "typographer", "'"],
9276
+ 8217: ["Curly apostrophe", "typographer", "'"],
9277
+ 8230: ["Ellipsis character", "typographer", "..."],
9278
+ 700: ["Modifier-letter apostrophe", "lookalike", "'"],
9279
+ 180: ["Acute accent", "lookalike", "'"],
9280
+ 8242: ["Prime", "lookalike", "'"],
9281
+ 8243: ["Double prime", "lookalike", '"'],
9282
+ 8219: ["Reversed single quote", "lookalike", "'"],
9283
+ 8223: ["Reversed double quote", "lookalike", '"'],
9284
+ 65282: ["Full-width quote", "lookalike", '"'],
9285
+ 65287: ["Full-width apostrophe", "lookalike", "'"],
9286
+ 8208: ["Unicode hyphen", "lookalike", "-"],
9287
+ 8209: ["Non-breaking hyphen", "lookalike", "-"],
9288
+ 8210: ["Figure dash", "lookalike", "-"],
9289
+ 8213: ["Horizontal bar", "lookalike", "---"],
9290
+ 8722: ["Minus sign", "lookalike", "-"],
9291
+ 8226: ["Bullet", "symbol", "•"],
9292
+ 9702: ["White bullet", "symbol", "◦"],
9293
+ 9642: ["Small square", "symbol", "▪"],
9294
+ 8594: ["Right arrow", "symbol", "→"],
9295
+ 8592: ["Left arrow", "symbol", "←"],
9296
+ 8596: ["Left-right arrow", "symbol", "↔"],
9297
+ 8658: ["Double right arrow", "symbol", "⇒"],
9298
+ 10003: ["Check mark", "symbol", "✓"],
9299
+ 10004: ["Heavy check mark", "symbol", "✔"],
9300
+ 10007: ["Ballot X", "symbol", "✗"],
9301
+ 9733: ["Star", "symbol", "★"],
9302
+ 8203: ["Zero-width space", "invisible", "​"],
9303
+ 8288: ["Word joiner", "invisible", "⁠"],
9304
+ 65279: ["Byte-order mark", "invisible"],
9305
+ 173: ["Soft hyphen", "invisible", "­"],
9306
+ 8204: ["Zero-width non-joiner", "invisible", "‌"],
9307
+ 8205: ["Zero-width joiner", "invisible", "‍"],
9308
+ 8206: ["Left-to-right mark", "invisible", "‎"],
9309
+ 8207: ["Right-to-left mark", "invisible", "‏"],
9310
+ 1564: ["Arabic letter mark", "invisible", "؜"],
9311
+ 8234: ["Left-to-right embedding", "invisible", "‪"],
9312
+ 8235: ["Right-to-left embedding", "invisible", "‫"],
9313
+ 8236: ["Pop directional formatting", "invisible", "‬"],
9314
+ 8237: ["Left-to-right override", "invisible", "‭"],
9315
+ 8238: ["Right-to-left override", "invisible", "‮"],
9316
+ 8294: ["Left-to-right isolate", "invisible", "⁦"],
9317
+ 8295: ["Right-to-left isolate", "invisible", "⁧"],
9318
+ 8296: ["First-strong isolate", "invisible", "⁨"],
9319
+ 8297: ["Pop directional isolate", "invisible", "⁩"],
9320
+ 8232: ["Line separator", "invisible"],
9321
+ 8233: ["Paragraph separator", "invisible"],
9322
+ 160: ["Non-breaking space", "whitespace", " "],
9323
+ 8192: ["En quad", "whitespace", " "],
9324
+ 8193: ["Em quad", "whitespace", " "],
9325
+ 8194: ["En space", "whitespace", " "],
9326
+ 8195: ["Em space", "whitespace", " "],
9327
+ 8196: ["Three-per-em space", "whitespace", " "],
9328
+ 8197: ["Four-per-em space", "whitespace", " "],
9329
+ 8198: ["Six-per-em space", "whitespace", " "],
9330
+ 8199: ["Figure space", "whitespace", " "],
9331
+ 8200: ["Punctuation space", "whitespace", " "],
9332
+ 8201: ["Thin space", "whitespace", " "],
9333
+ 8202: ["Hair space", "whitespace", " "],
9334
+ 8239: ["Narrow non-breaking space", "whitespace", " "],
9335
+ 8287: ["Medium mathematical space", "whitespace", " "],
9336
+ 12288: ["Ideographic space", "whitespace", " "],
9337
+ 65533: ["Replacement character", "replacement"]
9338
+ };
9339
+ function fixFor(code, spelling) {
9340
+ switch (code) {
9341
+ case "typographer":
9342
+ return `Type ${spelling} instead; it prints the same.`;
9343
+ case "lookalike":
9344
+ return `Type ${spelling} instead.`;
9345
+ case "whitespace":
9346
+ return `Type a space, or ${spelling} if you meant it.`;
9347
+ case "symbol":
9348
+ case "invisible":
9349
+ return spelling ? `Delete it, or type ${spelling} if you meant it.` : "Delete it.";
9350
+ case "control":
9351
+ return "Delete it; it does not print.";
9352
+ case "replacement":
9353
+ return "Retype the character that was lost here.";
9354
+ case "private-use":
9355
+ return "Replace it with the character you meant; this one only shows in the font it came from.";
9356
+ }
9357
+ }
9358
+ function info2(name, code, spelling) {
9359
+ return { name, code, fix: fixFor(code, spelling) };
9360
+ }
9361
+ function describeCharacter(char) {
9362
+ const cp2 = char.codePointAt(0);
9363
+ const named = NAMED[cp2];
9364
+ if (named)
9365
+ return info2(...named);
9366
+ if (cp2 <= 159)
9367
+ return info2("Control character", "control");
9368
+ if (cp2 >= 57344 && cp2 <= 63743)
9369
+ return info2("Private-use character", "private-use");
9370
+ return info2("Emoji", "symbol", `&#x${cp2.toString(16).toUpperCase()};`);
9371
+ }
9372
+ function codePointLabel(char) {
9373
+ return `U+${char.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
9374
+ }
9375
+
9376
+ // src/checks/source/characters.ts
9377
+ var PROBE = new RegExp(CHARACTER_PATTERN.source, "u");
9378
+ var check28 = {
9379
+ id: "source.characters",
9380
+ name: "Unusual Characters",
9381
+ description: "Finds invisible and control characters, odd spaces, and pasted typography (em dashes, curly quotes, emoji) in markdown",
9382
+ category: "source",
9383
+ phase: "pre-build",
9384
+ async run(ctx) {
9385
+ const results = [];
9386
+ for (const file of (ctx.markdownFiles ?? []).slice().sort()) {
9387
+ let content;
9388
+ try {
9389
+ content = await readFile24(file, "utf8");
9390
+ } catch {
9391
+ results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
9392
+ continue;
9393
+ }
9394
+ if (!PROBE.test(content))
9395
+ continue;
9396
+ const lines = content.split(`
9397
+ `);
9398
+ let silencedLine = -1;
9399
+ for (let i = 0;i < lines.length; i++) {
9400
+ const line = lines[i];
9401
+ const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
9402
+ if (comment && parseDisableDirective(comment[1])?.includes(check28.id)) {
9403
+ silencedLine = i + 1;
9404
+ continue;
9405
+ }
9406
+ if (i === silencedLine)
9407
+ continue;
9408
+ for (const match of line.matchAll(CHARACTER_PATTERN)) {
9409
+ const char = match[0];
9410
+ const { name, code, fix } = describeCharacter(char);
9411
+ results.push(finding(check28.id, {
9412
+ severity: "warning",
9413
+ code,
9414
+ message: `${name} (${codePointLabel(char)}). ${fix}`,
9415
+ file,
9416
+ line: i + 1,
9417
+ column: match.index + 1,
9418
+ data: { codePoint: char.codePointAt(0) }
9419
+ }));
9420
+ }
9421
+ }
9422
+ }
9423
+ return results;
9424
+ }
9425
+ };
9426
+ registerCheck(check28);
9427
+
9265
9428
  // src/checks/asset/image-file-size.ts
9266
9429
  import { stat as stat4 } from "node:fs/promises";
9267
9430
 
9268
9431
  // src/lib/image-inspect.ts
9269
- import { stat as stat3, readFile as readFile24 } from "node:fs/promises";
9432
+ import { stat as stat3, readFile as readFile25 } from "node:fs/promises";
9270
9433
  var DEFAULT_DPI = 72;
9271
9434
  var cache = new Map;
9272
9435
  async function inspectImage(path13) {
@@ -9279,15 +9442,15 @@ async function inspectImage(path13) {
9279
9442
  const hit = cache.get(path13);
9280
9443
  if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size)
9281
9444
  return hit.info;
9282
- let info2 = null;
9445
+ let info3 = null;
9283
9446
  try {
9284
- const buf = await readFile24(path13);
9285
- info2 = parseImage(buf);
9447
+ const buf = await readFile25(path13);
9448
+ info3 = parseImage(buf);
9286
9449
  } catch {
9287
- info2 = null;
9450
+ info3 = null;
9288
9451
  }
9289
- cache.set(path13, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
9290
- return info2;
9452
+ cache.set(path13, { mtimeMs: st.mtimeMs, size: st.size, info: info3 });
9453
+ return info3;
9291
9454
  }
9292
9455
  function parseImage(b) {
9293
9456
  if (b.length > 8 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
@@ -9486,7 +9649,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
9486
9649
  }
9487
9650
 
9488
9651
  // src/checks/asset/image-file-size.ts
9489
- var check28 = {
9652
+ var check29 = {
9490
9653
  id: "asset.image.file-size",
9491
9654
  name: "Image File Size",
9492
9655
  description: "Checks that image files do not exceed the maximum size limit",
@@ -9503,18 +9666,18 @@ var check28 = {
9503
9666
  const results = [];
9504
9667
  for (const file of files) {
9505
9668
  try {
9506
- const info2 = await stat4(file);
9507
- if (info2.size > maxSize) {
9508
- const sizeMb = (info2.size / 1e6).toFixed(1);
9669
+ const info3 = await stat4(file);
9670
+ if (info3.size > maxSize) {
9671
+ const sizeMb = (info3.size / 1e6).toFixed(1);
9509
9672
  const maxMb = (maxSize / 1e6).toFixed(1);
9510
- results.push(finding(check28.id, {
9673
+ results.push(finding(check29.id, {
9511
9674
  severity: "warning",
9512
9675
  message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
9513
9676
  file
9514
9677
  }));
9515
9678
  }
9516
9679
  } catch {
9517
- results.push(inspectionFailed(check28.id, `Could not stat image file: ${file}`, {
9680
+ results.push(inspectionFailed(check29.id, `Could not stat image file: ${file}`, {
9518
9681
  file
9519
9682
  }));
9520
9683
  }
@@ -9522,10 +9685,10 @@ var check28 = {
9522
9685
  return results;
9523
9686
  }
9524
9687
  };
9525
- registerCheck(check28);
9688
+ registerCheck(check29);
9526
9689
 
9527
9690
  // src/checks/asset/image-resolution.ts
9528
- var check29 = {
9691
+ var check30 = {
9529
9692
  id: "asset.image.resolution",
9530
9693
  name: "Image Resolution",
9531
9694
  description: "Checks source image DPI from embedded density metadata",
@@ -9541,13 +9704,13 @@ var check29 = {
9541
9704
  return [];
9542
9705
  const results = [];
9543
9706
  for (const file of files) {
9544
- const info2 = await inspectImage(file);
9545
- if (!info2)
9707
+ const info3 = await inspectImage(file);
9708
+ if (!info3)
9546
9709
  continue;
9547
- const { xDpi, yDpi } = info2;
9710
+ const { xDpi, yDpi } = info3;
9548
9711
  if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
9549
9712
  results.push({
9550
- checkId: check29.id,
9713
+ checkId: check30.id,
9551
9714
  severity: "warning",
9552
9715
  message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
9553
9716
  file
@@ -9557,7 +9720,7 @@ var check29 = {
9557
9720
  return results;
9558
9721
  }
9559
9722
  };
9560
- registerCheck(check29);
9723
+ registerCheck(check30);
9561
9724
 
9562
9725
  // src/checks/asset/image-color-space.ts
9563
9726
  var LABEL = {
@@ -9565,7 +9728,7 @@ var LABEL = {
9565
9728
  gray: "Gray",
9566
9729
  cmyk: "CMYK"
9567
9730
  };
9568
- var check30 = {
9731
+ var check31 = {
9569
9732
  id: "asset.image.color-space",
9570
9733
  name: "Image Color Space",
9571
9734
  description: "Validates image color spaces against allowed list",
@@ -9582,11 +9745,11 @@ var check30 = {
9582
9745
  const allowedLower = new Set(allowed.map((s) => s.toLowerCase()));
9583
9746
  const results = [];
9584
9747
  for (const file of files) {
9585
- const info2 = await inspectImage(file);
9586
- const cs = info2?.colorSpace;
9748
+ const info3 = await inspectImage(file);
9749
+ const cs = info3?.colorSpace;
9587
9750
  if (cs && !allowedLower.has(cs)) {
9588
9751
  results.push({
9589
- checkId: check30.id,
9752
+ checkId: check31.id,
9590
9753
  severity: "warning",
9591
9754
  message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
9592
9755
  file
@@ -9596,10 +9759,10 @@ var check30 = {
9596
9759
  return results;
9597
9760
  }
9598
9761
  };
9599
- registerCheck(check30);
9762
+ registerCheck(check31);
9600
9763
 
9601
9764
  // src/checks/asset/image-alpha.ts
9602
- var check31 = {
9765
+ var check32 = {
9603
9766
  id: "asset.image.alpha-channel",
9604
9767
  name: "Image Alpha Channel",
9605
9768
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -9614,10 +9777,10 @@ var check31 = {
9614
9777
  return [];
9615
9778
  const results = [];
9616
9779
  for (const file of files) {
9617
- const info2 = await inspectImage(file);
9618
- if (info2?.hasAlpha) {
9780
+ const info3 = await inspectImage(file);
9781
+ if (info3?.hasAlpha) {
9619
9782
  results.push({
9620
- checkId: check31.id,
9783
+ checkId: check32.id,
9621
9784
  severity: "warning",
9622
9785
  message: "Image contains alpha channel, which may cause print issues",
9623
9786
  file
@@ -9627,10 +9790,10 @@ var check31 = {
9627
9790
  return results;
9628
9791
  }
9629
9792
  };
9630
- registerCheck(check31);
9793
+ registerCheck(check32);
9631
9794
 
9632
9795
  // src/checks/asset/image-tac.ts
9633
- var check32 = {
9796
+ var check33 = {
9634
9797
  id: "asset.image.tac-raster",
9635
9798
  name: "Image TAC (Raster)",
9636
9799
  description: "Rasterizes and checks TAC per image using Ghostscript",
@@ -9649,7 +9812,7 @@ var check32 = {
9649
9812
  const ghostscript = await resolveGhostscript();
9650
9813
  if (!ghostscript) {
9651
9814
  return [
9652
- inspectionFailed(check32.id, "Could not inspect image ink coverage: Ghostscript executable not found")
9815
+ inspectionFailed(check33.id, "Could not inspect image ink coverage: Ghostscript executable not found")
9653
9816
  ];
9654
9817
  }
9655
9818
  for (const file of files) {
@@ -9667,7 +9830,7 @@ var check32 = {
9667
9830
  if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
9668
9831
  const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
9669
9832
  if (tac > maxTac) {
9670
- results.push(finding(check32.id, {
9833
+ results.push(finding(check33.id, {
9671
9834
  severity: "warning",
9672
9835
  message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
9673
9836
  file,
@@ -9679,16 +9842,16 @@ var check32 = {
9679
9842
  }
9680
9843
  }
9681
9844
  } catch {
9682
- results.push(inspectionFailed(check32.id, `Could not inspect image ink coverage: ${file}`, { file }));
9845
+ results.push(inspectionFailed(check33.id, `Could not inspect image ink coverage: ${file}`, { file }));
9683
9846
  }
9684
9847
  }
9685
9848
  return results;
9686
9849
  }
9687
9850
  };
9688
- registerCheck(check32);
9851
+ registerCheck(check33);
9689
9852
 
9690
9853
  // src/checks/asset/approved-fonts.ts
9691
- var check33 = {
9854
+ var check34 = {
9692
9855
  id: "asset.font.approved-files",
9693
9856
  name: "Approved Font Files",
9694
9857
  description: "Checks font files against the approved file patterns",
@@ -9727,7 +9890,7 @@ var check33 = {
9727
9890
  for (const font of allFonts) {
9728
9891
  if (!approvedFonts.has(font)) {
9729
9892
  results.push({
9730
- checkId: check33.id,
9893
+ checkId: check34.id,
9731
9894
  severity: "warning",
9732
9895
  message: "Font file not in approved list",
9733
9896
  file: font
@@ -9737,7 +9900,7 @@ var check33 = {
9737
9900
  return results;
9738
9901
  }
9739
9902
  };
9740
- registerCheck(check33);
9903
+ registerCheck(check34);
9741
9904
 
9742
9905
  // src/checks/asset/font-license.ts
9743
9906
  import { existsSync as existsSync11 } from "node:fs";
@@ -9752,7 +9915,7 @@ var LICENSE_NAMES = [
9752
9915
  "OFL-1.1.txt",
9753
9916
  "COPYING"
9754
9917
  ];
9755
- var check34 = {
9918
+ var check35 = {
9756
9919
  id: "asset.font.license",
9757
9920
  name: "Font License",
9758
9921
  description: "Checks for license files in font directories",
@@ -9779,7 +9942,7 @@ var check34 = {
9779
9942
  const hasLicense = LICENSE_NAMES.some((name) => existsSync11(resolve7(fontDir, name)));
9780
9943
  if (!hasLicense) {
9781
9944
  results.push({
9782
- checkId: check34.id,
9945
+ checkId: check35.id,
9783
9946
  severity: "warning",
9784
9947
  message: `No font license file found in directory`,
9785
9948
  file: fontDir
@@ -9789,10 +9952,10 @@ var check34 = {
9789
9952
  return results;
9790
9953
  }
9791
9954
  };
9792
- registerCheck(check34);
9955
+ registerCheck(check35);
9793
9956
 
9794
9957
  // src/checks/heuristic/text-density.ts
9795
- var check35 = {
9958
+ var check36 = {
9796
9959
  id: "heuristic.whitespace.text-density",
9797
9960
  name: "Text Density",
9798
9961
  description: "Checks characters-per-page ratio",
@@ -9821,7 +9984,7 @@ var check35 = {
9821
9984
  });
9822
9985
  if (lowPages.length > 0) {
9823
9986
  results.push({
9824
- checkId: check35.id,
9987
+ checkId: check36.id,
9825
9988
  severity: "info",
9826
9989
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
9827
9990
  file: ctx.pdfPath
@@ -9829,7 +9992,7 @@ var check35 = {
9829
9992
  }
9830
9993
  if (highPages.length > 0) {
9831
9994
  results.push({
9832
- checkId: check35.id,
9995
+ checkId: check36.id,
9833
9996
  severity: "info",
9834
9997
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
9835
9998
  file: ctx.pdfPath
@@ -9838,11 +10001,11 @@ var check35 = {
9838
10001
  return results;
9839
10002
  }
9840
10003
  };
9841
- registerCheck(check35);
10004
+ registerCheck(check36);
9842
10005
 
9843
10006
  // src/checks/heuristic/section-density.ts
9844
- import { readFile as readFile25 } from "node:fs/promises";
9845
- var check36 = {
10007
+ import { readFile as readFile26 } from "node:fs/promises";
10008
+ var check37 = {
9846
10009
  id: "heuristic.chunking.section-density",
9847
10010
  name: "Section Density",
9848
10011
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -9858,7 +10021,7 @@ var check36 = {
9858
10021
  const results = [];
9859
10022
  for (const file of files) {
9860
10023
  try {
9861
- const content = await readFile25(file, "utf8");
10024
+ const content = await readFile26(file, "utf8");
9862
10025
  const lines = content.split(`
9863
10026
  `);
9864
10027
  let currentSectionStart = 0;
@@ -9868,7 +10031,7 @@ var check36 = {
9868
10031
  const line = lines[i];
9869
10032
  if (/^#{1,6}\s/.test(line)) {
9870
10033
  if (paragraphCount > maxParas) {
9871
- results.push(finding(check36.id, {
10034
+ results.push(finding(check37.id, {
9872
10035
  severity: "info",
9873
10036
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
9874
10037
  file,
@@ -9888,7 +10051,7 @@ var check36 = {
9888
10051
  }
9889
10052
  }
9890
10053
  if (paragraphCount > maxParas) {
9891
- results.push(finding(check36.id, {
10054
+ results.push(finding(check37.id, {
9892
10055
  severity: "info",
9893
10056
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
9894
10057
  file,
@@ -9896,7 +10059,7 @@ var check36 = {
9896
10059
  }));
9897
10060
  }
9898
10061
  } catch {
9899
- results.push(inspectionFailed(check36.id, `Could not read source file: ${file}`, {
10062
+ results.push(inspectionFailed(check37.id, `Could not read source file: ${file}`, {
9900
10063
  file
9901
10064
  }));
9902
10065
  }
@@ -9904,10 +10067,10 @@ var check36 = {
9904
10067
  return results;
9905
10068
  }
9906
10069
  };
9907
- registerCheck(check36);
10070
+ registerCheck(check37);
9908
10071
 
9909
10072
  // src/checks/heuristic/layer-count.ts
9910
- var check37 = {
10073
+ var check38 = {
9911
10074
  id: "heuristic.decoration.layer-count",
9912
10075
  name: "Layer Count",
9913
10076
  description: "Counts image objects per page",
@@ -9932,7 +10095,7 @@ var check37 = {
9932
10095
  heavyPages.sort((a, b) => a - b);
9933
10096
  return [
9934
10097
  {
9935
- checkId: check37.id,
10098
+ checkId: check38.id,
9936
10099
  severity: "info",
9937
10100
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
9938
10101
  file: ctx.pdfPath
@@ -9942,10 +10105,10 @@ var check37 = {
9942
10105
  return [];
9943
10106
  }
9944
10107
  };
9945
- registerCheck(check37);
10108
+ registerCheck(check38);
9946
10109
 
9947
10110
  // src/checks/heuristic/placement-variance.ts
9948
- var check38 = {
10111
+ var check39 = {
9949
10112
  id: "heuristic.layout.placement-variance",
9950
10113
  name: "Placement Variance",
9951
10114
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -9963,7 +10126,7 @@ var check38 = {
9963
10126
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
9964
10127
  return [
9965
10128
  {
9966
- checkId: check38.id,
10129
+ checkId: check39.id,
9967
10130
  severity: "info",
9968
10131
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
9969
10132
  file: ctx.pdfPath
@@ -9971,7 +10134,7 @@ var check38 = {
9971
10134
  ];
9972
10135
  }
9973
10136
  };
9974
- registerCheck(check38);
10137
+ registerCheck(check39);
9975
10138
 
9976
10139
  // src/checks/runner.ts
9977
10140
  function getCheckSeverityOverride(checkId, config) {
@@ -10001,16 +10164,16 @@ async function runChecks(ctx, opts = {}) {
10001
10164
  }
10002
10165
  const release = retainPdfCache();
10003
10166
  try {
10004
- for (const check39 of checks2) {
10167
+ for (const check40 of checks2) {
10005
10168
  try {
10006
- let results = await check39.run(ctx);
10007
- const severityOverride = getCheckSeverityOverride(check39.id, ctx.config);
10169
+ let results = await check40.run(ctx);
10170
+ const severityOverride = getCheckSeverityOverride(check40.id, ctx.config);
10008
10171
  if (severityOverride) {
10009
10172
  for (const r of results) {
10010
10173
  r.severity = severityOverride;
10011
10174
  }
10012
10175
  }
10013
- const lintLevel = ruleLevel(ctx.config.lint.rules, check39.id);
10176
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check40.id);
10014
10177
  if (lintLevel === "off")
10015
10178
  results = [];
10016
10179
  else if (lintLevel) {
@@ -10018,15 +10181,15 @@ async function runChecks(ctx, opts = {}) {
10018
10181
  r.severity = lintLevel === "warn" ? "warning" : "error";
10019
10182
  }
10020
10183
  if (results.length === 0) {
10021
- passed.push(check39.id);
10184
+ passed.push(check40.id);
10022
10185
  } else {
10023
10186
  allResults.push(...results);
10024
10187
  }
10025
10188
  } catch (err) {
10026
10189
  allResults.push({
10027
- checkId: check39.id,
10190
+ checkId: check40.id,
10028
10191
  severity: "error",
10029
- message: `Check "${check39.id}" threw: ${err instanceof Error ? err.message : String(err)}`
10192
+ message: `Check "${check40.id}" threw: ${err instanceof Error ? err.message : String(err)}`
10030
10193
  });
10031
10194
  }
10032
10195
  }
@@ -10066,15 +10229,15 @@ function emptyReport() {
10066
10229
  async function checkToolAvailability(config, opts = {}) {
10067
10230
  const { checks: checks2 } = selectChecks(opts, config);
10068
10231
  const toolToChecks = new Map;
10069
- for (const check39 of checks2) {
10070
- if (!check39.requiredTools?.length)
10232
+ for (const check40 of checks2) {
10233
+ if (!check40.requiredTools?.length)
10071
10234
  continue;
10072
- for (const tool of check39.requiredTools) {
10235
+ for (const tool of check40.requiredTools) {
10073
10236
  const existing = toolToChecks.get(tool);
10074
10237
  if (existing) {
10075
- existing.push(check39.id);
10238
+ existing.push(check40.id);
10076
10239
  } else {
10077
- toolToChecks.set(tool, [check39.id]);
10240
+ toolToChecks.set(tool, [check40.id]);
10078
10241
  }
10079
10242
  }
10080
10243
  }
@@ -10090,11 +10253,11 @@ async function checkToolAvailability(config, opts = {}) {
10090
10253
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
10091
10254
  const missingSet = new Set(missing);
10092
10255
  const skippedChecks = [];
10093
- for (const check39 of checks2) {
10094
- if (!check39.requiredTools?.length)
10256
+ for (const check40 of checks2) {
10257
+ if (!check40.requiredTools?.length)
10095
10258
  continue;
10096
- if (check39.requiredTools.some((t) => missingSet.has(t))) {
10097
- skippedChecks.push(check39.id);
10259
+ if (check40.requiredTools.some((t) => missingSet.has(t))) {
10260
+ skippedChecks.push(check40.id);
10098
10261
  }
10099
10262
  }
10100
10263
  return { available, missing, skippedChecks, toolToChecks };
@@ -10367,7 +10530,7 @@ async function executeValidation(args) {
10367
10530
  };
10368
10531
  if (args.fix) {
10369
10532
  const selected = selectChecks(runnerOptions, config).checks;
10370
- if (selected.some((check39) => check39.id === MARKDOWNLINT_CHECK_ID)) {
10533
+ if (selected.some((check40) => check40.id === MARKDOWNLINT_CHECK_ID)) {
10371
10534
  await applyMarkdownlintFixes(context);
10372
10535
  } else {
10373
10536
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -10850,7 +11013,7 @@ async function runBuild(opts) {
10850
11013
  import path16 from "path";
10851
11014
  import { tmpdir } from "os";
10852
11015
  import { randomBytes as randomBytes2 } from "crypto";
10853
- import { readdir as readdir5, readFile as readFile26, writeFile as writeFile6 } from "node:fs/promises";
11016
+ import { readdir as readdir5, readFile as readFile27, writeFile as writeFile6 } from "node:fs/promises";
10854
11017
 
10855
11018
  // src/utils/file-utils.ts
10856
11019
  import { promises as fs3 } from "fs";
@@ -11402,7 +11565,7 @@ async function cleanupOrphanTempDirs() {
11402
11565
  const dir = path16.join(TEMP_DIR_BASE, entry);
11403
11566
  const pidFile = path16.join(dir, PID_FILE_NAME);
11404
11567
  try {
11405
- const raw = await readFile26(pidFile, "utf8");
11568
+ const raw = await readFile27(pidFile, "utf8");
11406
11569
  const pid = parseInt(raw.trim(), 10);
11407
11570
  if (!isProcessAlive(pid)) {
11408
11571
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -11509,7 +11672,7 @@ function createServerState(inputPath, tempDir, config, options) {
11509
11672
  import http from "node:http";
11510
11673
  import net from "node:net";
11511
11674
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
11512
- import { readFile as readFile27, stat as stat5 } from "node:fs/promises";
11675
+ import { readFile as readFile28, stat as stat5 } from "node:fs/promises";
11513
11676
  import path17 from "path";
11514
11677
  import { WebSocket, WebSocketServer } from "ws";
11515
11678
 
@@ -11608,7 +11771,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
11608
11771
  }
11609
11772
  let data;
11610
11773
  try {
11611
- data = await readFile27(filePath);
11774
+ data = await readFile28(filePath);
11612
11775
  } catch {
11613
11776
  res.writeHead(404);
11614
11777
  res.end("Not Found");
@@ -11657,7 +11820,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11657
11820
  let data;
11658
11821
  if (!validator || validator.stamp !== stamp) {
11659
11822
  try {
11660
- data = await readFile27(absPath);
11823
+ data = await readFile28(absPath);
11661
11824
  } catch {
11662
11825
  await serveStatic(absPath, res);
11663
11826
  return;
@@ -11969,7 +12132,7 @@ async function startPreviewServer(options) {
11969
12132
  import { platform, arch, release } from "node:os";
11970
12133
 
11971
12134
  // src/lib/remote-auth/token-store.ts
11972
- import { chmod, mkdir as mkdir7, readFile as readFile28, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
12135
+ import { chmod, mkdir as mkdir7, readFile as readFile29, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
11973
12136
  import os3 from "node:os";
11974
12137
  import path18 from "node:path";
11975
12138
  function credentialHostKey(hostOrUrl) {
@@ -12013,7 +12176,7 @@ class FileTokenStore {
12013
12176
  async read() {
12014
12177
  let raw;
12015
12178
  try {
12016
- raw = await readFile28(this.filePath, "utf8");
12179
+ raw = await readFile29(this.filePath, "utf8");
12017
12180
  } catch (e) {
12018
12181
  if (e?.code === "ENOENT") {
12019
12182
  return { version: 1, credentials: {} };
@@ -12213,13 +12376,13 @@ async function getSystemDiagnostics() {
12213
12376
  };
12214
12377
  }
12215
12378
  // src/lib/project-scaffold.ts
12216
- 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";
12379
+ import { access, copyFile, cp as cp2, mkdir as mkdir9, readdir as readdir6, readFile as readFile31, stat as stat6, writeFile as writeFile9 } from "node:fs/promises";
12217
12380
  import { constants as FS, existsSync as existsSync14 } from "node:fs";
12218
12381
  import path20 from "node:path";
12219
12382
 
12220
12383
  // src/lib/page-size.ts
12221
12384
  import { existsSync as existsSync13 } from "node:fs";
12222
- import { mkdir as mkdir8, readFile as readFile29, writeFile as writeFile8 } from "node:fs/promises";
12385
+ import { mkdir as mkdir8, readFile as readFile30, writeFile as writeFile8 } from "node:fs/promises";
12223
12386
  import path19 from "node:path";
12224
12387
  import postcss5 from "postcss";
12225
12388
  import { Scalar as Scalar2 } from "yaml";
@@ -12324,7 +12487,7 @@ var relDisplay2 = (projectDir, abs) => path19.relative(projectDir, abs).split(pa
12324
12487
  async function findCssSize(projectDir, sheets) {
12325
12488
  let found = null;
12326
12489
  for (const file of sheets) {
12327
- const size = readPageSizeFromCss(await readFile29(file, "utf8"));
12490
+ const size = readPageSizeFromCss(await readFile30(file, "utf8"));
12328
12491
  if (size)
12329
12492
  found = { file, size };
12330
12493
  }
@@ -12351,7 +12514,7 @@ async function setPageSetup(projectDir, choice) {
12351
12514
  const found = await findCssSize(projectDir, sheets);
12352
12515
  const newSheet = path19.join(projectDir, "styles", "book.css");
12353
12516
  const target = found?.file ?? sheets[sheets.length - 1] ?? newSheet;
12354
- const before = existsSync13(target) ? await readFile29(target, "utf8") : "";
12517
+ const before = existsSync13(target) ? await readFile30(target, "utf8") : "";
12355
12518
  let after;
12356
12519
  try {
12357
12520
  after = setPageSizeInCss(before, size);
@@ -12641,7 +12804,7 @@ styles:
12641
12804
  async function firstSourceFile(manifestPath) {
12642
12805
  let text;
12643
12806
  try {
12644
- text = await readFile30(manifestPath, "utf8");
12807
+ text = await readFile31(manifestPath, "utf8");
12645
12808
  } catch {
12646
12809
  return;
12647
12810
  }
@@ -12653,14 +12816,14 @@ async function firstSourceFile(manifestPath) {
12653
12816
  return m ? m[1].trim() : undefined;
12654
12817
  }
12655
12818
  async function fillTemplateFile(filePath, substitutions) {
12656
- let text = await readFile30(filePath, "utf8");
12819
+ let text = await readFile31(filePath, "utf8");
12657
12820
  for (const [token, value] of Object.entries(substitutions)) {
12658
12821
  text = text.split(token).join(value);
12659
12822
  }
12660
12823
  await writeFile9(filePath, text, "utf8");
12661
12824
  }
12662
12825
  // src/lib/extension-scaffold.ts
12663
- import { access as access2, mkdir as mkdir10, readFile as readFile31, writeFile as writeFile10 } from "node:fs/promises";
12826
+ import { access as access2, mkdir as mkdir10, readFile as readFile32, writeFile as writeFile10 } from "node:fs/promises";
12664
12827
  import { constants as FS2 } from "node:fs";
12665
12828
  import path21 from "node:path";
12666
12829
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -12777,7 +12940,7 @@ async function scaffoldExtension(options) {
12777
12940
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
12778
12941
  const dest = path21.join(extensionDir, relDest);
12779
12942
  await mkdir10(path21.dirname(dest), { recursive: true });
12780
- const text = await readFile31(src, "utf8");
12943
+ const text = await readFile32(src, "utf8");
12781
12944
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
12782
12945
  await writeFile10(dest, substitute(text, table), "utf8");
12783
12946
  written.push(relDest);
@@ -12803,7 +12966,7 @@ function substitute(text, table) {
12803
12966
  return out;
12804
12967
  }
12805
12968
  // src/lib/project-templates.ts
12806
- import { cp as cp3, mkdir as mkdir11, readFile as readFile32, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
12969
+ import { cp as cp3, mkdir as mkdir11, readFile as readFile33, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
12807
12970
  import { constants as FS3, existsSync as existsSync15 } from "node:fs";
12808
12971
  import { access as access3 } from "node:fs/promises";
12809
12972
  import path22 from "node:path";
@@ -12815,7 +12978,7 @@ var BUILT_IN_TEMPLATE_IDS = [
12815
12978
  ];
12816
12979
  async function readTemplateChoices(manifestPath) {
12817
12980
  try {
12818
- const text = await readFile32(manifestPath, "utf8");
12981
+ const text = await readFile33(manifestPath, "utf8");
12819
12982
  const doc = parseDocument2(text);
12820
12983
  const out = {};
12821
12984
  const preset = doc.get("preset");
@@ -12997,7 +13160,7 @@ async function saveProjectAsTemplate(options) {
12997
13160
  async function retokeniseManifest(manifestPath) {
12998
13161
  let text;
12999
13162
  try {
13000
- text = await readFile32(manifestPath, "utf8");
13163
+ text = await readFile33(manifestPath, "utf8");
13001
13164
  } catch {
13002
13165
  return;
13003
13166
  }
@@ -13018,7 +13181,7 @@ async function listCustomTemplates(templatesRoot) {
13018
13181
  const dir = path22.join(templatesRoot, entry.name);
13019
13182
  let label = prettify(entry.name);
13020
13183
  try {
13021
- const meta = JSON.parse(await readFile32(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
13184
+ const meta = JSON.parse(await readFile33(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
13022
13185
  if (typeof meta.label === "string" && meta.label)
13023
13186
  label = meta.label;
13024
13187
  } catch {}
@@ -13044,7 +13207,7 @@ async function importTemplateFromFolder(options) {
13044
13207
  return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
13045
13208
  }
13046
13209
  // src/lib/snippets.ts
13047
- import { readdir as readdir8, readFile as readFile33, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
13210
+ import { readdir as readdir8, readFile as readFile34, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
13048
13211
  import path23 from "node:path";
13049
13212
  var SNIPPETS_DIR = "snippets";
13050
13213
  var LEVEL_RANK = { project: 0, extension: 1, core: 2 };
@@ -13085,7 +13248,7 @@ async function scanSnippetFiles(dir) {
13085
13248
  continue;
13086
13249
  let body = "";
13087
13250
  try {
13088
- body = await readFile33(path23.join(dir, fileName), "utf8");
13251
+ body = await readFile34(path23.join(dir, fileName), "utf8");
13089
13252
  } catch {
13090
13253
  continue;
13091
13254
  }
@@ -13174,7 +13337,7 @@ async function collectLibrary(projectDir) {
13174
13337
  listed.component = name;
13175
13338
  continue;
13176
13339
  }
13177
- const body = await readFile33(path23.join(entry.dir, rel), "utf8").catch(() => null);
13340
+ const body = await readFile34(path23.join(entry.dir, rel), "utf8").catch(() => null);
13178
13341
  if (body === null)
13179
13342
  continue;
13180
13343
  snippets2.push({
@@ -13243,7 +13406,7 @@ async function listLocationVariants(projectDir) {
13243
13406
  }
13244
13407
  // src/lib/extension-import.ts
13245
13408
  import { existsSync as existsSync16 } from "node:fs";
13246
- import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile34, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
13409
+ import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile35, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
13247
13410
  import { tmpdir as tmpdir2 } from "node:os";
13248
13411
  import path24 from "node:path";
13249
13412
  import { unzipSync } from "fflate";
@@ -13316,7 +13479,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13316
13479
  if (!existsSync16(cssPath)) {
13317
13480
  throw new Error("A theme must contain a theme.css file.");
13318
13481
  }
13319
- const css = await readFile34(cssPath, "utf8");
13482
+ const css = await readFile35(cssPath, "utf8");
13320
13483
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
13321
13484
  if (reject) {
13322
13485
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -13344,7 +13507,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13344
13507
  for (let i = 0;i < extraSheets.length; i++) {
13345
13508
  const rel = extraSheets[i];
13346
13509
  const sheetPath = extraSheetPaths[i];
13347
- const sheetCss = await readFile34(sheetPath, "utf8");
13510
+ const sheetCss = await readFile35(sheetPath, "utf8");
13348
13511
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
13349
13512
  if (sheetReject) {
13350
13513
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -13377,7 +13540,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
13377
13540
  let pkg = {};
13378
13541
  if (metaExists) {
13379
13542
  try {
13380
- const parsed = JSON.parse(await readFile34(file, "utf8"));
13543
+ const parsed = JSON.parse(await readFile35(file, "utf8"));
13381
13544
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
13382
13545
  pkg = parsed;
13383
13546
  }
@@ -13479,11 +13642,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
13479
13642
  async function importExtensionFromFile(projectDir, filePath) {
13480
13643
  const ext = path24.extname(filePath).toLowerCase();
13481
13644
  if (ext === ".zip") {
13482
- const buf = await readFile34(filePath);
13645
+ const buf = await readFile35(filePath);
13483
13646
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
13484
13647
  }
13485
13648
  if (ext === ".css") {
13486
- const css = await readFile34(filePath, "utf8");
13649
+ const css = await readFile35(filePath, "utf8");
13487
13650
  return importExtensionFromCssText(projectDir, css, prettify(path24.basename(filePath, ".css")));
13488
13651
  }
13489
13652
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -13948,8 +14111,8 @@ async function readGoogleApiError(res) {
13948
14111
  }
13949
14112
  var RATE_LIMIT_REASONS = /^(dailyLimitExceeded|userRateLimitExceeded|rateLimitExceeded|quotaExceeded)$/i;
13950
14113
  var PERMISSION_REASONS = /^(insufficientPermissions|insufficientFilePermissions|forbidden|PERMISSION_DENIED)$/i;
13951
- function hintFor(info2) {
13952
- const reason = info2.reason ?? "";
14114
+ function hintFor(info3) {
14115
+ const reason = info3.reason ?? "";
13953
14116
  if (/^accessNotConfigured$/i.test(reason)) {
13954
14117
  return "Google Drive publishing isn't fully set up on this build: the Google Drive API isn't enabled for the app's Google Cloud project, so a maintainer needs to enable it (the link is in Google's message).";
13955
14118
  }
@@ -13959,18 +14122,18 @@ function hintFor(info2) {
13959
14122
  if (RATE_LIMIT_REASONS.test(reason)) {
13960
14123
  return "Google Drive is rate-limiting requests right now. Wait a minute and try again.";
13961
14124
  }
13962
- if (PERMISSION_REASONS.test(reason) || info2.status === 401) {
14125
+ if (PERMISSION_REASONS.test(reason) || info3.status === 401) {
13963
14126
  return "This Google Drive connection doesn't have permission for that — disconnect and connect Google Drive again to re-approve access.";
13964
14127
  }
13965
14128
  return;
13966
14129
  }
13967
- function googleApiFailure(what, info2) {
13968
- const parts = [`${what} (HTTP ${info2.status}${info2.reason ? `, ${info2.reason}` : ""}).`];
13969
- const hint = hintFor(info2);
14130
+ function googleApiFailure(what, info3) {
14131
+ const parts = [`${what} (HTTP ${info3.status}${info3.reason ? `, ${info3.reason}` : ""}).`];
14132
+ const hint = hintFor(info3);
13970
14133
  if (hint)
13971
14134
  parts.push(hint);
13972
- if (info2.message)
13973
- parts.push(`Google said: "${info2.message}"`);
14135
+ if (info3.message)
14136
+ parts.push(`Google said: "${info3.message}"`);
13974
14137
  let text = parts.join(" ");
13975
14138
  if (!/\bgoogle\b/i.test(text))
13976
14139
  text = `Google Drive: ${text}`;
@@ -14224,13 +14387,13 @@ function publishCredentialKey(host, account) {
14224
14387
  const a = (account ?? "").trim();
14225
14388
  return a ? `${host}#${a}` : host;
14226
14389
  }
14227
- async function resolvePublishCredential(info2, deps, account = deps.credentialAccount) {
14390
+ async function resolvePublishCredential(info3, deps, account = deps.credentialAccount) {
14228
14391
  const env = deps.env ?? process.env;
14229
- const fromEnv = info2.credential.envVar ? env[info2.credential.envVar]?.trim() : undefined;
14392
+ const fromEnv = info3.credential.envVar ? env[info3.credential.envVar]?.trim() : undefined;
14230
14393
  if (fromEnv) {
14231
14394
  return {
14232
14395
  credential: {
14233
- host: info2.credential.host,
14396
+ host: info3.credential.host,
14234
14397
  kind: "token",
14235
14398
  token: fromEnv,
14236
14399
  createdAt: 0
@@ -14238,21 +14401,21 @@ async function resolvePublishCredential(info2, deps, account = deps.credentialAc
14238
14401
  source: "env"
14239
14402
  };
14240
14403
  }
14241
- const stored = await deps.tokenStore.get(publishCredentialKey(info2.credential.host, account));
14404
+ const stored = await deps.tokenStore.get(publishCredentialKey(info3.credential.host, account));
14242
14405
  return stored ? { credential: stored, source: "store" } : null;
14243
14406
  }
14244
- async function publishConnectionStatus(info2, deps, account = deps.credentialAccount) {
14245
- if (!info2.credential.required)
14407
+ async function publishConnectionStatus(info3, deps, account = deps.credentialAccount) {
14408
+ if (!info3.credential.required)
14246
14409
  return { connected: true };
14247
- const resolved = await resolvePublishCredential(info2, deps, account);
14410
+ const resolved = await resolvePublishCredential(info3, deps, account);
14248
14411
  return resolved ? { connected: true, source: resolved.source } : { connected: false };
14249
14412
  }
14250
- async function listPublishAccounts(info2, deps) {
14251
- const wantHost = info2.credential.host.trim().toLowerCase();
14413
+ async function listPublishAccounts(info3, deps) {
14414
+ const wantHost = info3.credential.host.trim().toLowerCase();
14252
14415
  const all = await deps.tokenStore.list();
14253
14416
  return all.filter((c) => c.host.trim().toLowerCase() === wantHost).map((c) => ({
14254
14417
  account: (c.username ?? "").trim(),
14255
- label: c.label ?? (c.username || info2.label),
14418
+ label: c.label ?? (c.username || info3.label),
14256
14419
  createdAt: c.createdAt
14257
14420
  }));
14258
14421
  }
@@ -15924,7 +16087,7 @@ async function commandExists(cmd, runCommand = defaultCommandRunner, env) {
15924
16087
 
15925
16088
  // src/lib/publish/providers/azure-swa.ts
15926
16089
  var AZURE_SWA_HOST = "azure-swa";
15927
- var info2 = {
16090
+ var info3 = {
15928
16091
  id: "azure-swa",
15929
16092
  label: "Azure Static Web Apps",
15930
16093
  kind: "api",
@@ -15949,9 +16112,9 @@ async function resolveSwaCommand(req) {
15949
16112
  }
15950
16113
  var SWA_INSTALL_HINT = "Install the Azure SWA CLI first: `npm install -g @azure/static-web-apps-cli` " + "(needs Node.js), or set SWA_CLI_PATH to the swa binary.";
15951
16114
  var azureSwaProvider = {
15952
- info: info2,
16115
+ info: info3,
15953
16116
  async authenticate(req) {
15954
- const resolved = await resolvePublishCredential(info2, req.deps);
16117
+ const resolved = await resolvePublishCredential(info3, req.deps);
15955
16118
  if (!resolved) {
15956
16119
  return {
15957
16120
  ok: false,
@@ -15982,7 +16145,7 @@ var azureSwaProvider = {
15982
16145
  return issues;
15983
16146
  },
15984
16147
  async upload(req) {
15985
- const resolved = await resolvePublishCredential(info2, req.deps);
16148
+ const resolved = await resolvePublishCredential(info3, req.deps);
15986
16149
  if (!resolved) {
15987
16150
  throw new Error("No Azure deployment token found. Connect Azure Static Web Apps (or set SWA_CLI_DEPLOYMENT_TOKEN) first.");
15988
16151
  }
@@ -16038,7 +16201,7 @@ async function stageGuidedPackage(req, providerId, listingLines) {
16038
16201
 
16039
16202
  // src/lib/publish/providers/drivethrurpg.ts
16040
16203
  var PUBLISHER_HUB_URL = "https://www.drivethrurpg.com/publisher_hub";
16041
- var info3 = {
16204
+ var info4 = {
16042
16205
  id: "drivethrurpg",
16043
16206
  label: "DriveThruRPG",
16044
16207
  kind: "guided",
@@ -16054,7 +16217,7 @@ var info3 = {
16054
16217
  credential: { required: false, host: "drivethrurpg.com" }
16055
16218
  };
16056
16219
  var drivethrurpgProvider = {
16057
- info: info3,
16220
+ info: info4,
16058
16221
  async authenticate() {
16059
16222
  return { ok: true };
16060
16223
  },
@@ -16095,7 +16258,7 @@ var drivethrurpgProvider = {
16095
16258
  };
16096
16259
 
16097
16260
  // src/lib/publish/providers/gdrive.ts
16098
- import { mkdtemp as mkdtemp3, readFile as readFile35, readdir as readdir11, rm as rm9, stat as stat9, writeFile as writeFile16 } from "node:fs/promises";
16261
+ import { mkdtemp as mkdtemp3, readFile as readFile36, readdir as readdir11, rm as rm9, stat as stat9, writeFile as writeFile16 } from "node:fs/promises";
16099
16262
  import { tmpdir as tmpdir3 } from "node:os";
16100
16263
  import path30 from "node:path";
16101
16264
  import { Zip, ZipDeflate } from "fflate";
@@ -16363,7 +16526,7 @@ async function resumableUpload(fetchImpl, accessToken, opts) {
16363
16526
  // src/lib/publish/providers/gdrive.ts
16364
16527
  var DEFAULT_FOLDER_NAME = "Gutterpress";
16365
16528
  var NOT_CONNECTED_MESSAGE = "Google Drive isn't connected. Run `gutterpress publish --provider gdrive --connect` (or set GDRIVE_REFRESH_TOKEN) first.";
16366
- var info4 = {
16529
+ var info5 = {
16367
16530
  id: "gdrive",
16368
16531
  label: "Google Drive",
16369
16532
  kind: "api",
@@ -16402,7 +16565,7 @@ async function mintAccessToken(resolved, fetchImpl) {
16402
16565
  return accessToken;
16403
16566
  }
16404
16567
  async function getAccessToken(req) {
16405
- const resolved = await resolvePublishCredential(info4, req.deps);
16568
+ const resolved = await resolvePublishCredential(info5, req.deps);
16406
16569
  if (!resolved)
16407
16570
  throw new Error(NOT_CONNECTED_MESSAGE);
16408
16571
  const accessToken = await mintAccessToken(resolved, req.deps.fetch ?? globalThis.fetch);
@@ -16487,12 +16650,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
16487
16650
  await collectZipEntries(root, full, out, onWarn);
16488
16651
  } else if (entry.isFile()) {
16489
16652
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16490
- out[rel] = await readFile35(full);
16653
+ out[rel] = await readFile36(full);
16491
16654
  } else if (entry.isSymbolicLink()) {
16492
16655
  const resolved = await stat9(full).catch(() => null);
16493
16656
  if (resolved?.isFile()) {
16494
16657
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16495
- out[rel] = await readFile35(full);
16658
+ out[rel] = await readFile36(full);
16496
16659
  } else if (resolved?.isDirectory()) {
16497
16660
  await collectZipEntries(root, full, out, onWarn);
16498
16661
  } else {
@@ -16524,9 +16687,9 @@ async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl = writeF
16524
16687
  }
16525
16688
  }
16526
16689
  var gdriveProvider = {
16527
- info: info4,
16690
+ info: info5,
16528
16691
  async authenticate(req) {
16529
- const resolved = await resolvePublishCredential(info4, req.deps);
16692
+ const resolved = await resolvePublishCredential(info5, req.deps);
16530
16693
  if (!resolved)
16531
16694
  return { ok: false, message: NOT_CONNECTED_MESSAGE };
16532
16695
  try {
@@ -16691,7 +16854,7 @@ async function ensureButler(deps) {
16691
16854
  var ITCH_HOST = "itch.io";
16692
16855
  var TARGET_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
16693
16856
  var ITCH_API_TIMEOUT_MS = 15000;
16694
- var info5 = {
16857
+ var info6 = {
16695
16858
  id: "itch",
16696
16859
  label: "itch.io",
16697
16860
  kind: "api",
@@ -16721,9 +16884,9 @@ function itchProjectUrl(target) {
16721
16884
  return `https://${user}.itch.io/${game}`;
16722
16885
  }
16723
16886
  var itchProvider = {
16724
- info: info5,
16887
+ info: info6,
16725
16888
  async authenticate(req) {
16726
- const resolved = await resolvePublishCredential(info5, req.deps);
16889
+ const resolved = await resolvePublishCredential(info6, req.deps);
16727
16890
  if (!resolved) {
16728
16891
  return {
16729
16892
  ok: false,
@@ -16791,7 +16954,7 @@ var itchProvider = {
16791
16954
  },
16792
16955
  async upload(req) {
16793
16956
  const { target, channel } = readConfig2(req);
16794
- const resolved = await resolvePublishCredential(info5, req.deps);
16957
+ const resolved = await resolvePublishCredential(info6, req.deps);
16795
16958
  if (!resolved) {
16796
16959
  throw new Error("No itch.io API key found. Connect itch.io (or set BUTLER_API_KEY) first.");
16797
16960
  }
@@ -16819,7 +16982,7 @@ ${tail}` : ""}`);
16819
16982
 
16820
16983
  // src/lib/publish/providers/kdp.ts
16821
16984
  var KDP_URL = "https://kdp.amazon.com/en_US/bookshelf";
16822
- var info6 = {
16985
+ var info7 = {
16823
16986
  id: "kdp",
16824
16987
  label: "Amazon KDP",
16825
16988
  kind: "guided",
@@ -16829,7 +16992,7 @@ var info6 = {
16829
16992
  credential: { required: false, host: "kdp.amazon.com" }
16830
16993
  };
16831
16994
  var kdpProvider = {
16832
- info: info6,
16995
+ info: info7,
16833
16996
  async authenticate() {
16834
16997
  return { ok: true };
16835
16998
  },
@@ -16874,7 +17037,7 @@ var kdpProvider = {
16874
17037
  import { cp as cp7, mkdir as mkdir20, stat as stat11 } from "node:fs/promises";
16875
17038
  import path32 from "node:path";
16876
17039
  var LOCAL_PUBLISH_DEFAULT_DIR = "dist";
16877
- var info7 = {
17040
+ var info8 = {
16878
17041
  id: "local",
16879
17042
  label: "A folder on this computer",
16880
17043
  kind: "api",
@@ -16891,7 +17054,7 @@ function localPublishDir(req) {
16891
17054
  return path32.resolve(req.project.projectDir, dir);
16892
17055
  }
16893
17056
  var localFolderProvider = {
16894
- info: info7,
17057
+ info: info8,
16895
17058
  async authenticate() {
16896
17059
  return { ok: true };
16897
17060
  },
@@ -16932,7 +17095,7 @@ function requireValidShop(shop) {
16932
17095
  }
16933
17096
  return shop;
16934
17097
  }
16935
- var info8 = {
17098
+ var info9 = {
16936
17099
  id: "shopify",
16937
17100
  label: "Shopify",
16938
17101
  kind: "api",
@@ -16966,7 +17129,7 @@ function readConfig3(req) {
16966
17129
  async function adminGraphQL(req, query, variables = {}) {
16967
17130
  const { shop: rawShop, apiVersion } = readConfig3(req);
16968
17131
  const shop = requireValidShop(rawShop);
16969
- const resolved = await resolvePublishCredential(info8, req.deps);
17132
+ const resolved = await resolvePublishCredential(info9, req.deps);
16970
17133
  if (!resolved) {
16971
17134
  throw new Error("No Shopify access token found. Connect Shopify (or set SHOPIFY_ADMIN_TOKEN) first.");
16972
17135
  }
@@ -17017,9 +17180,9 @@ function userErrorsToMessage(result) {
17017
17180
  return errs?.length ? errs[0].message : null;
17018
17181
  }
17019
17182
  var shopifyProvider = {
17020
- info: info8,
17183
+ info: info9,
17021
17184
  async authenticate(req) {
17022
- const resolved = await resolvePublishCredential(info8, req.deps);
17185
+ const resolved = await resolvePublishCredential(info9, req.deps);
17023
17186
  if (!resolved) {
17024
17187
  return {
17025
17188
  ok: false,
@@ -17153,14 +17316,14 @@ function publishProviderFor(id) {
17153
17316
  }
17154
17317
 
17155
17318
  // src/lib/publish/run-publish.ts
17156
- function resolvePublishFormat(info9, providerConfig) {
17157
- if (info9.formats && info9.formats.length > 0) {
17319
+ function resolvePublishFormat(info10, providerConfig) {
17320
+ if (info10.formats && info10.formats.length > 0) {
17158
17321
  const configured = typeof providerConfig.format === "string" ? providerConfig.format.trim() : "";
17159
- if (configured && info9.formats.includes(configured)) {
17322
+ if (configured && info10.formats.includes(configured)) {
17160
17323
  return configured;
17161
17324
  }
17162
17325
  }
17163
- return info9.format;
17326
+ return info10.format;
17164
17327
  }
17165
17328
  async function resolvePublishRequest(options, deps) {
17166
17329
  const provider = publishProviderFor(options.providerId);
@@ -17309,17 +17472,17 @@ function overlayStore(inner, key, candidate) {
17309
17472
  }
17310
17473
  async function connectPublishProvider(options, deps) {
17311
17474
  const provider = publishProviderFor(options.providerId);
17312
- const info9 = provider.info;
17313
- if (!info9.credential.required) {
17314
- throw new Error(`${info9.label} needs no API key — just publish when you're ready.`);
17475
+ const info10 = provider.info;
17476
+ if (!info10.credential.required) {
17477
+ throw new Error(`${info10.label} needs no API key — just publish when you're ready.`);
17315
17478
  }
17316
- if (info9.credential.connect === "oauth") {
17317
- throw new Error(`${info9.label} connects through your browser, not a pasted key — run ` + `"gutterpress publish --provider ${info9.id} --connect" or use the desktop app's Connect button.`);
17479
+ if (info10.credential.connect === "oauth") {
17480
+ throw new Error(`${info10.label} connects through your browser, not a pasted key — run ` + `"gutterpress publish --provider ${info10.id} --connect" or use the desktop app's Connect button.`);
17318
17481
  }
17319
17482
  const token = options.token.trim();
17320
17483
  if (!token)
17321
17484
  throw new Error("Paste an API key first.");
17322
- const host = info9.credential.host;
17485
+ const host = info10.credential.host;
17323
17486
  const account = (options.account ?? "").trim();
17324
17487
  const key = publishCredentialKey(host, account);
17325
17488
  const candidate = {
@@ -17327,12 +17490,12 @@ async function connectPublishProvider(options, deps) {
17327
17490
  kind: "token",
17328
17491
  token,
17329
17492
  ...account ? { username: account } : {},
17330
- label: account || info9.label,
17493
+ label: account || info10.label,
17331
17494
  createdAt: Date.now()
17332
17495
  };
17333
17496
  const req = await resolvePublishRequest({
17334
17497
  projectDir: options.projectDir,
17335
- providerId: info9.id,
17498
+ providerId: info10.id,
17336
17499
  ...options.manifestPath ? { manifestPath: options.manifestPath } : {}
17337
17500
  }, deps);
17338
17501
  const trialDeps = {
@@ -17343,10 +17506,10 @@ async function connectPublishProvider(options, deps) {
17343
17506
  };
17344
17507
  const auth = await provider.authenticate({ ...req, deps: trialDeps });
17345
17508
  if (!auth.ok) {
17346
- throw new Error(auth.message ?? `${info9.label} didn't accept that key.`);
17509
+ throw new Error(auth.message ?? `${info10.label} didn't accept that key.`);
17347
17510
  }
17348
17511
  await deps.tokenStore.set(key, candidate);
17349
- return { connected: true, providerId: info9.id };
17512
+ return { connected: true, providerId: info10.id };
17350
17513
  }
17351
17514
  async function disconnectPublishCredential(key, deps, options = {}) {
17352
17515
  const existing = await deps.tokenStore.get(key);
@@ -17358,7 +17521,7 @@ async function disconnectPublishCredential(key, deps, options = {}) {
17358
17521
  }
17359
17522
  }
17360
17523
  // src/lib/publish/selections.ts
17361
- import { chmod as chmod3, mkdir as mkdir21, readFile as readFile36, writeFile as writeFile18 } from "node:fs/promises";
17524
+ import { chmod as chmod3, mkdir as mkdir21, readFile as readFile37, writeFile as writeFile18 } from "node:fs/promises";
17362
17525
  import path34 from "node:path";
17363
17526
  class PublishSelectionsStore {
17364
17527
  filePath;
@@ -17368,7 +17531,7 @@ class PublishSelectionsStore {
17368
17531
  }
17369
17532
  async read() {
17370
17533
  try {
17371
- const raw = await readFile36(this.filePath, "utf8");
17534
+ const raw = await readFile37(this.filePath, "utf8");
17372
17535
  const parsed = JSON.parse(raw);
17373
17536
  if (parsed && typeof parsed === "object") {
17374
17537
  return {