@databricks-solutions/lakebase-scm-utils 0.3.9 → 0.3.11

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 (69) hide show
  1. package/dist/{pr-e-E4aP4j.d.cts → pr-D-eayWyJ.d.cts} +59 -1
  2. package/dist/{pr-e-E4aP4j.d.ts → pr-D-eayWyJ.d.ts} +59 -1
  3. package/dist/scripts/git/index.cjs +30 -14
  4. package/dist/scripts/git/index.d.cts +32 -1
  5. package/dist/scripts/git/index.d.ts +32 -1
  6. package/dist/scripts/git/index.js +28 -14
  7. package/dist/scripts/github/index.d.cts +2 -60
  8. package/dist/scripts/github/index.d.ts +2 -60
  9. package/dist/scripts/index.cjs +301 -98
  10. package/dist/scripts/index.d.cts +4 -4
  11. package/dist/scripts/index.d.ts +4 -4
  12. package/dist/scripts/index.js +296 -98
  13. package/dist/scripts/lakebase/branch.cli.cjs +55 -10
  14. package/dist/scripts/lakebase/branch.cli.js +55 -10
  15. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +59 -24
  16. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +59 -24
  17. package/dist/scripts/lakebase/collapse-heads.cli.cjs +16 -14
  18. package/dist/scripts/lakebase/collapse-heads.cli.js +16 -14
  19. package/dist/scripts/lakebase/cut-backup.cli.cjs +43 -10
  20. package/dist/scripts/lakebase/cut-backup.cli.js +43 -10
  21. package/dist/scripts/lakebase/cut-tier.cli.cjs +43 -10
  22. package/dist/scripts/lakebase/cut-tier.cli.js +43 -10
  23. package/dist/scripts/lakebase/detect-language.cli.cjs +16 -14
  24. package/dist/scripts/lakebase/detect-language.cli.js +16 -14
  25. package/dist/scripts/lakebase/doctor.cli.cjs +16 -14
  26. package/dist/scripts/lakebase/doctor.cli.js +16 -14
  27. package/dist/scripts/lakebase/get-connection.cli.cjs +16 -14
  28. package/dist/scripts/lakebase/get-connection.cli.js +16 -14
  29. package/dist/scripts/lakebase/index.cjs +313 -99
  30. package/dist/scripts/lakebase/index.d.cts +97 -2
  31. package/dist/scripts/lakebase/index.d.ts +97 -2
  32. package/dist/scripts/lakebase/index.js +310 -99
  33. package/dist/scripts/lakebase/infra-runner.cli.cjs +16 -14
  34. package/dist/scripts/lakebase/infra-runner.cli.js +16 -14
  35. package/dist/scripts/lakebase/new-migration.cli.cjs +16 -14
  36. package/dist/scripts/lakebase/new-migration.cli.js +16 -14
  37. package/dist/scripts/lakebase/schema-diff.cli.cjs +16 -14
  38. package/dist/scripts/lakebase/schema-diff.cli.js +16 -14
  39. package/dist/scripts/lakebase/schema-migrate.cli.cjs +16 -14
  40. package/dist/scripts/lakebase/schema-migrate.cli.js +16 -14
  41. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +59 -24
  42. package/dist/scripts/lakebase/scm-claim-feature.cli.js +59 -24
  43. package/dist/scripts/lakebase/scm-doctor.cli.cjs +59 -24
  44. package/dist/scripts/lakebase/scm-doctor.cli.js +59 -24
  45. package/dist/scripts/lakebase/scm-merge.cli.cjs +16 -14
  46. package/dist/scripts/lakebase/scm-merge.cli.js +16 -14
  47. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +16 -14
  48. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +16 -14
  49. package/dist/scripts/lakebase/scm-recover-orphans.cli.cjs +43 -10
  50. package/dist/scripts/lakebase/scm-recover-orphans.cli.js +43 -10
  51. package/dist/scripts/lakebase/snapshot.cli.cjs +418 -0
  52. package/dist/scripts/lakebase/snapshot.cli.d.cts +1 -0
  53. package/dist/scripts/lakebase/snapshot.cli.d.ts +1 -0
  54. package/dist/scripts/lakebase/snapshot.cli.js +395 -0
  55. package/dist/scripts/util/index.cjs +2 -2
  56. package/dist/scripts/util/index.js +2 -2
  57. package/package.json +2 -1
  58. package/scripts/git/commit-push.ts +60 -14
  59. package/scripts/lakebase/branch-create.ts +93 -14
  60. package/scripts/lakebase/branch.cli.ts +15 -0
  61. package/scripts/lakebase/create-preflight.ts +213 -1
  62. package/scripts/lakebase/create-project.ts +98 -30
  63. package/scripts/lakebase/get-connection.ts +29 -12
  64. package/scripts/lakebase/snapshot.cli.ts +117 -0
  65. package/scripts/lakebase/snapshot.ts +85 -0
  66. package/templates/project/common/scripts/run-tests.sh +43 -2
  67. package/templates/project/python/tests/conftest.py +22 -0
  68. package/templates/project/common/tests/e2e/__pycache__/conftest.cpython-314.pyc +0 -0
  69. package/templates/project/python/tests/__pycache__/conftest.cpython-314.pyc +0 -0
