@databricks-solutions/lakebase-scm-utils 0.3.4 → 0.3.6

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 (47) hide show
  1. package/dist/scripts/github/index.cjs +14 -0
  2. package/dist/scripts/github/index.d.cts +19 -1
  3. package/dist/scripts/github/index.d.ts +19 -1
  4. package/dist/scripts/github/index.js +13 -0
  5. package/dist/scripts/index.cjs +94 -6
  6. package/dist/scripts/index.d.cts +2 -2
  7. package/dist/scripts/index.d.ts +2 -2
  8. package/dist/scripts/index.js +91 -6
  9. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
  10. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
  11. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
  12. package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
  13. package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
  14. package/dist/scripts/lakebase/detect-language.cli.js +2 -2
  15. package/dist/scripts/lakebase/doctor.cli.cjs +15664 -7598
  16. package/dist/scripts/lakebase/doctor.cli.js +8243 -106
  17. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  18. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  19. package/dist/scripts/lakebase/index.cjs +187 -99
  20. package/dist/scripts/lakebase/index.d.cts +22 -1
  21. package/dist/scripts/lakebase/index.d.ts +22 -1
  22. package/dist/scripts/lakebase/index.js +185 -99
  23. package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
  24. package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
  25. package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
  26. package/dist/scripts/lakebase/new-migration.cli.js +2 -2
  27. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  28. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  29. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
  30. package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
  31. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
  32. package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
  33. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
  34. package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
  35. package/dist/scripts/lakebase/scm-merge.cli.cjs +2 -2
  36. package/dist/scripts/lakebase/scm-merge.cli.js +2 -2
  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/util/index.cjs +2 -2
  40. package/dist/scripts/util/index.js +2 -2
  41. package/package.json +1 -1
  42. package/scripts/github/repo.ts +37 -1
  43. package/scripts/lakebase/create-preflight.ts +45 -0
  44. package/scripts/lakebase/create-project.ts +15 -0
  45. package/scripts/lakebase/doctor.ts +50 -0
  46. package/scripts/lakebase/enable-e2e.ts +34 -4
  47. package/templates/project/common/.github/workflows/pr.yml +14 -5
@@ -2521,8 +2521,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
2521
2521
  var cached;
