@fern-api/fern-api-dev 4.75.0 → 4.76.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/cli.cjs +611 -75
- package/package.json +1 -1
package/cli.cjs
CHANGED
|
@@ -350912,9 +350912,9 @@ var require_diff3 = __commonJS({
|
|
|
350912
350912
|
}
|
|
350913
350913
|
});
|
|
350914
350914
|
|
|
350915
|
-
// ../../../node_modules/.pnpm/@fern-api+replay@0.
|
|
350915
|
+
// ../../../node_modules/.pnpm/@fern-api+replay@0.11.0/node_modules/@fern-api/replay/dist/index.cjs
|
|
350916
350916
|
var require_dist14 = __commonJS({
|
|
350917
|
-
"../../../node_modules/.pnpm/@fern-api+replay@0.
|
|
350917
|
+
"../../../node_modules/.pnpm/@fern-api+replay@0.11.0/node_modules/@fern-api/replay/dist/index.cjs"(exports2, module4) {
|
|
350918
350918
|
"use strict";
|
|
350919
350919
|
var __defProp4 = Object.defineProperty;
|
|
350920
350920
|
var __getOwnPropDesc4 = Object.getOwnPropertyDescriptor;
|
|
@@ -351005,13 +351005,30 @@ var require_dist14 = __commonJS({
|
|
|
351005
351005
|
const output2 = await this.exec(["rev-parse", `${commitSha}^@`]);
|
|
351006
351006
|
return output2.trim().split("\n").filter(Boolean);
|
|
351007
351007
|
}
|
|
351008
|
+
/**
|
|
351009
|
+
* Detects renames and copies between two trees.
|
|
351010
|
+
*
|
|
351011
|
+
* Both `R` (rename) and `C` (copy) entries are returned as `{from, to}` pairs.
|
|
351012
|
+
* Copies are treated as rename-equivalent because the generator may produce the
|
|
351013
|
+
* new path while the old path remains in the tree (e.g., imperfect cleanup,
|
|
351014
|
+
* customer edits at the old path, or test helpers that only write new files).
|
|
351015
|
+
* Callers that care about "is the source still present" should verify via
|
|
351016
|
+
* `showFile(toTree, from)`.
|
|
351017
|
+
*/
|
|
351008
351018
|
async detectRenames(fromTree, toTree) {
|
|
351009
351019
|
try {
|
|
351010
|
-
const output2 = await this.exec([
|
|
351020
|
+
const output2 = await this.exec([
|
|
351021
|
+
"diff",
|
|
351022
|
+
"--find-renames",
|
|
351023
|
+
"--find-copies",
|
|
351024
|
+
"--name-status",
|
|
351025
|
+
fromTree,
|
|
351026
|
+
toTree
|
|
351027
|
+
]);
|
|
351011
351028
|
const renames = [];
|
|
351012
351029
|
for (const line of output2.trim().split("\n")) {
|
|
351013
351030
|
if (!line) continue;
|
|
351014
|
-
if (line.startsWith("R")) {
|
|
351031
|
+
if (line.startsWith("R") || line.startsWith("C")) {
|
|
351015
351032
|
const parts = line.split(" ");
|
|
351016
351033
|
if (parts.length >= 3) {
|
|
351017
351034
|
renames.push({ from: parts[1], to: parts[2] });
|
|
@@ -351305,6 +351322,7 @@ var require_dist14 = __commonJS({
|
|
|
351305
351322
|
});
|
|
351306
351323
|
var index_exports = {};
|
|
351307
351324
|
__export4(index_exports, {
|
|
351325
|
+
CREDENTIAL_PATTERNS: () => CREDENTIAL_PATTERNS,
|
|
351308
351326
|
FERN_BOT_EMAIL: () => FERN_BOT_EMAIL,
|
|
351309
351327
|
FERN_BOT_LOGIN: () => FERN_BOT_LOGIN,
|
|
351310
351328
|
FERN_BOT_NAME: () => FERN_BOT_NAME,
|
|
@@ -351316,19 +351334,53 @@ var require_dist14 = __commonJS({
|
|
|
351316
351334
|
ReplayCommitter: () => ReplayCommitter,
|
|
351317
351335
|
ReplayDetector: () => ReplayDetector,
|
|
351318
351336
|
ReplayService: () => ReplayService,
|
|
351337
|
+
SENSITIVE_FILE_PATTERNS: () => SENSITIVE_FILE_PATTERNS,
|
|
351319
351338
|
bootstrap: () => bootstrap,
|
|
351320
351339
|
forget: () => forget,
|
|
351321
351340
|
isGenerationCommit: () => isGenerationCommit,
|
|
351322
351341
|
isReplayCommit: () => isReplayCommit,
|
|
351323
351342
|
isRevertCommit: () => isRevertCommit,
|
|
351343
|
+
isSensitiveFile: () => isSensitiveFile,
|
|
351324
351344
|
parseRevertedMessage: () => parseRevertedMessage,
|
|
351325
351345
|
parseRevertedSha: () => parseRevertedSha,
|
|
351326
351346
|
reset: () => reset2,
|
|
351327
351347
|
resolve: () => resolve13,
|
|
351348
|
+
scanForCredentials: () => scanForCredentials,
|
|
351328
351349
|
status: () => status,
|
|
351329
351350
|
threeWayMerge: () => threeWayMerge
|
|
351330
351351
|
});
|
|
351331
351352
|
module4.exports = __toCommonJS3(index_exports);
|
|
351353
|
+
var CREDENTIAL_PATTERNS = [
|
|
351354
|
+
{ name: "PRIVATE KEY block", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
|
|
351355
|
+
{ name: "OPENSSH key", re: /-----BEGIN OPENSSH PRIVATE KEY-----/ },
|
|
351356
|
+
{ name: "RSA key", re: /-----BEGIN RSA PRIVATE KEY-----/ },
|
|
351357
|
+
{ name: "CERTIFICATE", re: /-----BEGIN CERTIFICATE-----/ },
|
|
351358
|
+
{ name: "TOKEN=", re: /\bTOKEN\s*=\s*["']?[A-Za-z0-9._\-+/=]{10,}/ },
|
|
351359
|
+
{ name: "api_key=", re: /\bapi[_-]?key\s*=\s*["']?[A-Za-z0-9._\-+/=]{10,}/i },
|
|
351360
|
+
{ name: "password=", re: /\bpassword\s*=\s*["']?[^\s"']{6,}/i },
|
|
351361
|
+
{ name: "AWS_SECRET_ACCESS_KEY", re: /AWS_SECRET_ACCESS_KEY/ },
|
|
351362
|
+
{ name: "JWT (eyJ...)", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/ }
|
|
351363
|
+
];
|
|
351364
|
+
var SENSITIVE_FILE_PATTERNS = [
|
|
351365
|
+
/(^|\/)\.env(\.[^/]+)?$/,
|
|
351366
|
+
/\.pem$/,
|
|
351367
|
+
/\.key$/,
|
|
351368
|
+
/\.pkce_state\.json$/,
|
|
351369
|
+
/(^|\/)id_rsa$/,
|
|
351370
|
+
/(^|\/)id_ed25519$/
|
|
351371
|
+
];
|
|
351372
|
+
function isSensitiveFile(filePath) {
|
|
351373
|
+
return SENSITIVE_FILE_PATTERNS.some((re17) => re17.test(filePath));
|
|
351374
|
+
}
|
|
351375
|
+
function scanForCredentials(content5) {
|
|
351376
|
+
const matches2 = [];
|
|
351377
|
+
for (const { name: name2, re: re17 } of CREDENTIAL_PATTERNS) {
|
|
351378
|
+
if (re17.test(content5)) {
|
|
351379
|
+
matches2.push(name2);
|
|
351380
|
+
}
|
|
351381
|
+
}
|
|
351382
|
+
return matches2;
|
|
351383
|
+
}
|
|
351332
351384
|
init_GitClient();
|
|
351333
351385
|
var FERN_BOT_NAME = "fern-api";
|
|
351334
351386
|
var FERN_BOT_EMAIL = "115122769+fern-api[bot]@users.noreply.github.com";
|
|
@@ -351370,6 +351422,7 @@ var require_dist14 = __commonJS({
|
|
|
351370
351422
|
var LockfileManager = class {
|
|
351371
351423
|
outputDir;
|
|
351372
351424
|
lock = null;
|
|
351425
|
+
warnings = [];
|
|
351373
351426
|
constructor(outputDir) {
|
|
351374
351427
|
this.outputDir = outputDir;
|
|
351375
351428
|
}
|
|
@@ -351418,6 +351471,27 @@ var require_dist14 = __commonJS({
|
|
|
351418
351471
|
if (!this.lock) {
|
|
351419
351472
|
throw new Error("No lockfile data to save. Call read() or initialize() first.");
|
|
351420
351473
|
}
|
|
351474
|
+
this.warnings.length = 0;
|
|
351475
|
+
const cleanPatches = [];
|
|
351476
|
+
for (const patch5 of this.lock.patches) {
|
|
351477
|
+
const credentialMatches = scanForCredentials(patch5.patch_content);
|
|
351478
|
+
const sensitiveFiles = patch5.files.filter((f10) => isSensitiveFile(f10));
|
|
351479
|
+
if (credentialMatches.length > 0 || sensitiveFiles.length > 0) {
|
|
351480
|
+
const reasons = [];
|
|
351481
|
+
if (credentialMatches.length > 0) {
|
|
351482
|
+
reasons.push(`credential patterns: ${credentialMatches.join(", ")}`);
|
|
351483
|
+
}
|
|
351484
|
+
if (sensitiveFiles.length > 0) {
|
|
351485
|
+
reasons.push(`sensitive files: ${sensitiveFiles.join(", ")}`);
|
|
351486
|
+
}
|
|
351487
|
+
this.warnings.push(
|
|
351488
|
+
`Patch ${patch5.id} removed from lockfile: ${reasons.join("; ")}. This prevents credential exposure in committed lockfiles.`
|
|
351489
|
+
);
|
|
351490
|
+
} else {
|
|
351491
|
+
cleanPatches.push(patch5);
|
|
351492
|
+
}
|
|
351493
|
+
}
|
|
351494
|
+
this.lock.patches = cleanPatches;
|
|
351421
351495
|
const dir = (0, import_node_path41.dirname)(this.lockfilePath);
|
|
351422
351496
|
if (!(0, import_node_fs32.existsSync)(dir)) {
|
|
351423
351497
|
(0, import_node_fs32.mkdirSync)(dir, { recursive: true });
|
|
@@ -351520,6 +351594,37 @@ var require_dist14 = __commonJS({
|
|
|
351520
351594
|
};
|
|
351521
351595
|
var import_node_crypto7 = require("crypto");
|
|
351522
351596
|
var INFRASTRUCTURE_FILES = /* @__PURE__ */ new Set([".fernignore"]);
|
|
351597
|
+
function parsePatchFileHeaders(patchContent) {
|
|
351598
|
+
const results = [];
|
|
351599
|
+
let currentPath = null;
|
|
351600
|
+
let currentIsCreate = false;
|
|
351601
|
+
let currentIsDelete = false;
|
|
351602
|
+
const flush2 = () => {
|
|
351603
|
+
if (currentPath !== null) {
|
|
351604
|
+
results.push({ path: currentPath, isCreate: currentIsCreate, isDelete: currentIsDelete });
|
|
351605
|
+
}
|
|
351606
|
+
};
|
|
351607
|
+
for (const line of patchContent.split("\n")) {
|
|
351608
|
+
if (line.startsWith("diff --git ")) {
|
|
351609
|
+
flush2();
|
|
351610
|
+
currentPath = null;
|
|
351611
|
+
currentIsCreate = false;
|
|
351612
|
+
currentIsDelete = false;
|
|
351613
|
+
const match3 = line.match(/^diff --git a\/(.+?) b\/(.+?)$/);
|
|
351614
|
+
if (match3) {
|
|
351615
|
+
currentPath = match3[2] ?? null;
|
|
351616
|
+
}
|
|
351617
|
+
} else if (currentPath !== null) {
|
|
351618
|
+
if (line.startsWith("new file mode ")) {
|
|
351619
|
+
currentIsCreate = true;
|
|
351620
|
+
} else if (line.startsWith("deleted file mode ")) {
|
|
351621
|
+
currentIsDelete = true;
|
|
351622
|
+
}
|
|
351623
|
+
}
|
|
351624
|
+
}
|
|
351625
|
+
flush2();
|
|
351626
|
+
return results;
|
|
351627
|
+
}
|
|
351523
351628
|
var ReplayDetector = class {
|
|
351524
351629
|
git;
|
|
351525
351630
|
lockManager;
|
|
@@ -351598,12 +351703,31 @@ var require_dist14 = __commonJS({
|
|
|
351598
351703
|
);
|
|
351599
351704
|
continue;
|
|
351600
351705
|
}
|
|
351601
|
-
|
|
351706
|
+
let contentHash = this.computeContentHash(patchContent);
|
|
351602
351707
|
if (lock.patches.find((p10) => p10.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
351603
351708
|
continue;
|
|
351604
351709
|
}
|
|
351605
351710
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
351606
|
-
const
|
|
351711
|
+
const allFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f10) => !INFRASTRUCTURE_FILES.has(f10)).filter((f10) => !f10.startsWith(".fern/"));
|
|
351712
|
+
const sensitiveFiles = allFiles.filter((f10) => isSensitiveFile(f10));
|
|
351713
|
+
const files = allFiles.filter((f10) => !isSensitiveFile(f10));
|
|
351714
|
+
if (sensitiveFiles.length > 0) {
|
|
351715
|
+
this.warnings.push(
|
|
351716
|
+
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
351717
|
+
);
|
|
351718
|
+
if (files.length > 0) {
|
|
351719
|
+
const parentSha = parents[0];
|
|
351720
|
+
if (parentSha) {
|
|
351721
|
+
try {
|
|
351722
|
+
patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
|
|
351723
|
+
} catch {
|
|
351724
|
+
continue;
|
|
351725
|
+
}
|
|
351726
|
+
if (!patchContent.trim()) continue;
|
|
351727
|
+
contentHash = this.computeContentHash(patchContent);
|
|
351728
|
+
}
|
|
351729
|
+
}
|
|
351730
|
+
}
|
|
351607
351731
|
if (files.length === 0) {
|
|
351608
351732
|
continue;
|
|
351609
351733
|
}
|
|
@@ -351615,14 +351739,16 @@ var require_dist14 = __commonJS({
|
|
|
351615
351739
|
original_author: `${commit.authorName} <${commit.authorEmail}>`,
|
|
351616
351740
|
base_generation: lastGen.commit_sha,
|
|
351617
351741
|
files,
|
|
351618
|
-
patch_content: patchContent
|
|
351742
|
+
patch_content: patchContent,
|
|
351743
|
+
...this.isCreationOnlyPatch(patchContent) ? { user_owned: true } : {}
|
|
351619
351744
|
});
|
|
351620
351745
|
}
|
|
351621
|
-
|
|
351746
|
+
const survivingPatches = await this.collapseNetZeroFiles(newPatches);
|
|
351747
|
+
survivingPatches.reverse();
|
|
351622
351748
|
const revertedPatchIdSet = /* @__PURE__ */ new Set();
|
|
351623
351749
|
const revertIndicesToRemove = /* @__PURE__ */ new Set();
|
|
351624
|
-
for (let i11 = 0; i11 <
|
|
351625
|
-
const patch5 =
|
|
351750
|
+
for (let i11 = 0; i11 < survivingPatches.length; i11++) {
|
|
351751
|
+
const patch5 = survivingPatches[i11];
|
|
351626
351752
|
if (!isRevertCommit(patch5.original_message)) continue;
|
|
351627
351753
|
let body = "";
|
|
351628
351754
|
try {
|
|
@@ -351651,7 +351777,7 @@ var require_dist14 = __commonJS({
|
|
|
351651
351777
|
if (matchedExisting) continue;
|
|
351652
351778
|
let matchedNew = false;
|
|
351653
351779
|
if (revertedSha) {
|
|
351654
|
-
const idx =
|
|
351780
|
+
const idx = survivingPatches.findIndex(
|
|
351655
351781
|
(p10, j16) => j16 !== i11 && !revertIndicesToRemove.has(j16) && p10.original_commit === revertedSha
|
|
351656
351782
|
);
|
|
351657
351783
|
if (idx !== -1) {
|
|
@@ -351661,7 +351787,7 @@ var require_dist14 = __commonJS({
|
|
|
351661
351787
|
}
|
|
351662
351788
|
}
|
|
351663
351789
|
if (!matchedNew && revertedMessage) {
|
|
351664
|
-
const idx =
|
|
351790
|
+
const idx = survivingPatches.findIndex(
|
|
351665
351791
|
(p10, j16) => j16 !== i11 && !revertIndicesToRemove.has(j16) && p10.original_message === revertedMessage
|
|
351666
351792
|
);
|
|
351667
351793
|
if (idx !== -1) {
|
|
@@ -351673,18 +351799,107 @@ var require_dist14 = __commonJS({
|
|
|
351673
351799
|
revertIndicesToRemove.add(i11);
|
|
351674
351800
|
}
|
|
351675
351801
|
}
|
|
351676
|
-
const filteredPatches =
|
|
351802
|
+
const filteredPatches = survivingPatches.filter((_16, i11) => !revertIndicesToRemove.has(i11));
|
|
351677
351803
|
return { patches: filteredPatches, revertedPatchIds: [...revertedPatchIdSet] };
|
|
351678
351804
|
}
|
|
351679
351805
|
/**
|
|
351680
351806
|
* Compute content hash for deduplication.
|
|
351681
|
-
*
|
|
351682
|
-
*
|
|
351807
|
+
*
|
|
351808
|
+
* Produces a format-agnostic hash so both `git format-patch` output
|
|
351809
|
+
* (email-wrapped) and plain `git diff` output hash to the same value
|
|
351810
|
+
* when the underlying diff hunks are identical.
|
|
351811
|
+
*
|
|
351812
|
+
* Normalization:
|
|
351813
|
+
* 1. Strip email wrapper: everything before the first `diff --git` line
|
|
351814
|
+
* (From:, Subject:, Date:, diffstat, blank separators).
|
|
351815
|
+
* 2. Strip `index` lines (blob SHA pairs change across rebases).
|
|
351816
|
+
* 3. Strip trailing git version marker (`-- \n<version>`).
|
|
351817
|
+
*
|
|
351818
|
+
* This ensures content hashes match across the no-patches and normal-
|
|
351819
|
+
* regeneration flows, which store formatPatch and git-diff formats
|
|
351820
|
+
* respectively (FER-9850, D5-D9).
|
|
351683
351821
|
*/
|
|
351684
351822
|
computeContentHash(patchContent) {
|
|
351685
|
-
const
|
|
351823
|
+
const lines = patchContent.split("\n");
|
|
351824
|
+
const diffStart = lines.findIndex((l14) => l14.startsWith("diff --git "));
|
|
351825
|
+
const relevantLines = diffStart > 0 ? lines.slice(diffStart) : lines;
|
|
351826
|
+
const normalized = relevantLines.filter((line) => !line.startsWith("index ")).join("\n").replace(/\n-- \n[\d]+\.[\d]+[^\n]*\s*$/, "");
|
|
351686
351827
|
return `sha256:${(0, import_node_crypto7.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
351687
351828
|
}
|
|
351829
|
+
/**
|
|
351830
|
+
* FER-9805 — Drop patches whose files were transiently created and then
|
|
351831
|
+
* deleted within the current linear detection window, and are absent from
|
|
351832
|
+
* HEAD at the end of the window.
|
|
351833
|
+
*
|
|
351834
|
+
* Rationale: on a merge commit, createMergeCompositePatch already diffs
|
|
351835
|
+
* firstParent..mergeCommit so net-zero files never enter the composite. On
|
|
351836
|
+
* linear history each commit becomes its own patch, so a file that was
|
|
351837
|
+
* briefly added and later removed survives as a create+delete pair whose
|
|
351838
|
+
* cumulative diff is empty. We drop such patches before they reach the
|
|
351839
|
+
* lockfile.
|
|
351840
|
+
*
|
|
351841
|
+
* A file is "transient" when:
|
|
351842
|
+
* 1. some patch in the window sets `new file mode` for it (a creation), AND
|
|
351843
|
+
* 2. some patch in the window sets `deleted file mode` for it (a deletion), AND
|
|
351844
|
+
* 3. it is absent from HEAD's tree.
|
|
351845
|
+
*
|
|
351846
|
+
* The HEAD guard (#3) prevents false positives when a file is deleted
|
|
351847
|
+
* and then recreated with different content — the cumulative diff of
|
|
351848
|
+
* such a pair is non-empty and must be preserved.
|
|
351849
|
+
*
|
|
351850
|
+
* This only drops patches whose `files` are a subset of the transient
|
|
351851
|
+
* set. A partial-transient patch (one that touches a transient file
|
|
351852
|
+
* alongside a persistent one) is left unmodified; surgical stripping of
|
|
351853
|
+
* single files from a multi-file patch would require a follow-up that
|
|
351854
|
+
* regenerates the patch content via `git format-patch -- <files>`. No
|
|
351855
|
+
* current failing test exercises this, and leaving the patch intact
|
|
351856
|
+
* preserves today's behaviour. TODO(FER-9805): revisit if a future
|
|
351857
|
+
* adversarial test demands per-file stripping.
|
|
351858
|
+
*/
|
|
351859
|
+
async collapseNetZeroFiles(patches) {
|
|
351860
|
+
if (patches.length === 0) return patches;
|
|
351861
|
+
const stateByFile = /* @__PURE__ */ new Map();
|
|
351862
|
+
for (const patch5 of patches) {
|
|
351863
|
+
for (const header of parsePatchFileHeaders(patch5.patch_content)) {
|
|
351864
|
+
if (!header.isCreate && !header.isDelete) continue;
|
|
351865
|
+
const prior = stateByFile.get(header.path) ?? { created: false, deleted: false };
|
|
351866
|
+
if (header.isCreate) prior.created = true;
|
|
351867
|
+
if (header.isDelete) prior.deleted = true;
|
|
351868
|
+
stateByFile.set(header.path, prior);
|
|
351869
|
+
}
|
|
351870
|
+
}
|
|
351871
|
+
let anyCandidate = false;
|
|
351872
|
+
for (const state of stateByFile.values()) {
|
|
351873
|
+
if (state.created && state.deleted) {
|
|
351874
|
+
anyCandidate = true;
|
|
351875
|
+
break;
|
|
351876
|
+
}
|
|
351877
|
+
}
|
|
351878
|
+
if (!anyCandidate) return patches;
|
|
351879
|
+
const headFiles = await this.listHeadFiles();
|
|
351880
|
+
const transient = /* @__PURE__ */ new Set();
|
|
351881
|
+
for (const [file4, state] of stateByFile) {
|
|
351882
|
+
if (state.created && state.deleted && !headFiles.has(file4)) {
|
|
351883
|
+
transient.add(file4);
|
|
351884
|
+
}
|
|
351885
|
+
}
|
|
351886
|
+
if (transient.size === 0) return patches;
|
|
351887
|
+
return patches.filter((patch5) => !patch5.files.every((f10) => transient.has(f10)));
|
|
351888
|
+
}
|
|
351889
|
+
/**
|
|
351890
|
+
* List every tracked path at HEAD (one `git ls-tree -r` invocation).
|
|
351891
|
+
* Returns an empty Set if HEAD is unborn or the invocation fails — the
|
|
351892
|
+
* caller then treats every candidate as "not in HEAD" and may collapse
|
|
351893
|
+
* more aggressively. On typical SDK repos this is a single sub-10ms call.
|
|
351894
|
+
*/
|
|
351895
|
+
async listHeadFiles() {
|
|
351896
|
+
try {
|
|
351897
|
+
const out = await this.git.exec(["ls-tree", "-r", "--name-only", "HEAD"]);
|
|
351898
|
+
return new Set(out.split("\n").map((l14) => l14.trim()).filter(Boolean));
|
|
351899
|
+
} catch {
|
|
351900
|
+
return /* @__PURE__ */ new Set();
|
|
351901
|
+
}
|
|
351902
|
+
}
|
|
351688
351903
|
/**
|
|
351689
351904
|
* Create a single composite patch for a merge commit by diffing the merge
|
|
351690
351905
|
* commit against its first parent. This captures the net change of the
|
|
@@ -351700,7 +351915,14 @@ var require_dist14 = __commonJS({
|
|
|
351700
351915
|
firstParent,
|
|
351701
351916
|
mergeCommit.sha
|
|
351702
351917
|
]);
|
|
351703
|
-
const
|
|
351918
|
+
const allMergeFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f10) => !INFRASTRUCTURE_FILES.has(f10)).filter((f10) => !f10.startsWith(".fern/"));
|
|
351919
|
+
const sensitiveMergeFiles = allMergeFiles.filter((f10) => isSensitiveFile(f10));
|
|
351920
|
+
const files = allMergeFiles.filter((f10) => !isSensitiveFile(f10));
|
|
351921
|
+
if (sensitiveMergeFiles.length > 0) {
|
|
351922
|
+
this.warnings.push(
|
|
351923
|
+
`Sensitive file(s) excluded from merge patch for commit ${mergeCommit.sha.slice(0, 7)}: ${sensitiveMergeFiles.join(", ")}. These files will not be tracked by replay.`
|
|
351924
|
+
);
|
|
351925
|
+
}
|
|
351704
351926
|
if (files.length === 0) return null;
|
|
351705
351927
|
const diff2 = await this.git.exec([
|
|
351706
351928
|
"diff",
|
|
@@ -351722,7 +351944,8 @@ var require_dist14 = __commonJS({
|
|
|
351722
351944
|
original_author: `${mergeCommit.authorName} <${mergeCommit.authorEmail}>`,
|
|
351723
351945
|
base_generation: lastGen.commit_sha,
|
|
351724
351946
|
files,
|
|
351725
|
-
patch_content: diff2
|
|
351947
|
+
patch_content: diff2,
|
|
351948
|
+
...this.isCreationOnlyPatch(diff2) ? { user_owned: true } : {}
|
|
351726
351949
|
};
|
|
351727
351950
|
}
|
|
351728
351951
|
/**
|
|
@@ -351736,7 +351959,14 @@ var require_dist14 = __commonJS({
|
|
|
351736
351959
|
return this.detectPatchesViaCommitScan();
|
|
351737
351960
|
}
|
|
351738
351961
|
const filesOutput = await this.git.exec(["diff", "--name-only", diffBase, "HEAD"]);
|
|
351739
|
-
const
|
|
351962
|
+
const allTreeFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f10) => !INFRASTRUCTURE_FILES.has(f10)).filter((f10) => !f10.startsWith(".fern/"));
|
|
351963
|
+
const sensitiveTreeFiles = allTreeFiles.filter((f10) => isSensitiveFile(f10));
|
|
351964
|
+
const files = allTreeFiles.filter((f10) => !isSensitiveFile(f10));
|
|
351965
|
+
if (sensitiveTreeFiles.length > 0) {
|
|
351966
|
+
this.warnings.push(
|
|
351967
|
+
`Sensitive file(s) excluded from composite patch: ${sensitiveTreeFiles.join(", ")}. These files will not be tracked by replay.`
|
|
351968
|
+
);
|
|
351969
|
+
}
|
|
351740
351970
|
if (files.length === 0) return { patches: [], revertedPatchIds: [] };
|
|
351741
351971
|
const diff2 = await this.git.exec(["diff", diffBase, "HEAD", "--", ...files]);
|
|
351742
351972
|
if (!diff2.trim()) return { patches: [], revertedPatchIds: [] };
|
|
@@ -351756,7 +351986,8 @@ var require_dist14 = __commonJS({
|
|
|
351756
351986
|
// reference to find base file content. diffBase may be the tree_hash.
|
|
351757
351987
|
base_generation: commitKnownMissing ? diffBase : lastGen.commit_sha,
|
|
351758
351988
|
files,
|
|
351759
|
-
patch_content: diff2
|
|
351989
|
+
patch_content: diff2,
|
|
351990
|
+
...this.isCreationOnlyPatch(diff2) ? { user_owned: true } : {}
|
|
351760
351991
|
};
|
|
351761
351992
|
return { patches: [compositePatch], revertedPatchIds: [] };
|
|
351762
351993
|
}
|
|
@@ -351803,7 +352034,7 @@ var require_dist14 = __commonJS({
|
|
|
351803
352034
|
} catch {
|
|
351804
352035
|
continue;
|
|
351805
352036
|
}
|
|
351806
|
-
|
|
352037
|
+
let contentHash = this.computeContentHash(patchContent);
|
|
351807
352038
|
if (existingHashes.has(contentHash) || forgottenHashes.has(contentHash)) {
|
|
351808
352039
|
continue;
|
|
351809
352040
|
}
|
|
@@ -351811,7 +352042,26 @@ var require_dist14 = __commonJS({
|
|
|
351811
352042
|
continue;
|
|
351812
352043
|
}
|
|
351813
352044
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
351814
|
-
const
|
|
352045
|
+
const allScanFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f10) => !INFRASTRUCTURE_FILES.has(f10));
|
|
352046
|
+
const sensitiveScanFiles = allScanFiles.filter((f10) => isSensitiveFile(f10));
|
|
352047
|
+
const files = allScanFiles.filter((f10) => !isSensitiveFile(f10));
|
|
352048
|
+
if (sensitiveScanFiles.length > 0) {
|
|
352049
|
+
this.warnings.push(
|
|
352050
|
+
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveScanFiles.join(", ")}. These files will not be tracked by replay.`
|
|
352051
|
+
);
|
|
352052
|
+
if (files.length > 0) {
|
|
352053
|
+
if (parents.length === 0) {
|
|
352054
|
+
continue;
|
|
352055
|
+
}
|
|
352056
|
+
try {
|
|
352057
|
+
patchContent = await this.git.exec(["diff", parents[0], commit.sha, "--", ...files]);
|
|
352058
|
+
} catch {
|
|
352059
|
+
continue;
|
|
352060
|
+
}
|
|
352061
|
+
if (!patchContent.trim()) continue;
|
|
352062
|
+
contentHash = this.computeContentHash(patchContent);
|
|
352063
|
+
}
|
|
352064
|
+
}
|
|
351815
352065
|
if (files.length === 0) {
|
|
351816
352066
|
continue;
|
|
351817
352067
|
}
|
|
@@ -352135,6 +352385,20 @@ var require_dist14 = __commonJS({
|
|
|
352135
352385
|
resetAccumulator() {
|
|
352136
352386
|
this.fileTheirsAccumulator.clear();
|
|
352137
352387
|
}
|
|
352388
|
+
/**
|
|
352389
|
+
* @internal Test-only.
|
|
352390
|
+
* Returns a defensive copy of the inter-patch accumulator. Used by the
|
|
352391
|
+
* adversarial test suite (FER-9791) to assert invariant I2: after every
|
|
352392
|
+
* applied patch, the accumulator has an entry for each file the patch
|
|
352393
|
+
* touched, and the entry's content matches on-disk.
|
|
352394
|
+
*/
|
|
352395
|
+
getAccumulatorSnapshot() {
|
|
352396
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
352397
|
+
for (const [path98, entry] of this.fileTheirsAccumulator) {
|
|
352398
|
+
snapshot.set(path98, { content: entry.content, baseGeneration: entry.baseGeneration });
|
|
352399
|
+
}
|
|
352400
|
+
return snapshot;
|
|
352401
|
+
}
|
|
352138
352402
|
/**
|
|
352139
352403
|
* Apply all patches, returning results for each.
|
|
352140
352404
|
* Skips patches that match exclude patterns in replay.yml
|
|
@@ -352200,7 +352464,7 @@ var require_dist14 = __commonJS({
|
|
|
352200
352464
|
const base4 = await this.git.showFile(baseGen.tree_hash, filePath);
|
|
352201
352465
|
const theirs = await this.applyPatchToContent(base4, patch5.patch_content, filePath, tempGit, tempDir2);
|
|
352202
352466
|
const effectiveTheirs = theirs ?? await (0, import_promises183.readFile)((0, import_node_path210.join)(this.outputDir, resolvedPath), "utf-8").catch(() => null);
|
|
352203
|
-
if (effectiveTheirs) {
|
|
352467
|
+
if (effectiveTheirs != null) {
|
|
352204
352468
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
352205
352469
|
content: effectiveTheirs,
|
|
352206
352470
|
baseGeneration: patch5.base_generation
|
|
@@ -352224,16 +352488,20 @@ var require_dist14 = __commonJS({
|
|
|
352224
352488
|
return this.fileTheirsAccumulator.has(resolved);
|
|
352225
352489
|
})
|
|
352226
352490
|
).then((results) => results.some(Boolean));
|
|
352227
|
-
|
|
352491
|
+
const resolvedFiles = {};
|
|
352492
|
+
for (const filePath of patch5.files) {
|
|
352493
|
+
const resolvedPath = baseGen ? await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash) : filePath;
|
|
352494
|
+
if (resolvedPath !== filePath) {
|
|
352495
|
+
resolvedFiles[filePath] = resolvedPath;
|
|
352496
|
+
}
|
|
352497
|
+
}
|
|
352498
|
+
const patchIsRenameAware = /^rename from /m.test(patch5.patch_content);
|
|
352499
|
+
const hasGeneratorRename = Object.keys(resolvedFiles).length > 0 && !patchIsRenameAware;
|
|
352500
|
+
if (!needsAccumulation && !hasGeneratorRename) {
|
|
352228
352501
|
const snapshots = /* @__PURE__ */ new Map();
|
|
352229
|
-
const resolvedFiles = {};
|
|
352230
352502
|
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));
|
|
352503
|
+
const fullPath = (0, import_node_path210.join)(this.outputDir, filePath);
|
|
352504
|
+
snapshots.set(filePath, await (0, import_promises183.readFile)(fullPath, "utf-8").catch(() => null));
|
|
352237
352505
|
}
|
|
352238
352506
|
try {
|
|
352239
352507
|
await this.git.execWithInput(["apply", "--3way"], patch5.patch_content);
|
|
@@ -352245,8 +352513,8 @@ var require_dist14 = __commonJS({
|
|
|
352245
352513
|
...Object.keys(resolvedFiles).length > 0 && { resolvedFiles }
|
|
352246
352514
|
};
|
|
352247
352515
|
} catch {
|
|
352248
|
-
for (const [
|
|
352249
|
-
const fullPath = (0, import_node_path210.join)(this.outputDir,
|
|
352516
|
+
for (const [filePath, content5] of snapshots) {
|
|
352517
|
+
const fullPath = (0, import_node_path210.join)(this.outputDir, filePath);
|
|
352250
352518
|
if (content5 != null) {
|
|
352251
352519
|
await (0, import_promises183.writeFile)(fullPath, content5);
|
|
352252
352520
|
} else {
|
|
@@ -352366,7 +352634,7 @@ var require_dist14 = __commonJS({
|
|
|
352366
352634
|
}
|
|
352367
352635
|
let useAccumulatorAsMergeBase = false;
|
|
352368
352636
|
const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
|
|
352369
|
-
if (accumulatorEntry && (
|
|
352637
|
+
if (accumulatorEntry && (theirs == null || base4 == null)) {
|
|
352370
352638
|
theirs = await this.applyPatchToContent(
|
|
352371
352639
|
accumulatorEntry.content,
|
|
352372
352640
|
patch5.patch_content,
|
|
@@ -352374,7 +352642,16 @@ var require_dist14 = __commonJS({
|
|
|
352374
352642
|
tempGit,
|
|
352375
352643
|
tempDir2
|
|
352376
352644
|
);
|
|
352377
|
-
if (theirs) {
|
|
352645
|
+
if (theirs != null) {
|
|
352646
|
+
useAccumulatorAsMergeBase = true;
|
|
352647
|
+
} else if (await this.isPatchAlreadyApplied(
|
|
352648
|
+
patch5.patch_content,
|
|
352649
|
+
filePath,
|
|
352650
|
+
accumulatorEntry.content,
|
|
352651
|
+
tempGit,
|
|
352652
|
+
tempDir2
|
|
352653
|
+
)) {
|
|
352654
|
+
theirs = accumulatorEntry.content;
|
|
352378
352655
|
useAccumulatorAsMergeBase = true;
|
|
352379
352656
|
}
|
|
352380
352657
|
}
|
|
@@ -352391,7 +352668,7 @@ var require_dist14 = __commonJS({
|
|
|
352391
352668
|
}
|
|
352392
352669
|
let effective_theirs = theirs;
|
|
352393
352670
|
let baseMismatchSkipped = false;
|
|
352394
|
-
if (theirs && base4 && !useAccumulatorAsMergeBase) {
|
|
352671
|
+
if (theirs != null && base4 != null && !useAccumulatorAsMergeBase) {
|
|
352395
352672
|
if (accumulatorEntry && accumulatorEntry.baseGeneration === patch5.base_generation) {
|
|
352396
352673
|
try {
|
|
352397
352674
|
const preMerged = threeWayMerge(base4, accumulatorEntry.content, theirs);
|
|
@@ -352407,7 +352684,7 @@ var require_dist14 = __commonJS({
|
|
|
352407
352684
|
baseMismatchSkipped = true;
|
|
352408
352685
|
}
|
|
352409
352686
|
}
|
|
352410
|
-
if (base4 == null &&
|
|
352687
|
+
if (base4 == null && ours == null && effective_theirs != null) {
|
|
352411
352688
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
352412
352689
|
content: effective_theirs,
|
|
352413
352690
|
baseGeneration: patch5.base_generation
|
|
@@ -352417,7 +352694,7 @@ var require_dist14 = __commonJS({
|
|
|
352417
352694
|
await (0, import_promises183.writeFile)(oursPath, effective_theirs);
|
|
352418
352695
|
return { file: resolvedPath, status: "merged", reason: "new-file" };
|
|
352419
352696
|
}
|
|
352420
|
-
if (base4 == null && ours && effective_theirs && !useAccumulatorAsMergeBase) {
|
|
352697
|
+
if (base4 == null && ours != null && effective_theirs != null && !useAccumulatorAsMergeBase) {
|
|
352421
352698
|
const merged2 = threeWayMerge("", ours, effective_theirs);
|
|
352422
352699
|
const outDir2 = (0, import_node_path210.dirname)(oursPath);
|
|
352423
352700
|
await (0, import_promises183.mkdir)(outDir2, { recursive: true });
|
|
@@ -352431,16 +352708,20 @@ var require_dist14 = __commonJS({
|
|
|
352431
352708
|
conflictMetadata: metadata
|
|
352432
352709
|
};
|
|
352433
352710
|
}
|
|
352711
|
+
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
352712
|
+
content: merged2.content,
|
|
352713
|
+
baseGeneration: patch5.base_generation
|
|
352714
|
+
});
|
|
352434
352715
|
return { file: resolvedPath, status: "merged" };
|
|
352435
352716
|
}
|
|
352436
|
-
if (
|
|
352717
|
+
if (effective_theirs == null) {
|
|
352437
352718
|
return {
|
|
352438
352719
|
file: resolvedPath,
|
|
352439
352720
|
status: "skipped",
|
|
352440
352721
|
reason: "missing-content"
|
|
352441
352722
|
};
|
|
352442
352723
|
}
|
|
352443
|
-
if (base4 == null && !useAccumulatorAsMergeBase ||
|
|
352724
|
+
if (base4 == null && !useAccumulatorAsMergeBase || ours == null) {
|
|
352444
352725
|
return {
|
|
352445
352726
|
file: resolvedPath,
|
|
352446
352727
|
status: "skipped",
|
|
@@ -352459,7 +352740,7 @@ var require_dist14 = __commonJS({
|
|
|
352459
352740
|
const outDir = (0, import_node_path210.dirname)(oursPath);
|
|
352460
352741
|
await (0, import_promises183.mkdir)(outDir, { recursive: true });
|
|
352461
352742
|
await (0, import_promises183.writeFile)(oursPath, merged.content);
|
|
352462
|
-
if (effective_theirs && !merged.hasConflicts) {
|
|
352743
|
+
if (effective_theirs != null && !merged.hasConflicts) {
|
|
352463
352744
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
352464
352745
|
content: effective_theirs,
|
|
352465
352746
|
baseGeneration: patch5.base_generation
|
|
@@ -352557,6 +352838,28 @@ var require_dist14 = __commonJS({
|
|
|
352557
352838
|
return null;
|
|
352558
352839
|
}
|
|
352559
352840
|
}
|
|
352841
|
+
/**
|
|
352842
|
+
* Detects whether a patch's additions are already present in `content`.
|
|
352843
|
+
* Uses `git apply --reverse --check` — if the reverse patch applies cleanly,
|
|
352844
|
+
* the forward patch is effectively a no-op (its +lines are already there and
|
|
352845
|
+
* its -lines are already gone). Used to distinguish "patch already applied"
|
|
352846
|
+
* from "patch base mismatch" after a forward-apply fails.
|
|
352847
|
+
*/
|
|
352848
|
+
async isPatchAlreadyApplied(patchContent, filePath, content5, tempGit, tempDir2) {
|
|
352849
|
+
const fileDiff = this.extractFileDiff(patchContent, filePath);
|
|
352850
|
+
if (!fileDiff) return false;
|
|
352851
|
+
try {
|
|
352852
|
+
const tempFilePath = (0, import_node_path210.join)(tempDir2, filePath);
|
|
352853
|
+
await (0, import_promises183.mkdir)((0, import_node_path210.dirname)(tempFilePath), { recursive: true });
|
|
352854
|
+
await (0, import_promises183.writeFile)(tempFilePath, content5);
|
|
352855
|
+
await tempGit.exec(["add", filePath]);
|
|
352856
|
+
await tempGit.exec(["commit", "-m", `check already-applied ${filePath}`, "--allow-empty"]);
|
|
352857
|
+
await tempGit.execWithInput(["apply", "--reverse", "--check", "--allow-empty"], fileDiff);
|
|
352858
|
+
return true;
|
|
352859
|
+
} catch {
|
|
352860
|
+
return false;
|
|
352861
|
+
}
|
|
352862
|
+
}
|
|
352560
352863
|
extractFileDiff(patchContent, filePath) {
|
|
352561
352864
|
const lines = patchContent.split("\n");
|
|
352562
352865
|
const diffLines = [];
|
|
@@ -352721,6 +353024,33 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352721
353024
|
committer;
|
|
352722
353025
|
lockManager;
|
|
352723
353026
|
outputDir;
|
|
353027
|
+
/** @internal Test-only. Captures the last ReplayResult[] returned from applicator.applyPatches(). */
|
|
353028
|
+
_lastApplyResults = [];
|
|
353029
|
+
/**
|
|
353030
|
+
* Service-level warnings accumulated during a single runReplay() call.
|
|
353031
|
+
* Merged with `detector.warnings` into `ReplayReport.warnings` by each flow handler.
|
|
353032
|
+
* Populated by `isFileUserOwned` when a patch's base generation tree is unreachable
|
|
353033
|
+
* (shallow clone / aggressive `git gc --prune`) and we fall back to a conservative
|
|
353034
|
+
* heuristic. Cleared at the top of `runReplay()`.
|
|
353035
|
+
*/
|
|
353036
|
+
warnings = [];
|
|
353037
|
+
/**
|
|
353038
|
+
* @internal Test-only accessor.
|
|
353039
|
+
* Returns the ReplayApplicator instance used for the last run. Tests use this
|
|
353040
|
+
* to inspect the inter-patch accumulator after runReplay() completes (FER-9791, I2).
|
|
353041
|
+
*/
|
|
353042
|
+
get applicatorRef() {
|
|
353043
|
+
return this.applicator;
|
|
353044
|
+
}
|
|
353045
|
+
/**
|
|
353046
|
+
* @internal Test-only accessor.
|
|
353047
|
+
* Returns the ReplayResult[] from the most recent applyPatches() call. Tests use
|
|
353048
|
+
* this to filter applied-vs-conflicted-vs-absorbed patches for invariant assertions
|
|
353049
|
+
* (FER-9791). Returns an empty array if no patches have been applied yet.
|
|
353050
|
+
*/
|
|
353051
|
+
getLastResults() {
|
|
353052
|
+
return this._lastApplyResults;
|
|
353053
|
+
}
|
|
352724
353054
|
constructor(outputDir, _config) {
|
|
352725
353055
|
const git = new GitClient2(outputDir);
|
|
352726
353056
|
this.git = git;
|
|
@@ -352731,6 +353061,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352731
353061
|
this.committer = new ReplayCommitter(git, outputDir);
|
|
352732
353062
|
}
|
|
352733
353063
|
async runReplay(options2) {
|
|
353064
|
+
this.warnings = [];
|
|
352734
353065
|
if (options2?.skipApplication) {
|
|
352735
353066
|
return this.handleSkipApplication(options2);
|
|
352736
353067
|
}
|
|
@@ -352804,6 +353135,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352804
353135
|
);
|
|
352805
353136
|
}
|
|
352806
353137
|
this.lockManager.save();
|
|
353138
|
+
this.detector.warnings.push(...this.lockManager.warnings);
|
|
352807
353139
|
}
|
|
352808
353140
|
determineFlow() {
|
|
352809
353141
|
try {
|
|
@@ -352870,6 +353202,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352870
353202
|
}
|
|
352871
353203
|
this.lockManager.setReplaySkippedAt((/* @__PURE__ */ new Date()).toISOString());
|
|
352872
353204
|
this.lockManager.save();
|
|
353205
|
+
const credentialWarnings = [...this.lockManager.warnings];
|
|
352873
353206
|
if (!options2?.stageOnly) {
|
|
352874
353207
|
await this.committer.commitReplay(0);
|
|
352875
353208
|
} else {
|
|
@@ -352881,13 +353214,15 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352881
353214
|
patchesApplied: 0,
|
|
352882
353215
|
patchesWithConflicts: 0,
|
|
352883
353216
|
patchesSkipped: 0,
|
|
352884
|
-
conflicts: []
|
|
353217
|
+
conflicts: [],
|
|
353218
|
+
warnings: credentialWarnings.length > 0 ? credentialWarnings : void 0
|
|
352885
353219
|
};
|
|
352886
353220
|
}
|
|
352887
353221
|
async handleNoPatchesRegeneration(options2) {
|
|
352888
|
-
|
|
352889
|
-
const
|
|
353222
|
+
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
353223
|
+
const detectorWarnings = [...this.detector.warnings];
|
|
352890
353224
|
if (options2?.dryRun) {
|
|
353225
|
+
const dryRunWarnings = [...detectorWarnings, ...this.warnings];
|
|
352891
353226
|
return {
|
|
352892
353227
|
flow: "no-patches",
|
|
352893
353228
|
patchesDetected: newPatches.length,
|
|
@@ -352897,9 +353232,26 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352897
353232
|
patchesReverted: revertedPatchIds.length,
|
|
352898
353233
|
conflicts: [],
|
|
352899
353234
|
wouldApply: newPatches,
|
|
352900
|
-
warnings:
|
|
353235
|
+
warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0
|
|
352901
353236
|
};
|
|
352902
353237
|
}
|
|
353238
|
+
{
|
|
353239
|
+
const preLock = this.lockManager.read();
|
|
353240
|
+
const preCurrentGen = preLock.current_generation;
|
|
353241
|
+
const uniqueFiles = [...new Set(newPatches.flatMap((p10) => p10.files))];
|
|
353242
|
+
const absorbedFiles = /* @__PURE__ */ new Set();
|
|
353243
|
+
for (const file4 of uniqueFiles) {
|
|
353244
|
+
const diff2 = await this.git.exec(["diff", preCurrentGen, "HEAD", "--", file4]).catch(() => null);
|
|
353245
|
+
if (diff2 !== null && !diff2.trim()) {
|
|
353246
|
+
absorbedFiles.add(file4);
|
|
353247
|
+
}
|
|
353248
|
+
}
|
|
353249
|
+
if (absorbedFiles.size > 0) {
|
|
353250
|
+
newPatches = newPatches.filter(
|
|
353251
|
+
(p10) => p10.files.length === 0 || !p10.files.every((f10) => absorbedFiles.has(f10))
|
|
353252
|
+
);
|
|
353253
|
+
}
|
|
353254
|
+
}
|
|
352903
353255
|
for (const id2 of revertedPatchIds) {
|
|
352904
353256
|
try {
|
|
352905
353257
|
this.lockManager.removePatch(id2);
|
|
@@ -352918,6 +353270,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352918
353270
|
let results = [];
|
|
352919
353271
|
if (newPatches.length > 0) {
|
|
352920
353272
|
results = await this.applicator.applyPatches(newPatches);
|
|
353273
|
+
this._lastApplyResults = results;
|
|
352921
353274
|
this.revertConflictingFiles(results);
|
|
352922
353275
|
for (const result of results) {
|
|
352923
353276
|
if (result.status === "conflict") {
|
|
@@ -352932,6 +353285,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352932
353285
|
}
|
|
352933
353286
|
}
|
|
352934
353287
|
this.lockManager.save();
|
|
353288
|
+
this.warnings.push(...this.lockManager.warnings);
|
|
352935
353289
|
if (newPatches.length > 0) {
|
|
352936
353290
|
if (!options2?.stageOnly) {
|
|
352937
353291
|
const appliedCount = results.filter((r23) => r23.status === "applied").length;
|
|
@@ -352940,13 +353294,14 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352940
353294
|
await this.committer.stageAll();
|
|
352941
353295
|
}
|
|
352942
353296
|
}
|
|
353297
|
+
const warnings = [...detectorWarnings, ...this.warnings];
|
|
352943
353298
|
return this.buildReport("no-patches", newPatches, results, options2, warnings, rebaseCounts, void 0, revertedPatchIds.length);
|
|
352944
353299
|
}
|
|
352945
353300
|
async handleNormalRegeneration(options2) {
|
|
352946
353301
|
if (options2?.dryRun) {
|
|
352947
353302
|
const existingPatches2 = this.lockManager.getPatches();
|
|
352948
353303
|
const { patches: newPatches2, revertedPatchIds: dryRunReverted } = await this.detector.detectNewPatches();
|
|
352949
|
-
const warnings2 = [...this.detector.warnings];
|
|
353304
|
+
const warnings2 = [...this.detector.warnings, ...this.warnings];
|
|
352950
353305
|
const allPatches2 = [...existingPatches2, ...newPatches2];
|
|
352951
353306
|
return {
|
|
352952
353307
|
flow: "normal-regeneration",
|
|
@@ -352978,7 +353333,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
352978
353333
|
}
|
|
352979
353334
|
existingPatches = this.lockManager.getPatches();
|
|
352980
353335
|
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
352981
|
-
const
|
|
353336
|
+
const detectorWarnings = [...this.detector.warnings];
|
|
352982
353337
|
if (removedByPreRebase.length > 0) {
|
|
352983
353338
|
const removedOriginalCommits = new Set(removedByPreRebase.map((p10) => p10.original_commit));
|
|
352984
353339
|
const removedOriginalMessages = new Set(removedByPreRebase.map((p10) => p10.original_message));
|
|
@@ -353010,6 +353365,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353010
353365
|
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
353011
353366
|
this.lockManager.addGeneration(genRecord);
|
|
353012
353367
|
const results = await this.applicator.applyPatches(allPatches);
|
|
353368
|
+
this._lastApplyResults = results;
|
|
353013
353369
|
this.revertConflictingFiles(results);
|
|
353014
353370
|
for (const result of results) {
|
|
353015
353371
|
if (result.status === "conflict") {
|
|
@@ -353031,12 +353387,14 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353031
353387
|
}
|
|
353032
353388
|
}
|
|
353033
353389
|
this.lockManager.save();
|
|
353390
|
+
this.warnings.push(...this.lockManager.warnings);
|
|
353034
353391
|
if (options2?.stageOnly) {
|
|
353035
353392
|
await this.committer.stageAll();
|
|
353036
353393
|
} else {
|
|
353037
353394
|
const appliedCount = results.filter((r23) => r23.status === "applied").length;
|
|
353038
353395
|
await this.committer.commitReplay(appliedCount, allPatches);
|
|
353039
353396
|
}
|
|
353397
|
+
const warnings = [...detectorWarnings, ...this.warnings];
|
|
353040
353398
|
return this.buildReport(
|
|
353041
353399
|
"normal-regeneration",
|
|
353042
353400
|
allPatches,
|
|
@@ -353060,6 +353418,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353060
353418
|
let keptAsUserOwned = 0;
|
|
353061
353419
|
const seenContentHashes = /* @__PURE__ */ new Set();
|
|
353062
353420
|
const absorbedPatchIds = /* @__PURE__ */ new Set();
|
|
353421
|
+
const fernignorePatterns = this.readFernignorePatterns();
|
|
353063
353422
|
for (const result of results) {
|
|
353064
353423
|
if (result.resolvedFiles && Object.keys(result.resolvedFiles).length > 0) {
|
|
353065
353424
|
const patch5 = result.patch;
|
|
@@ -353080,34 +353439,78 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353080
353439
|
const patch5 = result.patch;
|
|
353081
353440
|
if (patch5.base_generation === currentGenSha) continue;
|
|
353082
353441
|
try {
|
|
353083
|
-
const
|
|
353084
|
-
|
|
353442
|
+
const originalByResolved = {};
|
|
353443
|
+
if (result.resolvedFiles) {
|
|
353444
|
+
for (const [orig, resolved] of Object.entries(result.resolvedFiles)) {
|
|
353445
|
+
originalByResolved[resolved] = orig;
|
|
353446
|
+
}
|
|
353447
|
+
}
|
|
353448
|
+
const isUserOwnedPerFile = await Promise.all(
|
|
353085
353449
|
patch5.files.map(async (file4) => {
|
|
353086
|
-
if (file4 === ".fernignore")
|
|
353450
|
+
if (file4 === ".fernignore") return true;
|
|
353451
|
+
if (fernignorePatterns.some((p10) => file4 === p10 || (0, import_minimatch22.minimatch)(file4, p10))) {
|
|
353087
353452
|
return true;
|
|
353088
353453
|
}
|
|
353089
|
-
if (
|
|
353454
|
+
if (patch5.user_owned) return true;
|
|
353455
|
+
if (this.fileIsCreationInPatchContent(patch5.patch_content, originalByResolved[file4] ?? file4)) {
|
|
353090
353456
|
return true;
|
|
353091
353457
|
}
|
|
353092
353458
|
const content5 = await this.git.showFile(currentGenSha, file4);
|
|
353093
353459
|
return content5 === null;
|
|
353094
353460
|
})
|
|
353095
353461
|
);
|
|
353096
|
-
const
|
|
353097
|
-
|
|
353098
|
-
|
|
353099
|
-
|
|
353100
|
-
|
|
353462
|
+
const hasProtectedFiles = patch5.files.some(
|
|
353463
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p10) => file4 === p10 || (0, import_minimatch22.minimatch)(file4, p10))
|
|
353464
|
+
);
|
|
353465
|
+
const allUserOwned = isUserOwnedPerFile.every(Boolean);
|
|
353466
|
+
if (allUserOwned && patch5.files.length > 0) {
|
|
353467
|
+
const baseChanged = patch5.base_generation !== currentGenSha;
|
|
353468
|
+
const userDiff = await this.git.exec(["diff", currentGenSha, "--", ...patch5.files]).catch(() => null);
|
|
353469
|
+
if (userDiff != null && userDiff.trim()) {
|
|
353470
|
+
const newHash = this.detector.computeContentHash(userDiff);
|
|
353471
|
+
patch5.patch_content = userDiff;
|
|
353472
|
+
patch5.content_hash = newHash;
|
|
353473
|
+
}
|
|
353474
|
+
patch5.base_generation = currentGenSha;
|
|
353475
|
+
try {
|
|
353476
|
+
this.lockManager.updatePatch(patch5.id, {
|
|
353477
|
+
base_generation: currentGenSha,
|
|
353478
|
+
...!patch5.user_owned ? { user_owned: true } : {},
|
|
353479
|
+
...userDiff != null && userDiff.trim() ? { patch_content: patch5.patch_content, content_hash: patch5.content_hash } : {}
|
|
353480
|
+
});
|
|
353481
|
+
} catch {
|
|
353482
|
+
}
|
|
353483
|
+
patch5.user_owned = true;
|
|
353101
353484
|
keptAsUserOwned++;
|
|
353485
|
+
if (baseChanged) repointed++;
|
|
353102
353486
|
continue;
|
|
353103
353487
|
}
|
|
353104
353488
|
const diff2 = await this.git.exec(["diff", currentGenSha, "--", ...patch5.files]).catch(() => null);
|
|
353105
|
-
if (
|
|
353489
|
+
if (diff2 === null) continue;
|
|
353490
|
+
if (!diff2.trim()) {
|
|
353491
|
+
if (hasProtectedFiles) {
|
|
353492
|
+
patch5.base_generation = currentGenSha;
|
|
353493
|
+
try {
|
|
353494
|
+
this.lockManager.updatePatch(patch5.id, { base_generation: currentGenSha });
|
|
353495
|
+
} catch {
|
|
353496
|
+
}
|
|
353497
|
+
keptAsUserOwned++;
|
|
353498
|
+
continue;
|
|
353499
|
+
}
|
|
353106
353500
|
this.lockManager.removePatch(patch5.id);
|
|
353107
353501
|
absorbedPatchIds.add(patch5.id);
|
|
353108
353502
|
absorbed++;
|
|
353109
353503
|
continue;
|
|
353110
353504
|
}
|
|
353505
|
+
if (hasProtectedFiles) {
|
|
353506
|
+
patch5.base_generation = currentGenSha;
|
|
353507
|
+
try {
|
|
353508
|
+
this.lockManager.updatePatch(patch5.id, { base_generation: currentGenSha });
|
|
353509
|
+
} catch {
|
|
353510
|
+
}
|
|
353511
|
+
keptAsUserOwned++;
|
|
353512
|
+
continue;
|
|
353513
|
+
}
|
|
353111
353514
|
const newContentHash = this.detector.computeContentHash(diff2);
|
|
353112
353515
|
if (seenContentHashes.has(newContentHash)) {
|
|
353113
353516
|
this.lockManager.removePatch(patch5.id);
|
|
@@ -353116,17 +353519,89 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353116
353519
|
continue;
|
|
353117
353520
|
}
|
|
353118
353521
|
seenContentHashes.add(newContentHash);
|
|
353119
|
-
|
|
353120
|
-
|
|
353121
|
-
|
|
353122
|
-
|
|
353123
|
-
|
|
353522
|
+
patch5.base_generation = currentGenSha;
|
|
353523
|
+
patch5.patch_content = diff2;
|
|
353524
|
+
patch5.content_hash = newContentHash;
|
|
353525
|
+
try {
|
|
353526
|
+
this.lockManager.updatePatch(patch5.id, {
|
|
353527
|
+
base_generation: currentGenSha,
|
|
353528
|
+
patch_content: diff2,
|
|
353529
|
+
content_hash: newContentHash
|
|
353530
|
+
});
|
|
353531
|
+
} catch {
|
|
353532
|
+
}
|
|
353124
353533
|
contentRebased++;
|
|
353125
353534
|
} catch {
|
|
353126
353535
|
}
|
|
353127
353536
|
}
|
|
353128
353537
|
return { absorbed, repointed, contentRebased, keptAsUserOwned, absorbedPatchIds };
|
|
353129
353538
|
}
|
|
353539
|
+
/**
|
|
353540
|
+
* Determine whether `file` is user-owned (never produced by the generator).
|
|
353541
|
+
*
|
|
353542
|
+
* Resolution order (first match wins):
|
|
353543
|
+
* 1. `patch.user_owned === true` — authoritative, persisted across cycles.
|
|
353544
|
+
* 2. `.fernignore` itself, or file matches a .fernignore pattern.
|
|
353545
|
+
* 3. Legacy-safe signal: `patch_content` shows this file as a `--- /dev/null`
|
|
353546
|
+
* creation. Works for patches that predate the `user_owned` field or
|
|
353547
|
+
* whose flag was lost. Immune to rename tricks — relies on raw patch text.
|
|
353548
|
+
* 4. Tree-based check against `patch.base_generation` when it is reachable
|
|
353549
|
+
* AND distinct from `currentGenSha`. If the base tree is unreachable
|
|
353550
|
+
* (shallow clone / aggressive `git gc --prune`), emit a one-time
|
|
353551
|
+
* customer-actionable warning (deduplicated via warnedGens) and
|
|
353552
|
+
* conservatively treat as user-owned — strictly safer than silent
|
|
353553
|
+
* absorption (FER-9809).
|
|
353554
|
+
* 5. Final fallback — check `currentGenSha` when there is no usable base
|
|
353555
|
+
* (legacy one-gen lockfile). Preserves pre-fix behavior for that edge.
|
|
353556
|
+
*
|
|
353557
|
+
* `originalFileName` is the pre-rename path when the generator moved the
|
|
353558
|
+
* file between the patch's base and the current generation. Tree lookups
|
|
353559
|
+
* at ancestor generations use the original path — that's where the
|
|
353560
|
+
* generator produced the content.
|
|
353561
|
+
*/
|
|
353562
|
+
async isFileUserOwned(file4, patch5, currentGenSha, fernignorePatterns, warnedGens, originalFileName) {
|
|
353563
|
+
if (patch5.user_owned) return true;
|
|
353564
|
+
if (file4 === ".fernignore") return true;
|
|
353565
|
+
if (fernignorePatterns.some((p10) => file4 === p10 || (0, import_minimatch22.minimatch)(file4, p10))) return true;
|
|
353566
|
+
if (this.fileIsCreationInPatchContent(patch5.patch_content, originalFileName ?? file4)) {
|
|
353567
|
+
return true;
|
|
353568
|
+
}
|
|
353569
|
+
const base4 = patch5.base_generation;
|
|
353570
|
+
if (base4 && base4 !== currentGenSha) {
|
|
353571
|
+
const reachable = await this.git.commitExists(base4) || await this.git.treeExists(base4);
|
|
353572
|
+
if (!reachable) {
|
|
353573
|
+
if (!warnedGens.has(base4)) {
|
|
353574
|
+
warnedGens.add(base4);
|
|
353575
|
+
this.warnings.push(
|
|
353576
|
+
`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.`
|
|
353577
|
+
);
|
|
353578
|
+
}
|
|
353579
|
+
return true;
|
|
353580
|
+
}
|
|
353581
|
+
return await this.git.showFile(base4, originalFileName ?? file4) === null;
|
|
353582
|
+
}
|
|
353583
|
+
return await this.git.showFile(currentGenSha, originalFileName ?? file4) === null;
|
|
353584
|
+
}
|
|
353585
|
+
/**
|
|
353586
|
+
* Returns true if `file`'s diff section in `patchContent` starts with
|
|
353587
|
+
* `--- /dev/null`, meaning this file was introduced by the patch (user
|
|
353588
|
+
* creation). Used as a legacy-safe fallback when `patch.user_owned` is
|
|
353589
|
+
* absent or cleared.
|
|
353590
|
+
*/
|
|
353591
|
+
fileIsCreationInPatchContent(patchContent, file4) {
|
|
353592
|
+
const lines = patchContent.split("\n");
|
|
353593
|
+
let inFileSection = false;
|
|
353594
|
+
for (const line of lines) {
|
|
353595
|
+
if (line.startsWith("diff --git ")) {
|
|
353596
|
+
inFileSection = line.includes(` b/${file4}`) || line.endsWith(` b/${file4}`);
|
|
353597
|
+
continue;
|
|
353598
|
+
}
|
|
353599
|
+
if (inFileSection && line.startsWith("--- ")) {
|
|
353600
|
+
return line === "--- /dev/null";
|
|
353601
|
+
}
|
|
353602
|
+
}
|
|
353603
|
+
return false;
|
|
353604
|
+
}
|
|
353130
353605
|
/**
|
|
353131
353606
|
* For conflict patches with mixed results (some files merged, some conflicted),
|
|
353132
353607
|
* check if the cleanly merged files were absorbed by the generator (empty diff).
|
|
@@ -353140,13 +353615,14 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353140
353615
|
const cleanFiles = result.fileResults.filter((f10) => f10.status === "merged").map((f10) => f10.file);
|
|
353141
353616
|
if (cleanFiles.length === 0) return;
|
|
353142
353617
|
const patch5 = result.patch;
|
|
353618
|
+
if (patch5.user_owned) return;
|
|
353143
353619
|
const fernignorePatterns = this.readFernignorePatterns();
|
|
353620
|
+
const warnedGens = /* @__PURE__ */ new Set();
|
|
353144
353621
|
const generatorCleanFiles = [];
|
|
353145
353622
|
for (const file4 of cleanFiles) {
|
|
353146
|
-
if (file4
|
|
353147
|
-
|
|
353148
|
-
|
|
353149
|
-
if (content5 === null) continue;
|
|
353623
|
+
if (await this.isFileUserOwned(file4, patch5, currentGenSha, fernignorePatterns, warnedGens)) {
|
|
353624
|
+
continue;
|
|
353625
|
+
}
|
|
353150
353626
|
generatorCleanFiles.push(file4);
|
|
353151
353627
|
}
|
|
353152
353628
|
if (generatorCleanFiles.length === 0) return;
|
|
@@ -353183,7 +353659,35 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353183
353659
|
let conflictResolved = 0;
|
|
353184
353660
|
let conflictAbsorbed = 0;
|
|
353185
353661
|
let contentRefreshed = 0;
|
|
353662
|
+
const fernignorePatterns = this.readFernignorePatterns();
|
|
353663
|
+
const warnedGens = /* @__PURE__ */ new Set();
|
|
353664
|
+
const reachabilityCache = /* @__PURE__ */ new Map();
|
|
353665
|
+
const isReachable = async (sha) => {
|
|
353666
|
+
const cached2 = reachabilityCache.get(sha);
|
|
353667
|
+
if (cached2 !== void 0) return cached2;
|
|
353668
|
+
const reachable = await this.git.commitExists(sha) || await this.git.treeExists(sha);
|
|
353669
|
+
reachabilityCache.set(sha, reachable);
|
|
353670
|
+
return reachable;
|
|
353671
|
+
};
|
|
353672
|
+
const allPatchFilesUserOwned = async (patch5) => {
|
|
353673
|
+
if (patch5.user_owned) return true;
|
|
353674
|
+
if (patch5.files.length === 0) return false;
|
|
353675
|
+
for (const file4 of patch5.files) {
|
|
353676
|
+
if (!await this.isFileUserOwned(file4, patch5, currentGen, fernignorePatterns, warnedGens)) {
|
|
353677
|
+
return false;
|
|
353678
|
+
}
|
|
353679
|
+
}
|
|
353680
|
+
return true;
|
|
353681
|
+
};
|
|
353186
353682
|
for (const patch5 of patches) {
|
|
353683
|
+
if (patch5.base_generation && patch5.base_generation !== currentGen && !warnedGens.has(patch5.base_generation)) {
|
|
353684
|
+
if (!await isReachable(patch5.base_generation)) {
|
|
353685
|
+
warnedGens.add(patch5.base_generation);
|
|
353686
|
+
this.warnings.push(
|
|
353687
|
+
`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.`
|
|
353688
|
+
);
|
|
353689
|
+
}
|
|
353690
|
+
}
|
|
353187
353691
|
if (patch5.status != null) {
|
|
353188
353692
|
delete patch5.status;
|
|
353189
353693
|
continue;
|
|
@@ -353193,6 +353697,7 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353193
353697
|
const diff2 = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch5.files]).catch(() => null);
|
|
353194
353698
|
if (diff2 === null) continue;
|
|
353195
353699
|
if (!diff2.trim()) {
|
|
353700
|
+
if (await allPatchFilesUserOwned(patch5)) continue;
|
|
353196
353701
|
this.lockManager.removePatch(patch5.id);
|
|
353197
353702
|
contentRefreshed++;
|
|
353198
353703
|
continue;
|
|
@@ -353204,6 +353709,12 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353204
353709
|
if (hasStaleMarkers) {
|
|
353205
353710
|
continue;
|
|
353206
353711
|
}
|
|
353712
|
+
const isProtected = patch5.files.some(
|
|
353713
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p10) => file4 === p10 || (0, import_minimatch22.minimatch)(file4, p10))
|
|
353714
|
+
);
|
|
353715
|
+
if (isProtected) {
|
|
353716
|
+
continue;
|
|
353717
|
+
}
|
|
353207
353718
|
const newContentHash = this.detector.computeContentHash(diff2);
|
|
353208
353719
|
if (newContentHash !== patch5.content_hash) {
|
|
353209
353720
|
const filesOutput = await this.git.exec(["diff", "--name-only", currentGen, "HEAD", "--", ...patch5.files]).catch(() => null);
|
|
@@ -353229,6 +353740,13 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353229
353740
|
const diff2 = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch5.files]).catch(() => null);
|
|
353230
353741
|
if (diff2 === null) continue;
|
|
353231
353742
|
if (!diff2.trim()) {
|
|
353743
|
+
if (await allPatchFilesUserOwned(patch5)) {
|
|
353744
|
+
try {
|
|
353745
|
+
this.lockManager.updatePatch(patch5.id, { base_generation: currentGen });
|
|
353746
|
+
} catch {
|
|
353747
|
+
}
|
|
353748
|
+
continue;
|
|
353749
|
+
}
|
|
353232
353750
|
this.lockManager.removePatch(patch5.id);
|
|
353233
353751
|
conflictAbsorbed++;
|
|
353234
353752
|
continue;
|
|
@@ -353736,7 +354254,10 @@ CLI Version: ${options2.cliVersion}`;
|
|
|
353736
354254
|
(0, import_node_fs42.writeFileSync)(gitattributesPath, content5, "utf-8");
|
|
353737
354255
|
}
|
|
353738
354256
|
function computeContentHash(patchContent) {
|
|
353739
|
-
const
|
|
354257
|
+
const lines = patchContent.split("\n");
|
|
354258
|
+
const diffStart = lines.findIndex((l14) => l14.startsWith("diff --git "));
|
|
354259
|
+
const relevantLines = diffStart > 0 ? lines.slice(diffStart) : lines;
|
|
354260
|
+
const normalized = relevantLines.filter((line) => !line.startsWith("index ")).join("\n").replace(/\n-- \n[\d]+\.[\d]+[^\n]*\s*$/, "");
|
|
353740
354261
|
return `sha256:${(0, import_node_crypto32.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
353741
354262
|
}
|
|
353742
354263
|
var import_minimatch42 = require_commonjs3();
|
|
@@ -621394,7 +621915,7 @@ var AccessTokenPosthogManager = class {
|
|
|
621394
621915
|
properties: {
|
|
621395
621916
|
...event,
|
|
621396
621917
|
...event.properties,
|
|
621397
|
-
version: "4.
|
|
621918
|
+
version: "4.76.0",
|
|
621398
621919
|
usingAccessToken: true
|
|
621399
621920
|
}
|
|
621400
621921
|
});
|
|
@@ -621448,7 +621969,7 @@ var UserPosthogManager = class {
|
|
|
621448
621969
|
distinctId: this.userId ?? await this.getPersistedDistinctId(),
|
|
621449
621970
|
event: "CLI",
|
|
621450
621971
|
properties: {
|
|
621451
|
-
version: "4.
|
|
621972
|
+
version: "4.76.0",
|
|
621452
621973
|
...event,
|
|
621453
621974
|
...event.properties,
|
|
621454
621975
|
usingAccessToken: false,
|
|
@@ -838658,7 +839179,7 @@ var LOCAL_STORAGE_FOLDER4 = ".fern-dev";
|
|
|
838658
839179
|
var LOGS_FOLDER_NAME = "logs";
|
|
838659
839180
|
var MAX_LOGS_DIR_SIZE_BYTES = 100 * 1024 * 1024;
|
|
838660
839181
|
function getCliSource() {
|
|
838661
|
-
const version7 = "4.
|
|
839182
|
+
const version7 = "4.76.0";
|
|
838662
839183
|
return `cli@${version7}`;
|
|
838663
839184
|
}
|
|
838664
839185
|
var DebugLogger = class {
|
|
@@ -839024,7 +839545,7 @@ function isWithinOutputDir(resolvedPath, outputDir) {
|
|
|
839024
839545
|
const rel2 = import_path55.default.relative(outputDir, resolvedPath);
|
|
839025
839546
|
return !rel2.startsWith("..") && !import_path55.default.isAbsolute(rel2);
|
|
839026
839547
|
}
|
|
839027
|
-
function resolveWindowsSymlinks(outputDir, symlinks, logger4) {
|
|
839548
|
+
function resolveWindowsSymlinks(outputDir, symlinks, logger4, onProgress) {
|
|
839028
839549
|
if (symlinks.length === 0) {
|
|
839029
839550
|
return;
|
|
839030
839551
|
}
|
|
@@ -839094,6 +839615,7 @@ function resolveWindowsSymlinks(outputDir, symlinks, logger4) {
|
|
|
839094
839615
|
logger4.debug(`Failed to resolve symlink ${symlinkPath}: ${error50}`);
|
|
839095
839616
|
failed++;
|
|
839096
839617
|
}
|
|
839618
|
+
onProgress?.(junctions + copies + alreadyExisted + failed, symlinks.length);
|
|
839097
839619
|
}
|
|
839098
839620
|
logger4.debug(`Symlink resolution: ${junctions} junctions, ${copies} file copies, ${alreadyExisted} pre-existing, ${failed} failed (${fallbackUsed} via fallback) out of ${symlinks.length}`);
|
|
839099
839621
|
const standaloneNM = import_path55.default.join(outputDir, "standalone", "node_modules");
|
|
@@ -839232,7 +839754,7 @@ async function downloadBundle({ bucketUrl, logger: logger4, preferCached, app =
|
|
|
839232
839754
|
hideCursor: true
|
|
839233
839755
|
});
|
|
839234
839756
|
unzipProgressBar.start(100, 0);
|
|
839235
|
-
const UNZIP_DURATION_MS = 3e4;
|
|
839757
|
+
const UNZIP_DURATION_MS = PLATFORM_IS_WINDOWS ? 6e4 : 3e4;
|
|
839236
839758
|
const startTime = Date.now();
|
|
839237
839759
|
unzipInterval = setInterval(() => {
|
|
839238
839760
|
const elapsed = Date.now() - startTime;
|
|
@@ -839269,7 +839791,21 @@ async function downloadBundle({ bucketUrl, logger: logger4, preferCached, app =
|
|
|
839269
839791
|
}
|
|
839270
839792
|
}
|
|
839271
839793
|
if (PLATFORM_IS_WINDOWS && collectedSymlinks.length > 0) {
|
|
839272
|
-
|
|
839794
|
+
let symlinkProgressBar;
|
|
839795
|
+
if (app) {
|
|
839796
|
+
symlinkProgressBar = new import_cli_progress.default.SingleBar({
|
|
839797
|
+
format: `${DOCS_PREFIX} ${formatProgressLabel("Patching symlinks")} [{bar}] {percentage}% | {value}/{total}`,
|
|
839798
|
+
barCompleteChar: "\u2588",
|
|
839799
|
+
barIncompleteChar: "\u2591",
|
|
839800
|
+
hideCursor: true
|
|
839801
|
+
});
|
|
839802
|
+
symlinkProgressBar.start(collectedSymlinks.length, 0);
|
|
839803
|
+
}
|
|
839804
|
+
resolveWindowsSymlinks(absolutePathToBundleFolder, collectedSymlinks, logger4, symlinkProgressBar ? (resolved, total) => symlinkProgressBar?.update(resolved) : void 0);
|
|
839805
|
+
if (symlinkProgressBar) {
|
|
839806
|
+
symlinkProgressBar.update(collectedSymlinks.length);
|
|
839807
|
+
symlinkProgressBar.stop();
|
|
839808
|
+
}
|
|
839273
839809
|
}
|
|
839274
839810
|
await (0, import_promises78.writeFile)(eTagFilepath, eTag);
|
|
839275
839811
|
logger4.debug(`Downloaded bundle to ${absolutePathToBundleFolder}`);
|
|
@@ -849958,7 +850494,7 @@ var LegacyDocsPublisher = class {
|
|
|
849958
850494
|
previewId,
|
|
849959
850495
|
disableTemplates: void 0,
|
|
849960
850496
|
skipUpload,
|
|
849961
|
-
cliVersion: "4.
|
|
850497
|
+
cliVersion: "4.76.0"
|
|
849962
850498
|
});
|
|
849963
850499
|
if (taskContext.getResult() === TaskResult.Failure) {
|
|
849964
850500
|
return { success: false };
|
|
@@ -924416,7 +924952,7 @@ var CliContext = class _CliContext {
|
|
|
924416
924952
|
if (false) {
|
|
924417
924953
|
this.logger.error("CLI_VERSION is not defined");
|
|
924418
924954
|
}
|
|
924419
|
-
return "4.
|
|
924955
|
+
return "4.76.0";
|
|
924420
924956
|
}
|
|
924421
924957
|
getCliName() {
|
|
924422
924958
|
if (false) {
|
package/package.json
CHANGED