gutterpress 0.10.0 → 0.10.1-beta.2

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 (57) hide show
  1. package/README.md +3 -16
  2. package/dist/api/index.d.ts +10 -19
  3. package/dist/api/index.js +8 -64
  4. package/dist/{audit-cvarpa72.js → audit-dce99css.js} +5 -5
  5. package/dist/{build-6r502chw.js → build-bj4x0727.js} +5 -5
  6. package/dist/checks/source/index.d.ts +1 -0
  7. package/dist/checks/source/merge-markers.d.ts +3 -0
  8. package/dist/{cli-cqtggsng.js → cli-0z6zf98d.js} +2 -47
  9. package/dist/{cli-eq5naw4m.js → cli-7n848af2.js} +370 -1249
  10. package/dist/{cli-zfcryxg8.js → cli-eewynen9.js} +97 -60
  11. package/dist/{cli-k1065rkg.js → cli-w692g6dx.js} +75 -29
  12. package/dist/{cli-149edp6b.js → cli-ydpf09d9.js} +1 -1
  13. package/dist/cli.js +14 -15
  14. package/dist/{doctor-akvxbtjb.js → doctor-7nd6n2aa.js} +2 -2
  15. package/dist/{engine-ft4cr3ep.js → engine-57vw8zpd.js} +1 -1
  16. package/dist/{engine-b159tbns.js → engine-8kdwy0jz.js} +2 -2
  17. package/dist/{gutterpress-viewer-te8g5grx.js → gutterpress-viewer-tma1qwz2.js} +57 -15
  18. package/dist/{index-ycpvr0am.js → index-7hrvksrb.js} +4 -4
  19. package/dist/{index-mdefp0y5.js → index-dtrftayr.js} +75 -27
  20. package/dist/{index-ge7q9xj3.js → index-h1x7krkg.js} +771 -1482
  21. package/dist/{index-wq3r5pj7.js → index-p0ebr7dh.js} +94 -57
  22. package/dist/index.js +9 -65
  23. package/dist/lib/git-fs.d.ts +43 -0
  24. package/dist/lib/host-policy.d.ts +14 -2
  25. package/dist/lib/remote-auth/clone.d.ts +0 -21
  26. package/dist/lib/remote-auth/converge-merge.d.ts +24 -7
  27. package/dist/lib/remote-auth/generic-auth.d.ts +0 -22
  28. package/dist/lib/remote-auth/git-http.d.ts +1 -1
  29. package/dist/lib/remote-auth/github-auth.d.ts +2 -22
  30. package/dist/lib/remote-auth/operation-log.d.ts +4 -9
  31. package/dist/lib/remote-auth/sync-messages.d.ts +35 -12
  32. package/dist/lib/remote-auth/sync-types.d.ts +30 -101
  33. package/dist/lib/remote-auth/sync.d.ts +19 -21
  34. package/dist/lib/remote-auth/token-store.d.ts +0 -10
  35. package/dist/lib/remote-auth/transport.d.ts +42 -40
  36. package/dist/lib/source-provider.d.ts +12 -0
  37. package/dist/{lint-p2sw53d9.js → lint-c8ze8sqw.js} +5 -5
  38. package/dist/{new-hvq0x91q.js → new-xy2qc300.js} +5 -5
  39. package/dist/{plugin-pssmk0dx.js → plugin-g9hwzjsm.js} +5 -5
  40. package/dist/{preflight-4j00yd0g.js → preflight-prcvdvmk.js} +5 -5
  41. package/dist/{preview-bridge-fz7vpk8m.js → preview-bridge-2nnrs2hy.js} +15 -0
  42. package/dist/{preview-d4s6gk6p.js → preview-h479apj9.js} +5 -5
  43. package/dist/{preview-interface-0ssk8bmm.js → preview-interface-2f5v96hd.js} +411 -88
  44. package/dist/{preview-shell-c5mfa3q0.js → preview-shell-kyjq59d9.js} +80 -25
  45. package/dist/{publish-tkc26en7.js → publish-emk644a8.js} +5 -5
  46. package/dist/{source-provider-3tcj6qg2.js → source-provider-2j5x2hyw.js} +1 -1
  47. package/dist/{source-provider-vanafrt9.js → source-provider-kn41jmbs.js} +1 -1
  48. package/dist/{validate-w9vfefrw.js → validate-be54bdc6.js} +5 -5
  49. package/package.json +1 -1
  50. package/dist/lib/app-heartbeat.d.ts +0 -102
  51. package/dist/lib/remote-auth/image-clash.d.ts +0 -17
  52. package/dist/lib/remote-auth/recovery/classify.d.ts +0 -97
  53. package/dist/lib/remote-auth/recovery/inspect.d.ts +0 -75
  54. package/dist/lib/remote-auth/recovery/locks.d.ts +0 -20
  55. package/dist/lib/remote-auth/recovery/repair.d.ts +0 -29
  56. package/dist/lib/remote-auth/recovery/types.d.ts +0 -39
  57. package/dist/repair-2va4w12t.js +0 -137
@@ -2,11 +2,9 @@ import {
2
2
  PACKAGE_META,
3
3
  PACKAGE_VERSION,
4
4
  convertToPdfxCmyk,
5
- credentialHostKey,
6
5
  debug,
7
6
  defaultConfigDir,
8
7
  error,
9
- extractUrlCredential,
10
8
  hasLiveTransparency,
11
9
  info,
12
10
  log,
@@ -15,12 +13,12 @@ import {
15
13
  stampCreator,
16
14
  stripAnnotations,
17
15
  warn
18
- } from "./cli-cqtggsng.js";
16
+ } from "./cli-0z6zf98d.js";
19
17
  import {
20
18
  DEBOUNCE,
21
19
  UsageError,
22
20
  resolvePort
23
- } from "./cli-149edp6b.js";
21
+ } from "./cli-ydpf09d9.js";
24
22
  import {
25
23
  MARGIN_BOX_IGNORED_PROPERTIES,
26
24
  RENDER_TIMEOUT_MS,
@@ -29,7 +27,7 @@ import {
29
27
  getAssetPath,
30
28
  getBrowser,
31
29
  prewarmBrowser
32
- } from "./cli-zfcryxg8.js";
30
+ } from "./cli-eewynen9.js";
33
31
  import {
34
32
  INSTALL_HINTS,
35
33
  execCapture,
@@ -42,19 +40,10 @@ import {
42
40
  BuildError
43
41
  } from "./cli-46ycxe6r.js";
44
42
  import {
45
- gitDirFor,
46
- gitScopeFor,
47
- hasPendingChanges,
48
- hasUncommittedChanges,
49
- isNoChangesError,
50
- resolveGitAuthor,
51
- resolveLogger,
52
- snapshotWorkingTreeUnlocked,
53
- withRepoLock
54
- } from "./cli-k1065rkg.js";
43
+ hasUncommittedChanges
44
+ } from "./cli-w692g6dx.js";
55
45
  import {
56
- detectProjectSource,
57
- syncRemoteFor
46
+ detectProjectSource
58
47
  } from "./cli-v5mp7a6q.js";
