gutterpress 0.11.18-alpha.9 → 0.11.19-alpha.1

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 (29) hide show
  1. package/dist/api/index.d.ts +1 -1
  2. package/dist/api/index.js +4 -2
  3. package/dist/{build-t1wxx70d.js → build-vgz3pr8y.js} +6 -6
  4. package/dist/{cdp-smdx6mr3.js → cdp-1hn3xsk2.js} +2 -2
  5. package/dist/{cdp-jg95s674.js → cdp-h0wa42cz.js} +1 -1
  6. package/dist/{cli-t1m7mpdz.js → cli-5j0a5hmc.js} +74 -5
  7. package/dist/{cli-7fkhgtfn.js → cli-asap4t1z.js} +4 -3
  8. package/dist/{cli-kkzka31p.js → cli-eyyggect.js} +1 -1
  9. package/dist/{cli-7t8r97zk.js → cli-mkn97gxp.js} +197 -181
  10. package/dist/{cli-5xt70xgy.js → cli-n3nd3sav.js} +1 -1
  11. package/dist/{cli-ptez7zcc.js → cli-xx94a1k6.js} +1 -1
  12. package/dist/cli.js +12 -12
  13. package/dist/{doctor-hsyr28at.js → doctor-gxf8ty3n.js} +3 -3
  14. package/dist/{engine-wtnakejg.js → engine-fe0n7v9p.js} +3 -3
  15. package/dist/{engine-zy6fzs5a.js → engine-zehw6tkv.js} +2 -2
  16. package/dist/{ext-4m3fx6ts.js → ext-7j388f5z.js} +6 -6
  17. package/dist/{index-n8vs6pss.js → index-b866g2ph.js} +1 -1
  18. package/dist/{index-rzm1phg0.js → index-bt0qws5c.js} +4 -3
  19. package/dist/{index-qm9mw408.js → index-n4ek1358.js} +130 -6
  20. package/dist/index.js +5 -3
  21. package/dist/lib/exec.d.ts +2 -1
  22. package/dist/lib/ghostscript.d.ts +20 -0
  23. package/dist/lib/remote-auth/sync.d.ts +21 -0
  24. package/dist/{new-6a3vr08x.js → new-0wyn55dn.js} +6 -6
  25. package/dist/{preflight-qcxb1bq7.js → preflight-re9v4skc.js} +6 -6
  26. package/dist/{preview-yaxfszs5.js → preview-2x113vyc.js} +6 -8
  27. package/dist/{publish-nzvdfe9y.js → publish-r379k1hr.js} +6 -6
  28. package/dist/{validate-5ybtaw18.js → validate-fje7ca9g.js} +6 -6
  29. 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-t1m7mpdz.js";
17
+ } from "./cli-5j0a5hmc.js";
17
18
  import {
18
19
  DEBOUNCE,
19
20
  UsageError,
20
21
  resolvePort
21
- } from "./cli-5xt70xgy.js";
22
+ } from "./cli-n3nd3sav.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,
@@ -4760,124 +4761,6 @@ async function addBuiltInStyleSet(projectDir, id) {
4760
4761
  });
4761
4762
  }
4762
4763
 
