@databricks-solutions/lakebase-scm-utils 0.2.48 → 0.2.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ci-secrets-CnGrdQV-.d.cts +84 -0
- package/dist/ci-secrets-CnGrdQV-.d.ts +84 -0
- package/dist/scripts/index.cjs +2177 -2052
- package/dist/scripts/index.d.cts +2 -1
- package/dist/scripts/index.d.ts +2 -1
- package/dist/scripts/index.js +253 -134
- 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 +2 -2
- package/dist/scripts/lakebase/doctor.cli.js +2 -2
- 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 +2163 -2050
- package/dist/scripts/lakebase/index.d.cts +8 -0
- package/dist/scripts/lakebase/index.d.ts +8 -0
- package/dist/scripts/lakebase/index.js +243 -130
- 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 +2216 -1994
- package/dist/scripts/lakebase/scm-merge.cli.js +296 -74
- package/dist/scripts/lakebase/scm-prepare-pr.cli.cjs +479 -86
- package/dist/scripts/lakebase/scm-prepare-pr.cli.js +479 -86
- 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/lakebase/sync-ci-secrets.cli.cjs +56 -30
- package/dist/scripts/lakebase/sync-ci-secrets.cli.js +56 -30
- package/dist/scripts/util/index.cjs +142 -25
- package/dist/scripts/util/index.d.cts +2 -27
- package/dist/scripts/util/index.d.ts +2 -27
- package/dist/scripts/util/index.js +136 -25
- package/package.json +1 -1
- package/scripts/lakebase/create-project.ts +2 -2
- package/scripts/lakebase/scm-merge.ts +21 -0
- package/scripts/lakebase/scm-prepare-pr.ts +10 -1
- package/scripts/lakebase/sync-ci-secrets.cli.ts +2 -2
- package/scripts/util/ci-secrets.ts +212 -26
- package/templates/project/common/scripts/create-token-and-sync-secrets.sh +9 -86
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { C as CwdOnly, E as ExecOptions, e as exec, s as shq } from '../../exec-CwxSWhlz.cjs';
|
|
2
|
+
export { C as CI_TOKEN_LIFETIME_SECONDS, a as CI_TOKEN_REMINT_MARGIN_SECONDS, b as CiFreshnessResult, c as CiTokenExpiryArgs, E as EnsureCiSecretsFreshArgs, R as REQUIRED_CI_SECRETS, S as SyncCiSecretsArgs, d as ciTokenComment, e as ciTokenExpiry, f as ensureCiSecretsFresh, g as ensureCiSecretsFreshFromEnv, m as missingCiSecrets, s as syncCiSecrets } from '../../ci-secrets-CnGrdQV-.cjs';
|
|
2
3
|
|
|
3
4
|
/** Promise-based sleep – replaces shell `sleep` in poll loops. */
|
|
4
5
|
declare function delay(ms: number): Promise<void>;
|
|
@@ -33,32 +34,6 @@ declare function copyDirSubstituted(srcDir: string, destDir: string, args?: Copy
|
|
|
33
34
|
declare const LAKEBASE_BRANCH_NAME_MAX = 63;
|
|
34
35
|
declare function sanitizeBranchName(gitBranch: string): string;
|
|
35
36
|
|
|
36
|
-
/** The repo Actions secrets CI needs to provision a per-PR Lakebase branch. Missing ANY of
|
|
37
|
-
* these leaves CI unable to produce a DATABASE_URL, so the test step aborts — the failure
|
|
38
|
-
* that otherwise only surfaces at promotion, an entire feature after creation. */
|
|
39
|
-
declare const REQUIRED_CI_SECRETS: readonly ["DATABRICKS_HOST", "LAKEBASE_PROJECT_ID", "DATABRICKS_TOKEN"];
|
|
40
|
-
/** Which required CI secrets are NOT present on the repo (empty = fully provisioned). Used to
|
|
41
|
-
* VERIFY a sync landed — a `databricks tokens create` or `gh secret set` that fails must be
|
|
42
|
-
* caught here, not swallowed, so CI-auth is never silently absent. */
|
|
43
|
-
declare function missingCiSecrets(ownerRepo: string): Promise<string[]>;
|
|
44
|
-
interface SyncCiSecretsArgs {
|
|
45
|
-
/** Project root (used to resolve ownerRepo from `git remote` when not given,
|
|
46
|
-
* and as the cwd for the `databricks tokens create` call). */
|
|
47
|
-
projectDir: string;
|
|
48
|
-
/** Workspace host (DATABRICKS_HOST secret). Required. */
|
|
49
|
-
databricksHost: string;
|
|
50
|
-
/** Lakebase project id (LAKEBASE_PROJECT_ID secret). Required. */
|
|
51
|
-
lakebaseProjectId: string;
|
|
52
|
-
/** Token comment for `databricks tokens create`. */
|
|
53
|
-
comment?: string;
|
|
54
|
-
/** Token lifetime in seconds (default: 24h). */
|
|
55
|
-
lifetimeSeconds?: number;
|
|
56
|
-
/** Override the auto-detected ownerRepo (defaults to origin remote). */
|
|
57
|
-
ownerRepo?: string;
|
|
58
|
-
}
|
|
59
|
-
/** Synchronize Databricks + Lakebase CI secrets to the repo's Actions secrets. */
|
|
60
|
-
declare function syncCiSecrets(args: SyncCiSecretsArgs): Promise<void>;
|
|
61
|
-
|
|
62
37
|
/**
|
|
63
38
|
* Return true iff the current module is being executed as a Node.js
|
|
64
39
|
* entry point (vs. imported as a library). Pass `import.meta.url`
|
|
@@ -169,4 +144,4 @@ declare function pollUntilDefined<T>(probe: (ctx: {
|
|
|
169
144
|
elapsedMs: number;
|
|
170
145
|
}) => Promise<T | undefined>, opts: Omit<PollUntilArgs<T>, "probe">): Promise<PollUntilResult<T>>;
|
|
171
146
|
|
|
172
|
-
export { type CopyDirSubstitutedArgs, LAKEBASE_BRANCH_NAME_MAX, type OwnerRepo, PROXY_ENV_KEYS, type PollProbeDone, type PollProbePending, type PollProbeResult, type PollUntilArgs, type PollUntilDoneResult, type PollUntilResult, type PollUntilTimeoutResult,
|
|
147
|
+
export { type CopyDirSubstitutedArgs, LAKEBASE_BRANCH_NAME_MAX, type OwnerRepo, PROXY_ENV_KEYS, type PollProbeDone, type PollProbePending, type PollProbeResult, type PollUntilArgs, type PollUntilDoneResult, type PollUntilResult, type PollUntilTimeoutResult, copyDirSubstituted, delay, extractZipToDir, formatOwnerRepo, isCliEntry, parseOwnerRepo, patchPomForLakebase, pollUntil, pollUntilDefined, proxyEnvSubset, sanitizeArtifactId, sanitizeBranchName, withProxyEnv };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { C as CwdOnly, E as ExecOptions, e as exec, s as shq } from '../../exec-CwxSWhlz.js';
|
|
2
|
+
export { C as CI_TOKEN_LIFETIME_SECONDS, a as CI_TOKEN_REMINT_MARGIN_SECONDS, b as CiFreshnessResult, c as CiTokenExpiryArgs, E as EnsureCiSecretsFreshArgs, R as REQUIRED_CI_SECRETS, S as SyncCiSecretsArgs, d as ciTokenComment, e as ciTokenExpiry, f as ensureCiSecretsFresh, g as ensureCiSecretsFreshFromEnv, m as missingCiSecrets, s as syncCiSecrets } from '../../ci-secrets-CnGrdQV-.js';
|
|
2
3
|
|
|
3
4
|
/** Promise-based sleep – replaces shell `sleep` in poll loops. */
|
|
4
5
|
declare function delay(ms: number): Promise<void>;
|
|
@@ -33,32 +34,6 @@ declare function copyDirSubstituted(srcDir: string, destDir: string, args?: Copy
|
|
|
33
34
|
declare const LAKEBASE_BRANCH_NAME_MAX = 63;
|
|
34
35
|
declare function sanitizeBranchName(gitBranch: string): string;
|
|
35
36
|
|
|
36
|
-
/** The repo Actions secrets CI needs to provision a per-PR Lakebase branch. Missing ANY of
|
|
37
|
-
* these leaves CI unable to produce a DATABASE_URL, so the test step aborts — the failure
|
|
38
|
-
* that otherwise only surfaces at promotion, an entire feature after creation. */
|
|
39
|
-
declare const REQUIRED_CI_SECRETS: readonly ["DATABRICKS_HOST", "LAKEBASE_PROJECT_ID", "DATABRICKS_TOKEN"];
|
|
40
|
-
/** Which required CI secrets are NOT present on the repo (empty = fully provisioned). Used to
|
|
41
|
-
* VERIFY a sync landed — a `databricks tokens create` or `gh secret set` that fails must be
|
|
42
|
-
* caught here, not swallowed, so CI-auth is never silently absent. */
|
|
43
|
-
declare function missingCiSecrets(ownerRepo: string): Promise<string[]>;
|
|
44
|
-
interface SyncCiSecretsArgs {
|
|
45
|
-
/** Project root (used to resolve ownerRepo from `git remote` when not given,
|
|
46
|
-
* and as the cwd for the `databricks tokens create` call). */
|
|
47
|
-
projectDir: string;
|
|
48
|
-
/** Workspace host (DATABRICKS_HOST secret). Required. */
|
|
49
|
-
databricksHost: string;
|
|
50
|
-
/** Lakebase project id (LAKEBASE_PROJECT_ID secret). Required. */
|
|
51
|
-
lakebaseProjectId: string;
|
|
52
|
-
/** Token comment for `databricks tokens create`. */
|
|
53
|
-
comment?: string;
|
|
54
|
-
/** Token lifetime in seconds (default: 24h). */
|
|
55
|
-
lifetimeSeconds?: number;
|
|
56
|
-
/** Override the auto-detected ownerRepo (defaults to origin remote). */
|
|
57
|
-
ownerRepo?: string;
|
|
58
|
-
}
|
|
59
|
-
/** Synchronize Databricks + Lakebase CI secrets to the repo's Actions secrets. */
|
|
60
|
-
declare function syncCiSecrets(args: SyncCiSecretsArgs): Promise<void>;
|
|
61
|
-
|
|
62
37
|
/**
|
|
63
38
|
* Return true iff the current module is being executed as a Node.js
|
|
64
39
|
* entry point (vs. imported as a library). Pass `import.meta.url`
|
|
@@ -169,4 +144,4 @@ declare function pollUntilDefined<T>(probe: (ctx: {
|
|
|
169
144
|
elapsedMs: number;
|
|
170
145
|
}) => Promise<T | undefined>, opts: Omit<PollUntilArgs<T>, "probe">): Promise<PollUntilResult<T>>;
|
|
171
146
|
|
|
172
|
-
export { type CopyDirSubstitutedArgs, LAKEBASE_BRANCH_NAME_MAX, type OwnerRepo, PROXY_ENV_KEYS, type PollProbeDone, type PollProbePending, type PollProbeResult, type PollUntilArgs, type PollUntilDoneResult, type PollUntilResult, type PollUntilTimeoutResult,
|
|
147
|
+
export { type CopyDirSubstitutedArgs, LAKEBASE_BRANCH_NAME_MAX, type OwnerRepo, PROXY_ENV_KEYS, type PollProbeDone, type PollProbePending, type PollProbeResult, type PollUntilArgs, type PollUntilDoneResult, type PollUntilResult, type PollUntilTimeoutResult, copyDirSubstituted, delay, extractZipToDir, formatOwnerRepo, isCliEntry, parseOwnerRepo, patchPomForLakebase, pollUntil, pollUntilDefined, proxyEnvSubset, sanitizeArtifactId, sanitizeBranchName, withProxyEnv };
|
|
@@ -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.2.
|
|
1634
|
-
cached = "0.2.
|
|
1633
|
+
if ("0.2.49".length > 0) {
|
|
1634
|
+
cached = "0.2.49";
|
|
1635
1635
|
return cached;
|
|
1636
1636
|
}
|
|
1637
1637
|
cached = "unknown";
|
|
@@ -1691,6 +1691,7 @@ function sanitizeBranchName(gitBranch) {
|
|
|
1691
1691
|
|
|
1692
1692
|
// scripts/util/ci-secrets.ts
|
|
1693
1693
|
init_esm_shims();
|
|
1694
|
+
import * as path6 from "path";
|
|
1694
1695
|
|
|
1695
1696
|
// scripts/lakebase/databricks-cli.ts
|
|
1696
1697
|
init_esm_shims();
|
|
@@ -2992,7 +2993,7 @@ paginateRest.VERSION = VERSION5;
|
|
|
2992
2993
|
|
|
2993
2994
|
// node_modules/@octokit/plugin-paginate-graphql/dist-bundle/index.js
|
|
2994
2995
|
init_esm_shims();
|
|
2995
|
-
var generateMessage = (
|
|
2996
|
+
var generateMessage = (path7, cursorValue) => `The cursor at "${path7.join(
|
|
2996
2997
|
","
|
|
2997
2998
|
)}" did not change its value "${cursorValue}" after a page transition. Please make sure your that your query is set up correctly.`;
|
|
2998
2999
|
var MissingCursorChange = class extends Error {
|
|
@@ -3033,9 +3034,9 @@ function findPaginatedResourcePath(responseData) {
|
|
|
3033
3034
|
}
|
|
3034
3035
|
return paginatedResourcePath;
|
|
3035
3036
|
}
|
|
3036
|
-
var deepFindPathToProperty = (object, searchProp,
|
|
3037
|
+
var deepFindPathToProperty = (object, searchProp, path7 = []) => {
|
|
3037
3038
|
for (const key of Object.keys(object)) {
|
|
3038
|
-
const currentPath = [...
|
|
3039
|
+
const currentPath = [...path7, key];
|
|
3039
3040
|
const currentValue = object[key];
|
|
3040
3041
|
if (isObject(currentValue)) {
|
|
3041
3042
|
if (currentValue.hasOwnProperty(searchProp)) {
|
|
@@ -3053,12 +3054,12 @@ var deepFindPathToProperty = (object, searchProp, path6 = []) => {
|
|
|
3053
3054
|
}
|
|
3054
3055
|
return [];
|
|
3055
3056
|
};
|
|
3056
|
-
var get = (object,
|
|
3057
|
-
return
|
|
3057
|
+
var get = (object, path7) => {
|
|
3058
|
+
return path7.reduce((current, nextProperty) => current[nextProperty], object);
|
|
3058
3059
|
};
|
|
3059
|
-
var set = (object,
|
|
3060
|
-
const lastProperty =
|
|
3061
|
-
const parentPath = [...
|
|
3060
|
+
var set = (object, path7, mutator) => {
|
|
3061
|
+
const lastProperty = path7[path7.length - 1];
|
|
3062
|
+
const parentPath = [...path7].slice(0, -1);
|
|
3062
3063
|
const parent = get(object, parentPath);
|
|
3063
3064
|
if (typeof mutator === "function") {
|
|
3064
3065
|
parent[lastProperty] = mutator(parent[lastProperty]);
|
|
@@ -3110,22 +3111,22 @@ var mergeResponses = (response1, response2) => {
|
|
|
3110
3111
|
if (Object.keys(response1).length === 0) {
|
|
3111
3112
|
return Object.assign(response1, response2);
|
|
3112
3113
|
}
|
|
3113
|
-
const
|
|
3114
|
-
const nodesPath = [...
|
|
3114
|
+
const path7 = findPaginatedResourcePath(response1);
|
|
3115
|
+
const nodesPath = [...path7, "nodes"];
|
|
3115
3116
|
const newNodes = get(response2, nodesPath);
|
|
3116
3117
|
if (newNodes) {
|
|
3117
3118
|
set(response1, nodesPath, (values) => {
|
|
3118
3119
|
return [...values, ...newNodes];
|
|
3119
3120
|
});
|
|
3120
3121
|
}
|
|
3121
|
-
const edgesPath = [...
|
|
3122
|
+
const edgesPath = [...path7, "edges"];
|
|
3122
3123
|
const newEdges = get(response2, edgesPath);
|
|
3123
3124
|
if (newEdges) {
|
|
3124
3125
|
set(response1, edgesPath, (values) => {
|
|
3125
3126
|
return [...values, ...newEdges];
|
|
3126
3127
|
});
|
|
3127
3128
|
}
|
|
3128
|
-
const pageInfoPath = [...
|
|
3129
|
+
const pageInfoPath = [...path7, "pageInfo"];
|
|
3129
3130
|
set(response1, pageInfoPath, get(response2, pageInfoPath));
|
|
3130
3131
|
return response1;
|
|
3131
3132
|
};
|
|
@@ -5546,7 +5547,7 @@ var triggers_notification_paths_default = [
|
|
|
5546
5547
|
];
|
|
5547
5548
|
function routeMatcher(paths) {
|
|
5548
5549
|
const regexes = paths.map(
|
|
5549
|
-
(
|
|
5550
|
+
(path7) => path7.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
|
|
5550
5551
|
);
|
|
5551
5552
|
const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
|
|
5552
5553
|
return new RegExp(regex2, "i");
|
|
@@ -8601,13 +8602,40 @@ async function getOwnerRepo(cwd) {
|
|
|
8601
8602
|
|
|
8602
8603
|
// scripts/util/ci-secrets.ts
|
|
8603
8604
|
var REQUIRED_CI_SECRETS = ["DATABRICKS_HOST", "LAKEBASE_PROJECT_ID", "DATABRICKS_TOKEN"];
|
|
8605
|
+
var CI_TOKEN_LIFETIME_SECONDS = 7776e3;
|
|
8606
|
+
var CI_TOKEN_REMINT_MARGIN_SECONDS = 86400;
|
|
8607
|
+
function ciTokenComment(ownerRepo) {
|
|
8608
|
+
const repoName = ownerRepo.includes("/") ? ownerRepo.slice(ownerRepo.lastIndexOf("/") + 1) : ownerRepo;
|
|
8609
|
+
return `GitHub Actions (${repoName})`;
|
|
8610
|
+
}
|
|
8604
8611
|
async function missingCiSecrets(ownerRepo) {
|
|
8605
8612
|
const present = new Set(await listSecretNames(ownerRepo));
|
|
8606
8613
|
return REQUIRED_CI_SECRETS.filter((n) => !present.has(n));
|
|
8607
8614
|
}
|
|
8615
|
+
async function mintCiToken(args) {
|
|
8616
|
+
try {
|
|
8617
|
+
const raw = await runDatabricks(
|
|
8618
|
+
["tokens", "create", "--comment", args.comment, "--lifetime-seconds", String(args.lifetimeSeconds), "-o", "json"],
|
|
8619
|
+
{ host: args.databricksHost, cwd: args.projectDir, timeout: 3e4 }
|
|
8620
|
+
);
|
|
8621
|
+
const parsed = JSON.parse(raw.slice(Math.max(0, raw.indexOf("{"))));
|
|
8622
|
+
const token = parsed.token_value || parsed.token || "";
|
|
8623
|
+
if (token) return token;
|
|
8624
|
+
} catch {
|
|
8625
|
+
}
|
|
8626
|
+
try {
|
|
8627
|
+
const raw = await runDatabricks(["auth", "token", "-o", "json"], {
|
|
8628
|
+
host: args.databricksHost,
|
|
8629
|
+
cwd: args.projectDir,
|
|
8630
|
+
timeout: 3e4
|
|
8631
|
+
});
|
|
8632
|
+
const parsed = JSON.parse(raw.slice(Math.max(0, raw.indexOf("{"))));
|
|
8633
|
+
return parsed.access_token || "";
|
|
8634
|
+
} catch {
|
|
8635
|
+
return "";
|
|
8636
|
+
}
|
|
8637
|
+
}
|
|
8608
8638
|
async function syncCiSecrets(args) {
|
|
8609
|
-
const lifetime = args.lifetimeSeconds ?? 86400;
|
|
8610
|
-
const comment = args.comment ?? "GitHub Actions CI";
|
|
8611
8639
|
const ownerRepo = args.ownerRepo ?? await getOwnerRepo(args.projectDir);
|
|
8612
8640
|
if (!ownerRepo) {
|
|
8613
8641
|
throw new Error("Could not resolve GitHub repository from git remote");
|
|
@@ -8618,21 +8646,98 @@ async function syncCiSecrets(args) {
|
|
|
8618
8646
|
if (!args.lakebaseProjectId) {
|
|
8619
8647
|
throw new Error("syncCiSecrets: lakebaseProjectId is required");
|
|
8620
8648
|
}
|
|
8649
|
+
const lifetime = args.lifetimeSeconds ?? CI_TOKEN_LIFETIME_SECONDS;
|
|
8650
|
+
const comment = args.comment ?? ciTokenComment(ownerRepo);
|
|
8621
8651
|
const secrets = {
|
|
8622
8652
|
DATABRICKS_HOST: args.databricksHost,
|
|
8623
8653
|
LAKEBASE_PROJECT_ID: args.lakebaseProjectId
|
|
8624
8654
|
};
|
|
8655
|
+
const token = await mintCiToken({
|
|
8656
|
+
databricksHost: args.databricksHost,
|
|
8657
|
+
projectDir: args.projectDir,
|
|
8658
|
+
comment,
|
|
8659
|
+
lifetimeSeconds: lifetime
|
|
8660
|
+
});
|
|
8661
|
+
if (token) secrets.DATABRICKS_TOKEN = token;
|
|
8662
|
+
await setRepoSecrets(ownerRepo, secrets);
|
|
8663
|
+
}
|
|
8664
|
+
async function ciTokenExpiry(args) {
|
|
8665
|
+
const comment = ciTokenComment(args.ownerRepo);
|
|
8666
|
+
let raw;
|
|
8625
8667
|
try {
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
const token = parsed.token_value || parsed.token || "";
|
|
8632
|
-
if (token) secrets.DATABRICKS_TOKEN = token;
|
|
8668
|
+
raw = await runDatabricks(["tokens", "list", "-o", "json"], {
|
|
8669
|
+
host: args.databricksHost,
|
|
8670
|
+
cwd: args.projectDir,
|
|
8671
|
+
timeout: 3e4
|
|
8672
|
+
});
|
|
8633
8673
|
} catch {
|
|
8674
|
+
return null;
|
|
8675
|
+
}
|
|
8676
|
+
let infos;
|
|
8677
|
+
try {
|
|
8678
|
+
const parsed = JSON.parse(raw.slice(Math.max(0, raw.indexOf("["))));
|
|
8679
|
+
infos = Array.isArray(parsed) ? parsed : [];
|
|
8680
|
+
} catch {
|
|
8681
|
+
return null;
|
|
8682
|
+
}
|
|
8683
|
+
const expiries = infos.filter((t) => t.comment === comment).map((t) => t.expiry_time === -1 ? Number.POSITIVE_INFINITY : Number(t.expiry_time)).filter((n) => Number.isFinite(n) || n === Number.POSITIVE_INFINITY);
|
|
8684
|
+
if (expiries.length === 0) return null;
|
|
8685
|
+
return Math.max(...expiries);
|
|
8686
|
+
}
|
|
8687
|
+
async function ensureCiSecretsFresh(args) {
|
|
8688
|
+
const now = args.now ?? Date.now;
|
|
8689
|
+
const marginMs = (args.marginSeconds ?? CI_TOKEN_REMINT_MARGIN_SECONDS) * 1e3;
|
|
8690
|
+
const ownerRepo = args.ownerRepo ?? await getOwnerRepo(args.projectDir);
|
|
8691
|
+
if (!ownerRepo) {
|
|
8692
|
+
throw new Error("Could not resolve GitHub repository from git remote");
|
|
8693
|
+
}
|
|
8694
|
+
const sync = () => syncCiSecrets({
|
|
8695
|
+
projectDir: args.projectDir,
|
|
8696
|
+
databricksHost: args.databricksHost,
|
|
8697
|
+
lakebaseProjectId: args.lakebaseProjectId,
|
|
8698
|
+
ownerRepo
|
|
8699
|
+
});
|
|
8700
|
+
const missing = await missingCiSecrets(ownerRepo);
|
|
8701
|
+
if (missing.length > 0) {
|
|
8702
|
+
await sync();
|
|
8703
|
+
return { action: "provisioned", reason: `CI secret(s) were missing (${missing.join(", ")}); provisioned.` };
|
|
8704
|
+
}
|
|
8705
|
+
const expiry = await ciTokenExpiry({ projectDir: args.projectDir, databricksHost: args.databricksHost, ownerRepo });
|
|
8706
|
+
if (expiry === null) {
|
|
8707
|
+
await sync();
|
|
8708
|
+
return { action: "reminted", reason: "no live CI token found (expired or minted under a different identity); re-minted." };
|
|
8709
|
+
}
|
|
8710
|
+
if (expiry !== Number.POSITIVE_INFINITY && expiry < now() + marginMs) {
|
|
8711
|
+
await sync();
|
|
8712
|
+
const hrs = Math.max(0, Math.round((expiry - now()) / 36e5));
|
|
8713
|
+
return { action: "reminted", reason: `CI token expires in ~${hrs}h (within the re-mint margin); re-minted.` };
|
|
8714
|
+
}
|
|
8715
|
+
return { action: "ok", reason: "CI token is current." };
|
|
8716
|
+
}
|
|
8717
|
+
async function ensureCiSecretsFreshFromEnv(projectDir, opts) {
|
|
8718
|
+
const envPath = path6.join(projectDir, ".env");
|
|
8719
|
+
const databricksHost = readEnvVar(envPath, "DATABRICKS_HOST");
|
|
8720
|
+
const lakebaseProjectId = readEnvVar(envPath, "LAKEBASE_PROJECT_ID");
|
|
8721
|
+
if (!databricksHost || !lakebaseProjectId) {
|
|
8722
|
+
return {
|
|
8723
|
+
action: "skipped",
|
|
8724
|
+
reason: "CI-auth preflight skipped: .env is missing DATABRICKS_HOST / LAKEBASE_PROJECT_ID."
|
|
8725
|
+
};
|
|
8726
|
+
}
|
|
8727
|
+
try {
|
|
8728
|
+
return await ensureCiSecretsFresh({
|
|
8729
|
+
projectDir,
|
|
8730
|
+
databricksHost,
|
|
8731
|
+
lakebaseProjectId,
|
|
8732
|
+
ownerRepo: opts?.ownerRepo,
|
|
8733
|
+
marginSeconds: opts?.marginSeconds
|
|
8734
|
+
});
|
|
8735
|
+
} catch (err) {
|
|
8736
|
+
return {
|
|
8737
|
+
action: "failed",
|
|
8738
|
+
reason: `CI-auth preflight could not re-mint (${err instanceof Error ? err.message : String(err)}); if CI fails on auth, run lakebase-sync-ci-secrets.`
|
|
8739
|
+
};
|
|
8634
8740
|
}
|
|
8635
|
-
await setRepoSecrets(ownerRepo, secrets);
|
|
8636
8741
|
}
|
|
8637
8742
|
|
|
8638
8743
|
// scripts/util/cli-entry.ts
|
|
@@ -8740,11 +8845,17 @@ async function pollUntilDefined(probe, opts) {
|
|
|
8740
8845
|
});
|
|
8741
8846
|
}
|
|
8742
8847
|
export {
|
|
8848
|
+
CI_TOKEN_LIFETIME_SECONDS,
|
|
8849
|
+
CI_TOKEN_REMINT_MARGIN_SECONDS,
|
|
8743
8850
|
LAKEBASE_BRANCH_NAME_MAX,
|
|
8744
8851
|
PROXY_ENV_KEYS,
|
|
8745
8852
|
REQUIRED_CI_SECRETS,
|
|
8853
|
+
ciTokenComment,
|
|
8854
|
+
ciTokenExpiry,
|
|
8746
8855
|
copyDirSubstituted,
|
|
8747
8856
|
delay,
|
|
8857
|
+
ensureCiSecretsFresh,
|
|
8858
|
+
ensureCiSecretsFreshFromEnv,
|
|
8748
8859
|
exec2 as exec,
|
|
8749
8860
|
extractZipToDir,
|
|
8750
8861
|
formatOwnerRepo,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@databricks-solutions/lakebase-scm-utils",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.49",
|
|
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,
|
|
@@ -429,12 +429,12 @@ export async function createProject(
|
|
|
429
429
|
if (useGithub) {
|
|
430
430
|
report("Setting up CI auth (service principal)...");
|
|
431
431
|
try {
|
|
432
|
+
// Canonical CI-token identity (durable 90-day PAT, project-scoped comment) — defaults
|
|
433
|
+
// live in ci-secrets.ts so every mint path (create, repair, pre-push, preflight) matches.
|
|
432
434
|
await syncCiSecrets({
|
|
433
435
|
projectDir,
|
|
434
436
|
databricksHost: host,
|
|
435
437
|
lakebaseProjectId,
|
|
436
|
-
comment: "GitHub Actions CI",
|
|
437
|
-
lifetimeSeconds: 86_400,
|
|
438
438
|
ownerRepo: fullRepoName,
|
|
439
439
|
});
|
|
440
440
|
// VERIFY the secrets actually landed — syncCiSecrets fail-softs a dropped PAT, and a
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type WorkflowRunSummary,
|
|
19
19
|
} from "../github/pr.js";
|
|
20
20
|
import { getOwnerRepo } from "../git/remote.js";
|
|
21
|
+
import { ensureCiSecretsFreshFromEnv, type CiFreshnessResult } from "../util/ci-secrets.js";
|
|
21
22
|
import { pollUntil } from "../util/poll-until.js";
|
|
22
23
|
import { exec } from "../util/exec.js";
|
|
23
24
|
import { getCurrentBranch } from "../git/inspect.js";
|
|
@@ -175,6 +176,11 @@ export interface MergeArgs {
|
|
|
175
176
|
/** Fetch the PR's mergeability (to gate the reconcile). Defaults to the real getPullRequest;
|
|
176
177
|
* injected in tests so the gate is exercised without the network. */
|
|
177
178
|
fetchPrState?: (ownerRepo: string, headBranch: string) => Promise<{ mergeableState?: string } | undefined>;
|
|
179
|
+
/** Expiry-aware CI-auth preflight run before the merge: the promote triggers merge.yml, which
|
|
180
|
+
* needs a live DATABRICKS_TOKEN secret, and a merge is not a push so the pre-push hook does not
|
|
181
|
+
* fire. Re-mints the canonical token when missing / near expiry. BEST-EFFORT: never blocks the
|
|
182
|
+
* merge. Defaults to {@link ensureCiSecretsFreshFromEnv}; injected in tests. */
|
|
183
|
+
ensureCiFresh?: (ownerRepo: string) => Promise<CiFreshnessResult>;
|
|
178
184
|
}
|
|
179
185
|
|
|
180
186
|
export interface MergeResult {
|
|
@@ -450,6 +456,21 @@ export async function mergeFeature(args: MergeArgs): Promise<MergeResult> {
|
|
|
450
456
|
}
|
|
451
457
|
}
|
|
452
458
|
|
|
459
|
+
// ─── Pre-merge CI-auth freshness preflight ───
|
|
460
|
+
// The promote merges to the tier branch, which triggers merge.yml (migrate-target) — that needs
|
|
461
|
+
// a live DATABRICKS_TOKEN repo secret. A merge is not a push, so the pre-push hook does NOT
|
|
462
|
+
// refresh it here; if the token has expired (or was never provisioned) the promote CI fails on
|
|
463
|
+
// empty auth. Re-mint the canonical token when missing / near expiry. BEST-EFFORT: never blocks.
|
|
464
|
+
{
|
|
465
|
+
const ensure = args.ensureCiFresh ?? ((repo: string) => ensureCiSecretsFreshFromEnv(args.projectDir, { ownerRepo: repo }));
|
|
466
|
+
try {
|
|
467
|
+
const r = await ensure(ownerRepo);
|
|
468
|
+
if (r.action !== "ok" && r.action !== "skipped") preflightNotes.push(`CI-auth preflight: ${r.reason}`);
|
|
469
|
+
} catch (err) {
|
|
470
|
+
preflightNotes.push(`CI-auth preflight skipped (${err instanceof Error ? err.message : String(err)}); if the promote CI fails on auth, run lakebase-sync-ci-secrets.`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
453
474
|
// ─── Pre-merge pipeline.json-ledger reconcile ───
|
|
454
475
|
// The per-feature pipeline.json ledger is TRACKED (durable run-state) and can diverge between the
|
|
455
476
|
// feature branch and its tier, so the promotion PR lands CONFLICTING — GitHub won't dispatch CI or
|
|
@@ -9,6 +9,7 @@ import { exec } from "../util/exec.js";
|
|
|
9
9
|
import { getCurrentBranch } from "../git/inspect.js";
|
|
10
10
|
import { getAheadBehind, isDirty } from "../git/status.js";
|
|
11
11
|
import { getOwnerRepo } from "../git/remote.js";
|
|
12
|
+
import { ensureCiSecretsFreshFromEnv, type CiFreshnessResult } from "../util/ci-secrets.js";
|
|
12
13
|
import { resolveGitBase } from "./scm-git-base.js";
|
|
13
14
|
import { createPullRequest, getPullRequest } from "../github/pr.js";
|
|
14
15
|
import { RUNTIME_ARTIFACT_IGNORE } from "./constants.js";
|
|
@@ -63,6 +64,8 @@ export interface PreparePrResult {
|
|
|
63
64
|
prUrl: string;
|
|
64
65
|
/** True iff createPullRequest was invoked (vs. reusing an existing open PR). */
|
|
65
66
|
prCreated: boolean;
|
|
67
|
+
/** Outcome of the expiry-aware CI-auth preflight run before the push (fail-soft). */
|
|
68
|
+
ciFreshness: CiFreshnessResult;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
export async function preparePr(
|
|
@@ -138,6 +141,12 @@ export async function preparePr(
|
|
|
138
141
|
);
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
// Expiry-aware CI-auth preflight BEFORE the push: opening/updating the PR triggers
|
|
145
|
+
// build-and-test, which needs a live DATABRICKS_TOKEN secret. Re-mint the canonical token
|
|
146
|
+
// when it is missing or near expiry so the run isn't DOA. Fail-soft — never blocks the PR
|
|
147
|
+
// (the push's pre-push hook also refreshes; this is the belt-and-suspenders guarantee).
|
|
148
|
+
const ciFreshness = await ensureCiSecretsFreshFromEnv(args.projectDir, { ownerRepo });
|
|
149
|
+
|
|
141
150
|
const now = (args.now ?? (() => new Date()))();
|
|
142
151
|
let prUrl = args.prUrlOverride ?? "";
|
|
143
152
|
let prCreated = false;
|
|
@@ -191,7 +200,7 @@ export async function preparePr(
|
|
|
191
200
|
};
|
|
192
201
|
writeWorkflowState(args.projectDir, next);
|
|
193
202
|
|
|
194
|
-
return { state: next, prUrl, prCreated };
|
|
203
|
+
return { state: next, prUrl, prCreated, ciFreshness };
|
|
195
204
|
}
|
|
196
205
|
|
|
197
206
|
async function ensureAheadOfParent(
|
|
@@ -57,8 +57,8 @@ Flags:
|
|
|
57
57
|
--host <url> DATABRICKS_HOST (default: .env DATABRICKS_HOST)
|
|
58
58
|
--project-id <id> LAKEBASE_PROJECT_ID (default: .env LAKEBASE_PROJECT_ID)
|
|
59
59
|
--repo <owner/name> Target repo (default: origin remote)
|
|
60
|
-
--lifetime-sec <n> CI PAT lifetime (default:
|
|
61
|
-
--comment <text> Token comment (default: "GitHub Actions
|
|
60
|
+
--lifetime-sec <n> CI PAT lifetime (default: 7776000 = 90d, the canonical durable lifetime)
|
|
61
|
+
--comment <text> Token comment (default: canonical "GitHub Actions (<repo>)")
|
|
62
62
|
-h, --help Show this help
|
|
63
63
|
|
|
64
64
|
Exit codes:
|