2522
2522
  function substrateSelfVersion() {
2523
2523
  if (cached !== void 0) return cached;
2524
- if ("0.3.4".length > 0) {
2525
- cached = "0.3.4";
2524
+ if ("0.3.6".length > 0) {
2525
+ cached = "0.3.6";
2526
2526
  return cached;
2527
2527
  }
2528
2528
  cached = "unknown";
@@ -11015,6 +11015,113 @@ init_esm_shims();
11015
11015
  import { spawnSync } from "child_process";
11016
11016
  import * as fs5 from "fs";
11017
11017
  import * as path5 from "path";
11018
+
11019
+ // scripts/github/repo.ts
11020
+ init_esm_shims();
11021
+ var GitHubRepoError = class extends Error {
11022
+ status;
11023
+ constructor(message, status) {
11024
+ super(message);
11025
+ this.name = "GitHubRepoError";
11026
+ this.status = status;
11027
+ }
11028
+ };
11029
+ async function newContext() {
11030
+ const token = await resolveGitHubToken();
11031
+ return { octokit: new Octokit2({ auth: token }) };
11032
+ }
11033
+ function wrap2(err, context) {
11034
+ if (err instanceof RequestError) {
11035
+ throw new GitHubRepoError(`${context}: ${err.message}`, err.status);
11036
+ }
11037
+ if (err instanceof Error) {
11038
+ throw new GitHubRepoError(`${context}: ${err.message}`);
11039
+ }
11040
+ throw new GitHubRepoError(context);
11041
+ }
11042
+ async function getLogin(ctx) {
11043
+ if (!ctx.loginPromise) {
11044
+ ctx.loginPromise = ctx.octokit.rest.users.getAuthenticated().then(({ data }) => data.login);
11045
+ }
11046
+ return ctx.loginPromise;
11047
+ }
11048
+ async function getCurrentUser() {
11049
+ try {
11050
+ const ctx = await newContext();
11051
+ return await getLogin(ctx);
11052
+ } catch (err) {
11053
+ wrap2(err, "GitHub authentication failed");
11054
+ }
11055
+ }
11056
+ async function checkGitHubScopes(required = GITHUB_SCOPES) {
11057
+ try {
11058
+ const ctx = await newContext();
11059
+ const res = await ctx.octokit.rest.users.getAuthenticated();
11060
+ const header = res.headers["x-oauth-scopes"];
11061
+ if (header === void 0 || header === null) return { granted: null, missing: [] };
11062
+ const granted = String(header).split(",").map((s) => s.trim()).filter(Boolean);
11063
+ return { granted, missing: required.filter((s) => !granted.includes(s)) };
11064
+ } catch (err) {
11065
+ wrap2(err, "GitHub scope check failed");
11066
+ }
11067
+ }
11068
+ async function createRepo(name, opts = {}) {
11069
+ try {
11070
+ const ctx = await newContext();
11071
+ const isPrivate = opts.private !== false;
11072
+ const description = opts.description;
11073
+ if (name.includes("/")) {
11074
+ const { owner, repo } = parseOwnerRepo(name);
11075
+ const login = await getLogin(ctx);
11076
+ let data2;
11077
+ if (owner.toLowerCase() === login.toLowerCase()) {
11078
+ ({ data: data2 } = await ctx.octokit.rest.repos.createForAuthenticatedUser({
11079
+ name: repo,
11080
+ private: isPrivate,
11081
+ description
11082
+ }));
11083
+ } else {
11084
+ ({ data: data2 } = await ctx.octokit.rest.repos.createInOrg({
11085
+ org: owner,
11086
+ name: repo,
11087
+ private: isPrivate,
11088
+ description
11089
+ }));
11090
+ }
11091
+ return data2.html_url || `https://github.com/${formatOwnerRepo(owner, repo)}`;
11092
+ }
11093
+ const { data } = await ctx.octokit.rest.repos.createForAuthenticatedUser({
11094
+ name,
11095
+ private: isPrivate,
11096
+ description
11097
+ });
11098
+ return data.html_url || `https://github.com/${data.full_name}`;
11099
+ } catch (err) {
11100
+ wrap2(err, `Failed to create repository "${name}"`);
11101
+ }
11102
+ }
11103
+ async function getActionsEnabled(ownerRepo) {
11104
+ try {
11105
+ const { owner, repo } = parseOwnerRepo(ownerRepo);
11106
+ const ctx = await newContext();
11107
+ const { data } = await ctx.octokit.rest.actions.getGithubActionsPermissionsRepository({ owner, repo });
11108
+ return data.enabled;
11109
+ } catch {
11110
+ return void 0;
11111
+ }
11112
+ }
11113
+ async function getRepoFullName(name) {
11114
+ try {
11115
+ const { owner, repo } = parseOwnerRepo(name);
11116
+ const ctx = await newContext();
11117
+ const { data } = await ctx.octokit.rest.repos.get({ owner, repo });
11118
+ return data.full_name || formatOwnerRepo(owner, repo);
11119
+ } catch (err) {
11120
+ wrap2(err, `Repository "${name}" is not visible`);
11121
+ }
11122
+ }
11123
+
11124
+ // scripts/lakebase/create-preflight.ts
11018
11125
  function validateCreateInputs(input) {
11019
11126
  if (input.useGithub && !input.githubOwner) {
11020
11127
  return { ok: false, reason: "GitHub owner is required when creating a GitHub repository" };
@@ -11049,6 +11156,22 @@ async function checkDatabricksAuth(host, run = (args) => runDatabricks(args, { h
11049
11156
  return { ok: false, reason: lastLines(e.stderr, 2) || e.message || "databricks auth token failed" };
11050
11157
  }
11051
11158
  }
11159
+ async function checkGitHubWorkflowScope(probe = () => checkGitHubScopes(["workflow"])) {
11160
+ try {
11161
+ const { granted, missing } = await probe();
11162
+ if (granted === null) return { ok: true };
11163
+ if (missing.includes("workflow")) {
11164
+ return { ok: false, reason: `token grants [${granted.join(", ") || "none"}] but not \`workflow\`` };
11165
+ }
11166
+ return { ok: true };
11167
+ } catch {
11168
+ return { ok: true };
11169
+ }
11170
+ }
11171
+ function githubWorkflowScopePrereqMessage(reason) {
11172
+ 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
+ (${reason})` : "");
11174
+ }
11052
11175
  function databricksAuthPrereqMessage(host, reason) {
11053
11176
  const hostFlag = host ? ` --host ${host.replace(/\/+$/, "")}` : "";
11054
11177
  return `Databricks authentication is required before creating a project. Run: databricks auth login${hostFlag}` + (reason ? `
@@ -11148,99 +11271,6 @@ function verifyProject(projectDir) {
11148
11271
  return { hooks, workflows, warnings };
11149
11272
  }
11150
11273
 
11151
- // scripts/github/repo.ts
11152
- init_esm_shims();
11153
- var GitHubRepoError = class extends Error {
11154
- status;
11155
- constructor(message, status) {
11156
- super(message);
11157
- this.name = "GitHubRepoError";
11158
- this.status = status;
11159
- }
11160
- };
11161
- async function newContext() {
11162
- const token = await resolveGitHubToken();
11163
- return { octokit: new Octokit2({ auth: token }) };
11164
- }
11165
- function wrap2(err, context) {
11166
- if (err instanceof RequestError) {
11167
- throw new GitHubRepoError(`${context}: ${err.message}`, err.status);
11168
- }
11169
- if (err instanceof Error) {
11170
- throw new GitHubRepoError(`${context}: ${err.message}`);
11171
- }
11172
- throw new GitHubRepoError(context);
11173
- }
11174
- async function getLogin(ctx) {
11175
- if (!ctx.loginPromise) {
11176
- ctx.loginPromise = ctx.octokit.rest.users.getAuthenticated().then(({ data }) => data.login);
11177
- }
11178
- return ctx.loginPromise;
11179
- }
11180
- async function getCurrentUser() {
11181
- try {
11182
- const ctx = await newContext();
11183
- return await getLogin(ctx);
11184
- } catch (err) {
11185
- wrap2(err, "GitHub authentication failed");
11186
- }
11187
- }
11188
- async function createRepo(name, opts = {}) {
11189
- try {
11190
- const ctx = await newContext();
11191
- const isPrivate = opts.private !== false;
11192
- const description = opts.description;
11193
- if (name.includes("/")) {
11194
- const { owner, repo } = parseOwnerRepo(name);
11195
- const login = await getLogin(ctx);
11196
- let data2;
11197
- if (owner.toLowerCase() === login.toLowerCase()) {
11198
- ({ data: data2 } = await ctx.octokit.rest.repos.createForAuthenticatedUser({
11199
- name: repo,
11200
- private: isPrivate,
11201
- description
11202
- }));
11203
- } else {
11204
- ({ data: data2 } = await ctx.octokit.rest.repos.createInOrg({
11205
- org: owner,
11206
- name: repo,
11207
- private: isPrivate,
11208
- description
11209
- }));
11210
- }
11211
- return data2.html_url || `https://github.com/${formatOwnerRepo(owner, repo)}`;
11212
- }
11213
- const { data } = await ctx.octokit.rest.repos.createForAuthenticatedUser({
11214
- name,
11215
- private: isPrivate,
11216
- description
11217
- });
11218
- return data.html_url || `https://github.com/${data.full_name}`;
11219
- } catch (err) {
11220
- wrap2(err, `Failed to create repository "${name}"`);
11221
- }
11222
- }
11223
- async function getActionsEnabled(ownerRepo) {
11224
- try {
11225
- const { owner, repo } = parseOwnerRepo(ownerRepo);
11226
- const ctx = await newContext();
11227
- const { data } = await ctx.octokit.rest.actions.getGithubActionsPermissionsRepository({ owner, repo });
11228
- return data.enabled;
11229
- } catch {
11230
- return void 0;
11231
- }
11232
- }
11233
- async function getRepoFullName(name) {
11234
- try {
11235
- const { owner, repo } = parseOwnerRepo(name);
11236
- const ctx = await newContext();
11237
- const { data } = await ctx.octokit.rest.repos.get({ owner, repo });
11238
- return data.full_name || formatOwnerRepo(owner, repo);
11239
- } catch (err) {
11240
- wrap2(err, `Repository "${name}" is not visible`);
11241
- }
11242
- }
11243
-
11244
11274
  // scripts/git/clone.ts
11245
11275
  init_esm_shims();
11246
11276
  async function cloneRepo(args) {
@@ -12318,6 +12348,9 @@ function ensurePythonBddDeps(args) {
12318
12348
  }
12319
12349
  var RUN_TESTS_E2E_MARKER = "# run Playwright E2E suite when configured";
12320
12350
  var CLIENT_E2E_MARKER = "# run client Playwright E2E suite when configured";
12351
+ var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12352
+ var RUN_TESTS_E2E_MARKER_LINE = new RegExp(`^${escapeRe(RUN_TESTS_E2E_MARKER)}$`, "m");
12353
+ var CLIENT_E2E_MARKER_LINE = new RegExp(`^${escapeRe(CLIENT_E2E_MARKER)}$`, "m");
12321
12354
  function runTestsE2eBlock() {
12322
12355
  return [
12323
12356
  "",
@@ -12409,14 +12442,22 @@ function addE2eToRunTestsScript(args) {
12409
12442
  return { patched: false, inserted: false };
12410
12443
  }
12411
12444
  const original = fs14.readFileSync(scriptPath, "utf8");
12412
- const hasBase = original.includes(RUN_TESTS_E2E_MARKER);
12413
- const hasClient = original.includes(CLIENT_E2E_MARKER);
12445
+ const baseMarker = RUN_TESTS_E2E_MARKER_LINE.exec(original);
12446
+ const hasBase = baseMarker !== null;
12447
+ const hasClient = CLIENT_E2E_MARKER_LINE.test(original);
12414
12448
  if (hasBase && hasClient) {
12415
12449
  return { patched: true, inserted: false };
12416
12450
  }
12417
- const cut = hasBase ? original.slice(0, original.indexOf(RUN_TESTS_E2E_MARKER)) : original;
12451
+ const cut = hasBase ? original.slice(0, baseMarker.index) : original;
12418
12452
  const base = cut.replace(/\n+$/, "\n");
12419
- fs14.writeFileSync(scriptPath, base + runTestsE2eBlock(), "utf8");
12453
+ const assembled = base + runTestsE2eBlock();
12454
+ const BODY_SENTINEL = 'REPO_ROOT="$(cd';
12455
+ if (original.includes(BODY_SENTINEL) && !assembled.includes(BODY_SENTINEL)) {
12456
+ throw new Error(
12457
+ `addE2eToRunTestsScript: refusing to write ${scriptPath} \u2014 the E2E assembly would drop the primary-suite body (REPO_ROOT/pytest/vitest). This is the marker-match clobber; the marker must anchor to the appended block's own line, not a prose mention.`
12458
+ );
12459
+ }
12460
+ fs14.writeFileSync(scriptPath, assembled, "utf8");
12420
12461
  return { patched: true, inserted: true };
12421
12462
  }
12422
12463
  function enableE2eForProject(args) {
@@ -13584,6 +13625,13 @@ async function createProject(input, progress) {
13584
13625
  if (!auth7.ok) {
13585
13626
  throw new Error(databricksAuthPrereqMessage(host, auth7.reason));
13586
13627
  }
13628
+ 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
+ }
13587
13635
  if (useGithub) {
13588
13636
  report("Creating GitHub repository...", fullRepoName);
13589
13637
  await createRepo(fullRepoName, {
@@ -18253,6 +18301,40 @@ async function checkGitRemote(projectDir) {
18253
18301
  };
18254
18302
  }
18255
18303
  }
18304
+ async function checkGitHubTokenScope(probe = () => checkGitHubScopes(["workflow"])) {
18305
+ try {
18306
+ const { granted, missing } = await probe();
18307
+ if (granted === null) {
18308
+ return {
18309
+ name: "github-token-scope",
18310
+ status: "skip",
18311
+ message: "GitHub token scopes not visible (fine-grained PAT / App token) - `workflow` scope unverified"
18312
+ };
18313
+ }
18314
+ if (missing.includes("workflow")) {
18315
+ return {
18316
+ name: "github-token-scope",
18317
+ status: "fail",
18318
+ message: "GitHub token lacks the `workflow` scope - pushes touching .github/workflows/* will be rejected",
18319
+ detail: { granted },
18320
+ hint: "Grant it with `gh auth refresh -h github.com -s workflow`, re-sign in to GitHub in VS Code and approve the workflow scope, or set GITHUB_TOKEN to a PAT that includes `workflow`."
18321
+ };
18322
+ }
18323
+ return {
18324
+ name: "github-token-scope",
18325
+ status: "ok",
18326
+ message: "GitHub token has the `workflow` scope",
18327
+ detail: { granted }
18328
+ };
18329
+ } catch (err) {
18330
+ return {
18331
+ name: "github-token-scope",
18332
+ status: "skip",
18333
+ message: "GitHub token scope check skipped (no token resolved, or GitHub unreachable)",
18334
+ detail: { error: err.message }
18335
+ };
18336
+ }
18337
+ }
18256
18338
  function checkLanguage(projectDir) {
18257
18339
  try {
18258
18340
  const lang = detectLanguage(projectDir);
@@ -18362,6 +18444,7 @@ async function runDoctor2(args = {}) {
18362
18444
  host
18363
18445
  );
18364
18446
  const gitRemote = await checkGitRemote(projectDir);
18447
+ const githubTokenScope = await checkGitHubTokenScope();
18365
18448
  const language = checkLanguage(projectDir);
18366
18449
  const hooks = checkHooks(projectDir);
18367
18450
  const workflowDrift = checkWorkflowDrift(projectDir);
@@ -18383,6 +18466,7 @@ async function runDoctor2(args = {}) {
18383
18466
  configProfile,
18384
18467
  lakebaseProject,
18385
18468
  gitRemote,
18469
+ githubTokenScope,
18386
18470
  language,
18387
18471
  hooks,
18388
18472
  workflowDrift
@@ -18607,6 +18691,7 @@ export {
18607
18691
  catalogExists,
18608
18692
  catalogExplorerUrl,
18609
18693
  checkDatabricksAuth,
18694
+ checkGitHubWorkflowScope,
18610
18695
  checkoutPaired,
18611
18696
  claimFeatureBranch,
18612
18697
  clearRetentionCache,
@@ -18689,6 +18774,7 @@ export {
18689
18774
  getRunnerInfo,
18690
18775
  getSchemaDiff,
18691
18776
  getTargetNames,
18777
+ githubWorkflowScopePrereqMessage,
18692
18778
  grantLakebasePermission,
18693
18779
  grantUcCatalogPermission,
18694
18780
  hasDotnetMarker,
@@ -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.4".length > 0) {
79294
- cached = "0.3.4";
79293
+ if ("0.3.6".length > 0) {
79294
+ cached = "0.3.6";
79295
79295
  return cached;
79296
79296
  }
79297
79297
  cached = "unknown";
@@ -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.4".length > 0) {
397
- cached = "0.3.4";
396
+ if ("0.3.6".length > 0) {
397
+ cached = "0.3.6";
398
398
  return cached;
399
399
  }
400
400
  cached = "unknown";
@@ -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.4".length > 0) {
79294
- cached = "0.3.4";
79293
+ if ("0.3.6".length > 0) {
79294
+ cached = "0.3.6";
79295
79295
  return cached;
79296
79296
  }
79297
79297
  cached = "unknown";
@@ -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.4".length > 0) {
397
- cached = "0.3.4";
396
+ if ("0.3.6".length > 0) {
397
+ cached = "0.3.6";
398
398
  return cached;
399
399
  }
400
400
  cached = "unknown";
@@ -79288,8 +79288,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79288
79288
  var cached;
79289
79289
  function substrateSelfVersion() {
79290
79290
  if (cached !== void 0) return cached;
79291
- if ("0.3.4".length > 0) {
79292
- cached = "0.3.4";
79291
+ if ("0.3.6".length > 0) {
79292
+ cached = "0.3.6";
79293
79293
  return cached;
79294
79294
  }
79295
79295
  cached = "unknown";
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
389
389
  var cached;
390
390
  function substrateSelfVersion() {
391
391
  if (cached !== void 0) return cached;
392
- if ("0.3.4".length > 0) {
393
- cached = "0.3.4";
392
+ if ("0.3.6".length > 0) {
393
+ cached = "0.3.6";
394
394
  return cached;
395
395
  }
396
396
  cached = "unknown";
@@ -79324,8 +79324,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79324
79324
  var cached;
79325
79325
  function substrateSelfVersion() {
79326
79326
  if (cached !== void 0) return cached;
79327
- if ("0.3.4".length > 0) {
79328
- cached = "0.3.4";
79327
+ if ("0.3.6".length > 0) {
79328
+ cached = "0.3.6";
79329
79329
  return cached;
79330
79330
  }
79331
79331
  cached = "unknown";
@@ -427,8 +427,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
427
427
  var cached;
428
428
  function substrateSelfVersion() {
429
429
  if (cached !== void 0) return cached;
430
- if ("0.3.4".length > 0) {
431
- cached = "0.3.4";
430
+ if ("0.3.6".length > 0) {
431
+ cached = "0.3.6";
432
432
  return cached;
433
433
  }
434
434
  cached = "unknown";
@@ -79783,8 +79783,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79783
79783
  var cached;
79784
79784
  function substrateSelfVersion() {
79785
79785
  if (cached !== void 0) return cached;
79786
- if ("0.3.4".length > 0) {
79787
- cached = "0.3.4";
79786
+ if ("0.3.6".length > 0) {
79787
+ cached = "0.3.6";
79788
79788
  return cached;
79789
79789
  }
79790
79790
  cached = "unknown";
@@ -864,8 +864,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
864
864
  var cached;
865
865
  function substrateSelfVersion() {
866
866
  if (cached !== void 0) return cached;
867
- if ("0.3.4".length > 0) {
868
- cached = "0.3.4";
867
+ if ("0.3.6".length > 0) {
868
+ cached = "0.3.6";
869
869
  return cached;
870
870
  }
871
871
  cached = "unknown";
@@ -88240,8 +88240,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
88240
88240
  var cached;
88241
88241
  function substrateSelfVersion() {
88242
88242
  if (cached !== void 0) return cached;
88243
- if ("0.3.4".length > 0) {
88244
- cached = "0.3.4";
88243
+ if ("0.3.6".length > 0) {
88244
+ cached = "0.3.6";
88245
88245
  return cached;
88246
88246
  }
88247
88247
  cached = "unknown";
@@ -9393,8 +9393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
9393
9393
  var cached;
9394
9394
  function substrateSelfVersion() {
9395
9395
  if (cached !== void 0) return cached;
9396
- if ("0.3.4".length > 0) {
9397
- cached = "0.3.4";
9396
+ if ("0.3.6".length > 0) {
9397
+ cached = "0.3.6";
9398
9398
  return cached;
9399
9399
  }
9400
9400
  cached = "unknown";
@@ -88825,8 +88825,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
88825
88825
  var cached;
88826
88826
  function substrateSelfVersion() {
88827
88827
  if (cached !== void 0) return cached;
88828
- if ("0.3.4".length > 0) {
88829
- cached = "0.3.4";
88828
+ if ("0.3.6".length > 0) {
88829
+ cached = "0.3.6";
88830
88830
  return cached;
88831
88831
  }
88832
88832
  cached = "unknown";
@@ -9978,8 +9978,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
9978
9978
  var cached;
9979
9979
  function substrateSelfVersion() {
9980
9980
  if (cached !== void 0) return cached;
9981
- if ("0.3.4".length > 0) {
9982
- cached = "0.3.4";
9981
+ if ("0.3.6".length > 0) {
9982
+ cached = "0.3.6";
9983
9983
  return cached;
9984
9984
  }
9985
9985
  cached = "unknown";
@@ -79288,8 +79288,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
79288
79288
  var cached;
79289
79289
  function substrateSelfVersion() {
79290
79290
  if (cached !== void 0) return cached;
79291
- if ("0.3.4".length > 0) {
79292
- cached = "0.3.4";
79291
+ if ("0.3.6".length > 0) {
79292
+ cached = "0.3.6";
79293
79293
  return cached;
79294
79294
  }
79295
79295
  cached = "unknown";
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
389
389
  var cached;
390
390
  function substrateSelfVersion() {
391
391
  if (cached !== void 0) return cached;
392
- if ("0.3.4".length > 0) {
393
- cached = "0.3.4";
392
+ if ("0.3.6".length > 0) {
393
+ cached = "0.3.6";
394
394
  return cached;
395
395
  }
396
396
  cached = "unknown";
@@ -1667,8 +1667,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
1667
1667
  var cached;
1668
1668
  function substrateSelfVersion() {
1669
1669
  if (cached !== void 0) return cached;
1670
- if ("0.3.4".length > 0) {
1671
- cached = "0.3.4";
1670
+ if ("0.3.6".length > 0) {
1671
+ cached = "0.3.6";
1672
1672
  return cached;
1673
1673
  }
1674
1674
  cached = "unknown";
@@ -1630,8 +1630,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
1630
1630
  var cached;
1631
1631
  function substrateSelfVersion() {
1632
1632
  if (cached !== void 0) return cached;
1633
- if ("0.3.4".length > 0) {
1634
- cached = "0.3.4";
1633
+ if ("0.3.6".length > 0) {
1634
+ cached = "0.3.6";
1635
1635
  return cached;
1636
1636
  }
1637
1637
  cached = "unknown";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databricks-solutions/lakebase-scm-utils",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Lakebase SCM + substrate engine: database branching, paired-branch SCM workflows, connection/credential minting, schema migration, project scaffold + deploy primitives, and the git/github utility layer. Extracted from consort for portable, standalone consumption by lakebase-scm-extension and the SFTDD orchestration.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -4,7 +4,7 @@
4
4
  // (scripts/github/auth.ts).
5
5
 
6
6
  import { Octokit, RequestError } from "octokit";
7
- import { resolveGitHubToken } from "./auth.js";
7
+ import { resolveGitHubToken, GITHUB_SCOPES } from "./auth.js";
8
8
  import { parseOwnerRepo, formatOwnerRepo } from "../util/parse-owner-repo.js";
9
9
 
10
10
  export class GitHubRepoError extends Error {
@@ -56,6 +56,42 @@ export async function getCurrentUser(): Promise<string> {
56
56
  }
57
57
  }
58
58
 
59
+ export interface GitHubScopeStatus {
60
+ /** Granted OAuth scopes, or null when the token exposes NONE — a fine-grained
61
+ * PAT or GitHub App token carries no OAuth scope header; its permissions
62
+ * aren't a scope set, so callers MUST treat null as "unknown", never a miss. */
63
+ granted: string[] | null;
64
+ /** Required scopes absent from `granted` (empty when `granted` is null). */
65
+ missing: string[];
66
+ }
67
+
68
+ /**
69
+ * Read the authenticated token's granted OAuth scopes from the GitHub API
70
+ * `x-oauth-scopes` response header and diff them against `required`. A classic
71
+ * OAuth/PAT token returns a (possibly empty) comma-separated header; a
72
+ * fine-grained PAT / GitHub App token omits it entirely → `granted: null`.
73
+ * Reading your own user needs no special scope, so this succeeds for any VALID
74
+ * token regardless of scopes (and throws, like the other ops, on a bad token /
75
+ * network error — callers decide whether that is blocking).
76
+ */
77
+ export async function checkGitHubScopes(
78
+ required: readonly string[] = GITHUB_SCOPES
79
+ ): Promise<GitHubScopeStatus> {
80
+ try {
81
+ const ctx = await newContext();
82
+ const res = await ctx.octokit.rest.users.getAuthenticated();
83
+ const header = (res.headers as Record<string, string | undefined>)["x-oauth-scopes"];
84
+ if (header === undefined || header === null) return { granted: null, missing: [] };
85
+ const granted = String(header)
86
+ .split(",")
87
+ .map((s) => s.trim())
88
+ .filter(Boolean);
89
+ return { granted, missing: required.filter((s) => !granted.includes(s)) };
90
+ } catch (err) {
91
+ wrap(err, "GitHub scope check failed");
92
+ }
93
+ }
94
+
59
95
  export interface CreateRepoOptions {
60
96
  /** Make the repo private. Default: true. */
61
97
  private?: boolean;