@fern-api/generator-cli 0.9.29 → 0.9.31
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.d.ts +0 -2
- package/dist/api.js +3 -134
- package/dist/cli.js +113 -353
- package/package.json +2 -2
package/dist/api.d.ts
CHANGED
|
@@ -580,8 +580,6 @@ interface ReplayRunResult {
|
|
|
580
580
|
previousGenerationSha: string | null;
|
|
581
581
|
/** SHA of the [fern-generated] commit created by this replay run (null if replay didn't run or unreadable) */
|
|
582
582
|
currentGenerationSha: string | null;
|
|
583
|
-
/** SHA of main's HEAD before replay ran. Always on main's lineage, stable after squash merges. */
|
|
584
|
-
baseBranchHead: string | null;
|
|
585
583
|
/** True when this run inlined `bootstrap()` to create the lockfile (auto-init from `fern generate`). */
|
|
586
584
|
autoBootstrapped: boolean;
|
|
587
585
|
/**
|
package/dist/api.js
CHANGED
|
@@ -10356,19 +10356,10 @@ async function replayResolve(params) {
|
|
|
10356
10356
|
|
|
10357
10357
|
// src/replay/replay-run.ts
|
|
10358
10358
|
var import_replay4 = require("@fern-api/replay");
|
|
10359
|
-
var import_child_process3 = require("child_process");
|
|
10360
10359
|
var import_fs3 = require("fs");
|
|
10361
10360
|
var import_path5 = require("path");
|
|
10362
10361
|
async function replayPrepare(params, state) {
|
|
10363
|
-
const {
|
|
10364
|
-
outputDir,
|
|
10365
|
-
cliVersion,
|
|
10366
|
-
generatorVersions,
|
|
10367
|
-
stageOnly = false,
|
|
10368
|
-
generatorName,
|
|
10369
|
-
skipApplication,
|
|
10370
|
-
logger
|
|
10371
|
-
} = params;
|
|
10362
|
+
const { outputDir, cliVersion, generatorVersions, stageOnly = false, skipApplication, logger } = params;
|
|
10372
10363
|
const lockfilePath = (0, import_path5.join)(outputDir, ".fern", "replay.lock");
|
|
10373
10364
|
let autoBootstrapped = false;
|
|
10374
10365
|
let bootstrapAttempted = false;
|
|
@@ -10401,63 +10392,6 @@ async function replayPrepare(params, state) {
|
|
|
10401
10392
|
return null;
|
|
10402
10393
|
}
|
|
10403
10394
|
}
|
|
10404
|
-
const baseBranchHead = gitRevParse(outputDir, "HEAD");
|
|
10405
|
-
let previousGenerationSha = null;
|
|
10406
|
-
let prevBaseBranchHead = null;
|
|
10407
|
-
try {
|
|
10408
|
-
const lockManager = new import_replay4.LockfileManager(outputDir);
|
|
10409
|
-
if (lockManager.exists()) {
|
|
10410
|
-
const lock = lockManager.read();
|
|
10411
|
-
previousGenerationSha = lock.current_generation;
|
|
10412
|
-
const latestGen = lock.generations.find((g) => g.commit_sha === lock.current_generation);
|
|
10413
|
-
prevBaseBranchHead = latestGen?.base_branch_head ?? null;
|
|
10414
|
-
}
|
|
10415
|
-
} catch {
|
|
10416
|
-
}
|
|
10417
|
-
if (previousGenerationSha != null) {
|
|
10418
|
-
const sanitizedName = generatorName?.replace(/\//g, "--");
|
|
10419
|
-
const namespacedTag = sanitizedName != null ? `fern-generation-base--${sanitizedName}` : null;
|
|
10420
|
-
const tagSha = (namespacedTag != null ? gitRevParse(outputDir, namespacedTag) : null) ?? gitRevParse(outputDir, "fern-generation-base");
|
|
10421
|
-
let shouldSync = false;
|
|
10422
|
-
if (tagSha != null) {
|
|
10423
|
-
const tagParent = gitRevParse(outputDir, `${tagSha}^`);
|
|
10424
|
-
if (tagParent === prevBaseBranchHead || tagParent === previousGenerationSha) {
|
|
10425
|
-
const tagDistance = gitDiffNameOnly(outputDir, tagSha, "HEAD").length;
|
|
10426
|
-
const lockDistance = gitDiffNameOnly(
|
|
10427
|
-
outputDir,
|
|
10428
|
-
prevBaseBranchHead ?? previousGenerationSha,
|
|
10429
|
-
"HEAD"
|
|
10430
|
-
).length;
|
|
10431
|
-
shouldSync = tagDistance < lockDistance;
|
|
10432
|
-
}
|
|
10433
|
-
}
|
|
10434
|
-
if (shouldSync && tagSha != null) {
|
|
10435
|
-
if (!isTagMergedIntoHead(outputDir, tagSha)) {
|
|
10436
|
-
logger?.warn(
|
|
10437
|
-
`fern-generation-base tag ${tagSha} is not reachable from HEAD \u2014 skipping divergent-merge sync. The tag likely points at an unmerged generation (PR closed without merge).`
|
|
10438
|
-
);
|
|
10439
|
-
} else {
|
|
10440
|
-
try {
|
|
10441
|
-
const syncService = new import_replay4.ReplayService(outputDir, { enabled: true });
|
|
10442
|
-
await syncService.syncFromDivergentMerge(tagSha, {
|
|
10443
|
-
cliVersion,
|
|
10444
|
-
generatorVersions,
|
|
10445
|
-
baseBranchHead: baseBranchHead ?? void 0
|
|
10446
|
-
});
|
|
10447
|
-
} catch (error) {
|
|
10448
|
-
logger?.warn("Replay divergent-merge sync failed, continuing without sync: " + String(error));
|
|
10449
|
-
throw new ReplayPrepareError(String(error), error);
|
|
10450
|
-
}
|
|
10451
|
-
try {
|
|
10452
|
-
const freshLockManager = new import_replay4.LockfileManager(outputDir);
|
|
10453
|
-
if (freshLockManager.exists()) {
|
|
10454
|
-
previousGenerationSha = freshLockManager.read().current_generation;
|
|
10455
|
-
}
|
|
10456
|
-
} catch {
|
|
10457
|
-
}
|
|
10458
|
-
}
|
|
10459
|
-
}
|
|
10460
|
-
}
|
|
10461
10395
|
const service = new import_replay4.ReplayService(outputDir, { enabled: true });
|
|
10462
10396
|
let preparation;
|
|
10463
10397
|
try {
|
|
@@ -10465,8 +10399,7 @@ async function replayPrepare(params, state) {
|
|
|
10465
10399
|
cliVersion,
|
|
10466
10400
|
generatorVersions,
|
|
10467
10401
|
stageOnly,
|
|
10468
|
-
skipApplication
|
|
10469
|
-
baseBranchHead: baseBranchHead ?? void 0
|
|
10402
|
+
skipApplication
|
|
10470
10403
|
});
|
|
10471
10404
|
} catch (error) {
|
|
10472
10405
|
logger?.warn("Replay prepare failed, continuing without replay: " + String(error));
|
|
@@ -10477,9 +10410,8 @@ async function replayPrepare(params, state) {
|
|
|
10477
10410
|
_preparation: preparation,
|
|
10478
10411
|
outputDir,
|
|
10479
10412
|
flow: preparation.flow,
|
|
10480
|
-
previousGenerationSha,
|
|
10413
|
+
previousGenerationSha: preparation.previousGenerationSha,
|
|
10481
10414
|
currentGenerationSha: preparation.currentGenerationSha,
|
|
10482
|
-
baseBranchHead,
|
|
10483
10415
|
autoBootstrapped,
|
|
10484
10416
|
bootstrapAttempted
|
|
10485
10417
|
};
|
|
@@ -10496,24 +10428,11 @@ async function replayApply(prepared, params = {}) {
|
|
|
10496
10428
|
fernignoreUpdated: false,
|
|
10497
10429
|
previousGenerationSha: prepared.previousGenerationSha,
|
|
10498
10430
|
currentGenerationSha: prepared.currentGenerationSha,
|
|
10499
|
-
baseBranchHead: prepared.baseBranchHead,
|
|
10500
10431
|
autoBootstrapped: prepared.autoBootstrapped,
|
|
10501
10432
|
bootstrapAttempted: prepared.bootstrapAttempted,
|
|
10502
10433
|
failureReason: String(error)
|
|
10503
10434
|
};
|
|
10504
10435
|
}
|
|
10505
|
-
let resolvedBaseBranchHead = prepared.baseBranchHead;
|
|
10506
|
-
try {
|
|
10507
|
-
const freshLockManager = new import_replay4.LockfileManager(prepared.outputDir);
|
|
10508
|
-
if (freshLockManager.exists()) {
|
|
10509
|
-
const freshLock = freshLockManager.read();
|
|
10510
|
-
const latestGen = freshLock.generations.find((g) => g.commit_sha === freshLock.current_generation);
|
|
10511
|
-
if (latestGen?.base_branch_head) {
|
|
10512
|
-
resolvedBaseBranchHead = latestGen.base_branch_head;
|
|
10513
|
-
}
|
|
10514
|
-
}
|
|
10515
|
-
} catch {
|
|
10516
|
-
}
|
|
10517
10436
|
const fernignoreUpdated = await ensureReplayFernignoreEntries(prepared.outputDir);
|
|
10518
10437
|
await ensureGitattributesEntries(prepared.outputDir);
|
|
10519
10438
|
return {
|
|
@@ -10521,7 +10440,6 @@ async function replayApply(prepared, params = {}) {
|
|
|
10521
10440
|
fernignoreUpdated,
|
|
10522
10441
|
previousGenerationSha: prepared.previousGenerationSha,
|
|
10523
10442
|
currentGenerationSha: prepared.currentGenerationSha,
|
|
10524
|
-
baseBranchHead: resolvedBaseBranchHead,
|
|
10525
10443
|
autoBootstrapped: prepared.autoBootstrapped,
|
|
10526
10444
|
bootstrapAttempted: prepared.bootstrapAttempted
|
|
10527
10445
|
};
|
|
@@ -10537,7 +10455,6 @@ async function replayRun(params) {
|
|
|
10537
10455
|
fernignoreUpdated: false,
|
|
10538
10456
|
previousGenerationSha: null,
|
|
10539
10457
|
currentGenerationSha: null,
|
|
10540
|
-
baseBranchHead: null,
|
|
10541
10458
|
autoBootstrapped: false,
|
|
10542
10459
|
bootstrapAttempted: prepareState.bootstrapAttempted,
|
|
10543
10460
|
failureReason: error instanceof ReplayPrepareError ? error.reason : String(error)
|
|
@@ -10549,7 +10466,6 @@ async function replayRun(params) {
|
|
|
10549
10466
|
fernignoreUpdated: false,
|
|
10550
10467
|
previousGenerationSha: null,
|
|
10551
10468
|
currentGenerationSha: null,
|
|
10552
|
-
baseBranchHead: null,
|
|
10553
10469
|
autoBootstrapped: false,
|
|
10554
10470
|
bootstrapAttempted: prepareState.bootstrapAttempted
|
|
10555
10471
|
};
|
|
@@ -10568,53 +10484,6 @@ var ReplayPrepareError = class extends Error {
|
|
|
10568
10484
|
this.reason = reason;
|
|
10569
10485
|
}
|
|
10570
10486
|
};
|
|
10571
|
-
function isTagMergedIntoHead(cwd2, tagSha) {
|
|
10572
|
-
try {
|
|
10573
|
-
(0, import_child_process3.execFileSync)("git", ["merge-base", "--is-ancestor", tagSha, "HEAD"], {
|
|
10574
|
-
cwd: cwd2,
|
|
10575
|
-
stdio: "pipe"
|
|
10576
|
-
});
|
|
10577
|
-
return true;
|
|
10578
|
-
} catch {
|
|
10579
|
-
}
|
|
10580
|
-
const tagTree = gitRevParse(cwd2, `${tagSha}^{tree}`);
|
|
10581
|
-
if (tagTree == null) {
|
|
10582
|
-
return false;
|
|
10583
|
-
}
|
|
10584
|
-
try {
|
|
10585
|
-
const trees = (0, import_child_process3.execFileSync)("git", ["log", "HEAD", "--format=%T", "--max-count=500"], {
|
|
10586
|
-
cwd: cwd2,
|
|
10587
|
-
encoding: "utf-8",
|
|
10588
|
-
stdio: "pipe"
|
|
10589
|
-
});
|
|
10590
|
-
return trees.split("\n").some((t2) => t2.trim() === tagTree);
|
|
10591
|
-
} catch {
|
|
10592
|
-
return false;
|
|
10593
|
-
}
|
|
10594
|
-
}
|
|
10595
|
-
function gitRevParse(cwd2, rev) {
|
|
10596
|
-
try {
|
|
10597
|
-
return (0, import_child_process3.execFileSync)("git", ["rev-parse", "--verify", rev], {
|
|
10598
|
-
cwd: cwd2,
|
|
10599
|
-
encoding: "utf-8",
|
|
10600
|
-
stdio: "pipe"
|
|
10601
|
-
}).trim();
|
|
10602
|
-
} catch {
|
|
10603
|
-
return null;
|
|
10604
|
-
}
|
|
10605
|
-
}
|
|
10606
|
-
function gitDiffNameOnly(cwd2, from, to) {
|
|
10607
|
-
try {
|
|
10608
|
-
const output = (0, import_child_process3.execFileSync)("git", ["diff", "--name-only", from, to], {
|
|
10609
|
-
cwd: cwd2,
|
|
10610
|
-
encoding: "utf-8",
|
|
10611
|
-
stdio: "pipe"
|
|
10612
|
-
});
|
|
10613
|
-
return output.trim().split("\n").filter(Boolean);
|
|
10614
|
-
} catch {
|
|
10615
|
-
return [];
|
|
10616
|
-
}
|
|
10617
|
-
}
|
|
10618
10487
|
|
|
10619
10488
|
// src/replay/replay-status.ts
|
|
10620
10489
|
var import_replay5 = require("@fern-api/replay");
|
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.
|
|
22129
|
+
// ../../node_modules/.pnpm/@fern-api+replay@0.16.0/node_modules/@fern-api/replay/dist/index.js
|
|
22130
22130
|
var dist_exports = {};
|
|
22131
22131
|
__export(dist_exports, {
|
|
22132
22132
|
CREDENTIAL_PATTERNS: () => CREDENTIAL_PATTERNS,
|
|
@@ -22451,9 +22451,22 @@ function isGenerationCommit(commit) {
|
|
|
22451
22451
|
const hasGenerationMarker = commit.message.startsWith("[fern-generated]") || commit.message.startsWith("[fern-replay]") || commit.message.startsWith("[fern-autoversion]") || commit.message.includes("Generated by Fern") || commit.message.includes("\u{1F916} Generated with Fern") || // Squash merge of a Fern-generated PR uses the PR title as commit message.
|
|
22452
22452
|
// The default PR title is "SDK Generation" (from GithubStep's commitMessage default).
|
|
22453
22453
|
// GitHub appends "(#N)" for the PR number, e.g. "SDK Generation (#70)".
|
|
22454
|
-
commit.message.startsWith("SDK Generation")
|
|
22454
|
+
commit.message.startsWith("SDK Generation") || // Merge commit (GitHub's "Create a merge commit" option) of a fern-bot
|
|
22455
|
+
// regen PR. First-parent walking lands on this commit, not on the
|
|
22456
|
+
// generation commit on the side branch, so the classifier must treat
|
|
22457
|
+
// it as a generation boundary. GitHub's default subject is
|
|
22458
|
+
// "Merge pull request #N from <owner>/<branch>"; fern-bot branches are
|
|
22459
|
+
// named "<owner>/fern-bot/<...>".
|
|
22460
|
+
/^Merge pull request #\d+ from [^/]+\/fern-bot\//.test(commit.message);
|
|
22455
22461
|
return isBotAuthor || hasGenerationMarker;
|
|
22456
22462
|
}
|
|
22463
|
+
function isGenerationBoundary(commit) {
|
|
22464
|
+
const isFernSupport = FERN_SUPPORT_NAMES.includes(commit.authorName);
|
|
22465
|
+
const isBotAuthor = !isFernSupport && (commit.authorLogin === FERN_BOT_LOGIN || commit.authorEmail === FERN_BOT_EMAIL || commit.authorName === FERN_BOT_NAME);
|
|
22466
|
+
const isAutoversion = commit.message.startsWith("[fern-autoversion]");
|
|
22467
|
+
const hasBoundaryMarker = commit.message.startsWith("[fern-generated]") || commit.message.startsWith("[fern-replay]") || commit.message.includes("Generated by Fern") || commit.message.includes("\u{1F916} Generated with Fern") || commit.message.startsWith("SDK Generation");
|
|
22468
|
+
return isBotAuthor && !isAutoversion || hasBoundaryMarker;
|
|
22469
|
+
}
|
|
22457
22470
|
function isReplayCommit(commit) {
|
|
22458
22471
|
return commit.message.startsWith("[fern-replay]");
|
|
22459
22472
|
}
|
|
@@ -23934,7 +23947,7 @@ function status(outputDir) {
|
|
|
23934
23947
|
}
|
|
23935
23948
|
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;
|
|
23936
23949
|
var init_dist3 = __esm({
|
|
23937
|
-
"../../node_modules/.pnpm/@fern-api+replay@0.
|
|
23950
|
+
"../../node_modules/.pnpm/@fern-api+replay@0.16.0/node_modules/@fern-api/replay/dist/index.js"() {
|
|
23938
23951
|
"use strict";
|
|
23939
23952
|
init_esm4();
|
|
23940
23953
|
import_promises3 = require("fs/promises");
|
|
@@ -24414,8 +24427,48 @@ var init_dist3 = __esm({
|
|
|
24414
24427
|
this.lockManager = lockManager;
|
|
24415
24428
|
this.sdkOutputDir = sdkOutputDir;
|
|
24416
24429
|
}
|
|
24430
|
+
/**
|
|
24431
|
+
* Derive the previous-generation SHA from git history instead of trusting
|
|
24432
|
+
* `lock.current_generation`.
|
|
24433
|
+
*
|
|
24434
|
+
* Walks `git log HEAD --first-parent` and returns the SHA of the most recent
|
|
24435
|
+
* commit that `isGenerationBoundary` classifies as a customization-baseline
|
|
24436
|
+
* boundary, or null if no such commit is reachable from HEAD on the mainline.
|
|
24437
|
+
*
|
|
24438
|
+
* `isGenerationBoundary` is intentionally narrower than `isGenerationCommit`:
|
|
24439
|
+
* it excludes `[fern-autoversion]` (lightweight bumps that don't reset the
|
|
24440
|
+
* baseline) and the GitHub merge-commit subject for fern-bot regen PRs
|
|
24441
|
+
* (which sits on first-parent but where the customer fix-up commits live
|
|
24442
|
+
* on second-parent — stopping at the merge would hide them).
|
|
24443
|
+
*
|
|
24444
|
+
* See docs/adr/0001-derived-scan-boundary.md.
|
|
24445
|
+
*/
|
|
24446
|
+
async findPreviousGenerationFromHistory() {
|
|
24447
|
+
let log;
|
|
24448
|
+
try {
|
|
24449
|
+
log = await this.git.exec([
|
|
24450
|
+
"log",
|
|
24451
|
+
"--first-parent",
|
|
24452
|
+
"--format=%H%x00%an%x00%ae%x00%s",
|
|
24453
|
+
"HEAD"
|
|
24454
|
+
]);
|
|
24455
|
+
} catch {
|
|
24456
|
+
return null;
|
|
24457
|
+
}
|
|
24458
|
+
for (const commit of this.parseGitLog(log)) {
|
|
24459
|
+
if (isGenerationBoundary(commit)) {
|
|
24460
|
+
return commit.sha;
|
|
24461
|
+
}
|
|
24462
|
+
}
|
|
24463
|
+
return null;
|
|
24464
|
+
}
|
|
24417
24465
|
async detectNewPatches() {
|
|
24418
24466
|
const lock = this.lockManager.read();
|
|
24467
|
+
const derivedSha = await this.findPreviousGenerationFromHistory();
|
|
24468
|
+
if (derivedSha !== null) {
|
|
24469
|
+
const derivedRecord = await this.synthesizeGenerationRecord(derivedSha);
|
|
24470
|
+
return this.detectPatchesInRange(derivedSha, derivedRecord, lock);
|
|
24471
|
+
}
|
|
24419
24472
|
const lastGen = this.getLastGeneration(lock);
|
|
24420
24473
|
if (!lastGen) {
|
|
24421
24474
|
return { patches: [], revertedPatchIds: [] };
|
|
@@ -24423,7 +24476,7 @@ var init_dist3 = __esm({
|
|
|
24423
24476
|
const exists2 = await this.git.commitExists(lastGen.commit_sha);
|
|
24424
24477
|
if (!exists2) {
|
|
24425
24478
|
this.warnings.push(
|
|
24426
|
-
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to
|
|
24479
|
+
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to tree-diff detection (likely a shallow clone).`
|
|
24427
24480
|
);
|
|
24428
24481
|
return this.detectPatchesViaTreeDiff(
|
|
24429
24482
|
lastGen,
|
|
@@ -24431,44 +24484,8 @@ var init_dist3 = __esm({
|
|
|
24431
24484
|
true
|
|
24432
24485
|
);
|
|
24433
24486
|
}
|
|
24434
|
-
const isAncestor = await this.git.isAncestor(lastGen.commit_sha, "HEAD");
|
|
24435
|
-
if (!isAncestor) {
|
|
24436
|
-
return this.detectPatchesViaMergeBase(lastGen, lock);
|
|
24437
|
-
}
|
|
24438
24487
|
return this.detectPatchesInRange(lastGen.commit_sha, lastGen, lock);
|
|
24439
24488
|
}
|
|
24440
|
-
/**
|
|
24441
|
-
* Non-linear-history detection via `merge-base(prevGen, HEAD)`.
|
|
24442
|
-
*
|
|
24443
|
-
* After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
|
|
24444
|
-
* `[fern-generated]`) is orphaned but still in git's object database. The
|
|
24445
|
-
* merge-base is the commit on main from which the bot's branch was created —
|
|
24446
|
-
* a reachable ancestor of HEAD. Walking that range and classifying each
|
|
24447
|
-
* commit via `isGenerationCommit` mirrors the linear path and eliminates
|
|
24448
|
-
* the bug class where pipeline-driven changes (autoversion, replay,
|
|
24449
|
-
* generation) get bundled as customer customizations.
|
|
24450
|
-
*
|
|
24451
|
-
* Falls through to the legacy composite path when no common ancestor exists
|
|
24452
|
-
* (truly disjoint history — orphan branches with no shared root).
|
|
24453
|
-
*/
|
|
24454
|
-
async detectPatchesViaMergeBase(lastGen, lock) {
|
|
24455
|
-
let mergeBase = "";
|
|
24456
|
-
try {
|
|
24457
|
-
mergeBase = (await this.git.exec(["merge-base", lastGen.commit_sha, "HEAD"])).trim();
|
|
24458
|
-
} catch {
|
|
24459
|
-
}
|
|
24460
|
-
if (!mergeBase) {
|
|
24461
|
-
this.warnings.push(
|
|
24462
|
-
`No common ancestor between previous generation ${lastGen.commit_sha.slice(0, 7)} and HEAD. Falling back to composite tree-diff detection.`
|
|
24463
|
-
);
|
|
24464
|
-
return this.detectPatchesViaTreeDiff(
|
|
24465
|
-
lastGen,
|
|
24466
|
-
/* commitKnownMissing */
|
|
24467
|
-
false
|
|
24468
|
-
);
|
|
24469
|
-
}
|
|
24470
|
-
return this.detectPatchesInRange(mergeBase, lastGen, lock);
|
|
24471
|
-
}
|
|
24472
24489
|
/**
|
|
24473
24490
|
* Walk `${rangeStart}..HEAD`, classify each commit, emit one
|
|
24474
24491
|
* `StoredPatch` per customer commit. Body shared by the linear path
|
|
@@ -24481,7 +24498,7 @@ var init_dist3 = __esm({
|
|
|
24481
24498
|
async detectPatchesInRange(rangeStart, lastGen, lock) {
|
|
24482
24499
|
const log = await this.git.exec([
|
|
24483
24500
|
"log",
|
|
24484
|
-
"--
|
|
24501
|
+
"--no-merges",
|
|
24485
24502
|
"--format=%H%x00%an%x00%ae%x00%s",
|
|
24486
24503
|
`${rangeStart}..HEAD`,
|
|
24487
24504
|
"--",
|
|
@@ -24501,18 +24518,6 @@ var init_dist3 = __esm({
|
|
|
24501
24518
|
continue;
|
|
24502
24519
|
}
|
|
24503
24520
|
const parents = await this.git.getCommitParents(commit.sha);
|
|
24504
|
-
if (parents.length > 1) {
|
|
24505
|
-
const compositePatch = await this.createMergeCompositePatch({
|
|
24506
|
-
mergeCommit: commit,
|
|
24507
|
-
firstParent: parents[0],
|
|
24508
|
-
lastGen,
|
|
24509
|
-
lock
|
|
24510
|
-
});
|
|
24511
|
-
if (compositePatch) {
|
|
24512
|
-
newPatches.push(compositePatch);
|
|
24513
|
-
}
|
|
24514
|
-
continue;
|
|
24515
|
-
}
|
|
24516
24521
|
let patchContent;
|
|
24517
24522
|
try {
|
|
24518
24523
|
patchContent = await this.git.formatPatch(commit.sha);
|
|
@@ -24650,9 +24655,7 @@ var init_dist3 = __esm({
|
|
|
24650
24655
|
* within the current linear detection window, and are absent from
|
|
24651
24656
|
* HEAD at the end of the window.
|
|
24652
24657
|
*
|
|
24653
|
-
* Rationale:
|
|
24654
|
-
* firstParent..mergeCommit so net-zero files never enter the composite. On
|
|
24655
|
-
* linear history each commit becomes its own patch, so a file that was
|
|
24658
|
+
* Rationale: each commit becomes its own patch, so a file that was
|
|
24656
24659
|
* briefly added and later removed survives as a create+delete pair whose
|
|
24657
24660
|
* cumulative diff is empty. We drop such patches before they reach the
|
|
24658
24661
|
* lockfile.
|
|
@@ -24719,54 +24722,6 @@ var init_dist3 = __esm({
|
|
|
24719
24722
|
return /* @__PURE__ */ new Set();
|
|
24720
24723
|
}
|
|
24721
24724
|
}
|
|
24722
|
-
/**
|
|
24723
|
-
* Create a single composite patch for a merge commit by diffing the merge
|
|
24724
|
-
* commit against its first parent. This captures the net change of the
|
|
24725
|
-
* entire merged branch as one patch, eliminating accumulator chaining and
|
|
24726
|
-
* zero-net-change ghost conflicts.
|
|
24727
|
-
*/
|
|
24728
|
-
async createMergeCompositePatch(input) {
|
|
24729
|
-
const { mergeCommit, firstParent, lastGen, lock } = input;
|
|
24730
|
-
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
24731
|
-
const filesOutput = await this.git.exec([
|
|
24732
|
-
"diff",
|
|
24733
|
-
"--name-only",
|
|
24734
|
-
firstParent,
|
|
24735
|
-
mergeCommit.sha
|
|
24736
|
-
]);
|
|
24737
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
24738
|
-
if (sensitiveFiles.length > 0) {
|
|
24739
|
-
this.warnings.push(
|
|
24740
|
-
`Sensitive file(s) excluded from merge patch for commit ${mergeCommit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
24741
|
-
);
|
|
24742
|
-
}
|
|
24743
|
-
if (files.length === 0) return null;
|
|
24744
|
-
const diff = await this.git.exec([
|
|
24745
|
-
"diff",
|
|
24746
|
-
firstParent,
|
|
24747
|
-
mergeCommit.sha,
|
|
24748
|
-
"--",
|
|
24749
|
-
...files
|
|
24750
|
-
]);
|
|
24751
|
-
if (!diff.trim()) return null;
|
|
24752
|
-
const contentHash = this.computeContentHash(diff);
|
|
24753
|
-
if (lock.patches.some((p2) => p2.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
24754
|
-
return null;
|
|
24755
|
-
}
|
|
24756
|
-
const theirsSnapshot = await capturePatchSnapshot(this.git, files, mergeCommit.sha);
|
|
24757
|
-
return {
|
|
24758
|
-
id: `patch-merge-${mergeCommit.sha.slice(0, 8)}`,
|
|
24759
|
-
content_hash: contentHash,
|
|
24760
|
-
original_commit: mergeCommit.sha,
|
|
24761
|
-
original_message: mergeCommit.message,
|
|
24762
|
-
original_author: `${mergeCommit.authorName} <${mergeCommit.authorEmail}>`,
|
|
24763
|
-
base_generation: lastGen.commit_sha,
|
|
24764
|
-
files,
|
|
24765
|
-
patch_content: diff,
|
|
24766
|
-
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
24767
|
-
...this.isCreationOnlyPatch(diff) ? { user_owned: true } : {}
|
|
24768
|
-
};
|
|
24769
|
-
}
|
|
24770
24725
|
/**
|
|
24771
24726
|
* Detect patches via tree diff for non-linear history. Returns a composite patch.
|
|
24772
24727
|
* Revert reconciliation is skipped here because tree-diff produces a single composite
|
|
@@ -24966,6 +24921,26 @@ var init_dist3 = __esm({
|
|
|
24966
24921
|
getLastGeneration(lock) {
|
|
24967
24922
|
return lock.generations.find((g) => g.commit_sha === lock.current_generation);
|
|
24968
24923
|
}
|
|
24924
|
+
/**
|
|
24925
|
+
* Build a transient `GenerationRecord` from a SHA derived by walking git
|
|
24926
|
+
* history. Only `commit_sha` (and `tree_hash` when needed downstream) is
|
|
24927
|
+
* load-bearing here; the rest are filled with defensible placeholders.
|
|
24928
|
+
* This record is consumed by `detectPatchesInRange` and is never persisted.
|
|
24929
|
+
*/
|
|
24930
|
+
async synthesizeGenerationRecord(sha) {
|
|
24931
|
+
let tree_hash = "";
|
|
24932
|
+
try {
|
|
24933
|
+
tree_hash = await this.git.getTreeHash(sha);
|
|
24934
|
+
} catch {
|
|
24935
|
+
}
|
|
24936
|
+
return {
|
|
24937
|
+
commit_sha: sha,
|
|
24938
|
+
tree_hash,
|
|
24939
|
+
timestamp: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
24940
|
+
cli_version: "",
|
|
24941
|
+
generator_versions: {}
|
|
24942
|
+
};
|
|
24943
|
+
}
|
|
24969
24944
|
};
|
|
24970
24945
|
MergeAccumulator = class {
|
|
24971
24946
|
constructor(mode, sharedFiles) {
|
|
@@ -25588,8 +25563,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25588
25563
|
tree_hash: treeHash,
|
|
25589
25564
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25590
25565
|
cli_version: options?.cliVersion ?? "unknown",
|
|
25591
|
-
generator_versions: options?.generatorVersions ?? {}
|
|
25592
|
-
base_branch_head: options?.baseBranchHead
|
|
25566
|
+
generator_versions: options?.generatorVersions ?? {}
|
|
25593
25567
|
};
|
|
25594
25568
|
}
|
|
25595
25569
|
async stageAll() {
|
|
@@ -25660,7 +25634,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25660
25634
|
flow: "first-generation",
|
|
25661
25635
|
previousGenerationSha: null,
|
|
25662
25636
|
currentGenerationSha: headSha,
|
|
25663
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
25664
25637
|
_prepared: {
|
|
25665
25638
|
kind: "terminal",
|
|
25666
25639
|
flow: "first-generation",
|
|
@@ -25670,8 +25643,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25670
25643
|
}
|
|
25671
25644
|
const commitOpts = options ? {
|
|
25672
25645
|
cliVersion: options.cliVersion ?? "unknown",
|
|
25673
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
25674
|
-
baseBranchHead: options.baseBranchHead
|
|
25646
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
25675
25647
|
} : void 0;
|
|
25676
25648
|
await this.service.committer.commitGeneration("Initial SDK generation", commitOpts);
|
|
25677
25649
|
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
@@ -25680,7 +25652,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25680
25652
|
flow: "first-generation",
|
|
25681
25653
|
previousGenerationSha: null,
|
|
25682
25654
|
currentGenerationSha: genRecord.commit_sha,
|
|
25683
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
25684
25655
|
_prepared: {
|
|
25685
25656
|
kind: "active",
|
|
25686
25657
|
flow: "first-generation",
|
|
@@ -25705,8 +25676,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25705
25676
|
async prepare(options) {
|
|
25706
25677
|
const commitOpts = options ? {
|
|
25707
25678
|
cliVersion: options.cliVersion ?? "unknown",
|
|
25708
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
25709
|
-
baseBranchHead: options.baseBranchHead
|
|
25679
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
25710
25680
|
} : void 0;
|
|
25711
25681
|
await this.service.committer.commitGeneration("Update SDK (replay skipped)", commitOpts);
|
|
25712
25682
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -25728,7 +25698,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25728
25698
|
flow: "skip-application",
|
|
25729
25699
|
previousGenerationSha,
|
|
25730
25700
|
currentGenerationSha: genRecord.commit_sha,
|
|
25731
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
25732
25701
|
_prepared: {
|
|
25733
25702
|
kind: "active",
|
|
25734
25703
|
flow: "skip-application",
|
|
@@ -25787,7 +25756,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25787
25756
|
flow: "no-patches",
|
|
25788
25757
|
previousGenerationSha,
|
|
25789
25758
|
currentGenerationSha: headSha,
|
|
25790
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
25791
25759
|
_prepared: {
|
|
25792
25760
|
kind: "terminal",
|
|
25793
25761
|
flow: "no-patches",
|
|
@@ -25819,8 +25787,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25819
25787
|
}
|
|
25820
25788
|
const commitOpts = options ? {
|
|
25821
25789
|
cliVersion: options.cliVersion ?? "unknown",
|
|
25822
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
25823
|
-
baseBranchHead: options.baseBranchHead
|
|
25790
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
25824
25791
|
} : void 0;
|
|
25825
25792
|
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
25826
25793
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -25830,7 +25797,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25830
25797
|
flow: "no-patches",
|
|
25831
25798
|
previousGenerationSha,
|
|
25832
25799
|
currentGenerationSha: genRecord.commit_sha,
|
|
25833
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
25834
25800
|
_prepared: {
|
|
25835
25801
|
kind: "active",
|
|
25836
25802
|
flow: "no-patches",
|
|
@@ -25918,7 +25884,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25918
25884
|
flow: "normal-regeneration",
|
|
25919
25885
|
previousGenerationSha,
|
|
25920
25886
|
currentGenerationSha: headSha,
|
|
25921
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
25922
25887
|
_prepared: {
|
|
25923
25888
|
kind: "terminal",
|
|
25924
25889
|
flow: "normal-regeneration",
|
|
@@ -25936,14 +25901,16 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25936
25901
|
existingPatches = this.service.lockManager.getPatches();
|
|
25937
25902
|
const seenHashes = /* @__PURE__ */ new Map();
|
|
25938
25903
|
for (const p2 of existingPatches) {
|
|
25939
|
-
|
|
25940
|
-
|
|
25941
|
-
|
|
25942
|
-
|
|
25943
|
-
|
|
25944
|
-
|
|
25945
|
-
|
|
25946
|
-
|
|
25904
|
+
if (!p2.original_commit || p2.original_commit === "") {
|
|
25905
|
+
const priorPatchId = seenHashes.get(p2.content_hash);
|
|
25906
|
+
if (priorPatchId !== void 0) {
|
|
25907
|
+
this.service.lockManager.removePatch(p2.id);
|
|
25908
|
+
this.service.warnings.push(
|
|
25909
|
+
`Consolidated patch ${p2.id} into prior patch ${priorPatchId}: byte-identical patch_content. Lockfile collapsed to avoid duplicate-apply corruption on next regen.`
|
|
25910
|
+
);
|
|
25911
|
+
} else {
|
|
25912
|
+
seenHashes.set(p2.content_hash, p2.id);
|
|
25913
|
+
}
|
|
25947
25914
|
}
|
|
25948
25915
|
}
|
|
25949
25916
|
existingPatches = this.service.lockManager.getPatches();
|
|
@@ -25972,8 +25939,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25972
25939
|
const allPatches = [...existingPatches, ...newPatches];
|
|
25973
25940
|
const commitOpts = options ? {
|
|
25974
25941
|
cliVersion: options.cliVersion ?? "unknown",
|
|
25975
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
25976
|
-
baseBranchHead: options.baseBranchHead
|
|
25942
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
25977
25943
|
} : void 0;
|
|
25978
25944
|
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
25979
25945
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -25983,7 +25949,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
25983
25949
|
flow: "normal-regeneration",
|
|
25984
25950
|
previousGenerationSha,
|
|
25985
25951
|
currentGenerationSha: genRecord.commit_sha,
|
|
25986
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
25987
25952
|
_prepared: {
|
|
25988
25953
|
kind: "active",
|
|
25989
25954
|
flow: "normal-regeneration",
|
|
@@ -26167,68 +26132,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
26167
26132
|
const prep = await this.prepareReplay(options);
|
|
26168
26133
|
return this.applyPreparedReplay(prep, options);
|
|
26169
26134
|
}
|
|
26170
|
-
/**
|
|
26171
|
-
* Sync the lockfile after a divergent PR was squash-merged.
|
|
26172
|
-
* Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
|
|
26173
|
-
*
|
|
26174
|
-
* After updating the generation record, re-detects customer patches via
|
|
26175
|
-
* tree diff between the generation tag (pure generation tree) and HEAD
|
|
26176
|
-
* (which includes customer customizations after squash merge). This ensures
|
|
26177
|
-
* patches survive the squash merge → regenerate cycle even when the lockfile
|
|
26178
|
-
* was restored from the base branch during conflict PR creation.
|
|
26179
|
-
*/
|
|
26180
|
-
async syncFromDivergentMerge(generationCommitSha, options) {
|
|
26181
|
-
const treeHash = await this.git.getTreeHash(generationCommitSha);
|
|
26182
|
-
const timestamp = (await this.git.exec(["log", "-1", "--format=%aI", generationCommitSha])).trim();
|
|
26183
|
-
const record2 = {
|
|
26184
|
-
commit_sha: generationCommitSha,
|
|
26185
|
-
tree_hash: treeHash,
|
|
26186
|
-
timestamp,
|
|
26187
|
-
cli_version: options?.cliVersion ?? "unknown",
|
|
26188
|
-
generator_versions: options?.generatorVersions ?? {},
|
|
26189
|
-
base_branch_head: options?.baseBranchHead
|
|
26190
|
-
};
|
|
26191
|
-
let resolvedPatches;
|
|
26192
|
-
if (!this.lockManager.exists()) {
|
|
26193
|
-
this.lockManager.initializeInMemory(record2);
|
|
26194
|
-
} else {
|
|
26195
|
-
this.lockManager.read();
|
|
26196
|
-
const unresolvedPatches = [
|
|
26197
|
-
...this.lockManager.getUnresolvedPatches(),
|
|
26198
|
-
...this.lockManager.getResolvingPatches()
|
|
26199
|
-
];
|
|
26200
|
-
resolvedPatches = this.lockManager.getPatches().filter((p2) => p2.status == null);
|
|
26201
|
-
this.lockManager.addGeneration(record2);
|
|
26202
|
-
this.lockManager.clearPatches();
|
|
26203
|
-
for (const patch of unresolvedPatches) {
|
|
26204
|
-
this.lockManager.addPatch(patch);
|
|
26205
|
-
}
|
|
26206
|
-
}
|
|
26207
|
-
try {
|
|
26208
|
-
const { patches: redetectedPatches } = await this.detector.detectNewPatches();
|
|
26209
|
-
if (redetectedPatches.length > 0) {
|
|
26210
|
-
const redetectedFiles = new Set(redetectedPatches.flatMap((p2) => p2.files));
|
|
26211
|
-
const currentPatches = this.lockManager.getPatches();
|
|
26212
|
-
for (const patch of currentPatches) {
|
|
26213
|
-
if (patch.status != null && patch.files.some((f) => redetectedFiles.has(f))) {
|
|
26214
|
-
this.lockManager.removePatch(patch.id);
|
|
26215
|
-
}
|
|
26216
|
-
}
|
|
26217
|
-
for (const patch of redetectedPatches) {
|
|
26218
|
-
this.lockManager.addPatch(patch);
|
|
26219
|
-
}
|
|
26220
|
-
}
|
|
26221
|
-
} catch (error) {
|
|
26222
|
-
for (const patch of resolvedPatches ?? []) {
|
|
26223
|
-
this.lockManager.addPatch(patch);
|
|
26224
|
-
}
|
|
26225
|
-
this.detector.warnings.push(
|
|
26226
|
-
`Patch re-detection failed after divergent merge sync. ${(resolvedPatches ?? []).length} previously resolved patch(es) preserved. Error: ${error instanceof Error ? error.message : String(error)}`
|
|
26227
|
-
);
|
|
26228
|
-
}
|
|
26229
|
-
this.lockManager.save();
|
|
26230
|
-
this.detector.warnings.push(...this.lockManager.warnings);
|
|
26231
|
-
}
|
|
26232
26135
|
determineFlow() {
|
|
26233
26136
|
try {
|
|
26234
26137
|
const lock = this.lockManager.read();
|
|
@@ -39707,19 +39610,10 @@ init_dist3();
|
|
|
39707
39610
|
|
|
39708
39611
|
// src/replay/replay-run.ts
|
|
39709
39612
|
init_dist3();
|
|
39710
|
-
var import_child_process4 = require("child_process");
|
|
39711
39613
|
var import_fs12 = require("fs");
|
|
39712
39614
|
var import_path20 = require("path");
|
|
39713
39615
|
async function replayPrepare(params, state) {
|
|
39714
|
-
const {
|
|
39715
|
-
outputDir,
|
|
39716
|
-
cliVersion,
|
|
39717
|
-
generatorVersions,
|
|
39718
|
-
stageOnly = false,
|
|
39719
|
-
generatorName,
|
|
39720
|
-
skipApplication,
|
|
39721
|
-
logger
|
|
39722
|
-
} = params;
|
|
39616
|
+
const { outputDir, cliVersion, generatorVersions, stageOnly = false, skipApplication, logger } = params;
|
|
39723
39617
|
const lockfilePath = (0, import_path20.join)(outputDir, ".fern", "replay.lock");
|
|
39724
39618
|
let autoBootstrapped = false;
|
|
39725
39619
|
let bootstrapAttempted = false;
|
|
@@ -39752,63 +39646,6 @@ async function replayPrepare(params, state) {
|
|
|
39752
39646
|
return null;
|
|
39753
39647
|
}
|
|
39754
39648
|
}
|
|
39755
|
-
const baseBranchHead = gitRevParse(outputDir, "HEAD");
|
|
39756
|
-
let previousGenerationSha = null;
|
|
39757
|
-
let prevBaseBranchHead = null;
|
|
39758
|
-
try {
|
|
39759
|
-
const lockManager = new LockfileManager(outputDir);
|
|
39760
|
-
if (lockManager.exists()) {
|
|
39761
|
-
const lock = lockManager.read();
|
|
39762
|
-
previousGenerationSha = lock.current_generation;
|
|
39763
|
-
const latestGen = lock.generations.find((g) => g.commit_sha === lock.current_generation);
|
|
39764
|
-
prevBaseBranchHead = latestGen?.base_branch_head ?? null;
|
|
39765
|
-
}
|
|
39766
|
-
} catch {
|
|
39767
|
-
}
|
|
39768
|
-
if (previousGenerationSha != null) {
|
|
39769
|
-
const sanitizedName = generatorName?.replace(/\//g, "--");
|
|
39770
|
-
const namespacedTag = sanitizedName != null ? `fern-generation-base--${sanitizedName}` : null;
|
|
39771
|
-
const tagSha = (namespacedTag != null ? gitRevParse(outputDir, namespacedTag) : null) ?? gitRevParse(outputDir, "fern-generation-base");
|
|
39772
|
-
let shouldSync = false;
|
|
39773
|
-
if (tagSha != null) {
|
|
39774
|
-
const tagParent = gitRevParse(outputDir, `${tagSha}^`);
|
|
39775
|
-
if (tagParent === prevBaseBranchHead || tagParent === previousGenerationSha) {
|
|
39776
|
-
const tagDistance = gitDiffNameOnly(outputDir, tagSha, "HEAD").length;
|
|
39777
|
-
const lockDistance = gitDiffNameOnly(
|
|
39778
|
-
outputDir,
|
|
39779
|
-
prevBaseBranchHead ?? previousGenerationSha,
|
|
39780
|
-
"HEAD"
|
|
39781
|
-
).length;
|
|
39782
|
-
shouldSync = tagDistance < lockDistance;
|
|
39783
|
-
}
|
|
39784
|
-
}
|
|
39785
|
-
if (shouldSync && tagSha != null) {
|
|
39786
|
-
if (!isTagMergedIntoHead(outputDir, tagSha)) {
|
|
39787
|
-
logger?.warn(
|
|
39788
|
-
`fern-generation-base tag ${tagSha} is not reachable from HEAD \u2014 skipping divergent-merge sync. The tag likely points at an unmerged generation (PR closed without merge).`
|
|
39789
|
-
);
|
|
39790
|
-
} else {
|
|
39791
|
-
try {
|
|
39792
|
-
const syncService = new ReplayService(outputDir, { enabled: true });
|
|
39793
|
-
await syncService.syncFromDivergentMerge(tagSha, {
|
|
39794
|
-
cliVersion,
|
|
39795
|
-
generatorVersions,
|
|
39796
|
-
baseBranchHead: baseBranchHead ?? void 0
|
|
39797
|
-
});
|
|
39798
|
-
} catch (error) {
|
|
39799
|
-
logger?.warn("Replay divergent-merge sync failed, continuing without sync: " + String(error));
|
|
39800
|
-
throw new ReplayPrepareError(String(error), error);
|
|
39801
|
-
}
|
|
39802
|
-
try {
|
|
39803
|
-
const freshLockManager = new LockfileManager(outputDir);
|
|
39804
|
-
if (freshLockManager.exists()) {
|
|
39805
|
-
previousGenerationSha = freshLockManager.read().current_generation;
|
|
39806
|
-
}
|
|
39807
|
-
} catch {
|
|
39808
|
-
}
|
|
39809
|
-
}
|
|
39810
|
-
}
|
|
39811
|
-
}
|
|
39812
39649
|
const service = new ReplayService(outputDir, { enabled: true });
|
|
39813
39650
|
let preparation;
|
|
39814
39651
|
try {
|
|
@@ -39816,8 +39653,7 @@ async function replayPrepare(params, state) {
|
|
|
39816
39653
|
cliVersion,
|
|
39817
39654
|
generatorVersions,
|
|
39818
39655
|
stageOnly,
|
|
39819
|
-
skipApplication
|
|
39820
|
-
baseBranchHead: baseBranchHead ?? void 0
|
|
39656
|
+
skipApplication
|
|
39821
39657
|
});
|
|
39822
39658
|
} catch (error) {
|
|
39823
39659
|
logger?.warn("Replay prepare failed, continuing without replay: " + String(error));
|
|
@@ -39828,9 +39664,8 @@ async function replayPrepare(params, state) {
|
|
|
39828
39664
|
_preparation: preparation,
|
|
39829
39665
|
outputDir,
|
|
39830
39666
|
flow: preparation.flow,
|
|
39831
|
-
previousGenerationSha,
|
|
39667
|
+
previousGenerationSha: preparation.previousGenerationSha,
|
|
39832
39668
|
currentGenerationSha: preparation.currentGenerationSha,
|
|
39833
|
-
baseBranchHead,
|
|
39834
39669
|
autoBootstrapped,
|
|
39835
39670
|
bootstrapAttempted
|
|
39836
39671
|
};
|
|
@@ -39847,24 +39682,11 @@ async function replayApply(prepared, params = {}) {
|
|
|
39847
39682
|
fernignoreUpdated: false,
|
|
39848
39683
|
previousGenerationSha: prepared.previousGenerationSha,
|
|
39849
39684
|
currentGenerationSha: prepared.currentGenerationSha,
|
|
39850
|
-
baseBranchHead: prepared.baseBranchHead,
|
|
39851
39685
|
autoBootstrapped: prepared.autoBootstrapped,
|
|
39852
39686
|
bootstrapAttempted: prepared.bootstrapAttempted,
|
|
39853
39687
|
failureReason: String(error)
|
|
39854
39688
|
};
|
|
39855
39689
|
}
|
|
39856
|
-
let resolvedBaseBranchHead = prepared.baseBranchHead;
|
|
39857
|
-
try {
|
|
39858
|
-
const freshLockManager = new LockfileManager(prepared.outputDir);
|
|
39859
|
-
if (freshLockManager.exists()) {
|
|
39860
|
-
const freshLock = freshLockManager.read();
|
|
39861
|
-
const latestGen = freshLock.generations.find((g) => g.commit_sha === freshLock.current_generation);
|
|
39862
|
-
if (latestGen?.base_branch_head) {
|
|
39863
|
-
resolvedBaseBranchHead = latestGen.base_branch_head;
|
|
39864
|
-
}
|
|
39865
|
-
}
|
|
39866
|
-
} catch {
|
|
39867
|
-
}
|
|
39868
39690
|
const fernignoreUpdated = await ensureReplayFernignoreEntries(prepared.outputDir);
|
|
39869
39691
|
await ensureGitattributesEntries2(prepared.outputDir);
|
|
39870
39692
|
return {
|
|
@@ -39872,7 +39694,6 @@ async function replayApply(prepared, params = {}) {
|
|
|
39872
39694
|
fernignoreUpdated,
|
|
39873
39695
|
previousGenerationSha: prepared.previousGenerationSha,
|
|
39874
39696
|
currentGenerationSha: prepared.currentGenerationSha,
|
|
39875
|
-
baseBranchHead: resolvedBaseBranchHead,
|
|
39876
39697
|
autoBootstrapped: prepared.autoBootstrapped,
|
|
39877
39698
|
bootstrapAttempted: prepared.bootstrapAttempted
|
|
39878
39699
|
};
|
|
@@ -39888,7 +39709,6 @@ async function replayRun(params) {
|
|
|
39888
39709
|
fernignoreUpdated: false,
|
|
39889
39710
|
previousGenerationSha: null,
|
|
39890
39711
|
currentGenerationSha: null,
|
|
39891
|
-
baseBranchHead: null,
|
|
39892
39712
|
autoBootstrapped: false,
|
|
39893
39713
|
bootstrapAttempted: prepareState.bootstrapAttempted,
|
|
39894
39714
|
failureReason: error instanceof ReplayPrepareError ? error.reason : String(error)
|
|
@@ -39900,7 +39720,6 @@ async function replayRun(params) {
|
|
|
39900
39720
|
fernignoreUpdated: false,
|
|
39901
39721
|
previousGenerationSha: null,
|
|
39902
39722
|
currentGenerationSha: null,
|
|
39903
|
-
baseBranchHead: null,
|
|
39904
39723
|
autoBootstrapped: false,
|
|
39905
39724
|
bootstrapAttempted: prepareState.bootstrapAttempted
|
|
39906
39725
|
};
|
|
@@ -39919,53 +39738,6 @@ var ReplayPrepareError = class extends Error {
|
|
|
39919
39738
|
this.reason = reason;
|
|
39920
39739
|
}
|
|
39921
39740
|
};
|
|
39922
|
-
function isTagMergedIntoHead(cwd2, tagSha) {
|
|
39923
|
-
try {
|
|
39924
|
-
(0, import_child_process4.execFileSync)("git", ["merge-base", "--is-ancestor", tagSha, "HEAD"], {
|
|
39925
|
-
cwd: cwd2,
|
|
39926
|
-
stdio: "pipe"
|
|
39927
|
-
});
|
|
39928
|
-
return true;
|
|
39929
|
-
} catch {
|
|
39930
|
-
}
|
|
39931
|
-
const tagTree = gitRevParse(cwd2, `${tagSha}^{tree}`);
|
|
39932
|
-
if (tagTree == null) {
|
|
39933
|
-
return false;
|
|
39934
|
-
}
|
|
39935
|
-
try {
|
|
39936
|
-
const trees = (0, import_child_process4.execFileSync)("git", ["log", "HEAD", "--format=%T", "--max-count=500"], {
|
|
39937
|
-
cwd: cwd2,
|
|
39938
|
-
encoding: "utf-8",
|
|
39939
|
-
stdio: "pipe"
|
|
39940
|
-
});
|
|
39941
|
-
return trees.split("\n").some((t2) => t2.trim() === tagTree);
|
|
39942
|
-
} catch {
|
|
39943
|
-
return false;
|
|
39944
|
-
}
|
|
39945
|
-
}
|
|
39946
|
-
function gitRevParse(cwd2, rev) {
|
|
39947
|
-
try {
|
|
39948
|
-
return (0, import_child_process4.execFileSync)("git", ["rev-parse", "--verify", rev], {
|
|
39949
|
-
cwd: cwd2,
|
|
39950
|
-
encoding: "utf-8",
|
|
39951
|
-
stdio: "pipe"
|
|
39952
|
-
}).trim();
|
|
39953
|
-
} catch {
|
|
39954
|
-
return null;
|
|
39955
|
-
}
|
|
39956
|
-
}
|
|
39957
|
-
function gitDiffNameOnly(cwd2, from, to) {
|
|
39958
|
-
try {
|
|
39959
|
-
const output = (0, import_child_process4.execFileSync)("git", ["diff", "--name-only", from, to], {
|
|
39960
|
-
cwd: cwd2,
|
|
39961
|
-
encoding: "utf-8",
|
|
39962
|
-
stdio: "pipe"
|
|
39963
|
-
});
|
|
39964
|
-
return output.trim().split("\n").filter(Boolean);
|
|
39965
|
-
} catch {
|
|
39966
|
-
return [];
|
|
39967
|
-
}
|
|
39968
|
-
}
|
|
39969
39741
|
|
|
39970
39742
|
// src/replay/replay-status.ts
|
|
39971
39743
|
init_dist3();
|
|
@@ -40013,7 +39785,7 @@ var consolePipelineLogger = {
|
|
|
40013
39785
|
};
|
|
40014
39786
|
|
|
40015
39787
|
// src/pipeline/PostGenerationPipeline.ts
|
|
40016
|
-
var
|
|
39788
|
+
var import_child_process6 = require("child_process");
|
|
40017
39789
|
|
|
40018
39790
|
// src/pipeline/github/constants.ts
|
|
40019
39791
|
var FERN_BOT_NAME2 = "fern-api";
|
|
@@ -40021,7 +39793,7 @@ var FERN_BOT_EMAIL2 = "115122769+fern-api[bot]@users.noreply.github.com";
|
|
|
40021
39793
|
var FERN_BOT_LOGIN2 = "fern-api[bot]";
|
|
40022
39794
|
|
|
40023
39795
|
// src/pipeline/steps/AutoVersionStep.ts
|
|
40024
|
-
var
|
|
39796
|
+
var import_child_process4 = require("child_process");
|
|
40025
39797
|
var import_fs14 = require("fs");
|
|
40026
39798
|
var import_promises15 = require("fs/promises");
|
|
40027
39799
|
var import_path22 = require("path");
|
|
@@ -41395,7 +41167,7 @@ var AutoVersionStep = class extends BaseStep {
|
|
|
41395
41167
|
}
|
|
41396
41168
|
async readVersionFromMetadata() {
|
|
41397
41169
|
try {
|
|
41398
|
-
const output = (0,
|
|
41170
|
+
const output = (0, import_child_process4.execFileSync)("git", ["show", "HEAD~1:.fern/metadata.json"], {
|
|
41399
41171
|
cwd: this.outputDir,
|
|
41400
41172
|
encoding: "utf-8",
|
|
41401
41173
|
stdio: "pipe"
|
|
@@ -41411,7 +41183,7 @@ var AutoVersionStep = class extends BaseStep {
|
|
|
41411
41183
|
return mappedMagicVersion.startsWith("v") ? `v${stripped}` : stripped;
|
|
41412
41184
|
}
|
|
41413
41185
|
gitDiff(from, to) {
|
|
41414
|
-
return (0,
|
|
41186
|
+
return (0, import_child_process4.execFileSync)("git", ["diff", from, to, "--", ".", ":(exclude).fern/metadata.json"], {
|
|
41415
41187
|
cwd: this.outputDir,
|
|
41416
41188
|
encoding: "utf-8",
|
|
41417
41189
|
stdio: "pipe",
|
|
@@ -41424,12 +41196,12 @@ var AutoVersionStep = class extends BaseStep {
|
|
|
41424
41196
|
const fullMessage = body.length > 0 ? `${COMMIT_MARKER} ${subject}
|
|
41425
41197
|
|
|
41426
41198
|
${body}` : `${COMMIT_MARKER} ${subject}`;
|
|
41427
|
-
(0,
|
|
41428
|
-
(0,
|
|
41199
|
+
(0, import_child_process4.execFileSync)("git", ["add", "-A"], { cwd: this.outputDir, stdio: "pipe" });
|
|
41200
|
+
(0, import_child_process4.execFileSync)("git", ["commit", "--allow-empty", "-m", fullMessage], {
|
|
41429
41201
|
cwd: this.outputDir,
|
|
41430
41202
|
stdio: "pipe"
|
|
41431
41203
|
});
|
|
41432
|
-
return (0,
|
|
41204
|
+
return (0, import_child_process4.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
41433
41205
|
cwd: this.outputDir,
|
|
41434
41206
|
encoding: "utf-8",
|
|
41435
41207
|
stdio: "pipe"
|
|
@@ -41604,19 +41376,17 @@ ${newBlock}${remainder}`;
|
|
|
41604
41376
|
};
|
|
41605
41377
|
|
|
41606
41378
|
// src/pipeline/steps/GenerationCommitStep.ts
|
|
41607
|
-
var
|
|
41379
|
+
var import_child_process5 = require("child_process");
|
|
41608
41380
|
var GenerationCommitStep = class extends BaseStep {
|
|
41609
|
-
constructor(outputDir, logger, config, cliVersion, generatorVersions
|
|
41381
|
+
constructor(outputDir, logger, config, cliVersion, generatorVersions) {
|
|
41610
41382
|
super(outputDir, logger);
|
|
41611
41383
|
this.config = config;
|
|
41612
41384
|
this.cliVersion = cliVersion;
|
|
41613
41385
|
this.generatorVersions = generatorVersions;
|
|
41614
|
-
this.generatorName = generatorName;
|
|
41615
41386
|
}
|
|
41616
41387
|
config;
|
|
41617
41388
|
cliVersion;
|
|
41618
41389
|
generatorVersions;
|
|
41619
|
-
generatorName;
|
|
41620
41390
|
name = "generationCommit";
|
|
41621
41391
|
async execute(_context) {
|
|
41622
41392
|
const headBeforePrepare = tryRevParse(this.outputDir, "HEAD");
|
|
@@ -41628,7 +41398,6 @@ var GenerationCommitStep = class extends BaseStep {
|
|
|
41628
41398
|
outputDir: this.outputDir,
|
|
41629
41399
|
cliVersion: this.cliVersion,
|
|
41630
41400
|
generatorVersions: this.generatorVersions,
|
|
41631
|
-
generatorName: this.generatorName,
|
|
41632
41401
|
skipApplication: this.config.skipApplication,
|
|
41633
41402
|
logger: this.logger
|
|
41634
41403
|
},
|
|
@@ -41638,7 +41407,7 @@ var GenerationCommitStep = class extends BaseStep {
|
|
|
41638
41407
|
const reason = error instanceof ReplayPrepareError ? error.reason : String(error);
|
|
41639
41408
|
if (headBeforePrepare != null) {
|
|
41640
41409
|
try {
|
|
41641
|
-
(0,
|
|
41410
|
+
(0, import_child_process5.execFileSync)("git", ["reset", "--hard", headBeforePrepare], {
|
|
41642
41411
|
cwd: this.outputDir,
|
|
41643
41412
|
stdio: "pipe"
|
|
41644
41413
|
});
|
|
@@ -41667,7 +41436,6 @@ var GenerationCommitStep = class extends BaseStep {
|
|
|
41667
41436
|
preparedReplay: prepared,
|
|
41668
41437
|
previousGenerationSha: prepared.previousGenerationSha ?? void 0,
|
|
41669
41438
|
currentGenerationSha: prepared.currentGenerationSha,
|
|
41670
|
-
baseBranchHead: prepared.baseBranchHead ?? void 0,
|
|
41671
41439
|
flow: prepared.flow,
|
|
41672
41440
|
bootstrapAttempted: prepareState.bootstrapAttempted
|
|
41673
41441
|
};
|
|
@@ -41675,7 +41443,7 @@ var GenerationCommitStep = class extends BaseStep {
|
|
|
41675
41443
|
};
|
|
41676
41444
|
function tryRevParse(cwd2, rev) {
|
|
41677
41445
|
try {
|
|
41678
|
-
return (0,
|
|
41446
|
+
return (0, import_child_process5.execFileSync)("git", ["rev-parse", "--verify", rev], {
|
|
41679
41447
|
cwd: cwd2,
|
|
41680
41448
|
encoding: "utf-8",
|
|
41681
41449
|
stdio: "pipe"
|
|
@@ -42422,17 +42190,15 @@ function resolveBranchAction({
|
|
|
42422
42190
|
|
|
42423
42191
|
// src/pipeline/steps/ReplayStep.ts
|
|
42424
42192
|
var ReplayStep = class extends BaseStep {
|
|
42425
|
-
constructor(outputDir, logger, config, cliVersion, generatorVersions
|
|
42193
|
+
constructor(outputDir, logger, config, cliVersion, generatorVersions) {
|
|
42426
42194
|
super(outputDir, logger);
|
|
42427
42195
|
this.config = config;
|
|
42428
42196
|
this.cliVersion = cliVersion;
|
|
42429
42197
|
this.generatorVersions = generatorVersions;
|
|
42430
|
-
this.generatorName = generatorName;
|
|
42431
42198
|
}
|
|
42432
42199
|
config;
|
|
42433
42200
|
cliVersion;
|
|
42434
42201
|
generatorVersions;
|
|
42435
|
-
generatorName;
|
|
42436
42202
|
name = "replay";
|
|
42437
42203
|
async execute(context) {
|
|
42438
42204
|
const generationCommit = context.previousStepResults.generationCommit;
|
|
@@ -42471,7 +42237,6 @@ var ReplayStep = class extends BaseStep {
|
|
|
42471
42237
|
cliVersion: this.cliVersion,
|
|
42472
42238
|
generatorVersions: this.generatorVersions,
|
|
42473
42239
|
stageOnly: this.config.stageOnly ?? false,
|
|
42474
|
-
generatorName: this.generatorName,
|
|
42475
42240
|
skipApplication: this.config.skipApplication,
|
|
42476
42241
|
logger: this.logger
|
|
42477
42242
|
});
|
|
@@ -42483,7 +42248,6 @@ var ReplayStep = class extends BaseStep {
|
|
|
42483
42248
|
errorMessage: result.failureReason,
|
|
42484
42249
|
previousGenerationSha: result.previousGenerationSha ?? void 0,
|
|
42485
42250
|
currentGenerationSha: result.currentGenerationSha ?? void 0,
|
|
42486
|
-
baseBranchHead: result.baseBranchHead ?? void 0,
|
|
42487
42251
|
autoBootstrapped: result.autoBootstrapped,
|
|
42488
42252
|
bootstrapAttempted: result.bootstrapAttempted,
|
|
42489
42253
|
flow: "normal-regeneration",
|
|
@@ -42498,7 +42262,6 @@ var ReplayStep = class extends BaseStep {
|
|
|
42498
42262
|
success: true,
|
|
42499
42263
|
previousGenerationSha: result.previousGenerationSha ?? void 0,
|
|
42500
42264
|
currentGenerationSha: result.currentGenerationSha ?? void 0,
|
|
42501
|
-
baseBranchHead: result.baseBranchHead ?? void 0,
|
|
42502
42265
|
autoBootstrapped: result.autoBootstrapped,
|
|
42503
42266
|
bootstrapAttempted: result.bootstrapAttempted,
|
|
42504
42267
|
flow: "first-generation",
|
|
@@ -42513,7 +42276,6 @@ var ReplayStep = class extends BaseStep {
|
|
|
42513
42276
|
success: true,
|
|
42514
42277
|
previousGenerationSha: result.previousGenerationSha ?? void 0,
|
|
42515
42278
|
currentGenerationSha: result.currentGenerationSha ?? void 0,
|
|
42516
|
-
baseBranchHead: result.baseBranchHead ?? void 0,
|
|
42517
42279
|
autoBootstrapped: result.autoBootstrapped,
|
|
42518
42280
|
bootstrapAttempted: result.bootstrapAttempted,
|
|
42519
42281
|
flow: report.flow,
|
|
@@ -42785,8 +42547,7 @@ var PostGenerationPipeline = class {
|
|
|
42785
42547
|
this.logger,
|
|
42786
42548
|
{ enabled: true, skipApplication: config.replay.skipApplication },
|
|
42787
42549
|
config.cliVersion,
|
|
42788
|
-
config.generatorVersions
|
|
42789
|
-
config.generatorName
|
|
42550
|
+
config.generatorVersions
|
|
42790
42551
|
)
|
|
42791
42552
|
);
|
|
42792
42553
|
}
|
|
@@ -42800,8 +42561,7 @@ var PostGenerationPipeline = class {
|
|
|
42800
42561
|
this.logger,
|
|
42801
42562
|
config.replay,
|
|
42802
42563
|
config.cliVersion,
|
|
42803
|
-
config.generatorVersions
|
|
42804
|
-
config.generatorName
|
|
42564
|
+
config.generatorVersions
|
|
42805
42565
|
)
|
|
42806
42566
|
);
|
|
42807
42567
|
}
|
|
@@ -42826,8 +42586,8 @@ var PostGenerationPipeline = class {
|
|
|
42826
42586
|
async run() {
|
|
42827
42587
|
if (this.config.github?.enabled) {
|
|
42828
42588
|
try {
|
|
42829
|
-
(0,
|
|
42830
|
-
(0,
|
|
42589
|
+
(0, import_child_process6.execFileSync)("git", ["config", "user.name", FERN_BOT_NAME2], { cwd: this.config.outputDir });
|
|
42590
|
+
(0, import_child_process6.execFileSync)("git", ["config", "user.email", FERN_BOT_EMAIL2], { cwd: this.config.outputDir });
|
|
42831
42591
|
} catch {
|
|
42832
42592
|
}
|
|
42833
42593
|
}
|
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.31",
|
|
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.16.0",
|
|
27
27
|
"@octokit/rest": "^22.0.1",
|
|
28
28
|
"es-toolkit": "^1.45.1",
|
|
29
29
|
"semver": "^7.6.3",
|