@databricks-solutions/lakebase-scm-utils 0.2.43 → 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 (63) hide show
  1. package/dist/scripts/github/index.cjs +21 -4
  2. package/dist/scripts/github/index.js +21 -4
  3. package/dist/scripts/github/pr.cli.cjs +21 -4
  4. package/dist/scripts/github/pr.cli.js +21 -4
  5. package/dist/scripts/index.cjs +1224 -1157
  6. package/dist/scripts/index.d.cts +1 -1
  7. package/dist/scripts/index.d.ts +1 -1
  8. package/dist/scripts/index.js +1223 -1157
  9. package/dist/scripts/lakebase/branch.cli.cjs +21 -4
  10. package/dist/scripts/lakebase/branch.cli.js +21 -4
  11. package/dist/scripts/lakebase/ci-app-endpoint.cli.cjs +21 -4
  12. package/dist/scripts/lakebase/ci-app-endpoint.cli.js +21 -4
  13. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +23 -6
  14. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +23 -6
  15. package/dist/scripts/lakebase/collapse-heads.cli.cjs +23 -6
  16. package/dist/scripts/lakebase/collapse-heads.cli.js +23 -6
  17. package/dist/scripts/lakebase/cut-backup.cli.cjs +21 -4
  18. package/dist/scripts/lakebase/cut-backup.cli.js +21 -4
  19. package/dist/scripts/lakebase/cut-tier.cli.cjs +21 -4
  20. package/dist/scripts/lakebase/cut-tier.cli.js +21 -4
  21. package/dist/scripts/lakebase/detect-language.cli.cjs +23 -6
  22. package/dist/scripts/lakebase/detect-language.cli.js +23 -6
  23. package/dist/scripts/lakebase/doctor.cli.cjs +24 -7
  24. package/dist/scripts/lakebase/doctor.cli.js +24 -7
  25. package/dist/scripts/lakebase/get-connection.cli.cjs +23 -6
  26. package/dist/scripts/lakebase/get-connection.cli.js +23 -6
  27. package/dist/scripts/lakebase/index.cjs +489 -391
  28. package/dist/scripts/lakebase/index.d.cts +33 -1
  29. package/dist/scripts/lakebase/index.d.ts +33 -1
  30. package/dist/scripts/lakebase/index.js +488 -391
  31. package/dist/scripts/lakebase/infra-runner.cli.cjs +23 -6
  32. package/dist/scripts/lakebase/infra-runner.cli.js +23 -6
  33. package/dist/scripts/lakebase/new-migration.cli.cjs +23 -6
  34. package/dist/scripts/lakebase/new-migration.cli.js +23 -6
  35. package/dist/scripts/lakebase/schema-diff.cli.cjs +23 -6
  36. package/dist/scripts/lakebase/schema-diff.cli.js +23 -6
  37. package/dist/scripts/lakebase/schema-migrate.cli.cjs +23 -6
  38. package/dist/scripts/lakebase/schema-migrate.cli.js +23 -6
  39. package/dist/scripts/lakebase/scm-abandon-feature.cli.cjs +21 -4
  40. package/dist/scripts/lakebase/scm-abandon-feature.cli.js +21 -4
  41. package/dist/scripts/lakebase/scm-adopt-state.cli.cjs +21 -4
  42. package/dist/scripts/lakebase/scm-adopt-state.cli.js +21 -4
  43. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +23 -6
  44. package/dist/scripts/lakebase/scm-claim-feature.cli.js +23 -6
  45. package/dist/scripts/lakebase/scm-cleanup.cli.cjs +21 -4
  46. package/dist/scripts/lakebase/scm-cleanup.cli.js +21 -4
  47. package/dist/scripts/lakebase/scm-doctor.cli.cjs +23 -6
  48. package/dist/scripts/lakebase/scm-doctor.cli.js +23 -6
  49. package/dist/scripts/lakebase/scm-merge.cli.cjs +2305 -2037
  50. package/dist/scripts/lakebase/scm-merge.cli.js +360 -92
  51. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +23 -6
  52. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +23 -6
  53. package/dist/scripts/lakebase/scm-recover-orphans.cli.cjs +21 -4
  54. package/dist/scripts/lakebase/scm-recover-orphans.cli.js +21 -4
  55. package/dist/scripts/util/index.cjs +23 -6
  56. package/dist/scripts/util/index.js +23 -6
  57. package/package.json +1 -1
  58. package/scripts/lakebase/databricks-cli.ts +44 -4
  59. package/scripts/lakebase/doctor.ts +1 -1
  60. package/scripts/lakebase/runner-setup.ts +18 -2
  61. package/scripts/lakebase/scm-merge.ts +80 -1
  62. package/scripts/lakebase/secret-auth.ts +9 -2
  63. package/templates/project/common/scripts/run-tests.sh +10 -0
@@ -1457,7 +1457,7 @@ var require_light = __commonJS({
1457
1457
 
1458
1458
  // scripts/lakebase/scm-merge.cli.ts
1459
1459
  init_esm_shims();
1460
- import * as path12 from "path";
1460
+ import * as path13 from "path";
1461
1461
 
1462
1462
  // scripts/util/cli-entry.ts
1463
1463
  init_esm_shims();
@@ -2549,7 +2549,7 @@ paginateRest.VERSION = VERSION5;
2549
2549
 
2550
2550
  // node_modules/@octokit/plugin-paginate-graphql/dist-bundle/index.js
2551
2551
  init_esm_shims();
2552
- var generateMessage = (path13, cursorValue) => `The cursor at "${path13.join(
2552
+ var generateMessage = (path14, cursorValue) => `The cursor at "${path14.join(
2553
2553
  ","
2554
2554
  )}" did not change its value "${cursorValue}" after a page transition. Please make sure your that your query is set up correctly.`;
