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,4 +1,5 @@
1
1
  import {
2
+ BUNDLED_EXTENSIONS,
2
3
  EXTENSION_MANIFEST_FILENAME,
3
4
  TWO_VERSIONS_OPEN,
4
5
  bundledExtension,
@@ -6,22 +7,18 @@ import {
6
7
  extensionStyleList,
7
8
  isPathSpecifier,
8
9
  loadPlugin,
10
+ log,
9
11
  parseExtensionSpecifier,
10
12
  pinnedNpmSpecifier,
11
13
  prettify,
12
14
  readExtensionMeta,
15
+ resolveExtension,
13
16
  slugify
14
- } from "./cli-pwwfxdgb.js";
15
- import {
16
- log
17
- } from "./cli-aqh6brew.js";
18
- import {
19
- UsageError
20
- } from "./cli-d8xjns66.js";
17
+ } from "./index-8ywkd2sv.js";
21
18
  import {
22
19
  BuildError,
23
20
  EXIT_CODES
24
- } from "./cli-46ycxe6r.js";
21
+ } from "./index-46ycxe6r.js";
25
22
  import {
26
23
  EXTENSIONS_DIR,
27
24
  VENDORED_NPM_DIR,
@@ -34,10 +31,10 @@ import {
34
31
  vendoredNpmPluginRoot,
35
32
  vendoredNpmPluginRoots,
36
33
  windowsPathKey
37
- } from "./cli-y28j019d.js";
34
+ } from "./index-5x42amh8.js";
38
35
  import {
39
36
  __require
40
- } from "./cli-37x76zdn.js";
37
+ } from "./index-37x76zdn.js";
41
38
 
42
39
  // src/lib/extension-restore.ts
43
40
  import path5 from "node:path";
@@ -94,6 +91,117 @@ import { existsSync, statSync } from "node:fs";
94
91
  import { resolve, dirname } from "node:path";
95
92
  import { parse as parseYaml, YAMLParseError } from "yaml";
96
93
 
94
+ // src/engine/shared/print-area.ts
95
+ function isPositivePoints(value) {
96
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
97
+ }
98
+ function isNonNegativePoints(value) {
99
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
100
+ }
101
+ var round3 = (n) => Math.round(n * 1000) / 1000;
102
+ function cssLength(pt) {
103
+ const inches = round3(pt / 72);
104
+ return Math.abs(inches * 72 - pt) < 0.000001 ? `${inches}in` : `${round3(pt)}pt`;
105
+ }
106
+ function formatPageSize(page) {
107
+ return `${cssLength(page.width)} ${cssLength(page.height)}`;
108
+ }
109
+ function printAreaMeta(page) {
110
+ const area = JSON.stringify({
111
+ width: page.width,
112
+ height: page.height,
113
+ bleed: page.bleed ?? 0,
114
+ safe: page.safe ?? 0,
115
+ artSafe: page.artSafe,
116
+ gutter: page.gutter,
117
+ preset: page.preset,
118
+ spec: page.spec
119
+ });
120
+ return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
121
+ }
122
+ function pageBoxes(area, side, scale = 1) {
123
+ const w = area.width * scale;
124
+ const h = area.height * scale;
125
+ const b = Math.max(0, area.bleed) * scale;
126
+ const s = Math.max(0, area.safe) * scale;
127
+ const a = Math.max(0, area.artSafe) * scale;
128
+ const g = Math.max(0, area.gutter) * scale;
129
+ const [textLeft, textRight] = side === "verso" ? [s, g] : [g, s];
130
+ const [artLeft, artRight] = side === "verso" ? [a, g] : [g, a];
131
+ const trim = { x: side === "verso" ? b : 0, y: b, w: Math.max(0, w - b), h: Math.max(0, h - 2 * b) };
132
+ return {
133
+ sheet: { x: 0, y: 0, w, h },
134
+ trim,
135
+ safe: {
136
+ x: trim.x + textLeft,
137
+ y: trim.y + s,
138
+ w: Math.max(0, trim.w - textLeft - textRight),
139
+ h: Math.max(0, trim.h - 2 * s)
140
+ },
141
+ safeArt: {
142
+ x: trim.x + artLeft,
143
+ y: trim.y + a,
144
+ w: Math.max(0, trim.w - artLeft - artRight),
145
+ h: Math.max(0, trim.h - 2 * a)
146
+ },
147
+ bleed: b
148
+ };
149
+ }
150
+ var SEVERITY = { inside: 0, unsafe: 1, bleed: 2, "off-page": 3 };
151
+ function judgeRect(r, boxes, rule, tol = 0) {
152
+ const { sheet, trim } = boxes;
153
+ const safe = rule === "art" ? boxes.safeArt : boxes.safe;
154
+ const sides = [
155
+ ["left", -r.x, -safe.x, -trim.x, -sheet.x],
156
+ ["right", r.x + r.w, safe.x + safe.w, trim.x + trim.w, sheet.x + sheet.w],
157
+ ["top", -r.y, -safe.y, -trim.y, -sheet.y],
158
+ ["bottom", r.y + r.h, safe.y + safe.h, trim.y + trim.h, sheet.y + sheet.h]
159
+ ];
160
+ let worst = { kind: "inside", gap: 0, need: 0, edge: null };
161
+ let beyond = 0;
162
+ for (const [edge, ink, limit, trimEdge, sheetEdge] of sides) {
163
+ const past = ink - limit;
164
+ if (past <= tol || rule === "art" && ink >= sheetEdge - tol)
165
+ continue;
166
+ const kind = ink > sheetEdge + tol ? "off-page" : ink > trimEdge + tol ? "bleed" : "unsafe";
167
+ if (SEVERITY[kind] > SEVERITY[worst.kind] || kind === worst.kind && past > beyond) {
168
+ worst = { kind, gap: trimEdge - ink, need: trimEdge - limit, edge };
169
+ beyond = past;
170
+ }
171
+ }
172
+ return worst;
173
+ }
174
+
175
+ // src/constants.ts
176
+ var NETWORK = {
177
+ DEFAULT_PORT: 3579
178
+ };
179
+ var DEBOUNCE = {
180
+ FILE_WATCH: 100
181
+ };
182
+
183
+ // src/lib/cli-args.ts
184
+ class UsageError extends Error {
185
+ exitCode;
186
+ constructor(message, exitCode = EXIT_CODES.USAGE) {
187
+ super(message);
188
+ this.name = "UsageError";
189
+ this.exitCode = exitCode;
190
+ }
191
+ }
192
+ function resolvePort(raw) {
193
+ if (raw === undefined)
194
+ return NETWORK.DEFAULT_PORT;
195
+ if (raw === "" || typeof raw === "string" && raw.trim() === "") {
196
+ throw new UsageError("--port requires a value.");
197
+ }
198
+ const n = typeof raw === "number" || typeof raw === "string" ? Number(raw) : NaN;
199
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0 || n > 65535) {
200
+ throw new UsageError(`Invalid --port value: "${raw}". Expected an integer from 0 to 65535 (0 = OS-assigned).`);
201
+ }
202
+ return n;
203
+ }
204
+
97
205
  // src/lib/presets.ts
