@fern-api/replay 0.16.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -588,6 +588,7 @@ function parseRevertedMessage(subject) {
588
588
  // src/LockfileManager.ts
589
589
  var import_node_fs = require("fs");
590
590
  var import_node_path = require("path");
591
+ var import_minimatch = require("minimatch");
591
592
  var import_yaml = require("yaml");
592
593
  var LOCKFILE_HEADER = "# DO NOT EDIT MANUALLY - Managed by Fern Replay\n";
593
594
  var LockfileNotFoundError = class extends Error {
@@ -599,10 +600,23 @@ var LockfileNotFoundError = class extends Error {
599
600
  var LockfileManager = class {
600
601
  outputDir;
601
602
  lock = null;
603
+ fernignorePatterns = [];
602
604
  warnings = [];
603
605
  constructor(outputDir) {
604
606
  this.outputDir = outputDir;
605
607
  }
608
+ /**
609
+ * Inform the lockfile of the customer's `.fernignore` patterns so that
610
+ * `save()` can suppress its "Patch X removed from lockfile" warning when
611
+ * every file in the stripped patch is already `.fernignore`-protected.
612
+ * The strip itself still happens (credentials never persist); only the
613
+ * customer-visible warning is suppressed in that case, because the
614
+ * protected files survive the generator wipe regardless of whether the
615
+ * lockfile tracks them, so there's nothing for the customer to action.
616
+ */
617
+ setFernignorePatterns(patterns) {
618
+ this.fernignorePatterns = patterns;
619
+ }
606
620
  get lockfilePath() {
607
621
  return (0, import_node_path.join)(this.outputDir, ".fern", "replay.lock");
608
622
  }
@@ -671,9 +685,14 @@ var LockfileManager = class {
671
685
  if (sensitiveFiles.length > 0) {
672
686
  reasons.push(`sensitive files: ${sensitiveFiles.join(", ")}`);
673
687
  }
674
- this.warnings.push(
675
- `Patch ${patch.id} removed from lockfile: ${reasons.join("; ")}. This prevents credential exposure in committed lockfiles.`
688
+ const allProtected = patch.files.length > 0 && patch.files.every(
689
+ (f) => this.fernignorePatterns.some((p) => f === p || (0, import_minimatch.minimatch)(f, p))
676
690
  );
691
+ if (!allProtected) {
692
+ this.warnings.push(
693
+ `Patch ${patch.id} removed from lockfile: ${reasons.join("; ")}. This prevents credential exposure in committed lockfiles.`
694
+ );
695
+ }
677
696
  } else {
678
697
  cleanPatches.push(patch);
679
698
  }
@@ -1556,7 +1575,7 @@ function applyBothPatches(baseLines, oursPatches, theirsPatches) {
1556
1575
  var import_promises3 = require("fs/promises");
1557
1576
  var import_node_os = require("os");
1558
1577
  var import_node_path5 = require("path");
1559
- var import_minimatch = require("minimatch");
1578
+ var import_minimatch2 = require("minimatch");
1560
1579
 
1561
1580
  // src/accumulator/MergeAccumulator.ts
1562
1581
  var MergeAccumulator = class {
@@ -2272,13 +2291,13 @@ var ReplayApplicator = class {
2272
2291
  isExcluded(patch) {
2273
2292
  const config = this.lockManager.getCustomizationsConfig();
2274
2293
  if (!config.exclude) return false;
2275
- return patch.files.some((file) => config.exclude.some((pattern) => (0, import_minimatch.minimatch)(file, pattern)));
2294
+ return patch.files.some((file) => config.exclude.some((pattern) => (0, import_minimatch2.minimatch)(file, pattern)));
2276
2295
  }
2277
2296
  async resolveFilePath(filePath, baseTreeHash, currentTreeHash) {
2278
2297
  const config = this.lockManager.getCustomizationsConfig();
2279
2298
  if (config.moves) {
2280
2299
  for (const move of config.moves) {
2281
- if ((0, import_minimatch.minimatch)(filePath, move.from) || filePath === move.from) {
2300
+ if ((0, import_minimatch2.minimatch)(filePath, move.from) || filePath === move.from) {
2282
2301
  if (filePath === move.from) {
2283
2302
  return move.to;
2284
2303
  }
@@ -2569,7 +2588,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
2569
2588
  var import_node_fs2 = require("fs");
2570
2589
  var import_promises6 = require("fs/promises");
2571
2590
  var import_node_path8 = require("path");
2572
- var import_minimatch3 = require("minimatch");
2591
+ var import_minimatch4 = require("minimatch");
2573
2592
  init_GitClient();
2574
2593
 
2575
2594
  // src/PatchRegionDiff.ts
@@ -2680,12 +2699,23 @@ async function computePerPatchDiff(patch, getCurrentGenContent, getWorkingTreeCo
2680
2699
  unlocatableFiles.push(file);
2681
2700
  continue;
2682
2701
  }
2683
- const locInCurrentGen = locInCurrentGenRaw.map(
2684
- (l) => resolveSpan(l, currentGenLines, "old")
2685
- );
2686
- const locInWorkingTree = locInWorkingTreeRaw.map(
2687
- (l) => resolveSpan(l, workingTreeLines, "new")
2688
- );
2702
+ const locInCurrentGen = [];
2703
+ const locInWorkingTree = [];
2704
+ let spanResolutionFailed = false;
2705
+ for (let i = 0; i < locInCurrentGenRaw.length; i++) {
2706
+ const cg = resolveSpan(locInCurrentGenRaw[i], currentGenLines, "old");
2707
+ const wt = resolveSpan(locInWorkingTreeRaw[i], workingTreeLines, "new");
2708
+ if (cg === null || wt === null) {
2709
+ spanResolutionFailed = true;
2710
+ break;
2711
+ }
2712
+ locInCurrentGen.push(cg);
2713
+ locInWorkingTree.push(wt);
2714
+ }
2715
+ if (spanResolutionFailed) {
2716
+ unlocatableFiles.push(file);
2717
+ continue;
2718
+ }
2689
2719
  const overlay = overlayRegions(
2690
2720
  currentGenLines,
2691
2721
  locInCurrentGen,
@@ -2747,11 +2777,11 @@ function resolveSpan(loc, oursLines, prefer) {
2747
2777
  if (prefer === "old") {
2748
2778
  if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
2749
2779
  if (newFits) return { ...loc, oursSpan: hunk.newCount };
2750
- } else {
2751
- if (newFits) return { ...loc, oursSpan: hunk.newCount };
2752
- if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
2780
+ return loc;
2753
2781
  }
2754
- return loc;
2782
+ if (newFits) return { ...loc, oursSpan: hunk.newCount };
2783
+ if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
2784
+ return null;
2755
2785
  }
2756
2786
  function sequenceMatches(needle, haystack, offset) {
2757
2787
  if (offset + needle.length > haystack.length) return false;
@@ -2865,7 +2895,7 @@ function synthesizeDeleteFileDiff(file, content) {
2865
2895
  }
2866
2896
 
2867
2897
  // src/classifier/FileOwnership.ts
2868
- var import_minimatch2 = require("minimatch");
2898
+ var import_minimatch3 = require("minimatch");
2869
2899
  var FileOwnership = class {
2870
2900
  constructor(git) {
2871
2901
  this.git = git;
@@ -2889,7 +2919,7 @@ var FileOwnership = class {
2889
2919
  const { file, originalFileName, patch, currentGenSha, fernignorePatterns, warnedGens, warnings } = ctx;
2890
2920
  if (patch.user_owned) return true;
2891
2921
  if (file === ".fernignore") return true;
2892
- if (fernignorePatterns.some((p) => file === p || (0, import_minimatch2.minimatch)(file, p))) return true;
2922
+ if (fernignorePatterns.some((p) => file === p || (0, import_minimatch3.minimatch)(file, p))) return true;
2893
2923
  if (fileIsCreationInPatchContent(patch.patch_content, originalFileName ?? file)) {
2894
2924
  return true;
2895
2925
  }
@@ -2968,6 +2998,7 @@ var FirstGenerationFlow = class {
2968
2998
  };
2969
2999
  }
2970
3000
  async apply(_prep) {
3001
+ this.service.lockManager.setFernignorePatterns(this.service.readFernignorePatterns());
2971
3002
  this.service.lockManager.save();
2972
3003
  return firstGenerationReport();
2973
3004
  }
@@ -3026,6 +3057,7 @@ var SkipApplicationFlow = class {
3026
3057
  };
3027
3058
  }
3028
3059
  async apply(_prep, options) {
3060
+ this.service.lockManager.setFernignorePatterns(this.service.readFernignorePatterns());
3029
3061
  this.service.lockManager.save();
3030
3062
  const credentialWarnings = [...this.service.lockManager.warnings];
3031
3063
  if (!options?.stageOnly) {
@@ -3149,6 +3181,7 @@ var NoPatchesFlow = class {
3149
3181
  this.service.lockManager.addPatch(patch);
3150
3182
  }
3151
3183
  }
3184
+ this.service.lockManager.setFernignorePatterns(this.service.readFernignorePatterns());
3152
3185
  this.service.lockManager.save();
3153
3186
  this.service.warnings.push(...this.service.lockManager.warnings);
3154
3187
  if (newPatches.length > 0) {
@@ -3311,6 +3344,7 @@ var NormalRegenerationFlow = class {
3311
3344
  }
3312
3345
  }
3313
3346
  }
3347
+ this.service.lockManager.setFernignorePatterns(this.service.readFernignorePatterns());
3314
3348
  this.service.lockManager.save();
3315
3349
  this.service.warnings.push(...this.service.lockManager.warnings);
3316
3350
  if (options?.stageOnly) {
@@ -3544,7 +3578,7 @@ var ReplayService = class {
3544
3578
  )
3545
3579
  );
3546
3580
  const hasProtectedFiles = patch.files.some(
3547
- (file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch3.minimatch)(file, p))
3581
+ (file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch4.minimatch)(file, p))
3548
3582
  );
3549
3583
  const allUserOwned = isUserOwnedPerFile.every(Boolean);
3550
3584
  if (allUserOwned && patch.files.length > 0) {
@@ -3984,7 +4018,7 @@ var ReplayService = class {
3984
4018
  );
3985
4019
  if (snapHasStaleMarkers) continue;
3986
4020
  const isProtectedSnap = patch.files.some(
3987
- (file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch3.minimatch)(file, p))
4021
+ (file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch4.minimatch)(file, p))
3988
4022
  );
3989
4023
  if (isProtectedSnap) continue;
3990
4024
  const snapHash = this.detector.computeContentHash(snapshotDiff);
@@ -4004,6 +4038,11 @@ var ReplayService = class {
4004
4038
  (files) => this.git.exec(["diff", currentGen, "HEAD", "--", ...files]).catch(() => null)
4005
4039
  );
4006
4040
  if (ppResult === null) continue;
4041
+ if (ppResult.hadFallback) {
4042
+ this.warnings.push(
4043
+ `Patch ${patch.id}: couldn't fully isolate ${ppResult.fallbackFiles.join(", ")} \u2014 customization preserved, attribution may have merged with another patch.`
4044
+ );
4045
+ }
4007
4046
  const diff = ppResult.diff;
4008
4047
  if (!diff.trim()) {
4009
4048
  if (await allPatchFilesUserOwned(patch)) continue;
@@ -4019,7 +4058,7 @@ var ReplayService = class {
4019
4058
  continue;
4020
4059
  }
4021
4060
  const isProtected = patch.files.some(
4022
- (file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch3.minimatch)(file, p))
4061
+ (file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch4.minimatch)(file, p))
4023
4062
  );
4024
4063
  if (isProtected) {
4025
4064
  continue;
@@ -4091,6 +4130,11 @@ var ReplayService = class {
4091
4130
  (files) => this.git.exec(["diff", currentGen, "HEAD", "--", ...files]).catch(() => null)
4092
4131
  );
4093
4132
  if (ppResult === null) continue;
4133
+ if (ppResult.hadFallback) {
4134
+ this.warnings.push(
4135
+ `Patch ${patch.id}: couldn't fully isolate ${ppResult.fallbackFiles.join(", ")} \u2014 customization preserved, attribution may have merged with another patch.`
4136
+ );
4137
+ }
4094
4138
  const diff = ppResult.diff;
4095
4139
  if (!diff.trim()) {
4096
4140
  if (await allPatchFilesUserOwned(patch)) {
@@ -4178,13 +4222,14 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
4178
4222
  if (files.length === 0) return;
4179
4223
  const fernignorePatterns = this.readFernignorePatterns();
4180
4224
  for (const file of files) {
4181
- if (fernignorePatterns.some((pattern) => (0, import_minimatch3.minimatch)(file, pattern))) continue;
4225
+ if (fernignorePatterns.some((pattern) => (0, import_minimatch4.minimatch)(file, pattern))) continue;
4182
4226
  try {
4183
4227
  await this.git.exec(["checkout", "HEAD", "--", file]);
4184
4228
  } catch {
4185
4229
  }
4186
4230
  }
4187
4231
  }
4232
+ /** @internal Used by Flow implementations in `src/flows/`. */
4188
4233
  readFernignorePatterns() {
4189
4234
  const fernignorePath = (0, import_node_path8.join)(this.outputDir, ".fernignore");
4190
4235
  if (!(0, import_node_fs2.existsSync)(fernignorePath)) return [];
@@ -4267,7 +4312,7 @@ function computeCommitBuckets(results, absorbedPatchIds, patchesPassedToCommit)
4267
4312
  var import_node_crypto2 = require("crypto");
4268
4313
  var import_node_fs3 = require("fs");
4269
4314
  var import_node_path9 = require("path");
4270
- var import_minimatch4 = require("minimatch");
4315
+ var import_minimatch5 = require("minimatch");
4271
4316
  var import_yaml2 = require("yaml");
4272
4317
  var FernignoreMigrator = class {
4273
4318
  git;
@@ -4299,7 +4344,7 @@ var FernignoreMigrator = class {
4299
4344
  const commitsOnly = [];
4300
4345
  const syntheticPatches = [];
4301
4346
  for (const pattern of patterns) {
4302
- const matchingPatch = patches.find((p) => p.files.some((f) => (0, import_minimatch4.minimatch)(f, pattern) || f === pattern));
4347
+ const matchingPatch = patches.find((p) => p.files.some((f) => (0, import_minimatch5.minimatch)(f, pattern) || f === pattern));
4303
4348
  if (matchingPatch) {
4304
4349
  trackedByBoth.push({
4305
4350
  file: pattern,
@@ -4372,7 +4417,7 @@ var FernignoreMigrator = class {
4372
4417
  fernignoreOnly.push(...remainingFernignoreOnly);
4373
4418
  }
4374
4419
  for (const patch of patches) {
4375
- const hasUnprotectedFiles = patch.files.some((f) => !patterns.some((p) => (0, import_minimatch4.minimatch)(f, p) || f === p));
4420
+ const hasUnprotectedFiles = patch.files.some((f) => !patterns.some((p) => (0, import_minimatch5.minimatch)(f, p) || f === p));
4376
4421
  if (hasUnprotectedFiles) {
4377
4422
  commitsOnly.push(patch);
4378
4423
  }
@@ -4384,7 +4429,7 @@ var FernignoreMigrator = class {
4384
4429
  const results = [];
4385
4430
  for (const pattern of patterns) {
4386
4431
  const matching = allFiles.filter(
4387
- (f) => (0, import_minimatch4.minimatch)(f, pattern) || f === pattern || f.startsWith(pattern + "/")
4432
+ (f) => (0, import_minimatch5.minimatch)(f, pattern) || f === pattern || f.startsWith(pattern + "/")
4388
4433
  );
4389
4434
  results.push({ pattern, files: matching.length > 0 ? matching : [pattern] });
4390
4435
  }
@@ -4697,7 +4742,7 @@ function computeContentHash(patchContent) {
4697
4742
  }
4698
4743
 
4699
4744
  // src/commands/forget.ts
4700
- var import_minimatch5 = require("minimatch");
4745
+ var import_minimatch6 = require("minimatch");
4701
4746
  function parseDiffStat(patchContent) {
4702
4747
  let additions = 0;
4703
4748
  let deletions = 0;
@@ -4727,7 +4772,7 @@ function toMatchedPatch(patch) {
4727
4772
  }
4728
4773
  function matchesPatch(patch, pattern) {
4729
4774
  const fileMatch = patch.files.some(
4730
- (file) => file === pattern || (0, import_minimatch5.minimatch)(file, pattern)
4775
+ (file) => file === pattern || (0, import_minimatch6.minimatch)(file, pattern)
4731
4776
  );
4732
4777
  if (fileMatch) return true;
4733
4778
  return patch.original_message.toLowerCase().includes(pattern.toLowerCase());