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

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 (51) hide show
  1. package/dist/ci-secrets-CnGrdQV-.d.cts +84 -0
  2. package/dist/ci-secrets-CnGrdQV-.d.ts +84 -0
  3. package/dist/scripts/index.cjs +2177 -2052
  4. package/dist/scripts/index.d.cts +2 -1
  5. package/dist/scripts/index.d.ts +2 -1
  6. package/dist/scripts/index.js +253 -134
  7. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
  8. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
  9. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
  10. package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
  11. package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
  12. package/dist/scripts/lakebase/detect-language.cli.js +2 -2
  13. package/dist/scripts/lakebase/doctor.cli.cjs +2 -2
  14. package/dist/scripts/lakebase/doctor.cli.js +2 -2
  15. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  16. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  17. package/dist/scripts/lakebase/index.cjs +2163 -2050
  18. package/dist/scripts/lakebase/index.d.cts +8 -0
  19. package/dist/scripts/lakebase/index.d.ts +8 -0
  20. package/dist/scripts/lakebase/index.js +243 -130
  21. package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
  22. package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
  23. package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
  24. package/dist/scripts/lakebase/new-migration.cli.js +2 -2
  25. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  26. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  27. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
  28. package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
  29. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
  30. package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
  31. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
  32. package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
  33. package/dist/scripts/lakebase/scm-merge.cli.cjs +2216 -1994
  34. package/dist/scripts/lakebase/scm-merge.cli.js +296 -74
  35. package/dist/scripts/lakebase/scm-prepare-pr.cli.cjs +479 -86
  36. package/dist/scripts/lakebase/scm-prepare-pr.cli.js +479 -86
  37. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
  38. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
  39. package/dist/scripts/lakebase/sync-ci-secrets.cli.cjs +56 -30
  40. package/dist/scripts/lakebase/sync-ci-secrets.cli.js +56 -30
  41. package/dist/scripts/util/index.cjs +142 -25
  42. package/dist/scripts/util/index.d.cts +2 -27
  43. package/dist/scripts/util/index.d.ts +2 -27
  44. package/dist/scripts/util/index.js +136 -25
  45. package/package.json +1 -1
  46. package/scripts/lakebase/create-project.ts +2 -2
  47. package/scripts/lakebase/scm-merge.ts +21 -0
  48. package/scripts/lakebase/scm-prepare-pr.ts +10 -1
  49. package/scripts/lakebase/sync-ci-secrets.cli.ts +2 -2
  50. package/scripts/util/ci-secrets.ts +212 -26
  51. package/templates/project/common/scripts/create-token-and-sync-secrets.sh +9 -86
