@databricks-solutions/lakebase-scm-utils 0.2.46 → 0.2.48

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.
Files changed (63) hide show
  1. package/dist/{pr-D3CteJaf.d.cts → pr-e-E4aP4j.d.cts} +6 -0
  2. package/dist/{pr-D3CteJaf.d.ts → pr-e-E4aP4j.d.ts} +6 -0
  3. package/dist/scripts/github/index.cjs +2 -0
  4. package/dist/scripts/github/index.d.cts +1 -1
  5. package/dist/scripts/github/index.d.ts +1 -1
  6. package/dist/scripts/github/index.js +2 -0
  7. package/dist/scripts/github/pr.cli.cjs +2 -0
  8. package/dist/scripts/github/pr.cli.js +2 -0
  9. package/dist/scripts/index.cjs +81 -4
  10. package/dist/scripts/index.d.cts +3 -3
  11. package/dist/scripts/index.d.ts +3 -3
  12. package/dist/scripts/index.js +78 -4
  13. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
  14. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
  15. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
  16. package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
  17. package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
  18. package/dist/scripts/lakebase/detect-language.cli.js +2 -2
  19. package/dist/scripts/lakebase/doctor.cli.cjs +2 -2
  20. package/dist/scripts/lakebase/doctor.cli.js +2 -2
  21. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  22. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  23. package/dist/scripts/lakebase/index.cjs +87 -4
  24. package/dist/scripts/lakebase/index.d.cts +32 -14
  25. package/dist/scripts/lakebase/index.d.ts +32 -14
  26. package/dist/scripts/lakebase/index.js +86 -4
  27. package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
  28. package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
  29. package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
  30. package/dist/scripts/lakebase/new-migration.cli.js +2 -2
  31. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  32. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  33. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
  34. package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
  35. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
  36. package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
  37. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
  38. package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
  39. package/dist/scripts/lakebase/scm-merge.cli.cjs +131 -2
  40. package/dist/scripts/lakebase/scm-merge.cli.js +131 -2
  41. package/dist/scripts/lakebase/scm-prepare-pr.cli.cjs +2 -0
  42. package/dist/scripts/lakebase/scm-prepare-pr.cli.js +2 -0
  43. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
  44. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
  45. package/dist/scripts/lakebase/scm-wait-ci.cli.cjs +8 -0
  46. package/dist/scripts/lakebase/scm-wait-ci.cli.js +8 -0
  47. package/dist/scripts/lakebase/sync-ci-secrets.cli.cjs +8610 -0
  48. package/dist/scripts/lakebase/sync-ci-secrets.cli.d.cts +4 -0
  49. package/dist/scripts/lakebase/sync-ci-secrets.cli.d.ts +4 -0
  50. package/dist/scripts/lakebase/sync-ci-secrets.cli.js +8597 -0
  51. package/dist/scripts/util/index.cjs +21 -2
  52. package/dist/scripts/util/index.d.cts +9 -1
  53. package/dist/scripts/util/index.d.ts +9 -1
  54. package/dist/scripts/util/index.js +19 -2
  55. package/package.json +3 -2
  56. package/scripts/github/pr.ts +8 -0
  57. package/scripts/lakebase/create-project.ts +14 -3
  58. package/scripts/lakebase/scm-merge.ts +91 -0
  59. package/scripts/lakebase/scm-wait-ci.ts +13 -0
  60. package/scripts/lakebase/sync-ci-secrets.cli.ts +122 -0
  61. package/scripts/util/ci-secrets.ts +14 -1
  62. package/templates/project/common/.gitignore.base +4 -0
  63. package/templates/project/common/scripts/lk +22 -4
@@ -393,8 +393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
393
393
  var cached;
