@fern-api/replay 0.6.2 → 0.8.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/index.d.cts CHANGED
@@ -23,6 +23,10 @@ interface StoredPatch {
23
23
  base_generation: string;
24
24
  files: string[];
25
25
  patch_content: string;
26
+ /** When "unresolved", the patch conflicted during CI and needs local resolution.
27
+ * When "resolving", the resolve command has applied patches to the working tree
28
+ * and is waiting for the customer to resolve conflicts and run resolve again. */
29
+ status?: "unresolved" | "resolving";
26
30
  }
27
31
  interface CustomizationsConfig {
28
32
  exclude?: string[];
@@ -96,6 +100,7 @@ declare class GitClient {
96
100
  }>>;
97
101
  isAncestor(commit: string, descendant: string): Promise<boolean>;
98
102
  commitExists(sha: string): Promise<boolean>;
103
+ getCommitBody(commitSha: string): Promise<string>;
99
104
  getRepoPath(): string;
100
105
  }
101
106
 
@@ -104,6 +109,12 @@ declare const FERN_BOT_EMAIL = "115122769+fern-api[bot]@users.noreply.github.com
104
109
  declare const FERN_BOT_LOGIN = "fern-api[bot]";
105
110
  declare function isGenerationCommit(commit: CommitInfo): boolean;
106
111
  declare function isReplayCommit(commit: CommitInfo): boolean;
112
+ /** Check if a commit message indicates a git revert */
113
+ declare function isRevertCommit(message: string): boolean;
114
+ /** Extract the reverted commit SHA from a full commit body containing "This reverts commit SHA." */
115
+ declare function parseRevertedSha(fullBody: string): string | undefined;
116
+ /** Extract the original commit message from a revert subject like 'Revert "original message"' */
117
+ declare function parseRevertedMessage(subject: string): string | undefined;
107
118
 
