@fern-api/fern-api-dev 5.0.0 → 5.1.0

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/cli.cjs +950 -127
  2. package/package.json +1 -1
package/cli.cjs CHANGED
@@ -340394,9 +340394,9 @@ var require_diff3 = __commonJS({
340394
340394
  }
340395
340395
  });
340396
340396
 
340397
- // ../../../node_modules/.pnpm/@fern-api+replay@0.13.0/node_modules/@fern-api/replay/dist/index.cjs
340397
+ // ../../../node_modules/.pnpm/@fern-api+replay@0.14.0/node_modules/@fern-api/replay/dist/index.cjs
340398
340398
  var require_dist14 = __commonJS({
340399
- "../../../node_modules/.pnpm/@fern-api+replay@0.13.0/node_modules/@fern-api/replay/dist/index.cjs"(exports2, module4) {
340399
+ "../../../node_modules/.pnpm/@fern-api+replay@0.14.0/node_modules/@fern-api/replay/dist/index.cjs"(exports2, module4) {
340400
340400
  "use strict";
340401
340401
  var __defProp4 = Object.defineProperty;
340402
340402
  var __getOwnPropDesc4 = Object.getOwnPropertyDescriptor;
@@ -340439,9 +340439,9 @@ var require_dist14 = __commonJS({
340439
340439
  return this.git.raw(args);
340440
340440
  }
340441
340441
  async execWithInput(args, input3) {
340442
- const { spawn: spawn7 } = await import("child_process");
340442
+ const { spawn: spawn22 } = await import("child_process");
340443
340443
  return new Promise((resolve22, reject) => {
340444
- const proc2 = spawn7("git", args, { cwd: this.repoPath });
340444
+ const proc2 = spawn22("git", args, { cwd: this.repoPath });
340445
340445
  let stdout = "";
340446
340446
  let stderr = "";
340447
340447
  proc2.stdout.on("data", (data2) => {
@@ -341684,11 +341684,43 @@ var require_dist14 = __commonJS({
341684
341684
  }
341685
341685
  }
341686
341686
  }
341687
- return {
341687
+ const result = {
341688
341688
  content: outputLines.join("\n"),
341689
341689
  hasConflicts: conflicts2.length > 0,
341690
341690
  conflicts: conflicts2
341691
341691
  };
341692
+ if (conflicts2.length === 0) {
341693
+ const dropped = computeDroppedContextLines(baseLines, theirsLines, outputLines);
341694
+ if (dropped.length > 0) {
341695
+ result.droppedContextLines = dropped;
341696
+ }
341697
+ }
341698
+ return result;
341699
+ }
341700
+ function computeDroppedContextLines(baseLines, theirsLines, mergedLines) {
341701
+ const baseCounts = countLines2(baseLines);
341702
+ const theirsCounts = countLines2(theirsLines);
341703
+ const mergedCounts = countLines2(mergedLines);
341704
+ const dropped = [];
341705
+ const seen = /* @__PURE__ */ new Set();
341706
+ for (const line of baseLines) {
341707
+ if (seen.has(line)) continue;
341708
+ const inBase = baseCounts.get(line) ?? 0;
341709
+ const inTheirs = theirsCounts.get(line) ?? 0;
341710
+ const inMerged = mergedCounts.get(line) ?? 0;
341711
+ if (inBase > 0 && inTheirs > 0 && inMerged < Math.min(inBase, inTheirs)) {
341712
+ dropped.push(line);
341713
+ seen.add(line);
341714
+ }
341715
+ }
341716
+ return dropped;
341717
+ }
341718
+ function countLines2(lines) {
341719
+ const counts = /* @__PURE__ */ new Map();
341720
+ for (const line of lines) {
341721
+ counts.set(line, (counts.get(line) ?? 0) + 1);
341722
+ }
341723
+ return counts;
341692
341724
  }
341693
341725
  function tryResolveConflict(oursLines, baseLines, theirsLines) {
341694
341726
  if (baseLines.length === 0) {
@@ -341877,6 +341909,19 @@ var require_dist14 = __commonJS({
341877
341909
  renameCache = /* @__PURE__ */ new Map();
341878
341910
  treeExistsCache = /* @__PURE__ */ new Map();
341879
341911
  fileTheirsAccumulator = /* @__PURE__ */ new Map();
341912
+ /**
341913
+ * Apply mode for the current `applyPatches` invocation. Set at the start
341914
+ * of `applyPatches`, read by `mergeFile` to decide:
341915
+ * - whether to skip the intra-loop marker strip (kept in `applyPatches`)
341916
+ * - whether to populate the accumulator after a conflicted merge
341917
+ * (resolve mode populates so subsequent patches on the same file
341918
+ * can use patch A's THEIRS as a structurally-correct merge base
341919
+ * when their diff was authored against post-A structure)
341920
+ * - whether to retry THEIRS reconstruction against the accumulator
341921
+ * when the BASE-relative reconstruction produced markers from a
341922
+ * cross-patch context mismatch
341923
+ */
341924
+ currentApplyMode = "replay";
341880
341925
  constructor(git, lockManager, outputDir) {
341881
341926
  this.git = git;
341882
341927
  this.lockManager = lockManager;
@@ -341924,8 +341969,23 @@ var require_dist14 = __commonJS({
341924
341969
  /**
341925
341970
  * Apply all patches, returning results for each.
341926
341971
  * Skips patches that match exclude patterns in replay.yml
341927
- */
341928
- async applyPatches(patches) {
341972
+ *
341973
+ * `applyMode` controls the post-conflict marker strategy:
341974
+ * - `"replay"` (default): strip conflict markers from disk between
341975
+ * iterations whenever a later patch touches the same file. Lets the
341976
+ * follow-up patch's `git apply --3way` see clean OURS content,
341977
+ * which is what the silent-loss fix (PR #73) relies on. The replay
341978
+ * pipeline calls `revertConflictingFiles` after the loop to clean
341979
+ * any markers that survive.
341980
+ * - `"resolve"`: keep markers on disk. The resolve command needs the
341981
+ * customer to see them. Slow-path 3-way merge naturally preserves
341982
+ * A's markers and B's clean writes when their regions don't overlap;
341983
+ * when they do overlap, the customer gets nested markers and
341984
+ * resolves manually. Either way they aren't silently dropped.
341985
+ */
341986
+ async applyPatches(patches, opts) {
341987
+ const applyMode = opts?.applyMode ?? "replay";
341988
+ this.currentApplyMode = applyMode;
341929
341989
  this.resetAccumulator();
341930
341990
  const results = [];
341931
341991
  for (let i9 = 0; i9 < patches.length; i9++) {
@@ -341940,7 +342000,7 @@ var require_dist14 = __commonJS({
341940
342000
  }
341941
342001
  const result = await this.applyPatchWithFallback(patch5);
341942
342002
  results.push(result);
341943
- if (result.status === "conflict" && result.fileResults) {
342003
+ if (applyMode !== "resolve" && result.status === "conflict" && result.fileResults) {
341944
342004
  const laterFiles = /* @__PURE__ */ new Set();
341945
342005
  for (let j7 = i9 + 1; j7 < patches.length; j7++) {
341946
342006
  for (const f5 of patches[j7].files) {
@@ -341970,9 +342030,17 @@ var require_dist14 = __commonJS({
341970
342030
  }
341971
342031
  return results;
341972
342032
  }
341973
- /** Populate accumulator after git apply succeeds. */
342033
+ /**
342034
+ * Populate accumulator after git apply succeeds, AND collect per-file
342035
+ * results including any "dropped context lines" — lines that were
342036
+ * present in BASE and THEIRS (unchanged context) but absent from the
342037
+ * final on-disk state because OURS deleted them. This is the fast-path
342038
+ * counterpart to ThreeWayMerge.computeDroppedContextLines used by the
342039
+ * 3-way slow path; both must surface the same warning.
342040
+ */
341974
342041
  async populateAccumulatorForPatch(patch5, baseGen, currentTreeHash) {
341975
- if (!baseGen) return;
342042
+ const fileResults = [];
342043
+ if (!baseGen) return fileResults;
341976
342044
  const tempDir2 = await (0, import_promises192.mkdtemp)((0, import_node_path210.join)((0, import_node_os15.tmpdir)(), "replay-acc-"));
341977
342045
  const { GitClient: GitClient2 } = await Promise.resolve().then(() => (init_GitClient(), GitClient_exports));
341978
342046
  const tempGit = new GitClient2(tempDir2);
@@ -341985,18 +342053,30 @@ var require_dist14 = __commonJS({
341985
342053
  const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
341986
342054
  const base4 = await this.git.showFile(baseGen.tree_hash, filePath);
341987
342055
  const theirs = await this.applyPatchToContent(base4, patch5.patch_content, filePath, tempGit, tempDir2);
341988
- const effectiveTheirs = theirs ?? await (0, import_promises192.readFile)((0, import_node_path210.join)(this.outputDir, resolvedPath), "utf-8").catch(() => null);
342056
+ const finalOnDisk = await (0, import_promises192.readFile)((0, import_node_path210.join)(this.outputDir, resolvedPath), "utf-8").catch(() => null);
342057
+ const effectiveTheirs = theirs ?? finalOnDisk;
341989
342058
  if (effectiveTheirs != null) {
341990
342059
  this.fileTheirsAccumulator.set(resolvedPath, {
341991
342060
  content: effectiveTheirs,
341992
342061
  baseGeneration: patch5.base_generation
341993
342062
  });
341994
342063
  }
342064
+ if (base4 != null && effectiveTheirs != null && finalOnDisk != null) {
342065
+ const dropped = computeDroppedContextLinesForFile(base4, effectiveTheirs, finalOnDisk);
342066
+ if (dropped.length > 0) {
342067
+ fileResults.push({
342068
+ file: resolvedPath,
342069
+ status: "merged",
342070
+ droppedContextLines: dropped
342071
+ });
342072
+ }
342073
+ }
341995
342074
  }
341996
342075
  } finally {
341997
342076
  await (0, import_promises192.rm)(tempDir2, { recursive: true }).catch(() => {
341998
342077
  });
341999
342078
  }
342079
+ return fileResults;
342000
342080
  }
342001
342081
  async applyPatchWithFallback(patch5) {
342002
342082
  const baseGen = await this.resolveBaseGeneration(patch5.base_generation);
@@ -342027,11 +342107,12 @@ var require_dist14 = __commonJS({
342027
342107
  }
342028
342108
  try {
342029
342109
  await this.git.execWithInput(["apply", "--3way"], patch5.patch_content);
342030
- await this.populateAccumulatorForPatch(patch5, baseGen, currentTreeHash);
342110
+ const fastPathFileResults = await this.populateAccumulatorForPatch(patch5, baseGen, currentTreeHash);
342031
342111
  return {
342032
342112
  patch: patch5,
342033
342113
  status: "applied",
342034
342114
  method: "git-am",
342115
+ ...fastPathFileResults.length > 0 && { fileResults: fastPathFileResults },
342035
342116
  ...Object.keys(resolvedFiles).length > 0 && { resolvedFiles }
342036
342117
  };
342037
342118
  } catch {
@@ -342143,19 +342224,23 @@ var require_dist14 = __commonJS({
342143
342224
  renameSourcePath
342144
342225
  );
342145
342226
  }
342227
+ const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
342146
342228
  if (theirs) {
342147
342229
  const theirsHasMarkers = theirs.includes("<<<<<<< Generated") || theirs.includes(">>>>>>> Your customization");
342148
342230
  const baseHasMarkers = base4 != null && (base4.includes("<<<<<<< Generated") || base4.includes(">>>>>>> Your customization"));
342149
342231
  if (theirsHasMarkers && !baseHasMarkers) {
342150
- return {
342151
- file: resolvedPath,
342152
- status: "skipped",
342153
- reason: "stale-conflict-markers"
342154
- };
342232
+ if (accumulatorEntry) {
342233
+ theirs = null;
342234
+ } else {
342235
+ return {
342236
+ file: resolvedPath,
342237
+ status: "skipped",
342238
+ reason: "stale-conflict-markers"
342239
+ };
342240
+ }
342155
342241
  }
342156
342242
  }
342157
342243
  let useAccumulatorAsMergeBase = false;
342158
- const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
342159
342244
  if (accumulatorEntry && (theirs == null || base4 == null)) {
342160
342245
  theirs = await this.applyPatchToContent(
342161
342246
  accumulatorEntry.content,
@@ -342262,7 +342347,8 @@ var require_dist14 = __commonJS({
342262
342347
  const outDir = (0, import_node_path210.dirname)(oursPath);
342263
342348
  await (0, import_promises192.mkdir)(outDir, { recursive: true });
342264
342349
  await (0, import_promises192.writeFile)(oursPath, merged.content);
342265
- if (effective_theirs != null && !merged.hasConflicts) {
342350
+ const populateAccumulator = effective_theirs != null && (!merged.hasConflicts || this.currentApplyMode === "resolve");
342351
+ if (populateAccumulator) {
342266
342352
  this.fileTheirsAccumulator.set(resolvedPath, {
342267
342353
  content: effective_theirs,
342268
342354
  baseGeneration: patch5.base_generation
@@ -342277,7 +342363,11 @@ var require_dist14 = __commonJS({
342277
342363
  conflictMetadata: metadata
342278
342364
  };
342279
342365
  }
342280
- return { file: resolvedPath, status: "merged" };
342366
+ return {
342367
+ file: resolvedPath,
342368
+ status: "merged",
342369
+ ...merged.droppedContextLines && merged.droppedContextLines.length > 0 ? { droppedContextLines: merged.droppedContextLines } : {}
342370
+ };
342281
342371
  } catch (error50) {
342282
342372
  return {
342283
342373
  file: filePath,
@@ -342463,6 +342553,30 @@ var require_dist14 = __commonJS({
342463
342553
  const ext2 = (0, import_node_path210.extname)(filePath).toLowerCase();
342464
342554
  return BINARY_EXTENSIONS2.has(ext2);
342465
342555
  }
342556
+ function computeDroppedContextLinesForFile(base4, theirs, finalContent) {
342557
+ const baseLines = base4.split("\n");
342558
+ const theirsLines = theirs.split("\n");
342559
+ const finalLines = finalContent.split("\n");
342560
+ const baseCounts = /* @__PURE__ */ new Map();
342561
+ const theirsCounts = /* @__PURE__ */ new Map();
342562
+ const finalCounts = /* @__PURE__ */ new Map();
342563
+ for (const l9 of baseLines) baseCounts.set(l9, (baseCounts.get(l9) ?? 0) + 1);
342564
+ for (const l9 of theirsLines) theirsCounts.set(l9, (theirsCounts.get(l9) ?? 0) + 1);
342565
+ for (const l9 of finalLines) finalCounts.set(l9, (finalCounts.get(l9) ?? 0) + 1);
342566
+ const dropped = [];
342567
+ const seen = /* @__PURE__ */ new Set();
342568
+ for (const line of baseLines) {
342569
+ if (seen.has(line)) continue;
342570
+ const inBase = baseCounts.get(line) ?? 0;
342571
+ const inTheirs = theirsCounts.get(line) ?? 0;
342572
+ const inFinal = finalCounts.get(line) ?? 0;
342573
+ if (inBase > 0 && inTheirs > 0 && inFinal < Math.min(inBase, inTheirs)) {
342574
+ dropped.push(line);
342575
+ seen.add(line);
342576
+ }
342577
+ }
342578
+ return dropped;
342579
+ }
342466
342580
  function isDiffLineForFile(diffLine, filePath) {
342467
342581
  const match3 = diffLine.match(/^diff --git a\/.+ b\/(.+)$/);
342468
342582
  return match3 !== null && match3[1] === filePath;
@@ -342496,13 +342610,46 @@ CLI Version: ${options2.cliVersion}`;
342496
342610
  await this.git.exec(["commit", "-m", fullMessage]);
342497
342611
  return (await this.git.exec(["rev-parse", "HEAD"])).trim();
342498
342612
  }
342499
- async commitReplay(_patchCount, patches, message) {
342613
+ async commitReplay(_patchCount, patches, message, options2) {
342500
342614
  await this.stageAll();
342501
342615
  if (!await this.hasStagedChanges()) {
342502
342616
  return (await this.git.exec(["rev-parse", "HEAD"])).trim();
342503
342617
  }
342504
342618
  let fullMessage = message ?? `[fern-replay] Applied customizations`;
342505
- if (patches && patches.length > 0) {
342619
+ const buckets = options2?.buckets;
342620
+ if (buckets) {
342621
+ if (buckets.applied.length > 0) {
342622
+ fullMessage += `
342623
+
342624
+ Patches applied (${buckets.applied.length}):`;
342625
+ for (const p14 of buckets.applied) {
342626
+ fullMessage += `
342627
+ - ${p14.id}: ${p14.original_message}`;
342628
+ }
342629
+ }
342630
+ if (buckets.unresolved.length > 0) {
342631
+ fullMessage += `
342632
+
342633
+ Patches with unresolved conflicts (${buckets.unresolved.length}):`;
342634
+ for (const p14 of buckets.unresolved) {
342635
+ fullMessage += `
342636
+ - ${p14.id}: ${p14.original_message}`;
342637
+ }
342638
+ fullMessage += `
342639
+ Run \`fern-replay resolve\` to apply these customizations.`;
342640
+ }
342641
+ if (buckets.absorbed.length > 0) {
342642
+ fullMessage += `
342643
+
342644
+ Patches absorbed by generator (${buckets.absorbed.length}):`;
342645
+ for (const p14 of buckets.absorbed) {
342646
+ fullMessage += `
342647
+ - ${p14.id}: ${p14.original_message}`;
342648
+ }
342649
+ fullMessage += `
342650
+ The generator now produces these customizations natively.`;
342651
+ }
342652
+ } else if (patches && patches.length > 0) {
342506
342653
  fullMessage += "\n\nPatches replayed:";
342507
342654
  for (const patch5 of patches) {
342508
342655
  fullMessage += `
@@ -342536,9 +342683,299 @@ CLI Version: ${options2.cliVersion}`;
342536
342683
  }
342537
342684
  };
342538
342685
  var import_node_fs210 = require("fs");
342539
- var import_node_path310 = require("path");
342686
+ var import_node_path43 = require("path");
342540
342687
  var import_minimatch22 = require_commonjs3();
342541
342688
  init_GitClient();
342689
+ var import_promises210 = require("fs/promises");
342690
+ var import_node_os22 = require("os");
342691
+ var import_node_path310 = require("path");
342692
+ var import_node_child_process7 = require("child_process");
342693
+ init_HybridReconstruction();
342694
+ function normalizeHunkBody(hunk) {
342695
+ let contextC = 0;
342696
+ let removeC = 0;
342697
+ let addC = 0;
342698
+ const trimmed2 = [];
342699
+ for (const line of hunk.lines) {
342700
+ const oldUsed = contextC + removeC;
342701
+ const newUsed = contextC + addC;
342702
+ if (oldUsed >= hunk.oldCount && newUsed >= hunk.newCount) break;
342703
+ trimmed2.push(line);
342704
+ if (line.type === "context") contextC++;
342705
+ else if (line.type === "remove") removeC++;
342706
+ else if (line.type === "add") addC++;
342707
+ }
342708
+ return { ...hunk, lines: trimmed2 };
342709
+ }
342710
+ function extractFileDiffForFile(patchContent, filePath) {
342711
+ const lines = patchContent.split("\n");
342712
+ const out = [];
342713
+ let inTarget = false;
342714
+ for (const line of lines) {
342715
+ if (line.startsWith("diff --git")) {
342716
+ if (inTarget) break;
342717
+ if (isDiffLineForFile2(line, filePath)) {
342718
+ inTarget = true;
342719
+ out.push(line);
342720
+ }
342721
+ continue;
342722
+ }
342723
+ if (inTarget) out.push(line);
342724
+ }
342725
+ return out.length > 0 ? out.join("\n") + "\n" : null;
342726
+ }
342727
+ function isDiffLineForFile2(line, filePath) {
342728
+ const match3 = line.match(/^diff --git a\/(.+) b\/(.+)$/);
342729
+ return match3 !== null && (match3[2] === filePath || match3[1] === filePath);
342730
+ }
342731
+ async function computePerPatchDiff(patch5, getCurrentGenContent, getWorkingTreeContent) {
342732
+ const fragments = [];
342733
+ const unlocatableFiles = [];
342734
+ for (const file4 of patch5.files) {
342735
+ const fileDiff = extractFileDiffForFile(patch5.patch_content, file4);
342736
+ if (fileDiff == null) {
342737
+ unlocatableFiles.push(file4);
342738
+ continue;
342739
+ }
342740
+ const hunksRaw = parseHunks(fileDiff).map(normalizeHunkBody);
342741
+ const hunks = hunksRaw.filter((h4) => {
342742
+ if (h4.lines.length === 0) return false;
342743
+ let ctx = 0, rm32 = 0, add = 0;
342744
+ for (const ln10 of h4.lines) {
342745
+ if (ln10.type === "context") ctx++;
342746
+ else if (ln10.type === "remove") rm32++;
342747
+ else if (ln10.type === "add") add++;
342748
+ }
342749
+ return ctx + rm32 === h4.oldCount && ctx + add === h4.newCount;
342750
+ });
342751
+ if (hunks.length === 0) {
342752
+ if (hunksRaw.length > 0) unlocatableFiles.push(file4);
342753
+ continue;
342754
+ }
342755
+ const currentGen = await getCurrentGenContent(file4);
342756
+ const workingTree = await getWorkingTreeContent(file4);
342757
+ const isPureNewFile = hunks.every((h4) => h4.oldCount === 0);
342758
+ const isPureDelete = hunks.every((h4) => h4.newCount === 0);
342759
+ if (currentGen == null) {
342760
+ if (!isPureNewFile) {
342761
+ unlocatableFiles.push(file4);
342762
+ continue;
342763
+ }
342764
+ if (workingTree == null) continue;
342765
+ fragments.push(synthesizeNewFileDiff(file4, workingTree));
342766
+ continue;
342767
+ }
342768
+ if (isPureNewFile) {
342769
+ if (workingTree == null) {
342770
+ fragments.push(synthesizeDeleteFileDiff(file4, currentGen));
342771
+ continue;
342772
+ }
342773
+ if (workingTree === currentGen) {
342774
+ continue;
342775
+ }
342776
+ const fullDiff = await unifiedDiff(currentGen, workingTree, file4);
342777
+ if (fullDiff && fullDiff.trim()) fragments.push(fullDiff);
342778
+ continue;
342779
+ }
342780
+ if (workingTree == null) {
342781
+ if (!isPureDelete) {
342782
+ unlocatableFiles.push(file4);
342783
+ continue;
342784
+ }
342785
+ fragments.push(synthesizeDeleteFileDiff(file4, currentGen));
342786
+ continue;
342787
+ }
342788
+ const currentGenLines = splitLines(currentGen);
342789
+ const workingTreeLines = splitLines(workingTree);
342790
+ const locInCurrentGenRaw = locateHunksInOurs(hunks, currentGenLines);
342791
+ const locInWorkingTreeRaw = locateHunksInOurs(hunks, workingTreeLines);
342792
+ if (locInCurrentGenRaw == null || locInWorkingTreeRaw == null) {
342793
+ unlocatableFiles.push(file4);
342794
+ continue;
342795
+ }
342796
+ const locInCurrentGen = locInCurrentGenRaw.map(
342797
+ (l9) => resolveSpan(l9, currentGenLines, "old")
342798
+ );
342799
+ const locInWorkingTree = locInWorkingTreeRaw.map(
342800
+ (l9) => resolveSpan(l9, workingTreeLines, "new")
342801
+ );
342802
+ const overlay = overlayRegions(
342803
+ currentGenLines,
342804
+ locInCurrentGen,
342805
+ workingTreeLines,
342806
+ locInWorkingTree
342807
+ );
342808
+ if (overlay === null) {
342809
+ unlocatableFiles.push(file4);
342810
+ continue;
342811
+ }
342812
+ if (overlay === currentGen) {
342813
+ continue;
342814
+ }
342815
+ const fileUnifiedDiff = await unifiedDiff(currentGen, overlay, file4);
342816
+ if (fileUnifiedDiff && fileUnifiedDiff.trim()) {
342817
+ fragments.push(fileUnifiedDiff);
342818
+ }
342819
+ }
342820
+ return {
342821
+ diff: fragments.join(""),
342822
+ unlocatableFiles
342823
+ };
342824
+ }
342825
+ async function computePerPatchDiffWithFallback(patch5, getCurrentGenContent, getWorkingTreeContent, runCumulativeDiff) {
342826
+ const ppDiff = await computePerPatchDiff(
342827
+ patch5,
342828
+ getCurrentGenContent,
342829
+ getWorkingTreeContent
342830
+ );
342831
+ if (ppDiff.unlocatableFiles.length === 0) {
342832
+ return { diff: ppDiff.diff, hadFallback: false, fallbackFiles: [] };
342833
+ }
342834
+ const cumulative = await runCumulativeDiff(ppDiff.unlocatableFiles);
342835
+ if (cumulative === null) {
342836
+ return null;
342837
+ }
342838
+ const merged = ppDiff.diff + (cumulative.trim() ? cumulative : "");
342839
+ return { diff: merged, hadFallback: true, fallbackFiles: ppDiff.unlocatableFiles };
342840
+ }
342841
+ function splitLines(content5) {
342842
+ return content5.split("\n");
342843
+ }
342844
+ function resolveSpan(loc, oursLines, prefer) {
342845
+ const { hunk, oursOffset } = loc;
342846
+ const oldSide = [];
342847
+ const newSide = [];
342848
+ for (const line of hunk.lines) {
342849
+ if (line.type === "context") {
342850
+ oldSide.push(line.content);
342851
+ newSide.push(line.content);
342852
+ } else if (line.type === "remove") {
342853
+ oldSide.push(line.content);
342854
+ } else if (line.type === "add") {
342855
+ newSide.push(line.content);
342856
+ }
342857
+ }
342858
+ const oldFits = sequenceMatches(oldSide, oursLines, oursOffset);
342859
+ const newFits = sequenceMatches(newSide, oursLines, oursOffset);
342860
+ if (prefer === "old") {
342861
+ if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
342862
+ if (newFits) return { ...loc, oursSpan: hunk.newCount };
342863
+ } else {
342864
+ if (newFits) return { ...loc, oursSpan: hunk.newCount };
342865
+ if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
342866
+ }
342867
+ return loc;
342868
+ }
342869
+ function sequenceMatches(needle, haystack, offset2) {
342870
+ if (offset2 + needle.length > haystack.length) return false;
342871
+ for (let i9 = 0; i9 < needle.length; i9++) {
342872
+ if (haystack[offset2 + i9] !== needle[i9]) return false;
342873
+ }
342874
+ return true;
342875
+ }
342876
+ function overlayRegions(currentGenLines, locInCurrentGen, workingTreeLines, locInWorkingTree) {
342877
+ if (locInCurrentGen.length !== locInWorkingTree.length) {
342878
+ return null;
342879
+ }
342880
+ const out = [];
342881
+ let cursor = 0;
342882
+ for (let i9 = 0; i9 < locInCurrentGen.length; i9++) {
342883
+ const cg = locInCurrentGen[i9];
342884
+ const wt10 = locInWorkingTree[i9];
342885
+ if (cg.oursOffset > cursor) {
342886
+ out.push(...currentGenLines.slice(cursor, cg.oursOffset));
342887
+ }
342888
+ out.push(...workingTreeLines.slice(wt10.oursOffset, wt10.oursOffset + wt10.oursSpan));
342889
+ cursor = cg.oursOffset + cg.oursSpan;
342890
+ }
342891
+ if (cursor < currentGenLines.length) {
342892
+ out.push(...currentGenLines.slice(cursor));
342893
+ }
342894
+ return out.join("\n");
342895
+ }
342896
+ async function unifiedDiff(beforeContent, afterContent, filePath) {
342897
+ if (beforeContent === afterContent) return "";
342898
+ const tmp18 = await (0, import_promises210.mkdtemp)((0, import_node_path310.join)((0, import_node_os22.tmpdir)(), "fern-replay-pp-"));
342899
+ try {
342900
+ const beforePath = (0, import_node_path310.join)(tmp18, "before");
342901
+ const afterPath = (0, import_node_path310.join)(tmp18, "after");
342902
+ await (0, import_promises210.writeFile)(beforePath, beforeContent, "utf-8");
342903
+ await (0, import_promises210.writeFile)(afterPath, afterContent, "utf-8");
342904
+ const raw = await runGitDiffNoIndex(beforePath, afterPath);
342905
+ if (!raw.trim()) return "";
342906
+ return rewriteDiffHeaders(raw, filePath);
342907
+ } finally {
342908
+ await (0, import_promises210.rm)(tmp18, { recursive: true, force: true });
342909
+ }
342910
+ }
342911
+ function runGitDiffNoIndex(beforePath, afterPath) {
342912
+ return new Promise((resolve22, reject) => {
342913
+ const proc2 = (0, import_node_child_process7.spawn)("git", ["diff", "--no-index", "--", beforePath, afterPath]);
342914
+ let stdout = "";
342915
+ let stderr = "";
342916
+ proc2.stdout.on("data", (d5) => stdout += d5.toString());
342917
+ proc2.stderr.on("data", (d5) => stderr += d5.toString());
342918
+ proc2.on("close", (code5) => {
342919
+ if (code5 === 0 || code5 === 1) resolve22(stdout);
342920
+ else reject(new Error(`git diff --no-index failed (code ${code5}): ${stderr}`));
342921
+ });
342922
+ });
342923
+ }
342924
+ function rewriteDiffHeaders(raw, filePath) {
342925
+ const lines = raw.split("\n");
342926
+ const out = [];
342927
+ let seenFirstHeader = false;
342928
+ for (const line of lines) {
342929
+ if (line.startsWith("diff --git ")) {
342930
+ out.push(`diff --git a/${filePath} b/${filePath}`);
342931
+ seenFirstHeader = true;
342932
+ continue;
342933
+ }
342934
+ if (!seenFirstHeader) {
342935
+ continue;
342936
+ }
342937
+ if (line.startsWith("--- ")) {
342938
+ out.push(`--- a/${filePath}`);
342939
+ continue;
342940
+ }
342941
+ if (line.startsWith("+++ ")) {
342942
+ out.push(`+++ b/${filePath}`);
342943
+ continue;
342944
+ }
342945
+ out.push(line);
342946
+ }
342947
+ return out.join("\n");
342948
+ }
342949
+ function synthesizeNewFileDiff(file4, content5) {
342950
+ const lines = content5.split("\n");
342951
+ const hasTrailingNewline = content5.endsWith("\n");
342952
+ const bodyLines = hasTrailingNewline ? lines.slice(0, -1) : lines;
342953
+ const header = [
342954
+ `diff --git a/${file4} b/${file4}`,
342955
+ "new file mode 100644",
342956
+ "--- /dev/null",
342957
+ `+++ b/${file4}`,
342958
+ `@@ -0,0 +1,${bodyLines.length} @@`
342959
+ ].join("\n");
342960
+ const body = bodyLines.map((l9) => `+${l9}`).join("\n");
342961
+ const trailer = hasTrailingNewline ? "" : "\n\";
342962
+ return header + "\n" + body + trailer + "\n";
342963
+ }
342964
+ function synthesizeDeleteFileDiff(file4, content5) {
342965
+ const lines = content5.split("\n");
342966
+ const hasTrailingNewline = content5.endsWith("\n");
342967
+ const bodyLines = hasTrailingNewline ? lines.slice(0, -1) : lines;
342968
+ const header = [
342969
+ `diff --git a/${file4} b/${file4}`,
342970
+ "deleted file mode 100644",
342971
+ `--- a/${file4}`,
342972
+ "+++ /dev/null",
342973
+ `@@ -1,${bodyLines.length} +0,0 @@`
342974
+ ].join("\n");
342975
+ const body = bodyLines.map((l9) => `-${l9}`).join("\n");
342976
+ const trailer = hasTrailingNewline ? "" : "\n\";
342977
+ return header + "\n" + body + trailer + "\n";
342978
+ }
342542
342979
  var ReplayService = class {
342543
342980
  git;
342544
342981
  detector;
@@ -342932,6 +343369,7 @@ CLI Version: ${options2.cliVersion}`;
342932
343369
  if (newPatches.length > 0) {
342933
343370
  results = await this.applicator.applyPatches(newPatches);
342934
343371
  this._lastApplyResults = results;
343372
+ this.recordDroppedContextLineWarnings(results);
342935
343373
  this.revertConflictingFiles(results);
342936
343374
  for (const result of results) {
342937
343375
  if (result.status === "conflict") {
@@ -342950,7 +343388,8 @@ CLI Version: ${options2.cliVersion}`;
342950
343388
  if (newPatches.length > 0) {
342951
343389
  if (!options2?.stageOnly) {
342952
343390
  const appliedCount = results.filter((r5) => r5.status === "applied").length;
342953
- await this.committer.commitReplay(appliedCount, newPatches);
343391
+ const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, newPatches);
343392
+ await this.committer.commitReplay(appliedCount, newPatches, void 0, { buckets });
342954
343393
  } else {
342955
343394
  await this.committer.stageAll();
342956
343395
  }
@@ -343013,12 +343452,21 @@ CLI Version: ${options2.cliVersion}`;
343013
343452
  (p14) => preRebasePatchIds.has(p14.id) && !postRebasePatchIds.has(p14.id)
343014
343453
  );
343015
343454
  existingPatches = this.lockManager.getPatches();
343016
- const seenHashes = /* @__PURE__ */ new Set();
343455
+ const seenHashes = /* @__PURE__ */ new Map();
343017
343456
  for (const p14 of existingPatches) {
343018
- if (seenHashes.has(p14.content_hash)) {
343019
- this.lockManager.removePatch(p14.id);
343457
+ const priorOrigin = seenHashes.get(p14.content_hash);
343458
+ if (priorOrigin !== void 0) {
343459
+ const provenanceMissing = !priorOrigin || !p14.original_commit;
343460
+ const sameOrigin = priorOrigin === p14.original_commit;
343461
+ if (sameOrigin && !provenanceMissing) {
343462
+ this.lockManager.removePatch(p14.id);
343463
+ } else {
343464
+ this.warnings.push(
343465
+ `Preserving patch ${p14.id} (commit ${(p14.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).`
343466
+ );
343467
+ }
343020
343468
  } else {
343021
- seenHashes.add(p14.content_hash);
343469
+ seenHashes.set(p14.content_hash, p14.original_commit);
343022
343470
  }
343023
343471
  }
343024
343472
  existingPatches = this.lockManager.getPatches();
@@ -343079,6 +343527,7 @@ CLI Version: ${options2.cliVersion}`;
343079
343527
  const genSha = prep._prepared.genSha;
343080
343528
  const results = await this.applicator.applyPatches(allPatches);
343081
343529
  this._lastApplyResults = results;
343530
+ this.recordDroppedContextLineWarnings(results);
343082
343531
  this.revertConflictingFiles(results);
343083
343532
  for (const result of results) {
343084
343533
  if (result.status === "conflict") {
@@ -343106,7 +343555,8 @@ CLI Version: ${options2.cliVersion}`;
343106
343555
  await this.committer.stageAll();
343107
343556
  } else {
343108
343557
  const appliedCount = results.filter((r5) => r5.status === "applied").length;
343109
- await this.committer.commitReplay(appliedCount, allPatches);
343558
+ const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, allPatches);
343559
+ await this.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
343110
343560
  }
343111
343561
  const warnings = [...detectorWarnings, ...this.warnings];
343112
343562
  return this.buildReport(
@@ -343130,7 +343580,7 @@ CLI Version: ${options2.cliVersion}`;
343130
343580
  let repointed = 0;
343131
343581
  let contentRebased = 0;
343132
343582
  let keptAsUserOwned = 0;
343133
- const seenContentHashes = /* @__PURE__ */ new Set();
343583
+ const seenContentHashes = /* @__PURE__ */ new Map();
343134
343584
  const absorbedPatchIds = /* @__PURE__ */ new Set();
343135
343585
  const fernignorePatterns = this.readFernignorePatterns();
343136
343586
  for (const result of results) {
@@ -343144,6 +343594,23 @@ CLI Version: ${options2.cliVersion}`;
343144
343594
  }
343145
343595
  }
343146
343596
  }
343597
+ const conflictedFilePaths = /* @__PURE__ */ new Set();
343598
+ for (const r5 of results) {
343599
+ if (r5.status !== "conflict") continue;
343600
+ if (r5.fileResults) {
343601
+ for (const fr9 of r5.fileResults) {
343602
+ if (fr9.status !== "conflict") continue;
343603
+ conflictedFilePaths.add(fr9.file);
343604
+ if (r5.resolvedFiles) {
343605
+ for (const [orig, resolved] of Object.entries(r5.resolvedFiles)) {
343606
+ if (resolved === fr9.file) conflictedFilePaths.add(orig);
343607
+ }
343608
+ }
343609
+ }
343610
+ } else {
343611
+ for (const f5 of r5.patch.files) conflictedFilePaths.add(f5);
343612
+ }
343613
+ }
343147
343614
  for (const result of results) {
343148
343615
  if (result.status === "conflict" && result.fileResults) {
343149
343616
  await this.trimAbsorbedFiles(result, currentGenSha);
@@ -343211,6 +343678,18 @@ CLI Version: ${options2.cliVersion}`;
343211
343678
  keptAsUserOwned++;
343212
343679
  continue;
343213
343680
  }
343681
+ const overlapsConflicted = patch5.files.some((f5) => conflictedFilePaths.has(f5));
343682
+ if (overlapsConflicted) {
343683
+ patch5.status = "unresolved";
343684
+ try {
343685
+ this.lockManager.updatePatch(patch5.id, { status: "unresolved" });
343686
+ } catch {
343687
+ }
343688
+ this.warnings.push(
343689
+ `Patch ${patch5.id} (${patch5.original_message}) was preserved as unresolved because an earlier patch conflicted on the same file(s) (${patch5.files.join(", ")}). Run \`fern replay resolve\` to apply this customization manually.`
343690
+ );
343691
+ continue;
343692
+ }
343214
343693
  this.lockManager.removePatch(patch5.id);
343215
343694
  absorbedPatchIds.add(patch5.id);
343216
343695
  absorbed++;
@@ -343226,13 +343705,22 @@ CLI Version: ${options2.cliVersion}`;
343226
343705
  continue;
343227
343706
  }
343228
343707
  const newContentHash = this.detector.computeContentHash(diff2);
343229
- if (seenContentHashes.has(newContentHash)) {
343230
- this.lockManager.removePatch(patch5.id);
343231
- absorbedPatchIds.add(patch5.id);
343232
- absorbed++;
343233
- continue;
343708
+ const priorOrigin = seenContentHashes.get(newContentHash);
343709
+ if (priorOrigin !== void 0) {
343710
+ const provenanceMissing = !priorOrigin || !patch5.original_commit;
343711
+ const sameOrigin = priorOrigin === patch5.original_commit;
343712
+ if (sameOrigin && !provenanceMissing) {
343713
+ this.lockManager.removePatch(patch5.id);
343714
+ absorbedPatchIds.add(patch5.id);
343715
+ absorbed++;
343716
+ continue;
343717
+ }
343718
+ this.warnings.push(
343719
+ `Preserving patch ${patch5.id} (commit ${(patch5.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).`
343720
+ );
343721
+ } else {
343722
+ seenContentHashes.set(newContentHash, patch5.original_commit);
343234
343723
  }
343235
- seenContentHashes.add(newContentHash);
343236
343724
  patch5.base_generation = currentGenSha;
343237
343725
  patch5.patch_content = diff2;
343238
343726
  patch5.content_hash = newContentHash;
@@ -343408,8 +343896,14 @@ CLI Version: ${options2.cliVersion}`;
343408
343896
  }
343409
343897
  if (patch5.base_generation === currentGen) {
343410
343898
  try {
343411
- const diff2 = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch5.files]).catch(() => null);
343412
- if (diff2 === null) continue;
343899
+ const ppResult = await computePerPatchDiffWithFallback(
343900
+ patch5,
343901
+ (f5) => this.git.showFile(currentGen, f5),
343902
+ (f5) => this.git.showFile("HEAD", f5),
343903
+ (files) => this.git.exec(["diff", currentGen, "HEAD", "--", ...files]).catch(() => null)
343904
+ );
343905
+ if (ppResult === null) continue;
343906
+ const diff2 = ppResult.diff;
343413
343907
  if (!diff2.trim()) {
343414
343908
  if (await allPatchFilesUserOwned(patch5)) continue;
343415
343909
  this.lockManager.removePatch(patch5.id);
@@ -343451,8 +343945,14 @@ CLI Version: ${options2.cliVersion}`;
343451
343945
  try {
343452
343946
  const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<< Generated", "HEAD", "--", ...patch5.files]).catch(() => "");
343453
343947
  if (markerFiles.trim()) continue;
343454
- const diff2 = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch5.files]).catch(() => null);
343455
- if (diff2 === null) continue;
343948
+ const ppResult = await computePerPatchDiffWithFallback(
343949
+ patch5,
343950
+ (f5) => this.git.showFile(currentGen, f5),
343951
+ (f5) => this.git.showFile("HEAD", f5),
343952
+ (files) => this.git.exec(["diff", currentGen, "HEAD", "--", ...files]).catch(() => null)
343953
+ );
343954
+ if (ppResult === null) continue;
343955
+ const diff2 = ppResult.diff;
343456
343956
  if (!diff2.trim()) {
343457
343957
  if (await allPatchFilesUserOwned(patch5)) {
343458
343958
  try {
@@ -343477,6 +343977,32 @@ CLI Version: ${options2.cliVersion}`;
343477
343977
  }
343478
343978
  return { conflictResolved, conflictAbsorbed, contentRefreshed };
343479
343979
  }
343980
+ /**
343981
+ * After applyPatches(), surface a warning for each (patch × file) where
343982
+ * diff3 silently dropped lines that were unchanged context in the
343983
+ * customer's THEIRS. The deletion stays on disk (correct diff3 outcome),
343984
+ * but the customer must learn so they can re-add the lines if they
343985
+ * relied on them. This is the "surface or preserve, never silently drop"
343986
+ * contract for legitimate-deletion cases.
343987
+ */
343988
+ recordDroppedContextLineWarnings(results) {
343989
+ const PREVIEW_COUNT = 5;
343990
+ for (const result of results) {
343991
+ if (!result.fileResults) continue;
343992
+ for (const fr9 of result.fileResults) {
343993
+ const dropped = fr9.droppedContextLines;
343994
+ if (!dropped || dropped.length === 0) continue;
343995
+ const preview = dropped.slice(0, PREVIEW_COUNT).map((line) => ` ${line}`).join("\n");
343996
+ const more = dropped.length > PREVIEW_COUNT ? `
343997
+ ... and ${dropped.length - PREVIEW_COUNT} more` : "";
343998
+ this.warnings.push(
343999
+ `${fr9.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:
344000
+ ${preview}${more}
344001
+ 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.`
344002
+ );
344003
+ }
344004
+ }
344005
+ }
343480
344006
  /**
343481
344007
  * After applyPatches(), strip conflict markers from conflicting files
343482
344008
  * so only clean content is committed. Keeps the Generated (OURS) side.
@@ -343486,7 +344012,7 @@ CLI Version: ${options2.cliVersion}`;
343486
344012
  if (result.status !== "conflict" || !result.fileResults) continue;
343487
344013
  for (const fileResult of result.fileResults) {
343488
344014
  if (fileResult.status !== "conflict") continue;
343489
- const filePath = (0, import_node_path310.join)(this.outputDir, fileResult.file);
344015
+ const filePath = (0, import_node_path43.join)(this.outputDir, fileResult.file);
343490
344016
  try {
343491
344017
  const content5 = (0, import_node_fs210.readFileSync)(filePath, "utf-8");
343492
344018
  const stripped = stripConflictMarkers(content5);
@@ -343516,7 +344042,7 @@ CLI Version: ${options2.cliVersion}`;
343516
344042
  }
343517
344043
  }
343518
344044
  readFernignorePatterns() {
343519
- const fernignorePath = (0, import_node_path310.join)(this.outputDir, ".fernignore");
344045
+ const fernignorePath = (0, import_node_path43.join)(this.outputDir, ".fernignore");
343520
344046
  if (!(0, import_node_fs210.existsSync)(fernignorePath)) return [];
343521
344047
  return (0, import_node_fs210.readFileSync)(fernignorePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
343522
344048
  }
@@ -343534,6 +344060,9 @@ CLI Version: ${options2.cliVersion}`;
343534
344060
  };
343535
344061
  }).filter((d5) => d5.files.length > 0);
343536
344062
  const partialCount = conflictDetails.filter((d5) => d5.cleanFiles && d5.cleanFiles.length > 0).length;
344063
+ const unresolvedResults = results.filter(
344064
+ (r5) => r5.status === "conflict" || r5.patch.status === "unresolved"
344065
+ );
343537
344066
  return {
343538
344067
  flow: flow4,
343539
344068
  patchesDetected: patches.length,
@@ -343550,7 +344079,7 @@ CLI Version: ${options2.cliVersion}`;
343550
344079
  patchesRefreshed: preRebaseCounts && preRebaseCounts.contentRefreshed > 0 ? preRebaseCounts.contentRefreshed : void 0,
343551
344080
  conflicts: conflictResults.flatMap((r5) => r5.fileResults?.filter((f5) => f5.status === "conflict") ?? []),
343552
344081
  conflictDetails: conflictDetails.length > 0 ? conflictDetails : void 0,
343553
- unresolvedPatches: conflictResults.length > 0 ? conflictResults.map((r5) => ({
344082
+ unresolvedPatches: unresolvedResults.length > 0 ? unresolvedResults.map((r5) => ({
343554
344083
  patchId: r5.patch.id,
343555
344084
  patchMessage: r5.patch.original_message,
343556
344085
  files: r5.patch.files,
@@ -343561,9 +344090,36 @@ CLI Version: ${options2.cliVersion}`;
343561
344090
  };
343562
344091
  }
343563
344092
  };
344093
+ function computeCommitBuckets(results, absorbedPatchIds, patchesPassedToCommit) {
344094
+ const resultByPatchId = /* @__PURE__ */ new Map();
344095
+ for (const r5 of results) resultByPatchId.set(r5.patch.id, r5);
344096
+ const applied = [];
344097
+ const unresolved = [];
344098
+ const absorbed = [];
344099
+ for (const patch5 of patchesPassedToCommit) {
344100
+ if (absorbedPatchIds.has(patch5.id)) {
344101
+ absorbed.push(patch5);
344102
+ continue;
344103
+ }
344104
+ const r5 = resultByPatchId.get(patch5.id);
344105
+ if (!r5) {
344106
+ applied.push(patch5);
344107
+ continue;
344108
+ }
344109
+ if (r5.status === "conflict" || patch5.status === "unresolved") {
344110
+ unresolved.push(patch5);
344111
+ continue;
344112
+ }
344113
+ if (r5.status === "applied") {
344114
+ applied.push(patch5);
344115
+ continue;
344116
+ }
344117
+ }
344118
+ return { applied, unresolved, absorbed };
344119
+ }
343564
344120
  var import_node_crypto22 = require("crypto");
343565
344121
  var import_node_fs33 = require("fs");
343566
- var import_node_path43 = require("path");
344122
+ var import_node_path52 = require("path");
343567
344123
  var import_minimatch32 = require_commonjs3();
343568
344124
  var import_yaml22 = require_dist9();
343569
344125
  var FernignoreMigrator = class {
@@ -343576,10 +344132,10 @@ CLI Version: ${options2.cliVersion}`;
343576
344132
  this.outputDir = outputDir;
343577
344133
  }
343578
344134
  fernignoreExists() {
343579
- return (0, import_node_fs33.existsSync)((0, import_node_path43.join)(this.outputDir, ".fernignore"));
344135
+ return (0, import_node_fs33.existsSync)((0, import_node_path52.join)(this.outputDir, ".fernignore"));
343580
344136
  }
343581
344137
  readFernignorePatterns() {
343582
- const fernignorePath = (0, import_node_path43.join)(this.outputDir, ".fernignore");
344138
+ const fernignorePath = (0, import_node_path52.join)(this.outputDir, ".fernignore");
343583
344139
  if (!(0, import_node_fs33.existsSync)(fernignorePath)) {
343584
344140
  return [];
343585
344141
  }
@@ -343639,7 +344195,16 @@ CLI Version: ${options2.cliVersion}`;
343639
344195
  original_author: "Fern Replay <replay@buildwithfern.com>",
343640
344196
  base_generation: currentGen.commit_sha,
343641
344197
  files: patchFiles,
343642
- patch_content: patchContent
344198
+ patch_content: patchContent,
344199
+ // Synthetic patches captured from .fernignore-protected files
344200
+ // are user-owned by definition — the customer's content
344201
+ // diverges from the generator's pristine output, and the
344202
+ // generator never produced this divergent content. Without
344203
+ // this flag, removing the file from .fernignore later (so
344204
+ // it leaves the protection check in `isFileUserOwned`) would
344205
+ // expose the patch to absorption on the next regen,
344206
+ // silently losing the customization.
344207
+ user_owned: true
343643
344208
  });
343644
344209
  trackedByBoth.push({
343645
344210
  file: pattern,
@@ -343672,7 +344237,7 @@ CLI Version: ${options2.cliVersion}`;
343672
344237
  return results;
343673
344238
  }
343674
344239
  readFileContent(filePath) {
343675
- const fullPath = (0, import_node_path43.join)(this.outputDir, filePath);
344240
+ const fullPath = (0, import_node_path52.join)(this.outputDir, filePath);
343676
344241
  if (!(0, import_node_fs33.existsSync)(fullPath)) {
343677
344242
  return null;
343678
344243
  }
@@ -343737,7 +344302,7 @@ CLI Version: ${options2.cliVersion}`;
343737
344302
  };
343738
344303
  }
343739
344304
  movePatternsToReplayYml(patterns) {
343740
- const replayYmlPath = (0, import_node_path43.join)(this.outputDir, ".fern", "replay.yml");
344305
+ const replayYmlPath = (0, import_node_path52.join)(this.outputDir, ".fern", "replay.yml");
343741
344306
  let config5 = {};
343742
344307
  if ((0, import_node_fs33.existsSync)(replayYmlPath)) {
343743
344308
  const content5 = (0, import_node_fs33.readFileSync)(replayYmlPath, "utf-8");
@@ -343746,7 +344311,7 @@ CLI Version: ${options2.cliVersion}`;
343746
344311
  const existing = config5.exclude ?? [];
343747
344312
  const merged = [.../* @__PURE__ */ new Set([...existing, ...patterns])];
343748
344313
  config5.exclude = merged;
343749
- const dir = (0, import_node_path43.dirname)(replayYmlPath);
344314
+ const dir = (0, import_node_path52.dirname)(replayYmlPath);
343750
344315
  if (!(0, import_node_fs33.existsSync)(dir)) {
343751
344316
  (0, import_node_fs33.mkdirSync)(dir, { recursive: true });
343752
344317
  }
@@ -343755,7 +344320,7 @@ CLI Version: ${options2.cliVersion}`;
343755
344320
  };
343756
344321
  var import_node_crypto32 = require("crypto");
343757
344322
  var import_node_fs42 = require("fs");
343758
- var import_node_path52 = require("path");
344323
+ var import_node_path62 = require("path");
343759
344324
  init_GitClient();
343760
344325
  async function bootstrap(outputDir, options2) {
343761
344326
  const git = new GitClient(outputDir);
@@ -343922,7 +344487,7 @@ CLI Version: ${options2.cliVersion}`;
343922
344487
  }
343923
344488
  var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
343924
344489
  function ensureFernignoreEntries(outputDir) {
343925
- const fernignorePath = (0, import_node_path52.join)(outputDir, ".fernignore");
344490
+ const fernignorePath = (0, import_node_path62.join)(outputDir, ".fernignore");
343926
344491
  let content5 = "";
343927
344492
  if ((0, import_node_fs42.existsSync)(fernignorePath)) {
343928
344493
  content5 = (0, import_node_fs42.readFileSync)(fernignorePath, "utf-8");
@@ -343946,7 +344511,7 @@ CLI Version: ${options2.cliVersion}`;
343946
344511
  }
343947
344512
  var GITATTRIBUTES_ENTRIES = [".fern/replay.lock linguist-generated=true"];
343948
344513
  function ensureGitattributesEntries(outputDir) {
343949
- const gitattributesPath = (0, import_node_path52.join)(outputDir, ".gitattributes");
344514
+ const gitattributesPath = (0, import_node_path62.join)(outputDir, ".gitattributes");
343950
344515
  let content5 = "";
343951
344516
  if ((0, import_node_fs42.existsSync)(gitattributesPath)) {
343952
344517
  content5 = (0, import_node_fs42.readFileSync)(gitattributesPath, "utf-8");
@@ -344138,6 +344703,8 @@ CLI Version: ${options2.cliVersion}`;
344138
344703
  nothingToReset: false
344139
344704
  };
344140
344705
  }
344706
+ var import_promises310 = require("fs/promises");
344707
+ var import_node_path72 = require("path");
344141
344708
  init_GitClient();
344142
344709
  async function resolve15(outputDir, options2) {
344143
344710
  const lockManager = new LockfileManager(outputDir);
@@ -344153,7 +344720,7 @@ CLI Version: ${options2.cliVersion}`;
344153
344720
  const resolvingPatches = lockManager.getResolvingPatches();
344154
344721
  if (unresolvedPatches.length > 0) {
344155
344722
  const applicator = new ReplayApplicator(git, lockManager, outputDir);
344156
- await applicator.applyPatches(unresolvedPatches);
344723
+ await applicator.applyPatches(unresolvedPatches, { applyMode: "resolve" });
344157
344724
  const markerFiles = await findConflictMarkerFiles(git);
344158
344725
  if (markerFiles.length > 0) {
344159
344726
  for (const patch5 of unresolvedPatches) {
@@ -344179,20 +344746,38 @@ CLI Version: ${options2.cliVersion}`;
344179
344746
  if (patchesToCommit.length > 0) {
344180
344747
  const currentGen = lock.current_generation;
344181
344748
  const detector = new ReplayDetector(git, lockManager, outputDir);
344749
+ const warnings = [];
344182
344750
  let patchesResolved = 0;
344183
344751
  for (const patch5 of patchesToCommit) {
344184
- const diff2 = await git.exec(["diff", currentGen, "--", ...patch5.files]).catch(() => null);
344185
- if (!diff2 || !diff2.trim()) {
344752
+ const result = await computePerPatchDiffWithFallback(
344753
+ patch5,
344754
+ (f5) => git.showFile(currentGen, f5),
344755
+ (f5) => (0, import_promises310.readFile)((0, import_node_path72.join)(outputDir, f5), "utf-8").catch(() => null),
344756
+ (files) => git.exec(["diff", currentGen, "--", ...files]).catch(() => null)
344757
+ );
344758
+ if (result === null) {
344759
+ warnings.push(
344760
+ `Patch ${patch5.id}: cumulative-diff fallback failed for unlocatable files; preserving patch unchanged`
344761
+ );
344762
+ continue;
344763
+ }
344764
+ const diff2 = result.diff;
344765
+ if (!diff2.trim()) {
344186
344766
  lockManager.removePatch(patch5.id);
344187
344767
  continue;
344188
344768
  }
344769
+ if (result.hadFallback) {
344770
+ warnings.push(
344771
+ `Patch ${patch5.id}: ${result.fallbackFiles.join(", ")} could not be anchored; cumulative-diff fallback applied for those files`
344772
+ );
344773
+ }
344189
344774
  const newContentHash = detector.computeContentHash(diff2);
344190
- const changedFiles = await getChangedFiles(git, currentGen, patch5.files);
344775
+ const changedFiles = result.hadFallback ? await getChangedFiles(git, currentGen, patch5.files) : extractFilesFromDiff(diff2);
344191
344776
  lockManager.markPatchResolved(patch5.id, {
344192
344777
  patch_content: diff2,
344193
344778
  content_hash: newContentHash,
344194
344779
  base_generation: currentGen,
344195
- files: changedFiles
344780
+ files: changedFiles.length > 0 ? changedFiles : patch5.files
344196
344781
  });
344197
344782
  patchesResolved++;
344198
344783
  }
@@ -344208,7 +344793,8 @@ CLI Version: ${options2.cliVersion}`;
344208
344793
  success: true,
344209
344794
  commitSha: commitSha2,
344210
344795
  phase: "committed",
344211
- patchesResolved
344796
+ patchesResolved,
344797
+ warnings: warnings.length > 0 ? warnings : void 0
344212
344798
  };
344213
344799
  }
344214
344800
  const committer = new ReplayCommitter(git, outputDir);
@@ -344226,6 +344812,24 @@ CLI Version: ${options2.cliVersion}`;
344226
344812
  const changed = filesOutput.trim().split("\n").filter(Boolean).filter((f5) => !f5.startsWith(".fern/"));
344227
344813
  return changed.length > 0 ? changed : files;
344228
344814
  }
344815
+ function extractFilesFromDiff(unifiedDiff2) {
344816
+ const out = /* @__PURE__ */ new Set();
344817
+ const renameSources = /* @__PURE__ */ new Set();
344818
+ let currentTarget = null;
344819
+ for (const line of unifiedDiff2.split("\n")) {
344820
+ const headerMatch = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
344821
+ if (headerMatch) {
344822
+ currentTarget = headerMatch[2];
344823
+ if (!currentTarget.startsWith(".fern/")) out.add(currentTarget);
344824
+ continue;
344825
+ }
344826
+ if (currentTarget && line.startsWith("rename from ")) {
344827
+ renameSources.add(line.slice("rename from ".length));
344828
+ }
344829
+ }
344830
+ for (const src of renameSources) out.delete(src);
344831
+ return Array.from(out);
344832
+ }
344229
344833
  function status(outputDir) {
344230
344834
  const lockManager = new LockfileManager(outputDir);
344231
344835
  if (!lockManager.exists()) {
@@ -621386,7 +621990,7 @@ var AccessTokenPosthogManager = class {
621386
621990
  properties: {
621387
621991
  ...event,
621388
621992
  ...event.properties,
621389
- version: "5.0.0",
621993
+ version: "5.1.0",
621390
621994
  usingAccessToken: true
621391
621995
  }
621392
621996
  });
@@ -621440,7 +622044,7 @@ var UserPosthogManager = class {
621440
622044
  distinctId: this.userId ?? await this.getPersistedDistinctId(),
621441
622045
  event: "CLI",
621442
622046
  properties: {
621443
- version: "5.0.0",
622047
+ version: "5.1.0",
621444
622048
  ...event,
621445
622049
  ...event.properties,
621446
622050
  usingAccessToken: false,
@@ -625100,6 +625704,133 @@ var TelemetryClient = class _TelemetryClient {
625100
625704
  }
625101
625705
  };
625102
625706
 
625707
+ // ../cli-v2/lib/ui/TtyAwareLogger.js
625708
+ var import_is_ci3 = __toESM(require_is_ci(), 1);
625709
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
625710
+ var SPINNER_INTERVAL_MS = 100;
625711
+ var TtyAwareLogger2 = class {
625712
+ isTTY;
625713
+ stdout;
625714
+ stderr;
625715
+ paintables = [];
625716
+ lastPaint = "";
625717
+ frameIndex = 0;
625718
+ interval;
625719
+ suspended = false;
625720
+ suspendBuffer = [];
625721
+ finished = false;
625722
+ constructor(stdout, stderr) {
625723
+ this.stdout = stdout;
625724
+ this.stderr = stderr;
625725
+ this.isTTY = stdout.isTTY === true && !import_is_ci3.default;
625726
+ }
625727
+ /**
625728
+ * Register a paintable. The first registration starts the repaint loop.
625729
+ */
625730
+ register(paintable) {
625731
+ this.paintables.push(paintable);
625732
+ if (this.isTTY && this.interval == null) {
625733
+ this.stdout.write(ansi_escapes_default.cursorHide);
625734
+ this.repaint();
625735
+ this.interval = setInterval(() => this.repaint(), SPINNER_INTERVAL_MS);
625736
+ }
625737
+ }
625738
+ /**
625739
+ * Write content above the paint region.
625740
+ *
625741
+ * On non-TTY streams the content is written verbatim. On TTY streams the
625742
+ * paint region is cleared, the content is written, and the paint region
625743
+ * is redrawn in place.
625744
+ */
625745
+ write(content5, { stderr = false } = {}) {
625746
+ const stream5 = stderr ? this.stderr : this.stdout;
625747
+ if (!this.isTTY) {
625748
+ stream5.write(content5);
625749
+ return;
625750
+ }
625751
+ if (this.suspended) {
625752
+ this.suspendBuffer.push({ content: content5, stderr });
625753
+ return;
625754
+ }
625755
+ this.stdout.write(this.clear());
625756
+ stream5.write(content5);
625757
+ this.stdout.write(this.paint());
625758
+ }
625759
+ /**
625760
+ * Pause the paint loop while `run` executes, buffering any `write()` calls,
625761
+ * then flush the buffer and resume. Useful for writing content that must
625762
+ * not be clobbered by a repaint (e.g. an inquirer prompt).
625763
+ */
625764
+ async takeOverTerminal(run) {
625765
+ if (!this.isTTY) {
625766
+ await run();
625767
+ return;
625768
+ }
625769
+ this.stopLoop();
625770
+ this.stdout.write(this.clear());
625771
+ this.suspended = true;
625772
+ try {
625773
+ await run();
625774
+ } finally {
625775
+ this.suspended = false;
625776
+ for (const { content: content5, stderr: isStderr } of this.suspendBuffer) {
625777
+ const stream5 = isStderr ? this.stderr : this.stdout;
625778
+ stream5.write(content5);
625779
+ }
625780
+ this.suspendBuffer = [];
625781
+ if (this.paintables.length > 0) {
625782
+ this.stdout.write(ansi_escapes_default.cursorHide);
625783
+ this.repaint();
625784
+ this.interval = setInterval(() => this.repaint(), SPINNER_INTERVAL_MS);
625785
+ }
625786
+ }
625787
+ }
625788
+ /**
625789
+ * Stop the paint loop, perform one final repaint, and restore the cursor.
625790
+ * Idempotent.
625791
+ */
625792
+ finish() {
625793
+ if (!this.isTTY || this.finished) {
625794
+ return;
625795
+ }
625796
+ this.finished = true;
625797
+ this.stopLoop();
625798
+ this.stdout.write(this.clear() + this.paint());
625799
+ this.stdout.write(ansi_escapes_default.cursorShow);
625800
+ }
625801
+ stopLoop() {
625802
+ if (this.interval != null) {
625803
+ clearInterval(this.interval);
625804
+ this.interval = void 0;
625805
+ }
625806
+ }
625807
+ repaint() {
625808
+ this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
625809
+ this.stdout.write(this.clear() + this.paint());
625810
+ }
625811
+ clear() {
625812
+ if (this.lastPaint.length === 0) {
625813
+ return "";
625814
+ }
625815
+ return ansi_escapes_default.eraseLines(this.lastPaint.split("\n").length);
625816
+ }
625817
+ paint() {
625818
+ if (this.paintables.length === 0) {
625819
+ this.lastPaint = "";
625820
+ return "";
625821
+ }
625822
+ const spinnerFrame = SPINNER_FRAMES[this.frameIndex] ?? SPINNER_FRAMES[0];
625823
+ const bodies = this.paintables.map((p14) => p14.paint(spinnerFrame)).filter((body) => body.length > 0);
625824
+ if (bodies.length === 0) {
625825
+ this.lastPaint = "";
625826
+ return "";
625827
+ }
625828
+ const paint = bodies.join("\n") + "\n";
625829
+ this.lastPaint = paint;
625830
+ return paint;
625831
+ }
625832
+ };
625833
+
625103
625834
  // ../cli-v2/lib/api/config/converter/ApiDefinitionConverter.js
625104
625835
  init_lib6();
625105
625836
  var DEFAULT_API_NAME = "api";
@@ -626002,7 +626733,7 @@ var Context = class _Context {
626002
626733
  tokenService;
626003
626734
  ttyAwareLogger;
626004
626735
  static async create({ stdout, stderr, cwd: cwd2, logLevel }) {
626005
- const ttyAwareLogger = new TtyAwareLogger(stdout, stderr);
626736
+ const ttyAwareLogger = new TtyAwareLogger2(stdout, stderr);
626006
626737
  const telemetry = await TelemetryClient.create({ isTTY: ttyAwareLogger.isTTY });
626007
626738
  return new _Context({ stdout, stderr, cwd: cwd2, logLevel, ttyAwareLogger, telemetry });
626008
626739
  }
@@ -626245,31 +626976,44 @@ ${source_default.dim(`Logs written to: ${this.logs.absoluteFilePath}`)}
626245
626976
  this.ttyAwareLogger.finish();
626246
626977
  }
626247
626978
  log(level, ...parts) {
626248
- this.logImmediately([
626249
- {
626250
- parts,
626251
- level,
626252
- time: /* @__PURE__ */ new Date()
626253
- }
626254
- ]);
626979
+ this.writeLog(level, parts, { stderr: false });
626255
626980
  }
626256
626981
  logStderr(level, ...parts) {
626257
- this.logImmediately([
626258
- {
626259
- parts,
626260
- level,
626261
- time: /* @__PURE__ */ new Date()
626262
- }
626263
- ], { stderr: true });
626982
+ this.writeLog(level, parts, { stderr: true });
626264
626983
  }
626265
- logImmediately(logs4, { stderr = false } = {}) {
626266
- const filtered = logs4.filter((log4) => LOG_LEVELS.indexOf(log4.level) >= LOG_LEVELS.indexOf(this.logLevel));
626267
- this.ttyAwareLogger.log(filtered, {
626268
- includeDebugInfo: this.logLevel === LogLevel.Debug,
626269
- stderr
626984
+ writeLog(level, parts, { stderr }) {
626985
+ if (LOG_LEVELS.indexOf(level) < LOG_LEVELS.indexOf(this.logLevel)) {
626986
+ return;
626987
+ }
626988
+ const content5 = formatLogContent({
626989
+ level,
626990
+ parts,
626991
+ includeDebugInfo: this.logLevel === LogLevel.Debug
626270
626992
  });
626993
+ this.ttyAwareLogger.write(content5, { stderr });
626271
626994
  }
626272
626995
  };
626996
+ function formatLogContent({ level, parts, includeDebugInfo }) {
626997
+ let content5 = parts.join(" ");
626998
+ if (includeDebugInfo) {
626999
+ content5 = `${source_default.dim(`${getLogLevelPrefix(level)} ${(/* @__PURE__ */ new Date()).toISOString()} `)}${content5}`;
627000
+ }
627001
+ content5 += "\n";
627002
+ switch (level) {
627003
+ case LogLevel.Error:
627004
+ return source_default.red(content5);
627005
+ case LogLevel.Warn:
627006
+ return source_default.hex("FFA500")(content5);
627007
+ case LogLevel.Trace:
627008
+ case LogLevel.Debug:
627009
+ case LogLevel.Info:
627010
+ return content5;
627011
+ }
627012
+ }
627013
+ var LOG_LEVEL_PREFIX_WIDTH = Math.max(...LOG_LEVELS.map((l9) => l9.length));
627014
+ function getLogLevelPrefix(level) {
627015
+ return level.toUpperCase().padEnd(LOG_LEVEL_PREFIX_WIDTH);
627016
+ }
626273
627017
 
626274
627018
  // ../cli-v2/lib/context/loadDotenvFile.js
626275
627019
  var import_dotenv = __toESM(require_main2(), 1);
@@ -833175,7 +833919,7 @@ var LOCAL_STORAGE_FOLDER4 = ".fern-dev";
833175
833919
  var LOGS_FOLDER_NAME = "logs";
833176
833920
  var MAX_LOGS_DIR_SIZE_BYTES = 100 * 1024 * 1024;
833177
833921
  function getCliSource() {
833178
- const version7 = "5.0.0";
833922
+ const version7 = "5.1.0";
833179
833923
  return `cli@${version7}`;
833180
833924
  }
833181
833925
  var DebugLogger = class {
@@ -845593,7 +846337,7 @@ var LegacyDocsPublisher = class {
845593
846337
  previewId,
845594
846338
  disableTemplates: void 0,
845595
846339
  skipUpload,
845596
- cliVersion: "5.0.0",
846340
+ cliVersion: "5.1.0",
845597
846341
  loginCommand: "fern auth login"
845598
846342
  });
845599
846343
  if (taskContext.getResult() === TaskResult.Failure) {
@@ -845733,15 +846477,19 @@ var TaskGroup = class _TaskGroup {
845733
846477
  /**
845734
846478
  * Called by TtyAwareLogger to get the current display.
845735
846479
  * This is the integration point with TtyAwareLogger.
846480
+ *
846481
+ * Renders the task list wrapped in a bordered frame so multiple concurrent
846482
+ * TaskGroups (e.g. one per API in a fan-out) read as separate boxes rather
846483
+ * than a single megaframe.
845736
846484
  */
845737
- printInteractiveTasks({ spinner }) {
846485
+ paint(spinnerFrame) {
845738
846486
  const lines = [];
845739
846487
  for (const id2 of this.taskOrder) {
845740
846488
  const task = this.tasks[id2];
845741
846489
  if (task == null) {
845742
846490
  continue;
845743
846491
  }
845744
- const line = this.formatTaskLine(task, spinner);
846492
+ const line = this.formatTaskLine(task, spinnerFrame);
845745
846493
  if (line != null) {
845746
846494
  lines.push(line);
845747
846495
  }
@@ -845749,7 +846497,8 @@ var TaskGroup = class _TaskGroup {
845749
846497
  if (lines.length === 0) {
845750
846498
  return "";
845751
846499
  }
845752
- return lines.join("\n");
846500
+ const body = lines.join("\n");
846501
+ return ["\u250C\u2500", addPrefixToString({ content: body, prefix: "\u2502 ", includePrefixOnAllLines: true }), "\u2514\u2500"].join("\n");
845753
846502
  }
845754
846503
  /**
845755
846504
  * Complete the task group and show summary.
@@ -845842,7 +846591,7 @@ var TaskGroup = class _TaskGroup {
845842
846591
  }
845843
846592
  const lines = [];
845844
846593
  for (const stage of stages) {
845845
- const icon = this.getStageIcon(stage.status, spinnerFrame);
846594
+ const icon = this.getStageIcon(stage.status);
845846
846595
  let line = `
845847
846596
  ${icon} ${this.getStageLabel(stage)}`;
845848
846597
  if (stage.detail != null) {
@@ -845873,7 +846622,7 @@ var TaskGroup = class _TaskGroup {
845873
846622
  * Uses a static arrow (▸) for running stages to avoid visual noise
845874
846623
  * from having multiple spinners (the parent task already has a spinner).
845875
846624
  */
845876
- getStageIcon(status, _spinnerFrame) {
846625
+ getStageIcon(status) {
845877
846626
  switch (status) {
845878
846627
  case "pending":
845879
846628
  return source_default.dim("\u25CB");
@@ -846002,7 +846751,7 @@ ${indentStr}${source_default.dim(`... ${hiddenCount} earlier logs hidden ...`)}$
846002
846751
  return;
846003
846752
  }
846004
846753
  this.isRegistered = true;
846005
- this.context.ttyAwareLogger.registerTask(this);
846754
+ this.context.ttyAwareLogger.register(this);
846006
846755
  }
846007
846756
  };
846008
846757
 
@@ -866286,6 +867035,15 @@ var DynamicLiteralMapper = class extends WithGeneration {
866286
867035
  }));
866287
867036
  }
866288
867037
  convertObject({ object_, value: value2, fallbackToDefault }) {
867038
+ const classReference4 = this.csharp.classReference({
867039
+ origin: object_.declaration,
867040
+ namespace: this.context.getNamespace(object_.declaration.fernFilepath)
867041
+ });
867042
+ const propertyNameByWireValue = /* @__PURE__ */ new Map();
867043
+ for (const property23 of object_.properties) {
867044
+ const actualName = this.context.resolvePropertyName(classReference4, property23.name.name);
867045
+ propertyNameByWireValue.set(property23.name.wireValue, actualName);
867046
+ }
866289
867047
  const record22 = this.context.getRecord(value2) ?? {};
866290
867048
  const properties7 = this.context.associateByWireValue({
866291
867049
  parameters: object_.properties,
@@ -866295,7 +867053,7 @@ var DynamicLiteralMapper = class extends WithGeneration {
866295
867053
  this.context.errors.scope(property23.name.wireValue);
866296
867054
  try {
866297
867055
  return {
866298
- name: this.context.getClassName(property23.name.name),
867056
+ name: propertyNameByWireValue.get(property23.name.wireValue) ?? this.context.getClassName(property23.name.name),
866299
867057
  value: this.convert(property23)
866300
867058
  };
866301
867059
  } finally {
@@ -866304,7 +867062,7 @@ var DynamicLiteralMapper = class extends WithGeneration {
866304
867062
  });
866305
867063
  const providedWireValues = new Set(Object.keys(record22));
866306
867064
  for (const property23 of object_.properties) {
866307
- const fieldName = this.context.getClassName(property23.name.name);
867065
+ const fieldName = propertyNameByWireValue.get(property23.name.wireValue) ?? this.context.getClassName(property23.name.name);
866308
867066
  if (providedWireValues.has(property23.name.wireValue)) {
866309
867067
  if (this.isRequiredProperty(property23.typeReference)) {
866310
867068
  const existing = fields.find((f5) => f5.name === fieldName);
@@ -866328,10 +867086,7 @@ var DynamicLiteralMapper = class extends WithGeneration {
866328
867086
  }
866329
867087
  }
866330
867088
  return this.csharp.Literal.class_({
866331
- reference: this.csharp.classReference({
866332
- origin: object_.declaration,
866333
- namespace: this.context.getNamespace(object_.declaration.fernFilepath)
866334
- }),
867089
+ reference: classReference4,
866335
867090
  fields
866336
867091
  });
866337
867092
  }
@@ -866382,13 +867137,17 @@ var DynamicLiteralMapper = class extends WithGeneration {
866382
867137
  return void 0;
866383
867138
  }
866384
867139
  visited.add(typeId);
867140
+ const classReference4 = this.csharp.classReference({
867141
+ origin: object_.declaration,
867142
+ namespace: this.context.getNamespace(object_.declaration.fernFilepath)
867143
+ });
866385
867144
  const fields = [];
866386
867145
  for (const property23 of object_.properties) {
866387
867146
  if (this.isRequiredProperty(property23.typeReference)) {
866388
867147
  const defaultValue = this.getDefaultLiteralForType(property23.typeReference, visited);
866389
867148
  if (defaultValue != null) {
866390
867149
  fields.push({
866391
- name: this.context.getClassName(property23.name.name),
867150
+ name: this.context.resolvePropertyName(classReference4, property23.name.name),
866392
867151
  value: defaultValue
866393
867152
  });
866394
867153
  }
@@ -866396,10 +867155,7 @@ var DynamicLiteralMapper = class extends WithGeneration {
866396
867155
  }
866397
867156
  visited.delete(typeId);
866398
867157
  return this.csharp.Literal.class_({
866399
- reference: this.csharp.classReference({
866400
- origin: object_.declaration,
866401
- namespace: this.context.getNamespace(object_.declaration.fernFilepath)
866402
- }),
867158
+ reference: classReference4,
866403
867159
  fields
866404
867160
  });
866405
867161
  }
@@ -866953,6 +867709,22 @@ var DynamicSnippetsGeneratorContext = class _DynamicSnippetsGeneratorContext ext
866953
867709
  getPropertyName(name2) {
866954
867710
  return name2.pascalCase.safeName;
866955
867711
  }
867712
+ /**
867713
+ * Returns a property name that is safe within the given class reference's scope.
867714
+ * Detects collisions where PascalCase(property) == ClassName and returns the
867715
+ * renamed identifier (e.g., "CatalogV1Id_") to avoid CS0542 / CS0117.
867716
+ */
867717
+ resolvePropertyName(classReference4, name2) {
867718
+ const expectedName = name2.pascalCase.safeName;
867719
+ const redirected = classReference4.scope.getRedirectedFieldName(expectedName);
867720
+ if (redirected) {
867721
+ return redirected;
867722
+ }
867723
+ if (classReference4.scope.isField(expectedName)) {
867724
+ return expectedName;
867725
+ }
867726
+ return classReference4.registerField(expectedName);
867727
+ }
866956
867728
  getMethodName(name2) {
866957
867729
  return `${name2.pascalCase.safeName}Async`;
866958
867730
  }
@@ -867439,13 +868211,17 @@ var EndpointSnippetGenerator2 = class extends WithGeneration {
867439
868211
  });
867440
868212
  }
867441
868213
  getInlinedRequestArg({ request: request7, snippet: snippet2, pathParameterFields, filePropertyInfo }) {
868214
+ const classReference4 = this.csharp.classReference({
868215
+ origin: request7.declaration,
868216
+ namespace: this.context.getNamespace(request7.declaration.fernFilepath)
868217
+ });
867442
868218
  this.context.errors.scope(Scope4.QueryParameters);
867443
868219
  const queryParameters = this.context.associateQueryParametersByWireValue({
867444
868220
  parameters: request7.queryParameters ?? [],
867445
868221
  values: snippet2.queryParameters ?? {}
867446
868222
  });
867447
868223
  const queryParameterFields = queryParameters.map((queryParameter) => ({
867448
- name: this.context.getPropertyName(queryParameter.name.name),
868224
+ name: this.context.resolvePropertyName(classReference4, queryParameter.name.name),
867449
868225
  value: this.context.dynamicLiteralMapper.convert(queryParameter)
867450
868226
  }));
867451
868227
  this.context.errors.unscope();
@@ -867455,7 +868231,7 @@ var EndpointSnippetGenerator2 = class extends WithGeneration {
867455
868231
  values: snippet2.headers ?? {}
867456
868232
  });
867457
868233
  const headerFields = headers2.map((header) => ({
867458
- name: this.context.getPropertyName(header.name.name),
868234
+ name: this.context.resolvePropertyName(classReference4, header.name.name),
867459
868235
  value: this.context.dynamicLiteralMapper.convert({
867460
868236
  ...header,
867461
868237
  fallbackToDefault: header.name.wireValue
@@ -867466,30 +868242,32 @@ var EndpointSnippetGenerator2 = class extends WithGeneration {
867466
868242
  const requestBodyFields = request7.body != null ? this.getInlinedRequestBodyConstructorFields({
867467
868243
  body: request7.body,
867468
868244
  value: snippet2.requestBody,
867469
- filePropertyInfo
868245
+ filePropertyInfo,
868246
+ classReference: classReference4
867470
868247
  }) : [];
867471
868248
  this.context.errors.unscope();
867472
868249
  return this.csharp.Literal.class_({
867473
- reference: this.csharp.classReference({
867474
- origin: request7.declaration,
867475
- namespace: this.context.getNamespace(request7.declaration.fernFilepath)
867476
- }),
868250
+ reference: classReference4,
867477
868251
  fields: [...pathParameterFields, ...queryParameterFields, ...headerFields, ...requestBodyFields]
867478
868252
  });
867479
868253
  }
867480
- getInlinedRequestBodyConstructorFields({ body, value: value2, filePropertyInfo }) {
868254
+ getInlinedRequestBodyConstructorFields({ body, value: value2, filePropertyInfo, classReference: classReference4 }) {
867481
868255
  switch (body.type) {
867482
868256
  case "properties":
867483
- return this.getInlinedRequestBodyPropertyConstructorFields({ parameters: body.value, value: value2 });
868257
+ return this.getInlinedRequestBodyPropertyConstructorFields({
868258
+ parameters: body.value,
868259
+ value: value2,
868260
+ classReference: classReference4
868261
+ });
867484
868262
  case "referenced":
867485
- return [this.getReferencedRequestBodyPropertyConstructorField({ body, value: value2 })];
868263
+ return [this.getReferencedRequestBodyPropertyConstructorField({ body, value: value2, classReference: classReference4 })];
867486
868264
  case "fileUpload":
867487
868265
  return this.getFileUploadRequestBodyConstructorFields({ filePropertyInfo });
867488
868266
  default:
867489
868267
  assertNever(body);
867490
868268
  }
867491
868269
  }
867492
- getInlinedRequestBodyPropertyConstructorFields({ parameters: parameters2, value: value2 }) {
868270
+ getInlinedRequestBodyPropertyConstructorFields({ parameters: parameters2, value: value2, classReference: classReference4 }) {
867493
868271
  const fields = [];
867494
868272
  const bodyProperties = this.context.associateByWireValue({
867495
868273
  parameters: parameters2,
@@ -867497,7 +868275,7 @@ var EndpointSnippetGenerator2 = class extends WithGeneration {
867497
868275
  });
867498
868276
  for (const parameter6 of bodyProperties) {
867499
868277
  fields.push({
867500
- name: this.context.getPropertyName(parameter6.name.name),
868278
+ name: this.context.resolvePropertyName(classReference4, parameter6.name.name),
867501
868279
  value: this.context.dynamicLiteralMapper.convert({
867502
868280
  ...parameter6,
867503
868281
  fallbackToDefault: parameter6.name.wireValue
@@ -867509,9 +868287,9 @@ var EndpointSnippetGenerator2 = class extends WithGeneration {
867509
868287
  getFileUploadRequestBodyConstructorFields({ filePropertyInfo }) {
867510
868288
  return [...filePropertyInfo.fileFields, ...filePropertyInfo.bodyPropertyFields];
867511
868289
  }
867512
- getReferencedRequestBodyPropertyConstructorField({ body, value: value2 }) {
868290
+ getReferencedRequestBodyPropertyConstructorField({ body, value: value2, classReference: classReference4 }) {
867513
868291
  return {
867514
- name: this.context.getPropertyName(body.bodyKey),
868292
+ name: this.context.resolvePropertyName(classReference4, body.bodyKey),
867515
868293
  value: this.getReferencedRequestBodyPropertyLiteral({ body: body.bodyType, value: value2 })
867516
868294
  };
867517
868295
  }
@@ -874445,7 +875223,7 @@ var EndpointSnippetGenerator4 = class {
874445
875223
  args.push(...pathParameterFields.map((field5) => field5.value));
874446
875224
  }
874447
875225
  if (!inlineFileProperties) {
874448
- args.push(...filePropertyInfo.fileFields.map((field5) => field5.value));
875226
+ args.push(...filePropertyInfo.fileFields.map((field5) => java_exports.TypeLiteral.isNop(field5.value) ? java_exports.TypeLiteral.raw("null") : field5.value));
874449
875227
  }
874450
875228
  args.push(this.getInlinedRequestArg({
874451
875229
  request: request7,
@@ -890569,6 +891347,40 @@ init_lib4();
890569
891347
  function escapeSwiftStringLiteral(rawValue) {
890570
891348
  return rawValue.replace(/\\u([0-9a-fA-F]{4})/g, "\\u{$1}").replace(/(?<!\\)\\f/g, "\\u{000C}").replace(/(?<!\\)\\v/g, "\\u{000B}").replace(/(?<!\\)\\0/g, "\\u{0000}").replace(/(?<!\\)"/g, '\\"');
890571
891349
  }
891350
+ function escapeSwiftStringLiteralContent(value2) {
891351
+ let result = "";
891352
+ for (const ch of value2) {
891353
+ switch (ch) {
891354
+ case "\\":
891355
+ result += "\\\\";
891356
+ break;
891357
+ case '"':
891358
+ result += '\\"';
891359
+ break;
891360
+ case "\n":
891361
+ result += "\\n";
891362
+ break;
891363
+ case "\r":
891364
+ result += "\\r";
891365
+ break;
891366
+ case " ":
891367
+ result += "\\t";
891368
+ break;
891369
+ case "\0":
891370
+ result += "\\0";
891371
+ break;
891372
+ default: {
891373
+ const code5 = ch.charCodeAt(0);
891374
+ if (code5 < 32 || code5 === 127) {
891375
+ result += `\\u{${code5.toString(16).toUpperCase()}}`;
891376
+ } else {
891377
+ result += ch;
891378
+ }
891379
+ }
891380
+ }
891381
+ }
891382
+ return result;
891383
+ }
890572
891384
 
890573
891385
  // ../../../generators/swift/codegen/lib/syntax/reserved-keywords.js
890574
891386
  var reservedKeywords = [
@@ -890761,7 +891573,7 @@ var Expression2 = class extends AstNode8 {
890761
891573
  this.internalExpression.expression.write(writer2);
890762
891574
  break;
890763
891575
  case "string-literal":
890764
- writer2.write(`"${this.internalExpression.value}"`);
891576
+ writer2.write(this.internalExpression.escape ? `"${escapeSwiftStringLiteralContent(this.internalExpression.value)}"` : `"${this.internalExpression.value}"`);
890765
891577
  break;
890766
891578
  case "number-literal":
890767
891579
  writer2.write(this.internalExpression.value.toString());
@@ -890900,6 +891712,17 @@ var Expression2 = class extends AstNode8 {
890900
891712
  static stringLiteral(value2) {
890901
891713
  return new this({ type: "string-literal", value: value2 });
890902
891714
  }
891715
+ /**
891716
+ * Like {@link Expression.stringLiteral}, but escapes characters in `value`
891717
+ * that cannot appear unescaped inside a Swift `"..."` string literal
891718
+ * (control characters, real newlines, real backslashes, real double quotes).
891719
+ * Use this when `value` is raw user data (e.g. from JSON.parse), not a
891720
+ * pre-formed Swift string literal containing intentional backslash
891721
+ * sequences such as `\(...)` interpolation.
891722
+ */
891723
+ static escapedStringLiteral(value2) {
891724
+ return new this({ type: "string-literal", value: value2, escape: true });
891725
+ }
890903
891726
  static numberLiteral(value2) {
890904
891727
  return new this({ type: "number-literal", value: value2 });
890905
891728
  }
@@ -893071,7 +893894,7 @@ var DynamicTypeLiteralMapper6 = class {
893071
893894
  methodName: "string",
893072
893895
  arguments_: [
893073
893896
  swift_exports.functionArgument({
893074
- value: swift_exports.Expression.stringLiteral(value2)
893897
+ value: swift_exports.Expression.escapedStringLiteral(value2)
893075
893898
  })
893076
893899
  ]
893077
893900
  });
@@ -893109,7 +893932,7 @@ var DynamicTypeLiteralMapper6 = class {
893109
893932
  swift_exports.functionArgument({
893110
893933
  value: swift_exports.Expression.dictionaryLiteral({
893111
893934
  entries: Object.entries(value2).map(([key2, value3]) => [
893112
- swift_exports.Expression.stringLiteral(key2),
893935
+ swift_exports.Expression.escapedStringLiteral(key2),
893113
893936
  this.convertUnknown(value3)
893114
893937
  ]),
893115
893938
  multiline: true
@@ -893148,7 +893971,7 @@ var DynamicTypeLiteralMapper6 = class {
893148
893971
  if (str3 == null) {
893149
893972
  return swift_exports.Expression.nop();
893150
893973
  }
893151
- return swift_exports.Expression.stringLiteral(str3);
893974
+ return swift_exports.Expression.escapedStringLiteral(str3);
893152
893975
  }
893153
893976
  case "DATE": {
893154
893977
  const date25 = this.context.getValueAsString({ value: value2 });
@@ -893181,7 +894004,7 @@ var DynamicTypeLiteralMapper6 = class {
893181
894004
  if (base643 == null) {
893182
894005
  return swift_exports.Expression.nop();
893183
894006
  }
893184
- return swift_exports.Expression.stringLiteral(base643);
894007
+ return swift_exports.Expression.escapedStringLiteral(base643);
893185
894008
  }
893186
894009
  case "BIG_INTEGER": {
893187
894010
  const bigInt = this.context.getValueAsString({ value: value2 });
@@ -893724,7 +894547,7 @@ var EndpointSnippetGenerator9 = class {
893724
894547
  } else {
893725
894548
  return swift_exports.functionArgument({
893726
894549
  label: "baseURL",
893727
- value: swift_exports.Expression.stringLiteral(snippet2.baseURL)
894550
+ value: swift_exports.Expression.escapedStringLiteral(snippet2.baseURL)
893728
894551
  });
893729
894552
  }
893730
894553
  }
@@ -893748,10 +894571,10 @@ var EndpointSnippetGenerator9 = class {
893748
894571
  }
893749
894572
  args.push(swift_exports.functionArgument({
893750
894573
  label: auth8.username.camelCase.unsafeName,
893751
- value: swift_exports.Expression.stringLiteral(values2.username)
894574
+ value: swift_exports.Expression.escapedStringLiteral(values2.username)
893752
894575
  }), swift_exports.functionArgument({
893753
894576
  label: auth8.password.camelCase.unsafeName,
893754
- value: swift_exports.Expression.stringLiteral(values2.password)
894577
+ value: swift_exports.Expression.escapedStringLiteral(values2.password)
893755
894578
  }));
893756
894579
  break;
893757
894580
  case "bearer":
@@ -893764,7 +894587,7 @@ var EndpointSnippetGenerator9 = class {
893764
894587
  }
893765
894588
  args.push(swift_exports.functionArgument({
893766
894589
  label: auth8.token.camelCase.unsafeName,
893767
- value: swift_exports.Expression.stringLiteral(values2.token)
894590
+ value: swift_exports.Expression.escapedStringLiteral(values2.token)
893768
894591
  }));
893769
894592
  break;
893770
894593
  case "header":
@@ -919841,7 +920664,7 @@ var CliContext = class _CliContext {
919841
920664
  if (false) {
919842
920665
  this.logger.error("CLI_VERSION is not defined");
919843
920666
  }
919844
- return "5.0.0";
920667
+ return "5.1.0";
919845
920668
  }
919846
920669
  getCliName() {
919847
920670
  if (false) {
@@ -938672,7 +939495,7 @@ init_lib6();
938672
939495
  var import_cli_progress2 = __toESM(require_cli_progress(), 1);
938673
939496
  var import_fs42 = require("fs");
938674
939497
  var import_promises191 = require("fs/promises");
938675
- var import_is_ci3 = __toESM(require_is_ci(), 1);
939498
+ var import_is_ci4 = __toESM(require_is_ci(), 1);
938676
939499
  var import_path105 = __toESM(require("path"), 1);
938677
939500
 
938678
939501
  // src/commands/write-translation/translation-service.ts
@@ -939529,7 +940352,7 @@ async function writeTranslationForProject({
939529
940352
  }
939530
940353
  const totalFilesToProcess = filesToProcess.length + (docsConfigHasChanged ? 1 : 0);
939531
940354
  const totalProgressItems = totalFilesToProcess * targetLanguages.length;
939532
- const useProgressBar = process.stdout.isTTY && !import_is_ci3.default && totalProgressItems > 0;
940355
+ const useProgressBar = process.stdout.isTTY && !import_is_ci4.default && totalProgressItems > 0;
939533
940356
  let progressBar;
939534
940357
  if (useProgressBar) {
939535
940358
  progressBar = new import_cli_progress2.default.SingleBar({