4763
- // src/lib/extension-restore.ts
4764
- import path6 from "node:path";
4765
- var OFFLINE_REASON = "you appear to be offline (npm can't be reached)";
4766
- function pinnedEntries(manifest) {
4767
- const found = new Map;
4768
- for (const entry of Array.isArray(manifest.extensions) ? manifest.extensions : []) {
4769
- const object3 = typeof entry === "object" && entry !== null ? entry : null;
4770
- const use = typeof entry === "string" ? entry : object3?.use;
4771
- if (typeof use !== "string" || object3?.enabled === false)
4772
- continue;
4773
- let parsed;
4774
- try {
4775
- parsed = parseExtensionSpecifier(use);
4776
- } catch {
4777
- continue;
4778
- }
4779
- if (parsed.kind !== "npm" || !parsed.version || !isExactNpmVersion(parsed.version))
4780
- continue;
4781
- const spec = `${parsed.name}@${parsed.version}`;
4782
- if (found.has(spec))
4783
- continue;
4784
- found.set(spec, {
4785
- name: parsed.name,
4786
- version: parsed.version,
4787
- ...typeof object3?.export === "string" && object3.export ? { export: object3.export } : {}
4788
- });
4789
- }
4790
- return [...found.values()];
4791
- }
4792
- async function restoreAll(projectDir, manifestFile, pins, options) {
4793
- const result = { manifestFile, installed: [], failed: [], warnings: [], offline: false };
4794
- const missing = [];
4795
- for (const pin of pins)
4796
- if (await vendoredCopyNeedsInstall(projectDir, pin.name, pin.version))
4797
- missing.push(pin);
4798
- if (missing.length === 0)
4799
- return result;
4800
- const report = options.onProgress ?? (() => {});
4801
- const specs = missing.map((pin) => `${pin.name}@${pin.version}`);
4802
- report({ type: "start", specs });
4803
- for (const [index, pin] of missing.entries()) {
4804
- const spec = specs[index];
4805
- const step = { type: "package", spec, index, total: missing.length };
4806
- if (result.offline) {
4807
- result.failed.push({ use: spec, message: OFFLINE_REASON });
4808
- report({ ...step, state: "failed", message: OFFLINE_REASON });
4809
- continue;
4810
- }
4811
- report({ ...step, state: "downloading" });
4812
- try {
4813
- result.warnings.push(...await restoreNpmExtension(projectDir, pin.name, pin.version, pin.export, {
4814
- ...options,
4815
- keepVersions: pins.filter((other) => other.name === pin.name).map((other) => other.version)
4816
- }));
4817
- result.installed.push(spec);
4818
- report({ ...step, state: "done" });
4819
- } catch (error2) {
4820
- if (error2 instanceof FetchUnavailableError)
4821
- result.offline = true;
4822
- const message = result.offline ? OFFLINE_REASON : error2 instanceof Error ? error2.message : String(error2);
4823
- result.failed.push({ use: spec, message });
4824
- report({ ...step, state: "failed", message });
4825
- }
4826
- }
4827
- report({ type: "end", installed: result.installed, failed: result.failed });
4828
- return result;
4829
- }
4830
- var inFlight = new Map;
4831
- async function restorePinnedExtensions(projectDir, options = {}) {
4832
- let loaded;
4833
- try {
4834
- loaded = await loadManifestWithPath(options.manifestPath ?? projectDir);
4835
- } catch {
4836
- return { manifestFile: null, installed: [], failed: [], warnings: [], offline: false };
4837
- }
4838
- const { manifest, manifestDir, manifestPath } = loaded;
4839
- const pins = pinnedEntries(manifest);
4840
- if (manifestPath === null || pins.length === 0) {
4841
- return { manifestFile: manifestPath && path6.basename(manifestPath), installed: [], failed: [], warnings: [], offline: false };
4842
- }
4843
- const key = projectKey(manifestDir);
4844
- const running = inFlight.get(key);
4845
- if (running)
4846
- return running;
4847
- const run = restoreAll(manifestDir, path6.basename(manifestPath), pins, options).finally(() => {
4848
- inFlight.delete(key);
4849
- });
4850
- inFlight.set(key, run);
4851
- return run;
4852
- }
4853
- function restoreFailureMessage(failure, manifestFile) {
4854
- return `Could not download ${failure.use} (pinned in ${manifestFile ?? "the manifest"}): ${failure.message.replace(/[.\s]+$/, "")}. ` + `Check your internet connection and try again, or install it yourself with \`gutterpress ext add ${failure.use}\`.`;
4855
- }
4856
- function restoreOfflineMessage(failed, manifestFile) {
4857
- const list = failed.map((f) => f.use).join(", ");
4858
- return `You appear to be offline, and this book's extensions are not installed yet: ${list} ` + `(pinned in ${manifestFile ?? "the manifest"}). Connect to the internet and try again to install them; ` + `once installed, they work offline.`;
4859
- }
4860
- async function restoreForCommand(dir, options) {
4861
- const result = await restorePinnedExtensions(dir, { manifestPath: options.manifestPath });
4862
- const file = result.manifestFile ?? "the manifest";
4863
- for (const spec of result.installed)
4864
- console.error(`Downloaded ${spec} (pinned in ${file})`);
4865
- for (const warning of result.warnings)
4866
- log.warn(warning);
4867
- const offline = result.failed.filter((f) => result.offline && f.message === OFFLINE_REASON);
4868
- const problems = [
4869
- ...result.failed.filter((f) => !offline.includes(f)).map((f) => restoreFailureMessage(f, result.manifestFile)),
4870
- ...offline.length > 0 ? [restoreOfflineMessage(offline, result.manifestFile)] : []
4871
- ];
4872
- if (problems.length === 0)
4873
- return;
4874
- if (options.failFast)
4875
- throw new BuildError(problems.join(`
4876
- `), EXIT_CODES.PIPELINE);
4877
- for (const problem of problems)
4878
- log.warn(problem);
4879
- }
4880
-
4881
4764
  // src/checks/registry.ts
4882
4765
  var checks = new Map;
4883
4766
  function registerCheck(check) {
@@ -5021,17 +4904,17 @@ function evictLru() {
5021
4904
  destroyEntryAfterGrace(evicted);
5022
4905
  }
5023
4906
  }
