gutterpress 0.0.0 → 0.9.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +21 -16
  2. package/dist/api/index.d.ts +6 -2
  3. package/dist/api/index.js +15 -3
  4. package/dist/{audit-1jrwwacz.js → audit-k16v3n98.js} +5 -5
  5. package/dist/{build-5s92kgpc.js → build-8tqr4gbb.js} +5 -5
  6. package/dist/checks/types.d.ts +6 -0
  7. package/dist/{cli-hvqjs342.js → cli-rkqxbnkq.js} +2 -2
  8. package/dist/{cli-h7phg3ka.js → cli-v2dze8ja.js} +258 -258
  9. package/dist/{index-1tyj4bk3.js → cli-wchtvxvw.js} +4 -1
  10. package/dist/{cli-7ykx7pp0.js → cli-yp0d2hp8.js} +611 -259
  11. package/dist/{cli-5bxx0c4y.js → cli-yzrh708h.js} +2 -2
  12. package/dist/cli.js +15 -15
  13. package/dist/{doctor-25zjt8dz.js → doctor-57czxy3q.js} +2 -2
  14. package/dist/{index-3h3fef7r.js → index-m3pa4wyf.js} +826 -335
  15. package/dist/{cli-1tyj4bk3.js → index-wchtvxvw.js} +4 -1
  16. package/dist/{index-5bxx0c4y.js → index-yzrh708h.js} +2 -2
  17. package/dist/index.js +15 -3
  18. package/dist/lib/asset-inline.d.ts +19 -0
  19. package/dist/lib/build-error.d.ts +0 -1
  20. package/dist/lib/build-fingerprint.d.ts +14 -0
  21. package/dist/lib/build-runner.d.ts +21 -1
  22. package/dist/lib/ghostscript.d.ts +0 -9
  23. package/dist/lib/manifest-config.d.ts +7 -0
  24. package/dist/lib/manifest.d.ts +8 -0
  25. package/dist/lib/markdown/index.d.ts +0 -1
  26. package/dist/lib/npm-plugin-installer.d.ts +1 -1
  27. package/dist/lib/output-paths.d.ts +0 -1
  28. package/dist/lib/pagination.d.ts +0 -20
  29. package/dist/lib/pdf-inspect.d.ts +2 -2
  30. package/dist/lib/pdf-parse.d.ts +2 -1
  31. package/dist/lib/plugin-manager.d.ts +0 -2
  32. package/dist/lib/presets.d.ts +36 -7
  33. package/dist/lib/project-scaffold.d.ts +39 -2
  34. package/dist/lib/project-source.d.ts +11 -9
  35. package/dist/lib/project-templates.d.ts +45 -7
  36. package/dist/lib/publish/providers/azure-swa.d.ts +0 -1
  37. package/dist/lib/publish/providers/itch.d.ts +0 -1
  38. package/dist/lib/publish/providers/shopify.d.ts +0 -1
  39. package/dist/lib/remote-auth/github-repos.d.ts +0 -2
  40. package/dist/lib/remote-auth/recovery/dispatch.d.ts +0 -5
  41. package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +3 -2
  42. package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +0 -8
  43. package/dist/lib/remote-auth/sync.d.ts +2 -10
  44. package/dist/lib/remote-auth/test-access.d.ts +0 -2
  45. package/dist/lib/remote-auth/test-support/git-http-server.d.ts +0 -37
  46. package/dist/lib/source-provider.d.ts +0 -14
  47. package/dist/lib/static-serve.d.ts +39 -0
  48. package/dist/lib/style-resolver.d.ts +10 -1
  49. package/dist/lib/targets.d.ts +84 -0
  50. package/dist/lib/theme-import.d.ts +0 -2
  51. package/dist/lib/theme-manager.d.ts +1 -1
  52. package/dist/lib/validation-exec.d.ts +4 -3
  53. package/dist/{lint-f63nxjfj.js → lint-4sq0dvf6.js} +5 -5
  54. package/dist/{manifest.schema-rn1dpdpn.json → manifest.schema-z61rzw44.json} +12 -6
  55. package/dist/new-f2nmyr5b.js +185 -0
  56. package/dist/{plugin-wf2cbmwc.js → plugin-xskq3dtx.js} +5 -5
  57. package/dist/{preflight-tg4mpz1h.js → preflight-epcffags.js} +24 -29
  58. package/dist/preview/file-watcher.d.ts +9 -22
  59. package/dist/preview/server-context.d.ts +22 -0
  60. package/dist/{preview-h2bwbb2x.js → preview-xf5dktz2.js} +5 -5
  61. package/dist/{project-source-0tg0r6vm.js → project-source-p0gn1wd5.js} +3 -3
  62. package/dist/{publish-ztaet53r.js → publish-97vdwq31.js} +5 -5
  63. package/dist/{repair-jkp4n3j8.js → repair-cz4z4hkg.js} +5 -5
  64. package/dist/schema/manifest.types.d.ts +14 -3
  65. package/dist/{source-provider-hp96b5jf.js → source-provider-c1rjm2c0.js} +2 -8
  66. package/dist/types.d.ts +0 -1
  67. package/dist/{validate-k2drs19j.js → validate-swzejnd8.js} +8 -8
  68. package/package.json +2 -2
  69. package/dist/chapter-01-jj8ftn4c.md +0 -29
  70. package/dist/lib/validation-profile.d.ts +0 -18
  71. package/dist/manifest-ryyg71nf.yaml +0 -9
  72. package/dist/new-7b08a42h.js +0 -105
  73. package/dist/theme-0yzvthe8.css +0 -65
  74. package/dist/theme-6a2fngc8.json +0 -6
@@ -20,13 +20,13 @@ import {
20
20
  stampCreator,
21
21
  stripAnnotations,
22
22
  warn
23
- } from "./cli-h7phg3ka.js";
23
+ } from "./cli-v2dze8ja.js";
24
24
  import {
25
25
  BuildError,
26
26
  DEBOUNCE,
27
27
  UsageError,
28
28
  resolvePort
29
- } from "./cli-hvqjs342.js";
29
+ } from "./cli-rkqxbnkq.js";
30
30
  import {
31
31
  gitDirFor,
32
32
  gitScopeFor,
@@ -40,11 +40,11 @@ import {
40
40
  snapshotWorkingTreeUnlocked,
41
41
  stageChanges,
42
42
  withRepoLock
43
- } from "./cli-5bxx0c4y.js";
43
+ } from "./cli-yzrh708h.js";
44
44
  import {
45
45
  detectProjectSource,
46
46
  syncRemoteFor
47
- } from "./cli-1tyj4bk3.js";
47
+ } from "./cli-wchtvxvw.js";
48
48
  import {
49
49
  defaultGitHttp
50
50
  } from "./cli-yja077f6.js";
@@ -52,14 +52,10 @@ import {
52
52
  __require
53
53
  } from "./cli-37x76zdn.js";
54
54
 
55
- // src/lib/manifest.ts
56
- import { readFile } from "node:fs/promises";
57
- import { existsSync, statSync } from "node:fs";
58
- import { resolve, dirname } from "node:path";
59
- import { parse as parseYaml, YAMLParseError } from "yaml";
60
-
61
55
  // src/lib/presets.ts