108
119
  declare class LockfileManager {
109
120
  private outputDir;
@@ -123,9 +134,13 @@ declare class LockfileManager {
123
134
  save(): void;
124
135
  addGeneration(record: GenerationRecord): void;
125
136
  addPatch(patch: StoredPatch): void;
126
- updatePatch(patchId: string, updates: Partial<Pick<StoredPatch, "base_generation" | "patch_content" | "content_hash" | "files">>): void;
137
+ updatePatch(patchId: string, updates: Partial<Pick<StoredPatch, "base_generation" | "patch_content" | "content_hash" | "files" | "status">>): void;
127
138
  removePatch(patchId: string): void;
128
139
  clearPatches(): void;
140
+ getUnresolvedPatches(): StoredPatch[];
141
+ getResolvingPatches(): StoredPatch[];
142
+ markPatchUnresolved(patchId: string): void;
143
+ markPatchResolved(patchId: string, updates: Pick<StoredPatch, "patch_content" | "content_hash" | "base_generation" | "files">): void;
129
144
  getPatches(): StoredPatch[];
130
145
  setReplaySkippedAt(timestamp: string): void;
131
146
  clearReplaySkippedAt(): void;
@@ -135,20 +150,28 @@ declare class LockfileManager {
135
150
  private ensureLoaded;
136
151
  }
137
152
 
153
+ interface DetectionResult {
154
+ patches: StoredPatch[];
155
+ revertedPatchIds: string[];
156
+ }
138
157
  declare class ReplayDetector {
139
158
  private git;
140
159
  private lockManager;
141
160
  private sdkOutputDir;
142
161
  readonly warnings: string[];
143
162
  constructor(git: GitClient, lockManager: LockfileManager, sdkOutputDir: string);
144
- detectNewPatches(): Promise<StoredPatch[]>;
163
+ detectNewPatches(): Promise<DetectionResult>;
145
164
  /**
146
165
  * Compute content hash for deduplication.
147
166
  * Removes commit SHA line and index lines before hashing,
148
167
  * so rebased commits with same content produce the same hash.
149
168
  */
150
169
  computeContentHash(patchContent: string): string;
151
- /** Detect patches via tree diff for non-linear history. Returns a composite patch. */
170
+ /**
171
+ * Detect patches via tree diff for non-linear history. Returns a composite patch.
172
+ * Revert reconciliation is skipped here because tree-diff produces a single composite
173
+ * patch from the aggregate diff — individual revert commits are not distinguishable.
174
+ */
152
175
  private detectPatchesViaTreeDiff;
153
176
  private parseGitLog;
154
177
  private getLastGeneration;
@@ -201,7 +224,7 @@ declare class ReplayCommitter {
201
224
  private outputDir;
202
225
  constructor(git: GitClient, outputDir: string);
203
226
  commitGeneration(message: string, options?: CommitOptions): Promise<string>;
204
- commitReplay(_patchCount: number, patches?: StoredPatch[]): Promise<string>;
227
+ commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string): Promise<string>;
205
228
  createGenerationRecord(options?: CommitOptions): Promise<GenerationRecord>;
206
229
  stageAll(): Promise<void>;
207
230
  hasStagedChanges(): Promise<boolean>;
@@ -216,6 +239,12 @@ interface ConflictDetail {
216
239
  /** Files that applied cleanly in a patch that also had conflicts. */
217
240
  cleanFiles?: string[];
218
241
  }
242
+ interface UnresolvedPatchInfo {
243
+ patchId: string;
244
+ patchMessage: string;
245
+ files: string[];
246
+ conflictDetails: FileResult[];
247
+ }
219
248
  interface ReplayReport {
220
249
  flow: "first-generation" | "no-patches" | "normal-regeneration" | "skip-application";
221
250
  patchesDetected: number;
@@ -228,9 +257,12 @@ interface ReplayReport {
228
257
  patchesKeptAsUserOwned?: number;
229
258
  patchesPartiallyApplied?: number;
230
259
  patchesConflictResolved?: number;
260
+ patchesReverted?: number;
231
261
  patchesRefreshed?: number;
232
262
  conflicts: FileResult[];
233
263
  conflictDetails?: ConflictDetail[];
264
+ /** Patches that conflicted and need local resolution via `fern-replay resolve` */
265
+ unresolvedPatches?: UnresolvedPatchInfo[];
234
266
  wouldApply?: StoredPatch[];
235
267
  warnings?: string[];
236
268
  }
@@ -303,6 +335,11 @@ declare class ReplayService {
303
335
  * Called BEFORE commitGeneration() while HEAD has customer code.
304
336
  */
305
337
  private preGenerationRebase;
338
+ /**
339
+ * After applyPatches(), strip conflict markers from conflicting files
340
+ * so only clean content is committed. Keeps the Generated (OURS) side.
341
+ */
342
+ private revertConflictingFiles;
306
343
  private readFernignorePatterns;
307
344
  private buildReport;
308
345
  }
@@ -427,10 +464,16 @@ interface ResolveResult {
427
464
  success: boolean;
428
465
  /** Commit SHA of the [fern-replay] commit, if created */
429
466
  commitSha?: string;
430
- /** Reason for failure */
467
+ /** Reason for failure or current state */
431
468
  reason?: string;
432
- /** Files that still have conflict markers */
469
+ /** Files that have conflict markers */
433
470
  unresolvedFiles?: string[];
471
+ /** Phase the command executed */
472
+ phase?: "applied" | "committed" | "nothing-to-resolve";
473
+ /** Number of patches applied to working tree (phase 1) */
474
+ patchesApplied?: number;
475
+ /** Number of patches resolved and committed (phase 2) */
476
+ patchesResolved?: number;
434
477
  }
435
478
  declare function resolve(outputDir: string, options?: ResolveOptions): Promise<ResolveResult>;
436
479
 
@@ -460,4 +503,4 @@ interface StatusGeneration {
460
503
  }
461
504
  declare function status(outputDir: string): StatusResult;
462
505
 
463
- export { type BootstrapOptions, type BootstrapResult, type CommitInfo, type CommitOptions, type ConflictDetail, type ConflictMetadata, type ConflictReason, type ConflictRegion, type CustomizationsConfig, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_BOT_NAME, FernignoreMigrator, type FileResult, type ForgetOptions, type ForgetResult, type GenerationLock, type GenerationRecord, GitClient, LockfileManager, type MergeResult, type MigrationAnalysis, type MigrationResult, type MoveDeclaration, ReplayApplicator, ReplayCommitter, type ReplayConfig, ReplayDetector, type ReplayOptions, type ReplayReport, type ReplayResult, ReplayService, type ResetOptions, type ResetResult, type ResolveOptions, type ResolveResult, type StatusGeneration, type StatusPatch, type StatusResult, type StoredPatch, bootstrap, forget, isGenerationCommit, isReplayCommit, reset, resolve, status, threeWayMerge };
506
+ export { type BootstrapOptions, type BootstrapResult, type CommitInfo, type CommitOptions, type ConflictDetail, type ConflictMetadata, type ConflictReason, type ConflictRegion, type CustomizationsConfig, type DetectionResult, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_BOT_NAME, FernignoreMigrator, type FileResult, type ForgetOptions, type ForgetResult, type GenerationLock, type GenerationRecord, GitClient, LockfileManager, type MergeResult, type MigrationAnalysis, type MigrationResult, type MoveDeclaration, ReplayApplicator, ReplayCommitter, type ReplayConfig, ReplayDetector, type ReplayOptions, type ReplayReport, type ReplayResult, ReplayService, type ResetOptions, type ResetResult, type ResolveOptions, type ResolveResult, type StatusGeneration, type StatusPatch, type StatusResult, type StoredPatch, type UnresolvedPatchInfo, bootstrap, forget, isGenerationCommit, isReplayCommit, isRevertCommit, parseRevertedMessage, parseRevertedSha, reset, resolve, status, threeWayMerge };
package/dist/index.d.ts CHANGED
@@ -23,6 +23,10 @@ interface StoredPatch {
23
23
  base_generation: string;
24
24
  files: string[];
25
25
  patch_content: string;
26
+ /** When "unresolved", the patch conflicted during CI and needs local resolution.
27
+ * When "resolving", the resolve command has applied patches to the working tree
28
+ * and is waiting for the customer to resolve conflicts and run resolve again. */
29
+ status?: "unresolved" | "resolving";
26
30
  }
27
31
  interface CustomizationsConfig {
28
32
  exclude?: string[];
@@ -96,6 +100,7 @@ declare class GitClient {
96
100
  }>>;
97
101
  isAncestor(commit: string, descendant: string): Promise<boolean>;
98
102
  commitExists(sha: string): Promise<boolean>;
103
+ getCommitBody(commitSha: string): Promise<string>;
99
104
  getRepoPath(): string;
100
105
  }
101
106
 
@@ -104,6 +109,12 @@ declare const FERN_BOT_EMAIL = "115122769+fern-api[bot]@users.noreply.github.com
104
109
  declare const FERN_BOT_LOGIN = "fern-api[bot]";
105
110
  declare function isGenerationCommit(commit: CommitInfo): boolean;
106
111
  declare function isReplayCommit(commit: CommitInfo): boolean;
112
+ /** Check if a commit message indicates a git revert */
113
+ declare function isRevertCommit(message: string): boolean;
114
+ /** Extract the reverted commit SHA from a full commit body containing "This reverts commit SHA." */
115
+ declare function parseRevertedSha(fullBody: string): string | undefined;
116
+ /** Extract the original commit message from a revert subject like 'Revert "original message"' */
117
+ declare function parseRevertedMessage(subject: string): string | undefined;
107
118
 
108
119
  declare class LockfileManager {
109
120
  private outputDir;
@@ -123,9 +134,13 @@ declare class LockfileManager {
123
134
  save(): void;
124
135
  addGeneration(record: GenerationRecord): void;
125
136
  addPatch(patch: StoredPatch): void;
126
- updatePatch(patchId: string, updates: Partial<Pick<StoredPatch, "base_generation" | "patch_content" | "content_hash" | "files">>): void;
137
+ updatePatch(patchId: string, updates: Partial<Pick<StoredPatch, "base_generation" | "patch_content" | "content_hash" | "files" | "status">>): void;
127
138
  removePatch(patchId: string): void;
128
139
  clearPatches(): void;
140
+ getUnresolvedPatches(): StoredPatch[];
141
+ getResolvingPatches(): StoredPatch[];
142
+ markPatchUnresolved(patchId: string): void;
143
+ markPatchResolved(patchId: string, updates: Pick<StoredPatch, "patch_content" | "content_hash" | "base_generation" | "files">): void;
129
144
  getPatches(): StoredPatch[];
130
145
  setReplaySkippedAt(timestamp: string): void;
131
146
  clearReplaySkippedAt(): void;
@@ -135,20 +150,28 @@ declare class LockfileManager {
135
150
  private ensureLoaded;
136
151
  }
137
152
 
153
+ interface DetectionResult {
154
+ patches: StoredPatch[];
155
+ revertedPatchIds: string[];
156
+ }
138
157
  declare class ReplayDetector {
139
158
  private git;
140
159
  private lockManager;
141
160
  private sdkOutputDir;
142
161
  readonly warnings: string[];
143
162
  constructor(git: GitClient, lockManager: LockfileManager, sdkOutputDir: string);
144
- detectNewPatches(): Promise<StoredPatch[]>;
163
+ detectNewPatches(): Promise<DetectionResult>;
145
164
  /**
146
165
  * Compute content hash for deduplication.
147
166
  * Removes commit SHA line and index lines before hashing,
148
167
  * so rebased commits with same content produce the same hash.
149
168
  */
150
169
  computeContentHash(patchContent: string): string;
151
- /** Detect patches via tree diff for non-linear history. Returns a composite patch. */
170
+ /**
171
+ * Detect patches via tree diff for non-linear history. Returns a composite patch.
172
+ * Revert reconciliation is skipped here because tree-diff produces a single composite
173
+ * patch from the aggregate diff — individual revert commits are not distinguishable.
174
+ */
152
175
  private detectPatchesViaTreeDiff;
153
176
  private parseGitLog;
154
177
  private getLastGeneration;
@@ -201,7 +224,7 @@ declare class ReplayCommitter {
201
224
  private outputDir;
202
225
  constructor(git: GitClient, outputDir: string);
203
226
  commitGeneration(message: string, options?: CommitOptions): Promise<string>;
204
- commitReplay(_patchCount: number, patches?: StoredPatch[]): Promise<string>;
227
+ commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string): Promise<string>;
205
228
  createGenerationRecord(options?: CommitOptions): Promise<GenerationRecord>;
206
229
  stageAll(): Promise<void>;
207
230
  hasStagedChanges(): Promise<boolean>;
@@ -216,6 +239,12 @@ interface ConflictDetail {
216
239
  /** Files that applied cleanly in a patch that also had conflicts. */
217
240
  cleanFiles?: string[];
218
241
  }
242
+ interface UnresolvedPatchInfo {
243
+ patchId: string;
244
+ patchMessage: string;
245
+ files: string[];
246
+ conflictDetails: FileResult[];
247
+ }
219
248
  interface ReplayReport {
220
249
  flow: "first-generation" | "no-patches" | "normal-regeneration" | "skip-application";
221
250
  patchesDetected: number;
@@ -228,9 +257,12 @@ interface ReplayReport {
228
257
  patchesKeptAsUserOwned?: number;
229
258
  patchesPartiallyApplied?: number;
230
259
  patchesConflictResolved?: number;
260
+ patchesReverted?: number;
231
261
  patchesRefreshed?: number;
232
262
  conflicts: FileResult[];
233
263
  conflictDetails?: ConflictDetail[];
264
+ /** Patches that conflicted and need local resolution via `fern-replay resolve` */
265
+ unresolvedPatches?: UnresolvedPatchInfo[];
234
266
  wouldApply?: StoredPatch[];
235
267
  warnings?: string[];
236
268
  }
@@ -303,6 +335,11 @@ declare class ReplayService {
303
335
  * Called BEFORE commitGeneration() while HEAD has customer code.
304
336
  */
305
337
  private preGenerationRebase;
338
+ /**
339
+ * After applyPatches(), strip conflict markers from conflicting files
340
+ * so only clean content is committed. Keeps the Generated (OURS) side.
341
+ */
342
+ private revertConflictingFiles;
306
343
  private readFernignorePatterns;
307
344
  private buildReport;
308
345
  }
@@ -427,10 +464,16 @@ interface ResolveResult {
427
464
  success: boolean;
428
465
  /** Commit SHA of the [fern-replay] commit, if created */
429
466
  commitSha?: string;
430
- /** Reason for failure */
467
+ /** Reason for failure or current state */
431
468
  reason?: string;
432
- /** Files that still have conflict markers */
469
+ /** Files that have conflict markers */
433
470
  unresolvedFiles?: string[];
471
+ /** Phase the command executed */
472
+ phase?: "applied" | "committed" | "nothing-to-resolve";
473
+ /** Number of patches applied to working tree (phase 1) */
474
+ patchesApplied?: number;
475
+ /** Number of patches resolved and committed (phase 2) */
476
+ patchesResolved?: number;
434
477
  }
435
478
  declare function resolve(outputDir: string, options?: ResolveOptions): Promise<ResolveResult>;
436
479
 
@@ -460,4 +503,4 @@ interface StatusGeneration {
460
503
  }
461
504
  declare function status(outputDir: string): StatusResult;
462
505
 
463
- export { type BootstrapOptions, type BootstrapResult, type CommitInfo, type CommitOptions, type ConflictDetail, type ConflictMetadata, type ConflictReason, type ConflictRegion, type CustomizationsConfig, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_BOT_NAME, FernignoreMigrator, type FileResult, type ForgetOptions, type ForgetResult, type GenerationLock, type GenerationRecord, GitClient, LockfileManager, type MergeResult, type MigrationAnalysis, type MigrationResult, type MoveDeclaration, ReplayApplicator, ReplayCommitter, type ReplayConfig, ReplayDetector, type ReplayOptions, type ReplayReport, type ReplayResult, ReplayService, type ResetOptions, type ResetResult, type ResolveOptions, type ResolveResult, type StatusGeneration, type StatusPatch, type StatusResult, type StoredPatch, bootstrap, forget, isGenerationCommit, isReplayCommit, reset, resolve, status, threeWayMerge };
506
+ export { type BootstrapOptions, type BootstrapResult, type CommitInfo, type CommitOptions, type ConflictDetail, type ConflictMetadata, type ConflictReason, type ConflictRegion, type CustomizationsConfig, type DetectionResult, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_BOT_NAME, FernignoreMigrator, type FileResult, type ForgetOptions, type ForgetResult, type GenerationLock, type GenerationRecord, GitClient, LockfileManager, type MergeResult, type MigrationAnalysis, type MigrationResult, type MoveDeclaration, ReplayApplicator, ReplayCommitter, type ReplayConfig, ReplayDetector, type ReplayOptions, type ReplayReport, type ReplayResult, ReplayService, type ResetOptions, type ResetResult, type ResolveOptions, type ResolveResult, type StatusGeneration, type StatusPatch, type StatusResult, type StoredPatch, type UnresolvedPatchInfo, bootstrap, forget, isGenerationCommit, isReplayCommit, isRevertCommit, parseRevertedMessage, parseRevertedSha, reset, resolve, status, threeWayMerge };