@fern-api/generator-cli 0.9.37 → 0.9.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +114 -33
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -22134,7 +22134,7 @@ var init_diff3 = __esm({
|
|
|
22134
22134
|
}
|
|
22135
22135
|
});
|
|
22136
22136
|
|
|
22137
|
-
// ../../node_modules/.pnpm/@fern-api+replay@0.
|
|
22137
|
+
// ../../node_modules/.pnpm/@fern-api+replay@0.18.0/node_modules/@fern-api/replay/dist/index.js
|
|
22138
22138
|
var dist_exports = {};
|
|
22139
22139
|
__export(dist_exports, {
|
|
22140
22140
|
CREDENTIAL_PATTERNS: () => CREDENTIAL_PATTERNS,
|
|
@@ -22495,7 +22495,8 @@ function isBinaryFile(filePath) {
|
|
|
22495
22495
|
}
|
|
22496
22496
|
function matchesFernignorePattern(filePath, patterns) {
|
|
22497
22497
|
if (patterns.length === 0) return false;
|
|
22498
|
-
return patterns.some((
|
|
22498
|
+
return patterns.some((rawPattern) => {
|
|
22499
|
+
const p2 = rawPattern.endsWith("/") ? rawPattern.slice(0, -1) : rawPattern;
|
|
22499
22500
|
if (filePath === p2) return true;
|
|
22500
22501
|
if (minimatch(filePath, p2)) return true;
|
|
22501
22502
|
if (!p2.includes("*") && !p2.includes("?") && filePath.startsWith(p2 + "/")) return true;
|
|
@@ -22739,16 +22740,20 @@ async function resolveInputs(args) {
|
|
|
22739
22740
|
const oursPath = (0, import_path11.join)(outputDir, resolvedPath);
|
|
22740
22741
|
const ours = await (0, import_promises5.readFile)(oursPath, "utf-8").catch(() => null);
|
|
22741
22742
|
let ghostTheirs = null;
|
|
22742
|
-
|
|
22743
|
+
let baseTreeUnreachable = false;
|
|
22744
|
+
if (!base && !renameSourcePath) {
|
|
22743
22745
|
const treeReachable = await isTreeReachable(baseGen.tree_hash);
|
|
22744
22746
|
if (!treeReachable) {
|
|
22745
|
-
|
|
22746
|
-
if (
|
|
22747
|
-
const
|
|
22748
|
-
|
|
22749
|
-
|
|
22750
|
-
|
|
22751
|
-
|
|
22747
|
+
baseTreeUnreachable = true;
|
|
22748
|
+
if (ours) {
|
|
22749
|
+
const fileDiff = extractFileDiff2(patch.patch_content, filePath);
|
|
22750
|
+
if (fileDiff) {
|
|
22751
|
+
const { reconstructFromGhostPatch: reconstructFromGhostPatch2 } = await Promise.resolve().then(() => (init_HybridReconstruction(), HybridReconstruction_exports));
|
|
22752
|
+
const result = reconstructFromGhostPatch2(fileDiff, ours);
|
|
22753
|
+
if (result) {
|
|
22754
|
+
base = result.base;
|
|
22755
|
+
ghostTheirs = result.theirs;
|
|
22756
|
+
}
|
|
22752
22757
|
}
|
|
22753
22758
|
}
|
|
22754
22759
|
}
|
|
@@ -22760,7 +22765,11 @@ async function resolveInputs(args) {
|
|
|
22760
22765
|
ours,
|
|
22761
22766
|
oursPath,
|
|
22762
22767
|
renameSourcePath,
|
|
22763
|
-
ghostTheirs
|
|
22768
|
+
ghostTheirs,
|
|
22769
|
+
// Only meaningful when ghost reconstruction did NOT recover a base; if it
|
|
22770
|
+
// did, `base` is non-null and the merge proceeds normally. We still set
|
|
22771
|
+
// the flag truthfully (the tree IS unreachable) so Phase 3 can decide.
|
|
22772
|
+
baseTreeUnreachable
|
|
22764
22773
|
};
|
|
22765
22774
|
}
|
|
22766
22775
|
function trimCR(line) {
|
|
@@ -22936,7 +22945,11 @@ async function buildMergePlan(args) {
|
|
|
22936
22945
|
return { kind: "new-file-only-user", theirs: effective_theirs };
|
|
22937
22946
|
}
|
|
22938
22947
|
if (base == null && ours != null && effective_theirs != null && !useAccumulatorAsMergeBase) {
|
|
22939
|
-
return {
|
|
22948
|
+
return {
|
|
22949
|
+
kind: "new-file-both",
|
|
22950
|
+
theirs: effective_theirs,
|
|
22951
|
+
...inputs.baseTreeUnreachable ? { baseTreeUnreachable: true } : {}
|
|
22952
|
+
};
|
|
22940
22953
|
}
|
|
22941
22954
|
if (effective_theirs == null) {
|
|
22942
22955
|
return { kind: "skipped", reason: "missing-content" };
|
|
@@ -22980,17 +22993,19 @@ async function runMergeAndRecord(args) {
|
|
|
22980
22993
|
const merged2 = threeWayMerge("", ours, plan.theirs);
|
|
22981
22994
|
await (0, import_promises6.mkdir)((0, import_path12.dirname)(oursPath), { recursive: true });
|
|
22982
22995
|
await (0, import_promises6.writeFile)(oursPath, merged2.content);
|
|
22996
|
+
const unreachableFlag = plan.baseTreeUnreachable ? { baseTreeUnreachable: true } : {};
|
|
22983
22997
|
if (merged2.hasConflicts) {
|
|
22984
22998
|
return {
|
|
22985
22999
|
file: resolvedPath,
|
|
22986
23000
|
status: "conflict",
|
|
22987
23001
|
conflicts: merged2.conflicts,
|
|
22988
23002
|
conflictReason: "new-file-both",
|
|
22989
|
-
conflictMetadata: metadata
|
|
23003
|
+
conflictMetadata: metadata,
|
|
23004
|
+
...unreachableFlag
|
|
22990
23005
|
};
|
|
22991
23006
|
}
|
|
22992
23007
|
accumulator.recordMerge(resolvedPath, merged2.content, patch.base_generation);
|
|
22993
|
-
return { file: resolvedPath, status: "merged" };
|
|
23008
|
+
return { file: resolvedPath, status: "merged", ...unreachableFlag };
|
|
22994
23009
|
}
|
|
22995
23010
|
const merged = threeWayMerge(plan.mergeBase, ours, plan.theirs);
|
|
22996
23011
|
await (0, import_promises6.mkdir)((0, import_path12.dirname)(oursPath), { recursive: true });
|
|
@@ -24064,7 +24079,7 @@ function status(outputDir) {
|
|
|
24064
24079
|
}
|
|
24065
24080
|
var import_promises3, import_os, import_path7, import_fs6, import_path8, import_yaml, import_crypto, import_path9, import_promises4, import_os2, import_path10, import_promises5, import_path11, import_promises6, import_path12, import_fs7, import_promises7, import_path13, import_promises8, import_os3, import_path14, import_child_process3, import_crypto2, import_fs8, import_path15, import_yaml2, import_crypto3, import_fs9, import_path16, import_fs10, import_promises9, import_path17, __defProp3, __getOwnPropNames3, __esm3, __export3, GitClient_exports, GitClient, init_GitClient, HybridReconstruction_exports, init_HybridReconstruction, PatchApplyTheirs_exports, init_PatchApplyTheirs, CREDENTIAL_PATTERNS, SENSITIVE_FILE_PATTERNS, FERN_BOT_NAME, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_SUPPORT_NAMES, LOCKFILE_HEADER, LockfileNotFoundError, LockfileManager, BINARY_EXTENSIONS, INFRASTRUCTURE_FILES, ReplayDetector, MergeAccumulator, CONFLICT_OPENER, CONFLICT_SEPARATOR, CONFLICT_CLOSER, ReplayApplicator, ReplayCommitter, FileOwnership, FirstGenerationFlow, SkipApplicationFlow, NoPatchesFlow, NormalRegenerationFlow, ReplayService, FernignoreMigrator, REPLAY_FERNIGNORE_ENTRIES, GITATTRIBUTES_ENTRIES, EMPTY_RESULT;
|
|
24066
24081
|
var init_dist3 = __esm({
|
|
24067
|
-
"../../node_modules/.pnpm/@fern-api+replay@0.
|
|
24082
|
+
"../../node_modules/.pnpm/@fern-api+replay@0.18.0/node_modules/@fern-api/replay/dist/index.js"() {
|
|
24068
24083
|
"use strict";
|
|
24069
24084
|
init_esm4();
|
|
24070
24085
|
import_promises3 = require("fs/promises");
|
|
@@ -24145,6 +24160,11 @@ var init_dist3 = __esm({
|
|
|
24145
24160
|
proc.stderr.on("data", (data) => {
|
|
24146
24161
|
stderr += data.toString();
|
|
24147
24162
|
});
|
|
24163
|
+
proc.on("error", (err) => {
|
|
24164
|
+
reject(new Error(`git ${args.join(" ")} failed to spawn: ${err.message}`));
|
|
24165
|
+
});
|
|
24166
|
+
proc.stdin.on("error", () => {
|
|
24167
|
+
});
|
|
24148
24168
|
proc.on("close", (code) => {
|
|
24149
24169
|
if (code === 0) {
|
|
24150
24170
|
resolve22(stdout);
|
|
@@ -24599,6 +24619,22 @@ var init_dist3 = __esm({
|
|
|
24599
24619
|
return commit.sha;
|
|
24600
24620
|
}
|
|
24601
24621
|
}
|
|
24622
|
+
let fullLog;
|
|
24623
|
+
try {
|
|
24624
|
+
fullLog = await this.git.exec([
|
|
24625
|
+
"log",
|
|
24626
|
+
"--no-merges",
|
|
24627
|
+
"--format=%H%x00%an%x00%ae%x00%s",
|
|
24628
|
+
"HEAD"
|
|
24629
|
+
]);
|
|
24630
|
+
} catch {
|
|
24631
|
+
return null;
|
|
24632
|
+
}
|
|
24633
|
+
for (const commit of this.parseGitLog(fullLog)) {
|
|
24634
|
+
if (isGenerationBoundary(commit)) {
|
|
24635
|
+
return commit.sha;
|
|
24636
|
+
}
|
|
24637
|
+
}
|
|
24602
24638
|
return null;
|
|
24603
24639
|
}
|
|
24604
24640
|
async detectNewPatches() {
|
|
@@ -24610,6 +24646,11 @@ var init_dist3 = __esm({
|
|
|
24610
24646
|
}
|
|
24611
24647
|
const lastGen = this.getLastGeneration(lock);
|
|
24612
24648
|
if (!lastGen) {
|
|
24649
|
+
if (lock.current_generation) {
|
|
24650
|
+
this.warnings.push(
|
|
24651
|
+
`No generation boundary found in git history and the lockfile's recorded generation (${lock.current_generation.slice(0, 7)}) matches no entry in its generation records. Customer customizations may differ from the last known generation and could be lost on the next regeneration. Run \`fern-replay bootstrap --force\` to re-initialize tracking.`
|
|
24652
|
+
);
|
|
24653
|
+
}
|
|
24613
24654
|
return { patches: [], revertedPatchIds: [] };
|
|
24614
24655
|
}
|
|
24615
24656
|
const exists2 = await this.git.commitExists(lastGen.commit_sha);
|
|
@@ -24657,20 +24698,15 @@ var init_dist3 = __esm({
|
|
|
24657
24698
|
continue;
|
|
24658
24699
|
}
|
|
24659
24700
|
const parents = await this.git.getCommitParents(commit.sha);
|
|
24660
|
-
let
|
|
24701
|
+
let filesOutput;
|
|
24661
24702
|
try {
|
|
24662
|
-
|
|
24703
|
+
filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
24663
24704
|
} catch {
|
|
24664
24705
|
this.warnings.push(
|
|
24665
24706
|
`Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
|
|
24666
24707
|
);
|
|
24667
24708
|
continue;
|
|
24668
24709
|
}
|
|
24669
|
-
let contentHash = this.computeContentHash(patchContent);
|
|
24670
|
-
if (lock.patches.find((p2) => p2.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
24671
|
-
continue;
|
|
24672
|
-
}
|
|
24673
|
-
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
24674
24710
|
const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
|
|
24675
24711
|
filesOutput,
|
|
24676
24712
|
this.fernignorePatterns
|
|
@@ -24685,19 +24721,31 @@ var init_dist3 = __esm({
|
|
|
24685
24721
|
`.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
|
|
24686
24722
|
);
|
|
24687
24723
|
}
|
|
24688
|
-
if (
|
|
24689
|
-
|
|
24690
|
-
|
|
24691
|
-
|
|
24692
|
-
|
|
24693
|
-
|
|
24694
|
-
|
|
24695
|
-
|
|
24696
|
-
|
|
24697
|
-
|
|
24724
|
+
if (files.length === 0) {
|
|
24725
|
+
continue;
|
|
24726
|
+
}
|
|
24727
|
+
const wasFiltered = sensitiveFiles.length > 0 || fernignoredFiles.length > 0;
|
|
24728
|
+
const parentSha = parents[0];
|
|
24729
|
+
let patchContent;
|
|
24730
|
+
if (wasFiltered && parentSha) {
|
|
24731
|
+
try {
|
|
24732
|
+
patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
|
|
24733
|
+
} catch {
|
|
24734
|
+
continue;
|
|
24735
|
+
}
|
|
24736
|
+
if (!patchContent.trim()) continue;
|
|
24737
|
+
} else {
|
|
24738
|
+
try {
|
|
24739
|
+
patchContent = await this.git.formatPatch(commit.sha);
|
|
24740
|
+
} catch {
|
|
24741
|
+
this.warnings.push(
|
|
24742
|
+
`Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
|
|
24743
|
+
);
|
|
24744
|
+
continue;
|
|
24698
24745
|
}
|
|
24699
24746
|
}
|
|
24700
|
-
|
|
24747
|
+
const contentHash = this.computeContentHash(patchContent);
|
|
24748
|
+
if (lock.patches.find((p2) => p2.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
24701
24749
|
continue;
|
|
24702
24750
|
}
|
|
24703
24751
|
const theirsSnapshot = await capturePatchSnapshot(this.git, files, commit.sha);
|
|
@@ -25984,6 +26032,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25984
26032
|
results = await this.service.applicator.applyPatches(newPatches);
|
|
25985
26033
|
this.service._lastApplyResults = results;
|
|
25986
26034
|
this.service.recordDroppedContextLineWarnings(results);
|
|
26035
|
+
this.service.recordUnreachableBaseWarnings(results);
|
|
25987
26036
|
this.service.revertConflictingFiles(results);
|
|
25988
26037
|
for (const result of results) {
|
|
25989
26038
|
if (result.status === "conflict") {
|
|
@@ -26137,6 +26186,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
26137
26186
|
const results = await this.service.applicator.applyPatches(allPatches);
|
|
26138
26187
|
this.service._lastApplyResults = results;
|
|
26139
26188
|
this.service.recordDroppedContextLineWarnings(results);
|
|
26189
|
+
this.service.recordUnreachableBaseWarnings(results);
|
|
26140
26190
|
this.service.revertConflictingFiles(results);
|
|
26141
26191
|
for (const result of results) {
|
|
26142
26192
|
if (result.status === "conflict") {
|
|
@@ -27047,6 +27097,37 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
27047
27097
|
}
|
|
27048
27098
|
}
|
|
27049
27099
|
}
|
|
27100
|
+
/**
|
|
27101
|
+
* After applyPatches(), surface a warning for each (patch × file) where a
|
|
27102
|
+
* customization's BASE could not be read because the patch's
|
|
27103
|
+
* base-generation tree is unreachable in this clone (shallow-clone window
|
|
27104
|
+
* too small / pruned history). In that state the merge pipeline falls
|
|
27105
|
+
* through to `new-file-both` and the customer's edit can be dropped with NO
|
|
27106
|
+
* conflict and NO error — the INC-866 silent-loss class. This converts that
|
|
27107
|
+
* silence into an explicit, actionable warning naming the file and the
|
|
27108
|
+
* likely cause (insufficient clone window).
|
|
27109
|
+
*
|
|
27110
|
+
* Mirrors `recordDroppedContextLineWarnings`'s "surface, never silently
|
|
27111
|
+
* drop" contract. De-duplicated per file path so a single insufficient
|
|
27112
|
+
* window doesn't spam one warning per touched file repeatedly.
|
|
27113
|
+
*/
|
|
27114
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
27115
|
+
recordUnreachableBaseWarnings(results) {
|
|
27116
|
+
const warned = /* @__PURE__ */ new Set();
|
|
27117
|
+
for (const result of results) {
|
|
27118
|
+
if (!result.fileResults) continue;
|
|
27119
|
+
for (const fr of result.fileResults) {
|
|
27120
|
+
if (!fr.baseTreeUnreachable) continue;
|
|
27121
|
+
if (warned.has(fr.file)) continue;
|
|
27122
|
+
warned.add(fr.file);
|
|
27123
|
+
const base = fr.conflictMetadata?.baseGeneration;
|
|
27124
|
+
const baseRef = base ? ` (base generation ${base.slice(0, 7)})` : "";
|
|
27125
|
+
this.warnings.push(
|
|
27126
|
+
`${fr.file}: could not read the prior generation's version of this file${baseRef} because that generation's tree is outside the clone window (insufficient shallow-clone depth or pruned history). The customization was treated as a new file, which can drop your edits silently. Re-run with the prior generation in the clone window \u2014 e.g. \`git fetch --shallow-since=<date just before the prior [fern-generated] commit>\` or \`git fetch --unshallow\` \u2014 then re-run replay.`
|
|
27127
|
+
);
|
|
27128
|
+
}
|
|
27129
|
+
}
|
|
27130
|
+
}
|
|
27050
27131
|
/**
|
|
27051
27132
|
* After applyPatches(), strip conflict markers from conflicting files
|
|
27052
27133
|
* so only clean content is committed. Keeps the Generated (OURS) side.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fern-api/generator-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.39",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/fern-api/fern.git",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@boundaryml/baml": "^0.219.0",
|
|
26
|
-
"@fern-api/replay": "0.
|
|
26
|
+
"@fern-api/replay": "0.18.0",
|
|
27
27
|
"@octokit/rest": "^22.0.1",
|
|
28
28
|
"es-toolkit": "^1.45.1",
|
|
29
29
|
"semver": "^7.6.3",
|