59
48
  import {
60
49
  __require
@@ -5148,11 +5137,9 @@ var check22 = {
5148
5137
  };
5149
5138
  registerCheck(check22);
5150
5139
 
5151
- // src/checks/asset/image-file-size.ts
5152
- import { stat as stat3 } from "node:fs/promises";
5153
-
5154
- // src/lib/image-inspect.ts
5155
- import { stat as stat2, readFile as readFile15 } from "node:fs/promises";
5140
+ // src/checks/source/merge-markers.ts
5141
+ import { readFile as readFile15 } from "node:fs/promises";
5142
+ import path4 from "node:path";
5156
5143
 
5157
5144
  // src/checks/asset/extensions.ts
5158
5145
  var RASTER_INSPECTABLE_EXTS = [
@@ -5175,27 +5162,108 @@ var ASSET_SCAN_IGNORE_GLOBS = [
5175
5162
  "**/dist/**"
5176
5163
  ];
5177
5164
 
5165
+ // src/checks/source/merge-markers.ts
5166
+ var OPEN_SENTINEL = "<<<<<<< your version";
5167
+ var CLOSE_SENTINEL = ">>>>>>> online version";
5168
+ function withoutOnlineTag(relPath) {
5169
+ return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
5170
+ }
5171
+ var check23 = {
5172
+ id: "source.sync.merge-markers",
5173
+ name: "Combined Versions",
5174
+ description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
5175
+ category: "source",
5176
+ phase: "pre-build",
5177
+ async run(ctx) {
5178
+ const results = [];
5179
+ const textFiles = [...ctx.markdownFiles ?? [], ...ctx.cssFiles ?? []].sort();
5180
+ for (const file of textFiles) {
5181
+ let content;
5182
+ try {
5183
+ content = await readFile15(file, "utf8");
5184
+ } catch {
5185
+ results.push(inspectionFailed(check23.id, `Could not read source file: ${file}`, { file }));
5186
+ continue;
5187
+ }
5188
+ if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
5189
+ continue;
5190
+ let inBlock = false;
5191
+ const lines = content.split(`
5192
+ `);
5193
+ for (let i = 0;i < lines.length; i++) {
5194
+ const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
5195
+ if (line === OPEN_SENTINEL) {
5196
+ inBlock = true;
5197
+ results.push(finding(check23.id, {
5198
+ severity: "error",
5199
+ code: "two-versions-passage",
5200
+ message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
5201
+ file,
5202
+ line: i + 1
5203
+ }));
5204
+ } else if (line === CLOSE_SENTINEL) {
5205
+ if (!inBlock) {
5206
+ results.push(finding(check23.id, {
5207
+ severity: "error",
5208
+ code: "leftover-version-marker",
5209
+ message: "This marker line is left over from combining two versions — delete it.",
5210
+ file,
5211
+ line: i + 1
5212
+ }));
5213
+ }
5214
+ inBlock = false;
5215
+ }
5216
+ }
5217
+ }
5218
+ try {
5219
+ const { glob } = await import("glob");
5220
+ const siblings = await glob(["**/*.online", "**/*.online.*"], {
5221
+ cwd: ctx.inputDir,
5222
+ absolute: true,
5223
+ ignore: [...ASSET_SCAN_IGNORE_GLOBS]
5224
+ });
5225
+ for (const sibling of siblings.sort()) {
5226
+ const rel = path4.relative(ctx.inputDir, sibling).split(path4.sep).join("/");
5227
+ results.push(finding(check23.id, {
5228
+ severity: "warning",
5229
+ code: "kept-both-versions",
5230
+ message: `Two versions of ${withoutOnlineTag(rel)} are in your project — keep the one you want, then delete the other.`,
5231
+ file: sibling
5232
+ }));
5233
+ }
5234
+ } catch (error2) {
5235
+ results.push(inspectionFailed(check23.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
5236
+ }
5237
+ return results;
5238
+ }
5239
+ };
5240
+ registerCheck(check23);
5241
+
5242
+ // src/checks/asset/image-file-size.ts
5243
+ import { stat as stat3 } from "node:fs/promises";
5244
+
5178
5245
  // src/lib/image-inspect.ts
5246
+ import { stat as stat2, readFile as readFile16 } from "node:fs/promises";
5179
5247
  var DEFAULT_DPI = 72;
5180
5248
  var cache = new Map;
5181
- async function inspectImage(path4) {
5249
+ async function inspectImage(path5) {
5182
5250
  let st;
5183
5251
  try {
5184
- st = await stat2(path4);
5252
+ st = await stat2(path5);
5185
5253
  } catch {
5186
5254
  return null;
5187
5255
  }
5188
- const hit = cache.get(path4);
5256
+ const hit = cache.get(path5);
5189
5257
  if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size)
5190
5258
  return hit.info;
5191
5259
  let info2 = null;
5192
5260
  try {
5193
- const buf = await readFile15(path4);
5261
+ const buf = await readFile16(path5);
5194
5262
  info2 = parseImage(buf);
5195
5263
  } catch {
5196
5264
  info2 = null;
5197
5265
  }
5198
- cache.set(path4, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
5266
+ cache.set(path5, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
5199
5267
  return info2;
5200
5268
  }
5201
5269
  function parseImage(b) {
@@ -5395,7 +5463,7 @@ async function collectImageFiles(dirs, exts, ignore = ASSET_SCAN_IGNORE_GLOBS) {
5395
5463
  }
5396
5464
 
5397
5465
  // src/checks/asset/image-file-size.ts
5398
- var check23 = {
5466
+ var check24 = {
5399
5467
  id: "asset.image.file-size",
5400
5468
  name: "Image File Size",
5401
5469
  description: "Checks that image files do not exceed the maximum size limit",
@@ -5416,14 +5484,14 @@ var check23 = {
5416
5484
  if (info2.size > maxSize) {
5417
5485
  const sizeMb = (info2.size / 1e6).toFixed(1);
5418
5486
  const maxMb = (maxSize / 1e6).toFixed(1);
5419
- results.push(finding(check23.id, {
5487
+ results.push(finding(check24.id, {
5420
5488
  severity: "warning",
5421
5489
  message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
5422
5490
  file
5423
5491
  }));
5424
5492
  }
5425
5493
  } catch {
5426
- results.push(inspectionFailed(check23.id, `Could not stat image file: ${file}`, {
5494
+ results.push(inspectionFailed(check24.id, `Could not stat image file: ${file}`, {
5427
5495
  file
5428
5496
  }));
5429
5497
  }
@@ -5431,10 +5499,10 @@ var check23 = {
5431
5499
  return results;
5432
5500
  }
5433
5501
  };
5434
- registerCheck(check23);
5502
+ registerCheck(check24);
5435
5503
 
5436
5504
  // src/checks/asset/image-resolution.ts
5437
- var check24 = {
5505
+ var check25 = {
5438
5506
  id: "asset.image.resolution",
5439
5507
  name: "Image Resolution",
5440
5508
  description: "Checks source image DPI from embedded density metadata",
@@ -5456,7 +5524,7 @@ var check24 = {
5456
5524
  const { xDpi, yDpi } = info2;
5457
5525
  if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
5458
5526
  results.push({
5459
- checkId: check24.id,
5527
+ checkId: check25.id,
5460
5528
  severity: "warning",
5461
5529
  message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
5462
5530
  file
@@ -5466,7 +5534,7 @@ var check24 = {
5466
5534
  return results;
5467
5535
  }
5468
5536
  };
5469
- registerCheck(check24);
5537
+ registerCheck(check25);
5470
5538
 
5471
5539
  // src/checks/asset/image-color-space.ts
5472
5540
  var LABEL = {
@@ -5474,7 +5542,7 @@ var LABEL = {
5474
5542
  gray: "Gray",
5475
5543
  cmyk: "CMYK"
5476
5544
  };
5477
- var check25 = {
5545
+ var check26 = {
5478
5546
  id: "asset.image.color-space",
5479
5547
  name: "Image Color Space",
5480
5548
  description: "Validates image color spaces against allowed list",
@@ -5495,7 +5563,7 @@ var check25 = {
5495
5563
  const cs = info2?.colorSpace;
5496
5564
  if (cs && !allowedLower.has(cs)) {
5497
5565
  results.push({
5498
- checkId: check25.id,
5566
+ checkId: check26.id,
5499
5567
  severity: "warning",
5500
5568
  message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
5501
5569
  file
@@ -5505,10 +5573,10 @@ var check25 = {
5505
5573
  return results;
5506
5574
  }
5507
5575
  };
5508
- registerCheck(check25);
5576
+ registerCheck(check26);
5509
5577
 
5510
5578
  // src/checks/asset/image-alpha.ts
5511
- var check26 = {
5579
+ var check27 = {
5512
5580
  id: "asset.image.alpha-channel",
5513
5581
  name: "Image Alpha Channel",
5514
5582
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -5526,7 +5594,7 @@ var check26 = {
5526
5594
  const info2 = await inspectImage(file);
5527
5595
  if (info2?.hasAlpha) {
5528
5596
  results.push({
5529
- checkId: check26.id,
5597
+ checkId: check27.id,
5530
5598
  severity: "warning",
5531
5599
  message: "Image contains alpha channel, which may cause print issues",
5532
5600
  file
@@ -5536,10 +5604,10 @@ var check26 = {
5536
5604
  return results;
5537
5605
  }
5538
5606
  };
5539
- registerCheck(check26);
5607
+ registerCheck(check27);
5540
5608
 
5541
5609
  // src/checks/asset/image-tac.ts
5542
- var check27 = {
5610
+ var check28 = {
5543
5611
  id: "asset.image.tac-raster",
5544
5612
  name: "Image TAC (Raster)",
5545
5613
  description: "Rasterizes and checks TAC per image using Ghostscript",
@@ -5558,7 +5626,7 @@ var check27 = {
5558
5626
  const ghostscript = await resolveGhostscript();
5559
5627
  if (!ghostscript) {
5560
5628
  return [
5561
- inspectionFailed(check27.id, "Could not inspect image ink coverage: Ghostscript executable not found")
5629
+ inspectionFailed(check28.id, "Could not inspect image ink coverage: Ghostscript executable not found")
5562
5630
  ];
5563
5631
  }
5564
5632
  for (const file of files) {
@@ -5576,7 +5644,7 @@ var check27 = {
5576
5644
  if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
5577
5645
  const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
5578
5646
  if (tac > maxTac) {
5579
- results.push(finding(check27.id, {
5647
+ results.push(finding(check28.id, {
5580
5648
  severity: "warning",
5581
5649
  message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
5582
5650
  file,
@@ -5588,16 +5656,16 @@ var check27 = {
5588
5656
  }
5589
5657
  }
5590
5658
  } catch {
5591
- results.push(inspectionFailed(check27.id, `Could not inspect image ink coverage: ${file}`, { file }));
5659
+ results.push(inspectionFailed(check28.id, `Could not inspect image ink coverage: ${file}`, { file }));
5592
5660
  }
5593
5661
  }
5594
5662
  return results;
5595
5663
  }
5596
5664
  };
5597
- registerCheck(check27);
5665
+ registerCheck(check28);
5598
5666
 
5599
5667
  // src/checks/asset/approved-fonts.ts
5600
- var check28 = {
5668
+ var check29 = {
5601
5669
  id: "asset.font.approved-files",
5602
5670
  name: "Approved Font Files",
5603
5671
  description: "Checks font files against the approved file patterns",
@@ -5636,7 +5704,7 @@ var check28 = {
5636
5704
  for (const font of allFonts) {
5637
5705
  if (!approvedFonts.has(font)) {
5638
5706
  results.push({
5639
- checkId: check28.id,
5707
+ checkId: check29.id,
5640
5708
  severity: "warning",
5641
5709
  message: "Font file not in approved list",
5642
5710
  file: font
@@ -5646,7 +5714,7 @@ var check28 = {
5646
5714
  return results;
5647
5715
  }
5648
5716
  };
5649
- registerCheck(check28);
5717
+ registerCheck(check29);
5650
5718
 
5651
5719
  // src/checks/asset/font-license.ts
5652
5720
  import { existsSync as existsSync7 } from "node:fs";
@@ -5661,7 +5729,7 @@ var LICENSE_NAMES = [
5661
5729
  "OFL-1.1.txt",
5662
5730
  "COPYING"
5663
5731
  ];
5664
- var check29 = {
5732
+ var check30 = {
5665
5733
  id: "asset.font.license",
5666
5734
  name: "Font License",
5667
5735
  description: "Checks for license files in font directories",
@@ -5688,7 +5756,7 @@ var check29 = {
5688
5756
  const hasLicense = LICENSE_NAMES.some((name) => existsSync7(resolve6(fontDir, name)));
5689
5757
  if (!hasLicense) {
5690
5758
  results.push({
5691
- checkId: check29.id,
5759
+ checkId: check30.id,
5692
5760
  severity: "warning",
5693
5761
  message: `No font license file found in directory`,
5694
5762
  file: fontDir
@@ -5698,10 +5766,10 @@ var check29 = {
5698
5766
  return results;
5699
5767
  }
5700
5768
  };
5701
- registerCheck(check29);
5769
+ registerCheck(check30);
5702
5770
 
5703
5771
  // src/checks/heuristic/text-density.ts
5704
- var check30 = {
5772
+ var check31 = {
5705
5773
  id: "heuristic.whitespace.text-density",
5706
5774
  name: "Text Density",
5707
5775
  description: "Checks characters-per-page ratio",
@@ -5730,7 +5798,7 @@ var check30 = {
5730
5798
  });
5731
5799
  if (lowPages.length > 0) {
5732
5800
  results.push({
5733
- checkId: check30.id,
5801
+ checkId: check31.id,
5734
5802
  severity: "info",
5735
5803
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
5736
5804
  file: ctx.pdfPath
@@ -5738,7 +5806,7 @@ var check30 = {
5738
5806
  }
5739
5807
  if (highPages.length > 0) {
5740
5808
  results.push({
5741
- checkId: check30.id,
5809
+ checkId: check31.id,
5742
5810
  severity: "info",
5743
5811
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
5744
5812
  file: ctx.pdfPath
@@ -5747,11 +5815,11 @@ var check30 = {
5747
5815
  return results;
5748
5816
  }
5749
5817
  };
5750
- registerCheck(check30);
5818
+ registerCheck(check31);
5751
5819
 
5752
5820
  // src/checks/heuristic/section-density.ts
5753
- import { readFile as readFile16 } from "node:fs/promises";
5754
- var check31 = {
5821
+ import { readFile as readFile17 } from "node:fs/promises";
5822
+ var check32 = {
5755
5823
  id: "heuristic.chunking.section-density",
5756
5824
  name: "Section Density",
5757
5825
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -5767,7 +5835,7 @@ var check31 = {
5767
5835
  const results = [];
5768
5836
  for (const file of files) {
5769
5837
  try {
5770
- const content = await readFile16(file, "utf8");
5838
+ const content = await readFile17(file, "utf8");
5771
5839
  const lines = content.split(`
5772
5840
  `);
5773
5841
  let currentSectionStart = 0;
@@ -5777,7 +5845,7 @@ var check31 = {
5777
5845
  const line = lines[i];
5778
5846
  if (/^#{1,6}\s/.test(line)) {
5779
5847
  if (paragraphCount > maxParas) {
5780
- results.push(finding(check31.id, {
5848
+ results.push(finding(check32.id, {
5781
5849
  severity: "info",
5782
5850
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
5783
5851
  file,
@@ -5797,7 +5865,7 @@ var check31 = {
5797
5865
  }
5798
5866
  }
5799
5867
  if (paragraphCount > maxParas) {
5800
- results.push(finding(check31.id, {
5868
+ results.push(finding(check32.id, {
5801
5869
  severity: "info",
5802
5870
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
5803
5871
  file,
@@ -5805,7 +5873,7 @@ var check31 = {
5805
5873
  }));
5806
5874
  }
5807
5875
  } catch {
5808
- results.push(inspectionFailed(check31.id, `Could not read source file: ${file}`, {
5876
+ results.push(inspectionFailed(check32.id, `Could not read source file: ${file}`, {
5809
5877
  file
5810
5878
  }));
5811
5879
  }
@@ -5813,10 +5881,10 @@ var check31 = {
5813
5881
  return results;
5814
5882
  }
5815
5883
  };
5816
- registerCheck(check31);
5884
+ registerCheck(check32);
5817
5885
 
5818
5886
  // src/checks/heuristic/layer-count.ts
5819
- var check32 = {
5887
+ var check33 = {
5820
5888
  id: "heuristic.decoration.layer-count",
5821
5889
  name: "Layer Count",
5822
5890
  description: "Counts image objects per page",
@@ -5841,7 +5909,7 @@ var check32 = {
5841
5909
  heavyPages.sort((a, b) => a - b);
5842
5910
  return [
5843
5911
  {
5844
- checkId: check32.id,
5912
+ checkId: check33.id,
5845
5913
  severity: "info",
5846
5914
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
5847
5915
  file: ctx.pdfPath
@@ -5851,10 +5919,10 @@ var check32 = {
5851
5919
  return [];
5852
5920
  }
5853
5921
  };
5854
- registerCheck(check32);
5922
+ registerCheck(check33);
5855
5923
 
5856
5924
  // src/checks/heuristic/placement-variance.ts
5857
- var check33 = {
5925
+ var check34 = {
5858
5926
  id: "heuristic.layout.placement-variance",
5859
5927
  name: "Placement Variance",
5860
5928
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -5872,7 +5940,7 @@ var check33 = {
5872
5940
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
5873
5941
  return [
5874
5942
  {
5875
- checkId: check33.id,
5943
+ checkId: check34.id,
5876
5944
  severity: "info",
5877
5945
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
5878
5946
  file: ctx.pdfPath
@@ -5880,21 +5948,21 @@ var check33 = {
5880
5948
  ];
5881
5949
  }
5882
5950
  };
5883
- registerCheck(check33);
5951
+ registerCheck(check34);
5884
5952
 
5885
5953
  // src/checks/tool-check.ts
5886
5954
  async function checkToolAvailability(config, opts = {}) {
5887
5955
  const { checks: checks2 } = selectChecks(opts, config);
5888
5956
  const toolToChecks = new Map;
5889
- for (const check34 of checks2) {
5890
- if (!check34.requiredTools?.length)
5957
+ for (const check35 of checks2) {
5958
+ if (!check35.requiredTools?.length)
5891
5959
  continue;
5892
- for (const tool of check34.requiredTools) {
5960
+ for (const tool of check35.requiredTools) {
5893
5961
  const existing = toolToChecks.get(tool);
5894
5962
  if (existing) {
5895
- existing.push(check34.id);
5963
+ existing.push(check35.id);
5896
5964
  } else {
5897
- toolToChecks.set(tool, [check34.id]);
5965
+ toolToChecks.set(tool, [check35.id]);
5898
5966
  }
5899
5967
  }
5900
5968
  }
@@ -5910,11 +5978,11 @@ async function checkToolAvailability(config, opts = {}) {
5910
5978
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
5911
5979
  const missingSet = new Set(missing);
5912
5980
  const skippedChecks = [];
5913
- for (const check34 of checks2) {
5914
- if (!check34.requiredTools?.length)
5981
+ for (const check35 of checks2) {
5982
+ if (!check35.requiredTools?.length)
5915
5983
  continue;
5916
- if (check34.requiredTools.some((t) => missingSet.has(t))) {
5917
- skippedChecks.push(check34.id);
5984
+ if (check35.requiredTools.some((t) => missingSet.has(t))) {
5985
+ skippedChecks.push(check35.id);
5918
5986
  }
5919
5987
  }
5920
5988
  return { available, missing, skippedChecks, toolToChecks };
@@ -5936,7 +6004,7 @@ import { existsSync as existsSync8 } from "node:fs";
5936
6004
  import { dirname as dirname4, join as join3, resolve as resolve7 } from "node:path";
5937
6005
 
5938
6006
  // src/lib/output-paths.ts
5939
- import path4 from "node:path";
6007
+ import path5 from "node:path";
5940
6008
 
5941
6009
  // src/lib/slug.ts
5942
6010
  function slugify(name, fallback = "") {
@@ -5950,7 +6018,7 @@ function bookSlug(title) {
5950
6018
  return slugify(title ?? "", "book");
5951
6019
  }
5952
6020
  function resolveOutputDir(manifestDir, title) {
5953
- return path4.resolve(manifestDir, DIST_DIRNAME, bookSlug(title));
6021
+ return path5.resolve(manifestDir, DIST_DIRNAME, bookSlug(title));
5954
6022
  }
5955
6023
  function artifactName(title, format) {
5956
6024
  return `${bookSlug(title)}-${format}.pdf`;
@@ -5960,7 +6028,17 @@ function artifactName(title, format) {
5960
6028
  var BOOK_HTML_FILENAME = "book.html";
5961
6029
 
5962
6030
  // src/lib/markdown/index.ts
5963
- import { readdir as readdir3, readFile as readFile17, writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
6031
+ import { readdir as readdir3, readFile as readFile18, writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
6032
+
6033
+ // src/lib/remote-auth/sync-messages.ts
6034
+ function isOnlineSibling(filepath) {
6035
+ const dot = filepath.lastIndexOf(".");
6036
+ const slash = filepath.lastIndexOf("/");
6037
+ const stem = dot > slash + 1 ? filepath.slice(0, dot) : filepath;
6038
+ return stem.endsWith(".online");
6039
+ }
6040
+
6041
+ // src/lib/markdown/index.ts
5964
6042
  import { join as join2 } from "node:path";
5965
6043
 
5966
6044
  // src/lib/markdown/chapter-id.ts
@@ -6229,7 +6307,7 @@ ${bodyContent}
6229
6307
  async function resolveActiveMarkdownFiles(inputDir, configuredFiles) {
6230
6308
  if (configuredFiles && configuredFiles.length > 0)
6231
6309
  return configuredFiles;
6232
- return (await readdir3(inputDir)).filter((f) => f.endsWith(".md")).sort();
6310
+ return (await readdir3(inputDir)).filter((f) => f.endsWith(".md") && !isOnlineSibling(f)).sort();
6233
6311
  }
6234
6312
  async function renderChapters(inputDir, opts = {}) {
6235
6313
  const styles = await resolveActiveStyles(inputDir, opts.styles);
@@ -6244,7 +6322,7 @@ async function renderChapters(inputDir, opts = {}) {
6244
6322
  }
6245
6323
  return assembleBookHtml({
6246
6324
  files,
6247
- readText: (relPath) => readFile17(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
6325
+ readText: (relPath) => readFile18(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
6248
6326
  projectCss: inlined.css,
6249
6327
  title: opts.title,
6250
6328
  plugins: opts.plugins,
@@ -6359,25 +6437,25 @@ async function runChecks(ctx, opts = {}) {
6359
6437
  }
6360
6438
  const release = retainPdfCache();
6361
6439
  try {
6362
- for (const check34 of checks2) {
6440
+ for (const check35 of checks2) {
6363
6441
  try {
6364
- const results = await check34.run(ctx);
6365
- const severityOverride = getCheckSeverityOverride(check34.id, ctx.config);
6442
+ const results = await check35.run(ctx);
6443
+ const severityOverride = getCheckSeverityOverride(check35.id, ctx.config);
6366
6444
  if (severityOverride) {
6367
6445
  for (const r of results) {
6368
6446
  r.severity = severityOverride;
6369
6447
  }
6370
6448
  }
6371
6449
  if (results.length === 0) {
6372
- passed.push(check34.id);
6450
+ passed.push(check35.id);
6373
6451
  } else {
6374
6452
  allResults.push(...results);
6375
6453
  }
6376
6454
  } catch (err) {
6377
6455
  allResults.push({
6378
- checkId: check34.id,
6456
+ checkId: check35.id,
6379
6457
  severity: "error",
6380
- message: `Check "${check34.id}" threw: ${err instanceof Error ? err.message : String(err)}`
6458
+ message: `Check "${check35.id}" threw: ${err instanceof Error ? err.message : String(err)}`
6381
6459
  });
6382
6460
  }
6383
6461
  }
@@ -6734,7 +6812,7 @@ function buildPdfSummaryLines(results) {
6734
6812
  }
6735
6813
 
6736
6814
  // src/lib/build-runner.ts
6737
- import path7 from "node:path";
6815
+ import path8 from "node:path";
6738
6816
  import fs2 from "node:fs";
6739
6817
  import fsp2 from "node:fs/promises";
6740
6818
  import os2 from "node:os";
@@ -6742,9 +6820,9 @@ import { randomBytes } from "node:crypto";
6742
6820
 
6743
6821
  // src/lib/build-fingerprint.ts
6744
6822
  import * as fs from "node:fs";
6745
- import { mkdir as mkdir3, readFile as readFile18, writeFile as writeFile3 } from "node:fs/promises";
6823
+ import { mkdir as mkdir3, readFile as readFile19, writeFile as writeFile3 } from "node:fs/promises";
6746
6824
  import { createHash as createHash4 } from "node:crypto";
6747
- import path5 from "node:path";
6825
+ import path6 from "node:path";
6748
6826
  import git from "isomorphic-git";
6749
6827
  var FINGERPRINT_FILENAME = "build-fingerprint.json";
6750
6828
  var VERSION_TIMEOUT_MS = 4000;
@@ -6793,7 +6871,7 @@ async function getGitRevision(sourceDir) {
6793
6871
  const candidateDirs = [sourceDir, process.cwd()].filter((v) => Boolean(v));
6794
6872
  const seen = new Set;
6795
6873
  for (const dir of candidateDirs) {
6796
- const abs = path5.resolve(dir);
6874
+ const abs = path6.resolve(dir);
6797
6875
  if (seen.has(abs))
6798
6876
  continue;
6799
6877
  seen.add(abs);
@@ -6829,8 +6907,8 @@ async function getEngineBundleHash() {
6829
6907
  getAssetPath("engine/gutterpress-agent.js")
6830
6908
  ]);
6831
6909
  const [viewerBytes, agentBytes] = await Promise.all([
6832
- readFile18(viewerPath),
6833
- readFile18(agentPath)
6910
+ readFile19(viewerPath),
6911
+ readFile19(agentPath)
6834
6912
  ]);
6835
6913
  const hash = createHash4("sha256");
6836
6914
  hash.update(viewerBytes);
@@ -6878,8 +6956,8 @@ function sanitizeArgs(args) {
6878
6956
  return out;
6879
6957
  }
6880
6958
  async function writeBuildFingerprint(input) {
6881
- const outputDir = path5.resolve(input.outputDir);
6882
- const outPath = path5.join(outputDir, FINGERPRINT_FILENAME);
6959
+ const outputDir = path6.resolve(input.outputDir);
6960
+ const outPath = path6.join(outputDir, FINGERPRINT_FILENAME);
6883
6961
  const [tools, sourceRevision] = await Promise.all([
6884
6962
  getToolVersions(),
6885
6963
  getGitRevision(input.sourceDir)
@@ -6895,7 +6973,7 @@ async function writeBuildFingerprint(input) {
6895
6973
  iccPath: input.pdfx.iccPath,
6896
6974
  stripAnnotations: input.pdfx.stripAnnotations
6897
6975
  },
6898
- outputDir: input.recordedOutputDir ? path5.resolve(input.recordedOutputDir) : outputDir
6976
+ outputDir: input.recordedOutputDir ? path6.resolve(input.recordedOutputDir) : outputDir
6899
6977
  },
6900
6978
  sourceRevision,
6901
6979
  tools
@@ -6971,7 +7049,7 @@ function computeGates(format, opts, config) {
6971
7049
  }
6972
7050
 
6973
7051
  // src/lib/build-staging.ts
6974
- import path6 from "node:path";
7052
+ import path7 from "node:path";
6975
7053
  import os from "node:os";
6976
7054
  import fsp from "node:fs/promises";
6977
7055
 
@@ -7154,8 +7232,8 @@ function placeholderPng(width = 640, height = 480, cell = 32) {
7154
7232
 
7155
7233
  // src/lib/build-staging.ts
7156
7234
  async function shipViewerHtml(htmlFile, outDir) {
7157
- await fsp.mkdir(path6.join(outDir, "engine"), { recursive: true });
7158
- await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"), path6.join(outDir, "engine/gutterpress-viewer.js"));
7235
+ await fsp.mkdir(path7.join(outDir, "engine"), { recursive: true });
7236
+ await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"), path7.join(outDir, "engine/gutterpress-viewer.js"));
7159
7237
  const tag = ` <script src="engine/gutterpress-viewer.js"></script>
7160
7238
  `;
7161
7239
  const html = await fsp.readFile(htmlFile, "utf-8");
@@ -7184,18 +7262,18 @@ async function stageBookAssets(options) {
7184
7262
  return { missing: [...missingPlaceholders.keys()].sort() };
7185
7263
  }
7186
7264
  async function copyReferencedAssets(copies, outDir) {
7187
- const dirs = new Set(copies.map((c) => path6.dirname(path6.resolve(outDir, c.to))));
7265
+ const dirs = new Set(copies.map((c) => path7.dirname(path7.resolve(outDir, c.to))));
7188
7266
  await Promise.all([...dirs].map((d) => fsp.mkdir(d, { recursive: true })));
7189
7267
  const missing = new Map;
7190
7268
  await Promise.all(copies.map(async (c) => {
7191
- const dest = path6.resolve(outDir, c.to);
7269
+ const dest = path7.resolve(outDir, c.to);
7192
7270
  try {
7193
7271
  await fsp.copyFile(c.from, dest);
7194
7272
  } catch (err) {
7195
7273
  if (err?.code === "ENOENT") {
7196
7274
  const placeholder = placeholderOutputPath(c.to);
7197
- const placeholderDest = path6.resolve(outDir, placeholder);
7198
- await fsp.mkdir(path6.dirname(placeholderDest), { recursive: true });
7275
+ const placeholderDest = path7.resolve(outDir, placeholder);
7276
+ await fsp.mkdir(path7.dirname(placeholderDest), { recursive: true });
7199
7277
  await fsp.writeFile(placeholderDest, placeholderPng());
7200
7278
  missing.set(c.to, placeholder);
7201
7279
  return;
@@ -7206,7 +7284,7 @@ async function copyReferencedAssets(copies, outDir) {
7206
7284
  return missing;
7207
7285
  }
7208
7286
  async function createStageRoot() {
7209
- return fsp.mkdtemp(path6.join(os.tmpdir(), "gutterpress-stage-"));
7287
+ return fsp.mkdtemp(path7.join(os.tmpdir(), "gutterpress-stage-"));
7210
7288
  }
7211
7289
 
7212
7290
  // src/lib/build-runner.ts
@@ -7214,18 +7292,18 @@ function splitOutPath(outArg, format) {
7214
7292
  if (typeof outArg !== "string" || outArg.length === 0) {
7215
7293
  return { outDir: undefined, pdfFileOverride: null };
7216
7294
  }
7217
- const resolved = path7.resolve(outArg);
7295
+ const resolved = path8.resolve(outArg);
7218
7296
  if ((format === "pdf" || format === "pdfx") && resolved.toLowerCase().endsWith(".pdf")) {
7219
- return { outDir: path7.dirname(resolved), pdfFileOverride: resolved };
7297
+ return { outDir: path8.dirname(resolved), pdfFileOverride: resolved };
7220
7298
  }
7221
7299
  return { outDir: resolved, pdfFileOverride: null };
7222
7300
  }
7223
7301
  function layoutWarningKey(file, line, message) {
7224
- return `${path7.resolve(file)}\x00${line ?? 0}\x00${message}`;
7302
+ return `${path8.resolve(file)}\x00${line ?? 0}\x00${message}`;
7225
7303
  }
7226
7304
  async function resolveBuildContext(opts) {
7227
7305
  const { format } = opts;
7228
- const inputDir = path7.resolve(opts.inputDir);
7306
+ const inputDir = path8.resolve(opts.inputDir);
7229
7307
  const { manifest, manifestDir, manifestPath } = await loadManifestWithPath(opts.manifestPath ?? inputDir, { explicit: opts.manifestPath !== undefined });
7230
7308
  if (manifestPath === null) {
7231
7309
  throw new UsageError(`No project manifest found in ${inputDir}. Looked for ${MANIFEST_FILENAMES.join(" or ")}. ` + "Run from your project folder or pass that folder with `gutterpress build <project-dir>`. " + "For a custom manifest filename, pass `--manifest <path>`.");
@@ -7240,10 +7318,10 @@ async function resolveBuildContext(opts) {
7240
7318
  pdfx: pdfxConfigOverride,
7241
7319
  engine: opts.engine
7242
7320
  }, manifest);
7243
- const target = opts.pdfFileOverride ? { kind: "file", file: path7.resolve(opts.pdfFileOverride) } : opts.outDir ? { kind: "directory", dir: opts.outDir } : { kind: "project", dir: resolveOutputDir(manifestDir, config.title) };
7244
- const outDir = target.kind === "file" ? path7.dirname(target.file) : target.dir;
7321
+ const target = opts.pdfFileOverride ? { kind: "file", file: path8.resolve(opts.pdfFileOverride) } : opts.outDir ? { kind: "directory", dir: opts.outDir } : { kind: "project", dir: resolveOutputDir(manifestDir, config.title) };
7322
+ const outDir = target.kind === "file" ? path8.dirname(target.file) : target.dir;
7245
7323
  const gates = computeGates(format, opts, config);
7246
- const workDir = target.kind === "project" ? path7.join(path7.dirname(target.dir), `.${path7.basename(target.dir)}-build-${randomBytes(6).toString("hex")}`) : path7.join(os2.tmpdir(), `gutterpress-build-${randomBytes(6).toString("hex")}`);
7324
+ const workDir = target.kind === "project" ? path8.join(path8.dirname(target.dir), `.${path8.basename(target.dir)}-build-${randomBytes(6).toString("hex")}`) : path8.join(os2.tmpdir(), `gutterpress-build-${randomBytes(6).toString("hex")}`);
7247
7325
  return {
7248
7326
  opts,
7249
7327
  format,
@@ -7310,7 +7388,7 @@ async function renderBook(ctx) {
7310
7388
  pluginCss,
7311
7389
  onChapterWarnings: (file, warnings) => {
7312
7390
  for (const w of warnings) {
7313
- const key = layoutWarningKey(path7.resolve(renderDir, file), w.line, w.message);
7391
+ const key = layoutWarningKey(path8.resolve(renderDir, file), w.line, w.message);
7314
7392
  if (!ctx.prevalidatedLayoutWarningKeys.has(key)) {
7315
7393
  log.warn(` ${file}, line ${w.line}: ${w.message}`);
7316
7394
  }
@@ -7348,9 +7426,9 @@ async function renderBook(ctx) {
7348
7426
  return htmlFile;
7349
7427
  }
7350
7428
  async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
7351
- const iccCandidates = path7.isAbsolute(icc) ? [icc] : [path7.resolve(manifestDir, icc), path7.resolve(icc)];
7429
+ const iccCandidates = path8.isAbsolute(icc) ? [icc] : [path8.resolve(manifestDir, icc), path8.resolve(icc)];
7352
7430
  let effectiveIccPath = iccCandidates.find((candidate) => fs2.existsSync(candidate)) ?? null;
7353
- if (!effectiveIccPath && !explicitIccPath && path7.basename(icc) === "CGATS21_CRPC1.icc") {
7431
+ if (!effectiveIccPath && !explicitIccPath && path8.basename(icc) === "CGATS21_CRPC1.icc") {
7354
7432
  effectiveIccPath = await getAssetPath("profiles/CGATS21_CRPC1.icc");
7355
7433
  }
7356
7434
  if (!effectiveIccPath) {
@@ -7366,13 +7444,13 @@ async function finalizeBuild(ctx, fingerprint, wroteMessage, paths, artifactName
7366
7444
  });
7367
7445
  await publishBuild(ctx, artifactName2);
7368
7446
  const delivered = ctx.target.kind !== "file";
7369
- const fingerprintPath = delivered ? path7.join(ctx.outDir, path7.basename(workFingerprint)) : null;
7447
+ const fingerprintPath = delivered ? path8.join(ctx.outDir, path8.basename(workFingerprint)) : null;
7370
7448
  log.success(wroteMessage);
7371
7449
  if (fingerprintPath)
7372
7450
  log.info(`Fingerprint: ${fingerprintPath}`);
7373
7451
  return {
7374
7452
  outDir: ctx.outDir,
7375
- htmlPath: delivered ? path7.join(ctx.outDir, path7.relative(ctx.workDir, paths.htmlPath)) : null,
7453
+ htmlPath: delivered ? path8.join(ctx.outDir, path8.relative(ctx.workDir, paths.htmlPath)) : null,
7376
7454
  pdfPath: paths.pdfPath,
7377
7455
  fingerprintPath,
7378
7456
  diagnostics
@@ -7383,8 +7461,8 @@ async function publishBuild(ctx, artifactName2) {
7383
7461
  if (target.kind === "file") {
7384
7462
  if (!artifactName2)
7385
7463
  throw new BuildError("No artifact to write for this format", 1);
7386
- await fsp2.mkdir(path7.dirname(target.file), { recursive: true });
7387
- await fsp2.copyFile(path7.join(workDir, artifactName2), target.file);
7464
+ await fsp2.mkdir(path8.dirname(target.file), { recursive: true });
7465
+ await fsp2.copyFile(path8.join(workDir, artifactName2), target.file);
7388
7466
  return;
7389
7467
  }
7390
7468
  if (target.kind === "directory") {
@@ -7393,12 +7471,12 @@ async function publishBuild(ctx, artifactName2) {
7393
7471
  return;
7394
7472
  }
7395
7473
  for (const name of await listPdfs(target.dir)) {
7396
- if (!fs2.existsSync(path7.join(workDir, name))) {
7397
- await fsp2.rename(path7.join(target.dir, name), path7.join(workDir, name));
7474
+ if (!fs2.existsSync(path8.join(workDir, name))) {
7475
+ await fsp2.rename(path8.join(target.dir, name), path8.join(workDir, name));
7398
7476
  }
7399
7477
  }
7400
7478
  await fsp2.rm(target.dir, { recursive: true, force: true });
7401
- await fsp2.mkdir(path7.dirname(target.dir), { recursive: true });
7479
+ await fsp2.mkdir(path8.dirname(target.dir), { recursive: true });
7402
7480
  await fsp2.rename(workDir, target.dir);
7403
7481
  }
7404
7482
  async function listPdfs(dir) {
@@ -7414,7 +7492,7 @@ class HtmlOutput {
7414
7492
  const { workDir, outDir, inputDir, config, opts } = ctx;
7415
7493
  log.info("Shipping self-contained HTML + viewer bundle (native engine)");
7416
7494
  await shipViewerHtml(htmlFile, workDir);
7417
- await fsp2.writeFile(path7.join(workDir, "index.html"), `<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=book.html"><title>Gutterpress</title></head><body></body></html>
7495
+ await fsp2.writeFile(path8.join(workDir, "index.html"), `<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=book.html"><title>Gutterpress</title></head><body></body></html>
7418
7496
  `, "utf-8");
7419
7497
  return finalizeBuild(ctx, {
7420
7498
  command: "build",
@@ -7427,7 +7505,7 @@ class HtmlOutput {
7427
7505
  iccPath: null,
7428
7506
  stripAnnotations: null
7429
7507
  }
7430
- }, `Wrote: ${path7.join(outDir, BOOK_HTML)}`, { htmlPath: htmlFile, pdfPath: null });
7508
+ }, `Wrote: ${path8.join(outDir, BOOK_HTML)}`, { htmlPath: htmlFile, pdfPath: null });
7431
7509
  }
7432
7510
  }
7433
7511
 
@@ -7436,14 +7514,14 @@ class PdfOutput {
7436
7514
  const { workDir, outDir, inputDir, config, opts, format, manifestDir, gates } = ctx;
7437
7515
  const pdfxMode = format === "pdfx" ? opts.pdfxFlavor ?? config.pdfx.flavor : undefined;
7438
7516
  const pdfName = artifactName(config.title, pdfxMode ? "pdfx" : "pdf");
7439
- const pdfFile = path7.join(workDir, pdfName);
7440
- const reportedPdf = ctx.target.kind === "file" ? ctx.target.file : path7.join(outDir, pdfName);
7517
+ const pdfFile = path8.join(workDir, pdfName);
7518
+ const reportedPdf = ctx.target.kind === "file" ? ctx.target.file : path8.join(outDir, pdfName);
7441
7519
  const stage = await createStageRoot();
7442
7520
  try {
7443
- const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
7444
- await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
7521
+ const rawPdf = pdfxMode ? path8.join(stage, "raw.pdf") : path8.resolve(pdfFile);
7522
+ await fsp2.mkdir(path8.dirname(path8.resolve(pdfFile)), { recursive: true });
7445
7523
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
7446
- const { buildNativePdf } = await import("./engine-ft4cr3ep.js");
7524
+ const { buildNativePdf } = await import("./engine-57vw8zpd.js");
7447
7525
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
7448
7526
  title: config.title,
7449
7527
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -7474,7 +7552,7 @@ class PdfOutput {
7474
7552
  await stripAnnotations(rawPdf, stage);
7475
7553
  }
7476
7554
  log.info(`Converting to CMYK PDF/X (${pdfxMode})`);
7477
- await convertToPdfxCmyk(rawPdf, path7.resolve(pdfFile), {
7555
+ await convertToPdfxCmyk(rawPdf, path8.resolve(pdfFile), {
7478
7556
  iccPath: effectiveIccPath,
7479
7557
  pdfx: pdfxMode,
7480
7558
  title: config.title,
@@ -7563,10 +7641,10 @@ function openPath(filePath) {
7563
7641
  }
7564
7642
 
7565
7643
  // src/preview/lifecycle.ts
7566
- import path9 from "path";
7644
+ import path10 from "path";
7567
7645
  import { tmpdir as tmpdir2 } from "os";
7568
7646
  import { randomBytes as randomBytes2 } from "crypto";
7569
- import { readdir as readdir4, readFile as readFile19, writeFile as writeFile4 } from "node:fs/promises";
7647
+ import { readdir as readdir4, readFile as readFile20, writeFile as writeFile4 } from "node:fs/promises";
7570
7648
 
7571
7649
  // src/utils/file-utils.ts
7572
7650
  import { promises as fs3 } from "fs";
@@ -7588,7 +7666,7 @@ async function mkdir4(dirPath) {
7588
7666
  // src/preview/file-watcher.ts
7589
7667
  import { watch } from "chokidar";
7590
7668
  import fsp3 from "node:fs/promises";
7591
- import path8 from "path";
7669
+ import path9 from "path";
7592
7670
  var EMPTY_BOOK_HTML = `<!doctype html>
7593
7671
  <html lang="en">
7594
7672
  <head>
@@ -7641,7 +7719,7 @@ function injectPreviewScripts(html, pageIsolateChapters) {
7641
7719
  async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
7642
7720
  if (!inputPath) {
7643
7721
  cssAssets.clear();
7644
- await fsp3.writeFile(path8.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
7722
+ await fsp3.writeFile(path9.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
7645
7723
  return;
7646
7724
  }
7647
7725
  const nextAssets = new Map;
@@ -7656,7 +7734,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
7656
7734
  cssAssets.clear();
7657
7735
  for (const [to, from] of nextAssets)
7658
7736
  cssAssets.set(to, from);
7659
- await fsp3.writeFile(path8.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, false), "utf-8");
7737
+ await fsp3.writeFile(path9.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, false), "utf-8");
7660
7738
  }
7661
7739
  async function renderChapterPreviewHtml(inputPath, file, config) {
7662
7740
  const html = await renderPreviewBook(inputPath, config, {
@@ -7668,12 +7746,12 @@ async function renderChapterPreviewHtml(inputPath, file, config) {
7668
7746
  function describeChanges(changes, inputResolved) {
7669
7747
  const files = [];
7670
7748
  for (const [changedPath, event] of changes) {
7671
- const relative2 = path8.relative(inputResolved, path8.resolve(changedPath));
7672
- if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${path8.sep}`) || path8.isAbsolute(relative2))
7749
+ const relative2 = path9.relative(inputResolved, path9.resolve(changedPath));
7750
+ if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${path9.sep}`) || path9.isAbsolute(relative2))
7673
7751
  continue;
7674
7752
  files.push({
7675
7753
  relativePath: canonicalChapterId(relative2),
7676
- ext: path8.extname(changedPath).toLowerCase(),
7754
+ ext: path9.extname(changedPath).toLowerCase(),
7677
7755
  event
7678
7756
  });
7679
7757
  }
@@ -7725,23 +7803,23 @@ function isIgnoredWatchPath(candidatePath, projectRoot) {
7725
7803
  return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
7726
7804
  }
7727
7805
  function isExternalWatchCandidate(candidatePath, targets) {
7728
- const candidate = path8.resolve(candidatePath);
7806
+ const candidate = path9.resolve(candidatePath);
7729
7807
  for (const rawTarget of targets) {
7730
- const target = path8.resolve(rawTarget);
7731
- const rel = path8.relative(candidate, target);
7732
- if (rel === "" || rel !== ".." && !rel.startsWith(`..${path8.sep}`) && !path8.isAbsolute(rel))
7808
+ const target = path9.resolve(rawTarget);
7809
+ const rel = path9.relative(candidate, target);
7810
+ if (rel === "" || rel !== ".." && !rel.startsWith(`..${path9.sep}`) && !path9.isAbsolute(rel))
7733
7811
  return true;
7734
7812
  }
7735
7813
  return false;
7736
7814
  }
7737
7815
  async function nearestExistingDirectory(start) {
7738
- let current = path8.resolve(start);
7816
+ let current = path9.resolve(start);
7739
7817
  while (true) {
7740
7818
  try {
7741
7819
  if ((await fsp3.stat(current)).isDirectory())
7742
7820
  return current;
7743
7821
  } catch {}
7744
- const parent = path8.dirname(current);
7822
+ const parent = path9.dirname(current);
7745
7823
  if (parent === current)
7746
7824
  return current;
7747
7825
  current = parent;
@@ -7750,12 +7828,12 @@ async function nearestExistingDirectory(start) {
7750
7828
  async function externalWatchRoots(targets) {
7751
7829
  const roots = new Set;
7752
7830
  for (const target of targets) {
7753
- roots.add(await nearestExistingDirectory(path8.dirname(path8.resolve(target))));
7831
+ roots.add(await nearestExistingDirectory(path9.dirname(path9.resolve(target))));
7754
7832
  }
7755
7833
  return [...roots];
7756
7834
  }
7757
7835
  async function externalWatchTargets(projectDir, config) {
7758
- const root = path8.resolve(projectDir);
7836
+ const root = path9.resolve(projectDir);
7759
7837
  let canonicalRoot = root;
7760
7838
  try {
7761
7839
  canonicalRoot = await fsp3.realpath(root);
@@ -7763,21 +7841,21 @@ async function externalWatchTargets(projectDir, config) {
7763
7841
  const styles = await resolveActiveStyles(root, config.styles);
7764
7842
  const candidates = [
7765
7843
  ...await collectStyleDependencies(root, styles),
7766
- ...(config.plugins ?? []).map((p) => p.path).filter((p) => !!p).map((p) => path8.resolve(root, p))
7844
+ ...(config.plugins ?? []).map((p) => p.path).filter((p) => !!p).map((p) => path9.resolve(root, p))
7767
7845
  ];
7768
7846
  const external = new Set;
7769
7847
  for (const abs of candidates) {
7770
- const authoredPath = path8.resolve(abs);
7771
- const authoredRel = path8.relative(root, authoredPath);
7772
- if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${path8.sep}`) || path8.isAbsolute(authoredRel))) {
7848
+ const authoredPath = path9.resolve(abs);
7849
+ const authoredRel = path9.relative(root, authoredPath);
7850
+ if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${path9.sep}`) || path9.isAbsolute(authoredRel))) {
7773
7851
  external.add(authoredPath);
7774
7852
  }
7775
7853
  let canonicalPath = authoredPath;
7776
7854
  try {
7777
7855
  canonicalPath = await fsp3.realpath(canonicalPath);
7778
7856
  } catch {}
7779
- const rel = path8.relative(canonicalRoot, canonicalPath);
7780
- if (rel === "" || rel !== ".." && !rel.startsWith(`..${path8.sep}`) && !path8.isAbsolute(rel))
7857
+ const rel = path9.relative(canonicalRoot, canonicalPath);
7858
+ if (rel === "" || rel !== ".." && !rel.startsWith(`..${path9.sep}`) && !path9.isAbsolute(rel))
7781
7859
  continue;
7782
7860
  external.add(canonicalPath);
7783
7861
  }
@@ -7804,7 +7882,7 @@ async function addAndAwaitWatch(watcher, roots) {
7804
7882
  }
7805
7883
  }
7806
7884
  function createFileWatcher(state) {
7807
- const inputResolved = path8.resolve(state.currentInputPath);
7885
+ const inputResolved = path9.resolve(state.currentInputPath);
7808
7886
  const watcher = watch(inputResolved, {
7809
7887
  persistent: true,
7810
7888
  ignoreInitial: true,
@@ -7844,7 +7922,7 @@ function createFileWatcher(state) {
7844
7922
  async function recordChange(event, filePath) {
7845
7923
  if (closed)
7846
7924
  return;
7847
- const target = path8.resolve(filePath);
7925
+ const target = path9.resolve(filePath);
7848
7926
  const settled = settledWrites.get(target);
7849
7927
  if (settled && event !== "unlink" && Date.now() <= settled.expiresAt) {
7850
7928
  const diskContent = await fsp3.readFile(target, "utf8").catch(() => null);
@@ -7865,7 +7943,7 @@ function createFileWatcher(state) {
7865
7943
  recordChange(event, filePath);
7866
7944
  });
7867
7945
  externalWatcher.on("all", (event, filePath) => {
7868
- const target = path8.resolve(filePath);
7946
+ const target = path9.resolve(filePath);
7869
7947
  if (!desiredExternals.has(target))
7870
7948
  return;
7871
7949
  if (event === "add" && suppressInitialExternalAdds.delete(target))
@@ -7873,8 +7951,8 @@ function createFileWatcher(state) {
7873
7951
  recordChange(event, target);
7874
7952
  });
7875
7953
  function isWatchedSource(target) {
7876
- const rel = path8.relative(inputResolved, target);
7877
- const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path8.sep}`) && !path8.isAbsolute(rel);
7954
+ const rel = path9.relative(inputResolved, target);
7955
+ const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path9.sep}`) && !path9.isAbsolute(rel);
7878
7956
  if (inProject)
7879
7957
  return !isIgnoredWatchPath(target, inputResolved);
7880
7958
  return desiredExternals.has(target);
@@ -7899,7 +7977,7 @@ function createFileWatcher(state) {
7899
7977
  state.notifySettledWrite = (filePath, writtenContent) => {
7900
7978
  if (closed)
7901
7979
  return;
7902
- const target = path8.resolve(filePath);
7980
+ const target = path9.resolve(filePath);
7903
7981
  if (isWatchedSource(target)) {
7904
7982
  acceptSettledWrite(target, writtenContent);
7905
7983
  return;
@@ -7920,7 +7998,7 @@ function createFileWatcher(state) {
7920
7998
  return;
7921
7999
  let nextTargets;
7922
8000
  try {
7923
- nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) => path8.resolve(target)));
8001
+ nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) => path9.resolve(target)));
7924
8002
  } catch (err) {
7925
8003
  debug(`Could not resolve external watch targets: ${err}`);
7926
8004
  return;
@@ -8052,7 +8130,7 @@ async function stopFileWatcher(state) {
8052
8130
  }
8053
8131
 
8054
8132
  // src/preview/lifecycle.ts
8055
- var TEMP_DIR_BASE = path9.join(tmpdir2(), "gutterpress-preview");
8133
+ var TEMP_DIR_BASE = path10.join(tmpdir2(), "gutterpress-preview");
8056
8134
  var PID_FILE_NAME = ".gutterpress.pid";
8057
8135
  function isProcessAlive(pid) {
8058
8136
  if (!Number.isFinite(pid) || pid <= 0)
@@ -8072,10 +8150,10 @@ async function cleanupOrphanTempDirs() {
8072
8150
  return;
8073
8151
  }
8074
8152
  for (const entry of entries) {
8075
- const dir = path9.join(TEMP_DIR_BASE, entry);
8076
- const pidFile = path9.join(dir, PID_FILE_NAME);
8153
+ const dir = path10.join(TEMP_DIR_BASE, entry);
8154
+ const pidFile = path10.join(dir, PID_FILE_NAME);
8077
8155
  try {
8078
- const raw = await readFile19(pidFile, "utf8");
8156
+ const raw = await readFile20(pidFile, "utf8");
8079
8157
  const pid = parseInt(raw.trim(), 10);
8080
8158
  if (!isProcessAlive(pid)) {
8081
8159
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -8087,10 +8165,10 @@ async function cleanupOrphanTempDirs() {
8087
8165
  async function initializePreviewDirectories() {
8088
8166
  await cleanupOrphanTempDirs();
8089
8167
  const tempDirSuffix = randomBytes2(8).toString("hex");
8090
- const tempDir = path9.join(TEMP_DIR_BASE, tempDirSuffix);
8168
+ const tempDir = path10.join(TEMP_DIR_BASE, tempDirSuffix);
8091
8169
  await mkdir4(tempDir);
8092
8170
  debug(`Created temporary directory: ${tempDir}`);
8093
- await writeFile4(path9.join(tempDir, PID_FILE_NAME), `${process.pid}
8171
+ await writeFile4(path10.join(tempDir, PID_FILE_NAME), `${process.pid}
8094
8172
  `, "utf8");
8095
8173
  return tempDir;
8096
8174
  }
@@ -8181,12 +8259,12 @@ function createServerState(inputPath, tempDir, config, options) {
8181
8259
  import http from "node:http";
8182
8260
  import net from "node:net";
8183
8261
  import { createHash as createHash6, randomUUID } from "node:crypto";
8184
- import { readFile as readFile20, stat as stat4 } from "node:fs/promises";
8185
- import path11 from "path";
8262
+ import { readFile as readFile21, stat as stat4 } from "node:fs/promises";
8263
+ import path12 from "path";
8186
8264
  import { WebSocket, WebSocketServer } from "ws";
8187
8265
 
8188
8266
  // src/lib/static-serve.ts
8189
- import path10 from "node:path";
8267
+ import path11 from "node:path";
8190
8268
  var STATIC_MIME = {
8191
8269
  ".html": "text/html; charset=utf-8",
8192
8270
  ".htm": "text/html; charset=utf-8",
@@ -8208,9 +8286,9 @@ var STATIC_MIME = {
8208
8286
  ".otf": "font/otf"
8209
8287
  };
8210
8288
  function resolveWithinRoot(relPath, root) {
8211
- const resolvedRoot = path10.resolve(root);
8212
- const candidate = path10.resolve(resolvedRoot, "." + (relPath.startsWith("/") ? relPath : "/" + relPath));
8213
- if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path10.sep)) {
8289
+ const resolvedRoot = path11.resolve(root);
8290
+ const candidate = path11.resolve(resolvedRoot, "." + (relPath.startsWith("/") ? relPath : "/" + relPath));
8291
+ if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path11.sep)) {
8214
8292
  return null;
8215
8293
  }
8216
8294
  return candidate;
@@ -8463,7 +8541,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
8463
8541
  try {
8464
8542
  const s = await stat4(filePath);
8465
8543
  if (s.isDirectory())
8466
- filePath = path11.join(filePath, "index.html");
8544
+ filePath = path12.join(filePath, "index.html");
8467
8545
  } catch {
8468
8546
  res.writeHead(404);
8469
8547
  res.end("Not Found");
@@ -8471,7 +8549,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
8471
8549
  }
8472
8550
  let data;
8473
8551
  try {
8474
- data = await readFile20(filePath);
8552
+ data = await readFile21(filePath);
8475
8553
  } catch {
8476
8554
  res.writeHead(404);
8477
8555
  res.end("Not Found");
@@ -8484,7 +8562,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
8484
8562
  res.end(withHmr);
8485
8563
  return;
8486
8564
  }
8487
- const ct = STATIC_MIME[path11.extname(filePath).toLowerCase()] ?? "application/octet-stream";
8565
+ const ct = STATIC_MIME[path12.extname(filePath).toLowerCase()] ?? "application/octet-stream";
8488
8566
  res.writeHead(200, { "Content-Type": ct, "Cache-Control": cacheControl, ...extraHeaders });
8489
8567
  res.end(data);
8490
8568
  }
@@ -8494,7 +8572,7 @@ var EMBEDDED_CACHE_CONTROL = "public, no-cache";
8494
8572
  var EMBEDDED_ETAG = `"gutterpress-${PACKAGE_VERSION}"`;
8495
8573
  var PROJECT_ASSET_CACHE_CONTROL = "private, no-cache";
8496
8574
  async function serveRevalidatedStatic(req, absPath, res, validators) {
8497
- const extension = path11.extname(absPath).toLowerCase();
8575
+ const extension = path12.extname(absPath).toLowerCase();
8498
8576
  if (extension === ".html" || extension === ".htm") {
8499
8577
  await serveStatic(absPath, res);
8500
8578
  return;
@@ -8521,7 +8599,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
8521
8599
  let data;
8522
8600
  if (!validator || validator.stamp !== stamp) {
8523
8601
  try {
8524
- data = await readFile20(absPath);
8602
+ data = await readFile21(absPath);
8525
8603
  } catch {
8526
8604
  await serveStatic(absPath, res);
8527
8605
  return;
@@ -8660,7 +8738,7 @@ async function createPreviewServer(state, port) {
8660
8738
  const file = rawFile ? canonicalChapterId(rawFile) : "";
8661
8739
  const revision = Number(url.searchParams.get("revision"));
8662
8740
  const configuredFiles = state.config.source?.files;
8663
- if (!file || !state.currentInputPath || !incrementalPreviewEnabled() || !Number.isSafeInteger(revision) || revision !== reloadRevision || path11.extname(file).toLowerCase() !== ".md" || hasDotSegment(file) || !resolveWithinRoot(file, state.currentInputPath) || Array.isArray(configuredFiles) && configuredFiles.length > 0 && !configuredFiles.some((configured) => canonicalChapterId(configured) === file)) {
8741
+ if (!file || !state.currentInputPath || !incrementalPreviewEnabled() || !Number.isSafeInteger(revision) || revision !== reloadRevision || path12.extname(file).toLowerCase() !== ".md" || hasDotSegment(file) || !resolveWithinRoot(file, state.currentInputPath) || Array.isArray(configuredFiles) && configuredFiles.length > 0 && !configuredFiles.some((configured) => canonicalChapterId(configured) === file)) {
8664
8742
  res.writeHead(400);
8665
8743
  res.end("Bad Request");
8666
8744
  return;
@@ -8860,25 +8938,25 @@ async function startPreviewServer(options) {
8860
8938
  }
8861
8939
 
8862
8940
  // src/lib/project-scaffold.ts
8863
- import { access, copyFile, cp as cp2, mkdir as mkdir6, readdir as readdir5, readFile as readFile22, stat as stat5, writeFile as writeFile5 } from "node:fs/promises";
8941
+ import { access, copyFile, cp as cp2, mkdir as mkdir6, readdir as readdir5, readFile as readFile23, stat as stat5, writeFile as writeFile5 } from "node:fs/promises";
8864
8942
  import { constants as FS, existsSync as existsSync10 } from "node:fs";
8865
- import path13 from "node:path";
8943
+ import path14 from "node:path";
8866
8944
 
8867
8945
  // src/lib/manifest-doc.ts
8868
8946
  import { existsSync as existsSync9 } from "node:fs";
8869
8947
  import { randomUUID as randomUUID2 } from "node:crypto";
8870
- import { mkdir as mkdir5, open, readFile as readFile21, rename, rm as rm2 } from "node:fs/promises";
8871
- import path12 from "node:path";
8948
+ import { mkdir as mkdir5, open, readFile as readFile22, rename, rm as rm2 } from "node:fs/promises";
8949
+ import path13 from "node:path";
8872
8950
  import { parseDocument, isSeq, YAMLSeq } from "yaml";
8873
8951
  function resolveManifestPath(projectDir) {
8874
- const existing = MANIFEST_FILENAMES.find((name) => existsSync9(path12.join(projectDir, name)));
8875
- return path12.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
8952
+ const existing = MANIFEST_FILENAMES.find((name) => existsSync9(path13.join(projectDir, name)));
8953
+ return path13.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
8876
8954
  }
8877
8955
  async function loadManifestDoc(projectDir) {
8878
8956
  const file = resolveManifestPath(projectDir);
8879
8957
  let text = "";
8880
8958
  try {
8881
- text = await readFile21(file, "utf8");
8959
+ text = await readFile22(file, "utf8");
8882
8960
  } catch (error2) {
8883
8961
  if (error2.code !== "ENOENT")
8884
8962
  throw error2;
@@ -8886,9 +8964,9 @@ async function loadManifestDoc(projectDir) {
8886
8964
  return { doc: parseDocument(text), file };
8887
8965
  }
8888
8966
  async function writeManifestDoc(file, doc) {
8889
- const dir = path12.dirname(file);
8967
+ const dir = path13.dirname(file);
8890
8968
  await mkdir5(dir, { recursive: true });
8891
- const temporary = path12.join(dir, `.${path12.basename(file)}.tmp-${process.pid}-${randomUUID2()}`);
8969
+ const temporary = path13.join(dir, `.${path13.basename(file)}.tmp-${process.pid}-${randomUUID2()}`);
8892
8970
  try {
8893
8971
  const handle = await open(temporary, "wx", 438);
8894
8972
  try {
@@ -8983,7 +9061,7 @@ async function scaffoldProject(options) {
8983
9061
  throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
8984
9062
  }
8985
9063
  const parentDir = options.parentDir;
8986
- if (!parentDir || !path13.isAbsolute(parentDir)) {
9064
+ if (!parentDir || !path14.isAbsolute(parentDir)) {
8987
9065
  throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
8988
9066
  }
8989
9067
  try {
@@ -8991,7 +9069,7 @@ async function scaffoldProject(options) {
8991
9069
  } catch {
8992
9070
  throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
8993
9071
  }
8994
- const projectDir = path13.join(parentDir, folderName);
9072
+ const projectDir = path14.join(parentDir, folderName);
8995
9073
  let targetExists = true;
8996
9074
  try {
8997
9075
  await access(projectDir, FS.F_OK);
@@ -9012,19 +9090,19 @@ async function scaffoldProject(options) {
9012
9090
  for (const entry of entries) {
9013
9091
  if (entry.name === ".gutterpress-template.json")
9014
9092
  continue;
9015
- await cp2(path13.join(customTemplateDir, entry.name), path13.join(projectDir, entry.name), { recursive: true });
9093
+ await cp2(path14.join(customTemplateDir, entry.name), path14.join(projectDir, entry.name), { recursive: true });
9016
9094
  }
9017
- await mkdir6(path13.join(projectDir, "assets"), { recursive: true });
9095
+ await mkdir6(path14.join(projectDir, "assets"), { recursive: true });
9018
9096
  } else {
9019
9097
  const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
9020
9098
  const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
9021
- await mkdir6(path13.join(projectDir, "assets"), { recursive: true });
9022
- await copyFile(tplManifest, path13.join(projectDir, MANIFEST_FILENAMES[0]));
9023
- await copyFile(tplChapter, path13.join(projectDir, "chapter-01.md"));
9099
+ await mkdir6(path14.join(projectDir, "assets"), { recursive: true });
9100
+ await copyFile(tplManifest, path14.join(projectDir, MANIFEST_FILENAMES[0]));
9101
+ await copyFile(tplChapter, path14.join(projectDir, "chapter-01.md"));
9024
9102
  const starterThemeId = STARTER_THEME_FOR_TEMPLATE[template] ?? "clean-book";
9025
9103
  const starterCss = await getAssetPath(`themes/${starterThemeId}/theme.css`);
9026
- await mkdir6(path13.join(projectDir, "styles"), { recursive: true });
9027
- await copyFile(starterCss, path13.join(projectDir, "styles", "book.css"));
9104
+ await mkdir6(path14.join(projectDir, "styles"), { recursive: true });
9105
+ await copyFile(starterCss, path14.join(projectDir, "styles", "book.css"));
9028
9106
  }
9029
9107
  } catch (e) {
9030
9108
  throw new CreateProjectErrorImpl("scaffold-io", `Could not create the project files: ${e instanceof Error ? e.message : String(e)}`);
@@ -9034,13 +9112,13 @@ async function scaffoldProject(options) {
9034
9112
  } catch (e) {
9035
9113
  throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
9036
9114
  }
9037
- const manifestPath = MANIFEST_FILENAMES.map((name2) => path13.join(projectDir, name2)).find(existsSync10) ?? path13.join(projectDir, MANIFEST_FILENAMES[0]);
9115
+ const manifestPath = MANIFEST_FILENAMES.map((name2) => path14.join(projectDir, name2)).find(existsSync10) ?? path14.join(projectDir, MANIFEST_FILENAMES[0]);
9038
9116
  const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
9039
9117
  const substitutions = {
9040
9118
  "{{TITLE}}": escapeYamlScalar(name),
9041
9119
  "{{AUTHOR}}": escapeYamlScalar(author)
9042
9120
  };
9043
- let openFile = path13.join(projectDir, "chapter-01.md");
9121
+ let openFile = path14.join(projectDir, "chapter-01.md");
9044
9122
  try {
9045
9123
  await fillTemplateFile(manifestPath, substitutions);
9046
9124
  if (preset) {
@@ -9061,7 +9139,7 @@ async function scaffoldProject(options) {
9061
9139
  }
9062
9140
  const firstSource = await firstSourceFile(manifestPath);
9063
9141
  if (firstSource)
9064
- openFile = path13.join(projectDir, firstSource);
9142
+ openFile = path14.join(projectDir, firstSource);
9065
9143
  await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
9066
9144
  } catch (e) {
9067
9145
  throw new CreateProjectErrorImpl("scaffold-io", `Could not finalise the project files: ${e instanceof Error ? e.message : String(e)}`);
@@ -9071,7 +9149,7 @@ async function scaffoldProject(options) {
9071
9149
  let versionHistoryError;
9072
9150
  if (requested === "local-git") {
9073
9151
  try {
9074
- const { providerFor } = await import("./source-provider-3tcj6qg2.js");
9152
+ const { providerFor } = await import("./source-provider-2j5x2hyw.js");
9075
9153
  const provider = providerFor({ type: "local-folder", path: projectDir });
9076
9154
  await provider.initVersionHistory({
9077
9155
  projectDir,
@@ -9083,7 +9161,7 @@ async function scaffoldProject(options) {
9083
9161
  versionHistory = "none";
9084
9162
  const { findEnclosingRepoDir } = await import("./project-source-ekcyp63q.js");
9085
9163
  const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
9086
- versionHistoryError = enclosingRepoDir ? `This book was added to ${path13.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
9164
+ 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);
9087
9165
  }
9088
9166
  }
9089
9167
  const result = {
@@ -9098,10 +9176,10 @@ async function scaffoldProject(options) {
9098
9176
  return result;
9099
9177
  }
9100
9178
  async function ensureGitignoreHasDist(projectDir) {
9101
- const gitignorePath = path13.join(projectDir, ".gitignore");
9179
+ const gitignorePath = path14.join(projectDir, ".gitignore");
9102
9180
  let existing = null;
9103
9181
  try {
9104
- existing = await readFile22(gitignorePath, "utf8");
9182
+ existing = await readFile23(gitignorePath, "utf8");
9105
9183
  } catch {}
9106
9184
  if (existing === null) {
9107
9185
  await writeFile5(gitignorePath, `dist/
@@ -9121,7 +9199,7 @@ async function ensureGitignoreHasDist(projectDir) {
9121
9199
  async function firstSourceFile(manifestPath) {
9122
9200
  let text;
9123
9201
  try {
9124
- text = await readFile22(manifestPath, "utf8");
9202
+ text = await readFile23(manifestPath, "utf8");
9125
9203
  } catch {
9126
9204
  return;
9127
9205
  }
@@ -9133,7 +9211,7 @@ async function firstSourceFile(manifestPath) {
9133
9211
  return m ? m[1].trim() : undefined;
9134
9212
  }
9135
9213
  async function fillTemplateFile(filePath, substitutions) {
9136
- let text = await readFile22(filePath, "utf8");
9214
+ let text = await readFile23(filePath, "utf8");
9137
9215
  for (const [token, value] of Object.entries(substitutions)) {
9138
9216
  text = text.split(token).join(value);
9139
9217
  }
@@ -9158,7 +9236,7 @@ var SKIP_ENTRIES = new Set([
9158
9236
  ]);
9159
9237
 
9160
9238
  // src/lib/plugin-manager.ts
9161
- import path15 from "node:path";
9239
+ import path16 from "node:path";
9162
9240
  import { isSeq as isSeq3, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar } from "yaml";
9163
9241
 
9164
9242
  // src/lib/npm-plugin-installer.ts
@@ -9169,12 +9247,12 @@ import {
9169
9247
  mkdir as mkdir7,
9170
9248
  mkdtemp as mkdtemp2,
9171
9249
  open as open2,
9172
- readFile as readFile23,
9250
+ readFile as readFile24,
9173
9251
  realpath as realpath2,
9174
9252
  rename as rename2,
9175
9253
  rm as rm3
9176
9254
  } from "node:fs/promises";
9177
- import path14 from "node:path";
9255
+ import path15 from "node:path";
9178
9256
  import { Transform } from "node:stream";
9179
9257
  import { pipeline } from "node:stream/promises";
9180
9258
  import { createGunzip } from "node:zlib";
@@ -9254,8 +9332,8 @@ function mb(bytes) {
9254
9332
  return `${Math.ceil(bytes / 1024 / 1024)}MB`;
9255
9333
  }
9256
9334
  function isContained2(root, candidate) {
9257
- const relative2 = path14.relative(root, candidate);
9258
- return relative2 === "" || !relative2.startsWith("..") && !path14.isAbsolute(relative2);
9335
+ const relative2 = path15.relative(root, candidate);
9336
+ return relative2 === "" || !relative2.startsWith("..") && !path15.isAbsolute(relative2);
9259
9337
  }
9260
9338
  function mergeLimits(overrides) {
9261
9339
  const limits = { ...DEFAULT_LIMITS, ...overrides };
@@ -9562,13 +9640,13 @@ async function extractTarball(ctx, archivePath, packageDir, packageName) {
9562
9640
  throw rejected;
9563
9641
  }
9564
9642
  async function readInstalledManifest(packageDir, selected) {
9565
- const packageJsonPath = path14.join(packageDir, "package.json");
9643
+ const packageJsonPath = path15.join(packageDir, "package.json");
9566
9644
  let manifest = null;
9567
9645
  try {
9568
9646
  const info2 = await lstat2(packageJsonPath);
9569
9647
  if (!info2.isFile() || info2.isSymbolicLink())
9570
9648
  throw new Error("not a regular file");
9571
- manifest = object2(JSON.parse(await readFile23(packageJsonPath, "utf8")));
9649
+ manifest = object2(JSON.parse(await readFile24(packageJsonPath, "utf8")));
9572
9650
  } catch (error2) {
9573
9651
  throw new SecurityInstallError(`Downloaded ${selected.name}@${selected.version} has no valid package.json (${error2 instanceof Error ? error2.message : String(error2)}).`);
9574
9652
  }
@@ -9638,10 +9716,10 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
9638
9716
  throw new SecurityInstallError(`Dependency graph exceeds ${ctx.limits.totalPackages} packages.`);
9639
9717
  }
9640
9718
  const relativePath = relativePackagePath(parentPackagePath, name);
9641
- const packageDir = path14.join(ctx.stageRoot, ...relativePath.split("/"));
9642
- const archivePath = path14.join(ctx.downloadsDir, `${ctx.archiveCounter++}.tgz`);
9719
+ const packageDir = path15.join(ctx.stageRoot, ...relativePath.split("/"));
9720
+ const archivePath = path15.join(ctx.downloadsDir, `${ctx.archiveCounter++}.tgz`);
9643
9721
  try {
9644
- await mkdir7(path14.dirname(packageDir), { recursive: true });
9722
+ await mkdir7(path15.dirname(packageDir), { recursive: true });
9645
9723
  await downloadTarball(ctx, selected, archivePath);
9646
9724
  await extractTarball(ctx, archivePath, packageDir, selected.name);
9647
9725
  const manifest = await readInstalledManifest(packageDir, selected);
@@ -9740,9 +9818,9 @@ async function ensureDirectory(pathname) {
9740
9818
  }
9741
9819
  }
9742
9820
  async function prepareVendorParent(projectDir, name) {
9743
- const plugins = path14.join(projectDir, PLUGINS_DIR);
9744
- const npm = path14.join(plugins, VENDORED_NPM_DIR);
9745
- const packageParent = path14.dirname(vendoredNpmPluginRoot(projectDir, name, "0.0.0"));
9821
+ const plugins = path15.join(projectDir, PLUGINS_DIR);
9822
+ const npm = path15.join(plugins, VENDORED_NPM_DIR);
9823
+ const packageParent = path15.dirname(vendoredNpmPluginRoot(projectDir, name, "0.0.0"));
9746
9824
  await mkdir7(projectDir, { recursive: true });
9747
9825
  await ensureDirectory(plugins);
9748
9826
  await ensureDirectory(npm);
@@ -9770,14 +9848,14 @@ function publicReceipt(record) {
9770
9848
  };
9771
9849
  }
9772
9850
  async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
9773
- const requestedProjectDir = path14.resolve(projectDirInput);
9851
+ const requestedProjectDir = path15.resolve(projectDirInput);
9774
9852
  await mkdir7(requestedProjectDir, { recursive: true });
9775
9853
  const projectDir = await realpath2(requestedProjectDir);
9776
9854
  const requested = parseNpmPluginSpec(packageSpec);
9777
9855
  const npmRoot = await prepareVendorParent(projectDir, requested.name);
9778
- const container = await mkdtemp2(path14.join(npmRoot, ".install-"));
9779
- const stageRoot = path14.join(container, "root");
9780
- const downloadsDir = path14.join(container, "downloads");
9856
+ const container = await mkdtemp2(path15.join(npmRoot, ".install-"));
9857
+ const stageRoot = path15.join(container, "root");
9858
+ const downloadsDir = path15.join(container, "downloads");
9781
9859
  await Promise.all([mkdir7(stageRoot), mkdir7(downloadsDir)]);
9782
9860
  const ctx = {
9783
9861
  fetch: options.fetch ?? globalThis.fetch,
@@ -9823,7 +9901,7 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
9823
9901
  skipped: ctx.skipped,
9824
9902
  tree: { algorithm: "sha256", ...tree }
9825
9903
  };
9826
- const receiptFile = await open2(path14.join(stageRoot, VENDOR_RECEIPT_FILE), "wx");
9904
+ const receiptFile = await open2(path15.join(stageRoot, VENDOR_RECEIPT_FILE), "wx");
9827
9905
  try {
9828
9906
  await receiptFile.writeFile(`${JSON.stringify(receipt, null, 2)}
9829
9907
  `, "utf8");
@@ -9832,7 +9910,7 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
9832
9910
  await receiptFile.close();
9833
9911
  }
9834
9912
  finalRoot = vendoredNpmPluginRoot(projectDir, rootRecord.name, rootRecord.version);
9835
- const realFinalParent = await realpath2(path14.dirname(finalRoot));
9913
+ const realFinalParent = await realpath2(path15.dirname(finalRoot));
9836
9914
  if (!isContained2(projectDir, realFinalParent)) {
9837
9915
  throw new Error("Plugin install destination resolves outside the project.");
9838
9916
  }
@@ -9881,7 +9959,7 @@ async function rollbackNpmPluginInstall(installed) {
9881
9959
  // src/lib/plugin-manager.ts
9882
9960
  var pluginMutationQueues = new Map;
9883
9961
  function withPluginMutationLock(projectDir, mutation) {
9884
- const resolved = path15.resolve(projectDir);
9962
+ const resolved = path16.resolve(projectDir);
9885
9963
  const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
9886
9964
  const previous = pluginMutationQueues.get(key) ?? Promise.resolve();
9887
9965
  const run = previous.then(mutation, mutation);
@@ -10074,221 +10152,6 @@ async function addNpmPluginUnlocked(projectDir, packageSpec, options) {
10074
10152
  };
10075
10153
  }
10076
10154
 
10077
- // src/lib/app-heartbeat.ts
10078
- import { readFile as readFile24, unlink as unlink2, writeFile as writeFile6 } from "node:fs/promises";
10079
- import path16 from "node:path";
10080
- var HEARTBEAT_FILENAME = "gutterpress-app-heartbeat";
10081
- var APP_HEARTBEAT_FRESH_MS = 2 * 60000;
10082
- function appHeartbeatPath(repoDir) {
10083
- return path16.join(gitDirFor(repoDir), HEARTBEAT_FILENAME);
10084
- }
10085
- async function readAppHeartbeat(repoDir) {
10086
- try {
10087
- const raw = await readFile24(appHeartbeatPath(repoDir), "utf8");
10088
- const parsed = JSON.parse(raw);
10089
- if (parsed && typeof parsed === "object" && typeof parsed.pid === "number" && typeof parsed.timestamp === "number") {
10090
- const candidate = parsed;
10091
- const ttlMs = typeof candidate.ttlMs === "number" ? candidate.ttlMs : undefined;
10092
- return { pid: candidate.pid, timestamp: candidate.timestamp, ...ttlMs !== undefined ? { ttlMs } : {} };
10093
- }
10094
- return null;
10095
- } catch {
10096
- return null;
10097
- }
10098
- }
10099
- async function isAppHeartbeatFresh(repoDir, now = Date.now(), maxAgeMs = APP_HEARTBEAT_FRESH_MS) {
10100
- const heartbeat = await readAppHeartbeat(repoDir);
10101
- if (!heartbeat)
10102
- return false;
10103
- return now - heartbeat.timestamp < (heartbeat.ttlMs ?? maxAgeMs);
10104
- }
10105
-
10106
- // src/lib/remote-auth/recovery/classify.ts
10107
- var STALE_LOCK_MIN_AGE_MS = 2 * 60 * 1000;
10108
- class InsecureTransportError extends Error {
10109
- code = "InsecureTransport";
10110
- constructor() {
10111
- super("This online address isn't secure, so the saved connection wasn't sent — " + "connections are never sent over an insecure address. Switch the address " + "to a secure one (starting with https) to sync with a saved connection.");
10112
- this.name = "InsecureTransportError";
10113
- }
10114
- }
10115
- function isInsecureTransportError(e) {
10116
- return e?.code === "InsecureTransport";
10117
- }
10118
- function isMergeConflictError(e) {
10119
- return e?.code === "MergeConflictError";
10120
- }
10121
- function classifyFromHealth(health, opts = {}) {
10122
- const minLockAgeMs = opts.minLockAgeMs ?? STALE_LOCK_MIN_AGE_MS;
10123
- if (!health.hasGitDir)
10124
- return "needs_repair";
10125
- if (health.hasInterruptedRebase)
10126
- return "needs_repair";
10127
- if (health.hasInterruptedCherryPick)
10128
- return "needs_repair";
10129
- if (health.hasInterruptedMerge)
10130
- return "needs_repair";
10131
- if (health.headUnreadable)
10132
- return "needs_repair";
10133
- if (health.isDetachedHead)
10134
- return "needs_repair";
10135
- if (health.hasStaleLock && (health.lockAgeMs ?? 0) >= minLockAgeMs) {
10136
- return "stale_lock";
10137
- }
10138
- return null;
10139
- }
10140
-
10141
- // src/lib/remote-auth/recovery/inspect.ts
10142
- import * as fs4 from "node:fs";
10143
- import path18 from "node:path";
10144
- import git2 from "isomorphic-git";
10145
-
10146
- // src/lib/remote-auth/recovery/locks.ts
10147
- import { readdir as readdir6, rm as rm4, stat as stat6 } from "node:fs/promises";
10148
- import path17 from "node:path";
10149
- var TOP_LEVEL_LOCK_NAMES = [
10150
- "index.lock",
10151
- "HEAD.lock",
10152
- "config.lock",
10153
- "packed-refs.lock"
10154
- ];
10155
- async function collectRefLockPaths(dir, out) {
10156
- let entries;
10157
- try {
10158
- entries = await readdir6(dir, { withFileTypes: true });
10159
- } catch {
10160
- return;
10161
- }
10162
- for (const entry of entries) {
10163
- const abs = path17.join(dir, entry.name);
10164
- if (entry.isDirectory()) {
10165
- await collectRefLockPaths(abs, out);
10166
- } else if (entry.isFile() && entry.name.endsWith(".lock")) {
10167
- out.push(abs);
10168
- }
10169
- }
10170
- }
10171
- async function findLockCandidates(gitDir, now) {
10172
- const paths = TOP_LEVEL_LOCK_NAMES.map((name) => path17.join(gitDir, name));
10173
- await collectRefLockPaths(path17.join(gitDir, "refs"), paths);
10174
- const candidates = [];
10175
- for (const p of paths) {
10176
- try {
10177
- const s = await stat6(p);
10178
- if (s.isFile())
10179
- candidates.push({ path: p, ageMs: now - s.mtimeMs });
10180
- } catch {}
10181
- }
10182
- return candidates;
10183
- }
10184
- async function sweepStaleLocks(gitDir, now = Date.now(), minAgeMs = STALE_LOCK_MIN_AGE_MS) {
10185
- const candidates = await findLockCandidates(gitDir, now);
10186
- if (candidates.length === 0)
10187
- return "clean";
10188
- if (candidates.some((c) => c.ageMs < minAgeMs))
10189
- return "fresh";
10190
- for (const c of candidates) {
10191
- await rm4(c.path, { force: true });
10192
- }
10193
- return "swept";
10194
- }
10195
-
10196
- // src/lib/remote-auth/recovery/inspect.ts
10197
- async function inspectRepo(ctx, opts = {}) {
10198
- let repoDir = ctx.repoDir;
10199
- try {
10200
- const source = ctx.source ?? await detectProjectSource(ctx.repoDir);
10201
- if (source.type === "local-git-folder")
10202
- repoDir = gitScopeFor(source);
10203
- } catch {}
10204
- const gitDir = gitDirFor(repoDir);
10205
- const hasGitDir = fs4.existsSync(gitDir);
10206
- if (!hasGitDir) {
10207
- return {
10208
- hasGitDir: false,
10209
- isDetachedHead: false,
10210
- headUnreadable: false,
10211
- hasStaleLock: false,
10212
- hasInterruptedMerge: false,
10213
- hasInterruptedRebase: false,
10214
- hasInterruptedCherryPick: false,
10215
- hasLocalChanges: false
10216
- };
10217
- }
10218
- let currentBranch;
10219
- let isDetachedHead = false;
10220
- let headUnreadable = false;
10221
- try {
10222
- const branch = await git2.currentBranch({ fs: fs4, dir: repoDir });
10223
- if (branch == null) {
10224
- isDetachedHead = true;
10225
- } else {
10226
- currentBranch = branch;
10227
- }
10228
- } catch {
10229
- headUnreadable = true;
10230
- }
10231
- const lockCandidates = await findLockCandidates(gitDir, Date.now());
10232
- const hasStaleLock = lockCandidates.length > 0;
10233
- const lockAgeMs = hasStaleLock ? Math.min(...lockCandidates.map((c) => c.ageMs)) : undefined;
10234
- const hasInterruptedMerge = fs4.existsSync(path18.join(gitDir, "MERGE_HEAD"));
10235
- const hasInterruptedRebase = fs4.existsSync(path18.join(gitDir, "rebase-merge")) || fs4.existsSync(path18.join(gitDir, "rebase-apply"));
10236
- const hasInterruptedCherryPick = fs4.existsSync(path18.join(gitDir, "CHERRY_PICK_HEAD"));
10237
- let hasLocalChanges = false;
10238
- if (opts.checkLocalChanges !== false) {
10239
- try {
10240
- hasLocalChanges = await hasPendingChanges(repoDir);
10241
- } catch {
10242
- hasLocalChanges = true;
10243
- }
10244
- }
10245
- return {
10246
- hasGitDir,
10247
- currentBranch,
10248
- isDetachedHead,
10249
- headUnreadable,
10250
- hasStaleLock,
10251
- lockAgeMs,
10252
- hasInterruptedMerge,
10253
- hasInterruptedRebase,
10254
- hasInterruptedCherryPick,
10255
- hasLocalChanges
10256
- };
10257
- }
10258
- async function verifyRepoReadable(dir) {
10259
- const headOid = await git2.resolveRef({ fs: fs4, dir, ref: "HEAD" });
10260
- const { commit } = await git2.readCommit({ fs: fs4, dir, oid: headOid });
10261
- await git2.readTree({ fs: fs4, dir, oid: commit.tree });
10262
- }
10263
- function isUnbornRepo(repoDir) {
10264
- const objectsDir = path18.join(gitDirFor(repoDir), "objects");
10265
- let entries;
10266
- try {
10267
- entries = fs4.readdirSync(objectsDir);
10268
- } catch {
10269
- return false;
10270
- }
10271
- for (const entry of entries) {
10272
- if (/^[0-9a-f]{2}$/.test(entry)) {
10273
- try {
10274
- if (fs4.readdirSync(path18.join(objectsDir, entry)).length > 0)
10275
- return false;
10276
- } catch {
10277
- return false;
10278
- }
10279
- } else if (entry === "pack") {
10280
- try {
10281
- if (fs4.readdirSync(path18.join(objectsDir, entry)).some((f) => f.endsWith(".pack"))) {
10282
- return false;
10283
- }
10284
- } catch {
10285
- return false;
10286
- }
10287
- }
10288
- }
10289
- return true;
10290
- }
10291
-
10292
10155
  // src/lib/publish/types.ts
10293
10156
  function publishCredentialKey(host, account) {
10294
10157
  const a = (account ?? "").trim();
@@ -10474,25 +10337,25 @@ ${tail}` : ""}`);
10474
10337
  };
10475
10338
 
10476
10339
  // src/lib/publish/providers/guided.ts
10477
- import { copyFile as copyFile2, mkdir as mkdir8, writeFile as writeFile7 } from "node:fs/promises";
10478
- import path19 from "node:path";
10340
+ import { copyFile as copyFile2, mkdir as mkdir8, writeFile as writeFile6 } from "node:fs/promises";
10341
+ import path17 from "node:path";
10479
10342
  async function stageGuidedPackage(req, providerId, listingLines) {
10480
- const packageDir = path19.join(path19.dirname(req.artifact.path), "publish", providerId);
10343
+ const packageDir = path17.join(path17.dirname(req.artifact.path), "publish", providerId);
10481
10344
  await mkdir8(packageDir, { recursive: true });
10482
- await copyFile2(req.artifact.path, path19.join(packageDir, path19.basename(req.artifact.path)));
10345
+ await copyFile2(req.artifact.path, path17.join(packageDir, path17.basename(req.artifact.path)));
10483
10346
  const { title, authors } = req.project;
10484
10347
  const sheet = [
10485
10348
  `# ${title || "Untitled"}`,
10486
10349
  "",
10487
10350
  `- **Authors:** ${authors.length ? authors.join(", ") : "(not set)"}`,
10488
- `- **File:** ${path19.basename(req.artifact.path)}`,
10351
+ `- **File:** ${path17.basename(req.artifact.path)}`,
10489
10352
  ...listingLines,
10490
10353
  "",
10491
10354
  "Copy these details into the platform's listing form when uploading.",
10492
10355
  ""
10493
10356
  ].join(`
10494
10357
  `);
10495
- await writeFile7(path19.join(packageDir, "LISTING.md"), sheet, "utf8");
10358
+ await writeFile6(path17.join(packageDir, "LISTING.md"), sheet, "utf8");
10496
10359
  return packageDir;
10497
10360
  }
10498
10361
 
@@ -10555,8 +10418,8 @@ var drivethrurpgProvider = {
10555
10418
  };
10556
10419
 
10557
10420
  // src/lib/publish/butler.ts
10558
- import { chmod, mkdir as mkdir9, stat as stat7, writeFile as writeFile8 } from "node:fs/promises";
10559
- import path20 from "node:path";
10421
+ import { chmod, mkdir as mkdir9, stat as stat6, writeFile as writeFile7 } from "node:fs/promises";
10422
+ import path18 from "node:path";
10560
10423
  import { unzipSync } from "fflate";
10561
10424
  var BUTLER_DOWNLOAD_TIMEOUT_MS = 300000;
10562
10425
  function butlerBrothChannel(platform = process.platform, arch = process.arch) {
@@ -10576,7 +10439,7 @@ function butlerBinaryName(platform = process.platform) {
10576
10439
  }
10577
10440
  async function fileExists2(p) {
10578
10441
  try {
10579
- return (await stat7(p)).isFile();
10442
+ return (await stat6(p)).isFile();
10580
10443
  } catch {
10581
10444
  return false;
10582
10445
  }
@@ -10593,8 +10456,8 @@ async function ensureButler(deps) {
10593
10456
  }
10594
10457
  if (await commandExists("butler", runCommand, deps.env))
10595
10458
  return "butler";
10596
- const cacheDir = path20.join(deps.configDir ?? defaultConfigDir(), "tools", "butler");
10597
- const cached = path20.join(cacheDir, butlerBinaryName());
10459
+ const cacheDir = path18.join(deps.configDir ?? defaultConfigDir(), "tools", "butler");
10460
+ const cached = path18.join(cacheDir, butlerBinaryName());
10598
10461
  if (await fileExists2(cached))
10599
10462
  return cached;
10600
10463
  const channel = butlerBrothChannel();
@@ -10621,9 +10484,9 @@ async function ensureButler(deps) {
10621
10484
  for (const [name, data] of Object.entries(files)) {
10622
10485
  if (name.endsWith("/"))
10623
10486
  continue;
10624
- const target = path20.join(cacheDir, path20.basename(name));
10625
- await writeFile8(target, data);
10626
- if (path20.basename(name) === butlerBinaryName()) {
10487
+ const target = path18.join(cacheDir, path18.basename(name));
10488
+ await writeFile7(target, data);
10489
+ if (path18.basename(name) === butlerBinaryName()) {
10627
10490
  await chmod(target, 493).catch(() => {});
10628
10491
  foundBinary = true;
10629
10492
  }
@@ -11051,8 +10914,8 @@ function publishProviderFor(id) {
11051
10914
  }
11052
10915
 
11053
10916
  // src/lib/publish/run-publish.ts
11054
- import { readdir as readdir7, stat as stat8 } from "node:fs/promises";
11055
- import path21 from "node:path";
10917
+ import { readdir as readdir6, stat as stat7 } from "node:fs/promises";
10918
+ import path19 from "node:path";
11056
10919
  async function resolvePublishRequest(options, deps) {
11057
10920
  const provider = publishProviderFor(options.providerId);
11058
10921
  const { manifest, manifestDir } = await loadManifestWithPath(options.manifestPath ?? options.projectDir, { explicit: options.manifestPath !== undefined });
@@ -11063,9 +10926,9 @@ async function resolvePublishRequest(options, deps) {
11063
10926
  const effectiveAccount = bookAccount || deps.credentialAccount;
11064
10927
  const effectiveDeps = effectiveAccount ? { ...deps, credentialAccount: effectiveAccount } : deps;
11065
10928
  const outDir = resolveOutputDir(manifestDir, config.title);
11066
- const defaultArtifact = provider.info.format === "pdf" ? path21.join(outDir, artifactName(config.title, "pdf")) : outDir;
10929
+ const defaultArtifact = provider.info.format === "pdf" ? path19.join(outDir, artifactName(config.title, "pdf")) : outDir;
11067
10930
  const artifact = {
11068
- path: options.artifactPath ? path21.resolve(options.projectDir, options.artifactPath) : defaultArtifact,
10931
+ path: options.artifactPath ? path19.resolve(options.projectDir, options.artifactPath) : defaultArtifact,
11069
10932
  format: provider.info.format
11070
10933
  };
11071
10934
  return {
@@ -11083,7 +10946,7 @@ var PDF_HINT = "Build the PDF first (gutterpress build, or export it from the ap
11083
10946
  var HTML_HINT = "Build the website export first (gutterpress build --format html).";
11084
10947
  async function artifactIssues(artifact) {
11085
10948
  try {
11086
- const s = await stat8(artifact.path);
10949
+ const s = await stat7(artifact.path);
11087
10950
  if (artifact.format === "pdf" && !s.isFile()) {
11088
10951
  return [
11089
10952
  {
@@ -11129,7 +10992,7 @@ async function artifactIssues(artifact) {
11129
10992
  var BUILD_FINGERPRINT_FILENAME = "build-fingerprint.json";
11130
10993
  async function htmlDirIssues(dir) {
11131
10994
  const issues = [];
11132
- const isThere = async (rel) => stat8(path21.join(dir, rel)).then(() => true, () => false);
10995
+ const isThere = async (rel) => stat7(path19.join(dir, rel)).then(() => true, () => false);
11133
10996
  if (!await isThere(BOOK_HTML)) {
11134
10997
  issues.push({
11135
10998
  severity: "error",
@@ -11137,7 +11000,7 @@ async function htmlDirIssues(dir) {
11137
11000
  message: `${dir} has no ${BOOK_HTML} — it isn't an HTML export. ${HTML_HINT}`
11138
11001
  });
11139
11002
  }
11140
- const entries = await readdir7(dir).catch(() => []);
11003
+ const entries = await readdir6(dir).catch(() => []);
11141
11004
  const extras = entries.filter((name) => name.toLowerCase().endsWith(".pdf") || name === BUILD_FINGERPRINT_FILENAME);
11142
11005
  if (extras.length > 0) {
11143
11006
  issues.push({
@@ -11236,285 +11099,39 @@ async function connectPublishProvider(options, deps) {
11236
11099
  await deps.tokenStore.set(key, candidate);
11237
11100
  return { connected: true, providerId: info7.id };
11238
11101
  }
11239
-
11240
- // src/lib/remote-auth/recovery/repair.ts
11241
- import * as fs7 from "node:fs";
11242
- import { cp as cp3, mkdtemp as mkdtemp3, rename as rename3, rm as rm6 } from "node:fs/promises";
11243
- import os3 from "node:os";
11244
- import path24 from "node:path";
11245
- import git6 from "isomorphic-git";
11246
-
11247
- // src/lib/remote-auth/converge-merge.ts
11248
- import * as fs5 from "node:fs";
11249
- import { mkdir as mkdir10, unlink as unlink3, writeFile as writeFile9 } from "node:fs/promises";
11250
- import path22 from "node:path";
11251
- import git3 from "isomorphic-git";
11252
- import diff3Merge from "diff3";
11253
- var CONVERGE_PREPARE_MESSAGE = "Getting your changes ready to combine with the online version";
11254
- var CONVERGE_RESTORE_MESSAGE = "Kept the newest version of files that can't be combined";
11255
- var CONVERGE_MERGE_MESSAGE = "Combined your changes with the online version";
11256
- var IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)$/i;
11257
- var OUR_LABEL = "your version";
11258
- var THEIR_LABEL = "online version";
11259
- function stringLooksBinary(s) {
11260
- return s.includes("\x00");
11261
- }
11262
- var LINEBREAKS = /^.*(\r?\n|$)/gm;
11263
- function mergeWithMarkers(baseContent, ourContent, theirContent) {
11264
- const result = diff3Merge(ourContent.match(LINEBREAKS) ?? [], baseContent.match(LINEBREAKS) ?? [], theirContent.match(LINEBREAKS) ?? []);
11265
- let merged = "";
11266
- for (const item of result) {
11267
- if (item.ok)
11268
- merged += item.ok.join("");
11269
- if (item.conflict) {
11270
- merged += `<<<<<<< ${OUR_LABEL}
11271
- `;
11272
- merged += item.conflict.a.join("");
11273
- merged += `=======
11274
- `;
11275
- merged += item.conflict.b.join("");
11276
- merged += `>>>>>>> ${THEIR_LABEL}
11277
- `;
11278
- }
11279
- }
11280
- return merged;
11281
- }
11282
- async function readBlobOrNull(dir, cache2, oid, filepath) {
11283
- try {
11284
- const r = await git3.readBlob({ fs: fs5, dir, cache: cache2, oid, filepath });
11285
- return { oid: r.oid, blob: r.blob };
11286
- } catch {
11287
- return null;
11288
- }
11289
- }
11290
- async function writeTreeFile(dir, filepath, content) {
11291
- const abs = path22.join(dir, filepath);
11292
- await mkdir10(path22.dirname(abs), { recursive: true });
11293
- await writeFile9(abs, content);
11294
- }
11295
- async function bothAddedPaths(dir, cache2, ourTip, theirTip, allowUnrelated) {
11296
- const bases = await git3.findMergeBase({ fs: fs5, dir, cache: cache2, oids: [ourTip, theirTip] });
11297
- const base = bases[0];
11298
- if (!base && !allowUnrelated)
11299
- return null;
11300
- const out = [];
11301
- await git3.walk({
11302
- fs: fs5,
11303
- dir,
11304
- cache: cache2,
11305
- trees: [
11306
- git3.TREE({ ref: ourTip }),
11307
- git3.TREE({ ref: theirTip }),
11308
- ...base ? [git3.TREE({ ref: base })] : []
11309
- ],
11310
- map: async (filepath, entries) => {
11311
- if (filepath === ".")
11312
- return true;
11313
- const [ours, theirs, inBase] = entries;
11314
- const types = await Promise.all([ours, theirs, inBase].map((e) => e ? e.type() : Promise.resolve(undefined)));
11315
- if (types.some((t) => t === "tree"))
11316
- return true;
11317
- if (!ours || !theirs || inBase)
11318
- return null;
11319
- const [a, b] = await Promise.all([ours.oid(), theirs.oid()]);
11320
- if (a !== b)
11321
- out.push(filepath);
11322
- return null;
11323
- }
11324
- });
11325
- return out;
11326
- }
11327
- async function convergeMerge(params) {
11328
- const { dir, cache: cache2, branch, theirs, author } = params;
11329
- const ourTip = await git3.resolveRef({ fs: fs5, dir, ref: branch });
11330
- const [ourCommit, theirCommit] = await Promise.all([
11331
- git3.readCommit({ fs: fs5, dir, cache: cache2, oid: ourTip }),
11332
- git3.readCommit({ fs: fs5, dir, cache: cache2, oid: theirs })
11333
- ]);
11334
- const localNewer = ourCommit.commit.committer.timestamp >= theirCommit.commit.committer.timestamp;
11335
- const combined = new Set;
11336
- const driverBinary = new Set;
11337
- const imageClashes = [];
11338
- const attempt = () => git3.merge({
11339
- fs: fs5,
11340
- dir,
11341
- cache: cache2,
11342
- ours: branch,
11343
- theirs,
11344
- author,
11345
- message: CONVERGE_MERGE_MESSAGE,
11346
- allowUnrelatedHistories: params.allowUnrelatedHistories ?? false,
11347
- mergeDriver: ({ contents, path: filepath }) => {
11348
- const [base, ours, theirsContent] = contents;
11349
- if (IMAGE_EXT.test(filepath) || stringLooksBinary(ours) || stringLooksBinary(theirsContent) || stringLooksBinary(base)) {
11350
- driverBinary.add(filepath);
11351
- return { cleanMerge: false, mergedText: ours };
11352
- }
11353
- combined.add(filepath);
11354
- return {
11355
- cleanMerge: true,
11356
- mergedText: mergeWithMarkers(base, ours, theirsContent)
11357
- };
11358
- }
11359
- });
11360
- const snapshot = (message) => snapshotWorkingTreeUnlocked({
11361
- projectDir: dir,
11362
- repoRoot: dir,
11363
- message,
11364
- authorName: params.authorName,
11365
- authorEmail: params.authorEmail
11366
- });
11367
- const postRestore = [];
11368
- const equalize = async (paths) => {
11369
- for (const p of paths.binary) {
11370
- const [ourBlob, theirBlob] = await Promise.all([
11371
- readBlobOrNull(dir, cache2, ourTip, p),
11372
- readBlobOrNull(dir, cache2, theirs, p)
11373
- ]);
11374
- if (!ourBlob || !theirBlob)
11375
- continue;
11376
- await writeTreeFile(dir, p, theirBlob.blob);
11377
- if (localNewer)
11378
- postRestore.push({ path: p, bytes: ourBlob.blob });
11379
- if (IMAGE_EXT.test(p)) {
11380
- imageClashes.push({
11381
- path: p,
11382
- localOid: ourBlob.oid,
11383
- remoteOid: theirBlob.oid,
11384
- kept: localNewer ? "local" : "online"
11385
- });
11386
- }
11387
- }
11388
- for (const p of paths.deleteByUs) {
11389
- const theirBlob = await readBlobOrNull(dir, cache2, theirs, p);
11390
- if (theirBlob)
11391
- await writeTreeFile(dir, p, theirBlob.blob);
11392
- }
11393
- for (const p of paths.deleteByTheirs) {
11394
- const ourBlob = await readBlobOrNull(dir, cache2, ourTip, p);
11395
- await unlink3(path22.join(dir, p)).catch((e) => {
11396
- if (e?.code !== "ENOENT")
11397
- throw e;
11398
- });
11399
- if (ourBlob)
11400
- postRestore.push({ path: p, bytes: ourBlob.blob });
11401
- }
11402
- if (await hasPendingChanges(dir))
11403
- await snapshot(CONVERGE_PREPARE_MESSAGE);
11404
- };
11405
- try {
11406
- await attempt();
11407
- } catch (e) {
11408
- if (isMergeConflictError(e)) {
11409
- await equalize({
11410
- binary: (e.data.bothModified ?? []).filter((p) => driverBinary.has(p)),
11411
- deleteByUs: e.data.deleteByUs ?? [],
11412
- deleteByTheirs: e.data.deleteByTheirs ?? []
11413
- });
11414
- await attempt();
11415
- } else if (e?.code === "MergeNotSupportedError") {
11416
- const added = await bothAddedPaths(dir, cache2, ourTip, theirs, params.allowUnrelatedHistories ?? false);
11417
- if (added === null || added.length === 0)
11418
- throw e;
11419
- await equalize({ binary: added, deleteByUs: [], deleteByTheirs: [] });
11420
- await attempt();
11421
- } else {
11422
- throw e;
11423
- }
11424
- }
11425
- await git3.checkout({ fs: fs5, dir, cache: cache2, ref: branch, force: true });
11426
- if (postRestore.length > 0) {
11427
- for (const r of postRestore)
11428
- await writeTreeFile(dir, r.path, r.bytes);
11429
- if (await hasPendingChanges(dir))
11430
- await snapshot(CONVERGE_RESTORE_MESSAGE);
11431
- }
11432
- return {
11433
- oid: await git3.resolveRef({ fs: fs5, dir, ref: branch }),
11434
- combinedFiles: [...combined].sort(),
11435
- imageClashes
11436
- };
11437
- }
11438
-
11439
- // src/lib/remote-auth/transport.ts
11440
- import git4 from "isomorphic-git";
11441
-
11442
- // src/lib/remote-auth/sync-messages.ts
11443
- var MSG_NO_REMOTE = "This project isn't connected to an online repository yet.";
11444
- var MSG_SSH_REMOTE = "This project's online address uses SSH (git@…), which gutterpress can't sync to. Switch it to the web (HTTPS) address to sync from here.";
11445
-
11446
- // src/lib/remote-auth/transport.ts
11447
- async function repoDirFor(projectDir) {
11448
- const source = await detectProjectSource(projectDir);
11449
- if (source.type === "local-git-folder")
11450
- return gitScopeFor(source);
11451
- return projectDir;
11452
- }
11453
- function isCredentialTransmissionSafe(url) {
11454
- try {
11455
- const u = new URL(url);
11456
- if (u.protocol === "https:")
11457
- return true;
11458
- if (u.protocol === "http:") {
11459
- const host = u.hostname.toLowerCase();
11460
- return host === "127.0.0.1" || host === "[::1]" || host === "localhost";
11461
- }
11462
- return false;
11463
- } catch {
11464
- return false;
11465
- }
11466
- }
11467
- function onAuthFor(credential) {
11468
- if (!credential)
11469
- return {};
11470
- return {
11471
- onAuth: (url) => {
11472
- if (!isCredentialTransmissionSafe(url))
11473
- throw new InsecureTransportError;
11474
- return {
11475
- username: credential.kind === "github-oauth" ? "x-access-token" : credential.username || credential.token,
11476
- password: credential.token
11477
- };
11478
- }
11479
- };
11480
- }
11481
- async function resolveTransport(dir, options) {
11482
- const origin = await syncRemoteFor(dir);
11483
- if (!origin?.url)
11484
- throw new Error(MSG_NO_REMOTE);
11485
- const { cleanUrl, credential: urlCredential } = extractUrlCredential(origin.url);
11486
- let parsed;
11487
- try {
11488
- parsed = new URL(cleanUrl);
11489
- } catch {
11490
- throw new Error(MSG_SSH_REMOTE);
11491
- }
11492
- if (!/^https?:$/.test(parsed.protocol))
11493
- throw new Error(MSG_SSH_REMOTE);
11494
- const host = credentialHostKey(cleanUrl);
11495
- let credential = options.credential ?? undefined;
11496
- if (!credential && options.tokenStore) {
11497
- try {
11498
- credential = await options.tokenStore.get(host) ?? undefined;
11499
- } catch {
11500
- credential = undefined;
11501
- }
11502
- }
11503
- if (!credential && urlCredential)
11504
- credential = urlCredential;
11505
- return {
11506
- remote: origin.remote,
11507
- url: cleanUrl,
11508
- host,
11509
- ...credential ? { credential } : {}
11510
- };
11511
- }
11512
-
11102
+ // src/lib/theme-manager.ts
11103
+ import { isSeq as isSeq4, Scalar as Scalar2 } from "yaml";
11104
+ var THEMES_DIR = "themes";
11105
+ var THEME_HREF_RE = new RegExp(`^${THEMES_DIR}/([^/]+)/theme\\.css$`);
11106
+ // src/lib/theme-import.ts
11107
+ import { unzipSync as unzipSync2 } from "fflate";
11108
+ var MAX_THEME_ARCHIVE_BYTES = 25 * 1024 * 1024;
11109
+ var MAX_THEME_UNZIPPED_BYTES = 25 * 1024 * 1024;
11110
+ var KNOWN_THEME_FILES = new Set(["theme.css", "theme.json"]);
11111
+ var ALLOWED_ASSET_EXTS = new Set([
11112
+ ".woff",
11113
+ ".woff2",
11114
+ ".ttf",
11115
+ ".otf",
11116
+ ".eot",
11117
+ ".png",
11118
+ ".jpg",
11119
+ ".jpeg",
11120
+ ".gif",
11121
+ ".svg",
11122
+ ".webp",
11123
+ ".avif",
11124
+ ".css"
11125
+ ]);
11126
+ // src/lib/manifest-config.ts
11127
+ import { isSeq as isSeq5, isMap as isMap3, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
11128
+ // src/lib/host-policy.ts
11129
+ var AUTO_SNAPSHOT_MAX_MINUTES = 24 * 60;
11130
+ var AUTO_SYNC_MAX_MINUTES = 24 * 60;
11131
+ // src/lib/remote-auth/github-repos.ts
11132
+ var MANIFEST_NAMES = new Set(MANIFEST_FILENAMES);
11513
11133
  // src/lib/remote-auth/clone.ts
11514
- import * as fs6 from "node:fs";
11515
- import { mkdir as mkdir11, readdir as readdir8, readFile as readFile25, rm as rm5, writeFile as writeFile10 } from "node:fs/promises";
11516
- import path23 from "node:path";
11517
- import git5 from "isomorphic-git";
11134
+ import git3 from "isomorphic-git";
11518
11135
 
11519
11136
  // src/lib/remote-auth/git-http.ts
11520
11137
  import httpNode from "isomorphic-git/http/node";
@@ -11607,525 +11224,29 @@ function withIdleTimeout(http2, idleMs = GIT_HTTP_IDLE_TIMEOUT_MS, uploadMs = GI
11607
11224
  }
11608
11225
  var defaultGitHttp = withIdleTimeout(httpNode);
11609
11226
 
11610
- // src/lib/remote-auth/github-auth.ts
11611
- var OFFLINE_MESSAGE = "Couldn't reach GitHub. Check your connection and try again.";
11612
-
11613
- // src/lib/remote-auth/clone.ts
11614
- var PROVENANCE_FILE = "gutterpress-remote.json";
11615
- function provenancePath(projectDir) {
11616
- return path23.join(projectDir, ".git", PROVENANCE_FILE);
11617
- }
11618
- async function assertCloneTarget(dir) {
11619
- try {
11620
- const entries = await readdir8(dir);
11621
- if (entries.length > 0) {
11622
- throw new Error("That folder already has files in it. Choose an empty folder for the project.");
11623
- }
11624
- } catch (e) {
11625
- if (e.code === "ENOENT")
11626
- return;
11627
- throw e;
11628
- }
11629
- }
11630
- function friendlyCloneError(e) {
11631
- if (isInsecureTransportError(e)) {
11632
- return new Error("That repository address isn't secure, so the saved connection wasn't sent — connections are never sent over an insecure address. Use a secure address (starting with https), or a local loopback address for a server on this computer.", { cause: e });
11633
- }
11634
- const msg = e instanceof Error ? e.message : String(e);
11635
- if (/401|403|auth|credential/i.test(msg)) {
11636
- return new Error("GitHub didn't accept the connection for this repository. Reconnect GitHub and try again.", { cause: e });
11637
- }
11638
- if (/404|not found/i.test(msg)) {
11639
- return new Error("That repository couldn't be found. It may have been renamed or you may no longer have access.", { cause: e });
11640
- }
11641
- if (/ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|network|fetch failed/i.test(msg)) {
11642
- return new Error(OFFLINE_MESSAGE, { cause: e });
11643
- }
11644
- if (e instanceof Error && /already has files/i.test(e.message)) {
11645
- return e;
11646
- }
11647
- return new Error("The project couldn't be downloaded from GitHub. Please try again.", { cause: e });
11648
- }
11649
- async function cloneRepository(options) {
11650
- const { dir, onProgress, tokenStore, provenance } = options;
11651
- if (!path23.isAbsolute(dir)) {
11652
- throw new Error("cloneRepository requires an absolute destination path.");
11653
- }
11654
- let parsed;
11655
- try {
11656
- parsed = new URL(options.url);
11657
- } catch {
11658
- throw new Error("That repository address is not a valid web URL.");
11659
- }
11660
- if (!/^https?:$/.test(parsed.protocol)) {
11661
- throw new Error("Only HTTPS repository addresses are supported. SSH addresses (git@…) can't be used here.");
11662
- }
11663
- const { cleanUrl, credential: urlCredential } = extractUrlCredential(options.url);
11664
- const credential = options.credential ?? urlCredential;
11665
- if (urlCredential && tokenStore && !await tokenStore.get(urlCredential.host)) {
11666
- await tokenStore.set(urlCredential.host, urlCredential);
11667
- }
11668
- return withRepoLock(dir, async () => {
11669
- await assertCloneTarget(dir);
11670
- const dirExistedBefore = fs6.existsSync(dir);
11671
- await mkdir11(dir, { recursive: true });
11672
- try {
11673
- await git5.clone({
11674
- fs: fs6,
11675
- http: options.httpClient ?? defaultGitHttp,
11676
- dir,
11677
- url: cleanUrl,
11678
- singleBranch: true,
11679
- ...options.branch ? { ref: options.branch } : {},
11680
- ...options.depth ? { depth: options.depth } : {},
11681
- ...onAuthFor(credential),
11682
- ...onProgress ? {
11683
- onProgress: (p) => onProgress({
11684
- phase: p.phase,
11685
- loaded: p.loaded,
11686
- ...p.total ? { total: p.total } : {}
11687
- })
11688
- } : {}
11689
- });
11690
- } catch (e) {
11691
- const friendly = friendlyCloneError(e);
11692
- if (!dirExistedBefore) {
11693
- try {
11694
- await rm5(dir, { recursive: true, force: true });
11695
- } catch {
11696
- throw new Error(`${friendly.message} A partial download was left at ${dir} — delete that folder before trying again.`, { cause: e });
11697
- }
11698
- }
11699
- throw friendly;
11700
- }
11701
- if (provenance) {
11702
- try {
11703
- await writeFile10(provenancePath(dir), JSON.stringify(provenance, null, 2), "utf8");
11704
- } catch {}
11705
- }
11706
- let branch;
11707
- try {
11708
- branch = await git5.currentBranch({ fs: fs6, dir }) ?? undefined;
11709
- } catch {
11710
- branch = undefined;
11711
- }
11712
- return { projectDir: dir, ...branch ? { branch } : {} };
11713
- });
11714
- }
11715
-
11716
- // src/lib/remote-auth/recovery/repair.ts
11717
- var INTERRUPTED_STATE = [
11718
- "MERGE_HEAD",
11719
- "MERGE_MSG",
11720
- "MERGE_MODE",
11721
- "CHERRY_PICK_HEAD",
11722
- "REVERT_HEAD",
11723
- "sequencer",
11724
- "rebase-merge",
11725
- "rebase-apply"
11726
- ];
11727
- var MSG_REPAIRED = "Your project's version history is working again. Your files were not changed.";
11728
- var MSG_RETRY = "Another program may be using this project's history right now — try again in a moment.";
11729
- var MSG_FAILED = "Your project's history couldn't be repaired automatically, but your files are safe and untouched.";
11730
- function stamp() {
11731
- return new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
11732
- }
11733
- async function indexReadable(dir) {
11734
- try {
11735
- await git6.listFiles({ fs: fs7, dir });
11736
- return true;
11737
- } catch {
11738
- return false;
11739
- }
11740
- }
11741
- async function rebuildIndexFromHead(dir) {
11742
- await rm6(path24.join(gitDirFor(dir), "index"), { force: true });
11743
- const cache2 = {};
11744
- let files;
11745
- try {
11746
- files = await git6.listFiles({ fs: fs7, dir, ref: "HEAD" });
11747
- } catch {
11748
- return;
11749
- }
11750
- for (const filepath of files) {
11751
- await git6.resetIndex({ fs: fs7, dir, cache: cache2, filepath, ref: "HEAD" });
11752
- }
11753
- }
11754
- function readDamagedBranchTips(damagedGitDir) {
11755
- const tips = new Map;
11756
- const OID = /^[0-9a-f]{40}$/;
11757
- try {
11758
- const packed = fs7.readFileSync(path24.join(damagedGitDir, "packed-refs"), "utf8");
11759
- for (const line of packed.split(`
11760
- `)) {
11761
- const m = /^([0-9a-f]{40}) refs\/heads\/(.+)$/.exec(line.trim());
11762
- if (m)
11763
- tips.set(m[2], m[1]);
11764
- }
11765
- } catch {}
11766
- const headsDir = path24.join(damagedGitDir, "refs", "heads");
11767
- const walk = (dir, prefix) => {
11768
- let entries;
11769
- try {
11770
- entries = fs7.readdirSync(dir, { withFileTypes: true });
11771
- } catch {
11772
- return;
11773
- }
11774
- for (const e of entries) {
11775
- const name = prefix ? `${prefix}/${e.name}` : e.name;
11776
- if (e.isDirectory())
11777
- walk(path24.join(dir, e.name), name);
11778
- else if (e.isFile()) {
11779
- try {
11780
- const oid = fs7.readFileSync(path24.join(dir, e.name), "utf8").trim();
11781
- if (OID.test(oid))
11782
- tips.set(name, oid);
11783
- } catch {}
11784
- }
11785
- }
11786
- };
11787
- walk(headsDir, "");
11788
- return [...tips].map(([name, oid]) => ({ name, oid }));
11789
- }
11790
- async function looseCommitTips(dir, cache2) {
11791
- const objectsDir = path24.join(gitDirFor(dir), "objects");
11792
- const oids = [];
11793
- let fanouts;
11794
- try {
11795
- fanouts = fs7.readdirSync(objectsDir).filter((e) => /^[0-9a-f]{2}$/.test(e));
11796
- } catch {
11797
- return [];
11798
- }
11799
- for (const fan of fanouts) {
11800
- let files;
11801
- try {
11802
- files = fs7.readdirSync(path24.join(objectsDir, fan));
11803
- } catch {
11804
- continue;
11805
- }
11806
- for (const f of files) {
11807
- if (/^[0-9a-f]{38}$/.test(f))
11808
- oids.push(fan + f);
11809
- }
11810
- }
11811
- const commits = new Map;
11812
- for (const oid of oids) {
11813
- try {
11814
- const obj = await git6.readObject({ fs: fs7, dir, cache: cache2, oid, format: "parsed" });
11815
- if (obj.type === "commit") {
11816
- commits.set(oid, obj.object.parent ?? []);
11817
- }
11818
- } catch {}
11819
- }
11820
- const isParent = new Set;
11821
- for (const parents of commits.values())
11822
- for (const p of parents)
11823
- isParent.add(p);
11824
- return [...commits.keys()].filter((oid) => !isParent.has(oid));
11825
- }
11826
- async function targetBranch(dir) {
11827
- try {
11828
- const branches = await git6.listBranches({ fs: fs7, dir });
11829
- if (branches.includes("main"))
11830
- return "main";
11831
- if (branches.includes("master"))
11832
- return "master";
11833
- if (branches.length > 0)
11834
- return branches[0];
11835
- } catch {}
11836
- return "main";
11837
- }
11838
- async function repairRepo(options) {
11839
- const dir = await repoDirFor(options.projectDir);
11840
- return withRepoLock(dir, () => repairLocked(dir, options));
11841
- }
11842
- async function repairLocked(dir, options) {
11843
- const logger = resolveLogger(options.logFile, "repair");
11844
- const gitDir = gitDirFor(dir);
11845
- const actions = [];
11846
- const snapshotFiles = async (message) => {
11847
- try {
11848
- if (await hasPendingChanges(dir)) {
11849
- await snapshotWorkingTreeUnlocked({
11850
- projectDir: dir,
11851
- repoRoot: dir,
11852
- message,
11853
- authorName: options.authorName,
11854
- authorEmail: options.authorEmail
11855
- });
11856
- }
11857
- } catch (e) {
11858
- if (!isNoChangesError(e))
11859
- throw e;
11860
- }
11861
- };
11862
- try {
11863
- if (fs7.existsSync(gitDir)) {
11864
- const sweep = await sweepStaleLocks(gitDir);
11865
- if (sweep === "fresh") {
11866
- logger.info("repair", "fresh lock present — deferring");
11867
- return { status: "retry_later", message: MSG_RETRY, actions, retryAfterMs: 30000 };
11868
- }
11869
- if (sweep === "swept") {
11870
- actions.push("Removed leftover lock files from an interrupted operation.");
11871
- logger.info("repair", "swept stale locks");
11872
- }
11873
- }
11874
- if (fs7.existsSync(gitDir)) {
11875
- let removedState = false;
11876
- for (const name of INTERRUPTED_STATE) {
11877
- const p = path24.join(gitDir, name);
11878
- if (fs7.existsSync(p)) {
11879
- await rm6(p, { recursive: true, force: true });
11880
- removedState = true;
11881
- }
11882
- }
11883
- if (removedState) {
11884
- actions.push("Cleared an unfinished operation another program left behind.");
11885
- logger.info("repair", "removed interrupted-operation state");
11886
- await rebuildIndexFromHead(dir);
11887
- } else if (!await indexReadable(dir)) {
11888
- await rebuildIndexFromHead(dir);
11889
- actions.push("Rebuilt the project's tracking information.");
11890
- logger.info("repair", "rebuilt corrupt index");
11891
- }
11892
- }
11893
- let health = await inspectRepo({ repoDir: dir }, { checkLocalChanges: false });
11894
- if (health.hasGitDir && !health.headUnreadable && health.isDetachedHead) {
11895
- await snapshotFiles("Saved your latest changes");
11896
- const headOid = await git6.resolveRef({ fs: fs7, dir, ref: "HEAD" });
11897
- const rescueRef = `refs/heads/recovered-${stamp()}`;
11898
- await git6.writeRef({ fs: fs7, dir, ref: rescueRef, value: headOid, force: true });
11899
- const branch = await targetBranch(dir);
11900
- const branchExists = (await git6.listBranches({ fs: fs7, dir })).includes(branch);
11901
- if (!branchExists) {
11902
- await git6.writeRef({ fs: fs7, dir, ref: `refs/heads/${branch}`, value: headOid, force: true });
11903
- }
11904
- await git6.writeRef({
11905
- fs: fs7,
11906
- dir,
11907
- ref: "HEAD",
11908
- value: `refs/heads/${branch}`,
11909
- symbolic: true,
11910
- force: true
11911
- });
11912
- const cache2 = {};
11913
- await git6.checkout({ fs: fs7, dir, cache: cache2, ref: branch, force: true });
11914
- const branchTip = await git6.resolveRef({ fs: fs7, dir, ref: branch });
11915
- if (branchTip !== headOid) {
11916
- await convergeMerge({
11917
- dir,
11918
- cache: cache2,
11919
- branch,
11920
- theirs: headOid,
11921
- author: await resolveGitAuthor(dir, options.authorName, options.authorEmail),
11922
- authorName: options.authorName,
11923
- authorEmail: options.authorEmail,
11924
- allowUnrelatedHistories: true
11925
- });
11926
- }
11927
- actions.push("Reconnected your work to the project's version line.");
11928
- logger.info("repair", "reattached detached HEAD", { rescueRef });
11929
- }
11930
- health = await inspectRepo({ repoDir: dir }, { checkLocalChanges: false });
11931
- if (classifyFromHealth(health, { minLockAgeMs: 0 }) === null) {
11932
- await snapshotFiles("Saved your latest changes");
11933
- logger.info("repair", "repaired in place", { actions: actions.length });
11934
- return { status: "repaired", message: MSG_REPAIRED, actions };
11935
- }
11936
- return await recloneWithSalvage(dir, options, actions, logger, snapshotFiles);
11937
- } catch (e) {
11938
- logger.error("repair", "repair failed", { error: e instanceof Error ? e.message : String(e) });
11939
- return { status: "failed", message: MSG_FAILED, actions };
11940
- }
11941
- }
11942
- async function recloneWithSalvage(dir, options, actions, logger, snapshotFiles) {
11943
- const gitDir = gitDirFor(dir);
11944
- let remoteUrl;
11945
- let credential;
11946
- try {
11947
- const transport = await resolveTransport(dir, {
11948
- credential: options.credential,
11949
- tokenStore: options.tokenStore
11950
- });
11951
- remoteUrl = transport.url;
11952
- credential = transport.credential;
11953
- } catch {
11954
- remoteUrl = undefined;
11955
- }
11956
- let freshGitDir;
11957
- let tempDir;
11958
- if (remoteUrl) {
11959
- tempDir = await mkdtemp3(path24.join(os3.tmpdir(), "gutterpress-repair-"));
11960
- const cloneDir = path24.join(tempDir, "clone");
11961
- try {
11962
- await cloneRepository({
11963
- url: remoteUrl,
11964
- dir: cloneDir,
11965
- ...credential ? { credential } : {}
11966
- });
11967
- } catch (e) {
11968
- await rm6(tempDir, { recursive: true, force: true });
11969
- logger.warn("repair", "re-clone failed — deferring", {
11970
- error: e instanceof Error ? e.message : String(e)
11971
- });
11972
- return {
11973
- status: "retry_later",
11974
- message: "The online copy couldn't be reached to finish the repair. Your files are safe — it will be tried again.",
11975
- actions,
11976
- retryAfterMs: 60000
11977
- };
11978
- }
11979
- freshGitDir = path24.join(cloneDir, ".git");
11980
- } else {
11981
- tempDir = await mkdtemp3(path24.join(os3.tmpdir(), "gutterpress-repair-"));
11982
- const initDir = path24.join(tempDir, "init");
11983
- fs7.mkdirSync(initDir, { recursive: true });
11984
- await git6.init({ fs: fs7, dir: initDir, defaultBranch: "main" });
11985
- freshGitDir = path24.join(initDir, ".git");
11986
- }
11987
- let damagedBackup;
11988
- if (fs7.existsSync(gitDir)) {
11989
- damagedBackup = path24.join(dir, `.git-damaged-${stamp()}`);
11990
- await rename3(gitDir, damagedBackup);
11991
- actions.push("Kept the old history folder on disk as a backup.");
11992
- }
11993
- await rename3(freshGitDir, gitDir);
11994
- if (tempDir)
11995
- await rm6(tempDir, { recursive: true, force: true });
11996
- actions.push(remoteUrl ? "Rebuilt the project's history from the online copy." : "Started a fresh history for the project (no online copy is connected).");
11997
- logger.info("repair", "replaced .git", { fromRemote: Boolean(remoteUrl), damagedBackup });
11998
- if (damagedBackup) {
11999
- const from = path24.join(damagedBackup, "objects");
12000
- if (fs7.existsSync(from)) {
12001
- await cp3(from, path24.join(gitDir, "objects"), {
12002
- recursive: true,
12003
- force: false,
12004
- errorOnExist: false
12005
- }).catch(() => {});
12006
- }
12007
- const branch = await targetBranch(dir);
12008
- let branchExists = (await git6.listBranches({ fs: fs7, dir })).includes(branch);
12009
- const scanCache = {};
12010
- const tips = new Map;
12011
- for (const tip of readDamagedBranchTips(damagedBackup)) {
12012
- tips.set(tip.oid, tip.name);
12013
- }
12014
- for (const oid of await looseCommitTips(dir, scanCache)) {
12015
- if (!tips.has(oid))
12016
- tips.set(oid, "recovered-work");
12017
- }
12018
- for (const [oid, name] of tips) {
12019
- const cache2 = {};
12020
- try {
12021
- await git6.readCommit({ fs: fs7, dir, cache: cache2, oid });
12022
- } catch {
12023
- logger.warn("repair", "old tip unreadable — left in the backup", { tip: name });
12024
- continue;
12025
- }
12026
- if (!branchExists) {
12027
- await git6.writeRef({ fs: fs7, dir, ref: `refs/heads/${branch}`, value: oid, force: true });
12028
- await git6.writeRef({
12029
- fs: fs7,
12030
- dir,
12031
- ref: "HEAD",
12032
- value: `refs/heads/${branch}`,
12033
- symbolic: true,
12034
- force: true
12035
- });
12036
- await rebuildIndexFromHead(dir);
12037
- branchExists = true;
12038
- actions.push("Restored your saved history from the backup.");
12039
- continue;
12040
- }
12041
- try {
12042
- const branchTip = await git6.resolveRef({ fs: fs7, dir, ref: branch });
12043
- if (branchTip === oid)
12044
- continue;
12045
- await convergeMerge({
12046
- dir,
12047
- cache: cache2,
12048
- branch,
12049
- theirs: oid,
12050
- author: await resolveGitAuthor(dir, options.authorName, options.authorEmail),
12051
- authorName: options.authorName,
12052
- authorEmail: options.authorEmail,
12053
- allowUnrelatedHistories: true
12054
- });
12055
- actions.push("Brought your earlier saved versions back into the history.");
12056
- } catch (e) {
12057
- logger.warn("repair", "old tip could not be merged — pinned as a recovery branch", {
12058
- tip: name,
12059
- error: e instanceof Error ? e.message : String(e)
12060
- });
12061
- await git6.writeRef({
12062
- fs: fs7,
12063
- dir,
12064
- ref: `refs/heads/recovered-${stamp()}-${name.replace(/[^A-Za-z0-9_-]/g, "_")}`,
12065
- value: oid,
12066
- force: true
12067
- }).catch(() => {});
12068
- }
12069
- }
12070
- }
12071
- await snapshotFiles("Saved your files after repairing the project");
12072
- return {
12073
- status: "repaired",
12074
- message: MSG_REPAIRED,
12075
- actions,
12076
- ...damagedBackup ? { damagedGitBackupPath: damagedBackup } : {}
12077
- };
12078
- }
12079
- // src/lib/theme-manager.ts
12080
- import { isSeq as isSeq4, Scalar as Scalar2 } from "yaml";
12081
- var THEMES_DIR = "themes";
12082
- var THEME_HREF_RE = new RegExp(`^${THEMES_DIR}/([^/]+)/theme\\.css$`);
12083
- // src/lib/theme-import.ts
12084
- import { unzipSync as unzipSync2 } from "fflate";
12085
- var MAX_THEME_ARCHIVE_BYTES = 25 * 1024 * 1024;
12086
- var MAX_THEME_UNZIPPED_BYTES = 25 * 1024 * 1024;
12087
- var KNOWN_THEME_FILES = new Set(["theme.css", "theme.json"]);
12088
- var ALLOWED_ASSET_EXTS = new Set([
12089
- ".woff",
12090
- ".woff2",
12091
- ".ttf",
12092
- ".otf",
12093
- ".eot",
12094
- ".png",
12095
- ".jpg",
12096
- ".jpeg",
12097
- ".gif",
12098
- ".svg",
12099
- ".webp",
12100
- ".avif",
12101
- ".css"
12102
- ]);
12103
- // src/lib/manifest-config.ts
12104
- import { isSeq as isSeq5, isMap as isMap3, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
12105
- // src/lib/host-policy.ts
12106
- var AUTO_SNAPSHOT_MAX_MINUTES = 24 * 60;
12107
- var AUTO_SYNC_MAX_MINUTES = 24 * 60;
12108
- // src/lib/remote-auth/github-repos.ts
12109
- var MANIFEST_NAMES = new Set(MANIFEST_FILENAMES);
11227
+ // src/lib/remote-auth/transport.ts
11228
+ import git2 from "isomorphic-git";
12110
11229
  // src/lib/remote-auth/test-access.ts
12111
- import git7 from "isomorphic-git";
11230
+ import git4 from "isomorphic-git";
12112
11231
  import httpNode2 from "isomorphic-git/http/node";
12113
11232
  // src/lib/remote-auth/sync.ts
12114
- import git8 from "isomorphic-git";
12115
- // src/lib/remote-auth/image-clash.ts
12116
- import git9 from "isomorphic-git";
11233
+ import git6 from "isomorphic-git";
11234
+
11235
+ // src/lib/remote-auth/converge-merge.ts
11236
+ import git5 from "isomorphic-git";
11237
+ import diff3Merge from "diff3";
12117
11238
  // src/lib/publish/selections.ts
12118
- import { chmod as chmod2, mkdir as mkdir12, readFile as readFile26, writeFile as writeFile11 } from "node:fs/promises";
12119
- import path25 from "node:path";
11239
+ import { chmod as chmod2, mkdir as mkdir10, readFile as readFile25, writeFile as writeFile8 } from "node:fs/promises";
11240
+ import path20 from "node:path";
12120
11241
  class PublishSelectionsStore {
12121
11242
  filePath;
12122
11243
  queue = Promise.resolve();
12123
11244
  constructor(filePath) {
12124
- this.filePath = filePath ?? path25.join(defaultConfigDir(), "publish-selections.json");
11245
+ this.filePath = filePath ?? path20.join(defaultConfigDir(), "publish-selections.json");
12125
11246
  }
12126
11247
  async read() {
12127
11248
  try {
12128
- const raw = await readFile26(this.filePath, "utf8");
11249
+ const raw = await readFile25(this.filePath, "utf8");
12129
11250
  const parsed = JSON.parse(raw);
12130
11251
  if (parsed && typeof parsed === "object") {
12131
11252
  return {
@@ -12138,8 +11259,8 @@ class PublishSelectionsStore {
12138
11259
  return { version: 1, global: {}, projects: {} };
12139
11260
  }
12140
11261
  async write(data) {
12141
- await mkdir12(path25.dirname(this.filePath), { recursive: true });
12142
- await writeFile11(this.filePath, JSON.stringify(data, null, 2), {
11262
+ await mkdir10(path20.dirname(this.filePath), { recursive: true });
11263
+ await writeFile8(this.filePath, JSON.stringify(data, null, 2), {
12143
11264
  encoding: "utf8",
12144
11265
  mode: 384
12145
11266
  });
@@ -12204,4 +11325,4 @@ function normalizeProjectKey(projectDir) {
12204
11325
  }
12205
11326
  // src/lib/publish/manifest-publish.ts
12206
11327
  import { isMap as isMap4 } from "yaml";
12207
- export { PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, parseNpmPluginSpec, runLint, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, BUILT_IN_TEMPLATE_IDS, addNpmPlugin, isAppHeartbeatFresh, classifyFromHealth, inspectRepo, verifyRepoReadable, isUnbornRepo, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, runPublish, connectPublishProvider, repairRepo };
11328
+ export { PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, parseNpmPluginSpec, runLint, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, BUILT_IN_TEMPLATE_IDS, addNpmPlugin, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, runPublish, connectPublishProvider };