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
@@ -1,5 +1,4 @@
1
1
  import {
2
- BUNDLED_EXTENSIONS,
3
2
  EXTENSION_MANIFEST_FILENAME,
4
3
  TWO_VERSIONS_OPEN,
5
4
  bundledExtension,
@@ -7,18 +6,22 @@ import {
7
6
  extensionStyleList,
8
7
  isPathSpecifier,
9
8
  loadPlugin,
10
- log,
11
9
  parseExtensionSpecifier,
12
10
  pinnedNpmSpecifier,
13
11
  prettify,
14
12
  readExtensionMeta,
15
- resolveExtension,
16
13
  slugify
17
- } from "./index-2bjcghze.js";
14
+ } from "./cli-rqh295j9.js";
15
+ import {
16
+ log
17
+ } from "./cli-pspjxnc8.js";
18
+ import {
19
+ UsageError
20
+ } from "./cli-d8xjns66.js";
18
21
  import {
19
22
  BuildError,
20
23
  EXIT_CODES
21
- } from "./index-46ycxe6r.js";
24
+ } from "./cli-46ycxe6r.js";
22
25
  import {
23
26
  EXTENSIONS_DIR,
24
27
  VENDORED_NPM_DIR,
@@ -31,10 +34,10 @@ import {
31
34
  vendoredNpmPluginRoot,
32
35
  vendoredNpmPluginRoots,
33
36
  windowsPathKey
34
- } from "./index-5x42amh8.js";
37
+ } from "./cli-y28j019d.js";
35
38
  import {
36
39
  __require
37
- } from "./index-37x76zdn.js";
40
+ } from "./cli-37x76zdn.js";
38
41
 
39
42
  // src/lib/extension-restore.ts
40
43
  import path5 from "node:path";
@@ -91,34 +94,85 @@ import { existsSync, statSync } from "node:fs";
91
94
  import { resolve, dirname } from "node:path";
92
95
  import { parse as parseYaml, YAMLParseError } from "yaml";
93
96
 
