gutterpress 0.11.19-alpha.1 → 0.11.19-alpha.2

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.
@@ -14,12 +14,12 @@ import {
14
14
  stampCreator,
15
15
  stripAnnotations,
16
16
  warn
17
- } from "./cli-5j0a5hmc.js";
17
+ } from "./cli-r7czdzyz.js";
18
18
  import {
19
19
  DEBOUNCE,
20
20
  UsageError,
21
21
  resolvePort
22
- } from "./cli-n3nd3sav.js";
22
+ } from "./cli-xf9b7ptc.js";
23
23
  import {
24
24
  blackTextToGray
25
25
  } from "./cli-ht1bcqdd.js";
@@ -8008,11 +8008,174 @@ var check27 = {
8008
8008
  };
8009
8009
  registerCheck(check27);
8010
8010
 
8011
+ // src/checks/source/characters.ts
8012
+ import { readFile as readFile20 } from "node:fs/promises";
8013
+
8014
+ // src/checks/source/character-table.ts
8015
+ 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");
8016
+ var NAMED = {
8017
+ 8212: ["Em dash", "typographer", "---"],
8018
+ 8211: ["En dash", "typographer", "--"],
8019
+ 8220: ["Opening curly quote", "typographer", '"'],
8020
+ 8221: ["Closing curly quote", "typographer", '"'],
8021
+ 8216: ["Opening curly single quote", "typographer", "'"],
8022
+ 8217: ["Curly apostrophe", "typographer", "'"],
8023
+ 8230: ["Ellipsis character", "typographer", "..."],
8024
+ 700: ["Modifier-letter apostrophe", "lookalike", "'"],
8025
+ 180: ["Acute accent", "lookalike", "'"],
8026
+ 8242: ["Prime", "lookalike", "'"],
8027
+ 8243: ["Double prime", "lookalike", '"'],
8028
+ 8219: ["Reversed single quote", "lookalike", "'"],
8029
+ 8223: ["Reversed double quote", "lookalike", '"'],
8030
+ 65282: ["Full-width quote", "lookalike", '"'],
8031
+ 65287: ["Full-width apostrophe", "lookalike", "'"],
8032
+ 8208: ["Unicode hyphen", "lookalike", "-"],
8033
+ 8209: ["Non-breaking hyphen", "lookalike", "-"],
8034
+ 8210: ["Figure dash", "lookalike", "-"],
8035
+ 8213: ["Horizontal bar", "lookalike", "---"],
8036
+ 8722: ["Minus sign", "lookalike", "-"],
8037
+ 8226: ["Bullet", "symbol", "•"],
8038
+ 9702: ["White bullet", "symbol", "◦"],
8039
+ 9642: ["Small square", "symbol", "▪"],
8040
+ 8594: ["Right arrow", "symbol", "→"],
8041
+ 8592: ["Left arrow", "symbol", "←"],
8042
+ 8596: ["Left-right arrow", "symbol", "↔"],
8043
+ 8658: ["Double right arrow", "symbol", "⇒"],
8044
+ 10003: ["Check mark", "symbol", "✓"],
8045
+ 10004: ["Heavy check mark", "symbol", "✔"],
8046
+ 10007: ["Ballot X", "symbol", "✗"],
8047
+ 9733: ["Star", "symbol", "★"],
8048
+ 8203: ["Zero-width space", "invisible", "​"],
8049
+ 8288: ["Word joiner", "invisible", "⁠"],
8050
+ 65279: ["Byte-order mark", "invisible"],
8051
+ 173: ["Soft hyphen", "invisible", "­"],
8052
+ 8204: ["Zero-width non-joiner", "invisible", "‌"],
8053
+ 8205: ["Zero-width joiner", "invisible", "‍"],
8054
+ 8206: ["Left-to-right mark", "invisible", "‎"],
8055
+ 8207: ["Right-to-left mark", "invisible", "‏"],
8056
+ 1564: ["Arabic letter mark", "invisible", "؜"],
8057
+ 8234: ["Left-to-right embedding", "invisible", "‪"],
8058
+ 8235: ["Right-to-left embedding", "invisible", "‫"],
8059
+ 8236: ["Pop directional formatting", "invisible", "‬"],
8060
+ 8237: ["Left-to-right override", "invisible", "‭"],
8061
+ 8238: ["Right-to-left override", "invisible", "‮"],
8062
+ 8294: ["Left-to-right isolate", "invisible", "⁦"],
8063
+ 8295: ["Right-to-left isolate", "invisible", "⁧"],
8064
+ 8296: ["First-strong isolate", "invisible", "⁨"],
8065
+ 8297: ["Pop directional isolate", "invisible", "⁩"],
8066
+ 8232: ["Line separator", "invisible"],
8067
+ 8233: ["Paragraph separator", "invisible"],
8068
+ 160: ["Non-breaking space", "whitespace", " "],
8069
+ 8192: ["En quad", "whitespace", " "],
8070
+ 8193: ["Em quad", "whitespace", " "],
8071
+ 8194: ["En space", "whitespace", " "],
8072
+ 8195: ["Em space", "whitespace", " "],
8073
+ 8196: ["Three-per-em space", "whitespace", " "],
8074
+ 8197: ["Four-per-em space", "whitespace", " "],
8075
+ 8198: ["Six-per-em space", "whitespace", " "],
8076
+ 8199: ["Figure space", "whitespace", " "],
8077
+ 8200: ["Punctuation space", "whitespace", " "],
8078
+ 8201: ["Thin space", "whitespace", " "],
8079
+ 8202: ["Hair space", "whitespace", " "],
8080
+ 8239: ["Narrow non-breaking space", "whitespace", " "],
8081
+ 8287: ["Medium mathematical space", "whitespace", " "],
8082
+ 12288: ["Ideographic space", "whitespace", " "],
8083
+ 65533: ["Replacement character", "replacement"]
8084
+ };
8085
+ function fixFor(code, spelling) {
8086
+ switch (code) {
8087
+ case "typographer":
8088
+ return `Type ${spelling} instead; it prints the same.`;
8089
+ case "lookalike":
8090
+ return `Type ${spelling} instead.`;
8091
+ case "whitespace":
8092
+ return `Type a space, or ${spelling} if you meant it.`;
8093
+ case "symbol":
8094
+ case "invisible":
8095
+ return spelling ? `Delete it, or type ${spelling} if you meant it.` : "Delete it.";
8096
+ case "control":
8097
+ return "Delete it; it does not print.";
8098
+ case "replacement":
8099
+ return "Retype the character that was lost here.";
8100
+ case "private-use":
8101
+ return "Replace it with the character you meant; this one only shows in the font it came from.";
8102
+ }
8103
+ }
8104
+ function info2(name, code, spelling) {
8105
+ return { name, code, fix: fixFor(code, spelling) };
8106
+ }
8107
+ function describeCharacter(char) {
8108
+ const cp2 = char.codePointAt(0);
8109
+ const named = NAMED[cp2];
8110
+ if (named)
8111
+ return info2(...named);
8112
+ if (cp2 <= 159)
8113
+ return info2("Control character", "control");
8114
+ if (cp2 >= 57344 && cp2 <= 63743)
8115
+ return info2("Private-use character", "private-use");
8116
+ return info2("Emoji", "symbol", `&#x${cp2.toString(16).toUpperCase()};`);
8117
+ }
8118
+ function codePointLabel(char) {
8119
+ return `U+${char.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
8120
+ }
8121
+
8122
+ // src/checks/source/characters.ts
8123
+ var PROBE = new RegExp(CHARACTER_PATTERN.source, "u");
8124
+ var check28 = {
8125
+ id: "source.characters",
8126
+ name: "Unusual Characters",
8127
+ description: "Finds invisible and control characters, odd spaces, and pasted typography (em dashes, curly quotes, emoji) in markdown",
8128
+ category: "source",
8129
+ phase: "pre-build",
8130
+ async run(ctx) {
8131
+ const results = [];
8132
+ for (const file of (ctx.markdownFiles ?? []).slice().sort()) {
8133
+ let content;
8134
+ try {
8135
+ content = await readFile20(file, "utf8");
8136
+ } catch {
8137
+ results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
8138
+ continue;
8139
+ }
8140
+ if (!PROBE.test(content))
8141
+ continue;
8142
+ const lines = content.split(`
8143
+ `);
8144
+ let silencedLine = -1;
8145
+ for (let i = 0;i < lines.length; i++) {
8146
+ const line = lines[i];
8147
+ const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
8148
+ if (comment && parseDisableDirective(comment[1])?.includes(check28.id)) {
8149
+ silencedLine = i + 1;
8150
+ continue;
8151
+ }
8152
+ if (i === silencedLine)
8153
+ continue;
8154
+ for (const match of line.matchAll(CHARACTER_PATTERN)) {
8155
+ const char = match[0];
8156
+ const { name, code, fix } = describeCharacter(char);
8157
+ results.push(finding(check28.id, {
8158
+ severity: "warning",
8159
+ code,
8160
+ message: `${name} (${codePointLabel(char)}). ${fix}`,
8161
+ file,
8162
+ line: i + 1,
8163
+ column: match.index + 1,
8164
+ data: { codePoint: char.codePointAt(0) }
8165
+ }));
8166
+ }
8167
+ }
8168
+ }
8169
+ return results;
8170
+ }
8171
+ };
8172
+ registerCheck(check28);
8173
+
8011
8174
  // src/checks/asset/image-file-size.ts
8012
8175
  import { stat as stat4 } from "node:fs/promises";
8013
8176
 
8014
8177
  // src/lib/image-inspect.ts
8015
- import { stat as stat3, readFile as readFile20 } from "node:fs/promises";
8178
+ import { stat as stat3, readFile as readFile21 } from "node:fs/promises";
8016
8179
  var DEFAULT_DPI = 72;
8017
8180
  var cache = new Map;
8018
8181
  async function inspectImage(path10) {
@@ -8025,15 +8188,15 @@ async function inspectImage(path10) {
8025
8188
  const hit = cache.get(path10);
8026
8189
  if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size)
8027
8190
  return hit.info;
8028
- let info2 = null;
8191
+ let info3 = null;
8029
8192
  try {
8030
- const buf = await readFile20(path10);
8031
- info2 = parseImage(buf);
8193
+ const buf = await readFile21(path10);
8194
+ info3 = parseImage(buf);
8032
8195
  } catch {
8033
- info2 = null;
8196
+ info3 = null;
8034
8197
  }
8035
- cache.set(path10, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
8036
- return info2;
8198
+ cache.set(path10, { mtimeMs: st.mtimeMs, size: st.size, info: info3 });
8199
+ return info3;
8037
8200
  }
8038
8201
  function parseImage(b) {
8039
8202
  if (b.length > 8 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
@@ -8232,7 +8395,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
8232
8395
  }
8233
8396
 
8234
8397
  // src/checks/asset/image-file-size.ts
8235
- var check28 = {
8398
+ var check29 = {
8236
8399
  id: "asset.image.file-size",
8237
8400
  name: "Image File Size",
8238
8401
  description: "Checks that image files do not exceed the maximum size limit",
@@ -8249,18 +8412,18 @@ var check28 = {
8249
8412
  const results = [];
8250
8413
  for (const file of files) {
8251
8414
  try {
8252
- const info2 = await stat4(file);
8253
- if (info2.size > maxSize) {
8254
- const sizeMb = (info2.size / 1e6).toFixed(1);
8415
+ const info3 = await stat4(file);
8416
+ if (info3.size > maxSize) {
8417
+ const sizeMb = (info3.size / 1e6).toFixed(1);
8255
8418
  const maxMb = (maxSize / 1e6).toFixed(1);
8256
- results.push(finding(check28.id, {
8419
+ results.push(finding(check29.id, {
8257
8420
  severity: "warning",
8258
8421
  message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
8259
8422
  file
8260
8423
  }));
8261
8424
  }
8262
8425
  } catch {
8263
- results.push(inspectionFailed(check28.id, `Could not stat image file: ${file}`, {
8426
+ results.push(inspectionFailed(check29.id, `Could not stat image file: ${file}`, {
8264
8427
  file
8265
8428
  }));
8266
8429
  }
@@ -8268,10 +8431,10 @@ var check28 = {
8268
8431
  return results;
8269
8432
  }
8270
8433
  };
8271
- registerCheck(check28);
8434
+ registerCheck(check29);
8272
8435
 
8273
8436
  // src/checks/asset/image-resolution.ts
8274
- var check29 = {
8437
+ var check30 = {
8275
8438
  id: "asset.image.resolution",
8276
8439
  name: "Image Resolution",
8277
8440
  description: "Checks source image DPI from embedded density metadata",
@@ -8287,13 +8450,13 @@ var check29 = {
8287
8450
  return [];
8288
8451
  const results = [];
8289
8452
  for (const file of files) {
8290
- const info2 = await inspectImage(file);
8291
- if (!info2)
8453
+ const info3 = await inspectImage(file);
8454
+ if (!info3)
8292
8455
  continue;
8293
- const { xDpi, yDpi } = info2;
8456
+ const { xDpi, yDpi } = info3;
8294
8457
  if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
8295
8458
  results.push({
8296
- checkId: check29.id,
8459
+ checkId: check30.id,
8297
8460
  severity: "warning",
8298
8461
  message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
8299
8462
  file
@@ -8303,7 +8466,7 @@ var check29 = {
8303
8466
  return results;
8304
8467
  }
8305
8468
  };
8306
- registerCheck(check29);
8469
+ registerCheck(check30);
8307
8470
 
8308
8471
  // src/checks/asset/image-color-space.ts
8309
8472
  var LABEL = {
@@ -8311,7 +8474,7 @@ var LABEL = {
8311
8474
  gray: "Gray",
8312
8475
  cmyk: "CMYK"
8313
8476
  };
8314
- var check30 = {
8477
+ var check31 = {
8315
8478
  id: "asset.image.color-space",
8316
8479
  name: "Image Color Space",
8317
8480
  description: "Validates image color spaces against allowed list",
@@ -8328,11 +8491,11 @@ var check30 = {
8328
8491
  const allowedLower = new Set(allowed.map((s) => s.toLowerCase()));
8329
8492
  const results = [];
8330
8493
  for (const file of files) {
8331
- const info2 = await inspectImage(file);
8332
- const cs = info2?.colorSpace;
8494
+ const info3 = await inspectImage(file);
8495
+ const cs = info3?.colorSpace;
8333
8496
  if (cs && !allowedLower.has(cs)) {
8334
8497
  results.push({
8335
- checkId: check30.id,
8498
+ checkId: check31.id,
8336
8499
  severity: "warning",
8337
8500
  message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
8338
8501
  file
@@ -8342,10 +8505,10 @@ var check30 = {
8342
8505
  return results;
8343
8506
  }
8344
8507
  };
8345
- registerCheck(check30);
8508
+ registerCheck(check31);
8346
8509
 
8347
8510
  // src/checks/asset/image-alpha.ts
8348
- var check31 = {
8511
+ var check32 = {
8349
8512
  id: "asset.image.alpha-channel",
8350
8513
  name: "Image Alpha Channel",
8351
8514
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -8360,10 +8523,10 @@ var check31 = {
8360
8523
  return [];
8361
8524
  const results = [];
8362
8525
  for (const file of files) {
8363
- const info2 = await inspectImage(file);
8364
- if (info2?.hasAlpha) {
8526
+ const info3 = await inspectImage(file);
8527
+ if (info3?.hasAlpha) {
8365
8528
  results.push({
8366
- checkId: check31.id,
8529
+ checkId: check32.id,
8367
8530
  severity: "warning",
8368
8531
  message: "Image contains alpha channel, which may cause print issues",
8369
8532
  file
@@ -8373,10 +8536,10 @@ var check31 = {
8373
8536
  return results;
8374
8537
  }
8375
8538
  };
8376
- registerCheck(check31);
8539
+ registerCheck(check32);
8377
8540
 
8378
8541
  // src/checks/asset/image-tac.ts
8379
- var check32 = {
8542
+ var check33 = {
8380
8543
  id: "asset.image.tac-raster",
8381
8544
  name: "Image TAC (Raster)",
8382
8545
  description: "Rasterizes and checks TAC per image using Ghostscript",
@@ -8395,7 +8558,7 @@ var check32 = {
8395
8558
  const ghostscript = await resolveGhostscript();
8396
8559
  if (!ghostscript) {
8397
8560
  return [
8398
- inspectionFailed(check32.id, "Could not inspect image ink coverage: Ghostscript executable not found")
8561
+ inspectionFailed(check33.id, "Could not inspect image ink coverage: Ghostscript executable not found")
8399
8562
  ];
8400
8563
  }
8401
8564
  for (const file of files) {
@@ -8413,7 +8576,7 @@ var check32 = {
8413
8576
  if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
8414
8577
  const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
8415
8578
  if (tac > maxTac) {
8416
- results.push(finding(check32.id, {
8579
+ results.push(finding(check33.id, {
8417
8580
  severity: "warning",
8418
8581
  message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
8419
8582
  file,
@@ -8425,16 +8588,16 @@ var check32 = {
8425
8588
  }
8426
8589
  }
8427
8590
  } catch {
8428
- results.push(inspectionFailed(check32.id, `Could not inspect image ink coverage: ${file}`, { file }));
8591
+ results.push(inspectionFailed(check33.id, `Could not inspect image ink coverage: ${file}`, { file }));
8429
8592
  }
8430
8593
  }
8431
8594
  return results;
8432
8595
  }
8433
8596
  };
8434
- registerCheck(check32);
8597
+ registerCheck(check33);
8435
8598
 
8436
8599
  // src/checks/asset/approved-fonts.ts
8437
- var check33 = {
8600
+ var check34 = {
8438
8601
  id: "asset.font.approved-files",
8439
8602
  name: "Approved Font Files",
8440
8603
  description: "Checks font files against the approved file patterns",
@@ -8473,7 +8636,7 @@ var check33 = {
8473
8636
  for (const font of allFonts) {
8474
8637
  if (!approvedFonts.has(font)) {
8475
8638
  results.push({
8476
- checkId: check33.id,
8639
+ checkId: check34.id,
8477
8640
  severity: "warning",
8478
8641
  message: "Font file not in approved list",
8479
8642
  file: font
@@ -8483,7 +8646,7 @@ var check33 = {
8483
8646
  return results;
8484
8647
  }
8485
8648
  };
8486
- registerCheck(check33);
8649
+ registerCheck(check34);
8487
8650
 
8488
8651
  // src/checks/asset/font-license.ts
8489
8652
  import { existsSync as existsSync10 } from "node:fs";
@@ -8498,7 +8661,7 @@ var LICENSE_NAMES = [
8498
8661
  "OFL-1.1.txt",
8499
8662
  "COPYING"
8500
8663
  ];
8501
- var check34 = {
8664
+ var check35 = {
8502
8665
  id: "asset.font.license",
8503
8666
  name: "Font License",
8504
8667
  description: "Checks for license files in font directories",
@@ -8525,7 +8688,7 @@ var check34 = {
8525
8688
  const hasLicense = LICENSE_NAMES.some((name) => existsSync10(resolve6(fontDir, name)));
8526
8689
  if (!hasLicense) {
8527
8690
  results.push({
8528
- checkId: check34.id,
8691
+ checkId: check35.id,
8529
8692
  severity: "warning",
8530
8693
  message: `No font license file found in directory`,
8531
8694
  file: fontDir
@@ -8535,10 +8698,10 @@ var check34 = {
8535
8698
  return results;
8536
8699
  }
8537
8700
  };
8538
- registerCheck(check34);
8701
+ registerCheck(check35);
8539
8702
 
8540
8703
  // src/checks/heuristic/text-density.ts
8541
- var check35 = {
8704
+ var check36 = {
8542
8705
  id: "heuristic.whitespace.text-density",
8543
8706
  name: "Text Density",
8544
8707
  description: "Checks characters-per-page ratio",
@@ -8567,7 +8730,7 @@ var check35 = {
8567
8730
  });
8568
8731
  if (lowPages.length > 0) {
8569
8732
  results.push({
8570
- checkId: check35.id,
8733
+ checkId: check36.id,
8571
8734
  severity: "info",
8572
8735
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
8573
8736
  file: ctx.pdfPath
@@ -8575,7 +8738,7 @@ var check35 = {
8575
8738
  }
8576
8739
  if (highPages.length > 0) {
8577
8740
  results.push({
8578
- checkId: check35.id,
8741
+ checkId: check36.id,
8579
8742
  severity: "info",
8580
8743
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
8581
8744
  file: ctx.pdfPath
@@ -8584,11 +8747,11 @@ var check35 = {
8584
8747
  return results;
8585
8748
  }
8586
8749
  };
8587
- registerCheck(check35);
8750
+ registerCheck(check36);
8588
8751
 
8589
8752
  // src/checks/heuristic/section-density.ts
8590
- import { readFile as readFile21 } from "node:fs/promises";
8591
- var check36 = {
8753
+ import { readFile as readFile22 } from "node:fs/promises";
8754
+ var check37 = {
8592
8755
  id: "heuristic.chunking.section-density",
8593
8756
  name: "Section Density",
8594
8757
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -8604,7 +8767,7 @@ var check36 = {
8604
8767
  const results = [];
8605
8768
  for (const file of files) {
8606
8769
  try {
8607
- const content = await readFile21(file, "utf8");
8770
+ const content = await readFile22(file, "utf8");
8608
8771
  const lines = content.split(`
8609
8772
  `);
8610
8773
  let currentSectionStart = 0;
@@ -8614,7 +8777,7 @@ var check36 = {
8614
8777
  const line = lines[i];
8615
8778
  if (/^#{1,6}\s/.test(line)) {
8616
8779
  if (paragraphCount > maxParas) {
8617
- results.push(finding(check36.id, {
8780
+ results.push(finding(check37.id, {
8618
8781
  severity: "info",
8619
8782
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8620
8783
  file,
@@ -8634,7 +8797,7 @@ var check36 = {
8634
8797
  }
8635
8798
  }
8636
8799
  if (paragraphCount > maxParas) {
8637
- results.push(finding(check36.id, {
8800
+ results.push(finding(check37.id, {
8638
8801
  severity: "info",
8639
8802
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8640
8803
  file,
@@ -8642,7 +8805,7 @@ var check36 = {
8642
8805
  }));
8643
8806
  }
8644
8807
  } catch {
8645
- results.push(inspectionFailed(check36.id, `Could not read source file: ${file}`, {
8808
+ results.push(inspectionFailed(check37.id, `Could not read source file: ${file}`, {
8646
8809
  file
8647
8810
  }));
8648
8811
  }
@@ -8650,10 +8813,10 @@ var check36 = {
8650
8813
  return results;
8651
8814
  }
8652
8815
  };
8653
- registerCheck(check36);
8816
+ registerCheck(check37);
8654
8817
 
8655
8818
  // src/checks/heuristic/layer-count.ts
8656
- var check37 = {
8819
+ var check38 = {
8657
8820
  id: "heuristic.decoration.layer-count",
8658
8821
  name: "Layer Count",
8659
8822
  description: "Counts image objects per page",
@@ -8678,7 +8841,7 @@ var check37 = {
8678
8841
  heavyPages.sort((a, b) => a - b);
8679
8842
  return [
8680
8843
  {
8681
- checkId: check37.id,
8844
+ checkId: check38.id,
8682
8845
  severity: "info",
8683
8846
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
8684
8847
  file: ctx.pdfPath
@@ -8688,10 +8851,10 @@ var check37 = {
8688
8851
  return [];
8689
8852
  }
8690
8853
  };
8691
- registerCheck(check37);
8854
+ registerCheck(check38);
8692
8855
 
8693
8856
  // src/checks/heuristic/placement-variance.ts
8694
- var check38 = {
8857
+ var check39 = {
8695
8858
  id: "heuristic.layout.placement-variance",
8696
8859
  name: "Placement Variance",
8697
8860
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -8709,7 +8872,7 @@ var check38 = {
8709
8872
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
8710
8873
  return [
8711
8874
  {
8712
- checkId: check38.id,
8875
+ checkId: check39.id,
8713
8876
  severity: "info",
8714
8877
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
8715
8878
  file: ctx.pdfPath
@@ -8717,21 +8880,21 @@ var check38 = {
8717
8880
  ];
8718
8881
  }
8719
8882
  };
8720
- registerCheck(check38);
8883
+ registerCheck(check39);
8721
8884
 
8722
8885
  // src/checks/tool-check.ts
8723
8886
  async function checkToolAvailability(config, opts = {}) {
8724
8887
  const { checks: checks2 } = selectChecks(opts, config);
8725
8888
  const toolToChecks = new Map;
8726
- for (const check39 of checks2) {
8727
- if (!check39.requiredTools?.length)
8889
+ for (const check40 of checks2) {
8890
+ if (!check40.requiredTools?.length)
8728
8891
  continue;
8729
- for (const tool of check39.requiredTools) {
8892
+ for (const tool of check40.requiredTools) {
8730
8893
  const existing = toolToChecks.get(tool);
8731
8894
  if (existing) {
8732
- existing.push(check39.id);
8895
+ existing.push(check40.id);
8733
8896
  } else {
8734
- toolToChecks.set(tool, [check39.id]);
8897
+ toolToChecks.set(tool, [check40.id]);
8735
8898
  }
8736
8899
  }
8737
8900
  }
@@ -8747,11 +8910,11 @@ async function checkToolAvailability(config, opts = {}) {
8747
8910
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
8748
8911
  const missingSet = new Set(missing);
8749
8912
  const skippedChecks = [];
8750
- for (const check39 of checks2) {
8751
- if (!check39.requiredTools?.length)
8913
+ for (const check40 of checks2) {
8914
+ if (!check40.requiredTools?.length)
8752
8915
  continue;
8753
- if (check39.requiredTools.some((t) => missingSet.has(t))) {
8754
- skippedChecks.push(check39.id);
8916
+ if (check40.requiredTools.some((t) => missingSet.has(t))) {
8917
+ skippedChecks.push(check40.id);
8755
8918
  }
8756
8919
  }
8757
8920
  return { available, missing, skippedChecks, toolToChecks };
@@ -8908,7 +9071,7 @@ async function restoreForCommand(dir, options) {
8908
9071
  }
8909
9072
 
8910
9073
  // src/lib/markdown/index.ts
8911
- import { readdir as readdir3, readFile as readFile22, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
9074
+ import { readdir as readdir3, readFile as readFile23, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
8912
9075
 
8913
9076
  // src/lib/remote-auth/sync-messages.ts
8914
9077
  function isOnlineSibling(filepath) {
@@ -9050,7 +9213,7 @@ ${css.trim()}
9050
9213
  }
9051
9214
  return assembleBookHtml({
9052
9215
  files,
9053
- readText: (relPath) => readFile22(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9216
+ readText: (relPath) => readFile23(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9054
9217
  projectCss: inlined.css,
9055
9218
  preloadImages: cssAssetCopies.map((c) => c.to),
9056
9219
  title: opts.title,
@@ -9082,7 +9245,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
9082
9245
  }
9083
9246
 
9084
9247
  // src/lib/markdownlint-fix.ts
9085
- import { readFile as readFile23, writeFile as writeFile3 } from "node:fs/promises";
9248
+ import { readFile as readFile24, writeFile as writeFile3 } from "node:fs/promises";
9086
9249
  import { applyFixes } from "markdownlint";
9087
9250
  import { lint as lint2 } from "markdownlint/sync";
9088
9251
  async function uncommittedAmong(inputDir, files) {
@@ -9122,7 +9285,7 @@ async function applyMarkdownlintFixes(ctx) {
9122
9285
  for (const [file, violations] of Object.entries(results)) {
9123
9286
  if (!Array.isArray(violations) || violations.length === 0)
9124
9287
  continue;
9125
- const before = await readFile23(file, "utf8");
9288
+ const before = await readFile24(file, "utf8");
9126
9289
  const after = applyFixes(before, violations);
9127
9290
  if (after !== before)
9128
9291
  pending.push({ file, content: after });
@@ -9231,16 +9394,16 @@ async function runChecks(ctx, opts = {}) {
9231
9394
  }
9232
9395
  const release = retainPdfCache();
9233
9396
  try {
9234
- for (const check39 of checks2) {
9397
+ for (const check40 of checks2) {
9235
9398
  try {
9236
- let results = await check39.run(ctx);
9237
- const severityOverride = getCheckSeverityOverride(check39.id, ctx.config);
9399
+ let results = await check40.run(ctx);
9400
+ const severityOverride = getCheckSeverityOverride(check40.id, ctx.config);
9238
9401
  if (severityOverride) {
9239
9402
  for (const r of results) {
9240
9403
  r.severity = severityOverride;
9241
9404
  }
9242
9405
  }
9243
- const lintLevel = ruleLevel(ctx.config.lint.rules, check39.id);
9406
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check40.id);
9244
9407
  if (lintLevel === "off")
9245
9408
  results = [];
9246
9409
  else if (lintLevel) {
@@ -9248,15 +9411,15 @@ async function runChecks(ctx, opts = {}) {
9248
9411
  r.severity = lintLevel === "warn" ? "warning" : "error";
9249
9412
  }
9250
9413
  if (results.length === 0) {
9251
- passed.push(check39.id);
9414
+ passed.push(check40.id);
9252
9415
  } else {
9253
9416
  allResults.push(...results);
9254
9417
  }
9255
9418
  } catch (err) {
9256
9419
  allResults.push({
9257
- checkId: check39.id,
9420
+ checkId: check40.id,
9258
9421
  severity: "error",
9259
- message: `Check "${check39.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9422
+ message: `Check "${check40.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9260
9423
  });
9261
9424
  }
9262
9425
  }
@@ -9548,7 +9711,7 @@ async function executeValidation(args) {
9548
9711
  };
9549
9712
  if (args.fix) {
9550
9713
  const selected = selectChecks(runnerOptions, config).checks;
9551
- if (selected.some((check39) => check39.id === MARKDOWNLINT_CHECK_ID)) {
9714
+ if (selected.some((check40) => check40.id === MARKDOWNLINT_CHECK_ID)) {
9552
9715
  await applyMarkdownlintFixes(context);
9553
9716
  } else {
9554
9717
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -9635,7 +9798,7 @@ import { randomBytes } from "node:crypto";
9635
9798
 
9636
9799
  // src/lib/build-fingerprint.ts
9637
9800
  import * as fs from "node:fs";
9638
- import { mkdir as mkdir5, readFile as readFile24, writeFile as writeFile4 } from "node:fs/promises";
9801
+ import { mkdir as mkdir5, readFile as readFile25, writeFile as writeFile4 } from "node:fs/promises";
9639
9802
  import { createHash as createHash4 } from "node:crypto";
9640
9803
  import path12 from "node:path";
9641
9804
  import git from "isomorphic-git";
@@ -9722,8 +9885,8 @@ async function getEngineBundleHash() {
9722
9885
  getAssetPath("engine/gutterpress-agent.js")
9723
9886
  ]);
9724
9887
  const [viewerBytes, agentBytes] = await Promise.all([
9725
- readFile24(viewerPath),
9726
- readFile24(agentPath)
9888
+ readFile25(viewerPath),
9889
+ readFile25(agentPath)
9727
9890
  ]);
9728
9891
  const hash = createHash4("sha256");
9729
9892
  hash.update(viewerBytes);
@@ -10232,7 +10395,7 @@ function openPath(filePath) {
10232
10395
  import path16 from "path";
10233
10396
  import { tmpdir } from "os";
10234
10397
  import { randomBytes as randomBytes2 } from "crypto";
10235
- import { readdir as readdir4, readFile as readFile25, writeFile as writeFile5 } from "node:fs/promises";
10398
+ import { readdir as readdir4, readFile as readFile26, writeFile as writeFile5 } from "node:fs/promises";
10236
10399
 
10237
10400
  // src/utils/file-utils.ts
10238
10401
  import { promises as fs3 } from "fs";
@@ -10784,7 +10947,7 @@ async function cleanupOrphanTempDirs() {
10784
10947
  const dir = path16.join(TEMP_DIR_BASE, entry);
10785
10948
  const pidFile = path16.join(dir, PID_FILE_NAME);
10786
10949
  try {
10787
- const raw = await readFile25(pidFile, "utf8");
10950
+ const raw = await readFile26(pidFile, "utf8");
10788
10951
  const pid = parseInt(raw.trim(), 10);
10789
10952
  if (!isProcessAlive(pid)) {
10790
10953
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -10891,7 +11054,7 @@ function createServerState(inputPath, tempDir, config, options) {
10891
11054
  import http from "node:http";
10892
11055
  import net from "node:net";
10893
11056
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
10894
- import { readFile as readFile26, stat as stat5 } from "node:fs/promises";
11057
+ import { readFile as readFile27, stat as stat5 } from "node:fs/promises";
10895
11058
  import path17 from "path";
10896
11059
  import { WebSocket, WebSocketServer } from "ws";
10897
11060
  var HMR_PATH = "/__gutterpress-hmr";
@@ -10960,7 +11123,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
10960
11123
  }
10961
11124
  let data;
10962
11125
  try {
10963
- data = await readFile26(filePath);
11126
+ data = await readFile27(filePath);
10964
11127
  } catch {
10965
11128
  res.writeHead(404);
10966
11129
  res.end("Not Found");
@@ -11009,7 +11172,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11009
11172
  let data;
11010
11173
  if (!validator || validator.stamp !== stamp) {
11011
11174
  try {
11012
- data = await readFile26(absPath);
11175
+ data = await readFile27(absPath);
11013
11176
  } catch {
11014
11177
  await serveStatic(absPath, res);
11015
11178
  return;
@@ -11319,7 +11482,7 @@ async function startPreviewServer(options) {
11319
11482
  }
11320
11483
 
11321
11484
  // src/lib/project-scaffold.ts
11322
- import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile27, stat as stat6, writeFile as writeFile6 } from "node:fs/promises";
11485
+ import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile28, stat as stat6, writeFile as writeFile6 } from "node:fs/promises";
11323
11486
  import { constants as FS, existsSync as existsSync12 } from "node:fs";
11324
11487
  import path18 from "node:path";
11325
11488
 
@@ -11518,7 +11681,7 @@ async function scaffoldProject(options) {
11518
11681
  async function firstSourceFile(manifestPath) {
11519
11682
  let text;
11520
11683
  try {
11521
- text = await readFile27(manifestPath, "utf8");
11684
+ text = await readFile28(manifestPath, "utf8");
11522
11685
  } catch {
11523
11686
  return;
11524
11687
  }
@@ -11530,7 +11693,7 @@ async function firstSourceFile(manifestPath) {
11530
11693
  return m ? m[1].trim() : undefined;
11531
11694
  }
11532
11695
  async function fillTemplateFile(filePath, substitutions) {
11533
- let text = await readFile27(filePath, "utf8");
11696
+ let text = await readFile28(filePath, "utf8");
11534
11697
  for (const [token, value] of Object.entries(substitutions)) {
11535
11698
  text = text.split(token).join(value);
11536
11699
  }
@@ -11538,7 +11701,7 @@ async function fillTemplateFile(filePath, substitutions) {
11538
11701
  }
11539
11702
 
11540
11703
  // src/lib/extension-scaffold.ts
11541
- import { access as access2, mkdir as mkdir8, readFile as readFile28, writeFile as writeFile7 } from "node:fs/promises";
11704
+ import { access as access2, mkdir as mkdir8, readFile as readFile29, writeFile as writeFile7 } from "node:fs/promises";
11542
11705
  import { constants as FS2 } from "node:fs";
11543
11706
  import path19 from "node:path";
11544
11707
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -11655,7 +11818,7 @@ async function scaffoldExtension(options) {
11655
11818
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
11656
11819
  const dest = path19.join(extensionDir, relDest);
11657
11820
  await mkdir8(path19.dirname(dest), { recursive: true });
11658
- const text = await readFile28(src, "utf8");
11821
+ const text = await readFile29(src, "utf8");
11659
11822
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
11660
11823
  await writeFile7(dest, substitute(text, table), "utf8");
11661
11824
  written.push(relDest);
@@ -11700,7 +11863,7 @@ var SKIP_ENTRIES = new Set([
11700
11863
 
11701
11864
  // src/lib/extension-import.ts
11702
11865
  import { existsSync as existsSync13 } from "node:fs";
11703
- import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile29, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11866
+ import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile30, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11704
11867
  import { tmpdir as tmpdir2 } from "node:os";
11705
11868
  import path20 from "node:path";
11706
11869
  import { unzipSync } from "fflate";
@@ -11773,7 +11936,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11773
11936
  if (!existsSync13(cssPath)) {
11774
11937
  throw new Error("A theme must contain a theme.css file.");
11775
11938
  }
11776
- const css = await readFile29(cssPath, "utf8");
11939
+ const css = await readFile30(cssPath, "utf8");
11777
11940
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
11778
11941
  if (reject) {
11779
11942
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -11801,7 +11964,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11801
11964
  for (let i = 0;i < extraSheets.length; i++) {
11802
11965
  const rel = extraSheets[i];
11803
11966
  const sheetPath = extraSheetPaths[i];
11804
- const sheetCss = await readFile29(sheetPath, "utf8");
11967
+ const sheetCss = await readFile30(sheetPath, "utf8");
11805
11968
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
11806
11969
  if (sheetReject) {
11807
11970
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -11834,7 +11997,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
11834
11997
  let pkg = {};
11835
11998
  if (metaExists) {
11836
11999
  try {
11837
- const parsed = JSON.parse(await readFile29(file, "utf8"));
12000
+ const parsed = JSON.parse(await readFile30(file, "utf8"));
11838
12001
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
11839
12002
  pkg = parsed;
11840
12003
  }
@@ -11936,11 +12099,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
11936
12099
  async function importExtensionFromFile(projectDir, filePath) {
11937
12100
  const ext = path20.extname(filePath).toLowerCase();
11938
12101
  if (ext === ".zip") {
11939
- const buf = await readFile29(filePath);
12102
+ const buf = await readFile30(filePath);
11940
12103
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
11941
12104
  }
11942
12105
  if (ext === ".css") {
11943
- const css = await readFile29(filePath, "utf8");
12106
+ const css = await readFile30(filePath, "utf8");
11944
12107
  return importExtensionFromCssText(projectDir, css, prettify(path20.basename(filePath, ".css")));
11945
12108
  }
11946
12109
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -12144,13 +12307,13 @@ function publishCredentialKey(host, account) {
12144
12307
  const a = (account ?? "").trim();
12145
12308
  return a ? `${host}#${a}` : host;
12146
12309
  }
