@databricks-solutions/lakebase-scm-utils 0.2.46 → 0.2.47
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/{pr-D3CteJaf.d.cts → pr-e-E4aP4j.d.cts} +6 -0
- package/dist/{pr-D3CteJaf.d.ts → pr-e-E4aP4j.d.ts} +6 -0
- package/dist/scripts/github/index.cjs +2 -0
- package/dist/scripts/github/index.d.cts +1 -1
- package/dist/scripts/github/index.d.ts +1 -1
- package/dist/scripts/github/index.js +2 -0
- package/dist/scripts/github/pr.cli.cjs +2 -0
- package/dist/scripts/github/pr.cli.js +2 -0
- package/dist/scripts/index.cjs +28 -4
- package/dist/scripts/index.d.cts +2 -2
- package/dist/scripts/index.d.ts +2 -2
- package/dist/scripts/index.js +26 -4
- 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 +34 -4
- package/dist/scripts/lakebase/index.d.cts +3 -3
- package/dist/scripts/lakebase/index.d.ts +3 -3
- package/dist/scripts/lakebase/index.js +34 -4
- 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-prepare-pr.cli.cjs +2 -0
- package/dist/scripts/lakebase/scm-prepare-pr.cli.js +2 -0
- 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/scm-wait-ci.cli.cjs +8 -0
- package/dist/scripts/lakebase/scm-wait-ci.cli.js +8 -0
- package/dist/scripts/lakebase/sync-ci-secrets.cli.cjs +8610 -0
- package/dist/scripts/lakebase/sync-ci-secrets.cli.d.cts +4 -0
- package/dist/scripts/lakebase/sync-ci-secrets.cli.d.ts +4 -0
- package/dist/scripts/lakebase/sync-ci-secrets.cli.js +8597 -0
- package/dist/scripts/util/index.cjs +21 -2
- package/dist/scripts/util/index.d.cts +9 -1
- package/dist/scripts/util/index.d.ts +9 -1
- package/dist/scripts/util/index.js +19 -2
- package/package.json +3 -2
- package/scripts/github/pr.ts +8 -0
- package/scripts/lakebase/create-project.ts +14 -3
- package/scripts/lakebase/scm-wait-ci.ts +13 -0
- package/scripts/lakebase/sync-ci-secrets.cli.ts +122 -0
- package/scripts/util/ci-secrets.ts +14 -1
- package/templates/project/common/.gitignore.base +6 -0
- package/templates/project/common/scripts/lk +22 -4
|
@@ -1466,12 +1466,14 @@ var util_exports = {};
|
|
|
1466
1466
|
__export(util_exports, {
|
|
1467
1467
|
LAKEBASE_BRANCH_NAME_MAX: () => LAKEBASE_BRANCH_NAME_MAX,
|
|
1468
1468
|
PROXY_ENV_KEYS: () => PROXY_ENV_KEYS,
|
|
1469
|
+
REQUIRED_CI_SECRETS: () => REQUIRED_CI_SECRETS,
|
|
1469
1470
|
copyDirSubstituted: () => copyDirSubstituted,
|
|
1470
1471
|
delay: () => delay,
|
|
1471
1472
|
exec: () => exec2,
|
|
1472
1473
|
extractZipToDir: () => extractZipToDir,
|
|
1473
1474
|
formatOwnerRepo: () => formatOwnerRepo,
|
|
1474
1475
|
isCliEntry: () => isCliEntry,
|
|
1476
|
+
missingCiSecrets: () => missingCiSecrets,
|
|
1475
1477
|
parseOwnerRepo: () => parseOwnerRepo,
|
|
1476
1478
|
patchPomForLakebase: () => patchPomForLakebase,
|
|
1477
1479
|
pollUntil: () => pollUntil,
|
|
@@ -1659,8 +1661,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
1659
1661
|
var cached;
|
|
1660
1662
|
function substrateSelfVersion() {
|
|
1661
1663
|
if (cached !== void 0) return cached;
|
|
1662
|
-
if ("0.2.
|
|
1663
|
-
cached = "0.2.
|
|
1664
|
+
if ("0.2.47".length > 0) {
|
|
1665
|
+
cached = "0.2.47";
|
|
1664
1666
|
return cached;
|
|
1665
1667
|
}
|
|
1666
1668
|
cached = "unknown";
|
|
@@ -8580,6 +8582,16 @@ async function setRepoSecrets(ownerRepo, secrets) {
|
|
|
8580
8582
|
await setRepoSecret(ownerRepo, name, value);
|
|
8581
8583
|
}
|
|
8582
8584
|
}
|
|
8585
|
+
async function listSecretNames(ownerRepo) {
|
|
8586
|
+
try {
|
|
8587
|
+
const { owner, repo } = parseOwnerRepo(ownerRepo);
|
|
8588
|
+
const octokit = await getOctokit();
|
|
8589
|
+
const { data } = await octokit.rest.actions.listRepoSecrets({ owner, repo });
|
|
8590
|
+
return data.secrets.map((s) => s.name);
|
|
8591
|
+
} catch {
|
|
8592
|
+
return [];
|
|
8593
|
+
}
|
|
8594
|
+
}
|
|
8583
8595
|
|
|
8584
8596
|
// scripts/git/remote.ts
|
|
8585
8597
|
init_cjs_shims();
|
|
@@ -8619,6 +8631,11 @@ async function getOwnerRepo(cwd) {
|
|
|
8619
8631
|
}
|
|
8620
8632
|
|
|
8621
8633
|
// scripts/util/ci-secrets.ts
|
|
8634
|
+
var REQUIRED_CI_SECRETS = ["DATABRICKS_HOST", "LAKEBASE_PROJECT_ID", "DATABRICKS_TOKEN"];
|
|
8635
|
+
async function missingCiSecrets(ownerRepo) {
|
|
8636
|
+
const present = new Set(await listSecretNames(ownerRepo));
|
|
8637
|
+
return REQUIRED_CI_SECRETS.filter((n) => !present.has(n));
|
|
8638
|
+
}
|
|
8622
8639
|
async function syncCiSecrets(args) {
|
|
8623
8640
|
const lifetime = args.lifetimeSeconds ?? 86400;
|
|
8624
8641
|
const comment = args.comment ?? "GitHub Actions CI";
|
|
@@ -8757,12 +8774,14 @@ async function pollUntilDefined(probe, opts) {
|
|
|
8757
8774
|
0 && (module.exports = {
|
|
8758
8775
|
LAKEBASE_BRANCH_NAME_MAX,
|
|
8759
8776
|
PROXY_ENV_KEYS,
|
|
8777
|
+
REQUIRED_CI_SECRETS,
|
|
8760
8778
|
copyDirSubstituted,
|
|
8761
8779
|
delay,
|
|
8762
8780
|
exec,
|
|
8763
8781
|
extractZipToDir,
|
|
8764
8782
|
formatOwnerRepo,
|
|
8765
8783
|
isCliEntry,
|
|
8784
|
+
missingCiSecrets,
|
|
8766
8785
|
parseOwnerRepo,
|
|
8767
8786
|
patchPomForLakebase,
|
|
8768
8787
|
pollUntil,
|
|
@@ -33,6 +33,14 @@ declare function copyDirSubstituted(srcDir: string, destDir: string, args?: Copy
|
|
|
33
33
|
declare const LAKEBASE_BRANCH_NAME_MAX = 63;
|
|
34
34
|
declare function sanitizeBranchName(gitBranch: string): string;
|
|
35
35
|
|
|
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[]>;
|
|
36
44
|
interface SyncCiSecretsArgs {
|
|
37
45
|
/** Project root (used to resolve ownerRepo from `git remote` when not given,
|
|
38
46
|
* and as the cwd for the `databricks tokens create` call). */
|
|
@@ -161,4 +169,4 @@ declare function pollUntilDefined<T>(probe: (ctx: {
|
|
|
161
169
|
elapsedMs: number;
|
|
162
170
|
}) => Promise<T | undefined>, opts: Omit<PollUntilArgs<T>, "probe">): Promise<PollUntilResult<T>>;
|
|
163
171
|
|
|
164
|
-
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, type SyncCiSecretsArgs, copyDirSubstituted, delay, extractZipToDir, formatOwnerRepo, isCliEntry, parseOwnerRepo, patchPomForLakebase, pollUntil, pollUntilDefined, proxyEnvSubset, sanitizeArtifactId, sanitizeBranchName, syncCiSecrets, withProxyEnv };
|
|
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, REQUIRED_CI_SECRETS, type SyncCiSecretsArgs, copyDirSubstituted, delay, extractZipToDir, formatOwnerRepo, isCliEntry, missingCiSecrets, parseOwnerRepo, patchPomForLakebase, pollUntil, pollUntilDefined, proxyEnvSubset, sanitizeArtifactId, sanitizeBranchName, syncCiSecrets, withProxyEnv };
|
|
@@ -33,6 +33,14 @@ declare function copyDirSubstituted(srcDir: string, destDir: string, args?: Copy
|
|
|
33
33
|
declare const LAKEBASE_BRANCH_NAME_MAX = 63;
|
|
34
34
|
declare function sanitizeBranchName(gitBranch: string): string;
|
|
35
35
|
|
|
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[]>;
|
|
36
44
|
interface SyncCiSecretsArgs {
|
|
37
45
|
/** Project root (used to resolve ownerRepo from `git remote` when not given,
|
|
38
46
|
* and as the cwd for the `databricks tokens create` call). */
|
|
@@ -161,4 +169,4 @@ declare function pollUntilDefined<T>(probe: (ctx: {
|
|
|
161
169
|
elapsedMs: number;
|
|
162
170
|
}) => Promise<T | undefined>, opts: Omit<PollUntilArgs<T>, "probe">): Promise<PollUntilResult<T>>;
|
|
163
171
|
|
|
164
|
-
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, type SyncCiSecretsArgs, copyDirSubstituted, delay, extractZipToDir, formatOwnerRepo, isCliEntry, parseOwnerRepo, patchPomForLakebase, pollUntil, pollUntilDefined, proxyEnvSubset, sanitizeArtifactId, sanitizeBranchName, syncCiSecrets, withProxyEnv };
|
|
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, REQUIRED_CI_SECRETS, type SyncCiSecretsArgs, copyDirSubstituted, delay, extractZipToDir, formatOwnerRepo, isCliEntry, missingCiSecrets, parseOwnerRepo, patchPomForLakebase, pollUntil, pollUntilDefined, proxyEnvSubset, sanitizeArtifactId, sanitizeBranchName, syncCiSecrets, 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.47".length > 0) {
|
|
1634
|
+
cached = "0.2.47";
|
|
1635
1635
|
return cached;
|
|
1636
1636
|
}
|
|
1637
1637
|
cached = "unknown";
|
|
@@ -8551,6 +8551,16 @@ async function setRepoSecrets(ownerRepo, secrets) {
|
|
|
8551
8551
|
await setRepoSecret(ownerRepo, name, value);
|
|
8552
8552
|
}
|
|
8553
8553
|
}
|
|
8554
|
+
async function listSecretNames(ownerRepo) {
|
|
8555
|
+
try {
|
|
8556
|
+
const { owner, repo } = parseOwnerRepo(ownerRepo);
|
|
8557
|
+
const octokit = await getOctokit();
|
|
8558
|
+
const { data } = await octokit.rest.actions.listRepoSecrets({ owner, repo });
|
|
8559
|
+
return data.secrets.map((s) => s.name);
|
|
8560
|
+
} catch {
|
|
8561
|
+
return [];
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
8554
8564
|
|
|
8555
8565
|
// scripts/git/remote.ts
|
|
8556
8566
|
init_esm_shims();
|
|
@@ -8590,6 +8600,11 @@ async function getOwnerRepo(cwd) {
|
|
|
8590
8600
|
}
|
|
8591
8601
|
|
|
8592
8602
|
// scripts/util/ci-secrets.ts
|
|
8603
|
+
var REQUIRED_CI_SECRETS = ["DATABRICKS_HOST", "LAKEBASE_PROJECT_ID", "DATABRICKS_TOKEN"];
|
|
8604
|
+
async function missingCiSecrets(ownerRepo) {
|
|
8605
|
+
const present = new Set(await listSecretNames(ownerRepo));
|
|
8606
|
+
return REQUIRED_CI_SECRETS.filter((n) => !present.has(n));
|
|
8607
|
+
}
|
|
8593
8608
|
async function syncCiSecrets(args) {
|
|
8594
8609
|
const lifetime = args.lifetimeSeconds ?? 86400;
|
|
8595
8610
|
const comment = args.comment ?? "GitHub Actions CI";
|
|
@@ -8727,12 +8742,14 @@ async function pollUntilDefined(probe, opts) {
|
|
|
8727
8742
|
export {
|
|
8728
8743
|
LAKEBASE_BRANCH_NAME_MAX,
|
|
8729
8744
|
PROXY_ENV_KEYS,
|
|
8745
|
+
REQUIRED_CI_SECRETS,
|
|
8730
8746
|
copyDirSubstituted,
|
|
8731
8747
|
delay,
|
|
8732
8748
|
exec2 as exec,
|
|
8733
8749
|
extractZipToDir,
|
|
8734
8750
|
formatOwnerRepo,
|
|
8735
8751
|
isCliEntry,
|
|
8752
|
+
missingCiSecrets,
|
|
8736
8753
|
parseOwnerRepo,
|
|
8737
8754
|
patchPomForLakebase,
|
|
8738
8755
|
pollUntil,
|
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.47",
|
|
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,
|
|
@@ -105,7 +105,8 @@
|
|
|
105
105
|
"lakebase-scm-prepare-pr": "./dist/scripts/lakebase/scm-prepare-pr.cli.js",
|
|
106
106
|
"lakebase-scm-recover-orphans": "./dist/scripts/lakebase/scm-recover-orphans.cli.js",
|
|
107
107
|
"lakebase-scm-state": "./dist/scripts/lakebase/scm-state.cli.js",
|
|
108
|
-
"lakebase-scm-wait-ci": "./dist/scripts/lakebase/scm-wait-ci.cli.js"
|
|
108
|
+
"lakebase-scm-wait-ci": "./dist/scripts/lakebase/scm-wait-ci.cli.js",
|
|
109
|
+
"lakebase-sync-ci-secrets": "./dist/scripts/lakebase/sync-ci-secrets.cli.js"
|
|
109
110
|
},
|
|
110
111
|
"scripts": {
|
|
111
112
|
"build": "tsup",
|
package/scripts/github/pr.ts
CHANGED
|
@@ -67,6 +67,12 @@ export interface PullRequestInfo {
|
|
|
67
67
|
isDraft: boolean;
|
|
68
68
|
ciStatus: "pending" | "success" | "failure";
|
|
69
69
|
checks: PullRequestCheck[];
|
|
70
|
+
/** GitHub's mergeability triple: true/false/null (null = still computing). */
|
|
71
|
+
mergeable?: boolean | null;
|
|
72
|
+
/** GitHub's `mergeable_state`: `clean`/`unstable`/`behind`/`blocked`/`dirty`/`unknown`.
|
|
73
|
+
* `dirty` = merge conflict with the base — a `pull_request` CI run can't be dispatched
|
|
74
|
+
* (there is no mergeable ref), so a waiter must surface this, not poll until timeout. */
|
|
75
|
+
mergeableState?: string;
|
|
70
76
|
headBranch: string;
|
|
71
77
|
baseBranch: string;
|
|
72
78
|
body?: string;
|
|
@@ -184,6 +190,8 @@ export async function getPullRequest(
|
|
|
184
190
|
isDraft: pr.draft || false,
|
|
185
191
|
ciStatus,
|
|
186
192
|
checks,
|
|
193
|
+
mergeable: pr.mergeable,
|
|
194
|
+
mergeableState: pr.mergeable_state,
|
|
187
195
|
headBranch: pr.head?.ref || headBranch,
|
|
188
196
|
baseBranch: pr.base?.ref || "",
|
|
189
197
|
body: pr.body || undefined,
|
|
@@ -30,7 +30,7 @@ import { createLongRunningBranch } from "./long-running-branch.js";
|
|
|
30
30
|
import { enableE2eForProject } from "./enable-e2e.js";
|
|
31
31
|
import { enableInfraForProject } from "./enable-infra.js";
|
|
32
32
|
import { setupRunner } from "./runner-setup.js";
|
|
33
|
-
import { syncCiSecrets } from "../util/ci-secrets.js";
|
|
33
|
+
import { syncCiSecrets, missingCiSecrets } from "../util/ci-secrets.js";
|
|
34
34
|
import { delay } from "../util/delay.js";
|
|
35
35
|
import {
|
|
36
36
|
initWorkflowState,
|
|
@@ -437,10 +437,21 @@ export async function createProject(
|
|
|
437
437
|
lifetimeSeconds: 86_400,
|
|
438
438
|
ownerRepo: fullRepoName,
|
|
439
439
|
});
|
|
440
|
+
// VERIFY the secrets actually landed — syncCiSecrets fail-softs a dropped PAT, and a
|
|
441
|
+
// swallowed failure here only surfaces at the first promotion CI run (no DATABASE_URL),
|
|
442
|
+
// an entire feature later. Name the exact gaps + the one-command repair so it is actionable
|
|
443
|
+
// NOW, not a buried warning.
|
|
444
|
+
const missing = await missingCiSecrets(fullRepoName);
|
|
445
|
+
if (missing.length > 0) {
|
|
446
|
+
const m = `CI auth incomplete — repo secret(s) MISSING: ${missing.join(", ")}. CI cannot provision a per-PR Lakebase branch until fixed. Repair with: lakebase-sync-ci-secrets (run in the project).`;
|
|
447
|
+
warnings.push(m);
|
|
448
|
+
report(`WARNING: ${m}`);
|
|
449
|
+
}
|
|
440
450
|
} catch (err) {
|
|
441
451
|
const msg = err instanceof Error ? err.message : String(err);
|
|
442
|
-
|
|
443
|
-
|
|
452
|
+
const m = `CI auth setup failed: ${msg}. CI will fail (no DATABASE_URL) until repaired — run: lakebase-sync-ci-secrets (in the project).`;
|
|
453
|
+
warnings.push(m);
|
|
454
|
+
report(`WARNING: ${m}`);
|
|
444
455
|
}
|
|
445
456
|
}
|
|
446
457
|
|
|
@@ -24,6 +24,7 @@ export class ScmWaitCiError extends Error {
|
|
|
24
24
|
| "bad-precondition"
|
|
25
25
|
| "no-github-remote"
|
|
26
26
|
| "ci-failed"
|
|
27
|
+
| "pr-conflicting"
|
|
27
28
|
| "timeout"
|
|
28
29
|
| "pr-not-found",
|
|
29
30
|
) {
|
|
@@ -123,6 +124,18 @@ export async function waitForCi(args: WaitCiArgs): Promise<WaitCiResult> {
|
|
|
123
124
|
if (lastPr.ciStatus === "success") {
|
|
124
125
|
return { done: true, value: lastPr };
|
|
125
126
|
}
|
|
127
|
+
// A CONFLICTING PR (merge conflict with base) has no mergeable ref, so GitHub never
|
|
128
|
+
// dispatches the `pull_request` workflow — CI stays at 0 runs forever. Detect it and
|
|
129
|
+
// surface at once (resolve + re-push re-triggers), rather than spinning to the timeout
|
|
130
|
+
// on a `pending` that can never advance. Guard on `mergeableState==="dirty"` AND no
|
|
131
|
+
// checks yet, so a transient null/unknown while GitHub computes mergeability doesn't
|
|
132
|
+
// false-trip, and a PR that already has runs is judged by ciStatus as before.
|
|
133
|
+
if (lastPr.mergeableState === "dirty" && lastPr.checks.length === 0) {
|
|
134
|
+
throw new ScmWaitCiError(
|
|
135
|
+
`PR ${lastPr.url} is CONFLICTING with ${lastPr.baseBranch} — GitHub will not dispatch CI (0 runs) until the conflict is resolved. Reconcile the branch and re-push; the synchronize event will trigger CI.`,
|
|
136
|
+
"pr-conflicting",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
126
139
|
if (lastPr.ciStatus === "failure") {
|
|
127
140
|
const failed = lastPr.checks
|
|
128
141
|
.filter((c) => /(FAILURE|TIMED_OUT|CANCELLED|ACTION_REQUIRED)/i.test(c.conclusion))
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI: (re)provision the repo's CI auth secrets — the standalone repair path.
|
|
3
|
+
//
|
|
4
|
+
// create-project runs syncCiSecrets ONCE, fail-soft (a failed PAT mint or `gh secret set` is
|
|
5
|
+
// swallowed as a warning), and there was no way to retry without recreating the whole project.
|
|
6
|
+
// So a repo could sit with ZERO CI secrets until a promotion CI run failed an entire feature
|
|
7
|
+
// later (empty DATABRICKS_* → no DATABASE_URL → the test step aborts). This bin mints a fresh
|
|
8
|
+
// CI PAT and sets DATABRICKS_HOST / LAKEBASE_PROJECT_ID / DATABRICKS_TOKEN, then VERIFIES all
|
|
9
|
+
// three landed — exiting non-zero (naming the gaps) instead of leaving CI silently broken.
|
|
10
|
+
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { isCliEntry } from "../util/cli-entry.js";
|
|
13
|
+
import { readEnvVar } from "./env-file.js";
|
|
14
|
+
import { getOwnerRepo } from "../git/remote.js";
|
|
15
|
+
import { syncCiSecrets, missingCiSecrets, REQUIRED_CI_SECRETS } from "../util/ci-secrets.js";
|
|
16
|
+
|
|
17
|
+
interface ParsedArgs {
|
|
18
|
+
projectDir?: string;
|
|
19
|
+
host?: string;
|
|
20
|
+
projectId?: string;
|
|
21
|
+
repo?: string;
|
|
22
|
+
lifetimeSec?: number;
|
|
23
|
+
comment?: string;
|
|
24
|
+
help?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseArgs(argv: string[]): ParsedArgs {
|
|
28
|
+
const out: ParsedArgs = {};
|
|
29
|
+
for (let i = 0; i < argv.length; i++) {
|
|
30
|
+
const a = argv[i];
|
|
31
|
+
switch (a) {
|
|
32
|
+
case "--project-dir":
|
|
33
|
+
case "--cwd": out.projectDir = argv[++i]; break;
|
|
34
|
+
case "--host": out.host = argv[++i]; break;
|
|
35
|
+
case "--project-id": out.projectId = argv[++i]; break;
|
|
36
|
+
case "--repo": out.repo = argv[++i]; break;
|
|
37
|
+
case "--lifetime-sec": out.lifetimeSec = Number.parseInt(argv[++i], 10); break;
|
|
38
|
+
case "--comment": out.comment = argv[++i]; break;
|
|
39
|
+
case "--help": case "-h": out.help = true; break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const HELP = `lakebase-sync-ci-secrets
|
|
46
|
+
|
|
47
|
+
(Re)provision the GitHub repo's CI auth: mint a fresh Databricks CI PAT and set
|
|
48
|
+
DATABRICKS_HOST, LAKEBASE_PROJECT_ID, and DATABRICKS_TOKEN as repo Actions secrets,
|
|
49
|
+
then verify all three are present. The standalone repair for the create-time
|
|
50
|
+
"CI auth setup failed" warning — run it from the project, no recreate needed.
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
lakebase-sync-ci-secrets [flags]
|
|
54
|
+
|
|
55
|
+
Flags:
|
|
56
|
+
--project-dir <dir> Project root (default: cwd); resolves host/project-id from its .env
|
|
57
|
+
--host <url> DATABRICKS_HOST (default: .env DATABRICKS_HOST)
|
|
58
|
+
--project-id <id> LAKEBASE_PROJECT_ID (default: .env LAKEBASE_PROJECT_ID)
|
|
59
|
+
--repo <owner/name> Target repo (default: origin remote)
|
|
60
|
+
--lifetime-sec <n> CI PAT lifetime (default: 86400 = 24h)
|
|
61
|
+
--comment <text> Token comment (default: "GitHub Actions CI")
|
|
62
|
+
-h, --help Show this help
|
|
63
|
+
|
|
64
|
+
Exit codes:
|
|
65
|
+
0 = all three secrets present after sync
|
|
66
|
+
2 = could not resolve host / project-id / repo
|
|
67
|
+
3 = sync ran but a required secret is still missing (e.g. PAT mint failed)
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
export async function runSyncCiSecretsCli(argv: string[]): Promise<number> {
|
|
71
|
+
const args = parseArgs(argv);
|
|
72
|
+
if (args.help) {
|
|
73
|
+
process.stdout.write(`${HELP}\n`);
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
const projectDir = path.resolve(args.projectDir ?? process.cwd());
|
|
77
|
+
const envPath = path.join(projectDir, ".env");
|
|
78
|
+
const host = args.host ?? readEnvVar(envPath, "DATABRICKS_HOST");
|
|
79
|
+
const projectId = args.projectId ?? readEnvVar(envPath, "LAKEBASE_PROJECT_ID");
|
|
80
|
+
const ownerRepo = args.repo ?? (await getOwnerRepo(projectDir));
|
|
81
|
+
|
|
82
|
+
if (!ownerRepo) {
|
|
83
|
+
process.stderr.write("lakebase-sync-ci-secrets: no GitHub repo (pass --repo or run inside a repo with an origin remote).\n");
|
|
84
|
+
return 2;
|
|
85
|
+
}
|
|
86
|
+
if (!host || !projectId) {
|
|
87
|
+
process.stderr.write(
|
|
88
|
+
`lakebase-sync-ci-secrets: missing ${!host ? "DATABRICKS_HOST" : ""}${!host && !projectId ? " and " : ""}${!projectId ? "LAKEBASE_PROJECT_ID" : ""}` +
|
|
89
|
+
` — not in ${envPath}; pass --host / --project-id.\n`,
|
|
90
|
+
);
|
|
91
|
+
return 2;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
process.stdout.write(`Provisioning CI auth for ${ownerRepo} (host ${host}, project ${projectId})…\n`);
|
|
95
|
+
await syncCiSecrets({
|
|
96
|
+
projectDir,
|
|
97
|
+
databricksHost: host,
|
|
98
|
+
lakebaseProjectId: projectId,
|
|
99
|
+
ownerRepo,
|
|
100
|
+
...(args.comment ? { comment: args.comment } : {}),
|
|
101
|
+
...(args.lifetimeSec ? { lifetimeSeconds: args.lifetimeSec } : {}),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// VERIFY — the whole point of the repair. A silently-dropped PAT (the create-time failure
|
|
105
|
+
// mode) leaves DATABRICKS_TOKEN absent; catch it here and exit non-zero rather than report success.
|
|
106
|
+
const missing = await missingCiSecrets(ownerRepo);
|
|
107
|
+
if (missing.length > 0) {
|
|
108
|
+
process.stderr.write(
|
|
109
|
+
`lakebase-sync-ci-secrets: sync ran but these secrets are still MISSING on ${ownerRepo}: ${missing.join(", ")}.\n` +
|
|
110
|
+
(missing.includes("DATABRICKS_TOKEN")
|
|
111
|
+
? ` DATABRICKS_TOKEN missing usually means 'databricks tokens create' failed — check your auth for ${host} (e.g. 'databricks auth login --host ${host}'), then re-run.\n`
|
|
112
|
+
: ""),
|
|
113
|
+
);
|
|
114
|
+
return 3;
|
|
115
|
+
}
|
|
116
|
+
process.stdout.write(`CI auth ready: ${REQUIRED_CI_SECRETS.join(", ")} set on ${ownerRepo}.\n`);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (isCliEntry(import.meta.url)) {
|
|
121
|
+
void runSyncCiSecretsCli(process.argv.slice(2)).then((c) => process.exit(c));
|
|
122
|
+
}
|
|
@@ -7,9 +7,22 @@
|
|
|
7
7
|
// be set), the values have to come from the create-project caller's scope.
|
|
8
8
|
|
|
9
9
|
import { runDatabricks } from "../lakebase/databricks-cli.js";
|
|
10
|
-
import { setRepoSecrets } from "../github/secrets.js";
|
|
10
|
+
import { setRepoSecrets, listSecretNames } from "../github/secrets.js";
|
|
11
11
|
import { getOwnerRepo } from "../git/remote.js";
|
|
12
12
|
|
|
13
|
+
/** The repo Actions secrets CI needs to provision a per-PR Lakebase branch. Missing ANY of
|
|
14
|
+
* these leaves CI unable to produce a DATABASE_URL, so the test step aborts — the failure
|
|
15
|
+
* that otherwise only surfaces at promotion, an entire feature after creation. */
|
|
16
|
+
export const REQUIRED_CI_SECRETS = ["DATABRICKS_HOST", "LAKEBASE_PROJECT_ID", "DATABRICKS_TOKEN"] as const;
|
|
17
|
+
|
|
18
|
+
/** Which required CI secrets are NOT present on the repo (empty = fully provisioned). Used to
|
|
19
|
+
* VERIFY a sync landed — a `databricks tokens create` or `gh secret set` that fails must be
|
|
20
|
+
* caught here, not swallowed, so CI-auth is never silently absent. */
|
|
21
|
+
export async function missingCiSecrets(ownerRepo: string): Promise<string[]> {
|
|
22
|
+
const present = new Set(await listSecretNames(ownerRepo));
|
|
23
|
+
return REQUIRED_CI_SECRETS.filter((n) => !present.has(n));
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
export interface SyncCiSecretsArgs {
|
|
14
27
|
/** Project root (used to resolve ownerRepo from `git remote` when not given,
|
|
15
28
|
* and as the cwd for the `databricks tokens create` call). */
|
|
@@ -29,7 +29,13 @@ application-local.properties
|
|
|
29
29
|
# it on the feature branch at each cut so the spec history survives for later stories).
|
|
30
30
|
.consort/cycles/
|
|
31
31
|
.consort/experiments/
|
|
32
|
+
# The live build-pipeline pointer. It lives PER-FEATURE (.consort/features/<id>/pipeline.json);
|
|
33
|
+
# the bare `.consort/pipeline.json` is kept for older layouts. Matching only the top-level path
|
|
34
|
+
# left the per-feature file TRACKED — so it travelled onto staging/main and collided at the
|
|
35
|
+
# promotion merge (two branches editing one run's gate-bookkeeping), the exact "pipeline.json
|
|
36
|
+
# conflict blocks promotion" defect. Both forms are run-state, never the design corpus.
|
|
32
37
|
.consort/pipeline.json
|
|
38
|
+
.consort/features/*/pipeline.json
|
|
33
39
|
# The turn recorder's per-run observability: its internal file-hash map
|
|
34
40
|
# (.recorder-state.json, used for delta computation), the routing-decision
|
|
35
41
|
# diagnostic stream (routing-decisions.jsonl), and the per-turn record dirs
|
|
@@ -297,6 +297,14 @@ if [ "$first" = "--warm" ] || [ "$first" = "--rewarm" ]; then
|
|
|
297
297
|
fi
|
|
298
298
|
|
|
299
299
|
bin="$first"; shift
|
|
300
|
+
# A help request (`-h`/`--help`) must NEVER trigger a one-time cold-cache install:
|
|
301
|
+
# printing usage should have no side effects. Treat it like LK_NO_INSTALL so a cold
|
|
302
|
+
# cache SHORT-CIRCUITS with a clear "warm first" note instead of silently kicking off a
|
|
303
|
+
# multi-minute download (the "my --help refreshed the cache?!" surprise). When the cache
|
|
304
|
+
# is already warm, help still prints instantly, exactly as before.
|
|
305
|
+
help_req=0
|
|
306
|
+
for _a in "$@"; do case "$_a" in -h|--help) help_req=1; break ;; esac; done
|
|
307
|
+
[ "$help_req" -eq 1 ] && export LK_NO_INSTALL=1
|
|
300
308
|
# Deprecation: lakebase-sftdd-* / lakebase-tdd-* are legacy aliases from the
|
|
301
309
|
# pre-Consort skill names. They still run (routed to the kit below), but are
|
|
302
310
|
# scheduled for removal in consort v0.4.0 , prefer the consort-* / lakebase-*
|
|
@@ -306,8 +314,15 @@ case "$bin" in
|
|
|
306
314
|
echo "lk: [deprecated] '${bin}' is a legacy sftdd/tdd-era alias; use the consort-* / lakebase-* name (removed in consort v0.4.0). Still running." >&2 ;;
|
|
307
315
|
esac
|
|
308
316
|
slug="$(pkg_for_bin "$bin")"
|
|
309
|
-
|
|
310
|
-
|
|
317
|
+
# Capture the status with `|| rc=$?` so `set -e` does NOT abort on resolve_pkg's
|
|
318
|
+
# non-zero returns (notably 97 = cold cache under LK_NO_INSTALL / a help request) —
|
|
319
|
+
# a bare `resolve_pkg …; rc=$?` exits before rc is ever read.
|
|
320
|
+
rc=0; resolve_pkg "$slug" run || rc=$?
|
|
321
|
+
if [ "$rc" -eq 97 ]; then
|
|
322
|
+
# Cold cache + a help request: do NOT install just to print usage.
|
|
323
|
+
[ "$help_req" -eq 1 ] && { echo "lk: not downloading the toolkit just to show '--help'. The ${slug} cache for this ref is cold; run 'lk --warm' to install it (one-time), then retry '${bin} --help'. A help request never triggers a download." >&2; exit 0; }
|
|
324
|
+
exit 97
|
|
325
|
+
fi
|
|
311
326
|
[ "$rc" -ne 0 ] && exit "$rc"
|
|
312
327
|
rel="$(bin_rel "$pkg_dir" "$bin")"
|
|
313
328
|
|
|
@@ -317,8 +332,11 @@ rel="$(bin_rel "$pkg_dir" "$bin")"
|
|
|
317
332
|
# route to the kit up front and never reach here, so an unknown kit bin still fails
|
|
318
333
|
# fast with no substrate round-trip.
|
|
319
334
|
if [ -z "$rel" ] && [ "$slug" = "scm-utils" ] && project_uses_kit; then
|
|
320
|
-
resolve_pkg kit run
|
|
321
|
-
[ "$krc" -eq 97 ]
|
|
335
|
+
krc=0; resolve_pkg kit run || krc=$?
|
|
336
|
+
if [ "$krc" -eq 97 ]; then
|
|
337
|
+
[ "$help_req" -eq 1 ] && { echo "lk: not downloading the toolkit just to show '--help'. The kit cache for this ref is cold; run 'lk --warm' to install it (one-time), then retry '${bin} --help'. A help request never triggers a download." >&2; exit 0; }
|
|
338
|
+
exit 97
|
|
339
|
+
fi
|
|
322
340
|
if [ "$krc" -eq 0 ]; then
|
|
323
341
|
rel="$(bin_rel "$pkg_dir" "$bin")"
|
|
324
342
|
[ -n "$rel" ] && slug="kit"
|