56
+ var PRESET_IDS = ["dtrpg", "book", "custom"];
62
57
  var DTRPG_PRESET = {
58
+ defaultTargets: ["dtrpg"],
63
59
  plugins: [],
64
60
  source: {
65
61
  files: null
@@ -131,6 +127,7 @@ var DTRPG_PRESET = {
131
127
  }
132
128
  };
133
129
  var BOOK_PRESET = {
130
+ defaultTargets: [],
134
131
  plugins: [],
135
132
  source: {
136
133
  files: null
@@ -195,9 +192,15 @@ var BOOK_PRESET = {
195
192
  }
196
193
  }
197
194
  };
195
+ var CUSTOM_PRESET = {
196
+ ...BOOK_PRESET,
197
+ page: null,
198
+ defaultTargets: []
199
+ };
198
200
  var PRESETS = {
199
201
  dtrpg: DTRPG_PRESET,
200
- book: BOOK_PRESET
202
+ book: BOOK_PRESET,
203
+ custom: CUSTOM_PRESET
201
204
  };
202
205
  var warnedIds = new Set;
203
206
  function warnOnce(id, message, sink = defaultWarnSink) {
@@ -211,7 +214,7 @@ function defaultWarnSink(message) {
211
214
  }
212
215
  function resolvePreset(presetName) {
213
216
  if (presetName === undefined) {
214
- warnOnce("no-preset-set", '[gutterpress] No `preset` set in manifest.yaml — defaulting to "dtrpg" ' + "(DriveThruRPG trim size, TAC cap 240, forced PDF/X) for backward " + "compatibility. Set `preset: dtrpg` to keep this explicitly, or " + "`preset: book` for a neutral 6x9in trade-book default with no " + "vendor TAC cap or PDF/X forcing.");
217
+ warnOnce("no-preset-set", '[gutterpress] No `preset` in manifest.yaml — using "dtrpg" ' + "(DriveThruRPG print-ready defaults). Set `preset: dtrpg`, `book`, " + "or `custom` to choose explicitly.");
215
218
  return DTRPG_PRESET;
216
219
  }
217
220
  const preset = PRESETS[presetName];
@@ -221,7 +224,126 @@ function resolvePreset(presetName) {
221
224
  return preset;
222
225
  }
223
226
 
227
+ // src/lib/targets.ts
228
+ var DTRPG_TARGET = {
229
+ id: "dtrpg",
230
+ label: "DriveThruRPG",
231
+ overlay: {
232
+ pdfx: { flavor: DTRPG_PRESET.pdfx.flavor },
233
+ ink: {
234
+ maxTac: DTRPG_PRESET.ink.maxTac,
235
+ tacTolerance: DTRPG_PRESET.ink.tacTolerance
236
+ },
237
+ validate: {
238
+ checks: {
239
+ "pdf.structure.qpdf": { enabled: true, severity: "error" },
240
+ "pdf.print.pdfx-markers": { enabled: true, severity: "error" },
241
+ "pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
242
+ "pdf.print.embedded-fonts": { enabled: true, severity: "error" }
243
+ },
244
+ assets: {
245
+ allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
246
+ },
247
+ pdf: {
248
+ forbidTransparency: DTRPG_PRESET.validate.pdf.forbidTransparency,
249
+ minImageResolution: DTRPG_PRESET.validate.pdf.minImageResolution
250
+ }
251
+ }
252
+ },
253
+ requiredChecks: [
254
+ "pdf.structure.qpdf",
255
+ "pdf.print.pdfx-markers",
256
+ "pdf.print.pdfx-metadata",
257
+ "pdf.print.embedded-fonts"
258
+ ],
259
+ requiredTools: ["qpdf", "gs"]
260
+ };
261
+ var ITCH_TARGET = {
262
+ id: "itch",
263
+ label: "itch.io",
264
+ overlay: {
265
+ validate: {
266
+ checks: {
267
+ "pdf.structure.qpdf": { enabled: true, severity: "error" },
268
+ "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
269
+ "pdf.print.pdfx-markers": false,
270
+ "pdf.print.pdfx-metadata": false,
271
+ "pdf.print.ink-coverage": false,
272
+ "asset.image.tac-raster": false
273
+ },
274
+ assets: {
275
+ allowedColorSpaces: ["RGB", "CMYK", "Grayscale"],
276
+ allowAlpha: true
277
+ },
278
+ pdf: {
279
+ forbidTransparency: false,
280
+ requireBleed: false
281
+ }
282
+ }
283
+ },
284
+ requiredChecks: ["pdf.structure.qpdf", "pdf.print.embedded-fonts"],
285
+ requiredTools: []
286
+ };
287
+ var TARGETS = {
288
+ dtrpg: DTRPG_TARGET,
289
+ itch: ITCH_TARGET
290
+ };
291
+ var TARGET_IDS = Object.keys(TARGETS);
292
+ function publishTargetFor(id) {
293
+ const target = TARGETS[id];
294
+ if (!target) {
295
+ throw new UsageError(`Unknown publish target "${id}". Known targets: ${TARGET_IDS.join(", ")}.`);
296
+ }
297
+ return target;
298
+ }
299
+ function resolveTargets(requested, fallback) {
300
+ const ids = requested ?? fallback;
301
+ const seen = new Set;
302
+ const out = [];
303
+ for (const id of ids) {
304
+ publishTargetFor(id);
305
+ if (!seen.has(id)) {
306
+ seen.add(id);
307
+ out.push(id);
308
+ }
309
+ }
310
+ return out;
311
+ }
312
+ function overlayPreset(preset, target) {
313
+ const o = target.overlay;
314
+ return {
315
+ ...preset,
316
+ pdfx: { ...preset.pdfx, ...o.pdfx },
317
+ ink: { ...preset.ink, ...o.ink },
318
+ validate: {
319
+ ...preset.validate,
320
+ checks: { ...preset.validate.checks, ...o.validate?.checks },
321
+ assets: { ...preset.validate.assets, ...o.validate?.assets },
322
+ pdf: { ...preset.validate.pdf, ...o.validate?.pdf }
323
+ }
324
+ };
325
+ }
326
+ var DEFAULT_STRICT_PDF_CHECKS = [
327
+ "pdf.structure.qpdf",
328
+ "pdf.print.pdfx-markers",
329
+ "pdf.print.pdfx-metadata",
330
+ "pdf.print.embedded-fonts"
331
+ ];
332
+ function applyDefaultPdfStrictChecks(config) {
333
+ const next = structuredClone(config);
334
+ for (const checkId of DEFAULT_STRICT_PDF_CHECKS) {
335
+ if (next.validate.checks[checkId] === undefined) {
336
+ next.validate.checks[checkId] = { enabled: true, severity: "error" };
337
+ }
338
+ }
339
+ return next;
340
+ }
341
+
224
342
  // src/lib/manifest.ts
343
+ import { readFile } from "node:fs/promises";
344
+ import { existsSync, statSync } from "node:fs";
345
+ import { resolve, dirname } from "node:path";
346
+ import { parse as parseYaml, YAMLParseError } from "yaml";
225
347
  var MANIFEST_FILENAMES = [
226
348
  "manifest.yaml"
227
349
  ];
@@ -298,8 +420,34 @@ function mergeShape(cli, manifest, preset) {
298
420
  return out;
299
421
  }
300
422
  function resolveConfig(cliOverrides, manifest) {
423
+ const presetName = cliOverrides.preset ?? manifest.preset;
424
+ return resolveWithPreset(cliOverrides, manifest, resolvePreset(presetName));
425
+ }
426
+ function resolveConfigForTarget(cliOverrides, manifest, targetId) {
301
427
  const presetName = cliOverrides.preset ?? manifest.preset;
302
428
  const preset = resolvePreset(presetName);
429
+ return resolveWithPreset(cliOverrides, manifest, overlayPreset(preset, publishTargetFor(targetId)));
430
+ }
431
+ function resolveCustomPage(c, m) {
432
+ const width = c.page?.width ?? m.page?.width;
433
+ const height = c.page?.height ?? m.page?.height;
434
+ const missing = [
435
+ width === undefined ? "page.width" : null,
436
+ height === undefined ? "page.height" : null
437
+ ].filter((f) => f !== null);
438
+ if (missing.length > 0) {
439
+ throw new UsageError(`preset: custom requires ${missing.join(" and ")} in manifest.yaml ` + `(points; 72pt = 1in). Example for US Letter:
440
+ ` + ` page:
441
+ width: 612
442
+ height: 792`);
443
+ }
444
+ return {
445
+ width,
446
+ height,
447
+ tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5
448
+ };
449
+ }
450
+ function resolveWithPreset(cliOverrides, manifest, preset) {
303
451
  const m = manifest;
304
452
  const c = cliOverrides;
305
453
  const legacy = m;
@@ -321,9 +469,10 @@ function resolveConfig(cliOverrides, manifest) {
321
469
  authors: c.authors ?? m.authors ?? [],
322
470
  styles: c.styles ?? m.styles,
323
471
  plugins,
472
+ targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
324
473
  source: mergeShape(c.source, m.source, preset.source),
325
474
  pdfx: mergeShape(c.pdfx, m.pdfx, preset.pdfx),
326
- page: mergeShape(c.page, m.page, preset.page),
475
+ page: preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m),
327
476
  ink: mergeShape(c.ink, m.ink, preset.ink),
328
477
  lint: mergeShape(c.lint, m.lint, preset.lint),
329
478
  validate: {
@@ -2575,6 +2724,22 @@ function decodeRef(ref) {
2575
2724
  function toPosix(p) {
2576
2725
  return p.split(path3.sep).join("/");
2577
2726
  }
2727
+ function escapesProjectRoot(projectDir, absPath) {
2728
+ const rel = path3.relative(projectDir, absPath);
2729
+ return rel === ".." || rel.startsWith(`..${path3.sep}`) || path3.isAbsolute(rel);
2730
+ }
2731
+ function proseImageRefError(ref, projectDir) {
2732
+ const cleaned = stripUrlSuffix(decodeRef(ref));
2733
+ if (path3.isAbsolute(cleaned)) {
2734
+ return `Image reference must be relative to the project: ${ref}
2735
+ ` + ` Copy the file into your project folder and reference it from there.`;
2736
+ }
2737
+ if (escapesProjectRoot(projectDir, path3.resolve(projectDir, cleaned))) {
2738
+ return `Image reference points outside the project: ${ref}
2739
+ ` + ` Copy the file into your project folder and reference it from there.`;
2740
+ }
2741
+ return null;
2742
+ }
2578
2743
  function mimeFor(ext) {
2579
2744
  return MIME_BY_EXT[ext] ?? "application/octet-stream";
2580
2745
  }
@@ -2677,7 +2842,7 @@ async function inlineOne(cssPath, projectDir, copies, warnings, seen) {
2677
2842
  return dataUri(bytes, ext);
2678
2843
  }
2679
2844
  const projectRel = path3.relative(projectDir, absAsset);
2680
- const dest = projectRel && !projectRel.startsWith("..") && !path3.isAbsolute(projectRel) ? toPosix(projectRel) : `${HASHED_ASSET_DIR}/${contentHash(bytes)}${ext}`;
2845
+ const dest = projectRel && !escapesProjectRoot(projectDir, absAsset) ? toPosix(projectRel) : `${HASHED_ASSET_DIR}/${contentHash(bytes)}${ext}`;
2681
2846
  copies.set(dest, { from: absAsset, to: dest });
2682
2847
  return dest;
2683
2848
  });
@@ -2768,19 +2933,14 @@ async function planImageCopies(projectDir, refs) {
2768
2933
  for (const ref of refs) {
2769
2934
  if (isNonFileUrl(ref))
2770
2935
  continue;
2771
- const cleaned = stripUrlSuffix(decodeRef(ref));
2772
- if (path3.isAbsolute(cleaned)) {
2773
- errors.push(`Image reference must be relative to the project: ${ref}
2774
- ` + ` Copy the file into your project folder and reference it from there.`);
2936
+ const refError = proseImageRefError(ref, projectDir);
2937
+ if (refError) {
2938
+ errors.push(refError);
2775
2939
  continue;
2776
2940
  }
2941
+ const cleaned = stripUrlSuffix(decodeRef(ref));
2777
2942
  const abs = path3.resolve(projectDir, cleaned);
2778
2943
  const rel = path3.relative(projectDir, abs);
2779
- if (rel.startsWith("..")) {
2780
- errors.push(`Image reference points outside the project: ${ref}
2781
- ` + ` Copy the file into your project folder and reference it from there.`);
2782
- continue;
2783
- }
2784
2944
  const dest = toPosix(rel);
2785
2945
  copies.set(dest, { from: abs, to: dest });
2786
2946
  }
@@ -2814,6 +2974,16 @@ var check19 = {
2814
2974
  if (inFence)
2815
2975
  continue;
2816
2976
  for (const { ref, kind } of extractLocalRefs(line)) {
2977
+ const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
2978
+ if (escape) {
2979
+ results.push(finding(check19.id, {
2980
+ severity: "error",
2981
+ message: escape,
2982
+ file,
2983
+ line: i + 1
2984
+ }));
2985
+ continue;
2986
+ }
2817
2987
  if (localRefExists(ref, kind, file, ctx.inputDir))
2818
2988
  continue;
2819
2989
  results.push(finding(check19.id, {
@@ -3796,7 +3966,7 @@ function reportMissingTools(result) {
3796
3966
 
3797
3967
  // src/lib/validation-exec.ts
3798
3968
  import { existsSync as existsSync7 } from "node:fs";
3799
- import { join as join2, resolve as resolve6 } from "node:path";
3969
+ import { dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
3800
3970
 
3801
3971
  // src/lib/output-paths.ts
3802
3972
  import path4 from "node:path";
@@ -4523,19 +4693,19 @@ function formatReport(report, format = "text") {
4523
4693
  }
4524
4694
  function formatText(report) {
4525
4695
  for (const r of report.infos) {
4526
- const loc = formatLocation(r.file, r.line, r.column);
4696
+ const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
4527
4697
  log.info(`${loc}${r.message}`);
4528
4698
  if (r.detail)
4529
4699
  log.info(` ${r.detail}`);
4530
4700
  }
4531
4701
  for (const r of report.warnings) {
4532
- const loc = formatLocation(r.file, r.line, r.column);
4702
+ const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
4533
4703
  log.warn(`${loc}${r.message}`);
4534
4704
  if (r.detail)
4535
4705
  log.warn(` ${r.detail}`);
4536
4706
  }
4537
4707
  for (const r of report.errors) {
4538
- const loc = formatLocation(r.file, r.line, r.column);
4708
+ const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
4539
4709
  log.error(`${loc}${r.message}`);
4540
4710
  if (r.detail)
4541
4711
  log.error(` ${r.detail}`);
@@ -4549,6 +4719,9 @@ function formatText(report) {
4549
4719
  log.success("VALIDATION PASSED");
4550
4720
  }
4551
4721
  }
4722
+ function targetPrefix(target) {
4723
+ return target ? `[${target}] ` : "";
4724
+ }
4552
4725
  function formatJson(report) {
4553
4726
  console.log(JSON.stringify({
4554
4727
  results: report.results,
@@ -4650,58 +4823,46 @@ function emptyReport() {
4650
4823
  };
4651
4824
  }
4652
4825
 
4653
- // src/lib/validation-profile.ts
4654
- var DTRPG_STRICT_PDF_CHECKS = [
4655
- "pdf.structure.qpdf",
4656
- "pdf.print.pdfx-markers",
4657
- "pdf.print.pdfx-metadata",
4658
- "pdf.print.embedded-fonts"
4659
- ];
4660
- function enforceStrictPdfChecks(config, overwrite = false) {
4661
- for (const checkId of DTRPG_STRICT_PDF_CHECKS) {
4662
- if (overwrite || config.validate.checks[checkId] === undefined) {
4663
- config.validate.checks[checkId] = {
4664
- enabled: true,
4665
- severity: "error"
4666
- };
4667
- }
4826
+ // src/lib/validation-exec.ts
4827
+ var SHIPPED_ASSET_EXTS = new Set([
4828
+ ".woff2",
4829
+ ".woff",
4830
+ ".ttf",
4831
+ ".otf",
4832
+ ".png",
4833
+ ".jpg",
4834
+ ".jpeg",
4835
+ ".gif",
4836
+ ".webp",
4837
+ ".avif",
4838
+ ".svg",
4839
+ ".tif",
4840
+ ".tiff"
4841
+ ]);
4842
+ async function sharedAssetDirs(manifestDir, relStyles, inputDir) {
4843
+ let closure;
4844
+ try {
4845
+ closure = await collectStyleDependencies(manifestDir, relStyles);
4846
+ } catch {
4847
+ return [];
4668
4848
  }
4669
- }
4670
- function applyDefaultPdfStrictChecks(config) {
4671
- const next = structuredClone(config);
4672
- enforceStrictPdfChecks(next, false);
4673
- return next;
4674
- }
4675
- var applyDtrpgPdfDefaults = applyDefaultPdfStrictChecks;
4676
- function applyValidationProfile(config, profile) {
4677
- const next = structuredClone(config);
4678
- if (profile === "dtrpg") {
4679
- next.pdfx.flavor = DTRPG_PRESET.pdfx.flavor;
4680
- next.ink.maxTac = DTRPG_PRESET.ink.maxTac;
4681
- next.ink.tacTolerance = DTRPG_PRESET.ink.tacTolerance;
4682
- next.validate.pdf.forbidTransparency = DTRPG_PRESET.validate.pdf.forbidTransparency;
4683
- next.validate.pdf.minImageResolution = DTRPG_PRESET.validate.pdf.minImageResolution;
4684
- next.validate.assets.allowedColorSpaces = [
4685
- ...DTRPG_PRESET.validate.assets.allowedColorSpaces
4686
- ];
4687
- enforceStrictPdfChecks(next, true);
4849
+ const projectRoot = resolve6(inputDir);
4850
+ const dirs = new Set;
4851
+ for (const file of closure) {
4852
+ const ext = file.slice(file.lastIndexOf(".")).toLowerCase();
4853
+ if (!SHIPPED_ASSET_EXTS.has(ext))
4854
+ continue;
4855
+ const dir = dirname3(resolve6(file));
4856
+ if (escapesProjectRoot(projectRoot, dir))
4857
+ dirs.add(dir);
4688
4858
  }
4689
- return next;
4859
+ return [...dirs];
4690
4860
  }
4691
-
4692
- // src/lib/validation-exec.ts
4693
4861
  function parseCsv(value) {
4694
4862
  if (!value)
4695
4863
  return;
4696
4864
  return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
4697
4865
  }
4698
- function parseProfile(raw) {
4699
- if (!raw)
4700
- return;
4701
- if (raw === "dtrpg")
4702
- return raw;
4703
- throw new Error(`Unsupported profile: ${raw}. Supported profiles: dtrpg`);
4704
- }
4705
4866
  function resolvePhaseArg(raw) {
4706
4867
  const normalized = raw.trim().toLowerCase();
4707
4868
  if (normalized === "pre" || normalized === "pre-build")
@@ -4724,20 +4885,16 @@ function resolveCategoryArg(raw) {
4724
4885
  }
4725
4886
  return parsed;
4726
4887
  }
4727
- function withProfileRequiredCheckErrors(report, tools, profile) {
4728
- if (!profile)
4729
- return report;
4730
- const required = profile === "dtrpg" ? DTRPG_STRICT_PDF_CHECKS : [];
4731
- if (required.length === 0)
4732
- return report;
4888
+ function withTargetRequiredCheckErrors(report, tools, target) {
4733
4889
  const skippedSet = new Set(tools.skippedChecks);
4734
- const missingRequired = required.filter((checkId) => skippedSet.has(checkId));
4890
+ const missingRequired = target.requiredChecks.filter((checkId) => skippedSet.has(checkId));
4735
4891
  if (missingRequired.length === 0)
4736
4892
  return report;
4737
4893
  const syntheticErrors = missingRequired.map((checkId) => ({
4738
4894
  checkId,
4895
+ target: target.id,
4739
4896
  severity: "error",
4740
- message: `Profile ${profile} requires check ${checkId}, but it was skipped because required tools are not available.`
4897
+ message: `Target ${target.id} requires check ${checkId}, but it was skipped because required tools are not available.`
4741
4898
  }));
4742
4899
  const results = [...report.results, ...syntheticErrors];
4743
4900
  const errors = [...report.errors, ...syntheticErrors];
@@ -4751,19 +4908,115 @@ function withProfileRequiredCheckErrors(report, tools, profile) {
4751
4908
  }
4752
4909
  };
4753
4910
  }
4911
+ var TARGET_INDEPENDENT_CATEGORIES = ["source", "heuristic"];
4912
+ var TARGET_DEPENDENT_CATEGORIES = ["asset", "pdf"];
4913
+ function tagReport(report, targetId) {
4914
+ const tag = (r) => ({ ...r, target: targetId });
4915
+ return {
4916
+ results: report.results.map(tag),
4917
+ errors: report.errors.map(tag),
4918
+ warnings: report.warnings.map(tag),
4919
+ infos: report.infos.map(tag),
4920
+ passed: report.passed,
4921
+ summary: report.summary
4922
+ };
4923
+ }
4924
+ function mergeReports(reports) {
4925
+ const merged = {
4926
+ results: [],
4927
+ errors: [],
4928
+ warnings: [],
4929
+ infos: [],
4930
+ passed: [],
4931
+ summary: { total: 0, errors: 0, warnings: 0, infos: 0, passed: 0 }
4932
+ };
4933
+ for (const r of reports) {
4934
+ merged.results.push(...r.results);
4935
+ merged.errors.push(...r.errors);
4936
+ merged.warnings.push(...r.warnings);
4937
+ merged.infos.push(...r.infos);
4938
+ merged.passed.push(...r.passed);
4939
+ merged.summary.total += r.summary.total;
4940
+ merged.summary.errors += r.summary.errors;
4941
+ merged.summary.warnings += r.summary.warnings;
4942
+ merged.summary.infos += r.summary.infos;
4943
+ merged.summary.passed += r.summary.passed;
4944
+ }
4945
+ return merged;
4946
+ }
4947
+ function mergeTools(results) {
4948
+ const available = new Set;
4949
+ const missing = new Set;
4950
+ const skippedChecks = new Set;
4951
+ const toolToChecks = new Map;
4952
+ for (const r of results) {
4953
+ for (const t of r.available)
4954
+ available.add(t);
4955
+ for (const t of r.missing)
4956
+ missing.add(t);
4957
+ for (const id of r.skippedChecks)
4958
+ skippedChecks.add(id);
4959
+ for (const [tool, ids] of r.toolToChecks) {
4960
+ const merged = new Set([...toolToChecks.get(tool) ?? [], ...ids]);
4961
+ toolToChecks.set(tool, [...merged]);
4962
+ }
4963
+ }
4964
+ return {
4965
+ available: [...available],
4966
+ missing: [...missing],
4967
+ skippedChecks: [...skippedChecks],
4968
+ toolToChecks
4969
+ };
4970
+ }
4971
+ function planRuns(opts) {
4972
+ const { manifest, baseConfig, targetIds, pdfPath, categories, only } = opts;
4973
+ const finalize = (config) => pdfPath ? applyDefaultPdfStrictChecks(config) : config;
4974
+ if (targetIds.length === 0) {
4975
+ return [{ targetId: null, config: finalize(baseConfig), category: categories, only }];
4976
+ }
4977
+ const requested = categories ?? [...TARGET_INDEPENDENT_CATEGORIES, ...TARGET_DEPENDENT_CATEGORIES];
4978
+ const independentCats = requested.filter((c) => TARGET_INDEPENDENT_CATEGORIES.includes(c));
4979
+ const dependentCats = requested.filter((c) => TARGET_DEPENDENT_CATEGORIES.includes(c));
4980
+ let baseOnly;
4981
+ let dependentOnly;
4982
+ if (only && only.length > 0) {
4983
+ const { resolved, unmatched } = resolveCheckSelectors(only);
4984
+ const categoryById = new Map(getChecks({}).map((c) => [c.id, c.category]));
4985
+ const independentIds = resolved.filter((id) => TARGET_INDEPENDENT_CATEGORIES.includes(categoryById.get(id) ?? ""));
4986
+ const dependentIds = resolved.filter((id) => TARGET_DEPENDENT_CATEGORIES.includes(categoryById.get(id) ?? ""));
4987
+ baseOnly = [...independentIds, ...unmatched];
4988
+ dependentOnly = dependentIds;
4989
+ }
4990
+ const runs = [];
4991
+ const baseWanted = only ? (baseOnly?.length ?? 0) > 0 : independentCats.length > 0;
4992
+ if (baseWanted) {
4993
+ runs.push({
4994
+ targetId: null,
4995
+ config: baseConfig,
4996
+ category: only ? undefined : independentCats,
4997
+ only: baseOnly
4998
+ });
4999
+ }
5000
+ const dependentWanted = only ? (dependentOnly?.length ?? 0) > 0 : dependentCats.length > 0;
5001
+ if (dependentWanted) {
5002
+ for (const targetId of targetIds) {
5003
+ runs.push({
5004
+ targetId,
5005
+ config: finalize(resolveConfigForTarget({}, manifest, targetId)),
5006
+ category: only ? undefined : dependentCats,
5007
+ only: dependentOnly
5008
+ });
5009
+ }
5010
+ }
5011
+ return runs;
5012
+ }
4754
5013
  async function executeValidation(args) {
4755
5014
  const manifestPath = typeof args.manifest === "string" ? args.manifest : undefined;
4756
5015
  const { manifest, manifestDir } = await loadManifestWithPath(manifestPath ?? args.input ?? undefined, { explicit: manifestPath !== undefined });
4757
- const profile = parseProfile(typeof args.profile === "string" ? args.profile : undefined);
4758
- let config = resolveConfig({}, manifest);
5016
+ const config = resolveConfig({}, manifest);
5017
+ const targetIds = resolveTargets(parseCsv(typeof args.target === "string" ? args.target : undefined), config.targets);
4759
5018
  const pdfPath = typeof args.pdf === "string" ? resolve6(args.pdf) : undefined;
4760
5019
  const inputDir = typeof args.input === "string" ? resolve6(args.input) : undefined;
4761
- if (pdfPath) {
4762
- config = applyDtrpgPdfDefaults(config);
4763
- }
4764
- if (profile) {
4765
- config = applyValidationProfile(config, profile);
4766
- }
4767
5020
  if (pdfPath && !existsSync7(pdfPath)) {
4768
5021
  throw new Error(`File not found: ${pdfPath}`);
4769
5022
  }
@@ -4792,11 +5045,11 @@ async function executeValidation(args) {
4792
5045
  let htmlPath;
4793
5046
  const outDir = resolveOutputDir(manifestDir, config.title);
4794
5047
  if (inputDir) {
4795
- const relMarkdown = await resolveActiveMarkdownFiles(inputDir, config.source.files);
4796
- markdownFiles = relMarkdown.map((f) => join2(inputDir, canonicalChapterId(f)));
4797
- const relStyles = await resolveActiveStyles(inputDir, config.styles);
4798
- cssFiles = relStyles.map((rel) => resolve6(inputDir, rel)).filter((f) => !f.endsWith(".min.css"));
4799
- assetDirs = [inputDir];
5048
+ const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.source.files);
5049
+ markdownFiles = relMarkdown.map((f) => join2(manifestDir, canonicalChapterId(f)));
5050
+ const relStyles = await resolveActiveStyles(manifestDir, config.styles);
5051
+ cssFiles = relStyles.map((rel) => resolve6(manifestDir, rel)).filter((f) => !f.endsWith(".min.css"));
5052
+ assetDirs = [inputDir, ...await sharedAssetDirs(manifestDir, relStyles, inputDir)];
4800
5053
  const possibleHtml = join2(outDir, BOOK_HTML_FILENAME);
4801
5054
  if (existsSync7(possibleHtml)) {
4802
5055
  htmlPath = possibleHtml;
@@ -4818,15 +5071,30 @@ async function executeValidation(args) {
4818
5071
  only,
4819
5072
  skip
4820
5073
  };
4821
- const tools = await checkToolAvailability(config, runnerOptions);
4822
- const initialReport = await runChecks(context, {
4823
- ...runnerOptions,
4824
- skipMissingTools: tools.skippedChecks
4825
- });
4826
- const report = withProfileRequiredCheckErrors(initialReport, tools, profile);
5074
+ const runs = planRuns({ manifest, baseConfig: config, targetIds, pdfPath, categories, only });
5075
+ const reports = [];
5076
+ const toolResults = [];
5077
+ for (const run of runs) {
5078
+ const runOptions = {
5079
+ category: run.category,
5080
+ phase,
5081
+ only: run.only,
5082
+ skip
5083
+ };
5084
+ const runTools = await checkToolAvailability(run.config, runOptions);
5085
+ toolResults.push(runTools);
5086
+ let runReport = await runChecks({ ...context, config: run.config }, { ...runOptions, skipMissingTools: runTools.skippedChecks });
5087
+ if (run.targetId !== null) {
5088
+ runReport = withTargetRequiredCheckErrors(runReport, runTools, publishTargetFor(run.targetId));
5089
+ runReport = tagReport(runReport, run.targetId);
5090
+ }
5091
+ reports.push(runReport);
5092
+ }
5093
+ const tools = mergeTools(toolResults);
5094
+ const report = mergeReports(reports);
4827
5095
  return {
4828
5096
  config,
4829
- profile,
5097
+ targets: targetIds,
4830
5098
  context,
4831
5099
  runnerOptions,
4832
5100
  tools,
@@ -4887,7 +5155,7 @@ import { createHash as createHash3 } from "node:crypto";
4887
5155
  import { existsSync as existsSync8, lstatSync, realpathSync, rmSync, statSync as statSync2, unlinkSync } from "node:fs";
4888
5156
  import { cp, link, mkdir as mkdir2, mkdtemp, readFile as readFile16, rm, unlink, writeFile as writeFile2 } from "node:fs/promises";
4889
5157
  import { tmpdir } from "node:os";
4890
- import { resolve as resolve7, join as join3, dirname as dirname3, basename, extname, relative, isAbsolute } from "node:path";
5158
+ import { resolve as resolve7, join as join3, dirname as dirname4, basename, extname, relative, isAbsolute } from "node:path";
4891
5159
  import { fileURLToPath, pathToFileURL } from "node:url";
4892
5160
  import Module, { builtinModules, createRequire } from "node:module";
4893
5161
  import { parse as parseJavaScript } from "acorn";
@@ -5256,7 +5524,7 @@ async function createIsolatedVendorTree(verified, address) {
5256
5524
  for (let index = 0;index < sorted.length; index++) {
5257
5525
  const source = sorted[index];
5258
5526
  const destination = join3(loadRoot, "packages", String(index), "package");
5259
- await mkdir2(dirname3(destination), { recursive: true });
5527
+ await mkdir2(dirname4(destination), { recursive: true });
5260
5528
  await cp(source.packageDir, destination, {
5261
5529
  recursive: true,
5262
5530
  filter: (candidate) => isolatedPackageCopyFilter(source.packageDir, candidate)
@@ -5315,7 +5583,7 @@ async function snapshotAndIsolateVendoredPlugin(baseDir, packageName, version) {
5315
5583
  const snapshotProject = join3(container, "project");
5316
5584
  const snapshotRoot = vendoredNpmPluginRoot(snapshotProject, packageName, version);
5317
5585
  try {
5318
- await mkdir2(dirname3(snapshotRoot), { recursive: true });
5586
+ await mkdir2(dirname4(snapshotRoot), { recursive: true });
5319
5587
  await cp(sourceRoot, snapshotRoot, {
5320
5588
  recursive: true,
5321
5589
  dereference: false,
@@ -5453,7 +5721,7 @@ function shadowPathFor(pluginPath, mtimeMs) {
5453
5721
  const ext = extname(pluginPath);
5454
5722
  const stem = basename(pluginPath, ext);
5455
5723
  const token = String(mtimeMs).replace(/\./g, "-");
5456
- return join3(dirname3(pluginPath), `.${stem}.gutterpress-reload-${token}${ext}`);
5724
+ return join3(dirname4(pluginPath), `.${stem}.gutterpress-reload-${process.pid}-${token}${ext}`);
5457
5725
  }
5458
5726
  async function loadCachedPathPluginModule(pluginPath) {
5459
5727
  const mtimeMs = statSync2(pluginPath).mtimeMs;
@@ -5731,7 +5999,7 @@ async function writeBuildFingerprint(input) {
5731
5999
  iccPath: input.pdfx.iccPath,
5732
6000
  stripAnnotations: input.pdfx.stripAnnotations
5733
6001
  },
5734
- outputDir
6002
+ outputDir: input.recordedOutputDir ? path5.resolve(input.recordedOutputDir) : outputDir
5735
6003
  },
5736
6004
  sourceRevision,
5737
6005
  tools
@@ -5745,14 +6013,14 @@ async function writeBuildFingerprint(input) {
5745
6013
  import { existsSync as existsSync9 } from "node:fs";
5746
6014
  import { mkdtemp as mkdtemp2, mkdir as mkdir4, writeFile as writeFile4, readFile as readFile17 } from "node:fs/promises";
5747
6015
  import { tmpdir as tmpdir2 } from "node:os";
5748
- import { dirname as dirname4, join as join4, resolve as resolve8 } from "node:path";
6016
+ import { dirname as dirname5, join as join4, resolve as resolve8 } from "node:path";
5749
6017
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5750
6018
 
5751
6019
  // src/assets/favicon.ico
5752
6020
  var favicon_default = "./favicon-wkbm9cjn.ico";
5753
6021
 
5754
6022
  // src/assets/manifest.schema.json
5755
- var manifest_schema_default = "./manifest.schema-rn1dpdpn.json";
6023
+ var manifest_schema_default = "./manifest.schema-z61rzw44.json";
5756
6024
 
5757
6025
  // src/assets/preview/scripts/pagedjs-interface.js
5758
6026
  var pagedjs_interface_default = "./pagedjs-interface-80g3xa8p.js";
@@ -5775,23 +6043,17 @@ var manifest_default = "./manifest-n1gh3qw5.yaml";
5775
6043
  // src/assets/templates/book/chapter-01.md
5776
6044
  var chapter_01_default = "./chapter-01-rkpyvjna.md";
5777
6045
 
5778
- // src/assets/templates/ttrpg/manifest.yaml
5779
- var manifest_default2 = "./manifest-ryyg71nf.yaml";
5780
-
5781
- // src/assets/templates/ttrpg/chapter-01.md
5782
- var chapter_01_default2 = "./chapter-01-jj8ftn4c.md";
5783
-
5784
6046
  // src/assets/templates/zine/manifest.yaml
5785
- var manifest_default3 = "./manifest-n1gh3qw5.yaml";
6047
+ var manifest_default2 = "./manifest-n1gh3qw5.yaml";
5786
6048
 
5787
6049
  // src/assets/templates/zine/chapter-01.md
5788
- var chapter_01_default3 = "./chapter-01-7jwqg1ct.md";
6050
+ var chapter_01_default2 = "./chapter-01-7jwqg1ct.md";
5789
6051
 
5790
6052
  // src/assets/templates/technical/manifest.yaml
5791
- var manifest_default4 = "./manifest-n1gh3qw5.yaml";
6053
+ var manifest_default3 = "./manifest-n1gh3qw5.yaml";
5792
6054
 
5793
6055
  // src/assets/templates/technical/chapter-01.md
5794
- var chapter_01_default4 = "./chapter-01-6fn9pvd0.md";
6056
+ var chapter_01_default3 = "./chapter-01-6fn9pvd0.md";
5795
6057
 
5796
6058
  // src/assets/themes/clean-book/theme.css
5797
6059
  var theme_default = "./theme-570zmh2t.css";
@@ -5799,26 +6061,20 @@ var theme_default = "./theme-570zmh2t.css";
5799
6061
  // src/assets/themes/clean-book/theme.json
5800
6062
  var theme_default2 = "./theme-xt0hjb1b.json";
5801
6063
 
5802
- // src/assets/themes/ttrpg-supplement/theme.css
5803
- var theme_default3 = "./theme-0yzvthe8.css";
5804
-
5805
- // src/assets/themes/ttrpg-supplement/theme.json
5806
- var theme_default4 = "./theme-6a2fngc8.json";
5807
-
5808
6064
  // src/assets/themes/zine/theme.css
5809
- var theme_default5 = "./theme-zz2ktzqs.css";
6065
+ var theme_default3 = "./theme-zz2ktzqs.css";
5810
6066
 
5811
6067
  // src/assets/themes/zine/theme.json
5812
- var theme_default6 = "./theme-cd6efzjw.json";
6068
+ var theme_default4 = "./theme-cd6efzjw.json";
5813
6069
 
5814
6070
  // src/assets/themes/technical-doc/theme.css
5815
- var theme_default7 = "./theme-nya4nqh6.css";
6071
+ var theme_default5 = "./theme-nya4nqh6.css";
5816
6072
 
5817
6073
  // src/assets/themes/technical-doc/theme.json
5818
- var theme_default8 = "./theme-tqfypc1w.json";
6074
+ var theme_default6 = "./theme-tqfypc1w.json";
5819
6075
 
5820
6076
  // src/lib/embedded-assets.ts
5821
- var __libdir = dirname4(fileURLToPath2(import.meta.url));
6077
+ var __libdir = dirname5(fileURLToPath2(import.meta.url));
5822
6078
  var abs = (p) => resolve8(__libdir, p);
5823
6079
  var filePath = (v) => v;
5824
6080
  var EMBEDDED_ASSETS = {
@@ -5831,27 +6087,23 @@ var EMBEDDED_ASSETS = {
5831
6087
  "profiles/CGATS21_CRPC1.icc": abs(filePath(CGATS21_CRPC1_default)),
5832
6088
  "templates/book/manifest.yaml": abs(filePath(manifest_default)),
5833
6089
  "templates/book/chapter-01.md": abs(filePath(chapter_01_default)),
5834
- "templates/ttrpg/manifest.yaml": abs(filePath(manifest_default2)),
5835
- "templates/ttrpg/chapter-01.md": abs(filePath(chapter_01_default2)),
5836
- "templates/zine/manifest.yaml": abs(filePath(manifest_default3)),
5837
- "templates/zine/chapter-01.md": abs(filePath(chapter_01_default3)),
5838
- "templates/technical/manifest.yaml": abs(filePath(manifest_default4)),
5839
- "templates/technical/chapter-01.md": abs(filePath(chapter_01_default4)),
6090
+ "templates/zine/manifest.yaml": abs(filePath(manifest_default2)),
6091
+ "templates/zine/chapter-01.md": abs(filePath(chapter_01_default2)),
6092
+ "templates/technical/manifest.yaml": abs(filePath(manifest_default3)),
6093
+ "templates/technical/chapter-01.md": abs(filePath(chapter_01_default3)),
5840
6094
  "themes/clean-book/theme.css": abs(filePath(theme_default)),
5841
6095
  "themes/clean-book/theme.json": abs(filePath(theme_default2)),
5842
- "themes/ttrpg-supplement/theme.css": abs(filePath(theme_default3)),
5843
- "themes/ttrpg-supplement/theme.json": abs(filePath(theme_default4)),
5844
- "themes/zine/theme.css": abs(filePath(theme_default5)),
5845
- "themes/zine/theme.json": abs(filePath(theme_default6)),
5846
- "themes/technical-doc/theme.css": abs(filePath(theme_default7)),
5847
- "themes/technical-doc/theme.json": abs(filePath(theme_default8))
6096
+ "themes/zine/theme.css": abs(filePath(theme_default3)),
6097
+ "themes/zine/theme.json": abs(filePath(theme_default4)),
6098
+ "themes/technical-doc/theme.css": abs(filePath(theme_default5)),
6099
+ "themes/technical-doc/theme.json": abs(filePath(theme_default6))
5848
6100
  };
5849
6101
  var extractPromise = null;
5850
6102
  async function extractAssets() {
5851
6103
  const root = await mkdtemp2(join4(tmpdir2(), "gutterpress-assets-"));
5852
6104
  for (const [relPath, srcPath] of Object.entries(EMBEDDED_ASSETS)) {
5853
6105
  const dest = join4(root, relPath);
5854
- await mkdir4(dirname4(dest), { recursive: true });
6106
+ await mkdir4(dirname5(dest), { recursive: true });
5855
6107
  const bytes = await readFile17(srcPath);
5856
6108
  await writeFile4(dest, bytes);
5857
6109
  }
@@ -5905,7 +6157,7 @@ ${m.installHint}`).join(`
5905
6157
 
5906
6158
  ${list}
5907
6159
 
5908
- 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 8 (System Setup) at examples/gutterpress-user-guide/08-system-setup.md for the full per-feature matrix.`, 2);
6160
+ 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);
5909
6161
  }
5910
6162
  function computeGates(format, opts, config) {
5911
6163
  if (format === "html") {
@@ -5999,6 +6251,15 @@ function resolveWithinRoot(relPath, root) {
5999
6251
  }
6000
6252
  return candidate;
6001
6253
  }
6254
+ function hasDotSegment(urlPathname) {
6255
+ let decoded;
6256
+ try {
6257
+ decoded = decodeURIComponent(urlPathname);
6258
+ } catch {
6259
+ return true;
6260
+ }
6261
+ return decoded.split(/[/\\]/).some((segment) => segment.startsWith("."));
6262
+ }
6002
6263
  function resolveStaticPath(urlPathname, root) {
6003
6264
  let decoded;
6004
6265
  try {
@@ -6397,14 +6658,25 @@ async function resolveBuildContext(opts) {
6397
6658
  const outDir = target.kind === "file" ? path9.dirname(target.file) : target.dir;
6398
6659
  const gates = computeGates(format, opts, config);
6399
6660
  const workDir = target.kind === "project" ? path9.join(path9.dirname(target.dir), `.${path9.basename(target.dir)}-build-${randomBytes(6).toString("hex")}`) : path9.join(os2.tmpdir(), `gutterpress-build-${randomBytes(6).toString("hex")}`);
6400
- return { opts, format, inputDir, outDir, workDir, target, manifestDir, config, gates };
6661
+ return {
6662
+ opts,
6663
+ format,
6664
+ inputDir,
6665
+ renderDir: manifestDir,
6666
+ outDir,
6667
+ workDir,
6668
+ target,
6669
+ manifestDir,
6670
+ config,
6671
+ gates
6672
+ };
6401
6673
  }
6402
6674
  async function runQualityGates(ctx) {
6403
- const { gates, opts, inputDir } = ctx;
6675
+ const { gates, opts, renderDir } = ctx;
6404
6676
  if (gates.lint) {
6405
6677
  log.info("Lint: CSS print-safety");
6406
6678
  const lintResult = await runLint({
6407
- manifest: opts.manifestPath ?? inputDir
6679
+ manifest: opts.manifestPath ?? renderDir
6408
6680
  });
6409
6681
  if (!lintResult.ok) {
6410
6682
  throw new BuildError("CSS lint failed", 2);
@@ -6413,7 +6685,7 @@ async function runQualityGates(ctx) {
6413
6685
  if (gates.preValidate) {
6414
6686
  log.info("Pre-build validation");
6415
6687
  const result = await executeAndReport({
6416
- input: inputDir,
6688
+ input: renderDir,
6417
6689
  phase: "pre-build",
6418
6690
  manifest: opts.manifestPath
6419
6691
  }, "text");
@@ -6423,7 +6695,7 @@ async function runQualityGates(ctx) {
6423
6695
  }
6424
6696
  }
6425
6697
  async function renderBook(ctx) {
6426
- const { config, manifestDir, inputDir, workDir } = ctx;
6698
+ const { config, renderDir, workDir } = ctx;
6427
6699
  if (config.source.files && config.source.files.length > 0) {
6428
6700
  log.info(`Using specified files (${config.source.files.length} total)`);
6429
6701
  } else {
@@ -6432,13 +6704,13 @@ async function renderBook(ctx) {
6432
6704
  if (config.plugins.length > 0) {
6433
6705
  log.info(`Loading ${config.plugins.length} plugin(s)...`);
6434
6706
  }
6435
- const { plugins, pluginCss } = await loadPluginsWithCss(config.plugins, manifestDir);
6707
+ const { plugins, pluginCss } = await loadPluginsWithCss(config.plugins, renderDir);
6436
6708
  if (plugins && plugins.length > 0) {
6437
6709
  log.success(`Loaded ${plugins.length} plugin(s)`);
6438
6710
  }
6439
6711
  const imageRefs = [];
6440
6712
  const cssAssets = [];
6441
- const htmlFile = await renderChaptersToFile(inputDir, workDir, {
6713
+ const htmlFile = await renderChaptersToFile(renderDir, workDir, {
6442
6714
  title: config.title,
6443
6715
  styles: config.styles,
6444
6716
  files: config.source.files,
@@ -6457,7 +6729,7 @@ async function renderBook(ctx) {
6457
6729
  }
6458
6730
  });
6459
6731
  log.success(`Wrote ${htmlFile}`);
6460
- const { copies: imageCopies, errors } = await planImageCopies(inputDir, imageRefs);
6732
+ const { copies: imageCopies, errors } = await planImageCopies(renderDir, imageRefs);
6461
6733
  if (errors.length > 0) {
6462
6734
  throw new BuildError(`Cannot resolve ${errors.length} image reference(s):
6463
6735
  ` + errors.map((e) => ` - ${e}`).join(`
@@ -6496,7 +6768,8 @@ async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
6496
6768
  async function finalizeBuild(ctx, fingerprint, wroteMessage, paths, artifactName2 = null) {
6497
6769
  const workFingerprint = await writeBuildFingerprint({
6498
6770
  ...fingerprint,
6499
- outputDir: ctx.workDir
6771
+ outputDir: ctx.workDir,
6772
+ recordedOutputDir: fingerprint.outputDir
6500
6773
  });
6501
6774
  await publishBuild(ctx, artifactName2);
6502
6775
  const delivered = ctx.target.kind !== "file";
@@ -6587,7 +6860,9 @@ class PdfOutput {
6587
6860
  await renderHtmlToPdf(htmlFile, rawPdf, opts.pdfRenderer, staticHtmlRaw);
6588
6861
  if (staticHtmlRaw && fs2.existsSync(staticHtmlRaw)) {
6589
6862
  await finalizeStaticBook(await fsp3.readFile(staticHtmlRaw, "utf-8"), htmlFile, workDir);
6590
- log.success(`Wrote static desktop: ${path9.join(outDir, BOOK_HTML)}`);
6863
+ if (ctx.target.kind !== "file") {
6864
+ log.success(`Wrote static desktop: ${path9.join(outDir, BOOK_HTML)}`);
6865
+ }
6591
6866
  }
6592
6867
  if (!pdfxMode) {
6593
6868
  try {
@@ -6749,6 +7024,7 @@ async function renderPreviewBook(inputPath, config, opts) {
6749
7024
  plugins,
6750
7025
  pluginCss,
6751
7026
  wrapChapters: opts.wrapChapters,
7027
+ ...opts.onCssAssets ? { onCssAssets: opts.onCssAssets } : {},
6752
7028
  onChapterWarnings: (file, warnings) => {
6753
7029
  for (const w of warnings) {
6754
7030
  warn(` ${file}, line ${w.line}: ${w.message}`);
@@ -6763,15 +7039,24 @@ function injectPreviewScripts(html) {
6763
7039
  return html.replace(pagedjsPolyfillTagRegex(), iface + BREAK_INSIDE_HANDLER + `
6764
7040
  <script src="/vendor/paged.polyfill.js"></script>`);
6765
7041
  }
6766
- async function generateAndWriteHtml(inputPath, tempDir, config) {
7042
+ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
6767
7043
  if (!inputPath) {
7044
+ cssAssets.clear();
6768
7045
  await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
6769
7046
  return;
6770
7047
  }
7048
+ const nextAssets = new Map;
6771
7049
  const html = await renderPreviewBook(inputPath, config, {
6772
7050
  files: config.source?.files ?? null,
6773
- wrapChapters: true
7051
+ wrapChapters: true,
7052
+ onCssAssets: (copies) => {
7053
+ for (const copy of copies)
7054
+ nextAssets.set(copy.to, copy.from);
7055
+ }
6774
7056
  });
7057
+ cssAssets.clear();
7058
+ for (const [to, from] of nextAssets)
7059
+ cssAssets.set(to, from);
6775
7060
  await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
6776
7061
  }
6777
7062
  function isDotPathUnderRoot(candidatePath, root) {
@@ -6789,11 +7074,24 @@ function isDotPathUnderRoot(candidatePath, root) {
6789
7074
  return false;
6790
7075
  return rel.split("/").some((segment) => segment.startsWith("."));
6791
7076
  }
7077
+ function isGeneratedProjectPath(relative2) {
7078
+ const segments = relative2.split("/");
7079
+ for (let i = 0;i < segments.length; i++) {
7080
+ const segment = segments[i];
7081
+ if (segment === "dist" || segment === "node_modules")
7082
+ return true;
7083
+ if (segment === "plugins" && segments[i + 1] === "npm")
7084
+ return true;
7085
+ }
7086
+ return false;
7087
+ }
6792
7088
  function isIgnoredWatchPath(candidatePath, projectRoot) {
6793
7089
  const normalizedRoot = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
6794
7090
  const normalizedPath = candidatePath.replace(/\\/g, "/");
6795
- const inProject = normalizedPath === normalizedRoot || normalizedPath.startsWith(normalizedRoot + "/");
6796
- return inProject ? isDotPathUnderRoot(candidatePath, projectRoot) : false;
7091
+ if (!normalizedPath.startsWith(normalizedRoot + "/"))
7092
+ return false;
7093
+ const relative2 = normalizedPath.slice(normalizedRoot.length + 1);
7094
+ return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
6797
7095
  }
6798
7096
  function isExternalWatchCandidate(candidatePath, targets) {
6799
7097
  const candidate = path10.resolve(candidatePath);
@@ -6972,7 +7270,7 @@ function createFileWatcher(state) {
6972
7270
  await syncExternalWatches();
6973
7271
  if (closed)
6974
7272
  return;
6975
- await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig);
7273
+ await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig, state.cssAssets);
6976
7274
  if (closed)
6977
7275
  return;
6978
7276
  state.previewServer?.broadcastReload();
@@ -7089,7 +7387,7 @@ async function restartPreview(newInputPath, state) {
7089
7387
  state.currentInputPath = newInputPath;
7090
7388
  const manifest = await loadManifest(newInputPath);
7091
7389
  state.config = resolveConfig({}, manifest);
7092
- await generateAndWriteHtml(newInputPath, state.tempDir, state.config);
7390
+ await generateAndWriteHtml(newInputPath, state.tempDir, state.config, state.cssAssets);
7093
7391
  startFileWatcher(state);
7094
7392
  state.previewServer?.broadcastReload();
7095
7393
  info("Preview restarted successfully");
@@ -7147,7 +7445,8 @@ function createServerState(inputPath, tempDir, config, options) {
7147
7445
  isShuttingDown: false,
7148
7446
  tempDir,
7149
7447
  config,
7150
- options
7448
+ options,
7449
+ cssAssets: new Map
7151
7450
  };
7152
7451
  }
7153
7452
 
@@ -7350,15 +7649,6 @@ function matchesEmbedded(urlPathname) {
7350
7649
  return true;
7351
7650
  return EMBEDDED_PREFIXES.some((p) => urlPathname.startsWith(p));
7352
7651
  }
7353
- function isDotfileRequest(urlPathname) {
7354
- let decoded;
7355
- try {
7356
- decoded = decodeURIComponent(urlPathname);
7357
- } catch {
7358
- return true;
7359
- }
7360
- return decoded.split("/").some((segment) => segment.startsWith("."));
7361
- }
7362
7652
  async function createPreviewServer(state, port) {
7363
7653
  const clients = new Set;
7364
7654
  const wss = new WebSocketServer({ noServer: true });
@@ -7409,8 +7699,18 @@ async function createPreviewServer(state, port) {
7409
7699
  return;
7410
7700
  }
7411
7701
  const pathname = url.pathname === "/" ? "/book.html" : url.pathname;
7702
+ const cssAssetSource = state.cssAssets.get(pathname.replace(/^\/+/, ""));
7703
+ if (cssAssetSource) {
7704
+ await serveStatic(cssAssetSource, res).catch((err) => {
7705
+ if (!res.headersSent) {
7706
+ res.writeHead(500);
7707
+ res.end(`Internal Server Error: ${err.message}`);
7708
+ }
7709
+ });
7710
+ return;
7711
+ }
7412
7712
  const servingProjectRoot = pathname !== "/book.html";
7413
- if (servingProjectRoot && (!state.currentInputPath || isDotfileRequest(pathname))) {
7713
+ if (servingProjectRoot && (!state.currentInputPath || hasDotSegment(pathname))) {
7414
7714
  res.writeHead(404);
7415
7715
  res.end("Not Found");
7416
7716
  return;
@@ -7495,8 +7795,8 @@ async function startPreviewServer(options) {
7495
7795
  }
7496
7796
  const config = await initializeConfiguration(inputPath);
7497
7797
  const tempDir = await initializePreviewDirectories();
7498
- await generateAndWriteHtml(inputPath, tempDir, config);
7499
7798
  const state = createServerState(inputPath, tempDir, config, options);
7799
+ await generateAndWriteHtml(inputPath, tempDir, config, state.cssAssets);
7500
7800
  const restartPreview2 = async (newInputPath) => {
7501
7801
  await restartPreview(newInputPath, state);
7502
7802
  };
@@ -7534,12 +7834,66 @@ async function startPreviewServer(options) {
7534
7834
  }
7535
7835
 
7536
7836
  // src/lib/project-scaffold.ts
7537
- import { access, copyFile, cp as cp2, mkdir as mkdir6, readdir as readdir5, readFile as readFile21, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
7538
- import { constants as FS, existsSync as existsSync10 } from "node:fs";
7837
+ import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile22, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
7838
+ import { constants as FS, existsSync as existsSync11 } from "node:fs";
7839
+ import path14 from "node:path";
7840
+
7841
+ // src/lib/manifest-doc.ts
7842
+ import { existsSync as existsSync10 } from "node:fs";
7843
+ import { randomUUID } from "node:crypto";
7844
+ import { mkdir as mkdir6, open, readFile as readFile21, rename, rm as rm2 } from "node:fs/promises";
7539
7845
  import path13 from "node:path";
7846
+ import { parseDocument, isSeq, YAMLSeq } from "yaml";
7847
+ function resolveManifestPath(projectDir) {
7848
+ const existing = MANIFEST_FILENAMES.find((name) => existsSync10(path13.join(projectDir, name)));
7849
+ return path13.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
7850
+ }
7851
+ async function loadManifestDoc(projectDir) {
7852
+ const file = resolveManifestPath(projectDir);
7853
+ let text = "";
7854
+ try {
7855
+ text = await readFile21(file, "utf8");
7856
+ } catch (error2) {
7857
+ if (error2.code !== "ENOENT")
7858
+ throw error2;
7859
+ }
7860
+ return { doc: parseDocument(text), file };
7861
+ }
7862
+ async function writeManifestDoc(file, doc) {
7863
+ const dir = path13.dirname(file);
7864
+ await mkdir6(dir, { recursive: true });
7865
+ const temporary = path13.join(dir, `.${path13.basename(file)}.tmp-${process.pid}-${randomUUID()}`);
7866
+ try {
7867
+ const handle = await open(temporary, "wx", 438);
7868
+ try {
7869
+ await handle.writeFile(doc.toString(), "utf8");
7870
+ await handle.sync();
7871
+ } finally {
7872
+ await handle.close();
7873
+ }
7874
+ await rename(temporary, file);
7875
+ const directory = await open(dir, "r").catch(() => null);
7876
+ if (directory) {
7877
+ await directory.sync().catch(() => {});
7878
+ await directory.close();
7879
+ }
7880
+ } finally {
7881
+ await rm2(temporary, { force: true });
7882
+ }
7883
+ }
7884
+ function ensureSeq(doc, key) {
7885
+ let seq = doc.get(key, true);
7886
+ if (!isSeq(seq)) {
7887
+ const fresh = new YAMLSeq(doc.schema);
7888
+ doc.set(key, fresh);
7889
+ seq = fresh;
7890
+ }
7891
+ return seq;
7892
+ }
7893
+
7894
+ // src/lib/project-scaffold.ts
7540
7895
  var STARTER_THEME_FOR_TEMPLATE = {
7541
7896
  book: "clean-book",
7542
- ttrpg: "ttrpg-supplement",
7543
7897
  zine: "zine",
7544
7898
  technical: "technical-doc"
7545
7899
  };
@@ -7559,6 +7913,39 @@ function escapeYamlScalar(value) {
7559
7913
  return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
7560
7914
  }
7561
7915
  var DEFAULT_AUTHOR = "Anonymous";
7916
+ function isPositivePoints(value) {
7917
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
7918
+ }
7919
+ function requirePreset(preset, customPage) {
7920
+ if (!preset || !PRESET_IDS.includes(preset)) {
7921
+ const got = preset ? ` (got "${preset}")` : "";
7922
+ throw new CreateProjectErrorImpl("preset-required", `A preset is required to create a book${got}. Choose one of: ${PRESET_IDS.join(", ")}.`);
7923
+ }
7924
+ if (preset === "custom") {
7925
+ const missing = [];
7926
+ if (!customPage || !isPositivePoints(customPage.width))
7927
+ missing.push("page width");
7928
+ if (!customPage || !isPositivePoints(customPage.height))
7929
+ missing.push("page height");
7930
+ if (missing.length > 0) {
7931
+ throw new CreateProjectErrorImpl("custom-page-required", `The custom preset needs a trim size: ${missing.join(" and ")} in points ` + `(72pt = 1in — e.g. US Letter is 612 x 792).`);
7932
+ }
7933
+ } else if (customPage && (!isPositivePoints(customPage.width) || !isPositivePoints(customPage.height))) {
7934
+ throw new CreateProjectErrorImpl("custom-page-required", "Page overrides need a positive width and height in points (72pt = 1in).");
7935
+ }
7936
+ if (customPage?.tolerance !== undefined && !isPositivePoints(customPage.tolerance)) {
7937
+ throw new CreateProjectErrorImpl("custom-page-required", "Page tolerance must be a positive number of points.");
7938
+ }
7939
+ return preset;
7940
+ }
7941
+ function resolveScaffoldTargets(requested, preset) {
7942
+ const ids = requested ?? [...PRESETS[preset].defaultTargets];
7943
+ const unknown = ids.filter((id) => !TARGETS[id]);
7944
+ if (unknown.length > 0) {
7945
+ throw new CreateProjectErrorImpl("invalid-targets", `Unknown publish target${unknown.length > 1 ? "s" : ""} "${unknown.join('", "')}". ` + `Known targets: ${TARGET_IDS.join(", ")}.`);
7946
+ }
7947
+ return [...new Set(ids)];
7948
+ }
7562
7949
  async function scaffoldProject(options) {
7563
7950
  const name = (options.name ?? "").trim();
7564
7951
  if (!name) {
@@ -7570,7 +7957,7 @@ async function scaffoldProject(options) {
7570
7957
  throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
7571
7958
  }
7572
7959
  const parentDir = options.parentDir;
7573
- if (!parentDir || !path13.isAbsolute(parentDir)) {
7960
+ if (!parentDir || !path14.isAbsolute(parentDir)) {
7574
7961
  throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
7575
7962
  }
7576
7963
  try {
@@ -7578,7 +7965,7 @@ async function scaffoldProject(options) {
7578
7965
  } catch {
7579
7966
  throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
7580
7967
  }
7581
- const projectDir = path13.join(parentDir, folderName);
7968
+ const projectDir = path14.join(parentDir, folderName);
7582
7969
  let targetExists = true;
7583
7970
  try {
7584
7971
  await access(projectDir, FS.F_OK);
@@ -7590,26 +7977,28 @@ async function scaffoldProject(options) {
7590
7977
  }
7591
7978
  const template = options.template ?? "book";
7592
7979
  const customTemplateDir = options.templateDir;
7980
+ const preset = customTemplateDir && options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
7981
+ const targets = preset ? resolveScaffoldTargets(options.targets, preset) : undefined;
7593
7982
  try {
7594
- await mkdir6(projectDir, { recursive: true });
7983
+ await mkdir7(projectDir, { recursive: true });
7595
7984
  if (customTemplateDir) {
7596
7985
  const entries = await readdir5(customTemplateDir, { withFileTypes: true });
7597
7986
  for (const entry of entries) {
7598
7987
  if (entry.name === ".gutterpress-template.json")
7599
7988
  continue;
7600
- await cp2(path13.join(customTemplateDir, entry.name), path13.join(projectDir, entry.name), { recursive: true });
7989
+ await cp2(path14.join(customTemplateDir, entry.name), path14.join(projectDir, entry.name), { recursive: true });
7601
7990
  }
7602
- await mkdir6(path13.join(projectDir, "assets"), { recursive: true });
7991
+ await mkdir7(path14.join(projectDir, "assets"), { recursive: true });
7603
7992
  } else {
7604
7993
  const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
7605
7994
  const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
7606
- await mkdir6(path13.join(projectDir, "assets"), { recursive: true });
7607
- await copyFile(tplManifest, path13.join(projectDir, MANIFEST_FILENAMES[0]));
7608
- await copyFile(tplChapter, path13.join(projectDir, "chapter-01.md"));
7995
+ await mkdir7(path14.join(projectDir, "assets"), { recursive: true });
7996
+ await copyFile(tplManifest, path14.join(projectDir, MANIFEST_FILENAMES[0]));
7997
+ await copyFile(tplChapter, path14.join(projectDir, "chapter-01.md"));
7609
7998
  const starterThemeId = STARTER_THEME_FOR_TEMPLATE[template] ?? "clean-book";
7610
7999
  const starterCss = await getAssetPath(`themes/${starterThemeId}/theme.css`);
7611
- await mkdir6(path13.join(projectDir, "styles"), { recursive: true });
7612
- await copyFile(starterCss, path13.join(projectDir, "styles", "book.css"));
8000
+ await mkdir7(path14.join(projectDir, "styles"), { recursive: true });
8001
+ await copyFile(starterCss, path14.join(projectDir, "styles", "book.css"));
7613
8002
  }
7614
8003
  } catch (e) {
7615
8004
  throw new CreateProjectErrorImpl("scaffold-io", `Could not create the project files: ${e instanceof Error ? e.message : String(e)}`);
@@ -7619,18 +8008,34 @@ async function scaffoldProject(options) {
7619
8008
  } catch (e) {
7620
8009
  throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
7621
8010
  }
7622
- const manifestPath = MANIFEST_FILENAMES.map((name2) => path13.join(projectDir, name2)).find(existsSync10) ?? path13.join(projectDir, MANIFEST_FILENAMES[0]);
8011
+ const manifestPath = MANIFEST_FILENAMES.map((name2) => path14.join(projectDir, name2)).find(existsSync11) ?? path14.join(projectDir, MANIFEST_FILENAMES[0]);
7623
8012
  const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
7624
8013
  const substitutions = {
7625
8014
  "{{TITLE}}": escapeYamlScalar(name),
7626
8015
  "{{AUTHOR}}": escapeYamlScalar(author)
7627
8016
  };
7628
- let openFile = path13.join(projectDir, "chapter-01.md");
8017
+ let openFile = path14.join(projectDir, "chapter-01.md");
7629
8018
  try {
7630
8019
  await fillTemplateFile(manifestPath, substitutions);
8020
+ if (preset) {
8021
+ const { doc, file } = await loadManifestDoc(projectDir);
8022
+ doc.set("preset", preset);
8023
+ doc.set("targets", doc.createNode(targets ?? []));
8024
+ if (options.customPage) {
8025
+ const page = {
8026
+ width: options.customPage.width,
8027
+ height: options.customPage.height
8028
+ };
8029
+ if (options.customPage.tolerance !== undefined) {
8030
+ page.tolerance = options.customPage.tolerance;
8031
+ }
8032
+ doc.set("page", doc.createNode(page));
8033
+ }
8034
+ await writeManifestDoc(file, doc);
8035
+ }
7631
8036
  const firstSource = await firstSourceFile(manifestPath);
7632
8037
  if (firstSource)
7633
- openFile = path13.join(projectDir, firstSource);
8038
+ openFile = path14.join(projectDir, firstSource);
7634
8039
  await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
7635
8040
  } catch (e) {
7636
8041
  throw new CreateProjectErrorImpl("scaffold-io", `Could not finalise the project files: ${e instanceof Error ? e.message : String(e)}`);
@@ -7640,7 +8045,7 @@ async function scaffoldProject(options) {
7640
8045
  let versionHistoryError;
7641
8046
  if (requested === "local-git") {
7642
8047
  try {
7643
- const { providerFor } = await import("./source-provider-hp96b5jf.js");
8048
+ const { providerFor } = await import("./source-provider-c1rjm2c0.js");
7644
8049
  const provider = providerFor({ type: "local-folder", path: projectDir });
7645
8050
  await provider.initVersionHistory({
7646
8051
  projectDir,
@@ -7650,9 +8055,9 @@ async function scaffoldProject(options) {
7650
8055
  versionHistory = "local-git";
7651
8056
  } catch (e) {
7652
8057
  versionHistory = "none";
7653
- const { findEnclosingRepoDir } = await import("./project-source-0tg0r6vm.js");
8058
+ const { findEnclosingRepoDir } = await import("./project-source-p0gn1wd5.js");
7654
8059
  const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
7655
- versionHistoryError = enclosingRepoDir ? `This book was added to ${path13.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
8060
+ versionHistoryError = enclosingRepoDir ? `This book was added to ${path14.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
7656
8061
  }
7657
8062
  }
7658
8063
  const result = {
@@ -7667,10 +8072,10 @@ async function scaffoldProject(options) {
7667
8072
  return result;
7668
8073
  }
7669
8074
  async function ensureGitignoreHasDist(projectDir) {
7670
- const gitignorePath = path13.join(projectDir, ".gitignore");
8075
+ const gitignorePath = path14.join(projectDir, ".gitignore");
7671
8076
  let existing = null;
7672
8077
  try {
7673
- existing = await readFile21(gitignorePath, "utf8");
8078
+ existing = await readFile22(gitignorePath, "utf8");
7674
8079
  } catch {}
7675
8080
  if (existing === null) {
7676
8081
  await writeFile6(gitignorePath, `dist/
@@ -7690,7 +8095,7 @@ async function ensureGitignoreHasDist(projectDir) {
7690
8095
  async function firstSourceFile(manifestPath) {
7691
8096
  let text;
7692
8097
  try {
7693
- text = await readFile21(manifestPath, "utf8");
8098
+ text = await readFile22(manifestPath, "utf8");
7694
8099
  } catch {
7695
8100
  return;
7696
8101
  }
@@ -7702,7 +8107,7 @@ async function firstSourceFile(manifestPath) {
7702
8107
  return m ? m[1].trim() : undefined;
7703
8108
  }
7704
8109
  async function fillTemplateFile(filePath2, substitutions) {
7705
- let text = await readFile21(filePath2, "utf8");
8110
+ let text = await readFile22(filePath2, "utf8");
7706
8111
  for (const [token, value] of Object.entries(substitutions)) {
7707
8112
  text = text.split(token).join(value);
7708
8113
  }
@@ -7710,9 +8115,9 @@ async function fillTemplateFile(filePath2, substitutions) {
7710
8115
  }
7711
8116
 
7712
8117
  // src/lib/project-templates.ts
8118
+ import { isMap, isScalar, isSeq as isSeq2, parseDocument as parseDocument2 } from "yaml";
7713
8119
  var BUILT_IN_TEMPLATE_IDS = [
7714
8120
  "book",
7715
- "ttrpg",
7716
8121
  "zine",
7717
8122
  "technical"
7718
8123
  ];
@@ -7728,60 +8133,7 @@ var SKIP_ENTRIES = new Set([
7728
8133
 
7729
8134
  // src/lib/plugin-manager.ts
7730
8135
  import path16 from "node:path";
7731
- import { isSeq as isSeq2, isMap, isScalar, YAMLMap, Scalar } from "yaml";
7732
-
7733
- // src/lib/manifest-doc.ts
7734
- import { existsSync as existsSync11 } from "node:fs";
7735
- import { randomUUID } from "node:crypto";
7736
- import { mkdir as mkdir7, open, readFile as readFile22, rename, rm as rm2 } from "node:fs/promises";
7737
- import path14 from "node:path";
7738
- import { parseDocument, isSeq, YAMLSeq } from "yaml";
7739
- function resolveManifestPath(projectDir) {
7740
- const existing = MANIFEST_FILENAMES.find((name) => existsSync11(path14.join(projectDir, name)));
7741
- return path14.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
7742
- }
7743
- async function loadManifestDoc(projectDir) {
7744
- const file = resolveManifestPath(projectDir);
7745
- let text = "";
7746
- try {
7747
- text = await readFile22(file, "utf8");
7748
- } catch (error2) {
7749
- if (error2.code !== "ENOENT")
7750
- throw error2;
7751
- }
7752
- return { doc: parseDocument(text), file };
7753
- }
7754
- async function writeManifestDoc(file, doc) {
7755
- const dir = path14.dirname(file);
7756
- await mkdir7(dir, { recursive: true });
7757
- const temporary = path14.join(dir, `.${path14.basename(file)}.tmp-${process.pid}-${randomUUID()}`);
7758
- try {
7759
- const handle = await open(temporary, "wx", 438);
7760
- try {
7761
- await handle.writeFile(doc.toString(), "utf8");
7762
- await handle.sync();
7763
- } finally {
7764
- await handle.close();
7765
- }
7766
- await rename(temporary, file);
7767
- const directory = await open(dir, "r").catch(() => null);
7768
- if (directory) {
7769
- await directory.sync().catch(() => {});
7770
- await directory.close();
7771
- }
7772
- } finally {
7773
- await rm2(temporary, { force: true });
7774
- }
7775
- }
7776
- function ensureSeq(doc, key) {
7777
- let seq = doc.get(key, true);
7778
- if (!isSeq(seq)) {
7779
- const fresh = new YAMLSeq(doc.schema);
7780
- doc.set(key, fresh);
7781
- seq = fresh;
7782
- }
7783
- return seq;
7784
- }
8136
+ import { isSeq as isSeq3, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar } from "yaml";
7785
8137
 
7786
8138
  // src/lib/npm-plugin-installer.ts
7787
8139
  import { createHash as createHash4, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
@@ -8549,9 +8901,9 @@ function isLocalRef2(ref) {
8549
8901
  return ref.startsWith("./") || ref.startsWith("../") || ref.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(ref) || /\.(m?js|cjs|ts)$/i.test(ref) || !ref.startsWith("@") && ref.includes("/");
8550
8902
  }
8551
8903
  function itemRef(item) {
8552
- if (isScalar(item))
8904
+ if (isScalar2(item))
8553
8905
  return typeof item.value === "string" ? item.value : null;
8554
- if (isMap(item)) {
8906
+ if (isMap2(item)) {
8555
8907
  const p = item.get("path");
8556
8908
  const n = item.get("name");
8557
8909
  if (typeof p === "string")
@@ -8567,7 +8919,7 @@ function refKind(ref) {
8567
8919
  async function listProjectPlugins(projectDir) {
8568
8920
  const { doc } = await loadManifestDoc(projectDir);
8569
8921
  const seq = doc.get("plugins", true);
8570
- if (!isSeq2(seq))
8922
+ if (!isSeq3(seq))
8571
8923
  return [];
8572
8924
  const out = [];
8573
8925
  for (const item of seq.items) {
@@ -8578,7 +8930,7 @@ async function listProjectPlugins(projectDir) {
8578
8930
  let kind;
8579
8931
  let version;
8580
8932
  let exportName;
8581
- if (isMap(item)) {
8933
+ if (isMap2(item)) {
8582
8934
  const e = item.get("enabled");
8583
8935
  if (e === false)
8584
8936
  enabled = false;
@@ -8623,7 +8975,7 @@ async function recordInstalledNpmPlugin(projectDir, name, version, exportName) {
8623
8975
  const seq = ensureSeq(doc, "plugins");
8624
8976
  const idx = indexOfRef(seq, name);
8625
8977
  let map;
8626
- if (idx >= 0 && isMap(seq.items[idx])) {
8978
+ if (idx >= 0 && isMap2(seq.items[idx])) {
8627
8979
  map = seq.items[idx];
8628
8980
  } else {
8629
8981
  map = new YAMLMap(doc.schema);
@@ -9955,8 +10307,8 @@ var drivethrurpgProvider = {
9955
10307
  }
9956
10308
  issues.push({
9957
10309
  severity: "info",
9958
- id: "drivethrurpg/validate-profile",
9959
- message: "Tip: run `gutterpress validate --profile dtrpg` for DriveThruRPG's print-compliance checks before uploading."
10310
+ id: "drivethrurpg/validate-target",
10311
+ message: "Tip: run `gutterpress validate --target dtrpg` for DriveThruRPG's print-compliance checks before uploading."
9960
10312
  });
9961
10313
  return issues;
9962
10314
  },
@@ -10070,7 +10422,7 @@ var info4 = {
10070
10422
  label: "itch.io",
10071
10423
  kind: "api",
10072
10424
  format: "pdf",
10073
- description: "Upload the PDF to your itch.io project page (games, TTRPG supplements, zines).",
10425
+ description: "Upload the PDF to your itch.io project page (games, zines, and other printable projects).",
10074
10426
  configFields: [
10075
10427
  { key: "target", label: "Project (user/game)", placeholder: "you/your-book" },
10076
10428
  { key: "channel", label: "Channel", placeholder: "pdf" }
@@ -12253,7 +12605,7 @@ async function dispatchToHandler(kind, ctx, error2) {
12253
12605
  return result;
12254
12606
  }
12255
12607
  // src/lib/theme-manager.ts
12256
- import { isSeq as isSeq3, Scalar as Scalar2 } from "yaml";
12608
+ import { isSeq as isSeq4, Scalar as Scalar2 } from "yaml";
12257
12609
  var THEMES_DIR = "themes";
12258
12610
  var THEME_HREF_RE = new RegExp(`^${THEMES_DIR}/([^/]+)/theme\\.css$`);
12259
12611
  // src/lib/theme-import.ts
@@ -12277,7 +12629,7 @@ var ALLOWED_ASSET_EXTS = new Set([
12277
12629
  ".css"
12278
12630
  ]);
12279
12631
  // src/lib/manifest-config.ts
12280
- import { isSeq as isSeq4, isMap as isMap2, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
12632
+ import { isSeq as isSeq5, isMap as isMap3, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
12281
12633
  // src/lib/host-policy.ts
12282
12634
  var AUTO_SNAPSHOT_MAX_MINUTES = 24 * 60;
12283
12635
  var AUTO_SYNC_MAX_MINUTES = 24 * 60;
@@ -12374,5 +12726,5 @@ function normalizeProjectKey(projectDir) {
12374
12726
  return projectDir.replace(/[\\/]+$/, "");
12375
12727
  }
12376
12728
  // src/lib/publish/manifest-publish.ts
12377
- import { isMap as isMap3 } from "yaml";
12378
- export { MANIFEST_FILENAMES, parseNpmPluginSpec, runLint, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, BUILT_IN_TEMPLATE_IDS, addNpmPlugin, isAppHeartbeatFresh, classifyFromHealth, classifyGitError, makeManualGuidance, inspectRepo, verifyRepoReadable, isUnbornRepo, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, runPublish, connectPublishProvider, buildRecoveryContext, recover16 as recover };
12729
+ import { isMap as isMap4 } from "yaml";
12730
+ export { PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, parseNpmPluginSpec, runLint, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, BUILT_IN_TEMPLATE_IDS, addNpmPlugin, isAppHeartbeatFresh, classifyFromHealth, classifyGitError, makeManualGuidance, inspectRepo, verifyRepoReadable, isUnbornRepo, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, runPublish, connectPublishProvider, buildRecoveryContext, recover16 as recover };