12147
- async function resolvePublishCredential(info2, deps, account = deps.credentialAccount) {
12310
+ async function resolvePublishCredential(info3, deps, account = deps.credentialAccount) {
12148
12311
  const env = deps.env ?? process.env;
12149
- const fromEnv = info2.credential.envVar ? env[info2.credential.envVar]?.trim() : undefined;
12312
+ const fromEnv = info3.credential.envVar ? env[info3.credential.envVar]?.trim() : undefined;
12150
12313
  if (fromEnv) {
12151
12314
  return {
12152
12315
  credential: {
12153
- host: info2.credential.host,
12316
+ host: info3.credential.host,
12154
12317
  kind: "token",
12155
12318
  token: fromEnv,
12156
12319
  createdAt: 0
@@ -12158,21 +12321,21 @@ async function resolvePublishCredential(info2, deps, account = deps.credentialAc
12158
12321
  source: "env"
12159
12322
  };
12160
12323
  }
12161
- const stored = await deps.tokenStore.get(publishCredentialKey(info2.credential.host, account));
12324
+ const stored = await deps.tokenStore.get(publishCredentialKey(info3.credential.host, account));
12162
12325
  return stored ? { credential: stored, source: "store" } : null;
12163
12326
  }
12164
- async function publishConnectionStatus(info2, deps, account = deps.credentialAccount) {
12165
- if (!info2.credential.required)
12327
+ async function publishConnectionStatus(info3, deps, account = deps.credentialAccount) {
12328
+ if (!info3.credential.required)
12166
12329
  return { connected: true };
12167
- const resolved = await resolvePublishCredential(info2, deps, account);
12330
+ const resolved = await resolvePublishCredential(info3, deps, account);
12168
12331
  return resolved ? { connected: true, source: resolved.source } : { connected: false };
12169
12332
  }
12170
- async function listPublishAccounts(info2, deps) {
12171
- const wantHost = info2.credential.host.trim().toLowerCase();
12333
+ async function listPublishAccounts(info3, deps) {
12334
+ const wantHost = info3.credential.host.trim().toLowerCase();
12172
12335
  const all = await deps.tokenStore.list();
12173
12336
  return all.filter((c) => c.host.trim().toLowerCase() === wantHost).map((c) => ({
12174
12337
  account: (c.username ?? "").trim(),
12175
- label: c.label ?? (c.username || info2.label),
12338
+ label: c.label ?? (c.username || info3.label),
12176
12339
  createdAt: c.createdAt
12177
12340
  }));
12178
12341
  }
@@ -12203,8 +12366,8 @@ async function readGoogleApiError(res) {
12203
12366
  }
12204
12367
  var RATE_LIMIT_REASONS = /^(dailyLimitExceeded|userRateLimitExceeded|rateLimitExceeded|quotaExceeded)$/i;
12205
12368
  var PERMISSION_REASONS = /^(insufficientPermissions|insufficientFilePermissions|forbidden|PERMISSION_DENIED)$/i;
12206
- function hintFor(info2) {
12207
- const reason = info2.reason ?? "";
12369
+ function hintFor(info3) {
12370
+ const reason = info3.reason ?? "";
12208
12371
  if (/^accessNotConfigured$/i.test(reason)) {
12209
12372
  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).";
12210
12373
  }
@@ -12214,18 +12377,18 @@ function hintFor(info2) {
12214
12377
  if (RATE_LIMIT_REASONS.test(reason)) {
12215
12378
  return "Google Drive is rate-limiting requests right now. Wait a minute and try again.";
12216
12379
  }
12217
- if (PERMISSION_REASONS.test(reason) || info2.status === 401) {
12380
+ if (PERMISSION_REASONS.test(reason) || info3.status === 401) {
12218
12381
  return "This Google Drive connection doesn't have permission for that — disconnect and connect Google Drive again to re-approve access.";
12219
12382
  }
12220
12383
  return;
12221
12384
  }
12222
- function googleApiFailure(what, info2) {
12223
- const parts = [`${what} (HTTP ${info2.status}${info2.reason ? `, ${info2.reason}` : ""}).`];
12224
- const hint = hintFor(info2);
12385
+ function googleApiFailure(what, info3) {
12386
+ const parts = [`${what} (HTTP ${info3.status}${info3.reason ? `, ${info3.reason}` : ""}).`];
12387
+ const hint = hintFor(info3);
12225
12388
  if (hint)
12226
12389
  parts.push(hint);
12227
- if (info2.message)
12228
- parts.push(`Google said: "${info2.message}"`);
12390
+ if (info3.message)
12391
+ parts.push(`Google said: "${info3.message}"`);
12229
12392
  let text = parts.join(" ");
12230
12393
  if (!/\bgoogle\b/i.test(text))
12231
12394
  text = `Google Drive: ${text}`;
@@ -12553,7 +12716,7 @@ async function commandExists(cmd, runCommand = defaultCommandRunner, env) {
12553
12716
 
12554
12717
  // src/lib/publish/providers/azure-swa.ts
12555
12718
  var AZURE_SWA_HOST = "azure-swa";
12556
- var info2 = {
12719
+ var info3 = {
12557
12720
  id: "azure-swa",
12558
12721
  label: "Azure Static Web Apps",
12559
12722
  kind: "api",
@@ -12578,9 +12741,9 @@ async function resolveSwaCommand(req) {
12578
12741
  }
12579
12742
  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.";
12580
12743
  var azureSwaProvider = {
12581
- info: info2,
12744
+ info: info3,
12582
12745
  async authenticate(req) {
12583
- const resolved = await resolvePublishCredential(info2, req.deps);
12746
+ const resolved = await resolvePublishCredential(info3, req.deps);
12584
12747
  if (!resolved) {
12585
12748
  return {
12586
12749
  ok: false,
@@ -12611,7 +12774,7 @@ var azureSwaProvider = {
12611
12774
  return issues;
12612
12775
  },
12613
12776
  async upload(req) {
12614
- const resolved = await resolvePublishCredential(info2, req.deps);
12777
+ const resolved = await resolvePublishCredential(info3, req.deps);
12615
12778
  if (!resolved) {
12616
12779
  throw new Error("No Azure deployment token found. Connect Azure Static Web Apps (or set SWA_CLI_DEPLOYMENT_TOKEN) first.");
12617
12780
  }
@@ -12667,7 +12830,7 @@ async function stageGuidedPackage(req, providerId, listingLines) {
12667
12830
 
12668
12831
  // src/lib/publish/providers/drivethrurpg.ts
12669
12832
  var PUBLISHER_HUB_URL = "https://www.drivethrurpg.com/publisher_hub";
12670
- var info3 = {
12833
+ var info4 = {
12671
12834
  id: "drivethrurpg",
12672
12835
  label: "DriveThruRPG",
12673
12836
  kind: "guided",
@@ -12683,7 +12846,7 @@ var info3 = {
12683
12846
  credential: { required: false, host: "drivethrurpg.com" }
12684
12847
  };
12685
12848
  var drivethrurpgProvider = {
12686
- info: info3,
12849
+ info: info4,
12687
12850
  async authenticate() {
12688
12851
  return { ok: true };
12689
12852
  },
@@ -12724,7 +12887,7 @@ var drivethrurpgProvider = {
12724
12887
  };
12725
12888
 
12726
12889
  // src/lib/publish/providers/gdrive.ts
12727
- import { mkdtemp as mkdtemp3, readFile as readFile30, readdir as readdir6, rm as rm5, stat as stat7, writeFile as writeFile10 } from "node:fs/promises";
12890
+ import { mkdtemp as mkdtemp3, readFile as readFile31, readdir as readdir6, rm as rm5, stat as stat7, writeFile as writeFile10 } from "node:fs/promises";
12728
12891
  import { tmpdir as tmpdir3 } from "node:os";
12729
12892
  import path22 from "node:path";
12730
12893
  import { Zip, ZipDeflate } from "fflate";
@@ -12992,7 +13155,7 @@ async function resumableUpload(fetchImpl, accessToken, opts) {
12992
13155
  // src/lib/publish/providers/gdrive.ts
12993
13156
  var DEFAULT_FOLDER_NAME = "Gutterpress";
12994
13157
  var NOT_CONNECTED_MESSAGE = "Google Drive isn't connected. Run `gutterpress publish --provider gdrive --connect` (or set GDRIVE_REFRESH_TOKEN) first.";
12995
- var info4 = {
13158
+ var info5 = {
12996
13159
  id: "gdrive",
12997
13160
  label: "Google Drive",
12998
13161
  kind: "api",
@@ -13031,7 +13194,7 @@ async function mintAccessToken(resolved, fetchImpl) {
13031
13194
  return accessToken;
13032
13195
  }
13033
13196
  async function getAccessToken(req) {
13034
- const resolved = await resolvePublishCredential(info4, req.deps);
13197
+ const resolved = await resolvePublishCredential(info5, req.deps);
13035
13198
  if (!resolved)
13036
13199
  throw new Error(NOT_CONNECTED_MESSAGE);
13037
13200
  const accessToken = await mintAccessToken(resolved, req.deps.fetch ?? globalThis.fetch);
@@ -13116,12 +13279,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
13116
13279
  await collectZipEntries(root, full, out, onWarn);
13117
13280
  } else if (entry.isFile()) {
13118
13281
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13119
- out[rel] = await readFile30(full);
13282
+ out[rel] = await readFile31(full);
13120
13283
  } else if (entry.isSymbolicLink()) {
13121
13284
  const resolved = await stat7(full).catch(() => null);
13122
13285
  if (resolved?.isFile()) {
13123
13286
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13124
- out[rel] = await readFile30(full);
13287
+ out[rel] = await readFile31(full);
13125
13288
  } else if (resolved?.isDirectory()) {
13126
13289
  await collectZipEntries(root, full, out, onWarn);
13127
13290
  } else {
@@ -13153,9 +13316,9 @@ async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl = writeF
13153
13316
  }
13154
13317
  }
13155
13318
  var gdriveProvider = {
13156
- info: info4,
13319
+ info: info5,
13157
13320
  async authenticate(req) {
13158
- const resolved = await resolvePublishCredential(info4, req.deps);
13321
+ const resolved = await resolvePublishCredential(info5, req.deps);
13159
13322
  if (!resolved)
13160
13323
  return { ok: false, message: NOT_CONNECTED_MESSAGE };
13161
13324
  try {
@@ -13320,7 +13483,7 @@ async function ensureButler(deps) {
13320
13483
  var ITCH_HOST = "itch.io";
13321
13484
  var TARGET_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
13322
13485
  var ITCH_API_TIMEOUT_MS = 15000;
13323
- var info5 = {
13486
+ var info6 = {
13324
13487
  id: "itch",
13325
13488
  label: "itch.io",
13326
13489
  kind: "api",
@@ -13350,9 +13513,9 @@ function itchProjectUrl(target) {
13350
13513
  return `https://${user}.itch.io/${game}`;
13351
13514
  }
13352
13515
  var itchProvider = {
13353
- info: info5,
13516
+ info: info6,
13354
13517
  async authenticate(req) {
13355
- const resolved = await resolvePublishCredential(info5, req.deps);
13518
+ const resolved = await resolvePublishCredential(info6, req.deps);
13356
13519
  if (!resolved) {
13357
13520
  return {
13358
13521
  ok: false,
@@ -13420,7 +13583,7 @@ var itchProvider = {
13420
13583
  },
13421
13584
  async upload(req) {
13422
13585
  const { target, channel } = readConfig2(req);
13423
- const resolved = await resolvePublishCredential(info5, req.deps);
13586
+ const resolved = await resolvePublishCredential(info6, req.deps);
13424
13587
  if (!resolved) {
13425
13588
  throw new Error("No itch.io API key found. Connect itch.io (or set BUTLER_API_KEY) first.");
13426
13589
  }
@@ -13448,7 +13611,7 @@ ${tail}` : ""}`);
13448
13611
 
13449
13612
  // src/lib/publish/providers/kdp.ts
13450
13613
  var KDP_URL = "https://kdp.amazon.com/en_US/bookshelf";
13451
- var info6 = {
13614
+ var info7 = {
13452
13615
  id: "kdp",
13453
13616
  label: "Amazon KDP",
13454
13617
  kind: "guided",
@@ -13458,7 +13621,7 @@ var info6 = {
13458
13621
  credential: { required: false, host: "kdp.amazon.com" }
13459
13622
  };
13460
13623
  var kdpProvider = {
13461
- info: info6,
13624
+ info: info7,
13462
13625
  async authenticate() {
13463
13626
  return { ok: true };
13464
13627
  },
@@ -13503,7 +13666,7 @@ var kdpProvider = {
13503
13666
  import { cp as cp4, mkdir as mkdir12, stat as stat9 } from "node:fs/promises";
13504
13667
  import path24 from "node:path";
13505
13668
  var LOCAL_PUBLISH_DEFAULT_DIR = "dist";
13506
- var info7 = {
13669
+ var info8 = {
13507
13670
  id: "local",
13508
13671
  label: "A folder on this computer",
13509
13672
  kind: "api",
@@ -13520,7 +13683,7 @@ function localPublishDir(req) {
13520
13683
  return path24.resolve(req.project.projectDir, dir);
13521
13684
  }
13522
13685
  var localFolderProvider = {
13523
- info: info7,
13686
+ info: info8,
13524
13687
  async authenticate() {
13525
13688
  return { ok: true };
13526
13689
  },
@@ -13561,7 +13724,7 @@ function requireValidShop(shop) {
13561
13724
  }
13562
13725
  return shop;
13563
13726
  }
13564
- var info8 = {
13727
+ var info9 = {
13565
13728
  id: "shopify",
13566
13729
  label: "Shopify",
13567
13730
  kind: "api",
@@ -13595,7 +13758,7 @@ function readConfig3(req) {
13595
13758
  async function adminGraphQL(req, query, variables = {}) {
13596
13759
  const { shop: rawShop, apiVersion } = readConfig3(req);
13597
13760
  const shop = requireValidShop(rawShop);
13598
- const resolved = await resolvePublishCredential(info8, req.deps);
13761
+ const resolved = await resolvePublishCredential(info9, req.deps);
13599
13762
  if (!resolved) {
13600
13763
  throw new Error("No Shopify access token found. Connect Shopify (or set SHOPIFY_ADMIN_TOKEN) first.");
13601
13764
  }
@@ -13646,9 +13809,9 @@ function userErrorsToMessage(result) {
13646
13809
  return errs?.length ? errs[0].message : null;
13647
13810
  }
13648
13811
  var shopifyProvider = {
13649
- info: info8,
13812
+ info: info9,
13650
13813
  async authenticate(req) {
13651
- const resolved = await resolvePublishCredential(info8, req.deps);
13814
+ const resolved = await resolvePublishCredential(info9, req.deps);
13652
13815
  if (!resolved) {
13653
13816
  return {
13654
13817
  ok: false,
@@ -13784,14 +13947,14 @@ function publishProviderFor(id) {
13784
13947
  // src/lib/publish/run-publish.ts
13785
13948
  import { readdir as readdir7, stat as stat10 } from "node:fs/promises";
13786
13949
  import path25 from "node:path";
13787
- function resolvePublishFormat(info9, providerConfig) {
13788
- if (info9.formats && info9.formats.length > 0) {
13950
+ function resolvePublishFormat(info10, providerConfig) {
13951
+ if (info10.formats && info10.formats.length > 0) {
13789
13952
  const configured = typeof providerConfig.format === "string" ? providerConfig.format.trim() : "";
13790
- if (configured && info9.formats.includes(configured)) {
13953
+ if (configured && info10.formats.includes(configured)) {
13791
13954
  return configured;
13792
13955
  }
13793
13956
  }
13794
- return info9.format;
13957
+ return info10.format;
13795
13958
  }
13796
13959
  async function resolvePublishRequest(options, deps) {
13797
13960
  const provider = publishProviderFor(options.providerId);
@@ -13941,17 +14104,17 @@ function overlayStore(inner, key, candidate) {
13941
14104
  }
13942
14105
  async function connectPublishProvider(options, deps) {
13943
14106
  const provider = publishProviderFor(options.providerId);
13944
- const info9 = provider.info;
13945
- if (!info9.credential.required) {
13946
- throw new Error(`${info9.label} needs no API key — just publish when you're ready.`);
14107
+ const info10 = provider.info;
14108
+ if (!info10.credential.required) {
14109
+ throw new Error(`${info10.label} needs no API key — just publish when you're ready.`);
13947
14110
  }
13948
- if (info9.credential.connect === "oauth") {
13949
- 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.`);
14111
+ if (info10.credential.connect === "oauth") {
14112
+ 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.`);
13950
14113
  }
13951
14114
  const token = options.token.trim();
13952
14115
  if (!token)
13953
14116
  throw new Error("Paste an API key first.");
13954
- const host = info9.credential.host;
14117
+ const host = info10.credential.host;
13955
14118
  const account = (options.account ?? "").trim();
13956
14119
  const key = publishCredentialKey(host, account);
13957
14120
  const candidate = {
@@ -13959,12 +14122,12 @@ async function connectPublishProvider(options, deps) {
13959
14122
  kind: "token",
13960
14123
  token,
13961
14124
  ...account ? { username: account } : {},
13962
- label: account || info9.label,
14125
+ label: account || info10.label,
13963
14126
  createdAt: Date.now()
13964
14127
  };
13965
14128
  const req = await resolvePublishRequest({
13966
14129
  projectDir: options.projectDir,
13967
- providerId: info9.id,
14130
+ providerId: info10.id,
13968
14131
  ...options.manifestPath ? { manifestPath: options.manifestPath } : {}
13969
14132
  }, deps);
13970
14133
  const trialDeps = {
@@ -13975,10 +14138,10 @@ async function connectPublishProvider(options, deps) {
13975
14138
  };
13976
14139
  const auth = await provider.authenticate({ ...req, deps: trialDeps });
13977
14140
  if (!auth.ok) {
13978
- throw new Error(auth.message ?? `${info9.label} didn't accept that key.`);
14141
+ throw new Error(auth.message ?? `${info10.label} didn't accept that key.`);
13979
14142
  }
13980
14143
  await deps.tokenStore.set(key, candidate);
13981
- return { connected: true, providerId: info9.id };
14144
+ return { connected: true, providerId: info10.id };
13982
14145
  }
13983
14146
  async function disconnectPublishCredential(key, deps, options = {}) {
13984
14147
  const existing = await deps.tokenStore.get(key);
@@ -14108,7 +14271,7 @@ import git8 from "isomorphic-git";
14108
14271
  // src/lib/remote-auth/scorched-earth.ts
14109
14272
  import git9 from "isomorphic-git";
14110
14273
  // src/lib/publish/selections.ts
14111
- import { chmod as chmod2, mkdir as mkdir13, readFile as readFile31, writeFile as writeFile12 } from "node:fs/promises";
14274
+ import { chmod as chmod2, mkdir as mkdir13, readFile as readFile32, writeFile as writeFile12 } from "node:fs/promises";
14112
14275
  import path26 from "node:path";
14113
14276
  class PublishSelectionsStore {
14114
14277
  filePath;
@@ -14118,7 +14281,7 @@ class PublishSelectionsStore {
14118
14281
  }
14119
14282
  async read() {
14120
14283
  try {
14121
- const raw = await readFile31(this.filePath, "utf8");
14284
+ const raw = await readFile32(this.filePath, "utf8");
14122
14285
  const parsed = JSON.parse(raw);
14123
14286
  if (parsed && typeof parsed === "object") {
14124
14287
  return {