gutterpress 0.11.23-alpha.2 → 0.11.23-alpha.4

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 (82) hide show
  1. package/README.md +10 -13
  2. package/dist/{README-8ef336pp.md → README-syg2hn83.md} +13 -19
  3. package/dist/api/index.d.ts +1 -1
  4. package/dist/api/index.js +5 -5
  5. package/dist/{build-encgnv0c.js → build-c48eer6f.js} +8 -8
  6. package/dist/checks/asset/extensions.d.ts +1 -1
  7. package/dist/checks/asset/index.d.ts +0 -1
  8. package/dist/checks/policy.d.ts +5 -4
  9. package/dist/checks/source/config-file.d.ts +9 -6
  10. package/dist/checks/source/dangling-links.d.ts +1 -1
  11. package/dist/checks/types.d.ts +2 -2
  12. package/dist/{cli-pz4bn98t.js → cli-54xt7pjg.js} +19 -13
  13. package/dist/{index-2ppw6q9v.js → cli-5crpmz42.js} +80 -1
  14. package/dist/{cli-xq6djqbj.js → cli-93mkaw6v.js} +166 -308
  15. package/dist/{cli-evg8sbht.js → cli-d9mm3vvh.js} +7 -2
  16. package/dist/{cli-kh8v4vvw.js → cli-gnpea9dx.js} +1 -1
  17. package/dist/{cli-aqh6brew.js → cli-pspjxnc8.js} +1 -1
  18. package/dist/{cli-pwwfxdgb.js → cli-rqh295j9.js} +15 -21
  19. package/dist/{index-x3ema2w3.js → cli-y6d1arem.js} +321 -272
  20. package/dist/cli.js +12 -12
  21. package/dist/{doctor-5gtpgpd1.js → doctor-ptb346k8.js} +4 -3
  22. package/dist/engine/shared/gcpm-extract.d.ts +8 -0
  23. package/dist/engine/shared/print-area.d.ts +16 -4
  24. package/dist/{engine-mc5br8g8.js → engine-4rzdx6af.js} +1 -1
  25. package/dist/{engine-tn13gk0b.js → engine-6txhxmmv.js} +2 -2
  26. package/dist/{ext-wn2a5afq.js → ext-gy8szg3x.js} +8 -8
  27. package/dist/{extension-restore-s3x1618g.js → extension-restore-dymzcasz.js} +2 -2
  28. package/dist/{extension-restore-g8jnmmzq.js → extension-restore-m8tj43e7.js} +4 -4
  29. package/dist/{gutterpress-viewer-701vf4qp.js → gutterpress-viewer-mafsby5k.js} +9 -2
  30. package/dist/{cli-2ppw6q9v.js → index-5crpmz42.js} +80 -1
  31. package/dist/{index-0skhjx3e.js → index-5kh4c78h.js} +19 -13
  32. package/dist/{index-2bjcghze.js → index-8ywkd2sv.js} +15 -21
  33. package/dist/{index-j5z86r4a.js → index-ea94zbq4.js} +329 -488
  34. package/dist/{cli-sdmv4bba.js → index-kaks3mm2.js} +448 -156
  35. package/dist/{index-4x6y8krm.js → index-yc3xj266.js} +1 -1
  36. package/dist/index.js +6 -6
  37. package/dist/lib/build-preflight.d.ts +4 -10
  38. package/dist/lib/build-runner.d.ts +1 -1
  39. package/dist/lib/extension-manager.d.ts +8 -7
  40. package/dist/lib/extension-manifest.d.ts +1 -13
  41. package/dist/lib/ghostscript.d.ts +5 -2
  42. package/dist/lib/lint-rules.d.ts +5 -1
  43. package/dist/lib/manifest-config.d.ts +3 -2
  44. package/dist/lib/manifest-old-spellings.d.ts +64 -0
  45. package/dist/lib/manifest.d.ts +15 -1
  46. package/dist/lib/markdown/assemble.d.ts +4 -3
  47. package/dist/lib/markdown/chapter-id.d.ts +1 -1
  48. package/dist/lib/markdown/index.d.ts +1 -1
  49. package/dist/lib/markdown/table.d.ts +5 -3
  50. package/dist/lib/page-size.d.ts +45 -33
  51. package/dist/lib/pdf-inspect.d.ts +27 -0
  52. package/dist/lib/presets.d.ts +10 -8
  53. package/dist/lib/printsafe.d.ts +2 -1
  54. package/dist/lib/project-scaffold.d.ts +1 -1
  55. package/dist/lib/project-templates.d.ts +9 -0
  56. package/dist/lib/remote-auth/sync-messages.d.ts +2 -2
  57. package/dist/lib/targets.d.ts +4 -4
  58. package/dist/lib/validation-exec.d.ts +4 -4
  59. package/dist/manifest-2kc56wvw.yaml +22 -0
  60. package/dist/manifest-41ddzm13.yaml +18 -0
  61. package/dist/manifest-79zsgkpn.yaml +26 -0
  62. package/dist/manifest-sqzm4681.yaml +22 -0
  63. package/dist/{manifest.schema-t7nx2stg.json → manifest.schema-w86c1xw1.json} +30 -308
  64. package/dist/{new-mhs46pw3.js → new-t2t28ccb.js} +9 -15
  65. package/dist/{package-aenwjew3.json → package-awwkt7sa.json} +0 -1
  66. package/dist/{page-rules-nsj3aw42.css → page-rules-ytj9p04v.css} +12 -40
  67. package/dist/{pdf-inspect-wnw4za71.js → pdf-inspect-b3mf81ex.js} +3 -1
  68. package/dist/{plugin.test.js-xgxrd8hf.tpl → plugin.test.js-gxzqqfdh.tpl} +0 -1
  69. package/dist/{plugins-2cek4mz1.js → plugins-fec8t2bh.js} +1 -1
  70. package/dist/{plugins-1sxe0dkz.js → plugins-sqa4msdk.js} +3 -3
  71. package/dist/{preflight-3k2b1x8e.js → preflight-srq8fktp.js} +8 -8
  72. package/dist/preview/file-watcher.d.ts +1 -3
  73. package/dist/{preview-6p9v6rht.js → preview-zsp9hstd.js} +8 -8
  74. package/dist/{publish-qgsngy8s.js → publish-hhy31gr9.js} +8 -8
  75. package/dist/render.js +30 -562
  76. package/dist/schema/manifest.types.d.ts +60 -105
  77. package/dist/{tokens-7984461t.css → tokens-n82t4k9h.css} +12 -12
  78. package/dist/{validate-0v7xw6wa.js → validate-r9jdz85h.js} +8 -8
  79. package/package.json +1 -1
  80. package/dist/checks/asset/approved-fonts.d.ts +0 -3
  81. package/dist/manifest-eagfhba6.yaml +0 -19
  82. package/dist/manifest-n1gh3qw5.yaml +0 -9
@@ -2,6 +2,7 @@ import {
2
2
  DEBOUNCE,
3
3
  FriendlyHttpError,
4
4
  MANIFEST_FILENAMES,
5
+ MARGIN_EDGES,
5
6
  PRESETS,
6
7
  PRESET_IDS,
7
8
  TARGETS,
@@ -9,25 +10,34 @@ import {
9
10
  UsageError,
10
11
  addExtension,
11
12
  applyDefaultPdfStrictChecks,
13
+ cssLength,
12
14
  ensureProjectGitignore,
13
- ensureSeq,
14
15
  extensionConfigFor,
16
+ formatPageSize,
17
+ isNonNegativePoints,
18
+ isPositivePoints,
19
+ judgeRect,
15
20
  listProjectExtensions,
16
21
  loadManifest,
17
22
  loadManifestDoc,
18
23
  loadManifestWithPath,
19
24
  npmRegistryUrl,
25
+ pageBoxes,
26
+ printAreaMeta,
20
27
  publishTargetFor,
28
+ readEntryCss,
21
29
  resolveConfig,
22
30
  resolveConfigForTarget,
23
31
  resolvePort,
24
32
  resolveTargets,
25
33
  restoreForCommand,
34
+ round3,
26
35
  scalarString,
27
36
  uniqueExtensionId,
37
+ validatePageMargin,
28
38
  withFetchTimeout,
29
39
  writeManifestDoc
30
- } from "./index-x3ema2w3.js";
40
+ } from "./index-kaks3mm2.js";
31
41
  import {
32
42
  EXTENSION_MANIFEST_FILENAME,
33
43
  GUTTERPRESS_CSS,
@@ -63,18 +73,18 @@ import {
63
73
  setLogLevel,
64
74
  slugify,
65
75
  warn
66
- } from "./index-2bjcghze.js";
76
+ } from "./index-8ywkd2sv.js";
67
77
  import {
68
78
  blackTextToGray
69
79
  } from "./index-ht1bcqdd.js";
70
80
  import {
81
+ DEFAULT_PAGE_MARGIN_PT,
71
82
  MARGIN_BOX_IGNORED_PROPERTIES,
72
83
  extract,
73
84
  getAssetPath,
74
85
  lastPageMode,
75
- parseSize,
76
86
  resolvePage
77
- } from "./index-0skhjx3e.js";
87
+ } from "./index-5kh4c78h.js";
78
88
  import {
79
89
  BuildError
80
90
  } from "./index-46ycxe6r.js";
