@databricks-solutions/lakebase-scm-utils 0.2.44 → 0.2.45

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 (39) hide show
  1. package/dist/scripts/index.cjs +795 -758
  2. package/dist/scripts/index.d.cts +1 -1
  3. package/dist/scripts/index.d.ts +1 -1
  4. package/dist/scripts/index.js +794 -758
  5. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
  6. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
  7. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
  8. package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
  9. package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
  10. package/dist/scripts/lakebase/detect-language.cli.js +2 -2
  11. package/dist/scripts/lakebase/doctor.cli.cjs +3 -3
  12. package/dist/scripts/lakebase/doctor.cli.js +3 -3
  13. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  14. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  15. package/dist/scripts/lakebase/index.cjs +463 -395
  16. package/dist/scripts/lakebase/index.d.cts +33 -1
  17. package/dist/scripts/lakebase/index.d.ts +33 -1
  18. package/dist/scripts/lakebase/index.js +462 -395
  19. package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
  20. package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
  21. package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
  22. package/dist/scripts/lakebase/new-migration.cli.js +2 -2
  23. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  24. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  25. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
  26. package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
  27. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
  28. package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
  29. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
  30. package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
  31. package/dist/scripts/lakebase/scm-merge.cli.cjs +2284 -2033
  32. package/dist/scripts/lakebase/scm-merge.cli.js +339 -88
  33. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
  34. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
  35. package/dist/scripts/util/index.cjs +2 -2
  36. package/dist/scripts/util/index.js +2 -2
  37. package/package.json +1 -1
  38. package/scripts/lakebase/doctor.ts +1 -1
  39. package/scripts/lakebase/scm-merge.ts +80 -1
@@ -79287,8 +79287,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79287
79287
  var cached;
