@fern-api/generator-cli 0.9.20 → 0.9.21

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 (2) hide show
  1. package/dist/cli.js +726 -122
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -7078,7 +7078,7 @@ var require_require_directory = __commonJS({
7078
7078
  "../../node_modules/.pnpm/require-directory@2.1.1/node_modules/require-directory/index.js"(exports2, module2) {
7079
7079
  "use strict";
7080
7080
  var fs3 = require("fs");
7081
- var join12 = require("path").join;
7081
+ var join14 = require("path").join;
7082
7082
  var resolve8 = require("path").resolve;
7083
7083
  var dirname4 = require("path").dirname;
7084
7084
  var defaultOptions2 = {
@@ -7115,7 +7115,7 @@ var require_require_directory = __commonJS({
7115
7115
  }
7116
7116
  path5 = !path5 ? dirname4(m.filename) : resolve8(dirname4(m.filename), path5);
7117
7117
  fs3.readdirSync(path5).forEach(function(filename) {
7118
- var joined = join12(path5, filename), files, key, obj;
7118
+ var joined = join14(path5, filename), files, key, obj;
7119
7119
  if (fs3.statSync(joined).isDirectory() && options.recurse) {
7120
7120
  files = requireDirectory(m, joined, options);
7121
7121
  if (Object.keys(files).length) {
@@ -18885,7 +18885,7 @@ var require_resolve_block_scalar = __commonJS({
18885
18885
  if (!header)
18886
18886
  return { value: "", type: null, comment: "", range: [start, start, start] };
18887
18887
  const type = header.mode === ">" ? Scalar.Scalar.BLOCK_FOLDED : Scalar.Scalar.BLOCK_LITERAL;
18888
- const lines = scalar.source ? splitLines(scalar.source) : [];
18888
+ const lines = scalar.source ? splitLines2(scalar.source) : [];
18889
18889
  let chompStart = lines.length;
18890
18890
  for (let i = lines.length - 1; i >= 0; --i) {
18891
18891
  const content = lines[i][1];
@@ -19043,7 +19043,7 @@ var require_resolve_block_scalar = __commonJS({
19043
19043
  }
19044
19044
  return { mode, indent, chomp, comment, length };
19045
19045
  }
19046
- function splitLines(source) {
19046
+ function splitLines2(source) {
19047
19047
  const split = source.split(/\n( *)/);
19048
19048
  const first2 = split[0];
19049
19049
  const m = first2.match(/^( *)/);
@@ -22028,7 +22028,7 @@ var init_diff3 = __esm({
22028
22028
  }
22029
22029
  });
22030
22030
 
22031
- // ../../node_modules/.pnpm/@fern-api+replay@0.13.0/node_modules/@fern-api/replay/dist/index.js
22031
+ // ../../node_modules/.pnpm/@fern-api+replay@0.14.0/node_modules/@fern-api/replay/dist/index.js
22032
22032
  var dist_exports = {};
22033
22033
  __export(dist_exports, {
22034
22034
  CREDENTIAL_PATTERNS: () => CREDENTIAL_PATTERNS,
@@ -22401,11 +22401,43 @@ function threeWayMerge(base, ours, theirs) {
22401
22401
  }
22402
22402
  }
22403
22403
  }
22404
- return {
22404
+ const result = {
22405
22405
  content: outputLines.join("\n"),
22406
22406
  hasConflicts: conflicts2.length > 0,
22407
22407
  conflicts: conflicts2
22408
22408
  };
22409
+ if (conflicts2.length === 0) {
22410
+ const dropped = computeDroppedContextLines(baseLines, theirsLines, outputLines);
22411
+ if (dropped.length > 0) {
22412
+ result.droppedContextLines = dropped;
22413
+ }
22414
+ }
22415
+ return result;
22416
+ }
22417
+ function computeDroppedContextLines(baseLines, theirsLines, mergedLines) {
22418
+ const baseCounts = countLines(baseLines);
22419
+ const theirsCounts = countLines(theirsLines);
22420
+ const mergedCounts = countLines(mergedLines);
22421
+ const dropped = [];
22422
+ const seen = /* @__PURE__ */ new Set();
22423
+ for (const line of baseLines) {
22424
+ if (seen.has(line)) continue;
22425
+ const inBase = baseCounts.get(line) ?? 0;
22426
+ const inTheirs = theirsCounts.get(line) ?? 0;
22427
+ const inMerged = mergedCounts.get(line) ?? 0;
22428
+ if (inBase > 0 && inTheirs > 0 && inMerged < Math.min(inBase, inTheirs)) {
22429
+ dropped.push(line);
22430
+ seen.add(line);
22431
+ }
22432
+ }
22433
+ return dropped;
22434
+ }
22435
+ function countLines(lines) {
22436
+ const counts = /* @__PURE__ */ new Map();
22437
+ for (const line of lines) {
22438
+ counts.set(line, (counts.get(line) ?? 0) + 1);
22439
+ }
22440
+ return counts;
22409
22441
  }
22410
22442
  function tryResolveConflict(oursLines, baseLines, theirsLines) {
22411
22443
  if (baseLines.length === 0) {
@@ -22535,10 +22567,346 @@ function isBinaryFile(filePath) {
22535
22567
  const ext2 = (0, import_path8.extname)(filePath).toLowerCase();
22536
22568
  return BINARY_EXTENSIONS.has(ext2);
22537
22569
  }
22570
+ function computeDroppedContextLinesForFile(base, theirs, finalContent) {
22571
+ const baseLines = base.split("\n");
22572
+ const theirsLines = theirs.split("\n");
22573
+ const finalLines = finalContent.split("\n");
22574
+ const baseCounts = /* @__PURE__ */ new Map();
22575
+ const theirsCounts = /* @__PURE__ */ new Map();
22576
+ const finalCounts = /* @__PURE__ */ new Map();
22577
+ for (const l of baseLines) baseCounts.set(l, (baseCounts.get(l) ?? 0) + 1);
22578
+ for (const l of theirsLines) theirsCounts.set(l, (theirsCounts.get(l) ?? 0) + 1);
22579
+ for (const l of finalLines) finalCounts.set(l, (finalCounts.get(l) ?? 0) + 1);
22580
+ const dropped = [];
22581
+ const seen = /* @__PURE__ */ new Set();
22582
+ for (const line of baseLines) {
22583
+ if (seen.has(line)) continue;
22584
+ const inBase = baseCounts.get(line) ?? 0;
22585
+ const inTheirs = theirsCounts.get(line) ?? 0;
22586
+ const inFinal = finalCounts.get(line) ?? 0;
22587
+ if (inBase > 0 && inTheirs > 0 && inFinal < Math.min(inBase, inTheirs)) {
22588
+ dropped.push(line);
22589
+ seen.add(line);
22590
+ }
22591
+ }
22592
+ return dropped;
22593
+ }
22538
22594
  function isDiffLineForFile(diffLine, filePath) {
22539
22595
  const match2 = diffLine.match(/^diff --git a\/.+ b\/(.+)$/);
22540
22596
  return match2 !== null && match2[1] === filePath;
22541
22597
  }
22598
+ function normalizeHunkBody(hunk) {
22599
+ let contextC = 0;
22600
+ let removeC = 0;
22601
+ let addC = 0;
22602
+ const trimmed2 = [];
22603
+ for (const line of hunk.lines) {
22604
+ const oldUsed = contextC + removeC;
22605
+ const newUsed = contextC + addC;
22606
+ if (oldUsed >= hunk.oldCount && newUsed >= hunk.newCount) break;
22607
+ trimmed2.push(line);
22608
+ if (line.type === "context") contextC++;
22609
+ else if (line.type === "remove") removeC++;
22610
+ else if (line.type === "add") addC++;
22611
+ }
22612
+ return { ...hunk, lines: trimmed2 };
22613
+ }
22614
+ function extractFileDiffForFile(patchContent, filePath) {
22615
+ const lines = patchContent.split("\n");
22616
+ const out = [];
22617
+ let inTarget = false;
22618
+ for (const line of lines) {
22619
+ if (line.startsWith("diff --git")) {
22620
+ if (inTarget) break;
22621
+ if (isDiffLineForFile2(line, filePath)) {
22622
+ inTarget = true;
22623
+ out.push(line);
22624
+ }
22625
+ continue;
22626
+ }
22627
+ if (inTarget) out.push(line);
22628
+ }
22629
+ return out.length > 0 ? out.join("\n") + "\n" : null;
22630
+ }
22631
+ function isDiffLineForFile2(line, filePath) {
22632
+ const match2 = line.match(/^diff --git a\/(.+) b\/(.+)$/);
22633
+ return match2 !== null && (match2[2] === filePath || match2[1] === filePath);
22634
+ }
22635
+ async function computePerPatchDiff(patch, getCurrentGenContent, getWorkingTreeContent) {
22636
+ const fragments = [];
22637
+ const unlocatableFiles = [];
22638
+ for (const file of patch.files) {
22639
+ const fileDiff = extractFileDiffForFile(patch.patch_content, file);
22640
+ if (fileDiff == null) {
22641
+ unlocatableFiles.push(file);
22642
+ continue;
22643
+ }
22644
+ const hunksRaw = parseHunks(fileDiff).map(normalizeHunkBody);
22645
+ const hunks = hunksRaw.filter((h) => {
22646
+ if (h.lines.length === 0) return false;
22647
+ let ctx = 0, rm3 = 0, add = 0;
22648
+ for (const ln of h.lines) {
22649
+ if (ln.type === "context") ctx++;
22650
+ else if (ln.type === "remove") rm3++;
22651
+ else if (ln.type === "add") add++;
22652
+ }
22653
+ return ctx + rm3 === h.oldCount && ctx + add === h.newCount;
22654
+ });
22655
+ if (hunks.length === 0) {
22656
+ if (hunksRaw.length > 0) unlocatableFiles.push(file);
22657
+ continue;
22658
+ }
22659
+ const currentGen = await getCurrentGenContent(file);
22660
+ const workingTree = await getWorkingTreeContent(file);
22661
+ const isPureNewFile = hunks.every((h) => h.oldCount === 0);
22662
+ const isPureDelete = hunks.every((h) => h.newCount === 0);
22663
+ if (currentGen == null) {
22664
+ if (!isPureNewFile) {
22665
+ unlocatableFiles.push(file);
22666
+ continue;
22667
+ }
22668
+ if (workingTree == null) continue;
22669
+ fragments.push(synthesizeNewFileDiff(file, workingTree));
22670
+ continue;
22671
+ }
22672
+ if (isPureNewFile) {
22673
+ if (workingTree == null) {
22674
+ fragments.push(synthesizeDeleteFileDiff(file, currentGen));
22675
+ continue;
22676
+ }
22677
+ if (workingTree === currentGen) {
22678
+ continue;
22679
+ }
22680
+ const fullDiff = await unifiedDiff(currentGen, workingTree, file);
22681
+ if (fullDiff && fullDiff.trim()) fragments.push(fullDiff);
22682
+ continue;
22683
+ }
22684
+ if (workingTree == null) {
22685
+ if (!isPureDelete) {
22686
+ unlocatableFiles.push(file);
22687
+ continue;
22688
+ }
22689
+ fragments.push(synthesizeDeleteFileDiff(file, currentGen));
22690
+ continue;
22691
+ }
22692
+ const currentGenLines = splitLines(currentGen);
22693
+ const workingTreeLines = splitLines(workingTree);
22694
+ const locInCurrentGenRaw = locateHunksInOurs(hunks, currentGenLines);
22695
+ const locInWorkingTreeRaw = locateHunksInOurs(hunks, workingTreeLines);
22696
+ if (locInCurrentGenRaw == null || locInWorkingTreeRaw == null) {
22697
+ unlocatableFiles.push(file);
22698
+ continue;
22699
+ }
22700
+ const locInCurrentGen = locInCurrentGenRaw.map(
22701
+ (l) => resolveSpan(l, currentGenLines, "old")
22702
+ );
22703
+ const locInWorkingTree = locInWorkingTreeRaw.map(
22704
+ (l) => resolveSpan(l, workingTreeLines, "new")
22705
+ );
22706
+ const overlay = overlayRegions(
22707
+ currentGenLines,
22708
+ locInCurrentGen,
22709
+ workingTreeLines,
22710
+ locInWorkingTree
22711
+ );
22712
+ if (overlay === null) {
22713
+ unlocatableFiles.push(file);
22714
+ continue;
22715
+ }
22716
+ if (overlay === currentGen) {
22717
+ continue;
22718
+ }
22719
+ const fileUnifiedDiff = await unifiedDiff(currentGen, overlay, file);
22720
+ if (fileUnifiedDiff && fileUnifiedDiff.trim()) {
22721
+ fragments.push(fileUnifiedDiff);
22722
+ }
22723
+ }
22724
+ return {
22725
+ diff: fragments.join(""),
22726
+ unlocatableFiles
22727
+ };
22728
+ }
22729
+ async function computePerPatchDiffWithFallback(patch, getCurrentGenContent, getWorkingTreeContent, runCumulativeDiff) {
22730
+ const ppDiff = await computePerPatchDiff(
22731
+ patch,
22732
+ getCurrentGenContent,
22733
+ getWorkingTreeContent
22734
+ );
22735
+ if (ppDiff.unlocatableFiles.length === 0) {
22736
+ return { diff: ppDiff.diff, hadFallback: false, fallbackFiles: [] };
22737
+ }
22738
+ const cumulative = await runCumulativeDiff(ppDiff.unlocatableFiles);
22739
+ if (cumulative === null) {
22740
+ return null;
22741
+ }
22742
+ const merged = ppDiff.diff + (cumulative.trim() ? cumulative : "");
22743
+ return { diff: merged, hadFallback: true, fallbackFiles: ppDiff.unlocatableFiles };
22744
+ }
22745
+ function splitLines(content) {
22746
+ return content.split("\n");
22747
+ }
22748
+ function resolveSpan(loc, oursLines, prefer) {
22749
+ const { hunk, oursOffset } = loc;
22750
+ const oldSide = [];
22751
+ const newSide = [];
22752
+ for (const line of hunk.lines) {
22753
+ if (line.type === "context") {
22754
+ oldSide.push(line.content);
22755
+ newSide.push(line.content);
22756
+ } else if (line.type === "remove") {
22757
+ oldSide.push(line.content);
22758
+ } else if (line.type === "add") {
22759
+ newSide.push(line.content);
22760
+ }
22761
+ }
22762
+ const oldFits = sequenceMatches(oldSide, oursLines, oursOffset);
22763
+ const newFits = sequenceMatches(newSide, oursLines, oursOffset);
22764
+ if (prefer === "old") {
22765
+ if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
22766
+ if (newFits) return { ...loc, oursSpan: hunk.newCount };
22767
+ } else {
22768
+ if (newFits) return { ...loc, oursSpan: hunk.newCount };
22769
+ if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
22770
+ }
22771
+ return loc;
22772
+ }
22773
+ function sequenceMatches(needle, haystack, offset) {
22774
+ if (offset + needle.length > haystack.length) return false;
22775
+ for (let i = 0; i < needle.length; i++) {
22776
+ if (haystack[offset + i] !== needle[i]) return false;
22777
+ }
22778
+ return true;
22779
+ }
22780
+ function overlayRegions(currentGenLines, locInCurrentGen, workingTreeLines, locInWorkingTree) {
22781
+ if (locInCurrentGen.length !== locInWorkingTree.length) {
22782
+ return null;
22783
+ }
22784
+ const out = [];
22785
+ let cursor = 0;
22786
+ for (let i = 0; i < locInCurrentGen.length; i++) {
22787
+ const cg = locInCurrentGen[i];
22788
+ const wt = locInWorkingTree[i];
22789
+ if (cg.oursOffset > cursor) {
22790
+ out.push(...currentGenLines.slice(cursor, cg.oursOffset));
22791
+ }
22792
+ out.push(...workingTreeLines.slice(wt.oursOffset, wt.oursOffset + wt.oursSpan));
22793
+ cursor = cg.oursOffset + cg.oursSpan;
22794
+ }
22795
+ if (cursor < currentGenLines.length) {
22796
+ out.push(...currentGenLines.slice(cursor));
22797
+ }
22798
+ return out.join("\n");
22799
+ }
22800
+ async function unifiedDiff(beforeContent, afterContent, filePath) {
22801
+ if (beforeContent === afterContent) return "";
22802
+ const tmp3 = await (0, import_promises4.mkdtemp)((0, import_path10.join)((0, import_os2.tmpdir)(), "fern-replay-pp-"));
22803
+ try {
22804
+ const beforePath = (0, import_path10.join)(tmp3, "before");
22805
+ const afterPath = (0, import_path10.join)(tmp3, "after");
22806
+ await (0, import_promises4.writeFile)(beforePath, beforeContent, "utf-8");
22807
+ await (0, import_promises4.writeFile)(afterPath, afterContent, "utf-8");
22808
+ const raw = await runGitDiffNoIndex(beforePath, afterPath);
22809
+ if (!raw.trim()) return "";
22810
+ return rewriteDiffHeaders(raw, filePath);
22811
+ } finally {
22812
+ await (0, import_promises4.rm)(tmp3, { recursive: true, force: true });
22813
+ }
22814
+ }
22815
+ function runGitDiffNoIndex(beforePath, afterPath) {
22816
+ return new Promise((resolve22, reject) => {
22817
+ const proc = (0, import_child_process3.spawn)("git", ["diff", "--no-index", "--", beforePath, afterPath]);
22818
+ let stdout = "";
22819
+ let stderr = "";
22820
+ proc.stdout.on("data", (d) => stdout += d.toString());
22821
+ proc.stderr.on("data", (d) => stderr += d.toString());
22822
+ proc.on("close", (code) => {
22823
+ if (code === 0 || code === 1) resolve22(stdout);
22824
+ else reject(new Error(`git diff --no-index failed (code ${code}): ${stderr}`));
22825
+ });
22826
+ });
22827
+ }
22828
+ function rewriteDiffHeaders(raw, filePath) {
22829
+ const lines = raw.split("\n");
22830
+ const out = [];
22831
+ let seenFirstHeader = false;
22832
+ for (const line of lines) {
22833
+ if (line.startsWith("diff --git ")) {
22834
+ out.push(`diff --git a/${filePath} b/${filePath}`);
22835
+ seenFirstHeader = true;
22836
+ continue;
22837
+ }
22838
+ if (!seenFirstHeader) {
22839
+ continue;
22840
+ }
22841
+ if (line.startsWith("--- ")) {
22842
+ out.push(`--- a/${filePath}`);
22843
+ continue;
22844
+ }
22845
+ if (line.startsWith("+++ ")) {
22846
+ out.push(`+++ b/${filePath}`);
22847
+ continue;
22848
+ }
22849
+ out.push(line);
22850
+ }
22851
+ return out.join("\n");
22852
+ }
22853
+ function synthesizeNewFileDiff(file, content) {
22854
+ const lines = content.split("\n");
22855
+ const hasTrailingNewline = content.endsWith("\n");
22856
+ const bodyLines = hasTrailingNewline ? lines.slice(0, -1) : lines;
22857
+ const header = [
22858
+ `diff --git a/${file} b/${file}`,
22859
+ "new file mode 100644",
22860
+ "--- /dev/null",
22861
+ `+++ b/${file}`,
22862
+ `@@ -0,0 +1,${bodyLines.length} @@`
22863
+ ].join("\n");
22864
+ const body = bodyLines.map((l) => `+${l}`).join("\n");
22865
+ const trailer = hasTrailingNewline ? "" : "\n\";
22866
+ return header + "\n" + body + trailer + "\n";
22867
+ }
22868
+ function synthesizeDeleteFileDiff(file, content) {
22869
+ const lines = content.split("\n");
22870
+ const hasTrailingNewline = content.endsWith("\n");
22871
+ const bodyLines = hasTrailingNewline ? lines.slice(0, -1) : lines;
22872
+ const header = [
22873
+ `diff --git a/${file} b/${file}`,
22874
+ "deleted file mode 100644",
22875
+ `--- a/${file}`,
22876
+ "+++ /dev/null",
22877
+ `@@ -1,${bodyLines.length} +0,0 @@`
22878
+ ].join("\n");
22879
+ const body = bodyLines.map((l) => `-${l}`).join("\n");
22880
+ const trailer = hasTrailingNewline ? "" : "\n\";
22881
+ return header + "\n" + body + trailer + "\n";
22882
+ }
22883
+ function computeCommitBuckets(results, absorbedPatchIds, patchesPassedToCommit) {
22884
+ const resultByPatchId = /* @__PURE__ */ new Map();
22885
+ for (const r2 of results) resultByPatchId.set(r2.patch.id, r2);
22886
+ const applied = [];
22887
+ const unresolved = [];
22888
+ const absorbed = [];
22889
+ for (const patch of patchesPassedToCommit) {
22890
+ if (absorbedPatchIds.has(patch.id)) {
22891
+ absorbed.push(patch);
22892
+ continue;
22893
+ }
22894
+ const r2 = resultByPatchId.get(patch.id);
22895
+ if (!r2) {
22896
+ applied.push(patch);
22897
+ continue;
22898
+ }
22899
+ if (r2.status === "conflict" || patch.status === "unresolved") {
22900
+ unresolved.push(patch);
22901
+ continue;
22902
+ }
22903
+ if (r2.status === "applied") {
22904
+ applied.push(patch);
22905
+ continue;
22906
+ }
22907
+ }
22908
+ return { applied, unresolved, absorbed };
22909
+ }
22542
22910
  async function bootstrap(outputDir, options) {
22543
22911
  const git = new GitClient(outputDir);
22544
22912
  const lockManager = new LockfileManager(outputDir);
@@ -22703,7 +23071,7 @@ function parseGitLog(log) {
22703
23071
  });
22704
23072
  }
22705
23073
  function ensureFernignoreEntries(outputDir) {
22706
- const fernignorePath = (0, import_path11.join)(outputDir, ".fernignore");
23074
+ const fernignorePath = (0, import_path12.join)(outputDir, ".fernignore");
22707
23075
  let content = "";
22708
23076
  if ((0, import_fs9.existsSync)(fernignorePath)) {
22709
23077
  content = (0, import_fs9.readFileSync)(fernignorePath, "utf-8");
@@ -22726,7 +23094,7 @@ function ensureFernignoreEntries(outputDir) {
22726
23094
  return true;
22727
23095
  }
22728
23096
  function ensureGitattributesEntries(outputDir) {
22729
- const gitattributesPath = (0, import_path11.join)(outputDir, ".gitattributes");
23097
+ const gitattributesPath = (0, import_path12.join)(outputDir, ".gitattributes");
22730
23098
  let content = "";
22731
23099
  if ((0, import_fs9.existsSync)(gitattributesPath)) {
22732
23100
  content = (0, import_fs9.readFileSync)(gitattributesPath, "utf-8");
@@ -22921,7 +23289,7 @@ async function resolve7(outputDir, options) {
22921
23289
  const resolvingPatches = lockManager.getResolvingPatches();
22922
23290
  if (unresolvedPatches.length > 0) {
22923
23291
  const applicator = new ReplayApplicator(git, lockManager, outputDir);
22924
- await applicator.applyPatches(unresolvedPatches);
23292
+ await applicator.applyPatches(unresolvedPatches, { applyMode: "resolve" });
22925
23293
  const markerFiles = await findConflictMarkerFiles(git);
22926
23294
  if (markerFiles.length > 0) {
22927
23295
  for (const patch of unresolvedPatches) {
@@ -22947,20 +23315,38 @@ async function resolve7(outputDir, options) {
22947
23315
  if (patchesToCommit.length > 0) {
22948
23316
  const currentGen = lock.current_generation;
22949
23317
  const detector = new ReplayDetector(git, lockManager, outputDir);
23318
+ const warnings = [];
22950
23319
  let patchesResolved = 0;
22951
23320
  for (const patch of patchesToCommit) {
22952
- const diff = await git.exec(["diff", currentGen, "--", ...patch.files]).catch(() => null);
22953
- if (!diff || !diff.trim()) {
23321
+ const result = await computePerPatchDiffWithFallback(
23322
+ patch,
23323
+ (f2) => git.showFile(currentGen, f2),
23324
+ (f2) => (0, import_promises5.readFile)((0, import_path13.join)(outputDir, f2), "utf-8").catch(() => null),
23325
+ (files) => git.exec(["diff", currentGen, "--", ...files]).catch(() => null)
23326
+ );
23327
+ if (result === null) {
23328
+ warnings.push(
23329
+ `Patch ${patch.id}: cumulative-diff fallback failed for unlocatable files; preserving patch unchanged`
23330
+ );
23331
+ continue;
23332
+ }
23333
+ const diff = result.diff;
23334
+ if (!diff.trim()) {
22954
23335
  lockManager.removePatch(patch.id);
22955
23336
  continue;
22956
23337
  }
23338
+ if (result.hadFallback) {
23339
+ warnings.push(
23340
+ `Patch ${patch.id}: ${result.fallbackFiles.join(", ")} could not be anchored; cumulative-diff fallback applied for those files`
23341
+ );
23342
+ }
22957
23343
  const newContentHash = detector.computeContentHash(diff);
22958
- const changedFiles = await getChangedFiles(git, currentGen, patch.files);
23344
+ const changedFiles = result.hadFallback ? await getChangedFiles(git, currentGen, patch.files) : extractFilesFromDiff(diff);
22959
23345
  lockManager.markPatchResolved(patch.id, {
22960
23346
  patch_content: diff,
22961
23347
  content_hash: newContentHash,
22962
23348
  base_generation: currentGen,
22963
- files: changedFiles
23349
+ files: changedFiles.length > 0 ? changedFiles : patch.files
22964
23350
  });
22965
23351
  patchesResolved++;
22966
23352
  }
@@ -22976,7 +23362,8 @@ async function resolve7(outputDir, options) {
22976
23362
  success: true,
22977
23363
  commitSha: commitSha2,
22978
23364
  phase: "committed",
22979
- patchesResolved
23365
+ patchesResolved,
23366
+ warnings: warnings.length > 0 ? warnings : void 0
22980
23367
  };
22981
23368
  }
22982
23369
  const committer = new ReplayCommitter(git, outputDir);
@@ -22994,6 +23381,24 @@ async function getChangedFiles(git, currentGen, files) {
22994
23381
  const changed = filesOutput.trim().split("\n").filter(Boolean).filter((f2) => !f2.startsWith(".fern/"));
22995
23382
  return changed.length > 0 ? changed : files;
22996
23383
  }
23384
+ function extractFilesFromDiff(unifiedDiff2) {
23385
+ const out = /* @__PURE__ */ new Set();
23386
+ const renameSources = /* @__PURE__ */ new Set();
23387
+ let currentTarget = null;
23388
+ for (const line of unifiedDiff2.split("\n")) {
23389
+ const headerMatch = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
23390
+ if (headerMatch) {
23391
+ currentTarget = headerMatch[2];
23392
+ if (!currentTarget.startsWith(".fern/")) out.add(currentTarget);
23393
+ continue;
23394
+ }
23395
+ if (currentTarget && line.startsWith("rename from ")) {
23396
+ renameSources.add(line.slice("rename from ".length));
23397
+ }
23398
+ }
23399
+ for (const src of renameSources) out.delete(src);
23400
+ return Array.from(out);
23401
+ }
22997
23402
  function status(outputDir) {
22998
23403
  const lockManager = new LockfileManager(outputDir);
22999
23404
  if (!lockManager.exists()) {
@@ -23041,9 +23446,9 @@ function status(outputDir) {
23041
23446
  excludePatterns
23042
23447
  };
23043
23448
  }
23044
- var import_fs6, import_path7, import_yaml, import_crypto, import_promises3, import_os, import_path8, import_fs7, import_path9, import_crypto2, import_fs8, import_path10, import_yaml2, import_crypto3, import_fs9, import_path11, import_fs10, __defProp3, __getOwnPropNames3, __esm3, __export3, GitClient_exports, GitClient, init_GitClient, HybridReconstruction_exports, init_HybridReconstruction, CREDENTIAL_PATTERNS, SENSITIVE_FILE_PATTERNS, FERN_BOT_NAME, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_SUPPORT_NAMES, LOCKFILE_HEADER, LockfileNotFoundError, LockfileManager, INFRASTRUCTURE_FILES, ReplayDetector, CONFLICT_OPENER, CONFLICT_SEPARATOR, CONFLICT_CLOSER, BINARY_EXTENSIONS, ReplayApplicator, ReplayCommitter, ReplayService, FernignoreMigrator, REPLAY_FERNIGNORE_ENTRIES, GITATTRIBUTES_ENTRIES, EMPTY_RESULT;
23449
+ var import_fs6, import_path7, import_yaml, import_crypto, import_promises3, import_os, import_path8, import_fs7, import_path9, import_promises4, import_os2, import_path10, import_child_process3, import_crypto2, import_fs8, import_path11, import_yaml2, import_crypto3, import_fs9, import_path12, import_fs10, import_promises5, import_path13, __defProp3, __getOwnPropNames3, __esm3, __export3, GitClient_exports, GitClient, init_GitClient, HybridReconstruction_exports, init_HybridReconstruction, CREDENTIAL_PATTERNS, SENSITIVE_FILE_PATTERNS, FERN_BOT_NAME, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_SUPPORT_NAMES, LOCKFILE_HEADER, LockfileNotFoundError, LockfileManager, INFRASTRUCTURE_FILES, ReplayDetector, CONFLICT_OPENER, CONFLICT_SEPARATOR, CONFLICT_CLOSER, BINARY_EXTENSIONS, ReplayApplicator, ReplayCommitter, ReplayService, FernignoreMigrator, REPLAY_FERNIGNORE_ENTRIES, GITATTRIBUTES_ENTRIES, EMPTY_RESULT;
23045
23450
  var init_dist3 = __esm({
23046
- "../../node_modules/.pnpm/@fern-api+replay@0.13.0/node_modules/@fern-api/replay/dist/index.js"() {
23451
+ "../../node_modules/.pnpm/@fern-api+replay@0.14.0/node_modules/@fern-api/replay/dist/index.js"() {
23047
23452
  "use strict";
23048
23453
  init_esm4();
23049
23454
  import_fs6 = require("fs");
@@ -23058,16 +23463,22 @@ var init_dist3 = __esm({
23058
23463
  import_fs7 = require("fs");
23059
23464
  import_path9 = require("path");
23060
23465
  init_esm3();
23466
+ import_promises4 = require("fs/promises");
23467
+ import_os2 = require("os");
23468
+ import_path10 = require("path");
23469
+ import_child_process3 = require("child_process");
23061
23470
  import_crypto2 = require("crypto");
23062
23471
  import_fs8 = require("fs");
23063
- import_path10 = require("path");
23472
+ import_path11 = require("path");
23064
23473
  init_esm3();
23065
23474
  import_yaml2 = __toESM(require_dist3(), 1);
23066
23475
  import_crypto3 = require("crypto");
23067
23476
  import_fs9 = require("fs");
23068
- import_path11 = require("path");
23477
+ import_path12 = require("path");
23069
23478
  init_esm3();
23070
23479
  import_fs10 = require("fs");
23480
+ import_promises5 = require("fs/promises");
23481
+ import_path13 = require("path");
23071
23482
  __defProp3 = Object.defineProperty;
23072
23483
  __getOwnPropNames3 = Object.getOwnPropertyNames;
23073
23484
  __esm3 = (fn, res) => function __init() {
@@ -23095,9 +23506,9 @@ var init_dist3 = __esm({
23095
23506
  return this.git.raw(args);
23096
23507
  }
23097
23508
  async execWithInput(args, input) {
23098
- const { spawn: spawn2 } = await import("child_process");
23509
+ const { spawn: spawn22 } = await import("child_process");
23099
23510
  return new Promise((resolve22, reject) => {
23100
- const proc = spawn2("git", args, { cwd: this.repoPath });
23511
+ const proc = spawn22("git", args, { cwd: this.repoPath });
23101
23512
  let stdout = "";
23102
23513
  let stderr = "";
23103
23514
  proc.stdout.on("data", (data) => {
@@ -24021,6 +24432,19 @@ var init_dist3 = __esm({
24021
24432
  renameCache = /* @__PURE__ */ new Map();
24022
24433
  treeExistsCache = /* @__PURE__ */ new Map();
24023
24434
  fileTheirsAccumulator = /* @__PURE__ */ new Map();
24435
+ /**
24436
+ * Apply mode for the current `applyPatches` invocation. Set at the start
24437
+ * of `applyPatches`, read by `mergeFile` to decide:
24438
+ * - whether to skip the intra-loop marker strip (kept in `applyPatches`)
24439
+ * - whether to populate the accumulator after a conflicted merge
24440
+ * (resolve mode populates so subsequent patches on the same file
24441
+ * can use patch A's THEIRS as a structurally-correct merge base
24442
+ * when their diff was authored against post-A structure)
24443
+ * - whether to retry THEIRS reconstruction against the accumulator
24444
+ * when the BASE-relative reconstruction produced markers from a
24445
+ * cross-patch context mismatch
24446
+ */
24447
+ currentApplyMode = "replay";
24024
24448
  constructor(git, lockManager, outputDir) {
24025
24449
  this.git = git;
24026
24450
  this.lockManager = lockManager;
@@ -24068,8 +24492,23 @@ var init_dist3 = __esm({
24068
24492
  /**
24069
24493
  * Apply all patches, returning results for each.
24070
24494
  * Skips patches that match exclude patterns in replay.yml
24495
+ *
24496
+ * `applyMode` controls the post-conflict marker strategy:
24497
+ * - `"replay"` (default): strip conflict markers from disk between
24498
+ * iterations whenever a later patch touches the same file. Lets the
24499
+ * follow-up patch's `git apply --3way` see clean OURS content,
24500
+ * which is what the silent-loss fix (PR #73) relies on. The replay
24501
+ * pipeline calls `revertConflictingFiles` after the loop to clean
24502
+ * any markers that survive.
24503
+ * - `"resolve"`: keep markers on disk. The resolve command needs the
24504
+ * customer to see them. Slow-path 3-way merge naturally preserves
24505
+ * A's markers and B's clean writes when their regions don't overlap;
24506
+ * when they do overlap, the customer gets nested markers and
24507
+ * resolves manually. Either way they aren't silently dropped.
24071
24508
  */
24072
- async applyPatches(patches) {
24509
+ async applyPatches(patches, opts) {
24510
+ const applyMode = opts?.applyMode ?? "replay";
24511
+ this.currentApplyMode = applyMode;
24073
24512
  this.resetAccumulator();
24074
24513
  const results = [];
24075
24514
  for (let i = 0; i < patches.length; i++) {
@@ -24084,7 +24523,7 @@ var init_dist3 = __esm({
24084
24523
  }
24085
24524
  const result = await this.applyPatchWithFallback(patch);
24086
24525
  results.push(result);
24087
- if (result.status === "conflict" && result.fileResults) {
24526
+ if (applyMode !== "resolve" && result.status === "conflict" && result.fileResults) {
24088
24527
  const laterFiles = /* @__PURE__ */ new Set();
24089
24528
  for (let j2 = i + 1; j2 < patches.length; j2++) {
24090
24529
  for (const f2 of patches[j2].files) {
@@ -24114,9 +24553,17 @@ var init_dist3 = __esm({
24114
24553
  }
24115
24554
  return results;
24116
24555
  }
24117
- /** Populate accumulator after git apply succeeds. */
24556
+ /**
24557
+ * Populate accumulator after git apply succeeds, AND collect per-file
24558
+ * results including any "dropped context lines" — lines that were
24559
+ * present in BASE and THEIRS (unchanged context) but absent from the
24560
+ * final on-disk state because OURS deleted them. This is the fast-path
24561
+ * counterpart to ThreeWayMerge.computeDroppedContextLines used by the
24562
+ * 3-way slow path; both must surface the same warning.
24563
+ */
24118
24564
  async populateAccumulatorForPatch(patch, baseGen, currentTreeHash) {
24119
- if (!baseGen) return;
24565
+ const fileResults = [];
24566
+ if (!baseGen) return fileResults;
24120
24567
  const tempDir = await (0, import_promises3.mkdtemp)((0, import_path8.join)((0, import_os.tmpdir)(), "replay-acc-"));
24121
24568
  const { GitClient: GitClient2 } = await Promise.resolve().then(() => (init_GitClient(), GitClient_exports));
24122
24569
  const tempGit = new GitClient2(tempDir);
@@ -24129,18 +24576,30 @@ var init_dist3 = __esm({
24129
24576
  const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
24130
24577
  const base = await this.git.showFile(baseGen.tree_hash, filePath);
24131
24578
  const theirs = await this.applyPatchToContent(base, patch.patch_content, filePath, tempGit, tempDir);
24132
- const effectiveTheirs = theirs ?? await (0, import_promises3.readFile)((0, import_path8.join)(this.outputDir, resolvedPath), "utf-8").catch(() => null);
24579
+ const finalOnDisk = await (0, import_promises3.readFile)((0, import_path8.join)(this.outputDir, resolvedPath), "utf-8").catch(() => null);
24580
+ const effectiveTheirs = theirs ?? finalOnDisk;
24133
24581
  if (effectiveTheirs != null) {
24134
24582
  this.fileTheirsAccumulator.set(resolvedPath, {
24135
24583
  content: effectiveTheirs,
24136
24584
  baseGeneration: patch.base_generation
24137
24585
  });
24138
24586
  }
24587
+ if (base != null && effectiveTheirs != null && finalOnDisk != null) {
24588
+ const dropped = computeDroppedContextLinesForFile(base, effectiveTheirs, finalOnDisk);
24589
+ if (dropped.length > 0) {
24590
+ fileResults.push({
24591
+ file: resolvedPath,
24592
+ status: "merged",
24593
+ droppedContextLines: dropped
24594
+ });
24595
+ }
24596
+ }
24139
24597
  }
24140
24598
  } finally {
24141
24599
  await (0, import_promises3.rm)(tempDir, { recursive: true }).catch(() => {
24142
24600
  });
24143
24601
  }
24602
+ return fileResults;
24144
24603
  }
24145
24604
  async applyPatchWithFallback(patch) {
24146
24605
  const baseGen = await this.resolveBaseGeneration(patch.base_generation);
@@ -24171,11 +24630,12 @@ var init_dist3 = __esm({
24171
24630
  }
24172
24631
  try {
24173
24632
  await this.git.execWithInput(["apply", "--3way"], patch.patch_content);
24174
- await this.populateAccumulatorForPatch(patch, baseGen, currentTreeHash);
24633
+ const fastPathFileResults = await this.populateAccumulatorForPatch(patch, baseGen, currentTreeHash);
24175
24634
  return {
24176
24635
  patch,
24177
24636
  status: "applied",
24178
24637
  method: "git-am",
24638
+ ...fastPathFileResults.length > 0 && { fileResults: fastPathFileResults },
24179
24639
  ...Object.keys(resolvedFiles).length > 0 && { resolvedFiles }
24180
24640
  };
24181
24641
  } catch {
@@ -24287,19 +24747,23 @@ var init_dist3 = __esm({
24287
24747
  renameSourcePath
24288
24748
  );
24289
24749
  }
24750
+ const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
24290
24751
  if (theirs) {
24291
24752
  const theirsHasMarkers = theirs.includes("<<<<<<< Generated") || theirs.includes(">>>>>>> Your customization");
24292
24753
  const baseHasMarkers = base != null && (base.includes("<<<<<<< Generated") || base.includes(">>>>>>> Your customization"));
24293
24754
  if (theirsHasMarkers && !baseHasMarkers) {
24294
- return {
24295
- file: resolvedPath,
24296
- status: "skipped",
24297
- reason: "stale-conflict-markers"
24298
- };
24755
+ if (accumulatorEntry) {
24756
+ theirs = null;
24757
+ } else {
24758
+ return {
24759
+ file: resolvedPath,
24760
+ status: "skipped",
24761
+ reason: "stale-conflict-markers"
24762
+ };
24763
+ }
24299
24764
  }
24300
24765
  }
24301
24766
  let useAccumulatorAsMergeBase = false;
24302
- const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
24303
24767
  if (accumulatorEntry && (theirs == null || base == null)) {
24304
24768
  theirs = await this.applyPatchToContent(
24305
24769
  accumulatorEntry.content,
@@ -24406,7 +24870,8 @@ var init_dist3 = __esm({
24406
24870
  const outDir = (0, import_path8.dirname)(oursPath);
24407
24871
  await (0, import_promises3.mkdir)(outDir, { recursive: true });
24408
24872
  await (0, import_promises3.writeFile)(oursPath, merged.content);
24409
- if (effective_theirs != null && !merged.hasConflicts) {
24873
+ const populateAccumulator = effective_theirs != null && (!merged.hasConflicts || this.currentApplyMode === "resolve");
24874
+ if (populateAccumulator) {
24410
24875
  this.fileTheirsAccumulator.set(resolvedPath, {
24411
24876
  content: effective_theirs,
24412
24877
  baseGeneration: patch.base_generation
@@ -24421,7 +24886,11 @@ var init_dist3 = __esm({
24421
24886
  conflictMetadata: metadata
24422
24887
  };
24423
24888
  }
24424
- return { file: resolvedPath, status: "merged" };
24889
+ return {
24890
+ file: resolvedPath,
24891
+ status: "merged",
24892
+ ...merged.droppedContextLines && merged.droppedContextLines.length > 0 ? { droppedContextLines: merged.droppedContextLines } : {}
24893
+ };
24425
24894
  } catch (error) {
24426
24895
  return {
24427
24896
  file: filePath,
@@ -24632,13 +25101,46 @@ CLI Version: ${options.cliVersion}`;
24632
25101
  await this.git.exec(["commit", "-m", fullMessage]);
24633
25102
  return (await this.git.exec(["rev-parse", "HEAD"])).trim();
24634
25103
  }
24635
- async commitReplay(_patchCount, patches, message) {
25104
+ async commitReplay(_patchCount, patches, message, options) {
24636
25105
  await this.stageAll();
24637
25106
  if (!await this.hasStagedChanges()) {
24638
25107
  return (await this.git.exec(["rev-parse", "HEAD"])).trim();
24639
25108
  }
24640
25109
  let fullMessage = message ?? `[fern-replay] Applied customizations`;
24641
- if (patches && patches.length > 0) {
25110
+ const buckets = options?.buckets;
25111
+ if (buckets) {
25112
+ if (buckets.applied.length > 0) {
25113
+ fullMessage += `
25114
+
25115
+ Patches applied (${buckets.applied.length}):`;
25116
+ for (const p of buckets.applied) {
25117
+ fullMessage += `
25118
+ - ${p.id}: ${p.original_message}`;
25119
+ }
25120
+ }
25121
+ if (buckets.unresolved.length > 0) {
25122
+ fullMessage += `
25123
+
25124
+ Patches with unresolved conflicts (${buckets.unresolved.length}):`;
25125
+ for (const p of buckets.unresolved) {
25126
+ fullMessage += `
25127
+ - ${p.id}: ${p.original_message}`;
25128
+ }
25129
+ fullMessage += `
25130
+ Run \`fern-replay resolve\` to apply these customizations.`;
25131
+ }
25132
+ if (buckets.absorbed.length > 0) {
25133
+ fullMessage += `
25134
+
25135
+ Patches absorbed by generator (${buckets.absorbed.length}):`;
25136
+ for (const p of buckets.absorbed) {
25137
+ fullMessage += `
25138
+ - ${p.id}: ${p.original_message}`;
25139
+ }
25140
+ fullMessage += `
25141
+ The generator now produces these customizations natively.`;
25142
+ }
25143
+ } else if (patches && patches.length > 0) {
24642
25144
  fullMessage += "\n\nPatches replayed:";
24643
25145
  for (const patch of patches) {
24644
25146
  fullMessage += `
@@ -24672,6 +25174,7 @@ CLI Version: ${options.cliVersion}`;
24672
25174
  }
24673
25175
  };
24674
25176
  init_GitClient();
25177
+ init_HybridReconstruction();
24675
25178
  ReplayService = class {
24676
25179
  git;
24677
25180
  detector;
@@ -25065,6 +25568,7 @@ CLI Version: ${options.cliVersion}`;
25065
25568
  if (newPatches.length > 0) {
25066
25569
  results = await this.applicator.applyPatches(newPatches);
25067
25570
  this._lastApplyResults = results;
25571
+ this.recordDroppedContextLineWarnings(results);
25068
25572
  this.revertConflictingFiles(results);
25069
25573
  for (const result of results) {
25070
25574
  if (result.status === "conflict") {
@@ -25083,7 +25587,8 @@ CLI Version: ${options.cliVersion}`;
25083
25587
  if (newPatches.length > 0) {
25084
25588
  if (!options?.stageOnly) {
25085
25589
  const appliedCount = results.filter((r2) => r2.status === "applied").length;
25086
- await this.committer.commitReplay(appliedCount, newPatches);
25590
+ const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, newPatches);
25591
+ await this.committer.commitReplay(appliedCount, newPatches, void 0, { buckets });
25087
25592
  } else {
25088
25593
  await this.committer.stageAll();
25089
25594
  }
@@ -25146,12 +25651,21 @@ CLI Version: ${options.cliVersion}`;
25146
25651
  (p) => preRebasePatchIds.has(p.id) && !postRebasePatchIds.has(p.id)
25147
25652
  );
25148
25653
  existingPatches = this.lockManager.getPatches();
25149
- const seenHashes = /* @__PURE__ */ new Set();
25654
+ const seenHashes = /* @__PURE__ */ new Map();
25150
25655
  for (const p of existingPatches) {
25151
- if (seenHashes.has(p.content_hash)) {
25152
- this.lockManager.removePatch(p.id);
25656
+ const priorOrigin = seenHashes.get(p.content_hash);
25657
+ if (priorOrigin !== void 0) {
25658
+ const provenanceMissing = !priorOrigin || !p.original_commit;
25659
+ const sameOrigin = priorOrigin === p.original_commit;
25660
+ if (sameOrigin && !provenanceMissing) {
25661
+ this.lockManager.removePatch(p.id);
25662
+ } else {
25663
+ this.warnings.push(
25664
+ `Preserving patch ${p.id} (commit ${(p.original_commit || "<missing>").slice(0, 7)}): shares content_hash with a prior patch ${provenanceMissing ? "(provenance missing on at least one side)" : "from a different source commit"}. Both retained to avoid silent customization loss (FER-9983).`
25665
+ );
25666
+ }
25153
25667
  } else {
25154
- seenHashes.add(p.content_hash);
25668
+ seenHashes.set(p.content_hash, p.original_commit);
25155
25669
  }
25156
25670
  }
25157
25671
  existingPatches = this.lockManager.getPatches();
@@ -25212,6 +25726,7 @@ CLI Version: ${options.cliVersion}`;
25212
25726
  const genSha = prep._prepared.genSha;
25213
25727
  const results = await this.applicator.applyPatches(allPatches);
25214
25728
  this._lastApplyResults = results;
25729
+ this.recordDroppedContextLineWarnings(results);
25215
25730
  this.revertConflictingFiles(results);
25216
25731
  for (const result of results) {
25217
25732
  if (result.status === "conflict") {
@@ -25239,7 +25754,8 @@ CLI Version: ${options.cliVersion}`;
25239
25754
  await this.committer.stageAll();
25240
25755
  } else {
25241
25756
  const appliedCount = results.filter((r2) => r2.status === "applied").length;
25242
- await this.committer.commitReplay(appliedCount, allPatches);
25757
+ const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, allPatches);
25758
+ await this.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
25243
25759
  }
25244
25760
  const warnings = [...detectorWarnings, ...this.warnings];
25245
25761
  return this.buildReport(
@@ -25263,7 +25779,7 @@ CLI Version: ${options.cliVersion}`;
25263
25779
  let repointed = 0;
25264
25780
  let contentRebased = 0;
25265
25781
  let keptAsUserOwned = 0;
25266
- const seenContentHashes = /* @__PURE__ */ new Set();
25782
+ const seenContentHashes = /* @__PURE__ */ new Map();
25267
25783
  const absorbedPatchIds = /* @__PURE__ */ new Set();
25268
25784
  const fernignorePatterns = this.readFernignorePatterns();
25269
25785
  for (const result of results) {
@@ -25277,6 +25793,23 @@ CLI Version: ${options.cliVersion}`;
25277
25793
  }
25278
25794
  }
25279
25795
  }
25796
+ const conflictedFilePaths = /* @__PURE__ */ new Set();
25797
+ for (const r2 of results) {
25798
+ if (r2.status !== "conflict") continue;
25799
+ if (r2.fileResults) {
25800
+ for (const fr of r2.fileResults) {
25801
+ if (fr.status !== "conflict") continue;
25802
+ conflictedFilePaths.add(fr.file);
25803
+ if (r2.resolvedFiles) {
25804
+ for (const [orig, resolved] of Object.entries(r2.resolvedFiles)) {
25805
+ if (resolved === fr.file) conflictedFilePaths.add(orig);
25806
+ }
25807
+ }
25808
+ }
25809
+ } else {
25810
+ for (const f2 of r2.patch.files) conflictedFilePaths.add(f2);
25811
+ }
25812
+ }
25280
25813
  for (const result of results) {
25281
25814
  if (result.status === "conflict" && result.fileResults) {
25282
25815
  await this.trimAbsorbedFiles(result, currentGenSha);
@@ -25344,6 +25877,18 @@ CLI Version: ${options.cliVersion}`;
25344
25877
  keptAsUserOwned++;
25345
25878
  continue;
25346
25879
  }
25880
+ const overlapsConflicted = patch.files.some((f2) => conflictedFilePaths.has(f2));
25881
+ if (overlapsConflicted) {
25882
+ patch.status = "unresolved";
25883
+ try {
25884
+ this.lockManager.updatePatch(patch.id, { status: "unresolved" });
25885
+ } catch {
25886
+ }
25887
+ this.warnings.push(
25888
+ `Patch ${patch.id} (${patch.original_message}) was preserved as unresolved because an earlier patch conflicted on the same file(s) (${patch.files.join(", ")}). Run \`fern replay resolve\` to apply this customization manually.`
25889
+ );
25890
+ continue;
25891
+ }
25347
25892
  this.lockManager.removePatch(patch.id);
25348
25893
  absorbedPatchIds.add(patch.id);
25349
25894
  absorbed++;
@@ -25359,13 +25904,22 @@ CLI Version: ${options.cliVersion}`;
25359
25904
  continue;
25360
25905
  }
25361
25906
  const newContentHash = this.detector.computeContentHash(diff);
25362
- if (seenContentHashes.has(newContentHash)) {
25363
- this.lockManager.removePatch(patch.id);
25364
- absorbedPatchIds.add(patch.id);
25365
- absorbed++;
25366
- continue;
25907
+ const priorOrigin = seenContentHashes.get(newContentHash);
25908
+ if (priorOrigin !== void 0) {
25909
+ const provenanceMissing = !priorOrigin || !patch.original_commit;
25910
+ const sameOrigin = priorOrigin === patch.original_commit;
25911
+ if (sameOrigin && !provenanceMissing) {
25912
+ this.lockManager.removePatch(patch.id);
25913
+ absorbedPatchIds.add(patch.id);
25914
+ absorbed++;
25915
+ continue;
25916
+ }
25917
+ this.warnings.push(
25918
+ `Preserving patch ${patch.id} (commit ${(patch.original_commit || "<missing>").slice(0, 7)}): shares content_hash with a prior patch ${provenanceMissing ? "(provenance missing on at least one side)" : "from a different source commit"}. Both retained to avoid silent customization loss (FER-9983).`
25919
+ );
25920
+ } else {
25921
+ seenContentHashes.set(newContentHash, patch.original_commit);
25367
25922
  }
25368
- seenContentHashes.add(newContentHash);
25369
25923
  patch.base_generation = currentGenSha;
25370
25924
  patch.patch_content = diff;
25371
25925
  patch.content_hash = newContentHash;
@@ -25541,8 +26095,14 @@ CLI Version: ${options.cliVersion}`;
25541
26095
  }
25542
26096
  if (patch.base_generation === currentGen) {
25543
26097
  try {
25544
- const diff = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch.files]).catch(() => null);
25545
- if (diff === null) continue;
26098
+ const ppResult = await computePerPatchDiffWithFallback(
26099
+ patch,
26100
+ (f2) => this.git.showFile(currentGen, f2),
26101
+ (f2) => this.git.showFile("HEAD", f2),
26102
+ (files) => this.git.exec(["diff", currentGen, "HEAD", "--", ...files]).catch(() => null)
26103
+ );
26104
+ if (ppResult === null) continue;
26105
+ const diff = ppResult.diff;
25546
26106
  if (!diff.trim()) {
25547
26107
  if (await allPatchFilesUserOwned(patch)) continue;
25548
26108
  this.lockManager.removePatch(patch.id);
@@ -25584,8 +26144,14 @@ CLI Version: ${options.cliVersion}`;
25584
26144
  try {
25585
26145
  const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<< Generated", "HEAD", "--", ...patch.files]).catch(() => "");
25586
26146
  if (markerFiles.trim()) continue;
25587
- const diff = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch.files]).catch(() => null);
25588
- if (diff === null) continue;
26147
+ const ppResult = await computePerPatchDiffWithFallback(
26148
+ patch,
26149
+ (f2) => this.git.showFile(currentGen, f2),
26150
+ (f2) => this.git.showFile("HEAD", f2),
26151
+ (files) => this.git.exec(["diff", currentGen, "HEAD", "--", ...files]).catch(() => null)
26152
+ );
26153
+ if (ppResult === null) continue;
26154
+ const diff = ppResult.diff;
25589
26155
  if (!diff.trim()) {
25590
26156
  if (await allPatchFilesUserOwned(patch)) {
25591
26157
  try {
@@ -25610,6 +26176,32 @@ CLI Version: ${options.cliVersion}`;
25610
26176
  }
25611
26177
  return { conflictResolved, conflictAbsorbed, contentRefreshed };
25612
26178
  }
26179
+ /**
26180
+ * After applyPatches(), surface a warning for each (patch × file) where
26181
+ * diff3 silently dropped lines that were unchanged context in the
26182
+ * customer's THEIRS. The deletion stays on disk (correct diff3 outcome),
26183
+ * but the customer must learn so they can re-add the lines if they
26184
+ * relied on them. This is the "surface or preserve, never silently drop"
26185
+ * contract for legitimate-deletion cases.
26186
+ */
26187
+ recordDroppedContextLineWarnings(results) {
26188
+ const PREVIEW_COUNT = 5;
26189
+ for (const result of results) {
26190
+ if (!result.fileResults) continue;
26191
+ for (const fr of result.fileResults) {
26192
+ const dropped = fr.droppedContextLines;
26193
+ if (!dropped || dropped.length === 0) continue;
26194
+ const preview = dropped.slice(0, PREVIEW_COUNT).map((line) => ` ${line}`).join("\n");
26195
+ const more = dropped.length > PREVIEW_COUNT ? `
26196
+ ... and ${dropped.length - PREVIEW_COUNT} more` : "";
26197
+ this.warnings.push(
26198
+ `${fr.file}: ${dropped.length} line(s) that appeared as unchanged context in your customization were deleted by the new generator output. The merge followed the deletion. First lines deleted:
26199
+ ${preview}${more}
26200
+ If you want to keep these lines, restore them in a follow-up commit and re-run replay; otherwise this warning can be safely ignored.`
26201
+ );
26202
+ }
26203
+ }
26204
+ }
25613
26205
  /**
25614
26206
  * After applyPatches(), strip conflict markers from conflicting files
25615
26207
  * so only clean content is committed. Keeps the Generated (OURS) side.
@@ -25667,6 +26259,9 @@ CLI Version: ${options.cliVersion}`;
25667
26259
  };
25668
26260
  }).filter((d) => d.files.length > 0);
25669
26261
  const partialCount = conflictDetails.filter((d) => d.cleanFiles && d.cleanFiles.length > 0).length;
26262
+ const unresolvedResults = results.filter(
26263
+ (r2) => r2.status === "conflict" || r2.patch.status === "unresolved"
26264
+ );
25670
26265
  return {
25671
26266
  flow,
25672
26267
  patchesDetected: patches.length,
@@ -25683,7 +26278,7 @@ CLI Version: ${options.cliVersion}`;
25683
26278
  patchesRefreshed: preRebaseCounts && preRebaseCounts.contentRefreshed > 0 ? preRebaseCounts.contentRefreshed : void 0,
25684
26279
  conflicts: conflictResults.flatMap((r2) => r2.fileResults?.filter((f2) => f2.status === "conflict") ?? []),
25685
26280
  conflictDetails: conflictDetails.length > 0 ? conflictDetails : void 0,
25686
- unresolvedPatches: conflictResults.length > 0 ? conflictResults.map((r2) => ({
26281
+ unresolvedPatches: unresolvedResults.length > 0 ? unresolvedResults.map((r2) => ({
25687
26282
  patchId: r2.patch.id,
25688
26283
  patchMessage: r2.patch.original_message,
25689
26284
  files: r2.patch.files,
@@ -25704,10 +26299,10 @@ CLI Version: ${options.cliVersion}`;
25704
26299
  this.outputDir = outputDir;
25705
26300
  }
25706
26301
  fernignoreExists() {
25707
- return (0, import_fs8.existsSync)((0, import_path10.join)(this.outputDir, ".fernignore"));
26302
+ return (0, import_fs8.existsSync)((0, import_path11.join)(this.outputDir, ".fernignore"));
25708
26303
  }
25709
26304
  readFernignorePatterns() {
25710
- const fernignorePath = (0, import_path10.join)(this.outputDir, ".fernignore");
26305
+ const fernignorePath = (0, import_path11.join)(this.outputDir, ".fernignore");
25711
26306
  if (!(0, import_fs8.existsSync)(fernignorePath)) {
25712
26307
  return [];
25713
26308
  }
@@ -25767,7 +26362,16 @@ CLI Version: ${options.cliVersion}`;
25767
26362
  original_author: "Fern Replay <replay@buildwithfern.com>",
25768
26363
  base_generation: currentGen.commit_sha,
25769
26364
  files: patchFiles,
25770
- patch_content: patchContent
26365
+ patch_content: patchContent,
26366
+ // Synthetic patches captured from .fernignore-protected files
26367
+ // are user-owned by definition — the customer's content
26368
+ // diverges from the generator's pristine output, and the
26369
+ // generator never produced this divergent content. Without
26370
+ // this flag, removing the file from .fernignore later (so
26371
+ // it leaves the protection check in `isFileUserOwned`) would
26372
+ // expose the patch to absorption on the next regen,
26373
+ // silently losing the customization.
26374
+ user_owned: true
25771
26375
  });
25772
26376
  trackedByBoth.push({
25773
26377
  file: pattern,
@@ -25800,7 +26404,7 @@ CLI Version: ${options.cliVersion}`;
25800
26404
  return results;
25801
26405
  }
25802
26406
  readFileContent(filePath) {
25803
- const fullPath = (0, import_path10.join)(this.outputDir, filePath);
26407
+ const fullPath = (0, import_path11.join)(this.outputDir, filePath);
25804
26408
  if (!(0, import_fs8.existsSync)(fullPath)) {
25805
26409
  return null;
25806
26410
  }
@@ -25865,7 +26469,7 @@ CLI Version: ${options.cliVersion}`;
25865
26469
  };
25866
26470
  }
25867
26471
  movePatternsToReplayYml(patterns) {
25868
- const replayYmlPath = (0, import_path10.join)(this.outputDir, ".fern", "replay.yml");
26472
+ const replayYmlPath = (0, import_path11.join)(this.outputDir, ".fern", "replay.yml");
25869
26473
  let config = {};
25870
26474
  if ((0, import_fs8.existsSync)(replayYmlPath)) {
25871
26475
  const content = (0, import_fs8.readFileSync)(replayYmlPath, "utf-8");
@@ -25874,7 +26478,7 @@ CLI Version: ${options.cliVersion}`;
25874
26478
  const existing = config.exclude ?? [];
25875
26479
  const merged = [.../* @__PURE__ */ new Set([...existing, ...patterns])];
25876
26480
  config.exclude = merged;
25877
- const dir = (0, import_path10.dirname)(replayYmlPath);
26481
+ const dir = (0, import_path11.dirname)(replayYmlPath);
25878
26482
  if (!(0, import_fs8.existsSync)(dir)) {
25879
26483
  (0, import_fs8.mkdirSync)(dir, { recursive: true });
25880
26484
  }
@@ -26378,7 +26982,7 @@ var require_cross_spawn = __commonJS({
26378
26982
  var cp2 = require("child_process");
26379
26983
  var parse3 = require_parse2();
26380
26984
  var enoent = require_enoent();
26381
- function spawn2(command, args, options) {
26985
+ function spawn3(command, args, options) {
26382
26986
  const parsed = parse3(command, args, options);
26383
26987
  const spawned = cp2.spawn(parsed.command, parsed.args, parsed.options);
26384
26988
  enoent.hookChildProcess(spawned, parsed);
@@ -26390,8 +26994,8 @@ var require_cross_spawn = __commonJS({
26390
26994
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
26391
26995
  return result;
26392
26996
  }
26393
- module2.exports = spawn2;
26394
- module2.exports.spawn = spawn2;
26997
+ module2.exports = spawn3;
26998
+ module2.exports.spawn = spawn3;
26395
26999
  module2.exports.sync = spawnSync;
26396
27000
  module2.exports._parse = parse3;
26397
27001
  module2.exports._enoent = enoent;
@@ -28430,8 +29034,8 @@ function resolve(...paths) {
28430
29034
 
28431
29035
  // src/cli.ts
28432
29036
  var import_fs15 = __toESM(require("fs"));
28433
- var import_promises11 = require("fs/promises");
28434
- var import_path18 = __toESM(require("path"));
29037
+ var import_promises13 = require("fs/promises");
29038
+ var import_path20 = __toESM(require("path"));
28435
29039
 
28436
29040
  // ../../node_modules/.pnpm/yargs@17.7.2/node_modules/yargs/build/lib/yerror.js
28437
29041
  var YError = class _YError extends Error {
@@ -38132,42 +38736,42 @@ function replayForget(params) {
38132
38736
  // src/replay/replay-init.ts
38133
38737
  init_dist3();
38134
38738
  var import_fs11 = require("fs");
38135
- var import_path13 = require("path");
38739
+ var import_path15 = require("path");
38136
38740
  var import_tmp_promise2 = __toESM(require_tmp_promise());
38137
38741
 
38138
38742
  // src/replay/fernignore.ts
38139
- var import_promises4 = require("fs/promises");
38140
- var import_path12 = require("path");
38743
+ var import_promises6 = require("fs/promises");
38744
+ var import_path14 = require("path");
38141
38745
  var REPLAY_FERNIGNORE_ENTRIES2 = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
38142
38746
  var GITATTRIBUTES_ENTRIES2 = [".fern/replay.lock linguist-generated=true"];
38143
38747
  async function ensureReplayFernignoreEntries(outputDir) {
38144
- const fernignorePath = (0, import_path12.join)(outputDir, ".fernignore");
38748
+ const fernignorePath = (0, import_path14.join)(outputDir, ".fernignore");
38145
38749
  let content = "";
38146
38750
  try {
38147
- content = await (0, import_promises4.readFile)(fernignorePath, "utf-8");
38751
+ content = await (0, import_promises6.readFile)(fernignorePath, "utf-8");
38148
38752
  } catch {
38149
38753
  }
38150
38754
  const lines = content.split("\n");
38151
38755
  const toAdd = REPLAY_FERNIGNORE_ENTRIES2.filter((entry) => !lines.some((line) => line.trim() === entry));
38152
38756
  if (toAdd.length > 0) {
38153
38757
  const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
38154
- await (0, import_promises4.writeFile)(fernignorePath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
38758
+ await (0, import_promises6.writeFile)(fernignorePath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
38155
38759
  return true;
38156
38760
  }
38157
38761
  return false;
38158
38762
  }
38159
38763
  async function ensureGitattributesEntries2(outputDir) {
38160
- const gitattributesPath = (0, import_path12.join)(outputDir, ".gitattributes");
38764
+ const gitattributesPath = (0, import_path14.join)(outputDir, ".gitattributes");
38161
38765
  let content = "";
38162
38766
  try {
38163
- content = await (0, import_promises4.readFile)(gitattributesPath, "utf-8");
38767
+ content = await (0, import_promises6.readFile)(gitattributesPath, "utf-8");
38164
38768
  } catch {
38165
38769
  }
38166
38770
  const lines = content.split("\n");
38167
38771
  const toAdd = GITATTRIBUTES_ENTRIES2.filter((entry) => !lines.some((line) => line.trim() === entry));
38168
38772
  if (toAdd.length > 0) {
38169
38773
  const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
38170
- await (0, import_promises4.writeFile)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
38774
+ await (0, import_promises6.writeFile)(gitattributesPath, content + suffix + toAdd.join("\n") + "\n", "utf-8");
38171
38775
  }
38172
38776
  }
38173
38777
 
@@ -38194,7 +38798,7 @@ async function replayInit(params) {
38194
38798
  if (dryRun) {
38195
38799
  return { bootstrap: bootstrapResult, fernignoreEntries: [], gitattributesEntries: [] };
38196
38800
  }
38197
- const lockfilePath = (0, import_path13.join)(repoPath, ".fern", "replay.lock");
38801
+ const lockfilePath = (0, import_path15.join)(repoPath, ".fern", "replay.lock");
38198
38802
  const lockfileContent = (0, import_fs11.existsSync)(lockfilePath) ? (0, import_fs11.readFileSync)(lockfilePath, "utf-8") : void 0;
38199
38803
  return {
38200
38804
  bootstrap: bootstrapResult,
@@ -38315,9 +38919,9 @@ init_dist3();
38315
38919
 
38316
38920
  // src/replay/replay-run.ts
38317
38921
  init_dist3();
38318
- var import_child_process3 = require("child_process");
38922
+ var import_child_process4 = require("child_process");
38319
38923
  var import_fs12 = require("fs");
38320
- var import_path14 = require("path");
38924
+ var import_path16 = require("path");
38321
38925
  async function replayPrepare(params) {
38322
38926
  const {
38323
38927
  outputDir,
@@ -38328,7 +38932,7 @@ async function replayPrepare(params) {
38328
38932
  skipApplication,
38329
38933
  logger
38330
38934
  } = params;
38331
- const lockfilePath = (0, import_path14.join)(outputDir, ".fern", "replay.lock");
38935
+ const lockfilePath = (0, import_path16.join)(outputDir, ".fern", "replay.lock");
38332
38936
  if (!(0, import_fs12.existsSync)(lockfilePath)) {
38333
38937
  return null;
38334
38938
  }
@@ -38460,7 +39064,7 @@ async function replayRun(params) {
38460
39064
  }
38461
39065
  function isTagMergedIntoHead(cwd2, tagSha) {
38462
39066
  try {
38463
- (0, import_child_process3.execFileSync)("git", ["merge-base", "--is-ancestor", tagSha, "HEAD"], {
39067
+ (0, import_child_process4.execFileSync)("git", ["merge-base", "--is-ancestor", tagSha, "HEAD"], {
38464
39068
  cwd: cwd2,
38465
39069
  stdio: "pipe"
38466
39070
  });
@@ -38472,7 +39076,7 @@ function isTagMergedIntoHead(cwd2, tagSha) {
38472
39076
  return false;
38473
39077
  }
38474
39078
  try {
38475
- const trees = (0, import_child_process3.execFileSync)("git", ["log", "HEAD", "--format=%T", "--max-count=500"], {
39079
+ const trees = (0, import_child_process4.execFileSync)("git", ["log", "HEAD", "--format=%T", "--max-count=500"], {
38476
39080
  cwd: cwd2,
38477
39081
  encoding: "utf-8",
38478
39082
  stdio: "pipe"
@@ -38484,7 +39088,7 @@ function isTagMergedIntoHead(cwd2, tagSha) {
38484
39088
  }
38485
39089
  function gitRevParse(cwd2, rev) {
38486
39090
  try {
38487
- return (0, import_child_process3.execFileSync)("git", ["rev-parse", "--verify", rev], {
39091
+ return (0, import_child_process4.execFileSync)("git", ["rev-parse", "--verify", rev], {
38488
39092
  cwd: cwd2,
38489
39093
  encoding: "utf-8",
38490
39094
  stdio: "pipe"
@@ -38495,7 +39099,7 @@ function gitRevParse(cwd2, rev) {
38495
39099
  }
38496
39100
  function gitDiffNameOnly(cwd2, from, to) {
38497
39101
  try {
38498
- const output = (0, import_child_process3.execFileSync)("git", ["diff", "--name-only", from, to], {
39102
+ const output = (0, import_child_process4.execFileSync)("git", ["diff", "--name-only", from, to], {
38499
39103
  cwd: cwd2,
38500
39104
  encoding: "utf-8",
38501
39105
  stdio: "pipe"
@@ -38513,29 +39117,29 @@ function replayStatus(params) {
38513
39117
  }
38514
39118
 
38515
39119
  // src/configuration/loadGitHubConfig.ts
38516
- var import_promises5 = require("fs/promises");
39120
+ var import_promises7 = require("fs/promises");
38517
39121
  async function loadGitHubConfig({
38518
39122
  absolutePathToConfig
38519
39123
  }) {
38520
- const rawContents = await (0, import_promises5.readFile)(absolutePathToConfig, "utf8");
39124
+ const rawContents = await (0, import_promises7.readFile)(absolutePathToConfig, "utf8");
38521
39125
  return JSON.parse(rawContents);
38522
39126
  }
38523
39127
 
38524
39128
  // src/configuration/loadReadmeConfig.ts
38525
- var import_promises6 = require("fs/promises");
39129
+ var import_promises8 = require("fs/promises");
38526
39130
  async function loadReadmeConfig({
38527
39131
  absolutePathToConfig
38528
39132
  }) {
38529
- const rawContents = await (0, import_promises6.readFile)(absolutePathToConfig, "utf8");
39133
+ const rawContents = await (0, import_promises8.readFile)(absolutePathToConfig, "utf8");
38530
39134
  return JSON.parse(rawContents);
38531
39135
  }
38532
39136
 
38533
39137
  // src/configuration/loadReferenceConfig.ts
38534
- var import_promises7 = require("fs/promises");
39138
+ var import_promises9 = require("fs/promises");
38535
39139
  async function loadReferenceConfig({
38536
39140
  absolutePathToConfig
38537
39141
  }) {
38538
- const rawContents = await (0, import_promises7.readFile)(absolutePathToConfig, "utf8");
39142
+ const rawContents = await (0, import_promises9.readFile)(absolutePathToConfig, "utf8");
38539
39143
  return JSON.parse(rawContents);
38540
39144
  }
38541
39145
 
@@ -38552,7 +39156,7 @@ var consolePipelineLogger = {
38552
39156
  };
38553
39157
 
38554
39158
  // src/pipeline/PostGenerationPipeline.ts
38555
- var import_child_process5 = require("child_process");
39159
+ var import_child_process6 = require("child_process");
38556
39160
 
38557
39161
  // src/pipeline/github/constants.ts
38558
39162
  var FERN_BOT_NAME2 = "fern-api";
@@ -38560,10 +39164,10 @@ var FERN_BOT_EMAIL2 = "115122769+fern-api[bot]@users.noreply.github.com";
38560
39164
  var FERN_BOT_LOGIN2 = "fern-api[bot]";
38561
39165
 
38562
39166
  // src/pipeline/steps/AutoVersionStep.ts
38563
- var import_child_process4 = require("child_process");
39167
+ var import_child_process5 = require("child_process");
38564
39168
  var import_fs14 = require("fs");
38565
- var import_promises9 = require("fs/promises");
38566
- var import_path16 = require("path");
39169
+ var import_promises11 = require("fs/promises");
39170
+ var import_path18 = require("path");
38567
39171
 
38568
39172
  // ../commons/logging-execa/lib/loggingExeca.js
38569
39173
  var import_execa = __toESM(require_execa(), 1);
@@ -38622,8 +39226,8 @@ async function loggingExeca(logger, executable, args = [], { doNotPipeOutput = f
38622
39226
 
38623
39227
  // src/autoversion/AutoVersioningService.ts
38624
39228
  var import_fs13 = require("fs");
38625
- var import_promises8 = require("fs/promises");
38626
- var import_path15 = require("path");
39229
+ var import_promises10 = require("fs/promises");
39230
+ var import_path17 = require("path");
38627
39231
  var import_semver = __toESM(require_semver2());
38628
39232
  var AutoVersioningException = class extends Error {
38629
39233
  /**
@@ -39310,7 +39914,7 @@ var AutoVersioningService = class _AutoVersioningService {
39310
39914
  );
39311
39915
  return;
39312
39916
  }
39313
- const goModPath = (0, import_path15.join)(workingDirectory, "go.mod");
39917
+ const goModPath = (0, import_path17.join)(workingDirectory, "go.mod");
39314
39918
  if (!(0, import_fs13.existsSync)(goModPath)) {
39315
39919
  this.logger.debug(`No go.mod found in ${workingDirectory}, skipping Go module path update`);
39316
39920
  return;
@@ -39344,7 +39948,7 @@ var AutoVersioningService = class _AutoVersioningService {
39344
39948
  * Reads the module path from a go.mod file.
39345
39949
  */
39346
39950
  async readGoModulePath(goModPath) {
39347
- const content = await (0, import_promises8.readFile)(goModPath, "utf-8");
39951
+ const content = await (0, import_promises10.readFile)(goModPath, "utf-8");
39348
39952
  for (const line of content.split("\n")) {
39349
39953
  const trimmed2 = line.trim();
39350
39954
  if (trimmed2.startsWith("module ")) {
@@ -39357,7 +39961,7 @@ var AutoVersioningService = class _AutoVersioningService {
39357
39961
  * Adds a version suffix to the module line in go.mod.
39358
39962
  */
39359
39963
  async addSuffixToGoMod(goModPath, modulePath, suffix) {
39360
- const content = await (0, import_promises8.readFile)(goModPath, "utf-8");
39964
+ const content = await (0, import_promises10.readFile)(goModPath, "utf-8");
39361
39965
  const lines = content.split("\n");
39362
39966
  let changed = false;
39363
39967
  for (let i = 0; i < lines.length; i++) {
@@ -39369,7 +39973,7 @@ var AutoVersioningService = class _AutoVersioningService {
39369
39973
  }
39370
39974
  }
39371
39975
  if (changed) {
39372
- await (0, import_promises8.writeFile)(goModPath, lines.join("\n"), "utf-8");
39976
+ await (0, import_promises10.writeFile)(goModPath, lines.join("\n"), "utf-8");
39373
39977
  this.logger.debug(`Updated go.mod: added ${suffix} to module path`);
39374
39978
  }
39375
39979
  }
@@ -39378,10 +39982,10 @@ var AutoVersioningService = class _AutoVersioningService {
39378
39982
  */
39379
39983
  async walkDirectory(dir, filter2) {
39380
39984
  const results = [];
39381
- const entries2 = await (0, import_promises8.readdir)(dir);
39985
+ const entries2 = await (0, import_promises10.readdir)(dir);
39382
39986
  for (const entry of entries2) {
39383
- const fullPath = (0, import_path15.join)(dir, entry);
39384
- const entryStat = await (0, import_promises8.stat)(fullPath);
39987
+ const fullPath = (0, import_path17.join)(dir, entry);
39988
+ const entryStat = await (0, import_promises10.stat)(fullPath);
39385
39989
  if (entryStat.isDirectory()) {
39386
39990
  if (entry === ".git" || entry === "vendor") {
39387
39991
  continue;
@@ -39398,7 +40002,7 @@ var AutoVersioningService = class _AutoVersioningService {
39398
40002
  * Adds a version suffix to import paths in all .go files in the repository.
39399
40003
  */
39400
40004
  async addSuffixToGoFiles(repoRoot, modulePath, suffix) {
39401
- const goFiles = await this.walkDirectory(repoRoot, (filePath) => (0, import_path15.extname)(filePath) === ".go");
40005
+ const goFiles = await this.walkDirectory(repoRoot, (filePath) => (0, import_path17.extname)(filePath) === ".go");
39402
40006
  for (const filePath of goFiles) {
39403
40007
  await this.addSuffixToGoFile(filePath, modulePath, suffix);
39404
40008
  }
@@ -39408,7 +40012,7 @@ var AutoVersioningService = class _AutoVersioningService {
39408
40012
  * Replaces occurrences of the module path with modulePath + suffix in import statements.
39409
40013
  */
39410
40014
  async addSuffixToGoFile(filePath, modulePath, suffix) {
39411
- const content = await (0, import_promises8.readFile)(filePath, "utf-8");
40015
+ const content = await (0, import_promises10.readFile)(filePath, "utf-8");
39412
40016
  const lines = content.split("\n");
39413
40017
  let changed = false;
39414
40018
  let inImportBlock = false;
@@ -39437,7 +40041,7 @@ var AutoVersioningService = class _AutoVersioningService {
39437
40041
  }
39438
40042
  }
39439
40043
  if (changed) {
39440
- await (0, import_promises8.writeFile)(filePath, lines.join("\n"), "utf-8");
40044
+ await (0, import_promises10.writeFile)(filePath, lines.join("\n"), "utf-8");
39441
40045
  this.logger.debug(`Updated Go imports in ${filePath}`);
39442
40046
  }
39443
40047
  }
@@ -39800,7 +40404,7 @@ var AutoVersionStep = class extends BaseStep {
39800
40404
  }
39801
40405
  async readVersionFromMetadata() {
39802
40406
  try {
39803
- const output = (0, import_child_process4.execFileSync)("git", ["show", "HEAD~1:.fern/metadata.json"], {
40407
+ const output = (0, import_child_process5.execFileSync)("git", ["show", "HEAD~1:.fern/metadata.json"], {
39804
40408
  cwd: this.outputDir,
39805
40409
  encoding: "utf-8",
39806
40410
  stdio: "pipe"
@@ -39816,7 +40420,7 @@ var AutoVersionStep = class extends BaseStep {
39816
40420
  return mappedMagicVersion.startsWith("v") ? `v${stripped}` : stripped;
39817
40421
  }
39818
40422
  gitDiff(from, to) {
39819
- return (0, import_child_process4.execFileSync)("git", ["diff", from, to, "--", ".", ":(exclude).fern/metadata.json"], {
40423
+ return (0, import_child_process5.execFileSync)("git", ["diff", from, to, "--", ".", ":(exclude).fern/metadata.json"], {
39820
40424
  cwd: this.outputDir,
39821
40425
  encoding: "utf-8",
39822
40426
  stdio: "pipe",
@@ -39829,12 +40433,12 @@ var AutoVersionStep = class extends BaseStep {
39829
40433
  const fullMessage = body.length > 0 ? `${COMMIT_MARKER} ${subject}
39830
40434
 
39831
40435
  ${body}` : `${COMMIT_MARKER} ${subject}`;
39832
- (0, import_child_process4.execFileSync)("git", ["add", "-A"], { cwd: this.outputDir, stdio: "pipe" });
39833
- (0, import_child_process4.execFileSync)("git", ["commit", "--allow-empty", "-m", fullMessage], {
40436
+ (0, import_child_process5.execFileSync)("git", ["add", "-A"], { cwd: this.outputDir, stdio: "pipe" });
40437
+ (0, import_child_process5.execFileSync)("git", ["commit", "--allow-empty", "-m", fullMessage], {
39834
40438
  cwd: this.outputDir,
39835
40439
  stdio: "pipe"
39836
40440
  });
39837
- return (0, import_child_process4.execFileSync)("git", ["rev-parse", "HEAD"], {
40441
+ return (0, import_child_process5.execFileSync)("git", ["rev-parse", "HEAD"], {
39838
40442
  cwd: this.outputDir,
39839
40443
  encoding: "utf-8",
39840
40444
  stdio: "pipe"
@@ -39842,7 +40446,7 @@ ${body}` : `${COMMIT_MARKER} ${subject}`;
39842
40446
  }
39843
40447
  async prependChangelogEntry(params) {
39844
40448
  const { version: version2, entry } = params;
39845
- const changelogPath = (0, import_path16.join)(this.outputDir, "changelog.md");
40449
+ const changelogPath = (0, import_path18.join)(this.outputDir, "changelog.md");
39846
40450
  const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
39847
40451
  const header = `## [${version2}] - ${now}
39848
40452
  `;
@@ -39851,7 +40455,7 @@ ${body}` : `${COMMIT_MARKER} ${subject}`;
39851
40455
  `;
39852
40456
  let existing = "";
39853
40457
  if ((0, import_fs14.existsSync)(changelogPath)) {
39854
- existing = await (0, import_promises9.readFile)(changelogPath, "utf-8");
40458
+ existing = await (0, import_promises11.readFile)(changelogPath, "utf-8");
39855
40459
  }
39856
40460
  let output;
39857
40461
  if (existing.trim().length === 0) {
@@ -39868,7 +40472,7 @@ ${newBlock}${remainder}`;
39868
40472
  } else {
39869
40473
  output = `${newBlock}${existing}`;
39870
40474
  }
39871
- await (0, import_promises9.writeFile)(changelogPath, output, "utf-8");
40475
+ await (0, import_promises11.writeFile)(changelogPath, output, "utf-8");
39872
40476
  }
39873
40477
  brandMessage(message) {
39874
40478
  if (this.config.isWhitelabel) {
@@ -40050,8 +40654,8 @@ var GenerationCommitStep = class extends BaseStep {
40050
40654
  };
40051
40655
 
40052
40656
  // src/pipeline/steps/GithubStep.ts
40053
- var import_promises10 = require("fs/promises");
40054
- var import_path17 = require("path");
40657
+ var import_promises12 = require("fs/promises");
40658
+ var import_path19 = require("path");
40055
40659
 
40056
40660
  // src/pipeline/github/createReplayBranch.ts
40057
40661
  async function createReplayBranch(repository, branchName, commitMessage, replayConflictInfo, logger) {
@@ -40668,13 +41272,13 @@ var GithubStep = class extends BaseStep {
40668
41272
  }
40669
41273
  async ensureFernignore() {
40670
41274
  this.logger.debug("Checking for .fernignore file...");
40671
- const fernignorePath = (0, import_path17.join)(this.outputDir, ".fernignore");
41275
+ const fernignorePath = (0, import_path19.join)(this.outputDir, ".fernignore");
40672
41276
  try {
40673
- await (0, import_promises10.access)(fernignorePath);
41277
+ await (0, import_promises12.access)(fernignorePath);
40674
41278
  this.logger.debug(".fernignore already exists");
40675
41279
  } catch {
40676
41280
  this.logger.debug("Creating .fernignore file...");
40677
- await (0, import_promises10.writeFile)(fernignorePath, "# Specify files that shouldn't be modified by Fern\n", "utf-8");
41281
+ await (0, import_promises12.writeFile)(fernignorePath, "# Specify files that shouldn't be modified by Fern\n", "utf-8");
40678
41282
  }
40679
41283
  }
40680
41284
  deriveSkipCommit(replayResult) {
@@ -40896,8 +41500,8 @@ var PostGenerationPipeline = class {
40896
41500
  async run() {
40897
41501
  if (this.config.github?.enabled) {
40898
41502
  try {
40899
- (0, import_child_process5.execFileSync)("git", ["config", "user.name", FERN_BOT_NAME2], { cwd: this.config.outputDir });
40900
- (0, import_child_process5.execFileSync)("git", ["config", "user.email", FERN_BOT_EMAIL2], { cwd: this.config.outputDir });
41503
+ (0, import_child_process6.execFileSync)("git", ["config", "user.name", FERN_BOT_NAME2], { cwd: this.config.outputDir });
41504
+ (0, import_child_process6.execFileSync)("git", ["config", "user.email", FERN_BOT_EMAIL2], { cwd: this.config.outputDir });
40901
41505
  } catch {
40902
41506
  }
40903
41507
  }
@@ -40967,7 +41571,7 @@ void yargs_default(hideBin(process.argv)).scriptName(process.env.CLI_NAME ?? "ge
40967
41571
  const readmeConfig = await loadReadmeConfig({
40968
41572
  absolutePathToConfig: resolve(wd, argv.config)
40969
41573
  });
40970
- const originalReadmeContent = argv.originalReadme != null ? await (0, import_promises11.readFile)(argv.originalReadme, "utf8") : void 0;
41574
+ const originalReadmeContent = argv.originalReadme != null ? await (0, import_promises13.readFile)(argv.originalReadme, "utf8") : void 0;
40971
41575
  await generateReadmeToStream({
40972
41576
  originalReadmeContent,
40973
41577
  readmeConfig,
@@ -41095,7 +41699,7 @@ void yargs_default(hideBin(process.argv)).scriptName(process.env.CLI_NAME ?? "ge
41095
41699
  try {
41096
41700
  const wd = cwd();
41097
41701
  const configValue = argv.config;
41098
- const configContent = configValue === STDIN_MARKER ? await readStdin() : configValue.trimStart().startsWith("{") ? configValue : await (0, import_promises11.readFile)(resolve(wd, configValue), "utf-8");
41702
+ const configContent = configValue === STDIN_MARKER ? await readStdin() : configValue.trimStart().startsWith("{") ? configValue : await (0, import_promises13.readFile)(resolve(wd, configValue), "utf-8");
41099
41703
  let config;
41100
41704
  try {
41101
41705
  config = JSON.parse(configContent);
@@ -41391,7 +41995,7 @@ async function createWriteStream(outputPath) {
41391
41995
  }
41392
41996
  async function createWriteStreamFromFile(filepath) {
41393
41997
  if (!await doesPathExist(filepath)) {
41394
- await (0, import_promises11.mkdir)(import_path18.default.dirname(filepath), { recursive: true });
41998
+ await (0, import_promises13.mkdir)(import_path20.default.dirname(filepath), { recursive: true });
41395
41999
  }
41396
42000
  return import_fs15.default.createWriteStream(filepath);
41397
42001
  }