2555
2555
  var MissingCursorChange = class extends Error {
@@ -2590,9 +2590,9 @@ function findPaginatedResourcePath(responseData) {
2590
2590
  }
2591
2591
  return paginatedResourcePath;
2592
2592
  }
2593
- var deepFindPathToProperty = (object, searchProp, path13 = []) => {
2593
+ var deepFindPathToProperty = (object, searchProp, path14 = []) => {
2594
2594
  for (const key of Object.keys(object)) {
2595
- const currentPath = [...path13, key];
2595
+ const currentPath = [...path14, key];
2596
2596
  const currentValue = object[key];
2597
2597
  if (isObject(currentValue)) {
2598
2598
  if (currentValue.hasOwnProperty(searchProp)) {
@@ -2610,12 +2610,12 @@ var deepFindPathToProperty = (object, searchProp, path13 = []) => {
2610
2610
  }
2611
2611
  return [];
2612
2612
  };
2613
- var get = (object, path13) => {
2614
- return path13.reduce((current, nextProperty) => current[nextProperty], object);
2613
+ var get = (object, path14) => {
2614
+ return path14.reduce((current, nextProperty) => current[nextProperty], object);
2615
2615
  };
2616
- var set = (object, path13, mutator) => {
2617
- const lastProperty = path13[path13.length - 1];
2618
- const parentPath = [...path13].slice(0, -1);
2616
+ var set = (object, path14, mutator) => {
2617
+ const lastProperty = path14[path14.length - 1];
2618
+ const parentPath = [...path14].slice(0, -1);
2619
2619
  const parent = get(object, parentPath);
2620
2620
  if (typeof mutator === "function") {
2621
2621
  parent[lastProperty] = mutator(parent[lastProperty]);
@@ -2667,22 +2667,22 @@ var mergeResponses = (response1, response2) => {
2667
2667
  if (Object.keys(response1).length === 0) {
2668
2668
  return Object.assign(response1, response2);
2669
2669
  }
2670
- const path13 = findPaginatedResourcePath(response1);
2671
- const nodesPath = [...path13, "nodes"];
2670
+ const path14 = findPaginatedResourcePath(response1);
2671
+ const nodesPath = [...path14, "nodes"];
2672
2672
  const newNodes = get(response2, nodesPath);
2673
2673
  if (newNodes) {
2674
2674
  set(response1, nodesPath, (values) => {
2675
2675
  return [...values, ...newNodes];
2676
2676
  });
2677
2677
  }
2678
- const edgesPath = [...path13, "edges"];
2678
+ const edgesPath = [...path14, "edges"];
2679
2679
  const newEdges = get(response2, edgesPath);
2680
2680
  if (newEdges) {
2681
2681
  set(response1, edgesPath, (values) => {
2682
2682
  return [...values, ...newEdges];
2683
2683
  });
2684
2684
  }
2685
- const pageInfoPath = [...path13, "pageInfo"];
2685
+ const pageInfoPath = [...path14, "pageInfo"];
2686
2686
  set(response1, pageInfoPath, get(response2, pageInfoPath));
2687
2687
  return response1;
2688
2688
  };
@@ -5103,7 +5103,7 @@ var triggers_notification_paths_default = [
5103
5103
  ];
5104
5104
  function routeMatcher(paths) {
5105
5105
  const regexes = paths.map(
5106
- (path13) => path13.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
5106
+ (path14) => path14.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
5107
5107
  );
5108
5108
  const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
5109
5109
  return new RegExp(regex2, "i");
@@ -8313,16 +8313,33 @@ stdout: ${stdout}` : killed || signal ? `
8313
8313
  }
8314
8314
  async function runDatabricks(args, opts = {}) {
8315
8315
  const { argv, env, profile } = buildInvocation(args, opts);
8316
+ const timeout = opts.timeout ?? KIT_TIMEOUTS.cliDefault;
8316
8317
  try {
8317
- const { stdout } = await execFileP("databricks", argv, {
8318
- env,
8319
- timeout: opts.timeout ?? KIT_TIMEOUTS.cliDefault
8320
- });
8318
+ if (opts.input !== void 0) {
8319
+ return await execDatabricksWithStdin(argv, opts.input, env, timeout);
8320
+ }
8321
+ const { stdout } = await execFileP("databricks", argv, { env, timeout });
8321
8322
  return stdout.toString();
8322
8323
  } catch (err) {
8323
8324
  throw classifyDatabricksError(err, argv, profile);
8324
8325
  }
8325
8326
  }
8327
+ function execDatabricksWithStdin(argv, input, env, timeout) {
8328
+ return new Promise((resolve2, reject) => {
8329
+ const child = execFile("databricks", argv, { env, timeout }, (err, stdout, stderr) => {
8330
+ if (err) {
8331
+ err.stdout = String(stdout ?? "");
8332
+ err.stderr = String(stderr ?? "");
8333
+ reject(err);
8334
+ return;
8335
+ }
8336
+ resolve2(String(stdout ?? ""));
8337
+ });
8338
+ child.stdin?.on("error", () => {
8339
+ });
8340
+ child.stdin?.end(input);
8341
+ });
8342
+ }
8326
8343
  function runDatabricksSync(args, opts = {}) {
8327
8344
  const { argv, env, profile } = buildInvocation(args, opts);
8328
8345
  try {
@@ -8361,9 +8378,9 @@ function asBranchUid(s) {
8361
8378
  }
8362
8379
  return s;
8363
8380
  }
8364
- function branchNameFromResourcePath(path13) {
8365
- if (!path13.includes("/branches/")) return null;
8366
- const leaf = path13.split("/branches/").pop();
8381
+ function branchNameFromResourcePath(path14) {
8382
+ if (!path14.includes("/branches/")) return null;
8383
+ const leaf = path14.split("/branches/").pop();
8367
8384
  if (!leaf) return null;
8368
8385
  try {
8369
8386
  return asBranchName(leaf);
@@ -8768,6 +8785,42 @@ async function pollUntil(args) {
8768
8785
  }
8769
8786
  }
8770
8787
 
8788
+ // scripts/git/sync.ts
8789
+ init_esm_shims();
8790
+
8791
+ // scripts/git/status.ts
8792
+ init_esm_shims();
8793
+ async function hasUpstream(args) {
8794
+ try {
8795
+ await exec2("git rev-parse --abbrev-ref @{u}", { cwd: args.cwd });
8796
+ return true;
8797
+ } catch {
8798
+ return false;
8799
+ }
8800
+ }
8801
+
8802
+ // scripts/git/sync.ts
8803
+ async function currentBranchName(cwd) {
8804
+ try {
8805
+ return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
8806
+ } catch {
8807
+ return "";
8808
+ }
8809
+ }
8810
+ async function pushCurrentBranchForPr(args) {
8811
+ const remote = args.remote ?? "origin";
8812
+ const branch = await currentBranchName(args.cwd);
8813
+ if (!branch) throw new Error("No current branch");
8814
+ const upstreamSet = await hasUpstream({ cwd: args.cwd });
8815
+ if (!upstreamSet) {
8816
+ await exec2(`git push -u ${remote} ${shq(branch)}`, {
8817
+ cwd: args.cwd
8818
+ });
8819
+ } else {
8820
+ await exec2("git push", { cwd: args.cwd });
8821
+ }
8822
+ }
8823
+
8771
8824
  // scripts/lakebase/scm-git-base.ts
8772
8825
  init_esm_shims();
8773
8826
  async function resolveGitBase(parentBranch, cwd) {
@@ -8777,10 +8830,190 @@ async function resolveGitBase(parentBranch, cwd) {
8777
8830
  return resolveDefaultBranch({ cwd });
8778
8831
  }
8779
8832
 
8780
- // scripts/lakebase/scm-workflow-state.ts
8833
+ // scripts/lakebase/workflow-drift.ts
8781
8834
  init_esm_shims();
8782
8835
  import * as fs3 from "fs";
8783
8836
  import * as path3 from "path";
8837
+ function findKitTemplatesDir(start) {
8838
+ let dir = start;
8839
+ for (let i = 0; i < 6; i++) {
8840
+ const candidate = path3.join(
8841
+ dir,
8842
+ "templates",
8843
+ "project",
8844
+ "common",
8845
+ ".github",
8846
+ "workflows"
8847
+ );
8848
+ if (fs3.existsSync(candidate)) return candidate;
8849
+ const parent = path3.dirname(dir);
8850
+ if (parent === dir) break;
8851
+ dir = parent;
8852
+ }
8853
+ throw new Error(
8854
+ `Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
8855
+ );
8856
+ }
8857
+ function unifiedDiff(name, projectContent, templateContent) {
8858
+ if (projectContent === templateContent) return "";
8859
+ const a = projectContent.split("\n");
8860
+ const b = templateContent.split("\n");
8861
+ const out = [`--- project/${name}`, `+++ template/${name}`];
8862
+ const max = Math.max(a.length, b.length);
8863
+ for (let i = 0; i < max; i++) {
8864
+ const av = a[i];
8865
+ const bv = b[i];
8866
+ if (av === bv) continue;
8867
+ if (av !== void 0) out.push(`-${i + 1}: ${av}`);
8868
+ if (bv !== void 0) out.push(`+${i + 1}: ${bv}`);
8869
+ }
8870
+ return out.join("\n");
8871
+ }
8872
+ function detectWorkflowDrift(args) {
8873
+ const projectWorkflowsDir = path3.join(
8874
+ args.projectDir,
8875
+ ".github",
8876
+ "workflows"
8877
+ );
8878
+ const here = path3.dirname(new URL(import.meta.url).pathname);
8879
+ const kitWorkflowsDir = args.kitDir ? path3.join(
8880
+ args.kitDir,
8881
+ "templates",
8882
+ "project",
8883
+ "common",
8884
+ ".github",
8885
+ "workflows"
8886
+ ) : findKitTemplatesDir(here);
8887
+ const templateFiles = fs3.existsSync(kitWorkflowsDir) ? fs3.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
8888
+ const projectFiles = fs3.existsSync(projectWorkflowsDir) ? fs3.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
8889
+ const version = readKitVersion(kitWorkflowsDir);
8890
+ const seen = /* @__PURE__ */ new Set();
8891
+ const files = [];
8892
+ for (const name of templateFiles) {
8893
+ seen.add(name);
8894
+ const projectPath2 = path3.join(projectWorkflowsDir, name);
8895
+ const templatePath = path3.join(kitWorkflowsDir, name);
8896
+ if (!fs3.existsSync(projectPath2)) {
8897
+ files.push({ name, status: "missing" });
8898
+ continue;
8899
+ }
8900
+ const projectContent = fs3.readFileSync(projectPath2, "utf8");
8901
+ const templateContent = applyPlaceholders(fs3.readFileSync(templatePath, "utf8"), version);
8902
+ if (projectContent === templateContent) {
8903
+ files.push({ name, status: "unchanged" });
8904
+ } else {
8905
+ files.push({
8906
+ name,
8907
+ status: "drifted",
8908
+ diff: unifiedDiff(name, projectContent, templateContent)
8909
+ });
8910
+ }
8911
+ }
8912
+ for (const name of projectFiles) {
8913
+ if (seen.has(name)) continue;
8914
+ files.push({ name, status: "extra" });
8915
+ }
8916
+ const order = {
8917
+ drifted: 0,
8918
+ missing: 1,
8919
+ extra: 2,
8920
+ unchanged: 3
8921
+ };
8922
+ files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
8923
+ const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
8924
+ return {
8925
+ overall: hasDrift ? "drift" : "ok",
8926
+ files
8927
+ };
8928
+ }
8929
+ function readKitVersion(kitWorkflowsDir) {
8930
+ let dir = kitWorkflowsDir;
8931
+ for (let i = 0; i < 5; i++) {
8932
+ dir = path3.dirname(dir);
8933
+ }
8934
+ try {
8935
+ const raw = fs3.readFileSync(path3.join(dir, "package.json"), "utf-8");
8936
+ const pkg = JSON.parse(raw);
8937
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
8938
+ } catch {
8939
+ return "unknown";
8940
+ }
8941
+ }
8942
+ function applyPlaceholders(content, version) {
8943
+ return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
8944
+ }
8945
+ function updateWorkflows(args) {
8946
+ const projectWorkflowsDir = path3.join(
8947
+ args.projectDir,
8948
+ ".github",
8949
+ "workflows"
8950
+ );
8951
+ const here = path3.dirname(new URL(import.meta.url).pathname);
8952
+ const kitWorkflowsDir = args.kitDir ? path3.join(
8953
+ args.kitDir,
8954
+ "templates",
8955
+ "project",
8956
+ "common",
8957
+ ".github",
8958
+ "workflows"
8959
+ ) : findKitTemplatesDir(here);
8960
+ const substitute = args.substitute !== false;
8961
+ const dryRun = args.dryRun === true;
8962
+ const pruneExtras = args.pruneExtras === true;
8963
+ const templateFiles = fs3.existsSync(kitWorkflowsDir) ? fs3.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
8964
+ const projectFiles = fs3.existsSync(projectWorkflowsDir) ? fs3.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
8965
+ if (!dryRun && templateFiles.length > 0 && !fs3.existsSync(projectWorkflowsDir)) {
8966
+ fs3.mkdirSync(projectWorkflowsDir, { recursive: true });
8967
+ }
8968
+ const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
8969
+ const seen = /* @__PURE__ */ new Set();
8970
+ const files = [];
8971
+ for (const name of templateFiles) {
8972
+ seen.add(name);
8973
+ const projectPath2 = path3.join(projectWorkflowsDir, name);
8974
+ const templatePath = path3.join(kitWorkflowsDir, name);
8975
+ const templateRaw = fs3.readFileSync(templatePath, "utf-8");
8976
+ const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
8977
+ const existed = fs3.existsSync(projectPath2);
8978
+ const current = existed ? fs3.readFileSync(projectPath2, "utf-8") : "";
8979
+ let outcome;
8980
+ if (!existed) {
8981
+ outcome = "added";
8982
+ } else if (current === desired) {
8983
+ outcome = "unchanged";
8984
+ } else {
8985
+ outcome = "updated";
8986
+ }
8987
+ if (!dryRun && outcome !== "unchanged") {
8988
+ fs3.writeFileSync(projectPath2, desired);
8989
+ }
8990
+ files.push({ name, outcome });
8991
+ }
8992
+ if (pruneExtras) {
8993
+ for (const name of projectFiles) {
8994
+ if (seen.has(name)) continue;
8995
+ const projectPath2 = path3.join(projectWorkflowsDir, name);
8996
+ if (!dryRun) {
8997
+ fs3.unlinkSync(projectPath2);
8998
+ }
8999
+ files.push({ name, outcome: "removed" });
9000
+ }
9001
+ }
9002
+ const order = {
9003
+ added: 0,
9004
+ updated: 1,
9005
+ removed: 2,
9006
+ unchanged: 3
9007
+ };
9008
+ files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
9009
+ const changed = files.some((f) => f.outcome !== "unchanged");
9010
+ return { files, changed };
9011
+ }
9012
+
9013
+ // scripts/lakebase/scm-workflow-state.ts
9014
+ init_esm_shims();
9015
+ import * as fs4 from "fs";
9016
+ import * as path4 from "path";
8784
9017
  import { execFileSync as execFileSync4 } from "child_process";
8785
9018
  function isGitTracked(projectDir, rel) {
8786
9019
  try {
@@ -8799,11 +9032,11 @@ function ensureWorkflowStateUntracked(projectDir) {
8799
9032
  } catch {
8800
9033
  }
8801
9034
  try {
8802
- const gitignore = path3.join(projectDir, ".gitignore");
8803
- const existing = fs3.existsSync(gitignore) ? fs3.readFileSync(gitignore, "utf8") : "";
9035
+ const gitignore = path4.join(projectDir, ".gitignore");
9036
+ const existing = fs4.existsSync(gitignore) ? fs4.readFileSync(gitignore, "utf8") : "";
8804
9037
  if (!existing.split("\n").some((l) => l.trim() === rel)) {
8805
9038
  const sep2 = existing === "" || existing.endsWith("\n") ? "" : "\n";
8806
- fs3.appendFileSync(
9039
+ fs4.appendFileSync(
8807
9040
  gitignore,
8808
9041
  `${sep2}# Runtime SCM claim state (per working tree): a branch checkout must never restore
8809
9042
  # a stale committed claim over the live one (issue #203 / Finding 28).
@@ -8827,12 +9060,12 @@ var STATE_INDEX = SCM_STATES.reduce(
8827
9060
  );
8828
9061
  var STATE_FILE_REL = ".lakebase/workflow-state.json";
8829
9062
  function stateFilePath(projectDir) {
8830
- return path3.join(projectDir, STATE_FILE_REL);
9063
+ return path4.join(projectDir, STATE_FILE_REL);
8831
9064
  }
8832
9065
  function readWorkflowState(projectDir) {
8833
9066
  const p = stateFilePath(projectDir);
8834
- if (!fs3.existsSync(p)) return null;
8835
- const raw = fs3.readFileSync(p, "utf8");
9067
+ if (!fs4.existsSync(p)) return null;
9068
+ const raw = fs4.readFileSync(p, "utf8");
8836
9069
  let parsed;
8837
9070
  try {
8838
9071
  parsed = JSON.parse(raw);
@@ -8861,14 +9094,14 @@ function writeWorkflowState(projectDir, state) {
8861
9094
  ${summary}`);
8862
9095
  }
8863
9096
  ensureWorkflowStateUntracked(projectDir);
8864
- const dir = path3.join(projectDir, ".lakebase");
8865
- fs3.mkdirSync(dir, { recursive: true });
9097
+ const dir = path4.join(projectDir, ".lakebase");
9098
+ fs4.mkdirSync(dir, { recursive: true });
8866
9099
  const target = stateFilePath(projectDir);
8867
9100
  const tmp = `${target}.tmp`;
8868
9101
  const ordered = orderForOutput(result.value);
8869
- fs3.writeFileSync(tmp, `${JSON.stringify(ordered, null, 2)}
9102
+ fs4.writeFileSync(tmp, `${JSON.stringify(ordered, null, 2)}
8870
9103
  `, "utf8");
8871
- fs3.renameSync(tmp, target);
9104
+ fs4.renameSync(tmp, target);
8872
9105
  }
8873
9106
  function validateWorkflowState(value) {
8874
9107
  const errors = [];
@@ -9070,6 +9303,27 @@ function shaMigratePredicate(mergeCommitSha) {
9070
9303
  return !!run.headSha && run.headSha === mergeCommitSha;
9071
9304
  };
9072
9305
  }
9306
+ async function defaultRefreshPromoteWorkflows(projectDir) {
9307
+ const drift = detectWorkflowDrift({ projectDir });
9308
+ const touched = drift.files.filter((f) => f.status === "drifted").map((f) => f.name);
9309
+ if (touched.length === 0) return { refreshed: false };
9310
+ const result = updateWorkflows({ projectDir });
9311
+ if (!result.changed) return { refreshed: false };
9312
+ await exec2(`git add ${shellEscape(".github/workflows")}`, { cwd: projectDir, timeout: 1e4 });
9313
+ const staged = await exec2("git diff --cached --name-only -- .github/workflows", {
9314
+ cwd: projectDir,
9315
+ timeout: 1e4
9316
+ });
9317
+ if (!staged.trim()) return { refreshed: false };
9318
+ await exec2(
9319
+ `git commit -m ${shellEscape(
9320
+ "chore(ci): refresh scaffolded workflows to pinned substrate (promote uses apply-tier)"
9321
+ )} -- .github/workflows`,
9322
+ { cwd: projectDir, timeout: 15e3 }
9323
+ );
9324
+ await pushCurrentBranchForPr({ cwd: projectDir });
9325
+ return { refreshed: true, detail: `refreshed + pushed ${touched.join(", ") || "workflow(s)"} onto the PR head` };
9326
+ }
9073
9327
  async function mergeFeature(args) {
9074
9328
  const current = readWorkflowState(args.projectDir);
9075
9329
  if (!current) {
@@ -9124,6 +9378,20 @@ async function mergeFeature(args) {
9124
9378
  }
9125
9379
  authVerified = true;
9126
9380
  }
9381
+ const preflightNotes = [];
9382
+ {
9383
+ const refresh = args.refreshWorkflows ?? (() => defaultRefreshPromoteWorkflows(args.projectDir));
9384
+ try {
9385
+ const r = await refresh();
9386
+ if (r.refreshed) {
9387
+ preflightNotes.push(`Refreshed stale CI workflows before merge${r.detail ? ` (${r.detail})` : ""}.`);
9388
+ }
9389
+ } catch (err) {
9390
+ preflightNotes.push(
9391
+ `Workflow self-heal skipped (${err instanceof Error ? err.message : String(err)}); if the promote's migrate-target fails on the tier guard, refresh .github/workflows (merge.yml must call 'lakebase-schema-migrate apply-tier').`
9392
+ );
9393
+ }
9394
+ }
9127
9395
  let paired;
9128
9396
  try {
9129
9397
  paired = await mergePairedPullRequest({
@@ -9138,7 +9406,7 @@ async function mergeFeature(args) {
9138
9406
  "merge-failed"
9139
9407
  );
9140
9408
  }
9141
- const warnings = [...paired.warnings];
9409
+ const warnings = [...preflightNotes, ...paired.warnings];
9142
9410
  let localBranchDeleted = false;
9143
9411
  let headAfter = current.branch;
9144
9412
  if (!args.skipLocalCleanup) {
@@ -9332,8 +9600,8 @@ function shellEscape(s) {
9332
9600
 
9333
9601
  // scripts/lakebase/schema-migrate.ts
9334
9602
  init_esm_shims();
9335
- import * as fs10 from "fs";
9336
- import * as path11 from "path";
9603
+ import * as fs11 from "fs";
9604
+ import * as path12 from "path";
9337
9605
 
9338
9606
  // scripts/lakebase/get-connection.ts
9339
9607
  init_esm_shims();
@@ -9351,25 +9619,25 @@ var CONSORT_VERSION_ENV = "CONSORT_VERSION";
9351
9619
 
9352
9620
  // scripts/lakebase/self-version.ts
9353
9621
  init_esm_shims();
9354
- import * as fs4 from "fs";
9355
- import * as path4 from "path";
9622
+ import * as fs5 from "fs";
9623
+ import * as path5 from "path";
9356
9624
  import { fileURLToPath as fileURLToPath3 } from "url";
9357
9625
  var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
9358
9626
  var cached;
9359
9627
  function substrateSelfVersion() {
9360
9628
  if (cached !== void 0) return cached;
9361
- if ("0.2.43".length > 0) {
9362
- cached = "0.2.43";
9629
+ if ("0.2.45".length > 0) {
9630
+ cached = "0.2.45";
9363
9631
  return cached;
9364
9632
  }
9365
9633
  cached = "unknown";
9366
9634
  try {
9367
- let dir = path4.dirname(fileURLToPath3(import.meta.url));
9635
+ let dir = path5.dirname(fileURLToPath3(import.meta.url));
9368
9636
  for (let i = 0; i < 8; i++) {
9369
- const pkgPath = path4.join(dir, "package.json");
9370
- if (fs4.existsSync(pkgPath)) {
9637
+ const pkgPath = path5.join(dir, "package.json");
9638
+ if (fs5.existsSync(pkgPath)) {
9371
9639
  try {
9372
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
9640
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
9373
9641
  if (pkg.name === PKG_NAME && typeof pkg.version === "string" && pkg.version.length > 0) {
9374
9642
  cached = pkg.version;
9375
9643
  return cached;
@@ -9377,7 +9645,7 @@ function substrateSelfVersion() {
9377
9645
  } catch {
9378
9646
  }
9379
9647
  }
9380
- const parent = path4.dirname(dir);
9648
+ const parent = path5.dirname(dir);
9381
9649
  if (parent === dir) break;
9382
9650
  dir = parent;
9383
9651
  }
@@ -9464,22 +9732,22 @@ init_esm_shims();
9464
9732
 
9465
9733
  // scripts/lakebase/adapters/alembic-adapter.ts
9466
9734
  init_esm_shims();
9467
- import * as fs6 from "fs";
9468
- import * as path6 from "path";
9735
+ import * as fs7 from "fs";
9736
+ import * as path7 from "path";
9469
9737
 
9470
9738
  // scripts/lakebase/schema-migrate-runners/alembic.ts
9471
9739
  init_esm_shims();
9472
9740
  import { spawn } from "child_process";
9473
- import * as fs5 from "fs";
9474
- import * as path5 from "path";
9741
+ import * as fs6 from "fs";
9742
+ import * as path6 from "path";
9475
9743
  function resolveAlembicBin(projectDir) {
9476
9744
  const candidates = [
9477
- path5.join(projectDir, ".venv", "bin", "alembic"),
9478
- path5.join(projectDir, "venv", "bin", "alembic")
9745
+ path6.join(projectDir, ".venv", "bin", "alembic"),
9746
+ path6.join(projectDir, "venv", "bin", "alembic")
9479
9747
  ];
9480
9748
  for (const candidate of candidates) {
9481
9749
  try {
9482
- if (fs5.existsSync(candidate)) return candidate;
9750
+ if (fs6.existsSync(candidate)) return candidate;
9483
9751
  } catch {
9484
9752
  }
9485
9753
  }
@@ -9489,7 +9757,7 @@ function spawnAlembic(projectDir, args, dsn) {
9489
9757
  return new Promise((resolve2, reject) => {
9490
9758
  const bin = resolveAlembicBin(projectDir);
9491
9759
  const env = { ...process.env };
9492
- env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path5.delimiter);
9760
+ env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path6.delimiter);
9493
9761
  if (dsn) env.DATABASE_URL = dsn;
9494
9762
  const child = spawn(bin, args, {
9495
9763
  cwd: projectDir,
@@ -9537,10 +9805,10 @@ async function createAlembicRevision(opts) {
9537
9805
  const m = stdout.match(/Generating\s+(\S+\.py)/);
9538
9806
  if (m) return m[1].trim();
9539
9807
  for (const rel of ["migrations/versions", "alembic/versions"]) {
9540
- const dir = path5.join(opts.projectDir, rel);
9541
- if (!fs5.existsSync(dir)) continue;
9542
- const hit = fs5.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
9543
- if (hit) return path5.join(dir, hit);
9808
+ const dir = path6.join(opts.projectDir, rel);
9809
+ if (!fs6.existsSync(dir)) continue;
9810
+ const hit = fs6.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
9811
+ if (hit) return path6.join(dir, hit);
9544
9812
  }
9545
9813
  throw new SchemaMigrationError(
9546
9814
  `alembic revision succeeded but the created file could not be located.
@@ -9675,15 +9943,15 @@ async function buildDsn(args) {
9675
9943
  }
9676
9944
  function findVersionsDir(projectDir) {
9677
9945
  const candidates = [
9678
- path6.join(projectDir, "migrations", "versions"),
9679
- path6.join(projectDir, "alembic", "versions")
9946
+ path7.join(projectDir, "migrations", "versions"),
9947
+ path7.join(projectDir, "alembic", "versions")
9680
9948
  ];
9681
- return candidates.find((p) => fs6.existsSync(p));
9949
+ return candidates.find((p) => fs7.existsSync(p));
9682
9950
  }
9683
9951
  function listAlembicFiles(projectDir) {
9684
9952
  const dir = findVersionsDir(projectDir);
9685
9953
  if (!dir) return [];
9686
- const files = fs6.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
9954
+ const files = fs7.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
9687
9955
  return files.map((filename) => {
9688
9956
  const stem = filename.replace(/\.py$/, "");
9689
9957
  const sep2 = stem.indexOf("_");
@@ -9708,9 +9976,9 @@ var AlembicAdapter = {
9708
9976
  * here. Callers can still force-select via project.yaml#migration_tool.
9709
9977
  */
9710
9978
  detect(projectDir) {
9711
- if (fs6.existsSync(path6.join(projectDir, "alembic.ini"))) return true;
9712
- if (fs6.existsSync(path6.join(projectDir, "migrations", "env.py"))) return true;
9713
- if (fs6.existsSync(path6.join(projectDir, "alembic", "env.py"))) return true;
9979
+ if (fs7.existsSync(path7.join(projectDir, "alembic.ini"))) return true;
9980
+ if (fs7.existsSync(path7.join(projectDir, "migrations", "env.py"))) return true;
9981
+ if (fs7.existsSync(path7.join(projectDir, "alembic", "env.py"))) return true;
9714
9982
  return false;
9715
9983
  },
9716
9984
  async apply(args) {
@@ -9816,7 +10084,7 @@ var AlembicAdapter = {
9816
10084
  autogenerate: !!args.autogenerate,
9817
10085
  dsn
9818
10086
  });
9819
- return { status: "ok", version: revId, filename: path6.basename(created), path: created };
10087
+ return { status: "ok", version: revId, filename: path7.basename(created), path: created };
9820
10088
  } catch (err) {
9821
10089
  return {
9822
10090
  status: "error",
@@ -9833,7 +10101,7 @@ var AlembicAdapter = {
9833
10101
  if (heads.length <= 1) return { status: "noop", headsBefore: heads };
9834
10102
  if (args.dryRun) return { status: "ok", headsBefore: heads };
9835
10103
  const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
9836
- const mergeRevision = path6.basename(created).replace(/\.py$/, "").split("_")[0];
10104
+ const mergeRevision = path7.basename(created).replace(/\.py$/, "").split("_")[0];
9837
10105
  return { status: "ok", headsBefore: heads, mergeRevision, path: created };
9838
10106
  } catch (err) {
9839
10107
  return {
@@ -9848,13 +10116,13 @@ registerSchemaMigrationAdapter(AlembicAdapter);
9848
10116
 
9849
10117
  // scripts/lakebase/adapters/flyway-adapter.ts
9850
10118
  init_esm_shims();
9851
- import * as fs7 from "fs";
9852
- import * as path8 from "path";
10119
+ import * as fs8 from "fs";
10120
+ import * as path9 from "path";
9853
10121
 
9854
10122
  // scripts/lakebase/schema-migrate-runners/flyway.ts
9855
10123
  init_esm_shims();
9856
10124
  import { spawn as spawn2 } from "child_process";
9857
- import * as path7 from "path";
10125
+ import * as path8 from "path";
9858
10126
  function dsnToFlywayEnv(dsn) {
9859
10127
  const u = new URL(dsn);
9860
10128
  const user = decodeURIComponent(u.username);
@@ -9864,7 +10132,7 @@ function dsnToFlywayEnv(dsn) {
9864
10132
  return { url, user, password };
9865
10133
  }
9866
10134
  function migrationsLocation(projectDir) {
9867
- return `filesystem:${path7.join(projectDir, "src", "main", "resources", "db", "migration")}`;
10135
+ return `filesystem:${path8.join(projectDir, "src", "main", "resources", "db", "migration")}`;
9868
10136
  }
9869
10137
  function runFlyway(ctx, args) {
9870
10138
  const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
@@ -9965,7 +10233,7 @@ async function statusFlyway(ctx) {
9965
10233
  if (state === "SUCCESS" || state === "BASELINE") {
9966
10234
  current = m.version;
9967
10235
  } else if (state === "PENDING") {
9968
- const filename = m.filepath ? path7.basename(m.filepath) : `V${m.version}__migration.sql`;
10236
+ const filename = m.filepath ? path8.basename(m.filepath) : `V${m.version}__migration.sql`;
9969
10237
  pending.push({
9970
10238
  version: m.version,
9971
10239
  filename,
@@ -9988,9 +10256,9 @@ async function buildDsn2(args) {
9988
10256
  return result.url;
9989
10257
  }
9990
10258
  function listFlywayFiles(projectDir) {
9991
- const dir = path8.join(projectDir, "src", "main", "resources", "db", "migration");
9992
- if (!fs7.existsSync(dir)) return [];
9993
- const files = fs7.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
10259
+ const dir = path9.join(projectDir, "src", "main", "resources", "db", "migration");
10260
+ if (!fs8.existsSync(dir)) return [];
10261
+ const files = fs8.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
9994
10262
  return files.map((filename) => {
9995
10263
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
9996
10264
  const version = m[1];
@@ -10013,7 +10281,7 @@ var FlywayAdapter = {
10013
10281
  id: "flyway",
10014
10282
  languages: ["java", "kotlin"],
10015
10283
  detect(projectDir) {
10016
- return fs7.existsSync(path8.join(projectDir, "pom.xml"));
10284
+ return fs8.existsSync(path9.join(projectDir, "pom.xml"));
10017
10285
  },
10018
10286
  async apply(args) {
10019
10287
  const dsn = await buildDsn2(args);
@@ -10071,14 +10339,14 @@ var FlywayAdapter = {
10071
10339
  // optional-protocol shape makes this additive.
10072
10340
  async newMigration(args) {
10073
10341
  try {
10074
- const dir = path8.join(args.projectDir, "src", "main", "resources", "db", "migration");
10075
- fs7.mkdirSync(dir, { recursive: true });
10342
+ const dir = path9.join(args.projectDir, "src", "main", "resources", "db", "migration");
10343
+ fs8.mkdirSync(dir, { recursive: true });
10076
10344
  const version = migrationTimestamp();
10077
10345
  const slug = migrationSlug2(args.slug);
10078
10346
  const filename = `V${version}__${slug}.sql`;
10079
- const full = path8.join(dir, filename);
10080
- if (fs7.existsSync(full)) throw new Error(`${filename} already exists`);
10081
- fs7.writeFileSync(
10347
+ const full = path9.join(dir, filename);
10348
+ if (fs8.existsSync(full)) throw new Error(`${filename} already exists`);
10349
+ fs8.writeFileSync(
10082
10350
  full,
10083
10351
  `-- V${version}: ${args.slug}
10084
10352
  -- Flyway migration (write your DDL/DML below).
@@ -10101,19 +10369,19 @@ registerSchemaMigrationAdapter(FlywayAdapter);
10101
10369
 
10102
10370
  // scripts/lakebase/adapters/knex-adapter.ts
10103
10371
  init_esm_shims();
10104
- import * as fs9 from "fs";
10105
- import * as path10 from "path";
10372
+ import * as fs10 from "fs";
10373
+ import * as path11 from "path";
10106
10374
 
10107
10375
  // scripts/lakebase/schema-migrate-runners/knex.ts
10108
10376
  init_esm_shims();
10109
10377
  import { spawn as spawn3 } from "child_process";
10110
- import * as fs8 from "fs";
10111
- import * as path9 from "path";
10378
+ import * as fs9 from "fs";
10379
+ import * as path10 from "path";
10112
10380
  var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
10113
10381
  function findKnexfile(projectDir) {
10114
10382
  for (const name of KNEXFILE_VARIANTS) {
10115
- const p = path9.join(projectDir, name);
10116
- if (fs8.existsSync(p)) return p;
10383
+ const p = path10.join(projectDir, name);
10384
+ if (fs9.existsSync(p)) return p;
10117
10385
  }
10118
10386
  return void 0;
10119
10387
  }
@@ -10265,9 +10533,9 @@ async function buildDsn3(args) {
10265
10533
  }
10266
10534
  var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
10267
10535
  function listKnexFiles(projectDir) {
10268
- const dir = path10.join(projectDir, "migrations");
10269
- if (!fs9.existsSync(dir)) return [];
10270
- const files = fs9.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
10536
+ const dir = path11.join(projectDir, "migrations");
10537
+ if (!fs10.existsSync(dir)) return [];
10538
+ const files = fs10.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
10271
10539
  return files.map((filename) => {
10272
10540
  const stem = filename.replace(/\.(js|ts)$/, "");
10273
10541
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -10287,7 +10555,7 @@ var KnexAdapter = {
10287
10555
  * project.yaml#migration_tool.
10288
10556
  */
10289
10557
  detect(projectDir) {
10290
- return KNEXFILE_VARIANTS2.some((name) => fs9.existsSync(path10.join(projectDir, name)));
10558
+ return KNEXFILE_VARIANTS2.some((name) => fs10.existsSync(path11.join(projectDir, name)));
10291
10559
  },
10292
10560
  async apply(args) {
10293
10561
  const dsn = await buildDsn3(args);
@@ -10360,9 +10628,9 @@ var KnexAdapter = {
10360
10628
  async newMigration(args) {
10361
10629
  try {
10362
10630
  const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
10363
- const stem = path10.basename(created).replace(/\.(js|ts)$/, "");
10631
+ const stem = path11.basename(created).replace(/\.(js|ts)$/, "");
10364
10632
  const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
10365
- return { status: "ok", version, filename: path10.basename(created), path: created };
10633
+ return { status: "ok", version, filename: path11.basename(created), path: created };
10366
10634
  } catch (err) {
10367
10635
  return {
10368
10636
  status: "error",
@@ -10655,7 +10923,7 @@ async function runScmMergeCli(argv) {
10655
10923
  `);
10656
10924
  return 0;
10657
10925
  }
10658
- const projectDir = path12.resolve(args.projectDir ?? process.cwd());
10926
+ const projectDir = path13.resolve(args.projectDir ?? process.cwd());
10659
10927
  const waitMigrate = args.noWaitMigrate ? false : true;
10660
10928
  const verifyMigrateAuth = waitMigrate && !args.noVerifyMigrateAuth ? async () => {
10661
10929
  try {