79288
79288
  function substrateSelfVersion() {
79289
79289
  if (cached !== void 0) return cached;
79290
- if ("0.2.44".length > 0) {
79291
- cached = "0.2.44";
79290
+ if ("0.2.45".length > 0) {
79291
+ cached = "0.2.45";
79292
79292
  return cached;
79293
79293
  }
79294
79294
  cached = "unknown";
@@ -388,8 +388,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
388
388
  var cached;
389
389
  function substrateSelfVersion() {
390
390
  if (cached !== void 0) return cached;
391
- if ("0.2.44".length > 0) {
392
- cached = "0.2.44";
391
+ if ("0.2.45".length > 0) {
392
+ cached = "0.2.45";
393
393
  return cached;
394
394
  }
395
395
  cached = "unknown";
@@ -1659,8 +1659,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
1659
1659
  var cached;
1660
1660
  function substrateSelfVersion() {
1661
1661
  if (cached !== void 0) return cached;
1662
- if ("0.2.44".length > 0) {
1663
- cached = "0.2.44";
1662
+ if ("0.2.45".length > 0) {
1663
+ cached = "0.2.45";
1664
1664
  return cached;
1665
1665
  }
1666
1666
  cached = "unknown";
@@ -1630,8 +1630,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
1630
1630
  var cached;
1631
1631
  function substrateSelfVersion() {
1632
1632
  if (cached !== void 0) return cached;
1633
- if ("0.2.44".length > 0) {
1634
- cached = "0.2.44";
1633
+ if ("0.2.45".length > 0) {
1634
+ cached = "0.2.45";
1635
1635
  return cached;
1636
1636
  }
1637
1637
  cached = "unknown";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databricks-solutions/lakebase-scm-utils",
3
- "version": "0.2.44",
3
+ "version": "0.2.45",
4
4
  "description": "Lakebase SCM + substrate engine: database branching, paired-branch SCM workflows, connection/credential minting, schema migration, project scaffold + deploy primitives, and the git/github utility layer. Extracted from consort for portable, standalone consumption by lakebase-scm-extension and the SFTDD orchestration.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -628,7 +628,7 @@ function checkWorkflowDrift(projectDir: string): CheckResult {
628
628
  status: "warn",
629
629
  message: `Scaffolded workflows drift from kit: ${drifted} drifted, ${missing} missing`,
630
630
  detail: { files: report.files.map((f) => ({ name: f.name, status: f.status })) },
631
- hint: "Inspect via the lakebase_workflow_drift MCP tool (or detectWorkflowDrift import). Refresh manually until updateWorkflows lands.",
631
+ hint: "Refresh with updateWorkflows (or the lakebase_workflow_drift MCP tool). A stale merge.yml calling 'lakebase-schema-migrate apply' (not 'apply-tier') makes the promote's migrate-target fail on the tier guard; the scm-merge promote also self-heals drifted workflows onto the PR head before merging.",
632
632
  };
633
633
  } catch (err) {
634
634
  return {
@@ -20,7 +20,9 @@ import { getOwnerRepo } from "../git/remote.js";
20
20
  import { pollUntil } from "../util/poll-until.js";
21
21
  import { exec } from "../util/exec.js";
22
22
  import { getCurrentBranch } from "../git/inspect.js";
23
+ import { pushCurrentBranchForPr } from "../git/sync.js";
23
24
  import { resolveGitBase } from "./scm-git-base.js";
25
+ import { detectWorkflowDrift, updateWorkflows } from "./workflow-drift.js";
24
26
 
25
27
  /**
26
28
  * Fast-forward the local tier ref to origin/<tier> after a remote merge, WITHOUT
@@ -149,6 +151,21 @@ export interface MergeArgs {
149
151
  * already on the parent branch after the merge).
150
152
  */
151
153
  localMigrateFallback?: () => Promise<{ ok: boolean; detail?: string }>;
154
+ /**
155
+ * Pre-merge workflow self-heal. Bumping SCM_UTILS_REF upgrades the CLI (e.g.
156
+ * the tier-migration guard) but leaves a scaffolded project's committed
157
+ * `.github/workflows/*.yml` at their older shape — so the promote's
158
+ * `migrate-target` can run a stale `lakebase-schema-migrate apply` against the
159
+ * parent TIER, which the guard refuses (CI red, cleanup skipped). This step
160
+ * refreshes drifted workflow files to the pinned template (so the promote uses
161
+ * `apply-tier`) and commits+pushes them onto the PR head BEFORE the merge, so
162
+ * the merge commit carries the corrected workflow and the triggered `<tier>
163
+ * Merge` run passes. BEST-EFFORT: it never blocks the merge — a detect/refresh/
164
+ * push failure is recorded as a warning and the merge proceeds (the local
165
+ * migrate fallback still keeps git and schema in sync). Defaults to the built-in
166
+ * implementation ({@link defaultRefreshPromoteWorkflows}); injected in tests.
167
+ */
168
+ refreshWorkflows?: () => Promise<{ refreshed: boolean; detail?: string }>;
152
169
  }
153
170
 
154
171
  export interface MergeResult {
@@ -221,6 +238,43 @@ function shaMigratePredicate(
221
238
  };
222
239
  }
223
240
 
241
+ /**
242
+ * Built-in {@link MergeArgs.refreshWorkflows}. Refreshes drifted scaffolded
243
+ * `.github/workflows/*.yml` to the pinned template and commits + pushes them onto
244
+ * the PR head, so the merge commit carries the corrected promote workflow (the
245
+ * `migrate-target` step's `lakebase-schema-migrate apply-tier`) and the triggered
246
+ * `<tier> Merge` run passes instead of failing on the tier guard. No-op when
247
+ * nothing drifted. Scoped to `.github/workflows` so it never sweeps unrelated
248
+ * working-tree changes. Callers invoke it BEST-EFFORT — a failure here must never
249
+ * block the merge (the local migrate fallback still keeps git + schema in sync).
250
+ */
251
+ export async function defaultRefreshPromoteWorkflows(
252
+ projectDir: string,
253
+ ): Promise<{ refreshed: boolean; detail?: string }> {
254
+ const drift = detectWorkflowDrift({ projectDir });
255
+ // Only self-heal a stale EXISTING workflow (drifted). A project with no
256
+ // scaffolded workflows at all (all "missing") is not the stale-apply case, so
257
+ // the promote never scaffolds files it doesn't own.
258
+ const touched = drift.files.filter((f) => f.status === "drifted").map((f) => f.name);
259
+ if (touched.length === 0) return { refreshed: false };
260
+ const result = updateWorkflows({ projectDir });
261
+ if (!result.changed) return { refreshed: false };
262
+ await exec(`git add ${shellEscape(".github/workflows")}`, { cwd: projectDir, timeout: 10_000 });
263
+ const staged = await exec("git diff --cached --name-only -- .github/workflows", {
264
+ cwd: projectDir,
265
+ timeout: 10_000,
266
+ });
267
+ if (!staged.trim()) return { refreshed: false };
268
+ await exec(
269
+ `git commit -m ${shellEscape(
270
+ "chore(ci): refresh scaffolded workflows to pinned substrate (promote uses apply-tier)",
271
+ )} -- .github/workflows`,
272
+ { cwd: projectDir, timeout: 15_000 },
273
+ );
274
+ await pushCurrentBranchForPr({ cwd: projectDir });
275
+ return { refreshed: true, detail: `refreshed + pushed ${touched.join(", ") || "workflow(s)"} onto the PR head` };
276
+ }
277
+
224
278
  export async function mergeFeature(args: MergeArgs): Promise<MergeResult> {
225
279
  const current = readWorkflowState(args.projectDir);
226
280
  if (!current) {
@@ -297,6 +351,31 @@ export async function mergeFeature(args: MergeArgs): Promise<MergeResult> {
297
351
  authVerified = true;
298
352
  }
299
353
 
354
+ // ─── Pre-merge workflow self-heal ───
355
+ // A scaffolded project's committed workflows can lag the pinned substrate ref
356
+ // (SCM_UTILS_REF bumped, workflow files not regenerated), so the promote's
357
+ // migrate-target runs a stale `lakebase-schema-migrate apply` against the parent
358
+ // TIER, which the guard refuses (CI red, cleanup skipped). Refresh drifted
359
+ // workflows onto the PR head BEFORE the merge so the merge commit carries the
360
+ // corrected `apply-tier` step. BEST-EFFORT: never blocks the merge.
361
+ // Runs regardless of waitMigrate: the promote workflow triggers on the tier
362
+ // push either way, so a stale merge.yml fails CI whether or not the caller waits.
363
+ const preflightNotes: string[] = [];
364
+ {
365
+ const refresh = args.refreshWorkflows ?? (() => defaultRefreshPromoteWorkflows(args.projectDir));
366
+ try {
367
+ const r = await refresh();
368
+ if (r.refreshed) {
369
+ preflightNotes.push(`Refreshed stale CI workflows before merge${r.detail ? ` (${r.detail})` : ""}.`);
370
+ }
371
+ } catch (err) {
372
+ preflightNotes.push(
373
+ `Workflow self-heal skipped (${err instanceof Error ? err.message : String(err)}); if the promote's ` +
374
+ `migrate-target fails on the tier guard, refresh .github/workflows (merge.yml must call 'lakebase-schema-migrate apply-tier').`,
375
+ );
376
+ }
377
+ }
378
+
300
379
  let paired: Awaited<ReturnType<typeof mergePairedPullRequest>>;
301
380
  try {
302
381
  paired = await mergePairedPullRequest({
@@ -312,7 +391,7 @@ export async function mergeFeature(args: MergeArgs): Promise<MergeResult> {
312
391
  );
313
392
  }
314
393
 
315
- const warnings: string[] = [...paired.warnings];
394
+ const warnings: string[] = [...preflightNotes, ...paired.warnings];
316
395
  let localBranchDeleted = false;
317
396
  let headAfter = current.branch;
318
397
  if (!args.skipLocalCleanup) {