gutterpress 0.11.18 → 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.
Files changed (30) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-67bj810g.js → build-wbfzx37g.js} +6 -6
  3. package/dist/{cdp-smdx6mr3.js → cdp-1hn3xsk2.js} +2 -2
  4. package/dist/{cdp-jg95s674.js → cdp-h0wa42cz.js} +1 -1
  5. package/dist/checks/source/character-table.d.ts +63 -0
  6. package/dist/checks/source/characters.d.ts +1 -0
  7. package/dist/checks/source/index.d.ts +1 -0
  8. package/dist/{cli-6q7trt7z.js → cli-55jd2t8p.js} +341 -163
  9. package/dist/{cli-7fkhgtfn.js → cli-asap4t1z.js} +4 -3
  10. package/dist/{cli-kkzka31p.js → cli-eyyggect.js} +1 -1
  11. package/dist/{cli-xe29z84b.js → cli-r7czdzyz.js} +74 -5
  12. package/dist/{cli-rg8jq04a.js → cli-xf9b7ptc.js} +1 -1
  13. package/dist/{cli-ptez7zcc.js → cli-xx94a1k6.js} +1 -1
  14. package/dist/cli.js +12 -12
  15. package/dist/{doctor-gcqb6xtj.js → doctor-mkhpss2t.js} +3 -3
  16. package/dist/{engine-wtnakejg.js → engine-fe0n7v9p.js} +3 -3
  17. package/dist/{engine-zy6fzs5a.js → engine-zehw6tkv.js} +2 -2
  18. package/dist/{ext-pkaghr3f.js → ext-vw89b9sm.js} +6 -6
  19. package/dist/{index-n8vs6pss.js → index-b866g2ph.js} +1 -1
  20. package/dist/{index-rzm1phg0.js → index-bt0qws5c.js} +4 -3
  21. package/dist/{index-e1hbmb5a.js → index-eqxs5118.js} +412 -167
  22. package/dist/index.js +3 -3
  23. package/dist/lib/exec.d.ts +2 -1
  24. package/dist/lib/ghostscript.d.ts +20 -0
  25. package/dist/{new-rnnavkc8.js → new-3g19kf7m.js} +6 -6
  26. package/dist/{preflight-4769yg8n.js → preflight-62r2cnyx.js} +6 -6
  27. package/dist/{preview-f9csmejj.js → preview-sjq8afwc.js} +6 -6
  28. package/dist/{publish-40yvnb62.js → publish-fq6gmn4e.js} +6 -6
  29. package/dist/{validate-pt921n11.js → validate-2zsqvbtk.js} +6 -6
  30. package/package.json +1 -1
@@ -5,6 +5,7 @@ import {
5
5
  debug,
6
6
  defaultConfigDir,
7
7
  error,
8
+ findPostScriptShadingPages,
8
9
  hasLiveTransparency,
9
10
  info,
10
11
  log,
@@ -13,12 +14,12 @@ import {
13
14
  stampCreator,
14
15
  stripAnnotations,
15
16
  warn
16
- } from "./cli-xe29z84b.js";
17
+ } from "./cli-r7czdzyz.js";
17
18
  import {
18
19
  DEBOUNCE,
19
20
  UsageError,
20
21
  resolvePort
21
- } from "./cli-rg8jq04a.js";
22
+ } from "./cli-xf9b7ptc.js";
22
23
  import {
23
24
  blackTextToGray
24
25
  } from "./cli-ht1bcqdd.js";