94
- // src/constants.ts
95
- var NETWORK = {
96
- DEFAULT_PORT: 3579
97
- };
98
- var DEBOUNCE = {
99
- FILE_WATCH: 100
100
- };
101
-
102
- // src/lib/cli-args.ts
103
- class UsageError extends Error {
104
- exitCode;
105
- constructor(message, exitCode = EXIT_CODES.USAGE) {
106
- super(message);
107
- this.name = "UsageError";
108
- this.exitCode = exitCode;
109
- }
97
+ // src/engine/shared/print-area.ts
98
+ function isPositivePoints(value) {
99
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
100
+ }
101
+ function isNonNegativePoints(value) {
102
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
103
+ }
104
+ var round3 = (n) => Math.round(n * 1000) / 1000;
105
+ function cssLength(pt) {
106
+ const inches = round3(pt / 72);
107
+ return Math.abs(inches * 72 - pt) < 0.000001 ? `${inches}in` : `${round3(pt)}pt`;
108
+ }
109
+ function formatPageSize(page) {
110
+ return `${cssLength(page.width)} ${cssLength(page.height)}`;
111
+ }
112
+ function printAreaMeta(page) {
113
+ const area = JSON.stringify({
114
+ width: page.width,
115
+ height: page.height,
116
+ bleed: page.bleed ?? 0,
117
+ safe: page.safe ?? 0,
118
+ artSafe: page.artSafe,
119
+ gutter: page.gutter,
120
+ preset: page.preset,
121
+ spec: page.spec
122
+ });
123
+ return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
124
+ }
125
+ function pageBoxes(area, side, scale = 1) {
126
+ const w = area.width * scale;
127
+ const h = area.height * scale;
128
+ const b = Math.max(0, area.bleed) * scale;
129
+ const s = Math.max(0, area.safe) * scale;
130
+ const a = Math.max(0, area.artSafe) * scale;
131
+ const g = Math.max(0, area.gutter) * scale;
132
+ const [textLeft, textRight] = side === "verso" ? [s, g] : [g, s];
133
+ const [artLeft, artRight] = side === "verso" ? [a, g] : [g, a];
134
+ const trim = { x: side === "verso" ? b : 0, y: b, w: Math.max(0, w - b), h: Math.max(0, h - 2 * b) };
135
+ return {
136
+ sheet: { x: 0, y: 0, w, h },
137
+ trim,
138
+ safe: {
139
+ x: trim.x + textLeft,
140
+ y: trim.y + s,
141
+ w: Math.max(0, trim.w - textLeft - textRight),
142
+ h: Math.max(0, trim.h - 2 * s)
143
+ },
144
+ safeArt: {
145
+ x: trim.x + artLeft,
146
+ y: trim.y + a,
147
+ w: Math.max(0, trim.w - artLeft - artRight),
148
+ h: Math.max(0, trim.h - 2 * a)
149
+ },
150
+ bleed: b
151
+ };
110
152
  }
111
- function resolvePort(raw) {
112
- if (raw === undefined)
113
- return NETWORK.DEFAULT_PORT;
114
- if (raw === "" || typeof raw === "string" && raw.trim() === "") {
115
- throw new UsageError("--port requires a value.");
116
- }
117
- const n = typeof raw === "number" || typeof raw === "string" ? Number(raw) : NaN;
118
- if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0 || n > 65535) {
119
- throw new UsageError(`Invalid --port value: "${raw}". Expected an integer from 0 to 65535 (0 = OS-assigned).`);
153
+ var SEVERITY = { inside: 0, unsafe: 1, bleed: 2, "off-page": 3 };
154
+ function judgeRect(r, boxes, rule, tol = 0) {
155
+ const { sheet, trim } = boxes;
156
+ const safe = rule === "art" ? boxes.safeArt : boxes.safe;
157
+ const sides = [
158
+ ["left", -r.x, -safe.x, -trim.x, -sheet.x],
159
+ ["right", r.x + r.w, safe.x + safe.w, trim.x + trim.w, sheet.x + sheet.w],
160
+ ["top", -r.y, -safe.y, -trim.y, -sheet.y],
161
+ ["bottom", r.y + r.h, safe.y + safe.h, trim.y + trim.h, sheet.y + sheet.h]
162
+ ];
163
+ let worst = { kind: "inside", gap: 0, need: 0, edge: null };
164
+ let beyond = 0;
165
+ for (const [edge, ink, limit, trimEdge, sheetEdge] of sides) {
166
+ const past = ink - limit;
167
+ if (past <= tol || rule === "art" && ink >= sheetEdge - tol)
168
+ continue;
169
+ const kind = ink > sheetEdge + tol ? "off-page" : ink > trimEdge + tol ? "bleed" : "unsafe";
170
+ if (SEVERITY[kind] > SEVERITY[worst.kind] || kind === worst.kind && past > beyond) {
171
+ worst = { kind, gap: trimEdge - ink, need: trimEdge - limit, edge };
172
+ beyond = past;
173
+ }
120
174
  }
121
- return n;
175
+ return worst;
122
176
  }
123
177
 
124
178
  // src/lib/presets.ts
@@ -131,9 +185,7 @@ var DTRPG_PRESET = {
131
185
  },
132
186
  defaultTargets: ["dtrpg"],
133
187
  extensions: [],
134
- source: {
135
- files: null
136
- },
188
+ chapters: null,
137
189
  print: { signature: 4, reserveLastPage: true, lastPage: undefined },
138
190
  pdfx: {
139
191
  flavor: "x1a",
@@ -151,59 +203,30 @@ var DTRPG_PRESET = {
151
203
  bleed: 9,
152
204
  safe: 36,
153
205
  artSafe: 18,
154
- gutter: 36
206
+ gutter: 36,
207
+ margin: undefined
155
208
  },
156
209
  ink: {
157
210
  maxTac: 240,
158
211
  tacTolerance: 0.5
159
212
  },
160
- lint: {
161
- enabled: true,
162
- configPath: null,
163
- rules: {}
213
+ checks: {
214
+ "pdf.structure.qpdf": "error",
215
+ "pdf.print.pdfx-markers": "error",
216
+ "pdf.print.pdfx-metadata": "error",
217
+ "pdf.print.embedded-fonts": "error",
218
+ "pdf.print.safe-margins": "error",
219
+ "pdf.print.bleed": "off",
220
+ "pdf.nav.bookmarks": "off",
221
+ "pdf.nav.toc-links": "off",
222
+ "asset.font.license": "off"
164
223
  },
165
224
  validate: {
166
- enabled: true,
167
- checks: {
168
- "pdf.structure.qpdf": {
169
- enabled: true,
170
- severity: "error"
171
- },
172
- "pdf.print.pdfx-markers": {
173
- enabled: true,
174
- severity: "error"
175
- },
176
- "pdf.print.pdfx-metadata": {
177
- enabled: true,
178
- severity: "error"
179
- },
180
- "pdf.print.embedded-fonts": {
181
- enabled: true,
182
- severity: "error"
183
- },
184
- "pdf.print.safe-margins": {
185
- enabled: true,
186
- severity: "error"
187
- }
188
- },
189
- source: {
190
- markdownlint: null,
191
- htmlhint: null,
192
- stylelint: null,
193
- cssOwnership: null
194
- },
195
225
  assets: {
196
- maxImageSize: 1e7,
197
- allowAlpha: false,
198
- approvedFontFiles: [],
199
- requireFontLicense: false
226
+ maxImageSize: 1e7
200
227
  },
201
228
  pdf: {
202
- requireBookmarks: false,
203
- requireTocLinks: false,
204
229
  minImageResolution: 300,
205
- forbidTransparency: true,
206
- requireBleed: false,
207
230
  bleedSize: 9
208
231
  },
209
232
  heuristics: {
@@ -217,9 +240,7 @@ var BOOK_PRESET = {
217
240
  id: "book",
218
241
  defaultTargets: [],
219
242
  extensions: [],
220
- source: {
221
- files: null
222
- },
243
+ chapters: null,
223
244
  print: { signature: 1, reserveLastPage: false, lastPage: undefined },
224
245
  pdfx: {
225
246
  flavor: "x1a",
@@ -237,53 +258,32 @@ var BOOK_PRESET = {
237
258
  bleed: undefined,
238
259
  safe: 18,
239
260
  artSafe: undefined,
240
- gutter: undefined
261
+ gutter: undefined,
262
+ margin: undefined
241
263
  },
242
264
  ink: {
243
265
  maxTac: 400,
244
266
  tacTolerance: 0.5
245
267
  },
246
- lint: {
247
- enabled: true,
248
- configPath: null,
249
- rules: {}
268
+ checks: {
269
+ "pdf.structure.qpdf": "error",
270
+ "pdf.print.embedded-fonts": "error",
271
+ "pdf.print.safe-margins": "warning",
272
+ "pdf.print.pdfx-markers": "off",
273
+ "pdf.print.pdfx-metadata": "off",
274
+ "pdf.print.transparency": "off",
275
+ "asset.image.alpha-channel": "off",
276
+ "pdf.print.bleed": "off",
277
+ "pdf.nav.bookmarks": "off",
278
+ "pdf.nav.toc-links": "off",
279
+ "asset.font.license": "off"
250
280
  },
251
281
  validate: {
252
- enabled: true,
253
- checks: {
254
- "pdf.structure.qpdf": {
255
- enabled: true,
256
- severity: "error"
257
- },
258
- "pdf.print.embedded-fonts": {
259
- enabled: true,
260
- severity: "error"
261
- },
262
- "pdf.print.safe-margins": {
263
- enabled: true,
264
- severity: "warning"
265
- },
266
- "pdf.print.pdfx-markers": false,
267
- "pdf.print.pdfx-metadata": false
268
- },
269
- source: {
270
- markdownlint: null,
271
- htmlhint: null,
272
- stylelint: null,
273
- cssOwnership: null
274
- },
275
282
  assets: {
276
- maxImageSize: 1e7,
277
- allowAlpha: true,
278
- approvedFontFiles: [],
279
- requireFontLicense: false
283
+ maxImageSize: 1e7
280
284
  },
281
285
  pdf: {
282
- requireBookmarks: false,
283
- requireTocLinks: false,
284
286
  minImageResolution: 300,
285
- forbidTransparency: false,
286
- requireBleed: false,
287
287
  bleedSize: 9
288
288
  },
289
289
  heuristics: {
@@ -336,16 +336,16 @@ var DTRPG_TARGET = {
336
336
  maxTac: DTRPG_PRESET.ink.maxTac,
337
337
  tacTolerance: DTRPG_PRESET.ink.tacTolerance
338
338
  },
339
+ checks: {
340
+ "pdf.structure.qpdf": "error",
341
+ "pdf.print.pdfx-markers": "error",
342
+ "pdf.print.pdfx-metadata": "error",
343
+ "pdf.print.embedded-fonts": "error",
344
+ "pdf.print.safe-margins": "error",
345
+ "pdf.print.transparency": "on"
346
+ },
339
347
  validate: {
340
- checks: {
341
- "pdf.structure.qpdf": { enabled: true, severity: "error" },
342
- "pdf.print.pdfx-markers": { enabled: true, severity: "error" },
343
- "pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
344
- "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
345
- "pdf.print.safe-margins": { enabled: true, severity: "error" }
346
- },
347
348
  pdf: {
348
- forbidTransparency: DTRPG_PRESET.validate.pdf.forbidTransparency,
349
349
  minImageResolution: DTRPG_PRESET.validate.pdf.minImageResolution
350
350
  }
351
351
  }
@@ -363,22 +363,22 @@ var ITCH_TARGET = {
363
363
  id: "itch",
364
364
  label: "itch.io",
365
365
  overlay: {
366
+ checks: {
367
+ "pdf.structure.qpdf": "error",
368
+ "pdf.print.embedded-fonts": "error",
369
+ "pdf.print.pdfx-markers": "off",
370
+ "pdf.print.pdfx-metadata": "off",
371
+ "pdf.print.ink-coverage": "off",
372
+ "pdf.print.safe-margins": "off",
373
+ "pdf.print.transparency": "off",
374
+ "pdf.print.bleed": "off",
375
+ "asset.image.alpha-channel": "off"
376
+ },
366
377
  validate: {
367
- checks: {
368
- "pdf.structure.qpdf": { enabled: true, severity: "error" },
369
- "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
370
- "pdf.print.pdfx-markers": false,
371
- "pdf.print.pdfx-metadata": false,
372
- "pdf.print.ink-coverage": false,
373
- "pdf.print.safe-margins": false
374
- },
375
378
  assets: {
376
- allowAlpha: true,
377
379
  maxImageSize: 0
378
380
  },
379
381
  pdf: {
380
- forbidTransparency: false,
381
- requireBleed: false,
382
382
  minImageResolution: 0
383
383
  }
384
384
  }
@@ -417,9 +417,9 @@ function overlayPreset(preset, target) {
417
417
  ...preset,
418
418
  pdfx: { ...preset.pdfx, ...o.pdfx },
419
419
  ink: { ...preset.ink, ...o.ink },
420
+ checks: { ...preset.checks, ...o.checks },
420
421
  validate: {
421
422
  ...preset.validate,
422
- checks: { ...preset.validate.checks, ...o.validate?.checks },
423
423
  assets: { ...preset.validate.assets, ...o.validate?.assets },
424
424
  pdf: { ...preset.validate.pdf, ...o.validate?.pdf }
425
425
  }
@@ -434,27 +434,124 @@ var DEFAULT_STRICT_PDF_CHECKS = [
434
434
  function applyDefaultPdfStrictChecks(config) {
435
435
  const next = structuredClone(config);
436
436
  for (const checkId of DEFAULT_STRICT_PDF_CHECKS) {
437
- if (next.validate.checks[checkId] === undefined) {
438
- next.validate.checks[checkId] = { enabled: true, severity: "error" };
439
- }
437
+ if (next.checks[checkId] === undefined)
438
+ next.checks[checkId] = "error";
440
439
  }
441
440
  return next;
442
441
  }
443
442
 
443
+ // src/lib/manifest-old-spellings.ts
444
+ var SOURCE_TOOL_CHECKS = {
445
+ markdownlint: "source.markdownlint",
446
+ htmlhint: "source.htmlhint",
447
+ stylelint: "source.stylelint",
448
+ cssOwnership: "source.css-ownership"
449
+ };
450
+ var FLAG_CHECKS = {
451
+ "assets.allowAlpha": { check: "asset.image.alpha-channel", trueMeansOn: false },
452
+ "assets.requireFontLicense": { check: "asset.font.license", trueMeansOn: true },
453
+ "pdf.requireBookmarks": { check: "pdf.nav.bookmarks", trueMeansOn: true },
454
+ "pdf.requireTocLinks": { check: "pdf.nav.toc-links", trueMeansOn: true },
455
+ "pdf.requireBleed": { check: "pdf.print.bleed", trueMeansOn: true },
456
+ "pdf.forbidTransparency": { check: "pdf.print.transparency", trueMeansOn: true }
457
+ };
458
+ function isObject(v) {
459
+ return typeof v === "object" && v !== null && !Array.isArray(v);
460
+ }
461
+ function readOldSpellings(raw) {
462
+ const { source, lint, validate, ink, ...manifest } = raw;
463
+ const tolerance = isObject(raw.page) && "tolerance" in raw.page;
464
+ if (source === undefined && lint === undefined && validate === undefined && ink === undefined && !tolerance) {
465
+ return raw;
466
+ }
467
+ if (isObject(source) && source.assets !== undefined) {
468
+ throw new UsageError("Manifest field `source.assets` is no longer supported — remove it.\n" + " Assets are discovered from what your book actually references, so no list is needed.");
469
+ }
470
+ const renamed = [];
471
+ const dropped = [];
472
+ const checks = {};
473
+ if (isObject(source) && "files" in source) {
474
+ renamed.push("source.files -> chapters");
475
+ if (manifest.chapters === undefined)
476
+ manifest.chapters = source.files ?? null;
477
+ }
478
+ if (isObject(validate)) {
479
+ for (const [id, entry] of Object.entries(validate.checks ?? {})) {
480
+ if (entry === false || isObject(entry) && entry.enabled === false)
481
+ checks[id] = "off";
482
+ else if (isObject(entry) && typeof entry.severity === "string")
483
+ checks[id] = entry.severity;
484
+ else
485
+ checks[id] = "on";
486
+ }
487
+ if (validate.checks !== undefined)
488
+ renamed.push("validate.checks -> checks");
489
+ for (const [tool, value] of Object.entries(validate.source ?? {})) {
490
+ const id = SOURCE_TOOL_CHECKS[tool];
491
+ if (id && value === false) {
492
+ checks[id] = "off";
493
+ renamed.push(`validate.source.${tool}: false -> checks: { ${id}: off }`);
494
+ } else if (id && tool !== "stylelint" && typeof value === "string") {
495
+ dropped.push(`validate.source.${tool} (the config file is found by its usual name in the book's folder or a folder above it)`);
496
+ } else {
497
+ dropped.push(`validate.source.${tool}`);
498
+ }
499
+ }
500
+ for (const block of ["assets", "pdf", "heuristics"]) {
501
+ for (const [key, value] of Object.entries(validate[block] ?? {})) {
502
+ const flag = FLAG_CHECKS[`${block}.${key}`];
503
+ if (flag && typeof value === "boolean") {
504
+ const level = value === flag.trueMeansOn ? "on" : "off";
505
+ checks[flag.check] = level;
506
+ renamed.push(`validate.${block}.${key}: ${value} -> checks: { ${flag.check}: ${level} }`);
507
+ } else {
508
+ dropped.push(`validate.${block}.${key} (the preset and the publish target set it)`);
509
+ }
510
+ }
511
+ }
512
+ if (validate.enabled !== undefined) {
513
+ dropped.push("validate.enabled (`gutterpress build --skip-pre-validate --skip-post-validate` skips validation for one build)");
514
+ }
515
+ }
516
+ if (isObject(lint)) {
517
+ for (const [id, level] of Object.entries(lint.rules ?? {}))
518
+ checks[id] = level;
519
+ if (lint.rules !== undefined)
520
+ renamed.push("lint.rules -> checks");
521
+ if (lint.enabled === false) {
522
+ checks["source.stylelint"] = "off";
523
+ renamed.push("lint.enabled: false -> checks: { source.stylelint: off }");
524
+ } else if (lint.enabled !== undefined) {
525
+ dropped.push("lint.enabled");
526
+ }
527
+ if (lint.configPath !== undefined)
528
+ dropped.push("lint.configPath");
529
+ }
530
+ if (ink !== undefined)
531
+ dropped.push("ink (the preset and the publish target set it)");
532
+ if (tolerance) {
533
+ const { tolerance: _, ...page } = raw.page;
534
+ manifest.page = page;
535
+ dropped.push("page.tolerance (the preset sets it)");
536
+ }
537
+ if (Object.keys(checks).length > 0)
538
+ manifest.checks = { ...checks, ...manifest.checks };
539
+ const lines = [
540
+ ...renamed.map((r) => ` ${r}`),
541
+ ...dropped.map((d) => ` ${d} — no longer a setting, ignored`)
542
+ ];
543
+ if (lines.length > 0) {
544
+ warnOnce(`old-manifest-spellings:${lines.join("|")}`, "[gutterpress] manifest.yaml uses spellings 0.11.23 replaced. They still work in this " + `release; rewrite them before the next one:
545
+ ` + lines.join(`
546
+ `));
547
+ }
548
+ return manifest;
549
+ }
550
+
444
551
  // src/lib/manifest.ts
445
552
  var MANIFEST_FILENAMES = [
446
553
  "manifest.yaml"
447
554
  ];
448
- function hasProjectManifest(dir) {
449
- return MANIFEST_FILENAMES.some((name) => {
450
- const candidate = resolve(dir, name);
451
- try {
452
- return statSync(candidate).isFile();
453
- } catch {
454
- return false;
455
- }
456
- });
457
- }
458
555
  async function loadManifest(pathOrDir) {
459
556
  const { manifest } = await loadManifestWithPath(pathOrDir);
460
557
  return manifest;
@@ -468,8 +565,8 @@ async function loadManifestWithPath(pathOrDir, opts) {
468
565
  throw new UsageError(`"${p}" has two versions of a setting — yours and the online version — left by a sync. Open it, keep the one you want and delete the marker lines.`);
469
566
  }
470
567
  try {
471
- const manifest = parseYaml(raw) ?? {};
472
- return { manifest, manifestDir: dirname(p), manifestPath: p };
568
+ const parsed = parseYaml(raw) ?? {};
569
+ return { manifest: readOldSpellings(parsed), manifestDir: dirname(p), manifestPath: p };
473
570
  } catch (error) {
474
571
  if (!(error instanceof YAMLParseError))
475
572
  throw error;
@@ -605,32 +702,37 @@ function resolveCustomPage(c, m) {
605
702
  return {
606
703
  width,
607
704
  height,
608
- tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5,
705
+ tolerance: 0.5,
609
706
  bleed: c.page?.bleed ?? m.page?.bleed,
610
707
  safe: c.page?.safe ?? m.page?.safe,
611
708
  artSafe: c.page?.artSafe ?? m.page?.artSafe,
612
- gutter: c.page?.gutter ?? m.page?.gutter
709
+ gutter: c.page?.gutter ?? m.page?.gutter,
710
+ margin: c.page?.margin ?? m.page?.margin
613
711
  };
614
712
  }
713
+ var MARGIN_EDGES = ["top", "bottom", "inside", "outside"];
714
+ function validatePageMargin(margin, size) {
715
+ const edges = isPlainObject(margin) && Object.keys(margin).every((k) => MARGIN_EDGES.includes(k)) && MARGIN_EDGES.every((e) => isNonNegativePoints(margin[e]));
716
+ if (!edges) {
717
+ throw new UsageError("Manifest field `page.margin` must set all four edges in points (72pt = 1in), for example\n" + ` page:
718
+ margin:
719
+ top: 54
720
+ bottom: 54
721
+ inside: 54
722
+ outside: 54
723
+ ` + `got ${JSON.stringify(margin)}.`);
724
+ }
725
+ const m = margin;
726
+ if (m.inside + m.outside >= size.width || m.top + m.bottom >= size.height) {
727
+ throw new UsageError(`Manifest field \`page.margin\` leaves no room for text on a ${size.width} x ${size.height}pt page ` + `(inside ${m.inside} + outside ${m.outside} across, top ${m.top} + bottom ${m.bottom} down).`);
728
+ }
729
+ return m;
730
+ }
615
731
  function resolveWithPreset(cliOverrides, manifest, preset) {
616
732
  const m = manifest;
617
733
  const c = cliOverrides;
618
- const legacy = m;
619
- if (legacy.output !== undefined || legacy.source?.assets !== undefined) {
620
- const stale = [
621
- legacy.output !== undefined ? "`output`" : null,
622
- legacy.source?.assets !== undefined ? "`source.assets`" : null
623
- ].filter(Boolean);
624
- throw new UsageError(`Manifest field(s) ${stale.join(" and ")} are no longer supported — remove them.
625
- ` + " Output goes to `dist/<title-slug>/` automatically (use `--out` for a one-off location).\n" + " Assets are discovered from what your book actually references, so no list is needed.");
626
- }
627
- if ((m.validate?.source?.allowedCallouts?.length ?? 0) > 0 || (c.validate?.source?.allowedCallouts?.length ?? 0) > 0) {
628
- warnOnce("allowed-callouts-deprecated", "[gutterpress] manifest field `validate.source.allowedCallouts` is " + "deprecated and ignored. The `:::` container syntax it gated was " + "removed 2026-05-17. See docs/migrations/2026-05-removing-container-syntax.md.");
629
- }
630
- for (const key of ["minImageDpi", "allowedColorSpaces"]) {
631
- if (m.validate?.assets?.[key] !== undefined || c.validate?.assets?.[key] !== undefined) {
632
- warnOnce(`assets-${key}-deprecated`, `[gutterpress] manifest field \`validate.assets.${key}\` is deprecated and ` + "ignored. The source-image check it configured was removed; the build " + "and the PDF checks measure what actually prints.");
633
- }
734
+ if (m.output !== undefined) {
735
+ throw new UsageError("Manifest field `output` is no longer supported — remove it.\n" + " Output goes to `dist/<title-slug>/` automatically (use `--out` for a one-off location).");
634
736
  }
635
737
  const removed = m;
636
738
  if (removed.engine !== undefined) {
@@ -664,6 +766,9 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
664
766
  throw new UsageError("Manifest field `extensions` must be a list of specifiers.");
665
767
  }
666
768
  const extensions = rawExtensions.map((entry, index) => ({ entry, index })).filter(({ entry }) => typeof entry === "string" || entry?.enabled !== false).map(({ entry, index }) => normalizeExtensionEntry(entry, index));
769
+ const page = preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m);
770
+ if (page.margin !== undefined && page.margin !== null)
771
+ validatePageMargin(page.margin, page);
667
772
  return {
668
773
  title: c.title ?? m.title ?? "Document",
669
774
  authors: c.authors ?? m.authors ?? [],
@@ -673,25 +778,37 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
673
778
  styles: c.styles ?? m.styles,
674
779
  extensions,
675
780
  targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
676
- source: mergeShape(c.source, m.source, preset.source),
781
+ chapters: c.chapters ?? m.chapters ?? preset.chapters,
782
+ checks: { ...preset.checks, ...resolveCheckLevels(m.checks), ...resolveCheckLevels(c.checks) },
677
783
  pdfx: mergeShape(c.pdfx, m.pdfx, preset.pdfx),
678
784
  print: mergeShape(c.print, m.print, preset.print),
679
- page: preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m),
680
- ink: mergeShape(c.ink, m.ink, preset.ink),
681
- lint: {
682
- ...mergeShape(c.lint, m.lint, preset.lint),
683
- rules: { ...preset.lint.rules, ...m.lint?.rules, ...c.lint?.rules }
684
- },
685
- validate: {
686
- enabled: c.validate?.enabled ?? m.validate?.enabled ?? preset.validate.enabled,
687
- checks: { ...preset.validate.checks, ...m.validate?.checks, ...c.validate?.checks },
688
- source: mergeShape(c.validate?.source, m.validate?.source, preset.validate.source),
689
- assets: mergeShape(c.validate?.assets, m.validate?.assets, preset.validate.assets),
690
- pdf: mergeShape(c.validate?.pdf, m.validate?.pdf, preset.validate.pdf),
691
- heuristics: mergeShape(c.validate?.heuristics, m.validate?.heuristics, preset.validate.heuristics)
692
- }
785
+ page,
786
+ ink: structuredClone(preset.ink),
787
+ validate: structuredClone(preset.validate)
693
788
  };
694
789
  }
790
+ function resolveCheckLevels(input) {
791
+ if (input === undefined || input === null)
792
+ return {};
793
+ if (!isPlainObject(input)) {
794
+ throw new UsageError("Manifest field `checks` must map check ids to off, on, info, warn or error.");
795
+ }
796
+ const out = {};
797
+ for (const [id, value] of Object.entries(input)) {
798
+ if (value === false || value === "off")
799
+ out[id] = "off";
800
+ else if (value === true || value === "on")
801
+ out[id] = "on";
802
+ else if (value === "warn" || value === "warning")
803
+ out[id] = "warning";
804
+ else if (value === "info" || value === "error")
805
+ out[id] = value;
806
+ else {
807
+ throw new UsageError(`Manifest field \`checks.${id}\` must be off, on, info, warn or error, got ${JSON.stringify(value)}.`);
808
+ }
809
+ }
810
+ return out;
811
+ }
695
812
 
696
813
  // src/lib/manifest-doc.ts
697
814
  function resolveManifestPath(projectDir) {
@@ -740,13 +857,6 @@ function ensureSeq(doc, key) {
740
857
  }
741
858
  return seq;
742
859
  }
743
- function scalarString(item) {
744
- if (item && typeof item === "object" && "value" in item) {
745
- const v = item.value;
746
- return typeof v === "string" ? v : null;
747
- }
748
- return typeof item === "string" ? item : null;
749
- }
750
860
 
751
861
  // src/lib/npm-plugin-installer.ts
752
862
  import { createHash, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
@@ -1417,6 +1527,7 @@ function ignoreStatus(gitignore, entry) {
1417
1527
  return dir.unignored || file.unignored ? "negated" : "open";
1418
1528
  }
1419
1529
  var PROJECT_GITIGNORE_ENTRIES = ["dist/", "extensions/npm/"];
1530
+ var LEGACY_NPM_LINE = /^\/?plugins\/npm\/?[ \t]*\r?\n?/gm;
1420
1531
  async function ensureProjectGitignore(projectDir) {
1421
1532
  const gitignorePath = path3.join(projectDir, ".gitignore");
1422
1533
  let existing = null;
@@ -1433,10 +1544,11 @@ async function ensureProjectGitignore(projectDir) {
1433
1544
  negated.push(entry);
1434
1545
  }
1435
1546
  if (missing.length > 0) {
1436
- const lead = existing !== null && existing.length > 0 && !existing.endsWith(`
1547
+ const kept = missing.includes("extensions/npm/") ? (existing ?? "").replace(LEGACY_NPM_LINE, "") : existing ?? "";
1548
+ const lead = kept.length > 0 && !kept.endsWith(`
1437
1549
  `) ? `
1438
1550
  ` : "";
1439
- await writeFile(gitignorePath, `${existing ?? ""}${lead}${missing.join(`
1551
+ await writeFile(gitignorePath, `${kept}${lead}${missing.join(`
1440
1552
  `)}
1441
1553
  `, "utf8");
1442
1554
  }
@@ -1535,7 +1647,6 @@ async function describeFolder(dir, base, fallbackLabel, labelFrom = prettify) {
1535
1647
  ...meta.description?.trim() ? { description: meta.description.trim() } : {},
1536
1648
  ...meta.author?.trim() ? { author: meta.author.trim() } : {},
1537
1649
  ...meta.preview !== undefined ? { preview: meta.preview } : {},
1538
- ...meta.tokensFile?.trim() ? { tokensFile: meta.tokensFile.trim() } : {},
1539
1650
  ...styles.length > 0 ? { styles } : {},
1540
1651
  carries: extensionCarries(meta),
1541
1652
  dir
@@ -1669,29 +1780,6 @@ async function setExtensionEnabled(projectDir, use, enabled) {
1669
1780
  await writeManifestDoc(file, doc);
1670
1781
  });
1671
1782
  }
1672
- async function reorderExtensions(projectDir, order) {
1673
- return withMutationLock(projectDir, async () => {
1674
- const { doc, file } = await loadManifestDoc(projectDir);
1675
- const seq = doc.get("extensions", true);
1676
- if (!isSeq2(seq))
1677
- throw new Error(`No extensions are configured in ${path4.basename(file)}.`);
1678
- const items = seq.items;
1679
- const current = items.map((item) => entryUse(item));
1680
- const wanted = order.map((u) => u.trim());
1681
- const sameSet = current.length === wanted.length && [...current].sort().join(`
1682
- `) === [...wanted].sort().join(`
1683
- `);
1684
- if (!sameSet) {
1685
- throw new Error("The new order must list every configured extension exactly once — reload the list and try again.");
1686
- }
1687
- const remaining = [...items];
1688
- seq.items = wanted.map((u) => {
1689
- const i = remaining.findIndex((item) => entryUse(item) === u);
1690
- return remaining.splice(i, 1)[0];
1691
- });
1692
- await writeManifestDoc(file, doc);
1693
- });
1694
- }
1695
1783
  async function removeExtension(projectDir, use) {
1696
1784
  return withMutationLock(projectDir, async () => {
1697
1785
  const { doc, file } = await loadManifestDoc(projectDir);
@@ -1830,6 +1918,7 @@ async function vendorNpmExtension(projectDir, input, exportName, options, commit
1830
1918
  await finalizeNpmPluginInstall(installed).catch((error) => {
1831
1919
  warnings.push(`The extension was installed, but an old backup could not be removed: ` + `${error instanceof Error ? error.message : String(error)}`);
1832
1920
  });
1921
+ warnings.push(...await retireLegacyCopies(projectDir, installed.name));
1833
1922
  try {
1834
1923
  const { negated } = await ensureProjectGitignore(projectDir);
1835
1924
  if (negated.includes("extensions/npm/")) {
@@ -1840,6 +1929,19 @@ async function vendorNpmExtension(projectDir, input, exportName, options, commit
1840
1929
  }
1841
1930
  return { name: installed.name, version: installed.version, use, warnings };
1842
1931
  }
1932
+ async function retireLegacyCopies(projectDir, name) {
1933
+ const legacyPackageDir = path4.dirname(vendoredNpmPluginRoots(projectDir, name, "0.0.0")[1]);
1934
+ const legacyNpmDir = path4.dirname(legacyPackageDir);
1935
+ try {
1936
+ await rm3(legacyPackageDir, { recursive: true, force: true });
1937
+ await removeEmptyDirs([path4.dirname(legacyNpmDir), legacyNpmDir]);
1938
+ } catch (error) {
1939
+ return [
1940
+ `The extension was installed, but the copy an older version of Gutterpress kept under plugins/npm/ ` + `could not be removed: ${error instanceof Error ? error.message : String(error)}`
1941
+ ];
1942
+ }
1943
+ return [];
1944
+ }
1843
1945
  async function pinnedVersionsOf(projectDir, name) {
1844
1946
  const { doc } = await loadManifestDoc(projectDir);
1845
1947
  const seq = doc.get("extensions", true);
@@ -1937,9 +2039,6 @@ async function latestNpmVersion(name, options) {
1937
2039
  }
1938
2040
  return latest;
1939
2041
  }
1940
- async function listNpmVersions(name, options = {}) {
1941
- return (await fetchNpmVersions(name, options)).versions.sort(semverRcompare);
1942
- }
1943
2042
  async function checkExtensionUpdates(projectDir, options = {}) {
1944
2043
  const entries = await listProjectExtensions(projectDir);
1945
2044
  const pinned = entries.filter((e) => e.kind === "npm" && e.version);
@@ -1970,14 +2069,6 @@ async function updateExtensions(projectDir, options = {}) {
1970
2069
  }
1971
2070
  return updated;
1972
2071
  }
1973
- function listBundledExtensions() {
1974
- return BUNDLED_EXTENSIONS.map((b) => ({
1975
- use: b.name,
1976
- label: b.label,
1977
- description: b.description,
1978
- look: b.css !== undefined
1979
- }));
1980
- }
1981
2072
  async function uniqueExtensionId(projectDir, base) {
1982
2073
  const slug = slugify(base) || "extension";
1983
2074
  let candidate = slug;
@@ -1986,48 +2077,6 @@ async function uniqueExtensionId(projectDir, base) {
1986
2077
  }
1987
2078
  return candidate;
1988
2079
  }
1989
- async function validateProjectExtensions(projectDir) {
1990
- const out = [];
1991
- for (const entry of await listProjectExtensions(projectDir)) {
1992
- if (!entry.enabled) {
1993
- out.push({ use: entry.use, kind: entry.kind, enabled: false, ok: true });
1994
- continue;
1995
- }
1996
- try {
1997
- await loadPlugin(extensionConfigFor(entry), projectDir);
1998
- out.push({ use: entry.use, kind: entry.kind, enabled: true, ok: true });
1999
- } catch (e) {
2000
- out.push({
2001
- use: entry.use,
2002
- kind: entry.kind,
2003
- enabled: true,
2004
- ok: false,
2005
- error: e instanceof Error ? e.message : String(e)
2006
- });
2007
- }
2008
- }
2009
- return out;
2010
- }
2011
- async function readExtensionCss(projectDir, use) {
2012
- const entry = await describeExtension(projectDir, use);
2013
- if (!entry)
2014
- throw new Error(`Extension "${use}" is not in the manifest.`);
2015
- const bundledCss = entry.kind === "bundled" ? bundledExtension(entry.name)?.css : undefined;
2016
- if (bundledCss !== undefined)
2017
- return bundledCss;
2018
- if (!entry.dir)
2019
- throw new Error(`Extension "${use}" has no stylesheets to preview.`);
2020
- const meta = await readExtensionMeta(entry.dir);
2021
- const sheets = resolveExtension(entry.dir, meta, `Extension "${use}"`).styles ?? [];
2022
- if (sheets.length === 0)
2023
- throw new Error(`Extension "${use}" declares no stylesheets.`);
2024
- const parts = [];
2025
- for (const sheet of sheets)
2026
- parts.push(await readFile5(sheet, "utf8"));
2027
- return parts.join(`
2028
-
2029
- `);
2030
- }
2031
2080
 
2032
2081
  // src/lib/extension-restore.ts
2033
2082
  var OFFLINE_REASON = "you appear to be offline (npm can't be reached)";
@@ -2146,4 +2195,4 @@ async function restoreForCommand(dir, options) {
2146
2195
  log.warn(problem);
2147
2196
  }
2148
2197
 
2149
- export { DEBOUNCE, UsageError, resolvePort, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, resolveTargets, applyDefaultPdfStrictChecks, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, resolveConfigForTarget, FriendlyHttpError, withFetchTimeout, loadManifestDoc, writeManifestDoc, ensureSeq, scalarString, DEFAULT_NPM_REGISTRY, npmRegistryUrl, ensureProjectGitignore, sameExtension, listProjectExtensions, describeExtension, extensionConfigFor, setExtensionEnabled, reorderExtensions, removeExtension, addExtension, listNpmVersions, checkExtensionUpdates, updateExtensions, listBundledExtensions, uniqueExtensionId, validateProjectExtensions, readExtensionCss, restorePinnedExtensions, restoreFailureMessage, restoreOfflineMessage, restoreForCommand };
2198
+ export { isPositivePoints, cssLength, formatPageSize, printAreaMeta, pageBoxes, judgeRect, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, resolveTargets, applyDefaultPdfStrictChecks, MANIFEST_FILENAMES, loadManifest, loadManifestWithPath, resolveConfig, resolveConfigForTarget, FriendlyHttpError, withFetchTimeout, loadManifestDoc, writeManifestDoc, npmRegistryUrl, ensureProjectGitignore, listProjectExtensions, setExtensionEnabled, removeExtension, addExtension, checkExtensionUpdates, updateExtensions, uniqueExtensionId, restorePinnedExtensions, restoreFailureMessage, restoreOfflineMessage, restoreForCommand };