@fern-api/replay 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +92 -189
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +92 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -47
- package/dist/index.d.ts +25 -47
- package/dist/index.js +92 -189
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -13596,9 +13596,22 @@ function isGenerationCommit(commit) {
|
|
|
13596
13596
|
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.
|
|
13597
13597
|
// The default PR title is "SDK Generation" (from GithubStep's commitMessage default).
|
|
13598
13598
|
// GitHub appends "(#N)" for the PR number, e.g. "SDK Generation (#70)".
|
|
13599
|
-
commit.message.startsWith("SDK Generation")
|
|
13599
|
+
commit.message.startsWith("SDK Generation") || // Merge commit (GitHub's "Create a merge commit" option) of a fern-bot
|
|
13600
|
+
// regen PR. First-parent walking lands on this commit, not on the
|
|
13601
|
+
// generation commit on the side branch, so the classifier must treat
|
|
13602
|
+
// it as a generation boundary. GitHub's default subject is
|
|
13603
|
+
// "Merge pull request #N from <owner>/<branch>"; fern-bot branches are
|
|
13604
|
+
// named "<owner>/fern-bot/<...>".
|
|
13605
|
+
/^Merge pull request #\d+ from [^/]+\/fern-bot\//.test(commit.message);
|
|
13600
13606
|
return isBotAuthor || hasGenerationMarker;
|
|
13601
13607
|
}
|
|
13608
|
+
function isGenerationBoundary(commit) {
|
|
13609
|
+
const isFernSupport = FERN_SUPPORT_NAMES.includes(commit.authorName);
|
|
13610
|
+
const isBotAuthor = !isFernSupport && (commit.authorLogin === FERN_BOT_LOGIN || commit.authorEmail === FERN_BOT_EMAIL || commit.authorName === FERN_BOT_NAME);
|
|
13611
|
+
const isAutoversion = commit.message.startsWith("[fern-autoversion]");
|
|
13612
|
+
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");
|
|
13613
|
+
return isBotAuthor && !isAutoversion || hasBoundaryMarker;
|
|
13614
|
+
}
|
|
13602
13615
|
function isReplayCommit(commit) {
|
|
13603
13616
|
return commit.message.startsWith("[fern-replay]");
|
|
13604
13617
|
}
|
|
@@ -13672,8 +13685,48 @@ var ReplayDetector = class {
|
|
|
13672
13685
|
this.lockManager = lockManager;
|
|
13673
13686
|
this.sdkOutputDir = sdkOutputDir;
|
|
13674
13687
|
}
|
|
13688
|
+
/**
|
|
13689
|
+
* Derive the previous-generation SHA from git history instead of trusting
|
|
13690
|
+
* `lock.current_generation`.
|
|
13691
|
+
*
|
|
13692
|
+
* Walks `git log HEAD --first-parent` and returns the SHA of the most recent
|
|
13693
|
+
* commit that `isGenerationBoundary` classifies as a customization-baseline
|
|
13694
|
+
* boundary, or null if no such commit is reachable from HEAD on the mainline.
|
|
13695
|
+
*
|
|
13696
|
+
* `isGenerationBoundary` is intentionally narrower than `isGenerationCommit`:
|
|
13697
|
+
* it excludes `[fern-autoversion]` (lightweight bumps that don't reset the
|
|
13698
|
+
* baseline) and the GitHub merge-commit subject for fern-bot regen PRs
|
|
13699
|
+
* (which sits on first-parent but where the customer fix-up commits live
|
|
13700
|
+
* on second-parent — stopping at the merge would hide them).
|
|
13701
|
+
*
|
|
13702
|
+
* See docs/adr/0001-derived-scan-boundary.md.
|
|
13703
|
+
*/
|
|
13704
|
+
async findPreviousGenerationFromHistory() {
|
|
13705
|
+
let log;
|
|
13706
|
+
try {
|
|
13707
|
+
log = await this.git.exec([
|
|
13708
|
+
"log",
|
|
13709
|
+
"--first-parent",
|
|
13710
|
+
"--format=%H%x00%an%x00%ae%x00%s",
|
|
13711
|
+
"HEAD"
|
|
13712
|
+
]);
|
|
13713
|
+
} catch {
|
|
13714
|
+
return null;
|
|
13715
|
+
}
|
|
13716
|
+
for (const commit of this.parseGitLog(log)) {
|
|
13717
|
+
if (isGenerationBoundary(commit)) {
|
|
13718
|
+
return commit.sha;
|
|
13719
|
+
}
|
|
13720
|
+
}
|
|
13721
|
+
return null;
|
|
13722
|
+
}
|
|
13675
13723
|
async detectNewPatches() {
|
|
13676
13724
|
const lock = this.lockManager.read();
|
|
13725
|
+
const derivedSha = await this.findPreviousGenerationFromHistory();
|
|
13726
|
+
if (derivedSha !== null) {
|
|
13727
|
+
const derivedRecord = await this.synthesizeGenerationRecord(derivedSha);
|
|
13728
|
+
return this.detectPatchesInRange(derivedSha, derivedRecord, lock);
|
|
13729
|
+
}
|
|
13677
13730
|
const lastGen = this.getLastGeneration(lock);
|
|
13678
13731
|
if (!lastGen) {
|
|
13679
13732
|
return { patches: [], revertedPatchIds: [] };
|
|
@@ -13681,7 +13734,7 @@ var ReplayDetector = class {
|
|
|
13681
13734
|
const exists2 = await this.git.commitExists(lastGen.commit_sha);
|
|
13682
13735
|
if (!exists2) {
|
|
13683
13736
|
this.warnings.push(
|
|
13684
|
-
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to
|
|
13737
|
+
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to tree-diff detection (likely a shallow clone).`
|
|
13685
13738
|
);
|
|
13686
13739
|
return this.detectPatchesViaTreeDiff(
|
|
13687
13740
|
lastGen,
|
|
@@ -13689,44 +13742,8 @@ var ReplayDetector = class {
|
|
|
13689
13742
|
true
|
|
13690
13743
|
);
|
|
13691
13744
|
}
|
|
13692
|
-
const isAncestor = await this.git.isAncestor(lastGen.commit_sha, "HEAD");
|
|
13693
|
-
if (!isAncestor) {
|
|
13694
|
-
return this.detectPatchesViaMergeBase(lastGen, lock);
|
|
13695
|
-
}
|
|
13696
13745
|
return this.detectPatchesInRange(lastGen.commit_sha, lastGen, lock);
|
|
13697
13746
|
}
|
|
13698
|
-
/**
|
|
13699
|
-
* Non-linear-history detection via `merge-base(prevGen, HEAD)`.
|
|
13700
|
-
*
|
|
13701
|
-
* After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
|
|
13702
|
-
* `[fern-generated]`) is orphaned but still in git's object database. The
|
|
13703
|
-
* merge-base is the commit on main from which the bot's branch was created —
|
|
13704
|
-
* a reachable ancestor of HEAD. Walking that range and classifying each
|
|
13705
|
-
* commit via `isGenerationCommit` mirrors the linear path and eliminates
|
|
13706
|
-
* the bug class where pipeline-driven changes (autoversion, replay,
|
|
13707
|
-
* generation) get bundled as customer customizations.
|
|
13708
|
-
*
|
|
13709
|
-
* Falls through to the legacy composite path when no common ancestor exists
|
|
13710
|
-
* (truly disjoint history — orphan branches with no shared root).
|
|
13711
|
-
*/
|
|
13712
|
-
async detectPatchesViaMergeBase(lastGen, lock) {
|
|
13713
|
-
let mergeBase = "";
|
|
13714
|
-
try {
|
|
13715
|
-
mergeBase = (await this.git.exec(["merge-base", lastGen.commit_sha, "HEAD"])).trim();
|
|
13716
|
-
} catch {
|
|
13717
|
-
}
|
|
13718
|
-
if (!mergeBase) {
|
|
13719
|
-
this.warnings.push(
|
|
13720
|
-
`No common ancestor between previous generation ${lastGen.commit_sha.slice(0, 7)} and HEAD. Falling back to composite tree-diff detection.`
|
|
13721
|
-
);
|
|
13722
|
-
return this.detectPatchesViaTreeDiff(
|
|
13723
|
-
lastGen,
|
|
13724
|
-
/* commitKnownMissing */
|
|
13725
|
-
false
|
|
13726
|
-
);
|
|
13727
|
-
}
|
|
13728
|
-
return this.detectPatchesInRange(mergeBase, lastGen, lock);
|
|
13729
|
-
}
|
|
13730
13747
|
/**
|
|
13731
13748
|
* Walk `${rangeStart}..HEAD`, classify each commit, emit one
|
|
13732
13749
|
* `StoredPatch` per customer commit. Body shared by the linear path
|
|
@@ -13739,7 +13756,7 @@ var ReplayDetector = class {
|
|
|
13739
13756
|
async detectPatchesInRange(rangeStart, lastGen, lock) {
|
|
13740
13757
|
const log = await this.git.exec([
|
|
13741
13758
|
"log",
|
|
13742
|
-
"--
|
|
13759
|
+
"--no-merges",
|
|
13743
13760
|
"--format=%H%x00%an%x00%ae%x00%s",
|
|
13744
13761
|
`${rangeStart}..HEAD`,
|
|
13745
13762
|
"--",
|
|
@@ -13759,18 +13776,6 @@ var ReplayDetector = class {
|
|
|
13759
13776
|
continue;
|
|
13760
13777
|
}
|
|
13761
13778
|
const parents = await this.git.getCommitParents(commit.sha);
|
|
13762
|
-
if (parents.length > 1) {
|
|
13763
|
-
const compositePatch = await this.createMergeCompositePatch({
|
|
13764
|
-
mergeCommit: commit,
|
|
13765
|
-
firstParent: parents[0],
|
|
13766
|
-
lastGen,
|
|
13767
|
-
lock
|
|
13768
|
-
});
|
|
13769
|
-
if (compositePatch) {
|
|
13770
|
-
newPatches.push(compositePatch);
|
|
13771
|
-
}
|
|
13772
|
-
continue;
|
|
13773
|
-
}
|
|
13774
13779
|
let patchContent;
|
|
13775
13780
|
try {
|
|
13776
13781
|
patchContent = await this.git.formatPatch(commit.sha);
|
|
@@ -13908,9 +13913,7 @@ var ReplayDetector = class {
|
|
|
13908
13913
|
* within the current linear detection window, and are absent from
|
|
13909
13914
|
* HEAD at the end of the window.
|
|
13910
13915
|
*
|
|
13911
|
-
* Rationale:
|
|
13912
|
-
* firstParent..mergeCommit so net-zero files never enter the composite. On
|
|
13913
|
-
* linear history each commit becomes its own patch, so a file that was
|
|
13916
|
+
* Rationale: each commit becomes its own patch, so a file that was
|
|
13914
13917
|
* briefly added and later removed survives as a create+delete pair whose
|
|
13915
13918
|
* cumulative diff is empty. We drop such patches before they reach the
|
|
13916
13919
|
* lockfile.
|
|
@@ -13977,54 +13980,6 @@ var ReplayDetector = class {
|
|
|
13977
13980
|
return /* @__PURE__ */ new Set();
|
|
13978
13981
|
}
|
|
13979
13982
|
}
|
|
13980
|
-
/**
|
|
13981
|
-
* Create a single composite patch for a merge commit by diffing the merge
|
|
13982
|
-
* commit against its first parent. This captures the net change of the
|
|
13983
|
-
* entire merged branch as one patch, eliminating accumulator chaining and
|
|
13984
|
-
* zero-net-change ghost conflicts.
|
|
13985
|
-
*/
|
|
13986
|
-
async createMergeCompositePatch(input) {
|
|
13987
|
-
const { mergeCommit, firstParent, lastGen, lock } = input;
|
|
13988
|
-
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
13989
|
-
const filesOutput = await this.git.exec([
|
|
13990
|
-
"diff",
|
|
13991
|
-
"--name-only",
|
|
13992
|
-
firstParent,
|
|
13993
|
-
mergeCommit.sha
|
|
13994
|
-
]);
|
|
13995
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
13996
|
-
if (sensitiveFiles.length > 0) {
|
|
13997
|
-
this.warnings.push(
|
|
13998
|
-
`Sensitive file(s) excluded from merge patch for commit ${mergeCommit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
13999
|
-
);
|
|
14000
|
-
}
|
|
14001
|
-
if (files.length === 0) return null;
|
|
14002
|
-
const diff = await this.git.exec([
|
|
14003
|
-
"diff",
|
|
14004
|
-
firstParent,
|
|
14005
|
-
mergeCommit.sha,
|
|
14006
|
-
"--",
|
|
14007
|
-
...files
|
|
14008
|
-
]);
|
|
14009
|
-
if (!diff.trim()) return null;
|
|
14010
|
-
const contentHash = this.computeContentHash(diff);
|
|
14011
|
-
if (lock.patches.some((p) => p.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
14012
|
-
return null;
|
|
14013
|
-
}
|
|
14014
|
-
const theirsSnapshot = await capturePatchSnapshot(this.git, files, mergeCommit.sha);
|
|
14015
|
-
return {
|
|
14016
|
-
id: `patch-merge-${mergeCommit.sha.slice(0, 8)}`,
|
|
14017
|
-
content_hash: contentHash,
|
|
14018
|
-
original_commit: mergeCommit.sha,
|
|
14019
|
-
original_message: mergeCommit.message,
|
|
14020
|
-
original_author: `${mergeCommit.authorName} <${mergeCommit.authorEmail}>`,
|
|
14021
|
-
base_generation: lastGen.commit_sha,
|
|
14022
|
-
files,
|
|
14023
|
-
patch_content: diff,
|
|
14024
|
-
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
14025
|
-
...this.isCreationOnlyPatch(diff) ? { user_owned: true } : {}
|
|
14026
|
-
};
|
|
14027
|
-
}
|
|
14028
13983
|
/**
|
|
14029
13984
|
* Detect patches via tree diff for non-linear history. Returns a composite patch.
|
|
14030
13985
|
* Revert reconciliation is skipped here because tree-diff produces a single composite
|
|
@@ -14224,6 +14179,26 @@ var ReplayDetector = class {
|
|
|
14224
14179
|
getLastGeneration(lock) {
|
|
14225
14180
|
return lock.generations.find((g) => g.commit_sha === lock.current_generation);
|
|
14226
14181
|
}
|
|
14182
|
+
/**
|
|
14183
|
+
* Build a transient `GenerationRecord` from a SHA derived by walking git
|
|
14184
|
+
* history. Only `commit_sha` (and `tree_hash` when needed downstream) is
|
|
14185
|
+
* load-bearing here; the rest are filled with defensible placeholders.
|
|
14186
|
+
* This record is consumed by `detectPatchesInRange` and is never persisted.
|
|
14187
|
+
*/
|
|
14188
|
+
async synthesizeGenerationRecord(sha) {
|
|
14189
|
+
let tree_hash = "";
|
|
14190
|
+
try {
|
|
14191
|
+
tree_hash = await this.git.getTreeHash(sha);
|
|
14192
|
+
} catch {
|
|
14193
|
+
}
|
|
14194
|
+
return {
|
|
14195
|
+
commit_sha: sha,
|
|
14196
|
+
tree_hash,
|
|
14197
|
+
timestamp: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
14198
|
+
cli_version: "",
|
|
14199
|
+
generator_versions: {}
|
|
14200
|
+
};
|
|
14201
|
+
}
|
|
14227
14202
|
};
|
|
14228
14203
|
|
|
14229
14204
|
// src/ReplayService.ts
|
|
@@ -16944,8 +16919,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
16944
16919
|
tree_hash: treeHash,
|
|
16945
16920
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16946
16921
|
cli_version: options?.cliVersion ?? "unknown",
|
|
16947
|
-
generator_versions: options?.generatorVersions ?? {}
|
|
16948
|
-
base_branch_head: options?.baseBranchHead
|
|
16922
|
+
generator_versions: options?.generatorVersions ?? {}
|
|
16949
16923
|
};
|
|
16950
16924
|
}
|
|
16951
16925
|
async stageAll() {
|
|
@@ -17324,7 +17298,6 @@ var FirstGenerationFlow = class {
|
|
|
17324
17298
|
flow: "first-generation",
|
|
17325
17299
|
previousGenerationSha: null,
|
|
17326
17300
|
currentGenerationSha: headSha,
|
|
17327
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
17328
17301
|
_prepared: {
|
|
17329
17302
|
kind: "terminal",
|
|
17330
17303
|
flow: "first-generation",
|
|
@@ -17334,8 +17307,7 @@ var FirstGenerationFlow = class {
|
|
|
17334
17307
|
}
|
|
17335
17308
|
const commitOpts = options ? {
|
|
17336
17309
|
cliVersion: options.cliVersion ?? "unknown",
|
|
17337
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
17338
|
-
baseBranchHead: options.baseBranchHead
|
|
17310
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
17339
17311
|
} : void 0;
|
|
17340
17312
|
await this.service.committer.commitGeneration("Initial SDK generation", commitOpts);
|
|
17341
17313
|
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
@@ -17344,7 +17316,6 @@ var FirstGenerationFlow = class {
|
|
|
17344
17316
|
flow: "first-generation",
|
|
17345
17317
|
previousGenerationSha: null,
|
|
17346
17318
|
currentGenerationSha: genRecord.commit_sha,
|
|
17347
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
17348
17319
|
_prepared: {
|
|
17349
17320
|
kind: "active",
|
|
17350
17321
|
flow: "first-generation",
|
|
@@ -17381,8 +17352,7 @@ var SkipApplicationFlow = class {
|
|
|
17381
17352
|
async prepare(options) {
|
|
17382
17353
|
const commitOpts = options ? {
|
|
17383
17354
|
cliVersion: options.cliVersion ?? "unknown",
|
|
17384
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
17385
|
-
baseBranchHead: options.baseBranchHead
|
|
17355
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
17386
17356
|
} : void 0;
|
|
17387
17357
|
await this.service.committer.commitGeneration("Update SDK (replay skipped)", commitOpts);
|
|
17388
17358
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -17404,7 +17374,6 @@ var SkipApplicationFlow = class {
|
|
|
17404
17374
|
flow: "skip-application",
|
|
17405
17375
|
previousGenerationSha,
|
|
17406
17376
|
currentGenerationSha: genRecord.commit_sha,
|
|
17407
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
17408
17377
|
_prepared: {
|
|
17409
17378
|
kind: "active",
|
|
17410
17379
|
flow: "skip-application",
|
|
@@ -17465,7 +17434,6 @@ var NoPatchesFlow = class {
|
|
|
17465
17434
|
flow: "no-patches",
|
|
17466
17435
|
previousGenerationSha,
|
|
17467
17436
|
currentGenerationSha: headSha,
|
|
17468
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
17469
17437
|
_prepared: {
|
|
17470
17438
|
kind: "terminal",
|
|
17471
17439
|
flow: "no-patches",
|
|
@@ -17497,8 +17465,7 @@ var NoPatchesFlow = class {
|
|
|
17497
17465
|
}
|
|
17498
17466
|
const commitOpts = options ? {
|
|
17499
17467
|
cliVersion: options.cliVersion ?? "unknown",
|
|
17500
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
17501
|
-
baseBranchHead: options.baseBranchHead
|
|
17468
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
17502
17469
|
} : void 0;
|
|
17503
17470
|
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
17504
17471
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -17508,7 +17475,6 @@ var NoPatchesFlow = class {
|
|
|
17508
17475
|
flow: "no-patches",
|
|
17509
17476
|
previousGenerationSha,
|
|
17510
17477
|
currentGenerationSha: genRecord.commit_sha,
|
|
17511
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
17512
17478
|
_prepared: {
|
|
17513
17479
|
kind: "active",
|
|
17514
17480
|
flow: "no-patches",
|
|
@@ -17598,7 +17564,6 @@ var NormalRegenerationFlow = class {
|
|
|
17598
17564
|
flow: "normal-regeneration",
|
|
17599
17565
|
previousGenerationSha,
|
|
17600
17566
|
currentGenerationSha: headSha,
|
|
17601
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
17602
17567
|
_prepared: {
|
|
17603
17568
|
kind: "terminal",
|
|
17604
17569
|
flow: "normal-regeneration",
|
|
@@ -17616,14 +17581,16 @@ var NormalRegenerationFlow = class {
|
|
|
17616
17581
|
existingPatches = this.service.lockManager.getPatches();
|
|
17617
17582
|
const seenHashes = /* @__PURE__ */ new Map();
|
|
17618
17583
|
for (const p of existingPatches) {
|
|
17619
|
-
|
|
17620
|
-
|
|
17621
|
-
|
|
17622
|
-
|
|
17623
|
-
|
|
17624
|
-
|
|
17625
|
-
|
|
17626
|
-
|
|
17584
|
+
if (!p.original_commit || p.original_commit === "") {
|
|
17585
|
+
const priorPatchId = seenHashes.get(p.content_hash);
|
|
17586
|
+
if (priorPatchId !== void 0) {
|
|
17587
|
+
this.service.lockManager.removePatch(p.id);
|
|
17588
|
+
this.service.warnings.push(
|
|
17589
|
+
`Consolidated patch ${p.id} into prior patch ${priorPatchId}: byte-identical patch_content. Lockfile collapsed to avoid duplicate-apply corruption on next regen.`
|
|
17590
|
+
);
|
|
17591
|
+
} else {
|
|
17592
|
+
seenHashes.set(p.content_hash, p.id);
|
|
17593
|
+
}
|
|
17627
17594
|
}
|
|
17628
17595
|
}
|
|
17629
17596
|
existingPatches = this.service.lockManager.getPatches();
|
|
@@ -17652,8 +17619,7 @@ var NormalRegenerationFlow = class {
|
|
|
17652
17619
|
const allPatches = [...existingPatches, ...newPatches];
|
|
17653
17620
|
const commitOpts = options ? {
|
|
17654
17621
|
cliVersion: options.cliVersion ?? "unknown",
|
|
17655
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
17656
|
-
baseBranchHead: options.baseBranchHead
|
|
17622
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
17657
17623
|
} : void 0;
|
|
17658
17624
|
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
17659
17625
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -17663,7 +17629,6 @@ var NormalRegenerationFlow = class {
|
|
|
17663
17629
|
flow: "normal-regeneration",
|
|
17664
17630
|
previousGenerationSha,
|
|
17665
17631
|
currentGenerationSha: genRecord.commit_sha,
|
|
17666
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
17667
17632
|
_prepared: {
|
|
17668
17633
|
kind: "active",
|
|
17669
17634
|
flow: "normal-regeneration",
|
|
@@ -17856,68 +17821,6 @@ var ReplayService = class {
|
|
|
17856
17821
|
const prep = await this.prepareReplay(options);
|
|
17857
17822
|
return this.applyPreparedReplay(prep, options);
|
|
17858
17823
|
}
|
|
17859
|
-
/**
|
|
17860
|
-
* Sync the lockfile after a divergent PR was squash-merged.
|
|
17861
|
-
* Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
|
|
17862
|
-
*
|
|
17863
|
-
* After updating the generation record, re-detects customer patches via
|
|
17864
|
-
* tree diff between the generation tag (pure generation tree) and HEAD
|
|
17865
|
-
* (which includes customer customizations after squash merge). This ensures
|
|
17866
|
-
* patches survive the squash merge → regenerate cycle even when the lockfile
|
|
17867
|
-
* was restored from the base branch during conflict PR creation.
|
|
17868
|
-
*/
|
|
17869
|
-
async syncFromDivergentMerge(generationCommitSha, options) {
|
|
17870
|
-
const treeHash = await this.git.getTreeHash(generationCommitSha);
|
|
17871
|
-
const timestamp = (await this.git.exec(["log", "-1", "--format=%aI", generationCommitSha])).trim();
|
|
17872
|
-
const record = {
|
|
17873
|
-
commit_sha: generationCommitSha,
|
|
17874
|
-
tree_hash: treeHash,
|
|
17875
|
-
timestamp,
|
|
17876
|
-
cli_version: options?.cliVersion ?? "unknown",
|
|
17877
|
-
generator_versions: options?.generatorVersions ?? {},
|
|
17878
|
-
base_branch_head: options?.baseBranchHead
|
|
17879
|
-
};
|
|
17880
|
-
let resolvedPatches;
|
|
17881
|
-
if (!this.lockManager.exists()) {
|
|
17882
|
-
this.lockManager.initializeInMemory(record);
|
|
17883
|
-
} else {
|
|
17884
|
-
this.lockManager.read();
|
|
17885
|
-
const unresolvedPatches = [
|
|
17886
|
-
...this.lockManager.getUnresolvedPatches(),
|
|
17887
|
-
...this.lockManager.getResolvingPatches()
|
|
17888
|
-
];
|
|
17889
|
-
resolvedPatches = this.lockManager.getPatches().filter((p) => p.status == null);
|
|
17890
|
-
this.lockManager.addGeneration(record);
|
|
17891
|
-
this.lockManager.clearPatches();
|
|
17892
|
-
for (const patch of unresolvedPatches) {
|
|
17893
|
-
this.lockManager.addPatch(patch);
|
|
17894
|
-
}
|
|
17895
|
-
}
|
|
17896
|
-
try {
|
|
17897
|
-
const { patches: redetectedPatches } = await this.detector.detectNewPatches();
|
|
17898
|
-
if (redetectedPatches.length > 0) {
|
|
17899
|
-
const redetectedFiles = new Set(redetectedPatches.flatMap((p) => p.files));
|
|
17900
|
-
const currentPatches = this.lockManager.getPatches();
|
|
17901
|
-
for (const patch of currentPatches) {
|
|
17902
|
-
if (patch.status != null && patch.files.some((f) => redetectedFiles.has(f))) {
|
|
17903
|
-
this.lockManager.removePatch(patch.id);
|
|
17904
|
-
}
|
|
17905
|
-
}
|
|
17906
|
-
for (const patch of redetectedPatches) {
|
|
17907
|
-
this.lockManager.addPatch(patch);
|
|
17908
|
-
}
|
|
17909
|
-
}
|
|
17910
|
-
} catch (error) {
|
|
17911
|
-
for (const patch of resolvedPatches ?? []) {
|
|
17912
|
-
this.lockManager.addPatch(patch);
|
|
17913
|
-
}
|
|
17914
|
-
this.detector.warnings.push(
|
|
17915
|
-
`Patch re-detection failed after divergent merge sync. ${(resolvedPatches ?? []).length} previously resolved patch(es) preserved. Error: ${error instanceof Error ? error.message : String(error)}`
|
|
17916
|
-
);
|
|
17917
|
-
}
|
|
17918
|
-
this.lockManager.save();
|
|
17919
|
-
this.detector.warnings.push(...this.lockManager.warnings);
|
|
17920
|
-
}
|
|
17921
17824
|
determineFlow() {
|
|
17922
17825
|
try {
|
|
17923
17826
|
const lock = this.lockManager.read();
|