@fern-api/replay 0.14.1 → 0.15.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.
- package/dist/cli.cjs +131 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +131 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3551,6 +3551,66 @@ var ReplayService = class {
|
|
|
3551
3551
|
}
|
|
3552
3552
|
return snapshot;
|
|
3553
3553
|
}
|
|
3554
|
+
/**
|
|
3555
|
+
* FER-10203: returns true when a `[fern-autoversion]` commit between
|
|
3556
|
+
* `fromSha` and `toSha` touched any of the patch's `files`. Re-classifies
|
|
3557
|
+
* each grep match via `isGenerationCommit` so a customer commit that
|
|
3558
|
+
* merely quotes the marker in its body doesn't false-positive.
|
|
3559
|
+
*/
|
|
3560
|
+
async hasAutoversionContamination(fromSha, toSha, files) {
|
|
3561
|
+
if (files.length === 0) return false;
|
|
3562
|
+
const log = await this.git.exec([
|
|
3563
|
+
"log",
|
|
3564
|
+
`${fromSha}..${toSha}`,
|
|
3565
|
+
"--grep=\\[fern-autoversion\\]",
|
|
3566
|
+
"--extended-regexp",
|
|
3567
|
+
"--format=%H%x09%aN%x09%ae%x09%s",
|
|
3568
|
+
"--",
|
|
3569
|
+
...files
|
|
3570
|
+
]).catch(() => "");
|
|
3571
|
+
if (!log.trim()) return false;
|
|
3572
|
+
for (const line of log.trim().split("\n")) {
|
|
3573
|
+
const [sha, authorName, authorEmail, message] = line.split(" ");
|
|
3574
|
+
if (sha == null) continue;
|
|
3575
|
+
if (isGenerationCommit({
|
|
3576
|
+
sha,
|
|
3577
|
+
authorName: authorName ?? "",
|
|
3578
|
+
authorEmail: authorEmail ?? "",
|
|
3579
|
+
message: message ?? ""
|
|
3580
|
+
})) {
|
|
3581
|
+
return true;
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
return false;
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
* FER-10203: rebuild patch_content from `theirs_snapshot` (captured at
|
|
3588
|
+
* detection time, predates pipeline content) rather than a HEAD-relative
|
|
3589
|
+
* diff. Returns `null` when snapshot is missing or doesn't cover every
|
|
3590
|
+
* file in `patch.files` — caller leaves the patch unchanged.
|
|
3591
|
+
*/
|
|
3592
|
+
async computeSnapshotBasedPatchDiff(patch, currentGen) {
|
|
3593
|
+
if (!patch.theirs_snapshot) return null;
|
|
3594
|
+
if (Object.keys(patch.theirs_snapshot).length === 0) return null;
|
|
3595
|
+
for (const file of patch.files) {
|
|
3596
|
+
if (patch.theirs_snapshot[file] == null) return null;
|
|
3597
|
+
}
|
|
3598
|
+
const fragments = [];
|
|
3599
|
+
for (const file of patch.files) {
|
|
3600
|
+
const theirsContent = patch.theirs_snapshot[file];
|
|
3601
|
+
const oursContent = await this.git.showFile(currentGen, file).catch(() => null);
|
|
3602
|
+
if (oursContent == null) {
|
|
3603
|
+
fragments.push(synthesizeNewFileDiff(file, theirsContent));
|
|
3604
|
+
continue;
|
|
3605
|
+
}
|
|
3606
|
+
if (oursContent === theirsContent) continue;
|
|
3607
|
+
const fileDiff = await unifiedDiff(oursContent, theirsContent, file);
|
|
3608
|
+
if (fileDiff.trim()) {
|
|
3609
|
+
fragments.push(fileDiff);
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
return fragments.join("");
|
|
3613
|
+
}
|
|
3554
3614
|
/**
|
|
3555
3615
|
* Read THEIRS snapshot from a git tree-ish (commit, branch, or tree).
|
|
3556
3616
|
* Used when the diff that produced `patch_content` was relative to that
|
|
@@ -3816,6 +3876,41 @@ var ReplayService = class {
|
|
|
3816
3876
|
}
|
|
3817
3877
|
if (patch.base_generation === currentGen) {
|
|
3818
3878
|
try {
|
|
3879
|
+
const contaminated = await this.hasAutoversionContamination(
|
|
3880
|
+
currentGen,
|
|
3881
|
+
"HEAD",
|
|
3882
|
+
patch.files
|
|
3883
|
+
);
|
|
3884
|
+
if (contaminated) {
|
|
3885
|
+
const snapshotDiff = await this.computeSnapshotBasedPatchDiff(
|
|
3886
|
+
patch,
|
|
3887
|
+
currentGen
|
|
3888
|
+
);
|
|
3889
|
+
if (snapshotDiff === null) continue;
|
|
3890
|
+
if (!snapshotDiff.trim()) {
|
|
3891
|
+
if (await allPatchFilesUserOwned(patch)) continue;
|
|
3892
|
+
this.lockManager.removePatch(patch.id);
|
|
3893
|
+
contentRefreshed++;
|
|
3894
|
+
continue;
|
|
3895
|
+
}
|
|
3896
|
+
const snapHasStaleMarkers = snapshotDiff.split("\n").some(
|
|
3897
|
+
(l) => l.startsWith("+<<<<<<< Generated") || l.startsWith("+>>>>>>> Your customization")
|
|
3898
|
+
);
|
|
3899
|
+
if (snapHasStaleMarkers) continue;
|
|
3900
|
+
const isProtectedSnap = patch.files.some(
|
|
3901
|
+
(file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch2.minimatch)(file, p))
|
|
3902
|
+
);
|
|
3903
|
+
if (isProtectedSnap) continue;
|
|
3904
|
+
const snapHash = this.detector.computeContentHash(snapshotDiff);
|
|
3905
|
+
if (snapHash !== patch.content_hash) {
|
|
3906
|
+
this.lockManager.updatePatch(patch.id, {
|
|
3907
|
+
patch_content: snapshotDiff,
|
|
3908
|
+
content_hash: snapHash
|
|
3909
|
+
});
|
|
3910
|
+
contentRefreshed++;
|
|
3911
|
+
}
|
|
3912
|
+
continue;
|
|
3913
|
+
}
|
|
3819
3914
|
const ppResult = await computePerPatchDiffWithFallback(
|
|
3820
3915
|
patch,
|
|
3821
3916
|
(f) => this.git.showFile(currentGen, f),
|
|
@@ -3867,6 +3962,42 @@ var ReplayService = class {
|
|
|
3867
3962
|
try {
|
|
3868
3963
|
const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<< Generated", "HEAD", "--", ...patch.files]).catch(() => "");
|
|
3869
3964
|
if (markerFiles.trim()) continue;
|
|
3965
|
+
const contaminated = await this.hasAutoversionContamination(
|
|
3966
|
+
currentGen,
|
|
3967
|
+
"HEAD",
|
|
3968
|
+
patch.files
|
|
3969
|
+
);
|
|
3970
|
+
if (contaminated) {
|
|
3971
|
+
const snapshotDiff = await this.computeSnapshotBasedPatchDiff(
|
|
3972
|
+
patch,
|
|
3973
|
+
currentGen
|
|
3974
|
+
);
|
|
3975
|
+
if (snapshotDiff === null) continue;
|
|
3976
|
+
if (!snapshotDiff.trim()) {
|
|
3977
|
+
if (await allPatchFilesUserOwned(patch)) {
|
|
3978
|
+
try {
|
|
3979
|
+
this.lockManager.updatePatch(patch.id, { base_generation: currentGen });
|
|
3980
|
+
} catch {
|
|
3981
|
+
}
|
|
3982
|
+
continue;
|
|
3983
|
+
}
|
|
3984
|
+
this.lockManager.removePatch(patch.id);
|
|
3985
|
+
conflictAbsorbed++;
|
|
3986
|
+
continue;
|
|
3987
|
+
}
|
|
3988
|
+
const snapHasStaleMarkers = snapshotDiff.split("\n").some(
|
|
3989
|
+
(l) => l.startsWith("+<<<<<<< Generated") || l.startsWith("+>>>>>>> Your customization")
|
|
3990
|
+
);
|
|
3991
|
+
if (snapHasStaleMarkers) continue;
|
|
3992
|
+
const snapHash = this.detector.computeContentHash(snapshotDiff);
|
|
3993
|
+
this.lockManager.updatePatch(patch.id, {
|
|
3994
|
+
base_generation: currentGen,
|
|
3995
|
+
patch_content: snapshotDiff,
|
|
3996
|
+
content_hash: snapHash
|
|
3997
|
+
});
|
|
3998
|
+
conflictResolved++;
|
|
3999
|
+
continue;
|
|
4000
|
+
}
|
|
3870
4001
|
const ppResult = await computePerPatchDiffWithFallback(
|
|
3871
4002
|
patch,
|
|
3872
4003
|
(f) => this.git.showFile(currentGen, f),
|