@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.d.cts
CHANGED
|
@@ -734,6 +734,20 @@ declare class ReplayService {
|
|
|
734
734
|
* never be used during apply anyway).
|
|
735
735
|
*/
|
|
736
736
|
private readSnapshotFromDisk;
|
|
737
|
+
/**
|
|
738
|
+
* FER-10203: returns true when a `[fern-autoversion]` commit between
|
|
739
|
+
* `fromSha` and `toSha` touched any of the patch's `files`. Re-classifies
|
|
740
|
+
* each grep match via `isGenerationCommit` so a customer commit that
|
|
741
|
+
* merely quotes the marker in its body doesn't false-positive.
|
|
742
|
+
*/
|
|
743
|
+
private hasAutoversionContamination;
|
|
744
|
+
/**
|
|
745
|
+
* FER-10203: rebuild patch_content from `theirs_snapshot` (captured at
|
|
746
|
+
* detection time, predates pipeline content) rather than a HEAD-relative
|
|
747
|
+
* diff. Returns `null` when snapshot is missing or doesn't cover every
|
|
748
|
+
* file in `patch.files` — caller leaves the patch unchanged.
|
|
749
|
+
*/
|
|
750
|
+
private computeSnapshotBasedPatchDiff;
|
|
737
751
|
/**
|
|
738
752
|
* Read THEIRS snapshot from a git tree-ish (commit, branch, or tree).
|
|
739
753
|
* Used when the diff that produced `patch_content` was relative to that
|
package/dist/index.d.ts
CHANGED
|
@@ -734,6 +734,20 @@ declare class ReplayService {
|
|
|
734
734
|
* never be used during apply anyway).
|
|
735
735
|
*/
|
|
736
736
|
private readSnapshotFromDisk;
|
|
737
|
+
/**
|
|
738
|
+
* FER-10203: returns true when a `[fern-autoversion]` commit between
|
|
739
|
+
* `fromSha` and `toSha` touched any of the patch's `files`. Re-classifies
|
|
740
|
+
* each grep match via `isGenerationCommit` so a customer commit that
|
|
741
|
+
* merely quotes the marker in its body doesn't false-positive.
|
|
742
|
+
*/
|
|
743
|
+
private hasAutoversionContamination;
|
|
744
|
+
/**
|
|
745
|
+
* FER-10203: rebuild patch_content from `theirs_snapshot` (captured at
|
|
746
|
+
* detection time, predates pipeline content) rather than a HEAD-relative
|
|
747
|
+
* diff. Returns `null` when snapshot is missing or doesn't cover every
|
|
748
|
+
* file in `patch.files` — caller leaves the patch unchanged.
|
|
749
|
+
*/
|
|
750
|
+
private computeSnapshotBasedPatchDiff;
|
|
737
751
|
/**
|
|
738
752
|
* Read THEIRS snapshot from a git tree-ish (commit, branch, or tree).
|
|
739
753
|
* Used when the diff that produced `patch_content` was relative to that
|
package/dist/index.js
CHANGED
|
@@ -3496,6 +3496,66 @@ var ReplayService = class {
|
|
|
3496
3496
|
}
|
|
3497
3497
|
return snapshot;
|
|
3498
3498
|
}
|
|
3499
|
+
/**
|
|
3500
|
+
* FER-10203: returns true when a `[fern-autoversion]` commit between
|
|
3501
|
+
* `fromSha` and `toSha` touched any of the patch's `files`. Re-classifies
|
|
3502
|
+
* each grep match via `isGenerationCommit` so a customer commit that
|
|
3503
|
+
* merely quotes the marker in its body doesn't false-positive.
|
|
3504
|
+
*/
|
|
3505
|
+
async hasAutoversionContamination(fromSha, toSha, files) {
|
|
3506
|
+
if (files.length === 0) return false;
|
|
3507
|
+
const log = await this.git.exec([
|
|
3508
|
+
"log",
|
|
3509
|
+
`${fromSha}..${toSha}`,
|
|
3510
|
+
"--grep=\\[fern-autoversion\\]",
|
|
3511
|
+
"--extended-regexp",
|
|
3512
|
+
"--format=%H%x09%aN%x09%ae%x09%s",
|
|
3513
|
+
"--",
|
|
3514
|
+
...files
|
|
3515
|
+
]).catch(() => "");
|
|
3516
|
+
if (!log.trim()) return false;
|
|
3517
|
+
for (const line of log.trim().split("\n")) {
|
|
3518
|
+
const [sha, authorName, authorEmail, message] = line.split(" ");
|
|
3519
|
+
if (sha == null) continue;
|
|
3520
|
+
if (isGenerationCommit({
|
|
3521
|
+
sha,
|
|
3522
|
+
authorName: authorName ?? "",
|
|
3523
|
+
authorEmail: authorEmail ?? "",
|
|
3524
|
+
message: message ?? ""
|
|
3525
|
+
})) {
|
|
3526
|
+
return true;
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
return false;
|
|
3530
|
+
}
|
|
3531
|
+
/**
|
|
3532
|
+
* FER-10203: rebuild patch_content from `theirs_snapshot` (captured at
|
|
3533
|
+
* detection time, predates pipeline content) rather than a HEAD-relative
|
|
3534
|
+
* diff. Returns `null` when snapshot is missing or doesn't cover every
|
|
3535
|
+
* file in `patch.files` — caller leaves the patch unchanged.
|
|
3536
|
+
*/
|
|
3537
|
+
async computeSnapshotBasedPatchDiff(patch, currentGen) {
|
|
3538
|
+
if (!patch.theirs_snapshot) return null;
|
|
3539
|
+
if (Object.keys(patch.theirs_snapshot).length === 0) return null;
|
|
3540
|
+
for (const file of patch.files) {
|
|
3541
|
+
if (patch.theirs_snapshot[file] == null) return null;
|
|
3542
|
+
}
|
|
3543
|
+
const fragments = [];
|
|
3544
|
+
for (const file of patch.files) {
|
|
3545
|
+
const theirsContent = patch.theirs_snapshot[file];
|
|
3546
|
+
const oursContent = await this.git.showFile(currentGen, file).catch(() => null);
|
|
3547
|
+
if (oursContent == null) {
|
|
3548
|
+
fragments.push(synthesizeNewFileDiff(file, theirsContent));
|
|
3549
|
+
continue;
|
|
3550
|
+
}
|
|
3551
|
+
if (oursContent === theirsContent) continue;
|
|
3552
|
+
const fileDiff = await unifiedDiff(oursContent, theirsContent, file);
|
|
3553
|
+
if (fileDiff.trim()) {
|
|
3554
|
+
fragments.push(fileDiff);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
return fragments.join("");
|
|
3558
|
+
}
|
|
3499
3559
|
/**
|
|
3500
3560
|
* Read THEIRS snapshot from a git tree-ish (commit, branch, or tree).
|
|
3501
3561
|
* Used when the diff that produced `patch_content` was relative to that
|
|
@@ -3761,6 +3821,41 @@ var ReplayService = class {
|
|
|
3761
3821
|
}
|
|
3762
3822
|
if (patch.base_generation === currentGen) {
|
|
3763
3823
|
try {
|
|
3824
|
+
const contaminated = await this.hasAutoversionContamination(
|
|
3825
|
+
currentGen,
|
|
3826
|
+
"HEAD",
|
|
3827
|
+
patch.files
|
|
3828
|
+
);
|
|
3829
|
+
if (contaminated) {
|
|
3830
|
+
const snapshotDiff = await this.computeSnapshotBasedPatchDiff(
|
|
3831
|
+
patch,
|
|
3832
|
+
currentGen
|
|
3833
|
+
);
|
|
3834
|
+
if (snapshotDiff === null) continue;
|
|
3835
|
+
if (!snapshotDiff.trim()) {
|
|
3836
|
+
if (await allPatchFilesUserOwned(patch)) continue;
|
|
3837
|
+
this.lockManager.removePatch(patch.id);
|
|
3838
|
+
contentRefreshed++;
|
|
3839
|
+
continue;
|
|
3840
|
+
}
|
|
3841
|
+
const snapHasStaleMarkers = snapshotDiff.split("\n").some(
|
|
3842
|
+
(l) => l.startsWith("+<<<<<<< Generated") || l.startsWith("+>>>>>>> Your customization")
|
|
3843
|
+
);
|
|
3844
|
+
if (snapHasStaleMarkers) continue;
|
|
3845
|
+
const isProtectedSnap = patch.files.some(
|
|
3846
|
+
(file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || minimatch2(file, p))
|
|
3847
|
+
);
|
|
3848
|
+
if (isProtectedSnap) continue;
|
|
3849
|
+
const snapHash = this.detector.computeContentHash(snapshotDiff);
|
|
3850
|
+
if (snapHash !== patch.content_hash) {
|
|
3851
|
+
this.lockManager.updatePatch(patch.id, {
|
|
3852
|
+
patch_content: snapshotDiff,
|
|
3853
|
+
content_hash: snapHash
|
|
3854
|
+
});
|
|
3855
|
+
contentRefreshed++;
|
|
3856
|
+
}
|
|
3857
|
+
continue;
|
|
3858
|
+
}
|
|
3764
3859
|
const ppResult = await computePerPatchDiffWithFallback(
|
|
3765
3860
|
patch,
|
|
3766
3861
|
(f) => this.git.showFile(currentGen, f),
|
|
@@ -3812,6 +3907,42 @@ var ReplayService = class {
|
|
|
3812
3907
|
try {
|
|
3813
3908
|
const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<< Generated", "HEAD", "--", ...patch.files]).catch(() => "");
|
|
3814
3909
|
if (markerFiles.trim()) continue;
|
|
3910
|
+
const contaminated = await this.hasAutoversionContamination(
|
|
3911
|
+
currentGen,
|
|
3912
|
+
"HEAD",
|
|
3913
|
+
patch.files
|
|
3914
|
+
);
|
|
3915
|
+
if (contaminated) {
|
|
3916
|
+
const snapshotDiff = await this.computeSnapshotBasedPatchDiff(
|
|
3917
|
+
patch,
|
|
3918
|
+
currentGen
|
|
3919
|
+
);
|
|
3920
|
+
if (snapshotDiff === null) continue;
|
|
3921
|
+
if (!snapshotDiff.trim()) {
|
|
3922
|
+
if (await allPatchFilesUserOwned(patch)) {
|
|
3923
|
+
try {
|
|
3924
|
+
this.lockManager.updatePatch(patch.id, { base_generation: currentGen });
|
|
3925
|
+
} catch {
|
|
3926
|
+
}
|
|
3927
|
+
continue;
|
|
3928
|
+
}
|
|
3929
|
+
this.lockManager.removePatch(patch.id);
|
|
3930
|
+
conflictAbsorbed++;
|
|
3931
|
+
continue;
|
|
3932
|
+
}
|
|
3933
|
+
const snapHasStaleMarkers = snapshotDiff.split("\n").some(
|
|
3934
|
+
(l) => l.startsWith("+<<<<<<< Generated") || l.startsWith("+>>>>>>> Your customization")
|
|
3935
|
+
);
|
|
3936
|
+
if (snapHasStaleMarkers) continue;
|
|
3937
|
+
const snapHash = this.detector.computeContentHash(snapshotDiff);
|
|
3938
|
+
this.lockManager.updatePatch(patch.id, {
|
|
3939
|
+
base_generation: currentGen,
|
|
3940
|
+
patch_content: snapshotDiff,
|
|
3941
|
+
content_hash: snapHash
|
|
3942
|
+
});
|
|
3943
|
+
conflictResolved++;
|
|
3944
|
+
continue;
|
|
3945
|
+
}
|
|
3815
3946
|
const ppResult = await computePerPatchDiffWithFallback(
|
|
3816
3947
|
patch,
|
|
3817
3948
|
(f) => this.git.showFile(currentGen, f),
|