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
@@ -11,11 +11,11 @@ import {
11
11
  snapshotWorkingTreeUnlocked,
12
12
  stageChanges,
13
13
  withRepoLock
14
- } from "./index-5bxx0c4y.js";
14
+ } from "./index-yzrh708h.js";
15
15
  import {
16
16
  detectProjectSource,
17
17
  syncRemoteFor
18
- } from "./index-1tyj4bk3.js";
18
+ } from "./index-wchtvxvw.js";
19
19
  import {
20
20
  defaultGitHttp
21
21
  } from "./index-yja077f6.js";
@@ -84,7 +84,9 @@ function resolvePort(raw) {
84
84
  }
85
85
 
86
86
  // src/lib/presets.ts
87
+ var PRESET_IDS = ["dtrpg", "book", "custom"];
87
88
  var DTRPG_PRESET = {
89
+ defaultTargets: ["dtrpg"],
88
90
  plugins: [],
89
91
  source: {
90
92
  files: null
@@ -156,6 +158,7 @@ var DTRPG_PRESET = {
156
158
  }
157
159
  };
158
160
  var BOOK_PRESET = {
161
+ defaultTargets: [],
159
162
  plugins: [],
160
163
  source: {
161
164
  files: null
@@ -220,9 +223,15 @@ var BOOK_PRESET = {
220
223
  }
221
224
  }
222
225
  };
226
+ var CUSTOM_PRESET = {
227
+ ...BOOK_PRESET,
228
+ page: null,
229
+ defaultTargets: []
230
+ };
223
231
  var PRESETS = {
224
232
  dtrpg: DTRPG_PRESET,
225
- book: BOOK_PRESET
233
+ book: BOOK_PRESET,
234
+ custom: CUSTOM_PRESET
226
235
  };
227
236
  var warnedIds = new Set;
228
237
  function warnOnce(id, message, sink = defaultWarnSink) {
@@ -236,7 +245,7 @@ function defaultWarnSink(message) {
236
245
  }
237
246
  function resolvePreset(presetName) {
238
247
  if (presetName === undefined) {
239
- 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.");
248
+ 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.");
240
249
  return DTRPG_PRESET;
241
250
  }
242
251
  const preset = PRESETS[presetName];
@@ -246,6 +255,121 @@ function resolvePreset(presetName) {
246
255
  return preset;
247
256
  }
248
257
 
258
+ // src/lib/targets.ts
259
+ var DTRPG_TARGET = {
260
+ id: "dtrpg",
261
+ label: "DriveThruRPG",
262
+ overlay: {
263
+ pdfx: { flavor: DTRPG_PRESET.pdfx.flavor },
264
+ ink: {
265
+ maxTac: DTRPG_PRESET.ink.maxTac,
266
+ tacTolerance: DTRPG_PRESET.ink.tacTolerance
267
+ },
268
+ validate: {
269
+ checks: {
270
+ "pdf.structure.qpdf": { enabled: true, severity: "error" },
271
+ "pdf.print.pdfx-markers": { enabled: true, severity: "error" },
272
+ "pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
273
+ "pdf.print.embedded-fonts": { enabled: true, severity: "error" }
274
+ },
275
+ assets: {
276
+ allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
277
+ },
278
+ pdf: {
279
+ forbidTransparency: DTRPG_PRESET.validate.pdf.forbidTransparency,
280
+ minImageResolution: DTRPG_PRESET.validate.pdf.minImageResolution
281
+ }
282
+ }
283
+ },
284
+ requiredChecks: [
285
+ "pdf.structure.qpdf",
286
+ "pdf.print.pdfx-markers",
287
+ "pdf.print.pdfx-metadata",
288
+ "pdf.print.embedded-fonts"
289
+ ],
290
+ requiredTools: ["qpdf", "gs"]
291
+ };
292
+ var ITCH_TARGET = {
293
+ id: "itch",
294
+ label: "itch.io",
295
+ overlay: {
296
+ validate: {
297
+ checks: {
298
+ "pdf.structure.qpdf": { enabled: true, severity: "error" },
299
+ "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
300
+ "pdf.print.pdfx-markers": false,
301
+ "pdf.print.pdfx-metadata": false,
302
+ "pdf.print.ink-coverage": false,
303
+ "asset.image.tac-raster": false
304
+ },
305
+ assets: {
306
+ allowedColorSpaces: ["RGB", "CMYK", "Grayscale"],
307
+ allowAlpha: true
308
+ },
309
+ pdf: {
310
+ forbidTransparency: false,
311
+ requireBleed: false
312
+ }
313
+ }
314
+ },
315
+ requiredChecks: ["pdf.structure.qpdf", "pdf.print.embedded-fonts"],
316
+ requiredTools: []
317
+ };
318
+ var TARGETS = {
319
+ dtrpg: DTRPG_TARGET,
320
+ itch: ITCH_TARGET
321
+ };
322
+ var TARGET_IDS = Object.keys(TARGETS);
323
+ function publishTargetFor(id) {
324
+ const target = TARGETS[id];
325
+ if (!target) {
326
+ throw new UsageError(`Unknown publish target "${id}". Known targets: ${TARGET_IDS.join(", ")}.`);
327
+ }
328
+ return target;
329
+ }
330
+ function resolveTargets(requested, fallback) {
331
+ const ids = requested ?? fallback;
332
+ const seen = new Set;
333
+ const out = [];
334
+ for (const id of ids) {
335
+ publishTargetFor(id);
336
+ if (!seen.has(id)) {
337
+ seen.add(id);
338
+ out.push(id);
339
+ }
340
+ }
341
+ return out;
342
+ }
343
+ function overlayPreset(preset, target) {
344
+ const o = target.overlay;
345
+ return {
346
+ ...preset,
347
+ pdfx: { ...preset.pdfx, ...o.pdfx },
348
+ ink: { ...preset.ink, ...o.ink },
349
+ validate: {
350
+ ...preset.validate,
351
+ checks: { ...preset.validate.checks, ...o.validate?.checks },
352
+ assets: { ...preset.validate.assets, ...o.validate?.assets },
353
+ pdf: { ...preset.validate.pdf, ...o.validate?.pdf }
354
+ }
355
+ };
356
+ }
357
+ var DEFAULT_STRICT_PDF_CHECKS = [
358
+ "pdf.structure.qpdf",
359
+ "pdf.print.pdfx-markers",
360
+ "pdf.print.pdfx-metadata",
361
+ "pdf.print.embedded-fonts"
362
+ ];
363
+ function applyDefaultPdfStrictChecks(config) {
364
+ const next = structuredClone(config);
365
+ for (const checkId of DEFAULT_STRICT_PDF_CHECKS) {
366
+ if (next.validate.checks[checkId] === undefined) {
367
+ next.validate.checks[checkId] = { enabled: true, severity: "error" };
368
+ }
369
+ }
370
+ return next;
371
+ }
372
+
249
373
  // src/lib/manifest.ts
250
374
  var MANIFEST_FILENAMES = [
251
375
  "manifest.yaml"
@@ -333,8 +457,34 @@ function mergeShape(cli, manifest, preset) {
333
457
  return out;
334
458
  }
335
459
  function resolveConfig(cliOverrides, manifest) {
460
+ const presetName = cliOverrides.preset ?? manifest.preset;
461
+ return resolveWithPreset(cliOverrides, manifest, resolvePreset(presetName));
462
+ }
463
+ function resolveConfigForTarget(cliOverrides, manifest, targetId) {
336
464
  const presetName = cliOverrides.preset ?? manifest.preset;
337
465
  const preset = resolvePreset(presetName);
466
+ return resolveWithPreset(cliOverrides, manifest, overlayPreset(preset, publishTargetFor(targetId)));
467
+ }
468
+ function resolveCustomPage(c, m) {
469
+ const width = c.page?.width ?? m.page?.width;
470
+ const height = c.page?.height ?? m.page?.height;
471
+ const missing = [
472
+ width === undefined ? "page.width" : null,
473
+ height === undefined ? "page.height" : null
474
+ ].filter((f) => f !== null);
475
+ if (missing.length > 0) {
476
+ throw new UsageError(`preset: custom requires ${missing.join(" and ")} in manifest.yaml ` + `(points; 72pt = 1in). Example for US Letter:
477
+ ` + ` page:
478
+ width: 612
479
+ height: 792`);
480
+ }
481
+ return {
482
+ width,
483
+ height,
484
+ tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5
485
+ };
486
+ }
487
+ function resolveWithPreset(cliOverrides, manifest, preset) {
338
488
  const m = manifest;
339
489
  const c = cliOverrides;
340
490
  const legacy = m;
@@ -356,9 +506,10 @@ function resolveConfig(cliOverrides, manifest) {
356
506
  authors: c.authors ?? m.authors ?? [],
357
507
  styles: c.styles ?? m.styles,
358
508
  plugins,
509
+ targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
359
510
  source: mergeShape(c.source, m.source, preset.source),
360
511
  pdfx: mergeShape(c.pdfx, m.pdfx, preset.pdfx),
361
- page: mergeShape(c.page, m.page, preset.page),
512
+ page: preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m),
362
513
  ink: mergeShape(c.ink, m.ink, preset.ink),
363
514
  lint: mergeShape(c.lint, m.lint, preset.lint),
364
515
  validate: {
@@ -1153,11 +1304,24 @@ async function resolveActiveStyles(projectDir, manifestStyles) {
1153
1304
  }
1154
1305
  return [];
1155
1306
  }
1156
- async function listProjectStyles(projectDir) {
1157
- const [{ manifest }, discoveredAbs] = await Promise.all([
1307
+ var SHARED_STYLE_ROOTS = ["shared", "."];
1308
+ async function discoverSharedCssFiles(repoRoot) {
1309
+ const found = new Set;
1310
+ for (const rel of SHARED_STYLE_ROOTS) {
1311
+ const base = path.resolve(repoRoot, rel);
1312
+ for (const abs of await discoverCssFiles(base))
1313
+ found.add(abs);
1314
+ }
1315
+ return [...found];
1316
+ }
1317
+ async function listProjectStyles(projectDir, opts = {}) {
1318
+ const repoRoot = opts.repoRoot && path.resolve(opts.repoRoot) !== path.resolve(projectDir) ? path.resolve(opts.repoRoot) : null;
1319
+ const [{ manifest }, ownCss, sharedCss] = await Promise.all([
1158
1320
  loadManifestWithPath(projectDir),
1159
- discoverCssFiles(projectDir)
1321
+ discoverCssFiles(projectDir),
1322
+ repoRoot ? discoverSharedCssFiles(repoRoot) : Promise.resolve([])
1160
1323
  ]);
1324
+ const discoveredAbs = [...new Set([...ownCss, ...sharedCss])];
1161
1325
  const activeRels = await resolveActiveStyles(projectDir, Array.isArray(manifest.styles) ? manifest.styles : undefined);
1162
1326
  const out = [];
1163
1327
  const seen = new Set;
@@ -1216,6 +1380,22 @@ function decodeRef(ref) {
1216
1380
  function toPosix(p) {
1217
1381
  return p.split(path2.sep).join("/");
1218
1382
  }
1383
+ function escapesProjectRoot(projectDir, absPath) {
1384
+ const rel = path2.relative(projectDir, absPath);
1385
+ return rel === ".." || rel.startsWith(`..${path2.sep}`) || path2.isAbsolute(rel);
1386
+ }
1387
+ function proseImageRefError(ref, projectDir) {
1388
+ const cleaned = stripUrlSuffix(decodeRef(ref));
1389
+ if (path2.isAbsolute(cleaned)) {
1390
+ return `Image reference must be relative to the project: ${ref}
1391
+ ` + ` Copy the file into your project folder and reference it from there.`;
1392
+ }
1393
+ if (escapesProjectRoot(projectDir, path2.resolve(projectDir, cleaned))) {
1394
+ return `Image reference points outside the project: ${ref}
1395
+ ` + ` Copy the file into your project folder and reference it from there.`;
1396
+ }
1397
+ return null;
1398
+ }
1219
1399
  function mimeFor(ext) {
1220
1400
  return MIME_BY_EXT[ext] ?? "application/octet-stream";
1221
1401
  }
@@ -1318,7 +1498,7 @@ async function inlineOne(cssPath, projectDir, copies, warnings, seen) {
1318
1498
  return dataUri(bytes, ext);
1319
1499
  }
1320
1500
  const projectRel = path2.relative(projectDir, absAsset);
1321
- const dest = projectRel && !projectRel.startsWith("..") && !path2.isAbsolute(projectRel) ? toPosix(projectRel) : `${HASHED_ASSET_DIR}/${contentHash(bytes)}${ext}`;
1501
+ const dest = projectRel && !escapesProjectRoot(projectDir, absAsset) ? toPosix(projectRel) : `${HASHED_ASSET_DIR}/${contentHash(bytes)}${ext}`;
1322
1502
  copies.set(dest, { from: absAsset, to: dest });
1323
1503
  return dest;
1324
1504
  });
@@ -1409,19 +1589,14 @@ async function planImageCopies(projectDir, refs) {
1409
1589
  for (const ref of refs) {
1410
1590
  if (isNonFileUrl(ref))
1411
1591
  continue;
1412
- const cleaned = stripUrlSuffix(decodeRef(ref));
1413
- if (path2.isAbsolute(cleaned)) {
1414
- errors.push(`Image reference must be relative to the project: ${ref}
1415
- ` + ` Copy the file into your project folder and reference it from there.`);
1592
+ const refError = proseImageRefError(ref, projectDir);
1593
+ if (refError) {
1594
+ errors.push(refError);
1416
1595
  continue;
1417
1596
  }
1597
+ const cleaned = stripUrlSuffix(decodeRef(ref));
1418
1598
  const abs = path2.resolve(projectDir, cleaned);
1419
1599
  const rel = path2.relative(projectDir, abs);
1420
- if (rel.startsWith("..")) {
1421
- errors.push(`Image reference points outside the project: ${ref}
1422
- ` + ` Copy the file into your project folder and reference it from there.`);
1423
- continue;
1424
- }
1425
1600
  const dest = toPosix(rel);
1426
1601
  copies.set(dest, { from: abs, to: dest });
1427
1602
  }
@@ -2616,7 +2791,7 @@ function shadowPathFor(pluginPath, mtimeMs) {
2616
2791
  const ext = extname(pluginPath);
2617
2792
  const stem = basename(pluginPath, ext);
2618
2793
  const token = String(mtimeMs).replace(/\./g, "-");
2619
- return join2(dirname2(pluginPath), `.${stem}.gutterpress-reload-${token}${ext}`);
2794
+ return join2(dirname2(pluginPath), `.${stem}.gutterpress-reload-${process.pid}-${token}${ext}`);
2620
2795
  }
2621
2796
  async function loadCachedPathPluginModule(pluginPath) {
2622
2797
  const mtimeMs = statSync2(pluginPath).mtimeMs;
@@ -3212,7 +3387,7 @@ import git from "isomorphic-git";
3212
3387
  // package.json
3213
3388
  var package_default = {
3214
3389
  name: "gutterpress",
3215
- version: "0.0.0",
3390
+ version: "0.9.0-alpha.1",
3216
3391
  description: "Markdown-to-PDF converter for professional print layout using Paged.js and Ghostscript.",
3217
3392
  author: "itlackey",
3218
3393
  license: "MPL-2.0",
@@ -3224,7 +3399,7 @@ var package_default = {
3224
3399
  "ghostscript",
3225
3400
  "book",
3226
3401
  "publishing",
3227
- "ttrpg"
3402
+ "zine"
3228
3403
  ],
3229
3404
  repository: {
3230
3405
  type: "git",
@@ -3452,7 +3627,7 @@ async function writeBuildFingerprint(input) {
3452
3627
  iccPath: input.pdfx.iccPath,
3453
3628
  stripAnnotations: input.pdfx.stripAnnotations
3454
3629
  },
3455
- outputDir
3630
+ outputDir: input.recordedOutputDir ? path5.resolve(input.recordedOutputDir) : outputDir
3456
3631
  },
3457
3632
  sourceRevision,
3458
3633
  tools
@@ -3473,7 +3648,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
3473
3648
  var favicon_default = "./favicon-wkbm9cjn.ico";
3474
3649
 
3475
3650
  // src/assets/manifest.schema.json
3476
- var manifest_schema_default = "./manifest.schema-rn1dpdpn.json";
3651
+ var manifest_schema_default = "./manifest.schema-z61rzw44.json";
3477
3652
 
3478
3653
  // src/assets/preview/scripts/pagedjs-interface.js
3479
3654
  var pagedjs_interface_default = "./pagedjs-interface-80g3xa8p.js";
@@ -3496,23 +3671,17 @@ var manifest_default = "./manifest-n1gh3qw5.yaml";
3496
3671
  // src/assets/templates/book/chapter-01.md
3497
3672
  var chapter_01_default = "./chapter-01-rkpyvjna.md";
3498
3673
 
3499
- // src/assets/templates/ttrpg/manifest.yaml
3500
- var manifest_default2 = "./manifest-ryyg71nf.yaml";
3501
-
3502
- // src/assets/templates/ttrpg/chapter-01.md
3503
- var chapter_01_default2 = "./chapter-01-jj8ftn4c.md";
3504
-
3505
3674
  // src/assets/templates/zine/manifest.yaml
3506
- var manifest_default3 = "./manifest-n1gh3qw5.yaml";
3675
+ var manifest_default2 = "./manifest-n1gh3qw5.yaml";
3507
3676
 
3508
3677
  // src/assets/templates/zine/chapter-01.md
3509
- var chapter_01_default3 = "./chapter-01-7jwqg1ct.md";
3678
+ var chapter_01_default2 = "./chapter-01-7jwqg1ct.md";
3510
3679
 
3511
3680
  // src/assets/templates/technical/manifest.yaml
3512
- var manifest_default4 = "./manifest-n1gh3qw5.yaml";
3681
+ var manifest_default3 = "./manifest-n1gh3qw5.yaml";
3513
3682
 
3514
3683
  // src/assets/templates/technical/chapter-01.md
3515
- var chapter_01_default4 = "./chapter-01-6fn9pvd0.md";
3684
+ var chapter_01_default3 = "./chapter-01-6fn9pvd0.md";
3516
3685
 
3517
3686
  // src/assets/themes/clean-book/theme.css
3518
3687
  var theme_default = "./theme-570zmh2t.css";
@@ -3520,23 +3689,17 @@ var theme_default = "./theme-570zmh2t.css";
3520
3689
  // src/assets/themes/clean-book/theme.json
3521
3690
  var theme_default2 = "./theme-xt0hjb1b.json";
3522
3691
 
3523
- // src/assets/themes/ttrpg-supplement/theme.css
3524
- var theme_default3 = "./theme-0yzvthe8.css";
3525
-
3526
- // src/assets/themes/ttrpg-supplement/theme.json
3527
- var theme_default4 = "./theme-6a2fngc8.json";
3528
-
3529
3692
  // src/assets/themes/zine/theme.css
3530
- var theme_default5 = "./theme-zz2ktzqs.css";
3693
+ var theme_default3 = "./theme-zz2ktzqs.css";
3531
3694
 
3532
3695
  // src/assets/themes/zine/theme.json
3533
- var theme_default6 = "./theme-cd6efzjw.json";
3696
+ var theme_default4 = "./theme-cd6efzjw.json";
3534
3697
 
3535
3698
  // src/assets/themes/technical-doc/theme.css
3536
- var theme_default7 = "./theme-nya4nqh6.css";
3699
+ var theme_default5 = "./theme-nya4nqh6.css";
3537
3700
 
3538
3701
  // src/assets/themes/technical-doc/theme.json
3539
- var theme_default8 = "./theme-tqfypc1w.json";
3702
+ var theme_default6 = "./theme-tqfypc1w.json";
3540
3703
 
3541
3704
  // src/lib/embedded-assets.ts
3542
3705
  var __libdir = dirname3(fileURLToPath2(import.meta.url));
@@ -3552,20 +3715,16 @@ var EMBEDDED_ASSETS = {
3552
3715
  "profiles/CGATS21_CRPC1.icc": abs(filePath(CGATS21_CRPC1_default)),
3553
3716
  "templates/book/manifest.yaml": abs(filePath(manifest_default)),
3554
3717
  "templates/book/chapter-01.md": abs(filePath(chapter_01_default)),
3555
- "templates/ttrpg/manifest.yaml": abs(filePath(manifest_default2)),
3556
- "templates/ttrpg/chapter-01.md": abs(filePath(chapter_01_default2)),
3557
- "templates/zine/manifest.yaml": abs(filePath(manifest_default3)),
3558
- "templates/zine/chapter-01.md": abs(filePath(chapter_01_default3)),
3559
- "templates/technical/manifest.yaml": abs(filePath(manifest_default4)),
3560
- "templates/technical/chapter-01.md": abs(filePath(chapter_01_default4)),
3718
+ "templates/zine/manifest.yaml": abs(filePath(manifest_default2)),
3719
+ "templates/zine/chapter-01.md": abs(filePath(chapter_01_default2)),
3720
+ "templates/technical/manifest.yaml": abs(filePath(manifest_default3)),
3721
+ "templates/technical/chapter-01.md": abs(filePath(chapter_01_default3)),
3561
3722
  "themes/clean-book/theme.css": abs(filePath(theme_default)),
3562
3723
  "themes/clean-book/theme.json": abs(filePath(theme_default2)),
3563
- "themes/ttrpg-supplement/theme.css": abs(filePath(theme_default3)),
3564
- "themes/ttrpg-supplement/theme.json": abs(filePath(theme_default4)),
3565
- "themes/zine/theme.css": abs(filePath(theme_default5)),
3566
- "themes/zine/theme.json": abs(filePath(theme_default6)),
3567
- "themes/technical-doc/theme.css": abs(filePath(theme_default7)),
3568
- "themes/technical-doc/theme.json": abs(filePath(theme_default8))
3724
+ "themes/zine/theme.css": abs(filePath(theme_default3)),
3725
+ "themes/zine/theme.json": abs(filePath(theme_default4)),
3726
+ "themes/technical-doc/theme.css": abs(filePath(theme_default5)),
3727
+ "themes/technical-doc/theme.json": abs(filePath(theme_default6))
3569
3728
  };
3570
3729
  var extractPromise = null;
3571
3730
  async function extractAssets() {
@@ -3797,7 +3956,7 @@ async function runLint(opts = {}) {
3797
3956
 
3798
3957
  // src/lib/validation-exec.ts
3799
3958
  import { existsSync as existsSync11 } from "node:fs";
3800
- import { join as join6, resolve as resolve9 } from "node:path";
3959
+ import { dirname as dirname5, join as join6, resolve as resolve9 } from "node:path";
3801
3960
 
3802
3961
  // src/checks/formatter.ts
3803
3962
  function formatReport(report, format = "text") {
@@ -3809,19 +3968,19 @@ function formatReport(report, format = "text") {
3809
3968
  }
3810
3969
  function formatText(report) {
3811
3970
  for (const r of report.infos) {
3812
- const loc = formatLocation(r.file, r.line, r.column);
3971
+ const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
3813
3972
  log.info(`${loc}${r.message}`);
3814
3973
  if (r.detail)
3815
3974
  log.info(` ${r.detail}`);
3816
3975
  }
3817
3976
  for (const r of report.warnings) {
3818
- const loc = formatLocation(r.file, r.line, r.column);
3977
+ const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
3819
3978
  log.warn(`${loc}${r.message}`);
3820
3979
  if (r.detail)
3821
3980
  log.warn(` ${r.detail}`);
3822
3981
  }
3823
3982
  for (const r of report.errors) {
3824
- const loc = formatLocation(r.file, r.line, r.column);
3983
+ const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
3825
3984
  log.error(`${loc}${r.message}`);
3826
3985
  if (r.detail)
3827
3986
  log.error(` ${r.detail}`);
@@ -3835,6 +3994,9 @@ function formatText(report) {
3835
3994
  log.success("VALIDATION PASSED");
3836
3995
  }
3837
3996
  }
3997
+ function targetPrefix(target) {
3998
+ return target ? `[${target}] ` : "";
3999
+ }
3838
4000
  function formatJson(report) {
3839
4001
  console.log(JSON.stringify({
3840
4002
  results: report.results,
@@ -5239,6 +5401,16 @@ var check19 = {
5239
5401
  if (inFence)
5240
5402
  continue;
5241
5403
  for (const { ref, kind } of extractLocalRefs(line)) {
5404
+ const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
5405
+ if (escape) {
5406
+ results.push(finding(check19.id, {
5407
+ severity: "error",
5408
+ message: escape,
5409
+ file,
5410
+ line: i + 1
5411
+ }));
5412
+ continue;
5413
+ }
5242
5414
  if (localRefExists(ref, kind, file, ctx.inputDir))
5243
5415
  continue;
5244
5416
  results.push(finding(check19.id, {
@@ -6301,58 +6473,46 @@ function reportMissingTools(result) {
6301
6473
  }
6302
6474
  }
6303
6475
 
6304
- // src/lib/validation-profile.ts
6305
- var DTRPG_STRICT_PDF_CHECKS = [
6306
- "pdf.structure.qpdf",
6307
- "pdf.print.pdfx-markers",
6308
- "pdf.print.pdfx-metadata",
6309
- "pdf.print.embedded-fonts"
6310
- ];
6311
- function enforceStrictPdfChecks(config, overwrite = false) {
6312
- for (const checkId of DTRPG_STRICT_PDF_CHECKS) {
6313
- if (overwrite || config.validate.checks[checkId] === undefined) {
6314
- config.validate.checks[checkId] = {
6315
- enabled: true,
6316
- severity: "error"
6317
- };
6318
- }
6476
+ // src/lib/validation-exec.ts
6477
+ var SHIPPED_ASSET_EXTS = new Set([
6478
+ ".woff2",
6479
+ ".woff",
6480
+ ".ttf",
6481
+ ".otf",
6482
+ ".png",
6483
+ ".jpg",
6484
+ ".jpeg",
6485
+ ".gif",
6486
+ ".webp",
6487
+ ".avif",
6488
+ ".svg",
6489
+ ".tif",
6490
+ ".tiff"
6491
+ ]);
6492
+ async function sharedAssetDirs(manifestDir, relStyles, inputDir) {
6493
+ let closure;
6494
+ try {
6495
+ closure = await collectStyleDependencies(manifestDir, relStyles);
6496
+ } catch {
6497
+ return [];
6319
6498
  }
6320
- }
6321
- function applyDefaultPdfStrictChecks(config) {
6322
- const next = structuredClone(config);
6323
- enforceStrictPdfChecks(next, false);
6324
- return next;
6325
- }
6326
- var applyDtrpgPdfDefaults = applyDefaultPdfStrictChecks;
6327
- function applyValidationProfile(config, profile) {
6328
- const next = structuredClone(config);
6329
- if (profile === "dtrpg") {
6330
- next.pdfx.flavor = DTRPG_PRESET.pdfx.flavor;
6331
- next.ink.maxTac = DTRPG_PRESET.ink.maxTac;
6332
- next.ink.tacTolerance = DTRPG_PRESET.ink.tacTolerance;
6333
- next.validate.pdf.forbidTransparency = DTRPG_PRESET.validate.pdf.forbidTransparency;
6334
- next.validate.pdf.minImageResolution = DTRPG_PRESET.validate.pdf.minImageResolution;
6335
- next.validate.assets.allowedColorSpaces = [
6336
- ...DTRPG_PRESET.validate.assets.allowedColorSpaces
6337
- ];
6338
- enforceStrictPdfChecks(next, true);
6499
+ const projectRoot = resolve9(inputDir);
6500
+ const dirs = new Set;
6501
+ for (const file of closure) {
6502
+ const ext = file.slice(file.lastIndexOf(".")).toLowerCase();
6503
+ if (!SHIPPED_ASSET_EXTS.has(ext))
6504
+ continue;
6505
+ const dir = dirname5(resolve9(file));
6506
+ if (escapesProjectRoot(projectRoot, dir))
6507
+ dirs.add(dir);
6339
6508
  }
6340
- return next;
6509
+ return [...dirs];
6341
6510
  }
6342
-
6343
- // src/lib/validation-exec.ts
6344
6511
  function parseCsv(value) {
6345
6512
  if (!value)
6346
6513
  return;
6347
6514
  return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
6348
6515
  }
6349
- function parseProfile(raw) {
6350
- if (!raw)
6351
- return;
6352
- if (raw === "dtrpg")
6353
- return raw;
6354
- throw new Error(`Unsupported profile: ${raw}. Supported profiles: dtrpg`);
6355
- }
6356
6516
  function resolvePhaseArg(raw) {
6357
6517
  const normalized = raw.trim().toLowerCase();
6358
6518
  if (normalized === "pre" || normalized === "pre-build")
@@ -6375,20 +6535,16 @@ function resolveCategoryArg(raw) {
6375
6535
  }
6376
6536
  return parsed;
6377
6537
  }
6378
- function withProfileRequiredCheckErrors(report, tools, profile) {
6379
- if (!profile)
6380
- return report;
6381
- const required = profile === "dtrpg" ? DTRPG_STRICT_PDF_CHECKS : [];
6382
- if (required.length === 0)
6383
- return report;
6538
+ function withTargetRequiredCheckErrors(report, tools, target) {
6384
6539
  const skippedSet = new Set(tools.skippedChecks);
6385
- const missingRequired = required.filter((checkId) => skippedSet.has(checkId));
6540
+ const missingRequired = target.requiredChecks.filter((checkId) => skippedSet.has(checkId));
6386
6541
  if (missingRequired.length === 0)
6387
6542
  return report;
6388
6543
  const syntheticErrors = missingRequired.map((checkId) => ({
6389
6544
  checkId,
6545
+ target: target.id,
6390
6546
  severity: "error",
6391
- message: `Profile ${profile} requires check ${checkId}, but it was skipped because required tools are not available.`
6547
+ message: `Target ${target.id} requires check ${checkId}, but it was skipped because required tools are not available.`
6392
6548
  }));
6393
6549
  const results = [...report.results, ...syntheticErrors];
6394
6550
  const errors = [...report.errors, ...syntheticErrors];
@@ -6402,19 +6558,115 @@ function withProfileRequiredCheckErrors(report, tools, profile) {
6402
6558
  }
6403
6559
  };
6404
6560
  }
6561
+ var TARGET_INDEPENDENT_CATEGORIES = ["source", "heuristic"];
6562
+ var TARGET_DEPENDENT_CATEGORIES = ["asset", "pdf"];
6563
+ function tagReport(report, targetId) {
6564
+ const tag = (r) => ({ ...r, target: targetId });
6565
+ return {
6566
+ results: report.results.map(tag),
6567
+ errors: report.errors.map(tag),
6568
+ warnings: report.warnings.map(tag),
6569
+ infos: report.infos.map(tag),
6570
+ passed: report.passed,
6571
+ summary: report.summary
6572
+ };
6573
+ }
6574
+ function mergeReports(reports) {
6575
+ const merged = {
6576
+ results: [],
6577
+ errors: [],
6578
+ warnings: [],
6579
+ infos: [],
6580
+ passed: [],
6581
+ summary: { total: 0, errors: 0, warnings: 0, infos: 0, passed: 0 }
6582
+ };
6583
+ for (const r of reports) {
6584
+ merged.results.push(...r.results);
6585
+ merged.errors.push(...r.errors);
6586
+ merged.warnings.push(...r.warnings);
6587
+ merged.infos.push(...r.infos);
6588
+ merged.passed.push(...r.passed);
6589
+ merged.summary.total += r.summary.total;
6590
+ merged.summary.errors += r.summary.errors;
6591
+ merged.summary.warnings += r.summary.warnings;
6592
+ merged.summary.infos += r.summary.infos;
6593
+ merged.summary.passed += r.summary.passed;
6594
+ }
6595
+ return merged;
6596
+ }
6597
+ function mergeTools(results) {
6598
+ const available = new Set;
6599
+ const missing = new Set;
6600
+ const skippedChecks = new Set;
6601
+ const toolToChecks = new Map;
6602
+ for (const r of results) {
6603
+ for (const t of r.available)
6604
+ available.add(t);
6605
+ for (const t of r.missing)
6606
+ missing.add(t);
6607
+ for (const id of r.skippedChecks)
6608
+ skippedChecks.add(id);
6609
+ for (const [tool, ids] of r.toolToChecks) {
6610
+ const merged = new Set([...toolToChecks.get(tool) ?? [], ...ids]);
6611
+ toolToChecks.set(tool, [...merged]);
6612
+ }
6613
+ }
6614
+ return {
6615
+ available: [...available],
6616
+ missing: [...missing],
6617
+ skippedChecks: [...skippedChecks],
6618
+ toolToChecks
6619
+ };
6620
+ }
6621
+ function planRuns(opts) {
6622
+ const { manifest, baseConfig, targetIds, pdfPath, categories, only } = opts;
6623
+ const finalize = (config) => pdfPath ? applyDefaultPdfStrictChecks(config) : config;
6624
+ if (targetIds.length === 0) {
6625
+ return [{ targetId: null, config: finalize(baseConfig), category: categories, only }];
6626
+ }
6627
+ const requested = categories ?? [...TARGET_INDEPENDENT_CATEGORIES, ...TARGET_DEPENDENT_CATEGORIES];
6628
+ const independentCats = requested.filter((c) => TARGET_INDEPENDENT_CATEGORIES.includes(c));
6629
+ const dependentCats = requested.filter((c) => TARGET_DEPENDENT_CATEGORIES.includes(c));
6630
+ let baseOnly;
6631
+ let dependentOnly;
6632
+ if (only && only.length > 0) {
6633
+ const { resolved, unmatched } = resolveCheckSelectors(only);
6634
+ const categoryById = new Map(getChecks({}).map((c) => [c.id, c.category]));
6635
+ const independentIds = resolved.filter((id) => TARGET_INDEPENDENT_CATEGORIES.includes(categoryById.get(id) ?? ""));
6636
+ const dependentIds = resolved.filter((id) => TARGET_DEPENDENT_CATEGORIES.includes(categoryById.get(id) ?? ""));
6637
+ baseOnly = [...independentIds, ...unmatched];
6638
+ dependentOnly = dependentIds;
6639
+ }
6640
+ const runs = [];
6641
+ const baseWanted = only ? (baseOnly?.length ?? 0) > 0 : independentCats.length > 0;
6642
+ if (baseWanted) {
6643
+ runs.push({
6644
+ targetId: null,
6645
+ config: baseConfig,
6646
+ category: only ? undefined : independentCats,
6647
+ only: baseOnly
6648
+ });
6649
+ }
6650
+ const dependentWanted = only ? (dependentOnly?.length ?? 0) > 0 : dependentCats.length > 0;
6651
+ if (dependentWanted) {
6652
+ for (const targetId of targetIds) {
6653
+ runs.push({
6654
+ targetId,
6655
+ config: finalize(resolveConfigForTarget({}, manifest, targetId)),
6656
+ category: only ? undefined : dependentCats,
6657
+ only: dependentOnly
6658
+ });
6659
+ }
6660
+ }
6661
+ return runs;
6662
+ }
6405
6663
  async function executeValidation(args) {
6406
6664
  const manifestPath = typeof args.manifest === "string" ? args.manifest : undefined;
6407
6665
  const { manifest, manifestDir } = await loadManifestWithPath(manifestPath ?? args.input ?? undefined, { explicit: manifestPath !== undefined });
6408
- const profile = parseProfile(typeof args.profile === "string" ? args.profile : undefined);
6409
- let config = resolveConfig({}, manifest);
6666
+ const config = resolveConfig({}, manifest);
6667
+ const targetIds = resolveTargets(parseCsv(typeof args.target === "string" ? args.target : undefined), config.targets);
6410
6668
  const pdfPath = typeof args.pdf === "string" ? resolve9(args.pdf) : undefined;
6411
6669
  const inputDir = typeof args.input === "string" ? resolve9(args.input) : undefined;
6412
- if (pdfPath) {
6413
- config = applyDtrpgPdfDefaults(config);
6414
- }
6415
- if (profile) {
6416
- config = applyValidationProfile(config, profile);
6417
- }
6418
6670
  if (pdfPath && !existsSync11(pdfPath)) {
6419
6671
  throw new Error(`File not found: ${pdfPath}`);
6420
6672
  }
@@ -6443,11 +6695,11 @@ async function executeValidation(args) {
6443
6695
  let htmlPath;
6444
6696
  const outDir = resolveOutputDir(manifestDir, config.title);
6445
6697
  if (inputDir) {
6446
- const relMarkdown = await resolveActiveMarkdownFiles(inputDir, config.source.files);
6447
- markdownFiles = relMarkdown.map((f) => join6(inputDir, canonicalChapterId(f)));
6448
- const relStyles = await resolveActiveStyles(inputDir, config.styles);
6449
- cssFiles = relStyles.map((rel) => resolve9(inputDir, rel)).filter((f) => !f.endsWith(".min.css"));
6450
- assetDirs = [inputDir];
6698
+ const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.source.files);
6699
+ markdownFiles = relMarkdown.map((f) => join6(manifestDir, canonicalChapterId(f)));
6700
+ const relStyles = await resolveActiveStyles(manifestDir, config.styles);
6701
+ cssFiles = relStyles.map((rel) => resolve9(manifestDir, rel)).filter((f) => !f.endsWith(".min.css"));
6702
+ assetDirs = [inputDir, ...await sharedAssetDirs(manifestDir, relStyles, inputDir)];
6451
6703
  const possibleHtml = join6(outDir, BOOK_HTML_FILENAME);
6452
6704
  if (existsSync11(possibleHtml)) {
6453
6705
  htmlPath = possibleHtml;
@@ -6469,15 +6721,30 @@ async function executeValidation(args) {
6469
6721
  only,
6470
6722
  skip
6471
6723
  };
6472
- const tools = await checkToolAvailability(config, runnerOptions);
6473
- const initialReport = await runChecks(context, {
6474
- ...runnerOptions,
6475
- skipMissingTools: tools.skippedChecks
6476
- });
6477
- const report = withProfileRequiredCheckErrors(initialReport, tools, profile);
6724
+ const runs = planRuns({ manifest, baseConfig: config, targetIds, pdfPath, categories, only });
6725
+ const reports = [];
6726
+ const toolResults = [];
6727
+ for (const run2 of runs) {
6728
+ const runOptions = {
6729
+ category: run2.category,
6730
+ phase,
6731
+ only: run2.only,
6732
+ skip
6733
+ };
6734
+ const runTools = await checkToolAvailability(run2.config, runOptions);
6735
+ toolResults.push(runTools);
6736
+ let runReport = await runChecks({ ...context, config: run2.config }, { ...runOptions, skipMissingTools: runTools.skippedChecks });
6737
+ if (run2.targetId !== null) {
6738
+ runReport = withTargetRequiredCheckErrors(runReport, runTools, publishTargetFor(run2.targetId));
6739
+ runReport = tagReport(runReport, run2.targetId);
6740
+ }
6741
+ reports.push(runReport);
6742
+ }
6743
+ const tools = mergeTools(toolResults);
6744
+ const report = mergeReports(reports);
6478
6745
  return {
6479
6746
  config,
6480
- profile,
6747
+ targets: targetIds,
6481
6748
  context,
6482
6749
  runnerOptions,
6483
6750
  tools,
@@ -6555,7 +6822,7 @@ ${m.installHint}`).join(`
6555
6822
 
6556
6823
  ${list}
6557
6824
 
6558
- 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);
6825
+ 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);
6559
6826
  }
6560
6827
  function computeGates(format, opts, config) {
6561
6828
  if (format === "html") {
@@ -6649,6 +6916,15 @@ function resolveWithinRoot(relPath, root) {
6649
6916
  }
6650
6917
  return candidate;
6651
6918
  }
6919
+ function hasDotSegment(urlPathname) {
6920
+ let decoded;
6921
+ try {
6922
+ decoded = decodeURIComponent(urlPathname);
6923
+ } catch {
6924
+ return true;
6925
+ }
6926
+ return decoded.split(/[/\\]/).some((segment) => segment.startsWith("."));
6927
+ }
6652
6928
  function resolveStaticPath(urlPathname, root) {
6653
6929
  let decoded;
6654
6930
  try {
@@ -7047,14 +7323,25 @@ async function resolveBuildContext(opts) {
7047
7323
  const outDir = target.kind === "file" ? path9.dirname(target.file) : target.dir;
7048
7324
  const gates = computeGates(format, opts, config);
7049
7325
  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")}`);
7050
- return { opts, format, inputDir, outDir, workDir, target, manifestDir, config, gates };
7326
+ return {
7327
+ opts,
7328
+ format,
7329
+ inputDir,
7330
+ renderDir: manifestDir,
7331
+ outDir,
7332
+ workDir,
7333
+ target,
7334
+ manifestDir,
7335
+ config,
7336
+ gates
7337
+ };
7051
7338
  }
7052
7339
  async function runQualityGates(ctx) {
7053
- const { gates, opts, inputDir } = ctx;
7340
+ const { gates, opts, renderDir } = ctx;
7054
7341
  if (gates.lint) {
7055
7342
  log.info("Lint: CSS print-safety");
7056
7343
  const lintResult = await runLint({
7057
- manifest: opts.manifestPath ?? inputDir
7344
+ manifest: opts.manifestPath ?? renderDir
7058
7345
  });
7059
7346
  if (!lintResult.ok) {
7060
7347
  throw new BuildError("CSS lint failed", 2);
@@ -7063,7 +7350,7 @@ async function runQualityGates(ctx) {
7063
7350
  if (gates.preValidate) {
7064
7351
  log.info("Pre-build validation");
7065
7352
  const result = await executeAndReport({
7066
- input: inputDir,
7353
+ input: renderDir,
7067
7354
  phase: "pre-build",
7068
7355
  manifest: opts.manifestPath
7069
7356
  }, "text");
@@ -7073,7 +7360,7 @@ async function runQualityGates(ctx) {
7073
7360
  }
7074
7361
  }
7075
7362
  async function renderBook(ctx) {
7076
- const { config, manifestDir, inputDir, workDir } = ctx;
7363
+ const { config, renderDir, workDir } = ctx;
7077
7364
  if (config.source.files && config.source.files.length > 0) {
7078
7365
  log.info(`Using specified files (${config.source.files.length} total)`);
7079
7366
  } else {
@@ -7082,13 +7369,13 @@ async function renderBook(ctx) {
7082
7369
  if (config.plugins.length > 0) {
7083
7370
  log.info(`Loading ${config.plugins.length} plugin(s)...`);
7084
7371
  }
7085
- const { plugins, pluginCss } = await loadPluginsWithCss(config.plugins, manifestDir);
7372
+ const { plugins, pluginCss } = await loadPluginsWithCss(config.plugins, renderDir);
7086
7373
  if (plugins && plugins.length > 0) {
7087
7374
  log.success(`Loaded ${plugins.length} plugin(s)`);
7088
7375
  }
7089
7376
  const imageRefs = [];
7090
7377
  const cssAssets = [];
7091
- const htmlFile = await renderChaptersToFile(inputDir, workDir, {
7378
+ const htmlFile = await renderChaptersToFile(renderDir, workDir, {
7092
7379
  title: config.title,
7093
7380
  styles: config.styles,
7094
7381
  files: config.source.files,
@@ -7107,7 +7394,7 @@ async function renderBook(ctx) {
7107
7394
  }
7108
7395
  });
7109
7396
  log.success(`Wrote ${htmlFile}`);
7110
- const { copies: imageCopies, errors } = await planImageCopies(inputDir, imageRefs);
7397
+ const { copies: imageCopies, errors } = await planImageCopies(renderDir, imageRefs);
7111
7398
  if (errors.length > 0) {
7112
7399
  throw new BuildError(`Cannot resolve ${errors.length} image reference(s):
7113
7400
  ` + errors.map((e) => ` - ${e}`).join(`
@@ -7146,7 +7433,8 @@ async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
7146
7433
  async function finalizeBuild(ctx, fingerprint, wroteMessage, paths, artifactName2 = null) {
7147
7434
  const workFingerprint = await writeBuildFingerprint({
7148
7435
  ...fingerprint,
7149
- outputDir: ctx.workDir
7436
+ outputDir: ctx.workDir,
7437
+ recordedOutputDir: fingerprint.outputDir
7150
7438
  });
7151
7439
  await publishBuild(ctx, artifactName2);
7152
7440
  const delivered = ctx.target.kind !== "file";
@@ -7237,7 +7525,9 @@ class PdfOutput {
7237
7525
  await renderHtmlToPdf(htmlFile, rawPdf, opts.pdfRenderer, staticHtmlRaw);
7238
7526
  if (staticHtmlRaw && fs2.existsSync(staticHtmlRaw)) {
7239
7527
  await finalizeStaticBook(await fsp3.readFile(staticHtmlRaw, "utf-8"), htmlFile, workDir);
7240
- log.success(`Wrote static desktop: ${path9.join(outDir, BOOK_HTML)}`);
7528
+ if (ctx.target.kind !== "file") {
7529
+ log.success(`Wrote static desktop: ${path9.join(outDir, BOOK_HTML)}`);
7530
+ }
7241
7531
  }
7242
7532
  if (!pdfxMode) {
7243
7533
  try {
@@ -7370,6 +7660,7 @@ async function renderPreviewBook(inputPath, config, opts) {
7370
7660
  plugins,
7371
7661
  pluginCss,
7372
7662
  wrapChapters: opts.wrapChapters,
7663
+ ...opts.onCssAssets ? { onCssAssets: opts.onCssAssets } : {},
7373
7664
  onChapterWarnings: (file, warnings) => {
7374
7665
  for (const w of warnings) {
7375
7666
  warn2(` ${file}, line ${w.line}: ${w.message}`);
@@ -7384,15 +7675,24 @@ function injectPreviewScripts(html) {
7384
7675
  return html.replace(pagedjsPolyfillTagRegex(), iface + BREAK_INSIDE_HANDLER + `
7385
7676
  <script src="/vendor/paged.polyfill.js"></script>`);
7386
7677
  }
7387
- async function generateAndWriteHtml(inputPath, tempDir, config) {
7678
+ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
7388
7679
  if (!inputPath) {
7680
+ cssAssets.clear();
7389
7681
  await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
7390
7682
  return;
7391
7683
  }
7684
+ const nextAssets = new Map;
7392
7685
  const html = await renderPreviewBook(inputPath, config, {
7393
7686
  files: config.source?.files ?? null,
7394
- wrapChapters: true
7687
+ wrapChapters: true,
7688
+ onCssAssets: (copies) => {
7689
+ for (const copy of copies)
7690
+ nextAssets.set(copy.to, copy.from);
7691
+ }
7395
7692
  });
7693
+ cssAssets.clear();
7694
+ for (const [to, from] of nextAssets)
7695
+ cssAssets.set(to, from);
7396
7696
  await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
7397
7697
  }
7398
7698
  function isDotPathUnderRoot(candidatePath, root) {
@@ -7410,11 +7710,24 @@ function isDotPathUnderRoot(candidatePath, root) {
7410
7710
  return false;
7411
7711
  return rel.split("/").some((segment) => segment.startsWith("."));
7412
7712
  }
7713
+ function isGeneratedProjectPath(relative2) {
7714
+ const segments = relative2.split("/");
7715
+ for (let i = 0;i < segments.length; i++) {
7716
+ const segment = segments[i];
7717
+ if (segment === "dist" || segment === "node_modules")
7718
+ return true;
7719
+ if (segment === "plugins" && segments[i + 1] === "npm")
7720
+ return true;
7721
+ }
7722
+ return false;
7723
+ }
7413
7724
  function isIgnoredWatchPath(candidatePath, projectRoot) {
7414
7725
  const normalizedRoot = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
7415
7726
  const normalizedPath = candidatePath.replace(/\\/g, "/");
7416
- const inProject = normalizedPath === normalizedRoot || normalizedPath.startsWith(normalizedRoot + "/");
7417
- return inProject ? isDotPathUnderRoot(candidatePath, projectRoot) : false;
7727
+ if (!normalizedPath.startsWith(normalizedRoot + "/"))
7728
+ return false;
7729
+ const relative2 = normalizedPath.slice(normalizedRoot.length + 1);
7730
+ return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
7418
7731
  }
7419
7732
  function isExternalWatchCandidate(candidatePath, targets) {
7420
7733
  const candidate = path10.resolve(candidatePath);
@@ -7593,7 +7906,7 @@ function createFileWatcher(state) {
7593
7906
  await syncExternalWatches();
7594
7907
  if (closed)
7595
7908
  return;
7596
- await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig);
7909
+ await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig, state.cssAssets);
7597
7910
  if (closed)
7598
7911
  return;
7599
7912
  state.previewServer?.broadcastReload();
@@ -7710,7 +8023,7 @@ async function restartPreview(newInputPath, state) {
7710
8023
  state.currentInputPath = newInputPath;
7711
8024
  const manifest = await loadManifest(newInputPath);
7712
8025
  state.config = resolveConfig({}, manifest);
7713
- await generateAndWriteHtml(newInputPath, state.tempDir, state.config);
8026
+ await generateAndWriteHtml(newInputPath, state.tempDir, state.config, state.cssAssets);
7714
8027
  startFileWatcher(state);
7715
8028
  state.previewServer?.broadcastReload();
7716
8029
  info("Preview restarted successfully");
@@ -7768,7 +8081,8 @@ function createServerState(inputPath, tempDir, config, options) {
7768
8081
  isShuttingDown: false,
7769
8082
  tempDir,
7770
8083
  config,
7771
- options
8084
+ options,
8085
+ cssAssets: new Map
7772
8086
  };
7773
8087
  }
7774
8088
 
@@ -8001,15 +8315,6 @@ function matchesEmbedded(urlPathname) {
8001
8315
  return true;
8002
8316
  return EMBEDDED_PREFIXES.some((p) => urlPathname.startsWith(p));
8003
8317
  }
8004
- function isDotfileRequest(urlPathname) {
8005
- let decoded;
8006
- try {
8007
- decoded = decodeURIComponent(urlPathname);
8008
- } catch {
8009
- return true;
8010
- }
8011
- return decoded.split("/").some((segment) => segment.startsWith("."));
8012
- }
8013
8318
  async function createPreviewServer(state, port) {
8014
8319
  const clients = new Set;
8015
8320
  const wss = new WebSocketServer({ noServer: true });
@@ -8060,8 +8365,18 @@ async function createPreviewServer(state, port) {
8060
8365
  return;
8061
8366
  }
8062
8367
  const pathname = url.pathname === "/" ? "/book.html" : url.pathname;
8368
+ const cssAssetSource = state.cssAssets.get(pathname.replace(/^\/+/, ""));
8369
+ if (cssAssetSource) {
8370
+ await serveStatic(cssAssetSource, res).catch((err) => {
8371
+ if (!res.headersSent) {
8372
+ res.writeHead(500);
8373
+ res.end(`Internal Server Error: ${err.message}`);
8374
+ }
8375
+ });
8376
+ return;
8377
+ }
8063
8378
  const servingProjectRoot = pathname !== "/book.html";
8064
- if (servingProjectRoot && (!state.currentInputPath || isDotfileRequest(pathname))) {
8379
+ if (servingProjectRoot && (!state.currentInputPath || hasDotSegment(pathname))) {
8065
8380
  res.writeHead(404);
8066
8381
  res.end("Not Found");
8067
8382
  return;
@@ -8146,8 +8461,8 @@ async function startPreviewServer(options) {
8146
8461
  }
8147
8462
  const config = await initializeConfiguration(inputPath);
8148
8463
  const tempDir = await initializePreviewDirectories();
8149
- await generateAndWriteHtml(inputPath, tempDir, config);
8150
8464
  const state = createServerState(inputPath, tempDir, config, options);
8465
+ await generateAndWriteHtml(inputPath, tempDir, config, state.cssAssets);
8151
8466
  const restartPreview2 = async (newInputPath) => {
8152
8467
  await restartPreview(newInputPath, state);
8153
8468
  };
@@ -8437,16 +8752,77 @@ async function getSystemDiagnostics() {
8437
8752
  },
8438
8753
  tools: [chromium, ...tools],
8439
8754
  configDir: defaultConfigDir(),
8440
- docsUrl: "https://github.com/dimm-city/gutterpress/blob/main/examples/gutterpress-user-guide/08-system-setup.md"
8755
+ docsUrl: "https://github.com/dimm-city/gutterpress/blob/main/examples/gutterpress-user-guide/07-system-setup.md"
8441
8756
  };
8442
8757
  }
8443
8758
  // src/lib/project-scaffold.ts
8444
- import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir6, readFile as readFile23, stat as stat5, writeFile as writeFile8 } from "node:fs/promises";
8445
- import { constants as FS, existsSync as existsSync12 } from "node:fs";
8759
+ import { access, copyFile, cp as cp2, mkdir as mkdir8, readdir as readdir6, readFile as readFile24, stat as stat5, writeFile as writeFile8 } from "node:fs/promises";
8760
+ import { constants as FS, existsSync as existsSync13 } from "node:fs";
8761
+ import path15 from "node:path";
8762
+
8763
+ // src/lib/manifest-doc.ts
8764
+ import { existsSync as existsSync12 } from "node:fs";
8765
+ import { randomUUID } from "node:crypto";
8766
+ import { mkdir as mkdir7, open, readFile as readFile23, rename as rename3, rm as rm2 } from "node:fs/promises";
8446
8767
  import path14 from "node:path";
8768
+ import { parseDocument, isSeq, YAMLSeq } from "yaml";
8769
+ function resolveManifestPath(projectDir) {
8770
+ const existing = MANIFEST_FILENAMES.find((name) => existsSync12(path14.join(projectDir, name)));
8771
+ return path14.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
8772
+ }
8773
+ async function loadManifestDoc(projectDir) {
8774
+ const file = resolveManifestPath(projectDir);
8775
+ let text = "";
8776
+ try {
8777
+ text = await readFile23(file, "utf8");
8778
+ } catch (error2) {
8779
+ if (error2.code !== "ENOENT")
8780
+ throw error2;
8781
+ }
8782
+ return { doc: parseDocument(text), file };
8783
+ }
8784
+ async function writeManifestDoc(file, doc) {
8785
+ const dir = path14.dirname(file);
8786
+ await mkdir7(dir, { recursive: true });
8787
+ const temporary = path14.join(dir, `.${path14.basename(file)}.tmp-${process.pid}-${randomUUID()}`);
8788
+ try {
8789
+ const handle = await open(temporary, "wx", 438);
8790
+ try {
8791
+ await handle.writeFile(doc.toString(), "utf8");
8792
+ await handle.sync();
8793
+ } finally {
8794
+ await handle.close();
8795
+ }
8796
+ await rename3(temporary, file);
8797
+ const directory = await open(dir, "r").catch(() => null);
8798
+ if (directory) {
8799
+ await directory.sync().catch(() => {});
8800
+ await directory.close();
8801
+ }
8802
+ } finally {
8803
+ await rm2(temporary, { force: true });
8804
+ }
8805
+ }
8806
+ function ensureSeq(doc, key) {
8807
+ let seq = doc.get(key, true);
8808
+ if (!isSeq(seq)) {
8809
+ const fresh = new YAMLSeq(doc.schema);
8810
+ doc.set(key, fresh);
8811
+ seq = fresh;
8812
+ }
8813
+ return seq;
8814
+ }
8815
+ function scalarString(item) {
8816
+ if (item && typeof item === "object" && "value" in item) {
8817
+ const v = item.value;
8818
+ return typeof v === "string" ? v : null;
8819
+ }
8820
+ return typeof item === "string" ? item : null;
8821
+ }
8822
+
8823
+ // src/lib/project-scaffold.ts
8447
8824
  var STARTER_THEME_FOR_TEMPLATE = {
8448
8825
  book: "clean-book",
8449
- ttrpg: "ttrpg-supplement",
8450
8826
  zine: "zine",
8451
8827
  technical: "technical-doc"
8452
8828
  };
@@ -8466,6 +8842,39 @@ function escapeYamlScalar(value) {
8466
8842
  return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
8467
8843
  }
8468
8844
  var DEFAULT_AUTHOR = "Anonymous";
8845
+ function isPositivePoints(value) {
8846
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
8847
+ }
8848
+ function requirePreset(preset, customPage) {
8849
+ if (!preset || !PRESET_IDS.includes(preset)) {
8850
+ const got = preset ? ` (got "${preset}")` : "";
8851
+ throw new CreateProjectErrorImpl("preset-required", `A preset is required to create a book${got}. Choose one of: ${PRESET_IDS.join(", ")}.`);
8852
+ }
8853
+ if (preset === "custom") {
8854
+ const missing = [];
8855
+ if (!customPage || !isPositivePoints(customPage.width))
8856
+ missing.push("page width");
8857
+ if (!customPage || !isPositivePoints(customPage.height))
8858
+ missing.push("page height");
8859
+ if (missing.length > 0) {
8860
+ 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).`);
8861
+ }
8862
+ } else if (customPage && (!isPositivePoints(customPage.width) || !isPositivePoints(customPage.height))) {
8863
+ throw new CreateProjectErrorImpl("custom-page-required", "Page overrides need a positive width and height in points (72pt = 1in).");
8864
+ }
8865
+ if (customPage?.tolerance !== undefined && !isPositivePoints(customPage.tolerance)) {
8866
+ throw new CreateProjectErrorImpl("custom-page-required", "Page tolerance must be a positive number of points.");
8867
+ }
8868
+ return preset;
8869
+ }
8870
+ function resolveScaffoldTargets(requested, preset) {
8871
+ const ids = requested ?? [...PRESETS[preset].defaultTargets];
8872
+ const unknown = ids.filter((id) => !TARGETS[id]);
8873
+ if (unknown.length > 0) {
8874
+ throw new CreateProjectErrorImpl("invalid-targets", `Unknown publish target${unknown.length > 1 ? "s" : ""} "${unknown.join('", "')}". ` + `Known targets: ${TARGET_IDS.join(", ")}.`);
8875
+ }
8876
+ return [...new Set(ids)];
8877
+ }
8469
8878
  async function scaffoldProject(options) {
8470
8879
  const name = (options.name ?? "").trim();
8471
8880
  if (!name) {
@@ -8477,7 +8886,7 @@ async function scaffoldProject(options) {
8477
8886
  throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
8478
8887
  }
8479
8888
  const parentDir = options.parentDir;
8480
- if (!parentDir || !path14.isAbsolute(parentDir)) {
8889
+ if (!parentDir || !path15.isAbsolute(parentDir)) {
8481
8890
  throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
8482
8891
  }
8483
8892
  try {
@@ -8485,7 +8894,7 @@ async function scaffoldProject(options) {
8485
8894
  } catch {
8486
8895
  throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
8487
8896
  }
8488
- const projectDir = path14.join(parentDir, folderName);
8897
+ const projectDir = path15.join(parentDir, folderName);
8489
8898
  let targetExists = true;
8490
8899
  try {
8491
8900
  await access(projectDir, FS.F_OK);
@@ -8497,26 +8906,28 @@ async function scaffoldProject(options) {
8497
8906
  }
8498
8907
  const template = options.template ?? "book";
8499
8908
  const customTemplateDir = options.templateDir;
8909
+ const preset = customTemplateDir && options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
8910
+ const targets = preset ? resolveScaffoldTargets(options.targets, preset) : undefined;
8500
8911
  try {
8501
- await mkdir7(projectDir, { recursive: true });
8912
+ await mkdir8(projectDir, { recursive: true });
8502
8913
  if (customTemplateDir) {
8503
8914
  const entries = await readdir6(customTemplateDir, { withFileTypes: true });
8504
8915
  for (const entry of entries) {
8505
8916
  if (entry.name === ".gutterpress-template.json")
8506
8917
  continue;
8507
- await cp2(path14.join(customTemplateDir, entry.name), path14.join(projectDir, entry.name), { recursive: true });
8918
+ await cp2(path15.join(customTemplateDir, entry.name), path15.join(projectDir, entry.name), { recursive: true });
8508
8919
  }
8509
- await mkdir7(path14.join(projectDir, "assets"), { recursive: true });
8920
+ await mkdir8(path15.join(projectDir, "assets"), { recursive: true });
8510
8921
  } else {
8511
8922
  const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
8512
8923
  const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
8513
- await mkdir7(path14.join(projectDir, "assets"), { recursive: true });
8514
- await copyFile(tplManifest, path14.join(projectDir, MANIFEST_FILENAMES[0]));
8515
- await copyFile(tplChapter, path14.join(projectDir, "chapter-01.md"));
8924
+ await mkdir8(path15.join(projectDir, "assets"), { recursive: true });
8925
+ await copyFile(tplManifest, path15.join(projectDir, MANIFEST_FILENAMES[0]));
8926
+ await copyFile(tplChapter, path15.join(projectDir, "chapter-01.md"));
8516
8927
  const starterThemeId = STARTER_THEME_FOR_TEMPLATE[template] ?? "clean-book";
8517
8928
  const starterCss = await getAssetPath(`themes/${starterThemeId}/theme.css`);
8518
- await mkdir7(path14.join(projectDir, "styles"), { recursive: true });
8519
- await copyFile(starterCss, path14.join(projectDir, "styles", "book.css"));
8929
+ await mkdir8(path15.join(projectDir, "styles"), { recursive: true });
8930
+ await copyFile(starterCss, path15.join(projectDir, "styles", "book.css"));
8520
8931
  }
8521
8932
  } catch (e) {
8522
8933
  throw new CreateProjectErrorImpl("scaffold-io", `Could not create the project files: ${e instanceof Error ? e.message : String(e)}`);
@@ -8526,18 +8937,34 @@ async function scaffoldProject(options) {
8526
8937
  } catch (e) {
8527
8938
  throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
8528
8939
  }
8529
- const manifestPath = MANIFEST_FILENAMES.map((name2) => path14.join(projectDir, name2)).find(existsSync12) ?? path14.join(projectDir, MANIFEST_FILENAMES[0]);
8940
+ const manifestPath = MANIFEST_FILENAMES.map((name2) => path15.join(projectDir, name2)).find(existsSync13) ?? path15.join(projectDir, MANIFEST_FILENAMES[0]);
8530
8941
  const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
8531
8942
  const substitutions = {
8532
8943
  "{{TITLE}}": escapeYamlScalar(name),
8533
8944
  "{{AUTHOR}}": escapeYamlScalar(author)
8534
8945
  };
8535
- let openFile = path14.join(projectDir, "chapter-01.md");
8946
+ let openFile = path15.join(projectDir, "chapter-01.md");
8536
8947
  try {
8537
8948
  await fillTemplateFile(manifestPath, substitutions);
8949
+ if (preset) {
8950
+ const { doc, file } = await loadManifestDoc(projectDir);
8951
+ doc.set("preset", preset);
8952
+ doc.set("targets", doc.createNode(targets ?? []));
8953
+ if (options.customPage) {
8954
+ const page = {
8955
+ width: options.customPage.width,
8956
+ height: options.customPage.height
8957
+ };
8958
+ if (options.customPage.tolerance !== undefined) {
8959
+ page.tolerance = options.customPage.tolerance;
8960
+ }
8961
+ doc.set("page", doc.createNode(page));
8962
+ }
8963
+ await writeManifestDoc(file, doc);
8964
+ }
8538
8965
  const firstSource = await firstSourceFile(manifestPath);
8539
8966
  if (firstSource)
8540
- openFile = path14.join(projectDir, firstSource);
8967
+ openFile = path15.join(projectDir, firstSource);
8541
8968
  await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
8542
8969
  } catch (e) {
8543
8970
  throw new CreateProjectErrorImpl("scaffold-io", `Could not finalise the project files: ${e instanceof Error ? e.message : String(e)}`);
@@ -8547,7 +8974,7 @@ async function scaffoldProject(options) {
8547
8974
  let versionHistoryError;
8548
8975
  if (requested === "local-git") {
8549
8976
  try {
8550
- const { providerFor } = await import("./source-provider-hp96b5jf.js");
8977
+ const { providerFor } = await import("./source-provider-c1rjm2c0.js");
8551
8978
  const provider = providerFor({ type: "local-folder", path: projectDir });
8552
8979
  await provider.initVersionHistory({
8553
8980
  projectDir,
@@ -8557,9 +8984,9 @@ async function scaffoldProject(options) {
8557
8984
  versionHistory = "local-git";
8558
8985
  } catch (e) {
8559
8986
  versionHistory = "none";
8560
- const { findEnclosingRepoDir } = await import("./project-source-0tg0r6vm.js");
8987
+ const { findEnclosingRepoDir } = await import("./project-source-p0gn1wd5.js");
8561
8988
  const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
8562
- 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);
8989
+ versionHistoryError = enclosingRepoDir ? `This book was added to ${path15.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
8563
8990
  }
8564
8991
  }
8565
8992
  const result = {
@@ -8579,7 +9006,7 @@ function prettifyFolderName(base) {
8579
9006
  }
8580
9007
  async function adoptFolder(options) {
8581
9008
  const dir = options.dir;
8582
- if (typeof dir !== "string" || !path14.isAbsolute(dir)) {
9009
+ if (typeof dir !== "string" || !path15.isAbsolute(dir)) {
8583
9010
  throw new CreateProjectErrorImpl("scaffold-io", "An absolute folder path is required.");
8584
9011
  }
8585
9012
  let st;
@@ -8591,11 +9018,11 @@ async function adoptFolder(options) {
8591
9018
  if (!st.isDirectory()) {
8592
9019
  throw new CreateProjectErrorImpl("scaffold-io", `Not a folder: ${dir}`);
8593
9020
  }
8594
- if (MANIFEST_FILENAMES.some((name) => existsSync12(path14.join(dir, name)))) {
9021
+ if (MANIFEST_FILENAMES.some((name) => existsSync13(path15.join(dir, name)))) {
8595
9022
  throw new CreateProjectErrorImpl("target-exists", "This folder is already a Gutterpress project.");
8596
9023
  }
8597
9024
  const template = options.template ?? "book";
8598
- const title = (options.title ?? "").trim() || prettifyFolderName(path14.basename(dir));
9025
+ const title = (options.title ?? "").trim() || prettifyFolderName(path15.basename(dir));
8599
9026
  const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
8600
9027
  let mdFiles;
8601
9028
  try {
@@ -8603,14 +9030,14 @@ async function adoptFolder(options) {
8603
9030
  mdFiles = entries.filter((e) => e.isFile() && /\.(md|markdown)$/i.test(e.name)).map((e) => e.name).sort((a, b) => a.localeCompare(b));
8604
9031
  if (mdFiles.length === 0) {
8605
9032
  const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
8606
- await copyFile(tplChapter, path14.join(dir, "chapter-01.md"));
8607
- await fillTemplateFile(path14.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
9033
+ await copyFile(tplChapter, path15.join(dir, "chapter-01.md"));
9034
+ await fillTemplateFile(path15.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
8608
9035
  mdFiles = ["chapter-01.md"];
8609
9036
  }
8610
9037
  const themeId = STARTER_THEME_FOR_TEMPLATE[template] ?? "clean-book";
8611
- await mkdir7(path14.join(dir, "styles"), { recursive: true });
8612
- const bookCssPath = path14.join(dir, "styles", "book.css");
8613
- if (!existsSync12(bookCssPath)) {
9038
+ await mkdir8(path15.join(dir, "styles"), { recursive: true });
9039
+ const bookCssPath = path15.join(dir, "styles", "book.css");
9040
+ if (!existsSync13(bookCssPath)) {
8614
9041
  await copyFile(await getAssetPath(`themes/${themeId}/theme.css`), bookCssPath);
8615
9042
  }
8616
9043
  const filesYaml = mdFiles.map((f) => ` - "${escapeYamlScalar(f)}"`).join(`
@@ -8618,14 +9045,17 @@ async function adoptFolder(options) {
8618
9045
  const manifest = `title: "${escapeYamlScalar(title)}"
8619
9046
  authors:
8620
9047
  - "${escapeYamlScalar(author)}"
9048
+ preset: dtrpg
9049
+ targets:
9050
+ - dtrpg
8621
9051
  source:
8622
9052
  files:
8623
9053
  ${filesYaml}
8624
9054
  styles:
8625
9055
  - styles/book.css
8626
9056
  `;
8627
- await writeFile8(path14.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
8628
- await mkdir7(path14.join(dir, "assets"), { recursive: true });
9057
+ await writeFile8(path15.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
9058
+ await mkdir8(path15.join(dir, "assets"), { recursive: true });
8629
9059
  await ensureGitignoreHasDist(dir);
8630
9060
  } catch (e) {
8631
9061
  if (e instanceof CreateProjectErrorImpl)
@@ -8637,7 +9067,7 @@ styles:
8637
9067
  let versionHistoryError;
8638
9068
  if (requested === "local-git") {
8639
9069
  try {
8640
- const { providerFor } = await import("./source-provider-hp96b5jf.js");
9070
+ const { providerFor } = await import("./source-provider-c1rjm2c0.js");
8641
9071
  const provider = providerFor({ type: "local-folder", path: dir });
8642
9072
  await provider.initVersionHistory({
8643
9073
  projectDir: dir,
@@ -8652,8 +9082,8 @@ styles:
8652
9082
  }
8653
9083
  const result = {
8654
9084
  projectDir: dir,
8655
- manifestPath: path14.join(dir, MANIFEST_FILENAMES[0]),
8656
- openFile: path14.join(dir, mdFiles[0]),
9085
+ manifestPath: path15.join(dir, MANIFEST_FILENAMES[0]),
9086
+ openFile: path15.join(dir, mdFiles[0]),
8657
9087
  versionHistory
8658
9088
  };
8659
9089
  if (versionHistoryError !== undefined)
@@ -8661,10 +9091,10 @@ styles:
8661
9091
  return result;
8662
9092
  }
8663
9093
  async function ensureGitignoreHasDist(projectDir) {
8664
- const gitignorePath = path14.join(projectDir, ".gitignore");
9094
+ const gitignorePath = path15.join(projectDir, ".gitignore");
8665
9095
  let existing = null;
8666
9096
  try {
8667
- existing = await readFile23(gitignorePath, "utf8");
9097
+ existing = await readFile24(gitignorePath, "utf8");
8668
9098
  } catch {}
8669
9099
  if (existing === null) {
8670
9100
  await writeFile8(gitignorePath, `dist/
@@ -8684,7 +9114,7 @@ async function ensureGitignoreHasDist(projectDir) {
8684
9114
  async function firstSourceFile(manifestPath) {
8685
9115
  let text;
8686
9116
  try {
8687
- text = await readFile23(manifestPath, "utf8");
9117
+ text = await readFile24(manifestPath, "utf8");
8688
9118
  } catch {
8689
9119
  return;
8690
9120
  }
@@ -8696,32 +9126,45 @@ async function firstSourceFile(manifestPath) {
8696
9126
  return m ? m[1].trim() : undefined;
8697
9127
  }
8698
9128
  async function fillTemplateFile(filePath2, substitutions) {
8699
- let text = await readFile23(filePath2, "utf8");
9129
+ let text = await readFile24(filePath2, "utf8");
8700
9130
  for (const [token, value] of Object.entries(substitutions)) {
8701
9131
  text = text.split(token).join(value);
8702
9132
  }
8703
9133
  await writeFile8(filePath2, text, "utf8");
8704
9134
  }
8705
9135
  // src/lib/project-templates.ts
8706
- import { cp as cp3, mkdir as mkdir8, readFile as readFile24, readdir as readdir7, stat as stat6, writeFile as writeFile9 } from "node:fs/promises";
8707
- import { constants as FS2 } from "node:fs";
9136
+ import { cp as cp3, mkdir as mkdir9, readFile as readFile25, readdir as readdir7, stat as stat6, writeFile as writeFile9 } from "node:fs/promises";
9137
+ import { constants as FS2, existsSync as existsSync14 } from "node:fs";
8708
9138
  import { access as access2 } from "node:fs/promises";
8709
- import path15 from "node:path";
9139
+ import path16 from "node:path";
9140
+ import { isMap, isScalar, isSeq as isSeq2, parseDocument as parseDocument2 } from "yaml";
8710
9141
  var BUILT_IN_TEMPLATE_IDS = [
8711
9142
  "book",
8712
- "ttrpg",
8713
9143
  "zine",
8714
9144
  "technical"
8715
9145
  ];
9146
+ async function readTemplateChoices(manifestPath) {
9147
+ try {
9148
+ const text = await readFile25(manifestPath, "utf8");
9149
+ const doc = parseDocument2(text);
9150
+ const out = {};
9151
+ const preset = doc.get("preset");
9152
+ if (typeof preset === "string" && preset)
9153
+ out.preset = preset;
9154
+ const targets = doc.get("targets", true);
9155
+ if (isSeq2(targets)) {
9156
+ out.targets = targets.items.map((i) => scalarString(i)).filter((s) => s !== null);
9157
+ }
9158
+ return out;
9159
+ } catch {
9160
+ return {};
9161
+ }
9162
+ }
8716
9163
  var BUILT_IN_META = {
8717
9164
  book: {
8718
9165
  label: "Book",
8719
9166
  description: "A clean starting point for a novel, memoir, or any long-form book."
8720
9167
  },
8721
- ttrpg: {
8722
- label: "TTRPG supplement",
8723
- description: "Rules, stat blocks, and tables for a tabletop roleplaying supplement."
8724
- },
8725
9168
  zine: {
8726
9169
  label: "Zine",
8727
9170
  description: "A short, personal zine made to be printed, folded, and shared."
@@ -8732,12 +9175,13 @@ var BUILT_IN_META = {
8732
9175
  }
8733
9176
  };
8734
9177
  async function listBuiltInTemplates() {
8735
- return BUILT_IN_TEMPLATE_IDS.map((id) => ({
9178
+ return Promise.all(BUILT_IN_TEMPLATE_IDS.map(async (id) => ({
8736
9179
  id,
8737
9180
  label: BUILT_IN_META[id].label,
8738
9181
  description: BUILT_IN_META[id].description,
8739
- kind: "builtin"
8740
- }));
9182
+ kind: "builtin",
9183
+ ...await readTemplateChoices(await getAssetPath(`templates/${id}/manifest.yaml`))
9184
+ })));
8741
9185
  }
8742
9186
  var SKIP_ENTRIES = new Set([
8743
9187
  ".git",
@@ -8748,13 +9192,100 @@ var SKIP_ENTRIES = new Set([
8748
9192
  ".gutterpress",
8749
9193
  ".DS_Store"
8750
9194
  ]);
9195
+ var VENDOR_DIR = "shared";
9196
+ function collectEscaping(nodes, getValue, escapes) {
9197
+ const out = [];
9198
+ for (const node of nodes) {
9199
+ const value = getValue(node);
9200
+ if (value !== null && escapes(value))
9201
+ out.push({ node, value });
9202
+ }
9203
+ return out;
9204
+ }
9205
+ function pluginEntryPath(node) {
9206
+ return isMap(node) ? scalarString(node.get("path", true)) : scalarString(node);
9207
+ }
9208
+ function dropEscaping(doc, seq, key, escaping) {
9209
+ if (!isSeq2(seq))
9210
+ return;
9211
+ const drop = new Set(escaping.map((e) => e.node));
9212
+ seq.items = seq.items.filter((node) => !drop.has(node));
9213
+ if (seq.items.length === 0)
9214
+ doc.delete(key);
9215
+ }
9216
+ function rewriteEntry(node, newPath) {
9217
+ if (isMap(node))
9218
+ node.set("path", newPath);
9219
+ else if (isScalar(node))
9220
+ node.value = newPath;
9221
+ }
9222
+ function commonAncestorDir(absFiles) {
9223
+ const dirSegs = absFiles.map((f) => path16.resolve(f).split(path16.sep).slice(0, -1));
9224
+ if (dirSegs.length === 0)
9225
+ return "";
9226
+ let common = dirSegs[0];
9227
+ for (const segs of dirSegs.slice(1)) {
9228
+ let i = 0;
9229
+ while (i < common.length && i < segs.length && common[i] === segs[i])
9230
+ i++;
9231
+ common = common.slice(0, i);
9232
+ }
9233
+ return common.join(path16.sep) || path16.sep;
9234
+ }
9235
+ function pickVendorDir(templateDir, base) {
9236
+ const preferred = path16.basename(base) || VENDOR_DIR;
9237
+ if (!existsSync14(path16.join(templateDir, preferred)))
9238
+ return preferred;
9239
+ for (let n = 1;; n++) {
9240
+ const candidate = `${preferred}-${n}`;
9241
+ if (!existsSync14(path16.join(templateDir, candidate)))
9242
+ return candidate;
9243
+ }
9244
+ }
8751
9245
  var TEMPLATE_META_FILE = ".gutterpress-template.json";
9246
+ async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
9247
+ const { doc, file } = await loadManifestDoc(templateDir);
9248
+ const escapes = (p) => escapesProjectRoot(sourceProjectDir, path16.resolve(sourceProjectDir, p));
9249
+ const stylesSeq = doc.get("styles", true);
9250
+ const pluginsSeq = doc.get("plugins", true);
9251
+ const escapingStyles = collectEscaping(isSeq2(stylesSeq) ? stylesSeq.items : [], scalarString, escapes);
9252
+ const escapingPlugins = collectEscaping(isSeq2(pluginsSeq) ? pluginsSeq.items : [], pluginEntryPath, escapes);
9253
+ if (escapingStyles.length === 0 && escapingPlugins.length === 0) {
9254
+ return { vendoredRefs: [], excludedRefs: [] };
9255
+ }
9256
+ if (mode === "exclude") {
9257
+ const excludedRefs = [...escapingStyles, ...escapingPlugins].map((e) => e.value);
9258
+ dropEscaping(doc, stylesSeq, "styles", escapingStyles);
9259
+ dropEscaping(doc, pluginsSeq, "plugins", escapingPlugins);
9260
+ await writeManifestDoc(file, doc);
9261
+ return { vendoredRefs: [], excludedRefs };
9262
+ }
9263
+ const styleClosure = await collectStyleDependencies(sourceProjectDir, escapingStyles.map((s) => s.value));
9264
+ const pluginFiles = escapingPlugins.map((p) => path16.resolve(sourceProjectDir, p.value));
9265
+ const externalFiles = [...new Set([...styleClosure, ...pluginFiles])].filter((f) => escapesProjectRoot(sourceProjectDir, f));
9266
+ const base = commonAncestorDir(externalFiles);
9267
+ const vendorDir = pickVendorDir(templateDir, base);
9268
+ await Promise.all(externalFiles.filter((abs2) => existsSync14(abs2)).map(async (abs2) => {
9269
+ const dest = path16.join(templateDir, vendorDir, path16.relative(base, abs2));
9270
+ await mkdir9(path16.dirname(dest), { recursive: true });
9271
+ await cp3(abs2, dest);
9272
+ }));
9273
+ const bookLocal = (value) => toPosixPath(path16.join(vendorDir, path16.relative(base, path16.resolve(sourceProjectDir, value))));
9274
+ const vendoredRefs = [];
9275
+ for (const { node, value } of [...escapingStyles, ...escapingPlugins]) {
9276
+ const rewritten = bookLocal(value);
9277
+ rewriteEntry(node, rewritten);
9278
+ vendoredRefs.push(rewritten);
9279
+ }
9280
+ await writeManifestDoc(file, doc);
9281
+ return { vendoredRefs, excludedRefs: [] };
9282
+ }
8752
9283
  async function saveProjectAsTemplate(options) {
8753
9284
  const { projectDir, name, templatesRoot } = options;
8754
9285
  const id = slugify(name);
8755
9286
  if (!id)
8756
9287
  throw new Error(`Could not derive a template id from "${name}".`);
8757
- const dir = path15.join(templatesRoot, id);
9288
+ const dir = path16.join(templatesRoot, id);
8758
9289
  let exists = true;
8759
9290
  try {
8760
9291
  await access2(dir, FS2.F_OK);
@@ -8764,31 +9295,33 @@ async function saveProjectAsTemplate(options) {
8764
9295
  if (exists) {
8765
9296
  throw new Error(`A template named "${name}" already exists. Choose a different name.`);
8766
9297
  }
8767
- await mkdir8(dir, { recursive: true });
9298
+ await mkdir9(dir, { recursive: true });
8768
9299
  const entries = await readdir7(projectDir, { withFileTypes: true });
8769
9300
  for (const entry of entries) {
8770
9301
  if (SKIP_ENTRIES.has(entry.name))
8771
9302
  continue;
8772
9303
  if (entry.name === TEMPLATE_META_FILE)
8773
9304
  continue;
8774
- await cp3(path15.join(projectDir, entry.name), path15.join(dir, entry.name), { recursive: true });
9305
+ await cp3(path16.join(projectDir, entry.name), path16.join(dir, entry.name), { recursive: true });
8775
9306
  }
8776
9307
  const manifestName = MANIFEST_FILENAMES.find((name2) => entries.some((entry) => entry.name === name2));
9308
+ const shared = manifestName ? await reconcileSharedRefs(dir, projectDir, options.sharedRefs ?? "vendor") : { vendoredRefs: [], excludedRefs: [] };
8777
9309
  if (manifestName)
8778
- await retokeniseManifest(path15.join(dir, manifestName));
8779
- await writeFile9(path15.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
9310
+ await retokeniseManifest(path16.join(dir, manifestName));
9311
+ await writeFile9(path16.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
8780
9312
  return {
8781
9313
  id,
8782
9314
  label: name,
8783
9315
  description: "Your saved template.",
8784
9316
  kind: "custom",
8785
- dir
9317
+ dir,
9318
+ ...shared
8786
9319
  };
8787
9320
  }
8788
9321
  async function retokeniseManifest(manifestPath) {
8789
9322
  let text;
8790
9323
  try {
8791
- text = await readFile24(manifestPath, "utf8");
9324
+ text = await readFile25(manifestPath, "utf8");
8792
9325
  } catch {
8793
9326
  return;
8794
9327
  }
@@ -8806,14 +9339,21 @@ async function listCustomTemplates(templatesRoot) {
8806
9339
  for (const entry of entries) {
8807
9340
  if (!entry.isDirectory())
8808
9341
  continue;
8809
- const dir = path15.join(templatesRoot, entry.name);
9342
+ const dir = path16.join(templatesRoot, entry.name);
8810
9343
  let label = prettify(entry.name);
8811
9344
  try {
8812
- const meta = JSON.parse(await readFile24(path15.join(dir, TEMPLATE_META_FILE), "utf8"));
9345
+ const meta = JSON.parse(await readFile25(path16.join(dir, TEMPLATE_META_FILE), "utf8"));
8813
9346
  if (typeof meta.label === "string" && meta.label)
8814
9347
  label = meta.label;
8815
9348
  } catch {}
8816
- out.push({ id: entry.name, label, description: "Your saved template.", kind: "custom", dir });
9349
+ out.push({
9350
+ id: entry.name,
9351
+ label,
9352
+ description: "Your saved template.",
9353
+ kind: "custom",
9354
+ dir,
9355
+ ...await readTemplateChoices(path16.join(dir, MANIFEST_FILENAMES[0]))
9356
+ });
8817
9357
  }
8818
9358
  out.sort((a, b) => a.label.localeCompare(b.label));
8819
9359
  return out;
@@ -8824,12 +9364,12 @@ async function importTemplateFromFolder(options) {
8824
9364
  if (!srcStat.isDirectory()) {
8825
9365
  throw new Error("The chosen path is not a folder.");
8826
9366
  }
8827
- const name = options.name?.trim() || prettify(path15.basename(sourceDir));
9367
+ const name = options.name?.trim() || prettify(path16.basename(sourceDir));
8828
9368
  return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
8829
9369
  }
8830
9370
  // src/lib/snippets.ts
8831
- import { readdir as readdir8, readFile as readFile25, writeFile as writeFile10, mkdir as mkdir9, rm as rm2 } from "node:fs/promises";
8832
- import path16 from "node:path";
9371
+ import { readdir as readdir8, readFile as readFile26, writeFile as writeFile10, mkdir as mkdir10, rm as rm3 } from "node:fs/promises";
9372
+ import path17 from "node:path";
8833
9373
  var SNIPPETS_DIR = "snippets";
8834
9374
  var PLACEHOLDER_RE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
8835
9375
  function extractVariables(template) {
@@ -8848,15 +9388,15 @@ function substituteVariables(template, values) {
8848
9388
  return template.replace(PLACEHOLDER_RE, (_full, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : "");
8849
9389
  }
8850
9390
  function resolveSnippetPath(projectDir, fileName) {
8851
- const dir = path16.resolve(projectDir, SNIPPETS_DIR);
8852
- const full = path16.resolve(dir, fileName);
8853
- if (full !== path16.join(dir, path16.basename(fileName)) || path16.dirname(full) !== dir) {
9391
+ const dir = path17.resolve(projectDir, SNIPPETS_DIR);
9392
+ const full = path17.resolve(dir, fileName);
9393
+ if (full !== path17.join(dir, path17.basename(fileName)) || path17.dirname(full) !== dir) {
8854
9394
  throw new Error(`Unsafe snippet filename: ${fileName}`);
8855
9395
  }
8856
9396
  return full;
8857
9397
  }
8858
9398
  async function listSnippets(projectDir) {
8859
- const dir = path16.join(projectDir, SNIPPETS_DIR);
9399
+ const dir = path17.join(projectDir, SNIPPETS_DIR);
8860
9400
  let names;
8861
9401
  try {
8862
9402
  names = await readdir8(dir);
@@ -8869,7 +9409,7 @@ async function listSnippets(projectDir) {
8869
9409
  continue;
8870
9410
  let body = "";
8871
9411
  try {
8872
- body = await readFile25(path16.join(dir, fileName), "utf8");
9412
+ body = await readFile26(path17.join(dir, fileName), "utf8");
8873
9413
  } catch {
8874
9414
  continue;
8875
9415
  }
@@ -8883,86 +9423,26 @@ async function listSnippets(projectDir) {
8883
9423
  return entries;
8884
9424
  }
8885
9425
  async function readSnippet(projectDir, fileName) {
8886
- return readFile25(resolveSnippetPath(projectDir, fileName), "utf8");
9426
+ return readFile26(resolveSnippetPath(projectDir, fileName), "utf8");
8887
9427
  }
8888
9428
  async function saveSnippet(projectDir, name, body) {
8889
9429
  const stem = slugify(name);
8890
9430
  if (!stem)
8891
9431
  throw new Error(`Could not derive a filename from "${name}".`);
8892
9432
  const fileName = `${stem}.md`;
8893
- const dir = path16.join(projectDir, SNIPPETS_DIR);
8894
- await mkdir9(dir, { recursive: true });
9433
+ const dir = path17.join(projectDir, SNIPPETS_DIR);
9434
+ await mkdir10(dir, { recursive: true });
8895
9435
  await writeFile10(resolveSnippetPath(projectDir, fileName), body, "utf8");
8896
9436
  return { name, fileName, variables: extractVariables(body) };
8897
9437
  }
8898
9438
  async function deleteSnippet(projectDir, fileName) {
8899
- await rm2(resolveSnippetPath(projectDir, fileName), { force: true });
9439
+ await rm3(resolveSnippetPath(projectDir, fileName), { force: true });
8900
9440
  }
8901
9441
  // src/lib/plugin-manager.ts
8902
9442
  import { randomUUID as randomUUID3 } from "node:crypto";
8903
9443
  import { cp as cp4, lstat as lstat3, mkdir as mkdir12, rename as rename5, rm as rm5, stat as stat7 } from "node:fs/promises";
8904
9444
  import path19 from "node:path";
8905
- import { isSeq as isSeq2, isMap, isScalar, YAMLMap, Scalar } from "yaml";
8906
-
8907
- // src/lib/manifest-doc.ts
8908
- import { existsSync as existsSync13 } from "node:fs";
8909
- import { randomUUID } from "node:crypto";
8910
- import { mkdir as mkdir10, open, readFile as readFile26, rename as rename3, rm as rm3 } from "node:fs/promises";
8911
- import path17 from "node:path";
8912
- import { parseDocument, isSeq, YAMLSeq } from "yaml";
8913
- function resolveManifestPath(projectDir) {
8914
- const existing = MANIFEST_FILENAMES.find((name) => existsSync13(path17.join(projectDir, name)));
8915
- return path17.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
8916
- }
8917
- async function loadManifestDoc(projectDir) {
8918
- const file = resolveManifestPath(projectDir);
8919
- let text = "";
8920
- try {
8921
- text = await readFile26(file, "utf8");
8922
- } catch (error2) {
8923
- if (error2.code !== "ENOENT")
8924
- throw error2;
8925
- }
8926
- return { doc: parseDocument(text), file };
8927
- }
8928
- async function writeManifestDoc(file, doc) {
8929
- const dir = path17.dirname(file);
8930
- await mkdir10(dir, { recursive: true });
8931
- const temporary = path17.join(dir, `.${path17.basename(file)}.tmp-${process.pid}-${randomUUID()}`);
8932
- try {
8933
- const handle = await open(temporary, "wx", 438);
8934
- try {
8935
- await handle.writeFile(doc.toString(), "utf8");
8936
- await handle.sync();
8937
- } finally {
8938
- await handle.close();
8939
- }
8940
- await rename3(temporary, file);
8941
- const directory = await open(dir, "r").catch(() => null);
8942
- if (directory) {
8943
- await directory.sync().catch(() => {});
8944
- await directory.close();
8945
- }
8946
- } finally {
8947
- await rm3(temporary, { force: true });
8948
- }
8949
- }
8950
- function ensureSeq(doc, key) {
8951
- let seq = doc.get(key, true);
8952
- if (!isSeq(seq)) {
8953
- const fresh = new YAMLSeq(doc.schema);
8954
- doc.set(key, fresh);
8955
- seq = fresh;
8956
- }
8957
- return seq;
8958
- }
8959
- function scalarString(item) {
8960
- if (item && typeof item === "object" && "value" in item) {
8961
- const v = item.value;
8962
- return typeof v === "string" ? v : null;
8963
- }
8964
- return typeof item === "string" ? item : null;
8965
- }
9445
+ import { isSeq as isSeq3, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar } from "yaml";
8966
9446
 
8967
9447
  // src/lib/npm-plugin-installer.ts
8968
9448
  import { createHash as createHash4, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
@@ -9730,9 +10210,9 @@ function isLocalRef2(ref) {
9730
10210
  return ref.startsWith("./") || ref.startsWith("../") || ref.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(ref) || /\.(m?js|cjs|ts)$/i.test(ref) || !ref.startsWith("@") && ref.includes("/");
9731
10211
  }
9732
10212
  function itemRef(item) {
9733
- if (isScalar(item))
10213
+ if (isScalar2(item))
9734
10214
  return typeof item.value === "string" ? item.value : null;
9735
- if (isMap(item)) {
10215
+ if (isMap2(item)) {
9736
10216
  const p = item.get("path");
9737
10217
  const n = item.get("name");
9738
10218
  if (typeof p === "string")
@@ -9748,7 +10228,7 @@ function refKind(ref) {
9748
10228
  async function listProjectPlugins(projectDir) {
9749
10229
  const { doc } = await loadManifestDoc(projectDir);
9750
10230
  const seq = doc.get("plugins", true);
9751
- if (!isSeq2(seq))
10231
+ if (!isSeq3(seq))
9752
10232
  return [];
9753
10233
  const out = [];
9754
10234
  for (const item of seq.items) {
@@ -9759,7 +10239,7 @@ async function listProjectPlugins(projectDir) {
9759
10239
  let kind;
9760
10240
  let version;
9761
10241
  let exportName;
9762
- if (isMap(item)) {
10242
+ if (isMap2(item)) {
9763
10243
  const e = item.get("enabled");
9764
10244
  if (e === false)
9765
10245
  enabled = false;
@@ -9797,7 +10277,7 @@ async function setPluginEnabled(projectDir, ref, enabled) {
9797
10277
  async function setPluginEnabledUnlocked(projectDir, ref, enabled) {
9798
10278
  const { doc, file } = await loadManifestDoc(projectDir);
9799
10279
  const seq = doc.get("plugins", true);
9800
- if (!isSeq2(seq)) {
10280
+ if (!isSeq3(seq)) {
9801
10281
  throw new Error(`No plugins are configured in ${path19.basename(file)}.`);
9802
10282
  }
9803
10283
  const idx = indexOfRef(seq, ref);
@@ -9806,7 +10286,7 @@ async function setPluginEnabledUnlocked(projectDir, ref, enabled) {
9806
10286
  }
9807
10287
  const item = seq.items[idx];
9808
10288
  let map;
9809
- if (isMap(item)) {
10289
+ if (isMap2(item)) {
9810
10290
  map = item;
9811
10291
  } else {
9812
10292
  map = new YAMLMap(doc.schema);
@@ -9834,7 +10314,7 @@ async function recordInstalledNpmPlugin(projectDir, name, version, exportName) {
9834
10314
  const seq = ensureSeq(doc, "plugins");
9835
10315
  const idx = indexOfRef(seq, name);
9836
10316
  let map;
9837
- if (idx >= 0 && isMap(seq.items[idx])) {
10317
+ if (idx >= 0 && isMap2(seq.items[idx])) {
9838
10318
  map = seq.items[idx];
9839
10319
  } else {
9840
10320
  map = new YAMLMap(doc.schema);
@@ -9997,13 +10477,12 @@ async function validateProjectPlugins(projectDir) {
9997
10477
  }
9998
10478
  // src/lib/theme-manager.ts
9999
10479
  import { cp as cp5, mkdir as mkdir13, readFile as readFile28, readdir as readdir9, stat as stat8, writeFile as writeFile11, rm as rm6 } from "node:fs/promises";
10000
- import { existsSync as existsSync14 } from "node:fs";
10480
+ import { existsSync as existsSync15 } from "node:fs";
10001
10481
  import path20 from "node:path";
10002
- import { isSeq as isSeq3, Scalar as Scalar2 } from "yaml";
10482
+ import { isSeq as isSeq4, Scalar as Scalar2 } from "yaml";
10003
10483
  var THEMES_DIR = "themes";
10004
10484
  var BUILT_IN_THEME_IDS = [
10005
10485
  "clean-book",
10006
- "ttrpg-supplement",
10007
10486
  "zine",
10008
10487
  "technical-doc"
10009
10488
  ];
@@ -10012,10 +10491,6 @@ var BUILT_IN_FALLBACK_META = {
10012
10491
  name: "Clean Book",
10013
10492
  description: "A calm, classic book look: serif body, generous margins."
10014
10493
  },
10015
- "ttrpg-supplement": {
10016
- name: "TTRPG Supplement",
10017
- description: "Bold display headings, parchment fills, boxed stat blocks."
10018
- },
10019
10494
  zine: {
10020
10495
  name: "Zine",
10021
10496
  description: "High-contrast, punchy sans-serif for short printed zines."
@@ -10077,7 +10552,7 @@ async function resolveBuiltInTheme(id) {
10077
10552
  }
10078
10553
  const cssPath = await getAssetPath(`${THEMES_DIR}/${id}/theme.css`);
10079
10554
  const jsonPath = await getAssetPath(`${THEMES_DIR}/${id}/theme.json`);
10080
- if (!existsSync14(cssPath)) {
10555
+ if (!existsSync15(cssPath)) {
10081
10556
  throw new Error(`Built-in theme "${id}" is missing its theme.css.`);
10082
10557
  }
10083
10558
  const meta = await readThemeMeta(jsonPath);
@@ -10101,7 +10576,7 @@ async function listProjectThemes(projectDir) {
10101
10576
  if (!entry.isDirectory())
10102
10577
  continue;
10103
10578
  const dir = path20.join(root, entry.name);
10104
- if (!existsSync14(path20.join(dir, "theme.css")))
10579
+ if (!existsSync15(path20.join(dir, "theme.css")))
10105
10580
  continue;
10106
10581
  const meta = await readThemeMeta(path20.join(dir, "theme.json"));
10107
10582
  out.push(themeInfo(entry.name, "project", meta));
@@ -10126,7 +10601,7 @@ function activeThemeIdInSeq(seq) {
10126
10601
  async function getActiveTheme(projectDir) {
10127
10602
  const { doc } = await loadManifestDoc(projectDir);
10128
10603
  const seq = doc.get("styles", true);
10129
- if (!isSeq3(seq))
10604
+ if (!isSeq4(seq))
10130
10605
  return null;
10131
10606
  for (const item of seq.items) {
10132
10607
  const href = scalarString(item);
@@ -10137,7 +10612,7 @@ async function getActiveTheme(projectDir) {
10137
10612
  continue;
10138
10613
  const id = m[1];
10139
10614
  const dir = path20.join(projectDir, THEMES_DIR, id);
10140
- if (!existsSync14(path20.join(dir, "theme.css")))
10615
+ if (!existsSync15(path20.join(dir, "theme.css")))
10141
10616
  continue;
10142
10617
  const meta = await readThemeMeta(path20.join(dir, "theme.json"));
10143
10618
  return themeInfo(id, "project", meta);
@@ -10174,7 +10649,7 @@ async function getPreviousTheme(projectDir) {
10174
10649
  if (!id)
10175
10650
  return null;
10176
10651
  const dir = themeDirFor(projectDir, id);
10177
- if (!existsSync14(path20.join(dir, "theme.css")))
10652
+ if (!existsSync15(path20.join(dir, "theme.css")))
10178
10653
  return null;
10179
10654
  const active = await getActiveTheme(projectDir);
10180
10655
  if (active && active.id === id)
@@ -10194,7 +10669,7 @@ async function applyTheme(projectDir, target) {
10194
10669
  if (target.kind === "builtin") {
10195
10670
  const resolved = await resolveBuiltInTheme(target.id);
10196
10671
  let destId = target.id;
10197
- if (existsSync14(path20.join(projectDir, THEMES_DIR, destId, "theme.css"))) {
10672
+ if (existsSync15(path20.join(projectDir, THEMES_DIR, destId, "theme.css"))) {
10198
10673
  destId = await uniqueThemeId(projectDir, target.id);
10199
10674
  }
10200
10675
  const destDir = path20.join(projectDir, THEMES_DIR, destId);
@@ -10203,7 +10678,7 @@ async function applyTheme(projectDir, target) {
10203
10678
  info2 = themeInfo(destId, "project", await readThemeMeta(path20.join(destDir, "theme.json")));
10204
10679
  } else {
10205
10680
  const dir = themeDirFor(projectDir, target.id);
10206
- if (!existsSync14(path20.join(dir, "theme.css"))) {
10681
+ if (!existsSync15(path20.join(dir, "theme.css"))) {
10207
10682
  throw new Error(`Theme "${target.id}" is not present in this project.`);
10208
10683
  }
10209
10684
  info2 = themeInfo(target.id, "project", await readThemeMeta(path20.join(dir, "theme.json")));
@@ -10215,7 +10690,7 @@ async function uniqueThemeId(projectDir, base) {
10215
10690
  const root = path20.join(projectDir, THEMES_DIR);
10216
10691
  let id = themeSlug(base);
10217
10692
  let n = 2;
10218
- while (existsSync14(path20.join(root, id))) {
10693
+ while (existsSync15(path20.join(root, id))) {
10219
10694
  id = `${themeSlug(base)}-${n++}`;
10220
10695
  }
10221
10696
  return id;
@@ -10230,7 +10705,7 @@ async function importThemeFromFolder(projectDir, sourceDir) {
10230
10705
  if (!info2.isDirectory()) {
10231
10706
  throw new Error("The chosen path is not a folder.");
10232
10707
  }
10233
- if (!existsSync14(path20.join(sourceDir, "theme.css"))) {
10708
+ if (!existsSync15(path20.join(sourceDir, "theme.css"))) {
10234
10709
  throw new Error("A theme folder must contain a theme.css file.");
10235
10710
  }
10236
10711
  const meta = await readThemeMeta(path20.join(sourceDir, "theme.json"));
@@ -10323,20 +10798,20 @@ async function readThemeCss(projectDir, source) {
10323
10798
  if (!projectDir)
10324
10799
  throw new Error("A project is required to read a project theme.");
10325
10800
  const cssPath = path20.join(themeDirFor(projectDir, source.id), "theme.css");
10326
- if (!existsSync14(cssPath)) {
10801
+ if (!existsSync15(cssPath)) {
10327
10802
  throw new Error(`Theme "${source.id}" has no theme.css in this project.`);
10328
10803
  }
10329
10804
  return readFile28(cssPath, "utf8");
10330
10805
  }
10331
10806
  async function removeProjectTheme(projectDir, id) {
10332
10807
  const dir = themeDirFor(projectDir, id);
10333
- if (existsSync14(dir)) {
10808
+ if (existsSync15(dir)) {
10334
10809
  await rm6(dir, { recursive: true, force: true });
10335
10810
  }
10336
10811
  const { doc, file } = await loadManifestDoc(projectDir);
10337
10812
  let changed = false;
10338
10813
  const seq = doc.get("styles", true);
10339
- if (isSeq3(seq)) {
10814
+ if (isSeq4(seq)) {
10340
10815
  const href = themeStyleHref(id);
10341
10816
  const before = seq.items.length;
10342
10817
  seq.items = seq.items.filter((item) => scalarString(item) !== href);
@@ -10352,7 +10827,7 @@ async function removeProjectTheme(projectDir, id) {
10352
10827
  }
10353
10828
  }
10354
10829
  // src/lib/theme-import.ts
10355
- import { existsSync as existsSync15 } from "node:fs";
10830
+ import { existsSync as existsSync16 } from "node:fs";
10356
10831
  import { mkdir as mkdir14, mkdtemp as mkdtemp4, readFile as readFile29, rm as rm7, writeFile as writeFile12 } from "node:fs/promises";
10357
10832
  import { tmpdir as tmpdir4 } from "node:os";
10358
10833
  import path21 from "node:path";
@@ -10427,7 +10902,7 @@ async function readThemeJson(p) {
10427
10902
  }
10428
10903
  async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
10429
10904
  const cssPath = path21.join(sourceDir, "theme.css");
10430
- if (!existsSync15(cssPath)) {
10905
+ if (!existsSync16(cssPath)) {
10431
10906
  throw new Error("A theme must contain a theme.css file.");
10432
10907
  }
10433
10908
  const css = await readFile29(cssPath, "utf8");
@@ -10440,7 +10915,7 @@ async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
10440
10915
  warnings.push({ code: "print-safety", message: w.message });
10441
10916
  }
10442
10917
  const jsonPath = path21.join(sourceDir, "theme.json");
10443
- if (!existsSync15(jsonPath)) {
10918
+ if (!existsSync16(jsonPath)) {
10444
10919
  warnings.push({
10445
10920
  code: "no-theme-json",
10446
10921
  message: "No theme.json found — the theme's name was taken from the file/folder name."
@@ -10564,7 +11039,7 @@ async function importThemeFromFile(projectDir, filePath2) {
10564
11039
  throw new Error("Choose a .zip theme package or a .css stylesheet.");
10565
11040
  }
10566
11041
  // src/lib/manifest-config.ts
10567
- import { isSeq as isSeq4, isMap as isMap2, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
11042
+ import { isSeq as isSeq5, isMap as isMap3, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
10568
11043
  function hasKey(obj, key) {
10569
11044
  return Object.prototype.hasOwnProperty.call(obj, key);
10570
11045
  }
@@ -10585,12 +11060,24 @@ async function setManifestFields(projectDir, updates) {
10585
11060
  doc.set("authors", a);
10586
11061
  }
10587
11062
  }
11063
+ if (hasKey(updates, "targets")) {
11064
+ const t = updates.targets;
11065
+ if (t === undefined) {
11066
+ doc.delete("targets");
11067
+ } else {
11068
+ const unknown = t.filter((id) => !TARGETS[id]);
11069
+ if (unknown.length > 0) {
11070
+ throw new UsageError(`Unknown publish target${unknown.length > 1 ? "s" : ""} "${unknown.join('", "')}". ` + `Known targets: ${TARGET_IDS.join(", ")}.`);
11071
+ }
11072
+ doc.set("targets", [...new Set(t)]);
11073
+ }
11074
+ }
10588
11075
  if (hasKey(updates, "sourceFiles")) {
10589
11076
  const f = updates.sourceFiles;
10590
11077
  if (f === null || Array.isArray(f) && f.length === 0) {
10591
11078
  doc.deleteIn(["source", "files"]);
10592
11079
  const source = doc.get("source", true);
10593
- if (isMap2(source) && source.items.length === 0) {
11080
+ if (isMap3(source) && source.items.length === 0) {
10594
11081
  doc.delete("source");
10595
11082
  }
10596
11083
  } else {
@@ -10608,15 +11095,19 @@ async function readManifestFields(projectDir) {
10608
11095
  if (typeof title === "string")
10609
11096
  out.title = title;
10610
11097
  const authors = doc.get("authors", true);
10611
- if (isSeq4(authors)) {
11098
+ if (isSeq5(authors)) {
10612
11099
  out.authors = authors.items.map((i) => scalarString(i) ?? "");
10613
11100
  }
11101
+ const targetsNode = doc.get("targets", true);
11102
+ if (isSeq5(targetsNode)) {
11103
+ out.targets = targetsNode.items.map((i) => scalarString(i)).filter((s) => s !== null);
11104
+ }
10614
11105
  const filesNode = doc.getIn(["source", "files"], true);
10615
- if (isSeq4(filesNode)) {
11106
+ if (isSeq5(filesNode)) {
10616
11107
  out.sourceFiles = filesNode.items.map((i) => scalarString(i) ?? "");
10617
11108
  } else {
10618
11109
  const sourceNode = doc.get("source", true);
10619
- if (isMap2(sourceNode) && sourceNode.has("files"))
11110
+ if (isMap3(sourceNode) && sourceNode.has("files"))
10620
11111
  out.sourceFiles = null;
10621
11112
  }
10622
11113
  return out;
@@ -12643,12 +13134,12 @@ async function buildResolutionPlan(resolutions, localTip, remoteId, deps) {
12643
13134
 
12644
13135
  // src/lib/remote-auth/conflict-resolution.ts
12645
13136
  function onlineCopyPath(filepath, counter) {
12646
- const dirname5 = path27.posix.dirname(filepath);
13137
+ const dirname6 = path27.posix.dirname(filepath);
12647
13138
  const ext = path27.posix.extname(filepath);
12648
13139
  const stem = path27.posix.basename(filepath, ext);
12649
13140
  const label = counter && counter >= 2 ? `online copy ${counter}` : "online copy";
12650
13141
  const renamed = `${stem} (${label})${ext}`;
12651
- return dirname5 === "." ? renamed : `${dirname5}/${renamed}`;
13142
+ return dirname6 === "." ? renamed : `${dirname6}/${renamed}`;
12652
13143
  }
12653
13144
  async function uniqueOnlineCopyPath(dir, filepath, oids, cache2) {
12654
13145
  const taken = async (candidate2) => {
@@ -13384,8 +13875,8 @@ var drivethrurpgProvider = {
13384
13875
  }
13385
13876
  issues.push({
13386
13877
  severity: "info",
13387
- id: "drivethrurpg/validate-profile",
13388
- message: "Tip: run `gutterpress validate --profile dtrpg` for DriveThruRPG's print-compliance checks before uploading."
13878
+ id: "drivethrurpg/validate-target",
13879
+ message: "Tip: run `gutterpress validate --target dtrpg` for DriveThruRPG's print-compliance checks before uploading."
13389
13880
  });
13390
13881
  return issues;
13391
13882
  },
@@ -13499,7 +13990,7 @@ var info4 = {
13499
13990
  label: "itch.io",
13500
13991
  kind: "api",
13501
13992
  format: "pdf",
13502
- description: "Upload the PDF to your itch.io project page (games, TTRPG supplements, zines).",
13993
+ description: "Upload the PDF to your itch.io project page (games, zines, and other printable projects).",
13503
13994
  configFields: [
13504
13995
  { key: "target", label: "Project (user/game)", placeholder: "you/your-book" },
13505
13996
  { key: "channel", label: "Channel", placeholder: "pdf" }
@@ -14177,10 +14668,10 @@ function normalizeProjectKey(projectDir) {
14177
14668
  return projectDir.replace(/[\\/]+$/, "");
14178
14669
  }
14179
14670
  // src/lib/publish/manifest-publish.ts
14180
- import { isMap as isMap3 } from "yaml";
14671
+ import { isMap as isMap4 } from "yaml";
14181
14672
  function settingsFromDoc(doc) {
14182
14673
  const node = doc.get("publish", true);
14183
- if (!isMap3(node))
14674
+ if (!isMap4(node))
14184
14675
  return {};
14185
14676
  const json = node.toJSON();
14186
14677
  const out = {};
@@ -14205,11 +14696,11 @@ async function setPublishProviderConfig(projectDir, providerId, values) {
14205
14696
  }
14206
14697
  }
14207
14698
  const section = doc.getIn(["publish", providerId], true);
14208
- if (isMap3(section) && section.items.length === 0) {
14699
+ if (isMap4(section) && section.items.length === 0) {
14209
14700
  doc.deleteIn(["publish", providerId]);
14210
14701
  }
14211
14702
  const publish = doc.get("publish", true);
14212
- if (isMap3(publish) && publish.items.length === 0) {
14703
+ if (isMap4(publish) && publish.items.length === 0) {
14213
14704
  doc.delete("publish");
14214
14705
  }
14215
14706
  await writeManifestDoc(file, doc);
@@ -15215,4 +15706,4 @@ async function dispatchToHandler(kind, ctx, error2) {
15215
15706
  }
15216
15707
  return result;
15217
15708
  }
15218
- export { BuildError, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, log, resolveActiveStyles, listProjectStyles, PLUGINS_DIR, ruleRemoteUrls, ruleRiskyProps, rulePagedjsCrashSelectors, ruleSyntax, checkCss, runLint, formatReport, getChecks, getCheckById, resolveCheckSelectors, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, redactCredential, defaultConfigDir, FileTokenStore, extractUrlCredential, migrateUrlCredential, getSystemDiagnostics, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, readSnippet, saveSnippet, deleteSnippet, RECOMMENDED_PLUGINS, listProjectPlugins, setPluginEnabled, addNpmPlugin, addLocalPlugin, validateProjectPlugins, THEMES_DIR, BUILT_IN_THEME_IDS, listBuiltInThemes, resolveBuiltInTheme, listProjectThemes, getActiveTheme, getPreviousTheme, revertTheme, applyTheme, importThemeFromFolder, importThemeFromUrl, readThemeCss, removeProjectTheme, importThemeFromFile, setManifestFields, readManifestFields, setActiveStyles, APP_HEARTBEAT_FRESH_MS, heartbeatTtlMs, appHeartbeatPath, writeAppHeartbeat, removeAppHeartbeat, readAppHeartbeat, isAppHeartbeatFresh, AUTO_SNAPSHOT_MIN_MINUTES, AUTO_SNAPSHOT_MAX_MINUTES, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, listGitHubRepositories, listRepoBooks, listGitHubBranches, RepoNeedsRecoveryError, isRepoNeedsRecoveryError, classifyFromHealth, classifyGitError, SYNC_SNAPSHOT_MESSAGE, provenancePath, readProjectProvenance, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, normalizeForgeHost, knownForgeTokenUrl, connectGenericHost, GenericTokenAuthProvider, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, inspectRepo, verifyRepoReadable, isUnbornRepo, preflightStructuralReason, buildPreflightDiagnostics, onlineCopyPath, resolveConflicts, syncProject, pullChanges, pushChanges, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, resolvePublishRequest, runPublish, connectPublishProvider, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig, buildRecoveryContext, recover16 as recover };
15709
+ export { BuildError, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, log, resolveActiveStyles, listProjectStyles, PLUGINS_DIR, ruleRemoteUrls, ruleRiskyProps, rulePagedjsCrashSelectors, ruleSyntax, checkCss, runLint, formatReport, getChecks, getCheckById, resolveCheckSelectors, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, redactCredential, defaultConfigDir, FileTokenStore, extractUrlCredential, migrateUrlCredential, getSystemDiagnostics, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, readSnippet, saveSnippet, deleteSnippet, RECOMMENDED_PLUGINS, listProjectPlugins, setPluginEnabled, addNpmPlugin, addLocalPlugin, validateProjectPlugins, THEMES_DIR, BUILT_IN_THEME_IDS, listBuiltInThemes, resolveBuiltInTheme, listProjectThemes, getActiveTheme, getPreviousTheme, revertTheme, applyTheme, importThemeFromFolder, importThemeFromUrl, readThemeCss, removeProjectTheme, importThemeFromFile, setManifestFields, readManifestFields, setActiveStyles, APP_HEARTBEAT_FRESH_MS, heartbeatTtlMs, appHeartbeatPath, writeAppHeartbeat, removeAppHeartbeat, readAppHeartbeat, isAppHeartbeatFresh, AUTO_SNAPSHOT_MIN_MINUTES, AUTO_SNAPSHOT_MAX_MINUTES, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, listGitHubRepositories, listRepoBooks, listGitHubBranches, RepoNeedsRecoveryError, isRepoNeedsRecoveryError, classifyFromHealth, classifyGitError, SYNC_SNAPSHOT_MESSAGE, provenancePath, readProjectProvenance, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, normalizeForgeHost, knownForgeTokenUrl, connectGenericHost, GenericTokenAuthProvider, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, inspectRepo, verifyRepoReadable, isUnbornRepo, preflightStructuralReason, buildPreflightDiagnostics, onlineCopyPath, resolveConflicts, syncProject, pullChanges, pushChanges, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, resolvePublishRequest, runPublish, connectPublishProvider, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig, buildRecoveryContext, recover16 as recover };