@@ -89,8 +99,9 @@ import {
89
99
  isLoadable,
90
100
  loadPdf,
91
101
  pageHasMarks,
102
+ pagesWithVisibleShadingPaints,
92
103
  retainPdfCache
93
- } from "./index-2ppw6q9v.js";
104
+ } from "./index-5crpmz42.js";
94
105
  import {
95
106
  INSTALL_HINTS,
96
107
  enhancedPath,
@@ -146,14 +157,14 @@ var MSG_UP_TO_DATE = "Everything is in sync.";
146
157
  var MSG_UP_TO_DATE_PULLED = "Everything is in sync. The latest online changes were downloaded to this computer.";
147
158
  var MSG_SYNCED = "Your changes are online.";
148
159
  var MSG_SYNCED_MERGED = "Your changes are online, combined with changes from the online copy.";
149
- var MSG_OFFLINE = "Your changes are saved on this computer. gutterpress couldn't reach the online repository — try syncing again when you're back online.";
160
+ var MSG_OFFLINE = "Your changes are saved on this computer. Gutterpress couldn't reach the online repository — try syncing again when you're back online.";
150
161
  var MSG_AUTH = "The online repository didn't accept the saved connection. Reconnect and try again.";
151
162
  var MSG_INSECURE_TRANSPORT = "This book's online address isn't secure, so the saved connection wasn't sent — connections are never sent over an insecure address. Switch the address to a secure one (starting with https), or to a local loopback address for a server on this computer, to sync.";
152
163
  var MSG_BUSY = "The online copy is changing very quickly right now. Your work is saved on this computer — try Sync again in a moment.";
153
164
  var MSG_HISTORY_UNREADABLE = "This book's version history can't be read, so syncing can't continue. Your writing is safe — every file is still here on this computer. Use Repair online backup (in Where your work is kept) to get history and syncing working again.";
154
165
  var MSG_UNRELATED = "This book and the online copy have no history in common, so they can't be combined. Either the online address points at a different book, or this book's own history was lost and started over. Your writing is safe — every file is still here on this computer. Check the online address; if it is correct, use Repair online backup (in Where your work is kept).";
155
166
  var MSG_NO_REMOTE = "This book isn't connected to an online repository yet.";
156
- var MSG_SSH_REMOTE = "This book's online address uses SSH (git@…), which gutterpress can't sync to. Switch it to the web (HTTPS) address to sync from here.";
167
+ var MSG_SSH_REMOTE = "This book's online address uses SSH (git@…), which Gutterpress can't sync to. Switch it to the web (HTTPS) address to sync from here.";
157
168
  var MSG_NO_BRANCH = "This book's version history isn't on a named branch, so it can't be synced right now.";
158
169
  var SYNC_SNAPSHOT_MESSAGE = "Automatic backup of your work";
159
170
  var SYNC_LATE_EDIT_MESSAGE = "Saved the edit you made while syncing";
@@ -185,95 +196,26 @@ function canonicalChapterId(p) {
185
196
  return s;
186
197
  }
187
198
 
188
- // src/engine/shared/print-area.ts
189
- function isPositivePoints(value) {
190
- return typeof value === "number" && Number.isFinite(value) && value > 0;
191
- }
192
- var round3 = (n) => Math.round(n * 1000) / 1000;
193
- function cssLength(pt) {
194
- const inches = round3(pt / 72);
195
- return Math.abs(inches * 72 - pt) < 0.000001 ? `${inches}in` : `${round3(pt)}pt`;
196
- }
197
- function formatPageSize(page) {
198
- return `${cssLength(page.width)} ${cssLength(page.height)}`;
199
- }
200
- function printAreaMeta(page) {
201
- const area = JSON.stringify({
202
- width: page.width,
203
- height: page.height,
204
- bleed: page.bleed ?? 0,
205
- safe: page.safe ?? 0,
206
- artSafe: page.artSafe,
207
- gutter: page.gutter,
208
- preset: page.preset,
209
- spec: page.spec
210
- });
211
- return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
212
- }
213
- function pageBoxes(area, side, scale = 1) {
214
- const w = area.width * scale;
215
- const h = area.height * scale;
216
- const b = Math.max(0, area.bleed) * scale;
217
- const s = Math.max(0, area.safe) * scale;
218
- const a = Math.max(0, area.artSafe) * scale;
219
- const g = Math.max(0, area.gutter) * scale;
220
- const [textLeft, textRight] = side === "verso" ? [s, g] : [g, s];
221
- const [artLeft, artRight] = side === "verso" ? [a, g] : [g, a];
222
- const trim = { x: side === "verso" ? b : 0, y: b, w: Math.max(0, w - b), h: Math.max(0, h - 2 * b) };
223
- return {
224
- sheet: { x: 0, y: 0, w, h },
225
- trim,
226
- safe: {
227
- x: trim.x + textLeft,
228
- y: trim.y + s,
229
- w: Math.max(0, trim.w - textLeft - textRight),
230
- h: Math.max(0, trim.h - 2 * s)
231
- },
232
- safeArt: {
233
- x: trim.x + artLeft,
234
- y: trim.y + a,
235
- w: Math.max(0, trim.w - artLeft - artRight),
236
- h: Math.max(0, trim.h - 2 * a)
237
- },
238
- bleed: b
239
- };
240
- }
241
- var SEVERITY = { inside: 0, unsafe: 1, bleed: 2, "off-page": 3 };
242
- function judgeRect(r, boxes, rule, tol = 0) {
243
- const { sheet, trim } = boxes;
244
- const safe = rule === "art" ? boxes.safeArt : boxes.safe;
245
- const sides = [
246
- ["left", -r.x, -safe.x, -trim.x, -sheet.x],
247
- ["right", r.x + r.w, safe.x + safe.w, trim.x + trim.w, sheet.x + sheet.w],
248
- ["top", -r.y, -safe.y, -trim.y, -sheet.y],
249
- ["bottom", r.y + r.h, safe.y + safe.h, trim.y + trim.h, sheet.y + sheet.h]
250
- ];
251
- let worst = { kind: "inside", gap: 0, need: 0, edge: null };
252
- let beyond = 0;
253
- for (const [edge, ink, limit, trimEdge, sheetEdge] of sides) {
254
- const past = ink - limit;
255
- if (past <= tol || rule === "art" && ink >= sheetEdge - tol)
256
- continue;
257
- const kind = ink > sheetEdge + tol ? "off-page" : ink > trimEdge + tol ? "bleed" : "unsafe";
258
- if (SEVERITY[kind] > SEVERITY[worst.kind] || kind === worst.kind && past > beyond) {
259
- worst = { kind, gap: trimEdge - ink, need: trimEdge - limit, edge };
260
- beyond = past;
261
- }
262
- }
263
- return worst;
264
- }
265
-
266
199
  // src/lib/markdown/assemble.ts
267
- function defaultPageSizeCss(css, page) {
200
+ function pageGeometryCss(page) {
268
201
  if (!page || !isPositivePoints(page.width) || !isPositivePoints(page.height))
269
202
  return null;
270
- try {
271
- if (resolvePage(extract(css)).decls.size !== undefined)
272
- return null;
273
- } catch {
203
+ const size = `size: ${formatPageSize(page)};`;
204
+ const m = page.margin;
205
+ if (!m)
206
+ return `@page { ${size} }`;
207
+ const [top, bottom, inside, outside] = [m.top, m.bottom, m.inside, m.outside].map(cssLength);
208
+ const recto = `margin: ${top} ${outside} ${bottom} ${inside};`;
209
+ const verso = `margin: ${top} ${inside} ${bottom} ${outside};`;
210
+ return [`@page { ${size} ${recto} }`, `@page :left { ${verso} }`, `@page :right { ${recto} }`].join(`
211
+ `);
212
+ }
213
+ function defaultPageMarginCss(page) {
214
+ if (!page || page.margin)
274
215
  return null;
275
- }
276
- return `@page { size: ${formatPageSize(page)}; }`;
216
+ return `@layer gp.vocab {
217
+ @page { margin: ${cssLength(DEFAULT_PAGE_MARGIN_PT)}; }
218
+ }`;
277
219
  }
278
220
  async function assembleBookHtml(opts) {
279
221
  const title = opts.title ?? "Document";
@@ -313,8 +255,8 @@ async function assembleBookHtml(opts) {
313
255
  imageRefs.add(ref);
314
256
  opts.onImageRefs([...imageRefs]);
315
257
  }
316
- const pageSize = defaultPageSizeCss(`${pluginCss}
317
- ${projectCss}`, opts.printArea);
258
+ const pageGeometry = pageGeometryCss(opts.printArea);
259
+ const defaultMargin = defaultPageMarginCss(opts.printArea);
318
260
  const inlineCss = [
319
261
  "@layer gp.marker, gp.vocab;",
320
262
  `/* gutterpress markers */
@@ -326,14 +268,16 @@ ${TWO_VERSIONS_CSS.trim()}
326
268
  @layer gp.vocab {
327
269
  ${GUTTERPRESS_CSS.trim()}
328
270
  }`,
329
- pageSize ? `/* page size (manifest) */
330
- ${pageSize}` : null,
271
+ defaultMargin ? `/* page margins (default) */
272
+ ${defaultMargin}` : null,
331
273
  pluginCss ? `/* extension css */
332
274
  ${pluginCss.trim()}` : null,
333
275
  `/* column alignment */
334
276
  ${TABLE_ALIGN_CSS.trim()}`,
335
277
  projectCss ? `/* project css */
336
- ${projectCss.trim()}` : null
278
+ ${projectCss.trim()}` : null,
279
+ pageGeometry ? `/* page geometry (manifest) */
280
+ ${pageGeometry}` : null
337
281
  ].filter(Boolean).join(`
338
282
 
339
283
  `);
@@ -1012,7 +956,9 @@ async function findPostScriptShadingPages(pdfBytes) {
1012
956
  if (usesPostScriptShading(asDict(page.node.Resources())))
1013
957
  pages.push(i + 1);
1014
958
  });
1015
- return pages;
959
+ if (pages.length === 0)
960
+ return pages;
961
+ return pagesWithVisibleShadingPaints(pdfBytes, pages).catch(() => pages);
1016
962
  }
1017
963
  async function stripAnnotations(pdfPath, stagingDir) {
1018
964
  const tmp = join2(stagingDir, "annotations-stripped.pdf");
@@ -1407,7 +1353,7 @@ async function writeBuildFingerprint(input) {
1407
1353
  // src/lib/validation-exec.ts
1408
1354
  import { existsSync as existsSync7 } from "node:fs";
1409
1355
  import { readFile as readFile20 } from "node:fs/promises";
1410
- import { dirname as dirname2, join as join3, resolve as resolve5 } from "node:path";
1356
+ import { dirname as dirname3, join as join3, resolve as resolve5 } from "node:path";
1411
1357
 
1412
1358
  // src/lib/markdownlint-fix.ts
1413
1359
  import { readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
@@ -1416,18 +1362,20 @@ import { lint as lint2 } from "markdownlint/sync";
1416
1362
 
1417
1363
  // src/checks/source/config-file.ts
1418
1364
  import { existsSync as existsSync3 } from "node:fs";
1419
- import { resolve as resolve2 } from "node:path";
1420
- function findConfigFile(inputDir, names, explicit) {
1421
- if (explicit) {
1422
- const p = resolve2(inputDir, explicit);
1423
- return existsSync3(p) ? p : null;
1424
- }
1425
- for (const name of names) {
1426
- const p = resolve2(inputDir, name);
1427
- if (existsSync3(p))
1428
- return p;
1365
+ import { dirname, resolve as resolve2 } from "node:path";
1366
+ function findConfigFile(startDir, names) {
1367
+ let dir = resolve2(startDir);
1368
+ for (;; ) {
1369
+ for (const name of names) {
1370
+ const p = resolve2(dir, name);
1371
+ if (existsSync3(p))
1372
+ return p;
1373
+ }
1374
+ const parent = dirname(dir);
1375
+ if (existsSync3(resolve2(dir, ".git")) || parent === dir)
1376
+ return null;
1377
+ dir = parent;
1429
1378
  }
1430
- return null;
1431
1379
  }
1432
1380
 
1433
1381
  // src/checks/source/markdownlint.ts
@@ -1539,17 +1487,13 @@ var check = {
1539
1487
  category: "source",
1540
1488
  phase: "pre-build",
1541
1489
  async run(ctx) {
1542
- const sourceConfig = ctx.config.validate.source;
1543
- if (sourceConfig.markdownlint === false)
1544
- return [];
1545
1490
  const files = ctx.markdownFiles;
1546
1491
  if (!files || files.length === 0)
1547
1492
  return [];
1548
- const configPath = typeof sourceConfig.markdownlint === "string" ? sourceConfig.markdownlint : null;
1549
- const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES, configPath);
1550
- if (!resolvedConfig && !configPath)
1493
+ const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES);
1494
+ if (!resolvedConfig)
1551
1495
  return [];
1552
- const config = resolvedConfig ? await loadConfig(resolvedConfig) : { default: true };
1496
+ const config = await loadConfig(resolvedConfig);
1553
1497
  const results = lint({ files, config });
1554
1498
  const out = [];
1555
1499
  for (const [file, violations] of Object.entries(results)) {
@@ -1588,8 +1532,7 @@ async function uncommittedAmong(inputDir, files) {
1588
1532
  return files.filter((file) => changed.has(repoSubPath(repoRoot, file)));
1589
1533
  }
1590
1534
  async function applyMarkdownlintFixes(ctx) {
1591
- const sourceConfig = ctx.config.validate.source;
1592
- if (sourceConfig.markdownlint === false) {
1535
+ if (ctx.config.checks["source.markdownlint"] === "off") {
1593
1536
  log.warn("--fix: source.markdownlint is disabled for this book — no files were fixed.");
1594
1537
  return [];
1595
1538
  }
@@ -1598,13 +1541,12 @@ async function applyMarkdownlintFixes(ctx) {
1598
1541
  log.warn("--fix: no markdown files to fix.");
1599
1542
  return [];
1600
1543
  }
1601
- const configPath = typeof sourceConfig.markdownlint === "string" ? sourceConfig.markdownlint : null;
1602
- const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES, configPath);
1603
- if (!resolvedConfig && !configPath) {
1604
- log.warn(`--fix: no markdownlint config found in ${ctx.inputDir} (looked for ${CONFIG_NAMES.join(", ")}) — no files were fixed.`);
1544
+ const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES);
1545
+ if (!resolvedConfig) {
1546
+ log.warn(`--fix: no markdownlint config found in ${ctx.inputDir} or a folder above it (looked for ${CONFIG_NAMES.join(", ")}) — no files were fixed.`);
1605
1547
  return [];
1606
1548
  }
1607
- const config = resolvedConfig ? await loadConfig(resolvedConfig) : { default: true };
1549
+ const config = await loadConfig(resolvedConfig);
1608
1550
  const results = lint2({ files, config });
1609
1551
  const pending = [];
1610
1552
  for (const [file, violations] of Object.entries(results)) {
@@ -1638,10 +1580,7 @@ import { readFile as readFile7 } from "node:fs/promises";
1638
1580
  // src/checks/policy.ts
1639
1581
  var INSPECT_FAILED_CODE = "inspect-failed";
1640
1582
  function isCheckEnabled(check2, config) {
1641
- const entry = config.validate.checks[check2.id];
1642
- if (entry === false)
1643
- return false;
1644
- if (typeof entry === "object" && entry.enabled === false)
1583
+ if (config.checks[check2.id] === "off")
1645
1584
  return false;
1646
1585
  if (check2.enabledWhen && !check2.enabledWhen(config))
1647
1586
  return false;
@@ -2384,7 +2323,7 @@ var check9 = {
2384
2323
  severity: "warning",
2385
2324
  message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
2386
2325
  file: ctx.pdfPath,
2387
- detail: "Small text printed with all four inks can look fuzzy if the plates are slightly out of register, which is why print vendors such as DriveThruRPG want small black text on the black plate alone. `pdfx.blackText: k-only` already does that for near-black text; this text is either a dark colour (for example a dark brown, which keeps its colour) or painted in a way the build cannot rewrite (an image or a pattern). To print it on the black plate, make the text black (#000). If the colour is intended, set `lint.rules: { pdf.print.black-text: off }` in manifest.yaml.",
2326
+ detail: "Small text printed with all four inks can look fuzzy if the plates are slightly out of register, which is why print vendors such as DriveThruRPG want small black text on the black plate alone. `pdfx.blackText: k-only` already does that for near-black text; this text is either a dark colour (for example a dark brown, which keeps its colour) or painted in a way the build cannot rewrite (an image or a pattern). To print it on the black plate, make the text black (#000). If the colour is intended, set `checks: { pdf.print.black-text: off }` in manifest.yaml.",
2388
2327
  code: "rich-black-text",
2389
2328
  data: { runs: found.runs, pages: found.pages }
2390
2329
  })
@@ -2464,8 +2403,6 @@ var check11 = {
2464
2403
  async run(ctx) {
2465
2404
  if (!ctx.pdfPath)
2466
2405
  return [];
2467
- if (!ctx.config.validate.pdf.requireBookmarks)
2468
- return [];
2469
2406
  const doc = await loadPdf(ctx.pdfPath);
2470
2407
  if (!doc) {
2471
2408
  return [
@@ -2498,8 +2435,6 @@ var check12 = {
2498
2435
  async run(ctx) {
2499
2436
  if (!ctx.pdfPath)
2500
2437
  return [];
2501
- if (!ctx.config.validate.pdf.requireTocLinks)
2502
- return [];
2503
2438
  const doc = await loadPdf(ctx.pdfPath);
2504
2439
  if (!doc) {
2505
2440
  return [
@@ -2637,8 +2572,6 @@ var check16 = {
2637
2572
  async run(ctx) {
2638
2573
  if (!ctx.pdfPath)
2639
2574
  return [];
2640
- if (!ctx.config.validate.pdf.forbidTransparency)
2641
- return [];
2642
2575
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
2643
2576
  const found = [];
2644
2577
  if (bytes.includes("/Transparency"))
@@ -2679,8 +2612,6 @@ var check17 = {
2679
2612
  async run(ctx) {
2680
2613
  if (!ctx.pdfPath)
2681
2614
  return [];
2682
- if (!ctx.config.validate.pdf.requireBleed)
2683
- return [];
2684
2615
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
2685
2616
  if (!bytes) {
2686
2617
  return [
@@ -2777,14 +2708,10 @@ var check19 = {
2777
2708
  category: "source",
2778
2709
  phase: "pre-build",
2779
2710
  async run(ctx) {
2780
- const sourceConfig = ctx.config.validate.source;
2781
- if (sourceConfig.htmlhint === false)
2782
- return [];
2783
2711
  if (!ctx.htmlPath || !existsSync4(ctx.htmlPath))
2784
2712
  return [];
2785
- const configPath = typeof sourceConfig.htmlhint === "string" ? sourceConfig.htmlhint : null;
2786
- const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
2787
- if (!resolvedConfig && !configPath)
2713
+ const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2);
2714
+ if (!resolvedConfig)
2788
2715
  return [];
2789
2716
  const ruleset = resolvedConfig ? JSON.parse(await readFile10(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
2790
2717
  const html = await readFile10(ctx.htmlPath, "utf8");
@@ -2812,7 +2739,7 @@ function ruleLevel(rules, id) {
2812
2739
  const v = rules?.[id];
2813
2740
  if (v === "off" || v === false)
2814
2741
  return "off";
2815
- if (v === "warn" || v === "warning")
2742
+ if (v === "warn" || v === "warning" || v === "info")
2816
2743
  return "warn";
2817
2744
  if (v === "error")
2818
2745
  return "error";
@@ -2833,6 +2760,7 @@ var ruleRiskyProps = "printsafe/no-risky-print-effects";
2833
2760
  var ruleSyntax = "printsafe/syntax-error";
2834
2761
  var rulePageContainment = "printsafe/page-containment";
2835
2762
  var rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
2763
+ var rulePageSizeInStylesheet = "printsafe/page-size-in-stylesheet";
2836
2764
  function extractUrls(value) {
2837
2765
  const urls = [];
2838
2766
  const re = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
@@ -2881,6 +2809,12 @@ function isInPageBox(decl) {
2881
2809
  const page = decl.parent;
2882
2810
  return !!page && page.type === "atrule" && page.name.toLowerCase() === "page";
2883
2811
  }
2812
+ function isInBookPageRule(decl) {
2813
+ if (!isInPageBox(decl))
2814
+ return false;
2815
+ const params = decl.parent.params.trim();
2816
+ return params === "" || /^(:[a-z-]+\s*)+$/i.test(params);
2817
+ }
2884
2818
  function isDroppedPageGradient(decl) {
2885
2819
  const prop = decl.prop.toLowerCase();
2886
2820
  if (prop !== "background" && prop !== "background-image")
@@ -3120,6 +3054,15 @@ function checkCssRaw(css, from) {
3120
3054
  root.walkAtRules((at) => reportRemoteUrls(at.params || "", at));
3121
3055
  root.walkDecls((decl) => {
3122
3056
  const prop = decl.prop.toLowerCase();
3057
+ if (prop === "size" && isInBookPageRule(decl)) {
3058
+ warnings.push({
3059
+ rule: rulePageSizeInStylesheet,
3060
+ severity: "warning",
3061
+ message: `"size" in a stylesheet's @page rule is ignored: the page size comes from the manifest (preset:, or page: width and height — Setup > Book in the app), which is written after every stylesheet. Remove this declaration; to print at another size, change the manifest.`,
3062
+ ...nodeLoc(decl)
3063
+ });
3064
+ return;
3065
+ }
3123
3066
  if (isInertValue(prop, decl.value))
3124
3067
  return;
3125
3068
  if (marginBoxIgnoredProperties.has(prop) && isInPageMarginBox(decl)) {
@@ -3215,10 +3158,7 @@ var check20 = {
3215
3158
  description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
3216
3159
  category: "source",
3217
3160
  phase: "pre-build",
3218
- enabledWhen: (config) => config.validate.source.stylelint !== false,
3219
3161
  async run(ctx) {
3220
- if (ctx.config.validate.source.stylelint === false)
3221
- return [];
3222
3162
  const files = ctx.cssFiles;
3223
3163
  if (!files || files.length === 0)
3224
3164
  return [];
@@ -3236,7 +3176,7 @@ var check20 = {
3236
3176
  });
3237
3177
  continue;
3238
3178
  }
3239
- for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
3179
+ for (const w of checkCss(css, file, { rules: ctx.config.checks })) {
3240
3180
  results.push({
3241
3181
  checkId: check20.id,
3242
3182
  severity: w.severity,
@@ -3377,15 +3317,11 @@ var check21 = {
3377
3317
  description: "Checks an optional per-project CSS ownership contract (.gutterpress/css-contract.yaml): which file may declare which properties/at-rules, and which selectors must never appear unscoped.",
3378
3318
  category: "source",
3379
3319
  phase: "pre-build",
3380
- enabledWhen: (config) => config.validate.source.cssOwnership !== false,
3381
3320
  async run(ctx) {
3382
- if (ctx.config.validate.source.cssOwnership === false)
3383
- return [];
3384
3321
  const files = ctx.cssFiles;
3385
3322
  if (!files || files.length === 0)
3386
3323
  return [];
3387
- const explicit = typeof ctx.config.validate.source.cssOwnership === "string" ? ctx.config.validate.source.cssOwnership : null;
3388
- const configPath = findConfigFile(ctx.inputDir, CONFIG_NAMES3, explicit);
3324
+ const configPath = findConfigFile(ctx.inputDir, CONFIG_NAMES3);
3389
3325
  if (!configPath)
3390
3326
  return [];
3391
3327
  let contract;
@@ -3452,7 +3388,7 @@ registerCheck(check21);
3452
3388
 
3453
3389
  // src/checks/source/local-refs.ts
3454
3390
  import { existsSync as existsSync5 } from "node:fs";
3455
- import { dirname, resolve as resolve3 } from "node:path";
3391
+ import { dirname as dirname2, resolve as resolve3 } from "node:path";
3456
3392
  var check22 = {
3457
3393
  id: "source.links.local-refs",
3458
3394
  name: "Local Markdown References",
@@ -3492,7 +3428,7 @@ function filesystemRef(ref) {
3492
3428
  }
3493
3429
  function localRefExists(ref, kind, sourceFile, projectRoot) {
3494
3430
  const cleaned = filesystemRef(ref);
3495
- const fileRelative = resolve3(dirname(sourceFile), cleaned);
3431
+ const fileRelative = resolve3(dirname2(sourceFile), cleaned);
3496
3432
  const rootRelative = resolve3(projectRoot, cleaned);
3497
3433
  switch (kind) {
3498
3434
  case "image":
@@ -4568,8 +4504,6 @@ var check30 = {
4568
4504
  category: "asset",
4569
4505
  phase: "pre-build",
4570
4506
  async run(ctx) {
4571
- if (ctx.config.validate.assets.allowAlpha)
4572
- return [];
4573
4507
  if (ctx.config.pdfx.transparency === "preserve-text")
4574
4508
  return [];
4575
4509
  const files = shippedImages(ctx, ["png", "tiff", "tif"]);
@@ -4592,58 +4526,6 @@ var check30 = {
4592
4526
  };
4593
4527
  registerCheck(check30);
4594
4528
 
4595
- // src/checks/asset/approved-fonts.ts
4596
- var check31 = {
4597
- id: "asset.font.approved-files",
4598
- name: "Approved Font Files",
4599
- description: "Checks font files against the approved file patterns",
4600
- category: "asset",
4601
- phase: "pre-build",
4602
- async run(ctx) {
4603
- const patterns = ctx.config.validate.assets.approvedFontFiles;
4604
- if (!patterns || patterns.length === 0)
4605
- return [];
4606
- const { glob } = await import("glob");
4607
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
4608
- const allFonts = [];
4609
- for (const dir of dirs) {
4610
- const matches = await glob(`**/*.{${FONT_EXTS2.join(",")}}`, {
4611
- cwd: dir,
4612
- absolute: true,
4613
- ignore: [...ASSET_SCAN_IGNORE_GLOBS]
4614
- });
4615
- allFonts.push(...matches);
4616
- }
4617
- if (allFonts.length === 0)
4618
- return [];
4619
- const approvedFonts = new Set;
4620
- for (const dir of dirs) {
4621
- for (const pattern of patterns) {
4622
- const matches = await glob(pattern, {
4623
- cwd: dir,
4624
- absolute: true,
4625
- ignore: [...ASSET_SCAN_IGNORE_GLOBS]
4626
- });
4627
- for (const m of matches)
4628
- approvedFonts.add(m);
4629
- }
4630
- }
4631
- const results = [];
4632
- for (const font of allFonts) {
4633
- if (!approvedFonts.has(font)) {
4634
- results.push({
4635
- checkId: check31.id,
4636
- severity: "warning",
4637
- message: "Font file not in approved list",
4638
- file: font
4639
- });
4640
- }
4641
- }
4642
- return results;
4643
- }
4644
- };
4645
- registerCheck(check31);
4646
-
4647
4529
  // src/checks/asset/font-license.ts
4648
4530
  import { existsSync as existsSync6 } from "node:fs";
4649
4531
  import { resolve as resolve4 } from "node:path";
@@ -4657,15 +4539,13 @@ var LICENSE_NAMES = [
4657
4539
  "OFL-1.1.txt",
4658
4540
  "COPYING"
4659
4541
  ];
4660
- var check32 = {
4542
+ var check31 = {
4661
4543
  id: "asset.font.license",
4662
4544
  name: "Font License",
4663
4545
  description: "Checks for license files in font directories",
4664
4546
  category: "asset",
4665
4547
  phase: "pre-build",
4666
4548
  async run(ctx) {
4667
- if (!ctx.config.validate.assets.requireFontLicense)
4668
- return [];
4669
4549
  const { glob } = await import("glob");
4670
4550
  const dirs = ctx.assetDirs ?? [ctx.inputDir];
4671
4551
  const fontDirs = new Set;
@@ -4684,7 +4564,7 @@ var check32 = {
4684
4564
  const hasLicense = LICENSE_NAMES.some((name) => existsSync6(resolve4(fontDir, name)));
4685
4565
  if (!hasLicense) {
4686
4566
  results.push({
4687
- checkId: check32.id,
4567
+ checkId: check31.id,
4688
4568
  severity: "warning",
4689
4569
  message: `No font license file found in directory`,
4690
4570
  file: fontDir
@@ -4694,10 +4574,10 @@ var check32 = {
4694
4574
  return results;
4695
4575
  }
4696
4576
  };
4697
- registerCheck(check32);
4577
+ registerCheck(check31);
4698
4578
 
4699
4579
  // src/checks/heuristic/text-density.ts
4700
- var check33 = {
4580
+ var check32 = {
4701
4581
  id: "heuristic.whitespace.text-density",
4702
4582
  name: "Text Density",
4703
4583
  description: "Checks characters-per-page ratio",
@@ -4726,7 +4606,7 @@ var check33 = {
4726
4606
  });
4727
4607
  if (lowPages.length > 0) {
4728
4608
  results.push({
4729
- checkId: check33.id,
4609
+ checkId: check32.id,
4730
4610
  severity: "info",
4731
4611
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
4732
4612
  file: ctx.pdfPath
@@ -4734,7 +4614,7 @@ var check33 = {
4734
4614
  }
4735
4615
  if (highPages.length > 0) {
4736
4616
  results.push({
4737
- checkId: check33.id,
4617
+ checkId: check32.id,
4738
4618
  severity: "info",
4739
4619
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
4740
4620
  file: ctx.pdfPath
@@ -4743,11 +4623,11 @@ var check33 = {
4743
4623
  return results;
4744
4624
  }
4745
4625
  };
4746
- registerCheck(check33);
4626
+ registerCheck(check32);
4747
4627
 
4748
4628
  // src/checks/heuristic/section-density.ts
4749
4629
  import { readFile as readFile19 } from "node:fs/promises";
4750
- var check34 = {
4630
+ var check33 = {
4751
4631
  id: "heuristic.chunking.section-density",
4752
4632
  name: "Section Density",
4753
4633
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -4773,7 +4653,7 @@ var check34 = {
4773
4653
  const line = lines[i];
4774
4654
  if (/^#{1,6}\s/.test(line)) {
4775
4655
  if (paragraphCount > maxParas) {
4776
- results.push(finding(check34.id, {
4656
+ results.push(finding(check33.id, {
4777
4657
  severity: "info",
4778
4658
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
4779
4659
  file,
@@ -4793,7 +4673,7 @@ var check34 = {
4793
4673
  }
4794
4674
  }
4795
4675
  if (paragraphCount > maxParas) {
4796
- results.push(finding(check34.id, {
4676
+ results.push(finding(check33.id, {
4797
4677
  severity: "info",
4798
4678
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
4799
4679
  file,
@@ -4801,7 +4681,7 @@ var check34 = {
4801
4681
  }));
4802
4682
  }
4803
4683
  } catch {
4804
- results.push(inspectionFailed(check34.id, `Could not read source file: ${file}`, {
4684
+ results.push(inspectionFailed(check33.id, `Could not read source file: ${file}`, {
4805
4685
  file
4806
4686
  }));
4807
4687
  }
@@ -4809,10 +4689,10 @@ var check34 = {
4809
4689
  return results;
4810
4690
  }
4811
4691
  };
4812
- registerCheck(check34);
4692
+ registerCheck(check33);
4813
4693
 
4814
4694
  // src/checks/heuristic/layer-count.ts
4815
- var check35 = {
4695
+ var check34 = {
4816
4696
  id: "heuristic.decoration.layer-count",
4817
4697
  name: "Layer Count",
4818
4698
  description: "Counts image objects per page",
@@ -4837,7 +4717,7 @@ var check35 = {
4837
4717
  heavyPages.sort((a, b) => a - b);
4838
4718
  return [
4839
4719
  {
4840
- checkId: check35.id,
4720
+ checkId: check34.id,
4841
4721
  severity: "info",
4842
4722
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
4843
4723
  file: ctx.pdfPath
@@ -4847,10 +4727,10 @@ var check35 = {
4847
4727
  return [];
4848
4728
  }
4849
4729
  };
4850
- registerCheck(check35);
4730
+ registerCheck(check34);
4851
4731
 
4852
4732
  // src/checks/heuristic/placement-variance.ts
4853
- var check36 = {
4733
+ var check35 = {
4854
4734
  id: "heuristic.layout.placement-variance",
4855
4735
  name: "Placement Variance",
4856
4736
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -4868,7 +4748,7 @@ var check36 = {
4868
4748
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
4869
4749
  return [
4870
4750
  {
4871
- checkId: check36.id,
4751
+ checkId: check35.id,
4872
4752
  severity: "info",
4873
4753
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
4874
4754
  file: ctx.pdfPath
@@ -4876,19 +4756,14 @@ var check36 = {
4876
4756
  ];
4877
4757
  }
4878
4758
  };
4879
- registerCheck(check36);
4759
+ registerCheck(check35);
4880
4760
 
4881
4761
  // src/checks/runner.ts
4882
4762
  function getCheckSeverityOverride(checkId, config) {
4883
- const entry = config.validate.checks[checkId];
4884
- if (typeof entry === "object" && entry.severity)
4885
- return entry.severity;
4886
- return;
4763
+ const level = config.checks[checkId];
4764
+ return level === "off" || level === "on" ? undefined : level;
4887
4765
  }
4888
4766
  async function runChecks(ctx, opts = {}) {
4889
- if (ctx.config.validate.enabled === false) {
4890
- return emptyReport();
4891
- }
4892
4767
  const { checks: selected, unmatched: unmatchedSelectors } = selectChecks(opts, ctx.config);
4893
4768
  let checks2 = selected;
4894
4769
  if (opts.skipMissingTools && opts.skipMissingTools.length > 0) {
@@ -4906,32 +4781,25 @@ async function runChecks(ctx, opts = {}) {
4906
4781
  }
4907
4782
  const release = retainPdfCache();
4908
4783
  try {
4909
- for (const check37 of checks2) {
4784
+ for (const check36 of checks2) {
4910
4785
  try {
4911
- let results = await check37.run(ctx);
4912
- const severityOverride = getCheckSeverityOverride(check37.id, ctx.config);
4786
+ const results = await check36.run(ctx);
4787
+ const severityOverride = getCheckSeverityOverride(check36.id, ctx.config);
4913
4788
  if (severityOverride) {
4914
4789
  for (const r of results) {
4915
4790
  r.severity = severityOverride;
4916
4791
  }
4917
4792
  }
4918
- const lintLevel = ruleLevel(ctx.config.lint.rules, check37.id);
4919
- if (lintLevel === "off")
4920
- results = [];
4921
- else if (lintLevel) {
4922
- for (const r of results)
4923
- r.severity = lintLevel === "warn" ? "warning" : "error";
4924
- }
4925
4793
  if (results.length === 0) {
4926
- passed.push(check37.id);
4794
+ passed.push(check36.id);
4927
4795
  } else {
4928
4796
  allResults.push(...results);
4929
4797
  }
4930
4798
  } catch (err) {
4931
4799
  allResults.push({
4932
- checkId: check37.id,
4800
+ checkId: check36.id,
4933
4801
  severity: "error",
4934
- message: `Check "${check37.id}" threw: ${err instanceof Error ? err.message : String(err)}`
4802
+ message: `Check "${check36.id}" threw: ${err instanceof Error ? err.message : String(err)}`
4935
4803
  });
4936
4804
  }
4937
4805
  }
@@ -4956,30 +4824,20 @@ async function runChecks(ctx, opts = {}) {
4956
4824
  }
4957
4825
  };
4958
4826
  }
4959
- function emptyReport() {
4960
- return {
4961
- results: [],
4962
- errors: [],
4963
- warnings: [],
4964
- infos: [],
4965
- passed: [],
4966
- summary: { total: 0, errors: 0, warnings: 0, infos: 0, passed: 0 }
4967
- };
4968
- }
4969
4827
 
4970
4828
  // src/checks/tool-check.ts
4971
4829
  async function checkToolAvailability(config, opts = {}) {
4972
4830
  const { checks: checks2 } = selectChecks(opts, config);
4973
4831
  const toolToChecks = new Map;
4974
- for (const check37 of checks2) {
4975
- if (!check37.requiredTools?.length)
4832
+ for (const check36 of checks2) {
4833
+ if (!check36.requiredTools?.length)
4976
4834
  continue;
4977
- for (const tool of check37.requiredTools) {
4835
+ for (const tool of check36.requiredTools) {
4978
4836
  const existing = toolToChecks.get(tool);
4979
4837
  if (existing) {
4980
- existing.push(check37.id);
4838
+ existing.push(check36.id);
4981
4839
  } else {
4982
- toolToChecks.set(tool, [check37.id]);
4840
+ toolToChecks.set(tool, [check36.id]);
4983
4841
  }
4984
4842
  }
4985
4843
  }
@@ -4995,11 +4853,11 @@ async function checkToolAvailability(config, opts = {}) {
4995
4853
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
4996
4854
  const missingSet = new Set(missing);
4997
4855
  const skippedChecks = [];
4998
- for (const check37 of checks2) {
4999
- if (!check37.requiredTools?.length)
4856
+ for (const check36 of checks2) {
4857
+ if (!check36.requiredTools?.length)
5000
4858
  continue;
5001
- if (check37.requiredTools.some((t) => missingSet.has(t))) {
5002
- skippedChecks.push(check37.id);
4859
+ if (check36.requiredTools.some((t) => missingSet.has(t))) {
4860
+ skippedChecks.push(check36.id);
5003
4861
  }
5004
4862
  }
5005
4863
  return { available, missing, skippedChecks, toolToChecks };
@@ -5070,7 +4928,7 @@ async function sharedAssetDirs(manifestDir, relStyles, inputDir) {
5070
4928
  const ext = file.slice(file.lastIndexOf(".")).toLowerCase();
5071
4929
  if (!SHIPPED_ASSET_EXTS.has(ext))
5072
4930
  continue;
5073
- const dir = dirname2(resolve5(file));
4931
+ const dir = dirname3(resolve5(file));
5074
4932
  if (escapesProjectRoot(projectRoot, dir))
5075
4933
  dirs.add(dir);
5076
4934
  }
@@ -5231,7 +5089,7 @@ function planRuns(opts) {
5231
5089
  async function executeValidation(args) {
5232
5090
  const manifestPath = typeof args.manifest === "string" ? args.manifest : undefined;
5233
5091
  const { manifest, manifestDir } = await loadManifestWithPath(manifestPath ?? args.input ?? undefined, { explicit: manifestPath !== undefined });
5234
- const config = resolveConfig(args.skipStylelint ? { validate: { source: { stylelint: false } } } : {}, manifest);
5092
+ const config = resolveConfig(args.skipStylelint ? { checks: { "source.stylelint": "off" } } : {}, manifest);
5235
5093
  const targetIds = resolveTargets(parseCsv(typeof args.target === "string" ? args.target : undefined), config.targets);
5236
5094
  const pdfPath = typeof args.pdf === "string" ? resolve5(args.pdf) : undefined;
5237
5095
  const inputDir = typeof args.input === "string" ? resolve5(args.input) : undefined;
@@ -5264,7 +5122,7 @@ async function executeValidation(args) {
5264
5122
  let htmlPath;
5265
5123
  const outDir = resolveOutputDir(manifestDir, config.title);
5266
5124
  if (inputDir) {
5267
- const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.source.files);
5125
+ const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.chapters);
5268
5126
  markdownFiles = relMarkdown.map((f) => join3(manifestDir, canonicalChapterId(f)));
5269
5127
  const relStyles = await resolveActiveStyles(manifestDir, config.styles);
5270
5128
  const projectCssFiles = relStyles.map((rel) => resolve5(manifestDir, rel));
@@ -5300,7 +5158,7 @@ async function executeValidation(args) {
5300
5158
  };
5301
5159
  if (args.fix) {
5302
5160
  const selected = selectChecks(runnerOptions, config).checks;
5303
- if (selected.some((check37) => check37.id === MARKDOWNLINT_CHECK_ID)) {
5161
+ if (selected.some((check36) => check36.id === MARKDOWNLINT_CHECK_ID)) {
5304
5162
  await applyMarkdownlintFixes(context);
5305
5163
  } else {
5306
5164
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -5403,16 +5261,16 @@ ${list}
5403
5261
 
5404
5262
  Install the missing tools and re-run, or set GHOSTSCRIPT_PATH, CHROMIUM_PATH, or system PATH so gutterpress can find them. See the User Guide Chapter 7 (System Setup) at examples/gutterpress-user-guide/07-system-setup.md for the full per-feature matrix.`, 2);
5405
5263
  }
5406
- function computeGates(format, opts, config) {
5264
+ function computeGates(format, opts) {
5407
5265
  if (format === "html") {
5408
5266
  if (opts.skipLint || opts.skipPreValidate || opts.skipPostValidate) {
5409
5267
  log.info("Validation/lint flags ignored for --format html (no validation phases apply)");
5410
5268
  }
5411
5269
  return { skipStylelint: false, preValidate: false, postValidate: false };
5412
5270
  }
5413
- const skipStylelint = !!opts.skipLint || config.lint.enabled === false;
5414
- const preValidate = !opts.skipPreValidate && config.validate.enabled !== false;
5415
- const postValidate = format === "pdfx" && !opts.skipPostValidate && config.validate.enabled !== false;
5271
+ const skipStylelint = !!opts.skipLint;
5272
+ const preValidate = !opts.skipPreValidate;
5273
+ const postValidate = format === "pdfx" && !opts.skipPostValidate;
5416
5274
  return { skipStylelint, preValidate, postValidate };
5417
5275
  }
5418
5276
 
@@ -5448,7 +5306,7 @@ async function resolveBuildContext(opts) {
5448
5306
  }, manifest);
5449
5307
  const target = opts.pdfFileOverride ? { kind: "file", file: path7.resolve(opts.pdfFileOverride) } : opts.outDir ? { kind: "directory", dir: opts.outDir } : { kind: "project", dir: resolveOutputDir(manifestDir, config.title) };
5450
5308
  const outDir = target.kind === "file" ? path7.dirname(target.file) : target.dir;
5451
- const gates = computeGates(format, opts, config);
5309
+ const gates = computeGates(format, opts);
5452
5310
  const workDir = target.kind === "project" ? path7.join(path7.dirname(target.dir), `.${path7.basename(target.dir)}-build-${randomBytes(6).toString("hex")}`) : path7.join(os2.tmpdir(), `gutterpress-build-${randomBytes(6).toString("hex")}`);
5453
5311
  return {
5454
5312
  opts,
@@ -5504,8 +5362,8 @@ async function runQualityGates(ctx) {
5504
5362
  }
5505
5363
  async function renderBook(ctx) {
5506
5364
  const { config, gates, renderDir, workDir, opts, format } = ctx;
5507
- if (config.source.files && config.source.files.length > 0) {
5508
- log.info(`Using specified files (${config.source.files.length} total)`);
5365
+ if (config.chapters && config.chapters.length > 0) {
5366
+ log.info(`Using specified files (${config.chapters.length} total)`);
5509
5367
  } else {
5510
5368
  log.info("Using all .md files in alphabetical order");
5511
5369
  }
@@ -5518,7 +5376,7 @@ async function renderBook(ctx) {
5518
5376
  print: config.print,
5519
5377
  printArea: { ...config.page, preset: config.preset, spec: config.spec },
5520
5378
  styles: config.styles,
5521
- files: config.source.files,
5379
+ files: config.chapters,
5522
5380
  plugins,
5523
5381
  pluginStyles,
5524
5382
  onChapterWarnings: (file, warnings) => {
@@ -5669,7 +5527,7 @@ class PdfOutput {
5669
5527
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
5670
5528
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
5671
5529
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
5672
- const { buildNativePdf } = await import("./engine-tn13gk0b.js");
5530
+ const { buildNativePdf } = await import("./engine-6txhxmmv.js");
5673
5531
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
5674
5532
  title: config.title,
5675
5533
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -5706,7 +5564,7 @@ class PdfOutput {
5706
5564
  }
5707
5565
  const flattenPages = config.pdfx.flattenPages;
5708
5566
  if (transparent.length > 0 && config.pdfx.transparency === "preserve-text") {
5709
- const { unsplittableTextReasons } = await import("./pdf-inspect-wnw4za71.js");
5567
+ const { unsplittableTextReasons } = await import("./pdf-inspect-b3mf81ex.js");
5710
5568
  const reasons = await unsplittableTextReasons(rawBytes, transparent).catch((err) => {
5711
5569
  log.warn(`pdfx.transparency: preserve-text could not inspect this PDF's text (${err instanceof Error ? err.message : String(err)}), so every page that uses transparency is flattened whole, as with flatten-page.`);
5712
5570
  return null;
@@ -5979,7 +5837,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
5979
5837
  ...config,
5980
5838
  printArea: config.page && { ...config.page, preset: config.preset, spec: config.spec }
5981
5839
  }, {
5982
- files: config.source?.files ?? null,
5840
+ files: config.chapters ?? null,
5983
5841
  onCssAssets: (copies) => {
5984
5842
  for (const copy of copies)
5985
5843
  nextAssets.set(copy.to, copy.from);
@@ -7206,24 +7064,20 @@ async function getSystemDiagnostics() {
7206
7064
  };
7207
7065
  }
7208
7066
  // src/lib/project-scaffold.ts
7209
- import { access, copyFile, cp, mkdir as mkdir6, readdir as readdir6, readFile as readFile25, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
7067
+ import { access, copyFile, cp, mkdir as mkdir5, readdir as readdir6, readFile as readFile25, stat as stat4, writeFile as writeFile7 } from "node:fs/promises";
7210
7068
  import { constants as FS, existsSync as existsSync9 } from "node:fs";
7211
7069
  import path14 from "node:path";
7212
7070
 
7213
7071
  // src/lib/page-size.ts
7214
7072
  import { existsSync as existsSync8 } from "node:fs";
7215
- import { mkdir as mkdir5, readFile as readFile24, writeFile as writeFile7 } from "node:fs/promises";
7073
+ import { readFile as readFile24 } from "node:fs/promises";
7216
7074
  import path13 from "node:path";
7217
- import postcss5 from "postcss";
7218
- import { isMap, Scalar } from "yaml";
7075
+ import { isMap } from "yaml";
7219
7076
  function writePageBounds(doc, page) {
7220
- const tolerance = page.tolerance ?? numberAt(doc, ["page", "tolerance"]);
7221
- if (!isMap(doc.get("page", true)))
7222
- doc.set("page", doc.createNode({}));
7077
+ ensurePageMap(doc);
7223
7078
  doc.setIn(["page", "width"], page.width);
7224
7079
  doc.setIn(["page", "height"], page.height);
7225
- if (tolerance !== undefined)
7226
- doc.setIn(["page", "tolerance"], tolerance);
7080
+ doc.deleteIn(["page", "tolerance"]);
7227
7081
  }
7228
7082
  function deletePageBounds(doc) {
7229
7083
  const block = doc.get("page", true);
@@ -7236,6 +7090,25 @@ function deletePageBounds(doc) {
7236
7090
  if (block.items.length === 0)
7237
7091
  doc.delete("page");
7238
7092
  }
7093
+ function deletePageMargin(doc) {
7094
+ const block = doc.get("page", true);
7095
+ if (!isMap(block))
7096
+ return;
7097
+ block.delete("margin");
7098
+ if (block.items.length === 0)
7099
+ doc.delete("page");
7100
+ }
7101
+ function writePageMargin(doc, margin) {
7102
+ ensurePageMap(doc);
7103
+ if (!isMap(doc.getIn(["page", "margin"], true)))
7104
+ doc.setIn(["page", "margin"], doc.createNode({}));
7105
+ for (const edge of MARGIN_EDGES)
7106
+ doc.setIn(["page", "margin", edge], margin[edge]);
7107
+ }
7108
+ function ensurePageMap(doc) {
7109
+ if (!isMap(doc.get("page", true)))
7110
+ doc.set("page", doc.createNode({}));
7111
+ }
7239
7112
  function numberAt(doc, keyPath) {
7240
7113
  const v = doc.getIn(keyPath);
7241
7114
  return isPositivePoints(v) ? v : undefined;
@@ -7254,132 +7127,97 @@ function pageSizeForChoice(choice) {
7254
7127
  const base = PRESETS[choice.preset].page;
7255
7128
  return { width: base.width, height: base.height };
7256
7129
  }
7257
- var SAME_SIZE_PT = 0.5;
7258
- function sameSize(a, b) {
7259
- return Math.abs(a.width - b.width) < SAME_SIZE_PT && Math.abs(a.height - b.height) < SAME_SIZE_PT;
7260
- }
7261
- function plainPageRules(root) {
7262
- const rules = [];
7263
- root.walkAtRules("page", (rule) => {
7264
- if (rule.params.trim() === "")
7265
- rules.push(rule);
7266
- });
7267
- return rules;
7268
- }
7269
- function sizeDeclsOf(rule) {
7270
- const decls = [];
7271
- rule.each((node) => {
7272
- if (node.type === "decl" && node.prop.toLowerCase() === "size")
7273
- decls.push(node);
7274
- });
7275
- return decls;
7276
- }
7277
- function readPageSizeFromCss(css) {
7278
- let root;
7279
- try {
7280
- root = postcss5.parse(css);
7281
- } catch {
7282
- return null;
7283
- }
7284
- const decls = plainPageRules(root).flatMap(sizeDeclsOf);
7285
- const last = decls[decls.length - 1];
7286
- return last ? parseSize(last.value) ?? null : null;
7287
- }
7288
- function setPageSizeInCss(css, page) {
7289
- const root = postcss5.parse(css);
7290
- const decls = plainPageRules(root).flatMap(sizeDeclsOf);
7291
- const last = decls[decls.length - 1];
7292
- const value = formatPageSize(page);
7293
- if (last) {
7294
- const current = parseSize(last.value);
7295
- if (current && sameSize(current, page))
7296
- return css;
7297
- last.value = value;
7298
- return root.toString();
7299
- }
7300
- const rule = `@page {
7301
- size: ${value};
7302
- }
7303
- `;
7304
- if (css.trim() === "")
7305
- return css + rule;
7306
- return `${css}${css.endsWith(`
7307
- `) ? "" : `
7308
- `}
7309
- ${rule}`;
7310
- }
7311
- async function ownStylesheets(projectDir) {
7312
- const rels = await resolveActiveStyles(projectDir).catch(() => []);
7313
- return rels.map((rel) => path13.resolve(projectDir, rel)).filter((abs) => {
7314
- const inside = path13.relative(projectDir, abs);
7315
- return !inside.startsWith("..") && !path13.isAbsolute(inside) && existsSync8(abs);
7316
- });
7317
- }
7318
- var relDisplay2 = (projectDir, abs) => path13.relative(projectDir, abs).split(path13.sep).join("/");
7319
- async function findCssSize(projectDir, sheets) {
7320
- let found = null;
7321
- for (const file of sheets) {
7322
- const size2 = readPageSizeFromCss(await readFile24(file, "utf8"));
7323
- if (size2)
7324
- found = { file, size: size2 };
7325
- }
7326
- return found;
7327
- }
7328
- async function readPageSetup(projectDir) {
7329
- const { doc } = await loadManifestDoc(projectDir);
7130
+ function manifestGeometry(doc) {
7330
7131
  const declared = doc.get("preset");
7331
7132
  const preset = declared === undefined || declared === null ? "dtrpg" : PRESET_IDS.includes(String(declared)) ? declared : null;
7332
7133
  const base = preset ? PRESETS[preset].page : null;
7333
7134
  const width = numberAt(doc, ["page", "width"]) ?? base?.width;
7334
7135
  const height = numberAt(doc, ["page", "height"]) ?? base?.height;
7335
- const tolerance = numberAt(doc, ["page", "tolerance"]) ?? base?.tolerance ?? 0.5;
7336
- const found = await findCssSize(projectDir, await ownStylesheets(projectDir));
7337
- return {
7338
- preset,
7339
- bounds: width !== undefined && height !== undefined ? { width, height, tolerance } : null,
7340
- css: found ? { ...found.size, file: relDisplay2(projectDir, found.file) } : null
7136
+ const tolerance = base?.tolerance ?? 0.5;
7137
+ const bounds = width !== undefined && height !== undefined ? { width, height, tolerance } : null;
7138
+ const presetSizeOverridden = base !== null && (numberAt(doc, ["page", "width"]) !== undefined || numberAt(doc, ["page", "height"]) !== undefined);
7139
+ const at = (edge) => doc.getIn(["page", "margin", edge]);
7140
+ const margins = MARGIN_EDGES.every((edge) => isNonNegativePoints(at(edge))) ? { top: at("top"), bottom: at("bottom"), inside: at("inside"), outside: at("outside") } : undefined;
7141
+ return { preset, bounds, presetSizeOverridden, ...margins ? { margins } : {} };
7142
+ }
7143
+ async function stylesheetMargins(projectDir, manifestStyles) {
7144
+ const d = DEFAULT_PAGE_MARGIN_PT;
7145
+ const fallback = {
7146
+ margins: { top: d, bottom: d, inside: d, outside: d },
7147
+ marginsFrom: "default"
7341
7148
  };
7342
- }
7343
- async function setPageSetup(projectDir, choice) {
7344
- const size2 = pageSizeForChoice(choice);
7345
- const sheets = await ownStylesheets(projectDir);
7346
- const found = await findCssSize(projectDir, sheets);
7347
- const newSheet = path13.join(projectDir, "styles", "book.css");
7348
- const target = found?.file ?? sheets[sheets.length - 1] ?? newSheet;
7349
- const before = existsSync8(target) ? await readFile24(target, "utf8") : "";
7350
- let after;
7149
+ const [entries, rels] = await Promise.all([
7150
+ listProjectExtensions(projectDir).catch(() => []),
7151
+ resolveActiveStyles(projectDir, manifestStyles).catch(() => [])
7152
+ ]);
7153
+ const sheets = rels.map((rel) => path13.resolve(projectDir, rel)).filter((abs) => existsSync8(abs));
7154
+ const parts = await Promise.all([
7155
+ ...entries.filter((e) => e.enabled && e.carries.styles).map((e) => readEntryCss(e).catch(() => "")),
7156
+ ...sheets.map((file) => readFile24(file, "utf8").catch(() => ""))
7157
+ ]);
7351
7158
  try {
7352
- after = setPageSizeInCss(before, size2);
7353
- } catch (e) {
7354
- throw new UsageError(`Could not update the page size: ${relDisplay2(projectDir, target)} has a CSS error ` + `(${e instanceof Error ? e.message : String(e)}). Fix it, then try again.`);
7159
+ const page = resolvePage(extract(parts.join(`
7160
+
7161
+ `)), { pseudos: ["right"] });
7162
+ const declared = Object.keys(page.decls).some((prop) => prop === "margin" || prop.startsWith("margin-"));
7163
+ if (!declared)
7164
+ return fallback;
7165
+ const m = page.geometry.margin;
7166
+ return {
7167
+ margins: { top: round3(m.top), bottom: round3(m.bottom), inside: round3(m.left), outside: round3(m.right) },
7168
+ marginsFrom: "stylesheet"
7169
+ };
7170
+ } catch {
7171
+ return fallback;
7355
7172
  }
7173
+ }
7174
+ async function setupOf(projectDir, doc) {
7175
+ const { margins, ...geometry } = manifestGeometry(doc);
7176
+ const styles = doc.toJS()?.styles;
7177
+ const sheets = await stylesheetMargins(projectDir, Array.isArray(styles) ? styles : undefined);
7178
+ return margins ? { ...geometry, margins, marginsFrom: "manifest", fallbackMarginsFrom: sheets.marginsFrom } : { ...geometry, ...sheets, fallbackMarginsFrom: sheets.marginsFrom };
7179
+ }
7180
+ async function readPageSetup(projectDir) {
7181
+ const { doc } = await loadManifestDoc(projectDir);
7182
+ return setupOf(projectDir, doc);
7183
+ }
7184
+ async function setPageSetup(projectDir, change) {
7356
7185
  const { doc, file } = await loadManifestDoc(projectDir);
7357
- doc.set("preset", choice.preset);
7358
- if (choice.preset === "custom")
7359
- writePageBounds(doc, size2);
7360
- else
7361
- deletePageBounds(doc);
7362
- if (target === newSheet && !sheets.includes(newSheet)) {
7363
- ensureSeq(doc, "styles").add(new Scalar("styles/book.css"));
7186
+ const size2 = change.size ? pageSizeForChoice(change.size) : null;
7187
+ let margins;
7188
+ if (change.margins) {
7189
+ const against = size2 ?? manifestGeometry(doc).bounds;
7190
+ if (!against)
7191
+ throw new UsageError("Choose a page size before setting margins: the manifest records none.");
7192
+ margins = validatePageMargin(change.margins, against);
7193
+ }
7194
+ if (change.size && size2) {
7195
+ doc.set("preset", change.size.preset);
7196
+ if (change.size.preset === "custom")
7197
+ writePageBounds(doc, size2);
7198
+ else
7199
+ deletePageBounds(doc);
7364
7200
  }
7201
+ if (margins)
7202
+ writePageMargin(doc, margins);
7203
+ else if (change.margins === null)
7204
+ deletePageMargin(doc);
7365
7205
  await writeManifestDoc(file, doc);
7366
- if (after !== before || !existsSync8(target)) {
7367
- await mkdir5(path13.dirname(target), { recursive: true });
7368
- await writeFile7(target, after, "utf8");
7369
- }
7370
- return readPageSetup(projectDir);
7206
+ return setupOf(projectDir, doc);
7371
7207
  }
7372
7208
 
7373
7209
  // src/lib/project-scaffold.ts
7374
7210
  function starterOverridesCss() {
7375
7211
  return `/**
7376
- * Your book's own styles.
7212
+ * Your book's own styles. Rules here win over your look's.
7213
+ *
7214
+ * In the app you rarely need this file: Setup > Look chooses a look and
7215
+ * changes its fonts and colors (those changes are saved here as :root
7216
+ * custom properties), Setup > Advanced lists every setting, and Setup > Book
7217
+ * sets the page size and margins (in the manifest).
7377
7218
  *
7378
- * This file loads AFTER every extension, so any rule you add here overrides
7379
- * a look's defaults. A new book has no look until its template or you add
7380
- * one:
7381
- * gutterpress ext add academic <book> (a built-in look)
7382
- * gutterpress ext add <package> <book> (a look from npm)
7219
+ * To change the look from the command line: gutterpress ext remove <old>
7220
+ * <book>, then gutterpress ext add <look> <book>.
7383
7221
  */
7384
7222
  `;
7385
7223
  }
@@ -7415,9 +7253,6 @@ function requirePreset(preset, customPage) {
7415
7253
  } else if (customPage && (!isPositivePoints(customPage.width) || !isPositivePoints(customPage.height))) {
7416
7254
  throw new CreateProjectErrorImpl("custom-page-required", "Page overrides need a positive width and height in points (72pt = 1in).");
7417
7255
  }
7418
- if (customPage?.tolerance !== undefined && !isPositivePoints(customPage.tolerance)) {
7419
- throw new CreateProjectErrorImpl("custom-page-required", "Page tolerance must be a positive number of points.");
7420
- }
7421
7256
  return preset;
7422
7257
  }
7423
7258
  function resolveScaffoldTargets(requested, preset) {
@@ -7462,7 +7297,7 @@ async function scaffoldProject(options) {
7462
7297
  const preset = options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
7463
7298
  const targets = preset ? resolveScaffoldTargets(options.targets, preset) : undefined;
7464
7299
  try {
7465
- await mkdir6(projectDir, { recursive: true });
7300
+ await mkdir5(projectDir, { recursive: true });
7466
7301
  if (customTemplateDir) {
7467
7302
  const entries = await readdir6(customTemplateDir, { withFileTypes: true });
7468
7303
  for (const entry of entries) {
@@ -7470,15 +7305,15 @@ async function scaffoldProject(options) {
7470
7305
  continue;
7471
7306
  await cp(path14.join(customTemplateDir, entry.name), path14.join(projectDir, entry.name), { recursive: true });
7472
7307
  }
7473
- await mkdir6(path14.join(projectDir, "assets"), { recursive: true });
7308
+ await mkdir5(path14.join(projectDir, "assets"), { recursive: true });
7474
7309
  } else {
7475
7310
  const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
7476
7311
  const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
7477
- await mkdir6(path14.join(projectDir, "assets"), { recursive: true });
7312
+ await mkdir5(path14.join(projectDir, "assets"), { recursive: true });
7478
7313
  await copyFile(tplManifest, path14.join(projectDir, MANIFEST_FILENAMES[0]));
7479
7314
  await copyFile(tplChapter, path14.join(projectDir, "chapter-01.md"));
7480
- await mkdir6(path14.join(projectDir, "styles"), { recursive: true });
7481
- await writeFile8(path14.join(projectDir, "styles", "book.css"), starterOverridesCss(), "utf8");
7315
+ await mkdir5(path14.join(projectDir, "styles"), { recursive: true });
7316
+ await writeFile7(path14.join(projectDir, "styles", "book.css"), starterOverridesCss(), "utf8");
7482
7317
  }
7483
7318
  } catch (e) {
7484
7319
  throw new CreateProjectErrorImpl("scaffold-io", `Could not create the book files: ${e instanceof Error ? e.message : String(e)}`);
@@ -7502,6 +7337,8 @@ async function scaffoldProject(options) {
7502
7337
  if (preset) {
7503
7338
  doc.set("preset", preset);
7504
7339
  doc.set("targets", doc.createNode(targets ?? []));
7340
+ if (preset !== "custom")
7341
+ deletePageBounds(doc);
7505
7342
  }
7506
7343
  if (options.customPage)
7507
7344
  writePageBounds(doc, options.customPage);
@@ -7579,12 +7416,12 @@ async function adoptFolder(options) {
7579
7416
  await fillTemplateFile(path14.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
7580
7417
  mdFiles = ["chapter-01.md"];
7581
7418
  }
7582
- await mkdir6(path14.join(dir, "styles"), { recursive: true });
7419
+ await mkdir5(path14.join(dir, "styles"), { recursive: true });
7583
7420
  const bookCssPath = path14.join(dir, "styles", "book.css");
7584
7421
  if (!existsSync9(bookCssPath)) {
7585
- await writeFile8(bookCssPath, starterOverridesCss(), "utf8");
7422
+ await writeFile7(bookCssPath, starterOverridesCss(), "utf8");
7586
7423
  }
7587
- const filesYaml = mdFiles.map((f) => ` - "${escapeYamlScalar(f)}"`).join(`
7424
+ const filesYaml = mdFiles.map((f) => ` - "${escapeYamlScalar(f)}"`).join(`
7588
7425
  `);
7589
7426
  const manifest = `title: "${escapeYamlScalar(title)}"
7590
7427
  authors:
@@ -7592,14 +7429,13 @@ authors:
7592
7429
  preset: dtrpg
7593
7430
  targets:
7594
7431
  - dtrpg
7595
- source:
7596
- files:
7432
+ chapters:
7597
7433
  ${filesYaml}
7598
7434
  styles:
7599
7435
  - styles/book.css
7600
7436
  `;
7601
- await writeFile8(path14.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
7602
- await mkdir6(path14.join(dir, "assets"), { recursive: true });
7437
+ await writeFile7(path14.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
7438
+ await mkdir5(path14.join(dir, "assets"), { recursive: true });
7603
7439
  await ensureProjectGitignore(dir);
7604
7440
  } catch (e) {
7605
7441
  if (e instanceof CreateProjectErrorImpl)
@@ -7641,7 +7477,7 @@ async function firstSourceFile(manifestPath) {
7641
7477
  } catch {
7642
7478
  return;
7643
7479
  }
7644
- const filesIdx = text.search(/^\s*files:\s*$/m);
7480
+ const filesIdx = text.search(/^(?:chapters|\s+files):\s*$/m);
7645
7481
  if (filesIdx === -1)
7646
7482
  return;
7647
7483
  const rest = text.slice(filesIdx);
@@ -7653,10 +7489,10 @@ async function fillTemplateFile(filePath, substitutions) {
7653
7489
  for (const [token, value] of Object.entries(substitutions)) {
7654
7490
  text = text.split(token).join(value);
7655
7491
  }
7656
- await writeFile8(filePath, text, "utf8");
7492
+ await writeFile7(filePath, text, "utf8");
7657
7493
  }
7658
7494
  // src/lib/extension-scaffold.ts
7659
- import { access as access2, mkdir as mkdir7, readFile as readFile26, writeFile as writeFile9 } from "node:fs/promises";
7495
+ import { access as access2, mkdir as mkdir6, readFile as readFile26, writeFile as writeFile8 } from "node:fs/promises";
7660
7496
  import { constants as FS2 } from "node:fs";
7661
7497
  import path15 from "node:path";
7662
7498
  var EXTENSION_KINDS = ["plugin", "look"];
@@ -7768,14 +7604,14 @@ async function scaffoldExtension(options) {
7768
7604
  const entries = TEMPLATE_FILES[kind];
7769
7605
  const written = [];
7770
7606
  try {
7771
- await mkdir7(extensionDir, { recursive: true });
7607
+ await mkdir6(extensionDir, { recursive: true });
7772
7608
  for (const [assetName, relDest] of entries) {
7773
7609
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
7774
7610
  const dest = path15.join(extensionDir, relDest);
7775
- await mkdir7(path15.dirname(dest), { recursive: true });
7611
+ await mkdir6(path15.dirname(dest), { recursive: true });
7776
7612
  const text = await readFile26(src, "utf8");
7777
7613
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
7778
- await writeFile9(dest, substitute(text, table), "utf8");
7614
+ await writeFile8(dest, substitute(text, table), "utf8");
7779
7615
  written.push(relDest);
7780
7616
  }
7781
7617
  } catch (e) {
@@ -7799,7 +7635,7 @@ function substitute(text, table) {
7799
7635
  return out;
7800
7636
  }
7801
7637
  // src/lib/project-templates.ts
7802
- import { cp as cp2, mkdir as mkdir8, readFile as readFile27, readdir as readdir7, stat as stat5, writeFile as writeFile10 } from "node:fs/promises";
7638
+ import { cp as cp2, mkdir as mkdir7, readFile as readFile27, readdir as readdir7, stat as stat5, writeFile as writeFile9 } from "node:fs/promises";
7803
7639
  import { constants as FS3, existsSync as existsSync10 } from "node:fs";
7804
7640
  import { access as access3 } from "node:fs/promises";
7805
7641
  import path16 from "node:path";
@@ -7822,6 +7658,10 @@ async function readTemplateChoices(manifestPath) {
7822
7658
  if (isSeq(targets)) {
7823
7659
  out.targets = targets.items.map((i) => scalarString(i)).filter((s) => s !== null);
7824
7660
  }
7661
+ const width = numberAt(doc, ["page", "width"]);
7662
+ const height = numberAt(doc, ["page", "height"]);
7663
+ if (width !== undefined && height !== undefined)
7664
+ out.page = { width, height };
7825
7665
  return out;
7826
7666
  } catch {
7827
7667
  return {};
@@ -7830,19 +7670,19 @@ async function readTemplateChoices(manifestPath) {
7830
7670
  var BUILT_IN_META = {
7831
7671
  book: {
7832
7672
  label: "Book",
7833
- description: "A clean starting point for a novel, memoir, or any long-form book."
7673
+ description: "A clean starting point for a novel, memoir, or any long-form book. Uses the Clean book look."
7834
7674
  },
7835
7675
  zine: {
7836
7676
  label: "Zine",
7837
- description: "A short, personal zine made to be printed, folded, and shared."
7677
+ description: "A short, personal zine made to be printed, folded, and shared. Uses the Zine look."
7838
7678
  },
7839
7679
  technical: {
7840
7680
  label: "Technical document",
7841
- description: "A manual or guide with steps, reference tables, and code examples."
7681
+ description: "A manual or guide with steps, reference tables, and code examples. Uses the Technical doc look."
7842
7682
  },
7843
7683
  paper: {
7844
7684
  label: "Academic paper",
7845
- description: "A journal-style article with LaTeX math, numbered equations, figures, tables, notes and references."
7685
+ description: "A journal-style article with LaTeX math, numbered equations, figures, tables, notes and references. Uses the Academic look."
7846
7686
  }
7847
7687
  };
7848
7688
  async function listBuiltInTemplates() {
@@ -7939,7 +7779,7 @@ async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
7939
7779
  const vendorDir = pickVendorDir(templateDir, base);
7940
7780
  await Promise.all(externalFiles.filter((abs) => existsSync10(abs)).map(async (abs) => {
7941
7781
  const dest = path16.join(templateDir, vendorDir, path16.relative(base, abs));
7942
- await mkdir8(path16.dirname(dest), { recursive: true });
7782
+ await mkdir7(path16.dirname(dest), { recursive: true });
7943
7783
  await cp2(abs, dest, { recursive: true });
7944
7784
  }));
7945
7785
  const bookLocal = (value) => toPosixPath(path16.join(vendorDir, path16.relative(base, path16.resolve(sourceProjectDir, value))));
@@ -7972,7 +7812,7 @@ async function saveProjectAsTemplate(options) {
7972
7812
  if (exists) {
7973
7813
  throw new Error(`A template named "${name}" already exists. Choose a different name.`);
7974
7814
  }
7975
- await mkdir8(dir, { recursive: true });
7815
+ await mkdir7(dir, { recursive: true });
7976
7816
  const entries = await readdir7(projectDir, { withFileTypes: true });
7977
7817
  for (const entry of entries) {
7978
7818
  if (SKIP_ENTRIES.has(entry.name))
@@ -7985,7 +7825,7 @@ async function saveProjectAsTemplate(options) {
7985
7825
  const shared = manifestName ? await reconcileSharedRefs(dir, projectDir, options.sharedRefs ?? "vendor") : { vendoredRefs: [], excludedRefs: [] };
7986
7826
  if (manifestName)
7987
7827
  await retokeniseManifest(path16.join(dir, manifestName));
7988
- await writeFile10(path16.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
7828
+ await writeFile9(path16.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
7989
7829
  return {
7990
7830
  id,
7991
7831
  label: name,
@@ -8003,7 +7843,7 @@ async function retokeniseManifest(manifestPath) {
8003
7843
  return;
8004
7844
  }
8005
7845
  text = text.replace(/^title:\s*.*$/m, 'title: "{{TITLE}}"').replace(/^(authors:[^\n]*\n[ \t]*-[ \t]*).*$/m, '$1"{{AUTHOR}}"');
8006
- await writeFile10(manifestPath, text, "utf8");
7846
+ await writeFile9(manifestPath, text, "utf8");
8007
7847
  }
8008
7848
  async function listCustomTemplates(templatesRoot) {
8009
7849
  let entries;
@@ -8045,7 +7885,7 @@ async function importTemplateFromFolder(options) {
8045
7885
  return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
8046
7886
  }
8047
7887
  // src/lib/snippets.ts
8048
- import { readdir as readdir8, readFile as readFile28, writeFile as writeFile11, mkdir as mkdir9, rm } from "node:fs/promises";
7888
+ import { readdir as readdir8, readFile as readFile28, writeFile as writeFile10, mkdir as mkdir8, rm } from "node:fs/promises";
8049
7889
  import path17 from "node:path";
8050
7890
  var SNIPPETS_DIR = "snippets";
8051
7891
  var LEVEL_RANK = { project: 0, extension: 1, core: 2 };
@@ -8110,8 +7950,8 @@ async function saveSnippet(projectDir, name, body) {
8110
7950
  throw new Error(`Could not derive a filename from "${name}".`);
8111
7951
  const fileName = `${stem}.md`;
8112
7952
  const dir = path17.join(projectDir, SNIPPETS_DIR);
8113
- await mkdir9(dir, { recursive: true });
8114
- await writeFile11(resolveSnippetPath(projectDir, fileName), body, "utf8");
7953
+ await mkdir8(dir, { recursive: true });
7954
+ await writeFile10(resolveSnippetPath(projectDir, fileName), body, "utf8");
8115
7955
  return { name, fileName, variables: extractVariables(body), body, source: { kind: "project" } };
8116
7956
  }
8117
7957
  async function deleteSnippet(projectDir, fileName) {
@@ -8244,7 +8084,7 @@ async function listLocationVariants(projectDir) {
8244
8084
  }
8245
8085
  // src/lib/extension-import.ts
8246
8086
  import { existsSync as existsSync11 } from "node:fs";
8247
- import { cp as cp3, mkdir as mkdir10, mkdtemp, readFile as readFile29, rm as rm2, writeFile as writeFile12 } from "node:fs/promises";
8087
+ import { cp as cp3, mkdir as mkdir9, mkdtemp, readFile as readFile29, rm as rm2, writeFile as writeFile11 } from "node:fs/promises";
8248
8088
  import { tmpdir as tmpdir2 } from "node:os";
8249
8089
  import path18 from "node:path";
8250
8090
  import { unzipSync } from "fflate";
@@ -8365,7 +8205,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
8365
8205
  }
8366
8206
  const id = await uniqueExtensionId(projectDir, meta.name?.trim() || fallbackName);
8367
8207
  const dest = path18.join(projectDir, EXTENSIONS_DIR, id);
8368
- await mkdir10(path18.dirname(dest), { recursive: true });
8208
+ await mkdir9(path18.dirname(dest), { recursive: true });
8369
8209
  await cp3(sourceDir, dest, { recursive: true });
8370
8210
  await completeLandedManifest(dest, id, meta, metaExists);
8371
8211
  const entry = await addExtension(projectDir, `./${EXTENSIONS_DIR}/${id}`);
@@ -8385,7 +8225,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
8385
8225
  } catch {}
8386
8226
  }
8387
8227
  const gutterpress = pkg.gutterpress && typeof pkg.gutterpress === "object" && !Array.isArray(pkg.gutterpress) ? pkg.gutterpress : {};
8388
- await writeFile12(file, JSON.stringify({
8228
+ await writeFile11(file, JSON.stringify({
8389
8229
  ...pkg,
8390
8230
  name: typeof pkg.name === "string" && pkg.name.trim() ? pkg.name : id,
8391
8231
  gutterpress: { ...gutterpress, styles: ["theme.css"] }
@@ -8449,8 +8289,8 @@ async function importExtensionFromZip(projectDir, archive) {
8449
8289
  throw new Error(`The package contains an unsafe path and was rejected: ${name}`);
8450
8290
  }
8451
8291
  relPaths.push(rel);
8452
- await mkdir10(path18.dirname(dest), { recursive: true });
8453
- await writeFile12(dest, data);
8292
+ await mkdir9(path18.dirname(dest), { recursive: true });
8293
+ await writeFile11(dest, data);
8454
8294
  }
8455
8295
  return await finalizeExtensionImport(projectDir, tmp, relPaths, root || "imported-look");
8456
8296
  } finally {
@@ -8465,8 +8305,8 @@ async function importExtensionFromCssText(projectDir, css, name) {
8465
8305
  const displayName = name.trim() || "Imported theme";
8466
8306
  const tmp = await mkdtemp(path18.join(tmpdir2(), "gutterpress-theme-"));
8467
8307
  try {
8468
- await writeFile12(path18.join(tmp, "theme.css"), css, "utf8");
8469
- await writeFile12(path18.join(tmp, EXTENSION_MANIFEST_FILENAME), JSON.stringify({ name: slugify(displayName, "look"), gutterpress: { styles: ["theme.css"] } }, null, 2), "utf8");
8308
+ await writeFile11(path18.join(tmp, "theme.css"), css, "utf8");
8309
+ await writeFile11(path18.join(tmp, EXTENSION_MANIFEST_FILENAME), JSON.stringify({ name: slugify(displayName, "look"), gutterpress: { styles: ["theme.css"] } }, null, 2), "utf8");
8470
8310
  const result = await finalizeExtensionImport(projectDir, tmp, ["theme.css", EXTENSION_MANIFEST_FILENAME], displayName);
8471
8311
  result.warnings.unshift({
8472
8312
  code: "no-theme-json",
@@ -8549,7 +8389,7 @@ async function importExtensionFromUrl(projectDir, url) {
8549
8389
  assertLooksLikeCss(css, trimmed);
8550
8390
  const tmp = await mkdtemp(path18.join(tmpdir2(), "gutterpress-ext-"));
8551
8391
  try {
8552
- await writeFile12(path18.join(tmp, "theme.css"), css, "utf8");
8392
+ await writeFile11(path18.join(tmp, "theme.css"), css, "utf8");
8553
8393
  const gp = meta.gutterpress && typeof meta.gutterpress === "object" && !Array.isArray(meta.gutterpress) ? meta.gutterpress : {};
8554
8394
  const finalMeta = {
8555
8395
  name: typeof meta.name === "string" && meta.name || slugify(baseName, "look"),
@@ -8561,7 +8401,7 @@ async function importExtensionFromUrl(projectDir, url) {
8561
8401
  preview: typeof gp.preview === "string" ? gp.preview : null
8562
8402
  }
8563
8403
  };
8564
- await writeFile12(path18.join(tmp, EXTENSION_MANIFEST_FILENAME), JSON.stringify(finalMeta, null, 2), "utf8");
8404
+ await writeFile11(path18.join(tmp, EXTENSION_MANIFEST_FILENAME), JSON.stringify(finalMeta, null, 2), "utf8");
8565
8405
  return await finalizeExtensionImport(projectDir, tmp, ["theme.css", EXTENSION_MANIFEST_FILENAME], baseName);
8566
8406
  } finally {
8567
8407
  await rm2(tmp, { recursive: true, force: true });
@@ -8682,7 +8522,7 @@ async function searchNpmExtensions(query, options = {}) {
8682
8522
  return { matches, total: pages.reduce((sum, page) => sum + page.total, 0) };
8683
8523
  }
8684
8524
  // src/lib/manifest-config.ts
8685
- import { isSeq as isSeq2, isMap as isMap3, YAMLSeq, Scalar as Scalar2 } from "yaml";
8525
+ import { isSeq as isSeq2, isMap as isMap3, YAMLSeq, Scalar } from "yaml";
8686
8526
  function hasKey(obj, key) {
8687
8527
  return Object.prototype.hasOwnProperty.call(obj, key);
8688
8528
  }
@@ -8717,15 +8557,16 @@ async function setManifestFields(projectDir, updates) {
8717
8557
  }
8718
8558
  if (hasKey(updates, "sourceFiles")) {
8719
8559
  const f = updates.sourceFiles;
8720
- if (f === null || Array.isArray(f) && f.length === 0) {
8721
- doc.deleteIn(["source", "files"]);
8722
- const source = doc.get("source", true);
8723
- if (isMap3(source) && source.items.length === 0) {
8560
+ const source = doc.get("source", true);
8561
+ if (isMap3(source)) {
8562
+ source.delete("files");
8563
+ if (source.items.length === 0)
8724
8564
  doc.delete("source");
8725
- }
8726
- } else {
8727
- doc.setIn(["source", "files"], f);
8728
8565
  }
8566
+ if (f === null || Array.isArray(f) && f.length === 0)
8567
+ doc.delete("chapters");
8568
+ else
8569
+ doc.set("chapters", f);
8729
8570
  }
8730
8571
  await writeManifestDoc(file, doc);
8731
8572
  return readManifestFields(projectDir);
@@ -8745,13 +8586,13 @@ async function readManifestFields(projectDir) {
8745
8586
  if (isSeq2(targetsNode)) {
8746
8587
  out.targets = targetsNode.items.map((i) => scalarString(i)).filter((s) => s !== null);
8747
8588
  }
8748
- const filesNode = doc.getIn(["source", "files"], true);
8589
+ const sourceNode = doc.get("source", true);
8590
+ const oldSpelling = isMap3(sourceNode) && sourceNode.has("files");
8591
+ const filesNode = doc.has("chapters") ? doc.get("chapters", true) : doc.getIn(["source", "files"], true);
8749
8592
  if (isSeq2(filesNode)) {
8750
8593
  out.sourceFiles = filesNode.items.map((i) => scalarString(i) ?? "");
8751
- } else {
8752
- const sourceNode = doc.get("source", true);
8753
- if (isMap3(sourceNode) && sourceNode.has("files"))
8754
- out.sourceFiles = null;
8594
+ } else if (doc.has("chapters") || oldSpelling) {
8595
+ out.sourceFiles = null;
8755
8596
  }
8756
8597
  return out;
8757
8598
  } catch {
@@ -8765,7 +8606,7 @@ async function setActiveStyles(projectDir, paths) {
8765
8606
  } else {
8766
8607
  const seq = new YAMLSeq(doc.schema);
8767
8608
  for (const p of paths)
8768
- seq.add(new Scalar2(p));
8609
+ seq.add(new Scalar(p));
8769
8610
  doc.set("styles", seq);
8770
8611
  }
8771
8612
  await writeManifestDoc(file, doc);
@@ -8773,9 +8614,9 @@ async function setActiveStyles(projectDir, paths) {
8773
8614
  }
8774
8615
  // src/lib/version-changes.ts
8775
8616
  import path19 from "node:path";
8776
- import { mkdir as mkdir11, readFile as readFile30, writeFile as writeFile13 } from "node:fs/promises";
8617
+ import { mkdir as mkdir10, readFile as readFile30, writeFile as writeFile12 } from "node:fs/promises";
8777
8618
  import git3 from "isomorphic-git";
8778
- import postcss6 from "postcss";
8619
+ import postcss5 from "postcss";
8779
8620
 
8780
8621
  // src/lib/remote-auth/transport.ts
8781
8622
  import git2 from "isomorphic-git";
@@ -9228,7 +9069,7 @@ function paragraphs(text) {
9228
9069
  }
9229
9070
  function cssRules(text) {
9230
9071
  try {
9231
- return postcss6.parse(text).nodes.map((node) => node.toString().trim()).filter((rule) => rule.length > 0);
9072
+ return postcss5.parse(text).nodes.map((node) => node.toString().trim()).filter((rule) => rule.length > 0);
9232
9073
  } catch {
9233
9074
  return textLines(text);
9234
9075
  }
@@ -9467,8 +9308,8 @@ async function exportVersionTree(projectDir, id, destDir) {
9467
9308
  if (!content)
9468
9309
  return null;
9469
9310
  const abs = path19.join(destDir, filepath);
9470
- await mkdir11(path19.dirname(abs), { recursive: true });
9471
- await writeFile13(abs, content);
9311
+ await mkdir10(path19.dirname(abs), { recursive: true });
9312
+ await writeFile12(abs, content);
9472
9313
  return null;
9473
9314
  }
9474
9315
  });
@@ -10084,7 +9925,7 @@ import git5 from "isomorphic-git";
10084
9925
  import { randomUUID as randomUUID2 } from "node:crypto";
10085
9926
 
10086
9927
  // src/lib/remote-auth/converge-merge.ts
10087
- import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile14 } from "node:fs/promises";
9928
+ import { mkdir as mkdir11, unlink as unlink2, writeFile as writeFile13 } from "node:fs/promises";
10088
9929
  import path20 from "node:path";
10089
9930
  import git4 from "isomorphic-git";
10090
9931
  import diff3Merge from "diff3";
@@ -10134,8 +9975,8 @@ async function readBlob(dir, cache2, oid, filepath) {
10134
9975
  }
10135
9976
  async function writeTreeFile(dir, filepath, content) {
10136
9977
  const abs = path20.join(dir, filepath);
10137
- await mkdir12(path20.dirname(abs), { recursive: true });
10138
- await writeFile14(abs, content);
9978
+ await mkdir11(path20.dirname(abs), { recursive: true });
9979
+ await writeFile13(abs, content);
10139
9980
  }
10140
9981
  async function bothAddedPaths(dir, cache2, ourTip, theirTip, allowUnrelated) {
10141
9982
  const bases = await git4.findMergeBase({ fs: gitFs, dir, cache: cache2, oids: [ourTip, theirTip] });
@@ -10791,7 +10632,7 @@ async function setUpOnlineBackup(options) {
10791
10632
  }
10792
10633
  }
10793
10634
  // src/lib/remote-auth/clone.ts
10794
- import { mkdir as mkdir13, readdir as readdir9, rm as rm3 } from "node:fs/promises";
10635
+ import { mkdir as mkdir12, readdir as readdir9, rm as rm3 } from "node:fs/promises";
10795
10636
  import path21 from "node:path";
10796
10637
  import git7 from "isomorphic-git";
10797
10638
  function sanitizeCloneFolderName(name) {
@@ -10850,7 +10691,7 @@ async function cloneRepository(options) {
10850
10691
  return withRepoLock(dir, async () => {
10851
10692
  await assertCloneTarget(dir);
10852
10693
  const dirExistedBefore = gitFs.existsSync(dir);
10853
- await mkdir13(dir, { recursive: true });
10694
+ await mkdir12(dir, { recursive: true });
10854
10695
  try {
10855
10696
  await git7.clone({
10856
10697
  fs: gitFs,
@@ -10902,7 +10743,7 @@ var FAILURE_MESSAGES = {
10902
10743
  auth: "The Git server didn't accept the saved connection. Reconnect to this server with a current access token and try again.",
10903
10744
  "not-found": "The server answered, but no repository was found at that address. It may have been moved, renamed, or you may no longer have access.",
10904
10745
  unreachable: "Couldn't reach the Git server. Check your internet connection (and VPN, if this is a private server), then try again.",
10905
- "ssh-unsupported": "This book's online address uses SSH (git@…), which gutterpress can't check or sync with. Everything on this computer still works — sync with your usual Git tool.",
10746
+ "ssh-unsupported": "This book's online address uses SSH (git@…), which Gutterpress can't check or sync with. Everything on this computer still works — sync with your usual Git tool.",
10906
10747
  "insecure-transport": "This address isn't secure, so the saved connection wasn't sent — connections are never sent over an insecure address. Switch the address to a secure one (starting with https), or to a local loopback address for a server on this computer, to use a saved connection.",
10907
10748
  tls: "The server's security certificate couldn't be verified. If this is a private server with its own certificate, ask its administrator about trusting it (NODE_EXTRA_CA_CERTS).",
10908
10749
  unknown: "The connection test failed unexpectedly. The server may not be a Git server, or it may be temporarily unavailable."
@@ -11140,11 +10981,11 @@ async function diagnoseProjectRemote(projectDir, options = {}) {
11140
10981
  }
11141
10982
  // src/lib/remote-auth/repair.ts
11142
10983
  import git9 from "isomorphic-git";
11143
- import { cp as cp4, mkdir as mkdir14, rename as rename3, rm as rm4 } from "node:fs/promises";
10984
+ import { cp as cp4, mkdir as mkdir13, rename as rename3, rm as rm4 } from "node:fs/promises";
11144
10985
  import path22 from "node:path";
11145
10986
  var REPAIR_SNAPSHOT_MESSAGE = "Repaired online backup";
11146
10987
  async function moveDir(from, to) {
11147
- await mkdir14(path22.dirname(to), { recursive: true });
10988
+ await mkdir13(path22.dirname(to), { recursive: true });
11148
10989
  try {
11149
10990
  await rename3(from, to);
11150
10991
  } catch (e) {
@@ -11221,7 +11062,7 @@ async function repairOnlineBackup(options) {
11221
11062
  }
11222
11063
  // src/lib/remote-auth/scorched-earth.ts
11223
11064
  import git10 from "isomorphic-git";
11224
- import { cp as cp5, mkdir as mkdir15, readdir as readdir10, rm as rm5 } from "node:fs/promises";
11065
+ import { cp as cp5, mkdir as mkdir14, readdir as readdir10, rm as rm5 } from "node:fs/promises";
11225
11066
  import path23 from "node:path";
11226
11067
  async function emptyDir(dir) {
11227
11068
  for (const entry of await readdir10(dir)) {
@@ -11251,7 +11092,7 @@ async function scorchedEarth(options) {
11251
11092
  branch2 = undefined;
11252
11093
  }
11253
11094
  logger.info("backup", "copying folder", { to: backupDir });
11254
- await mkdir15(backupDir, { recursive: true });
11095
+ await mkdir14(backupDir, { recursive: true });
11255
11096
  await cp5(dir, backupDir, { recursive: true });
11256
11097
  logger.info("delete", "emptying folder", { dir });
11257
11098
  await emptyDir(dir);
@@ -11437,11 +11278,11 @@ ${tail}` : ""}`);
11437
11278
  };
11438
11279
 
11439
11280
  // src/lib/publish/providers/guided.ts
11440
- import { copyFile as copyFile2, mkdir as mkdir16, writeFile as writeFile15 } from "node:fs/promises";
11281
+ import { copyFile as copyFile2, mkdir as mkdir15, writeFile as writeFile14 } from "node:fs/promises";
11441
11282
  import path24 from "node:path";
11442
11283
  async function stageGuidedPackage(req, providerId, listingLines) {
11443
11284
  const packageDir = path24.join(path24.dirname(req.artifact.path), "publish", providerId);
11444
- await mkdir16(packageDir, { recursive: true });
11285
+ await mkdir15(packageDir, { recursive: true });
11445
11286
  await copyFile2(req.artifact.path, path24.join(packageDir, path24.basename(req.artifact.path)));
11446
11287
  const { title, authors } = req.project;
11447
11288
  const sheet = [
@@ -11455,7 +11296,7 @@ async function stageGuidedPackage(req, providerId, listingLines) {
11455
11296
  ""
11456
11297
  ].join(`
11457
11298
  `);
11458
- await writeFile15(path24.join(packageDir, "LISTING.md"), sheet, "utf8");
11299
+ await writeFile14(path24.join(packageDir, "LISTING.md"), sheet, "utf8");
11459
11300
  return packageDir;
11460
11301
  }
11461
11302
 
@@ -11518,7 +11359,7 @@ var drivethrurpgProvider = {
11518
11359
  };
11519
11360
 
11520
11361
  // src/lib/publish/providers/gdrive.ts
11521
- import { mkdtemp as mkdtemp2, readFile as readFile31, readdir as readdir11, rm as rm6, stat as stat7, writeFile as writeFile16 } from "node:fs/promises";
11362
+ import { mkdtemp as mkdtemp2, readFile as readFile31, readdir as readdir11, rm as rm6, stat as stat7, writeFile as writeFile15 } from "node:fs/promises";
11522
11363
  import { tmpdir as tmpdir3 } from "node:os";
11523
11364
  import path25 from "node:path";
11524
11365
  import { Zip, ZipDeflate } from "fflate";
@@ -11926,7 +11767,7 @@ async function collectZipEntries(root, dir, out, onWarn) {
11926
11767
  }
11927
11768
  }
11928
11769
  }
11929
- async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl = writeFile16) {
11770
+ async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl = writeFile15) {
11930
11771
  const entries = {};
11931
11772
  await collectZipEntries(exportDir, exportDir, entries, onWarn);
11932
11773
  const fileName = `${bookSlug(title)}-website.zip`;
@@ -12030,7 +11871,7 @@ var gdriveProvider = {
12030
11871
  };
12031
11872
 
12032
11873
  // src/lib/publish/butler.ts
12033
- import { chmod as chmod2, mkdir as mkdir17, stat as stat8, writeFile as writeFile17 } from "node:fs/promises";
11874
+ import { chmod as chmod2, mkdir as mkdir16, stat as stat8, writeFile as writeFile16 } from "node:fs/promises";
12034
11875
  import path26 from "node:path";
12035
11876
  import { unzipSync as unzipSync2 } from "fflate";
12036
11877
  var BUTLER_DOWNLOAD_TIMEOUT_MS = 300000;
@@ -12091,13 +11932,13 @@ async function ensureButler(deps) {
12091
11932
  return new Uint8Array(await response.arrayBuffer());
12092
11933
  });
12093
11934
  const files = unzipSync2(archive);
12094
- await mkdir17(cacheDir, { recursive: true });
11935
+ await mkdir16(cacheDir, { recursive: true });
12095
11936
  let foundBinary = false;
12096
11937
  for (const [name, data] of Object.entries(files)) {
12097
11938
  if (name.endsWith("/"))
12098
11939
  continue;
12099
11940
  const target = path26.join(cacheDir, path26.basename(name));
12100
- await writeFile17(target, data);
11941
+ await writeFile16(target, data);
12101
11942
  if (path26.basename(name) === butlerBinaryName()) {
12102
11943
  await chmod2(target, 493).catch(() => {});
12103
11944
  foundBinary = true;
@@ -12294,7 +12135,7 @@ var kdpProvider = {
12294
12135
  };
12295
12136
 
12296
12137
  // src/lib/publish/providers/local-folder.ts
12297
- import { cp as cp6, mkdir as mkdir18, stat as stat9 } from "node:fs/promises";
12138
+ import { cp as cp6, mkdir as mkdir17, stat as stat9 } from "node:fs/promises";
12298
12139
  import path27 from "node:path";
12299
12140
  var LOCAL_PUBLISH_DEFAULT_DIR = "dist";
12300
12141
  var info8 = {
@@ -12332,7 +12173,7 @@ var localFolderProvider = {
12332
12173
  },
12333
12174
  async upload(req) {
12334
12175
  const dir = localPublishDir(req);
12335
- await mkdir18(dir, { recursive: true });
12176
+ await mkdir17(dir, { recursive: true });
12336
12177
  const dest = req.artifact.format === "html" ? dir : path27.join(dir, path27.basename(req.artifact.path));
12337
12178
  if (path27.resolve(dest) !== path27.resolve(req.artifact.path)) {
12338
12179
  await cp6(req.artifact.path, dest, { recursive: true });
@@ -12632,7 +12473,7 @@ async function resolvePublishProvider(id, book) {
12632
12473
  const ext = await bookExtensions(book);
12633
12474
  const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
12634
12475
  if (npmEntry) {
12635
- const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-s3x1618g.js");
12476
+ const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-dymzcasz.js");
12636
12477
  await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
12637
12478
  }
12638
12479
  const publishers = await discover(ext);
@@ -12670,7 +12511,7 @@ function infoFor(p) {
12670
12511
  }
12671
12512
  function pluginProvider(p) {
12672
12513
  let loading;
12673
- const load = () => loading ??= import("./plugins-2cek4mz1.js").then((m) => m.loadPublishFunction(p));
12514
+ const load = () => loading ??= import("./plugins-fec8t2bh.js").then((m) => m.loadPublishFunction(p));
12674
12515
  return {
12675
12516
  info: infoFor(p),
12676
12517
  async authenticate() {
@@ -12941,7 +12782,7 @@ async function disconnectPublishCredential(key, deps, options = {}) {
12941
12782
  }
12942
12783
  }
12943
12784
  // src/lib/publish/selections.ts
12944
- import { chmod as chmod3, mkdir as mkdir19, readFile as readFile32, writeFile as writeFile18 } from "node:fs/promises";
12785
+ import { chmod as chmod3, mkdir as mkdir18, readFile as readFile32, writeFile as writeFile17 } from "node:fs/promises";
12945
12786
  import path30 from "node:path";
12946
12787
  class PublishSelectionsStore {
12947
12788
  filePath;
@@ -12964,8 +12805,8 @@ class PublishSelectionsStore {
12964
12805
  return { version: 1, global: {}, projects: {} };
12965
12806
  }
12966
12807
  async write(data) {
12967
- await mkdir19(path30.dirname(this.filePath), { recursive: true });
12968
- await writeFile18(this.filePath, JSON.stringify(data, null, 2), {
12808
+ await mkdir18(path30.dirname(this.filePath), { recursive: true });
12809
+ await writeFile17(this.filePath, JSON.stringify(data, null, 2), {
12969
12810
  encoding: "utf8",
12970
12811
  mode: 384
12971
12812
  });