@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/cli.cjs +2340 -2296
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +73 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +73 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
675
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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 =
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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());
|