@@ -27,7 +28,7 @@ import {
27
28
  getAssetPath,
28
29
  getAssetsDir,
29
30
  lastPageMode
30
- } from "./cli-ptez7zcc.js";
31
+ } from "./cli-xx94a1k6.js";
31
32
  import {
32
33
  BuildError,
33
34
  EXIT_CODES
@@ -39,7 +40,7 @@ import {
39
40
  requireChromiumExecutable,
40
41
  resolveChromiumExecutable,
41
42
  spawnCapture
42
- } from "./cli-7fkhgtfn.js";
43
+ } from "./cli-asap4t1z.js";
43
44
  import {
44
45
  PLUGINS_DIR,
45
46
  VENDORED_NPM_DIR,
@@ -6289,6 +6290,9 @@ function isDroppedPageGradient(decl) {
6289
6290
  return false;
6290
6291
  return isInPageBox(decl);
6291
6292
  }
6293
+ function isPostScriptShadingGradient(decl) {
6294
+ return /(repeating-(linear|radial|conic)|conic)-gradient\(/i.test(decl.value);
6295
+ }
6292
6296
  function isInPageMarginBox(decl) {
6293
6297
  const box = decl.parent;
6294
6298
  if (!box || box.type !== "atrule")
@@ -6537,6 +6541,13 @@ function checkCssRaw(css, from) {
6537
6541
  message: `"${decl.prop}: ${decl.value}" on @page paints nothing in print — Chromium drops a gradient in the @page box's own background, with no error and a valid-looking PDF. A solid colour there paints the whole sheet; the same gradient also works on "html" (it covers the content area, not the sheet) or in a margin box.`,
6538
6542
  ...nodeLoc(decl)
6539
6543
  });
6544
+ } else if (isPostScriptShadingGradient(decl)) {
6545
+ warnings.push({
6546
+ rule: ruleRiskyProps,
6547
+ severity: "warning",
6548
+ message: `"${decl.prop}: ${decl.value}" prints blank in PDF/X exports (--format pdfx): Chromium emits repeating-*-gradient() and conic-gradient() in a form Ghostscript cannot convert. Plain PDF output is unaffected. Use linear-gradient() or radial-gradient() instead; for 0deg/90deg stripes write "linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)" with "background-size: 10px 100%", and for anything else a small tiled image.`,
6549
+ ...nodeLoc(decl)
6550
+ });
6540
6551
  } else if (isUnpairedFitContentWidth(decl)) {
6541
6552
  warnings.push({
6542
6553
  rule: ruleRiskyProps,
@@ -7997,11 +8008,174 @@ var check27 = {
7997
8008
  };
7998
8009
  registerCheck(check27);
7999
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
+
8000
8174
  // src/checks/asset/image-file-size.ts
8001
8175
  import { stat as stat4 } from "node:fs/promises";
8002
8176
 
8003
8177
  // src/lib/image-inspect.ts
8004
- import { stat as stat3, readFile as readFile20 } from "node:fs/promises";
8178
+ import { stat as stat3, readFile as readFile21 } from "node:fs/promises";
8005
8179
  var DEFAULT_DPI = 72;
8006
8180
  var cache = new Map;
8007
8181
  async function inspectImage(path10) {
@@ -8014,15 +8188,15 @@ async function inspectImage(path10) {
8014
8188
  const hit = cache.get(path10);
8015
8189
  if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size)
8016
8190
  return hit.info;
8017
- let info2 = null;
8191
+ let info3 = null;
8018
8192
  try {
8019
- const buf = await readFile20(path10);
8020
- info2 = parseImage(buf);
8193
+ const buf = await readFile21(path10);
8194
+ info3 = parseImage(buf);
8021
8195
  } catch {
8022
- info2 = null;
8196
+ info3 = null;
8023
8197
  }
8024
- cache.set(path10, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
8025
- return info2;
8198
+ cache.set(path10, { mtimeMs: st.mtimeMs, size: st.size, info: info3 });
8199
+ return info3;
8026
8200
  }
8027
8201
  function parseImage(b) {
8028
8202
  if (b.length > 8 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
@@ -8221,7 +8395,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
8221
8395
  }
8222
8396
 
8223
8397
  // src/checks/asset/image-file-size.ts
8224
- var check28 = {
8398
+ var check29 = {
8225
8399
  id: "asset.image.file-size",
8226
8400
  name: "Image File Size",
8227
8401
  description: "Checks that image files do not exceed the maximum size limit",
@@ -8238,18 +8412,18 @@ var check28 = {
8238
8412
  const results = [];
8239
8413
  for (const file of files) {
8240
8414
  try {
8241
- const info2 = await stat4(file);
8242
- if (info2.size > maxSize) {
8243
- 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);
8244
8418
  const maxMb = (maxSize / 1e6).toFixed(1);
8245
- results.push(finding(check28.id, {
8419
+ results.push(finding(check29.id, {
8246
8420
  severity: "warning",
8247
8421
  message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
8248
8422
  file
8249
8423
  }));
8250
8424
  }
8251
8425
  } catch {
8252
- results.push(inspectionFailed(check28.id, `Could not stat image file: ${file}`, {
8426
+ results.push(inspectionFailed(check29.id, `Could not stat image file: ${file}`, {
8253
8427
  file
8254
8428
  }));
8255
8429
  }
@@ -8257,10 +8431,10 @@ var check28 = {
8257
8431
  return results;
8258
8432
  }
8259
8433
  };
8260
- registerCheck(check28);
8434
+ registerCheck(check29);
8261
8435
 
8262
8436
  // src/checks/asset/image-resolution.ts
8263
- var check29 = {
8437
+ var check30 = {
8264
8438
  id: "asset.image.resolution",
8265
8439
  name: "Image Resolution",
8266
8440
  description: "Checks source image DPI from embedded density metadata",
@@ -8276,13 +8450,13 @@ var check29 = {
8276
8450
  return [];
8277
8451
  const results = [];
8278
8452
  for (const file of files) {
8279
- const info2 = await inspectImage(file);
8280
- if (!info2)
8453
+ const info3 = await inspectImage(file);
8454
+ if (!info3)
8281
8455
  continue;
8282
- const { xDpi, yDpi } = info2;
8456
+ const { xDpi, yDpi } = info3;
8283
8457
  if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
8284
8458
  results.push({
8285
- checkId: check29.id,
8459
+ checkId: check30.id,
8286
8460
  severity: "warning",
8287
8461
  message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
8288
8462
  file
@@ -8292,7 +8466,7 @@ var check29 = {
8292
8466
  return results;
8293
8467
  }
8294
8468
  };
8295
- registerCheck(check29);
8469
+ registerCheck(check30);
8296
8470
 
8297
8471
  // src/checks/asset/image-color-space.ts
8298
8472
  var LABEL = {
@@ -8300,7 +8474,7 @@ var LABEL = {
8300
8474
  gray: "Gray",
8301
8475
  cmyk: "CMYK"
8302
8476
  };
8303
- var check30 = {
8477
+ var check31 = {
8304
8478
  id: "asset.image.color-space",
8305
8479
  name: "Image Color Space",
8306
8480
  description: "Validates image color spaces against allowed list",
@@ -8317,11 +8491,11 @@ var check30 = {
8317
8491
  const allowedLower = new Set(allowed.map((s) => s.toLowerCase()));
8318
8492
  const results = [];
8319
8493
  for (const file of files) {
8320
- const info2 = await inspectImage(file);
8321
- const cs = info2?.colorSpace;
8494
+ const info3 = await inspectImage(file);
8495
+ const cs = info3?.colorSpace;
8322
8496
  if (cs && !allowedLower.has(cs)) {
8323
8497
  results.push({
8324
- checkId: check30.id,
8498
+ checkId: check31.id,
8325
8499
  severity: "warning",
8326
8500
  message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
8327
8501
  file
@@ -8331,10 +8505,10 @@ var check30 = {
8331
8505
  return results;
8332
8506
  }
8333
8507
  };
8334
- registerCheck(check30);
8508
+ registerCheck(check31);
8335
8509
 
8336
8510
  // src/checks/asset/image-alpha.ts
8337
- var check31 = {
8511
+ var check32 = {
8338
8512
  id: "asset.image.alpha-channel",
8339
8513
  name: "Image Alpha Channel",
8340
8514
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -8349,10 +8523,10 @@ var check31 = {
8349
8523
  return [];
8350
8524
  const results = [];
8351
8525
  for (const file of files) {
8352
- const info2 = await inspectImage(file);
8353
- if (info2?.hasAlpha) {
8526
+ const info3 = await inspectImage(file);
8527
+ if (info3?.hasAlpha) {
8354
8528
  results.push({
8355
- checkId: check31.id,
8529
+ checkId: check32.id,
8356
8530
  severity: "warning",
8357
8531
  message: "Image contains alpha channel, which may cause print issues",
8358
8532
  file
@@ -8362,10 +8536,10 @@ var check31 = {
8362
8536
  return results;
8363
8537
  }
8364
8538
  };
8365
- registerCheck(check31);
8539
+ registerCheck(check32);
8366
8540
 
8367
8541
  // src/checks/asset/image-tac.ts
8368
- var check32 = {
8542
+ var check33 = {
8369
8543
  id: "asset.image.tac-raster",
8370
8544
  name: "Image TAC (Raster)",
8371
8545
  description: "Rasterizes and checks TAC per image using Ghostscript",
@@ -8384,7 +8558,7 @@ var check32 = {
8384
8558
  const ghostscript = await resolveGhostscript();
8385
8559
  if (!ghostscript) {
8386
8560
  return [
8387
- 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")
8388
8562
  ];
8389
8563
  }
8390
8564
  for (const file of files) {
@@ -8402,7 +8576,7 @@ var check32 = {
8402
8576
  if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
8403
8577
  const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
8404
8578
  if (tac > maxTac) {
8405
- results.push(finding(check32.id, {
8579
+ results.push(finding(check33.id, {
8406
8580
  severity: "warning",
8407
8581
  message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
8408
8582
  file,
@@ -8414,16 +8588,16 @@ var check32 = {
8414
8588
  }
8415
8589
  }
8416
8590
  } catch {
8417
- 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 }));
8418
8592
  }
8419
8593
  }
8420
8594
  return results;
8421
8595
  }
8422
8596
  };
8423
- registerCheck(check32);
8597
+ registerCheck(check33);
8424
8598
 
8425
8599
  // src/checks/asset/approved-fonts.ts
8426
- var check33 = {
8600
+ var check34 = {
8427
8601
  id: "asset.font.approved-files",
8428
8602
  name: "Approved Font Files",
8429
8603
  description: "Checks font files against the approved file patterns",
@@ -8462,7 +8636,7 @@ var check33 = {
8462
8636
  for (const font of allFonts) {
8463
8637
  if (!approvedFonts.has(font)) {
8464
8638
  results.push({
8465
- checkId: check33.id,
8639
+ checkId: check34.id,
8466
8640
  severity: "warning",
8467
8641
  message: "Font file not in approved list",
8468
8642
  file: font
@@ -8472,7 +8646,7 @@ var check33 = {
8472
8646
  return results;
8473
8647
  }
8474
8648
  };
8475
- registerCheck(check33);
8649
+ registerCheck(check34);
8476
8650
 
8477
8651
  // src/checks/asset/font-license.ts
8478
8652
  import { existsSync as existsSync10 } from "node:fs";
@@ -8487,7 +8661,7 @@ var LICENSE_NAMES = [
8487
8661
  "OFL-1.1.txt",
8488
8662
  "COPYING"
8489
8663
  ];
8490
- var check34 = {
8664
+ var check35 = {
8491
8665
  id: "asset.font.license",
8492
8666
  name: "Font License",
8493
8667
  description: "Checks for license files in font directories",
@@ -8514,7 +8688,7 @@ var check34 = {
8514
8688
  const hasLicense = LICENSE_NAMES.some((name) => existsSync10(resolve6(fontDir, name)));
8515
8689
  if (!hasLicense) {
8516
8690
  results.push({
8517
- checkId: check34.id,
8691
+ checkId: check35.id,
8518
8692
  severity: "warning",
8519
8693
  message: `No font license file found in directory`,
8520
8694
  file: fontDir
@@ -8524,10 +8698,10 @@ var check34 = {
8524
8698
  return results;
8525
8699
  }
8526
8700
  };
8527
- registerCheck(check34);
8701
+ registerCheck(check35);
8528
8702
 
8529
8703
  // src/checks/heuristic/text-density.ts
8530
- var check35 = {
8704
+ var check36 = {
8531
8705
  id: "heuristic.whitespace.text-density",
8532
8706
  name: "Text Density",
8533
8707
  description: "Checks characters-per-page ratio",
@@ -8556,7 +8730,7 @@ var check35 = {
8556
8730
  });
8557
8731
  if (lowPages.length > 0) {
8558
8732
  results.push({
8559
- checkId: check35.id,
8733
+ checkId: check36.id,
8560
8734
  severity: "info",
8561
8735
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
8562
8736
  file: ctx.pdfPath
@@ -8564,7 +8738,7 @@ var check35 = {
8564
8738
  }
8565
8739
  if (highPages.length > 0) {
8566
8740
  results.push({
8567
- checkId: check35.id,
8741
+ checkId: check36.id,
8568
8742
  severity: "info",
8569
8743
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
8570
8744
  file: ctx.pdfPath
@@ -8573,11 +8747,11 @@ var check35 = {
8573
8747
  return results;
8574
8748
  }
8575
8749
  };
8576
- registerCheck(check35);
8750
+ registerCheck(check36);
8577
8751
 
8578
8752
  // src/checks/heuristic/section-density.ts
8579
- import { readFile as readFile21 } from "node:fs/promises";
8580
- var check36 = {
8753
+ import { readFile as readFile22 } from "node:fs/promises";
8754
+ var check37 = {
8581
8755
  id: "heuristic.chunking.section-density",
8582
8756
  name: "Section Density",
8583
8757
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -8593,7 +8767,7 @@ var check36 = {
8593
8767
  const results = [];
8594
8768
  for (const file of files) {
8595
8769
  try {
8596
- const content = await readFile21(file, "utf8");
8770
+ const content = await readFile22(file, "utf8");
8597
8771
  const lines = content.split(`
8598
8772
  `);
8599
8773
  let currentSectionStart = 0;
@@ -8603,7 +8777,7 @@ var check36 = {
8603
8777
  const line = lines[i];
8604
8778
  if (/^#{1,6}\s/.test(line)) {
8605
8779
  if (paragraphCount > maxParas) {
8606
- results.push(finding(check36.id, {
8780
+ results.push(finding(check37.id, {
8607
8781
  severity: "info",
8608
8782
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8609
8783
  file,
@@ -8623,7 +8797,7 @@ var check36 = {
8623
8797
  }
8624
8798
  }
8625
8799
  if (paragraphCount > maxParas) {
8626
- results.push(finding(check36.id, {
8800
+ results.push(finding(check37.id, {
8627
8801
  severity: "info",
8628
8802
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8629
8803
  file,
@@ -8631,7 +8805,7 @@ var check36 = {
8631
8805
  }));
8632
8806
  }
8633
8807
  } catch {
8634
- results.push(inspectionFailed(check36.id, `Could not read source file: ${file}`, {
8808
+ results.push(inspectionFailed(check37.id, `Could not read source file: ${file}`, {
8635
8809
  file
8636
8810
  }));
8637
8811
  }
@@ -8639,10 +8813,10 @@ var check36 = {
8639
8813
  return results;
8640
8814
  }
8641
8815
  };
8642
- registerCheck(check36);
8816
+ registerCheck(check37);
8643
8817
 
8644
8818
  // src/checks/heuristic/layer-count.ts
8645
- var check37 = {
8819
+ var check38 = {
8646
8820
  id: "heuristic.decoration.layer-count",
8647
8821
  name: "Layer Count",
8648
8822
  description: "Counts image objects per page",
@@ -8667,7 +8841,7 @@ var check37 = {
8667
8841
  heavyPages.sort((a, b) => a - b);
8668
8842
  return [
8669
8843
  {
8670
- checkId: check37.id,
8844
+ checkId: check38.id,
8671
8845
  severity: "info",
8672
8846
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
8673
8847
  file: ctx.pdfPath
@@ -8677,10 +8851,10 @@ var check37 = {
8677
8851
  return [];
8678
8852
  }
8679
8853
  };
8680
- registerCheck(check37);
8854
+ registerCheck(check38);
8681
8855
 
8682
8856
  // src/checks/heuristic/placement-variance.ts
8683
- var check38 = {
8857
+ var check39 = {
8684
8858
  id: "heuristic.layout.placement-variance",
8685
8859
  name: "Placement Variance",
8686
8860
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -8698,7 +8872,7 @@ var check38 = {
8698
8872
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
8699
8873
  return [
8700
8874
  {
8701
- checkId: check38.id,
8875
+ checkId: check39.id,
8702
8876
  severity: "info",
8703
8877
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
8704
8878
  file: ctx.pdfPath
@@ -8706,21 +8880,21 @@ var check38 = {
8706
8880
  ];
8707
8881
  }
8708
8882
  };
8709
- registerCheck(check38);
8883
+ registerCheck(check39);
8710
8884
 
8711
8885
  // src/checks/tool-check.ts
8712
8886
  async function checkToolAvailability(config, opts = {}) {
8713
8887
  const { checks: checks2 } = selectChecks(opts, config);
8714
8888
  const toolToChecks = new Map;
8715
- for (const check39 of checks2) {
8716
- if (!check39.requiredTools?.length)
8889
+ for (const check40 of checks2) {
8890
+ if (!check40.requiredTools?.length)
8717
8891
  continue;
8718
- for (const tool of check39.requiredTools) {
8892
+ for (const tool of check40.requiredTools) {
8719
8893
  const existing = toolToChecks.get(tool);
8720
8894
  if (existing) {
8721
- existing.push(check39.id);
8895
+ existing.push(check40.id);
8722
8896
  } else {
8723
- toolToChecks.set(tool, [check39.id]);
8897
+ toolToChecks.set(tool, [check40.id]);
8724
8898
  }
8725
8899
  }
8726
8900
  }
@@ -8736,11 +8910,11 @@ async function checkToolAvailability(config, opts = {}) {
8736
8910
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
8737
8911
  const missingSet = new Set(missing);
8738
8912
  const skippedChecks = [];
8739
- for (const check39 of checks2) {
8740
- if (!check39.requiredTools?.length)
8913
+ for (const check40 of checks2) {
8914
+ if (!check40.requiredTools?.length)
8741
8915
  continue;
8742
- if (check39.requiredTools.some((t) => missingSet.has(t))) {
8743
- skippedChecks.push(check39.id);
8916
+ if (check40.requiredTools.some((t) => missingSet.has(t))) {
8917
+ skippedChecks.push(check40.id);
8744
8918
  }
8745
8919
  }
8746
8920
  return { available, missing, skippedChecks, toolToChecks };
@@ -8897,7 +9071,7 @@ async function restoreForCommand(dir, options) {
8897
9071
  }
8898
9072
 
8899
9073
  // src/lib/markdown/index.ts
8900
- 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";
8901
9075
 
8902
9076
  // src/lib/remote-auth/sync-messages.ts
8903
9077
  function isOnlineSibling(filepath) {
@@ -9039,7 +9213,7 @@ ${css.trim()}
9039
9213
  }
9040
9214
  return assembleBookHtml({
9041
9215
  files,
9042
- readText: (relPath) => readFile22(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9216
+ readText: (relPath) => readFile23(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9043
9217
  projectCss: inlined.css,
9044
9218
  preloadImages: cssAssetCopies.map((c) => c.to),
9045
9219
  title: opts.title,
@@ -9071,7 +9245,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
9071
9245
  }
9072
9246
 
9073
9247
  // src/lib/markdownlint-fix.ts
9074
- import { readFile as readFile23, writeFile as writeFile3 } from "node:fs/promises";
9248
+ import { readFile as readFile24, writeFile as writeFile3 } from "node:fs/promises";
9075
9249
  import { applyFixes } from "markdownlint";
9076
9250
  import { lint as lint2 } from "markdownlint/sync";
9077
9251
  async function uncommittedAmong(inputDir, files) {
@@ -9111,7 +9285,7 @@ async function applyMarkdownlintFixes(ctx) {
9111
9285
  for (const [file, violations] of Object.entries(results)) {
9112
9286
  if (!Array.isArray(violations) || violations.length === 0)
9113
9287
  continue;
9114
- const before = await readFile23(file, "utf8");
9288
+ const before = await readFile24(file, "utf8");
9115
9289
  const after = applyFixes(before, violations);
9116
9290
  if (after !== before)
9117
9291
  pending.push({ file, content: after });
@@ -9220,16 +9394,16 @@ async function runChecks(ctx, opts = {}) {
9220
9394
  }
9221
9395
  const release = retainPdfCache();
9222
9396
  try {
9223
- for (const check39 of checks2) {
9397
+ for (const check40 of checks2) {
9224
9398
  try {
9225
- let results = await check39.run(ctx);
9226
- const severityOverride = getCheckSeverityOverride(check39.id, ctx.config);
9399
+ let results = await check40.run(ctx);
9400
+ const severityOverride = getCheckSeverityOverride(check40.id, ctx.config);
9227
9401
  if (severityOverride) {
9228
9402
  for (const r of results) {
9229
9403
  r.severity = severityOverride;
9230
9404
  }
9231
9405
  }
9232
- const lintLevel = ruleLevel(ctx.config.lint.rules, check39.id);
9406
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check40.id);
9233
9407
  if (lintLevel === "off")
9234
9408
  results = [];
9235
9409
  else if (lintLevel) {
@@ -9237,15 +9411,15 @@ async function runChecks(ctx, opts = {}) {
9237
9411
  r.severity = lintLevel === "warn" ? "warning" : "error";
9238
9412
  }
9239
9413
  if (results.length === 0) {
9240
- passed.push(check39.id);
9414
+ passed.push(check40.id);
9241
9415
  } else {
9242
9416
  allResults.push(...results);
9243
9417
  }
9244
9418
  } catch (err) {
9245
9419
  allResults.push({
9246
- checkId: check39.id,
9420
+ checkId: check40.id,
9247
9421
  severity: "error",
9248
- 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)}`
9249
9423
  });
9250
9424
  }
9251
9425
  }
@@ -9537,7 +9711,7 @@ async function executeValidation(args) {
9537
9711
  };
9538
9712
  if (args.fix) {
9539
9713
  const selected = selectChecks(runnerOptions, config).checks;
9540
- if (selected.some((check39) => check39.id === MARKDOWNLINT_CHECK_ID)) {
9714
+ if (selected.some((check40) => check40.id === MARKDOWNLINT_CHECK_ID)) {
9541
9715
  await applyMarkdownlintFixes(context);
9542
9716
  } else {
9543
9717
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -9624,7 +9798,7 @@ import { randomBytes } from "node:crypto";
9624
9798
 
9625
9799
  // src/lib/build-fingerprint.ts
9626
9800
  import * as fs from "node:fs";
9627
- 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";
9628
9802
  import { createHash as createHash4 } from "node:crypto";
9629
9803
  import path12 from "node:path";
9630
9804
  import git from "isomorphic-git";
@@ -9711,8 +9885,8 @@ async function getEngineBundleHash() {
9711
9885
  getAssetPath("engine/gutterpress-agent.js")
9712
9886
  ]);
9713
9887
  const [viewerBytes, agentBytes] = await Promise.all([
9714
- readFile24(viewerPath),
9715
- readFile24(agentPath)
9888
+ readFile25(viewerPath),
9889
+ readFile25(agentPath)
9716
9890
  ]);
9717
9891
  const hash = createHash4("sha256");
9718
9892
  hash.update(viewerBytes);
@@ -10082,7 +10256,7 @@ class PdfOutput {
10082
10256
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10083
10257
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10084
10258
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10085
- const { buildNativePdf } = await import("./engine-wtnakejg.js");
10259
+ const { buildNativePdf } = await import("./engine-fe0n7v9p.js");
10086
10260
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10087
10261
  title: config.title,
10088
10262
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10107,6 +10281,10 @@ class PdfOutput {
10107
10281
  if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
10108
10282
  log.warn("This book uses transparency — an image with an alpha channel, or a CSS " + `rule like \`opacity\` or \`mix-blend-mode\`. PDF/${pdfxMode === "x1a" ? "X-1a" : "X-3"} has no way to represent that (both are based on PDF 1.3, which predates PDF transparency), so Ghostscript will flatten every page that uses it into a single raster ` + "image — that page loses its embedded fonts and searchable text in the " + "PDF/X output. To keep vector text, remove the `opacity`/blend rule and flatten any alpha-channel image against its intended background before including it (export it with no alpha channel), or build --format pdf instead of pdfx if this book doesn't need print-ready CMYK.");
10109
10283
  }
10284
+ const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
10285
+ if (blankGradientPages.length > 0) {
10286
+ log.warn(`Page${blankGradientPages.length === 1 ? "" : "s"} ${blankGradientPages.join(", ")}: ` + "a repeating-*-gradient() or conic-gradient() will print blank — " + "Ghostscript cannot convert it to PDF/X. Replace it with a plain linear-gradient() or radial-gradient(), or a small tiled image. For 0deg/90deg stripes, `linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)` with `background-size: 10px 100%` converts correctly.");
10287
+ }
10110
10288
  const shouldStrip = opts.stripAnnotations ?? config.pdfx.stripAnnotations;
10111
10289
  shouldStripAnnotations = shouldStrip;
10112
10290
  if (shouldStrip) {
@@ -10161,7 +10339,7 @@ async function runBuild(opts) {
10161
10339
  if (ctx.format !== "html") {
10162
10340
  await preflightBuildTools(ctx.format, opts, ctx.config);
10163
10341
  }
10164
- const launch = opts.engineBrowser ?? (async () => (await import("./cdp-smdx6mr3.js")).launchChromium());
10342
+ const launch = opts.engineBrowser ?? (async () => (await import("./cdp-1hn3xsk2.js")).launchChromium());
10165
10343
  const browserPromise = ctx.format === "html" ? null : launch();
10166
10344
  browserPromise?.catch(() => {});
10167
10345
  try {
@@ -10217,7 +10395,7 @@ function openPath(filePath) {
10217
10395
  import path16 from "path";
10218
10396
  import { tmpdir } from "os";
10219
10397
  import { randomBytes as randomBytes2 } from "crypto";
10220
- 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";
10221
10399
 
10222
10400
  // src/utils/file-utils.ts
10223
10401
  import { promises as fs3 } from "fs";
@@ -10769,7 +10947,7 @@ async function cleanupOrphanTempDirs() {
10769
10947
  const dir = path16.join(TEMP_DIR_BASE, entry);
10770
10948
  const pidFile = path16.join(dir, PID_FILE_NAME);
10771
10949
  try {
10772
- const raw = await readFile25(pidFile, "utf8");
10950
+ const raw = await readFile26(pidFile, "utf8");
10773
10951
  const pid = parseInt(raw.trim(), 10);
10774
10952
  if (!isProcessAlive(pid)) {
10775
10953
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -10876,7 +11054,7 @@ function createServerState(inputPath, tempDir, config, options) {
10876
11054
  import http from "node:http";
10877
11055
  import net from "node:net";
10878
11056
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
10879
- import { readFile as readFile26, stat as stat5 } from "node:fs/promises";
11057
+ import { readFile as readFile27, stat as stat5 } from "node:fs/promises";
10880
11058
  import path17 from "path";
10881
11059
  import { WebSocket, WebSocketServer } from "ws";
10882
11060
  var HMR_PATH = "/__gutterpress-hmr";
@@ -10945,7 +11123,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
10945
11123
  }
10946
11124
  let data;
10947
11125
  try {
10948
- data = await readFile26(filePath);
11126
+ data = await readFile27(filePath);
10949
11127
  } catch {
10950
11128
  res.writeHead(404);
10951
11129
  res.end("Not Found");
@@ -10994,7 +11172,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
10994
11172
  let data;
10995
11173
  if (!validator || validator.stamp !== stamp) {
10996
11174
  try {
10997
- data = await readFile26(absPath);
11175
+ data = await readFile27(absPath);
10998
11176
  } catch {
10999
11177
  await serveStatic(absPath, res);
11000
11178
  return;
@@ -11304,7 +11482,7 @@ async function startPreviewServer(options) {
11304
11482
  }
11305
11483
 
11306
11484
  // src/lib/project-scaffold.ts
11307
- 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";
11308
11486
  import { constants as FS, existsSync as existsSync12 } from "node:fs";
11309
11487
  import path18 from "node:path";
11310
11488
 
@@ -11503,7 +11681,7 @@ async function scaffoldProject(options) {
11503
11681
  async function firstSourceFile(manifestPath) {
11504
11682
  let text;
11505
11683
  try {
11506
- text = await readFile27(manifestPath, "utf8");
11684
+ text = await readFile28(manifestPath, "utf8");
11507
11685
  } catch {
11508
11686
  return;
11509
11687
  }
@@ -11515,7 +11693,7 @@ async function firstSourceFile(manifestPath) {
11515
11693
  return m ? m[1].trim() : undefined;
11516
11694
  }
11517
11695
  async function fillTemplateFile(filePath, substitutions) {
11518
- let text = await readFile27(filePath, "utf8");
11696
+ let text = await readFile28(filePath, "utf8");
11519
11697
  for (const [token, value] of Object.entries(substitutions)) {
11520
11698
  text = text.split(token).join(value);
11521
11699
  }
@@ -11523,7 +11701,7 @@ async function fillTemplateFile(filePath, substitutions) {
11523
11701
  }
11524
11702
 
11525
11703
  // src/lib/extension-scaffold.ts
11526
- 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";
11527
11705
  import { constants as FS2 } from "node:fs";
11528
11706
  import path19 from "node:path";
11529
11707
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -11640,7 +11818,7 @@ async function scaffoldExtension(options) {
11640
11818
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
11641
11819
  const dest = path19.join(extensionDir, relDest);
11642
11820
  await mkdir8(path19.dirname(dest), { recursive: true });
11643
- const text = await readFile28(src, "utf8");
11821
+ const text = await readFile29(src, "utf8");
11644
11822
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
11645
11823
  await writeFile7(dest, substitute(text, table), "utf8");
11646
11824
  written.push(relDest);
@@ -11685,7 +11863,7 @@ var SKIP_ENTRIES = new Set([
11685
11863
 
11686
11864
  // src/lib/extension-import.ts
11687
11865
  import { existsSync as existsSync13 } from "node:fs";
11688
- 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";
11689
11867
  import { tmpdir as tmpdir2 } from "node:os";
11690
11868
  import path20 from "node:path";
11691
11869
  import { unzipSync } from "fflate";
@@ -11758,7 +11936,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11758
11936
  if (!existsSync13(cssPath)) {
11759
11937
  throw new Error("A theme must contain a theme.css file.");
11760
11938
  }
11761
- const css = await readFile29(cssPath, "utf8");
11939
+ const css = await readFile30(cssPath, "utf8");
11762
11940
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
11763
11941
  if (reject) {
11764
11942
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -11786,7 +11964,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11786
11964
  for (let i = 0;i < extraSheets.length; i++) {
11787
11965
  const rel = extraSheets[i];
11788
11966
  const sheetPath = extraSheetPaths[i];
11789
- const sheetCss = await readFile29(sheetPath, "utf8");
11967
+ const sheetCss = await readFile30(sheetPath, "utf8");
11790
11968
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
11791
11969
  if (sheetReject) {
11792
11970
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -11819,7 +11997,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
11819
11997
  let pkg = {};
11820
11998
  if (metaExists) {
11821
11999
  try {
11822
- const parsed = JSON.parse(await readFile29(file, "utf8"));
12000
+ const parsed = JSON.parse(await readFile30(file, "utf8"));
11823
12001
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
11824
12002
  pkg = parsed;
11825
12003
  }
@@ -11921,11 +12099,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
11921
12099
  async function importExtensionFromFile(projectDir, filePath) {
11922
12100
  const ext = path20.extname(filePath).toLowerCase();
11923
12101
  if (ext === ".zip") {
11924
- const buf = await readFile29(filePath);
12102
+ const buf = await readFile30(filePath);
11925
12103
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
11926
12104
  }
11927
12105
  if (ext === ".css") {
11928
- const css = await readFile29(filePath, "utf8");
12106
+ const css = await readFile30(filePath, "utf8");
11929
12107
  return importExtensionFromCssText(projectDir, css, prettify(path20.basename(filePath, ".css")));
11930
12108
  }
11931
12109
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -12129,13 +12307,13 @@ function publishCredentialKey(host, account) {
12129
12307
  const a = (account ?? "").trim();
12130
12308
  return a ? `${host}#${a}` : host;
12131
12309
  }
12132
- async function resolvePublishCredential(info2, deps, account = deps.credentialAccount) {
12310
+ async function resolvePublishCredential(info3, deps, account = deps.credentialAccount) {
12133
12311
  const env = deps.env ?? process.env;
12134
- const fromEnv = info2.credential.envVar ? env[info2.credential.envVar]?.trim() : undefined;
12312
+ const fromEnv = info3.credential.envVar ? env[info3.credential.envVar]?.trim() : undefined;
12135
12313
  if (fromEnv) {
12136
12314
  return {
12137
12315
  credential: {
12138
- host: info2.credential.host,
12316
+ host: info3.credential.host,
12139
12317
  kind: "token",
12140
12318
  token: fromEnv,
12141
12319
  createdAt: 0
@@ -12143,21 +12321,21 @@ async function resolvePublishCredential(info2, deps, account = deps.credentialAc
12143
12321
  source: "env"
12144
12322
  };
12145
12323
  }
12146
- const stored = await deps.tokenStore.get(publishCredentialKey(info2.credential.host, account));
12324
+ const stored = await deps.tokenStore.get(publishCredentialKey(info3.credential.host, account));
12147
12325
  return stored ? { credential: stored, source: "store" } : null;
12148
12326
  }
12149
- async function publishConnectionStatus(info2, deps, account = deps.credentialAccount) {
12150
- if (!info2.credential.required)
12327
+ async function publishConnectionStatus(info3, deps, account = deps.credentialAccount) {
12328
+ if (!info3.credential.required)
12151
12329
  return { connected: true };
12152
- const resolved = await resolvePublishCredential(info2, deps, account);
12330
+ const resolved = await resolvePublishCredential(info3, deps, account);
12153
12331
  return resolved ? { connected: true, source: resolved.source } : { connected: false };
12154
12332
  }
12155
- async function listPublishAccounts(info2, deps) {
12156
- const wantHost = info2.credential.host.trim().toLowerCase();
12333
+ async function listPublishAccounts(info3, deps) {
12334
+ const wantHost = info3.credential.host.trim().toLowerCase();
12157
12335
  const all = await deps.tokenStore.list();
12158
12336
  return all.filter((c) => c.host.trim().toLowerCase() === wantHost).map((c) => ({
12159
12337
  account: (c.username ?? "").trim(),
12160
- label: c.label ?? (c.username || info2.label),
12338
+ label: c.label ?? (c.username || info3.label),
12161
12339
  createdAt: c.createdAt
12162
12340
  }));
12163
12341
  }
@@ -12188,8 +12366,8 @@ async function readGoogleApiError(res) {
12188
12366
  }
12189
12367
  var RATE_LIMIT_REASONS = /^(dailyLimitExceeded|userRateLimitExceeded|rateLimitExceeded|quotaExceeded)$/i;
12190
12368
  var PERMISSION_REASONS = /^(insufficientPermissions|insufficientFilePermissions|forbidden|PERMISSION_DENIED)$/i;
12191
- function hintFor(info2) {
12192
- const reason = info2.reason ?? "";
12369
+ function hintFor(info3) {
12370
+ const reason = info3.reason ?? "";
12193
12371
  if (/^accessNotConfigured$/i.test(reason)) {
12194
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).";
12195
12373
  }
@@ -12199,18 +12377,18 @@ function hintFor(info2) {
12199
12377
  if (RATE_LIMIT_REASONS.test(reason)) {
12200
12378
  return "Google Drive is rate-limiting requests right now. Wait a minute and try again.";
12201
12379
  }
12202
- if (PERMISSION_REASONS.test(reason) || info2.status === 401) {
12380
+ if (PERMISSION_REASONS.test(reason) || info3.status === 401) {
12203
12381
  return "This Google Drive connection doesn't have permission for that — disconnect and connect Google Drive again to re-approve access.";
12204
12382
  }
12205
12383
  return;
12206
12384
  }
12207
- function googleApiFailure(what, info2) {
12208
- const parts = [`${what} (HTTP ${info2.status}${info2.reason ? `, ${info2.reason}` : ""}).`];
12209
- 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);
12210
12388
  if (hint)
12211
12389
  parts.push(hint);
12212
- if (info2.message)
12213
- parts.push(`Google said: "${info2.message}"`);
12390
+ if (info3.message)
12391
+ parts.push(`Google said: "${info3.message}"`);
12214
12392
  let text = parts.join(" ");
12215
12393
  if (!/\bgoogle\b/i.test(text))
12216
12394
  text = `Google Drive: ${text}`;
@@ -12538,7 +12716,7 @@ async function commandExists(cmd, runCommand = defaultCommandRunner, env) {
12538
12716
 
12539
12717
  // src/lib/publish/providers/azure-swa.ts
12540
12718
  var AZURE_SWA_HOST = "azure-swa";
12541
- var info2 = {
12719
+ var info3 = {
12542
12720
  id: "azure-swa",
12543
12721
  label: "Azure Static Web Apps",
12544
12722
  kind: "api",
@@ -12563,9 +12741,9 @@ async function resolveSwaCommand(req) {
12563
12741
  }
12564
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.";
12565
12743
  var azureSwaProvider = {
12566
- info: info2,
12744
+ info: info3,
12567
12745
  async authenticate(req) {
12568
- const resolved = await resolvePublishCredential(info2, req.deps);
12746
+ const resolved = await resolvePublishCredential(info3, req.deps);
12569
12747
  if (!resolved) {
12570
12748
  return {
12571
12749
  ok: false,
@@ -12596,7 +12774,7 @@ var azureSwaProvider = {
12596
12774
  return issues;
12597
12775
  },
12598
12776
  async upload(req) {
12599
- const resolved = await resolvePublishCredential(info2, req.deps);
12777
+ const resolved = await resolvePublishCredential(info3, req.deps);
12600
12778
  if (!resolved) {
12601
12779
  throw new Error("No Azure deployment token found. Connect Azure Static Web Apps (or set SWA_CLI_DEPLOYMENT_TOKEN) first.");
12602
12780
  }
@@ -12652,7 +12830,7 @@ async function stageGuidedPackage(req, providerId, listingLines) {
12652
12830
 
12653
12831
  // src/lib/publish/providers/drivethrurpg.ts
12654
12832
  var PUBLISHER_HUB_URL = "https://www.drivethrurpg.com/publisher_hub";
12655
- var info3 = {
12833
+ var info4 = {
12656
12834
  id: "drivethrurpg",
12657
12835
  label: "DriveThruRPG",
12658
12836
  kind: "guided",
@@ -12668,7 +12846,7 @@ var info3 = {
12668
12846
  credential: { required: false, host: "drivethrurpg.com" }
12669
12847
  };
12670
12848
  var drivethrurpgProvider = {
12671
- info: info3,
12849
+ info: info4,
12672
12850
  async authenticate() {
12673
12851
  return { ok: true };
12674
12852
  },
@@ -12709,7 +12887,7 @@ var drivethrurpgProvider = {
12709
12887
  };
12710
12888
 
12711
12889
  // src/lib/publish/providers/gdrive.ts
12712
- 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";
12713
12891
  import { tmpdir as tmpdir3 } from "node:os";
12714
12892
  import path22 from "node:path";
12715
12893
  import { Zip, ZipDeflate } from "fflate";
@@ -12977,7 +13155,7 @@ async function resumableUpload(fetchImpl, accessToken, opts) {
12977
13155
  // src/lib/publish/providers/gdrive.ts
12978
13156
  var DEFAULT_FOLDER_NAME = "Gutterpress";
12979
13157
  var NOT_CONNECTED_MESSAGE = "Google Drive isn't connected. Run `gutterpress publish --provider gdrive --connect` (or set GDRIVE_REFRESH_TOKEN) first.";
12980
- var info4 = {
13158
+ var info5 = {
12981
13159
  id: "gdrive",
12982
13160
  label: "Google Drive",
12983
13161
  kind: "api",
@@ -13016,7 +13194,7 @@ async function mintAccessToken(resolved, fetchImpl) {
13016
13194
  return accessToken;
13017
13195
  }
13018
13196
  async function getAccessToken(req) {
13019
- const resolved = await resolvePublishCredential(info4, req.deps);
13197
+ const resolved = await resolvePublishCredential(info5, req.deps);
13020
13198
  if (!resolved)
13021
13199
  throw new Error(NOT_CONNECTED_MESSAGE);
13022
13200
  const accessToken = await mintAccessToken(resolved, req.deps.fetch ?? globalThis.fetch);
@@ -13101,12 +13279,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
13101
13279
  await collectZipEntries(root, full, out, onWarn);
13102
13280
  } else if (entry.isFile()) {
13103
13281
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13104
- out[rel] = await readFile30(full);
13282
+ out[rel] = await readFile31(full);
13105
13283
  } else if (entry.isSymbolicLink()) {
13106
13284
  const resolved = await stat7(full).catch(() => null);
13107
13285
  if (resolved?.isFile()) {
13108
13286
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13109
- out[rel] = await readFile30(full);
13287
+ out[rel] = await readFile31(full);
13110
13288
  } else if (resolved?.isDirectory()) {
13111
13289
  await collectZipEntries(root, full, out, onWarn);
13112
13290
  } else {
@@ -13138,9 +13316,9 @@ async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl = writeF
13138
13316
  }
13139
13317
  }
13140
13318
  var gdriveProvider = {
13141
- info: info4,
13319
+ info: info5,
13142
13320
  async authenticate(req) {
13143
- const resolved = await resolvePublishCredential(info4, req.deps);
13321
+ const resolved = await resolvePublishCredential(info5, req.deps);
13144
13322
  if (!resolved)
13145
13323
  return { ok: false, message: NOT_CONNECTED_MESSAGE };
13146
13324
  try {
@@ -13305,7 +13483,7 @@ async function ensureButler(deps) {
13305
13483
  var ITCH_HOST = "itch.io";
13306
13484
  var TARGET_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
13307
13485
  var ITCH_API_TIMEOUT_MS = 15000;
13308
- var info5 = {
13486
+ var info6 = {
13309
13487
  id: "itch",
13310
13488
  label: "itch.io",
13311
13489
  kind: "api",
@@ -13335,9 +13513,9 @@ function itchProjectUrl(target) {
13335
13513
  return `https://${user}.itch.io/${game}`;
13336
13514
  }
13337
13515
  var itchProvider = {
13338
- info: info5,
13516
+ info: info6,
13339
13517
  async authenticate(req) {
13340
- const resolved = await resolvePublishCredential(info5, req.deps);
13518
+ const resolved = await resolvePublishCredential(info6, req.deps);
13341
13519
  if (!resolved) {
13342
13520
  return {
13343
13521
  ok: false,
@@ -13405,7 +13583,7 @@ var itchProvider = {
13405
13583
  },
13406
13584
  async upload(req) {
13407
13585
  const { target, channel } = readConfig2(req);
13408
- const resolved = await resolvePublishCredential(info5, req.deps);
13586
+ const resolved = await resolvePublishCredential(info6, req.deps);
13409
13587
  if (!resolved) {
13410
13588
  throw new Error("No itch.io API key found. Connect itch.io (or set BUTLER_API_KEY) first.");
13411
13589
  }
@@ -13433,7 +13611,7 @@ ${tail}` : ""}`);
13433
13611
 
13434
13612
  // src/lib/publish/providers/kdp.ts
13435
13613
  var KDP_URL = "https://kdp.amazon.com/en_US/bookshelf";
13436
- var info6 = {
13614
+ var info7 = {
13437
13615
  id: "kdp",
13438
13616
  label: "Amazon KDP",
13439
13617
  kind: "guided",
@@ -13443,7 +13621,7 @@ var info6 = {
13443
13621
  credential: { required: false, host: "kdp.amazon.com" }
13444
13622
  };
13445
13623
  var kdpProvider = {
13446
- info: info6,
13624
+ info: info7,
13447
13625
  async authenticate() {
13448
13626
  return { ok: true };
13449
13627
  },
@@ -13488,7 +13666,7 @@ var kdpProvider = {
13488
13666
  import { cp as cp4, mkdir as mkdir12, stat as stat9 } from "node:fs/promises";
13489
13667
  import path24 from "node:path";
13490
13668
  var LOCAL_PUBLISH_DEFAULT_DIR = "dist";
13491
- var info7 = {
13669
+ var info8 = {
13492
13670
  id: "local",
13493
13671
  label: "A folder on this computer",
13494
13672
  kind: "api",
@@ -13505,7 +13683,7 @@ function localPublishDir(req) {
13505
13683
  return path24.resolve(req.project.projectDir, dir);
13506
13684
  }
13507
13685
  var localFolderProvider = {
13508
- info: info7,
13686
+ info: info8,
13509
13687
  async authenticate() {
13510
13688
  return { ok: true };
13511
13689
  },
@@ -13546,7 +13724,7 @@ function requireValidShop(shop) {
13546
13724
  }
13547
13725
  return shop;
13548
13726
  }
13549
- var info8 = {
13727
+ var info9 = {
13550
13728
  id: "shopify",
13551
13729
  label: "Shopify",
13552
13730
  kind: "api",
@@ -13580,7 +13758,7 @@ function readConfig3(req) {
13580
13758
  async function adminGraphQL(req, query, variables = {}) {
13581
13759
  const { shop: rawShop, apiVersion } = readConfig3(req);
13582
13760
  const shop = requireValidShop(rawShop);
13583
- const resolved = await resolvePublishCredential(info8, req.deps);
13761
+ const resolved = await resolvePublishCredential(info9, req.deps);
13584
13762
  if (!resolved) {
13585
13763
  throw new Error("No Shopify access token found. Connect Shopify (or set SHOPIFY_ADMIN_TOKEN) first.");
13586
13764
  }
@@ -13631,9 +13809,9 @@ function userErrorsToMessage(result) {
13631
13809
  return errs?.length ? errs[0].message : null;
13632
13810
  }
13633
13811
  var shopifyProvider = {
13634
- info: info8,
13812
+ info: info9,
13635
13813
  async authenticate(req) {
13636
- const resolved = await resolvePublishCredential(info8, req.deps);
13814
+ const resolved = await resolvePublishCredential(info9, req.deps);
13637
13815
  if (!resolved) {
13638
13816
  return {
13639
13817
  ok: false,
@@ -13769,14 +13947,14 @@ function publishProviderFor(id) {
13769
13947
  // src/lib/publish/run-publish.ts
13770
13948
  import { readdir as readdir7, stat as stat10 } from "node:fs/promises";
13771
13949
  import path25 from "node:path";
13772
- function resolvePublishFormat(info9, providerConfig) {
13773
- if (info9.formats && info9.formats.length > 0) {
13950
+ function resolvePublishFormat(info10, providerConfig) {
13951
+ if (info10.formats && info10.formats.length > 0) {
13774
13952
  const configured = typeof providerConfig.format === "string" ? providerConfig.format.trim() : "";
13775
- if (configured && info9.formats.includes(configured)) {
13953
+ if (configured && info10.formats.includes(configured)) {
13776
13954
  return configured;
13777
13955
  }
13778
13956
  }
13779
- return info9.format;
13957
+ return info10.format;
13780
13958
  }
13781
13959
  async function resolvePublishRequest(options, deps) {
13782
13960
  const provider = publishProviderFor(options.providerId);
@@ -13926,17 +14104,17 @@ function overlayStore(inner, key, candidate) {
13926
14104
  }
13927
14105
  async function connectPublishProvider(options, deps) {
13928
14106
  const provider = publishProviderFor(options.providerId);
13929
- const info9 = provider.info;
13930
- if (!info9.credential.required) {
13931
- 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.`);
13932
14110
  }
13933
- if (info9.credential.connect === "oauth") {
13934
- 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.`);
13935
14113
  }
13936
14114
  const token = options.token.trim();
13937
14115
  if (!token)
13938
14116
  throw new Error("Paste an API key first.");
13939
- const host = info9.credential.host;
14117
+ const host = info10.credential.host;
13940
14118
  const account = (options.account ?? "").trim();
13941
14119
  const key = publishCredentialKey(host, account);
13942
14120
  const candidate = {
@@ -13944,12 +14122,12 @@ async function connectPublishProvider(options, deps) {
13944
14122
  kind: "token",
13945
14123
  token,
13946
14124
  ...account ? { username: account } : {},
13947
- label: account || info9.label,
14125
+ label: account || info10.label,
13948
14126
  createdAt: Date.now()
13949
14127
  };
13950
14128
  const req = await resolvePublishRequest({
13951
14129
  projectDir: options.projectDir,
13952
- providerId: info9.id,
14130
+ providerId: info10.id,
13953
14131
  ...options.manifestPath ? { manifestPath: options.manifestPath } : {}
13954
14132
  }, deps);
13955
14133
  const trialDeps = {
@@ -13960,10 +14138,10 @@ async function connectPublishProvider(options, deps) {
13960
14138
  };
13961
14139
  const auth = await provider.authenticate({ ...req, deps: trialDeps });
13962
14140
  if (!auth.ok) {
13963
- 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.`);
13964
14142
  }
13965
14143
  await deps.tokenStore.set(key, candidate);
13966
- return { connected: true, providerId: info9.id };
14144
+ return { connected: true, providerId: info10.id };
13967
14145
  }
13968
14146
  async function disconnectPublishCredential(key, deps, options = {}) {
13969
14147
  const existing = await deps.tokenStore.get(key);
@@ -14093,7 +14271,7 @@ import git8 from "isomorphic-git";
14093
14271
  // src/lib/remote-auth/scorched-earth.ts
14094
14272
  import git9 from "isomorphic-git";
14095
14273
  // src/lib/publish/selections.ts
14096
- 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";
14097
14275
  import path26 from "node:path";
14098
14276
  class PublishSelectionsStore {
14099
14277
  filePath;
@@ -14103,7 +14281,7 @@ class PublishSelectionsStore {
14103
14281
  }
14104
14282
  async read() {
14105
14283
  try {
14106
- const raw = await readFile31(this.filePath, "utf8");
14284
+ const raw = await readFile32(this.filePath, "utf8");
14107
14285
  const parsed = JSON.parse(raw);
14108
14286
  if (parsed && typeof parsed === "object") {
14109
14287
  return {