@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/cli.cjs
CHANGED
|
@@ -17911,6 +17911,66 @@ var ReplayService = class {
|
|
|
17911
17911
|
}
|
|
17912
17912
|
return snapshot;
|
|
17913
17913
|
}
|
|
17914
|
+
/**
|
|
17915
|
+
* FER-10203: returns true when a `[fern-autoversion]` commit between
|
|
17916
|
+
* `fromSha` and `toSha` touched any of the patch's `files`. Re-classifies
|
|
17917
|
+
* each grep match via `isGenerationCommit` so a customer commit that
|
|
17918
|
+
* merely quotes the marker in its body doesn't false-positive.
|
|
17919
|
+
*/
|
|
17920
|
+
async hasAutoversionContamination(fromSha, toSha, files) {
|
|
17921
|
+
if (files.length === 0) return false;
|
|
17922
|
+
const log = await this.git.exec([
|
|
17923
|
+
"log",
|
|
17924
|
+
`${fromSha}..${toSha}`,
|
|
17925
|
+
"--grep=\\[fern-autoversion\\]",
|
|
17926
|
+
"--extended-regexp",
|
|
17927
|
+
"--format=%H%x09%aN%x09%ae%x09%s",
|
|
17928
|
+
"--",
|
|
17929
|
+
...files
|
|
17930
|
+
]).catch(() => "");
|
|
17931
|
+
if (!log.trim()) return false;
|
|
17932
|
+
for (const line of log.trim().split("\n")) {
|
|
17933
|
+
const [sha, authorName, authorEmail, message] = line.split(" ");
|
|
17934
|
+
if (sha == null) continue;
|
|
17935
|
+
if (isGenerationCommit({
|
|
17936
|
+
sha,
|
|
17937
|
+
authorName: authorName ?? "",
|
|
17938
|
+
authorEmail: authorEmail ?? "",
|
|
17939
|
+
message: message ?? ""
|
|
17940
|
+
})) {
|
|
17941
|
+
return true;
|
|
17942
|
+
}
|
|
17943
|
+
}
|
|
17944
|
+
return false;
|
|
17945
|
+
}
|
|
17946
|
+
/**
|
|
17947
|
+
* FER-10203: rebuild patch_content from `theirs_snapshot` (captured at
|
|
17948
|
+
* detection time, predates pipeline content) rather than a HEAD-relative
|
|
17949
|
+
* diff. Returns `null` when snapshot is missing or doesn't cover every
|
|
17950
|
+
* file in `patch.files` — caller leaves the patch unchanged.
|
|
17951
|
+
*/
|
|
17952
|
+
async computeSnapshotBasedPatchDiff(patch, currentGen) {
|
|
17953
|
+
if (!patch.theirs_snapshot) return null;
|
|
17954
|
+
if (Object.keys(patch.theirs_snapshot).length === 0) return null;
|
|
17955
|
+
for (const file of patch.files) {
|
|
17956
|
+
if (patch.theirs_snapshot[file] == null) return null;
|
|
17957
|
+
}
|
|
17958
|
+
const fragments = [];
|
|
17959
|
+
for (const file of patch.files) {
|
|
17960
|
+
const theirsContent = patch.theirs_snapshot[file];
|
|
17961
|
+
const oursContent = await this.git.showFile(currentGen, file).catch(() => null);
|
|
17962
|
+
if (oursContent == null) {
|
|
17963
|
+
fragments.push(synthesizeNewFileDiff(file, theirsContent));
|
|
17964
|
+
continue;
|
|
17965
|
+
}
|
|
17966
|
+
if (oursContent === theirsContent) continue;
|
|
17967
|
+
const fileDiff = await unifiedDiff(oursContent, theirsContent, file);
|
|
17968
|
+
if (fileDiff.trim()) {
|
|
17969
|
+
fragments.push(fileDiff);
|
|
17970
|
+
}
|
|
17971
|
+
}
|
|
17972
|
+
return fragments.join("");
|
|
17973
|
+
}
|
|
17914
17974
|
/**
|
|
17915
17975
|
* Read THEIRS snapshot from a git tree-ish (commit, branch, or tree).
|
|
17916
17976
|
* Used when the diff that produced `patch_content` was relative to that
|
|
@@ -18176,6 +18236,41 @@ var ReplayService = class {
|
|
|
18176
18236
|
}
|
|
18177
18237
|
if (patch.base_generation === currentGen) {
|
|
18178
18238
|
try {
|
|
18239
|
+
const contaminated = await this.hasAutoversionContamination(
|
|
18240
|
+
currentGen,
|
|
18241
|
+
"HEAD",
|
|
18242
|
+
patch.files
|
|
18243
|
+
);
|
|
18244
|
+
if (contaminated) {
|
|
18245
|
+
const snapshotDiff = await this.computeSnapshotBasedPatchDiff(
|
|
18246
|
+
patch,
|
|
18247
|
+
currentGen
|
|
18248
|
+
);
|
|
18249
|
+
if (snapshotDiff === null) continue;
|
|
18250
|
+
if (!snapshotDiff.trim()) {
|
|
18251
|
+
if (await allPatchFilesUserOwned(patch)) continue;
|
|
18252
|
+
this.lockManager.removePatch(patch.id);
|
|
18253
|
+
contentRefreshed++;
|
|
18254
|
+
continue;
|
|
18255
|
+
}
|
|
18256
|
+
const snapHasStaleMarkers = snapshotDiff.split("\n").some(
|
|
18257
|
+
(l) => l.startsWith("+<<<<<<< Generated") || l.startsWith("+>>>>>>> Your customization")
|
|
18258
|
+
);
|
|
18259
|
+
if (snapHasStaleMarkers) continue;
|
|
18260
|
+
const isProtectedSnap = patch.files.some(
|
|
18261
|
+
(file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || minimatch(file, p))
|
|
18262
|
+
);
|
|
18263
|
+
if (isProtectedSnap) continue;
|
|
18264
|
+
const snapHash = this.detector.computeContentHash(snapshotDiff);
|
|
18265
|
+
if (snapHash !== patch.content_hash) {
|
|
18266
|
+
this.lockManager.updatePatch(patch.id, {
|
|
18267
|
+
patch_content: snapshotDiff,
|
|
18268
|
+
content_hash: snapHash
|
|
18269
|
+
});
|
|
18270
|
+
contentRefreshed++;
|
|
18271
|
+
}
|
|
18272
|
+
continue;
|
|
18273
|
+
}
|
|
18179
18274
|
const ppResult = await computePerPatchDiffWithFallback(
|
|
18180
18275
|
patch,
|
|
18181
18276
|
(f) => this.git.showFile(currentGen, f),
|
|
@@ -18227,6 +18322,42 @@ var ReplayService = class {
|
|
|
18227
18322
|
try {
|
|
18228
18323
|
const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<< Generated", "HEAD", "--", ...patch.files]).catch(() => "");
|
|
18229
18324
|
if (markerFiles.trim()) continue;
|
|
18325
|
+
const contaminated = await this.hasAutoversionContamination(
|
|
18326
|
+
currentGen,
|
|
18327
|
+
"HEAD",
|
|
18328
|
+
patch.files
|
|
18329
|
+
);
|
|
18330
|
+
if (contaminated) {
|
|
18331
|
+
const snapshotDiff = await this.computeSnapshotBasedPatchDiff(
|
|
18332
|
+
patch,
|
|
18333
|
+
currentGen
|
|
18334
|
+
);
|
|
18335
|
+
if (snapshotDiff === null) continue;
|
|
18336
|
+
if (!snapshotDiff.trim()) {
|
|
18337
|
+
if (await allPatchFilesUserOwned(patch)) {
|
|
18338
|
+
try {
|
|
18339
|
+
this.lockManager.updatePatch(patch.id, { base_generation: currentGen });
|
|
18340
|
+
} catch {
|
|
18341
|
+
}
|
|
18342
|
+
continue;
|
|
18343
|
+
}
|
|
18344
|
+
this.lockManager.removePatch(patch.id);
|
|
18345
|
+
conflictAbsorbed++;
|
|
18346
|
+
continue;
|
|
18347
|
+
}
|
|
18348
|
+
const snapHasStaleMarkers = snapshotDiff.split("\n").some(
|
|
18349
|
+
(l) => l.startsWith("+<<<<<<< Generated") || l.startsWith("+>>>>>>> Your customization")
|
|
18350
|
+
);
|
|
18351
|
+
if (snapHasStaleMarkers) continue;
|
|
18352
|
+
const snapHash = this.detector.computeContentHash(snapshotDiff);
|
|
18353
|
+
this.lockManager.updatePatch(patch.id, {
|
|
18354
|
+
base_generation: currentGen,
|
|
18355
|
+
patch_content: snapshotDiff,
|
|
18356
|
+
content_hash: snapHash
|
|
18357
|
+
});
|
|
18358
|
+
conflictResolved++;
|
|
18359
|
+
continue;
|
|
18360
|
+
}
|
|
18230
18361
|
const ppResult = await computePerPatchDiffWithFallback(
|
|
18231
18362
|
patch,
|
|
18232
18363
|
(f) => this.git.showFile(currentGen, f),
|