@@ -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 path13 from "path";
1460
+ import * as path14 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 = (path14, cursorValue) => `The cursor at "${path14.join(
2552
+ var generateMessage = (path15, cursorValue) => `The cursor at "${path15.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, path14 = []) => {
2593
+ var deepFindPathToProperty = (object, searchProp, path15 = []) => {
2594
2594
  for (const key of Object.keys(object)) {
2595
- const currentPath = [...path14, key];
2595
+ const currentPath = [...path15, 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, path14 = []) => {
2610
2610
  }
2611
2611
  return [];
2612
2612
  };
2613
- var get = (object, path14) => {
2614
- return path14.reduce((current, nextProperty) => current[nextProperty], object);
2613
+ var get = (object, path15) => {
2614
+ return path15.reduce((current, nextProperty) => current[nextProperty], object);
2615
2615
  };
2616
- var set = (object, path14, mutator) => {
2617
- const lastProperty = path14[path14.length - 1];
2618
- const parentPath = [...path14].slice(0, -1);
2616
+ var set = (object, path15, mutator) => {
2617
+ const lastProperty = path15[path15.length - 1];
2618
+ const parentPath = [...path15].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 path14 = findPaginatedResourcePath(response1);
2671
- const nodesPath = [...path14, "nodes"];
2670
+ const path15 = findPaginatedResourcePath(response1);
2671
+ const nodesPath = [...path15, "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 = [...path14, "edges"];
2678
+ const edgesPath = [...path15, "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 = [...path14, "pageInfo"];
2685
+ const pageInfoPath = [...path15, "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
- (path14) => path14.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
5106
+ (path15) => path15.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");
@@ -8378,9 +8378,9 @@ function asBranchUid(s) {
8378
8378
  }
8379
8379
  return s;
8380
8380
  }
8381
- function branchNameFromResourcePath(path14) {
8382
- if (!path14.includes("/branches/")) return null;
8383
- const leaf = path14.split("/branches/").pop();
8381
+ function branchNameFromResourcePath(path15) {
8382
+ if (!path15.includes("/branches/")) return null;
8383
+ const leaf = path15.split("/branches/").pop();
8384
8384
  if (!leaf) return null;
8385
8385
  try {
8386
8386
  return asBranchName(leaf);
@@ -8817,6 +8817,219 @@ async function getOwnerRepo(cwd) {
8817
8817
  }
8818
8818
  }
8819
8819
 
8820
+ // scripts/util/ci-secrets.ts
8821
+ init_esm_shims();
8822
+ import * as path3 from "path";
8823
+
8824
+ // scripts/github/secrets.ts
8825
+ init_esm_shims();
8826
+ import sodium from "tweetsodium";
8827
+ var GitHubSecretsError = class extends Error {
8828
+ status;
8829
+ constructor(message, status) {
8830
+ super(message);
8831
+ this.name = "GitHubSecretsError";
8832
+ this.status = status;
8833
+ }
8834
+ };
8835
+ async function getOctokit() {
8836
+ const token = await resolveGitHubToken();
8837
+ return new Octokit2({ auth: token });
8838
+ }
8839
+ function wrap2(err, context) {
8840
+ if (err instanceof RequestError) {
8841
+ throw new GitHubSecretsError(`${context}: ${err.message}`, err.status);
8842
+ }
8843
+ if (err instanceof Error) {
8844
+ throw new GitHubSecretsError(`${context}: ${err.message}`);
8845
+ }
8846
+ throw new GitHubSecretsError(context);
8847
+ }
8848
+ function encryptSecret(publicKey, secretValue) {
8849
+ const keyBytes = Buffer.from(publicKey, "base64");
8850
+ const messageBytes = Buffer.from(secretValue);
8851
+ const encryptedBytes = sodium.seal(messageBytes, keyBytes);
8852
+ return Buffer.from(encryptedBytes).toString("base64");
8853
+ }
8854
+ async function setRepoSecret(ownerRepo, secretName, secretValue) {
8855
+ try {
8856
+ const { owner, repo } = parseOwnerRepo(ownerRepo);
8857
+ const octokit2 = await getOctokit();
8858
+ const { data: keyData } = await octokit2.rest.actions.getRepoPublicKey({ owner, repo });
8859
+ const encryptedValue = encryptSecret(keyData.key, secretValue);
8860
+ await octokit2.rest.actions.createOrUpdateRepoSecret({
8861
+ owner,
8862
+ repo,
8863
+ secret_name: secretName,
8864
+ encrypted_value: encryptedValue,
8865
+ key_id: keyData.key_id
8866
+ });
8867
+ } catch (err) {
8868
+ if (err instanceof GitHubSecretsError) throw err;
8869
+ wrap2(err, `Failed to set secret ${secretName} on ${ownerRepo}`);
8870
+ }
8871
+ }
8872
+ async function setRepoSecrets(ownerRepo, secrets) {
8873
+ for (const [name, value] of Object.entries(secrets)) {
8874
+ if (!value) {
8875
+ throw new GitHubSecretsError(`Missing value for secret ${name}`);
8876
+ }
8877
+ }
8878
+ for (const [name, value] of Object.entries(secrets)) {
8879
+ await setRepoSecret(ownerRepo, name, value);
8880
+ }
8881
+ }
8882
+ async function listSecretNames(ownerRepo) {
8883
+ try {
8884
+ const { owner, repo } = parseOwnerRepo(ownerRepo);
8885
+ const octokit2 = await getOctokit();
8886
+ const { data } = await octokit2.rest.actions.listRepoSecrets({ owner, repo });
8887
+ return data.secrets.map((s) => s.name);
8888
+ } catch {
8889
+ return [];
8890
+ }
8891
+ }
8892
+
8893
+ // scripts/util/ci-secrets.ts
8894
+ var REQUIRED_CI_SECRETS = ["DATABRICKS_HOST", "LAKEBASE_PROJECT_ID", "DATABRICKS_TOKEN"];
8895
+ var CI_TOKEN_LIFETIME_SECONDS = 7776e3;
8896
+ var CI_TOKEN_REMINT_MARGIN_SECONDS = 86400;
8897
+ function ciTokenComment(ownerRepo) {
8898
+ const repoName = ownerRepo.includes("/") ? ownerRepo.slice(ownerRepo.lastIndexOf("/") + 1) : ownerRepo;
8899
+ return `GitHub Actions (${repoName})`;
8900
+ }
8901
+ async function missingCiSecrets(ownerRepo) {
8902
+ const present = new Set(await listSecretNames(ownerRepo));
8903
+ return REQUIRED_CI_SECRETS.filter((n) => !present.has(n));
8904
+ }
8905
+ async function mintCiToken(args) {
8906
+ try {
8907
+ const raw = await runDatabricks(
8908
+ ["tokens", "create", "--comment", args.comment, "--lifetime-seconds", String(args.lifetimeSeconds), "-o", "json"],
8909
+ { host: args.databricksHost, cwd: args.projectDir, timeout: 3e4 }
8910
+ );
8911
+ const parsed = JSON.parse(raw.slice(Math.max(0, raw.indexOf("{"))));
8912
+ const token = parsed.token_value || parsed.token || "";
8913
+ if (token) return token;
8914
+ } catch {
8915
+ }
8916
+ try {
8917
+ const raw = await runDatabricks(["auth", "token", "-o", "json"], {
8918
+ host: args.databricksHost,
8919
+ cwd: args.projectDir,
8920
+ timeout: 3e4
8921
+ });
8922
+ const parsed = JSON.parse(raw.slice(Math.max(0, raw.indexOf("{"))));
8923
+ return parsed.access_token || "";
8924
+ } catch {
8925
+ return "";
8926
+ }
8927
+ }
8928
+ async function syncCiSecrets(args) {
8929
+ const ownerRepo = args.ownerRepo ?? await getOwnerRepo(args.projectDir);
8930
+ if (!ownerRepo) {
8931
+ throw new Error("Could not resolve GitHub repository from git remote");
8932
+ }
8933
+ if (!args.databricksHost) {
8934
+ throw new Error("syncCiSecrets: databricksHost is required");
8935
+ }
8936
+ if (!args.lakebaseProjectId) {
8937
+ throw new Error("syncCiSecrets: lakebaseProjectId is required");
8938
+ }
8939
+ const lifetime = args.lifetimeSeconds ?? CI_TOKEN_LIFETIME_SECONDS;
8940
+ const comment = args.comment ?? ciTokenComment(ownerRepo);
8941
+ const secrets = {
8942
+ DATABRICKS_HOST: args.databricksHost,
8943
+ LAKEBASE_PROJECT_ID: args.lakebaseProjectId
8944
+ };
8945
+ const token = await mintCiToken({
8946
+ databricksHost: args.databricksHost,
8947
+ projectDir: args.projectDir,
8948
+ comment,
8949
+ lifetimeSeconds: lifetime
8950
+ });
8951
+ if (token) secrets.DATABRICKS_TOKEN = token;
8952
+ await setRepoSecrets(ownerRepo, secrets);
8953
+ }
8954
+ async function ciTokenExpiry(args) {
8955
+ const comment = ciTokenComment(args.ownerRepo);
8956
+ let raw;
8957
+ try {
8958
+ raw = await runDatabricks(["tokens", "list", "-o", "json"], {
8959
+ host: args.databricksHost,
8960
+ cwd: args.projectDir,
8961
+ timeout: 3e4
8962
+ });
8963
+ } catch {
8964
+ return null;
8965
+ }
8966
+ let infos;
8967
+ try {
8968
+ const parsed = JSON.parse(raw.slice(Math.max(0, raw.indexOf("["))));
8969
+ infos = Array.isArray(parsed) ? parsed : [];
8970
+ } catch {
8971
+ return null;
8972
+ }
8973
+ const expiries = infos.filter((t) => t.comment === comment).map((t) => t.expiry_time === -1 ? Number.POSITIVE_INFINITY : Number(t.expiry_time)).filter((n) => Number.isFinite(n) || n === Number.POSITIVE_INFINITY);
8974
+ if (expiries.length === 0) return null;
8975
+ return Math.max(...expiries);
8976
+ }
8977
+ async function ensureCiSecretsFresh(args) {
8978
+ const now = args.now ?? Date.now;
8979
+ const marginMs = (args.marginSeconds ?? CI_TOKEN_REMINT_MARGIN_SECONDS) * 1e3;
8980
+ const ownerRepo = args.ownerRepo ?? await getOwnerRepo(args.projectDir);
8981
+ if (!ownerRepo) {
8982
+ throw new Error("Could not resolve GitHub repository from git remote");
8983
+ }
8984
+ const sync = () => syncCiSecrets({
8985
+ projectDir: args.projectDir,
8986
+ databricksHost: args.databricksHost,
8987
+ lakebaseProjectId: args.lakebaseProjectId,
8988
+ ownerRepo
8989
+ });
8990
+ const missing = await missingCiSecrets(ownerRepo);
8991
+ if (missing.length > 0) {
8992
+ await sync();
8993
+ return { action: "provisioned", reason: `CI secret(s) were missing (${missing.join(", ")}); provisioned.` };
8994
+ }
8995
+ const expiry = await ciTokenExpiry({ projectDir: args.projectDir, databricksHost: args.databricksHost, ownerRepo });
8996
+ if (expiry === null) {
8997
+ await sync();
8998
+ return { action: "reminted", reason: "no live CI token found (expired or minted under a different identity); re-minted." };
8999
+ }
9000
+ if (expiry !== Number.POSITIVE_INFINITY && expiry < now() + marginMs) {
9001
+ await sync();
9002
+ const hrs = Math.max(0, Math.round((expiry - now()) / 36e5));
9003
+ return { action: "reminted", reason: `CI token expires in ~${hrs}h (within the re-mint margin); re-minted.` };
9004
+ }
9005
+ return { action: "ok", reason: "CI token is current." };
9006
+ }
9007
+ async function ensureCiSecretsFreshFromEnv(projectDir, opts) {
9008
+ const envPath = path3.join(projectDir, ".env");
9009
+ const databricksHost = readEnvVar(envPath, "DATABRICKS_HOST");
9010
+ const lakebaseProjectId = readEnvVar(envPath, "LAKEBASE_PROJECT_ID");
9011
+ if (!databricksHost || !lakebaseProjectId) {
9012
+ return {
9013
+ action: "skipped",
9014
+ reason: "CI-auth preflight skipped: .env is missing DATABRICKS_HOST / LAKEBASE_PROJECT_ID."
9015
+ };
9016
+ }
9017
+ try {
9018
+ return await ensureCiSecretsFresh({
9019
+ projectDir,
9020
+ databricksHost,
9021
+ lakebaseProjectId,
9022
+ ownerRepo: opts?.ownerRepo,
9023
+ marginSeconds: opts?.marginSeconds
9024
+ });
9025
+ } catch (err) {
9026
+ return {
9027
+ action: "failed",
9028
+ reason: `CI-auth preflight could not re-mint (${err instanceof Error ? err.message : String(err)}); if CI fails on auth, run lakebase-sync-ci-secrets.`
9029
+ };
9030
+ }
9031
+ }
9032
+
8820
9033
  // scripts/util/poll-until.ts
8821
9034
  init_esm_shims();
8822
9035
 
@@ -8911,11 +9124,11 @@ async function resolveGitBase(parentBranch, cwd) {
8911
9124
  // scripts/lakebase/workflow-drift.ts
8912
9125
  init_esm_shims();
8913
9126
  import * as fs3 from "fs";
8914
- import * as path3 from "path";
9127
+ import * as path4 from "path";
8915
9128
  function findKitTemplatesDir(start) {
8916
9129
  let dir = start;
8917
9130
  for (let i = 0; i < 6; i++) {
8918
- const candidate = path3.join(
9131
+ const candidate = path4.join(
8919
9132
  dir,
8920
9133
  "templates",
8921
9134
  "project",
@@ -8924,7 +9137,7 @@ function findKitTemplatesDir(start) {
8924
9137
  "workflows"
8925
9138
  );
8926
9139
  if (fs3.existsSync(candidate)) return candidate;
8927
- const parent = path3.dirname(dir);
9140
+ const parent = path4.dirname(dir);
8928
9141
  if (parent === dir) break;
8929
9142
  dir = parent;
8930
9143
  }
@@ -8948,13 +9161,13 @@ function unifiedDiff(name, projectContent, templateContent) {
8948
9161
  return out.join("\n");
8949
9162
  }
8950
9163
  function detectWorkflowDrift(args) {
8951
- const projectWorkflowsDir = path3.join(
9164
+ const projectWorkflowsDir = path4.join(
8952
9165
  args.projectDir,
8953
9166
  ".github",
8954
9167
  "workflows"
8955
9168
  );
8956
- const here = path3.dirname(new URL(import.meta.url).pathname);
8957
- const kitWorkflowsDir = args.kitDir ? path3.join(
9169
+ const here = path4.dirname(new URL(import.meta.url).pathname);
9170
+ const kitWorkflowsDir = args.kitDir ? path4.join(
8958
9171
  args.kitDir,
8959
9172
  "templates",
8960
9173
  "project",
@@ -8969,8 +9182,8 @@ function detectWorkflowDrift(args) {
8969
9182
  const files = [];
8970
9183
  for (const name of templateFiles) {
8971
9184
  seen.add(name);
8972
- const projectPath2 = path3.join(projectWorkflowsDir, name);
8973
- const templatePath = path3.join(kitWorkflowsDir, name);
9185
+ const projectPath2 = path4.join(projectWorkflowsDir, name);
9186
+ const templatePath = path4.join(kitWorkflowsDir, name);
8974
9187
  if (!fs3.existsSync(projectPath2)) {
8975
9188
  files.push({ name, status: "missing" });
8976
9189
  continue;
@@ -9007,10 +9220,10 @@ function detectWorkflowDrift(args) {
9007
9220
  function readKitVersion(kitWorkflowsDir) {
9008
9221
  let dir = kitWorkflowsDir;
9009
9222
  for (let i = 0; i < 5; i++) {
9010
- dir = path3.dirname(dir);
9223
+ dir = path4.dirname(dir);
9011
9224
  }
9012
9225
  try {
9013
- const raw = fs3.readFileSync(path3.join(dir, "package.json"), "utf-8");
9226
+ const raw = fs3.readFileSync(path4.join(dir, "package.json"), "utf-8");
9014
9227
  const pkg = JSON.parse(raw);
9015
9228
  return typeof pkg.version === "string" ? pkg.version : "unknown";
9016
9229
  } catch {
@@ -9021,13 +9234,13 @@ function applyPlaceholders(content, version) {
9021
9234
  return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
9022
9235
  }
9023
9236
  function updateWorkflows(args) {
9024
- const projectWorkflowsDir = path3.join(
9237
+ const projectWorkflowsDir = path4.join(
9025
9238
  args.projectDir,
9026
9239
  ".github",
9027
9240
  "workflows"
9028
9241
  );
9029
- const here = path3.dirname(new URL(import.meta.url).pathname);
9030
- const kitWorkflowsDir = args.kitDir ? path3.join(
9242
+ const here = path4.dirname(new URL(import.meta.url).pathname);
9243
+ const kitWorkflowsDir = args.kitDir ? path4.join(
9031
9244
  args.kitDir,
9032
9245
  "templates",
9033
9246
  "project",
@@ -9048,8 +9261,8 @@ function updateWorkflows(args) {
9048
9261
  const files = [];
9049
9262
  for (const name of templateFiles) {
9050
9263
  seen.add(name);
9051
- const projectPath2 = path3.join(projectWorkflowsDir, name);
9052
- const templatePath = path3.join(kitWorkflowsDir, name);
9264
+ const projectPath2 = path4.join(projectWorkflowsDir, name);
9265
+ const templatePath = path4.join(kitWorkflowsDir, name);
9053
9266
  const templateRaw = fs3.readFileSync(templatePath, "utf-8");
9054
9267
  const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
9055
9268
  const existed = fs3.existsSync(projectPath2);
@@ -9070,7 +9283,7 @@ function updateWorkflows(args) {
9070
9283
  if (pruneExtras) {
9071
9284
  for (const name of projectFiles) {
9072
9285
  if (seen.has(name)) continue;
9073
- const projectPath2 = path3.join(projectWorkflowsDir, name);
9286
+ const projectPath2 = path4.join(projectWorkflowsDir, name);
9074
9287
  if (!dryRun) {
9075
9288
  fs3.unlinkSync(projectPath2);
9076
9289
  }
@@ -9091,7 +9304,7 @@ function updateWorkflows(args) {
9091
9304
  // scripts/lakebase/scm-workflow-state.ts
9092
9305
  init_esm_shims();
9093
9306
  import * as fs4 from "fs";
9094
- import * as path4 from "path";
9307
+ import * as path5 from "path";
9095
9308
  import { execFileSync as execFileSync4 } from "child_process";
9096
9309
  function isGitTracked(projectDir, rel) {
9097
9310
  try {
@@ -9110,7 +9323,7 @@ function ensureWorkflowStateUntracked(projectDir) {
9110
9323
  } catch {
9111
9324
  }
9112
9325
  try {
9113
- const gitignore = path4.join(projectDir, ".gitignore");
9326
+ const gitignore = path5.join(projectDir, ".gitignore");
9114
9327
  const existing = fs4.existsSync(gitignore) ? fs4.readFileSync(gitignore, "utf8") : "";
9115
9328
  if (!existing.split("\n").some((l) => l.trim() === rel)) {
9116
9329
  const sep2 = existing === "" || existing.endsWith("\n") ? "" : "\n";
@@ -9138,7 +9351,7 @@ var STATE_INDEX = SCM_STATES.reduce(
9138
9351
  );
9139
9352
  var STATE_FILE_REL = ".lakebase/workflow-state.json";
9140
9353
  function stateFilePath(projectDir) {
9141
- return path4.join(projectDir, STATE_FILE_REL);
9354
+ return path5.join(projectDir, STATE_FILE_REL);
9142
9355
  }
9143
9356
  function readWorkflowState(projectDir) {
9144
9357
  const p = stateFilePath(projectDir);
@@ -9172,7 +9385,7 @@ function writeWorkflowState(projectDir, state) {
9172
9385
  ${summary}`);
9173
9386
  }
9174
9387
  ensureWorkflowStateUntracked(projectDir);
9175
- const dir = path4.join(projectDir, ".lakebase");
9388
+ const dir = path5.join(projectDir, ".lakebase");
9176
9389
  fs4.mkdirSync(dir, { recursive: true });
9177
9390
  const target = stateFilePath(projectDir);
9178
9391
  const tmp = `${target}.tmp`;
@@ -9512,6 +9725,15 @@ async function mergeFeature(args) {
9512
9725
  );
9513
9726
  }
9514
9727
  }
9728
+ {
9729
+ const ensure = args.ensureCiFresh ?? ((repo) => ensureCiSecretsFreshFromEnv(args.projectDir, { ownerRepo: repo }));
9730
+ try {
9731
+ const r = await ensure(ownerRepo);
9732
+ if (r.action !== "ok" && r.action !== "skipped") preflightNotes.push(`CI-auth preflight: ${r.reason}`);
9733
+ } catch (err) {
9734
+ preflightNotes.push(`CI-auth preflight skipped (${err instanceof Error ? err.message : String(err)}); if the promote CI fails on auth, run lakebase-sync-ci-secrets.`);
9735
+ }
9736
+ }
9515
9737
  {
9516
9738
  const fetchState = args.fetchPrState ?? ((o, h) => getPullRequest(o, h));
9517
9739
  const prState = await fetchState(ownerRepo, current.branch).catch(() => void 0);
@@ -9730,7 +9952,7 @@ function shellEscape(s) {
9730
9952
  // scripts/lakebase/schema-migrate.ts
9731
9953
  init_esm_shims();
9732
9954
  import * as fs11 from "fs";
9733
- import * as path12 from "path";
9955
+ import * as path13 from "path";
9734
9956
 
9735
9957
  // scripts/lakebase/get-connection.ts
9736
9958
  init_esm_shims();
@@ -9750,21 +9972,21 @@ var CONSORT_VERSION_ENV = "CONSORT_VERSION";
9750
9972
  // scripts/lakebase/self-version.ts
9751
9973
  init_esm_shims();
9752
9974
  import * as fs5 from "fs";
9753
- import * as path5 from "path";
9975
+ import * as path6 from "path";
9754
9976
  import { fileURLToPath as fileURLToPath3 } from "url";
9755
9977
  var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
9756
9978
  var cached;
9757
9979
  function substrateSelfVersion() {
9758
9980
  if (cached !== void 0) return cached;
9759
- if ("0.2.48".length > 0) {
9760
- cached = "0.2.48";
9981
+ if ("0.2.49".length > 0) {
9982
+ cached = "0.2.49";
9761
9983
  return cached;
9762
9984
  }
9763
9985
  cached = "unknown";
9764
9986
  try {
9765
- let dir = path5.dirname(fileURLToPath3(import.meta.url));
9987
+ let dir = path6.dirname(fileURLToPath3(import.meta.url));
9766
9988
  for (let i = 0; i < 8; i++) {
9767
- const pkgPath = path5.join(dir, "package.json");
9989
+ const pkgPath = path6.join(dir, "package.json");
9768
9990
  if (fs5.existsSync(pkgPath)) {
9769
9991
  try {
9770
9992
  const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
@@ -9775,7 +9997,7 @@ function substrateSelfVersion() {
9775
9997
  } catch {
9776
9998
  }
9777
9999
  }
9778
- const parent = path5.dirname(dir);
10000
+ const parent = path6.dirname(dir);
9779
10001
  if (parent === dir) break;
9780
10002
  dir = parent;
9781
10003
  }
@@ -9871,17 +10093,17 @@ init_esm_shims();
9871
10093
  // scripts/lakebase/adapters/alembic-adapter.ts
9872
10094
  init_esm_shims();
9873
10095
  import * as fs7 from "fs";
9874
- import * as path7 from "path";
10096
+ import * as path8 from "path";
9875
10097
 
9876
10098
  // scripts/lakebase/schema-migrate-runners/alembic.ts
9877
10099
  init_esm_shims();
9878
10100
  import { spawn } from "child_process";
9879
10101
  import * as fs6 from "fs";
9880
- import * as path6 from "path";
10102
+ import * as path7 from "path";
9881
10103
  function resolveAlembicBin(projectDir) {
9882
10104
  const candidates = [
9883
- path6.join(projectDir, ".venv", "bin", "alembic"),
9884
- path6.join(projectDir, "venv", "bin", "alembic")
10105
+ path7.join(projectDir, ".venv", "bin", "alembic"),
10106
+ path7.join(projectDir, "venv", "bin", "alembic")
9885
10107
  ];
9886
10108
  for (const candidate of candidates) {
9887
10109
  try {
@@ -9895,7 +10117,7 @@ function spawnAlembic(projectDir, args, dsn) {
9895
10117
  return new Promise((resolve2, reject) => {
9896
10118
  const bin = resolveAlembicBin(projectDir);
9897
10119
  const env = { ...process.env };
9898
- env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path6.delimiter);
10120
+ env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path7.delimiter);
9899
10121
  if (dsn) env.DATABASE_URL = dsn;
9900
10122
  const child = spawn(bin, args, {
9901
10123
  cwd: projectDir,
@@ -9943,10 +10165,10 @@ async function createAlembicRevision(opts) {
9943
10165
  const m = stdout.match(/Generating\s+(\S+\.py)/);
9944
10166
  if (m) return m[1].trim();
9945
10167
  for (const rel of ["migrations/versions", "alembic/versions"]) {
9946
- const dir = path6.join(opts.projectDir, rel);
10168
+ const dir = path7.join(opts.projectDir, rel);
9947
10169
  if (!fs6.existsSync(dir)) continue;
9948
10170
  const hit = fs6.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
9949
- if (hit) return path6.join(dir, hit);
10171
+ if (hit) return path7.join(dir, hit);
9950
10172
  }
9951
10173
  throw new SchemaMigrationError(
9952
10174
  `alembic revision succeeded but the created file could not be located.
@@ -10081,8 +10303,8 @@ async function buildDsn(args) {
10081
10303
  }
10082
10304
  function findVersionsDir(projectDir) {
10083
10305
  const candidates = [
10084
- path7.join(projectDir, "migrations", "versions"),
10085
- path7.join(projectDir, "alembic", "versions")
10306
+ path8.join(projectDir, "migrations", "versions"),
10307
+ path8.join(projectDir, "alembic", "versions")
10086
10308
  ];
10087
10309
  return candidates.find((p) => fs7.existsSync(p));
10088
10310
  }
@@ -10114,9 +10336,9 @@ var AlembicAdapter = {
10114
10336
  * here. Callers can still force-select via project.yaml#migration_tool.
10115
10337
  */
10116
10338
  detect(projectDir) {
10117
- if (fs7.existsSync(path7.join(projectDir, "alembic.ini"))) return true;
10118
- if (fs7.existsSync(path7.join(projectDir, "migrations", "env.py"))) return true;
10119
- if (fs7.existsSync(path7.join(projectDir, "alembic", "env.py"))) return true;
10339
+ if (fs7.existsSync(path8.join(projectDir, "alembic.ini"))) return true;
10340
+ if (fs7.existsSync(path8.join(projectDir, "migrations", "env.py"))) return true;
10341
+ if (fs7.existsSync(path8.join(projectDir, "alembic", "env.py"))) return true;
10120
10342
  return false;
10121
10343
  },
10122
10344
  async apply(args) {
@@ -10222,7 +10444,7 @@ var AlembicAdapter = {
10222
10444
  autogenerate: !!args.autogenerate,
10223
10445
  dsn
10224
10446
  });
10225
- return { status: "ok", version: revId, filename: path7.basename(created), path: created };
10447
+ return { status: "ok", version: revId, filename: path8.basename(created), path: created };
10226
10448
  } catch (err) {
10227
10449
  return {
10228
10450
  status: "error",
@@ -10239,7 +10461,7 @@ var AlembicAdapter = {
10239
10461
  if (heads.length <= 1) return { status: "noop", headsBefore: heads };
10240
10462
  if (args.dryRun) return { status: "ok", headsBefore: heads };
10241
10463
  const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
10242
- const mergeRevision = path7.basename(created).replace(/\.py$/, "").split("_")[0];
10464
+ const mergeRevision = path8.basename(created).replace(/\.py$/, "").split("_")[0];
10243
10465
  return { status: "ok", headsBefore: heads, mergeRevision, path: created };
10244
10466
  } catch (err) {
10245
10467
  return {
@@ -10255,12 +10477,12 @@ registerSchemaMigrationAdapter(AlembicAdapter);
10255
10477
  // scripts/lakebase/adapters/flyway-adapter.ts
10256
10478
  init_esm_shims();
10257
10479
  import * as fs8 from "fs";
10258
- import * as path9 from "path";
10480
+ import * as path10 from "path";
10259
10481
 
10260
10482
  // scripts/lakebase/schema-migrate-runners/flyway.ts
10261
10483
  init_esm_shims();
10262
10484
  import { spawn as spawn2 } from "child_process";
10263
- import * as path8 from "path";
10485
+ import * as path9 from "path";
10264
10486
  function dsnToFlywayEnv(dsn) {
10265
10487
  const u = new URL(dsn);
10266
10488
  const user = decodeURIComponent(u.username);
@@ -10270,7 +10492,7 @@ function dsnToFlywayEnv(dsn) {
10270
10492
  return { url, user, password };
10271
10493
  }
10272
10494
  function migrationsLocation(projectDir) {
10273
- return `filesystem:${path8.join(projectDir, "src", "main", "resources", "db", "migration")}`;
10495
+ return `filesystem:${path9.join(projectDir, "src", "main", "resources", "db", "migration")}`;
10274
10496
  }
10275
10497
  function runFlyway(ctx, args) {
10276
10498
  const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
@@ -10371,7 +10593,7 @@ async function statusFlyway(ctx) {
10371
10593
  if (state === "SUCCESS" || state === "BASELINE") {
10372
10594
  current = m.version;
10373
10595
  } else if (state === "PENDING") {
10374
- const filename = m.filepath ? path8.basename(m.filepath) : `V${m.version}__migration.sql`;
10596
+ const filename = m.filepath ? path9.basename(m.filepath) : `V${m.version}__migration.sql`;
10375
10597
  pending.push({
10376
10598
  version: m.version,
10377
10599
  filename,
@@ -10394,7 +10616,7 @@ async function buildDsn2(args) {
10394
10616
  return result.url;
10395
10617
  }
10396
10618
  function listFlywayFiles(projectDir) {
10397
- const dir = path9.join(projectDir, "src", "main", "resources", "db", "migration");
10619
+ const dir = path10.join(projectDir, "src", "main", "resources", "db", "migration");
10398
10620
  if (!fs8.existsSync(dir)) return [];
10399
10621
  const files = fs8.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
10400
10622
  return files.map((filename) => {
@@ -10419,7 +10641,7 @@ var FlywayAdapter = {
10419
10641
  id: "flyway",
10420
10642
  languages: ["java", "kotlin"],
10421
10643
  detect(projectDir) {
10422
- return fs8.existsSync(path9.join(projectDir, "pom.xml"));
10644
+ return fs8.existsSync(path10.join(projectDir, "pom.xml"));
10423
10645
  },
10424
10646
  async apply(args) {
10425
10647
  const dsn = await buildDsn2(args);
@@ -10477,12 +10699,12 @@ var FlywayAdapter = {
10477
10699
  // optional-protocol shape makes this additive.
10478
10700
  async newMigration(args) {
10479
10701
  try {
10480
- const dir = path9.join(args.projectDir, "src", "main", "resources", "db", "migration");
10702
+ const dir = path10.join(args.projectDir, "src", "main", "resources", "db", "migration");
10481
10703
  fs8.mkdirSync(dir, { recursive: true });
10482
10704
  const version = migrationTimestamp();
10483
10705
  const slug = migrationSlug2(args.slug);
10484
10706
  const filename = `V${version}__${slug}.sql`;
10485
- const full = path9.join(dir, filename);
10707
+ const full = path10.join(dir, filename);
10486
10708
  if (fs8.existsSync(full)) throw new Error(`${filename} already exists`);
10487
10709
  fs8.writeFileSync(
10488
10710
  full,
@@ -10508,17 +10730,17 @@ registerSchemaMigrationAdapter(FlywayAdapter);
10508
10730
  // scripts/lakebase/adapters/knex-adapter.ts
10509
10731
  init_esm_shims();
10510
10732
  import * as fs10 from "fs";
10511
- import * as path11 from "path";
10733
+ import * as path12 from "path";
10512
10734
 
10513
10735
  // scripts/lakebase/schema-migrate-runners/knex.ts
10514
10736
  init_esm_shims();
10515
10737
  import { spawn as spawn3 } from "child_process";
10516
10738
  import * as fs9 from "fs";
10517
- import * as path10 from "path";
10739
+ import * as path11 from "path";
10518
10740
  var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
10519
10741
  function findKnexfile(projectDir) {
10520
10742
  for (const name of KNEXFILE_VARIANTS) {
10521
- const p = path10.join(projectDir, name);
10743
+ const p = path11.join(projectDir, name);
10522
10744
  if (fs9.existsSync(p)) return p;
10523
10745
  }
10524
10746
  return void 0;
@@ -10671,7 +10893,7 @@ async function buildDsn3(args) {
10671
10893
  }
10672
10894
  var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
10673
10895
  function listKnexFiles(projectDir) {
10674
- const dir = path11.join(projectDir, "migrations");
10896
+ const dir = path12.join(projectDir, "migrations");
10675
10897
  if (!fs10.existsSync(dir)) return [];
10676
10898
  const files = fs10.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
10677
10899
  return files.map((filename) => {
@@ -10693,7 +10915,7 @@ var KnexAdapter = {
10693
10915
  * project.yaml#migration_tool.
10694
10916
  */
10695
10917
  detect(projectDir) {
10696
- return KNEXFILE_VARIANTS2.some((name) => fs10.existsSync(path11.join(projectDir, name)));
10918
+ return KNEXFILE_VARIANTS2.some((name) => fs10.existsSync(path12.join(projectDir, name)));
10697
10919
  },
10698
10920
  async apply(args) {
10699
10921
  const dsn = await buildDsn3(args);
@@ -10766,9 +10988,9 @@ var KnexAdapter = {
10766
10988
  async newMigration(args) {
10767
10989
  try {
10768
10990
  const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
10769
- const stem = path11.basename(created).replace(/\.(js|ts)$/, "");
10991
+ const stem = path12.basename(created).replace(/\.(js|ts)$/, "");
10770
10992
  const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
10771
- return { status: "ok", version, filename: path11.basename(created), path: created };
10993
+ return { status: "ok", version, filename: path12.basename(created), path: created };
10772
10994
  } catch (err) {
10773
10995
  return {
10774
10996
  status: "error",
@@ -11061,7 +11283,7 @@ async function runScmMergeCli(argv) {
11061
11283
  `);
11062
11284
  return 0;
11063
11285
  }
11064
- const projectDir = path13.resolve(args.projectDir ?? process.cwd());
11286
+ const projectDir = path14.resolve(args.projectDir ?? process.cwd());
11065
11287
  const waitMigrate = args.noWaitMigrate ? false : true;
11066
11288
  const verifyMigrateAuth = waitMigrate && !args.noVerifyMigrateAuth ? async () => {
11067
11289
  try {