@@ -2291,9 +2291,46 @@ function dbcli2(args, host, timeout = KIT_TIMEOUTS.cliDefault) {
2291
2291
  }
2292
2292
 
2293
2293
  // scripts/lakebase/branch-create.ts
2294
+ function snapshotPath(instance, snapshot) {
2295
+ return snapshot.includes("/") ? snapshot : `${projectPath(instance)}/snapshots/${snapshot}`;
2296
+ }
2297
+ function applyExpiration(spec, ttl, noExpiry) {
2298
+ if (ttl && noExpiry === true) {
2299
+ throw new LakebaseBranchError(
2300
+ `Cannot set both ttl ("${ttl}") and noExpiry: true on the same branch \u2013 they are mutually exclusive. Pass one or the other.`
2301
+ );
2302
+ }
2303
+ if (ttl) spec.ttl = ttl;
2304
+ else if (noExpiry ?? true) spec.no_expiry = true;
2305
+ }
2294
2306
  async function createBranch(args) {
2295
2307
  const sanitized = sanitizeBranchName(args.branch);
2296
2308
  const lookup = { instance: args.instance, host: args.host };
2309
+ if (args.fromSnapshot) {
2310
+ if (args.parentBranch || args.sourceBranchTime || args.sourceBranchLsn) {
2311
+ throw new LakebaseBranchError(
2312
+ `fromSnapshot is mutually exclusive with parentBranch / sourceBranchTime / sourceBranchLsn (the Lakebase API rejects source_snapshot combined with any source_branch field).`
2313
+ );
2314
+ }
2315
+ const existing2 = await getBranchByName(sanitized, lookup);
2316
+ if (existing2) return existing2;
2317
+ const spec = { source_snapshot: snapshotPath(args.instance, args.fromSnapshot) };
2318
+ applyExpiration(spec, args.ttl, args.noExpiry);
2319
+ try {
2320
+ await createWithTtlRecovery(args.instance, sanitized, spec, args.host);
2321
+ } catch (err) {
2322
+ if (err instanceof LakebaseBranchTtlTooLongError) throw err;
2323
+ const landed = await getBranchByName(sanitized, lookup);
2324
+ if (!landed) throw err;
2325
+ }
2326
+ return waitForBranchReady({
2327
+ instance: args.instance,
2328
+ host: args.host,
2329
+ branch: sanitized,
2330
+ timeoutMs: args.readyTimeoutMs ?? KIT_TIMEOUTS.readyWait,
2331
+ pollIntervalMs: args.pollIntervalMs ?? KIT_TIMEOUTS.readyPoll
2332
+ });
2333
+ }
2297
2334
  let sourceBranchPath;
2298
2335
  if (args.parentBranch) {
2299
2336
  if (looksLikeBranchUid(args.parentBranch)) {
@@ -2341,19 +2378,15 @@ async function createBranch(args) {
2341
2378
  assertSourceMatches(existing, sourceBranchPath, sanitized);
2342
2379
  return existing;
2343
2380
  }
2344
- if (args.ttl && args.noExpiry === true) {
2381
+ if (args.sourceBranchTime && args.sourceBranchLsn) {
2345
2382
  throw new LakebaseBranchError(
2346
- `Cannot set both ttl ("${args.ttl}") and noExpiry: true on the same branch \u2013 they are mutually exclusive. Pass one or the other.`
2383
+ `sourceBranchTime and sourceBranchLsn are mutually exclusive \u2013 pass at most one point-in-time.`
2347
2384
  );
2348
2385
  }
2349
- const specObj = {
2350
- source_branch: sourceBranchPath
2351
- };
2352
- if (args.ttl) {
2353
- specObj.ttl = args.ttl;
2354
- } else if (args.noExpiry ?? true) {
2355
- specObj.no_expiry = true;
2356
- }
2386
+ const specObj = { source_branch: sourceBranchPath };
2387
+ if (args.sourceBranchTime) specObj.source_branch_time = args.sourceBranchTime;
2388
+ else if (args.sourceBranchLsn) specObj.source_branch_lsn = args.sourceBranchLsn;
2389
+ applyExpiration(specObj, args.ttl, args.noExpiry);
2357
2390
  try {
2358
2391
  await createWithTtlRecovery(args.instance, sanitized, specObj, args.host);
2359
2392
  } catch (err) {
@@ -2521,8 +2554,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
2521
2554
  var cached;
2522
2555
  function substrateSelfVersion() {
2523
2556
  if (cached !== void 0) return cached;
2524
- if ("0.3.9".length > 0) {
2525
- cached = "0.3.9";
2557
+ if ("0.3.11".length > 0) {
2558
+ cached = "0.3.11";
2526
2559
  return cached;
2527
2560
  }
2528
2561
  cached = "unknown";
@@ -2569,22 +2602,24 @@ async function getConnection(args) {
2569
2602
  const endpointPath2 = `projects/${args.instance}/branches/${branchId}/endpoints/${endpointName}`;
2570
2603
  if (args.output === "dsn") {
2571
2604
  const host2 = await resolveEndpointHost(args.instance, branchId);
2572
- const { token, email: email2 } = await mintCredential(endpointPath2);
2573
- const url = buildPostgresUrl({ host: host2, port: POSTGRES_PORT, database, user: email2, password: token });
2605
+ const { token: token2, email: email2 } = await mintCredential(endpointPath2);
2606
+ const url = buildPostgresUrl({ host: host2, port: POSTGRES_PORT, database, user: email2, password: token2 });
2574
2607
  return { url, host: host2, port: POSTGRES_PORT, database, user: email2, endpointPath: endpointPath2 };
2575
2608
  }
2576
2609
  const host = await resolveEndpointHost(args.instance, branchId);
2577
- const email = await resolveCurrentUser();
2578
2610
  process.env.PGAPPNAME = connectionApplicationName();
2579
- return createLakebasePool({
2580
- endpoint: endpointPath2,
2581
- host,
2582
- database,
2583
- user: email,
2584
- // workspaceClient is passed through verbatim. createLakebasePool falls
2585
- // back to environment / ServiceContext when omitted.
2586
- ...args.workspaceClient !== void 0 ? { workspaceClient: args.workspaceClient } : {}
2587
- });
2611
+ if (args.workspaceClient !== void 0) {
2612
+ const email2 = await resolveCurrentUser();
2613
+ return createLakebasePool({
2614
+ endpoint: endpointPath2,
2615
+ host,
2616
+ database,
2617
+ user: email2,
2618
+ workspaceClient: args.workspaceClient
2619
+ });
2620
+ }
2621
+ const { token, email } = await mintCredential(endpointPath2);
2622
+ return createLakebasePool({ host, database, user: email, password: token });
2588
2623
  }
2589
2624
  async function resolveEndpointHost(instance, branch) {
2590
2625
  const branchId = await resolveBranchId({ instance, branch });
@@ -11100,6 +11135,26 @@ async function createRepo(name, opts = {}) {
11100
11135
  wrap2(err, `Failed to create repository "${name}"`);
11101
11136
  }
11102
11137
  }
11138
+ async function deleteRepo(name) {
11139
+ try {
11140
+ const { owner, repo } = parseOwnerRepo(name);
11141
+ const ctx = await newContext();
11142
+ await ctx.octokit.rest.repos.delete({ owner, repo });
11143
+ } catch (err) {
11144
+ wrap2(err, `Failed to delete repository "${name}"`);
11145
+ }
11146
+ }
11147
+ async function repoExists(name) {
11148
+ try {
11149
+ const { owner, repo } = parseOwnerRepo(name);
11150
+ const ctx = await newContext();
11151
+ await ctx.octokit.rest.repos.get({ owner, repo });
11152
+ return true;
11153
+ } catch (err) {
11154
+ if (err instanceof RequestError && err.status === 404) return false;
11155
+ wrap2(err, `Failed to check repository "${name}"`);
11156
+ }
11157
+ }
11103
11158
  async function getActionsEnabled(ownerRepo) {
11104
11159
  try {
11105
11160
  const { owner, repo } = parseOwnerRepo(ownerRepo);
@@ -11168,6 +11223,70 @@ async function checkGitHubWorkflowScope(probe = () => checkGitHubScopes(["workfl
11168
11223
  return { ok: true };
11169
11224
  }
11170
11225
  }
11226
+ async function checkGitHubPreflight(input) {
11227
+ const warnings = [];
11228
+ const hard = input.hardScopes ?? ["repo", "workflow"];
11229
+ const advisory = input.advisoryScopes ?? ["delete_repo"];
11230
+ const currentUserProbe = input.currentUser ?? getCurrentUser;
11231
+ const scopesProbe = input.scopes ?? ((req) => checkGitHubScopes(req));
11232
+ const repoExistsProbe = input.repoExists ?? repoExists;
11233
+ let activeUser;
11234
+ try {
11235
+ activeUser = await currentUserProbe();
11236
+ } catch (err) {
11237
+ return {
11238
+ ok: false,
11239
+ warnings,
11240
+ reason: `GitHub authentication failed: ${err instanceof Error ? err.message : String(err)}. Sign in with \`gh auth login\`, or set GITHUB_TOKEN to a valid token, then re-run.`
11241
+ };
11242
+ }
11243
+ if (activeUser.toLowerCase() !== input.owner.toLowerCase()) {
11244
+ warnings.push(
11245
+ `Signed in to GitHub as "${activeUser}" but creating under "${input.owner}" (an org or another account); ensure "${activeUser}" can create repositories there.`
11246
+ );
11247
+ }
11248
+ try {
11249
+ const { granted, missing } = await scopesProbe([...hard, ...advisory]);
11250
+ if (granted !== null) {
11251
+ const missingHard = missing.filter((s) => hard.includes(s));
11252
+ const missingAdvisory = missing.filter((s) => advisory.includes(s));
11253
+ if (missingHard.length > 0) {
11254
+ return {
11255
+ ok: false,
11256
+ warnings,
11257
+ activeUser,
11258
+ reason: `GitHub token missing required scope(s): ${missingHard.join(", ")} (grants [${granted.join(", ") || "none"}]). Fix: gh auth refresh -h github.com -s ${[...hard].join(",")}`
11259
+ };
11260
+ }
11261
+ if (missingAdvisory.length > 0) {
11262
+ warnings.push(
11263
+ `GitHub token missing the ${missingAdvisory.join(", ")} scope \u2014 automatic rollback of the repo on a failed create is unavailable (you would delete it by hand). Grant it with: gh auth refresh -h github.com -s delete_repo`
11264
+ );
11265
+ }
11266
+ } else {
11267
+ warnings.push(
11268
+ "GitHub token scopes are not visible (fine-grained PAT or App token); cannot pre-verify repo/workflow access , the create will surface any gap when it pushes."
11269
+ );
11270
+ }
11271
+ } catch {
11272
+ warnings.push("Could not read GitHub token scopes (probe error); skipping the scope pre-check.");
11273
+ }
11274
+ try {
11275
+ if (await repoExistsProbe(`${input.owner}/${input.repoName}`)) {
11276
+ return {
11277
+ ok: false,
11278
+ warnings,
11279
+ activeUser,
11280
+ reason: `GitHub repo "${input.owner}/${input.repoName}" already exists. Pick a different project name, or delete the existing repo first.`
11281
+ };
11282
+ }
11283
+ } catch {
11284
+ warnings.push(
11285
+ `Could not check whether "${input.owner}/${input.repoName}" already exists (probe error); the create will fail clearly if it does.`
11286
+ );
11287
+ }
11288
+ return { ok: true, warnings, activeUser };
11289
+ }
11171
11290
  function githubWorkflowScopePrereqMessage(reason) {
11172
11291
  return "The GitHub token lacks the `workflow` OAuth scope, required to push the project's `.github/workflows/*` CI files. (Without this check, scaffolding runs all the way to the final push, is rejected, and rolls back.) Grant the scope, then re-run:\n - gh CLI: gh auth refresh -h github.com -s workflow\n - VS Code: re-sign in to GitHub and approve the workflow scope\n - token: set GITHUB_TOKEN to a PAT that includes the `workflow` scope" + (reason ? `
11173
11292
  (${reason})` : "");
@@ -11233,6 +11352,53 @@ async function withLakebaseRollback(opts, fn) {
11233
11352
  throw wrapped;
11234
11353
  }
11235
11354
  }
11355
+ function createRollbackLedger() {
11356
+ const steps = [];
11357
+ return {
11358
+ register: (label, undo) => {
11359
+ steps.push({ label, undo });
11360
+ },
11361
+ steps: () => steps
11362
+ };
11363
+ }
11364
+ async function withCreateRollback(ledger, opts, fn) {
11365
+ try {
11366
+ return await fn();
11367
+ } catch (err) {
11368
+ const report = opts.report ?? (() => {
11369
+ });
11370
+ const steps = [...ledger.steps()].reverse();
11371
+ const rolledBack = [];
11372
+ const failed = [];
11373
+ if (steps.length > 0) report(`Create failed; rolling back ${steps.length} created resource(s)...`);
11374
+ for (const s of steps) {
11375
+ try {
11376
+ report(` rolling back: ${s.label}`);
11377
+ await s.undo();
11378
+ rolledBack.push(s.label);
11379
+ } catch (e) {
11380
+ const m = e instanceof Error ? e.message : String(e);
11381
+ if (/not.?found/i.test(m)) {
11382
+ rolledBack.push(s.label);
11383
+ continue;
11384
+ }
11385
+ failed.push(`${s.label} (${m})`);
11386
+ }
11387
+ }
11388
+ const base = err instanceof Error ? err.message : String(err);
11389
+ const parts = [];
11390
+ if (rolledBack.length) parts.push(`rolled back: ${rolledBack.join(", ")}`);
11391
+ if (failed.length) {
11392
+ parts.push(
11393
+ `COULD NOT roll back: ${failed.join("; ")} , clean these up by hand before retrying the same name`
11394
+ );
11395
+ }
11396
+ const suffix = parts.length ? ` (${parts.join("; ")})` : "";
11397
+ const wrapped = err instanceof Error ? err : new Error(base);
11398
+ wrapped.message = `${base}${suffix}`;
11399
+ throw wrapped;
11400
+ }
11401
+ }
11236
11402
 
11237
11403
  // scripts/lakebase/create-project.ts
11238
11404
  init_esm_shims();
@@ -11300,27 +11466,34 @@ To finish:
11300
11466
  this.name = "WorkflowScopeError";
11301
11467
  }
11302
11468
  };
11303
- async function commitAndPush(args) {
11304
- await exec2("git add -A", { cwd: args.projectDir });
11305
- await exec2(`git commit -m ${JSON.stringify(args.message)}`, {
11306
- cwd: args.projectDir,
11469
+ async function commitInitial(projectDir, message) {
11470
+ await exec2("git add -A", { cwd: projectDir });
11471
+ await exec2(`git commit -m ${JSON.stringify(message)}`, {
11472
+ cwd: projectDir,
11307
11473
  timeout: 3e4
11308
11474
  });
11309
- if (args.push === false) return;
11475
+ }
11476
+ async function pushInitial(args) {
11310
11477
  const remote = args.remote ?? "origin";
11311
11478
  const branch = args.branch ?? "main";
11312
- try {
11313
- await exec2(`git push -u ${remote} ${branch}`, {
11314
- cwd: args.projectDir,
11315
- timeout: 3e4
11316
- });
11317
- } catch (err) {
11318
- const msg = err instanceof Error ? err.message : String(err);
11319
- if (/without `?workflow`? scope|workflow scope/i.test(msg)) {
11320
- throw new WorkflowScopeError(args.projectDir);
11479
+ const attempts = Math.max(1, args.attempts ?? 1);
11480
+ const sleep2 = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
11481
+ const run = args.run ?? ((cmd, cwd) => exec2(cmd, { cwd, timeout: 3e4 }).then(() => void 0));
11482
+ let lastErr;
11483
+ for (let i = 1; i <= attempts; i++) {
11484
+ try {
11485
+ await run(`git push -u ${remote} ${branch}`, args.projectDir);
11486
+ return;
11487
+ } catch (err) {
11488
+ const msg = err instanceof Error ? err.message : String(err);
11489
+ if (/without `?workflow`? scope|workflow scope/i.test(msg)) {
11490
+ throw new WorkflowScopeError(args.projectDir);
11491
+ }
11492
+ lastErr = err;
11493
+ if (i < attempts) await sleep2(args.delayMs ?? 1500);
11321
11494
  }
11322
- throw err;
11323
11495
  }
11496
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
11324
11497
  }
11325
11498
 
11326
11499
  // scripts/lakebase/scaffold.ts
@@ -13626,64 +13799,72 @@ async function createProject(input, progress) {
13626
13799
  throw new Error(databricksAuthPrereqMessage(host, auth7.reason));
13627
13800
  }
13628
13801
  if (useGithub) {
13629
- report("Checking GitHub token scopes...");
13630
- const scope = await checkGitHubWorkflowScope();
13631
- if (!scope.ok) {
13632
- throw new Error(githubWorkflowScopePrereqMessage(scope.reason));
13633
- }
13634
- }
13635
- if (useGithub) {
13636
- report("Creating GitHub repository...", fullRepoName);
13637
- await createRepo(fullRepoName, {
13638
- private: input.privateRepo !== false,
13639
- description: `Lakebase project: ${input.projectName}`
13640
- });
13641
- report("Waiting for GitHub repo to be visible...", fullRepoName);
13642
- const probeDelays = [1e3, 2e3, 3e3, 5e3, 8e3];
13643
- let probeErr = "";
13644
- let visible = false;
13645
- for (const waitMs of probeDelays) {
13646
- try {
13647
- await getRepoFullName(fullRepoName);
13648
- visible = true;
13649
- break;
13650
- } catch (err) {
13651
- probeErr = err instanceof Error ? err.message : String(err);
13652
- await delay(waitMs);
13653
- }
13654
- }
13655
- if (!visible) {
13656
- let activeUser = "";
13657
- try {
13658
- activeUser = await getCurrentUser();
13659
- } catch {
13660
- }
13661
- const samlHint = /SAML|scope does not match|sso/i.test(probeErr) ? "\n\nThe error mentions SAML \u2013 re-sign in to GitHub and authorize SSO for this org." : "";
13662
- const userHint = activeUser && activeUser !== input.githubOwner ? `
13802
+ report("Checking GitHub account + token requisites...");
13803
+ const gh = await checkGitHubPreflight({ owner: input.githubOwner, repoName: input.projectName });
13804
+ for (const w of gh.warnings) {
13805
+ warnings.push(w);
13806
+ report(`Note: ${w}`);
13807
+ }
13808
+ if (!gh.ok) throw new Error(gh.reason);
13809
+ }
13810
+ const rollback = createRollbackLedger();
13811
+ return await withCreateRollback(
13812
+ rollback,
13813
+ { report },
13814
+ async () => {
13815
+ if (useGithub) {
13816
+ report("Creating GitHub repository...", fullRepoName);
13817
+ await createRepo(fullRepoName, {
13818
+ private: input.privateRepo !== false,
13819
+ description: `Lakebase project: ${input.projectName}`
13820
+ });
13821
+ rollback.register(`GitHub repo ${fullRepoName}`, () => deleteRepo(fullRepoName));
13822
+ report("Waiting for GitHub repo to be visible...", fullRepoName);
13823
+ const probeDelays = [1e3, 2e3, 3e3, 5e3, 8e3];
13824
+ let probeErr = "";
13825
+ let visible = false;
13826
+ for (const waitMs of probeDelays) {
13827
+ try {
13828
+ await getRepoFullName(fullRepoName);
13829
+ visible = true;
13830
+ break;
13831
+ } catch (err) {
13832
+ probeErr = err instanceof Error ? err.message : String(err);
13833
+ await delay(waitMs);
13834
+ }
13835
+ }
13836
+ if (!visible) {
13837
+ let activeUser = "";
13838
+ try {
13839
+ activeUser = await getCurrentUser();
13840
+ } catch {
13841
+ }
13842
+ const samlHint = /SAML|scope does not match|sso/i.test(probeErr) ? "\n\nThe error mentions SAML \u2013 re-sign in to GitHub and authorize SSO for this org." : "";
13843
+ const userHint = activeUser && activeUser !== input.githubOwner ? `
13663
13844
 
13664
13845
  Note: signed in as "${activeUser}", but the repo was created under "${input.githubOwner}".` : "";
13665
- throw new Error(
13666
- `GitHub repo "${fullRepoName}" was created but isn't visible after ~19s of polling.${samlHint}${userHint}
13846
+ throw new Error(
13847
+ `GitHub repo "${fullRepoName}" was created but isn't visible after ~19s of polling.${samlHint}${userHint}
13667
13848
 
13668
13849
  Last probe error:
13669
13850
  ${probeErr.split("\n")[0].slice(0, 200)}`
13670
- );
13671
- }
13672
- report("Cloning repository...", projectDir);
13673
- await cloneRepo({
13674
- repoUrl: `https://github.com/${fullRepoName}.git`,
13675
- parentDir: input.parentDir
13676
- });
13677
- } else {
13678
- report("Creating local project directory...", projectDir);
13679
- fs19.mkdirSync(projectDir, { recursive: true });
13680
- await gitInit(projectDir);
13681
- }
13682
- report("Creating Lakebase database (provisioning Postgres)...", lakebaseProjectId);
13683
- await createLakebaseProject({ projectId: lakebaseProjectId, host });
13684
- return await withLakebaseRollback(
13685
- { projectId: lakebaseProjectId, host, report },
13686
- async () => {
13851
+ );
13852
+ }
13853
+ report("Cloning repository...", projectDir);
13854
+ await cloneRepo({
13855
+ repoUrl: `https://github.com/${fullRepoName}.git`,
13856
+ parentDir: input.parentDir
13857
+ });
13858
+ rollback.register(`local project dir ${projectDir}`, () => fs19.rmSync(projectDir, { recursive: true, force: true }));
13859
+ } else {
13860
+ report("Creating local project directory...", projectDir);
13861
+ fs19.mkdirSync(projectDir, { recursive: true });
13862
+ await gitInit(projectDir);
13863
+ rollback.register(`local project dir ${projectDir}`, () => fs19.rmSync(projectDir, { recursive: true, force: true }));
13864
+ }
13865
+ report("Creating Lakebase database (provisioning Postgres)...", lakebaseProjectId);
13866
+ await createLakebaseProject({ projectId: lakebaseProjectId, host });
13867
+ rollback.register(`Lakebase project ${lakebaseProjectId}`, () => deleteLakebaseProject({ projectId: lakebaseProjectId, host }));
13687
13868
  report("Resolving database endpoint...");
13688
13869
  const defaultBranchId = await getDefaultBranchId({
13689
13870
  projectId: lakebaseProjectId,
@@ -13767,6 +13948,10 @@ Last probe error:
13767
13948
  projectName: input.projectName,
13768
13949
  report: (m) => report(m)
13769
13950
  });
13951
+ rollback.register(
13952
+ `self-hosted runner ${input.projectName}`,
13953
+ () => removeRunner({ fullRepoName, projectName: input.projectName })
13954
+ );
13770
13955
  } catch (err) {
13771
13956
  const msg = err instanceof Error ? err.message : String(err);
13772
13957
  warnings.push(`Runner setup failed: ${msg}`);
@@ -13835,6 +14020,16 @@ Last probe error:
13835
14020
  "The Consort toolkit downloads on first use , run `./scripts/lk --refresh`, or just your first `./scripts/lk` command, and it installs then."
13836
14021
  );
13837
14022
  }
14023
+ if (enableSftdd && useGithub) {
14024
+ report("Warming the Consort toolkit (so the first push doesn't cold-download mid-hook)...");
14025
+ let warm = warmAndVerifyKit(projectDir);
14026
+ if (!warm.ok) warm = warmAndVerifyKit(projectDir);
14027
+ if (!warm.ok) {
14028
+ const m = kitWarmWarning(projectDir, warm.reason);
14029
+ warnings.push(m);
14030
+ report(`Note: ${m}`);
14031
+ }
14032
+ }
13838
14033
  const langLabels = {
13839
14034
  java: "Java/Spring Boot",
13840
14035
  kotlin: "Kotlin/Spring Boot",
@@ -13855,12 +14050,25 @@ Last probe error:
13855
14050
  }
13856
14051
  }
13857
14052
  report("Creating initial commit...");
13858
- await commitAndPush({
13859
- projectDir,
13860
- message: `Initial project scaffold (${langLabel} + Lakebase)`,
13861
- push: useGithub
13862
- });
13863
- if (tiers === 2 || tiers === 3) {
14053
+ await commitInitial(projectDir, `Initial project scaffold (${langLabel} + Lakebase)`);
14054
+ let pushLanded = !useGithub;
14055
+ if (useGithub) {
14056
+ try {
14057
+ await pushInitial({ projectDir, attempts: 2 });
14058
+ pushLanded = true;
14059
+ } catch (err) {
14060
+ if (err instanceof WorkflowScopeError) throw err;
14061
+ const msg = err instanceof Error ? err.message : String(err);
14062
+ const m = `Provisioning is complete, but the initial push did not land after a retry: ${msg}. The project on disk is fine , finish it with: cd ${projectDir} && git push -u origin main`;
14063
+ warnings.push(m);
14064
+ report(`WARNING: ${m}`);
14065
+ }
14066
+ }
14067
+ if ((tiers === 2 || tiers === 3) && !pushLanded) {
14068
+ const m = `INCOMPLETE TIERS: you requested ${tiers} tiers, but the initial push hasn't landed, so the tier branches can't fork off origin yet. After you finish the push (above), cut them with \`./scripts/lk lakebase-cut-tier --name staging --fork-from main\`${tiers === 3 ? " and `--name dev --fork-from staging`" : ""}.`;
14069
+ warnings.push(m);
14070
+ report(`WARNING: ${m}`);
14071
+ } else if (tiers === 2 || tiers === 3) {
13864
14072
  report(`Cutting staging tier (tiers=${tiers}) via createLongRunningBranch...`);
13865
14073
  try {
13866
14074
  await createLongRunningBranch({
@@ -13911,7 +14119,7 @@ Last probe error:
13911
14119
  warnings
13912
14120
  };
13913
14121
  }
13914
- // end withLakebaseRollback closure
14122
+ // end withCreateRollback closure
13915
14123
  );
13916
14124
  }
13917
14125
 
@@ -18709,6 +18917,7 @@ export {
18709
18917
  catalogExists,
18710
18918
  catalogExplorerUrl,
18711
18919
  checkDatabricksAuth,
18920
+ checkGitHubPreflight,
18712
18921
  checkGitHubWorkflowScope,
18713
18922
  checkoutPaired,
18714
18923
  claimFeatureBranch,
@@ -18722,6 +18931,7 @@ export {
18722
18931
  createPairedBranch,
18723
18932
  createPerfPairedBranch,
18724
18933
  createProject,
18934
+ createRollbackLedger,
18725
18935
  createTestPairedBranch,
18726
18936
  createUatPairedBranch,
18727
18937
  cutBackup,
@@ -18891,6 +19101,7 @@ export {
18891
19101
  waitForBranchReady,
18892
19102
  waitForCi,
18893
19103
  warmAndVerifyKit,
19104
+ withCreateRollback,
18894
19105
  withLakebaseRollback,
18895
19106
  workflowStateFileExists,
18896
19107
  writeEnvFile,
@@ -79290,8 +79290,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79290
79290
  var cached;
79291
79291
  function substrateSelfVersion() {
79292
79292
  if (cached !== void 0) return cached;
79293
- if ("0.3.9".length > 0) {
79294
- cached = "0.3.9";
79293
+ if ("0.3.11".length > 0) {
79294
+ cached = "0.3.11";
79295
79295
  return cached;
79296
79296
  }
79297
79297
  cached = "unknown";
@@ -79338,22 +79338,24 @@ async function getConnection(args) {
79338
79338
  const endpointPath2 = `projects/${args.instance}/branches/${branchId}/endpoints/${endpointName}`;
79339
79339
  if (args.output === "dsn") {
79340
79340
  const host2 = await resolveEndpointHost(args.instance, branchId);
79341
- const { token, email: email2 } = await mintCredential(endpointPath2);
79342
- const url = buildPostgresUrl({ host: host2, port: POSTGRES_PORT, database, user: email2, password: token });
79341
+ const { token: token2, email: email2 } = await mintCredential(endpointPath2);
79342
+ const url = buildPostgresUrl({ host: host2, port: POSTGRES_PORT, database, user: email2, password: token2 });
79343
79343
  return { url, host: host2, port: POSTGRES_PORT, database, user: email2, endpointPath: endpointPath2 };
79344
79344
  }
79345
79345
  const host = await resolveEndpointHost(args.instance, branchId);
79346
- const email = await resolveCurrentUser();
79347
79346
  process.env.PGAPPNAME = connectionApplicationName();
79348
- return createLakebasePool({
79349
- endpoint: endpointPath2,
79350
- host,
79351
- database,
79352
- user: email,
79353
- // workspaceClient is passed through verbatim. createLakebasePool falls
79354
- // back to environment / ServiceContext when omitted.
79355
- ...args.workspaceClient !== void 0 ? { workspaceClient: args.workspaceClient } : {}
79356
- });
79347
+ if (args.workspaceClient !== void 0) {
79348
+ const email2 = await resolveCurrentUser();
79349
+ return createLakebasePool({
79350
+ endpoint: endpointPath2,
79351
+ host,
79352
+ database,
79353
+ user: email2,
79354
+ workspaceClient: args.workspaceClient
79355
+ });
79356
+ }
79357
+ const { token, email } = await mintCredential(endpointPath2);
79358
+ return createLakebasePool({ host, database, user: email, password: token });
79357
79359
  }
79358
79360
  async function resolveEndpointHost(instance, branch) {
79359
79361
  const branchId = await resolveBranchId({ instance, branch });
@@ -393,8 +393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
393
393
  var cached;
394
394
  function substrateSelfVersion() {
395
395
  if (cached !== void 0) return cached;
396
- if ("0.3.9".length > 0) {
397
- cached = "0.3.9";
396
+ if ("0.3.11".length > 0) {
397
+ cached = "0.3.11";
398
398
  return cached;
399
399
  }
400
400
  cached = "unknown";
@@ -441,22 +441,24 @@ async function getConnection(args) {
441
441
  const endpointPath2 = `projects/${args.instance}/branches/${branchId}/endpoints/${endpointName}`;
442
442
  if (args.output === "dsn") {
443
443
  const host2 = await resolveEndpointHost(args.instance, branchId);
444
- const { token, email: email2 } = await mintCredential(endpointPath2);
445
- const url = buildPostgresUrl({ host: host2, port: POSTGRES_PORT, database, user: email2, password: token });
444
+ const { token: token2, email: email2 } = await mintCredential(endpointPath2);
445
+ const url = buildPostgresUrl({ host: host2, port: POSTGRES_PORT, database, user: email2, password: token2 });
446
446
  return { url, host: host2, port: POSTGRES_PORT, database, user: email2, endpointPath: endpointPath2 };
447
447
  }
448
448
  const host = await resolveEndpointHost(args.instance, branchId);
449
- const email = await resolveCurrentUser();
450
449
  process.env.PGAPPNAME = connectionApplicationName();
451
- return createLakebasePool({
452
- endpoint: endpointPath2,
453
- host,
454
- database,
455
- user: email,
456
- // workspaceClient is passed through verbatim. createLakebasePool falls
457
- // back to environment / ServiceContext when omitted.
458
- ...args.workspaceClient !== void 0 ? { workspaceClient: args.workspaceClient } : {}
459
- });
450
+ if (args.workspaceClient !== void 0) {
451
+ const email2 = await resolveCurrentUser();
452
+ return createLakebasePool({
453
+ endpoint: endpointPath2,
454
+ host,
455
+ database,
456
+ user: email2,
457
+ workspaceClient: args.workspaceClient
458
+ });
459
+ }
460
+ const { token, email } = await mintCredential(endpointPath2);
461
+ return createLakebasePool({ host, database, user: email, password: token });
460
462
  }
461
463
  async function resolveEndpointHost(instance, branch) {
462
464
  const branchId = await resolveBranchId({ instance, branch });