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
@@ -12,6 +12,7 @@ import {
12
12
  } from "./index-jt552wtf.js";
13
13
  import {
14
14
  INSTALL_HINTS,
15
+ enhancedPath,
15
16
  execCapture,
16
17
  findTool,
17
18
  fullInstallHint,
@@ -20,7 +21,7 @@ import {
20
21
  resolveChromiumExecutable,
21
22
  run,
22
23
  spawnCapture
23
- } from "./index-rzm1phg0.js";
24
+ } from "./index-bt0qws5c.js";
24
25
  import {
25
26
  PLUGINS_DIR,
26
27
  VENDORED_NPM_DIR,
@@ -5774,6 +5775,58 @@ async function hasLiveTransparency(pdfBytes) {
5774
5775
  }
5775
5776
  return false;
5776
5777
  }
5778
+ async function findPostScriptShadingPages(pdfBytes) {
5779
+ const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } = await import("pdf-lib");
5780
+ let doc;
5781
+ try {
5782
+ doc = await PDFDocument.load(pdfBytes, {
5783
+ updateMetadata: false,
5784
+ throwOnInvalidObject: false
5785
+ });
5786
+ } catch {
5787
+ return [];
5788
+ }
5789
+ const ctx = doc.context;
5790
+ const asDict = (obj) => {
5791
+ const o = obj instanceof PDFRef ? ctx.lookup(obj) : obj;
5792
+ return o instanceof PDFRawStream ? o.dict : o instanceof PDFDict ? o : undefined;
5793
+ };
5794
+ const num = (obj) => {
5795
+ const o = obj instanceof PDFRef ? ctx.lookup(obj) : obj;
5796
+ return o instanceof PDFNumber ? o.asNumber() : undefined;
5797
+ };
5798
+ const isPostScriptShading = (shading) => {
5799
+ const dict = asDict(shading);
5800
+ if (!dict || num(dict.get(PDFName.of("ShadingType"))) !== 1)
5801
+ return false;
5802
+ return num(asDict(dict.get(PDFName.of("Function")))?.get(PDFName.of("FunctionType"))) === 4;
5803
+ };
5804
+ const subDicts = (resources, key) => {
5805
+ const group = asDict(resources.get(PDFName.of(key)));
5806
+ return group ? group.entries().map(([, v]) => v) : [];
5807
+ };
5808
+ const seen = new Set;
5809
+ const usesPostScriptShading = (resources) => {
5810
+ if (!resources || seen.has(resources))
5811
+ return false;
5812
+ seen.add(resources);
5813
+ if (subDicts(resources, "Shading").some(isPostScriptShading))
5814
+ return true;
5815
+ for (const pattern of subDicts(resources, "Pattern")) {
5816
+ const dict = asDict(pattern);
5817
+ if (dict && isPostScriptShading(dict.get(PDFName.of("Shading"))))
5818
+ return true;
5819
+ }
5820
+ return subDicts(resources, "XObject").some((x) => usesPostScriptShading(asDict(asDict(x)?.get(PDFName.of("Resources")))));
5821
+ };
5822
+ const pages = [];
5823
+ doc.getPages().forEach((page, i) => {
5824
+ seen.clear();
5825
+ if (usesPostScriptShading(asDict(page.node.Resources())))
5826
+ pages.push(i + 1);
5827
+ });
5828
+ return pages;
5829
+ }
5777
5830
  async function stripAnnotations(pdfPath, stagingDir) {
5778
5831
  const tmp = join3(stagingDir, "annotations-stripped.pdf");
5779
5832
  try {
@@ -5901,7 +5954,22 @@ async function convertToPdfxCmyk(inputPdf, outPdf, config) {
5901
5954
  if (!ghostscript) {
5902
5955
  throw new Error("Ghostscript executable not found. Install Ghostscript or set GHOSTSCRIPT_PATH to its command-line executable.");
5903
5956
  }
5904
- await run(ghostscript, args);
5957
+ const { code, signal, stdout, stderr } = await spawnCapture(ghostscript, args, {
5958
+ env: { ...process.env, PATH: enhancedPath }
5959
+ });
5960
+ if (signal) {
5961
+ const version = await execCapture(ghostscript, ["--version"]).then((r) => r.stdout.trim()).catch(() => "unknown version");
5962
+ throw new Error(`Ghostscript ${version} crashed (${signal}) while converting to PDF/X. This is a Ghostscript bug, not a problem with the PDF. Ghostscript 10.02.1 has been seen to crash in the PDF 1.3 transparency flattening PDF/X requires, on a page with an alpha-channel image. Flattening that image against its background avoided it there; a newer Ghostscript may too. A plain PDF (--format pdf) does not run this step.`);
5963
+ }
5964
+ if (code !== 0) {
5965
+ throw new Error(`${ghostscript} ${args.join(" ")} exited ${code}
5966
+ ${stdout}${stderr}`);
5967
+ }
5968
+ const at = stderr.indexOf("The following warnings");
5969
+ if (at >= 0) {
5970
+ log.warn(`Ghostscript reported problems converting to PDF/X:
5971
+ ${stderr.slice(at).trim()}`);
5972
+ }
5905
5973
  } finally {
5906
5974
  await unlink2(tmpDef).catch(() => {});
5907
5975
  }
@@ -5916,7 +5984,7 @@ import git from "isomorphic-git";
5916
5984
  // package.json
5917
5985
  var package_default = {
5918
5986
  name: "gutterpress",
5919
- version: "0.11.18",
5987
+ version: "0.11.19-alpha.2",
5920
5988
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5921
5989
  author: "itlackey",
5922
5990
  license: "MPL-2.0",
@@ -7843,6 +7911,9 @@ function isDroppedPageGradient(decl) {
7843
7911
  return false;
7844
7912
  return isInPageBox(decl);
7845
7913
  }
7914
+ function isPostScriptShadingGradient(decl) {
7915
+ return /(repeating-(linear|radial|conic)|conic)-gradient\(/i.test(decl.value);
7916
+ }
7846
7917
  function isInPageMarginBox(decl) {
7847
7918
  const box = decl.parent;
7848
7919
  if (!box || box.type !== "atrule")
@@ -8091,6 +8162,13 @@ function checkCssRaw(css, from) {
8091
8162
  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.`,
8092
8163
  ...nodeLoc(decl)
8093
8164
  });
8165
+ } else if (isPostScriptShadingGradient(decl)) {
8166
+ warnings.push({
8167
+ rule: ruleRiskyProps,
8168
+ severity: "warning",
8169
+ 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.`,
8170
+ ...nodeLoc(decl)
8171
+ });
8094
8172
  } else if (isUnpairedFitContentWidth(decl)) {
8095
8173
  warnings.push({
8096
8174
  rule: ruleRiskyProps,
@@ -9184,11 +9262,174 @@ var check27 = {
9184
9262
  };
9185
9263
  registerCheck(check27);
9186
9264
 
9265
+ // src/checks/source/characters.ts
9266
+ import { readFile as readFile24 } from "node:fs/promises";
9267
+
9268
+ // src/checks/source/character-table.ts
9269
+ var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\u2019\\u201B-\\u201D\\u201F\\u2022\\u2026" + "\\u2028-\\u202F\\u2032\\u2033\\u205F\\u2060\\u2066-\\u2069\\u2190\\u2192" + "\\u2194\\u21D2\\u2212\\u25AA\\u25E6\\u2605\\u2713\\u2714\\u2717\\u3000" + "\\uE000-\\uF8FF\\uFEFF\\uFF02\\uFF07\\uFFFD]" + "|\\p{Emoji_Presentation}|\\p{Extended_Pictographic}(?=\\uFE0F)", "gu");
9270
+ var NAMED = {
9271
+ 8212: ["Em dash", "typographer", "---"],
9272
+ 8211: ["En dash", "typographer", "--"],
9273
+ 8220: ["Opening curly quote", "typographer", '"'],
9274
+ 8221: ["Closing curly quote", "typographer", '"'],
9275
+ 8216: ["Opening curly single quote", "typographer", "'"],
9276
+ 8217: ["Curly apostrophe", "typographer", "'"],
9277
+ 8230: ["Ellipsis character", "typographer", "..."],
9278
+ 700: ["Modifier-letter apostrophe", "lookalike", "'"],
9279
+ 180: ["Acute accent", "lookalike", "'"],
9280
+ 8242: ["Prime", "lookalike", "'"],
9281
+ 8243: ["Double prime", "lookalike", '"'],
9282
+ 8219: ["Reversed single quote", "lookalike", "'"],
9283
+ 8223: ["Reversed double quote", "lookalike", '"'],
9284
+ 65282: ["Full-width quote", "lookalike", '"'],
9285
+ 65287: ["Full-width apostrophe", "lookalike", "'"],
9286
+ 8208: ["Unicode hyphen", "lookalike", "-"],
9287
+ 8209: ["Non-breaking hyphen", "lookalike", "-"],
9288
+ 8210: ["Figure dash", "lookalike", "-"],
9289
+ 8213: ["Horizontal bar", "lookalike", "---"],
9290
+ 8722: ["Minus sign", "lookalike", "-"],
9291
+ 8226: ["Bullet", "symbol", "•"],
9292
+ 9702: ["White bullet", "symbol", "◦"],
9293
+ 9642: ["Small square", "symbol", "▪"],
9294
+ 8594: ["Right arrow", "symbol", "→"],
9295
+ 8592: ["Left arrow", "symbol", "←"],
9296
+ 8596: ["Left-right arrow", "symbol", "↔"],
9297
+ 8658: ["Double right arrow", "symbol", "⇒"],
9298
+ 10003: ["Check mark", "symbol", "✓"],
9299
+ 10004: ["Heavy check mark", "symbol", "✔"],
9300
+ 10007: ["Ballot X", "symbol", "✗"],
9301
+ 9733: ["Star", "symbol", "★"],
9302
+ 8203: ["Zero-width space", "invisible", "​"],
9303
+ 8288: ["Word joiner", "invisible", "⁠"],
9304
+ 65279: ["Byte-order mark", "invisible"],
9305
+ 173: ["Soft hyphen", "invisible", "­"],
9306
+ 8204: ["Zero-width non-joiner", "invisible", "‌"],
9307
+ 8205: ["Zero-width joiner", "invisible", "‍"],
9308
+ 8206: ["Left-to-right mark", "invisible", "‎"],
9309
+ 8207: ["Right-to-left mark", "invisible", "‏"],
9310
+ 1564: ["Arabic letter mark", "invisible", "؜"],
9311
+ 8234: ["Left-to-right embedding", "invisible", "‪"],
9312
+ 8235: ["Right-to-left embedding", "invisible", "‫"],
9313
+ 8236: ["Pop directional formatting", "invisible", "‬"],
9314
+ 8237: ["Left-to-right override", "invisible", "‭"],
9315
+ 8238: ["Right-to-left override", "invisible", "‮"],
9316
+ 8294: ["Left-to-right isolate", "invisible", "⁦"],
9317
+ 8295: ["Right-to-left isolate", "invisible", "⁧"],
9318
+ 8296: ["First-strong isolate", "invisible", "⁨"],
9319
+ 8297: ["Pop directional isolate", "invisible", "⁩"],
9320
+ 8232: ["Line separator", "invisible"],
9321
+ 8233: ["Paragraph separator", "invisible"],
9322
+ 160: ["Non-breaking space", "whitespace", " "],
9323
+ 8192: ["En quad", "whitespace", " "],
9324
+ 8193: ["Em quad", "whitespace", " "],
9325
+ 8194: ["En space", "whitespace", " "],
9326
+ 8195: ["Em space", "whitespace", " "],
9327
+ 8196: ["Three-per-em space", "whitespace", " "],
9328
+ 8197: ["Four-per-em space", "whitespace", " "],
9329
+ 8198: ["Six-per-em space", "whitespace", " "],
9330
+ 8199: ["Figure space", "whitespace", " "],
9331
+ 8200: ["Punctuation space", "whitespace", " "],
9332
+ 8201: ["Thin space", "whitespace", " "],
9333
+ 8202: ["Hair space", "whitespace", " "],
9334
+ 8239: ["Narrow non-breaking space", "whitespace", " "],
9335
+ 8287: ["Medium mathematical space", "whitespace", " "],
9336
+ 12288: ["Ideographic space", "whitespace", " "],
9337
+ 65533: ["Replacement character", "replacement"]
9338
+ };
9339
+ function fixFor(code, spelling) {
9340
+ switch (code) {
9341
+ case "typographer":
9342
+ return `Type ${spelling} instead; it prints the same.`;
9343
+ case "lookalike":
9344
+ return `Type ${spelling} instead.`;
9345
+ case "whitespace":
9346
+ return `Type a space, or ${spelling} if you meant it.`;
9347
+ case "symbol":
9348
+ case "invisible":
9349
+ return spelling ? `Delete it, or type ${spelling} if you meant it.` : "Delete it.";
9350
+ case "control":
9351
+ return "Delete it; it does not print.";
9352
+ case "replacement":
9353
+ return "Retype the character that was lost here.";
9354
+ case "private-use":
9355
+ return "Replace it with the character you meant; this one only shows in the font it came from.";
9356
+ }
9357
+ }
9358
+ function info2(name, code, spelling) {
9359
+ return { name, code, fix: fixFor(code, spelling) };
9360
+ }
9361
+ function describeCharacter(char) {
9362
+ const cp2 = char.codePointAt(0);
9363
+ const named = NAMED[cp2];
9364
+ if (named)
9365
+ return info2(...named);
9366
+ if (cp2 <= 159)
9367
+ return info2("Control character", "control");
9368
+ if (cp2 >= 57344 && cp2 <= 63743)
9369
+ return info2("Private-use character", "private-use");
9370
+ return info2("Emoji", "symbol", `&#x${cp2.toString(16).toUpperCase()};`);
9371
+ }
9372
+ function codePointLabel(char) {
9373
+ return `U+${char.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
9374
+ }
9375
+
9376
+ // src/checks/source/characters.ts
9377
+ var PROBE = new RegExp(CHARACTER_PATTERN.source, "u");
9378
+ var check28 = {
9379
+ id: "source.characters",
9380
+ name: "Unusual Characters",
9381
+ description: "Finds invisible and control characters, odd spaces, and pasted typography (em dashes, curly quotes, emoji) in markdown",
9382
+ category: "source",
9383
+ phase: "pre-build",
9384
+ async run(ctx) {
9385
+ const results = [];
9386
+ for (const file of (ctx.markdownFiles ?? []).slice().sort()) {
9387
+ let content;
9388
+ try {
9389
+ content = await readFile24(file, "utf8");
9390
+ } catch {
9391
+ results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
9392
+ continue;
9393
+ }
9394
+ if (!PROBE.test(content))
9395
+ continue;
9396
+ const lines = content.split(`
9397
+ `);
9398
+ let silencedLine = -1;
9399
+ for (let i = 0;i < lines.length; i++) {
9400
+ const line = lines[i];
9401
+ const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
9402
+ if (comment && parseDisableDirective(comment[1])?.includes(check28.id)) {
9403
+ silencedLine = i + 1;
9404
+ continue;
9405
+ }
9406
+ if (i === silencedLine)
9407
+ continue;
9408
+ for (const match of line.matchAll(CHARACTER_PATTERN)) {
9409
+ const char = match[0];
9410
+ const { name, code, fix } = describeCharacter(char);
9411
+ results.push(finding(check28.id, {
9412
+ severity: "warning",
9413
+ code,
9414
+ message: `${name} (${codePointLabel(char)}). ${fix}`,
9415
+ file,
9416
+ line: i + 1,
9417
+ column: match.index + 1,
9418
+ data: { codePoint: char.codePointAt(0) }
9419
+ }));
9420
+ }
9421
+ }
9422
+ }
9423
+ return results;
9424
+ }
9425
+ };
9426
+ registerCheck(check28);
9427
+
9187
9428
  // src/checks/asset/image-file-size.ts
9188
9429
  import { stat as stat4 } from "node:fs/promises";
9189
9430
 
9190
9431
  // src/lib/image-inspect.ts
9191
- import { stat as stat3, readFile as readFile24 } from "node:fs/promises";
9432
+ import { stat as stat3, readFile as readFile25 } from "node:fs/promises";
9192
9433
  var DEFAULT_DPI = 72;
9193
9434
  var cache = new Map;
9194
9435
  async function inspectImage(path13) {
@@ -9201,15 +9442,15 @@ async function inspectImage(path13) {
9201
9442
  const hit = cache.get(path13);
9202
9443
  if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size)
9203
9444
  return hit.info;
9204
- let info2 = null;
9445
+ let info3 = null;
9205
9446
  try {
9206
- const buf = await readFile24(path13);
9207
- info2 = parseImage(buf);
9447
+ const buf = await readFile25(path13);
9448
+ info3 = parseImage(buf);
9208
9449
  } catch {
9209
- info2 = null;
9450
+ info3 = null;
9210
9451
  }
9211
- cache.set(path13, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
9212
- return info2;
9452
+ cache.set(path13, { mtimeMs: st.mtimeMs, size: st.size, info: info3 });
9453
+ return info3;
9213
9454
  }
9214
9455
  function parseImage(b) {
9215
9456
  if (b.length > 8 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
@@ -9408,7 +9649,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
9408
9649
  }
9409
9650
 
9410
9651
  // src/checks/asset/image-file-size.ts
9411
- var check28 = {
9652
+ var check29 = {
9412
9653
  id: "asset.image.file-size",
9413
9654
  name: "Image File Size",
9414
9655
  description: "Checks that image files do not exceed the maximum size limit",
@@ -9425,18 +9666,18 @@ var check28 = {
9425
9666
  const results = [];
9426
9667
  for (const file of files) {
9427
9668
  try {
9428
- const info2 = await stat4(file);
9429
- if (info2.size > maxSize) {
9430
- const sizeMb = (info2.size / 1e6).toFixed(1);
9669
+ const info3 = await stat4(file);
9670
+ if (info3.size > maxSize) {
9671
+ const sizeMb = (info3.size / 1e6).toFixed(1);
9431
9672
  const maxMb = (maxSize / 1e6).toFixed(1);
9432
- results.push(finding(check28.id, {
9673
+ results.push(finding(check29.id, {
9433
9674
  severity: "warning",
9434
9675
  message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
9435
9676
  file
9436
9677
  }));
9437
9678
  }
9438
9679
  } catch {
9439
- results.push(inspectionFailed(check28.id, `Could not stat image file: ${file}`, {
9680
+ results.push(inspectionFailed(check29.id, `Could not stat image file: ${file}`, {
9440
9681
  file
9441
9682
  }));
9442
9683
  }
@@ -9444,10 +9685,10 @@ var check28 = {
9444
9685
  return results;
9445
9686
  }
9446
9687
  };
9447
- registerCheck(check28);
9688
+ registerCheck(check29);
9448
9689
 
9449
9690
  // src/checks/asset/image-resolution.ts
9450
- var check29 = {
9691
+ var check30 = {
9451
9692
  id: "asset.image.resolution",
9452
9693
  name: "Image Resolution",
9453
9694
  description: "Checks source image DPI from embedded density metadata",
@@ -9463,13 +9704,13 @@ var check29 = {
9463
9704
  return [];
9464
9705
  const results = [];
9465
9706
  for (const file of files) {
9466
- const info2 = await inspectImage(file);
9467
- if (!info2)
9707
+ const info3 = await inspectImage(file);
9708
+ if (!info3)
9468
9709
  continue;
9469
- const { xDpi, yDpi } = info2;
9710
+ const { xDpi, yDpi } = info3;
9470
9711
  if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
9471
9712
  results.push({
9472
- checkId: check29.id,
9713
+ checkId: check30.id,
9473
9714
  severity: "warning",
9474
9715
  message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
9475
9716
  file
@@ -9479,7 +9720,7 @@ var check29 = {
9479
9720
  return results;
9480
9721
  }
9481
9722
  };
9482
- registerCheck(check29);
9723
+ registerCheck(check30);
9483
9724
 
9484
9725
  // src/checks/asset/image-color-space.ts
9485
9726
  var LABEL = {
@@ -9487,7 +9728,7 @@ var LABEL = {
9487
9728
  gray: "Gray",
9488
9729
  cmyk: "CMYK"
9489
9730
  };
9490
- var check30 = {
9731
+ var check31 = {
9491
9732
  id: "asset.image.color-space",
9492
9733
  name: "Image Color Space",
9493
9734
  description: "Validates image color spaces against allowed list",
@@ -9504,11 +9745,11 @@ var check30 = {
9504
9745
  const allowedLower = new Set(allowed.map((s) => s.toLowerCase()));
9505
9746
  const results = [];
9506
9747
  for (const file of files) {
9507
- const info2 = await inspectImage(file);
9508
- const cs = info2?.colorSpace;
9748
+ const info3 = await inspectImage(file);
9749
+ const cs = info3?.colorSpace;
9509
9750
  if (cs && !allowedLower.has(cs)) {
9510
9751
  results.push({
9511
- checkId: check30.id,
9752
+ checkId: check31.id,
9512
9753
  severity: "warning",
9513
9754
  message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
9514
9755
  file
@@ -9518,10 +9759,10 @@ var check30 = {
9518
9759
  return results;
9519
9760
  }
9520
9761
  };
9521
- registerCheck(check30);
9762
+ registerCheck(check31);
9522
9763
 
9523
9764
  // src/checks/asset/image-alpha.ts
9524
- var check31 = {
9765
+ var check32 = {
9525
9766
  id: "asset.image.alpha-channel",
9526
9767
  name: "Image Alpha Channel",
9527
9768
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -9536,10 +9777,10 @@ var check31 = {
9536
9777
  return [];
9537
9778
  const results = [];
9538
9779
  for (const file of files) {
9539
- const info2 = await inspectImage(file);
9540
- if (info2?.hasAlpha) {
9780
+ const info3 = await inspectImage(file);
9781
+ if (info3?.hasAlpha) {
9541
9782
  results.push({
9542
- checkId: check31.id,
9783
+ checkId: check32.id,
9543
9784
  severity: "warning",
9544
9785
  message: "Image contains alpha channel, which may cause print issues",
9545
9786
  file
@@ -9549,10 +9790,10 @@ var check31 = {
9549
9790
  return results;
9550
9791
  }
9551
9792
  };
9552
- registerCheck(check31);
9793
+ registerCheck(check32);
9553
9794
 
9554
9795
  // src/checks/asset/image-tac.ts
9555
- var check32 = {
9796
+ var check33 = {
9556
9797
  id: "asset.image.tac-raster",
9557
9798
  name: "Image TAC (Raster)",
9558
9799
  description: "Rasterizes and checks TAC per image using Ghostscript",
@@ -9571,7 +9812,7 @@ var check32 = {
9571
9812
  const ghostscript = await resolveGhostscript();
9572
9813
  if (!ghostscript) {
9573
9814
  return [
9574
- inspectionFailed(check32.id, "Could not inspect image ink coverage: Ghostscript executable not found")
9815
+ inspectionFailed(check33.id, "Could not inspect image ink coverage: Ghostscript executable not found")
9575
9816
  ];
9576
9817
  }
9577
9818
  for (const file of files) {
@@ -9589,7 +9830,7 @@ var check32 = {
9589
9830
  if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
9590
9831
  const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
9591
9832
  if (tac > maxTac) {
9592
- results.push(finding(check32.id, {
9833
+ results.push(finding(check33.id, {
9593
9834
  severity: "warning",
9594
9835
  message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
9595
9836
  file,
@@ -9601,16 +9842,16 @@ var check32 = {
9601
9842
  }
9602
9843
  }
9603
9844
  } catch {
9604
- results.push(inspectionFailed(check32.id, `Could not inspect image ink coverage: ${file}`, { file }));
9845
+ results.push(inspectionFailed(check33.id, `Could not inspect image ink coverage: ${file}`, { file }));
9605
9846
  }
9606
9847
  }
9607
9848
  return results;
9608
9849
  }
9609
9850
  };
9610
- registerCheck(check32);
9851
+ registerCheck(check33);
9611
9852
 
9612
9853
  // src/checks/asset/approved-fonts.ts
9613
- var check33 = {
9854
+ var check34 = {
9614
9855
  id: "asset.font.approved-files",
9615
9856
  name: "Approved Font Files",
9616
9857
  description: "Checks font files against the approved file patterns",
@@ -9649,7 +9890,7 @@ var check33 = {
9649
9890
  for (const font of allFonts) {
9650
9891
  if (!approvedFonts.has(font)) {
9651
9892
  results.push({
9652
- checkId: check33.id,
9893
+ checkId: check34.id,
9653
9894
  severity: "warning",
9654
9895
  message: "Font file not in approved list",
9655
9896
  file: font
@@ -9659,7 +9900,7 @@ var check33 = {
9659
9900
  return results;
9660
9901
  }
9661
9902
  };
9662
- registerCheck(check33);
9903
+ registerCheck(check34);
9663
9904
 
9664
9905
  // src/checks/asset/font-license.ts
9665
9906
  import { existsSync as existsSync11 } from "node:fs";
@@ -9674,7 +9915,7 @@ var LICENSE_NAMES = [
9674
9915
  "OFL-1.1.txt",
9675
9916
  "COPYING"
9676
9917
  ];
9677
- var check34 = {
9918
+ var check35 = {
9678
9919
  id: "asset.font.license",
9679
9920
  name: "Font License",
9680
9921
  description: "Checks for license files in font directories",
@@ -9701,7 +9942,7 @@ var check34 = {
9701
9942
  const hasLicense = LICENSE_NAMES.some((name) => existsSync11(resolve7(fontDir, name)));
9702
9943
  if (!hasLicense) {
9703
9944
  results.push({
9704
- checkId: check34.id,
9945
+ checkId: check35.id,
9705
9946
  severity: "warning",
9706
9947
  message: `No font license file found in directory`,
9707
9948
  file: fontDir
@@ -9711,10 +9952,10 @@ var check34 = {
9711
9952
  return results;
9712
9953
  }
9713
9954
  };
9714
- registerCheck(check34);
9955
+ registerCheck(check35);
9715
9956
 
9716
9957
  // src/checks/heuristic/text-density.ts
9717
- var check35 = {
9958
+ var check36 = {
9718
9959
  id: "heuristic.whitespace.text-density",
9719
9960
  name: "Text Density",
9720
9961
  description: "Checks characters-per-page ratio",
@@ -9743,7 +9984,7 @@ var check35 = {
9743
9984
  });
9744
9985
  if (lowPages.length > 0) {
9745
9986
  results.push({
9746
- checkId: check35.id,
9987
+ checkId: check36.id,
9747
9988
  severity: "info",
9748
9989
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
9749
9990
  file: ctx.pdfPath
@@ -9751,7 +9992,7 @@ var check35 = {
9751
9992
  }
9752
9993
  if (highPages.length > 0) {
9753
9994
  results.push({
9754
- checkId: check35.id,
9995
+ checkId: check36.id,
9755
9996
  severity: "info",
9756
9997
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
9757
9998
  file: ctx.pdfPath
@@ -9760,11 +10001,11 @@ var check35 = {
9760
10001
  return results;
9761
10002
  }
9762
10003
  };
9763
- registerCheck(check35);
10004
+ registerCheck(check36);
9764
10005
 
9765
10006
  // src/checks/heuristic/section-density.ts
9766
- import { readFile as readFile25 } from "node:fs/promises";
9767
- var check36 = {
10007
+ import { readFile as readFile26 } from "node:fs/promises";
10008
+ var check37 = {
9768
10009
  id: "heuristic.chunking.section-density",
9769
10010
  name: "Section Density",
9770
10011
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -9780,7 +10021,7 @@ var check36 = {
9780
10021
  const results = [];
9781
10022
  for (const file of files) {
9782
10023
  try {
9783
- const content = await readFile25(file, "utf8");
10024
+ const content = await readFile26(file, "utf8");
9784
10025
  const lines = content.split(`
9785
10026
  `);
9786
10027
  let currentSectionStart = 0;
@@ -9790,7 +10031,7 @@ var check36 = {
9790
10031
  const line = lines[i];
9791
10032
  if (/^#{1,6}\s/.test(line)) {
9792
10033
  if (paragraphCount > maxParas) {
9793
- results.push(finding(check36.id, {
10034
+ results.push(finding(check37.id, {
9794
10035
  severity: "info",
9795
10036
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
9796
10037
  file,
@@ -9810,7 +10051,7 @@ var check36 = {
9810
10051
  }
9811
10052
  }
9812
10053
  if (paragraphCount > maxParas) {
9813
- results.push(finding(check36.id, {
10054
+ results.push(finding(check37.id, {
9814
10055
  severity: "info",
9815
10056
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
9816
10057
  file,
@@ -9818,7 +10059,7 @@ var check36 = {
9818
10059
  }));
9819
10060
  }
9820
10061
  } catch {
9821
- results.push(inspectionFailed(check36.id, `Could not read source file: ${file}`, {
10062
+ results.push(inspectionFailed(check37.id, `Could not read source file: ${file}`, {
9822
10063
  file
9823
10064
  }));
9824
10065
  }
@@ -9826,10 +10067,10 @@ var check36 = {
9826
10067
  return results;
9827
10068
  }
9828
10069
  };
9829
- registerCheck(check36);
10070
+ registerCheck(check37);
9830
10071
 
9831
10072
  // src/checks/heuristic/layer-count.ts
9832
- var check37 = {
10073
+ var check38 = {
9833
10074
  id: "heuristic.decoration.layer-count",
9834
10075
  name: "Layer Count",
9835
10076
  description: "Counts image objects per page",
@@ -9854,7 +10095,7 @@ var check37 = {
9854
10095
  heavyPages.sort((a, b) => a - b);
9855
10096
  return [
9856
10097
  {
9857
- checkId: check37.id,
10098
+ checkId: check38.id,
9858
10099
  severity: "info",
9859
10100
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
9860
10101
  file: ctx.pdfPath
@@ -9864,10 +10105,10 @@ var check37 = {
9864
10105
  return [];
9865
10106
  }
9866
10107
  };
9867
- registerCheck(check37);
10108
+ registerCheck(check38);
9868
10109
 
9869
10110
  // src/checks/heuristic/placement-variance.ts
9870
- var check38 = {
10111
+ var check39 = {
9871
10112
  id: "heuristic.layout.placement-variance",
9872
10113
  name: "Placement Variance",
9873
10114
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -9885,7 +10126,7 @@ var check38 = {
9885
10126
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
9886
10127
  return [
9887
10128
  {
9888
- checkId: check38.id,
10129
+ checkId: check39.id,
9889
10130
  severity: "info",
9890
10131
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
9891
10132
  file: ctx.pdfPath
@@ -9893,7 +10134,7 @@ var check38 = {
9893
10134
  ];
9894
10135
  }
9895
10136
  };
9896
- registerCheck(check38);
10137
+ registerCheck(check39);
9897
10138
 
9898
10139
  // src/checks/runner.ts
9899
10140
  function getCheckSeverityOverride(checkId, config) {
@@ -9923,16 +10164,16 @@ async function runChecks(ctx, opts = {}) {
9923
10164
  }
9924
10165
  const release = retainPdfCache();
9925
10166
  try {
9926
- for (const check39 of checks2) {
10167
+ for (const check40 of checks2) {
9927
10168
  try {
9928
- let results = await check39.run(ctx);
9929
- const severityOverride = getCheckSeverityOverride(check39.id, ctx.config);
10169
+ let results = await check40.run(ctx);
10170
+ const severityOverride = getCheckSeverityOverride(check40.id, ctx.config);
9930
10171
  if (severityOverride) {
9931
10172
  for (const r of results) {
9932
10173
  r.severity = severityOverride;
9933
10174
  }
9934
10175
  }
9935
- const lintLevel = ruleLevel(ctx.config.lint.rules, check39.id);
10176
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check40.id);
9936
10177
  if (lintLevel === "off")
9937
10178
  results = [];
9938
10179
  else if (lintLevel) {
@@ -9940,15 +10181,15 @@ async function runChecks(ctx, opts = {}) {
9940
10181
  r.severity = lintLevel === "warn" ? "warning" : "error";
9941
10182
  }
9942
10183
  if (results.length === 0) {
9943
- passed.push(check39.id);
10184
+ passed.push(check40.id);
9944
10185
  } else {
9945
10186
  allResults.push(...results);
9946
10187
  }
9947
10188
  } catch (err) {
9948
10189
  allResults.push({
9949
- checkId: check39.id,
10190
+ checkId: check40.id,
9950
10191
  severity: "error",
9951
- message: `Check "${check39.id}" threw: ${err instanceof Error ? err.message : String(err)}`
10192
+ message: `Check "${check40.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9952
10193
  });
9953
10194
  }
9954
10195
  }
@@ -9988,15 +10229,15 @@ function emptyReport() {
9988
10229
  async function checkToolAvailability(config, opts = {}) {
9989
10230
  const { checks: checks2 } = selectChecks(opts, config);
9990
10231
  const toolToChecks = new Map;
9991
- for (const check39 of checks2) {
9992
- if (!check39.requiredTools?.length)
10232
+ for (const check40 of checks2) {
10233
+ if (!check40.requiredTools?.length)
9993
10234
  continue;
9994
- for (const tool of check39.requiredTools) {
10235
+ for (const tool of check40.requiredTools) {
9995
10236
  const existing = toolToChecks.get(tool);
9996
10237
  if (existing) {
9997
- existing.push(check39.id);
10238
+ existing.push(check40.id);
9998
10239
  } else {
9999
- toolToChecks.set(tool, [check39.id]);
10240
+ toolToChecks.set(tool, [check40.id]);
10000
10241
  }
10001
10242
  }
10002
10243
  }
@@ -10012,11 +10253,11 @@ async function checkToolAvailability(config, opts = {}) {
10012
10253
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
10013
10254
  const missingSet = new Set(missing);
10014
10255
  const skippedChecks = [];
10015
- for (const check39 of checks2) {
10016
- if (!check39.requiredTools?.length)
10256
+ for (const check40 of checks2) {
10257
+ if (!check40.requiredTools?.length)
10017
10258
  continue;
10018
- if (check39.requiredTools.some((t) => missingSet.has(t))) {
10019
- skippedChecks.push(check39.id);
10259
+ if (check40.requiredTools.some((t) => missingSet.has(t))) {
10260
+ skippedChecks.push(check40.id);
10020
10261
  }
10021
10262
  }
10022
10263
  return { available, missing, skippedChecks, toolToChecks };
@@ -10289,7 +10530,7 @@ async function executeValidation(args) {
10289
10530
  };
10290
10531
  if (args.fix) {
10291
10532
  const selected = selectChecks(runnerOptions, config).checks;
10292
- if (selected.some((check39) => check39.id === MARKDOWNLINT_CHECK_ID)) {
10533
+ if (selected.some((check40) => check40.id === MARKDOWNLINT_CHECK_ID)) {
10293
10534
  await applyMarkdownlintFixes(context);
10294
10535
  } else {
10295
10536
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -10662,7 +10903,7 @@ class PdfOutput {
10662
10903
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10663
10904
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10664
10905
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10665
- const { buildNativePdf } = await import("./engine-zy6fzs5a.js");
10906
+ const { buildNativePdf } = await import("./engine-zehw6tkv.js");
10666
10907
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10667
10908
  title: config.title,
10668
10909
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10687,6 +10928,10 @@ class PdfOutput {
10687
10928
  if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
10688
10929
  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.");
10689
10930
  }
10931
+ const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
10932
+ if (blankGradientPages.length > 0) {
10933
+ 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.");
10934
+ }
10690
10935
  const shouldStrip = opts.stripAnnotations ?? config.pdfx.stripAnnotations;
10691
10936
  shouldStripAnnotations = shouldStrip;
10692
10937
  if (shouldStrip) {
@@ -10741,7 +10986,7 @@ async function runBuild(opts) {
10741
10986
  if (ctx.format !== "html") {
10742
10987
  await preflightBuildTools(ctx.format, opts, ctx.config);
10743
10988
  }
10744
- const launch = opts.engineBrowser ?? (async () => (await import("./cdp-jg95s674.js")).launchChromium());
10989
+ const launch = opts.engineBrowser ?? (async () => (await import("./cdp-h0wa42cz.js")).launchChromium());
10745
10990
  const browserPromise = ctx.format === "html" ? null : launch();
10746
10991
  browserPromise?.catch(() => {});
10747
10992
  try {
@@ -10768,7 +11013,7 @@ async function runBuild(opts) {
10768
11013
  import path16 from "path";
10769
11014
  import { tmpdir } from "os";
10770
11015
  import { randomBytes as randomBytes2 } from "crypto";
10771
- import { readdir as readdir5, readFile as readFile26, writeFile as writeFile6 } from "node:fs/promises";
11016
+ import { readdir as readdir5, readFile as readFile27, writeFile as writeFile6 } from "node:fs/promises";
10772
11017
 
10773
11018
  // src/utils/file-utils.ts
10774
11019
  import { promises as fs3 } from "fs";
@@ -11320,7 +11565,7 @@ async function cleanupOrphanTempDirs() {
11320
11565
  const dir = path16.join(TEMP_DIR_BASE, entry);
11321
11566
  const pidFile = path16.join(dir, PID_FILE_NAME);
11322
11567
  try {
11323
- const raw = await readFile26(pidFile, "utf8");
11568
+ const raw = await readFile27(pidFile, "utf8");
11324
11569
  const pid = parseInt(raw.trim(), 10);
11325
11570
  if (!isProcessAlive(pid)) {
11326
11571
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -11427,7 +11672,7 @@ function createServerState(inputPath, tempDir, config, options) {
11427
11672
  import http from "node:http";
11428
11673
  import net from "node:net";
11429
11674
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
11430
- import { readFile as readFile27, stat as stat5 } from "node:fs/promises";
11675
+ import { readFile as readFile28, stat as stat5 } from "node:fs/promises";
11431
11676
  import path17 from "path";
11432
11677
  import { WebSocket, WebSocketServer } from "ws";
11433
11678
 
@@ -11526,7 +11771,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
11526
11771
  }
11527
11772
  let data;
11528
11773
  try {
11529
- data = await readFile27(filePath);
11774
+ data = await readFile28(filePath);
11530
11775
  } catch {
11531
11776
  res.writeHead(404);
11532
11777
  res.end("Not Found");
@@ -11575,7 +11820,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11575
11820
  let data;
11576
11821
  if (!validator || validator.stamp !== stamp) {
11577
11822
  try {
11578
- data = await readFile27(absPath);
11823
+ data = await readFile28(absPath);
11579
11824
  } catch {
11580
11825
  await serveStatic(absPath, res);
11581
11826
  return;
@@ -11887,7 +12132,7 @@ async function startPreviewServer(options) {
11887
12132
  import { platform, arch, release } from "node:os";
11888
12133
 
11889
12134
  // src/lib/remote-auth/token-store.ts
11890
- import { chmod, mkdir as mkdir7, readFile as readFile28, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
12135
+ import { chmod, mkdir as mkdir7, readFile as readFile29, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
11891
12136
  import os3 from "node:os";
11892
12137
  import path18 from "node:path";
11893
12138
  function credentialHostKey(hostOrUrl) {
@@ -11931,7 +12176,7 @@ class FileTokenStore {
11931
12176
  async read() {
11932
12177
  let raw;
11933
12178
  try {
11934
- raw = await readFile28(this.filePath, "utf8");
12179
+ raw = await readFile29(this.filePath, "utf8");
11935
12180
  } catch (e) {
11936
12181
  if (e?.code === "ENOENT") {
11937
12182
  return { version: 1, credentials: {} };
@@ -12131,13 +12376,13 @@ async function getSystemDiagnostics() {
12131
12376
  };
12132
12377
  }
12133
12378
  // src/lib/project-scaffold.ts
12134
- import { access, copyFile, cp as cp2, mkdir as mkdir9, readdir as readdir6, readFile as readFile30, stat as stat6, writeFile as writeFile9 } from "node:fs/promises";
12379
+ import { access, copyFile, cp as cp2, mkdir as mkdir9, readdir as readdir6, readFile as readFile31, stat as stat6, writeFile as writeFile9 } from "node:fs/promises";
12135
12380
  import { constants as FS, existsSync as existsSync14 } from "node:fs";
12136
12381
  import path20 from "node:path";
12137
12382
 
12138
12383
  // src/lib/page-size.ts
12139
12384
  import { existsSync as existsSync13 } from "node:fs";
12140
- import { mkdir as mkdir8, readFile as readFile29, writeFile as writeFile8 } from "node:fs/promises";
12385
+ import { mkdir as mkdir8, readFile as readFile30, writeFile as writeFile8 } from "node:fs/promises";
12141
12386
  import path19 from "node:path";
12142
12387
  import postcss5 from "postcss";
12143
12388
  import { Scalar as Scalar2 } from "yaml";
@@ -12242,7 +12487,7 @@ var relDisplay2 = (projectDir, abs) => path19.relative(projectDir, abs).split(pa
12242
12487
  async function findCssSize(projectDir, sheets) {
12243
12488
  let found = null;
12244
12489
  for (const file of sheets) {
12245
- const size = readPageSizeFromCss(await readFile29(file, "utf8"));
12490
+ const size = readPageSizeFromCss(await readFile30(file, "utf8"));
12246
12491
  if (size)
12247
12492
  found = { file, size };
12248
12493
  }
@@ -12269,7 +12514,7 @@ async function setPageSetup(projectDir, choice) {
12269
12514
  const found = await findCssSize(projectDir, sheets);
12270
12515
  const newSheet = path19.join(projectDir, "styles", "book.css");
12271
12516
  const target = found?.file ?? sheets[sheets.length - 1] ?? newSheet;
12272
- const before = existsSync13(target) ? await readFile29(target, "utf8") : "";
12517
+ const before = existsSync13(target) ? await readFile30(target, "utf8") : "";
12273
12518
  let after;
12274
12519
  try {
12275
12520
  after = setPageSizeInCss(before, size);
@@ -12559,7 +12804,7 @@ styles:
12559
12804
  async function firstSourceFile(manifestPath) {
12560
12805
  let text;
12561
12806
  try {
12562
- text = await readFile30(manifestPath, "utf8");
12807
+ text = await readFile31(manifestPath, "utf8");
12563
12808
  } catch {
12564
12809
  return;
12565
12810
  }
@@ -12571,14 +12816,14 @@ async function firstSourceFile(manifestPath) {
12571
12816
  return m ? m[1].trim() : undefined;
12572
12817
  }
12573
12818
  async function fillTemplateFile(filePath, substitutions) {
12574
- let text = await readFile30(filePath, "utf8");
12819
+ let text = await readFile31(filePath, "utf8");
12575
12820
  for (const [token, value] of Object.entries(substitutions)) {
12576
12821
  text = text.split(token).join(value);
12577
12822
  }
12578
12823
  await writeFile9(filePath, text, "utf8");
12579
12824
  }
12580
12825
  // src/lib/extension-scaffold.ts
12581
- import { access as access2, mkdir as mkdir10, readFile as readFile31, writeFile as writeFile10 } from "node:fs/promises";
12826
+ import { access as access2, mkdir as mkdir10, readFile as readFile32, writeFile as writeFile10 } from "node:fs/promises";
12582
12827
  import { constants as FS2 } from "node:fs";
12583
12828
  import path21 from "node:path";
12584
12829
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -12695,7 +12940,7 @@ async function scaffoldExtension(options) {
12695
12940
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
12696
12941
  const dest = path21.join(extensionDir, relDest);
12697
12942
  await mkdir10(path21.dirname(dest), { recursive: true });
12698
- const text = await readFile31(src, "utf8");
12943
+ const text = await readFile32(src, "utf8");
12699
12944
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
12700
12945
  await writeFile10(dest, substitute(text, table), "utf8");
12701
12946
  written.push(relDest);
@@ -12721,7 +12966,7 @@ function substitute(text, table) {
12721
12966
  return out;
12722
12967
  }
12723
12968
  // src/lib/project-templates.ts
12724
- import { cp as cp3, mkdir as mkdir11, readFile as readFile32, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
12969
+ import { cp as cp3, mkdir as mkdir11, readFile as readFile33, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
12725
12970
  import { constants as FS3, existsSync as existsSync15 } from "node:fs";
12726
12971
  import { access as access3 } from "node:fs/promises";
12727
12972
  import path22 from "node:path";
@@ -12733,7 +12978,7 @@ var BUILT_IN_TEMPLATE_IDS = [
12733
12978
  ];
12734
12979
  async function readTemplateChoices(manifestPath) {
12735
12980
  try {
12736
- const text = await readFile32(manifestPath, "utf8");
12981
+ const text = await readFile33(manifestPath, "utf8");
12737
12982
  const doc = parseDocument2(text);
12738
12983
  const out = {};
12739
12984
  const preset = doc.get("preset");
@@ -12915,7 +13160,7 @@ async function saveProjectAsTemplate(options) {
12915
13160
  async function retokeniseManifest(manifestPath) {
12916
13161
  let text;
12917
13162
  try {
12918
- text = await readFile32(manifestPath, "utf8");
13163
+ text = await readFile33(manifestPath, "utf8");
12919
13164
  } catch {
12920
13165
  return;
12921
13166
  }
@@ -12936,7 +13181,7 @@ async function listCustomTemplates(templatesRoot) {
12936
13181
  const dir = path22.join(templatesRoot, entry.name);
12937
13182
  let label = prettify(entry.name);
12938
13183
  try {
12939
- const meta = JSON.parse(await readFile32(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
13184
+ const meta = JSON.parse(await readFile33(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
12940
13185
  if (typeof meta.label === "string" && meta.label)
12941
13186
  label = meta.label;
12942
13187
  } catch {}
@@ -12962,7 +13207,7 @@ async function importTemplateFromFolder(options) {
12962
13207
  return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
12963
13208
  }
12964
13209
  // src/lib/snippets.ts
12965
- import { readdir as readdir8, readFile as readFile33, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
13210
+ import { readdir as readdir8, readFile as readFile34, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
12966
13211
  import path23 from "node:path";
12967
13212
  var SNIPPETS_DIR = "snippets";
12968
13213
  var LEVEL_RANK = { project: 0, extension: 1, core: 2 };
@@ -13003,7 +13248,7 @@ async function scanSnippetFiles(dir) {
13003
13248
  continue;
13004
13249
  let body = "";
13005
13250
  try {
13006
- body = await readFile33(path23.join(dir, fileName), "utf8");
13251
+ body = await readFile34(path23.join(dir, fileName), "utf8");
13007
13252
  } catch {
13008
13253
  continue;
13009
13254
  }
@@ -13092,7 +13337,7 @@ async function collectLibrary(projectDir) {
13092
13337
  listed.component = name;
13093
13338
  continue;
13094
13339
  }
13095
- const body = await readFile33(path23.join(entry.dir, rel), "utf8").catch(() => null);
13340
+ const body = await readFile34(path23.join(entry.dir, rel), "utf8").catch(() => null);
13096
13341
  if (body === null)
13097
13342
  continue;
13098
13343
  snippets2.push({
@@ -13161,7 +13406,7 @@ async function listLocationVariants(projectDir) {
13161
13406
  }
13162
13407
  // src/lib/extension-import.ts
13163
13408
  import { existsSync as existsSync16 } from "node:fs";
13164
- import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile34, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
13409
+ import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile35, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
13165
13410
  import { tmpdir as tmpdir2 } from "node:os";
13166
13411
  import path24 from "node:path";
13167
13412
  import { unzipSync } from "fflate";
@@ -13234,7 +13479,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13234
13479
  if (!existsSync16(cssPath)) {
13235
13480
  throw new Error("A theme must contain a theme.css file.");
13236
13481
  }
13237
- const css = await readFile34(cssPath, "utf8");
13482
+ const css = await readFile35(cssPath, "utf8");
13238
13483
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
13239
13484
  if (reject) {
13240
13485
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -13262,7 +13507,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13262
13507
  for (let i = 0;i < extraSheets.length; i++) {
13263
13508
  const rel = extraSheets[i];
13264
13509
  const sheetPath = extraSheetPaths[i];
13265
- const sheetCss = await readFile34(sheetPath, "utf8");
13510
+ const sheetCss = await readFile35(sheetPath, "utf8");
13266
13511
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
13267
13512
  if (sheetReject) {
13268
13513
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -13295,7 +13540,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
13295
13540
  let pkg = {};
13296
13541
  if (metaExists) {
13297
13542
  try {
13298
- const parsed = JSON.parse(await readFile34(file, "utf8"));
13543
+ const parsed = JSON.parse(await readFile35(file, "utf8"));
13299
13544
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
13300
13545
  pkg = parsed;
13301
13546
  }
@@ -13397,11 +13642,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
13397
13642
  async function importExtensionFromFile(projectDir, filePath) {
13398
13643
  const ext = path24.extname(filePath).toLowerCase();
13399
13644
  if (ext === ".zip") {
13400
- const buf = await readFile34(filePath);
13645
+ const buf = await readFile35(filePath);
13401
13646
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
13402
13647
  }
13403
13648
  if (ext === ".css") {
13404
- const css = await readFile34(filePath, "utf8");
13649
+ const css = await readFile35(filePath, "utf8");
13405
13650
  return importExtensionFromCssText(projectDir, css, prettify(path24.basename(filePath, ".css")));
13406
13651
  }
13407
13652
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -13866,8 +14111,8 @@ async function readGoogleApiError(res) {
13866
14111
  }
13867
14112
  var RATE_LIMIT_REASONS = /^(dailyLimitExceeded|userRateLimitExceeded|rateLimitExceeded|quotaExceeded)$/i;
13868
14113
  var PERMISSION_REASONS = /^(insufficientPermissions|insufficientFilePermissions|forbidden|PERMISSION_DENIED)$/i;
13869
- function hintFor(info2) {
13870
- const reason = info2.reason ?? "";
14114
+ function hintFor(info3) {
14115
+ const reason = info3.reason ?? "";
13871
14116
  if (/^accessNotConfigured$/i.test(reason)) {
13872
14117
  return "Google Drive publishing isn't fully set up on this build: the Google Drive API isn't enabled for the app's Google Cloud project, so a maintainer needs to enable it (the link is in Google's message).";
13873
14118
  }
@@ -13877,18 +14122,18 @@ function hintFor(info2) {
13877
14122
  if (RATE_LIMIT_REASONS.test(reason)) {
13878
14123
  return "Google Drive is rate-limiting requests right now. Wait a minute and try again.";
13879
14124
  }
13880
- if (PERMISSION_REASONS.test(reason) || info2.status === 401) {
14125
+ if (PERMISSION_REASONS.test(reason) || info3.status === 401) {
13881
14126
  return "This Google Drive connection doesn't have permission for that — disconnect and connect Google Drive again to re-approve access.";
13882
14127
  }
13883
14128
  return;
13884
14129
  }
13885
- function googleApiFailure(what, info2) {
13886
- const parts = [`${what} (HTTP ${info2.status}${info2.reason ? `, ${info2.reason}` : ""}).`];
13887
- const hint = hintFor(info2);
14130
+ function googleApiFailure(what, info3) {
14131
+ const parts = [`${what} (HTTP ${info3.status}${info3.reason ? `, ${info3.reason}` : ""}).`];
14132
+ const hint = hintFor(info3);
13888
14133
  if (hint)
13889
14134
  parts.push(hint);
13890
- if (info2.message)
13891
- parts.push(`Google said: "${info2.message}"`);
14135
+ if (info3.message)
14136
+ parts.push(`Google said: "${info3.message}"`);
13892
14137
  let text = parts.join(" ");
13893
14138
  if (!/\bgoogle\b/i.test(text))
13894
14139
  text = `Google Drive: ${text}`;
@@ -14142,13 +14387,13 @@ function publishCredentialKey(host, account) {
14142
14387
  const a = (account ?? "").trim();
14143
14388
  return a ? `${host}#${a}` : host;
14144
14389
  }
14145
- async function resolvePublishCredential(info2, deps, account = deps.credentialAccount) {
14390
+ async function resolvePublishCredential(info3, deps, account = deps.credentialAccount) {
14146
14391
  const env = deps.env ?? process.env;
14147
- const fromEnv = info2.credential.envVar ? env[info2.credential.envVar]?.trim() : undefined;
14392
+ const fromEnv = info3.credential.envVar ? env[info3.credential.envVar]?.trim() : undefined;
14148
14393
  if (fromEnv) {
14149
14394
  return {
14150
14395
  credential: {
14151
- host: info2.credential.host,
14396
+ host: info3.credential.host,
14152
14397
  kind: "token",
14153
14398
  token: fromEnv,
14154
14399
  createdAt: 0
@@ -14156,21 +14401,21 @@ async function resolvePublishCredential(info2, deps, account = deps.credentialAc
14156
14401
  source: "env"
14157
14402
  };
14158
14403
  }
14159
- const stored = await deps.tokenStore.get(publishCredentialKey(info2.credential.host, account));
14404
+ const stored = await deps.tokenStore.get(publishCredentialKey(info3.credential.host, account));
14160
14405
  return stored ? { credential: stored, source: "store" } : null;
14161
14406
  }
14162
- async function publishConnectionStatus(info2, deps, account = deps.credentialAccount) {
14163
- if (!info2.credential.required)
14407
+ async function publishConnectionStatus(info3, deps, account = deps.credentialAccount) {
14408
+ if (!info3.credential.required)
14164
14409
  return { connected: true };
14165
- const resolved = await resolvePublishCredential(info2, deps, account);
14410
+ const resolved = await resolvePublishCredential(info3, deps, account);
14166
14411
  return resolved ? { connected: true, source: resolved.source } : { connected: false };
14167
14412
  }
14168
- async function listPublishAccounts(info2, deps) {
14169
- const wantHost = info2.credential.host.trim().toLowerCase();
14413
+ async function listPublishAccounts(info3, deps) {
14414
+ const wantHost = info3.credential.host.trim().toLowerCase();
14170
14415
  const all = await deps.tokenStore.list();
14171
14416
  return all.filter((c) => c.host.trim().toLowerCase() === wantHost).map((c) => ({
14172
14417
  account: (c.username ?? "").trim(),
14173
- label: c.label ?? (c.username || info2.label),
14418
+ label: c.label ?? (c.username || info3.label),
14174
14419
  createdAt: c.createdAt
14175
14420
  }));
14176
14421
  }
@@ -15842,7 +16087,7 @@ async function commandExists(cmd, runCommand = defaultCommandRunner, env) {
15842
16087
 
15843
16088
  // src/lib/publish/providers/azure-swa.ts
15844
16089
  var AZURE_SWA_HOST = "azure-swa";
15845
- var info2 = {
16090
+ var info3 = {
15846
16091
  id: "azure-swa",
15847
16092
  label: "Azure Static Web Apps",
15848
16093
  kind: "api",
@@ -15867,9 +16112,9 @@ async function resolveSwaCommand(req) {
15867
16112
  }
15868
16113
  var SWA_INSTALL_HINT = "Install the Azure SWA CLI first: `npm install -g @azure/static-web-apps-cli` " + "(needs Node.js), or set SWA_CLI_PATH to the swa binary.";
15869
16114
  var azureSwaProvider = {
15870
- info: info2,
16115
+ info: info3,
15871
16116
  async authenticate(req) {
15872
- const resolved = await resolvePublishCredential(info2, req.deps);
16117
+ const resolved = await resolvePublishCredential(info3, req.deps);
15873
16118
  if (!resolved) {
15874
16119
  return {
15875
16120
  ok: false,
@@ -15900,7 +16145,7 @@ var azureSwaProvider = {
15900
16145
  return issues;
15901
16146
  },
15902
16147
  async upload(req) {
15903
- const resolved = await resolvePublishCredential(info2, req.deps);
16148
+ const resolved = await resolvePublishCredential(info3, req.deps);
15904
16149
  if (!resolved) {
15905
16150
  throw new Error("No Azure deployment token found. Connect Azure Static Web Apps (or set SWA_CLI_DEPLOYMENT_TOKEN) first.");
15906
16151
  }
@@ -15956,7 +16201,7 @@ async function stageGuidedPackage(req, providerId, listingLines) {
15956
16201
 
15957
16202
  // src/lib/publish/providers/drivethrurpg.ts
15958
16203
  var PUBLISHER_HUB_URL = "https://www.drivethrurpg.com/publisher_hub";
15959
- var info3 = {
16204
+ var info4 = {
15960
16205
  id: "drivethrurpg",
15961
16206
  label: "DriveThruRPG",
15962
16207
  kind: "guided",
@@ -15972,7 +16217,7 @@ var info3 = {
15972
16217
  credential: { required: false, host: "drivethrurpg.com" }
15973
16218
  };
15974
16219
  var drivethrurpgProvider = {
15975
- info: info3,
16220
+ info: info4,
15976
16221
  async authenticate() {
15977
16222
  return { ok: true };
15978
16223
  },
@@ -16013,7 +16258,7 @@ var drivethrurpgProvider = {
16013
16258
  };
16014
16259
 
16015
16260
  // src/lib/publish/providers/gdrive.ts
16016
- import { mkdtemp as mkdtemp3, readFile as readFile35, readdir as readdir11, rm as rm9, stat as stat9, writeFile as writeFile16 } from "node:fs/promises";
16261
+ import { mkdtemp as mkdtemp3, readFile as readFile36, readdir as readdir11, rm as rm9, stat as stat9, writeFile as writeFile16 } from "node:fs/promises";
16017
16262
  import { tmpdir as tmpdir3 } from "node:os";
16018
16263
  import path30 from "node:path";
16019
16264
  import { Zip, ZipDeflate } from "fflate";
@@ -16281,7 +16526,7 @@ async function resumableUpload(fetchImpl, accessToken, opts) {
16281
16526
  // src/lib/publish/providers/gdrive.ts
16282
16527
  var DEFAULT_FOLDER_NAME = "Gutterpress";
16283
16528
  var NOT_CONNECTED_MESSAGE = "Google Drive isn't connected. Run `gutterpress publish --provider gdrive --connect` (or set GDRIVE_REFRESH_TOKEN) first.";
16284
- var info4 = {
16529
+ var info5 = {
16285
16530
  id: "gdrive",
16286
16531
  label: "Google Drive",
16287
16532
  kind: "api",
@@ -16320,7 +16565,7 @@ async function mintAccessToken(resolved, fetchImpl) {
16320
16565
  return accessToken;
16321
16566
  }
16322
16567
  async function getAccessToken(req) {
16323
- const resolved = await resolvePublishCredential(info4, req.deps);
16568
+ const resolved = await resolvePublishCredential(info5, req.deps);
16324
16569
  if (!resolved)
16325
16570
  throw new Error(NOT_CONNECTED_MESSAGE);
16326
16571
  const accessToken = await mintAccessToken(resolved, req.deps.fetch ?? globalThis.fetch);
@@ -16405,12 +16650,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
16405
16650
  await collectZipEntries(root, full, out, onWarn);
16406
16651
  } else if (entry.isFile()) {
16407
16652
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16408
- out[rel] = await readFile35(full);
16653
+ out[rel] = await readFile36(full);
16409
16654
  } else if (entry.isSymbolicLink()) {
16410
16655
  const resolved = await stat9(full).catch(() => null);
16411
16656
  if (resolved?.isFile()) {
16412
16657
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16413
- out[rel] = await readFile35(full);
16658
+ out[rel] = await readFile36(full);
16414
16659
  } else if (resolved?.isDirectory()) {
16415
16660
  await collectZipEntries(root, full, out, onWarn);
16416
16661
  } else {
@@ -16442,9 +16687,9 @@ async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl = writeF
16442
16687
  }
16443
16688
  }
16444
16689
  var gdriveProvider = {
16445
- info: info4,
16690
+ info: info5,
16446
16691
  async authenticate(req) {
16447
- const resolved = await resolvePublishCredential(info4, req.deps);
16692
+ const resolved = await resolvePublishCredential(info5, req.deps);
16448
16693
  if (!resolved)
16449
16694
  return { ok: false, message: NOT_CONNECTED_MESSAGE };
16450
16695
  try {
@@ -16609,7 +16854,7 @@ async function ensureButler(deps) {
16609
16854
  var ITCH_HOST = "itch.io";
16610
16855
  var TARGET_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
16611
16856
  var ITCH_API_TIMEOUT_MS = 15000;
16612
- var info5 = {
16857
+ var info6 = {
16613
16858
  id: "itch",
16614
16859
  label: "itch.io",
16615
16860
  kind: "api",
@@ -16639,9 +16884,9 @@ function itchProjectUrl(target) {
16639
16884
  return `https://${user}.itch.io/${game}`;
16640
16885
  }
16641
16886
  var itchProvider = {
16642
- info: info5,
16887
+ info: info6,
16643
16888
  async authenticate(req) {
16644
- const resolved = await resolvePublishCredential(info5, req.deps);
16889
+ const resolved = await resolvePublishCredential(info6, req.deps);
16645
16890
  if (!resolved) {
16646
16891
  return {
16647
16892
  ok: false,
@@ -16709,7 +16954,7 @@ var itchProvider = {
16709
16954
  },
16710
16955
  async upload(req) {
16711
16956
  const { target, channel } = readConfig2(req);
16712
- const resolved = await resolvePublishCredential(info5, req.deps);
16957
+ const resolved = await resolvePublishCredential(info6, req.deps);
16713
16958
  if (!resolved) {
16714
16959
  throw new Error("No itch.io API key found. Connect itch.io (or set BUTLER_API_KEY) first.");
16715
16960
  }
@@ -16737,7 +16982,7 @@ ${tail}` : ""}`);
16737
16982
 
16738
16983
  // src/lib/publish/providers/kdp.ts
16739
16984
  var KDP_URL = "https://kdp.amazon.com/en_US/bookshelf";
16740
- var info6 = {
16985
+ var info7 = {
16741
16986
  id: "kdp",
16742
16987
  label: "Amazon KDP",
16743
16988
  kind: "guided",
@@ -16747,7 +16992,7 @@ var info6 = {
16747
16992
  credential: { required: false, host: "kdp.amazon.com" }
16748
16993
  };
16749
16994
  var kdpProvider = {
16750
- info: info6,
16995
+ info: info7,
16751
16996
  async authenticate() {
16752
16997
  return { ok: true };
16753
16998
  },
@@ -16792,7 +17037,7 @@ var kdpProvider = {
16792
17037
  import { cp as cp7, mkdir as mkdir20, stat as stat11 } from "node:fs/promises";
16793
17038
  import path32 from "node:path";
16794
17039
  var LOCAL_PUBLISH_DEFAULT_DIR = "dist";
16795
- var info7 = {
17040
+ var info8 = {
16796
17041
  id: "local",
16797
17042
  label: "A folder on this computer",
16798
17043
  kind: "api",
@@ -16809,7 +17054,7 @@ function localPublishDir(req) {
16809
17054
  return path32.resolve(req.project.projectDir, dir);
16810
17055
  }
16811
17056
  var localFolderProvider = {
16812
- info: info7,
17057
+ info: info8,
16813
17058
  async authenticate() {
16814
17059
  return { ok: true };
16815
17060
  },
@@ -16850,7 +17095,7 @@ function requireValidShop(shop) {
16850
17095
  }
16851
17096
  return shop;
16852
17097
  }
16853
- var info8 = {
17098
+ var info9 = {
16854
17099
  id: "shopify",
16855
17100
  label: "Shopify",
16856
17101
  kind: "api",
@@ -16884,7 +17129,7 @@ function readConfig3(req) {
16884
17129
  async function adminGraphQL(req, query, variables = {}) {
16885
17130
  const { shop: rawShop, apiVersion } = readConfig3(req);
16886
17131
  const shop = requireValidShop(rawShop);
16887
- const resolved = await resolvePublishCredential(info8, req.deps);
17132
+ const resolved = await resolvePublishCredential(info9, req.deps);
16888
17133
  if (!resolved) {
16889
17134
  throw new Error("No Shopify access token found. Connect Shopify (or set SHOPIFY_ADMIN_TOKEN) first.");
16890
17135
  }
@@ -16935,9 +17180,9 @@ function userErrorsToMessage(result) {
16935
17180
  return errs?.length ? errs[0].message : null;
16936
17181
  }
16937
17182
  var shopifyProvider = {
16938
- info: info8,
17183
+ info: info9,
16939
17184
  async authenticate(req) {
16940
- const resolved = await resolvePublishCredential(info8, req.deps);
17185
+ const resolved = await resolvePublishCredential(info9, req.deps);
16941
17186
  if (!resolved) {
16942
17187
  return {
16943
17188
  ok: false,
@@ -17071,14 +17316,14 @@ function publishProviderFor(id) {
17071
17316
  }
17072
17317
 
17073
17318
  // src/lib/publish/run-publish.ts
17074
- function resolvePublishFormat(info9, providerConfig) {
17075
- if (info9.formats && info9.formats.length > 0) {
17319
+ function resolvePublishFormat(info10, providerConfig) {
17320
+ if (info10.formats && info10.formats.length > 0) {
17076
17321
  const configured = typeof providerConfig.format === "string" ? providerConfig.format.trim() : "";
17077
- if (configured && info9.formats.includes(configured)) {
17322
+ if (configured && info10.formats.includes(configured)) {
17078
17323
  return configured;
17079
17324
  }
17080
17325
  }
17081
- return info9.format;
17326
+ return info10.format;
17082
17327
  }
17083
17328
  async function resolvePublishRequest(options, deps) {
17084
17329
  const provider = publishProviderFor(options.providerId);
@@ -17227,17 +17472,17 @@ function overlayStore(inner, key, candidate) {
17227
17472
  }
17228
17473
  async function connectPublishProvider(options, deps) {
17229
17474
  const provider = publishProviderFor(options.providerId);
17230
- const info9 = provider.info;
17231
- if (!info9.credential.required) {
17232
- throw new Error(`${info9.label} needs no API key — just publish when you're ready.`);
17475
+ const info10 = provider.info;
17476
+ if (!info10.credential.required) {
17477
+ throw new Error(`${info10.label} needs no API key — just publish when you're ready.`);
17233
17478
  }
17234
- if (info9.credential.connect === "oauth") {
17235
- throw new Error(`${info9.label} connects through your browser, not a pasted key — run ` + `"gutterpress publish --provider ${info9.id} --connect" or use the desktop app's Connect button.`);
17479
+ if (info10.credential.connect === "oauth") {
17480
+ throw new Error(`${info10.label} connects through your browser, not a pasted key — run ` + `"gutterpress publish --provider ${info10.id} --connect" or use the desktop app's Connect button.`);
17236
17481
  }
17237
17482
  const token = options.token.trim();
17238
17483
  if (!token)
17239
17484
  throw new Error("Paste an API key first.");
17240
- const host = info9.credential.host;
17485
+ const host = info10.credential.host;
17241
17486
  const account = (options.account ?? "").trim();
17242
17487
  const key = publishCredentialKey(host, account);
17243
17488
  const candidate = {
@@ -17245,12 +17490,12 @@ async function connectPublishProvider(options, deps) {
17245
17490
  kind: "token",
17246
17491
  token,
17247
17492
  ...account ? { username: account } : {},
17248
- label: account || info9.label,
17493
+ label: account || info10.label,
17249
17494
  createdAt: Date.now()
17250
17495
  };
17251
17496
  const req = await resolvePublishRequest({
17252
17497
  projectDir: options.projectDir,
17253
- providerId: info9.id,
17498
+ providerId: info10.id,
17254
17499
  ...options.manifestPath ? { manifestPath: options.manifestPath } : {}
17255
17500
  }, deps);
17256
17501
  const trialDeps = {
@@ -17261,10 +17506,10 @@ async function connectPublishProvider(options, deps) {
17261
17506
  };
17262
17507
  const auth = await provider.authenticate({ ...req, deps: trialDeps });
17263
17508
  if (!auth.ok) {
17264
- throw new Error(auth.message ?? `${info9.label} didn't accept that key.`);
17509
+ throw new Error(auth.message ?? `${info10.label} didn't accept that key.`);
17265
17510
  }
17266
17511
  await deps.tokenStore.set(key, candidate);
17267
- return { connected: true, providerId: info9.id };
17512
+ return { connected: true, providerId: info10.id };
17268
17513
  }
17269
17514
  async function disconnectPublishCredential(key, deps, options = {}) {
17270
17515
  const existing = await deps.tokenStore.get(key);
@@ -17276,7 +17521,7 @@ async function disconnectPublishCredential(key, deps, options = {}) {
17276
17521
  }
17277
17522
  }
17278
17523
  // src/lib/publish/selections.ts
17279
- import { chmod as chmod3, mkdir as mkdir21, readFile as readFile36, writeFile as writeFile18 } from "node:fs/promises";
17524
+ import { chmod as chmod3, mkdir as mkdir21, readFile as readFile37, writeFile as writeFile18 } from "node:fs/promises";
17280
17525
  import path34 from "node:path";
17281
17526
  class PublishSelectionsStore {
17282
17527
  filePath;
@@ -17286,7 +17531,7 @@ class PublishSelectionsStore {
17286
17531
  }
17287
17532
  async read() {
17288
17533
  try {
17289
- const raw = await readFile36(this.filePath, "utf8");
17534
+ const raw = await readFile37(this.filePath, "utf8");
17290
17535
  const parsed = JSON.parse(raw);
17291
17536
  if (parsed && typeof parsed === "object") {
17292
17537
  return {