@databricks-solutions/lakebase-scm-utils 0.3.4 → 0.3.5
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.
- package/dist/scripts/github/index.cjs +14 -0
- package/dist/scripts/github/index.d.cts +19 -1
- package/dist/scripts/github/index.d.ts +19 -1
- package/dist/scripts/github/index.js +13 -0
- package/dist/scripts/index.cjs +79 -2
- package/dist/scripts/index.d.cts +2 -2
- package/dist/scripts/index.d.ts +2 -2
- package/dist/scripts/index.js +76 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
- package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
- package/dist/scripts/lakebase/detect-language.cli.js +2 -2
- package/dist/scripts/lakebase/doctor.cli.cjs +15664 -7598
- package/dist/scripts/lakebase/doctor.cli.js +8243 -106
- package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
- package/dist/scripts/lakebase/get-connection.cli.js +2 -2
- package/dist/scripts/lakebase/index.cjs +172 -95
- package/dist/scripts/lakebase/index.d.cts +22 -1
- package/dist/scripts/lakebase/index.d.ts +22 -1
- package/dist/scripts/lakebase/index.js +170 -95
- package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
- package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
- package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
- package/dist/scripts/lakebase/new-migration.cli.js +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.js +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/scripts/github/repo.ts +37 -1
- package/scripts/lakebase/create-preflight.ts +45 -0
- package/scripts/lakebase/create-project.ts +15 -0
- package/scripts/lakebase/doctor.ts +50 -0
- 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.
|
|
2525
|
-
cached = "0.3.
|
|
2524
|
+
if ("0.3.5".length > 0) {
|
|
2525
|
+
cached = "0.3.5";
|
|
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) {
|
|
@@ -13584,6 +13614,13 @@ async function createProject(input, progress) {
|
|
|
13584
13614
|
if (!auth7.ok) {
|
|
13585
13615
|
throw new Error(databricksAuthPrereqMessage(host, auth7.reason));
|
|
13586
13616
|
}
|
|
13617
|
+
if (useGithub) {
|
|
13618
|
+
report("Checking GitHub token scopes...");
|
|
13619
|
+
const scope = await checkGitHubWorkflowScope();
|
|
13620
|
+
if (!scope.ok) {
|
|
13621
|
+
throw new Error(githubWorkflowScopePrereqMessage(scope.reason));
|
|
13622
|
+
}
|
|
13623
|
+
}
|
|
13587
13624
|
if (useGithub) {
|
|
13588
13625
|
report("Creating GitHub repository...", fullRepoName);
|
|
13589
13626
|
await createRepo(fullRepoName, {
|
|
@@ -18253,6 +18290,40 @@ async function checkGitRemote(projectDir) {
|
|
|
18253
18290
|
};
|
|
18254
18291
|
}
|
|
18255
18292
|
}
|
|
18293
|
+
async function checkGitHubTokenScope(probe = () => checkGitHubScopes(["workflow"])) {
|
|
18294
|
+
try {
|
|
18295
|
+
const { granted, missing } = await probe();
|
|
18296
|
+
if (granted === null) {
|
|
18297
|
+
return {
|
|
18298
|
+
name: "github-token-scope",
|
|
18299
|
+
status: "skip",
|
|
18300
|
+
message: "GitHub token scopes not visible (fine-grained PAT / App token) - `workflow` scope unverified"
|
|
18301
|
+
};
|
|
18302
|
+
}
|
|
18303
|
+
if (missing.includes("workflow")) {
|
|
18304
|
+
return {
|
|
18305
|
+
name: "github-token-scope",
|
|
18306
|
+
status: "fail",
|
|
18307
|
+
message: "GitHub token lacks the `workflow` scope - pushes touching .github/workflows/* will be rejected",
|
|
18308
|
+
detail: { granted },
|
|
18309
|
+
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`."
|
|
18310
|
+
};
|
|
18311
|
+
}
|
|
18312
|
+
return {
|
|
18313
|
+
name: "github-token-scope",
|
|
18314
|
+
status: "ok",
|
|
18315
|
+
message: "GitHub token has the `workflow` scope",
|
|
18316
|
+
detail: { granted }
|
|
18317
|
+
};
|
|
18318
|
+
} catch (err) {
|
|
18319
|
+
return {
|
|
18320
|
+
name: "github-token-scope",
|
|
18321
|
+
status: "skip",
|
|
18322
|
+
message: "GitHub token scope check skipped (no token resolved, or GitHub unreachable)",
|
|
18323
|
+
detail: { error: err.message }
|
|
18324
|
+
};
|
|
18325
|
+
}
|
|
18326
|
+
}
|
|
18256
18327
|
function checkLanguage(projectDir) {
|
|
18257
18328
|
try {
|
|
18258
18329
|
const lang = detectLanguage(projectDir);
|
|
@@ -18362,6 +18433,7 @@ async function runDoctor2(args = {}) {
|
|
|
18362
18433
|
host
|
|
18363
18434
|
);
|
|
18364
18435
|
const gitRemote = await checkGitRemote(projectDir);
|
|
18436
|
+
const githubTokenScope = await checkGitHubTokenScope();
|
|
18365
18437
|
const language = checkLanguage(projectDir);
|
|
18366
18438
|
const hooks = checkHooks(projectDir);
|
|
18367
18439
|
const workflowDrift = checkWorkflowDrift(projectDir);
|
|
@@ -18383,6 +18455,7 @@ async function runDoctor2(args = {}) {
|
|
|
18383
18455
|
configProfile,
|
|
18384
18456
|
lakebaseProject,
|
|
18385
18457
|
gitRemote,
|
|
18458
|
+
githubTokenScope,
|
|
18386
18459
|
language,
|
|
18387
18460
|
hooks,
|
|
18388
18461
|
workflowDrift
|
|
@@ -18607,6 +18680,7 @@ export {
|
|
|
18607
18680
|
catalogExists,
|
|
18608
18681
|
catalogExplorerUrl,
|
|
18609
18682
|
checkDatabricksAuth,
|
|
18683
|
+
checkGitHubWorkflowScope,
|
|
18610
18684
|
checkoutPaired,
|
|
18611
18685
|
claimFeatureBranch,
|
|
18612
18686
|
clearRetentionCache,
|
|
@@ -18689,6 +18763,7 @@ export {
|
|
|
18689
18763
|
getRunnerInfo,
|
|
18690
18764
|
getSchemaDiff,
|
|
18691
18765
|
getTargetNames,
|
|
18766
|
+
githubWorkflowScopePrereqMessage,
|
|
18692
18767
|
grantLakebasePermission,
|
|
18693
18768
|
grantUcCatalogPermission,
|
|
18694
18769
|
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.
|
|
79294
|
-
cached = "0.3.
|
|
79293
|
+
if ("0.3.5".length > 0) {
|
|
79294
|
+
cached = "0.3.5";
|
|
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.
|
|
397
|
-
cached = "0.3.
|
|
396
|
+
if ("0.3.5".length > 0) {
|
|
397
|
+
cached = "0.3.5";
|
|
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.
|
|
79294
|
-
cached = "0.3.
|
|
79293
|
+
if ("0.3.5".length > 0) {
|
|
79294
|
+
cached = "0.3.5";
|
|
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.
|
|
397
|
-
cached = "0.3.
|
|
396
|
+
if ("0.3.5".length > 0) {
|
|
397
|
+
cached = "0.3.5";
|
|
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.
|
|
79292
|
-
cached = "0.3.
|
|
79291
|
+
if ("0.3.5".length > 0) {
|
|
79292
|
+
cached = "0.3.5";
|
|
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.
|
|
393
|
-
cached = "0.3.
|
|
392
|
+
if ("0.3.5".length > 0) {
|
|
393
|
+
cached = "0.3.5";
|
|
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.
|
|
79328
|
-
cached = "0.3.
|
|
79327
|
+
if ("0.3.5".length > 0) {
|
|
79328
|
+
cached = "0.3.5";
|
|
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.
|
|
431
|
-
cached = "0.3.
|
|
430
|
+
if ("0.3.5".length > 0) {
|
|
431
|
+
cached = "0.3.5";
|
|
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.
|
|
79787
|
-
cached = "0.3.
|
|
79786
|
+
if ("0.3.5".length > 0) {
|
|
79787
|
+
cached = "0.3.5";
|
|
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.
|
|
868
|
-
cached = "0.3.
|
|
867
|
+
if ("0.3.5".length > 0) {
|
|
868
|
+
cached = "0.3.5";
|
|
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.
|
|
88244
|
-
cached = "0.3.
|
|
88243
|
+
if ("0.3.5".length > 0) {
|
|
88244
|
+
cached = "0.3.5";
|
|
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.
|
|
9397
|
-
cached = "0.3.
|
|
9396
|
+
if ("0.3.5".length > 0) {
|
|
9397
|
+
cached = "0.3.5";
|
|
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.
|
|
88829
|
-
cached = "0.3.
|
|
88828
|
+
if ("0.3.5".length > 0) {
|
|
88829
|
+
cached = "0.3.5";
|
|
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.
|
|
9982
|
-
cached = "0.3.
|
|
9981
|
+
if ("0.3.5".length > 0) {
|
|
9982
|
+
cached = "0.3.5";
|
|
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.
|
|
79292
|
-
cached = "0.3.
|
|
79291
|
+
if ("0.3.5".length > 0) {
|
|
79292
|
+
cached = "0.3.5";
|
|
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.
|
|
393
|
-
cached = "0.3.
|
|
392
|
+
if ("0.3.5".length > 0) {
|
|
393
|
+
cached = "0.3.5";
|
|
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.
|
|
1671
|
-
cached = "0.3.
|
|
1670
|
+
if ("0.3.5".length > 0) {
|
|
1671
|
+
cached = "0.3.5";
|
|
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.
|
|
1634
|
-
cached = "0.3.
|
|
1633
|
+
if ("0.3.5".length > 0) {
|
|
1634
|
+
cached = "0.3.5";
|
|
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.
|
|
3
|
+
"version": "0.3.5",
|
|
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,
|
package/scripts/github/repo.ts
CHANGED
|
@@ -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;
|
|
@@ -19,6 +19,7 @@ import * as fs from "node:fs";
|
|
|
19
19
|
import * as path from "node:path";
|
|
20
20
|
import { deleteLakebaseProject } from "./lakebase-project.js";
|
|
21
21
|
import { runDatabricks } from "./databricks-cli.js";
|
|
22
|
+
import { checkGitHubScopes } from "../github/repo.js";
|
|
22
23
|
|
|
23
24
|
export interface PreflightResult {
|
|
24
25
|
ok: boolean;
|
|
@@ -113,6 +114,50 @@ export async function checkDatabricksAuth(
|
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
/** Injectable scope probe for checkGitHubWorkflowScope (defaults to the real GitHub API read). */
|
|
118
|
+
type ScopeProbe = () => Promise<{ granted: string[] | null; missing: string[] }>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* W-workflow-scope: preflight the GitHub token's `workflow` OAuth scope BEFORE
|
|
122
|
+
* any repo / Lakebase project is created. The scaffold's FINAL push commits
|
|
123
|
+
* `.github/workflows/*`, which GitHub REJECTS unless the token carries
|
|
124
|
+
* `workflow` — without this, that rejection only surfaces at the very end,
|
|
125
|
+
* after all the provisioning, forcing a full rollback. Gating the ONE scope the
|
|
126
|
+
* push needs here turns that into an immediate, actionable failure.
|
|
127
|
+
*
|
|
128
|
+
* Non-blocking in two cases (ok:true): a token that exposes NO OAuth scopes
|
|
129
|
+
* (fine-grained PAT / GitHub App token — `granted` null; its workflow
|
|
130
|
+
* permission isn't visible this way, so we can't assert), and any probe error
|
|
131
|
+
* (a genuinely bad/absent token surfaces clearly at repo creation, not here).
|
|
132
|
+
*/
|
|
133
|
+
export async function checkGitHubWorkflowScope(
|
|
134
|
+
probe: ScopeProbe = () => checkGitHubScopes(["workflow"]),
|
|
135
|
+
): Promise<PreflightResult> {
|
|
136
|
+
try {
|
|
137
|
+
const { granted, missing } = await probe();
|
|
138
|
+
if (granted === null) return { ok: true }; // scopes not visible → can't assert, don't false-fail
|
|
139
|
+
if (missing.includes("workflow")) {
|
|
140
|
+
return { ok: false, reason: `token grants [${granted.join(", ") || "none"}] but not \`workflow\`` };
|
|
141
|
+
}
|
|
142
|
+
return { ok: true };
|
|
143
|
+
} catch {
|
|
144
|
+
return { ok: true }; // best-effort: a probe failure must not block scaffold
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The actionable prereq message for a GitHub token missing the `workflow` scope. */
|
|
149
|
+
export function githubWorkflowScopePrereqMessage(reason?: string): string {
|
|
150
|
+
return (
|
|
151
|
+
"The GitHub token lacks the `workflow` OAuth scope, required to push the project's " +
|
|
152
|
+
"`.github/workflows/*` CI files. (Without this check, scaffolding runs all the way to the " +
|
|
153
|
+
"final push, is rejected, and rolls back.) Grant the scope, then re-run:\n" +
|
|
154
|
+
" - gh CLI: gh auth refresh -h github.com -s workflow\n" +
|
|
155
|
+
" - VS Code: re-sign in to GitHub and approve the workflow scope\n" +
|
|
156
|
+
" - token: set GITHUB_TOKEN to a PAT that includes the `workflow` scope" +
|
|
157
|
+
(reason ? `\n(${reason})` : "")
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
116
161
|
/** The actionable prereq message for a failed auth probe (W5). */
|
|
117
162
|
export function databricksAuthPrereqMessage(host?: string, reason?: string): string {
|
|
118
163
|
const hostFlag = host ? ` --host ${host.replace(/\/+$/, "")}` : "";
|