@fern-api/generator-cli 0.9.32 → 0.9.34
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/api.js +1877 -64
- package/dist/cli.js +364 -62
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -22126,7 +22126,7 @@ var init_diff3 = __esm({
|
|
|
22126
22126
|
}
|
|
22127
22127
|
});
|
|
22128
22128
|
|
|
22129
|
-
// ../../node_modules/.pnpm/@fern-api+replay@0.16.
|
|
22129
|
+
// ../../node_modules/.pnpm/@fern-api+replay@0.16.2/node_modules/@fern-api/replay/dist/index.js
|
|
22130
22130
|
var dist_exports = {};
|
|
22131
22131
|
__export(dist_exports, {
|
|
22132
22132
|
CREDENTIAL_PATTERNS: () => CREDENTIAL_PATTERNS,
|
|
@@ -22437,13 +22437,13 @@ function isSensitiveFile(filePath) {
|
|
|
22437
22437
|
return SENSITIVE_FILE_PATTERNS.some((re) => re.test(filePath));
|
|
22438
22438
|
}
|
|
22439
22439
|
function scanForCredentials(content) {
|
|
22440
|
-
const
|
|
22440
|
+
const matches2 = [];
|
|
22441
22441
|
for (const { name, re } of CREDENTIAL_PATTERNS) {
|
|
22442
22442
|
if (re.test(content)) {
|
|
22443
|
-
|
|
22443
|
+
matches2.push(name);
|
|
22444
22444
|
}
|
|
22445
22445
|
}
|
|
22446
|
-
return
|
|
22446
|
+
return matches2;
|
|
22447
22447
|
}
|
|
22448
22448
|
function isGenerationCommit(commit) {
|
|
22449
22449
|
const isFernSupport = FERN_SUPPORT_NAMES.includes(commit.authorName);
|
|
@@ -22485,6 +22485,15 @@ function isBinaryFile(filePath) {
|
|
|
22485
22485
|
const ext2 = (0, import_path9.extname)(filePath).toLowerCase();
|
|
22486
22486
|
return BINARY_EXTENSIONS.has(ext2);
|
|
22487
22487
|
}
|
|
22488
|
+
function matchesFernignorePattern(filePath, patterns) {
|
|
22489
|
+
if (patterns.length === 0) return false;
|
|
22490
|
+
return patterns.some((p2) => {
|
|
22491
|
+
if (filePath === p2) return true;
|
|
22492
|
+
if (minimatch(filePath, p2)) return true;
|
|
22493
|
+
if (!p2.includes("*") && !p2.includes("?") && filePath.startsWith(p2 + "/")) return true;
|
|
22494
|
+
return false;
|
|
22495
|
+
});
|
|
22496
|
+
}
|
|
22488
22497
|
function parsePatchFileHeaders(patchContent) {
|
|
22489
22498
|
const results = [];
|
|
22490
22499
|
let currentPath = null;
|
|
@@ -22525,11 +22534,13 @@ async function capturePatchSnapshot(git, files, treeish) {
|
|
|
22525
22534
|
}
|
|
22526
22535
|
return snapshot;
|
|
22527
22536
|
}
|
|
22528
|
-
function filterInfrastructureAndSensitiveFiles(rawFilesOutput) {
|
|
22537
|
+
function filterInfrastructureAndSensitiveFiles(rawFilesOutput, fernignorePatterns) {
|
|
22529
22538
|
const allFiles = rawFilesOutput.trim().split("\n").filter(Boolean).filter((f) => !INFRASTRUCTURE_FILES.has(f)).filter((f) => !f.startsWith(".fern/"));
|
|
22530
22539
|
const sensitiveFiles = allFiles.filter((f) => isSensitiveFile(f));
|
|
22531
|
-
const
|
|
22532
|
-
|
|
22540
|
+
const nonSensitive = allFiles.filter((f) => !isSensitiveFile(f));
|
|
22541
|
+
const fernignoredFiles = nonSensitive.filter((f) => matchesFernignorePattern(f, fernignorePatterns));
|
|
22542
|
+
const files = nonSensitive.filter((f) => !matchesFernignorePattern(f, fernignorePatterns));
|
|
22543
|
+
return { files, sensitiveFiles, fernignoredFiles };
|
|
22533
22544
|
}
|
|
22534
22545
|
function threeWayMerge(base, ours, theirs) {
|
|
22535
22546
|
const baseLines = base.split("\n");
|
|
@@ -23123,12 +23134,23 @@ async function computePerPatchDiff(patch, getCurrentGenContent, getWorkingTreeCo
|
|
|
23123
23134
|
unlocatableFiles.push(file);
|
|
23124
23135
|
continue;
|
|
23125
23136
|
}
|
|
23126
|
-
const locInCurrentGen =
|
|
23127
|
-
|
|
23128
|
-
|
|
23129
|
-
|
|
23130
|
-
|
|
23131
|
-
|
|
23137
|
+
const locInCurrentGen = [];
|
|
23138
|
+
const locInWorkingTree = [];
|
|
23139
|
+
let spanResolutionFailed = false;
|
|
23140
|
+
for (let i2 = 0; i2 < locInCurrentGenRaw.length; i2++) {
|
|
23141
|
+
const cg = resolveSpan(locInCurrentGenRaw[i2], currentGenLines, "old");
|
|
23142
|
+
const wt = resolveSpan(locInWorkingTreeRaw[i2], workingTreeLines, "new");
|
|
23143
|
+
if (cg === null || wt === null) {
|
|
23144
|
+
spanResolutionFailed = true;
|
|
23145
|
+
break;
|
|
23146
|
+
}
|
|
23147
|
+
locInCurrentGen.push(cg);
|
|
23148
|
+
locInWorkingTree.push(wt);
|
|
23149
|
+
}
|
|
23150
|
+
if (spanResolutionFailed) {
|
|
23151
|
+
unlocatableFiles.push(file);
|
|
23152
|
+
continue;
|
|
23153
|
+
}
|
|
23132
23154
|
const overlay = overlayRegions(
|
|
23133
23155
|
currentGenLines,
|
|
23134
23156
|
locInCurrentGen,
|
|
@@ -23190,11 +23212,11 @@ function resolveSpan(loc, oursLines, prefer) {
|
|
|
23190
23212
|
if (prefer === "old") {
|
|
23191
23213
|
if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
|
|
23192
23214
|
if (newFits) return { ...loc, oursSpan: hunk.newCount };
|
|
23193
|
-
|
|
23194
|
-
if (newFits) return { ...loc, oursSpan: hunk.newCount };
|
|
23195
|
-
if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
|
|
23215
|
+
return loc;
|
|
23196
23216
|
}
|
|
23197
|
-
return loc;
|
|
23217
|
+
if (newFits) return { ...loc, oursSpan: hunk.newCount };
|
|
23218
|
+
if (oldFits) return { ...loc, oursSpan: hunk.oldCount };
|
|
23219
|
+
return null;
|
|
23198
23220
|
}
|
|
23199
23221
|
function sequenceMatches(needle, haystack, offset) {
|
|
23200
23222
|
if (offset + needle.length > haystack.length) return false;
|
|
@@ -23364,6 +23386,93 @@ function computeCommitBuckets(results, absorbedPatchIds, patchesPassedToCommit)
|
|
|
23364
23386
|
}
|
|
23365
23387
|
return { applied, unresolved, absorbed };
|
|
23366
23388
|
}
|
|
23389
|
+
function computeLegacyFernignoreCleanse(patch, fernignorePatterns, computeContentHash2) {
|
|
23390
|
+
const matchesFernignore = (filePath) => fernignorePatterns.some((p2) => {
|
|
23391
|
+
if (filePath === p2) return true;
|
|
23392
|
+
if (minimatch(filePath, p2)) return true;
|
|
23393
|
+
if (!p2.includes("*") && !p2.includes("?") && filePath.startsWith(p2 + "/")) return true;
|
|
23394
|
+
return false;
|
|
23395
|
+
});
|
|
23396
|
+
const strippedFromFiles = patch.files.filter(matchesFernignore);
|
|
23397
|
+
const survivingFiles = patch.files.filter((f) => !matchesFernignore(f));
|
|
23398
|
+
let survivingSnapshot = void 0;
|
|
23399
|
+
let snapshotChanged = false;
|
|
23400
|
+
if (patch.theirs_snapshot) {
|
|
23401
|
+
survivingSnapshot = {};
|
|
23402
|
+
for (const [key, value] of Object.entries(patch.theirs_snapshot)) {
|
|
23403
|
+
if (matchesFernignore(key)) {
|
|
23404
|
+
snapshotChanged = true;
|
|
23405
|
+
} else {
|
|
23406
|
+
survivingSnapshot[key] = value;
|
|
23407
|
+
}
|
|
23408
|
+
}
|
|
23409
|
+
}
|
|
23410
|
+
const { stripped: patchContentStripped, content: newPatchContent, strippedSectionPaths } = stripFernignoreDiffSections(patch.patch_content, matchesFernignore);
|
|
23411
|
+
const unchanged = strippedFromFiles.length === 0 && !snapshotChanged && !patchContentStripped;
|
|
23412
|
+
if (unchanged) {
|
|
23413
|
+
return {
|
|
23414
|
+
unchanged: true,
|
|
23415
|
+
remove: false,
|
|
23416
|
+
files: patch.files,
|
|
23417
|
+
patch_content: patch.patch_content,
|
|
23418
|
+
content_hash: patch.content_hash,
|
|
23419
|
+
theirs_snapshot: patch.theirs_snapshot,
|
|
23420
|
+
strippedPaths: []
|
|
23421
|
+
};
|
|
23422
|
+
}
|
|
23423
|
+
const strippedPaths = Array.from(/* @__PURE__ */ new Set([...strippedFromFiles, ...strippedSectionPaths]));
|
|
23424
|
+
if (survivingFiles.length === 0 || newPatchContent.trim() === "") {
|
|
23425
|
+
return {
|
|
23426
|
+
unchanged: false,
|
|
23427
|
+
remove: true,
|
|
23428
|
+
files: [],
|
|
23429
|
+
patch_content: "",
|
|
23430
|
+
content_hash: "",
|
|
23431
|
+
strippedPaths
|
|
23432
|
+
};
|
|
23433
|
+
}
|
|
23434
|
+
const newContentHash = patchContentStripped ? computeContentHash2(newPatchContent) : patch.content_hash;
|
|
23435
|
+
return {
|
|
23436
|
+
unchanged: false,
|
|
23437
|
+
remove: false,
|
|
23438
|
+
files: survivingFiles,
|
|
23439
|
+
patch_content: newPatchContent,
|
|
23440
|
+
content_hash: newContentHash,
|
|
23441
|
+
...survivingSnapshot !== void 0 ? { theirs_snapshot: survivingSnapshot } : {},
|
|
23442
|
+
strippedPaths
|
|
23443
|
+
};
|
|
23444
|
+
}
|
|
23445
|
+
function stripFernignoreDiffSections(patchContent, matchesFernignore) {
|
|
23446
|
+
const lines = patchContent.split("\n");
|
|
23447
|
+
const sectionStarts = [];
|
|
23448
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
23449
|
+
const line = lines[i2];
|
|
23450
|
+
if (line.startsWith("diff --git ")) {
|
|
23451
|
+
const match2 = line.match(/^diff --git a\/(.+?) b\/(.+?)$/);
|
|
23452
|
+
const path5 = match2 ? match2[2] : null;
|
|
23453
|
+
sectionStarts.push({ idx: i2, path: path5 });
|
|
23454
|
+
}
|
|
23455
|
+
}
|
|
23456
|
+
if (sectionStarts.length === 0) {
|
|
23457
|
+
return { stripped: false, content: patchContent, strippedSectionPaths: [] };
|
|
23458
|
+
}
|
|
23459
|
+
const preamble = lines.slice(0, sectionStarts[0].idx);
|
|
23460
|
+
const newLines = [...preamble];
|
|
23461
|
+
const strippedSectionPaths = [];
|
|
23462
|
+
let stripped = false;
|
|
23463
|
+
for (let i2 = 0; i2 < sectionStarts.length; i2++) {
|
|
23464
|
+
const section = sectionStarts[i2];
|
|
23465
|
+
const next = sectionStarts[i2 + 1];
|
|
23466
|
+
const endIdx = next ? next.idx : lines.length;
|
|
23467
|
+
if (section.path !== null && matchesFernignore(section.path)) {
|
|
23468
|
+
stripped = true;
|
|
23469
|
+
strippedSectionPaths.push(section.path);
|
|
23470
|
+
continue;
|
|
23471
|
+
}
|
|
23472
|
+
newLines.push(...lines.slice(section.idx, endIdx));
|
|
23473
|
+
}
|
|
23474
|
+
return { stripped, content: newLines.join("\n"), strippedSectionPaths };
|
|
23475
|
+
}
|
|
23367
23476
|
async function bootstrap(outputDir, options) {
|
|
23368
23477
|
const git = new GitClient(outputDir);
|
|
23369
23478
|
const lockManager = new LockfileManager(outputDir);
|
|
@@ -23947,7 +24056,7 @@ function status(outputDir) {
|
|
|
23947
24056
|
}
|
|
23948
24057
|
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;
|
|
23949
24058
|
var init_dist3 = __esm({
|
|
23950
|
-
"../../node_modules/.pnpm/@fern-api+replay@0.16.
|
|
24059
|
+
"../../node_modules/.pnpm/@fern-api+replay@0.16.2/node_modules/@fern-api/replay/dist/index.js"() {
|
|
23951
24060
|
"use strict";
|
|
23952
24061
|
init_esm4();
|
|
23953
24062
|
import_promises3 = require("fs/promises");
|
|
@@ -23955,8 +24064,10 @@ var init_dist3 = __esm({
|
|
|
23955
24064
|
import_path7 = require("path");
|
|
23956
24065
|
import_fs6 = require("fs");
|
|
23957
24066
|
import_path8 = require("path");
|
|
24067
|
+
init_esm3();
|
|
23958
24068
|
import_yaml = __toESM(require_dist3(), 1);
|
|
23959
24069
|
import_crypto = require("crypto");
|
|
24070
|
+
init_esm3();
|
|
23960
24071
|
import_path9 = require("path");
|
|
23961
24072
|
init_diff3();
|
|
23962
24073
|
import_promises4 = require("fs/promises");
|
|
@@ -24187,10 +24298,23 @@ var init_dist3 = __esm({
|
|
|
24187
24298
|
LockfileManager = class {
|
|
24188
24299
|
outputDir;
|
|
24189
24300
|
lock = null;
|
|
24301
|
+
fernignorePatterns = [];
|
|
24190
24302
|
warnings = [];
|
|
24191
24303
|
constructor(outputDir) {
|
|
24192
24304
|
this.outputDir = outputDir;
|
|
24193
24305
|
}
|
|
24306
|
+
/**
|
|
24307
|
+
* Inform the lockfile of the customer's `.fernignore` patterns so that
|
|
24308
|
+
* `save()` can suppress its "Patch X removed from lockfile" warning when
|
|
24309
|
+
* every file in the stripped patch is already `.fernignore`-protected.
|
|
24310
|
+
* The strip itself still happens (credentials never persist); only the
|
|
24311
|
+
* customer-visible warning is suppressed in that case, because the
|
|
24312
|
+
* protected files survive the generator wipe regardless of whether the
|
|
24313
|
+
* lockfile tracks them, so there's nothing for the customer to action.
|
|
24314
|
+
*/
|
|
24315
|
+
setFernignorePatterns(patterns) {
|
|
24316
|
+
this.fernignorePatterns = patterns;
|
|
24317
|
+
}
|
|
24194
24318
|
get lockfilePath() {
|
|
24195
24319
|
return (0, import_path8.join)(this.outputDir, ".fern", "replay.lock");
|
|
24196
24320
|
}
|
|
@@ -24259,9 +24383,14 @@ var init_dist3 = __esm({
|
|
|
24259
24383
|
if (sensitiveFiles.length > 0) {
|
|
24260
24384
|
reasons.push(`sensitive files: ${sensitiveFiles.join(", ")}`);
|
|
24261
24385
|
}
|
|
24262
|
-
|
|
24263
|
-
|
|
24386
|
+
const allProtected = patch.files.length > 0 && patch.files.every(
|
|
24387
|
+
(f) => this.fernignorePatterns.some((p2) => f === p2 || minimatch(f, p2))
|
|
24264
24388
|
);
|
|
24389
|
+
if (!allProtected) {
|
|
24390
|
+
this.warnings.push(
|
|
24391
|
+
`Patch ${patch.id} removed from lockfile: ${reasons.join("; ")}. This prevents credential exposure in committed lockfiles.`
|
|
24392
|
+
);
|
|
24393
|
+
}
|
|
24265
24394
|
} else {
|
|
24266
24395
|
cleanPatches.push(patch);
|
|
24267
24396
|
}
|
|
@@ -24421,11 +24550,13 @@ var init_dist3 = __esm({
|
|
|
24421
24550
|
git;
|
|
24422
24551
|
lockManager;
|
|
24423
24552
|
sdkOutputDir;
|
|
24553
|
+
fernignorePatterns;
|
|
24424
24554
|
warnings = [];
|
|
24425
|
-
constructor(git, lockManager, sdkOutputDir) {
|
|
24555
|
+
constructor(git, lockManager, sdkOutputDir, fernignorePatterns = []) {
|
|
24426
24556
|
this.git = git;
|
|
24427
24557
|
this.lockManager = lockManager;
|
|
24428
24558
|
this.sdkOutputDir = sdkOutputDir;
|
|
24559
|
+
this.fernignorePatterns = fernignorePatterns;
|
|
24429
24560
|
}
|
|
24430
24561
|
/**
|
|
24431
24562
|
* Derive the previous-generation SHA from git history instead of trusting
|
|
@@ -24532,22 +24663,30 @@ var init_dist3 = __esm({
|
|
|
24532
24663
|
continue;
|
|
24533
24664
|
}
|
|
24534
24665
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
24535
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(
|
|
24666
|
+
const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
|
|
24667
|
+
filesOutput,
|
|
24668
|
+
this.fernignorePatterns
|
|
24669
|
+
);
|
|
24536
24670
|
if (sensitiveFiles.length > 0) {
|
|
24537
24671
|
this.warnings.push(
|
|
24538
24672
|
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
24539
24673
|
);
|
|
24540
|
-
|
|
24541
|
-
|
|
24542
|
-
|
|
24543
|
-
|
|
24544
|
-
|
|
24545
|
-
|
|
24546
|
-
|
|
24547
|
-
|
|
24548
|
-
|
|
24549
|
-
|
|
24674
|
+
}
|
|
24675
|
+
if (fernignoredFiles.length > 0) {
|
|
24676
|
+
this.warnings.push(
|
|
24677
|
+
`.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
|
|
24678
|
+
);
|
|
24679
|
+
}
|
|
24680
|
+
if ((sensitiveFiles.length > 0 || fernignoredFiles.length > 0) && files.length > 0) {
|
|
24681
|
+
const parentSha = parents[0];
|
|
24682
|
+
if (parentSha) {
|
|
24683
|
+
try {
|
|
24684
|
+
patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
|
|
24685
|
+
} catch {
|
|
24686
|
+
continue;
|
|
24550
24687
|
}
|
|
24688
|
+
if (!patchContent.trim()) continue;
|
|
24689
|
+
contentHash = this.computeContentHash(patchContent);
|
|
24551
24690
|
}
|
|
24552
24691
|
}
|
|
24553
24692
|
if (files.length === 0) {
|
|
@@ -24746,12 +24885,20 @@ var init_dist3 = __esm({
|
|
|
24746
24885
|
resolvedBaseGeneration = fallback;
|
|
24747
24886
|
}
|
|
24748
24887
|
const filesOutput = await this.git.exec(["diff", "--name-only", diffBase, "HEAD"]);
|
|
24749
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(
|
|
24888
|
+
const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
|
|
24889
|
+
filesOutput,
|
|
24890
|
+
this.fernignorePatterns
|
|
24891
|
+
);
|
|
24750
24892
|
if (sensitiveFiles.length > 0) {
|
|
24751
24893
|
this.warnings.push(
|
|
24752
24894
|
`Sensitive file(s) excluded from composite patch: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
24753
24895
|
);
|
|
24754
24896
|
}
|
|
24897
|
+
if (fernignoredFiles.length > 0) {
|
|
24898
|
+
this.warnings.push(
|
|
24899
|
+
`.fernignore-protected file(s) excluded from composite patch: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
|
|
24900
|
+
);
|
|
24901
|
+
}
|
|
24755
24902
|
if (files.length === 0) return { patches: [], revertedPatchIds: [] };
|
|
24756
24903
|
const diff = await this.git.exec(["diff", diffBase, "HEAD", "--", ...files]);
|
|
24757
24904
|
if (!diff.trim()) return { patches: [], revertedPatchIds: [] };
|
|
@@ -24834,23 +24981,31 @@ var init_dist3 = __esm({
|
|
|
24834
24981
|
continue;
|
|
24835
24982
|
}
|
|
24836
24983
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
24837
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(
|
|
24984
|
+
const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
|
|
24985
|
+
filesOutput,
|
|
24986
|
+
this.fernignorePatterns
|
|
24987
|
+
);
|
|
24838
24988
|
if (sensitiveFiles.length > 0) {
|
|
24839
24989
|
this.warnings.push(
|
|
24840
24990
|
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
24841
24991
|
);
|
|
24842
|
-
|
|
24843
|
-
|
|
24844
|
-
|
|
24845
|
-
}
|
|
24846
|
-
|
|
24847
|
-
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
|
|
24852
|
-
|
|
24992
|
+
}
|
|
24993
|
+
if (fernignoredFiles.length > 0) {
|
|
24994
|
+
this.warnings.push(
|
|
24995
|
+
`.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
|
|
24996
|
+
);
|
|
24997
|
+
}
|
|
24998
|
+
if ((sensitiveFiles.length > 0 || fernignoredFiles.length > 0) && files.length > 0) {
|
|
24999
|
+
if (parents.length === 0) {
|
|
25000
|
+
continue;
|
|
25001
|
+
}
|
|
25002
|
+
try {
|
|
25003
|
+
patchContent = await this.git.exec(["diff", parents[0], commit.sha, "--", ...files]);
|
|
25004
|
+
} catch {
|
|
25005
|
+
continue;
|
|
24853
25006
|
}
|
|
25007
|
+
if (!patchContent.trim()) continue;
|
|
25008
|
+
contentHash = this.computeContentHash(patchContent);
|
|
24854
25009
|
}
|
|
24855
25010
|
if (files.length === 0) {
|
|
24856
25011
|
continue;
|
|
@@ -25665,6 +25820,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25665
25820
|
};
|
|
25666
25821
|
}
|
|
25667
25822
|
async apply(_prep) {
|
|
25823
|
+
this.service.lockManager.setFernignorePatterns(this.service.readFernignorePatterns());
|
|
25668
25824
|
this.service.lockManager.save();
|
|
25669
25825
|
return firstGenerationReport();
|
|
25670
25826
|
}
|
|
@@ -25711,6 +25867,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25711
25867
|
};
|
|
25712
25868
|
}
|
|
25713
25869
|
async apply(_prep, options) {
|
|
25870
|
+
this.service.lockManager.setFernignorePatterns(this.service.readFernignorePatterns());
|
|
25714
25871
|
this.service.lockManager.save();
|
|
25715
25872
|
const credentialWarnings = [...this.service.lockManager.warnings];
|
|
25716
25873
|
if (!options?.stageOnly) {
|
|
@@ -25832,6 +25989,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25832
25989
|
this.service.lockManager.addPatch(patch);
|
|
25833
25990
|
}
|
|
25834
25991
|
}
|
|
25992
|
+
this.service.lockManager.setFernignorePatterns(this.service.readFernignorePatterns());
|
|
25835
25993
|
this.service.lockManager.save();
|
|
25836
25994
|
this.service.warnings.push(...this.service.lockManager.warnings);
|
|
25837
25995
|
if (newPatches.length > 0) {
|
|
@@ -25992,6 +26150,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25992
26150
|
}
|
|
25993
26151
|
}
|
|
25994
26152
|
}
|
|
26153
|
+
this.service.lockManager.setFernignorePatterns(this.service.readFernignorePatterns());
|
|
25995
26154
|
this.service.lockManager.save();
|
|
25996
26155
|
this.service.warnings.push(...this.service.lockManager.warnings);
|
|
25997
26156
|
if (options?.stageOnly) {
|
|
@@ -26063,7 +26222,8 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
26063
26222
|
this.git = git;
|
|
26064
26223
|
this.outputDir = outputDir;
|
|
26065
26224
|
this.lockManager = new LockfileManager(outputDir);
|
|
26066
|
-
|
|
26225
|
+
const fernignorePatterns = this.readFernignorePatterns();
|
|
26226
|
+
this.detector = new ReplayDetector(git, this.lockManager, outputDir, fernignorePatterns);
|
|
26067
26227
|
this.applicator = new ReplayApplicator(git, this.lockManager, outputDir);
|
|
26068
26228
|
this.committer = new ReplayCommitter(git, outputDir);
|
|
26069
26229
|
this.fileOwnership = new FileOwnership(git);
|
|
@@ -26083,8 +26243,60 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
26083
26243
|
async prepareReplay(options) {
|
|
26084
26244
|
this.warnings = [];
|
|
26085
26245
|
this.detector.warnings.length = 0;
|
|
26246
|
+
this.cleanseLegacyFernignoreEntries();
|
|
26086
26247
|
return this.selectFlow(options).prepare(options);
|
|
26087
26248
|
}
|
|
26249
|
+
/**
|
|
26250
|
+
* ADR 0002 migration step. Strips `.fernignore`-matched entries from
|
|
26251
|
+
* `patch.files`, `patch.theirs_snapshot` keys, and `diff --git` sections
|
|
26252
|
+
* in `patch.patch_content` for every patch in the lockfile. Patches that
|
|
26253
|
+
* end up with no surviving files are removed entirely.
|
|
26254
|
+
*
|
|
26255
|
+
* Idempotent: a second run is a no-op because the first run already
|
|
26256
|
+
* matches the contract. Per-patch try/catch ensures one bad patch does
|
|
26257
|
+
* not block the rest. Each affected patch emits a warning naming the
|
|
26258
|
+
* stripped paths. `patch_content` is left untouched if no parseable
|
|
26259
|
+
* `diff --git` headers are found (fail-safe for exotic legacy formats).
|
|
26260
|
+
*/
|
|
26261
|
+
cleanseLegacyFernignoreEntries() {
|
|
26262
|
+
const patterns = this.readFernignorePatterns();
|
|
26263
|
+
if (patterns.length === 0) return;
|
|
26264
|
+
if (!this.lockManager.exists()) return;
|
|
26265
|
+
this.lockManager.read();
|
|
26266
|
+
let lockfileMutated = false;
|
|
26267
|
+
const patches = this.lockManager.getPatches();
|
|
26268
|
+
for (const patch of patches) {
|
|
26269
|
+
try {
|
|
26270
|
+
const result = computeLegacyFernignoreCleanse(
|
|
26271
|
+
patch,
|
|
26272
|
+
patterns,
|
|
26273
|
+
(content) => this.detector.computeContentHash(content)
|
|
26274
|
+
);
|
|
26275
|
+
if (result.unchanged) continue;
|
|
26276
|
+
if (result.remove) {
|
|
26277
|
+
this.lockManager.removePatch(patch.id);
|
|
26278
|
+
this.warnings.push(
|
|
26279
|
+
`Cleansed legacy patch ${patch.id}: all referenced files match .fernignore \u2014 patch removed. Customer's on-disk content for those files is preserved by .fernignore. (ADR 0002: .fernignore-protected files are not tracked by Replay.)`
|
|
26280
|
+
);
|
|
26281
|
+
} else {
|
|
26282
|
+
this.lockManager.updatePatch(patch.id, {
|
|
26283
|
+
files: result.files,
|
|
26284
|
+
patch_content: result.patch_content,
|
|
26285
|
+
content_hash: result.content_hash,
|
|
26286
|
+
...result.theirs_snapshot !== void 0 ? { theirs_snapshot: result.theirs_snapshot } : {}
|
|
26287
|
+
});
|
|
26288
|
+
this.warnings.push(
|
|
26289
|
+
`Cleansed legacy patch ${patch.id}: stripped .fernignore-matched entries (${result.strippedPaths.join(", ")}). Customer's on-disk content for those files is preserved by .fernignore. (ADR 0002.)`
|
|
26290
|
+
);
|
|
26291
|
+
}
|
|
26292
|
+
lockfileMutated = true;
|
|
26293
|
+
} catch {
|
|
26294
|
+
}
|
|
26295
|
+
}
|
|
26296
|
+
if (lockfileMutated) {
|
|
26297
|
+
this.lockManager.save();
|
|
26298
|
+
}
|
|
26299
|
+
}
|
|
26088
26300
|
/**
|
|
26089
26301
|
* Phase 2 of the two-phase replay flow. Applies patches, rebases them against
|
|
26090
26302
|
* the generation, saves the lockfile, and commits `[fern-replay]` (or stages
|
|
@@ -26676,6 +26888,11 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
26676
26888
|
(files) => this.git.exec(["diff", currentGen, "HEAD", "--", ...files]).catch(() => null)
|
|
26677
26889
|
);
|
|
26678
26890
|
if (ppResult === null) continue;
|
|
26891
|
+
if (ppResult.hadFallback) {
|
|
26892
|
+
this.warnings.push(
|
|
26893
|
+
`Patch ${patch.id}: couldn't fully isolate ${ppResult.fallbackFiles.join(", ")} \u2014 customization preserved, attribution may have merged with another patch.`
|
|
26894
|
+
);
|
|
26895
|
+
}
|
|
26679
26896
|
const diff = ppResult.diff;
|
|
26680
26897
|
if (!diff.trim()) {
|
|
26681
26898
|
if (await allPatchFilesUserOwned(patch)) continue;
|
|
@@ -26763,6 +26980,11 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
26763
26980
|
(files) => this.git.exec(["diff", currentGen, "HEAD", "--", ...files]).catch(() => null)
|
|
26764
26981
|
);
|
|
26765
26982
|
if (ppResult === null) continue;
|
|
26983
|
+
if (ppResult.hadFallback) {
|
|
26984
|
+
this.warnings.push(
|
|
26985
|
+
`Patch ${patch.id}: couldn't fully isolate ${ppResult.fallbackFiles.join(", ")} \u2014 customization preserved, attribution may have merged with another patch.`
|
|
26986
|
+
);
|
|
26987
|
+
}
|
|
26766
26988
|
const diff = ppResult.diff;
|
|
26767
26989
|
if (!diff.trim()) {
|
|
26768
26990
|
if (await allPatchFilesUserOwned(patch)) {
|
|
@@ -26857,6 +27079,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
26857
27079
|
}
|
|
26858
27080
|
}
|
|
26859
27081
|
}
|
|
27082
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
26860
27083
|
readFernignorePatterns() {
|
|
26861
27084
|
const fernignorePath = (0, import_path13.join)(this.outputDir, ".fernignore");
|
|
26862
27085
|
if (!(0, import_fs7.existsSync)(fernignorePath)) return [];
|
|
@@ -31368,6 +31591,11 @@ var ClonedRepository = class _ClonedRepository {
|
|
|
31368
31591
|
}
|
|
31369
31592
|
await this.git.raw([...args, ...Array.isArray(files) ? files : [files]]);
|
|
31370
31593
|
}
|
|
31594
|
+
async listTrackedFiles() {
|
|
31595
|
+
await this.git.cwd(this.clonePath);
|
|
31596
|
+
const result = await this.git.raw(["ls-tree", "-r", "HEAD", "--name-only"]);
|
|
31597
|
+
return result.split("\n").filter((line) => line.length > 0);
|
|
31598
|
+
}
|
|
31371
31599
|
async restoreFilesFromCommit(commitSha, files) {
|
|
31372
31600
|
await this.git.cwd(this.clonePath);
|
|
31373
31601
|
const fileList = Array.isArray(files) ? files : [files];
|
|
@@ -31790,6 +32018,20 @@ async function cloneRepository({ githubRepository, installationToken, targetDire
|
|
|
31790
32018
|
});
|
|
31791
32019
|
}
|
|
31792
32020
|
|
|
32021
|
+
// ../commons/github/lib/expandFernignorePatterns.js
|
|
32022
|
+
init_esm3();
|
|
32023
|
+
function expandFernignorePatterns(fernignoreContent, candidatePaths) {
|
|
32024
|
+
const patterns = fernignoreContent.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
32025
|
+
return candidatePaths.filter((candidate) => patterns.some((pattern) => matches(candidate, pattern)));
|
|
32026
|
+
}
|
|
32027
|
+
function matches(candidate, pattern) {
|
|
32028
|
+
const normalized = pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
|
|
32029
|
+
if (minimatch(candidate, normalized, { dot: true })) {
|
|
32030
|
+
return true;
|
|
32031
|
+
}
|
|
32032
|
+
return candidate === normalized || candidate.startsWith(normalized + "/");
|
|
32033
|
+
}
|
|
32034
|
+
|
|
31793
32035
|
// ../../node_modules/.pnpm/universal-user-agent@7.0.3/node_modules/universal-user-agent/index.js
|
|
31794
32036
|
function getUserAgent() {
|
|
31795
32037
|
if (typeof navigator === "object" && "userAgent" in navigator) {
|
|
@@ -32006,11 +32248,11 @@ function removeNonChars(variableName) {
|
|
|
32006
32248
|
return variableName.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/);
|
|
32007
32249
|
}
|
|
32008
32250
|
function extractUrlVariableNames(url) {
|
|
32009
|
-
const
|
|
32010
|
-
if (!
|
|
32251
|
+
const matches2 = url.match(urlVariableRegex);
|
|
32252
|
+
if (!matches2) {
|
|
32011
32253
|
return [];
|
|
32012
32254
|
}
|
|
32013
|
-
return
|
|
32255
|
+
return matches2.map(removeNonChars).reduce((a, b3) => a.concat(b3), []);
|
|
32014
32256
|
}
|
|
32015
32257
|
function omit(object2, keysToOmit) {
|
|
32016
32258
|
const result = { __proto__: null };
|
|
@@ -32453,8 +32695,8 @@ async function fetchWrapper(requestOptions) {
|
|
|
32453
32695
|
data: ""
|
|
32454
32696
|
};
|
|
32455
32697
|
if ("deprecation" in responseHeaders) {
|
|
32456
|
-
const
|
|
32457
|
-
const deprecationLink =
|
|
32698
|
+
const matches2 = responseHeaders.link && responseHeaders.link.match(/<([^<>]+)>; rel="deprecation"/);
|
|
32699
|
+
const deprecationLink = matches2 && matches2.pop();
|
|
32458
32700
|
log.warn(
|
|
32459
32701
|
`[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${responseHeaders.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}`
|
|
32460
32702
|
);
|
|
@@ -35538,15 +35780,8 @@ var GitHub = class {
|
|
|
35538
35780
|
if (fernignore === void 0) {
|
|
35539
35781
|
return [];
|
|
35540
35782
|
}
|
|
35541
|
-
const
|
|
35542
|
-
|
|
35543
|
-
for (const line of fernignoreLines) {
|
|
35544
|
-
const trimmedLine = line.trim();
|
|
35545
|
-
if (!trimmedLine.startsWith("#") && trimmedLine.length > 0 && await repository.fileExists({ relativeFilePath: trimmedLine })) {
|
|
35546
|
-
fernignoreFiles.push(trimmedLine);
|
|
35547
|
-
}
|
|
35548
|
-
}
|
|
35549
|
-
return fernignoreFiles;
|
|
35783
|
+
const tracked = await repository.listTrackedFiles();
|
|
35784
|
+
return expandFernignorePatterns(fernignore, tracked);
|
|
35550
35785
|
}
|
|
35551
35786
|
async restoreFiles(repository, files) {
|
|
35552
35787
|
if (files.length === 0) {
|
|
@@ -41858,6 +42093,8 @@ var GithubStep = class extends BaseStep {
|
|
|
41858
42093
|
);
|
|
41859
42094
|
case "push":
|
|
41860
42095
|
return await this.executePushMode(repository, resolvedPrFields);
|
|
42096
|
+
case "commit-and-release":
|
|
42097
|
+
return await this.executeCommitAndReleaseMode(repository, resolvedPrFields);
|
|
41861
42098
|
default: {
|
|
41862
42099
|
const exhaustive = mode;
|
|
41863
42100
|
throw new Error(`Unexpected GitHub mode: ${String(exhaustive)}`);
|
|
@@ -42084,6 +42321,71 @@ var GithubStep = class extends BaseStep {
|
|
|
42084
42321
|
}
|
|
42085
42322
|
return result;
|
|
42086
42323
|
}
|
|
42324
|
+
async executeCommitAndReleaseMode(repository, resolved) {
|
|
42325
|
+
const baseBranch = this.config.branch ?? await repository.getDefaultBranch();
|
|
42326
|
+
if (this.config.branch != null) {
|
|
42327
|
+
this.logger.debug(`Checking out branch ${this.config.branch}`);
|
|
42328
|
+
await repository.checkout(this.config.branch);
|
|
42329
|
+
}
|
|
42330
|
+
await this.ensureFernignore();
|
|
42331
|
+
this.logger.debug("Committing changes...");
|
|
42332
|
+
await repository.commitAllChanges(resolved.commitMessage);
|
|
42333
|
+
this.logger.debug(`Committed changes to local copy of GitHub repository at ${this.outputDir}`);
|
|
42334
|
+
if (shouldCheckNoDiff(this.config)) {
|
|
42335
|
+
const noDiff = await repository.treeHashEquals(`origin/${baseBranch}`);
|
|
42336
|
+
if (noDiff) {
|
|
42337
|
+
this.logger.info("No changes detected after generation \u2014 skipping commit-and-release");
|
|
42338
|
+
return { executed: true, success: true, skippedNoDiff: true };
|
|
42339
|
+
}
|
|
42340
|
+
}
|
|
42341
|
+
const result = {
|
|
42342
|
+
executed: true,
|
|
42343
|
+
success: true
|
|
42344
|
+
};
|
|
42345
|
+
if (!this.config.previewMode) {
|
|
42346
|
+
const octokit = this.createOctokit();
|
|
42347
|
+
const { owner, repo, remote } = parseRepository(this.config.uri);
|
|
42348
|
+
await pushSignedCommit({
|
|
42349
|
+
repository,
|
|
42350
|
+
octokit,
|
|
42351
|
+
owner,
|
|
42352
|
+
repo,
|
|
42353
|
+
branch: baseBranch,
|
|
42354
|
+
force: false,
|
|
42355
|
+
rebaseOnConflict: true,
|
|
42356
|
+
author: this.config.author,
|
|
42357
|
+
logger: this.logger
|
|
42358
|
+
});
|
|
42359
|
+
const pushedBranch = await repository.getCurrentBranch();
|
|
42360
|
+
result.branchUrl = `https://${remote}/${owner}/${repo}/tree/${pushedBranch}`;
|
|
42361
|
+
this.logger.info(`Pushed branch: ${result.branchUrl}`);
|
|
42362
|
+
if (resolved.newVersion == null) {
|
|
42363
|
+
this.logger.warn(
|
|
42364
|
+
"No new version available \u2014 skipping release tag creation. Pass --version <semver> or --version AUTO to fern generate to enable release tagging."
|
|
42365
|
+
);
|
|
42366
|
+
} else {
|
|
42367
|
+
try {
|
|
42368
|
+
const tagName = resolved.newVersion;
|
|
42369
|
+
const headSha = await repository.getHeadSha();
|
|
42370
|
+
await octokit.repos.createRelease({
|
|
42371
|
+
owner,
|
|
42372
|
+
repo,
|
|
42373
|
+
tag_name: tagName,
|
|
42374
|
+
target_commitish: headSha,
|
|
42375
|
+
name: tagName,
|
|
42376
|
+
body: resolved.changelogEntry ?? resolved.commitMessage,
|
|
42377
|
+
draft: false,
|
|
42378
|
+
prerelease: false
|
|
42379
|
+
});
|
|
42380
|
+
this.logger.info(`Created GitHub release ${tagName}`);
|
|
42381
|
+
result.releaseUrl = `https://${remote}/${owner}/${repo}/releases/tag/${tagName}`;
|
|
42382
|
+
} catch (error) {
|
|
42383
|
+
this.logger.warn(`Could not create GitHub release: ${extractErrorMessage(error)}`);
|
|
42384
|
+
}
|
|
42385
|
+
}
|
|
42386
|
+
}
|
|
42387
|
+
return result;
|
|
42388
|
+
}
|
|
42087
42389
|
createOctokit() {
|
|
42088
42390
|
const opts = { auth: this.config.token };
|
|
42089
42391
|
if (this.config.apiBaseUrl != null) {
|
|
@@ -42528,9 +42830,9 @@ var PostGenerationPipeline = class {
|
|
|
42528
42830
|
this.logger = logger;
|
|
42529
42831
|
let replayEnabled = config.replay?.enabled ?? false;
|
|
42530
42832
|
let autoVersionEnabled = config.autoVersion?.enabled ?? false;
|
|
42531
|
-
if (replayEnabled && config.github?.mode === "push") {
|
|
42833
|
+
if (replayEnabled && (config.github?.mode === "push" || config.github?.mode === "commit-and-release")) {
|
|
42532
42834
|
this.logger.warn(
|
|
42533
|
-
|
|
42835
|
+
`Replay is not supported with GitHub ${config.github.mode} mode. Disabling replay to prevent push to base branch.`
|
|
42534
42836
|
);
|
|
42535
42837
|
replayEnabled = false;
|
|
42536
42838
|
}
|