394
394
  function substrateSelfVersion() {
395
395
  if (cached !== void 0) return cached;
396
- if ("0.2.46".length > 0) {
397
- cached = "0.2.46";
396
+ if ("0.2.48".length > 0) {
397
+ cached = "0.2.48";
398
398
  return cached;
399
399
  }
400
400
  cached = "unknown";
@@ -79288,8 +79288,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79288
79288
  var cached;
79289
79289
  function substrateSelfVersion() {
79290
79290
  if (cached !== void 0) return cached;
79291
- if ("0.2.46".length > 0) {
79292
- cached = "0.2.46";
79291
+ if ("0.2.48".length > 0) {
79292
+ cached = "0.2.48";
79293
79293
  return cached;
79294
79294
  }
79295
79295
  cached = "unknown";
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
389
389
  var cached;
390
390
  function substrateSelfVersion() {
391
391
  if (cached !== void 0) return cached;
392
- if ("0.2.46".length > 0) {
393
- cached = "0.2.46";
392
+ if ("0.2.48".length > 0) {
393
+ cached = "0.2.48";
394
394
  return cached;
395
395
  }
396
396
  cached = "unknown";
@@ -79324,8 +79324,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79324
79324
  var cached;
79325
79325
  function substrateSelfVersion() {
79326
79326
  if (cached !== void 0) return cached;
79327
- if ("0.2.46".length > 0) {
79328
- cached = "0.2.46";
79327
+ if ("0.2.48".length > 0) {
79328
+ cached = "0.2.48";
79329
79329
  return cached;
79330
79330
  }
79331
79331
  cached = "unknown";
@@ -427,8 +427,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
427
427
  var cached;
428
428
  function substrateSelfVersion() {
429
429
  if (cached !== void 0) return cached;
430
- if ("0.2.46".length > 0) {
431
- cached = "0.2.46";
430
+ if ("0.2.48".length > 0) {
431
+ cached = "0.2.48";
432
432
  return cached;
433
433
  }
434
434
  cached = "unknown";
@@ -79783,8 +79783,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79783
79783
  var cached;
79784
79784
  function substrateSelfVersion() {
79785
79785
  if (cached !== void 0) return cached;
79786
- if ("0.2.46".length > 0) {
79787
- cached = "0.2.46";
79786
+ if ("0.2.48".length > 0) {
79787
+ cached = "0.2.48";
79788
79788
  return cached;
79789
79789
  }
79790
79790
  cached = "unknown";
@@ -864,8 +864,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
864
864
  var cached;
865
865
  function substrateSelfVersion() {
866
866
  if (cached !== void 0) return cached;
867
- if ("0.2.46".length > 0) {
868
- cached = "0.2.46";
867
+ if ("0.2.48".length > 0) {
868
+ cached = "0.2.48";
869
869
  return cached;
870
870
  }
871
871
  cached = "unknown";
@@ -88240,8 +88240,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
88240
88240
  var cached;
88241
88241
  function substrateSelfVersion() {
88242
88242
  if (cached !== void 0) return cached;
88243
- if ("0.2.46".length > 0) {
88244
- cached = "0.2.46";
88243
+ if ("0.2.48".length > 0) {
88244
+ cached = "0.2.48";
88245
88245
  return cached;
88246
88246
  }
88247
88247
  cached = "unknown";
@@ -9393,8 +9393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
9393
9393
  var cached;
9394
9394
  function substrateSelfVersion() {
9395
9395
  if (cached !== void 0) return cached;
9396
- if ("0.2.46".length > 0) {
9397
- cached = "0.2.46";
9396
+ if ("0.2.48".length > 0) {
9397
+ cached = "0.2.48";
9398
9398
  return cached;
9399
9399
  }
9400
9400
  cached = "unknown";
@@ -86087,6 +86087,84 @@ function wrap(err, context) {
86087
86087
  }
86088
86088
  throw new GitHubPullRequestError(context);
86089
86089
  }
86090
+ async function getPullRequest(ownerRepo, headBranch) {
86091
+ try {
86092
+ const { owner, repo } = parseOwnerRepo(ownerRepo);
86093
+ const ok = await octokit();
86094
+ const { data: pulls } = await ok.rest.pulls.list({
86095
+ owner,
86096
+ repo,
86097
+ state: "open",
86098
+ head: `${owner}:${headBranch}`,
86099
+ per_page: 1
86100
+ });
86101
+ if (pulls.length === 0) return void 0;
86102
+ const { data: pr } = await ok.rest.pulls.get({
86103
+ owner,
86104
+ repo,
86105
+ pull_number: pulls[0].number
86106
+ });
86107
+ if (pr.state !== "open") return void 0;
86108
+ let checks = [];
86109
+ let ciStatus = "pending";
86110
+ const headSha = pr.head?.sha;
86111
+ if (headSha) {
86112
+ try {
86113
+ const { data: checksData } = await ok.rest.checks.listForRef({
86114
+ owner,
86115
+ repo,
86116
+ ref: headSha
86117
+ });
86118
+ const runs = checksData.check_runs || [];
86119
+ checks = runs.map((c) => ({
86120
+ name: c.name || "unknown",
86121
+ status: (c.status || "").toUpperCase(),
86122
+ conclusion: (c.conclusion || "").toUpperCase(),
86123
+ detailsUrl: c.details_url || void 0
86124
+ }));
86125
+ ciStatus = parseCiStatus(runs);
86126
+ } catch {
86127
+ ciStatus = "pending";
86128
+ }
86129
+ }
86130
+ return {
86131
+ number: pr.number,
86132
+ title: pr.title,
86133
+ url: pr.html_url || "",
86134
+ state: (pr.state || "open").toUpperCase(),
86135
+ isDraft: pr.draft || false,
86136
+ ciStatus,
86137
+ checks,
86138
+ mergeable: pr.mergeable,
86139
+ mergeableState: pr.mergeable_state,
86140
+ headBranch: pr.head?.ref || headBranch,
86141
+ baseBranch: pr.base?.ref || "",
86142
+ body: pr.body || void 0,
86143
+ additions: pr.additions,
86144
+ deletions: pr.deletions,
86145
+ changedFiles: pr.changed_files
86146
+ };
86147
+ } catch {
86148
+ return void 0;
86149
+ }
86150
+ }
86151
+ function parseCiStatus(rawChecks) {
86152
+ if (rawChecks.length === 0) return "pending";
86153
+ const latestByName = /* @__PURE__ */ new Map();
86154
+ for (const c of rawChecks) {
86155
+ latestByName.set(c.name || "unknown", c);
86156
+ }
86157
+ const states = Array.from(latestByName.values()).map(
86158
+ (c) => (c.conclusion || c.status || "").toUpperCase()
86159
+ );
86160
+ if (states.some((s2) => s2 === "FAILURE" || s2 === "ERROR" || s2 === "ACTION_REQUIRED")) {
86161
+ return "failure";
86162
+ }
86163
+ if (states.every((s2) => s2 === "SUCCESS" || s2 === "NEUTRAL" || s2 === "SKIPPED")) {
86164
+ return "success";
86165
+ }
86166
+ return "pending";
86167
+ }
86090
86168
  async function mergePullRequest(args) {
86091
86169
  const method = args.method ?? "merge";
86092
86170
  const deleteRemoteBranch = args.deleteRemoteBranch !== false;
@@ -86781,6 +86859,48 @@ function shaMigratePredicate(mergeCommitSha) {
86781
86859
  return !!run.headSha && run.headSha === mergeCommitSha;
86782
86860
  };
86783
86861
  }
86862
+ var PROMOTE_RUNSTATE_CONFLICT = /(^|\/)\.consort\/(features\/[^/]+\/)?pipeline\.json$/;
86863
+ async function reconcilePromoteRunState(projectDir, baseBranch) {
86864
+ const o = { cwd: projectDir, timeout: 3e4 };
86865
+ const dirty = (await exec2("git status --porcelain", o).catch(() => "")).split("\n").map((l) => l.slice(3).trim()).filter(Boolean);
86866
+ const nonRunStateDirty = dirty.filter((p) => !p.startsWith(".consort/"));
86867
+ if (nonRunStateDirty.length > 0) {
86868
+ throw new ScmMergeError(
86869
+ `working tree has uncommitted non-run-state changes (${nonRunStateDirty.slice(0, 5).join(", ")}${nonRunStateDirty.length > 5 ? ", \u2026" : ""}); commit or stash before promoting.`,
86870
+ "promote-conflict"
86871
+ );
86872
+ }
86873
+ if (dirty.length > 0) {
86874
+ await exec2(`git add ${shellEscape(".consort")}`, o);
86875
+ await exec2(`git commit -m ${shellEscape("chore(run): checkpoint run state before promotion")}`, o);
86876
+ }
86877
+ await exec2(`git fetch origin ${shellEscape(baseBranch)}`, o).catch(() => {
86878
+ });
86879
+ let conflicted = false;
86880
+ try {
86881
+ await exec2(`git merge --no-ff -m ${shellEscape(`merge ${baseBranch} for promotion`)} ${shellEscape(`origin/${baseBranch}`)}`, o);
86882
+ } catch {
86883
+ conflicted = true;
86884
+ }
86885
+ if (!conflicted) return { reconciled: false };
86886
+ const u = (await exec2("git diff --name-only --diff-filter=U", o).catch(() => "")).split("\n").map((s2) => s2.trim()).filter(Boolean);
86887
+ const other = u.filter((p) => !PROMOTE_RUNSTATE_CONFLICT.test(p));
86888
+ if (u.length === 0 || other.length > 0) {
86889
+ await exec2("git merge --abort", o).catch(() => {
86890
+ });
86891
+ throw new ScmMergeError(
86892
+ other.length > 0 ? `promotion conflicts on non-ledger file(s): ${other.join(", ")}. Resolve the real divergence manually, then re-run the promote.` : `promotion merge failed with no resolvable conflicts; resolve manually and re-run.`,
86893
+ "promote-conflict"
86894
+ );
86895
+ }
86896
+ for (const p of u) {
86897
+ await exec2(`git checkout --ours -- ${shellEscape(p)}`, o);
86898
+ await exec2(`git add -- ${shellEscape(p)}`, o);
86899
+ }
86900
+ await exec2(`git commit -m ${shellEscape(`merge ${baseBranch} for promotion (pipeline.json ledger kept at run state)`)}`, o);
86901
+ await pushCurrentBranchForPr({ cwd: projectDir });
86902
+ return { reconciled: true, detail: `reconciled ${u.length} ledger path(s) to the run side + pushed` };
86903
+ }
86784
86904
  async function defaultRefreshPromoteWorkflows(projectDir) {
86785
86905
  const drift = detectWorkflowDrift({ projectDir });
86786
86906
  const touched = drift.files.filter((f3) => f3.status === "drifted").map((f3) => f3.name);
@@ -86870,6 +86990,15 @@ async function mergeFeature(args) {
86870
86990
  );
86871
86991
  }
86872
86992
  }
86993
+ {
86994
+ const fetchState = args.fetchPrState ?? ((o, h2) => getPullRequest(o, h2));
86995
+ const prState = await fetchState(ownerRepo, current.branch).catch(() => void 0);
86996
+ if (prState?.mergeableState === "dirty") {
86997
+ const reconcile = args.reconcileRunState ?? ((base) => reconcilePromoteRunState(args.projectDir, base));
86998
+ const r2 = await reconcile(gitBase);
86999
+ if (r2.reconciled) preflightNotes.push(`Reconciled a pipeline.json-ledger promotion conflict to the run side${r2.detail ? ` (${r2.detail})` : ""}.`);
87000
+ }
87001
+ }
86873
87002
  let paired;
86874
87003
  try {
86875
87004
  paired = await mergePairedPullRequest({
@@ -88474,8 +88603,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
88474
88603
  var cached;
88475
88604
  function substrateSelfVersion() {
88476
88605
  if (cached !== void 0) return cached;
88477
- if ("0.2.46".length > 0) {
88478
- cached = "0.2.46";
88606
+ if ("0.2.48".length > 0) {
88607
+ cached = "0.2.48";
88479
88608
  return cached;
88480
88609
  }
88481
88610
  cached = "unknown";
@@ -8609,6 +8609,84 @@ function wrap(err, context) {
8609
8609
  }
8610
8610
  throw new GitHubPullRequestError(context);
8611
8611
  }
8612
+ async function getPullRequest(ownerRepo, headBranch) {
8613
+ try {
8614
+ const { owner, repo } = parseOwnerRepo(ownerRepo);
8615
+ const ok = await octokit();
8616
+ const { data: pulls } = await ok.rest.pulls.list({
8617
+ owner,
8618
+ repo,
8619
+ state: "open",
8620
+ head: `${owner}:${headBranch}`,
8621
+ per_page: 1
8622
+ });
8623
+ if (pulls.length === 0) return void 0;
8624
+ const { data: pr } = await ok.rest.pulls.get({
8625
+ owner,
8626
+ repo,
8627
+ pull_number: pulls[0].number
8628
+ });
8629
+ if (pr.state !== "open") return void 0;
8630
+ let checks = [];
8631
+ let ciStatus = "pending";
8632
+ const headSha = pr.head?.sha;
8633
+ if (headSha) {
8634
+ try {
8635
+ const { data: checksData } = await ok.rest.checks.listForRef({
8636
+ owner,
8637
+ repo,
8638
+ ref: headSha
8639
+ });
8640
+ const runs = checksData.check_runs || [];
8641
+ checks = runs.map((c) => ({
8642
+ name: c.name || "unknown",
8643
+ status: (c.status || "").toUpperCase(),
8644
+ conclusion: (c.conclusion || "").toUpperCase(),
8645
+ detailsUrl: c.details_url || void 0
8646
+ }));
8647
+ ciStatus = parseCiStatus(runs);
8648
+ } catch {
8649
+ ciStatus = "pending";
8650
+ }
8651
+ }
8652
+ return {
8653
+ number: pr.number,
8654
+ title: pr.title,
8655
+ url: pr.html_url || "",
8656
+ state: (pr.state || "open").toUpperCase(),
8657
+ isDraft: pr.draft || false,
8658
+ ciStatus,
8659
+ checks,
8660
+ mergeable: pr.mergeable,
8661
+ mergeableState: pr.mergeable_state,
8662
+ headBranch: pr.head?.ref || headBranch,
8663
+ baseBranch: pr.base?.ref || "",
8664
+ body: pr.body || void 0,
8665
+ additions: pr.additions,
8666
+ deletions: pr.deletions,
8667
+ changedFiles: pr.changed_files
8668
+ };
8669
+ } catch {
8670
+ return void 0;
8671
+ }
8672
+ }
8673
+ function parseCiStatus(rawChecks) {
8674
+ if (rawChecks.length === 0) return "pending";
8675
+ const latestByName = /* @__PURE__ */ new Map();
8676
+ for (const c of rawChecks) {
8677
+ latestByName.set(c.name || "unknown", c);
8678
+ }
8679
+ const states = Array.from(latestByName.values()).map(
8680
+ (c) => (c.conclusion || c.status || "").toUpperCase()
8681
+ );
8682
+ if (states.some((s) => s === "FAILURE" || s === "ERROR" || s === "ACTION_REQUIRED")) {
8683
+ return "failure";
8684
+ }
8685
+ if (states.every((s) => s === "SUCCESS" || s === "NEUTRAL" || s === "SKIPPED")) {
8686
+ return "success";
8687
+ }
8688
+ return "pending";
8689
+ }
8612
8690
  async function mergePullRequest(args) {
8613
8691
  const method = args.method ?? "merge";
8614
8692
  const deleteRemoteBranch = args.deleteRemoteBranch !== false;
@@ -9303,6 +9381,48 @@ function shaMigratePredicate(mergeCommitSha) {
9303
9381
  return !!run.headSha && run.headSha === mergeCommitSha;
9304
9382
  };
9305
9383
  }
9384
+ var PROMOTE_RUNSTATE_CONFLICT = /(^|\/)\.consort\/(features\/[^/]+\/)?pipeline\.json$/;
9385
+ async function reconcilePromoteRunState(projectDir, baseBranch) {
9386
+ const o = { cwd: projectDir, timeout: 3e4 };
9387
+ const dirty = (await exec2("git status --porcelain", o).catch(() => "")).split("\n").map((l) => l.slice(3).trim()).filter(Boolean);
9388
+ const nonRunStateDirty = dirty.filter((p) => !p.startsWith(".consort/"));
9389
+ if (nonRunStateDirty.length > 0) {
9390
+ throw new ScmMergeError(
9391
+ `working tree has uncommitted non-run-state changes (${nonRunStateDirty.slice(0, 5).join(", ")}${nonRunStateDirty.length > 5 ? ", \u2026" : ""}); commit or stash before promoting.`,
9392
+ "promote-conflict"
9393
+ );
9394
+ }
9395
+ if (dirty.length > 0) {
9396
+ await exec2(`git add ${shellEscape(".consort")}`, o);
9397
+ await exec2(`git commit -m ${shellEscape("chore(run): checkpoint run state before promotion")}`, o);
9398
+ }
9399
+ await exec2(`git fetch origin ${shellEscape(baseBranch)}`, o).catch(() => {
9400
+ });
9401
+ let conflicted = false;
9402
+ try {
9403
+ await exec2(`git merge --no-ff -m ${shellEscape(`merge ${baseBranch} for promotion`)} ${shellEscape(`origin/${baseBranch}`)}`, o);
9404
+ } catch {
9405
+ conflicted = true;
9406
+ }
9407
+ if (!conflicted) return { reconciled: false };
9408
+ const u = (await exec2("git diff --name-only --diff-filter=U", o).catch(() => "")).split("\n").map((s) => s.trim()).filter(Boolean);
9409
+ const other = u.filter((p) => !PROMOTE_RUNSTATE_CONFLICT.test(p));
9410
+ if (u.length === 0 || other.length > 0) {
9411
+ await exec2("git merge --abort", o).catch(() => {
9412
+ });
9413
+ throw new ScmMergeError(
9414
+ other.length > 0 ? `promotion conflicts on non-ledger file(s): ${other.join(", ")}. Resolve the real divergence manually, then re-run the promote.` : `promotion merge failed with no resolvable conflicts; resolve manually and re-run.`,
9415
+ "promote-conflict"
9416
+ );
9417
+ }
9418
+ for (const p of u) {
9419
+ await exec2(`git checkout --ours -- ${shellEscape(p)}`, o);
9420
+ await exec2(`git add -- ${shellEscape(p)}`, o);
9421
+ }
9422
+ await exec2(`git commit -m ${shellEscape(`merge ${baseBranch} for promotion (pipeline.json ledger kept at run state)`)}`, o);
9423
+ await pushCurrentBranchForPr({ cwd: projectDir });
9424
+ return { reconciled: true, detail: `reconciled ${u.length} ledger path(s) to the run side + pushed` };
9425
+ }
9306
9426
  async function defaultRefreshPromoteWorkflows(projectDir) {
9307
9427
  const drift = detectWorkflowDrift({ projectDir });
9308
9428
  const touched = drift.files.filter((f) => f.status === "drifted").map((f) => f.name);
@@ -9392,6 +9512,15 @@ async function mergeFeature(args) {
9392
9512
  );
9393
9513
  }
9394
9514
  }
9515
+ {
9516
+ const fetchState = args.fetchPrState ?? ((o, h) => getPullRequest(o, h));
9517
+ const prState = await fetchState(ownerRepo, current.branch).catch(() => void 0);
9518
+ if (prState?.mergeableState === "dirty") {
9519
+ const reconcile = args.reconcileRunState ?? ((base) => reconcilePromoteRunState(args.projectDir, base));
9520
+ const r = await reconcile(gitBase);
9521
+ if (r.reconciled) preflightNotes.push(`Reconciled a pipeline.json-ledger promotion conflict to the run side${r.detail ? ` (${r.detail})` : ""}.`);
9522
+ }
9523
+ }
9395
9524
  let paired;
9396
9525
  try {
9397
9526
  paired = await mergePairedPullRequest({
@@ -9627,8 +9756,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
9627
9756
  var cached;
9628
9757
  function substrateSelfVersion() {
9629
9758
  if (cached !== void 0) return cached;
9630
- if ("0.2.46".length > 0) {
9631
- cached = "0.2.46";
9759
+ if ("0.2.48".length > 0) {
9760
+ cached = "0.2.48";
9632
9761
  return cached;
9633
9762
  }
9634
9763
  cached = "unknown";
@@ -8393,6 +8393,8 @@ async function getPullRequest(ownerRepo, headBranch) {
8393
8393
  isDraft: pr.draft || false,
8394
8394
  ciStatus,
8395
8395
  checks,
8396
+ mergeable: pr.mergeable,
8397
+ mergeableState: pr.mergeable_state,
8396
8398
  headBranch: pr.head?.ref || headBranch,
8397
8399
  baseBranch: pr.base?.ref || "",
8398
8400
  body: pr.body || void 0,
@@ -8381,6 +8381,8 @@ async function getPullRequest(ownerRepo, headBranch) {
8381
8381
  isDraft: pr.draft || false,
8382
8382
  ciStatus,
8383
8383
  checks,
8384
+ mergeable: pr.mergeable,
8385
+ mergeableState: pr.mergeable_state,
8384
8386
  headBranch: pr.head?.ref || headBranch,
8385
8387
  baseBranch: pr.base?.ref || "",
8386
8388
  body: pr.body || void 0,
@@ -79288,8 +79288,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79288
79288
  var cached;
79289
79289
  function substrateSelfVersion() {
79290
79290
  if (cached !== void 0) return cached;
79291
- if ("0.2.46".length > 0) {
79292
- cached = "0.2.46";
79291
+ if ("0.2.48".length > 0) {
79292
+ cached = "0.2.48";
79293
79293
  return cached;
79294
79294
  }
79295
79295
  cached = "unknown";
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
389
389
  var cached;
390
390
  function substrateSelfVersion() {
391
391
  if (cached !== void 0) return cached;
392
- if ("0.2.46".length > 0) {
393
- cached = "0.2.46";
392
+ if ("0.2.48".length > 0) {
393
+ cached = "0.2.48";
394
394
  return cached;
395
395
  }
396
396
  cached = "unknown";
@@ -8235,6 +8235,8 @@ async function getPullRequest(ownerRepo, headBranch) {
8235
8235
  isDraft: pr.draft || false,
8236
8236
  ciStatus,
8237
8237
  checks,
8238
+ mergeable: pr.mergeable,
8239
+ mergeableState: pr.mergeable_state,
8238
8240
  headBranch: pr.head?.ref || headBranch,
8239
8241
  baseBranch: pr.base?.ref || "",
8240
8242
  body: pr.body || void 0,
@@ -8662,6 +8664,12 @@ async function waitForCi(args) {
8662
8664
  if (lastPr.ciStatus === "success") {
8663
8665
  return { done: true, value: lastPr };
8664
8666
  }
8667
+ if (lastPr.mergeableState === "dirty" && lastPr.checks.length === 0) {
8668
+ throw new ScmWaitCiError(
8669
+ `PR ${lastPr.url} is CONFLICTING with ${lastPr.baseBranch} \u2014 GitHub will not dispatch CI (0 runs) until the conflict is resolved. Reconcile the branch and re-push; the synchronize event will trigger CI.`,
8670
+ "pr-conflicting"
8671
+ );
8672
+ }
8665
8673
  if (lastPr.ciStatus === "failure") {
8666
8674
  const failed = lastPr.checks.filter((c) => /(FAILURE|TIMED_OUT|CANCELLED|ACTION_REQUIRED)/i.test(c.conclusion)).map((c) => `${c.name} (${c.conclusion})`);
8667
8675
  throw new ScmWaitCiError(
@@ -8223,6 +8223,8 @@ async function getPullRequest(ownerRepo, headBranch) {
8223
8223
  isDraft: pr.draft || false,
8224
8224
  ciStatus,
8225
8225
  checks,
8226
+ mergeable: pr.mergeable,
8227
+ mergeableState: pr.mergeable_state,
8226
8228
  headBranch: pr.head?.ref || headBranch,
8227
8229
  baseBranch: pr.base?.ref || "",
8228
8230
  body: pr.body || void 0,
@@ -8650,6 +8652,12 @@ async function waitForCi(args) {
8650
8652
  if (lastPr.ciStatus === "success") {
8651
8653
  return { done: true, value: lastPr };
8652
8654
  }
8655
+ if (lastPr.mergeableState === "dirty" && lastPr.checks.length === 0) {
8656
+ throw new ScmWaitCiError(
8657
+ `PR ${lastPr.url} is CONFLICTING with ${lastPr.baseBranch} \u2014 GitHub will not dispatch CI (0 runs) until the conflict is resolved. Reconcile the branch and re-push; the synchronize event will trigger CI.`,
8658
+ "pr-conflicting"
8659
+ );
8660
+ }
8653
8661
  if (lastPr.ciStatus === "failure") {
8654
8662
  const failed = lastPr.checks.filter((c) => /(FAILURE|TIMED_OUT|CANCELLED|ACTION_REQUIRED)/i.test(c.conclusion)).map((c) => `${c.name} (${c.conclusion})`);
8655
8663
  throw new ScmWaitCiError(