@fern-api/replay 0.18.0 → 0.19.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 +427 -120
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +372 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +193 -3
- package/dist/index.d.ts +193 -3
- package/dist/index.js +355 -94
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/cli.cjs
CHANGED
|
@@ -13245,15 +13245,15 @@ __export(PatchApplyTheirs_exports, {
|
|
|
13245
13245
|
async function applyPatchToContent(base, patchContent, filePath) {
|
|
13246
13246
|
const fileDiff = extractFileDiff(patchContent, filePath);
|
|
13247
13247
|
if (!fileDiff) return null;
|
|
13248
|
-
const tempDir = await (0, import_promises5.mkdtemp)((0,
|
|
13248
|
+
const tempDir = await (0, import_promises5.mkdtemp)((0, import_node_path9.join)((0, import_node_os3.tmpdir)(), "replay-migrate-"));
|
|
13249
13249
|
const tempGit = new GitClient(tempDir);
|
|
13250
13250
|
try {
|
|
13251
13251
|
await tempGit.exec(["init"]);
|
|
13252
13252
|
await tempGit.exec(["config", "user.email", "migrate@fern.com"]);
|
|
13253
13253
|
await tempGit.exec(["config", "user.name", "Fern Replay Migration"]);
|
|
13254
13254
|
await tempGit.exec(["config", "commit.gpgSign", "false"]);
|
|
13255
|
-
const tempFilePath = (0,
|
|
13256
|
-
await (0, import_promises5.mkdir)((0,
|
|
13255
|
+
const tempFilePath = (0, import_node_path9.join)(tempDir, filePath);
|
|
13256
|
+
await (0, import_promises5.mkdir)((0, import_node_path9.dirname)(tempFilePath), { recursive: true });
|
|
13257
13257
|
await (0, import_promises5.writeFile)(tempFilePath, base);
|
|
13258
13258
|
await tempGit.exec(["add", filePath]);
|
|
13259
13259
|
await tempGit.exec(["commit", "-m", `base for ${filePath}`, "--allow-empty"]);
|
|
@@ -13284,20 +13284,20 @@ function extractFileDiff(patchContent, filePath) {
|
|
|
13284
13284
|
}
|
|
13285
13285
|
return out.length > 0 ? out.join("\n") + "\n" : null;
|
|
13286
13286
|
}
|
|
13287
|
-
var import_promises5, import_node_os3,
|
|
13287
|
+
var import_promises5, import_node_os3, import_node_path9;
|
|
13288
13288
|
var init_PatchApplyTheirs = __esm({
|
|
13289
13289
|
"src/PatchApplyTheirs.ts"() {
|
|
13290
13290
|
"use strict";
|
|
13291
13291
|
import_promises5 = require("fs/promises");
|
|
13292
13292
|
import_node_os3 = require("os");
|
|
13293
|
-
|
|
13293
|
+
import_node_path9 = require("path");
|
|
13294
13294
|
init_GitClient();
|
|
13295
13295
|
}
|
|
13296
13296
|
});
|
|
13297
13297
|
|
|
13298
13298
|
// src/cli.ts
|
|
13299
|
-
var
|
|
13300
|
-
var
|
|
13299
|
+
var import_node_path14 = require("path");
|
|
13300
|
+
var import_node_fs7 = require("fs");
|
|
13301
13301
|
var import_node_readline = require("readline");
|
|
13302
13302
|
init_GitClient();
|
|
13303
13303
|
|
|
@@ -14827,6 +14827,22 @@ var LockfileManager = class {
|
|
|
14827
14827
|
this.lock.forgotten_hashes.push(hash);
|
|
14828
14828
|
}
|
|
14829
14829
|
}
|
|
14830
|
+
/**
|
|
14831
|
+
* Record a dismissed customization: visibility metadata in `dismissals`
|
|
14832
|
+
* plus a tombstone in `forgotten_hashes` for every content hash.
|
|
14833
|
+
* `forgotten_hashes` stays the only list the detector consults; the
|
|
14834
|
+
* metadata exists so `status` can show what was dismissed.
|
|
14835
|
+
*/
|
|
14836
|
+
addDismissal(record) {
|
|
14837
|
+
this.ensureLoaded();
|
|
14838
|
+
if (!this.lock.dismissals) {
|
|
14839
|
+
this.lock.dismissals = [];
|
|
14840
|
+
}
|
|
14841
|
+
this.lock.dismissals.push(record);
|
|
14842
|
+
for (const hash of record.content_hashes) {
|
|
14843
|
+
this.addForgottenHash(hash);
|
|
14844
|
+
}
|
|
14845
|
+
}
|
|
14830
14846
|
getUnresolvedPatches() {
|
|
14831
14847
|
this.ensureLoaded();
|
|
14832
14848
|
return this.lock.patches.filter((p) => p.status === "unresolved");
|
|
@@ -15035,12 +15051,14 @@ async function capturePatchSnapshot(git, files, treeish) {
|
|
|
15035
15051
|
return snapshot;
|
|
15036
15052
|
}
|
|
15037
15053
|
function filterInfrastructureAndSensitiveFiles(rawFilesOutput, fernignorePatterns) {
|
|
15038
|
-
const
|
|
15054
|
+
const rawFiles = rawFilesOutput.trim().split("\n").filter(Boolean);
|
|
15055
|
+
const infrastructureFiles = rawFiles.filter((f) => INFRASTRUCTURE_FILES.has(f) || f.startsWith(".fern/"));
|
|
15056
|
+
const allFiles = rawFiles.filter((f) => !INFRASTRUCTURE_FILES.has(f) && !f.startsWith(".fern/"));
|
|
15039
15057
|
const sensitiveFiles = allFiles.filter((f) => isSensitiveFile(f));
|
|
15040
15058
|
const nonSensitive = allFiles.filter((f) => !isSensitiveFile(f));
|
|
15041
15059
|
const fernignoredFiles = nonSensitive.filter((f) => matchesFernignorePattern(f, fernignorePatterns));
|
|
15042
15060
|
const files = nonSensitive.filter((f) => !matchesFernignorePattern(f, fernignorePatterns));
|
|
15043
|
-
return { files, sensitiveFiles, fernignoredFiles };
|
|
15061
|
+
return { files, sensitiveFiles, fernignoredFiles, infrastructureFiles };
|
|
15044
15062
|
}
|
|
15045
15063
|
var ReplayDetector = class {
|
|
15046
15064
|
git;
|
|
@@ -15048,6 +15066,15 @@ var ReplayDetector = class {
|
|
|
15048
15066
|
sdkOutputDir;
|
|
15049
15067
|
fernignorePatterns;
|
|
15050
15068
|
warnings = [];
|
|
15069
|
+
/**
|
|
15070
|
+
* Structured degraded-state channel, mirroring `warnings`. Populated when
|
|
15071
|
+
* detection cannot guarantee customizations were carried forward (baseline
|
|
15072
|
+
* unreachable in this clone). Cleared per-run by `ReplayService.prepareReplay`
|
|
15073
|
+
* alongside `warnings`. Merged into `ReplayReport.degradedReasons` by each
|
|
15074
|
+
* flow handler. Degraded is observability only: detection results are
|
|
15075
|
+
* unchanged.
|
|
15076
|
+
*/
|
|
15077
|
+
degradedReasons = [];
|
|
15051
15078
|
constructor(git, lockManager, sdkOutputDir, fernignorePatterns = []) {
|
|
15052
15079
|
this.git = git;
|
|
15053
15080
|
this.lockManager = lockManager;
|
|
@@ -15118,6 +15145,10 @@ var ReplayDetector = class {
|
|
|
15118
15145
|
this.warnings.push(
|
|
15119
15146
|
`No generation boundary found in git history and the lockfile's recorded generation (${lock.current_generation.slice(0, 7)}) matches no entry in its generation records. Customer customizations may differ from the last known generation and could be lost on the next regeneration. Run \`fern-replay bootstrap --force\` to re-initialize tracking.`
|
|
15120
15147
|
);
|
|
15148
|
+
this.degradedReasons.push({
|
|
15149
|
+
code: "baseline-unresolvable",
|
|
15150
|
+
message: `The previous generation recorded in the lockfile (${lock.current_generation.slice(0, 7)}) could not be resolved to any reachable baseline during this run. Customizations may not have been carried forward \u2014 review the result before merging.`
|
|
15151
|
+
});
|
|
15121
15152
|
}
|
|
15122
15153
|
return { patches: [], revertedPatchIds: [] };
|
|
15123
15154
|
}
|
|
@@ -15126,6 +15157,12 @@ var ReplayDetector = class {
|
|
|
15126
15157
|
this.warnings.push(
|
|
15127
15158
|
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to tree-diff detection (likely a shallow clone).`
|
|
15128
15159
|
);
|
|
15160
|
+
if (lock.patches.length > 0) {
|
|
15161
|
+
this.degradedReasons.push({
|
|
15162
|
+
code: "generation-anchor-unreachable",
|
|
15163
|
+
message: `The previous generation (${lastGen.commit_sha.slice(0, 7)}) was not reachable during this run, and ${lock.patches.length} tracked customization(s) could not be verified against it. Customizations may not have been carried forward \u2014 review the result before merging.`
|
|
15164
|
+
});
|
|
15165
|
+
}
|
|
15129
15166
|
return this.detectPatchesViaTreeDiff(
|
|
15130
15167
|
lastGen,
|
|
15131
15168
|
/* commitKnownMissing */
|
|
@@ -15165,54 +15202,27 @@ var ReplayDetector = class {
|
|
|
15165
15202
|
if (lock.patches.find((p) => p.original_commit === commit.sha)) {
|
|
15166
15203
|
continue;
|
|
15167
15204
|
}
|
|
15168
|
-
const
|
|
15169
|
-
|
|
15170
|
-
try {
|
|
15171
|
-
filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
15172
|
-
} catch {
|
|
15205
|
+
const materialized = await this.materializeCommitPatch(commit.sha);
|
|
15206
|
+
if (materialized.sensitiveFiles.length > 0) {
|
|
15173
15207
|
this.warnings.push(
|
|
15174
|
-
`
|
|
15208
|
+
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${materialized.sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
15175
15209
|
);
|
|
15176
|
-
continue;
|
|
15177
15210
|
}
|
|
15178
|
-
|
|
15179
|
-
filesOutput,
|
|
15180
|
-
this.fernignorePatterns
|
|
15181
|
-
);
|
|
15182
|
-
if (sensitiveFiles.length > 0) {
|
|
15211
|
+
if (materialized.fernignoredFiles.length > 0) {
|
|
15183
15212
|
this.warnings.push(
|
|
15184
|
-
|
|
15213
|
+
`.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${materialized.fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
|
|
15185
15214
|
);
|
|
15186
15215
|
}
|
|
15187
|
-
if (
|
|
15216
|
+
if (materialized.status === "unreachable") {
|
|
15188
15217
|
this.warnings.push(
|
|
15189
|
-
|
|
15218
|
+
`Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
|
|
15190
15219
|
);
|
|
15191
|
-
}
|
|
15192
|
-
if (files.length === 0) {
|
|
15193
15220
|
continue;
|
|
15194
15221
|
}
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
let patchContent;
|
|
15198
|
-
if (wasFiltered && parentSha) {
|
|
15199
|
-
try {
|
|
15200
|
-
patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
|
|
15201
|
-
} catch {
|
|
15202
|
-
continue;
|
|
15203
|
-
}
|
|
15204
|
-
if (!patchContent.trim()) continue;
|
|
15205
|
-
} else {
|
|
15206
|
-
try {
|
|
15207
|
-
patchContent = await this.git.formatPatch(commit.sha);
|
|
15208
|
-
} catch {
|
|
15209
|
-
this.warnings.push(
|
|
15210
|
-
`Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
|
|
15211
|
-
);
|
|
15212
|
-
continue;
|
|
15213
|
-
}
|
|
15222
|
+
if (materialized.status === "empty") {
|
|
15223
|
+
continue;
|
|
15214
15224
|
}
|
|
15215
|
-
const contentHash =
|
|
15225
|
+
const { files, patchContent, contentHash } = materialized;
|
|
15216
15226
|
if (lock.patches.find((p) => p.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
15217
15227
|
continue;
|
|
15218
15228
|
}
|
|
@@ -15289,6 +15299,63 @@ var ReplayDetector = class {
|
|
|
15289
15299
|
const filteredPatches = survivingPatches.filter((_, i) => !revertIndicesToRemove.has(i));
|
|
15290
15300
|
return { patches: filteredPatches, revertedPatchIds: [...revertedPatchIdSet] };
|
|
15291
15301
|
}
|
|
15302
|
+
/**
|
|
15303
|
+
* Materialize a commit into patch content exactly as detection does.
|
|
15304
|
+
*
|
|
15305
|
+
* Pipeline: `diff-tree --name-only` → filter infrastructure / sensitive /
|
|
15306
|
+
* `.fernignore`d files → scoped `git diff` when files were stripped,
|
|
15307
|
+
* otherwise full `git format-patch` → content hash.
|
|
15308
|
+
*
|
|
15309
|
+
* This is THE definition of a commit's re-emission identity: the content
|
|
15310
|
+
* hash this method returns is what `detectPatchesInRange` will compute if
|
|
15311
|
+
* the commit re-enters the scan range (e.g. after a regen PR is merged
|
|
15312
|
+
* with a merge commit). Callers that tombstone a dismissed patch must use
|
|
15313
|
+
* this hash — the stored `content_hash` drifts every time the patch is
|
|
15314
|
+
* carried forward, so it does not identify the commit on re-detection.
|
|
15315
|
+
*
|
|
15316
|
+
* Never throws; failures are reported through the `status` discriminant.
|
|
15317
|
+
*/
|
|
15318
|
+
async materializeCommitPatch(sha) {
|
|
15319
|
+
let parents;
|
|
15320
|
+
let filesOutput;
|
|
15321
|
+
try {
|
|
15322
|
+
parents = await this.git.getCommitParents(sha);
|
|
15323
|
+
filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", sha]);
|
|
15324
|
+
} catch {
|
|
15325
|
+
return { status: "unreachable", sensitiveFiles: [], fernignoredFiles: [] };
|
|
15326
|
+
}
|
|
15327
|
+
const { files, sensitiveFiles, fernignoredFiles, infrastructureFiles } = filterInfrastructureAndSensitiveFiles(filesOutput, this.fernignorePatterns);
|
|
15328
|
+
if (files.length === 0) {
|
|
15329
|
+
return { status: "empty", sensitiveFiles, fernignoredFiles };
|
|
15330
|
+
}
|
|
15331
|
+
const wasFiltered = sensitiveFiles.length > 0 || fernignoredFiles.length > 0 || infrastructureFiles.length > 0;
|
|
15332
|
+
const parentSha = parents[0];
|
|
15333
|
+
let patchContent;
|
|
15334
|
+
if (wasFiltered && parentSha) {
|
|
15335
|
+
try {
|
|
15336
|
+
patchContent = await this.git.exec(["diff", parentSha, sha, "--", ...files]);
|
|
15337
|
+
} catch {
|
|
15338
|
+
return { status: "empty", sensitiveFiles, fernignoredFiles };
|
|
15339
|
+
}
|
|
15340
|
+
if (!patchContent.trim()) {
|
|
15341
|
+
return { status: "empty", sensitiveFiles, fernignoredFiles };
|
|
15342
|
+
}
|
|
15343
|
+
} else {
|
|
15344
|
+
try {
|
|
15345
|
+
patchContent = await this.git.formatPatch(sha);
|
|
15346
|
+
} catch {
|
|
15347
|
+
return { status: "unreachable", sensitiveFiles, fernignoredFiles };
|
|
15348
|
+
}
|
|
15349
|
+
}
|
|
15350
|
+
return {
|
|
15351
|
+
status: "ok",
|
|
15352
|
+
files,
|
|
15353
|
+
sensitiveFiles,
|
|
15354
|
+
fernignoredFiles,
|
|
15355
|
+
patchContent,
|
|
15356
|
+
contentHash: this.computeContentHash(patchContent)
|
|
15357
|
+
};
|
|
15358
|
+
}
|
|
15292
15359
|
/**
|
|
15293
15360
|
* Compute content hash for deduplication.
|
|
15294
15361
|
*
|
|
@@ -15393,6 +15460,13 @@ var ReplayDetector = class {
|
|
|
15393
15460
|
async detectPatchesViaTreeDiff(lastGen, commitKnownMissing) {
|
|
15394
15461
|
const diffBase = await this.resolveDiffBase(lastGen, commitKnownMissing);
|
|
15395
15462
|
if (!diffBase) {
|
|
15463
|
+
this.warnings.push(
|
|
15464
|
+
`Neither the previous generation's commit (${lastGen.commit_sha.slice(0, 7)}) nor its recorded tree was reachable during this run. Falling back to a bounded commit scan, which cannot see customizations older than the clone window. Widen the clone window (e.g. \`git fetch --unshallow\`) and re-run replay to verify customizations were carried forward.`
|
|
15465
|
+
);
|
|
15466
|
+
this.degradedReasons.push({
|
|
15467
|
+
code: "generation-tree-unreachable",
|
|
15468
|
+
message: `The previous generation (${lastGen.commit_sha.slice(0, 7)}) was not reachable during this run \u2014 neither its commit nor its recorded tree. Customizations may not have been carried forward \u2014 review the result before merging, or widen the clone window and re-run replay.`
|
|
15469
|
+
});
|
|
15396
15470
|
return this.detectPatchesViaCommitScan();
|
|
15397
15471
|
}
|
|
15398
15472
|
const lockForGuard = this.lockManager.read();
|
|
@@ -15505,10 +15579,7 @@ var ReplayDetector = class {
|
|
|
15505
15579
|
continue;
|
|
15506
15580
|
}
|
|
15507
15581
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
15508
|
-
const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
|
|
15509
|
-
filesOutput,
|
|
15510
|
-
this.fernignorePatterns
|
|
15511
|
-
);
|
|
15582
|
+
const { files, sensitiveFiles, fernignoredFiles, infrastructureFiles } = filterInfrastructureAndSensitiveFiles(filesOutput, this.fernignorePatterns);
|
|
15512
15583
|
if (sensitiveFiles.length > 0) {
|
|
15513
15584
|
this.warnings.push(
|
|
15514
15585
|
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
@@ -15519,7 +15590,8 @@ var ReplayDetector = class {
|
|
|
15519
15590
|
`.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
|
|
15520
15591
|
);
|
|
15521
15592
|
}
|
|
15522
|
-
|
|
15593
|
+
const wasFiltered = sensitiveFiles.length > 0 || fernignoredFiles.length > 0 || infrastructureFiles.length > 0;
|
|
15594
|
+
if (wasFiltered && files.length > 0) {
|
|
15523
15595
|
if (parents.length === 0) {
|
|
15524
15596
|
continue;
|
|
15525
15597
|
}
|
|
@@ -15623,9 +15695,9 @@ var ReplayDetector = class {
|
|
|
15623
15695
|
};
|
|
15624
15696
|
|
|
15625
15697
|
// src/ReplayService.ts
|
|
15626
|
-
var
|
|
15698
|
+
var import_node_fs3 = require("fs");
|
|
15627
15699
|
var import_promises6 = require("fs/promises");
|
|
15628
|
-
var
|
|
15700
|
+
var import_node_path10 = require("path");
|
|
15629
15701
|
init_GitClient();
|
|
15630
15702
|
|
|
15631
15703
|
// src/ReplayApplicator.ts
|
|
@@ -16935,6 +17007,15 @@ function isDiffLineForFile(diffLine, filePath) {
|
|
|
16935
17007
|
return match2 !== null && match2[1] === filePath;
|
|
16936
17008
|
}
|
|
16937
17009
|
|
|
17010
|
+
// src/shared/fernignore.ts
|
|
17011
|
+
var import_node_fs2 = require("fs");
|
|
17012
|
+
var import_node_path7 = require("path");
|
|
17013
|
+
function readFernignorePatterns(outputDir) {
|
|
17014
|
+
const fernignorePath = (0, import_node_path7.join)(outputDir, ".fernignore");
|
|
17015
|
+
if (!(0, import_node_fs2.existsSync)(fernignorePath)) return [];
|
|
17016
|
+
return (0, import_node_fs2.readFileSync)(fernignorePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
17017
|
+
}
|
|
17018
|
+
|
|
16938
17019
|
// src/ReplayCommitter.ts
|
|
16939
17020
|
var ReplayCommitter = class {
|
|
16940
17021
|
git;
|
|
@@ -17040,7 +17121,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
17040
17121
|
// src/PatchRegionDiff.ts
|
|
17041
17122
|
var import_promises4 = require("fs/promises");
|
|
17042
17123
|
var import_node_os2 = require("os");
|
|
17043
|
-
var
|
|
17124
|
+
var import_node_path8 = require("path");
|
|
17044
17125
|
var import_node_child_process = require("child_process");
|
|
17045
17126
|
init_HybridReconstruction();
|
|
17046
17127
|
function normalizeHunkBody(hunk) {
|
|
@@ -17258,10 +17339,10 @@ function overlayRegions(currentGenLines, locInCurrentGen, workingTreeLines, locI
|
|
|
17258
17339
|
}
|
|
17259
17340
|
async function unifiedDiff(beforeContent, afterContent, filePath) {
|
|
17260
17341
|
if (beforeContent === afterContent) return "";
|
|
17261
|
-
const tmp = await (0, import_promises4.mkdtemp)((0,
|
|
17342
|
+
const tmp = await (0, import_promises4.mkdtemp)((0, import_node_path8.join)((0, import_node_os2.tmpdir)(), "fern-replay-pp-"));
|
|
17262
17343
|
try {
|
|
17263
|
-
const beforePath = (0,
|
|
17264
|
-
const afterPath = (0,
|
|
17344
|
+
const beforePath = (0, import_node_path8.join)(tmp, "before");
|
|
17345
|
+
const afterPath = (0, import_node_path8.join)(tmp, "after");
|
|
17265
17346
|
await (0, import_promises4.writeFile)(beforePath, beforeContent, "utf-8");
|
|
17266
17347
|
await (0, import_promises4.writeFile)(afterPath, afterContent, "utf-8");
|
|
17267
17348
|
const raw = await runGitDiffNoIndex(beforePath, afterPath);
|
|
@@ -17436,6 +17517,7 @@ var FirstGenerationFlow = class {
|
|
|
17436
17517
|
patchesToApply: [],
|
|
17437
17518
|
newPatchIds: /* @__PURE__ */ new Set(),
|
|
17438
17519
|
detectorWarnings: [],
|
|
17520
|
+
detectorDegraded: [],
|
|
17439
17521
|
revertedCount: 0,
|
|
17440
17522
|
genSha: genRecord.commit_sha,
|
|
17441
17523
|
optionsSnapshot: options
|
|
@@ -17495,6 +17577,7 @@ var SkipApplicationFlow = class {
|
|
|
17495
17577
|
patchesToApply: [],
|
|
17496
17578
|
newPatchIds: /* @__PURE__ */ new Set(),
|
|
17497
17579
|
detectorWarnings: [],
|
|
17580
|
+
detectorDegraded: [],
|
|
17498
17581
|
revertedCount: 0,
|
|
17499
17582
|
genSha: genRecord.commit_sha,
|
|
17500
17583
|
optionsSnapshot: options
|
|
@@ -17532,8 +17615,10 @@ var NoPatchesFlow = class {
|
|
|
17532
17615
|
const previousGenerationSha = preLock.current_generation ?? null;
|
|
17533
17616
|
let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
|
|
17534
17617
|
const detectorWarnings = [...this.service.detector.warnings];
|
|
17618
|
+
const detectorDegraded = [...this.service.detector.degradedReasons];
|
|
17535
17619
|
if (options?.dryRun) {
|
|
17536
17620
|
const dryRunWarnings = [...detectorWarnings, ...this.service.warnings];
|
|
17621
|
+
const dryRunDegraded = dedupeDegradedReasons([...detectorDegraded, ...this.service.degradedReasons]);
|
|
17537
17622
|
const preparedReport = {
|
|
17538
17623
|
flow: "no-patches",
|
|
17539
17624
|
patchesDetected: newPatches.length,
|
|
@@ -17543,7 +17628,9 @@ var NoPatchesFlow = class {
|
|
|
17543
17628
|
patchesReverted: revertedPatchIds.length,
|
|
17544
17629
|
conflicts: [],
|
|
17545
17630
|
wouldApply: newPatches,
|
|
17546
|
-
warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0
|
|
17631
|
+
warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0,
|
|
17632
|
+
degraded: dryRunDegraded.length > 0 ? true : void 0,
|
|
17633
|
+
degradedReasons: dryRunDegraded.length > 0 ? dryRunDegraded : void 0
|
|
17547
17634
|
};
|
|
17548
17635
|
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s) => s.trim()).catch(() => "");
|
|
17549
17636
|
return {
|
|
@@ -17597,6 +17684,7 @@ var NoPatchesFlow = class {
|
|
|
17597
17684
|
patchesToApply: newPatches,
|
|
17598
17685
|
newPatchIds: new Set(newPatches.map((p) => p.id)),
|
|
17599
17686
|
detectorWarnings,
|
|
17687
|
+
detectorDegraded,
|
|
17600
17688
|
revertedCount: revertedPatchIds.length,
|
|
17601
17689
|
genSha: genRecord.commit_sha,
|
|
17602
17690
|
optionsSnapshot: options
|
|
@@ -17640,6 +17728,7 @@ var NoPatchesFlow = class {
|
|
|
17640
17728
|
}
|
|
17641
17729
|
}
|
|
17642
17730
|
const warnings = [...detectorWarnings, ...this.service.warnings];
|
|
17731
|
+
const degradedReasons = [...prep._prepared.detectorDegraded, ...this.service.degradedReasons];
|
|
17643
17732
|
return this.service.buildReport(
|
|
17644
17733
|
"no-patches",
|
|
17645
17734
|
newPatches,
|
|
@@ -17648,7 +17737,8 @@ var NoPatchesFlow = class {
|
|
|
17648
17737
|
warnings,
|
|
17649
17738
|
rebaseCounts,
|
|
17650
17739
|
void 0,
|
|
17651
|
-
prep._prepared.revertedCount
|
|
17740
|
+
prep._prepared.revertedCount,
|
|
17741
|
+
degradedReasons
|
|
17652
17742
|
);
|
|
17653
17743
|
}
|
|
17654
17744
|
};
|
|
@@ -17665,6 +17755,10 @@ var NormalRegenerationFlow = class {
|
|
|
17665
17755
|
const existingPatches2 = this.service.lockManager.getPatches();
|
|
17666
17756
|
const { patches: newPatches2, revertedPatchIds: dryRunReverted } = await this.service.detector.detectNewPatches();
|
|
17667
17757
|
const warnings = [...this.service.detector.warnings, ...this.service.warnings];
|
|
17758
|
+
const dryRunDegraded = dedupeDegradedReasons([
|
|
17759
|
+
...this.service.detector.degradedReasons,
|
|
17760
|
+
...this.service.degradedReasons
|
|
17761
|
+
]);
|
|
17668
17762
|
const allPatches2 = [...existingPatches2, ...newPatches2];
|
|
17669
17763
|
const preparedReport = {
|
|
17670
17764
|
flow: "normal-regeneration",
|
|
@@ -17675,7 +17769,9 @@ var NormalRegenerationFlow = class {
|
|
|
17675
17769
|
patchesReverted: dryRunReverted.length,
|
|
17676
17770
|
conflicts: [],
|
|
17677
17771
|
wouldApply: allPatches2,
|
|
17678
|
-
warnings: warnings.length > 0 ? warnings : void 0
|
|
17772
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17773
|
+
degraded: dryRunDegraded.length > 0 ? true : void 0,
|
|
17774
|
+
degradedReasons: dryRunDegraded.length > 0 ? dryRunDegraded : void 0
|
|
17679
17775
|
};
|
|
17680
17776
|
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s) => s.trim()).catch(() => "");
|
|
17681
17777
|
return {
|
|
@@ -17714,6 +17810,7 @@ var NormalRegenerationFlow = class {
|
|
|
17714
17810
|
existingPatches = this.service.lockManager.getPatches();
|
|
17715
17811
|
let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
|
|
17716
17812
|
const detectorWarnings = [...this.service.detector.warnings];
|
|
17813
|
+
const detectorDegraded = [...this.service.detector.degradedReasons];
|
|
17717
17814
|
if (removedByPreRebase.length > 0) {
|
|
17718
17815
|
const removedOriginalCommits = new Set(removedByPreRebase.map((p) => p.original_commit));
|
|
17719
17816
|
const removedOriginalMessages = new Set(removedByPreRebase.map((p) => p.original_message));
|
|
@@ -17754,6 +17851,7 @@ var NormalRegenerationFlow = class {
|
|
|
17754
17851
|
newPatchIds: new Set(newPatches.map((p) => p.id)),
|
|
17755
17852
|
preRebaseCounts,
|
|
17756
17853
|
detectorWarnings,
|
|
17854
|
+
detectorDegraded,
|
|
17757
17855
|
revertedCount: revertedPatchIds.length,
|
|
17758
17856
|
genSha: genRecord.commit_sha,
|
|
17759
17857
|
optionsSnapshot: options
|
|
@@ -17802,6 +17900,7 @@ var NormalRegenerationFlow = class {
|
|
|
17802
17900
|
await this.service.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
|
|
17803
17901
|
}
|
|
17804
17902
|
const warnings = [...detectorWarnings, ...this.service.warnings];
|
|
17903
|
+
const degradedReasons = [...prep._prepared.detectorDegraded, ...this.service.degradedReasons];
|
|
17805
17904
|
return this.service.buildReport(
|
|
17806
17905
|
"normal-regeneration",
|
|
17807
17906
|
allPatches,
|
|
@@ -17810,7 +17909,8 @@ var NormalRegenerationFlow = class {
|
|
|
17810
17909
|
warnings,
|
|
17811
17910
|
rebaseCounts,
|
|
17812
17911
|
prep._prepared.preRebaseCounts,
|
|
17813
|
-
prep._prepared.revertedCount
|
|
17912
|
+
prep._prepared.revertedCount,
|
|
17913
|
+
degradedReasons
|
|
17814
17914
|
);
|
|
17815
17915
|
}
|
|
17816
17916
|
};
|
|
@@ -17850,6 +17950,16 @@ var ReplayService = class {
|
|
|
17850
17950
|
* @internal Used by Flow implementations in `src/flows/`.
|
|
17851
17951
|
*/
|
|
17852
17952
|
warnings = [];
|
|
17953
|
+
/**
|
|
17954
|
+
* Service-level structured degraded reasons, mirroring `warnings`.
|
|
17955
|
+
* Populated at apply time (e.g. `recordUnreachableBaseWarnings` when a
|
|
17956
|
+
* patch's base-generation tree is unreachable). Merged with
|
|
17957
|
+
* `detector.degradedReasons` into `ReplayReport.degradedReasons` by each
|
|
17958
|
+
* flow handler. Cleared at the top of `prepareReplay()`.
|
|
17959
|
+
*
|
|
17960
|
+
* @internal Used by Flow implementations in `src/flows/`.
|
|
17961
|
+
*/
|
|
17962
|
+
degradedReasons = [];
|
|
17853
17963
|
/**
|
|
17854
17964
|
* @internal Test-only accessor.
|
|
17855
17965
|
* Returns the ReplayApplicator instance used for the last run. Tests use this
|
|
@@ -17893,7 +18003,10 @@ var ReplayService = class {
|
|
|
17893
18003
|
async prepareReplay(options) {
|
|
17894
18004
|
this.warnings = [];
|
|
17895
18005
|
this.detector.warnings.length = 0;
|
|
18006
|
+
this.degradedReasons = [];
|
|
18007
|
+
this.detector.degradedReasons.length = 0;
|
|
17896
18008
|
this.cleanseLegacyFernignoreEntries();
|
|
18009
|
+
this.cleanseInfrastructureFileEntries();
|
|
17897
18010
|
return this.selectFlow(options).prepare(options);
|
|
17898
18011
|
}
|
|
17899
18012
|
/**
|
|
@@ -17947,6 +18060,50 @@ var ReplayService = class {
|
|
|
17947
18060
|
this.lockManager.save();
|
|
17948
18061
|
}
|
|
17949
18062
|
}
|
|
18063
|
+
/**
|
|
18064
|
+
* Migration step: strips infrastructure file diffs (`.fernignore`, `.fern/*`)
|
|
18065
|
+
* from existing patches' `patch_content`. These diffs were bundled by older
|
|
18066
|
+
* detection logic that did not scope `git diff` when only infrastructure files
|
|
18067
|
+
* were stripped. Patches that end up with no surviving diff sections are
|
|
18068
|
+
* removed entirely.
|
|
18069
|
+
*
|
|
18070
|
+
* Idempotent: a second run is a no-op because the first run already removed
|
|
18071
|
+
* all infrastructure sections. Per-patch try/catch ensures isolation.
|
|
18072
|
+
*/
|
|
18073
|
+
cleanseInfrastructureFileEntries() {
|
|
18074
|
+
if (!this.lockManager.exists()) return;
|
|
18075
|
+
this.lockManager.read();
|
|
18076
|
+
let lockfileMutated = false;
|
|
18077
|
+
const patches = this.lockManager.getPatches();
|
|
18078
|
+
for (const patch of patches) {
|
|
18079
|
+
try {
|
|
18080
|
+
const result = computeInfrastructureFileCleanse(
|
|
18081
|
+
patch,
|
|
18082
|
+
(content) => this.detector.computeContentHash(content)
|
|
18083
|
+
);
|
|
18084
|
+
if (result.unchanged) continue;
|
|
18085
|
+
if (result.remove) {
|
|
18086
|
+
this.lockManager.removePatch(patch.id);
|
|
18087
|
+
this.warnings.push(
|
|
18088
|
+
`Stripped infrastructure file diffs from patch ${patch.id}: patch contained only infrastructure file changes (${result.strippedPaths.join(", ")}) \u2014 removed.`
|
|
18089
|
+
);
|
|
18090
|
+
} else {
|
|
18091
|
+
this.lockManager.updatePatch(patch.id, {
|
|
18092
|
+
patch_content: result.patch_content,
|
|
18093
|
+
content_hash: result.content_hash
|
|
18094
|
+
});
|
|
18095
|
+
this.warnings.push(
|
|
18096
|
+
`Stripped infrastructure file diffs from patch ${patch.id}: removed bundled diffs for ${result.strippedPaths.join(", ")}.`
|
|
18097
|
+
);
|
|
18098
|
+
}
|
|
18099
|
+
lockfileMutated = true;
|
|
18100
|
+
} catch {
|
|
18101
|
+
}
|
|
18102
|
+
}
|
|
18103
|
+
if (lockfileMutated) {
|
|
18104
|
+
this.lockManager.save();
|
|
18105
|
+
}
|
|
18106
|
+
}
|
|
17950
18107
|
/**
|
|
17951
18108
|
* Phase 2 of the two-phase replay flow. Applies patches, rebases them against
|
|
17952
18109
|
* the generation, saves the lockfile, and commits `[fern-replay]` (or stages
|
|
@@ -18202,7 +18359,7 @@ var ReplayService = class {
|
|
|
18202
18359
|
for (const file of files) {
|
|
18203
18360
|
if (isBinaryFile(file)) continue;
|
|
18204
18361
|
try {
|
|
18205
|
-
const content = await (0, import_promises6.readFile)((0,
|
|
18362
|
+
const content = await (0, import_promises6.readFile)((0, import_node_path10.join)(this.outputDir, file), "utf-8");
|
|
18206
18363
|
snapshot[file] = content;
|
|
18207
18364
|
} catch {
|
|
18208
18365
|
}
|
|
@@ -18719,6 +18876,12 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
18719
18876
|
);
|
|
18720
18877
|
}
|
|
18721
18878
|
}
|
|
18879
|
+
if (warned.size > 0) {
|
|
18880
|
+
this.degradedReasons.push({
|
|
18881
|
+
code: "patch-base-unreachable",
|
|
18882
|
+
message: `${warned.size} customization file(s) could not be checked against the generation they were made on because that generation's tree was not reachable during this run. Customizations may not have been carried forward \u2014 review the result before merging, or widen the clone window and re-run replay.`
|
|
18883
|
+
});
|
|
18884
|
+
}
|
|
18722
18885
|
}
|
|
18723
18886
|
/**
|
|
18724
18887
|
* After applyPatches(), strip conflict markers from conflicting files
|
|
@@ -18730,11 +18893,11 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
18730
18893
|
if (result.status !== "conflict" || !result.fileResults) continue;
|
|
18731
18894
|
for (const fileResult of result.fileResults) {
|
|
18732
18895
|
if (fileResult.status !== "conflict") continue;
|
|
18733
|
-
const filePath = (0,
|
|
18896
|
+
const filePath = (0, import_node_path10.join)(this.outputDir, fileResult.file);
|
|
18734
18897
|
try {
|
|
18735
|
-
const content = (0,
|
|
18898
|
+
const content = (0, import_node_fs3.readFileSync)(filePath, "utf-8");
|
|
18736
18899
|
const stripped = stripConflictMarkers(content);
|
|
18737
|
-
(0,
|
|
18900
|
+
(0, import_node_fs3.writeFileSync)(filePath, stripped);
|
|
18738
18901
|
} catch {
|
|
18739
18902
|
}
|
|
18740
18903
|
}
|
|
@@ -18762,12 +18925,11 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
18762
18925
|
}
|
|
18763
18926
|
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
18764
18927
|
readFernignorePatterns() {
|
|
18765
|
-
|
|
18766
|
-
if (!(0, import_node_fs2.existsSync)(fernignorePath)) return [];
|
|
18767
|
-
return (0, import_node_fs2.readFileSync)(fernignorePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
18928
|
+
return readFernignorePatterns(this.outputDir);
|
|
18768
18929
|
}
|
|
18769
18930
|
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
18770
|
-
buildReport(flow, patches, results, options, warnings, rebaseCounts, preRebaseCounts, patchesReverted) {
|
|
18931
|
+
buildReport(flow, patches, results, options, warnings, rebaseCounts, preRebaseCounts, patchesReverted, degradedReasons) {
|
|
18932
|
+
const degraded = dedupeDegradedReasons(degradedReasons ?? []);
|
|
18771
18933
|
const conflictResults = results.filter((r) => r.status === "conflict");
|
|
18772
18934
|
const conflictDetails = conflictResults.map((r) => {
|
|
18773
18935
|
const conflictFiles = r.fileResults?.filter((f) => f.status === "conflict") ?? [];
|
|
@@ -18807,10 +18969,23 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
18807
18969
|
conflictDetails: r.fileResults?.filter((f) => f.status === "conflict") ?? []
|
|
18808
18970
|
})) : void 0,
|
|
18809
18971
|
wouldApply: options?.dryRun ? patches : void 0,
|
|
18810
|
-
warnings: warnings && warnings.length > 0 ? warnings : void 0
|
|
18972
|
+
warnings: warnings && warnings.length > 0 ? warnings : void 0,
|
|
18973
|
+
degraded: degraded.length > 0 ? true : void 0,
|
|
18974
|
+
degradedReasons: degraded.length > 0 ? degraded : void 0
|
|
18811
18975
|
};
|
|
18812
18976
|
}
|
|
18813
18977
|
};
|
|
18978
|
+
function dedupeDegradedReasons(reasons) {
|
|
18979
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18980
|
+
const out = [];
|
|
18981
|
+
for (const reason of reasons) {
|
|
18982
|
+
const key = `${reason.code}\0${reason.message}`;
|
|
18983
|
+
if (seen.has(key)) continue;
|
|
18984
|
+
seen.add(key);
|
|
18985
|
+
out.push(reason);
|
|
18986
|
+
}
|
|
18987
|
+
return out;
|
|
18988
|
+
}
|
|
18814
18989
|
function computeCommitBuckets(results, absorbedPatchIds, patchesPassedToCommit) {
|
|
18815
18990
|
const resultByPatchId = /* @__PURE__ */ new Map();
|
|
18816
18991
|
for (const r of results) resultByPatchId.set(r.patch.id, r);
|
|
@@ -18859,7 +19034,7 @@ function computeLegacyFernignoreCleanse(patch, fernignorePatterns, computeConten
|
|
|
18859
19034
|
}
|
|
18860
19035
|
}
|
|
18861
19036
|
}
|
|
18862
|
-
const { stripped: patchContentStripped, content: newPatchContent, strippedSectionPaths } =
|
|
19037
|
+
const { stripped: patchContentStripped, content: newPatchContent, strippedSectionPaths } = stripDiffSectionsByPredicate(patch.patch_content, matchesFernignore);
|
|
18863
19038
|
const unchanged = strippedFromFiles.length === 0 && !snapshotChanged && !patchContentStripped;
|
|
18864
19039
|
if (unchanged) {
|
|
18865
19040
|
return {
|
|
@@ -18894,7 +19069,38 @@ function computeLegacyFernignoreCleanse(patch, fernignorePatterns, computeConten
|
|
|
18894
19069
|
strippedPaths
|
|
18895
19070
|
};
|
|
18896
19071
|
}
|
|
18897
|
-
function
|
|
19072
|
+
function isInfrastructureFile(filePath) {
|
|
19073
|
+
return filePath === ".fernignore" || filePath.startsWith(".fern/");
|
|
19074
|
+
}
|
|
19075
|
+
function computeInfrastructureFileCleanse(patch, computeContentHash2) {
|
|
19076
|
+
const { stripped, content: newPatchContent, strippedSectionPaths } = stripDiffSectionsByPredicate(patch.patch_content, isInfrastructureFile);
|
|
19077
|
+
if (!stripped) {
|
|
19078
|
+
return {
|
|
19079
|
+
unchanged: true,
|
|
19080
|
+
remove: false,
|
|
19081
|
+
patch_content: patch.patch_content,
|
|
19082
|
+
content_hash: patch.content_hash,
|
|
19083
|
+
strippedPaths: []
|
|
19084
|
+
};
|
|
19085
|
+
}
|
|
19086
|
+
if (newPatchContent.trim() === "" || patch.files.length === 0) {
|
|
19087
|
+
return {
|
|
19088
|
+
unchanged: false,
|
|
19089
|
+
remove: true,
|
|
19090
|
+
patch_content: "",
|
|
19091
|
+
content_hash: "",
|
|
19092
|
+
strippedPaths: strippedSectionPaths
|
|
19093
|
+
};
|
|
19094
|
+
}
|
|
19095
|
+
return {
|
|
19096
|
+
unchanged: false,
|
|
19097
|
+
remove: false,
|
|
19098
|
+
patch_content: newPatchContent,
|
|
19099
|
+
content_hash: computeContentHash2(newPatchContent),
|
|
19100
|
+
strippedPaths: strippedSectionPaths
|
|
19101
|
+
};
|
|
19102
|
+
}
|
|
19103
|
+
function stripDiffSectionsByPredicate(patchContent, shouldStrip) {
|
|
18898
19104
|
const lines = patchContent.split("\n");
|
|
18899
19105
|
const sectionStarts = [];
|
|
18900
19106
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -18916,7 +19122,7 @@ function stripFernignoreDiffSections(patchContent, matchesFernignore) {
|
|
|
18916
19122
|
const section = sectionStarts[i];
|
|
18917
19123
|
const next = sectionStarts[i + 1];
|
|
18918
19124
|
const endIdx = next ? next.idx : lines.length;
|
|
18919
|
-
if (section.path !== null &&
|
|
19125
|
+
if (section.path !== null && shouldStrip(section.path)) {
|
|
18920
19126
|
stripped = true;
|
|
18921
19127
|
strippedSectionPaths.push(section.path);
|
|
18922
19128
|
continue;
|
|
@@ -18928,8 +19134,8 @@ function stripFernignoreDiffSections(patchContent, matchesFernignore) {
|
|
|
18928
19134
|
|
|
18929
19135
|
// src/FernignoreMigrator.ts
|
|
18930
19136
|
var import_node_crypto2 = require("crypto");
|
|
18931
|
-
var
|
|
18932
|
-
var
|
|
19137
|
+
var import_node_fs4 = require("fs");
|
|
19138
|
+
var import_node_path11 = require("path");
|
|
18933
19139
|
var import_yaml2 = __toESM(require_dist3(), 1);
|
|
18934
19140
|
var FernignoreMigrator = class {
|
|
18935
19141
|
git;
|
|
@@ -18941,14 +19147,14 @@ var FernignoreMigrator = class {
|
|
|
18941
19147
|
this.outputDir = outputDir;
|
|
18942
19148
|
}
|
|
18943
19149
|
fernignoreExists() {
|
|
18944
|
-
return (0,
|
|
19150
|
+
return (0, import_node_fs4.existsSync)((0, import_node_path11.join)(this.outputDir, ".fernignore"));
|
|
18945
19151
|
}
|
|
18946
19152
|
readFernignorePatterns() {
|
|
18947
|
-
const fernignorePath = (0,
|
|
18948
|
-
if (!(0,
|
|
19153
|
+
const fernignorePath = (0, import_node_path11.join)(this.outputDir, ".fernignore");
|
|
19154
|
+
if (!(0, import_node_fs4.existsSync)(fernignorePath)) {
|
|
18949
19155
|
return [];
|
|
18950
19156
|
}
|
|
18951
|
-
const content = (0,
|
|
19157
|
+
const content = (0, import_node_fs4.readFileSync)(fernignorePath, "utf-8");
|
|
18952
19158
|
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
18953
19159
|
}
|
|
18954
19160
|
/** Analyze .fernignore patterns vs git history. Creates synthetic patches for files differing from pristine generation. */
|
|
@@ -19053,16 +19259,16 @@ var FernignoreMigrator = class {
|
|
|
19053
19259
|
return results;
|
|
19054
19260
|
}
|
|
19055
19261
|
readFileContent(filePath) {
|
|
19056
|
-
const fullPath = (0,
|
|
19057
|
-
if (!(0,
|
|
19262
|
+
const fullPath = (0, import_node_path11.join)(this.outputDir, filePath);
|
|
19263
|
+
if (!(0, import_node_fs4.existsSync)(fullPath)) {
|
|
19058
19264
|
return null;
|
|
19059
19265
|
}
|
|
19060
19266
|
try {
|
|
19061
|
-
const stat = (0,
|
|
19267
|
+
const stat = (0, import_node_fs4.statSync)(fullPath);
|
|
19062
19268
|
if (stat.isDirectory()) {
|
|
19063
19269
|
return null;
|
|
19064
19270
|
}
|
|
19065
|
-
return (0,
|
|
19271
|
+
return (0, import_node_fs4.readFileSync)(fullPath, "utf-8");
|
|
19066
19272
|
} catch {
|
|
19067
19273
|
return null;
|
|
19068
19274
|
}
|
|
@@ -19118,27 +19324,27 @@ var FernignoreMigrator = class {
|
|
|
19118
19324
|
};
|
|
19119
19325
|
}
|
|
19120
19326
|
movePatternsToReplayYml(patterns) {
|
|
19121
|
-
const replayYmlPath = (0,
|
|
19327
|
+
const replayYmlPath = (0, import_node_path11.join)(this.outputDir, ".fern", "replay.yml");
|
|
19122
19328
|
let config = {};
|
|
19123
|
-
if ((0,
|
|
19124
|
-
const content = (0,
|
|
19329
|
+
if ((0, import_node_fs4.existsSync)(replayYmlPath)) {
|
|
19330
|
+
const content = (0, import_node_fs4.readFileSync)(replayYmlPath, "utf-8");
|
|
19125
19331
|
config = (0, import_yaml2.parse)(content) ?? {};
|
|
19126
19332
|
}
|
|
19127
19333
|
const existing = config.exclude ?? [];
|
|
19128
19334
|
const merged = [.../* @__PURE__ */ new Set([...existing, ...patterns])];
|
|
19129
19335
|
config.exclude = merged;
|
|
19130
|
-
const dir = (0,
|
|
19131
|
-
if (!(0,
|
|
19132
|
-
(0,
|
|
19336
|
+
const dir = (0, import_node_path11.dirname)(replayYmlPath);
|
|
19337
|
+
if (!(0, import_node_fs4.existsSync)(dir)) {
|
|
19338
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
19133
19339
|
}
|
|
19134
|
-
(0,
|
|
19340
|
+
(0, import_node_fs4.writeFileSync)(replayYmlPath, (0, import_yaml2.stringify)(config, { lineWidth: 0 }), "utf-8");
|
|
19135
19341
|
}
|
|
19136
19342
|
};
|
|
19137
19343
|
|
|
19138
19344
|
// src/commands/bootstrap.ts
|
|
19139
19345
|
var import_node_crypto3 = require("crypto");
|
|
19140
|
-
var
|
|
19141
|
-
var
|
|
19346
|
+
var import_node_fs5 = require("fs");
|
|
19347
|
+
var import_node_path12 = require("path");
|
|
19142
19348
|
init_GitClient();
|
|
19143
19349
|
async function bootstrap(outputDir, options) {
|
|
19144
19350
|
const git = new GitClient(outputDir);
|
|
@@ -19305,10 +19511,10 @@ function parseGitLog(log) {
|
|
|
19305
19511
|
}
|
|
19306
19512
|
var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
|
|
19307
19513
|
function ensureFernignoreEntries(outputDir) {
|
|
19308
|
-
const fernignorePath = (0,
|
|
19514
|
+
const fernignorePath = (0, import_node_path12.join)(outputDir, ".fernignore");
|
|
19309
19515
|
let content = "";
|
|
19310
|
-
if ((0,
|
|
19311
|
-
content = (0,
|
|
19516
|
+
if ((0, import_node_fs5.existsSync)(fernignorePath)) {
|
|
19517
|
+
content = (0, import_node_fs5.readFileSync)(fernignorePath, "utf-8");
|
|
19312
19518
|
}
|
|
19313
19519
|
const lines = content.split("\n");
|
|
19314
19520
|
const toAdd = [];
|
|
@@ -19324,15 +19530,15 @@ function ensureFernignoreEntries(outputDir) {
|
|
|
19324
19530
|
content += "\n";
|
|
19325
19531
|
}
|
|
19326
19532
|
content += toAdd.join("\n") + "\n";
|
|
19327
|
-
(0,
|
|
19533
|
+
(0, import_node_fs5.writeFileSync)(fernignorePath, content, "utf-8");
|
|
19328
19534
|
return true;
|
|
19329
19535
|
}
|
|
19330
19536
|
var GITATTRIBUTES_ENTRIES = [".fern/replay.lock linguist-generated=true"];
|
|
19331
19537
|
function ensureGitattributesEntries(outputDir) {
|
|
19332
|
-
const gitattributesPath = (0,
|
|
19538
|
+
const gitattributesPath = (0, import_node_path12.join)(outputDir, ".gitattributes");
|
|
19333
19539
|
let content = "";
|
|
19334
|
-
if ((0,
|
|
19335
|
-
content = (0,
|
|
19540
|
+
if ((0, import_node_fs5.existsSync)(gitattributesPath)) {
|
|
19541
|
+
content = (0, import_node_fs5.readFileSync)(gitattributesPath, "utf-8");
|
|
19336
19542
|
}
|
|
19337
19543
|
const lines = content.split("\n");
|
|
19338
19544
|
const toAdd = [];
|
|
@@ -19348,7 +19554,7 @@ function ensureGitattributesEntries(outputDir) {
|
|
|
19348
19554
|
content += "\n";
|
|
19349
19555
|
}
|
|
19350
19556
|
content += toAdd.join("\n") + "\n";
|
|
19351
|
-
(0,
|
|
19557
|
+
(0, import_node_fs5.writeFileSync)(gitattributesPath, content, "utf-8");
|
|
19352
19558
|
}
|
|
19353
19559
|
function computeContentHash(patchContent) {
|
|
19354
19560
|
const lines = patchContent.split("\n");
|
|
@@ -19359,6 +19565,7 @@ function computeContentHash(patchContent) {
|
|
|
19359
19565
|
}
|
|
19360
19566
|
|
|
19361
19567
|
// src/commands/forget.ts
|
|
19568
|
+
init_GitClient();
|
|
19362
19569
|
function parseDiffStat(patchContent) {
|
|
19363
19570
|
let additions = 0;
|
|
19364
19571
|
let deletions = 0;
|
|
@@ -19417,7 +19624,28 @@ var EMPTY_RESULT = {
|
|
|
19417
19624
|
totalPatches: 0,
|
|
19418
19625
|
warnings: []
|
|
19419
19626
|
};
|
|
19420
|
-
function
|
|
19627
|
+
async function dismissPatches(outputDir, lockManager, patches) {
|
|
19628
|
+
const git = new GitClient(outputDir);
|
|
19629
|
+
const detector = new ReplayDetector(git, lockManager, outputDir, readFernignorePatterns(outputDir));
|
|
19630
|
+
for (const patch of patches) {
|
|
19631
|
+
const hashes = [patch.content_hash];
|
|
19632
|
+
if (patch.original_commit) {
|
|
19633
|
+
const materialized = await detector.materializeCommitPatch(patch.original_commit);
|
|
19634
|
+
if (materialized.status === "ok" && !hashes.includes(materialized.contentHash)) {
|
|
19635
|
+
hashes.push(materialized.contentHash);
|
|
19636
|
+
}
|
|
19637
|
+
}
|
|
19638
|
+
lockManager.addDismissal({
|
|
19639
|
+
patch_id: patch.id,
|
|
19640
|
+
original_commit: patch.original_commit,
|
|
19641
|
+
original_message: patch.original_message,
|
|
19642
|
+
content_hashes: hashes,
|
|
19643
|
+
dismissed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19644
|
+
via: "forget"
|
|
19645
|
+
});
|
|
19646
|
+
}
|
|
19647
|
+
}
|
|
19648
|
+
async function forget(outputDir, options) {
|
|
19421
19649
|
const lockManager = new LockfileManager(outputDir);
|
|
19422
19650
|
if (!lockManager.exists()) {
|
|
19423
19651
|
return { ...EMPTY_RESULT };
|
|
@@ -19428,9 +19656,7 @@ function forget(outputDir, options) {
|
|
|
19428
19656
|
const removed = lock.patches.map(toMatchedPatch);
|
|
19429
19657
|
const warnings = buildWarnings(lock.patches);
|
|
19430
19658
|
if (!options.dryRun) {
|
|
19431
|
-
|
|
19432
|
-
lockManager.addForgottenHash(patch.content_hash);
|
|
19433
|
-
}
|
|
19659
|
+
await dismissPatches(outputDir, lockManager, lock.patches);
|
|
19434
19660
|
lockManager.clearPatches();
|
|
19435
19661
|
lockManager.save();
|
|
19436
19662
|
}
|
|
@@ -19459,8 +19685,8 @@ function forget(outputDir, options) {
|
|
|
19459
19685
|
}
|
|
19460
19686
|
const warnings = buildWarnings(patchesToRemove);
|
|
19461
19687
|
if (!options.dryRun) {
|
|
19688
|
+
await dismissPatches(outputDir, lockManager, patchesToRemove);
|
|
19462
19689
|
for (const patch of patchesToRemove) {
|
|
19463
|
-
lockManager.addForgottenHash(patch.content_hash);
|
|
19464
19690
|
lockManager.removePatch(patch.id);
|
|
19465
19691
|
}
|
|
19466
19692
|
lockManager.save();
|
|
@@ -19501,7 +19727,7 @@ function forget(outputDir, options) {
|
|
|
19501
19727
|
}
|
|
19502
19728
|
|
|
19503
19729
|
// src/commands/reset.ts
|
|
19504
|
-
var
|
|
19730
|
+
var import_node_fs6 = require("fs");
|
|
19505
19731
|
function reset(outputDir, options) {
|
|
19506
19732
|
const lockManager = new LockfileManager(outputDir);
|
|
19507
19733
|
if (!lockManager.exists()) {
|
|
@@ -19515,7 +19741,7 @@ function reset(outputDir, options) {
|
|
|
19515
19741
|
const lock = lockManager.read();
|
|
19516
19742
|
const patchCount = lock.patches.length;
|
|
19517
19743
|
if (!options?.dryRun) {
|
|
19518
|
-
(0,
|
|
19744
|
+
(0, import_node_fs6.unlinkSync)(lockManager.lockfilePath);
|
|
19519
19745
|
}
|
|
19520
19746
|
return {
|
|
19521
19747
|
success: true,
|
|
@@ -19527,7 +19753,7 @@ function reset(outputDir, options) {
|
|
|
19527
19753
|
|
|
19528
19754
|
// src/commands/resolve.ts
|
|
19529
19755
|
var import_promises7 = require("fs/promises");
|
|
19530
|
-
var
|
|
19756
|
+
var import_node_path13 = require("path");
|
|
19531
19757
|
init_GitClient();
|
|
19532
19758
|
async function resolve(outputDir, options) {
|
|
19533
19759
|
const lockManager = new LockfileManager(outputDir);
|
|
@@ -19568,15 +19794,16 @@ async function resolve(outputDir, options) {
|
|
|
19568
19794
|
const patchesToCommit = [...resolvingPatches, ...unresolvedPatches];
|
|
19569
19795
|
if (patchesToCommit.length > 0) {
|
|
19570
19796
|
const currentGen = lock.current_generation;
|
|
19571
|
-
const detector = new ReplayDetector(git, lockManager, outputDir);
|
|
19797
|
+
const detector = new ReplayDetector(git, lockManager, outputDir, readFernignorePatterns(outputDir));
|
|
19572
19798
|
const warnings = [];
|
|
19799
|
+
const patchesDismissed = [];
|
|
19573
19800
|
let patchesResolved = 0;
|
|
19574
19801
|
const pass1 = [];
|
|
19575
19802
|
for (const patch of patchesToCommit) {
|
|
19576
19803
|
const result = await computePerPatchDiffWithFallback(
|
|
19577
19804
|
patch,
|
|
19578
19805
|
(f) => git.showFile(currentGen, f),
|
|
19579
|
-
(f) => (0, import_promises7.readFile)((0,
|
|
19806
|
+
(f) => (0, import_promises7.readFile)((0, import_node_path13.join)(outputDir, f), "utf-8").catch(() => null),
|
|
19580
19807
|
(files) => git.exec(["diff", currentGen, "--", ...files]).catch(() => null)
|
|
19581
19808
|
);
|
|
19582
19809
|
if (result === null) {
|
|
@@ -19610,7 +19837,27 @@ async function resolve(outputDir, options) {
|
|
|
19610
19837
|
const r = pass1[i];
|
|
19611
19838
|
if (r.kind === "skip") continue;
|
|
19612
19839
|
if (r.kind === "revert") {
|
|
19840
|
+
const hashes = [patch.content_hash];
|
|
19841
|
+
if (patch.original_commit) {
|
|
19842
|
+
const materialized = await detector.materializeCommitPatch(patch.original_commit);
|
|
19843
|
+
if (materialized.status === "ok" && !hashes.includes(materialized.contentHash)) {
|
|
19844
|
+
hashes.push(materialized.contentHash);
|
|
19845
|
+
} else if (materialized.status === "unreachable") {
|
|
19846
|
+
warnings.push(
|
|
19847
|
+
`Patch ${patch.id}: original commit ${patch.original_commit.slice(0, 7)} is unreachable; dismissed using the stored content hash only. If this commit reappears in history, the customization may be re-detected once.`
|
|
19848
|
+
);
|
|
19849
|
+
}
|
|
19850
|
+
}
|
|
19851
|
+
lockManager.addDismissal({
|
|
19852
|
+
patch_id: patch.id,
|
|
19853
|
+
original_commit: patch.original_commit,
|
|
19854
|
+
original_message: patch.original_message,
|
|
19855
|
+
content_hashes: hashes,
|
|
19856
|
+
dismissed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19857
|
+
via: "resolve"
|
|
19858
|
+
});
|
|
19613
19859
|
lockManager.removePatch(patch.id);
|
|
19860
|
+
patchesDismissed.push({ id: patch.id, message: patch.original_message });
|
|
19614
19861
|
continue;
|
|
19615
19862
|
}
|
|
19616
19863
|
if (lastIndexByHash.get(r.hash) !== i) {
|
|
@@ -19632,7 +19879,7 @@ async function resolve(outputDir, options) {
|
|
|
19632
19879
|
const theirsSnapshot = {};
|
|
19633
19880
|
for (const file of filesForSnapshot) {
|
|
19634
19881
|
if (isBinaryFile(file)) continue;
|
|
19635
|
-
const content = await (0, import_promises7.readFile)((0,
|
|
19882
|
+
const content = await (0, import_promises7.readFile)((0, import_node_path13.join)(outputDir, file), "utf-8").catch(() => null);
|
|
19636
19883
|
if (content != null) {
|
|
19637
19884
|
theirsSnapshot[file] = content;
|
|
19638
19885
|
}
|
|
@@ -19659,6 +19906,7 @@ async function resolve(outputDir, options) {
|
|
|
19659
19906
|
commitSha: commitSha2,
|
|
19660
19907
|
phase: "committed",
|
|
19661
19908
|
patchesResolved,
|
|
19909
|
+
patchesDismissed: patchesDismissed.length > 0 ? patchesDismissed : void 0,
|
|
19662
19910
|
warnings: warnings.length > 0 ? warnings : void 0
|
|
19663
19911
|
};
|
|
19664
19912
|
}
|
|
@@ -19706,7 +19954,9 @@ function status(outputDir) {
|
|
|
19706
19954
|
lastGeneration: void 0,
|
|
19707
19955
|
patches: [],
|
|
19708
19956
|
unresolvedCount: 0,
|
|
19709
|
-
excludePatterns: []
|
|
19957
|
+
excludePatterns: [],
|
|
19958
|
+
dismissed: [],
|
|
19959
|
+
legacyDismissedCount: 0
|
|
19710
19960
|
};
|
|
19711
19961
|
}
|
|
19712
19962
|
const lock = lockManager.read();
|
|
@@ -19735,13 +19985,24 @@ function status(outputDir) {
|
|
|
19735
19985
|
}
|
|
19736
19986
|
const config = lockManager.getCustomizationsConfig();
|
|
19737
19987
|
const excludePatterns = config.exclude ?? [];
|
|
19988
|
+
const dismissals = lock.dismissals ?? [];
|
|
19989
|
+
const dismissed = dismissals.map((d) => ({
|
|
19990
|
+
message: d.original_message,
|
|
19991
|
+
sha: d.original_commit.slice(0, 7),
|
|
19992
|
+
via: d.via,
|
|
19993
|
+
dismissedAt: d.dismissed_at
|
|
19994
|
+
}));
|
|
19995
|
+
const coveredHashes = new Set(dismissals.flatMap((d) => d.content_hashes));
|
|
19996
|
+
const legacyDismissedCount = (lock.forgotten_hashes ?? []).filter((h) => !coveredHashes.has(h)).length;
|
|
19738
19997
|
return {
|
|
19739
19998
|
initialized: true,
|
|
19740
19999
|
generationCount: lock.generations.length,
|
|
19741
20000
|
lastGeneration,
|
|
19742
20001
|
patches,
|
|
19743
20002
|
unresolvedCount,
|
|
19744
|
-
excludePatterns
|
|
20003
|
+
excludePatterns,
|
|
20004
|
+
dismissed,
|
|
20005
|
+
legacyDismissedCount
|
|
19745
20006
|
};
|
|
19746
20007
|
}
|
|
19747
20008
|
|
|
@@ -19829,7 +20090,7 @@ function parseArgs(argv) {
|
|
|
19829
20090
|
positionals.push(arg);
|
|
19830
20091
|
}
|
|
19831
20092
|
}
|
|
19832
|
-
return { command, dir: (0,
|
|
20093
|
+
return { command, dir: (0, import_node_path14.resolve)(dir), flags, positionals };
|
|
19833
20094
|
}
|
|
19834
20095
|
async function runBootstrap(dir, flags) {
|
|
19835
20096
|
const dryRun = !!flags.dryRun;
|
|
@@ -19907,6 +20168,7 @@ async function runStatus(dir, flags) {
|
|
|
19907
20168
|
}
|
|
19908
20169
|
if (result.patches.length === 0) {
|
|
19909
20170
|
console.log("\nNo customizations tracked. Edit SDK files and commit \u2014 Replay will detect them on next generation.");
|
|
20171
|
+
printDismissedSection(result);
|
|
19910
20172
|
return;
|
|
19911
20173
|
}
|
|
19912
20174
|
const unresolvedSuffix = result.unresolvedCount > 0 ? `, ${result.unresolvedCount} unresolved` : "";
|
|
@@ -19944,6 +20206,26 @@ ${prefix}${patch.id} [${patch.type}] "${patch.message}"`);
|
|
|
19944
20206
|
console.log(` ... and ${result.patches.length - maxDisplay} more`);
|
|
19945
20207
|
}
|
|
19946
20208
|
}
|
|
20209
|
+
printDismissedSection(result);
|
|
20210
|
+
}
|
|
20211
|
+
function printDismissedSection(result) {
|
|
20212
|
+
if (result.dismissed.length === 0 && result.legacyDismissedCount === 0) {
|
|
20213
|
+
return;
|
|
20214
|
+
}
|
|
20215
|
+
const total = result.dismissed.length + result.legacyDismissedCount;
|
|
20216
|
+
console.log(`
|
|
20217
|
+
Dismissed customizations: ${total}`);
|
|
20218
|
+
for (const d of result.dismissed) {
|
|
20219
|
+
const shaSuffix = d.sha ? ` (${d.sha})` : "";
|
|
20220
|
+
const date = d.dismissedAt ? d.dismissedAt.slice(0, 10) : "";
|
|
20221
|
+
const dateSuffix = date ? ` on ${date}` : "";
|
|
20222
|
+
console.log(` "${d.message}"${shaSuffix} \u2014 dismissed via ${d.via}${dateSuffix}`);
|
|
20223
|
+
}
|
|
20224
|
+
if (result.legacyDismissedCount > 0) {
|
|
20225
|
+
console.log(
|
|
20226
|
+
` ... and ${result.legacyDismissedCount} dismissed before this version \u2014 details unavailable`
|
|
20227
|
+
);
|
|
20228
|
+
}
|
|
19947
20229
|
}
|
|
19948
20230
|
async function runDetect(dir) {
|
|
19949
20231
|
const lockManager = new LockfileManager(dir);
|
|
@@ -19955,6 +20237,7 @@ async function runDetect(dir) {
|
|
|
19955
20237
|
const git = new GitClient(dir);
|
|
19956
20238
|
const detector = new ReplayDetector(git, lockManager, dir);
|
|
19957
20239
|
const { patches, revertedPatchIds } = await detector.detectNewPatches();
|
|
20240
|
+
printDegradedBanner(detector.degradedReasons);
|
|
19958
20241
|
if (detector.warnings.length > 0) {
|
|
19959
20242
|
for (const w of detector.warnings) console.log(`Warning: ${w}`);
|
|
19960
20243
|
console.log();
|
|
@@ -19985,7 +20268,20 @@ async function runReplay(dir, flags) {
|
|
|
19985
20268
|
const report = await service.runReplay({ dryRun });
|
|
19986
20269
|
printReport(report);
|
|
19987
20270
|
}
|
|
20271
|
+
function printDegradedBanner(reasons) {
|
|
20272
|
+
if (reasons.length === 0) return;
|
|
20273
|
+
console.log("============================================================");
|
|
20274
|
+
console.log(" REPLAY DEGRADED \u2014 customizations may not have been");
|
|
20275
|
+
console.log(" carried forward in this run. Review the result before");
|
|
20276
|
+
console.log(" merging.");
|
|
20277
|
+
console.log("============================================================");
|
|
20278
|
+
for (const reason of reasons) {
|
|
20279
|
+
console.log(` [${reason.code}] ${reason.message}`);
|
|
20280
|
+
}
|
|
20281
|
+
console.log();
|
|
20282
|
+
}
|
|
19988
20283
|
function printReport(report) {
|
|
20284
|
+
printDegradedBanner(report.degradedReasons ?? []);
|
|
19989
20285
|
console.log(`Flow: ${report.flow}`);
|
|
19990
20286
|
console.log(`Patches detected: ${report.patchesDetected}`);
|
|
19991
20287
|
console.log(`Patches applied: ${report.patchesApplied}`);
|
|
@@ -20153,7 +20449,7 @@ async function runForget(dir, flags, positionals) {
|
|
|
20153
20449
|
const pattern = !isPatchIdMode && positionals.length > 0 ? positionals[0] : void 0;
|
|
20154
20450
|
const patchIds = isPatchIdMode ? positionals : void 0;
|
|
20155
20451
|
if (all) {
|
|
20156
|
-
const preview = forget(dir, { all: true, dryRun: true });
|
|
20452
|
+
const preview = await forget(dir, { all: true, dryRun: true });
|
|
20157
20453
|
if (!preview.initialized) {
|
|
20158
20454
|
console.log("Replay is not initialized. Run 'fern replay init' to get started.");
|
|
20159
20455
|
process.exit(1);
|
|
@@ -20172,14 +20468,14 @@ async function runForget(dir, flags, positionals) {
|
|
|
20172
20468
|
return;
|
|
20173
20469
|
}
|
|
20174
20470
|
}
|
|
20175
|
-
const finalResult = dryRun ? preview : forget(dir, { all: true });
|
|
20471
|
+
const finalResult = dryRun ? preview : await forget(dir, { all: true });
|
|
20176
20472
|
printForgetWarnings(finalResult.warnings);
|
|
20177
20473
|
const verb2 = dryRun ? "Would remove" : "Removed";
|
|
20178
20474
|
console.log(`${verb2} ${finalResult.removed.length} patches. ${finalResult.remaining} patches remaining.`);
|
|
20179
20475
|
return;
|
|
20180
20476
|
}
|
|
20181
20477
|
if (isPatchIdMode && patchIds) {
|
|
20182
|
-
const result2 = forget(dir, { patchIds, dryRun });
|
|
20478
|
+
const result2 = await forget(dir, { patchIds, dryRun });
|
|
20183
20479
|
if (!result2.initialized) {
|
|
20184
20480
|
console.log("Replay is not initialized. Run 'fern replay init' to get started.");
|
|
20185
20481
|
process.exit(1);
|
|
@@ -20206,7 +20502,7 @@ async function runForget(dir, flags, positionals) {
|
|
|
20206
20502
|
console.log(`${result2.remaining} patches remaining.`);
|
|
20207
20503
|
return;
|
|
20208
20504
|
}
|
|
20209
|
-
const searchResult = pattern ? forget(dir, { pattern }) : forget(dir);
|
|
20505
|
+
const searchResult = pattern ? await forget(dir, { pattern }) : await forget(dir);
|
|
20210
20506
|
if (!searchResult.initialized) {
|
|
20211
20507
|
console.log("Replay is not initialized. Run 'fern replay init' to get started.");
|
|
20212
20508
|
process.exit(1);
|
|
@@ -20254,7 +20550,7 @@ This will remove ${selectedIds.length} patch(es). Affected files will be overwri
|
|
|
20254
20550
|
return;
|
|
20255
20551
|
}
|
|
20256
20552
|
}
|
|
20257
|
-
const result = forget(dir, { patchIds: selectedIds, dryRun });
|
|
20553
|
+
const result = await forget(dir, { patchIds: selectedIds, dryRun });
|
|
20258
20554
|
printForgetWarnings(result.warnings);
|
|
20259
20555
|
const verb = dryRun ? "Would remove" : "Removed";
|
|
20260
20556
|
console.log(`
|
|
@@ -20321,12 +20617,23 @@ Resolve the conflicts in your editor, then run 'fern-replay resolve ${dir}' agai
|
|
|
20321
20617
|
if (result.patchesResolved) {
|
|
20322
20618
|
console.log(`Resolution complete. Updated ${result.patchesResolved} patch(es) in lockfile.`);
|
|
20323
20619
|
}
|
|
20620
|
+
if (result.patchesDismissed && result.patchesDismissed.length > 0) {
|
|
20621
|
+
console.log(`Permanently dismissed ${result.patchesDismissed.length} customization(s) (you kept the generated code):`);
|
|
20622
|
+
for (const d of result.patchesDismissed) {
|
|
20623
|
+
console.log(` "${d.message}" (${d.id}) \u2014 will not return on future generations`);
|
|
20624
|
+
}
|
|
20625
|
+
}
|
|
20626
|
+
if (result.warnings) {
|
|
20627
|
+
for (const w of result.warnings) {
|
|
20628
|
+
console.log(`Warning: ${w}`);
|
|
20629
|
+
}
|
|
20630
|
+
}
|
|
20324
20631
|
console.log(`Created [fern-replay] commit: ${result.commitSha?.slice(0, 7)}`);
|
|
20325
20632
|
console.log("Push to update the PR.");
|
|
20326
20633
|
}
|
|
20327
20634
|
async function main() {
|
|
20328
20635
|
const { command, dir, flags, positionals } = parseArgs(process.argv);
|
|
20329
|
-
if (!(0,
|
|
20636
|
+
if (!(0, import_node_fs7.existsSync)(dir)) {
|
|
20330
20637
|
console.error(`Directory not found: ${dir}`);
|
|
20331
20638
|
process.exit(1);
|
|
20332
20639
|
}
|