@fern-api/replay 0.8.1 → 0.9.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 +492 -126
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +246 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -16
- package/dist/index.d.ts +61 -16
- package/dist/index.js +246 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -290,6 +290,15 @@ var LockfileManager = class {
|
|
|
290
290
|
this.ensureLoaded();
|
|
291
291
|
this.lock.patches = [];
|
|
292
292
|
}
|
|
293
|
+
addForgottenHash(hash) {
|
|
294
|
+
this.ensureLoaded();
|
|
295
|
+
if (!this.lock.forgotten_hashes) {
|
|
296
|
+
this.lock.forgotten_hashes = [];
|
|
297
|
+
}
|
|
298
|
+
if (!this.lock.forgotten_hashes.includes(hash)) {
|
|
299
|
+
this.lock.forgotten_hashes.push(hash);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
293
302
|
getUnresolvedPatches() {
|
|
294
303
|
this.ensureLoaded();
|
|
295
304
|
return this.lock.patches.filter((p) => p.status === "unresolved");
|
|
@@ -386,6 +395,7 @@ var ReplayDetector = class {
|
|
|
386
395
|
}
|
|
387
396
|
const commits = this.parseGitLog(log);
|
|
388
397
|
const newPatches = [];
|
|
398
|
+
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
389
399
|
for (const commit of commits) {
|
|
390
400
|
if (isGenerationCommit(commit)) {
|
|
391
401
|
continue;
|
|
@@ -399,7 +409,7 @@ var ReplayDetector = class {
|
|
|
399
409
|
}
|
|
400
410
|
const patchContent = await this.git.formatPatch(commit.sha);
|
|
401
411
|
const contentHash = this.computeContentHash(patchContent);
|
|
402
|
-
if (lock.patches.find((p) => p.content_hash === contentHash)) {
|
|
412
|
+
if (lock.patches.find((p) => p.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
403
413
|
continue;
|
|
404
414
|
}
|
|
405
415
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
@@ -498,7 +508,7 @@ var ReplayDetector = class {
|
|
|
498
508
|
if (!diff.trim()) return { patches: [], revertedPatchIds: [] };
|
|
499
509
|
const contentHash = this.computeContentHash(diff);
|
|
500
510
|
const lock = this.lockManager.read();
|
|
501
|
-
if (lock.patches.some((p) => p.content_hash === contentHash)) {
|
|
511
|
+
if (lock.patches.some((p) => p.content_hash === contentHash) || (lock.forgotten_hashes ?? []).includes(contentHash)) {
|
|
502
512
|
return { patches: [], revertedPatchIds: [] };
|
|
503
513
|
}
|
|
504
514
|
const headSha = (await this.git.exec(["rev-parse", "HEAD"])).trim();
|
|
@@ -568,6 +578,36 @@ var import_promises = require("fs/promises");
|
|
|
568
578
|
var import_node_os = require("os");
|
|
569
579
|
var import_node_path2 = require("path");
|
|
570
580
|
var import_minimatch = require("minimatch");
|
|
581
|
+
|
|
582
|
+
// src/conflict-utils.ts
|
|
583
|
+
function stripConflictMarkers(content) {
|
|
584
|
+
const lines = content.split("\n");
|
|
585
|
+
const result = [];
|
|
586
|
+
let inConflict = false;
|
|
587
|
+
let inOurs = false;
|
|
588
|
+
for (const line of lines) {
|
|
589
|
+
if (line.startsWith("<<<<<<< ")) {
|
|
590
|
+
inConflict = true;
|
|
591
|
+
inOurs = true;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (inConflict && line === "=======") {
|
|
595
|
+
inOurs = false;
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
if (inConflict && line.startsWith(">>>>>>> ")) {
|
|
599
|
+
inConflict = false;
|
|
600
|
+
inOurs = false;
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
if (!inConflict || inOurs) {
|
|
604
|
+
result.push(line);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return result.join("\n");
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/ReplayApplicator.ts
|
|
571
611
|
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
572
612
|
".png",
|
|
573
613
|
".jpg",
|
|
@@ -639,7 +679,8 @@ var ReplayApplicator = class {
|
|
|
639
679
|
async applyPatches(patches) {
|
|
640
680
|
this.resetAccumulator();
|
|
641
681
|
const results = [];
|
|
642
|
-
for (
|
|
682
|
+
for (let i = 0; i < patches.length; i++) {
|
|
683
|
+
const patch = patches[i];
|
|
643
684
|
if (this.isExcluded(patch)) {
|
|
644
685
|
results.push({
|
|
645
686
|
patch,
|
|
@@ -650,6 +691,33 @@ var ReplayApplicator = class {
|
|
|
650
691
|
}
|
|
651
692
|
const result = await this.applyPatchWithFallback(patch);
|
|
652
693
|
results.push(result);
|
|
694
|
+
if (result.status === "conflict" && result.fileResults) {
|
|
695
|
+
const laterFiles = /* @__PURE__ */ new Set();
|
|
696
|
+
for (let j = i + 1; j < patches.length; j++) {
|
|
697
|
+
for (const f of patches[j].files) {
|
|
698
|
+
laterFiles.add(f);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const resolvedToOriginal = /* @__PURE__ */ new Map();
|
|
702
|
+
if (result.resolvedFiles) {
|
|
703
|
+
for (const [orig, resolved] of Object.entries(result.resolvedFiles)) {
|
|
704
|
+
resolvedToOriginal.set(resolved, orig);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
for (const fileResult of result.fileResults) {
|
|
708
|
+
if (fileResult.status !== "conflict") continue;
|
|
709
|
+
const originalPath = resolvedToOriginal.get(fileResult.file) ?? fileResult.file;
|
|
710
|
+
if (laterFiles.has(fileResult.file) || laterFiles.has(originalPath)) {
|
|
711
|
+
const filePath = (0, import_node_path2.join)(this.outputDir, fileResult.file);
|
|
712
|
+
try {
|
|
713
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
714
|
+
const stripped = stripConflictMarkers(content);
|
|
715
|
+
await (0, import_promises.writeFile)(filePath, stripped);
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
653
721
|
}
|
|
654
722
|
return results;
|
|
655
723
|
}
|
|
@@ -668,7 +736,7 @@ var ReplayApplicator = class {
|
|
|
668
736
|
const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
|
|
669
737
|
const base = await this.git.showFile(baseGen.tree_hash, filePath);
|
|
670
738
|
const theirs = await this.applyPatchToContent(base, patch.patch_content, filePath, tempGit, tempDir);
|
|
671
|
-
if (theirs
|
|
739
|
+
if (theirs) {
|
|
672
740
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
673
741
|
content: theirs,
|
|
674
742
|
baseGeneration: patch.base_generation
|
|
@@ -800,7 +868,7 @@ var ReplayApplicator = class {
|
|
|
800
868
|
);
|
|
801
869
|
let useAccumulatorAsMergeBase = false;
|
|
802
870
|
const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
|
|
803
|
-
if (!theirs &&
|
|
871
|
+
if (!theirs && accumulatorEntry) {
|
|
804
872
|
theirs = await this.applyPatchToContent(
|
|
805
873
|
accumulatorEntry.content,
|
|
806
874
|
patch.patch_content,
|
|
@@ -859,7 +927,7 @@ var ReplayApplicator = class {
|
|
|
859
927
|
reason: "missing-content"
|
|
860
928
|
};
|
|
861
929
|
}
|
|
862
|
-
if (!base || !ours) {
|
|
930
|
+
if (!base && !useAccumulatorAsMergeBase || !ours) {
|
|
863
931
|
return {
|
|
864
932
|
file: resolvedPath,
|
|
865
933
|
status: "skipped",
|
|
@@ -867,11 +935,18 @@ var ReplayApplicator = class {
|
|
|
867
935
|
};
|
|
868
936
|
}
|
|
869
937
|
const mergeBase = useAccumulatorAsMergeBase && accumulatorEntry ? accumulatorEntry.content : base;
|
|
938
|
+
if (mergeBase == null) {
|
|
939
|
+
return {
|
|
940
|
+
file: resolvedPath,
|
|
941
|
+
status: "skipped",
|
|
942
|
+
reason: "missing-content"
|
|
943
|
+
};
|
|
944
|
+
}
|
|
870
945
|
const merged = threeWayMerge(mergeBase, ours, effective_theirs);
|
|
871
946
|
const outDir = (0, import_node_path2.dirname)(oursPath);
|
|
872
947
|
await (0, import_promises.mkdir)(outDir, { recursive: true });
|
|
873
948
|
await (0, import_promises.writeFile)(oursPath, merged.content);
|
|
874
|
-
if (effective_theirs
|
|
949
|
+
if (effective_theirs) {
|
|
875
950
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
876
951
|
content: effective_theirs,
|
|
877
952
|
baseGeneration: patch.base_generation
|
|
@@ -1114,36 +1189,6 @@ var import_node_fs2 = require("fs");
|
|
|
1114
1189
|
var import_node_path3 = require("path");
|
|
1115
1190
|
var import_minimatch2 = require("minimatch");
|
|
1116
1191
|
init_GitClient();
|
|
1117
|
-
|
|
1118
|
-
// src/conflict-utils.ts
|
|
1119
|
-
function stripConflictMarkers(content) {
|
|
1120
|
-
const lines = content.split("\n");
|
|
1121
|
-
const result = [];
|
|
1122
|
-
let inConflict = false;
|
|
1123
|
-
let inOurs = false;
|
|
1124
|
-
for (const line of lines) {
|
|
1125
|
-
if (line.startsWith("<<<<<<< ")) {
|
|
1126
|
-
inConflict = true;
|
|
1127
|
-
inOurs = true;
|
|
1128
|
-
continue;
|
|
1129
|
-
}
|
|
1130
|
-
if (inConflict && line === "=======") {
|
|
1131
|
-
inOurs = false;
|
|
1132
|
-
continue;
|
|
1133
|
-
}
|
|
1134
|
-
if (inConflict && line.startsWith(">>>>>>> ")) {
|
|
1135
|
-
inConflict = false;
|
|
1136
|
-
inOurs = false;
|
|
1137
|
-
continue;
|
|
1138
|
-
}
|
|
1139
|
-
if (!inConflict || inOurs) {
|
|
1140
|
-
result.push(line);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
return result.join("\n");
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
// src/ReplayService.ts
|
|
1147
1192
|
var ReplayService = class {
|
|
1148
1193
|
git;
|
|
1149
1194
|
detector;
|
|
@@ -2111,30 +2156,145 @@ function computeContentHash(patchContent) {
|
|
|
2111
2156
|
|
|
2112
2157
|
// src/commands/forget.ts
|
|
2113
2158
|
var import_minimatch4 = require("minimatch");
|
|
2114
|
-
function
|
|
2159
|
+
function parseDiffStat(patchContent) {
|
|
2160
|
+
let additions = 0;
|
|
2161
|
+
let deletions = 0;
|
|
2162
|
+
let inDiffHunk = false;
|
|
2163
|
+
for (const line of patchContent.split("\n")) {
|
|
2164
|
+
if (line.startsWith("diff --git ")) {
|
|
2165
|
+
inDiffHunk = true;
|
|
2166
|
+
continue;
|
|
2167
|
+
}
|
|
2168
|
+
if (!inDiffHunk) continue;
|
|
2169
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
2170
|
+
additions++;
|
|
2171
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
2172
|
+
deletions++;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
return { additions, deletions };
|
|
2176
|
+
}
|
|
2177
|
+
function toMatchedPatch(patch) {
|
|
2178
|
+
return {
|
|
2179
|
+
id: patch.id,
|
|
2180
|
+
message: patch.original_message,
|
|
2181
|
+
files: patch.files,
|
|
2182
|
+
diffstat: parseDiffStat(patch.patch_content),
|
|
2183
|
+
...patch.status ? { status: patch.status } : {}
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
function matchesPatch(patch, pattern) {
|
|
2187
|
+
const fileMatch = patch.files.some(
|
|
2188
|
+
(file) => file === pattern || (0, import_minimatch4.minimatch)(file, pattern)
|
|
2189
|
+
);
|
|
2190
|
+
if (fileMatch) return true;
|
|
2191
|
+
return patch.original_message.toLowerCase().includes(pattern.toLowerCase());
|
|
2192
|
+
}
|
|
2193
|
+
function buildWarnings(patches) {
|
|
2194
|
+
const warnings = [];
|
|
2195
|
+
for (const patch of patches) {
|
|
2196
|
+
if (patch.status === "resolving") {
|
|
2197
|
+
warnings.push(
|
|
2198
|
+
`patch ${patch.id} has conflict markers in these files: ${patch.files.join(", ")}. Run \`git checkout -- <files>\` to restore the generated versions.`
|
|
2199
|
+
);
|
|
2200
|
+
} else if (patch.status === "unresolved") {
|
|
2201
|
+
warnings.push(
|
|
2202
|
+
`patch ${patch.id} had unresolved conflicts (files: ${patch.files.join(", ")}).`
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
return warnings;
|
|
2207
|
+
}
|
|
2208
|
+
var EMPTY_RESULT = {
|
|
2209
|
+
initialized: false,
|
|
2210
|
+
removed: [],
|
|
2211
|
+
remaining: 0,
|
|
2212
|
+
notFound: false,
|
|
2213
|
+
alreadyForgotten: [],
|
|
2214
|
+
totalPatches: 0,
|
|
2215
|
+
warnings: []
|
|
2216
|
+
};
|
|
2217
|
+
function forget(outputDir, options) {
|
|
2115
2218
|
const lockManager = new LockfileManager(outputDir);
|
|
2116
2219
|
if (!lockManager.exists()) {
|
|
2117
|
-
return {
|
|
2220
|
+
return { ...EMPTY_RESULT };
|
|
2118
2221
|
}
|
|
2119
2222
|
const lock = lockManager.read();
|
|
2120
|
-
const
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2223
|
+
const totalPatches = lock.patches.length;
|
|
2224
|
+
if (options?.all) {
|
|
2225
|
+
const removed = lock.patches.map(toMatchedPatch);
|
|
2226
|
+
const warnings = buildWarnings(lock.patches);
|
|
2227
|
+
if (!options.dryRun) {
|
|
2228
|
+
for (const patch of lock.patches) {
|
|
2229
|
+
lockManager.addForgottenHash(patch.content_hash);
|
|
2230
|
+
}
|
|
2231
|
+
lockManager.clearPatches();
|
|
2232
|
+
lockManager.save();
|
|
2233
|
+
}
|
|
2234
|
+
return {
|
|
2235
|
+
initialized: true,
|
|
2236
|
+
removed,
|
|
2237
|
+
remaining: 0,
|
|
2238
|
+
notFound: false,
|
|
2239
|
+
alreadyForgotten: [],
|
|
2240
|
+
totalPatches,
|
|
2241
|
+
warnings
|
|
2242
|
+
};
|
|
2125
2243
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2244
|
+
if (options?.patchIds && options.patchIds.length > 0) {
|
|
2245
|
+
const removed = [];
|
|
2246
|
+
const alreadyForgotten = [];
|
|
2247
|
+
const patchesToRemove = [];
|
|
2248
|
+
for (const id of options.patchIds) {
|
|
2249
|
+
const patch = lock.patches.find((p) => p.id === id);
|
|
2250
|
+
if (patch) {
|
|
2251
|
+
removed.push(toMatchedPatch(patch));
|
|
2252
|
+
patchesToRemove.push(patch);
|
|
2253
|
+
} else {
|
|
2254
|
+
alreadyForgotten.push(id);
|
|
2255
|
+
}
|
|
2134
2256
|
}
|
|
2135
|
-
|
|
2257
|
+
const warnings = buildWarnings(patchesToRemove);
|
|
2258
|
+
if (!options.dryRun) {
|
|
2259
|
+
for (const patch of patchesToRemove) {
|
|
2260
|
+
lockManager.addForgottenHash(patch.content_hash);
|
|
2261
|
+
lockManager.removePatch(patch.id);
|
|
2262
|
+
}
|
|
2263
|
+
lockManager.save();
|
|
2264
|
+
}
|
|
2265
|
+
return {
|
|
2266
|
+
initialized: true,
|
|
2267
|
+
removed,
|
|
2268
|
+
remaining: totalPatches - removed.length,
|
|
2269
|
+
notFound: removed.length === 0 && alreadyForgotten.length > 0,
|
|
2270
|
+
alreadyForgotten,
|
|
2271
|
+
totalPatches,
|
|
2272
|
+
warnings
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
if (options?.pattern) {
|
|
2276
|
+
const matched = lock.patches.filter((p) => matchesPatch(p, options.pattern)).map(toMatchedPatch);
|
|
2277
|
+
return {
|
|
2278
|
+
initialized: true,
|
|
2279
|
+
removed: [],
|
|
2280
|
+
remaining: totalPatches,
|
|
2281
|
+
notFound: matched.length === 0,
|
|
2282
|
+
alreadyForgotten: [],
|
|
2283
|
+
totalPatches,
|
|
2284
|
+
warnings: [],
|
|
2285
|
+
matched
|
|
2286
|
+
};
|
|
2136
2287
|
}
|
|
2137
|
-
return {
|
|
2288
|
+
return {
|
|
2289
|
+
initialized: true,
|
|
2290
|
+
removed: [],
|
|
2291
|
+
remaining: totalPatches,
|
|
2292
|
+
notFound: totalPatches === 0,
|
|
2293
|
+
alreadyForgotten: [],
|
|
2294
|
+
totalPatches,
|
|
2295
|
+
warnings: [],
|
|
2296
|
+
matched: lock.patches.map(toMatchedPatch)
|
|
2297
|
+
};
|
|
2138
2298
|
}
|
|
2139
2299
|
|
|
2140
2300
|
// src/commands/reset.ts
|
|
@@ -2256,24 +2416,49 @@ async function getChangedFiles(git, currentGen, files) {
|
|
|
2256
2416
|
function status(outputDir) {
|
|
2257
2417
|
const lockManager = new LockfileManager(outputDir);
|
|
2258
2418
|
if (!lockManager.exists()) {
|
|
2259
|
-
return {
|
|
2419
|
+
return {
|
|
2420
|
+
initialized: false,
|
|
2421
|
+
generationCount: 0,
|
|
2422
|
+
lastGeneration: void 0,
|
|
2423
|
+
patches: [],
|
|
2424
|
+
unresolvedCount: 0,
|
|
2425
|
+
excludePatterns: []
|
|
2426
|
+
};
|
|
2260
2427
|
}
|
|
2261
2428
|
const lock = lockManager.read();
|
|
2262
2429
|
const patches = lock.patches.map((patch) => ({
|
|
2263
|
-
|
|
2264
|
-
|
|
2430
|
+
id: patch.id,
|
|
2431
|
+
type: patch.patch_content.includes("new file mode") ? "added" : "modified",
|
|
2265
2432
|
message: patch.original_message,
|
|
2266
|
-
|
|
2433
|
+
author: patch.original_author.split("<")[0]?.trim() || "unknown",
|
|
2434
|
+
sha: patch.original_commit.slice(0, 7),
|
|
2435
|
+
files: patch.files,
|
|
2436
|
+
fileCount: patch.files.length,
|
|
2437
|
+
...patch.status ? { status: patch.status } : {}
|
|
2267
2438
|
}));
|
|
2439
|
+
const unresolvedCount = lock.patches.filter(
|
|
2440
|
+
(p) => p.status === "unresolved" || p.status === "resolving"
|
|
2441
|
+
).length;
|
|
2268
2442
|
let lastGeneration;
|
|
2269
2443
|
const lastGen = lock.generations.find((g) => g.commit_sha === lock.current_generation);
|
|
2270
2444
|
if (lastGen) {
|
|
2271
2445
|
lastGeneration = {
|
|
2272
|
-
sha: lastGen.commit_sha,
|
|
2273
|
-
timestamp: lastGen.timestamp
|
|
2446
|
+
sha: lastGen.commit_sha.slice(0, 7),
|
|
2447
|
+
timestamp: lastGen.timestamp,
|
|
2448
|
+
cliVersion: lastGen.cli_version,
|
|
2449
|
+
generatorVersions: lastGen.generator_versions
|
|
2274
2450
|
};
|
|
2275
2451
|
}
|
|
2276
|
-
|
|
2452
|
+
const config = lockManager.getCustomizationsConfig();
|
|
2453
|
+
const excludePatterns = config.exclude ?? [];
|
|
2454
|
+
return {
|
|
2455
|
+
initialized: true,
|
|
2456
|
+
generationCount: lock.generations.length,
|
|
2457
|
+
lastGeneration,
|
|
2458
|
+
patches,
|
|
2459
|
+
unresolvedCount,
|
|
2460
|
+
excludePatterns
|
|
2461
|
+
};
|
|
2277
2462
|
}
|
|
2278
2463
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2279
2464
|
0 && (module.exports = {
|