@fern-api/generator-cli 0.9.19 → 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 +767 -123
  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.12.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.12.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) => {
@@ -23457,17 +23868,57 @@ var init_dist3 = __esm({
23457
23868
  }
23458
23869
  const isAncestor = await this.git.isAncestor(lastGen.commit_sha, "HEAD");
23459
23870
  if (!isAncestor) {
23871
+ return this.detectPatchesViaMergeBase(lastGen, lock);
23872
+ }
23873
+ return this.detectPatchesInRange(lastGen.commit_sha, lastGen, lock);
23874
+ }
23875
+ /**
23876
+ * FER-10201 — Non-linear-history detection via `merge-base(prevGen, HEAD)`.
23877
+ *
23878
+ * After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
23879
+ * `[fern-generated]`) is orphaned but still in git's object database. The
23880
+ * merge-base is the commit on main from which the bot's branch was created —
23881
+ * a reachable ancestor of HEAD. Walking that range and classifying each
23882
+ * commit via `isGenerationCommit` mirrors the linear path and eliminates
23883
+ * the bug class where pipeline-driven changes (autoversion, replay,
23884
+ * generation) get bundled as customer customizations.
23885
+ *
23886
+ * Falls through to the legacy composite path when no common ancestor exists
23887
+ * (truly disjoint history — orphan branches with no shared root).
23888
+ */
23889
+ async detectPatchesViaMergeBase(lastGen, lock) {
23890
+ let mergeBase = "";
23891
+ try {
23892
+ mergeBase = (await this.git.exec(["merge-base", lastGen.commit_sha, "HEAD"])).trim();
23893
+ } catch {
23894
+ }
23895
+ if (!mergeBase) {
23896
+ this.warnings.push(
23897
+ `No common ancestor between previous generation ${lastGen.commit_sha.slice(0, 7)} and HEAD. Falling back to composite tree-diff detection.`
23898
+ );
23460
23899
  return this.detectPatchesViaTreeDiff(
23461
23900
  lastGen,
23462
23901
  /* commitKnownMissing */
23463
23902
  false
23464
23903
  );
23465
23904
  }
23905
+ return this.detectPatchesInRange(mergeBase, lastGen, lock);
23906
+ }
23907
+ /**
23908
+ * FER-10201 — Walk `${rangeStart}..HEAD`, classify each commit, emit one
23909
+ * `StoredPatch` per customer commit. Body shared by the linear path
23910
+ * (rangeStart = lastGen.commit_sha) and the merge-base path.
23911
+ *
23912
+ * Patch `base_generation` is always `lastGen.commit_sha` — the
23913
+ * lockfile-tracked reference the applicator uses to fetch base file
23914
+ * content, regardless of where the walk actually started.
23915
+ */
23916
+ async detectPatchesInRange(rangeStart, lastGen, lock) {
23466
23917
  const log = await this.git.exec([
23467
23918
  "log",
23468
23919
  "--first-parent",
23469
23920
  "--format=%H%x00%an%x00%ae%x00%s",
23470
- `${lastGen.commit_sha}..HEAD`,
23921
+ `${rangeStart}..HEAD`,
23471
23922
  "--",
23472
23923
  this.sdkOutputDir
23473
23924
  ]);
@@ -23981,6 +24432,19 @@ var init_dist3 = __esm({
23981
24432
  renameCache = /* @__PURE__ */ new Map();
23982
24433
  treeExistsCache = /* @__PURE__ */ new Map();
23983
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";
23984
24448
  constructor(git, lockManager, outputDir) {
23985
24449
  this.git = git;
23986
24450
  this.lockManager = lockManager;
@@ -24028,8 +24492,23 @@ var init_dist3 = __esm({
24028
24492
  /**
24029
24493
  * Apply all patches, returning results for each.
24030
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.
24031
24508
  */
24032
- async applyPatches(patches) {
24509
+ async applyPatches(patches, opts) {
24510
+ const applyMode = opts?.applyMode ?? "replay";
24511
+ this.currentApplyMode = applyMode;
24033
24512
  this.resetAccumulator();
24034
24513
  const results = [];
24035
24514
  for (let i = 0; i < patches.length; i++) {
@@ -24044,7 +24523,7 @@ var init_dist3 = __esm({
24044
24523
  }
24045
24524
  const result = await this.applyPatchWithFallback(patch);
24046
24525
  results.push(result);
24047
- if (result.status === "conflict" && result.fileResults) {
24526
+ if (applyMode !== "resolve" && result.status === "conflict" && result.fileResults) {
24048
24527
  const laterFiles = /* @__PURE__ */ new Set();
24049
24528
  for (let j2 = i + 1; j2 < patches.length; j2++) {
24050
24529
  for (const f2 of patches[j2].files) {
@@ -24074,9 +24553,17 @@ var init_dist3 = __esm({
24074
24553
  }
24075
24554
  return results;
24076
24555
  }
24077
- /** 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
+ */
24078
24564
  async populateAccumulatorForPatch(patch, baseGen, currentTreeHash) {
24079
- if (!baseGen) return;
24565
+ const fileResults = [];
24566
+ if (!baseGen) return fileResults;
24080
24567
  const tempDir = await (0, import_promises3.mkdtemp)((0, import_path8.join)((0, import_os.tmpdir)(), "replay-acc-"));
24081
24568
  const { GitClient: GitClient2 } = await Promise.resolve().then(() => (init_GitClient(), GitClient_exports));
24082
24569
  const tempGit = new GitClient2(tempDir);
@@ -24089,18 +24576,30 @@ var init_dist3 = __esm({
24089
24576
  const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
24090
24577
  const base = await this.git.showFile(baseGen.tree_hash, filePath);
24091
24578
  const theirs = await this.applyPatchToContent(base, patch.patch_content, filePath, tempGit, tempDir);
24092
- 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;
24093
24581
  if (effectiveTheirs != null) {
24094
24582
  this.fileTheirsAccumulator.set(resolvedPath, {
24095
24583
  content: effectiveTheirs,
24096
24584
  baseGeneration: patch.base_generation
24097
24585
  });
24098
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
+ }
24099
24597
  }
24100
24598
  } finally {
24101
24599
  await (0, import_promises3.rm)(tempDir, { recursive: true }).catch(() => {
24102
24600
  });
24103
24601
  }
24602
+ return fileResults;
24104
24603
  }
24105
24604
  async applyPatchWithFallback(patch) {
24106
24605
  const baseGen = await this.resolveBaseGeneration(patch.base_generation);
@@ -24131,11 +24630,12 @@ var init_dist3 = __esm({
24131
24630
  }
24132
24631
  try {
24133
24632
  await this.git.execWithInput(["apply", "--3way"], patch.patch_content);
24134
- await this.populateAccumulatorForPatch(patch, baseGen, currentTreeHash);
24633
+ const fastPathFileResults = await this.populateAccumulatorForPatch(patch, baseGen, currentTreeHash);
24135
24634
  return {
24136
24635
  patch,
24137
24636
  status: "applied",
24138
24637
  method: "git-am",
24638
+ ...fastPathFileResults.length > 0 && { fileResults: fastPathFileResults },
24139
24639
  ...Object.keys(resolvedFiles).length > 0 && { resolvedFiles }
24140
24640
  };
24141
24641
  } catch {
@@ -24247,19 +24747,23 @@ var init_dist3 = __esm({
24247
24747
  renameSourcePath
24248
24748
  );
24249
24749
  }
24750
+ const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
24250
24751
  if (theirs) {
24251
24752
  const theirsHasMarkers = theirs.includes("<<<<<<< Generated") || theirs.includes(">>>>>>> Your customization");
24252
24753
  const baseHasMarkers = base != null && (base.includes("<<<<<<< Generated") || base.includes(">>>>>>> Your customization"));
24253
24754
  if (theirsHasMarkers && !baseHasMarkers) {
24254
- return {
24255
- file: resolvedPath,
24256
- status: "skipped",
24257
- reason: "stale-conflict-markers"
24258
- };
24755
+ if (accumulatorEntry) {
24756
+ theirs = null;
24757
+ } else {
24758
+ return {
24759
+ file: resolvedPath,
24760
+ status: "skipped",
24761
+ reason: "stale-conflict-markers"
24762
+ };
24763
+ }
24259
24764
  }
24260
24765
  }
24261
24766
  let useAccumulatorAsMergeBase = false;
24262
- const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
24263
24767
  if (accumulatorEntry && (theirs == null || base == null)) {
24264
24768
  theirs = await this.applyPatchToContent(
24265
24769
  accumulatorEntry.content,
@@ -24366,7 +24870,8 @@ var init_dist3 = __esm({
24366
24870
  const outDir = (0, import_path8.dirname)(oursPath);
24367
24871
  await (0, import_promises3.mkdir)(outDir, { recursive: true });
24368
24872
  await (0, import_promises3.writeFile)(oursPath, merged.content);
24369
- if (effective_theirs != null && !merged.hasConflicts) {
24873
+ const populateAccumulator = effective_theirs != null && (!merged.hasConflicts || this.currentApplyMode === "resolve");
24874
+ if (populateAccumulator) {
24370
24875
  this.fileTheirsAccumulator.set(resolvedPath, {
24371
24876
  content: effective_theirs,
24372
24877
  baseGeneration: patch.base_generation
@@ -24381,7 +24886,11 @@ var init_dist3 = __esm({
24381
24886
  conflictMetadata: metadata
24382
24887
  };
24383
24888
  }
24384
- return { file: resolvedPath, status: "merged" };
24889
+ return {
24890
+ file: resolvedPath,
24891
+ status: "merged",
24892
+ ...merged.droppedContextLines && merged.droppedContextLines.length > 0 ? { droppedContextLines: merged.droppedContextLines } : {}
24893
+ };
24385
24894
  } catch (error) {
24386
24895
  return {
24387
24896
  file: filePath,
@@ -24592,13 +25101,46 @@ CLI Version: ${options.cliVersion}`;
24592
25101
  await this.git.exec(["commit", "-m", fullMessage]);
24593
25102
  return (await this.git.exec(["rev-parse", "HEAD"])).trim();
24594
25103
  }
24595
- async commitReplay(_patchCount, patches, message) {
25104
+ async commitReplay(_patchCount, patches, message, options) {
24596
25105
  await this.stageAll();
24597
25106
  if (!await this.hasStagedChanges()) {
24598
25107
  return (await this.git.exec(["rev-parse", "HEAD"])).trim();
24599
25108
  }
24600
25109
  let fullMessage = message ?? `[fern-replay] Applied customizations`;
24601
- 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) {
24602
25144
  fullMessage += "\n\nPatches replayed:";
24603
25145
  for (const patch of patches) {
24604
25146
  fullMessage += `
@@ -24632,6 +25174,7 @@ CLI Version: ${options.cliVersion}`;
24632
25174
  }
24633
25175
  };
24634
25176
  init_GitClient();
25177
+ init_HybridReconstruction();
24635
25178
  ReplayService = class {
24636
25179
  git;
24637
25180
  detector;
@@ -25025,6 +25568,7 @@ CLI Version: ${options.cliVersion}`;
25025
25568
  if (newPatches.length > 0) {
25026
25569
  results = await this.applicator.applyPatches(newPatches);
25027
25570
  this._lastApplyResults = results;
25571
+ this.recordDroppedContextLineWarnings(results);
25028
25572
  this.revertConflictingFiles(results);
25029
25573
  for (const result of results) {
25030
25574
  if (result.status === "conflict") {
@@ -25043,7 +25587,8 @@ CLI Version: ${options.cliVersion}`;
25043
25587
  if (newPatches.length > 0) {
25044
25588
  if (!options?.stageOnly) {
25045
25589
  const appliedCount = results.filter((r2) => r2.status === "applied").length;
25046
- await this.committer.commitReplay(appliedCount, newPatches);
25590
+ const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, newPatches);
25591
+ await this.committer.commitReplay(appliedCount, newPatches, void 0, { buckets });
25047
25592
  } else {
25048
25593
  await this.committer.stageAll();
25049
25594
  }
@@ -25106,12 +25651,21 @@ CLI Version: ${options.cliVersion}`;
25106
25651
  (p) => preRebasePatchIds.has(p.id) && !postRebasePatchIds.has(p.id)
25107
25652
  );
25108
25653
  existingPatches = this.lockManager.getPatches();
25109
- const seenHashes = /* @__PURE__ */ new Set();
25654
+ const seenHashes = /* @__PURE__ */ new Map();
25110
25655
  for (const p of existingPatches) {
25111
- if (seenHashes.has(p.content_hash)) {
25112
- 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
+ }
25113
25667
  } else {
25114
- seenHashes.add(p.content_hash);
25668
+ seenHashes.set(p.content_hash, p.original_commit);
25115
25669
  }
25116
25670
  }
25117
25671
  existingPatches = this.lockManager.getPatches();
@@ -25172,6 +25726,7 @@ CLI Version: ${options.cliVersion}`;
25172
25726
  const genSha = prep._prepared.genSha;
25173
25727
  const results = await this.applicator.applyPatches(allPatches);
25174
25728
  this._lastApplyResults = results;
25729
+ this.recordDroppedContextLineWarnings(results);
25175
25730
  this.revertConflictingFiles(results);
25176
25731
  for (const result of results) {
25177
25732
  if (result.status === "conflict") {
@@ -25199,7 +25754,8 @@ CLI Version: ${options.cliVersion}`;
25199
25754
  await this.committer.stageAll();
25200
25755
  } else {
25201
25756
  const appliedCount = results.filter((r2) => r2.status === "applied").length;
25202
- await this.committer.commitReplay(appliedCount, allPatches);
25757
+ const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, allPatches);
25758
+ await this.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
25203
25759
  }
25204
25760
  const warnings = [...detectorWarnings, ...this.warnings];
25205
25761
  return this.buildReport(
@@ -25223,7 +25779,7 @@ CLI Version: ${options.cliVersion}`;
25223
25779
  let repointed = 0;
25224
25780
  let contentRebased = 0;
25225
25781
  let keptAsUserOwned = 0;
25226
- const seenContentHashes = /* @__PURE__ */ new Set();
25782
+ const seenContentHashes = /* @__PURE__ */ new Map();
25227
25783
  const absorbedPatchIds = /* @__PURE__ */ new Set();
25228
25784
  const fernignorePatterns = this.readFernignorePatterns();
25229
25785
  for (const result of results) {
@@ -25237,6 +25793,23 @@ CLI Version: ${options.cliVersion}`;
25237
25793
  }
25238
25794
  }
25239
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
+ }
25240
25813
  for (const result of results) {
25241
25814
  if (result.status === "conflict" && result.fileResults) {
25242
25815
  await this.trimAbsorbedFiles(result, currentGenSha);
@@ -25304,6 +25877,18 @@ CLI Version: ${options.cliVersion}`;
25304
25877
  keptAsUserOwned++;
25305
25878
  continue;
25306
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
+ }
25307
25892
  this.lockManager.removePatch(patch.id);
25308
25893
  absorbedPatchIds.add(patch.id);
25309
25894
  absorbed++;
@@ -25319,13 +25904,22 @@ CLI Version: ${options.cliVersion}`;
25319
25904
  continue;
25320
25905
  }
25321
25906
  const newContentHash = this.detector.computeContentHash(diff);
25322
- if (seenContentHashes.has(newContentHash)) {
25323
- this.lockManager.removePatch(patch.id);
25324
- absorbedPatchIds.add(patch.id);
25325
- absorbed++;
25326
- 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);
25327
25922
  }
25328
- seenContentHashes.add(newContentHash);
25329
25923
  patch.base_generation = currentGenSha;
25330
25924
  patch.patch_content = diff;
25331
25925
  patch.content_hash = newContentHash;
@@ -25501,8 +26095,14 @@ CLI Version: ${options.cliVersion}`;
25501
26095
  }
25502
26096
  if (patch.base_generation === currentGen) {
25503
26097
  try {
25504
- const diff = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch.files]).catch(() => null);
25505
- 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;
25506
26106
  if (!diff.trim()) {
25507
26107
  if (await allPatchFilesUserOwned(patch)) continue;
25508
26108
  this.lockManager.removePatch(patch.id);
@@ -25544,8 +26144,14 @@ CLI Version: ${options.cliVersion}`;
25544
26144
  try {
25545
26145
  const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<< Generated", "HEAD", "--", ...patch.files]).catch(() => "");
25546
26146
  if (markerFiles.trim()) continue;
25547
- const diff = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch.files]).catch(() => null);
25548
- 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;
25549
26155
  if (!diff.trim()) {
25550
26156
  if (await allPatchFilesUserOwned(patch)) {
25551
26157
  try {
@@ -25570,6 +26176,32 @@ CLI Version: ${options.cliVersion}`;
25570
26176
  }
25571
26177
  return { conflictResolved, conflictAbsorbed, contentRefreshed };
25572
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
+ }
25573
26205
  /**
25574
26206
  * After applyPatches(), strip conflict markers from conflicting files
25575
26207
  * so only clean content is committed. Keeps the Generated (OURS) side.
@@ -25627,6 +26259,9 @@ CLI Version: ${options.cliVersion}`;
25627
26259
  };
25628
26260
  }).filter((d) => d.files.length > 0);
25629
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
+ );
25630
26265
  return {
25631
26266
  flow,
25632
26267
  patchesDetected: patches.length,
@@ -25643,7 +26278,7 @@ CLI Version: ${options.cliVersion}`;
25643
26278
  patchesRefreshed: preRebaseCounts && preRebaseCounts.contentRefreshed > 0 ? preRebaseCounts.contentRefreshed : void 0,
25644
26279
  conflicts: conflictResults.flatMap((r2) => r2.fileResults?.filter((f2) => f2.status === "conflict") ?? []),
25645
26280
  conflictDetails: conflictDetails.length > 0 ? conflictDetails : void 0,
25646
- unresolvedPatches: conflictResults.length > 0 ? conflictResults.map((r2) => ({
26281
+ unresolvedPatches: unresolvedResults.length > 0 ? unresolvedResults.map((r2) => ({
25647
26282
  patchId: r2.patch.id,
25648
26283
  patchMessage: r2.patch.original_message,
25649
26284
  files: r2.patch.files,
@@ -25664,10 +26299,10 @@ CLI Version: ${options.cliVersion}`;
25664
26299
  this.outputDir = outputDir;
25665
26300
  }
25666
26301
  fernignoreExists() {
25667
- 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"));
25668
26303
  }
25669
26304
  readFernignorePatterns() {
25670
- const fernignorePath = (0, import_path10.join)(this.outputDir, ".fernignore");
26305
+ const fernignorePath = (0, import_path11.join)(this.outputDir, ".fernignore");
25671
26306
  if (!(0, import_fs8.existsSync)(fernignorePath)) {
25672
26307
  return [];
25673
26308
  }
@@ -25727,7 +26362,16 @@ CLI Version: ${options.cliVersion}`;
25727
26362
  original_author: "Fern Replay <replay@buildwithfern.com>",
25728
26363
  base_generation: currentGen.commit_sha,
25729
26364
  files: patchFiles,
25730
- 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
25731
26375
  });
25732
26376
  trackedByBoth.push({
25733
26377
  file: pattern,
@@ -25760,7 +26404,7 @@ CLI Version: ${options.cliVersion}`;
25760
26404
  return results;
25761
26405
  }
25762
26406
  readFileContent(filePath) {
25763
- const fullPath = (0, import_path10.join)(this.outputDir, filePath);
26407
+ const fullPath = (0, import_path11.join)(this.outputDir, filePath);
25764
26408
  if (!(0, import_fs8.existsSync)(fullPath)) {
25765
26409
  return null;
25766
26410
  }
@@ -25825,7 +26469,7 @@ CLI Version: ${options.cliVersion}`;
25825
26469
  };
25826
26470
  }
25827
26471
  movePatternsToReplayYml(patterns) {
25828
- const replayYmlPath = (0, import_path10.join)(this.outputDir, ".fern", "replay.yml");
26472
+ const replayYmlPath = (0, import_path11.join)(this.outputDir, ".fern", "replay.yml");
25829
26473
  let config = {};
25830
26474
  if ((0, import_fs8.existsSync)(replayYmlPath)) {
25831
26475
  const content = (0, import_fs8.readFileSync)(replayYmlPath, "utf-8");
@@ -25834,7 +26478,7 @@ CLI Version: ${options.cliVersion}`;
25834
26478
  const existing = config.exclude ?? [];
25835
26479
  const merged = [.../* @__PURE__ */ new Set([...existing, ...patterns])];
25836
26480
  config.exclude = merged;
25837
- const dir = (0, import_path10.dirname)(replayYmlPath);
26481
+ const dir = (0, import_path11.dirname)(replayYmlPath);
25838
26482
  if (!(0, import_fs8.existsSync)(dir)) {
25839
26483
  (0, import_fs8.mkdirSync)(dir, { recursive: true });
25840
26484
  }
@@ -26338,7 +26982,7 @@ var require_cross_spawn = __commonJS({
26338
26982
  var cp2 = require("child_process");
26339
26983
  var parse3 = require_parse2();
26340
26984
  var enoent = require_enoent();
26341
- function spawn2(command, args, options) {
26985
+ function spawn3(command, args, options) {
26342
26986
  const parsed = parse3(command, args, options);
26343
26987
  const spawned = cp2.spawn(parsed.command, parsed.args, parsed.options);
26344
26988
  enoent.hookChildProcess(spawned, parsed);
@@ -26350,8 +26994,8 @@ var require_cross_spawn = __commonJS({
26350
26994
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
26351
26995
  return result;
26352
26996
  }
26353
- module2.exports = spawn2;
26354
- module2.exports.spawn = spawn2;
26997
+ module2.exports = spawn3;
26998
+ module2.exports.spawn = spawn3;
26355
26999
  module2.exports.sync = spawnSync;
26356
27000
  module2.exports._parse = parse3;
26357
27001
  module2.exports._enoent = enoent;
@@ -28390,8 +29034,8 @@ function resolve(...paths) {
28390
29034
 
28391
29035
  // src/cli.ts
28392
29036
  var import_fs15 = __toESM(require("fs"));
28393
- var import_promises11 = require("fs/promises");
28394
- var import_path18 = __toESM(require("path"));
29037
+ var import_promises13 = require("fs/promises");
29038
+ var import_path20 = __toESM(require("path"));
28395
29039
 
28396
29040
  // ../../node_modules/.pnpm/yargs@17.7.2/node_modules/yargs/build/lib/yerror.js
28397
29041
  var YError = class _YError extends Error {
@@ -38092,42 +38736,42 @@ function replayForget(params) {
38092
38736
  // src/replay/replay-init.ts
38093
38737
  init_dist3();
38094
38738
  var import_fs11 = require("fs");
38095
- var import_path13 = require("path");
38739
+ var import_path15 = require("path");
38096
38740
  var import_tmp_promise2 = __toESM(require_tmp_promise());
38097
38741
 
38098
38742
  // src/replay/fernignore.ts
38099
- var import_promises4 = require("fs/promises");
38100
- var import_path12 = require("path");
38743
+ var import_promises6 = require("fs/promises");
38744
+ var import_path14 = require("path");
38101
38745
  var REPLAY_FERNIGNORE_ENTRIES2 = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
38102
38746
  var GITATTRIBUTES_ENTRIES2 = [".fern/replay.lock linguist-generated=true"];
38103
38747
  async function ensureReplayFernignoreEntries(outputDir) {
38104
- const fernignorePath = (0, import_path12.join)(outputDir, ".fernignore");
38748
+ const fernignorePath = (0, import_path14.join)(outputDir, ".fernignore");
38105
38749
  let content = "";
38106
38750
  try {
38107
- content = await (0, import_promises4.readFile)(fernignorePath, "utf-8");
38751
+ content = await (0, import_promises6.readFile)(fernignorePath, "utf-8");
38108
38752
  } catch {
38109
38753
  }
38110
38754
  const lines = content.split("\n");
38111
38755
  const toAdd = REPLAY_FERNIGNORE_ENTRIES2.filter((entry) => !lines.some((line) => line.trim() === entry));
38112
38756
  if (toAdd.length > 0) {
38113
38757
  const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
38114
- 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");
38115
38759
  return true;
38116
38760
  }
38117
38761
  return false;
38118
38762
  }
38119
38763
  async function ensureGitattributesEntries2(outputDir) {
38120
- const gitattributesPath = (0, import_path12.join)(outputDir, ".gitattributes");
38764
+ const gitattributesPath = (0, import_path14.join)(outputDir, ".gitattributes");
38121
38765
  let content = "";
38122
38766
  try {
38123
- content = await (0, import_promises4.readFile)(gitattributesPath, "utf-8");
38767
+ content = await (0, import_promises6.readFile)(gitattributesPath, "utf-8");
38124
38768
  } catch {
38125
38769
  }
38126
38770
  const lines = content.split("\n");
38127
38771
  const toAdd = GITATTRIBUTES_ENTRIES2.filter((entry) => !lines.some((line) => line.trim() === entry));
38128
38772
  if (toAdd.length > 0) {
38129
38773
  const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
38130
- 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");
38131
38775
  }
38132
38776
  }
38133
38777
 
@@ -38154,7 +38798,7 @@ async function replayInit(params) {
38154
38798
  if (dryRun) {
38155
38799
  return { bootstrap: bootstrapResult, fernignoreEntries: [], gitattributesEntries: [] };
38156
38800
  }
38157
- const lockfilePath = (0, import_path13.join)(repoPath, ".fern", "replay.lock");
38801
+ const lockfilePath = (0, import_path15.join)(repoPath, ".fern", "replay.lock");
38158
38802
  const lockfileContent = (0, import_fs11.existsSync)(lockfilePath) ? (0, import_fs11.readFileSync)(lockfilePath, "utf-8") : void 0;
38159
38803
  return {
38160
38804
  bootstrap: bootstrapResult,
@@ -38275,9 +38919,9 @@ init_dist3();
38275
38919
 
38276
38920
  // src/replay/replay-run.ts
38277
38921
  init_dist3();
38278
- var import_child_process3 = require("child_process");
38922
+ var import_child_process4 = require("child_process");
38279
38923
  var import_fs12 = require("fs");
38280
- var import_path14 = require("path");
38924
+ var import_path16 = require("path");
38281
38925
  async function replayPrepare(params) {
38282
38926
  const {
38283
38927
  outputDir,
@@ -38288,7 +38932,7 @@ async function replayPrepare(params) {
38288
38932
  skipApplication,
38289
38933
  logger
38290
38934
  } = params;
38291
- const lockfilePath = (0, import_path14.join)(outputDir, ".fern", "replay.lock");
38935
+ const lockfilePath = (0, import_path16.join)(outputDir, ".fern", "replay.lock");
38292
38936
  if (!(0, import_fs12.existsSync)(lockfilePath)) {
38293
38937
  return null;
38294
38938
  }
@@ -38420,7 +39064,7 @@ async function replayRun(params) {
38420
39064
  }
38421
39065
  function isTagMergedIntoHead(cwd2, tagSha) {
38422
39066
  try {
38423
- (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"], {
38424
39068
  cwd: cwd2,
38425
39069
  stdio: "pipe"
38426
39070
  });
@@ -38432,7 +39076,7 @@ function isTagMergedIntoHead(cwd2, tagSha) {
38432
39076
  return false;
38433
39077
  }
38434
39078
  try {
38435
- 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"], {
38436
39080
  cwd: cwd2,
38437
39081
  encoding: "utf-8",
38438
39082
  stdio: "pipe"
@@ -38444,7 +39088,7 @@ function isTagMergedIntoHead(cwd2, tagSha) {
38444
39088
  }
38445
39089
  function gitRevParse(cwd2, rev) {
38446
39090
  try {
38447
- return (0, import_child_process3.execFileSync)("git", ["rev-parse", "--verify", rev], {
39091
+ return (0, import_child_process4.execFileSync)("git", ["rev-parse", "--verify", rev], {
38448
39092
  cwd: cwd2,
38449
39093
  encoding: "utf-8",
38450
39094
  stdio: "pipe"
@@ -38455,7 +39099,7 @@ function gitRevParse(cwd2, rev) {
38455
39099
  }
38456
39100
  function gitDiffNameOnly(cwd2, from, to) {
38457
39101
  try {
38458
- 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], {
38459
39103
  cwd: cwd2,
38460
39104
  encoding: "utf-8",
38461
39105
  stdio: "pipe"
@@ -38473,29 +39117,29 @@ function replayStatus(params) {
38473
39117
  }
38474
39118
 
38475
39119
  // src/configuration/loadGitHubConfig.ts
38476
- var import_promises5 = require("fs/promises");
39120
+ var import_promises7 = require("fs/promises");
38477
39121
  async function loadGitHubConfig({
38478
39122
  absolutePathToConfig
38479
39123
  }) {
38480
- const rawContents = await (0, import_promises5.readFile)(absolutePathToConfig, "utf8");
39124
+ const rawContents = await (0, import_promises7.readFile)(absolutePathToConfig, "utf8");
38481
39125
  return JSON.parse(rawContents);
38482
39126
  }
38483
39127
 
38484
39128
  // src/configuration/loadReadmeConfig.ts
38485
- var import_promises6 = require("fs/promises");
39129
+ var import_promises8 = require("fs/promises");
38486
39130
  async function loadReadmeConfig({
38487
39131
  absolutePathToConfig
38488
39132
  }) {
38489
- const rawContents = await (0, import_promises6.readFile)(absolutePathToConfig, "utf8");
39133
+ const rawContents = await (0, import_promises8.readFile)(absolutePathToConfig, "utf8");
38490
39134
  return JSON.parse(rawContents);
38491
39135
  }
38492
39136
 
38493
39137
  // src/configuration/loadReferenceConfig.ts
38494
- var import_promises7 = require("fs/promises");
39138
+ var import_promises9 = require("fs/promises");
38495
39139
  async function loadReferenceConfig({
38496
39140
  absolutePathToConfig
38497
39141
  }) {
38498
- const rawContents = await (0, import_promises7.readFile)(absolutePathToConfig, "utf8");
39142
+ const rawContents = await (0, import_promises9.readFile)(absolutePathToConfig, "utf8");
38499
39143
  return JSON.parse(rawContents);
38500
39144
  }
38501
39145
 
@@ -38512,7 +39156,7 @@ var consolePipelineLogger = {
38512
39156
  };
38513
39157
 
38514
39158
  // src/pipeline/PostGenerationPipeline.ts
38515
- var import_child_process5 = require("child_process");
39159
+ var import_child_process6 = require("child_process");
38516
39160
 
38517
39161
  // src/pipeline/github/constants.ts
38518
39162
  var FERN_BOT_NAME2 = "fern-api";
@@ -38520,10 +39164,10 @@ var FERN_BOT_EMAIL2 = "115122769+fern-api[bot]@users.noreply.github.com";
38520
39164
  var FERN_BOT_LOGIN2 = "fern-api[bot]";
38521
39165
 
38522
39166
  // src/pipeline/steps/AutoVersionStep.ts
38523
- var import_child_process4 = require("child_process");
39167
+ var import_child_process5 = require("child_process");
38524
39168
  var import_fs14 = require("fs");
38525
- var import_promises9 = require("fs/promises");
38526
- var import_path16 = require("path");
39169
+ var import_promises11 = require("fs/promises");
39170
+ var import_path18 = require("path");
38527
39171
 
38528
39172
  // ../commons/logging-execa/lib/loggingExeca.js
38529
39173
  var import_execa = __toESM(require_execa(), 1);
@@ -38582,8 +39226,8 @@ async function loggingExeca(logger, executable, args = [], { doNotPipeOutput = f
38582
39226
 
38583
39227
  // src/autoversion/AutoVersioningService.ts
38584
39228
  var import_fs13 = require("fs");
38585
- var import_promises8 = require("fs/promises");
38586
- var import_path15 = require("path");
39229
+ var import_promises10 = require("fs/promises");
39230
+ var import_path17 = require("path");
38587
39231
  var import_semver = __toESM(require_semver2());
38588
39232
  var AutoVersioningException = class extends Error {
38589
39233
  /**
@@ -39270,7 +39914,7 @@ var AutoVersioningService = class _AutoVersioningService {
39270
39914
  );
39271
39915
  return;
39272
39916
  }
39273
- const goModPath = (0, import_path15.join)(workingDirectory, "go.mod");
39917
+ const goModPath = (0, import_path17.join)(workingDirectory, "go.mod");
39274
39918
  if (!(0, import_fs13.existsSync)(goModPath)) {
39275
39919
  this.logger.debug(`No go.mod found in ${workingDirectory}, skipping Go module path update`);
39276
39920
  return;
@@ -39304,7 +39948,7 @@ var AutoVersioningService = class _AutoVersioningService {
39304
39948
  * Reads the module path from a go.mod file.
39305
39949
  */
39306
39950
  async readGoModulePath(goModPath) {
39307
- const content = await (0, import_promises8.readFile)(goModPath, "utf-8");
39951
+ const content = await (0, import_promises10.readFile)(goModPath, "utf-8");
39308
39952
  for (const line of content.split("\n")) {
39309
39953
  const trimmed2 = line.trim();
39310
39954
  if (trimmed2.startsWith("module ")) {
@@ -39317,7 +39961,7 @@ var AutoVersioningService = class _AutoVersioningService {
39317
39961
  * Adds a version suffix to the module line in go.mod.
39318
39962
  */
39319
39963
  async addSuffixToGoMod(goModPath, modulePath, suffix) {
39320
- const content = await (0, import_promises8.readFile)(goModPath, "utf-8");
39964
+ const content = await (0, import_promises10.readFile)(goModPath, "utf-8");
39321
39965
  const lines = content.split("\n");
39322
39966
  let changed = false;
39323
39967
  for (let i = 0; i < lines.length; i++) {
@@ -39329,7 +39973,7 @@ var AutoVersioningService = class _AutoVersioningService {
39329
39973
  }
39330
39974
  }
39331
39975
  if (changed) {
39332
- await (0, import_promises8.writeFile)(goModPath, lines.join("\n"), "utf-8");
39976
+ await (0, import_promises10.writeFile)(goModPath, lines.join("\n"), "utf-8");
39333
39977
  this.logger.debug(`Updated go.mod: added ${suffix} to module path`);
39334
39978
  }
39335
39979
  }
@@ -39338,10 +39982,10 @@ var AutoVersioningService = class _AutoVersioningService {
39338
39982
  */
39339
39983
  async walkDirectory(dir, filter2) {
39340
39984
  const results = [];
39341
- const entries2 = await (0, import_promises8.readdir)(dir);
39985
+ const entries2 = await (0, import_promises10.readdir)(dir);
39342
39986
  for (const entry of entries2) {
39343
- const fullPath = (0, import_path15.join)(dir, entry);
39344
- 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);
39345
39989
  if (entryStat.isDirectory()) {
39346
39990
  if (entry === ".git" || entry === "vendor") {
39347
39991
  continue;
@@ -39358,7 +40002,7 @@ var AutoVersioningService = class _AutoVersioningService {
39358
40002
  * Adds a version suffix to import paths in all .go files in the repository.
39359
40003
  */
39360
40004
  async addSuffixToGoFiles(repoRoot, modulePath, suffix) {
39361
- 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");
39362
40006
  for (const filePath of goFiles) {
39363
40007
  await this.addSuffixToGoFile(filePath, modulePath, suffix);
39364
40008
  }
@@ -39368,7 +40012,7 @@ var AutoVersioningService = class _AutoVersioningService {
39368
40012
  * Replaces occurrences of the module path with modulePath + suffix in import statements.
39369
40013
  */
39370
40014
  async addSuffixToGoFile(filePath, modulePath, suffix) {
39371
- const content = await (0, import_promises8.readFile)(filePath, "utf-8");
40015
+ const content = await (0, import_promises10.readFile)(filePath, "utf-8");
39372
40016
  const lines = content.split("\n");
39373
40017
  let changed = false;
39374
40018
  let inImportBlock = false;
@@ -39397,7 +40041,7 @@ var AutoVersioningService = class _AutoVersioningService {
39397
40041
  }
39398
40042
  }
39399
40043
  if (changed) {
39400
- await (0, import_promises8.writeFile)(filePath, lines.join("\n"), "utf-8");
40044
+ await (0, import_promises10.writeFile)(filePath, lines.join("\n"), "utf-8");
39401
40045
  this.logger.debug(`Updated Go imports in ${filePath}`);
39402
40046
  }
39403
40047
  }
@@ -39760,7 +40404,7 @@ var AutoVersionStep = class extends BaseStep {
39760
40404
  }
39761
40405
  async readVersionFromMetadata() {
39762
40406
  try {
39763
- 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"], {
39764
40408
  cwd: this.outputDir,
39765
40409
  encoding: "utf-8",
39766
40410
  stdio: "pipe"
@@ -39776,7 +40420,7 @@ var AutoVersionStep = class extends BaseStep {
39776
40420
  return mappedMagicVersion.startsWith("v") ? `v${stripped}` : stripped;
39777
40421
  }
39778
40422
  gitDiff(from, to) {
39779
- 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"], {
39780
40424
  cwd: this.outputDir,
39781
40425
  encoding: "utf-8",
39782
40426
  stdio: "pipe",
@@ -39789,12 +40433,12 @@ var AutoVersionStep = class extends BaseStep {
39789
40433
  const fullMessage = body.length > 0 ? `${COMMIT_MARKER} ${subject}
39790
40434
 
39791
40435
  ${body}` : `${COMMIT_MARKER} ${subject}`;
39792
- (0, import_child_process4.execFileSync)("git", ["add", "-A"], { cwd: this.outputDir, stdio: "pipe" });
39793
- (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], {
39794
40438
  cwd: this.outputDir,
39795
40439
  stdio: "pipe"
39796
40440
  });
39797
- return (0, import_child_process4.execFileSync)("git", ["rev-parse", "HEAD"], {
40441
+ return (0, import_child_process5.execFileSync)("git", ["rev-parse", "HEAD"], {
39798
40442
  cwd: this.outputDir,
39799
40443
  encoding: "utf-8",
39800
40444
  stdio: "pipe"
@@ -39802,7 +40446,7 @@ ${body}` : `${COMMIT_MARKER} ${subject}`;
39802
40446
  }
39803
40447
  async prependChangelogEntry(params) {
39804
40448
  const { version: version2, entry } = params;
39805
- const changelogPath = (0, import_path16.join)(this.outputDir, "changelog.md");
40449
+ const changelogPath = (0, import_path18.join)(this.outputDir, "changelog.md");
39806
40450
  const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
39807
40451
  const header = `## [${version2}] - ${now}
39808
40452
  `;
@@ -39811,7 +40455,7 @@ ${body}` : `${COMMIT_MARKER} ${subject}`;
39811
40455
  `;
39812
40456
  let existing = "";
39813
40457
  if ((0, import_fs14.existsSync)(changelogPath)) {
39814
- existing = await (0, import_promises9.readFile)(changelogPath, "utf-8");
40458
+ existing = await (0, import_promises11.readFile)(changelogPath, "utf-8");
39815
40459
  }
39816
40460
  let output;
39817
40461
  if (existing.trim().length === 0) {
@@ -39828,7 +40472,7 @@ ${newBlock}${remainder}`;
39828
40472
  } else {
39829
40473
  output = `${newBlock}${existing}`;
39830
40474
  }
39831
- await (0, import_promises9.writeFile)(changelogPath, output, "utf-8");
40475
+ await (0, import_promises11.writeFile)(changelogPath, output, "utf-8");
39832
40476
  }
39833
40477
  brandMessage(message) {
39834
40478
  if (this.config.isWhitelabel) {
@@ -40010,8 +40654,8 @@ var GenerationCommitStep = class extends BaseStep {
40010
40654
  };
40011
40655
 
40012
40656
  // src/pipeline/steps/GithubStep.ts
40013
- var import_promises10 = require("fs/promises");
40014
- var import_path17 = require("path");
40657
+ var import_promises12 = require("fs/promises");
40658
+ var import_path19 = require("path");
40015
40659
 
40016
40660
  // src/pipeline/github/createReplayBranch.ts
40017
40661
  async function createReplayBranch(repository, branchName, commitMessage, replayConflictInfo, logger) {
@@ -40628,13 +41272,13 @@ var GithubStep = class extends BaseStep {
40628
41272
  }
40629
41273
  async ensureFernignore() {
40630
41274
  this.logger.debug("Checking for .fernignore file...");
40631
- const fernignorePath = (0, import_path17.join)(this.outputDir, ".fernignore");
41275
+ const fernignorePath = (0, import_path19.join)(this.outputDir, ".fernignore");
40632
41276
  try {
40633
- await (0, import_promises10.access)(fernignorePath);
41277
+ await (0, import_promises12.access)(fernignorePath);
40634
41278
  this.logger.debug(".fernignore already exists");
40635
41279
  } catch {
40636
41280
  this.logger.debug("Creating .fernignore file...");
40637
- 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");
40638
41282
  }
40639
41283
  }
40640
41284
  deriveSkipCommit(replayResult) {
@@ -40856,8 +41500,8 @@ var PostGenerationPipeline = class {
40856
41500
  async run() {
40857
41501
  if (this.config.github?.enabled) {
40858
41502
  try {
40859
- (0, import_child_process5.execFileSync)("git", ["config", "user.name", FERN_BOT_NAME2], { cwd: this.config.outputDir });
40860
- (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 });
40861
41505
  } catch {
40862
41506
  }
40863
41507
  }
@@ -40927,7 +41571,7 @@ void yargs_default(hideBin(process.argv)).scriptName(process.env.CLI_NAME ?? "ge
40927
41571
  const readmeConfig = await loadReadmeConfig({
40928
41572
  absolutePathToConfig: resolve(wd, argv.config)
40929
41573
  });
40930
- 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;
40931
41575
  await generateReadmeToStream({
40932
41576
  originalReadmeContent,
40933
41577
  readmeConfig,
@@ -41055,7 +41699,7 @@ void yargs_default(hideBin(process.argv)).scriptName(process.env.CLI_NAME ?? "ge
41055
41699
  try {
41056
41700
  const wd = cwd();
41057
41701
  const configValue = argv.config;
41058
- 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");
41059
41703
  let config;
41060
41704
  try {
41061
41705
  config = JSON.parse(configContent);
@@ -41351,7 +41995,7 @@ async function createWriteStream(outputPath) {
41351
41995
  }
41352
41996
  async function createWriteStreamFromFile(filepath) {
41353
41997
  if (!await doesPathExist(filepath)) {
41354
- 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 });
41355
41999
  }
41356
42000
  return import_fs15.default.createWriteStream(filepath);
41357
42001
  }