5024
- async function loadPdf(path7) {
4907
+ async function loadPdf(path6) {
5025
4908
  let st;
5026
4909
  try {
5027
- st = await stat2(path7);
4910
+ st = await stat2(path6);
5028
4911
  } catch {
5029
4912
  return null;
5030
4913
  }
5031
- const hit = docCache.get(path7);
4914
+ const hit = docCache.get(path6);
5032
4915
  if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
5033
- docCache.delete(path7);
5034
- docCache.set(path7, hit);
4916
+ docCache.delete(path6);
4917
+ docCache.set(path6, hit);
5035
4918
  try {
5036
4919
  return await hit.doc;
5037
4920
  } catch {
@@ -5040,15 +4923,15 @@ async function loadPdf(path7) {
5040
4923
  }
5041
4924
  if (hit)
5042
4925
  destroyEntry(hit);
5043
- const docPromise = readFile7(path7).then((buf) => getDocumentProxy(new Uint8Array(buf)));
5044
- docCache.delete(path7);
5045
- docCache.set(path7, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
4926
+ const docPromise = readFile7(path6).then((buf) => getDocumentProxy(new Uint8Array(buf)));
4927
+ docCache.delete(path6);
4928
+ docCache.set(path6, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
5046
4929
  evictLru();
5047
4930
  try {
5048
4931
  return await docPromise;
5049
4932
  } catch {
5050
- if (docCache.get(path7)?.doc === docPromise)
5051
- docCache.delete(path7);
4933
+ if (docCache.get(path6)?.doc === docPromise)
4934
+ docCache.delete(path6);
5052
4935
  return null;
5053
4936
  }
5054
4937
  }
@@ -6407,6 +6290,9 @@ function isDroppedPageGradient(decl) {
6407
6290
  return false;
6408
6291
  return isInPageBox(decl);
6409
6292
  }
6293
+ function isPostScriptShadingGradient(decl) {
6294
+ return /(repeating-(linear|radial|conic)|conic)-gradient\(/i.test(decl.value);
6295
+ }
6410
6296
  function isInPageMarginBox(decl) {
6411
6297
  const box = decl.parent;
6412
6298
  if (!box || box.type !== "atrule")
@@ -6655,6 +6541,13 @@ function checkCssRaw(css, from) {
6655
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.`,
6656
6542
  ...nodeLoc(decl)
6657
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
+ });
6658
6551
  } else if (isUnpairedFitContentWidth(decl)) {
6659
6552
  warnings.push({
6660
6553
  rule: ruleRiskyProps,
@@ -6749,7 +6642,7 @@ import { readFile as readFile13 } from "node:fs/promises";
6749
6642
  // src/lib/style-resolver.ts
6750
6643
  import { existsSync as existsSync8 } from "node:fs";
6751
6644
  import { readdir as readdir2 } from "node:fs/promises";
6752
- import path7 from "node:path";
6645
+ import path6 from "node:path";
6753
6646
  var STYLES_SUBDIR = "styles";
6754
6647
  var FALLBACK_PRIORITY = [
6755
6648
  "styles/book.css",
@@ -6759,7 +6652,7 @@ var FALLBACK_PRIORITY = [
6759
6652
  "css/main.css"
6760
6653
  ];
6761
6654
  function relDisplay(projectDir, absPath) {
6762
- return path7.relative(projectDir, absPath).split(path7.sep).join("/");
6655
+ return path6.relative(projectDir, absPath).split(path6.sep).join("/");
6763
6656
  }
6764
6657
  async function dirEntries(dir) {
6765
6658
  try {
@@ -6769,11 +6662,11 @@ async function dirEntries(dir) {
6769
6662
  }
6770
6663
  }
6771
6664
  function cssFilesFrom(dir, entries) {
6772
- return entries.filter((e) => e.isFile() && e.name.toLowerCase().endsWith(".css")).map((e) => path7.join(dir, e.name));
6665
+ return entries.filter((e) => e.isFile() && e.name.toLowerCase().endsWith(".css")).map((e) => path6.join(dir, e.name));
6773
6666
  }
6774
6667
  async function discoverCssFiles(projectDir) {
6775
- const stylesDir = path7.join(projectDir, STYLES_SUBDIR);
6776
- const cssDir = path7.join(projectDir, "css");
6668
+ const stylesDir = path6.join(projectDir, STYLES_SUBDIR);
6669
+ const cssDir = path6.join(projectDir, "css");
6777
6670
  const [rootEntries, stylesEntries, cssEntries] = await Promise.all([
6778
6671
  dirEntries(projectDir),
6779
6672
  dirEntries(stylesDir),
@@ -6796,7 +6689,7 @@ async function resolveActiveStyles(projectDir, manifestStyles) {
6796
6689
  if (manifest.length > 0)
6797
6690
  return manifest;
6798
6691
  for (const rel of FALLBACK_PRIORITY) {
6799
- if (existsSync8(path7.join(projectDir, rel)))
6692
+ if (existsSync8(path6.join(projectDir, rel)))
6800
6693
  return [rel];
6801
6694
  }
6802
6695
  const discovered = await discoverCssFiles(projectDir);
@@ -7011,7 +6904,7 @@ import { dirname as dirname3, resolve as resolve5 } from "node:path";
7011
6904
  // src/lib/asset-inline.ts
7012
6905
  import { createHash as createHash2 } from "node:crypto";
7013
6906
  import { readFile as readFile14 } from "node:fs/promises";
7014
- import path8 from "node:path";
6907
+ import path7 from "node:path";
7015
6908
  import postcss3 from "postcss";
7016
6909
  var FONT_EXTS = new Set([".woff2", ".woff", ".ttf", ".otf"]);
7017
6910
  var HASHED_ASSET_DIR = "assets";
@@ -7052,19 +6945,19 @@ function decodeRef(ref) {
7052
6945
  }
7053
6946
  }
7054
6947
  function toPosix(p) {
7055
- return p.split(path8.sep).join("/");
6948
+ return p.split(path7.sep).join("/");
7056
6949
  }
7057
6950
  function escapesProjectRoot(projectDir, absPath) {
7058
- const rel = path8.relative(projectDir, absPath);
7059
- return rel === ".." || rel.startsWith(`..${path8.sep}`) || path8.isAbsolute(rel);
6951
+ const rel = path7.relative(projectDir, absPath);
6952
+ return rel === ".." || rel.startsWith(`..${path7.sep}`) || path7.isAbsolute(rel);
7060
6953
  }
7061
6954
  function proseImageRefError(ref, projectDir) {
7062
6955
  const cleaned = stripUrlSuffix(decodeRef(ref));
7063
- if (/^file:/i.test(cleaned) || path8.isAbsolute(cleaned)) {
6956
+ if (/^file:/i.test(cleaned) || path7.isAbsolute(cleaned)) {
7064
6957
  return `Image reference must be relative to the book folder: ${ref}
7065
6958
  ` + ` Copy the file into your book folder and reference it from there.`;
7066
6959
  }
7067
- if (escapesProjectRoot(projectDir, path8.resolve(projectDir, cleaned))) {
6960
+ if (escapesProjectRoot(projectDir, path7.resolve(projectDir, cleaned))) {
7068
6961
  return `Image reference points outside the book folder: ${ref}
7069
6962
  ` + ` Copy the file into your book folder and reference it from there.`;
7070
6963
  }
@@ -7134,12 +7027,12 @@ function importTarget(atRule) {
7134
7027
  return target;
7135
7028
  }
7136
7029
  async function inlineOne(cssPath, copies, warnings, seen) {
7137
- const abs = path8.resolve(cssPath);
7030
+ const abs = path7.resolve(cssPath);
7138
7031
  if (seen.has(abs))
7139
7032
  return "";
7140
7033
  seen.add(abs);
7141
7034
  const source = await readOrThrow(abs, "stylesheet", "the book manifest");
7142
- const cssDir = path8.dirname(abs);
7035
+ const cssDir = path7.dirname(abs);
7143
7036
  let root;
7144
7037
  try {
7145
7038
  root = postcss3.parse(source.toString("utf-8"), { from: abs });
@@ -7161,8 +7054,8 @@ async function inlineOne(cssPath, copies, warnings, seen) {
7161
7054
  return null;
7162
7055
  }
7163
7056
  const relPath = stripUrlSuffix(decodeRef(raw));
7164
- const absAsset = path8.resolve(cssDir, relPath);
7165
- const ext = path8.extname(absAsset).toLowerCase();
7057
+ const absAsset = path7.resolve(cssDir, relPath);
7058
+ const ext = path7.extname(absAsset).toLowerCase();
7166
7059
  if (FONT_EXTS.has(ext)) {
7167
7060
  const bytes2 = await readOrThrow(absAsset, "font file", abs);
7168
7061
  return dataUri(bytes2, ext);
@@ -7180,7 +7073,7 @@ async function inlineOne(cssPath, copies, warnings, seen) {
7180
7073
  imports.push({ node, target });
7181
7074
  });
7182
7075
  for (const { node, target } of imports) {
7183
- const importedAbs = path8.resolve(cssDir, stripUrlSuffix(decodeRef(target)));
7076
+ const importedAbs = path7.resolve(cssDir, stripUrlSuffix(decodeRef(target)));
7184
7077
  const inlined = await inlineOne(importedAbs, copies, warnings, seen);
7185
7078
  node.replaceWith(wrapImportConditions(node, postcss3.parse(inlined, { from: importedAbs })));
7186
7079
  }
@@ -7192,10 +7085,10 @@ async function inlineStyles(projectDir, stylePaths) {
7192
7085
  const seen = new Set;
7193
7086
  const parts = [];
7194
7087
  for (const rel of stylePaths) {
7195
- const abs = path8.resolve(projectDir, rel);
7088
+ const abs = path7.resolve(projectDir, rel);
7196
7089
  const css = await inlineOne(abs, copies, warnings, seen);
7197
7090
  if (css.trim().length > 0) {
7198
- parts.push(`/* ${toPosix(path8.relative(projectDir, abs))} */
7091
+ parts.push(`/* ${toPosix(path7.relative(projectDir, abs))} */
7199
7092
  ${css.trim()}`);
7200
7093
  }
7201
7094
  }
@@ -7211,7 +7104,7 @@ async function collectStyleDependencies(projectDir, stylePaths) {
7211
7104
  const found = new Set;
7212
7105
  const visited = new Set;
7213
7106
  async function walk(cssPath) {
7214
- const abs = path8.resolve(cssPath);
7107
+ const abs = path7.resolve(cssPath);
7215
7108
  if (visited.has(abs))
7216
7109
  return;
7217
7110
  visited.add(abs);
@@ -7228,14 +7121,14 @@ async function collectStyleDependencies(projectDir, stylePaths) {
7228
7121
  } catch {
7229
7122
  return;
7230
7123
  }
7231
- const cssDir = path8.dirname(abs);
7124
+ const cssDir = path7.dirname(abs);
7232
7125
  root.walkDecls((decl) => {
7233
7126
  if (!decl.value.includes("url("))
7234
7127
  return;
7235
7128
  for (const raw of extractCssUrls(decl.value)) {
7236
7129
  if (!raw || isNonFilesystemRef(raw))
7237
7130
  continue;
7238
- found.add(path8.resolve(cssDir, stripUrlSuffix(decodeRef(raw))));
7131
+ found.add(path7.resolve(cssDir, stripUrlSuffix(decodeRef(raw))));
7239
7132
  }
7240
7133
  });
7241
7134
  const imports = [];
@@ -7245,11 +7138,11 @@ async function collectStyleDependencies(projectDir, stylePaths) {
7245
7138
  imports.push(target);
7246
7139
  });
7247
7140
  for (const target of imports) {
7248
- await walk(path8.resolve(cssDir, stripUrlSuffix(decodeRef(target))));
7141
+ await walk(path7.resolve(cssDir, stripUrlSuffix(decodeRef(target))));
7249
7142
  }
7250
7143
  }
7251
7144
  for (const rel of stylePaths) {
7252
- await walk(path8.resolve(projectDir, rel));
7145
+ await walk(path7.resolve(projectDir, rel));
7253
7146
  }
7254
7147
  return [...found];
7255
7148
  }
@@ -7266,8 +7159,8 @@ async function planImageCopies(projectDir, refs) {
7266
7159
  continue;
7267
7160
  }
7268
7161
  const cleaned = stripUrlSuffix(decodeRef(ref));
7269
- const abs = path8.resolve(projectDir, cleaned);
7270
- const rel = path8.relative(projectDir, abs);
7162
+ const abs = path7.resolve(projectDir, cleaned);
7163
+ const rel = path7.relative(projectDir, abs);
7271
7164
  const dest = toPosix(rel);
7272
7165
  copies.set(dest, { from: abs, to: dest });
7273
7166
  destinations.set(ref, dest);
@@ -7288,9 +7181,9 @@ async function inlineShapeUrls(html, baseDir) {
7288
7181
  if (!raw || isNonFilesystemRef(raw))
7289
7182
  continue;
7290
7183
  const cleaned = stripUrlSuffix(decodeRef(raw));
7291
- if (path8.isAbsolute(cleaned))
7184
+ if (path7.isAbsolute(cleaned))
7292
7185
  continue;
7293
- const abs = path8.resolve(baseDir, cleaned);
7186
+ const abs = path7.resolve(baseDir, cleaned);
7294
7187
  if (escapesProjectRoot(baseDir, abs))
7295
7188
  continue;
7296
7189
  let bytes;
@@ -7299,7 +7192,7 @@ async function inlineShapeUrls(html, baseDir) {
7299
7192
  } catch {
7300
7193
  continue;
7301
7194
  }
7302
- const mime = mimeFor(path8.extname(abs).toLowerCase());
7195
+ const mime = mimeFor(path7.extname(abs).toLowerCase());
7303
7196
  const dataUri2 = `data:${mime};base64,${bytes.toString("base64")}`;
7304
7197
  replacements.set(m[0], `--gp-shape:url("${dataUri2}")`);
7305
7198
  }
@@ -7444,7 +7337,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
7444
7337
  registerCheck(check22);
7445
7338
 
7446
7339
  // src/lib/build-staging.ts
7447
- import path9 from "node:path";
7340
+ import path8 from "node:path";
7448
7341
  import os from "node:os";
7449
7342
  import fsp from "node:fs/promises";
7450
7343
 
@@ -7629,8 +7522,8 @@ function placeholderPng(width = 640, height = 480, cell = 32) {
7629
7522
 
7630
7523
  // src/lib/build-staging.ts
7631
7524
  async function shipViewerHtml(htmlFile, outDir) {
7632
- await fsp.mkdir(path9.join(outDir, "engine"), { recursive: true });
7633
- await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"), path9.join(outDir, "engine/gutterpress-viewer.js"));
7525
+ await fsp.mkdir(path8.join(outDir, "engine"), { recursive: true });
7526
+ await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"), path8.join(outDir, "engine/gutterpress-viewer.js"));
7634
7527
  const tag = ` <script src="engine/gutterpress-viewer.js"></script>
7635
7528
  `;
7636
7529
  const html = await fsp.readFile(htmlFile, "utf-8");
@@ -7662,18 +7555,18 @@ async function stageBookAssets(options) {
7662
7555
  return { missing: [...missingPlaceholders.keys()].sort() };
7663
7556
  }
7664
7557
  async function copyReferencedAssets(copies, outDir) {
7665
- const dirs = new Set(copies.map((c) => path9.dirname(path9.resolve(outDir, c.to))));
7558
+ const dirs = new Set(copies.map((c) => path8.dirname(path8.resolve(outDir, c.to))));
7666
7559
  await Promise.all([...dirs].map((d) => fsp.mkdir(d, { recursive: true })));
7667
7560
  const missing = new Map;
7668
7561
  await Promise.all(copies.map(async (c) => {
7669
- const dest = path9.resolve(outDir, c.to);
7562
+ const dest = path8.resolve(outDir, c.to);
7670
7563
  try {
7671
7564
  await fsp.copyFile(c.from, dest);
7672
7565
  } catch (err) {
7673
7566
  if (err?.code === "ENOENT") {
7674
7567
  const placeholder = placeholderOutputPath(c.to);
7675
- const placeholderDest = path9.resolve(outDir, placeholder);
7676
- await fsp.mkdir(path9.dirname(placeholderDest), { recursive: true });
7568
+ const placeholderDest = path8.resolve(outDir, placeholder);
7569
+ await fsp.mkdir(path8.dirname(placeholderDest), { recursive: true });
7677
7570
  await fsp.writeFile(placeholderDest, placeholderPng());
7678
7571
  missing.set(c.to, placeholder);
7679
7572
  return;
@@ -7703,7 +7596,7 @@ function dropRelativeLinkHrefs(html) {
7703
7596
  });
7704
7597
  }
7705
7598
  async function createStageRoot() {
7706
- return fsp.mkdtemp(path9.join(os.tmpdir(), "gutterpress-stage-"));
7599
+ return fsp.mkdtemp(path8.join(os.tmpdir(), "gutterpress-stage-"));
7707
7600
  }
7708
7601
 
7709
7602
  // src/checks/source/dangling-links.ts
@@ -8015,7 +7908,7 @@ registerCheck(check26);
8015
7908
 
8016
7909
  // src/checks/source/merge-markers.ts
8017
7910
  import { readFile as readFile19 } from "node:fs/promises";
8018
- import path10 from "node:path";
7911
+ import path9 from "node:path";
8019
7912
 
8020
7913
  // src/checks/asset/extensions.ts
8021
7914
  var RASTER_INSPECTABLE_EXTS = [
@@ -8099,7 +7992,7 @@ var check27 = {
8099
7992
  ignore: [...ASSET_SCAN_IGNORE_GLOBS]
8100
7993
  });
8101
7994
  for (const sibling of siblings.sort()) {
8102
- const rel = path10.relative(ctx.inputDir, sibling).split(path10.sep).join("/");
7995
+ const rel = path9.relative(ctx.inputDir, sibling).split(path9.sep).join("/");
8103
7996
  results.push(finding(check27.id, {
8104
7997
  severity: "warning",
8105
7998
  code: "kept-both-versions",
@@ -8122,24 +8015,24 @@ import { stat as stat4 } from "node:fs/promises";
8122
8015
  import { stat as stat3, readFile as readFile20 } from "node:fs/promises";
8123
8016
  var DEFAULT_DPI = 72;
8124
8017
  var cache = new Map;
8125
- async function inspectImage(path11) {
8018
+ async function inspectImage(path10) {
8126
8019
  let st;
8127
8020
  try {
8128
- st = await stat3(path11);
8021
+ st = await stat3(path10);
8129
8022
  } catch {
8130
8023
  return null;
8131
8024
  }
8132
- const hit = cache.get(path11);
8025
+ const hit = cache.get(path10);
8133
8026
  if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size)
8134
8027
  return hit.info;
8135
8028
  let info2 = null;
8136
8029
  try {
8137
- const buf = await readFile20(path11);
8030
+ const buf = await readFile20(path10);
8138
8031
  info2 = parseImage(buf);
8139
8032
  } catch {
8140
8033
  info2 = null;
8141
8034
  }
8142
- cache.set(path11, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
8035
+ cache.set(path10, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
8143
8036
  return info2;
8144
8037
  }
8145
8038
  function parseImage(b) {
@@ -8880,14 +8773,14 @@ import { existsSync as existsSync11 } from "node:fs";
8880
8773
  import { dirname as dirname4, join as join3, resolve as resolve7 } from "node:path";
8881
8774
 
8882
8775
  // src/lib/output-paths.ts
8883
- import path11 from "node:path";
8776
+ import path10 from "node:path";
8884
8777
  var DIST_DIRNAME = "dist";
8885
8778
  var BOOK_HTML = "book.html";
8886
8779
  function bookSlug(title) {
8887
8780
  return slugify(title ?? "", "book");
8888
8781
  }
8889
8782
  function resolveOutputDir(manifestDir, title) {
8890
- return path11.resolve(manifestDir, DIST_DIRNAME, bookSlug(title));
8783
+ return path10.resolve(manifestDir, DIST_DIRNAME, bookSlug(title));
8891
8784
  }
8892
8785
  function artifactName(title, format) {
8893
8786
  return `${bookSlug(title)}-${format}.pdf`;
@@ -8896,6 +8789,124 @@ function artifactName(title, format) {
8896
8789
  // src/lib/desktop.ts
8897
8790
  var BOOK_HTML_FILENAME = "book.html";
8898
8791
 
8792
+ // src/lib/extension-restore.ts
8793
+ import path11 from "node:path";
8794
+ var OFFLINE_REASON = "you appear to be offline (npm can't be reached)";
8795
+ function pinnedEntries(manifest) {
8796
+ const found = new Map;
8797
+ for (const entry of Array.isArray(manifest.extensions) ? manifest.extensions : []) {
8798
+ const object3 = typeof entry === "object" && entry !== null ? entry : null;
8799
+ const use = typeof entry === "string" ? entry : object3?.use;
8800
+ if (typeof use !== "string" || object3?.enabled === false)
8801
+ continue;
8802
+ let parsed;
8803
+ try {
8804
+ parsed = parseExtensionSpecifier(use);
8805
+ } catch {
8806
+ continue;
8807
+ }
8808
+ if (parsed.kind !== "npm" || !parsed.version || !isExactNpmVersion(parsed.version))
8809
+ continue;
8810
+ const spec = `${parsed.name}@${parsed.version}`;
8811
+ if (found.has(spec))
8812
+ continue;
8813
+ found.set(spec, {
8814
+ name: parsed.name,
8815
+ version: parsed.version,
8816
+ ...typeof object3?.export === "string" && object3.export ? { export: object3.export } : {}
8817
+ });
8818
+ }
8819
+ return [...found.values()];
8820
+ }
8821
+ async function restoreAll(projectDir, manifestFile, pins, options) {
8822
+ const result = { manifestFile, installed: [], failed: [], warnings: [], offline: false };
8823
+ const missing = [];
8824
+ for (const pin of pins)
8825
+ if (await vendoredCopyNeedsInstall(projectDir, pin.name, pin.version))
8826
+ missing.push(pin);
8827
+ if (missing.length === 0)
8828
+ return result;
8829
+ const report = options.onProgress ?? (() => {});
8830
+ const specs = missing.map((pin) => `${pin.name}@${pin.version}`);
8831
+ report({ type: "start", specs });
8832
+ for (const [index, pin] of missing.entries()) {
8833
+ const spec = specs[index];
8834
+ const step = { type: "package", spec, index, total: missing.length };
8835
+ if (result.offline) {
8836
+ result.failed.push({ use: spec, message: OFFLINE_REASON });
8837
+ report({ ...step, state: "failed", message: OFFLINE_REASON });
8838
+ continue;
8839
+ }
8840
+ report({ ...step, state: "downloading" });
8841
+ try {
8842
+ result.warnings.push(...await restoreNpmExtension(projectDir, pin.name, pin.version, pin.export, {
8843
+ ...options,
8844
+ keepVersions: pins.filter((other) => other.name === pin.name).map((other) => other.version)
8845
+ }));
8846
+ result.installed.push(spec);
8847
+ report({ ...step, state: "done" });
8848
+ } catch (error2) {
8849
+ if (error2 instanceof FetchUnavailableError)
8850
+ result.offline = true;
8851
+ const message = result.offline ? OFFLINE_REASON : error2 instanceof Error ? error2.message : String(error2);
8852
+ result.failed.push({ use: spec, message });
8853
+ report({ ...step, state: "failed", message });
8854
+ }
8855
+ }
8856
+ report({ type: "end", installed: result.installed, failed: result.failed });
8857
+ return result;
8858
+ }
8859
+ var inFlight = new Map;
8860
+ async function restorePinnedExtensions(projectDir, options = {}) {
8861
+ let loaded;
8862
+ try {
8863
+ loaded = await loadManifestWithPath(options.manifestPath ?? projectDir);
8864
+ } catch {
8865
+ return { manifestFile: null, installed: [], failed: [], warnings: [], offline: false };
8866
+ }
8867
+ const { manifest, manifestDir, manifestPath } = loaded;
8868
+ const pins = pinnedEntries(manifest);
8869
+ if (manifestPath === null || pins.length === 0) {
8870
+ return { manifestFile: manifestPath && path11.basename(manifestPath), installed: [], failed: [], warnings: [], offline: false };
8871
+ }
8872
+ const key = projectKey(manifestDir);
8873
+ const running = inFlight.get(key);
8874
+ if (running)
8875
+ return running;
8876
+ const run = restoreAll(manifestDir, path11.basename(manifestPath), pins, options).finally(() => {
8877
+ inFlight.delete(key);
8878
+ });
8879
+ inFlight.set(key, run);
8880
+ return run;
8881
+ }
8882
+ function restoreFailureMessage(failure, manifestFile) {
8883
+ return `Could not download ${failure.use} (pinned in ${manifestFile ?? "the manifest"}): ${failure.message.replace(/[.\s]+$/, "")}. ` + `Check your internet connection and try again, or install it yourself with \`gutterpress ext add ${failure.use}\`.`;
8884
+ }
8885
+ function restoreOfflineMessage(failed, manifestFile) {
8886
+ const list = failed.map((f) => f.use).join(", ");
8887
+ return `You appear to be offline, and this book's extensions are not installed yet: ${list} ` + `(pinned in ${manifestFile ?? "the manifest"}). Connect to the internet and try again to install them; ` + `once installed, they work offline.`;
8888
+ }
8889
+ async function restoreForCommand(dir, options) {
8890
+ const result = await restorePinnedExtensions(dir, { manifestPath: options.manifestPath });
8891
+ const file = result.manifestFile ?? "the manifest";
8892
+ for (const spec of result.installed)
8893
+ console.error(`Downloaded ${spec} (pinned in ${file})`);
8894
+ for (const warning of result.warnings)
8895
+ log.warn(warning);
8896
+ const offline = result.failed.filter((f) => result.offline && f.message === OFFLINE_REASON);
8897
+ const problems = [
8898
+ ...result.failed.filter((f) => !offline.includes(f)).map((f) => restoreFailureMessage(f, result.manifestFile)),
8899
+ ...offline.length > 0 ? [restoreOfflineMessage(offline, result.manifestFile)] : []
8900
+ ];
8901
+ if (problems.length === 0)
8902
+ return;
8903
+ if (options.failFast)
8904
+ throw new BuildError(problems.join(`
8905
+ `), EXIT_CODES.PIPELINE);
8906
+ for (const problem of problems)
8907
+ log.warn(problem);
8908
+ }
8909
+
8899
8910
  // src/lib/markdown/index.ts
8900
8911
  import { readdir as readdir3, readFile as readFile22, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
8901
8912
 
@@ -10082,7 +10093,7 @@ class PdfOutput {
10082
10093
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10083
10094
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10084
10095
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10085
- const { buildNativePdf } = await import("./engine-wtnakejg.js");
10096
+ const { buildNativePdf } = await import("./engine-fe0n7v9p.js");
10086
10097
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10087
10098
  title: config.title,
10088
10099
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10107,6 +10118,10 @@ class PdfOutput {
10107
10118
  if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
10108
10119
  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
10120
  }
10121
+ const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
10122
+ if (blankGradientPages.length > 0) {
10123
+ 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.");
10124
+ }
10110
10125
  const shouldStrip = opts.stripAnnotations ?? config.pdfx.stripAnnotations;
10111
10126
  shouldStripAnnotations = shouldStrip;
10112
10127
  if (shouldStrip) {
@@ -10161,7 +10176,7 @@ async function runBuild(opts) {
10161
10176
  if (ctx.format !== "html") {
10162
10177
  await preflightBuildTools(ctx.format, opts, ctx.config);
10163
10178
  }
10164
- const launch = opts.engineBrowser ?? (async () => (await import("./cdp-smdx6mr3.js")).launchChromium());
10179
+ const launch = opts.engineBrowser ?? (async () => (await import("./cdp-1hn3xsk2.js")).launchChromium());
10165
10180
  const browserPromise = ctx.format === "html" ? null : launch();
10166
10181
  browserPromise?.catch(() => {});
10167
10182
  try {
@@ -10315,6 +10330,7 @@ var EMPTY_BOOK_HTML = `<!doctype html>
10315
10330
  </html>
10316
10331
  `;
10317
10332
  async function renderPreviewBook(inputPath, config, opts) {
10333
+ await restoreForCommand(inputPath, { failFast: false });
10318
10334
  const { plugins, pluginStyles } = await loadPluginsWithCss(config.extensions, inputPath, (ref, err) => warn(`Skipping plugin "${ref}" in preview — ${err.message}`));
10319
10335
  return renderChapters(inputPath, {
10320
10336
  title: config.title ?? "Document",
@@ -14181,4 +14197,4 @@ function normalizeProjectKey(projectDir) {
14181
14197
  }
14182
14198
  // src/lib/publish/manifest-publish.ts
14183
14199
  import { isMap as isMap4 } from "yaml";
14184
- export { isPathSpecifier, parseExtensionSpecifier, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, loadManifestWithPath, BUILT_IN_STYLE_SET_IDS, listProjectExtensions, setExtensionEnabled, removeExtension, addExtension, checkExtensionUpdates, updateExtensions, addBuiltInStyleSet, restoreForCommand, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, EXTENSION_KINDS, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, importExtensionFromFile, importExtensionFromUrl, searchNpmExtensions, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listPublishProviders, publishProviderFor, resolvePublishFormat, runPublish, connectPublishProvider, disconnectPublishCredential };
14200
+ export { isPathSpecifier, parseExtensionSpecifier, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, loadManifestWithPath, BUILT_IN_STYLE_SET_IDS, listProjectExtensions, setExtensionEnabled, removeExtension, addExtension, checkExtensionUpdates, updateExtensions, addBuiltInStyleSet, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, EXTENSION_KINDS, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, importExtensionFromFile, importExtensionFromUrl, searchNpmExtensions, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listPublishProviders, publishProviderFor, resolvePublishFormat, runPublish, connectPublishProvider, disconnectPublishCredential };