98
206
  var PRESET_IDS = ["dtrpg", "book", "custom"];
99
207
  var DTRPG_PRESET = {
@@ -104,9 +212,7 @@ var DTRPG_PRESET = {
104
212
  },
105
213
  defaultTargets: ["dtrpg"],
106
214
  extensions: [],
107
- source: {
108
- files: null
109
- },
215
+ chapters: null,
110
216
  print: { signature: 4, reserveLastPage: true, lastPage: undefined },
111
217
  pdfx: {
112
218
  flavor: "x1a",
@@ -124,59 +230,30 @@ var DTRPG_PRESET = {
124
230
  bleed: 9,
125
231
  safe: 36,
126
232
  artSafe: 18,
127
- gutter: 36
233
+ gutter: 36,
234
+ margin: undefined
128
235
  },
129
236
  ink: {
130
237
  maxTac: 240,
131
238
  tacTolerance: 0.5
132
239
  },
133
- lint: {
134
- enabled: true,
135
- configPath: null,
136
- rules: {}
240
+ checks: {
241
+ "pdf.structure.qpdf": "error",
242
+ "pdf.print.pdfx-markers": "error",
243
+ "pdf.print.pdfx-metadata": "error",
244
+ "pdf.print.embedded-fonts": "error",
245
+ "pdf.print.safe-margins": "error",
246
+ "pdf.print.bleed": "off",
247
+ "pdf.nav.bookmarks": "off",
248
+ "pdf.nav.toc-links": "off",
249
+ "asset.font.license": "off"
137
250
  },
138
251
  validate: {
139
- enabled: true,
140
- checks: {
141
- "pdf.structure.qpdf": {
142
- enabled: true,
143
- severity: "error"
144
- },
145
- "pdf.print.pdfx-markers": {
146
- enabled: true,
147
- severity: "error"
148
- },
149
- "pdf.print.pdfx-metadata": {
150
- enabled: true,
151
- severity: "error"
152
- },
153
- "pdf.print.embedded-fonts": {
154
- enabled: true,
155
- severity: "error"
156
- },
157
- "pdf.print.safe-margins": {
158
- enabled: true,
159
- severity: "error"
160
- }
161
- },
162
- source: {
163
- markdownlint: null,
164
- htmlhint: null,
165
- stylelint: null,
166
- cssOwnership: null
167
- },
168
252
  assets: {
169
- maxImageSize: 1e7,
170
- allowAlpha: false,
171
- approvedFontFiles: [],
172
- requireFontLicense: false
253
+ maxImageSize: 1e7
173
254
  },
174
255
  pdf: {
175
- requireBookmarks: false,
176
- requireTocLinks: false,
177
256
  minImageResolution: 300,
178
- forbidTransparency: true,
179
- requireBleed: false,
180
257
  bleedSize: 9
181
258
  },
182
259
  heuristics: {
@@ -190,9 +267,7 @@ var BOOK_PRESET = {
190
267
  id: "book",
191
268
  defaultTargets: [],
192
269
  extensions: [],
193
- source: {
194
- files: null
195
- },
270
+ chapters: null,
196
271
  print: { signature: 1, reserveLastPage: false, lastPage: undefined },
197
272
  pdfx: {
198
273
  flavor: "x1a",
@@ -210,53 +285,32 @@ var BOOK_PRESET = {
210
285
  bleed: undefined,
211
286
  safe: 18,
212
287
  artSafe: undefined,
213
- gutter: undefined
288
+ gutter: undefined,
289
+ margin: undefined
214
290
  },
215
291
  ink: {
216
292
  maxTac: 400,
217
293
  tacTolerance: 0.5
218
294
  },
219
- lint: {
220
- enabled: true,
221
- configPath: null,
222
- rules: {}
295
+ checks: {
296
+ "pdf.structure.qpdf": "error",
297
+ "pdf.print.embedded-fonts": "error",
298
+ "pdf.print.safe-margins": "warning",
299
+ "pdf.print.pdfx-markers": "off",
300
+ "pdf.print.pdfx-metadata": "off",
301
+ "pdf.print.transparency": "off",
302
+ "asset.image.alpha-channel": "off",
303
+ "pdf.print.bleed": "off",
304
+ "pdf.nav.bookmarks": "off",
305
+ "pdf.nav.toc-links": "off",
306
+ "asset.font.license": "off"
223
307
  },
224
308
  validate: {
225
- enabled: true,
226
- checks: {
227
- "pdf.structure.qpdf": {
228
- enabled: true,
229
- severity: "error"
230
- },
231
- "pdf.print.embedded-fonts": {
232
- enabled: true,
233
- severity: "error"
234
- },
235
- "pdf.print.safe-margins": {
236
- enabled: true,
237
- severity: "warning"
238
- },
239
- "pdf.print.pdfx-markers": false,
240
- "pdf.print.pdfx-metadata": false
241
- },
242
- source: {
243
- markdownlint: null,
244
- htmlhint: null,
245
- stylelint: null,
246
- cssOwnership: null
247
- },
248
309
  assets: {
249
- maxImageSize: 1e7,
250
- allowAlpha: true,
251
- approvedFontFiles: [],
252
- requireFontLicense: false
310
+ maxImageSize: 1e7
253
311
  },
254
312
  pdf: {
255
- requireBookmarks: false,
256
- requireTocLinks: false,
257
313
  minImageResolution: 300,
258
- forbidTransparency: false,
259
- requireBleed: false,
260
314
  bleedSize: 9
261
315
  },
262
316
  heuristics: {
@@ -309,16 +363,16 @@ var DTRPG_TARGET = {
309
363
  maxTac: DTRPG_PRESET.ink.maxTac,
310
364
  tacTolerance: DTRPG_PRESET.ink.tacTolerance
311
365
  },
366
+ checks: {
367
+ "pdf.structure.qpdf": "error",
368
+ "pdf.print.pdfx-markers": "error",
369
+ "pdf.print.pdfx-metadata": "error",
370
+ "pdf.print.embedded-fonts": "error",
371
+ "pdf.print.safe-margins": "error",
372
+ "pdf.print.transparency": "on"
373
+ },
312
374
  validate: {
313
- checks: {
314
- "pdf.structure.qpdf": { enabled: true, severity: "error" },
315
- "pdf.print.pdfx-markers": { enabled: true, severity: "error" },
316
- "pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
317
- "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
318
- "pdf.print.safe-margins": { enabled: true, severity: "error" }
319
- },
320
375
  pdf: {
321
- forbidTransparency: DTRPG_PRESET.validate.pdf.forbidTransparency,
322
376
  minImageResolution: DTRPG_PRESET.validate.pdf.minImageResolution
323
377
  }
324
378
  }
@@ -336,22 +390,22 @@ var ITCH_TARGET = {
336
390
  id: "itch",
337
391
  label: "itch.io",
338
392
  overlay: {
393
+ checks: {
394
+ "pdf.structure.qpdf": "error",
395
+ "pdf.print.embedded-fonts": "error",
396
+ "pdf.print.pdfx-markers": "off",
397
+ "pdf.print.pdfx-metadata": "off",
398
+ "pdf.print.ink-coverage": "off",
399
+ "pdf.print.safe-margins": "off",
400
+ "pdf.print.transparency": "off",
401
+ "pdf.print.bleed": "off",
402
+ "asset.image.alpha-channel": "off"
403
+ },
339
404
  validate: {
340
- checks: {
341
- "pdf.structure.qpdf": { enabled: true, severity: "error" },
342
- "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
343
- "pdf.print.pdfx-markers": false,
344
- "pdf.print.pdfx-metadata": false,
345
- "pdf.print.ink-coverage": false,
346
- "pdf.print.safe-margins": false
347
- },
348
405
  assets: {
349
- allowAlpha: true,
350
406
  maxImageSize: 0
351
407
  },
352
408
  pdf: {
353
- forbidTransparency: false,
354
- requireBleed: false,
355
409
  minImageResolution: 0
356
410
  }
357
411
  }
@@ -390,9 +444,9 @@ function overlayPreset(preset, target) {
390
444
  ...preset,
391
445
  pdfx: { ...preset.pdfx, ...o.pdfx },
392
446
  ink: { ...preset.ink, ...o.ink },
447
+ checks: { ...preset.checks, ...o.checks },
393
448
  validate: {
394
449
  ...preset.validate,
395
- checks: { ...preset.validate.checks, ...o.validate?.checks },
396
450
  assets: { ...preset.validate.assets, ...o.validate?.assets },
397
451
  pdf: { ...preset.validate.pdf, ...o.validate?.pdf }
398
452
  }
@@ -407,17 +461,134 @@ var DEFAULT_STRICT_PDF_CHECKS = [
407
461
  function applyDefaultPdfStrictChecks(config) {
408
462
  const next = structuredClone(config);
409
463
  for (const checkId of DEFAULT_STRICT_PDF_CHECKS) {
410
- if (next.validate.checks[checkId] === undefined) {
411
- next.validate.checks[checkId] = { enabled: true, severity: "error" };
412
- }
464
+ if (next.checks[checkId] === undefined)
465
+ next.checks[checkId] = "error";
413
466
  }
414
467
  return next;
415
468
  }
416
469
 
470
+ // src/lib/manifest-old-spellings.ts
471
+ var SOURCE_TOOL_CHECKS = {
472
+ markdownlint: "source.markdownlint",
473
+ htmlhint: "source.htmlhint",
474
+ stylelint: "source.stylelint",
475
+ cssOwnership: "source.css-ownership"
476
+ };
477
+ var FLAG_CHECKS = {
478
+ "assets.allowAlpha": { check: "asset.image.alpha-channel", trueMeansOn: false },
479
+ "assets.requireFontLicense": { check: "asset.font.license", trueMeansOn: true },
480
+ "pdf.requireBookmarks": { check: "pdf.nav.bookmarks", trueMeansOn: true },
481
+ "pdf.requireTocLinks": { check: "pdf.nav.toc-links", trueMeansOn: true },
482
+ "pdf.requireBleed": { check: "pdf.print.bleed", trueMeansOn: true },
483
+ "pdf.forbidTransparency": { check: "pdf.print.transparency", trueMeansOn: true }
484
+ };
485
+ function isObject(v) {
486
+ return typeof v === "object" && v !== null && !Array.isArray(v);
487
+ }
488
+ function readOldSpellings(raw) {
489
+ const { source, lint, validate, ink, ...manifest } = raw;
490
+ const tolerance = isObject(raw.page) && "tolerance" in raw.page;
491
+ if (source === undefined && lint === undefined && validate === undefined && ink === undefined && !tolerance) {
492
+ return raw;
493
+ }
494
+ if (isObject(source) && source.assets !== undefined) {
495
+ 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.");
496
+ }
497
+ const renamed = [];
498
+ const dropped = [];
499
+ const checks = {};
500
+ if (isObject(source) && "files" in source) {
501
+ renamed.push("source.files -> chapters");
502
+ if (manifest.chapters === undefined)
503
+ manifest.chapters = source.files ?? null;
504
+ }
505
+ if (isObject(validate)) {
506
+ for (const [id, entry] of Object.entries(validate.checks ?? {})) {
507
+ if (entry === false || isObject(entry) && entry.enabled === false)
508
+ checks[id] = "off";
509
+ else if (isObject(entry) && typeof entry.severity === "string")
510
+ checks[id] = entry.severity;
511
+ else
512
+ checks[id] = "on";
513
+ }
514
+ if (validate.checks !== undefined)
515
+ renamed.push("validate.checks -> checks");
516
+ for (const [tool, value] of Object.entries(validate.source ?? {})) {
517
+ const id = SOURCE_TOOL_CHECKS[tool];
518
+ if (id && value === false) {
519
+ checks[id] = "off";
520
+ renamed.push(`validate.source.${tool}: false -> checks: { ${id}: off }`);
521
+ } else if (id && tool !== "stylelint" && typeof value === "string") {
522
+ dropped.push(`validate.source.${tool} (the config file is found by its usual name in the book's folder or a folder above it)`);
523
+ } else {
524
+ dropped.push(`validate.source.${tool}`);
525
+ }
526
+ }
527
+ for (const block of ["assets", "pdf", "heuristics"]) {
528
+ for (const [key, value] of Object.entries(validate[block] ?? {})) {
529
+ const flag = FLAG_CHECKS[`${block}.${key}`];
530
+ if (flag && typeof value === "boolean") {
531
+ const level = value === flag.trueMeansOn ? "on" : "off";
532
+ checks[flag.check] = level;
533
+ renamed.push(`validate.${block}.${key}: ${value} -> checks: { ${flag.check}: ${level} }`);
534
+ } else {
535
+ dropped.push(`validate.${block}.${key} (the preset and the publish target set it)`);
536
+ }
537
+ }
538
+ }
539
+ if (validate.enabled !== undefined) {
540
+ dropped.push("validate.enabled (`gutterpress build --skip-pre-validate --skip-post-validate` skips validation for one build)");
541
+ }
542
+ }
543
+ if (isObject(lint)) {
544
+ for (const [id, level] of Object.entries(lint.rules ?? {}))
545
+ checks[id] = level;
546
+ if (lint.rules !== undefined)
547
+ renamed.push("lint.rules -> checks");
548
+ if (lint.enabled === false) {
549
+ checks["source.stylelint"] = "off";
550
+ renamed.push("lint.enabled: false -> checks: { source.stylelint: off }");
551
+ } else if (lint.enabled !== undefined) {
552
+ dropped.push("lint.enabled");
553
+ }
554
+ if (lint.configPath !== undefined)
555
+ dropped.push("lint.configPath");
556
+ }
557
+ if (ink !== undefined)
558
+ dropped.push("ink (the preset and the publish target set it)");
559
+ if (tolerance) {
560
+ const { tolerance: _, ...page } = raw.page;
561
+ manifest.page = page;
562
+ dropped.push("page.tolerance (the preset sets it)");
563
+ }
564
+ if (Object.keys(checks).length > 0)
565
+ manifest.checks = { ...checks, ...manifest.checks };
566
+ const lines = [
567
+ ...renamed.map((r) => ` ${r}`),
568
+ ...dropped.map((d) => ` ${d} — no longer a setting, ignored`)
569
+ ];
570
+ if (lines.length > 0) {
571
+ 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:
572
+ ` + lines.join(`
573
+ `));
574
+ }
575
+ return manifest;
576
+ }
577
+
417
578
  // src/lib/manifest.ts
418
579
  var MANIFEST_FILENAMES = [
419
580
  "manifest.yaml"
420
581
  ];
582
+ function hasProjectManifest(dir) {
583
+ return MANIFEST_FILENAMES.some((name) => {
584
+ const candidate = resolve(dir, name);
585
+ try {
586
+ return statSync(candidate).isFile();
587
+ } catch {
588
+ return false;
589
+ }
590
+ });
591
+ }
421
592
  async function loadManifest(pathOrDir) {
422
593
  const { manifest } = await loadManifestWithPath(pathOrDir);
423
594
  return manifest;
@@ -431,8 +602,8 @@ async function loadManifestWithPath(pathOrDir, opts) {
431
602
  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.`);
432
603
  }
433
604
  try {
434
- const manifest = parseYaml(raw) ?? {};
435
- return { manifest, manifestDir: dirname(p), manifestPath: p };
605
+ const parsed = parseYaml(raw) ?? {};
606
+ return { manifest: readOldSpellings(parsed), manifestDir: dirname(p), manifestPath: p };
436
607
  } catch (error) {
437
608
  if (!(error instanceof YAMLParseError))
438
609
  throw error;
@@ -568,32 +739,37 @@ function resolveCustomPage(c, m) {
568
739
  return {
569
740
  width,
570
741
  height,
571
- tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5,
742
+ tolerance: 0.5,
572
743
  bleed: c.page?.bleed ?? m.page?.bleed,
573
744
  safe: c.page?.safe ?? m.page?.safe,
574
745
  artSafe: c.page?.artSafe ?? m.page?.artSafe,
575
- gutter: c.page?.gutter ?? m.page?.gutter
746
+ gutter: c.page?.gutter ?? m.page?.gutter,
747
+ margin: c.page?.margin ?? m.page?.margin
576
748
  };
577
749
  }
750
+ var MARGIN_EDGES = ["top", "bottom", "inside", "outside"];
751
+ function validatePageMargin(margin, size) {
752
+ const edges = isPlainObject(margin) && Object.keys(margin).every((k) => MARGIN_EDGES.includes(k)) && MARGIN_EDGES.every((e) => isNonNegativePoints(margin[e]));
753
+ if (!edges) {
754
+ throw new UsageError("Manifest field `page.margin` must set all four edges in points (72pt = 1in), for example\n" + ` page:
755
+ margin:
756
+ top: 54
757
+ bottom: 54
758
+ inside: 54
759
+ outside: 54
760
+ ` + `got ${JSON.stringify(margin)}.`);
761
+ }
762
+ const m = margin;
763
+ if (m.inside + m.outside >= size.width || m.top + m.bottom >= size.height) {
764
+ 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).`);
765
+ }
766
+ return m;
767
+ }
578
768
  function resolveWithPreset(cliOverrides, manifest, preset) {
579
769
  const m = manifest;
580
770
  const c = cliOverrides;
581
- const legacy = m;
582
- if (legacy.output !== undefined || legacy.source?.assets !== undefined) {
583
- const stale = [
584
- legacy.output !== undefined ? "`output`" : null,
585
- legacy.source?.assets !== undefined ? "`source.assets`" : null
586
- ].filter(Boolean);
587
- throw new UsageError(`Manifest field(s) ${stale.join(" and ")} are no longer supported — remove them.
588
- ` + " 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.");
589
- }
590
- if ((m.validate?.source?.allowedCallouts?.length ?? 0) > 0 || (c.validate?.source?.allowedCallouts?.length ?? 0) > 0) {
591
- 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.");
592
- }
593
- for (const key of ["minImageDpi", "allowedColorSpaces"]) {
594
- if (m.validate?.assets?.[key] !== undefined || c.validate?.assets?.[key] !== undefined) {
595
- 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.");
596
- }
771
+ if (m.output !== undefined) {
772
+ 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).");
597
773
  }
598
774
  const removed = m;
599
775
  if (removed.engine !== undefined) {
@@ -627,6 +803,9 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
627
803
  throw new UsageError("Manifest field `extensions` must be a list of specifiers.");
628
804
  }
629
805
  const extensions = rawExtensions.map((entry, index) => ({ entry, index })).filter(({ entry }) => typeof entry === "string" || entry?.enabled !== false).map(({ entry, index }) => normalizeExtensionEntry(entry, index));
806
+ const page = preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m);
807
+ if (page.margin !== undefined && page.margin !== null)
808
+ validatePageMargin(page.margin, page);
630
809
  return {
631
810
  title: c.title ?? m.title ?? "Document",
632
811
  authors: c.authors ?? m.authors ?? [],
@@ -636,25 +815,37 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
636
815
  styles: c.styles ?? m.styles,
637
816
  extensions,
638
817
  targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
639
- source: mergeShape(c.source, m.source, preset.source),
818
+ chapters: c.chapters ?? m.chapters ?? preset.chapters,
819
+ checks: { ...preset.checks, ...resolveCheckLevels(m.checks), ...resolveCheckLevels(c.checks) },
640
820
  pdfx: mergeShape(c.pdfx, m.pdfx, preset.pdfx),
641
821
  print: mergeShape(c.print, m.print, preset.print),
642
- page: preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m),
643
- ink: mergeShape(c.ink, m.ink, preset.ink),
644
- lint: {
645
- ...mergeShape(c.lint, m.lint, preset.lint),
646
- rules: { ...preset.lint.rules, ...m.lint?.rules, ...c.lint?.rules }
647
- },
648
- validate: {
649
- enabled: c.validate?.enabled ?? m.validate?.enabled ?? preset.validate.enabled,
650
- checks: { ...preset.validate.checks, ...m.validate?.checks, ...c.validate?.checks },
651
- source: mergeShape(c.validate?.source, m.validate?.source, preset.validate.source),
652
- assets: mergeShape(c.validate?.assets, m.validate?.assets, preset.validate.assets),
653
- pdf: mergeShape(c.validate?.pdf, m.validate?.pdf, preset.validate.pdf),
654
- heuristics: mergeShape(c.validate?.heuristics, m.validate?.heuristics, preset.validate.heuristics)
655
- }
822
+ page,
823
+ ink: structuredClone(preset.ink),
824
+ validate: structuredClone(preset.validate)
656
825
  };
657
826
  }
827
+ function resolveCheckLevels(input) {
828
+ if (input === undefined || input === null)
829
+ return {};
830
+ if (!isPlainObject(input)) {
831
+ throw new UsageError("Manifest field `checks` must map check ids to off, on, info, warn or error.");
832
+ }
833
+ const out = {};
834
+ for (const [id, value] of Object.entries(input)) {
835
+ if (value === false || value === "off")
836
+ out[id] = "off";
837
+ else if (value === true || value === "on")
838
+ out[id] = "on";
839
+ else if (value === "warn" || value === "warning")
840
+ out[id] = "warning";
841
+ else if (value === "info" || value === "error")
842
+ out[id] = value;
843
+ else {
844
+ throw new UsageError(`Manifest field \`checks.${id}\` must be off, on, info, warn or error, got ${JSON.stringify(value)}.`);
845
+ }
846
+ }
847
+ return out;
848
+ }
658
849
 
659
850
  // src/lib/manifest-doc.ts
660
851
  function resolveManifestPath(projectDir) {
@@ -703,6 +894,13 @@ function ensureSeq(doc, key) {
703
894
  }
704
895
  return seq;
705
896
  }
897
+ function scalarString(item) {
898
+ if (item && typeof item === "object" && "value" in item) {
899
+ const v = item.value;
900
+ return typeof v === "string" ? v : null;
901
+ }
902
+ return typeof item === "string" ? item : null;
903
+ }
706
904
 
707
905
  // src/lib/npm-plugin-installer.ts
708
906
  import { createHash, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
@@ -1373,6 +1571,7 @@ function ignoreStatus(gitignore, entry) {
1373
1571
  return dir.unignored || file.unignored ? "negated" : "open";
1374
1572
  }
1375
1573
  var PROJECT_GITIGNORE_ENTRIES = ["dist/", "extensions/npm/"];
1574
+ var LEGACY_NPM_LINE = /^\/?plugins\/npm\/?[ \t]*\r?\n?/gm;
1376
1575
  async function ensureProjectGitignore(projectDir) {
1377
1576
  const gitignorePath = path3.join(projectDir, ".gitignore");
1378
1577
  let existing = null;
@@ -1389,10 +1588,11 @@ async function ensureProjectGitignore(projectDir) {
1389
1588
  negated.push(entry);
1390
1589
  }
1391
1590
  if (missing.length > 0) {
1392
- const lead = existing !== null && existing.length > 0 && !existing.endsWith(`
1591
+ const kept = missing.includes("extensions/npm/") ? (existing ?? "").replace(LEGACY_NPM_LINE, "") : existing ?? "";
1592
+ const lead = kept.length > 0 && !kept.endsWith(`
1393
1593
  `) ? `
1394
1594
  ` : "";
1395
- await writeFile(gitignorePath, `${existing ?? ""}${lead}${missing.join(`
1595
+ await writeFile(gitignorePath, `${kept}${lead}${missing.join(`
1396
1596
  `)}
1397
1597
  `, "utf8");
1398
1598
  }
@@ -1491,7 +1691,6 @@ async function describeFolder(dir, base, fallbackLabel, labelFrom = prettify) {
1491
1691
  ...meta.description?.trim() ? { description: meta.description.trim() } : {},
1492
1692
  ...meta.author?.trim() ? { author: meta.author.trim() } : {},
1493
1693
  ...meta.preview !== undefined ? { preview: meta.preview } : {},
1494
- ...meta.tokensFile?.trim() ? { tokensFile: meta.tokensFile.trim() } : {},
1495
1694
  ...styles.length > 0 ? { styles } : {},
1496
1695
  carries: extensionCarries(meta),
1497
1696
  dir
@@ -1625,6 +1824,29 @@ async function setExtensionEnabled(projectDir, use, enabled) {
1625
1824
  await writeManifestDoc(file, doc);
1626
1825
  });
1627
1826
  }
1827
+ async function reorderExtensions(projectDir, order) {
1828
+ return withMutationLock(projectDir, async () => {
1829
+ const { doc, file } = await loadManifestDoc(projectDir);
1830
+ const seq = doc.get("extensions", true);
1831
+ if (!isSeq2(seq))
1832
+ throw new Error(`No extensions are configured in ${path4.basename(file)}.`);
1833
+ const items = seq.items;
1834
+ const current = items.map((item) => entryUse(item));
1835
+ const wanted = order.map((u) => u.trim());
1836
+ const sameSet = current.length === wanted.length && [...current].sort().join(`
1837
+ `) === [...wanted].sort().join(`
1838
+ `);
1839
+ if (!sameSet) {
1840
+ throw new Error("The new order must list every configured extension exactly once — reload the list and try again.");
1841
+ }
1842
+ const remaining = [...items];
1843
+ seq.items = wanted.map((u) => {
1844
+ const i = remaining.findIndex((item) => entryUse(item) === u);
1845
+ return remaining.splice(i, 1)[0];
1846
+ });
1847
+ await writeManifestDoc(file, doc);
1848
+ });
1849
+ }
1628
1850
  async function removeExtension(projectDir, use) {
1629
1851
  return withMutationLock(projectDir, async () => {
1630
1852
  const { doc, file } = await loadManifestDoc(projectDir);
@@ -1763,6 +1985,7 @@ async function vendorNpmExtension(projectDir, input, exportName, options, commit
1763
1985
  await finalizeNpmPluginInstall(installed).catch((error) => {
1764
1986
  warnings.push(`The extension was installed, but an old backup could not be removed: ` + `${error instanceof Error ? error.message : String(error)}`);
1765
1987
  });
1988
+ warnings.push(...await retireLegacyCopies(projectDir, installed.name));
1766
1989
  try {
1767
1990
  const { negated } = await ensureProjectGitignore(projectDir);
1768
1991
  if (negated.includes("extensions/npm/")) {
@@ -1773,6 +1996,19 @@ async function vendorNpmExtension(projectDir, input, exportName, options, commit
1773
1996
  }
1774
1997
  return { name: installed.name, version: installed.version, use, warnings };
1775
1998
  }
1999
+ async function retireLegacyCopies(projectDir, name) {
2000
+ const legacyPackageDir = path4.dirname(vendoredNpmPluginRoots(projectDir, name, "0.0.0")[1]);
2001
+ const legacyNpmDir = path4.dirname(legacyPackageDir);
2002
+ try {
2003
+ await rm3(legacyPackageDir, { recursive: true, force: true });
2004
+ await removeEmptyDirs([path4.dirname(legacyNpmDir), legacyNpmDir]);
2005
+ } catch (error) {
2006
+ return [
2007
+ `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)}`
2008
+ ];
2009
+ }
2010
+ return [];
2011
+ }
1776
2012
  async function pinnedVersionsOf(projectDir, name) {
1777
2013
  const { doc } = await loadManifestDoc(projectDir);
1778
2014
  const seq = doc.get("extensions", true);
@@ -1870,6 +2106,9 @@ async function latestNpmVersion(name, options) {
1870
2106
  }
1871
2107
  return latest;
1872
2108
  }
2109
+ async function listNpmVersions(name, options = {}) {
2110
+ return (await fetchNpmVersions(name, options)).versions.sort(semverRcompare);
2111
+ }
1873
2112
  async function checkExtensionUpdates(projectDir, options = {}) {
1874
2113
  const entries = await listProjectExtensions(projectDir);
1875
2114
  const pinned = entries.filter((e) => e.kind === "npm" && e.version);
@@ -1900,6 +2139,14 @@ async function updateExtensions(projectDir, options = {}) {
1900
2139
  }
1901
2140
  return updated;
1902
2141
  }
2142
+ function listBundledExtensions() {
2143
+ return BUNDLED_EXTENSIONS.map((b) => ({
2144
+ use: b.name,
2145
+ label: b.label,
2146
+ description: b.description,
2147
+ look: b.css !== undefined
2148
+ }));
2149
+ }
1903
2150
  async function uniqueExtensionId(projectDir, base) {
1904
2151
  const slug = slugify(base) || "extension";
1905
2152
  let candidate = slug;
@@ -1908,6 +2155,51 @@ async function uniqueExtensionId(projectDir, base) {
1908
2155
  }
1909
2156
  return candidate;
1910
2157
  }
2158
+ async function validateProjectExtensions(projectDir) {
2159
+ const out = [];
2160
+ for (const entry of await listProjectExtensions(projectDir)) {
2161
+ if (!entry.enabled) {
2162
+ out.push({ use: entry.use, kind: entry.kind, enabled: false, ok: true });
2163
+ continue;
2164
+ }
2165
+ try {
2166
+ await loadPlugin(extensionConfigFor(entry), projectDir);
2167
+ out.push({ use: entry.use, kind: entry.kind, enabled: true, ok: true });
2168
+ } catch (e) {
2169
+ out.push({
2170
+ use: entry.use,
2171
+ kind: entry.kind,
2172
+ enabled: true,
2173
+ ok: false,
2174
+ error: e instanceof Error ? e.message : String(e)
2175
+ });
2176
+ }
2177
+ }
2178
+ return out;
2179
+ }
2180
+ async function readExtensionCss(projectDir, use) {
2181
+ const direct = bundledExtension(use)?.css;
2182
+ if (direct !== undefined)
2183
+ return direct;
2184
+ const entry = await describeExtension(projectDir, use);
2185
+ if (!entry)
2186
+ throw new Error(`Extension "${use}" is not in the manifest.`);
2187
+ return readEntryCss(entry);
2188
+ }
2189
+ async function readEntryCss(entry) {
2190
+ const direct = bundledExtension(entry.use)?.css;
2191
+ if (direct !== undefined)
2192
+ return direct;
2193
+ if (!entry.dir)
2194
+ throw new Error(`Extension "${entry.use}" has no stylesheets to preview.`);
2195
+ const meta = await readExtensionMeta(entry.dir);
2196
+ const sheets = resolveExtension(entry.dir, meta, `Extension "${entry.use}"`).styles ?? [];
2197
+ if (sheets.length === 0)
2198
+ throw new Error(`Extension "${entry.use}" declares no stylesheets.`);
2199
+ return (await Promise.all(sheets.map((sheet) => readFile5(sheet, "utf8")))).join(`
2200
+
2201
+ `);
2202
+ }
1911
2203
 
1912
2204
  // src/lib/extension-restore.ts
1913
2205
  var OFFLINE_REASON = "you appear to be offline (npm can't be reached)";
@@ -2026,4 +2318,4 @@ async function restoreForCommand(dir, options) {
2026
2318
  log.warn(problem);
2027
2319
  }
2028
2320
 
2029
- export { 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 };
2321
+ export { isPositivePoints, isNonNegativePoints, round3, cssLength, formatPageSize, printAreaMeta, pageBoxes, judgeRect, DEBOUNCE, UsageError, resolvePort, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, resolveTargets, applyDefaultPdfStrictChecks, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, resolveConfigForTarget, MARGIN_EDGES, validatePageMargin, FriendlyHttpError, withFetchTimeout, loadManifestDoc, writeManifestDoc, scalarString, DEFAULT_NPM_REGISTRY, npmRegistryUrl, ensureProjectGitignore, sameExtension, listProjectExtensions, describeExtension, extensionConfigFor, setExtensionEnabled, reorderExtensions, removeExtension, addExtension, listNpmVersions, checkExtensionUpdates, updateExtensions, listBundledExtensions, uniqueExtensionId, validateProjectExtensions, readExtensionCss, readEntryCss, restorePinnedExtensions, restoreFailureMessage, restoreOfflineMessage, restoreForCommand };