@fern-api/fern-api-dev 4.75.0 → 4.76.0-2-g00f8bb5e19c
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/cli.cjs +720 -118
- package/package.json +1 -1
package/cli.cjs
CHANGED
|
@@ -345167,6 +345167,7 @@ var require_GithubStep = __commonJS({
|
|
|
345167
345167
|
exports2.GithubStep = void 0;
|
|
345168
345168
|
exports2.enrichPrBodyForAutomation = enrichPrBodyForAutomation;
|
|
345169
345169
|
exports2.shouldEnableAutomerge = shouldEnableAutomerge;
|
|
345170
|
+
exports2.shouldPushGenerationBaseTag = shouldPushGenerationBaseTag;
|
|
345170
345171
|
exports2.resolveBranchAction = resolveBranchAction;
|
|
345171
345172
|
var core_utils_1 = (init_lib4(), __toCommonJS(lib_exports));
|
|
345172
345173
|
var github_1 = (init_lib7(), __toCommonJS(lib_exports3));
|
|
@@ -345246,7 +345247,11 @@ var require_GithubStep = __commonJS({
|
|
|
345246
345247
|
});
|
|
345247
345248
|
switch (branchAction) {
|
|
345248
345249
|
case "replay-branch":
|
|
345249
|
-
|
|
345250
|
+
if (shouldPushGenerationBaseTag(replayResult)) {
|
|
345251
|
+
generationBaseSha = await (0, createReplayBranch_1.createReplayBranch)(repository, prBranch, this.config.commitMessage, replayConflictInfo, this.logger);
|
|
345252
|
+
} else {
|
|
345253
|
+
await repository.createBranchFromHead(prBranch);
|
|
345254
|
+
}
|
|
345250
345255
|
break;
|
|
345251
345256
|
case "create-from-head":
|
|
345252
345257
|
await repository.createBranchFromHead(prBranch);
|
|
@@ -345444,6 +345449,9 @@ _Fern Run ID: \`${config5.runId}\`_`;
|
|
|
345444
345449
|
function shouldEnableAutomerge(config5) {
|
|
345445
345450
|
return config5.automationMode === true && config5.autoMerge === true && config5.hasBreakingChanges !== true;
|
|
345446
345451
|
}
|
|
345452
|
+
function shouldPushGenerationBaseTag(replayResult) {
|
|
345453
|
+
return (replayResult?.patchesWithConflicts ?? 0) > 0;
|
|
345454
|
+
}
|
|
345447
345455
|
function resolveBranchAction({ automationMode, skipCommit, existingPR }) {
|
|
345448
345456
|
if (skipCommit) {
|
|
345449
345457
|
return "replay-branch";
|
|
@@ -350912,9 +350920,9 @@ var require_diff3 = __commonJS({
|
|
|
350912
350920
|
}
|
|
350913
350921
|
});
|
|
350914
350922
|
|
|
350915
|
-
// ../../../node_modules/.pnpm/@fern-api+replay@0.
|
|
350923
|
+
// ../../../node_modules/.pnpm/@fern-api+replay@0.11.0/node_modules/@fern-api/replay/dist/index.cjs
|
|
350916
350924
|
var require_dist14 = __commonJS({
|
|
350917
|
-
"../../../node_modules/.pnpm/@fern-api+replay@0.
|
|
350925
|
+
"../../../node_modules/.pnpm/@fern-api+replay@0.11.0/node_modules/@fern-api/replay/dist/index.cjs"(exports2, module4) {
|
|
350918
350926
|
"use strict";
|
|
350919
350927
|
var __defProp4 = Object.defineProperty;
|
|
350920
350928
|
var __getOwnPropDesc4 = Object.getOwnPropertyDescriptor;
|
|
@@ -351005,13 +351013,30 @@ var require_dist14 = __commonJS({
|
|
|
351005
351013
|
const output2 = await this.exec(["rev-parse", `${commitSha}^@`]);
|
|
351006
351014
|
return output2.trim().split("\n").filter(Boolean);
|
|
351007
351015
|
}
|
|
351016
|
+
/**
|
|
351017
|
+
* Detects renames and copies between two trees.
|
|
351018
|
+
*
|
|
351019
|
+
* Both `R` (rename) and `C` (copy) entries are returned as `{from, to}` pairs.
|
|
351020
|
+
* Copies are treated as rename-equivalent because the generator may produce the
|
|
351021
|
+
* new path while the old path remains in the tree (e.g., imperfect cleanup,
|
|
351022
|
+
* customer edits at the old path, or test helpers that only write new files).
|
|
351023
|
+
* Callers that care about "is the source still present" should verify via
|
|
351024
|
+
* `showFile(toTree, from)`.
|
|
351025
|
+
*/
|
|
351008
351026
|
async detectRenames(fromTree, toTree) {
|
|
351009
351027
|
try {
|
|
351010
|
-
const output2 = await this.exec([
|
|
351028
|
+
const output2 = await this.exec([
|
|
351029
|
+
"diff",
|
|
351030
|
+
"--find-renames",
|
|
351031
|
+
"--find-copies",
|
|
351032
|
+
"--name-status",
|
|
351033
|
+
fromTree,
|
|
351034
|
+
toTree
|
|
351035
|
+
]);
|
|
351011
351036
|
const renames = [];
|
|
351012
351037
|
for (const line of output2.trim().split("\n")) {
|
|
351013
351038
|
if (!line) continue;
|
|
351014
|
-
if (line.startsWith("R")) {
|
|
351039
|
+
if (line.startsWith("R") || line.startsWith("C")) {
|
|
351015
351040
|
const parts = line.split(" ");
|
|
351016
351041
|
if (parts.length >= 3) {
|
|
351017
351042
|
renames.push({ from: parts[1], to: parts[2] });
|
|
@@ -351305,6 +351330,7 @@ var require_dist14 = __commonJS({
|
|
|
351305
351330
|
});
|
|
351306
351331
|
var index_exports = {};
|
|
351307
351332
|
__export4(index_exports, {
|
|
351333
|
+
CREDENTIAL_PATTERNS: () => CREDENTIAL_PATTERNS,
|
|
351308
351334
|
FERN_BOT_EMAIL: () => FERN_BOT_EMAIL,
|
|
351309
351335
|
FERN_BOT_LOGIN: () => FERN_BOT_LOGIN,
|
|
351310
351336
|
FERN_BOT_NAME: () => FERN_BOT_NAME,
|
|
@@ -351316,19 +351342,53 @@ var require_dist14 = __commonJS({
|
|
|
351316
351342
|
ReplayCommitter: () => ReplayCommitter,
|
|
351317
351343
|
ReplayDetector: () => ReplayDetector,
|
|
351318
351344
|
ReplayService: () => ReplayService,
|
|
351345
|
+
SENSITIVE_FILE_PATTERNS: () => SENSITIVE_FILE_PATTERNS,
|
|
351319
351346
|
bootstrap: () => bootstrap,
|
|
351320
351347
|
forget: () => forget,
|
|
351321
351348
|
isGenerationCommit: () => isGenerationCommit,
|
|
351322
351349
|
isReplayCommit: () => isReplayCommit,
|
|
351323
351350
|
isRevertCommit: () => isRevertCommit,
|
|
351351
|
+
isSensitiveFile: () => isSensitiveFile,
|
|
351324
351352
|
parseRevertedMessage: () => parseRevertedMessage,
|
|
351325
351353
|
parseRevertedSha: () => parseRevertedSha,
|
|
351326
351354
|
reset: () => reset2,
|
|
351327
351355
|
resolve: () => resolve13,
|
|
351356
|
+
scanForCredentials: () => scanForCredentials,
|
|
351328
351357
|
status: () => status,
|
|
351329
351358
|
threeWayMerge: () => threeWayMerge
|
|
351330
351359
|
});
|
|
351331
351360
|
module4.exports = __toCommonJS3(index_exports);
|
|
351361
|
+
var CREDENTIAL_PATTERNS = [
|
|
351362
|
+
{ name: "PRIVATE KEY block", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
|
|
351363
|
+
{ name: "OPENSSH key", re: /-----BEGIN OPENSSH PRIVATE KEY-----/ },
|
|
351364
|
+
{ name: "RSA key", re: /-----BEGIN RSA PRIVATE KEY-----/ },
|
|
351365
|
+
{ name: "CERTIFICATE", re: /-----BEGIN CERTIFICATE-----/ },
|
|
351366
|
+
{ name: "TOKEN=", re: /\bTOKEN\s*=\s*["']?[A-Za-z0-9._\-+/=]{10,}/ },
|
|
351367
|
+
{ name: "api_key=", re: /\bapi[_-]?key\s*=\s*["']?[A-Za-z0-9._\-+/=]{10,}/i },
|
|
351368
|
+
{ name: "password=", re: /\bpassword\s*=\s*["']?[^\s"']{6,}/i },
|
|
351369
|
+
{ name: "AWS_SECRET_ACCESS_KEY", re: /AWS_SECRET_ACCESS_KEY/ },
|
|
351370
|
+
{ name: "JWT (eyJ...)", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/ }
|
|
351371
|
+
];
|
|
351372
|
+
var SENSITIVE_FILE_PATTERNS = [
|
|
351373
|
+
/(^|\/)\.env(\.[^/]+)?$/,
|
|
351374
|
+
/\.pem$/,
|
|
351375
|
+
/\.key$/,
|
|
351376
|
+
/\.pkce_state\.json$/,
|
|
351377
|
+
/(^|\/)id_rsa$/,
|
|
351378
|
+
/(^|\/)id_ed25519$/
|
|
351379
|
+
];
|
|
351380
|
+
function isSensitiveFile(filePath) {
|
|
351381
|
+
return SENSITIVE_FILE_PATTERNS.some((re17) => re17.test(filePath));
|
|
351382
|
+
}
|
|
351383
|
+
function scanForCredentials(content5) {
|
|
351384
|
+
const matches2 = [];
|
|
351385
|
+
for (const { name: name2, re: re17 } of CREDENTIAL_PATTERNS) {
|
|
351386
|
+
if (re17.test(content5)) {
|
|
351387
|
+
matches2.push(name2);
|
|
351388
|
+
}
|
|
351389
|
+
}
|
|
351390
|
+
return matches2;
|
|
351391
|
+
}
|
|
351332
351392
|
init_GitClient();
|
|
351333
351393
|
var FERN_BOT_NAME = "fern-api";
|
|
351334
351394
|
var FERN_BOT_EMAIL = "115122769+fern-api[bot]@users.noreply.github.com";
|
|
@@ -351370,6 +351430,7 @@ var require_dist14 = __commonJS({
|
|
|
351370
351430
|
var LockfileManager = class {
|
|
351371
351431
|
outputDir;
|
|
351372
351432
|
lock = null;
|
|
351433
|
+
warnings = [];
|
|
351373
351434
|
constructor(outputDir) {
|
|
351374
351435
|
this.outputDir = outputDir;
|
|
351375
351436
|
}
|
|
@@ -351418,6 +351479,27 @@ var require_dist14 = __commonJS({
|
|
|
351418
351479
|
if (!this.lock) {
|
|
351419
351480
|
throw new Error("No lockfile data to save. Call read() or initialize() first.");
|
|
351420
351481
|
}
|
|
351482
|
+
this.warnings.length = 0;
|
|
351483
|
+
const cleanPatches = [];
|
|
351484
|
+
for (const patch5 of this.lock.patches) {
|
|
351485
|
+
const credentialMatches = scanForCredentials(patch5.patch_content);
|
|
351486
|
+
const sensitiveFiles = patch5.files.filter((f10) => isSensitiveFile(f10));
|
|
351487
|
+
if (credentialMatches.length > 0 || sensitiveFiles.length > 0) {
|
|
351488
|
+
const reasons = [];
|
|
351489
|
+
if (credentialMatches.length > 0) {
|
|
351490
|
+
reasons.push(`credential patterns: ${credentialMatches.join(", ")}`);
|
|
351491
|
+
}
|
|
351492
|
+
if (sensitiveFiles.length > 0) {
|
|
351493
|
+
reasons.push(`sensitive files: ${sensitiveFiles.join(", ")}`);
|
|
351494
|
+
}
|
|
351495
|
+
this.warnings.push(
|
|
351496
|
+
`Patch ${patch5.id} removed from lockfile: ${reasons.join("; ")}. This prevents credential exposure in committed lockfiles.`
|
|
351497
|
+
);
|
|
351498
|
+
} else {
|
|
351499
|
+
cleanPatches.push(patch5);
|
|
351500
|
+
}
|
|
351501
|
+
}
|
|
351502
|
+
this.lock.patches = cleanPatches;
|
|
351421
351503
|
const dir = (0, import_node_path41.dirname)(this.lockfilePath);
|
|
351422
351504
|
if (!(0, import_node_fs32.existsSync)(dir)) {
|
|
351423
351505
|
(0, import_node_fs32.mkdirSync)(dir, { recursive: true });
|
|
@@ -351520,6 +351602,37 @@ var require_dist14 = __commonJS({
|
|
|
351520
351602
|
};
|
|
351521
351603
|
var import_node_crypto7 = require("crypto");
|
|
351522
351604
|
var INFRASTRUCTURE_FILES = /* @__PURE__ */ new Set([".fernignore"]);
|
|
351605
|
+
function parsePatchFileHeaders(patchContent) {
|
|
351606
|
+
const results = [];
|
|
351607
|
+
let currentPath = null;
|
|
351608
|
+
let currentIsCreate = false;
|
|
351609
|
+
let currentIsDelete = false;
|
|
351610
|
+
const flush2 = () => {
|
|
351611
|
+
if (currentPath !== null) {
|
|
351612
|
+
results.push({ path: currentPath, isCreate: currentIsCreate, isDelete: currentIsDelete });
|
|
351613
|
+
}
|
|
351614
|
+
};
|
|
351615
|
+
for (const line of patchContent.split("\n")) {
|
|
351616
|
+
if (line.startsWith("diff --git ")) {
|
|
351617
|
+
flush2();
|
|
351618
|
+
currentPath = null;
|
|
351619
|
+
currentIsCreate = false;
|
|
351620
|
+
currentIsDelete = false;
|
|
351621
|
+
const match3 = line.match(/^diff --git a\/(.+?) b\/(.+?)$/);
|
|
351622
|
+
if (match3) {
|
|
351623
|
+
currentPath = match3[2] ?? null;
|
|
351624
|
+
}
|
|
351625
|
+
} else if (currentPath !== null) {
|
|
351626
|
+
if (line.startsWith("new file mode ")) {
|
|
351627
|
+
currentIsCreate = true;
|
|
351628
|
+
} else if (line.startsWith("deleted file mode ")) {
|
|
351629
|
+
currentIsDelete = true;
|
|
351630
|
+
}
|
|
351631
|
+
}
|
|
351632
|
+
}
|
|
351633
|
+
flush2();
|
|
351634
|
+
return results;
|
|
351635
|
+
}
|
|
351523
351636
|
var ReplayDetector = class {
|
|
351524
351637
|
git;
|
|
351525
351638
|
lockManager;
|
|
@@ -351598,12 +351711,31 @@ var require_dist14 = __commonJS({
|
|
|
351598
351711
|
);
|
|
351599
351712
|
continue;
|
|
351600
351713
|
}
|
|
351601
|
-
|
|
351714
|
+
let contentHash = this.computeContentHash(patchContent);
|
|
351602
351715
|
if (lock.patches.find((p10) => p10.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
351603
351716
|
continue;
|
|
351604
351717
|
}
|
|
351605
351718
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
351606
|
-
const
|
|
351719
|
+
const allFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f10) => !INFRASTRUCTURE_FILES.has(f10)).filter((f10) => !f10.startsWith(".fern/"));
|
|
351720
|
+
const sensitiveFiles = allFiles.filter((f10) => isSensitiveFile(f10));
|
|
351721
|
+
const files = allFiles.filter((f10) => !isSensitiveFile(f10));
|
|
351722
|
+
if (sensitiveFiles.length > 0) {
|
|
351723
|
+
this.warnings.push(
|
|
351724
|
+
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
351725
|
+
);
|
|
351726
|
+
if (files.length > 0) {
|
|
351727
|
+
const parentSha = parents[0];
|
|
351728
|
+
if (parentSha) {
|
|
351729
|
+
try {
|
|
351730
|
+
patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
|
|
351731
|
+
} catch {
|
|
351732
|
+
continue;
|
|
351733
|
+
}
|
|
351734
|
+
if (!patchContent.trim()) continue;
|
|
351735
|
+
contentHash = this.computeContentHash(patchContent);
|
|
351736
|
+
}
|
|
351737
|
+
}
|
|
351738
|
+
}
|
|
351607
351739
|
if (files.length === 0) {
|
|
351608
351740
|
continue;
|
|
351609
351741
|
}
|
|
@@ -351615,14 +351747,16 @@ var require_dist14 = __commonJS({
|
|
|
351615
351747
|
original_author: `${commit.authorName} <${commit.authorEmail}>`,
|
|
351616
351748
|
base_generation: lastGen.commit_sha,
|
|
351617
351749
|
files,
|
|
351618
|
-
patch_content: patchContent
|
|
351750
|
+
patch_content: patchContent,
|
|
351751
|
+
...this.isCreationOnlyPatch(patchContent) ? { user_owned: true } : {}
|
|
351619
351752
|
});
|
|
351620
351753
|
}
|
|
351621
|
-
|
|
351754
|
+
const survivingPatches = await this.collapseNetZeroFiles(newPatches);
|
|
351755
|
+
survivingPatches.reverse();
|
|
351622
351756
|
const revertedPatchIdSet = /* @__PURE__ */ new Set();
|
|
351623
351757
|
const revertIndicesToRemove = /* @__PURE__ */ new Set();
|
|
351624
|
-
for (let i11 = 0; i11 <
|
|
351625
|
-
const patch5 =
|
|
351758
|
+
for (let i11 = 0; i11 < survivingPatches.length; i11++) {
|
|
351759
|
+
const patch5 = survivingPatches[i11];
|
|
351626
351760
|
if (!isRevertCommit(patch5.original_message)) continue;
|
|
351627
351761
|
let body = "";
|
|
351628
351762
|
try {
|
|
@@ -351651,7 +351785,7 @@ var require_dist14 = __commonJS({
|
|
|
351651
351785
|
if (matchedExisting) continue;
|
|
351652
351786
|
let matchedNew = false;
|
|
351653
351787
|
if (revertedSha) {
|
|
351654
|
-
const idx =
|
|
351788
|
+
const idx = survivingPatches.findIndex(
|
|
351655
351789
|
(p10, j16) => j16 !== i11 && !revertIndicesToRemove.has(j16) && p10.original_commit === revertedSha
|
|
351656
351790
|
);
|
|
351657
351791
|
if (idx !== -1) {
|
|
@@ -351661,7 +351795,7 @@ var require_dist14 = __commonJS({
|
|
|
351661
351795
|
}
|
|
351662
351796
|
}
|
|
351663
351797
|
if (!matchedNew && revertedMessage) {
|
|
351664
|
-
const idx =
|
|
351798
|
+
const idx = survivingPatches.findIndex(
|
|
351665
351799
|
(p10, j16) => j16 !== i11 && !revertIndicesToRemove.has(j16) && p10.original_message === revertedMessage
|
|
351666
351800
|
);
|
|
351667
351801
|
if (idx !== -1) {
|
|
@@ -351673,18 +351807,107 @@ var require_dist14 = __commonJS({
|
|
|
351673
351807
|
revertIndicesToRemove.add(i11);
|
|
351674
351808
|
}
|
|
351675
351809
|
}
|
|
351676
|
-
const filteredPatches =
|
|
351810
|
+
const filteredPatches = survivingPatches.filter((_16, i11) => !revertIndicesToRemove.has(i11));
|
|
351677
351811
|
return { patches: filteredPatches, revertedPatchIds: [...revertedPatchIdSet] };
|
|
351678
351812
|
}
|
|
351679
351813
|
/**
|
|
351680
351814
|
* Compute content hash for deduplication.
|
|
351681
|
-
*
|
|
351682
|
-
*
|
|
351815
|
+
*
|
|
351816
|
+
* Produces a format-agnostic hash so both `git format-patch` output
|
|
351817
|
+
* (email-wrapped) and plain `git diff` output hash to the same value
|
|
351818
|
+
* when the underlying diff hunks are identical.
|
|
351819
|
+
*
|
|
351820
|
+
* Normalization:
|
|
351821
|
+
* 1. Strip email wrapper: everything before the first `diff --git` line
|
|
351822
|
+
* (From:, Subject:, Date:, diffstat, blank separators).
|
|
351823
|
+
* 2. Strip `index` lines (blob SHA pairs change across rebases).
|
|
351824
|
+
* 3. Strip trailing git version marker (`-- \n<version>`).
|
|
351825
|
+
*
|
|
351826
|
+
* This ensures content hashes match across the no-patches and normal-
|
|
351827
|
+
* regeneration flows, which store formatPatch and git-diff formats
|
|
351828
|
+
* respectively (FER-9850, D5-D9).
|
|
351683
351829
|
*/
|
|
351684
351830
|
computeContentHash(patchContent) {
|
|
351685
|
-
const
|
|
351831
|
+
const lines = patchContent.split("\n");
|
|
351832
|
+
const diffStart = lines.findIndex((l14) => l14.startsWith("diff --git "));
|
|
351833
|
+
const relevantLines = diffStart > 0 ? lines.slice(diffStart) : lines;
|
|
351834
|
+
const normalized = relevantLines.filter((line) => !line.startsWith("index ")).join("\n").replace(/\n-- \n[\d]+\.[\d]+[^\n]*\s*$/, "");
|
|
351686
351835
|
return `sha256:${(0, import_node_crypto7.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
351687
351836
|
}
|
|
351837
|
+
/**
|
|
351838
|
+
* FER-9805 — Drop patches whose files were transiently created and then
|
|
351839
|
+
* deleted within the current linear detection window, and are absent from
|
|
351840
|
+
* HEAD at the end of the window.
|
|
351841
|
+
*
|
|
351842
|
+
* Rationale: on a merge commit, createMergeCompositePatch already diffs
|
|
351843
|
+
* firstParent..mergeCommit so net-zero files never enter the composite. On
|
|
351844
|
+
* linear history each commit becomes its own patch, so a file that was
|
|
351845
|
+
* briefly added and later removed survives as a create+delete pair whose
|
|
351846
|
+
* cumulative diff is empty. We drop such patches before they reach the
|
|
351847
|
+
* lockfile.
|
|
351848
|
+
*
|
|
351849
|
+
* A file is "transient" when:
|
|
351850
|
+
* 1. some patch in the window sets `new file mode` for it (a creation), AND
|
|
351851
|
+
* 2. some patch in the window sets `deleted file mode` for it (a deletion), AND
|
|
351852
|
+
* 3. it is absent from HEAD's tree.
|
|
351853
|
+
*
|
|
351854
|
+
* The HEAD guard (#3) prevents false positives when a file is deleted
|
|
351855
|
+
* and then recreated with different content — the cumulative diff of
|
|
351856
|
+
* such a pair is non-empty and must be preserved.
|
|
351857
|
+
*
|
|
351858
|
+
* This only drops patches whose `files` are a subset of the transient
|
|
351859
|
+
* set. A partial-transient patch (one that touches a transient file
|
|
351860
|
+
* alongside a persistent one) is left unmodified; surgical stripping of
|
|
351861
|
+
* single files from a multi-file patch would require a follow-up that
|
|
351862
|
+
* regenerates the patch content via `git format-patch -- <files>`. No
|
|
351863
|
+
* current failing test exercises this, and leaving the patch intact
|
|
351864
|
+
* preserves today's behaviour. TODO(FER-9805): revisit if a future
|
|
351865
|
+
* adversarial test demands per-file stripping.
|
|
351866
|
+
*/
|
|
351867
|
+
async collapseNetZeroFiles(patches) {
|
|
351868
|
+
if (patches.length === 0) return patches;
|
|
351869
|
+
const stateByFile = /* @__PURE__ */ new Map();
|
|
351870
|
+
for (const patch5 of patches) {
|
|
351871
|
+
for (const header of parsePatchFileHeaders(patch5.patch_content)) {
|
|
351872
|
+
if (!header.isCreate && !header.isDelete) continue;
|
|
351873
|
+
const prior = stateByFile.get(header.path) ?? { created: false, deleted: false };
|
|
351874
|
+
if (header.isCreate) prior.created = true;
|
|
351875
|
+
if (header.isDelete) prior.deleted = true;
|
|
351876
|
+
stateByFile.set(header.path, prior);
|
|
351877
|
+
}
|
|
351878
|
+
}
|
|
351879
|
+
let anyCandidate = false;
|
|
351880
|
+
for (const state of stateByFile.values()) {
|
|
351881
|
+
if (state.created && state.deleted) {
|
|
351882
|
+
anyCandidate = true;
|
|
351883
|
+
break;
|
|
351884
|
+
}
|
|
351885
|
+
}
|
|
351886
|
+
if (!anyCandidate) return patches;
|
|
351887
|
+
const headFiles = await this.listHeadFiles();
|
|
351888
|
+
const transient = /* @__PURE__ */ new Set();
|
|
351889
|
+
for (const [file4, state] of stateByFile) {
|
|
351890
|
+
if (state.created && state.deleted && !headFiles.has(file4)) {
|
|
351891
|
+
transient.add(file4);
|
|
351892
|
+
}
|
|
351893
|
+
}
|
|
351894
|
+
if (transient.size === 0) return patches;
|
|
351895
|
+
return patches.filter((patch5) => !patch5.files.every((f10) => transient.has(f10)));
|
|
351896
|
+
}
|
|
351897
|
+
/**
|
|
351898
|
+
* List every tracked path at HEAD (one `git ls-tree -r` invocation).
|
|
351899
|
+
* Returns an empty Set if HEAD is unborn or the invocation fails — the
|
|
351900
|
+
* caller then treats every candidate as "not in HEAD" and may collapse
|
|
351901
|
+
* more aggressively. On typical SDK repos this is a single sub-10ms call.
|
|
351902
|
+
*/
|
|
351903
|
+
async listHeadFiles() {
|
|
351904
|
+
try {
|
|
351905
|
+
const out = await this.git.exec(["ls-tree", "-r", "--name-only", "HEAD"]);
|
|
351906
|
+
return new Set(out.split("\n").map((l14) => l14.trim()).filter(Boolean));
|
|
351907
|
+
} catch {
|
|
351908
|
+
return /* @__PURE__ */ new Set();
|
|
351909
|
+
}
|
|
351910
|
+
}
|
|
351688
351911
|
/**
|
|
351689
351912
|
* Create a single composite patch for a merge commit by diffing the merge
|
|
351690
351913
|
* commit against its first parent. This captures the net change of the
|
|
@@ -351700,7 +351923,14 @@ var require_dist14 = __commonJS({
|
|
|
351700
351923
|
firstParent,
|
|
351701
351924
|
mergeCommit.sha
|
|
351702
351925
|
]);
|
|
351703
|
-
const
|
|
351926
|
+
const allMergeFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f10) => !INFRASTRUCTURE_FILES.has(f10)).filter((f10) => !f10.startsWith(".fern/"));
|
|
351927
|
+
const sensitiveMergeFiles = allMergeFiles.filter((f10) => isSensitiveFile(f10));
|
|
351928
|
+
const files = allMergeFiles.filter((f10) => !isSensitiveFile(f10));
|
|
351929
|
+
if (sensitiveMergeFiles.length > 0) {
|
|
351930
|
+
this.warnings.push(
|
|
351931
|
+
`Sensitive file(s) excluded from merge patch for commit ${mergeCommit.sha.slice(0, 7)}: ${sensitiveMergeFiles.join(", ")}. These files will not be tracked by replay.`
|
|
351932
|
+
);
|
|
351933
|
+
}
|
|
351704
351934
|
if (files.length === 0) return null;
|
|
351705
351935
|
const diff2 = await this.git.exec([
|
|
351706
351936
|
"diff",
|
|
@@ -351722,7 +351952,8 @@ var require_dist14 = __commonJS({
|
|
|
351722
351952
|
original_author: `${mergeCommit.authorName} <${mergeCommit.authorEmail}>`,
|
|
351723
351953
|
base_generation: lastGen.commit_sha,
|
|
351724
351954
|
files,
|
|
351725
|
-
patch_content: diff2
|
|
351955
|
+
patch_content: diff2,
|
|
351956
|
+
...this.isCreationOnlyPatch(diff2) ? { user_owned: true } : {}
|
|
351726
351957
|
};
|
|
351727
351958
|
}
|
|
351728
351959
|
/**
|
|
@@ -351736,7 +351967,14 @@ var require_dist14 = __commonJS({
|
|
|
351736
351967
|
return this.detectPatchesViaCommitScan();
|
|
351737
351968
|
}
|
|
351738
351969
|
const filesOutput = await this.git.exec(["diff", "--name-only", diffBase, "HEAD"]);
|
|
351739
|
-
const
|
|
351970
|
+
const allTreeFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f10) => !INFRASTRUCTURE_FILES.has(f10)).filter((f10) => !f10.startsWith(".fern/"));
|
|
351971
|
+
const sensitiveTreeFiles = allTreeFiles.filter((f10) => isSensitiveFile(f10));
|
|
351972
|
+
const files = allTreeFiles.filter((f10) => !isSensitiveFile(f10));
|
|
351973
|
+
if (sensitiveTreeFiles.length > 0) {
|
|
351974
|
+
this.warnings.push(
|
|
351975
|
+
`Sensitive file(s) excluded from composite patch: ${sensitiveTreeFiles.join(", ")}. These files will not be tracked by replay.`
|
|
351976
|
+
);
|
|
351977
|
+
}
|
|
351740
351978
|
if (files.length === 0) return { patches: [], revertedPatchIds: [] };
|
|
351741
351979
|
const diff2 = await this.git.exec(["diff", diffBase, "HEAD", "--", ...files]);
|
|
351742
351980
|
if (!diff2.trim()) return { patches: [], revertedPatchIds: [] };
|
|
@@ -351756,7 +351994,8 @@ var require_dist14 = __commonJS({
|
|
|
351756
351994
|
// reference to find base file content. diffBase may be the tree_hash.
|
|
351757
351995
|
base_generation: commitKnownMissing ? diffBase : lastGen.commit_sha,
|
|
351758
351996
|
files,
|
|
351759
|
-
patch_content: diff2
|
|
351997
|
+
patch_content: diff2,
|
|
351998
|
+
...this.isCreationOnlyPatch(diff2) ? { user_owned: true } : {}
|
|
351760
351999
|
};
|
|
351761
352000
|
return { patches: [compositePatch], revertedPatchIds: [] };
|
|
351762
352001
|
}
|
|
@@ -351803,7 +352042,7 @@ var require_dist14 = __commonJS({
|
|
|
351803
352042
|
} catch {
|
|
351804
352043
|
continue;
|
|
351805
352044
|
}
|
|
351806
|
-
|
|
352045
|
+
let contentHash = this.computeContentHash(patchContent);
|
|
351807
352046
|
if (existingHashes.has(contentHash) || forgottenHashes.has(contentHash)) {
|
|
351808
352047
|
continue;
|
|
351809
352048
|
}
|
|
@@ -351811,7 +352050,26 @@ var require_dist14 = __commonJS({
|
|
|
351811
352050
|
continue;
|
|
351812
352051
|
}
|
|
351813
352052
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
351814
|
-
const
|
|
352053
|
+
const allScanFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f10) => !INFRASTRUCTURE_FILES.has(f10));
|
|
352054
|
+
const sensitiveScanFiles = allScanFiles.filter((f10) => isSensitiveFile(f10));
|
|
352055
|
+
const files = allScanFiles.filter((f10) => !isSensitiveFile(f10));
|
|
352056
|
+
if (sensitiveScanFiles.length > 0) {
|
|
352057
|
+
this.warnings.push(
|
|
352058
|
+
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveScanFiles.join(", ")}. These files will not be tracked by replay.`
|
|
352059
|
+
);
|
|
352060
|
+
if (files.length > 0) {
|
|
352061
|
+
if (parents.length === 0) {
|
|
352062
|
+
continue;
|
|
352063
|
+
}
|
|
352064
|
+
try {
|
|
352065
|
+
patchContent = await this.git.exec(["diff", parents[0], commit.sha, "--", ...files]);
|
|
352066
|
+
} catch {
|
|
352067
|
+
continue;
|
|
352068
|
+
}
|
|
352069
|
+
if (!patchContent.trim()) continue;
|
|
352070
|
+
contentHash = this.computeContentHash(patchContent);
|
|
352071
|
+
}
|
|
352072
|
+
}
|
|
351815
352073
|
if (files.length === 0) {
|
|
351816
352074
|
continue;
|
|
351817
352075
|
}
|
|
@@ -352135,6 +352393,20 @@ var require_dist14 = __commonJS({
|
|
|
352135
352393
|
resetAccumulator() {
|
|
352136
352394
|
this.fileTheirsAccumulator.clear();
|
|
352137
352395
|
}
|
|
352396
|
+
/**
|
|
352397
|
+
* @internal Test-only.
|
|
352398
|
+
* Returns a defensive copy of the inter-patch accumulator. Used by the
|
|
352399
|
+
* adversarial test suite (FER-9791) to assert invariant I2: after every
|
|
352400
|
+
* applied patch, the accumulator has an entry for each file the patch
|
|
352401
|
+
* touched, and the entry's content matches on-disk.
|
|
352402
|
+
*/
|
|
352403
|
+
getAccumulatorSnapshot() {
|
|
352404
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
352405
|
+
for (const [path98, entry] of this.fileTheirsAccumulator) {
|
|
352406
|
+
snapshot.set(path98, { content: entry.content, baseGeneration: entry.baseGeneration });
|
|
352407
|
+
}
|
|
352408
|
+
return snapshot;
|
|
352409
|
+
}
|
|
352138
352410
|
/**
|
|
352139
352411
|
* Apply all patches, returning results for each.
|
|
352140
352412
|
* Skips patches that match exclude patterns in replay.yml
|
|
@@ -352200,7 +352472,7 @@ var require_dist14 = __commonJS({
|
|
|
352200
352472
|
const base4 = await this.git.showFile(baseGen.tree_hash, filePath);
|
|
352201
352473
|
const theirs = await this.applyPatchToContent(base4, patch5.patch_content, filePath, tempGit, tempDir2);
|
|
352202
352474
|
const effectiveTheirs = theirs ?? await (0, import_promises183.readFile)((0, import_node_path210.join)(this.outputDir, resolvedPath), "utf-8").catch(() => null);
|
|
352203
|
-
if (effectiveTheirs) {
|
|
352475
|
+
if (effectiveTheirs != null) {
|
|
352204
352476
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
352205
352477
|
content: effectiveTheirs,
|
|
352206
352478
|
baseGeneration: patch5.base_generation
|
|
@@ -352224,16 +352496,20 @@ var require_dist14 = __commonJS({
|
|
|
352224
352496
|
return this.fileTheirsAccumulator.has(resolved);
|
|
352225
352497
|
})
|
|
352226
352498
|
).then((results) => results.some(Boolean));
|
|
352227
|
-
|
|
352499
|
+
const resolvedFiles = {};
|
|
352500
|
+
for (const filePath of patch5.files) {
|
|
352501
|
+
const resolvedPath = baseGen ? await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash) : filePath;
|
|
352502
|
+
if (resolvedPath !== filePath) {
|
|
352503
|
+
resolvedFiles[filePath] = resolvedPath;
|
|
352504
|
+
}
|
|
352505
|
+
}
|
|
352506
|
+
const patchIsRenameAware = /^rename from /m.test(patch5.patch_content);
|
|
352507
|
+
const hasGeneratorRename = Object.keys(resolvedFiles).length > 0 && !patchIsRenameAware;
|
|
352508
|
+
if (!needsAccumulation && !hasGeneratorRename) {
|
|
352228
352509
|
const snapshots = /* @__PURE__ */ new Map();
|
|
352229
|
-
const resolvedFiles = {};
|
|
352230
352510
|
for (const filePath of patch5.files) {
|
|
352231
|
-
const
|
|
352232
|
-
|
|
352233
|
-
resolvedFiles[filePath] = resolvedPath;
|
|
352234
|
-
}
|
|
352235
|
-
const fullPath = (0, import_node_path210.join)(this.outputDir, resolvedPath);
|
|
352236
|
-
snapshots.set(resolvedPath, await (0, import_promises183.readFile)(fullPath, "utf-8").catch(() => null));
|
|
352511
|
+
const fullPath = (0, import_node_path210.join)(this.outputDir, filePath);
|
|
352512
|
+
snapshots.set(filePath, await (0, import_promises183.readFile)(fullPath, "utf-8").catch(() => null));
|
|
352237
352513
|
}
|
|
352238
352514
|
try {
|
|
352239
352515
|
await this.git.execWithInput(["apply", "--3way"], patch5.patch_content);
|
|
@@ -352245,8 +352521,8 @@ var require_dist14 = __commonJS({
|
|
|
352245
352521
|
...Object.keys(resolvedFiles).length > 0 && { resolvedFiles }
|
|
352246
352522
|
};
|
|
352247
352523
|
} catch {
|
|
352248
|
-
for (const [
|
|
352249
|
-
const fullPath = (0, import_node_path210.join)(this.outputDir,
|
|
352524
|
+
for (const [filePath, content5] of snapshots) {
|
|
352525
|
+
const fullPath = (0, import_node_path210.join)(this.outputDir, filePath);
|
|
352250
352526
|
if (content5 != null) {
|
|
352251
352527
|
await (0, import_promises183.writeFile)(fullPath, content5);
|
|
352252
352528
|
} else {
|
|
@@ -352366,7 +352642,7 @@ var require_dist14 = __commonJS({
|
|
|
352366
352642
|
}
|
|
352367
352643
|
let useAccumulatorAsMergeBase = false;
|
|
352368
352644
|
const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
|
|
352369
|
-
if (accumulatorEntry && (
|
|
352645
|
+
if (accumulatorEntry && (theirs == null || base4 == null)) {
|
|
352370
352646
|
theirs = await this.applyPatchToContent(
|
|
352371
352647
|
accumulatorEntry.content,
|
|
352372
352648
|
patch5.patch_content,
|
|
@@ -352374,7 +352650,16 @@ var require_dist14 = __commonJS({
|
|
|
352374
352650
|
tempGit,
|
|
352375
352651
|
tempDir2
|
|
352376
352652
|
);
|
|
352377
|
-
if (theirs) {
|
|
352653
|
+
if (theirs != null) {
|
|
352654
|
+
useAccumulatorAsMergeBase = true;
|
|
352655
|
+
} else if (await this.isPatchAlreadyApplied(
|
|
352656
|
+
patch5.patch_content,
|
|
352657
|
+
filePath,
|
|
352658
|
+
accumulatorEntry.content,
|
|
352659
|
+
tempGit,
|
|
352660
|
+
tempDir2
|
|
352661
|
+
)) {
|
|
352662
|
+
theirs = accumulatorEntry.content;
|
|
352378
352663
|
useAccumulatorAsMergeBase = true;
|
|
352379
352664
|
}
|
|
352380
352665
|
}
|
|
@@ -352391,7 +352676,7 @@ var require_dist14 = __commonJS({
|
|
|
352391
352676
|
}
|
|
352392
352677
|
let effective_theirs = theirs;
|
|
352393
352678
|
let baseMismatchSkipped = false;
|
|
352394
|
-
if (theirs && base4 && !useAccumulatorAsMergeBase) {
|
|
352679
|
+
if (theirs != null && base4 != null && !useAccumulatorAsMergeBase) {
|
|
352395
352680
|
if (accumulatorEntry && accumulatorEntry.baseGeneration === patch5.base_generation) {
|
|
352396
352681
|
try {
|
|
352397
352682
|
const preMerged = threeWayMerge(base4, accumulatorEntry.content, theirs);
|
|
@@ -352407,7 +352692,7 @@ var require_dist14 = __commonJS({
|
|
|
352407
352692
|
baseMismatchSkipped = true;
|
|
352408
352693
|
}
|
|
352409
352694
|
}
|
|
352410
|
-
if (base4 == null &&
|
|
352695
|
+
if (base4 == null && ours == null && effective_theirs != null) {
|
|
352411
352696
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
352412
352697
|
content: effective_theirs,
|
|
352413
352698
|
baseGeneration: patch5.base_generation
|
|
@@ -352417,7 +352702,7 @@ var require_dist14 = __commonJS({
|
|
|
352417
352702
|
await (0, import_promises183.writeFile)(oursPath, effective_theirs);
|
|
352418
352703
|
return { file: resolvedPath, status: "merged", reason: "new-file" };
|
|
352419
352704
|
}
|
|
352420
|
-
if (base4 == null && ours && effective_theirs && !useAccumulatorAsMergeBase) {
|
|
352705
|
+
if (base4 == null && ours != null && effective_theirs != null && !useAccumulatorAsMergeBase) {
|
|
352421
352706
|
const merged2 = threeWayMerge("", ours, effective_theirs);
|
|
352422
352707
|
const outDir2 = (0, import_node_path210.dirname)(oursPath);
|
|
352423
352708
|
await (0, import_promises183.mkdir)(outDir2, { recursive: true });
|
|
@@ -352431,16 +352716,20 @@ var require_dist14 = __commonJS({
|
|
|
352431
352716
|
conflictMetadata: metadata
|
|
352432
352717
|
};
|
|
352433
352718
|
}
|
|
352719
|
+
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
352720
|
+
content: merged2.content,
|
|
352721
|
+
baseGeneration: patch5.base_generation
|
|
352722
|
+
});
|
|
352434
352723
|
return { file: resolvedPath, status: "merged" };
|
|
352435
352724
|
}
|
|
352436
|
-
if (
|
|
352725
|
+
if (effective_theirs == null) {
|
|
352437
352726
|
return {
|
|
352438
352727
|
file: resolvedPath,
|
|
352439
352728
|
status: "skipped",
|
|
352440
352729
|
reason: "missing-content"
|
|
352441
352730
|
};
|
|
352442
352731
|
}
|
|
352443
|
-
if (base4 == null && !useAccumulatorAsMergeBase ||
|
|
352732
|
+
if (base4 == null && !useAccumulatorAsMergeBase || ours == null) {
|
|
352444
352733
|
return {
|
|
352445
352734
|
file: resolvedPath,
|
|
352446
352735
|
status: "skipped",
|
|
@@ -352459,7 +352748,7 @@ var require_dist14 = __commonJS({
|
|
|
352459
352748
|
const outDir = (0, import_node_path210.dirname)(oursPath);
|
|
352460
352749
|
await (0, import_promises183.mkdir)(outDir, { recursive: true });
|
|
352461
352750
|
await (0, import_promises183.writeFile)(oursPath, merged.content);
|
|
352462
|
-
if (effective_theirs && !merged.hasConflicts) {
|
|
352751
|
+
if (effective_theirs != null && !merged.hasConflicts) {
|
|
352463
352752
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
352464
352753
|
content: effective_theirs,
|
|
352465
352754
|
baseGeneration: patch5.base_generation
|
|
@@ -352557,6 +352846,28 @@ var require_dist14 = __commonJS({
|
|
|
352557
352846
|
return null;
|
|
352558
352847
|
}
|
|
352559
352848
|
}
|
|
352849
|
+
/**
|
|
352850
|
+
* Detects whether a patch's additions are already present in `content`.
|
|
352851
|
+
* Uses `git apply --reverse --check` — if the reverse patch applies cleanly,
|
|
352852
|
+
* the forward patch is effectively a no-op (its +lines are already there and
|
|
352853
|
+
* its -lines are already gone). Used to distinguish "patch already applied"
|
|
352854
|
+
* from "patch base mismatch" after a forward-apply fails.
|
|
352855
|
+
*/
|
|
352856
|
+
async isPatchAlreadyApplied(patchContent, filePath, content5, tempGit, tempDir2) {
|
|
352857
|
+
const fileDiff = this.extractFileDiff(patchContent, filePath);
|
|
352858
|
+
if (!fileDiff) return false;
|
|
352859
|
+
try {
|
|
352860
|
+
const tempFilePath = (0, import_node_path210.join)(tempDir2, filePath);
|
|
352861
|
+
await (0, import_promises183.mkdir)((0, import_node_path210.dirname)(tempFilePath), { recursive: true });
|
|
352862
|
+
await (0, import_promises183.writeFile)(tempFilePath, content5);
|
|
352863
|
+
await tempGit.exec(["add", filePath]);
|
|
352864
|
+
await tempGit.exec(["commit", "-m", `check already-applied ${filePath}`, "--allow-empty"]);
|
|
352865
|
+
await tempGit.execWithInput(["apply", "--reverse", "--check", "--allow-empty"], fileDiff);
|
|
352866
|
+
return true;
|
|
352867
|
+
} catch {
|
|
352868
|
+
return false;
|
|
352869
|
+
}
|
|
352870
|
+
}
|
|
352560
352871
|
extractFileDiff(patchContent, filePath) {
|
|
352561
352872
|
const lines = patchContent.split("\n");
|
|
352562
352873
|
const diffLines = [];
|
|
@@ -352721,6 +353032,33 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352721
353032
|
committer;
|
|
352722
353033
|
lockManager;
|
|
352723
353034
|
outputDir;
|
|
353035
|
+
/** @internal Test-only. Captures the last ReplayResult[] returned from applicator.applyPatches(). */
|
|
353036
|
+
_lastApplyResults = [];
|
|
353037
|
+
/**
|
|
353038
|
+
* Service-level warnings accumulated during a single runReplay() call.
|
|
353039
|
+
* Merged with `detector.warnings` into `ReplayReport.warnings` by each flow handler.
|
|
353040
|
+
* Populated by `isFileUserOwned` when a patch's base generation tree is unreachable
|
|
353041
|
+
* (shallow clone / aggressive `git gc --prune`) and we fall back to a conservative
|
|
353042
|
+
* heuristic. Cleared at the top of `runReplay()`.
|
|
353043
|
+
*/
|
|
353044
|
+
warnings = [];
|
|
353045
|
+
/**
|
|
353046
|
+
* @internal Test-only accessor.
|
|
353047
|
+
* Returns the ReplayApplicator instance used for the last run. Tests use this
|
|
353048
|
+
* to inspect the inter-patch accumulator after runReplay() completes (FER-9791, I2).
|
|
353049
|
+
*/
|
|
353050
|
+
get applicatorRef() {
|
|
353051
|
+
return this.applicator;
|
|
353052
|
+
}
|
|
353053
|
+
/**
|
|
353054
|
+
* @internal Test-only accessor.
|
|
353055
|
+
* Returns the ReplayResult[] from the most recent applyPatches() call. Tests use
|
|
353056
|
+
* this to filter applied-vs-conflicted-vs-absorbed patches for invariant assertions
|
|
353057
|
+
* (FER-9791). Returns an empty array if no patches have been applied yet.
|
|
353058
|
+
*/
|
|
353059
|
+
getLastResults() {
|
|
353060
|
+
return this._lastApplyResults;
|
|
353061
|
+
}
|
|
352724
353062
|
constructor(outputDir, _config) {
|
|
352725
353063
|
const git = new GitClient2(outputDir);
|
|
352726
353064
|
this.git = git;
|
|
@@ -352731,6 +353069,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352731
353069
|
this.committer = new ReplayCommitter(git, outputDir);
|
|
352732
353070
|
}
|
|
352733
353071
|
async runReplay(options2) {
|
|
353072
|
+
this.warnings = [];
|
|
352734
353073
|
if (options2?.skipApplication) {
|
|
352735
353074
|
return this.handleSkipApplication(options2);
|
|
352736
353075
|
}
|
|
@@ -352804,6 +353143,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352804
353143
|
);
|
|
352805
353144
|
}
|
|
352806
353145
|
this.lockManager.save();
|
|
353146
|
+
this.detector.warnings.push(...this.lockManager.warnings);
|
|
352807
353147
|
}
|
|
352808
353148
|
determineFlow() {
|
|
352809
353149
|
try {
|
|
@@ -352870,6 +353210,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352870
353210
|
}
|
|
352871
353211
|
this.lockManager.setReplaySkippedAt((/* @__PURE__ */ new Date()).toISOString());
|
|
352872
353212
|
this.lockManager.save();
|
|
353213
|
+
const credentialWarnings = [...this.lockManager.warnings];
|
|
352873
353214
|
if (!options2?.stageOnly) {
|
|
352874
353215
|
await this.committer.commitReplay(0);
|
|
352875
353216
|
} else {
|
|
@@ -352881,13 +353222,15 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352881
353222
|
patchesApplied: 0,
|
|
352882
353223
|
patchesWithConflicts: 0,
|
|
352883
353224
|
patchesSkipped: 0,
|
|
352884
|
-
conflicts: []
|
|
353225
|
+
conflicts: [],
|
|
353226
|
+
warnings: credentialWarnings.length > 0 ? credentialWarnings : void 0
|
|
352885
353227
|
};
|
|
352886
353228
|
}
|
|
352887
353229
|
async handleNoPatchesRegeneration(options2) {
|
|
352888
|
-
|
|
352889
|
-
const
|
|
353230
|
+
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
353231
|
+
const detectorWarnings = [...this.detector.warnings];
|
|
352890
353232
|
if (options2?.dryRun) {
|
|
353233
|
+
const dryRunWarnings = [...detectorWarnings, ...this.warnings];
|
|
352891
353234
|
return {
|
|
352892
353235
|
flow: "no-patches",
|
|
352893
353236
|
patchesDetected: newPatches.length,
|
|
@@ -352897,9 +353240,26 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352897
353240
|
patchesReverted: revertedPatchIds.length,
|
|
352898
353241
|
conflicts: [],
|
|
352899
353242
|
wouldApply: newPatches,
|
|
352900
|
-
warnings:
|
|
353243
|
+
warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0
|
|
352901
353244
|
};
|
|
352902
353245
|
}
|
|
353246
|
+
{
|
|
353247
|
+
const preLock = this.lockManager.read();
|
|
353248
|
+
const preCurrentGen = preLock.current_generation;
|
|
353249
|
+
const uniqueFiles = [...new Set(newPatches.flatMap((p10) => p10.files))];
|
|
353250
|
+
const absorbedFiles = /* @__PURE__ */ new Set();
|
|
353251
|
+
for (const file4 of uniqueFiles) {
|
|
353252
|
+
const diff2 = await this.git.exec(["diff", preCurrentGen, "HEAD", "--", file4]).catch(() => null);
|
|
353253
|
+
if (diff2 !== null && !diff2.trim()) {
|
|
353254
|
+
absorbedFiles.add(file4);
|
|
353255
|
+
}
|
|
353256
|
+
}
|
|
353257
|
+
if (absorbedFiles.size > 0) {
|
|
353258
|
+
newPatches = newPatches.filter(
|
|
353259
|
+
(p10) => p10.files.length === 0 || !p10.files.every((f10) => absorbedFiles.has(f10))
|
|
353260
|
+
);
|
|
353261
|
+
}
|
|
353262
|
+
}
|
|
352903
353263
|
for (const id2 of revertedPatchIds) {
|
|
352904
353264
|
try {
|
|
352905
353265
|
this.lockManager.removePatch(id2);
|
|
@@ -352918,6 +353278,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352918
353278
|
let results = [];
|
|
352919
353279
|
if (newPatches.length > 0) {
|
|
352920
353280
|
results = await this.applicator.applyPatches(newPatches);
|
|
353281
|
+
this._lastApplyResults = results;
|
|
352921
353282
|
this.revertConflictingFiles(results);
|
|
352922
353283
|
for (const result of results) {
|
|
352923
353284
|
if (result.status === "conflict") {
|
|
@@ -352932,6 +353293,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352932
353293
|
}
|
|
352933
353294
|
}
|
|
352934
353295
|
this.lockManager.save();
|
|
353296
|
+
this.warnings.push(...this.lockManager.warnings);
|
|
352935
353297
|
if (newPatches.length > 0) {
|
|
352936
353298
|
if (!options2?.stageOnly) {
|
|
352937
353299
|
const appliedCount = results.filter((r23) => r23.status === "applied").length;
|
|
@@ -352940,13 +353302,14 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352940
353302
|
await this.committer.stageAll();
|
|
352941
353303
|
}
|
|
352942
353304
|
}
|
|
353305
|
+
const warnings = [...detectorWarnings, ...this.warnings];
|
|
352943
353306
|
return this.buildReport("no-patches", newPatches, results, options2, warnings, rebaseCounts, void 0, revertedPatchIds.length);
|
|
352944
353307
|
}
|
|
352945
353308
|
async handleNormalRegeneration(options2) {
|
|
352946
353309
|
if (options2?.dryRun) {
|
|
352947
353310
|
const existingPatches2 = this.lockManager.getPatches();
|
|
352948
353311
|
const { patches: newPatches2, revertedPatchIds: dryRunReverted } = await this.detector.detectNewPatches();
|
|
352949
|
-
const warnings2 = [...this.detector.warnings];
|
|
353312
|
+
const warnings2 = [...this.detector.warnings, ...this.warnings];
|
|
352950
353313
|
const allPatches2 = [...existingPatches2, ...newPatches2];
|
|
352951
353314
|
return {
|
|
352952
353315
|
flow: "normal-regeneration",
|
|
@@ -352978,7 +353341,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352978
353341
|
}
|
|
352979
353342
|
existingPatches = this.lockManager.getPatches();
|
|
352980
353343
|
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
352981
|
-
const
|
|
353344
|
+
const detectorWarnings = [...this.detector.warnings];
|
|
352982
353345
|
if (removedByPreRebase.length > 0) {
|
|
352983
353346
|
const removedOriginalCommits = new Set(removedByPreRebase.map((p10) => p10.original_commit));
|
|
352984
353347
|
const removedOriginalMessages = new Set(removedByPreRebase.map((p10) => p10.original_message));
|
|
@@ -353010,6 +353373,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353010
353373
|
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
353011
353374
|
this.lockManager.addGeneration(genRecord);
|
|
353012
353375
|
const results = await this.applicator.applyPatches(allPatches);
|
|
353376
|
+
this._lastApplyResults = results;
|
|
353013
353377
|
this.revertConflictingFiles(results);
|
|
353014
353378
|
for (const result of results) {
|
|
353015
353379
|
if (result.status === "conflict") {
|
|
@@ -353031,12 +353395,14 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353031
353395
|
}
|
|
353032
353396
|
}
|
|
353033
353397
|
this.lockManager.save();
|
|
353398
|
+
this.warnings.push(...this.lockManager.warnings);
|
|
353034
353399
|
if (options2?.stageOnly) {
|
|
353035
353400
|
await this.committer.stageAll();
|
|
353036
353401
|
} else {
|
|
353037
353402
|
const appliedCount = results.filter((r23) => r23.status === "applied").length;
|
|
353038
353403
|
await this.committer.commitReplay(appliedCount, allPatches);
|
|
353039
353404
|
}
|
|
353405
|
+
const warnings = [...detectorWarnings, ...this.warnings];
|
|
353040
353406
|
return this.buildReport(
|
|
353041
353407
|
"normal-regeneration",
|
|
353042
353408
|
allPatches,
|
|
@@ -353060,6 +353426,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353060
353426
|
let keptAsUserOwned = 0;
|
|
353061
353427
|
const seenContentHashes = /* @__PURE__ */ new Set();
|
|
353062
353428
|
const absorbedPatchIds = /* @__PURE__ */ new Set();
|
|
353429
|
+
const fernignorePatterns = this.readFernignorePatterns();
|
|
353063
353430
|
for (const result of results) {
|
|
353064
353431
|
if (result.resolvedFiles && Object.keys(result.resolvedFiles).length > 0) {
|
|
353065
353432
|
const patch5 = result.patch;
|
|
@@ -353080,34 +353447,78 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353080
353447
|
const patch5 = result.patch;
|
|
353081
353448
|
if (patch5.base_generation === currentGenSha) continue;
|
|
353082
353449
|
try {
|
|
353083
|
-
const
|
|
353084
|
-
|
|
353450
|
+
const originalByResolved = {};
|
|
353451
|
+
if (result.resolvedFiles) {
|
|
353452
|
+
for (const [orig, resolved] of Object.entries(result.resolvedFiles)) {
|
|
353453
|
+
originalByResolved[resolved] = orig;
|
|
353454
|
+
}
|
|
353455
|
+
}
|
|
353456
|
+
const isUserOwnedPerFile = await Promise.all(
|
|
353085
353457
|
patch5.files.map(async (file4) => {
|
|
353086
|
-
if (file4 === ".fernignore")
|
|
353458
|
+
if (file4 === ".fernignore") return true;
|
|
353459
|
+
if (fernignorePatterns.some((p10) => file4 === p10 || (0, import_minimatch22.minimatch)(file4, p10))) {
|
|
353087
353460
|
return true;
|
|
353088
353461
|
}
|
|
353089
|
-
if (
|
|
353462
|
+
if (patch5.user_owned) return true;
|
|
353463
|
+
if (this.fileIsCreationInPatchContent(patch5.patch_content, originalByResolved[file4] ?? file4)) {
|
|
353090
353464
|
return true;
|
|
353091
353465
|
}
|
|
353092
353466
|
const content5 = await this.git.showFile(currentGenSha, file4);
|
|
353093
353467
|
return content5 === null;
|
|
353094
353468
|
})
|
|
353095
353469
|
);
|
|
353096
|
-
const
|
|
353097
|
-
|
|
353098
|
-
|
|
353099
|
-
|
|
353100
|
-
|
|
353470
|
+
const hasProtectedFiles = patch5.files.some(
|
|
353471
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p10) => file4 === p10 || (0, import_minimatch22.minimatch)(file4, p10))
|
|
353472
|
+
);
|
|
353473
|
+
const allUserOwned = isUserOwnedPerFile.every(Boolean);
|
|
353474
|
+
if (allUserOwned && patch5.files.length > 0) {
|
|
353475
|
+
const baseChanged = patch5.base_generation !== currentGenSha;
|
|
353476
|
+
const userDiff = await this.git.exec(["diff", currentGenSha, "--", ...patch5.files]).catch(() => null);
|
|
353477
|
+
if (userDiff != null && userDiff.trim()) {
|
|
353478
|
+
const newHash = this.detector.computeContentHash(userDiff);
|
|
353479
|
+
patch5.patch_content = userDiff;
|
|
353480
|
+
patch5.content_hash = newHash;
|
|
353481
|
+
}
|
|
353482
|
+
patch5.base_generation = currentGenSha;
|
|
353483
|
+
try {
|
|
353484
|
+
this.lockManager.updatePatch(patch5.id, {
|
|
353485
|
+
base_generation: currentGenSha,
|
|
353486
|
+
...!patch5.user_owned ? { user_owned: true } : {},
|
|
353487
|
+
...userDiff != null && userDiff.trim() ? { patch_content: patch5.patch_content, content_hash: patch5.content_hash } : {}
|
|
353488
|
+
});
|
|
353489
|
+
} catch {
|
|
353490
|
+
}
|
|
353491
|
+
patch5.user_owned = true;
|
|
353101
353492
|
keptAsUserOwned++;
|
|
353493
|
+
if (baseChanged) repointed++;
|
|
353102
353494
|
continue;
|
|
353103
353495
|
}
|
|
353104
353496
|
const diff2 = await this.git.exec(["diff", currentGenSha, "--", ...patch5.files]).catch(() => null);
|
|
353105
|
-
if (
|
|
353497
|
+
if (diff2 === null) continue;
|
|
353498
|
+
if (!diff2.trim()) {
|
|
353499
|
+
if (hasProtectedFiles) {
|
|
353500
|
+
patch5.base_generation = currentGenSha;
|
|
353501
|
+
try {
|
|
353502
|
+
this.lockManager.updatePatch(patch5.id, { base_generation: currentGenSha });
|
|
353503
|
+
} catch {
|
|
353504
|
+
}
|
|
353505
|
+
keptAsUserOwned++;
|
|
353506
|
+
continue;
|
|
353507
|
+
}
|
|
353106
353508
|
this.lockManager.removePatch(patch5.id);
|
|
353107
353509
|
absorbedPatchIds.add(patch5.id);
|
|
353108
353510
|
absorbed++;
|
|
353109
353511
|
continue;
|
|
353110
353512
|
}
|
|
353513
|
+
if (hasProtectedFiles) {
|
|
353514
|
+
patch5.base_generation = currentGenSha;
|
|
353515
|
+
try {
|
|
353516
|
+
this.lockManager.updatePatch(patch5.id, { base_generation: currentGenSha });
|
|
353517
|
+
} catch {
|
|
353518
|
+
}
|
|
353519
|
+
keptAsUserOwned++;
|
|
353520
|
+
continue;
|
|
353521
|
+
}
|
|
353111
353522
|
const newContentHash = this.detector.computeContentHash(diff2);
|
|
353112
353523
|
if (seenContentHashes.has(newContentHash)) {
|
|
353113
353524
|
this.lockManager.removePatch(patch5.id);
|
|
@@ -353116,17 +353527,89 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353116
353527
|
continue;
|
|
353117
353528
|
}
|
|
353118
353529
|
seenContentHashes.add(newContentHash);
|
|
353119
|
-
|
|
353120
|
-
|
|
353121
|
-
|
|
353122
|
-
|
|
353123
|
-
|
|
353530
|
+
patch5.base_generation = currentGenSha;
|
|
353531
|
+
patch5.patch_content = diff2;
|
|
353532
|
+
patch5.content_hash = newContentHash;
|
|
353533
|
+
try {
|
|
353534
|
+
this.lockManager.updatePatch(patch5.id, {
|
|
353535
|
+
base_generation: currentGenSha,
|
|
353536
|
+
patch_content: diff2,
|
|
353537
|
+
content_hash: newContentHash
|
|
353538
|
+
});
|
|
353539
|
+
} catch {
|
|
353540
|
+
}
|
|
353124
353541
|
contentRebased++;
|
|
353125
353542
|
} catch {
|
|
353126
353543
|
}
|
|
353127
353544
|
}
|
|
353128
353545
|
return { absorbed, repointed, contentRebased, keptAsUserOwned, absorbedPatchIds };
|
|
353129
353546
|
}
|
|
353547
|
+
/**
|
|
353548
|
+
* Determine whether `file` is user-owned (never produced by the generator).
|
|
353549
|
+
*
|
|
353550
|
+
* Resolution order (first match wins):
|
|
353551
|
+
* 1. `patch.user_owned === true` — authoritative, persisted across cycles.
|
|
353552
|
+
* 2. `.fernignore` itself, or file matches a .fernignore pattern.
|
|
353553
|
+
* 3. Legacy-safe signal: `patch_content` shows this file as a `--- /dev/null`
|
|
353554
|
+
* creation. Works for patches that predate the `user_owned` field or
|
|
353555
|
+
* whose flag was lost. Immune to rename tricks — relies on raw patch text.
|
|
353556
|
+
* 4. Tree-based check against `patch.base_generation` when it is reachable
|
|
353557
|
+
* AND distinct from `currentGenSha`. If the base tree is unreachable
|
|
353558
|
+
* (shallow clone / aggressive `git gc --prune`), emit a one-time
|
|
353559
|
+
* customer-actionable warning (deduplicated via warnedGens) and
|
|
353560
|
+
* conservatively treat as user-owned — strictly safer than silent
|
|
353561
|
+
* absorption (FER-9809).
|
|
353562
|
+
* 5. Final fallback — check `currentGenSha` when there is no usable base
|
|
353563
|
+
* (legacy one-gen lockfile). Preserves pre-fix behavior for that edge.
|
|
353564
|
+
*
|
|
353565
|
+
* `originalFileName` is the pre-rename path when the generator moved the
|
|
353566
|
+
* file between the patch's base and the current generation. Tree lookups
|
|
353567
|
+
* at ancestor generations use the original path — that's where the
|
|
353568
|
+
* generator produced the content.
|
|
353569
|
+
*/
|
|
353570
|
+
async isFileUserOwned(file4, patch5, currentGenSha, fernignorePatterns, warnedGens, originalFileName) {
|
|
353571
|
+
if (patch5.user_owned) return true;
|
|
353572
|
+
if (file4 === ".fernignore") return true;
|
|
353573
|
+
if (fernignorePatterns.some((p10) => file4 === p10 || (0, import_minimatch22.minimatch)(file4, p10))) return true;
|
|
353574
|
+
if (this.fileIsCreationInPatchContent(patch5.patch_content, originalFileName ?? file4)) {
|
|
353575
|
+
return true;
|
|
353576
|
+
}
|
|
353577
|
+
const base4 = patch5.base_generation;
|
|
353578
|
+
if (base4 && base4 !== currentGenSha) {
|
|
353579
|
+
const reachable = await this.git.commitExists(base4) || await this.git.treeExists(base4);
|
|
353580
|
+
if (!reachable) {
|
|
353581
|
+
if (!warnedGens.has(base4)) {
|
|
353582
|
+
warnedGens.add(base4);
|
|
353583
|
+
this.warnings.push(
|
|
353584
|
+
`Base generation ${base4.slice(0, 7)} is unreachable (shallow clone or pruned history). Treating affected files as user-owned to avoid silent data loss. Run \`git fetch --unshallow\` or avoid \`git gc --prune\` to restore full history.`
|
|
353585
|
+
);
|
|
353586
|
+
}
|
|
353587
|
+
return true;
|
|
353588
|
+
}
|
|
353589
|
+
return await this.git.showFile(base4, originalFileName ?? file4) === null;
|
|
353590
|
+
}
|
|
353591
|
+
return await this.git.showFile(currentGenSha, originalFileName ?? file4) === null;
|
|
353592
|
+
}
|
|
353593
|
+
/**
|
|
353594
|
+
* Returns true if `file`'s diff section in `patchContent` starts with
|
|
353595
|
+
* `--- /dev/null`, meaning this file was introduced by the patch (user
|
|
353596
|
+
* creation). Used as a legacy-safe fallback when `patch.user_owned` is
|
|
353597
|
+
* absent or cleared.
|
|
353598
|
+
*/
|
|
353599
|
+
fileIsCreationInPatchContent(patchContent, file4) {
|
|
353600
|
+
const lines = patchContent.split("\n");
|
|
353601
|
+
let inFileSection = false;
|
|
353602
|
+
for (const line of lines) {
|
|
353603
|
+
if (line.startsWith("diff --git ")) {
|
|
353604
|
+
inFileSection = line.includes(` b/${file4}`) || line.endsWith(` b/${file4}`);
|
|
353605
|
+
continue;
|
|
353606
|
+
}
|
|
353607
|
+
if (inFileSection && line.startsWith("--- ")) {
|
|
353608
|
+
return line === "--- /dev/null";
|
|
353609
|
+
}
|
|
353610
|
+
}
|
|
353611
|
+
return false;
|
|
353612
|
+
}
|
|
353130
353613
|
/**
|
|
353131
353614
|
* For conflict patches with mixed results (some files merged, some conflicted),
|
|
353132
353615
|
* check if the cleanly merged files were absorbed by the generator (empty diff).
|
|
@@ -353140,13 +353623,14 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353140
353623
|
const cleanFiles = result.fileResults.filter((f10) => f10.status === "merged").map((f10) => f10.file);
|
|
353141
353624
|
if (cleanFiles.length === 0) return;
|
|
353142
353625
|
const patch5 = result.patch;
|
|
353626
|
+
if (patch5.user_owned) return;
|
|
353143
353627
|
const fernignorePatterns = this.readFernignorePatterns();
|
|
353628
|
+
const warnedGens = /* @__PURE__ */ new Set();
|
|
353144
353629
|
const generatorCleanFiles = [];
|
|
353145
353630
|
for (const file4 of cleanFiles) {
|
|
353146
|
-
if (file4
|
|
353147
|
-
|
|
353148
|
-
|
|
353149
|
-
if (content5 === null) continue;
|
|
353631
|
+
if (await this.isFileUserOwned(file4, patch5, currentGenSha, fernignorePatterns, warnedGens)) {
|
|
353632
|
+
continue;
|
|
353633
|
+
}
|
|
353150
353634
|
generatorCleanFiles.push(file4);
|
|
353151
353635
|
}
|
|
353152
353636
|
if (generatorCleanFiles.length === 0) return;
|
|
@@ -353183,7 +353667,35 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353183
353667
|
let conflictResolved = 0;
|
|
353184
353668
|
let conflictAbsorbed = 0;
|
|
353185
353669
|
let contentRefreshed = 0;
|
|
353670
|
+
const fernignorePatterns = this.readFernignorePatterns();
|
|
353671
|
+
const warnedGens = /* @__PURE__ */ new Set();
|
|
353672
|
+
const reachabilityCache = /* @__PURE__ */ new Map();
|
|
353673
|
+
const isReachable = async (sha) => {
|
|
353674
|
+
const cached2 = reachabilityCache.get(sha);
|
|
353675
|
+
if (cached2 !== void 0) return cached2;
|
|
353676
|
+
const reachable = await this.git.commitExists(sha) || await this.git.treeExists(sha);
|
|
353677
|
+
reachabilityCache.set(sha, reachable);
|
|
353678
|
+
return reachable;
|
|
353679
|
+
};
|
|
353680
|
+
const allPatchFilesUserOwned = async (patch5) => {
|
|
353681
|
+
if (patch5.user_owned) return true;
|
|
353682
|
+
if (patch5.files.length === 0) return false;
|
|
353683
|
+
for (const file4 of patch5.files) {
|
|
353684
|
+
if (!await this.isFileUserOwned(file4, patch5, currentGen, fernignorePatterns, warnedGens)) {
|
|
353685
|
+
return false;
|
|
353686
|
+
}
|
|
353687
|
+
}
|
|
353688
|
+
return true;
|
|
353689
|
+
};
|
|
353186
353690
|
for (const patch5 of patches) {
|
|
353691
|
+
if (patch5.base_generation && patch5.base_generation !== currentGen && !warnedGens.has(patch5.base_generation)) {
|
|
353692
|
+
if (!await isReachable(patch5.base_generation)) {
|
|
353693
|
+
warnedGens.add(patch5.base_generation);
|
|
353694
|
+
this.warnings.push(
|
|
353695
|
+
`Base generation ${patch5.base_generation.slice(0, 7)} is unreachable (shallow clone or pruned history). Treating affected files as user-owned to avoid silent data loss. Run \`git fetch --unshallow\` or avoid \`git gc --prune\` to restore full history.`
|
|
353696
|
+
);
|
|
353697
|
+
}
|
|
353698
|
+
}
|
|
353187
353699
|
if (patch5.status != null) {
|
|
353188
353700
|
delete patch5.status;
|
|
353189
353701
|
continue;
|
|
@@ -353193,6 +353705,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353193
353705
|
const diff2 = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch5.files]).catch(() => null);
|
|
353194
353706
|
if (diff2 === null) continue;
|
|
353195
353707
|
if (!diff2.trim()) {
|
|
353708
|
+
if (await allPatchFilesUserOwned(patch5)) continue;
|
|
353196
353709
|
this.lockManager.removePatch(patch5.id);
|
|
353197
353710
|
contentRefreshed++;
|
|
353198
353711
|
continue;
|
|
@@ -353204,6 +353717,12 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353204
353717
|
if (hasStaleMarkers) {
|
|
353205
353718
|
continue;
|
|
353206
353719
|
}
|
|
353720
|
+
const isProtected = patch5.files.some(
|
|
353721
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p10) => file4 === p10 || (0, import_minimatch22.minimatch)(file4, p10))
|
|
353722
|
+
);
|
|
353723
|
+
if (isProtected) {
|
|
353724
|
+
continue;
|
|
353725
|
+
}
|
|
353207
353726
|
const newContentHash = this.detector.computeContentHash(diff2);
|
|
353208
353727
|
if (newContentHash !== patch5.content_hash) {
|
|
353209
353728
|
const filesOutput = await this.git.exec(["diff", "--name-only", currentGen, "HEAD", "--", ...patch5.files]).catch(() => null);
|
|
@@ -353229,6 +353748,13 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353229
353748
|
const diff2 = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch5.files]).catch(() => null);
|
|
353230
353749
|
if (diff2 === null) continue;
|
|
353231
353750
|
if (!diff2.trim()) {
|
|
353751
|
+
if (await allPatchFilesUserOwned(patch5)) {
|
|
353752
|
+
try {
|
|
353753
|
+
this.lockManager.updatePatch(patch5.id, { base_generation: currentGen });
|
|
353754
|
+
} catch {
|
|
353755
|
+
}
|
|
353756
|
+
continue;
|
|
353757
|
+
}
|
|
353232
353758
|
this.lockManager.removePatch(patch5.id);
|
|
353233
353759
|
conflictAbsorbed++;
|
|
353234
353760
|
continue;
|
|
@@ -353736,7 +354262,10 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353736
354262
|
(0, import_node_fs42.writeFileSync)(gitattributesPath, content5, "utf-8");
|
|
353737
354263
|
}
|
|
353738
354264
|
function computeContentHash(patchContent) {
|
|
353739
|
-
const
|
|
354265
|
+
const lines = patchContent.split("\n");
|
|
354266
|
+
const diffStart = lines.findIndex((l14) => l14.startsWith("diff --git "));
|
|
354267
|
+
const relevantLines = diffStart > 0 ? lines.slice(diffStart) : lines;
|
|
354268
|
+
const normalized = relevantLines.filter((line) => !line.startsWith("index ")).join("\n").replace(/\n-- \n[\d]+\.[\d]+[^\n]*\s*$/, "");
|
|
353740
354269
|
return `sha256:${(0, import_node_crypto32.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
353741
354270
|
}
|
|
353742
354271
|
var import_minimatch42 = require_commonjs3();
|
|
@@ -354121,6 +354650,7 @@ var require_replay_run = __commonJS({
|
|
|
354121
354650
|
"use strict";
|
|
354122
354651
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
354123
354652
|
exports2.replayRun = replayRun;
|
|
354653
|
+
exports2.isTagMergedIntoHead = isTagMergedIntoHead;
|
|
354124
354654
|
var replay_1 = require_dist14();
|
|
354125
354655
|
var child_process_1 = require("child_process");
|
|
354126
354656
|
var fs_1 = require("fs");
|
|
@@ -354165,18 +354695,22 @@ var require_replay_run = __commonJS({
|
|
|
354165
354695
|
}
|
|
354166
354696
|
}
|
|
354167
354697
|
if (shouldSync && tagSha != null) {
|
|
354168
|
-
|
|
354169
|
-
|
|
354170
|
-
|
|
354171
|
-
|
|
354172
|
-
|
|
354173
|
-
|
|
354174
|
-
|
|
354175
|
-
|
|
354176
|
-
|
|
354177
|
-
|
|
354698
|
+
if (!isTagMergedIntoHead(outputDir, tagSha)) {
|
|
354699
|
+
logger4?.warn(`fern-generation-base tag ${tagSha} is not reachable from HEAD \u2014 skipping divergent-merge sync. The tag likely points at an unmerged generation (PR closed without merge).`);
|
|
354700
|
+
} else {
|
|
354701
|
+
const syncService = new replay_1.ReplayService(outputDir, { enabled: true });
|
|
354702
|
+
await syncService.syncFromDivergentMerge(tagSha, {
|
|
354703
|
+
cliVersion,
|
|
354704
|
+
generatorVersions,
|
|
354705
|
+
baseBranchHead: baseBranchHead ?? void 0
|
|
354706
|
+
});
|
|
354707
|
+
try {
|
|
354708
|
+
const freshLockManager = new replay_1.LockfileManager(outputDir);
|
|
354709
|
+
if (freshLockManager.exists()) {
|
|
354710
|
+
previousGenerationSha = freshLockManager.read().current_generation;
|
|
354711
|
+
}
|
|
354712
|
+
} catch {
|
|
354178
354713
|
}
|
|
354179
|
-
} catch {
|
|
354180
354714
|
}
|
|
354181
354715
|
}
|
|
354182
354716
|
}
|
|
@@ -354226,6 +354760,30 @@ var require_replay_run = __commonJS({
|
|
|
354226
354760
|
baseBranchHead: resolvedBaseBranchHead
|
|
354227
354761
|
};
|
|
354228
354762
|
}
|
|
354763
|
+
function isTagMergedIntoHead(cwd2, tagSha) {
|
|
354764
|
+
try {
|
|
354765
|
+
(0, child_process_1.execFileSync)("git", ["merge-base", "--is-ancestor", tagSha, "HEAD"], {
|
|
354766
|
+
cwd: cwd2,
|
|
354767
|
+
stdio: "pipe"
|
|
354768
|
+
});
|
|
354769
|
+
return true;
|
|
354770
|
+
} catch {
|
|
354771
|
+
}
|
|
354772
|
+
const tagTree = gitRevParse(cwd2, `${tagSha}^{tree}`);
|
|
354773
|
+
if (tagTree == null) {
|
|
354774
|
+
return false;
|
|
354775
|
+
}
|
|
354776
|
+
try {
|
|
354777
|
+
const trees = (0, child_process_1.execFileSync)("git", ["log", "HEAD", "--format=%T", "--max-count=500"], {
|
|
354778
|
+
cwd: cwd2,
|
|
354779
|
+
encoding: "utf-8",
|
|
354780
|
+
stdio: "pipe"
|
|
354781
|
+
});
|
|
354782
|
+
return trees.split("\n").some((t3) => t3.trim() === tagTree);
|
|
354783
|
+
} catch {
|
|
354784
|
+
return false;
|
|
354785
|
+
}
|
|
354786
|
+
}
|
|
354229
354787
|
function gitRevParse(cwd2, rev) {
|
|
354230
354788
|
try {
|
|
354231
354789
|
return (0, child_process_1.execFileSync)("git", ["rev-parse", "--verify", rev], {
|
|
@@ -521745,9 +522303,7 @@ function convertQueryParameterExample2(namedFullExamples) {
|
|
|
521745
522303
|
const result = {};
|
|
521746
522304
|
namedFullExamples.forEach((namedFullExample) => {
|
|
521747
522305
|
const convertedExample = convertFullExample2(namedFullExample.value);
|
|
521748
|
-
if (
|
|
521749
|
-
result[namedFullExample.name] = convertedExample[0];
|
|
521750
|
-
} else if (convertedExample != null) {
|
|
522306
|
+
if (convertedExample != null) {
|
|
521751
522307
|
result[namedFullExample.name] = convertedExample;
|
|
521752
522308
|
}
|
|
521753
522309
|
});
|
|
@@ -533039,8 +533595,8 @@ var ExampleEndpointFactory = class {
|
|
|
533039
533595
|
ignoreOptionals: true
|
|
533040
533596
|
}
|
|
533041
533597
|
});
|
|
533042
|
-
if (example != null && !isExamplePrimitive(example)) {
|
|
533043
|
-
this.logger.debug(`Expected a primitive example but got ${example.type} for query parameter ${queryParameter.name} for ${endpoint3.method.toUpperCase()} ${endpoint3.path}`);
|
|
533598
|
+
if (example != null && !isExamplePrimitive(example) && example.type !== "array") {
|
|
533599
|
+
this.logger.debug(`Expected a primitive or array example but got ${example.type} for query parameter ${queryParameter.name} for ${endpoint3.method.toUpperCase()} ${endpoint3.path}`);
|
|
533044
533600
|
example = void 0;
|
|
533045
533601
|
}
|
|
533046
533602
|
if (required6 && example == null) {
|
|
@@ -539549,10 +540105,7 @@ function convertParameters({ path: path98, httpMethod, parameters: parameters2,
|
|
|
539549
540105
|
const generatedName = getGeneratedTypeName(parameterBreadcrumbs, context3.options.preserveSchemaIds);
|
|
539550
540106
|
const isHeader = resolvedParameter.in === "header";
|
|
539551
540107
|
const [isOptional, isNullable] = context3.options.coerceOptionalSchemasToNullable && !isHeader ? [false, !isRequired2] : [!isRequired2, false];
|
|
539552
|
-
let schema2 = resolvedParameter.schema != null ? convertSchema2(resolvedParameter.schema, isOptional, isNullable, context3, parameterBreadcrumbs, source2, context3.namespace, false, /* @__PURE__ */ new Set(),
|
|
539553
|
-
schema: resolvedParameter,
|
|
539554
|
-
logger: context3.logger
|
|
539555
|
-
})) : isRequired2 ? SchemaWithExample.primitive({
|
|
540108
|
+
let schema2 = resolvedParameter.schema != null ? convertSchema2(resolvedParameter.schema, isOptional, isNullable, context3, parameterBreadcrumbs, source2, context3.namespace, false, /* @__PURE__ */ new Set(), getParameterExample(resolvedParameter, context3)) : isRequired2 ? SchemaWithExample.primitive({
|
|
539556
540109
|
nameOverride: void 0,
|
|
539557
540110
|
generatedName,
|
|
539558
540111
|
title: void 0,
|
|
@@ -539671,6 +540224,13 @@ function getExplodeForQueryParameter(parameter6) {
|
|
|
539671
540224
|
}
|
|
539672
540225
|
return explode === false ? void 0 : explode;
|
|
539673
540226
|
}
|
|
540227
|
+
function getParameterExample(parameter6, context3) {
|
|
540228
|
+
const arrayExample = getExampleAsArray({ schema: parameter6, logger: context3.logger });
|
|
540229
|
+
if (arrayExample != null) {
|
|
540230
|
+
return arrayExample;
|
|
540231
|
+
}
|
|
540232
|
+
return getExamplesString({ schema: parameter6, logger: context3.logger });
|
|
540233
|
+
}
|
|
539674
540234
|
function getExplodeForPathParameter(parameter6) {
|
|
539675
540235
|
const explode = parameter6.explode;
|
|
539676
540236
|
if (explode === void 0) {
|
|
@@ -561544,6 +562104,37 @@ function convertExampleEndpointCall2({ service, endpoint: endpoint3, example, ty
|
|
|
561544
562104
|
if (queryParameterDeclaration == null) {
|
|
561545
562105
|
throw new Error(`Query parameter ${wireKey} does not exist`);
|
|
561546
562106
|
}
|
|
562107
|
+
const isAllowMultiple = typeof queryParameterDeclaration !== "string" && queryParameterDeclaration["allow-multiple"] === true;
|
|
562108
|
+
const rawType = typeof queryParameterDeclaration === "string" ? queryParameterDeclaration : queryParameterDeclaration.type;
|
|
562109
|
+
let convertedValue;
|
|
562110
|
+
if (isAllowMultiple && Array.isArray(value)) {
|
|
562111
|
+
const items = value.map((item) => convertTypeReferenceExample({
|
|
562112
|
+
example: item,
|
|
562113
|
+
rawTypeBeingExemplified: rawType,
|
|
562114
|
+
typeResolver,
|
|
562115
|
+
exampleResolver,
|
|
562116
|
+
fileContainingRawTypeReference: file4,
|
|
562117
|
+
fileContainingExample: file4,
|
|
562118
|
+
workspace
|
|
562119
|
+
}));
|
|
562120
|
+
convertedValue = {
|
|
562121
|
+
jsonExample: value,
|
|
562122
|
+
shape: ExampleTypeReferenceShape.container(ExampleContainer.list({
|
|
562123
|
+
list: items,
|
|
562124
|
+
itemType: file4.parseTypeReference(rawType)
|
|
562125
|
+
}))
|
|
562126
|
+
};
|
|
562127
|
+
} else {
|
|
562128
|
+
convertedValue = convertTypeReferenceExample({
|
|
562129
|
+
example: value,
|
|
562130
|
+
rawTypeBeingExemplified: rawType,
|
|
562131
|
+
typeResolver,
|
|
562132
|
+
exampleResolver,
|
|
562133
|
+
fileContainingRawTypeReference: file4,
|
|
562134
|
+
fileContainingExample: file4,
|
|
562135
|
+
workspace
|
|
562136
|
+
});
|
|
562137
|
+
}
|
|
561547
562138
|
return {
|
|
561548
562139
|
name: file4.casingsGenerator.generateNameAndWireValue({
|
|
561549
562140
|
name: getQueryParameterName({
|
|
@@ -561552,15 +562143,7 @@ function convertExampleEndpointCall2({ service, endpoint: endpoint3, example, ty
|
|
|
561552
562143
|
}).name,
|
|
561553
562144
|
wireValue: wireKey
|
|
561554
562145
|
}),
|
|
561555
|
-
value:
|
|
561556
|
-
example: value,
|
|
561557
|
-
rawTypeBeingExemplified: typeof queryParameterDeclaration === "string" ? queryParameterDeclaration : queryParameterDeclaration.type,
|
|
561558
|
-
typeResolver,
|
|
561559
|
-
exampleResolver,
|
|
561560
|
-
fileContainingRawTypeReference: file4,
|
|
561561
|
-
fileContainingExample: file4,
|
|
561562
|
-
workspace
|
|
561563
|
-
}),
|
|
562146
|
+
value: convertedValue,
|
|
561564
562147
|
shape: getQueryParamaterDeclationShape({ queryParameter: queryParameterDeclaration })
|
|
561565
562148
|
};
|
|
561566
562149
|
}) : [],
|
|
@@ -569009,7 +569592,7 @@ var ValidEndpointPathRule = {
|
|
|
569009
569592
|
};
|
|
569010
569593
|
|
|
569011
569594
|
// ../fern-definition/validator/lib/rules/valid-example-endpoint-call/validateExampleEndpointCallParameters.js
|
|
569012
|
-
function validateExampleEndpointCallParameters({ allDeclarations = {}, examples, parameterDisplayName, typeResolver, exampleResolver, workspace, getRawType, breadcrumbs }) {
|
|
569595
|
+
function validateExampleEndpointCallParameters({ allDeclarations = {}, examples, parameterDisplayName, typeResolver, exampleResolver, workspace, getRawType, isAllowMultiple, breadcrumbs }) {
|
|
569013
569596
|
const violations = [];
|
|
569014
569597
|
const requiredParameters = Object.entries(allDeclarations).reduce((acc, [key2, parameter6]) => {
|
|
569015
569598
|
const rawType = getRawType(parameter6);
|
|
@@ -569044,18 +569627,21 @@ function validateExampleEndpointCallParameters({ allDeclarations = {}, examples,
|
|
|
569044
569627
|
} else {
|
|
569045
569628
|
const rawType = getRawType(expectedType);
|
|
569046
569629
|
if (rawType != null) {
|
|
569047
|
-
|
|
569048
|
-
|
|
569049
|
-
|
|
569050
|
-
|
|
569051
|
-
|
|
569052
|
-
|
|
569053
|
-
|
|
569054
|
-
|
|
569055
|
-
|
|
569056
|
-
|
|
569057
|
-
|
|
569058
|
-
|
|
569630
|
+
const examplesToValidate = isAllowMultiple?.(expectedType) && Array.isArray(exampleParameter) ? exampleParameter : [exampleParameter];
|
|
569631
|
+
for (const singleExample of examplesToValidate) {
|
|
569632
|
+
violations.push(...examples_exports6.validateTypeReferenceExample({
|
|
569633
|
+
rawTypeReference: rawType.rawType,
|
|
569634
|
+
example: singleExample,
|
|
569635
|
+
file: rawType.file,
|
|
569636
|
+
workspace,
|
|
569637
|
+
typeResolver,
|
|
569638
|
+
exampleResolver,
|
|
569639
|
+
breadcrumbs,
|
|
569640
|
+
depth: 0
|
|
569641
|
+
}).map((val) => {
|
|
569642
|
+
return { severity: val.severity ?? "fatal", message: val.message };
|
|
569643
|
+
}));
|
|
569644
|
+
}
|
|
569059
569645
|
}
|
|
569060
569646
|
}
|
|
569061
569647
|
}
|
|
@@ -569344,6 +569930,7 @@ var ValidExampleEndpointCallRule = {
|
|
|
569344
569930
|
}),
|
|
569345
569931
|
rawType: typeof queryParameter === "string" ? queryParameter : queryParameter.type
|
|
569346
569932
|
}),
|
|
569933
|
+
isAllowMultiple: (queryParameter) => typeof queryParameter !== "string" && queryParameter["allow-multiple"] === true,
|
|
569347
569934
|
breadcrumbs: ["query-parameters"]
|
|
569348
569935
|
});
|
|
569349
569936
|
},
|
|
@@ -621394,7 +621981,7 @@ var AccessTokenPosthogManager = class {
|
|
|
621394
621981
|
properties: {
|
|
621395
621982
|
...event,
|
|
621396
621983
|
...event.properties,
|
|
621397
|
-
version: "4.
|
|
621984
|
+
version: "4.76.0-2-g00f8bb5e19c",
|
|
621398
621985
|
usingAccessToken: true
|
|
621399
621986
|
}
|
|
621400
621987
|
});
|
|
@@ -621448,7 +622035,7 @@ var UserPosthogManager = class {
|
|
|
621448
622035
|
distinctId: this.userId ?? await this.getPersistedDistinctId(),
|
|
621449
622036
|
event: "CLI",
|
|
621450
622037
|
properties: {
|
|
621451
|
-
version: "4.
|
|
622038
|
+
version: "4.76.0-2-g00f8bb5e19c",
|
|
621452
622039
|
...event,
|
|
621453
622040
|
...event.properties,
|
|
621454
622041
|
usingAccessToken: false,
|
|
@@ -838658,7 +839245,7 @@ var LOCAL_STORAGE_FOLDER4 = ".fern-dev";
|
|
|
838658
839245
|
var LOGS_FOLDER_NAME = "logs";
|
|
838659
839246
|
var MAX_LOGS_DIR_SIZE_BYTES = 100 * 1024 * 1024;
|
|
838660
839247
|
function getCliSource() {
|
|
838661
|
-
const version7 = "4.
|
|
839248
|
+
const version7 = "4.76.0-2-g00f8bb5e19c";
|
|
838662
839249
|
return `cli@${version7}`;
|
|
838663
839250
|
}
|
|
838664
839251
|
var DebugLogger = class {
|
|
@@ -839024,7 +839611,7 @@ function isWithinOutputDir(resolvedPath, outputDir) {
|
|
|
839024
839611
|
const rel2 = import_path55.default.relative(outputDir, resolvedPath);
|
|
839025
839612
|
return !rel2.startsWith("..") && !import_path55.default.isAbsolute(rel2);
|
|
839026
839613
|
}
|
|
839027
|
-
function resolveWindowsSymlinks(outputDir, symlinks, logger4) {
|
|
839614
|
+
function resolveWindowsSymlinks(outputDir, symlinks, logger4, onProgress) {
|
|
839028
839615
|
if (symlinks.length === 0) {
|
|
839029
839616
|
return;
|
|
839030
839617
|
}
|
|
@@ -839094,6 +839681,7 @@ function resolveWindowsSymlinks(outputDir, symlinks, logger4) {
|
|
|
839094
839681
|
logger4.debug(`Failed to resolve symlink ${symlinkPath}: ${error50}`);
|
|
839095
839682
|
failed++;
|
|
839096
839683
|
}
|
|
839684
|
+
onProgress?.(junctions + copies + alreadyExisted + failed, symlinks.length);
|
|
839097
839685
|
}
|
|
839098
839686
|
logger4.debug(`Symlink resolution: ${junctions} junctions, ${copies} file copies, ${alreadyExisted} pre-existing, ${failed} failed (${fallbackUsed} via fallback) out of ${symlinks.length}`);
|
|
839099
839687
|
const standaloneNM = import_path55.default.join(outputDir, "standalone", "node_modules");
|
|
@@ -839232,7 +839820,7 @@ async function downloadBundle({ bucketUrl, logger: logger4, preferCached, app =
|
|
|
839232
839820
|
hideCursor: true
|
|
839233
839821
|
});
|
|
839234
839822
|
unzipProgressBar.start(100, 0);
|
|
839235
|
-
const UNZIP_DURATION_MS = 3e4;
|
|
839823
|
+
const UNZIP_DURATION_MS = PLATFORM_IS_WINDOWS ? 6e4 : 3e4;
|
|
839236
839824
|
const startTime = Date.now();
|
|
839237
839825
|
unzipInterval = setInterval(() => {
|
|
839238
839826
|
const elapsed = Date.now() - startTime;
|
|
@@ -839269,7 +839857,21 @@ async function downloadBundle({ bucketUrl, logger: logger4, preferCached, app =
|
|
|
839269
839857
|
}
|
|
839270
839858
|
}
|
|
839271
839859
|
if (PLATFORM_IS_WINDOWS && collectedSymlinks.length > 0) {
|
|
839272
|
-
|
|
839860
|
+
let symlinkProgressBar;
|
|
839861
|
+
if (app) {
|
|
839862
|
+
symlinkProgressBar = new import_cli_progress.default.SingleBar({
|
|
839863
|
+
format: `${DOCS_PREFIX} ${formatProgressLabel("Patching symlinks")} [{bar}] {percentage}% | {value}/{total}`,
|
|
839864
|
+
barCompleteChar: "\u2588",
|
|
839865
|
+
barIncompleteChar: "\u2591",
|
|
839866
|
+
hideCursor: true
|
|
839867
|
+
});
|
|
839868
|
+
symlinkProgressBar.start(collectedSymlinks.length, 0);
|
|
839869
|
+
}
|
|
839870
|
+
resolveWindowsSymlinks(absolutePathToBundleFolder, collectedSymlinks, logger4, symlinkProgressBar ? (resolved, total) => symlinkProgressBar?.update(resolved) : void 0);
|
|
839871
|
+
if (symlinkProgressBar) {
|
|
839872
|
+
symlinkProgressBar.update(collectedSymlinks.length);
|
|
839873
|
+
symlinkProgressBar.stop();
|
|
839874
|
+
}
|
|
839273
839875
|
}
|
|
839274
839876
|
await (0, import_promises78.writeFile)(eTagFilepath, eTag);
|
|
839275
839877
|
logger4.debug(`Downloaded bundle to ${absolutePathToBundleFolder}`);
|
|
@@ -849958,7 +850560,7 @@ var LegacyDocsPublisher = class {
|
|
|
849958
850560
|
previewId,
|
|
849959
850561
|
disableTemplates: void 0,
|
|
849960
850562
|
skipUpload,
|
|
849961
|
-
cliVersion: "4.
|
|
850563
|
+
cliVersion: "4.76.0-2-g00f8bb5e19c"
|
|
849962
850564
|
});
|
|
849963
850565
|
if (taskContext.getResult() === TaskResult.Failure) {
|
|
849964
850566
|
return { success: false };
|
|
@@ -924416,7 +925018,7 @@ var CliContext = class _CliContext {
|
|
|
924416
925018
|
if (false) {
|
|
924417
925019
|
this.logger.error("CLI_VERSION is not defined");
|
|
924418
925020
|
}
|
|
924419
|
-
return "4.
|
|
925021
|
+
return "4.76.0-2-g00f8bb5e19c";
|
|
924420
925022
|
}
|
|
924421
925023
|
getCliName() {
|
|
924422
925024
|
if (false) {
|