@fern-api/replay 0.12.0 → 0.14.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 +707 -61
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +705 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -3
- package/dist/index.d.ts +97 -3
- package/dist/index.js +704 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -69,6 +69,15 @@ interface FileResult {
|
|
|
69
69
|
conflicts?: ConflictRegion[];
|
|
70
70
|
conflictReason?: ConflictReason;
|
|
71
71
|
conflictMetadata?: ConflictMetadata;
|
|
72
|
+
/**
|
|
73
|
+
* Lines that appeared as unchanged context in the customer's THEIRS (also
|
|
74
|
+
* present in BASE) but were absent from the merged output because OURS
|
|
75
|
+
* deleted them. diff3 sides with the deletion correctly, but the customer
|
|
76
|
+
* may not have realized those lines depended on the generator never
|
|
77
|
+
* removing them. Surfaced as a warning so the customer can re-add them
|
|
78
|
+
* if needed. Empty/undefined when no such lines exist.
|
|
79
|
+
*/
|
|
80
|
+
droppedContextLines?: string[];
|
|
72
81
|
}
|
|
73
82
|
interface ConflictRegion {
|
|
74
83
|
startLine: number;
|
|
@@ -80,6 +89,14 @@ interface MergeResult {
|
|
|
80
89
|
content: string;
|
|
81
90
|
hasConflicts: boolean;
|
|
82
91
|
conflicts: ConflictRegion[];
|
|
92
|
+
/**
|
|
93
|
+
* Lines present in BOTH base and theirs but absent from the merged output.
|
|
94
|
+
* These are lines the customer's THEIRS treated as context; OURS deleted
|
|
95
|
+
* them; diff3 sided with the deletion. Empty when no such lines exist.
|
|
96
|
+
* Only populated when `hasConflicts` is false — when conflicts surface,
|
|
97
|
+
* the customer already knows something needs review.
|
|
98
|
+
*/
|
|
99
|
+
droppedContextLines?: string[];
|
|
83
100
|
}
|
|
84
101
|
interface CommitInfo {
|
|
85
102
|
sha: string;
|
|
@@ -210,6 +227,31 @@ declare class ReplayDetector {
|
|
|
210
227
|
readonly warnings: string[];
|
|
211
228
|
constructor(git: GitClient, lockManager: LockfileManager, sdkOutputDir: string);
|
|
212
229
|
detectNewPatches(): Promise<DetectionResult>;
|
|
230
|
+
/**
|
|
231
|
+
* FER-10201 — Non-linear-history detection via `merge-base(prevGen, HEAD)`.
|
|
232
|
+
*
|
|
233
|
+
* After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
|
|
234
|
+
* `[fern-generated]`) is orphaned but still in git's object database. The
|
|
235
|
+
* merge-base is the commit on main from which the bot's branch was created —
|
|
236
|
+
* a reachable ancestor of HEAD. Walking that range and classifying each
|
|
237
|
+
* commit via `isGenerationCommit` mirrors the linear path and eliminates
|
|
238
|
+
* the bug class where pipeline-driven changes (autoversion, replay,
|
|
239
|
+
* generation) get bundled as customer customizations.
|
|
240
|
+
*
|
|
241
|
+
* Falls through to the legacy composite path when no common ancestor exists
|
|
242
|
+
* (truly disjoint history — orphan branches with no shared root).
|
|
243
|
+
*/
|
|
244
|
+
private detectPatchesViaMergeBase;
|
|
245
|
+
/**
|
|
246
|
+
* FER-10201 — Walk `${rangeStart}..HEAD`, classify each commit, emit one
|
|
247
|
+
* `StoredPatch` per customer commit. Body shared by the linear path
|
|
248
|
+
* (rangeStart = lastGen.commit_sha) and the merge-base path.
|
|
249
|
+
*
|
|
250
|
+
* Patch `base_generation` is always `lastGen.commit_sha` — the
|
|
251
|
+
* lockfile-tracked reference the applicator uses to fetch base file
|
|
252
|
+
* content, regardless of where the walk actually started.
|
|
253
|
+
*/
|
|
254
|
+
private detectPatchesInRange;
|
|
213
255
|
/**
|
|
214
256
|
* Compute content hash for deduplication.
|
|
215
257
|
*
|
|
@@ -321,6 +363,19 @@ declare class ReplayApplicator {
|
|
|
321
363
|
private renameCache;
|
|
322
364
|
private treeExistsCache;
|
|
323
365
|
private fileTheirsAccumulator;
|
|
366
|
+
/**
|
|
367
|
+
* Apply mode for the current `applyPatches` invocation. Set at the start
|
|
368
|
+
* of `applyPatches`, read by `mergeFile` to decide:
|
|
369
|
+
* - whether to skip the intra-loop marker strip (kept in `applyPatches`)
|
|
370
|
+
* - whether to populate the accumulator after a conflicted merge
|
|
371
|
+
* (resolve mode populates so subsequent patches on the same file
|
|
372
|
+
* can use patch A's THEIRS as a structurally-correct merge base
|
|
373
|
+
* when their diff was authored against post-A structure)
|
|
374
|
+
* - whether to retry THEIRS reconstruction against the accumulator
|
|
375
|
+
* when the BASE-relative reconstruction produced markers from a
|
|
376
|
+
* cross-patch context mismatch
|
|
377
|
+
*/
|
|
378
|
+
private currentApplyMode;
|
|
324
379
|
constructor(git: GitClient, lockManager: LockfileManager, outputDir: string);
|
|
325
380
|
/**
|
|
326
381
|
* Resolve the GenerationRecord for a patch's base_generation.
|
|
@@ -344,9 +399,31 @@ declare class ReplayApplicator {
|
|
|
344
399
|
/**
|
|
345
400
|
* Apply all patches, returning results for each.
|
|
346
401
|
* Skips patches that match exclude patterns in replay.yml
|
|
402
|
+
*
|
|
403
|
+
* `applyMode` controls the post-conflict marker strategy:
|
|
404
|
+
* - `"replay"` (default): strip conflict markers from disk between
|
|
405
|
+
* iterations whenever a later patch touches the same file. Lets the
|
|
406
|
+
* follow-up patch's `git apply --3way` see clean OURS content,
|
|
407
|
+
* which is what the silent-loss fix (PR #73) relies on. The replay
|
|
408
|
+
* pipeline calls `revertConflictingFiles` after the loop to clean
|
|
409
|
+
* any markers that survive.
|
|
410
|
+
* - `"resolve"`: keep markers on disk. The resolve command needs the
|
|
411
|
+
* customer to see them. Slow-path 3-way merge naturally preserves
|
|
412
|
+
* A's markers and B's clean writes when their regions don't overlap;
|
|
413
|
+
* when they do overlap, the customer gets nested markers and
|
|
414
|
+
* resolves manually. Either way they aren't silently dropped.
|
|
415
|
+
*/
|
|
416
|
+
applyPatches(patches: StoredPatch[], opts?: {
|
|
417
|
+
applyMode?: "replay" | "resolve";
|
|
418
|
+
}): Promise<ReplayResult[]>;
|
|
419
|
+
/**
|
|
420
|
+
* Populate accumulator after git apply succeeds, AND collect per-file
|
|
421
|
+
* results including any "dropped context lines" — lines that were
|
|
422
|
+
* present in BASE and THEIRS (unchanged context) but absent from the
|
|
423
|
+
* final on-disk state because OURS deleted them. This is the fast-path
|
|
424
|
+
* counterpart to ThreeWayMerge.computeDroppedContextLines used by the
|
|
425
|
+
* 3-way slow path; both must surface the same warning.
|
|
347
426
|
*/
|
|
348
|
-
applyPatches(patches: StoredPatch[]): Promise<ReplayResult[]>;
|
|
349
|
-
/** Populate accumulator after git apply succeeds. */
|
|
350
427
|
private populateAccumulatorForPatch;
|
|
351
428
|
private applyPatchWithFallback;
|
|
352
429
|
private applyWithThreeWayMerge;
|
|
@@ -378,7 +455,13 @@ declare class ReplayCommitter {
|
|
|
378
455
|
private outputDir;
|
|
379
456
|
constructor(git: GitClient, outputDir: string);
|
|
380
457
|
commitGeneration(message: string, options?: CommitOptions): Promise<string>;
|
|
381
|
-
commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string
|
|
458
|
+
commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string, options?: {
|
|
459
|
+
buckets?: {
|
|
460
|
+
applied: StoredPatch[];
|
|
461
|
+
unresolved: StoredPatch[];
|
|
462
|
+
absorbed: StoredPatch[];
|
|
463
|
+
};
|
|
464
|
+
}): Promise<string>;
|
|
382
465
|
createGenerationRecord(options?: CommitOptions): Promise<GenerationRecord>;
|
|
383
466
|
stageAll(): Promise<void>;
|
|
384
467
|
hasStagedChanges(): Promise<boolean>;
|
|
@@ -643,6 +726,15 @@ declare class ReplayService {
|
|
|
643
726
|
* Called BEFORE commitGeneration() while HEAD has customer code.
|
|
644
727
|
*/
|
|
645
728
|
private preGenerationRebase;
|
|
729
|
+
/**
|
|
730
|
+
* After applyPatches(), surface a warning for each (patch × file) where
|
|
731
|
+
* diff3 silently dropped lines that were unchanged context in the
|
|
732
|
+
* customer's THEIRS. The deletion stays on disk (correct diff3 outcome),
|
|
733
|
+
* but the customer must learn so they can re-add the lines if they
|
|
734
|
+
* relied on them. This is the "surface or preserve, never silently drop"
|
|
735
|
+
* contract for legitimate-deletion cases.
|
|
736
|
+
*/
|
|
737
|
+
private recordDroppedContextLineWarnings;
|
|
646
738
|
/**
|
|
647
739
|
* After applyPatches(), strip conflict markers from conflicting files
|
|
648
740
|
* so only clean content is committed. Keeps the Generated (OURS) side.
|
|
@@ -814,6 +906,8 @@ interface ResolveResult {
|
|
|
814
906
|
patchesApplied?: number;
|
|
815
907
|
/** Number of patches resolved and committed (phase 2) */
|
|
816
908
|
patchesResolved?: number;
|
|
909
|
+
/** Non-fatal diagnostic messages (e.g., per-patch fallbacks) */
|
|
910
|
+
warnings?: string[];
|
|
817
911
|
}
|
|
818
912
|
declare function resolve(outputDir: string, options?: ResolveOptions): Promise<ResolveResult>;
|
|
819
913
|
|
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,15 @@ interface FileResult {
|
|
|
69
69
|
conflicts?: ConflictRegion[];
|
|
70
70
|
conflictReason?: ConflictReason;
|
|
71
71
|
conflictMetadata?: ConflictMetadata;
|
|
72
|
+
/**
|
|
73
|
+
* Lines that appeared as unchanged context in the customer's THEIRS (also
|
|
74
|
+
* present in BASE) but were absent from the merged output because OURS
|
|
75
|
+
* deleted them. diff3 sides with the deletion correctly, but the customer
|
|
76
|
+
* may not have realized those lines depended on the generator never
|
|
77
|
+
* removing them. Surfaced as a warning so the customer can re-add them
|
|
78
|
+
* if needed. Empty/undefined when no such lines exist.
|
|
79
|
+
*/
|
|
80
|
+
droppedContextLines?: string[];
|
|
72
81
|
}
|
|
73
82
|
interface ConflictRegion {
|
|
74
83
|
startLine: number;
|
|
@@ -80,6 +89,14 @@ interface MergeResult {
|
|
|
80
89
|
content: string;
|
|
81
90
|
hasConflicts: boolean;
|
|
82
91
|
conflicts: ConflictRegion[];
|
|
92
|
+
/**
|
|
93
|
+
* Lines present in BOTH base and theirs but absent from the merged output.
|
|
94
|
+
* These are lines the customer's THEIRS treated as context; OURS deleted
|
|
95
|
+
* them; diff3 sided with the deletion. Empty when no such lines exist.
|
|
96
|
+
* Only populated when `hasConflicts` is false — when conflicts surface,
|
|
97
|
+
* the customer already knows something needs review.
|
|
98
|
+
*/
|
|
99
|
+
droppedContextLines?: string[];
|
|
83
100
|
}
|
|
84
101
|
interface CommitInfo {
|
|
85
102
|
sha: string;
|
|
@@ -210,6 +227,31 @@ declare class ReplayDetector {
|
|
|
210
227
|
readonly warnings: string[];
|
|
211
228
|
constructor(git: GitClient, lockManager: LockfileManager, sdkOutputDir: string);
|
|
212
229
|
detectNewPatches(): Promise<DetectionResult>;
|
|
230
|
+
/**
|
|
231
|
+
* FER-10201 — Non-linear-history detection via `merge-base(prevGen, HEAD)`.
|
|
232
|
+
*
|
|
233
|
+
* After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
|
|
234
|
+
* `[fern-generated]`) is orphaned but still in git's object database. The
|
|
235
|
+
* merge-base is the commit on main from which the bot's branch was created —
|
|
236
|
+
* a reachable ancestor of HEAD. Walking that range and classifying each
|
|
237
|
+
* commit via `isGenerationCommit` mirrors the linear path and eliminates
|
|
238
|
+
* the bug class where pipeline-driven changes (autoversion, replay,
|
|
239
|
+
* generation) get bundled as customer customizations.
|
|
240
|
+
*
|
|
241
|
+
* Falls through to the legacy composite path when no common ancestor exists
|
|
242
|
+
* (truly disjoint history — orphan branches with no shared root).
|
|
243
|
+
*/
|
|
244
|
+
private detectPatchesViaMergeBase;
|
|
245
|
+
/**
|
|
246
|
+
* FER-10201 — Walk `${rangeStart}..HEAD`, classify each commit, emit one
|
|
247
|
+
* `StoredPatch` per customer commit. Body shared by the linear path
|
|
248
|
+
* (rangeStart = lastGen.commit_sha) and the merge-base path.
|
|
249
|
+
*
|
|
250
|
+
* Patch `base_generation` is always `lastGen.commit_sha` — the
|
|
251
|
+
* lockfile-tracked reference the applicator uses to fetch base file
|
|
252
|
+
* content, regardless of where the walk actually started.
|
|
253
|
+
*/
|
|
254
|
+
private detectPatchesInRange;
|
|
213
255
|
/**
|
|
214
256
|
* Compute content hash for deduplication.
|
|
215
257
|
*
|
|
@@ -321,6 +363,19 @@ declare class ReplayApplicator {
|
|
|
321
363
|
private renameCache;
|
|
322
364
|
private treeExistsCache;
|
|
323
365
|
private fileTheirsAccumulator;
|
|
366
|
+
/**
|
|
367
|
+
* Apply mode for the current `applyPatches` invocation. Set at the start
|
|
368
|
+
* of `applyPatches`, read by `mergeFile` to decide:
|
|
369
|
+
* - whether to skip the intra-loop marker strip (kept in `applyPatches`)
|
|
370
|
+
* - whether to populate the accumulator after a conflicted merge
|
|
371
|
+
* (resolve mode populates so subsequent patches on the same file
|
|
372
|
+
* can use patch A's THEIRS as a structurally-correct merge base
|
|
373
|
+
* when their diff was authored against post-A structure)
|
|
374
|
+
* - whether to retry THEIRS reconstruction against the accumulator
|
|
375
|
+
* when the BASE-relative reconstruction produced markers from a
|
|
376
|
+
* cross-patch context mismatch
|
|
377
|
+
*/
|
|
378
|
+
private currentApplyMode;
|
|
324
379
|
constructor(git: GitClient, lockManager: LockfileManager, outputDir: string);
|
|
325
380
|
/**
|
|
326
381
|
* Resolve the GenerationRecord for a patch's base_generation.
|
|
@@ -344,9 +399,31 @@ declare class ReplayApplicator {
|
|
|
344
399
|
/**
|
|
345
400
|
* Apply all patches, returning results for each.
|
|
346
401
|
* Skips patches that match exclude patterns in replay.yml
|
|
402
|
+
*
|
|
403
|
+
* `applyMode` controls the post-conflict marker strategy:
|
|
404
|
+
* - `"replay"` (default): strip conflict markers from disk between
|
|
405
|
+
* iterations whenever a later patch touches the same file. Lets the
|
|
406
|
+
* follow-up patch's `git apply --3way` see clean OURS content,
|
|
407
|
+
* which is what the silent-loss fix (PR #73) relies on. The replay
|
|
408
|
+
* pipeline calls `revertConflictingFiles` after the loop to clean
|
|
409
|
+
* any markers that survive.
|
|
410
|
+
* - `"resolve"`: keep markers on disk. The resolve command needs the
|
|
411
|
+
* customer to see them. Slow-path 3-way merge naturally preserves
|
|
412
|
+
* A's markers and B's clean writes when their regions don't overlap;
|
|
413
|
+
* when they do overlap, the customer gets nested markers and
|
|
414
|
+
* resolves manually. Either way they aren't silently dropped.
|
|
415
|
+
*/
|
|
416
|
+
applyPatches(patches: StoredPatch[], opts?: {
|
|
417
|
+
applyMode?: "replay" | "resolve";
|
|
418
|
+
}): Promise<ReplayResult[]>;
|
|
419
|
+
/**
|
|
420
|
+
* Populate accumulator after git apply succeeds, AND collect per-file
|
|
421
|
+
* results including any "dropped context lines" — lines that were
|
|
422
|
+
* present in BASE and THEIRS (unchanged context) but absent from the
|
|
423
|
+
* final on-disk state because OURS deleted them. This is the fast-path
|
|
424
|
+
* counterpart to ThreeWayMerge.computeDroppedContextLines used by the
|
|
425
|
+
* 3-way slow path; both must surface the same warning.
|
|
347
426
|
*/
|
|
348
|
-
applyPatches(patches: StoredPatch[]): Promise<ReplayResult[]>;
|
|
349
|
-
/** Populate accumulator after git apply succeeds. */
|
|
350
427
|
private populateAccumulatorForPatch;
|
|
351
428
|
private applyPatchWithFallback;
|
|
352
429
|
private applyWithThreeWayMerge;
|
|
@@ -378,7 +455,13 @@ declare class ReplayCommitter {
|
|
|
378
455
|
private outputDir;
|
|
379
456
|
constructor(git: GitClient, outputDir: string);
|
|
380
457
|
commitGeneration(message: string, options?: CommitOptions): Promise<string>;
|
|
381
|
-
commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string
|
|
458
|
+
commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string, options?: {
|
|
459
|
+
buckets?: {
|
|
460
|
+
applied: StoredPatch[];
|
|
461
|
+
unresolved: StoredPatch[];
|
|
462
|
+
absorbed: StoredPatch[];
|
|
463
|
+
};
|
|
464
|
+
}): Promise<string>;
|
|
382
465
|
createGenerationRecord(options?: CommitOptions): Promise<GenerationRecord>;
|
|
383
466
|
stageAll(): Promise<void>;
|
|
384
467
|
hasStagedChanges(): Promise<boolean>;
|
|
@@ -643,6 +726,15 @@ declare class ReplayService {
|
|
|
643
726
|
* Called BEFORE commitGeneration() while HEAD has customer code.
|
|
644
727
|
*/
|
|
645
728
|
private preGenerationRebase;
|
|
729
|
+
/**
|
|
730
|
+
* After applyPatches(), surface a warning for each (patch × file) where
|
|
731
|
+
* diff3 silently dropped lines that were unchanged context in the
|
|
732
|
+
* customer's THEIRS. The deletion stays on disk (correct diff3 outcome),
|
|
733
|
+
* but the customer must learn so they can re-add the lines if they
|
|
734
|
+
* relied on them. This is the "surface or preserve, never silently drop"
|
|
735
|
+
* contract for legitimate-deletion cases.
|
|
736
|
+
*/
|
|
737
|
+
private recordDroppedContextLineWarnings;
|
|
646
738
|
/**
|
|
647
739
|
* After applyPatches(), strip conflict markers from conflicting files
|
|
648
740
|
* so only clean content is committed. Keeps the Generated (OURS) side.
|
|
@@ -814,6 +906,8 @@ interface ResolveResult {
|
|
|
814
906
|
patchesApplied?: number;
|
|
815
907
|
/** Number of patches resolved and committed (phase 2) */
|
|
816
908
|
patchesResolved?: number;
|
|
909
|
+
/** Non-fatal diagnostic messages (e.g., per-patch fallbacks) */
|
|
910
|
+
warnings?: string[];
|
|
817
911
|
}
|
|
818
912
|
declare function resolve(outputDir: string, options?: ResolveOptions): Promise<ResolveResult>;
|
|
819
913
|
|