@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
@@ -78993,6 +78993,7 @@ __export(lakebase_exports, {
78993
78993
  createUatPairedBranch: () => createUatPairedBranch,
78994
78994
  cutBackup: () => cutBackup,
78995
78995
  databricksAuthPrereqMessage: () => databricksAuthPrereqMessage,
78996
+ defaultRefreshPromoteWorkflows: () => defaultRefreshPromoteWorkflows,
78996
78997
  deleteAppEndpoint: () => deleteAppEndpoint,
78997
78998
  deleteBranch: () => deleteBranch,
78998
78999
  deleteLakebaseProject: () => deleteLakebaseProject,
@@ -79485,16 +79486,33 @@ stdout: ${stdout}` : killed || signal ? `
79485
79486
  }
79486
79487
  async function runDatabricks(args, opts = {}) {
79487
79488
  const { argv, env, profile } = buildInvocation(args, opts);
79489
+ const timeout = opts.timeout ?? KIT_TIMEOUTS.cliDefault;
79488
79490
  try {
79489
- const { stdout } = await execFileP("databricks", argv, {
79490
- env,
79491
- timeout: opts.timeout ?? KIT_TIMEOUTS.cliDefault
79492
- });
79491
+ if (opts.input !== void 0) {
79492
+ return await execDatabricksWithStdin(argv, opts.input, env, timeout);
79493
+ }
79494
+ const { stdout } = await execFileP("databricks", argv, { env, timeout });
79493
79495
  return stdout.toString();
79494
79496
  } catch (err) {
79495
79497
  throw classifyDatabricksError(err, argv, profile);
79496
79498
  }
79497
79499
  }
79500
+ function execDatabricksWithStdin(argv, input, env, timeout) {
79501
+ return new Promise((resolve2, reject) => {
79502
+ const child = (0, import_node_child_process2.execFile)("databricks", argv, { env, timeout }, (err, stdout, stderr) => {
79503
+ if (err) {
79504
+ err.stdout = String(stdout ?? "");
79505
+ err.stderr = String(stderr ?? "");
79506
+ reject(err);
79507
+ return;
79508
+ }
79509
+ resolve2(String(stdout ?? ""));
79510
+ });
79511
+ child.stdin?.on("error", () => {
79512
+ });
79513
+ child.stdin?.end(input);
79514
+ });
79515
+ }
79498
79516
  function runDatabricksSync(args, opts = {}) {
79499
79517
  const { argv, env, profile } = buildInvocation(args, opts);
79500
79518
  try {
@@ -81570,8 +81588,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
81570
81588
  var cached;
81571
81589
  function substrateSelfVersion() {
81572
81590
  if (cached !== void 0) return cached;
81573
- if ("0.2.43".length > 0) {
81574
- cached = "0.2.43";
81591
+ if ("0.2.45".length > 0) {
81592
+ cached = "0.2.45";
81575
81593
  return cached;
81576
81594
  }
81577
81595
  cached = "unknown";
@@ -81799,6 +81817,14 @@ async function getCredential(args) {
81799
81817
 
81800
81818
  // scripts/git/status.ts
81801
81819
  init_cjs_shims();
81820
+ async function hasUpstream(args) {
81821
+ try {
81822
+ await exec2("git rev-parse --abbrev-ref @{u}", { cwd: args.cwd });
81823
+ return true;
81824
+ } catch {
81825
+ return false;
81826
+ }
81827
+ }
81802
81828
  async function getAheadBehind(args) {
81803
81829
  const { cwd } = args;
81804
81830
  try {
@@ -91830,8 +91856,20 @@ async function setupRunner(args) {
91830
91856
  if (needsConfig) {
91831
91857
  report("Registering runner with GitHub...");
91832
91858
  const regToken = await createRegistrationToken(args.fullRepoName);
91833
- cp4.execSync(
91834
- `./config.sh --url "https://github.com/${args.fullRepoName}" --token "${regToken}" --name "${name}" --labels self-hosted --unattended --replace`,
91859
+ cp4.execFileSync(
91860
+ "./config.sh",
91861
+ [
91862
+ "--url",
91863
+ `https://github.com/${args.fullRepoName}`,
91864
+ "--token",
91865
+ regToken,
91866
+ "--name",
91867
+ name,
91868
+ "--labels",
91869
+ "self-hosted",
91870
+ "--unattended",
91871
+ "--replace"
91872
+ ],
91835
91873
  { cwd: dir, timeout: KIT_TIMEOUTS.cliLong }
91836
91874
  );
91837
91875
  }
@@ -94890,104 +94928,447 @@ function pickRunUrl(pr) {
94890
94928
 
94891
94929
  // scripts/lakebase/scm-merge.ts
94892
94930
  init_cjs_shims();
94893
- async function reconcileTierToOrigin(args) {
94894
- const { cwd, tier } = args;
94931
+
94932
+ // scripts/git/sync.ts
94933
+ init_cjs_shims();
94934
+ async function currentBranchName(cwd) {
94895
94935
  try {
94896
- const head = await getCurrentBranch({ cwd });
94897
- if (head === tier) {
94898
- await exec2(`git pull --ff-only`, { cwd, timeout: 3e4 });
94899
- } else {
94900
- await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
94901
- }
94902
- return null;
94903
- } catch (err) {
94904
- return `local reconcile of ${tier} to origin/${tier} failed: ${err instanceof Error ? err.message : String(err)}. Reconcile it by hand (git checkout ${tier} && git pull --ff-only).`;
94936
+ return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
94937
+ } catch {
94938
+ return "";
94905
94939
  }
94906
94940
  }
94907
- var ScmMergeError = class extends Error {
94908
- constructor(message, code) {
94909
- super(message);
94910
- this.code = code;
94911
- this.name = "ScmMergeError";
94941
+ async function pushCurrentBranchForPr(args) {
94942
+ const remote = args.remote ?? "origin";
94943
+ const branch = await currentBranchName(args.cwd);
94944
+ if (!branch) throw new Error("No current branch");
94945
+ const upstreamSet = await hasUpstream({ cwd: args.cwd });
94946
+ if (!upstreamSet) {
94947
+ await exec2(`git push -u ${remote} ${shq(branch)}`, {
94948
+ cwd: args.cwd
94949
+ });
94950
+ } else {
94951
+ await exec2("git push", { cwd: args.cwd });
94912
94952
  }
94913
- code;
94914
- };
94915
- var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
94916
- var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
94917
- function defaultMigratePredicate(run, mergedAt) {
94918
- if (!run.createdAt) return false;
94919
- const created = Date.parse(run.createdAt);
94920
- if (!Number.isFinite(created)) return false;
94921
- if (run.event && run.event !== "push") return false;
94922
- return created >= mergedAt.getTime() - 5e3;
94923
- }
94924
- function shaMigratePredicate(mergeCommitSha) {
94925
- return (run) => {
94926
- if (run.event && run.event !== "push") return false;
94927
- return !!run.headSha && run.headSha === mergeCommitSha;
94928
- };
94929
94953
  }
94930
- async function mergeFeature(args) {
94931
- const current = readWorkflowState(args.projectDir);
94932
- if (!current) {
94933
- throw new ScmMergeError(
94934
- "No SCM workflow state. wait-ci first.",
94935
- "no-state-file"
94954
+
94955
+ // scripts/lakebase/workflow-drift.ts
94956
+ init_cjs_shims();
94957
+ var fs30 = __toESM(require("fs"), 1);
94958
+ var path28 = __toESM(require("path"), 1);
94959
+ function findKitTemplatesDir(start) {
94960
+ let dir = start;
94961
+ for (let i2 = 0; i2 < 6; i2++) {
94962
+ const candidate = path28.join(
94963
+ dir,
94964
+ "templates",
94965
+ "project",
94966
+ "common",
94967
+ ".github",
94968
+ "workflows"
94936
94969
  );
94970
+ if (fs30.existsSync(candidate)) return candidate;
94971
+ const parent = path28.dirname(dir);
94972
+ if (parent === dir) break;
94973
+ dir = parent;
94937
94974
  }
94938
- if (current.state !== "ci-green") {
94939
- throw new ScmMergeError(
94940
- `merge refuses state "${current.state}". Allowed predecessor: ci-green.`,
94941
- "bad-precondition"
94942
- );
94975
+ throw new Error(
94976
+ `Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
94977
+ );
94978
+ }
94979
+ function unifiedDiff(name, projectContent, templateContent) {
94980
+ if (projectContent === templateContent) return "";
94981
+ const a = projectContent.split("\n");
94982
+ const b = templateContent.split("\n");
94983
+ const out = [`--- project/${name}`, `+++ template/${name}`];
94984
+ const max = Math.max(a.length, b.length);
94985
+ for (let i2 = 0; i2 < max; i2++) {
94986
+ const av = a[i2];
94987
+ const bv = b[i2];
94988
+ if (av === bv) continue;
94989
+ if (av !== void 0) out.push(`-${i2 + 1}: ${av}`);
94990
+ if (bv !== void 0) out.push(`+${i2 + 1}: ${bv}`);
94943
94991
  }
94944
- if (!current.pr_url) {
94945
- throw new ScmMergeError(
94946
- "ci-green row is missing pr_url; cannot resolve the PR to merge.",
94947
- "no-pr-url"
94948
- );
94992
+ return out.join("\n");
94993
+ }
94994
+ function detectWorkflowDrift(args) {
94995
+ const projectWorkflowsDir = path28.join(
94996
+ args.projectDir,
94997
+ ".github",
94998
+ "workflows"
94999
+ );
95000
+ const here = path28.dirname(new URL(importMetaUrl).pathname);
95001
+ const kitWorkflowsDir = args.kitDir ? path28.join(
95002
+ args.kitDir,
95003
+ "templates",
95004
+ "project",
95005
+ "common",
95006
+ ".github",
95007
+ "workflows"
95008
+ ) : findKitTemplatesDir(here);
95009
+ const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
95010
+ const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
95011
+ const version = readKitVersion(kitWorkflowsDir);
95012
+ const seen = /* @__PURE__ */ new Set();
95013
+ const files = [];
95014
+ for (const name of templateFiles) {
95015
+ seen.add(name);
95016
+ const projectPath2 = path28.join(projectWorkflowsDir, name);
95017
+ const templatePath = path28.join(kitWorkflowsDir, name);
95018
+ if (!fs30.existsSync(projectPath2)) {
95019
+ files.push({ name, status: "missing" });
95020
+ continue;
95021
+ }
95022
+ const projectContent = fs30.readFileSync(projectPath2, "utf8");
95023
+ const templateContent = applyPlaceholders(fs30.readFileSync(templatePath, "utf8"), version);
95024
+ if (projectContent === templateContent) {
95025
+ files.push({ name, status: "unchanged" });
95026
+ } else {
95027
+ files.push({
95028
+ name,
95029
+ status: "drifted",
95030
+ diff: unifiedDiff(name, projectContent, templateContent)
95031
+ });
95032
+ }
94949
95033
  }
94950
- if (!current.branch || !current.parent_branch) {
94951
- throw new ScmMergeError(
94952
- "ci-green row missing branch / parent_branch; refusing to merge.",
94953
- "bad-precondition"
94954
- );
95034
+ for (const name of projectFiles) {
95035
+ if (seen.has(name)) continue;
95036
+ files.push({ name, status: "extra" });
94955
95037
  }
94956
- const ownerRepo = await getOwnerRepo(args.projectDir);
94957
- if (!ownerRepo) {
94958
- throw new ScmMergeError(
94959
- "No GitHub remote found at origin.",
94960
- "no-github-remote"
94961
- );
95038
+ const order = {
95039
+ drifted: 0,
95040
+ missing: 1,
95041
+ extra: 2,
95042
+ unchanged: 3
95043
+ };
95044
+ files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
95045
+ const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
95046
+ return {
95047
+ overall: hasDrift ? "drift" : "ok",
95048
+ files
95049
+ };
95050
+ }
95051
+ function readKitVersion(kitWorkflowsDir) {
95052
+ let dir = kitWorkflowsDir;
95053
+ for (let i2 = 0; i2 < 5; i2++) {
95054
+ dir = path28.dirname(dir);
94962
95055
  }
94963
- const pullNumber = extractPullNumber(current.pr_url);
94964
- if (!pullNumber) {
94965
- throw new ScmMergeError(
94966
- `Could not extract PR number from URL: ${current.pr_url}`,
94967
- "bad-pr-url"
95056
+ try {
95057
+ const raw = fs30.readFileSync(path28.join(dir, "package.json"), "utf-8");
95058
+ const pkg = JSON.parse(raw);
95059
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
95060
+ } catch {
95061
+ return "unknown";
95062
+ }
95063
+ }
95064
+ function applyPlaceholders(content, version) {
95065
+ return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
95066
+ }
95067
+ function applyCommandPlaceholders(content, version) {
95068
+ return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
95069
+ }
95070
+ var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
95071
+ function findKitCommandsDir(start) {
95072
+ let dir = start;
95073
+ for (let i2 = 0; i2 < 6; i2++) {
95074
+ const candidate = path28.join(
95075
+ dir,
95076
+ "templates",
95077
+ "project",
95078
+ "common",
95079
+ ".claude",
95080
+ "commands"
94968
95081
  );
95082
+ if (fs30.existsSync(candidate)) return candidate;
95083
+ const parent = path28.dirname(dir);
95084
+ if (parent === dir) break;
95085
+ dir = parent;
94969
95086
  }
94970
- const instance = args.instance ?? current.project_id;
94971
- const gitBase = await resolveGitBase(current.parent_branch, args.projectDir);
94972
- const wantMigrate = args.waitMigrate !== false;
94973
- let authVerified = false;
94974
- if (wantMigrate && args.verifyMigrateAuth) {
94975
- const probe = await args.verifyMigrateAuth();
94976
- if (!probe.ok) {
94977
- throw new ScmMergeError(
94978
- `Migrate-auth precondition failed: ${probe.detail ?? "the Databricks credential is not usable"}. Refusing to merge: the downstream migrate would promote git without applying the schema. Refresh your credential (databricks auth login), then re-run.`,
94979
- "migrate-auth"
94980
- );
95087
+ throw new Error(
95088
+ `Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
95089
+ );
95090
+ }
95091
+ function parsePinnedVersion(content) {
95092
+ const m2 = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
95093
+ return m2 ? m2[1] : void 0;
95094
+ }
95095
+ function detectCommandDrift(args) {
95096
+ const projectCommandsDir = path28.join(args.projectDir, ".claude", "commands");
95097
+ const here = path28.dirname(new URL(importMetaUrl).pathname);
95098
+ const kitCommandsDir = args.kitDir ? path28.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
95099
+ const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
95100
+ const templateFiles = fs30.existsSync(kitCommandsDir) ? fs30.readdirSync(kitCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
95101
+ const projectFiles = fs30.existsSync(projectCommandsDir) ? fs30.readdirSync(projectCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
95102
+ const seen = /* @__PURE__ */ new Set();
95103
+ const files = [];
95104
+ for (const name of templateFiles) {
95105
+ seen.add(name);
95106
+ const projectPath2 = path28.join(projectCommandsDir, name);
95107
+ const templatePath = path28.join(kitCommandsDir, name);
95108
+ const templateRaw = fs30.readFileSync(templatePath, "utf8");
95109
+ if (!fs30.existsSync(projectPath2)) {
95110
+ files.push({ name, status: "missing", kit_version: kitVersion });
95111
+ continue;
95112
+ }
95113
+ const projectContent = fs30.readFileSync(projectPath2, "utf8");
95114
+ const pinned = parsePinnedVersion(projectContent);
95115
+ const versionForCompare = pinned ?? kitVersion;
95116
+ const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
95117
+ if (projectContent === templateContent) {
95118
+ files.push({
95119
+ name,
95120
+ status: "unchanged",
95121
+ pinned_version: pinned,
95122
+ kit_version: kitVersion
95123
+ });
95124
+ } else {
95125
+ files.push({
95126
+ name,
95127
+ status: "drifted",
95128
+ pinned_version: pinned,
95129
+ kit_version: kitVersion,
95130
+ diff: unifiedDiff(name, projectContent, templateContent)
95131
+ });
94981
95132
  }
94982
- authVerified = true;
94983
95133
  }
94984
- let paired;
94985
- try {
94986
- paired = await mergePairedPullRequest({
94987
- ownerRepo,
94988
- pullNumber,
94989
- lakebaseInstance: instance,
94990
- method: args.method ?? "squash"
95134
+ for (const name of projectFiles) {
95135
+ if (seen.has(name)) continue;
95136
+ files.push({ name, status: "extra", kit_version: kitVersion });
95137
+ }
95138
+ const order = {
95139
+ drifted: 0,
95140
+ missing: 1,
95141
+ extra: 2,
95142
+ unchanged: 3
95143
+ };
95144
+ files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
95145
+ const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
95146
+ return { overall: hasDrift ? "drift" : "ok", files };
95147
+ }
95148
+ function readKitVersionFromCommandsDir(kitCommandsDir) {
95149
+ let dir = kitCommandsDir;
95150
+ for (let i2 = 0; i2 < 5; i2++) {
95151
+ dir = path28.dirname(dir);
95152
+ }
95153
+ try {
95154
+ const raw = fs30.readFileSync(path28.join(dir, "package.json"), "utf-8");
95155
+ const pkg = JSON.parse(raw);
95156
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
95157
+ } catch {
95158
+ return "unknown";
95159
+ }
95160
+ }
95161
+ function detectScaffoldedDrift(args) {
95162
+ const workflows = detectWorkflowDrift(args);
95163
+ const commands = detectCommandDrift(args);
95164
+ return {
95165
+ overall: workflows.overall === "drift" || commands.overall === "drift" ? "drift" : "ok",
95166
+ workflows,
95167
+ commands
95168
+ };
95169
+ }
95170
+ function updateWorkflows(args) {
95171
+ const projectWorkflowsDir = path28.join(
95172
+ args.projectDir,
95173
+ ".github",
95174
+ "workflows"
95175
+ );
95176
+ const here = path28.dirname(new URL(importMetaUrl).pathname);
95177
+ const kitWorkflowsDir = args.kitDir ? path28.join(
95178
+ args.kitDir,
95179
+ "templates",
95180
+ "project",
95181
+ "common",
95182
+ ".github",
95183
+ "workflows"
95184
+ ) : findKitTemplatesDir(here);
95185
+ const substitute = args.substitute !== false;
95186
+ const dryRun = args.dryRun === true;
95187
+ const pruneExtras = args.pruneExtras === true;
95188
+ const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
95189
+ const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
95190
+ if (!dryRun && templateFiles.length > 0 && !fs30.existsSync(projectWorkflowsDir)) {
95191
+ fs30.mkdirSync(projectWorkflowsDir, { recursive: true });
95192
+ }
95193
+ const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
95194
+ const seen = /* @__PURE__ */ new Set();
95195
+ const files = [];
95196
+ for (const name of templateFiles) {
95197
+ seen.add(name);
95198
+ const projectPath2 = path28.join(projectWorkflowsDir, name);
95199
+ const templatePath = path28.join(kitWorkflowsDir, name);
95200
+ const templateRaw = fs30.readFileSync(templatePath, "utf-8");
95201
+ const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
95202
+ const existed = fs30.existsSync(projectPath2);
95203
+ const current = existed ? fs30.readFileSync(projectPath2, "utf-8") : "";
95204
+ let outcome;
95205
+ if (!existed) {
95206
+ outcome = "added";
95207
+ } else if (current === desired) {
95208
+ outcome = "unchanged";
95209
+ } else {
95210
+ outcome = "updated";
95211
+ }
95212
+ if (!dryRun && outcome !== "unchanged") {
95213
+ fs30.writeFileSync(projectPath2, desired);
95214
+ }
95215
+ files.push({ name, outcome });
95216
+ }
95217
+ if (pruneExtras) {
95218
+ for (const name of projectFiles) {
95219
+ if (seen.has(name)) continue;
95220
+ const projectPath2 = path28.join(projectWorkflowsDir, name);
95221
+ if (!dryRun) {
95222
+ fs30.unlinkSync(projectPath2);
95223
+ }
95224
+ files.push({ name, outcome: "removed" });
95225
+ }
95226
+ }
95227
+ const order = {
95228
+ added: 0,
95229
+ updated: 1,
95230
+ removed: 2,
95231
+ unchanged: 3
95232
+ };
95233
+ files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
95234
+ const changed = files.some((f3) => f3.outcome !== "unchanged");
95235
+ return { files, changed };
95236
+ }
95237
+
95238
+ // scripts/lakebase/scm-merge.ts
95239
+ async function reconcileTierToOrigin(args) {
95240
+ const { cwd, tier } = args;
95241
+ try {
95242
+ const head = await getCurrentBranch({ cwd });
95243
+ if (head === tier) {
95244
+ await exec2(`git pull --ff-only`, { cwd, timeout: 3e4 });
95245
+ } else {
95246
+ await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
95247
+ }
95248
+ return null;
95249
+ } catch (err) {
95250
+ return `local reconcile of ${tier} to origin/${tier} failed: ${err instanceof Error ? err.message : String(err)}. Reconcile it by hand (git checkout ${tier} && git pull --ff-only).`;
95251
+ }
95252
+ }
95253
+ var ScmMergeError = class extends Error {
95254
+ constructor(message, code) {
95255
+ super(message);
95256
+ this.code = code;
95257
+ this.name = "ScmMergeError";
95258
+ }
95259
+ code;
95260
+ };
95261
+ var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
95262
+ var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
95263
+ function defaultMigratePredicate(run, mergedAt) {
95264
+ if (!run.createdAt) return false;
95265
+ const created = Date.parse(run.createdAt);
95266
+ if (!Number.isFinite(created)) return false;
95267
+ if (run.event && run.event !== "push") return false;
95268
+ return created >= mergedAt.getTime() - 5e3;
95269
+ }
95270
+ function shaMigratePredicate(mergeCommitSha) {
95271
+ return (run) => {
95272
+ if (run.event && run.event !== "push") return false;
95273
+ return !!run.headSha && run.headSha === mergeCommitSha;
95274
+ };
95275
+ }
95276
+ async function defaultRefreshPromoteWorkflows(projectDir) {
95277
+ const drift = detectWorkflowDrift({ projectDir });
95278
+ const touched = drift.files.filter((f3) => f3.status === "drifted").map((f3) => f3.name);
95279
+ if (touched.length === 0) return { refreshed: false };
95280
+ const result = updateWorkflows({ projectDir });
95281
+ if (!result.changed) return { refreshed: false };
95282
+ await exec2(`git add ${shellEscape2(".github/workflows")}`, { cwd: projectDir, timeout: 1e4 });
95283
+ const staged = await exec2("git diff --cached --name-only -- .github/workflows", {
95284
+ cwd: projectDir,
95285
+ timeout: 1e4
95286
+ });
95287
+ if (!staged.trim()) return { refreshed: false };
95288
+ await exec2(
95289
+ `git commit -m ${shellEscape2(
95290
+ "chore(ci): refresh scaffolded workflows to pinned substrate (promote uses apply-tier)"
95291
+ )} -- .github/workflows`,
95292
+ { cwd: projectDir, timeout: 15e3 }
95293
+ );
95294
+ await pushCurrentBranchForPr({ cwd: projectDir });
95295
+ return { refreshed: true, detail: `refreshed + pushed ${touched.join(", ") || "workflow(s)"} onto the PR head` };
95296
+ }
95297
+ async function mergeFeature(args) {
95298
+ const current = readWorkflowState(args.projectDir);
95299
+ if (!current) {
95300
+ throw new ScmMergeError(
95301
+ "No SCM workflow state. wait-ci first.",
95302
+ "no-state-file"
95303
+ );
95304
+ }
95305
+ if (current.state !== "ci-green") {
95306
+ throw new ScmMergeError(
95307
+ `merge refuses state "${current.state}". Allowed predecessor: ci-green.`,
95308
+ "bad-precondition"
95309
+ );
95310
+ }
95311
+ if (!current.pr_url) {
95312
+ throw new ScmMergeError(
95313
+ "ci-green row is missing pr_url; cannot resolve the PR to merge.",
95314
+ "no-pr-url"
95315
+ );
95316
+ }
95317
+ if (!current.branch || !current.parent_branch) {
95318
+ throw new ScmMergeError(
95319
+ "ci-green row missing branch / parent_branch; refusing to merge.",
95320
+ "bad-precondition"
95321
+ );
95322
+ }
95323
+ const ownerRepo = await getOwnerRepo(args.projectDir);
95324
+ if (!ownerRepo) {
95325
+ throw new ScmMergeError(
95326
+ "No GitHub remote found at origin.",
95327
+ "no-github-remote"
95328
+ );
95329
+ }
95330
+ const pullNumber = extractPullNumber(current.pr_url);
95331
+ if (!pullNumber) {
95332
+ throw new ScmMergeError(
95333
+ `Could not extract PR number from URL: ${current.pr_url}`,
95334
+ "bad-pr-url"
95335
+ );
95336
+ }
95337
+ const instance = args.instance ?? current.project_id;
95338
+ const gitBase = await resolveGitBase(current.parent_branch, args.projectDir);
95339
+ const wantMigrate = args.waitMigrate !== false;
95340
+ let authVerified = false;
95341
+ if (wantMigrate && args.verifyMigrateAuth) {
95342
+ const probe = await args.verifyMigrateAuth();
95343
+ if (!probe.ok) {
95344
+ throw new ScmMergeError(
95345
+ `Migrate-auth precondition failed: ${probe.detail ?? "the Databricks credential is not usable"}. Refusing to merge: the downstream migrate would promote git without applying the schema. Refresh your credential (databricks auth login), then re-run.`,
95346
+ "migrate-auth"
95347
+ );
95348
+ }
95349
+ authVerified = true;
95350
+ }
95351
+ const preflightNotes = [];
95352
+ {
95353
+ const refresh = args.refreshWorkflows ?? (() => defaultRefreshPromoteWorkflows(args.projectDir));
95354
+ try {
95355
+ const r2 = await refresh();
95356
+ if (r2.refreshed) {
95357
+ preflightNotes.push(`Refreshed stale CI workflows before merge${r2.detail ? ` (${r2.detail})` : ""}.`);
95358
+ }
95359
+ } catch (err) {
95360
+ preflightNotes.push(
95361
+ `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').`
95362
+ );
95363
+ }
95364
+ }
95365
+ let paired;
95366
+ try {
95367
+ paired = await mergePairedPullRequest({
95368
+ ownerRepo,
95369
+ pullNumber,
95370
+ lakebaseInstance: instance,
95371
+ method: args.method ?? "squash"
94991
95372
  });
94992
95373
  } catch (err) {
94993
95374
  throw new ScmMergeError(
@@ -94995,7 +95376,7 @@ async function mergeFeature(args) {
94995
95376
  "merge-failed"
94996
95377
  );
94997
95378
  }
94998
- const warnings = [...paired.warnings];
95379
+ const warnings = [...preflightNotes, ...paired.warnings];
94999
95380
  let localBranchDeleted = false;
95000
95381
  let headAfter = current.branch;
95001
95382
  if (!args.skipLocalCleanup) {
@@ -95192,7 +95573,7 @@ init_cjs_shims();
95192
95573
 
95193
95574
  // scripts/git/branches.ts
95194
95575
  init_cjs_shims();
95195
- async function currentBranchName(cwd) {
95576
+ async function currentBranchName2(cwd) {
95196
95577
  try {
95197
95578
  return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
95198
95579
  } catch {
@@ -95211,7 +95592,7 @@ async function listLocalBranches(args) {
95211
95592
  return [];
95212
95593
  }
95213
95594
  if (!raw) return [];
95214
- const current = await currentBranchName(cwd);
95595
+ const current = await currentBranchName2(cwd);
95215
95596
  return raw.split("\n").filter(Boolean).map((line) => {
95216
95597
  const [name, tracking, trackInfo] = line.split("|");
95217
95598
  let ahead = 0;
@@ -95376,15 +95757,15 @@ function parentForTopology(t2, defaultLeaf) {
95376
95757
 
95377
95758
  // scripts/lakebase/scm-doctor.ts
95378
95759
  init_cjs_shims();
95379
- var fs30 = __toESM(require("fs"), 1);
95380
- var path28 = __toESM(require("path"), 1);
95760
+ var fs31 = __toESM(require("fs"), 1);
95761
+ var path29 = __toESM(require("path"), 1);
95381
95762
  var FEATURE_PREFIX = "feature/";
95382
95763
  var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
95383
95764
  function readEnv(projectDir) {
95384
- const envPath = path28.join(projectDir, ".env");
95765
+ const envPath = path29.join(projectDir, ".env");
95385
95766
  const out = /* @__PURE__ */ new Map();
95386
- if (!fs30.existsSync(envPath)) return out;
95387
- const lines = fs30.readFileSync(envPath, "utf8").split("\n");
95767
+ if (!fs31.existsSync(envPath)) return out;
95768
+ const lines = fs31.readFileSync(envPath, "utf8").split("\n");
95388
95769
  for (const line of lines) {
95389
95770
  const m2 = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
95390
95771
  if (m2) out.set(m2[1], m2[2].replace(/^["']|["']$/g, ""));
@@ -95439,9 +95820,9 @@ async function runDoctor(args, deps = {}) {
95439
95820
  }
95440
95821
  for (const wf of ["pr.yml", "merge.yml"]) {
95441
95822
  try {
95442
- const p = path28.join(projectDir, ".github", "workflows", wf);
95443
- if (!fs30.existsSync(p)) continue;
95444
- const body = fs30.readFileSync(p, "utf8");
95823
+ const p = path29.join(projectDir, ".github", "workflows", wf);
95824
+ if (!fs31.existsSync(p)) continue;
95825
+ const body = fs31.readFileSync(p, "utf8");
95445
95826
  if (/lakebase-scm-utils#v\d/.test(body)) {
95446
95827
  findings.push({
95447
95828
  id: "ci-workflow-substrate-pin",
@@ -95656,7 +96037,7 @@ async function fixFinding(args) {
95656
96037
  );
95657
96038
  }
95658
96039
  const sanitized = sanitizeBranchName(branch);
95659
- const envFile = path28.join(args.projectDir, ".env");
96040
+ const envFile = path29.join(args.projectDir, ".env");
95660
96041
  updateEnvConnection({
95661
96042
  envPath: envFile,
95662
96043
  projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
@@ -95761,291 +96142,6 @@ init_cjs_shims();
95761
96142
  var fs32 = __toESM(require("fs"), 1);
95762
96143
  var path30 = __toESM(require("path"), 1);
95763
96144
  var cp6 = __toESM(require("child_process"), 1);
95764
-
95765
- // scripts/lakebase/workflow-drift.ts
95766
- init_cjs_shims();
95767
- var fs31 = __toESM(require("fs"), 1);
95768
- var path29 = __toESM(require("path"), 1);
95769
- function findKitTemplatesDir(start) {
95770
- let dir = start;
95771
- for (let i2 = 0; i2 < 6; i2++) {
95772
- const candidate = path29.join(
95773
- dir,
95774
- "templates",
95775
- "project",
95776
- "common",
95777
- ".github",
95778
- "workflows"
95779
- );
95780
- if (fs31.existsSync(candidate)) return candidate;
95781
- const parent = path29.dirname(dir);
95782
- if (parent === dir) break;
95783
- dir = parent;
95784
- }
95785
- throw new Error(
95786
- `Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
95787
- );
95788
- }
95789
- function unifiedDiff(name, projectContent, templateContent) {
95790
- if (projectContent === templateContent) return "";
95791
- const a = projectContent.split("\n");
95792
- const b = templateContent.split("\n");
95793
- const out = [`--- project/${name}`, `+++ template/${name}`];
95794
- const max = Math.max(a.length, b.length);
95795
- for (let i2 = 0; i2 < max; i2++) {
95796
- const av = a[i2];
95797
- const bv = b[i2];
95798
- if (av === bv) continue;
95799
- if (av !== void 0) out.push(`-${i2 + 1}: ${av}`);
95800
- if (bv !== void 0) out.push(`+${i2 + 1}: ${bv}`);
95801
- }
95802
- return out.join("\n");
95803
- }
95804
- function detectWorkflowDrift(args) {
95805
- const projectWorkflowsDir = path29.join(
95806
- args.projectDir,
95807
- ".github",
95808
- "workflows"
95809
- );
95810
- const here = path29.dirname(new URL(importMetaUrl).pathname);
95811
- const kitWorkflowsDir = args.kitDir ? path29.join(
95812
- args.kitDir,
95813
- "templates",
95814
- "project",
95815
- "common",
95816
- ".github",
95817
- "workflows"
95818
- ) : findKitTemplatesDir(here);
95819
- const templateFiles = fs31.existsSync(kitWorkflowsDir) ? fs31.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
95820
- const projectFiles = fs31.existsSync(projectWorkflowsDir) ? fs31.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
95821
- const version = readKitVersion(kitWorkflowsDir);
95822
- const seen = /* @__PURE__ */ new Set();
95823
- const files = [];
95824
- for (const name of templateFiles) {
95825
- seen.add(name);
95826
- const projectPath2 = path29.join(projectWorkflowsDir, name);
95827
- const templatePath = path29.join(kitWorkflowsDir, name);
95828
- if (!fs31.existsSync(projectPath2)) {
95829
- files.push({ name, status: "missing" });
95830
- continue;
95831
- }
95832
- const projectContent = fs31.readFileSync(projectPath2, "utf8");
95833
- const templateContent = applyPlaceholders(fs31.readFileSync(templatePath, "utf8"), version);
95834
- if (projectContent === templateContent) {
95835
- files.push({ name, status: "unchanged" });
95836
- } else {
95837
- files.push({
95838
- name,
95839
- status: "drifted",
95840
- diff: unifiedDiff(name, projectContent, templateContent)
95841
- });
95842
- }
95843
- }
95844
- for (const name of projectFiles) {
95845
- if (seen.has(name)) continue;
95846
- files.push({ name, status: "extra" });
95847
- }
95848
- const order = {
95849
- drifted: 0,
95850
- missing: 1,
95851
- extra: 2,
95852
- unchanged: 3
95853
- };
95854
- files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
95855
- const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
95856
- return {
95857
- overall: hasDrift ? "drift" : "ok",
95858
- files
95859
- };
95860
- }
95861
- function readKitVersion(kitWorkflowsDir) {
95862
- let dir = kitWorkflowsDir;
95863
- for (let i2 = 0; i2 < 5; i2++) {
95864
- dir = path29.dirname(dir);
95865
- }
95866
- try {
95867
- const raw = fs31.readFileSync(path29.join(dir, "package.json"), "utf-8");
95868
- const pkg = JSON.parse(raw);
95869
- return typeof pkg.version === "string" ? pkg.version : "unknown";
95870
- } catch {
95871
- return "unknown";
95872
- }
95873
- }
95874
- function applyPlaceholders(content, version) {
95875
- return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
95876
- }
95877
- function applyCommandPlaceholders(content, version) {
95878
- return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
95879
- }
95880
- var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
95881
- function findKitCommandsDir(start) {
95882
- let dir = start;
95883
- for (let i2 = 0; i2 < 6; i2++) {
95884
- const candidate = path29.join(
95885
- dir,
95886
- "templates",
95887
- "project",
95888
- "common",
95889
- ".claude",
95890
- "commands"
95891
- );
95892
- if (fs31.existsSync(candidate)) return candidate;
95893
- const parent = path29.dirname(dir);
95894
- if (parent === dir) break;
95895
- dir = parent;
95896
- }
95897
- throw new Error(
95898
- `Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
95899
- );
95900
- }
95901
- function parsePinnedVersion(content) {
95902
- const m2 = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
95903
- return m2 ? m2[1] : void 0;
95904
- }
95905
- function detectCommandDrift(args) {
95906
- const projectCommandsDir = path29.join(args.projectDir, ".claude", "commands");
95907
- const here = path29.dirname(new URL(importMetaUrl).pathname);
95908
- const kitCommandsDir = args.kitDir ? path29.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
95909
- const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
95910
- const templateFiles = fs31.existsSync(kitCommandsDir) ? fs31.readdirSync(kitCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
95911
- const projectFiles = fs31.existsSync(projectCommandsDir) ? fs31.readdirSync(projectCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
95912
- const seen = /* @__PURE__ */ new Set();
95913
- const files = [];
95914
- for (const name of templateFiles) {
95915
- seen.add(name);
95916
- const projectPath2 = path29.join(projectCommandsDir, name);
95917
- const templatePath = path29.join(kitCommandsDir, name);
95918
- const templateRaw = fs31.readFileSync(templatePath, "utf8");
95919
- if (!fs31.existsSync(projectPath2)) {
95920
- files.push({ name, status: "missing", kit_version: kitVersion });
95921
- continue;
95922
- }
95923
- const projectContent = fs31.readFileSync(projectPath2, "utf8");
95924
- const pinned = parsePinnedVersion(projectContent);
95925
- const versionForCompare = pinned ?? kitVersion;
95926
- const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
95927
- if (projectContent === templateContent) {
95928
- files.push({
95929
- name,
95930
- status: "unchanged",
95931
- pinned_version: pinned,
95932
- kit_version: kitVersion
95933
- });
95934
- } else {
95935
- files.push({
95936
- name,
95937
- status: "drifted",
95938
- pinned_version: pinned,
95939
- kit_version: kitVersion,
95940
- diff: unifiedDiff(name, projectContent, templateContent)
95941
- });
95942
- }
95943
- }
95944
- for (const name of projectFiles) {
95945
- if (seen.has(name)) continue;
95946
- files.push({ name, status: "extra", kit_version: kitVersion });
95947
- }
95948
- const order = {
95949
- drifted: 0,
95950
- missing: 1,
95951
- extra: 2,
95952
- unchanged: 3
95953
- };
95954
- files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
95955
- const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
95956
- return { overall: hasDrift ? "drift" : "ok", files };
95957
- }
95958
- function readKitVersionFromCommandsDir(kitCommandsDir) {
95959
- let dir = kitCommandsDir;
95960
- for (let i2 = 0; i2 < 5; i2++) {
95961
- dir = path29.dirname(dir);
95962
- }
95963
- try {
95964
- const raw = fs31.readFileSync(path29.join(dir, "package.json"), "utf-8");
95965
- const pkg = JSON.parse(raw);
95966
- return typeof pkg.version === "string" ? pkg.version : "unknown";
95967
- } catch {
95968
- return "unknown";
95969
- }
95970
- }
95971
- function detectScaffoldedDrift(args) {
95972
- const workflows = detectWorkflowDrift(args);
95973
- const commands = detectCommandDrift(args);
95974
- return {
95975
- overall: workflows.overall === "drift" || commands.overall === "drift" ? "drift" : "ok",
95976
- workflows,
95977
- commands
95978
- };
95979
- }
95980
- function updateWorkflows(args) {
95981
- const projectWorkflowsDir = path29.join(
95982
- args.projectDir,
95983
- ".github",
95984
- "workflows"
95985
- );
95986
- const here = path29.dirname(new URL(importMetaUrl).pathname);
95987
- const kitWorkflowsDir = args.kitDir ? path29.join(
95988
- args.kitDir,
95989
- "templates",
95990
- "project",
95991
- "common",
95992
- ".github",
95993
- "workflows"
95994
- ) : findKitTemplatesDir(here);
95995
- const substitute = args.substitute !== false;
95996
- const dryRun = args.dryRun === true;
95997
- const pruneExtras = args.pruneExtras === true;
95998
- const templateFiles = fs31.existsSync(kitWorkflowsDir) ? fs31.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
95999
- const projectFiles = fs31.existsSync(projectWorkflowsDir) ? fs31.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
96000
- if (!dryRun && templateFiles.length > 0 && !fs31.existsSync(projectWorkflowsDir)) {
96001
- fs31.mkdirSync(projectWorkflowsDir, { recursive: true });
96002
- }
96003
- const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
96004
- const seen = /* @__PURE__ */ new Set();
96005
- const files = [];
96006
- for (const name of templateFiles) {
96007
- seen.add(name);
96008
- const projectPath2 = path29.join(projectWorkflowsDir, name);
96009
- const templatePath = path29.join(kitWorkflowsDir, name);
96010
- const templateRaw = fs31.readFileSync(templatePath, "utf-8");
96011
- const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
96012
- const existed = fs31.existsSync(projectPath2);
96013
- const current = existed ? fs31.readFileSync(projectPath2, "utf-8") : "";
96014
- let outcome;
96015
- if (!existed) {
96016
- outcome = "added";
96017
- } else if (current === desired) {
96018
- outcome = "unchanged";
96019
- } else {
96020
- outcome = "updated";
96021
- }
96022
- if (!dryRun && outcome !== "unchanged") {
96023
- fs31.writeFileSync(projectPath2, desired);
96024
- }
96025
- files.push({ name, outcome });
96026
- }
96027
- if (pruneExtras) {
96028
- for (const name of projectFiles) {
96029
- if (seen.has(name)) continue;
96030
- const projectPath2 = path29.join(projectWorkflowsDir, name);
96031
- if (!dryRun) {
96032
- fs31.unlinkSync(projectPath2);
96033
- }
96034
- files.push({ name, outcome: "removed" });
96035
- }
96036
- }
96037
- const order = {
96038
- added: 0,
96039
- updated: 1,
96040
- removed: 2,
96041
- unchanged: 3
96042
- };
96043
- files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
96044
- const changed = files.some((f3) => f3.outcome !== "unchanged");
96045
- return { files, changed };
96046
- }
96047
-
96048
- // scripts/lakebase/doctor.ts
96049
96145
  function readEnvFile(projectDir) {
96050
96146
  const envPath = path30.join(projectDir, ".env");
96051
96147
  if (!fs32.existsSync(envPath)) return {};
@@ -96478,7 +96574,7 @@ function checkWorkflowDrift(projectDir) {
96478
96574
  status: "warn",
96479
96575
  message: `Scaffolded workflows drift from kit: ${drifted} drifted, ${missing} missing`,
96480
96576
  detail: { files: report.files.map((f3) => ({ name: f3.name, status: f3.status })) },
96481
- hint: "Inspect via the lakebase_workflow_drift MCP tool (or detectWorkflowDrift import). Refresh manually until updateWorkflows lands."
96577
+ 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."
96482
96578
  };
96483
96579
  } catch (err) {
96484
96580
  return {
@@ -96589,9 +96685,10 @@ async function ensureLakebaseSecretAuth(args) {
96589
96685
  if (typeof pat !== "string" || !pat) {
96590
96686
  throw new Error("databricks tokens create returned no token_value");
96591
96687
  }
96592
- await runDatabricks(["secrets", "put-secret", scopeName, keyName, "--string-value", pat], {
96688
+ await runDatabricks(["secrets", "put-secret", "--json", "@/dev/stdin"], {
96593
96689
  profile,
96594
- timeout: timeoutMs
96690
+ timeout: timeoutMs,
96691
+ input: JSON.stringify({ scope: scopeName, key: keyName, string_value: pat })
96595
96692
  });
96596
96693
  let aclGranted = false;
96597
96694
  if (servicePrincipalClientId) {
@@ -96786,6 +96883,7 @@ function isUcMissingError(msg) {
96786
96883
  createUatPairedBranch,
96787
96884
  cutBackup,
96788
96885
  databricksAuthPrereqMessage,
96886
+ defaultRefreshPromoteWorkflows,
96789
96887
  deleteAppEndpoint,
96790
96888
  deleteBranch,
96791
96889
  deleteLakebaseProject,