@databricks-solutions/lakebase-scm-utils 0.2.41 → 0.2.42
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 +3 -1
- package/dist/scripts/github/index.js +3 -1
- package/dist/scripts/github/pr.cli.cjs +3 -1
- package/dist/scripts/github/pr.cli.js +3 -1
- package/dist/scripts/index.cjs +66 -21
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +59 -15
- package/dist/scripts/lakebase/branch.cli.cjs +3 -1
- package/dist/scripts/lakebase/branch.cli.js +3 -1
- package/dist/scripts/lakebase/ci-app-endpoint.cli.cjs +3 -1
- package/dist/scripts/lakebase/ci-app-endpoint.cli.js +3 -1
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +5 -3
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +5 -3
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +5 -3
- package/dist/scripts/lakebase/collapse-heads.cli.js +5 -3
- package/dist/scripts/lakebase/cut-backup.cli.cjs +3 -1
- package/dist/scripts/lakebase/cut-backup.cli.js +3 -1
- package/dist/scripts/lakebase/cut-tier.cli.cjs +3 -1
- package/dist/scripts/lakebase/cut-tier.cli.js +3 -1
- package/dist/scripts/lakebase/detect-language.cli.cjs +5 -3
- package/dist/scripts/lakebase/detect-language.cli.js +5 -3
- package/dist/scripts/lakebase/doctor.cli.cjs +5 -3
- package/dist/scripts/lakebase/doctor.cli.js +5 -3
- package/dist/scripts/lakebase/get-connection.cli.cjs +5 -3
- package/dist/scripts/lakebase/get-connection.cli.js +5 -3
- package/dist/scripts/lakebase/index.cjs +66 -21
- package/dist/scripts/lakebase/index.d.cts +5 -1
- package/dist/scripts/lakebase/index.d.ts +5 -1
- package/dist/scripts/lakebase/index.js +59 -15
- package/dist/scripts/lakebase/infra-runner.cli.cjs +5 -3
- package/dist/scripts/lakebase/infra-runner.cli.js +5 -3
- package/dist/scripts/lakebase/new-migration.cli.cjs +5 -3
- package/dist/scripts/lakebase/new-migration.cli.js +5 -3
- package/dist/scripts/lakebase/schema-diff.cli.cjs +5 -3
- package/dist/scripts/lakebase/schema-diff.cli.js +5 -3
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +5 -3
- package/dist/scripts/lakebase/schema-migrate.cli.js +5 -3
- package/dist/scripts/lakebase/scm-abandon-feature.cli.cjs +37 -1
- package/dist/scripts/lakebase/scm-abandon-feature.cli.js +37 -1
- package/dist/scripts/lakebase/scm-adopt-state.cli.cjs +37 -1
- package/dist/scripts/lakebase/scm-adopt-state.cli.js +37 -1
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +45 -9
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +39 -3
- package/dist/scripts/lakebase/scm-cleanup.cli.cjs +3 -1
- package/dist/scripts/lakebase/scm-cleanup.cli.js +3 -1
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +56 -29
- package/dist/scripts/lakebase/scm-doctor.cli.js +50 -23
- package/dist/scripts/lakebase/scm-feature-branch.cli.cjs +1 -0
- package/dist/scripts/lakebase/scm-feature-branch.cli.js +1 -0
- package/dist/scripts/lakebase/scm-merge.cli.cjs +45 -9
- package/dist/scripts/lakebase/scm-merge.cli.js +39 -3
- package/dist/scripts/lakebase/scm-prepare-pr.cli.cjs +34 -0
- package/dist/scripts/lakebase/scm-prepare-pr.cli.js +34 -0
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +6 -3
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +6 -3
- package/dist/scripts/lakebase/scm-recover-orphans.cli.cjs +37 -1
- package/dist/scripts/lakebase/scm-recover-orphans.cli.js +37 -1
- package/dist/scripts/lakebase/scm-state.cli.cjs +5 -4
- package/dist/scripts/lakebase/scm-state.cli.js +4 -3
- package/dist/scripts/lakebase/scm-wait-ci.cli.cjs +34 -0
- package/dist/scripts/lakebase/scm-wait-ci.cli.js +34 -0
- package/dist/scripts/util/index.cjs +5 -3
- package/dist/scripts/util/index.js +5 -3
- package/package.json +1 -1
- package/scripts/lakebase/databricks-cli.ts +7 -1
- package/scripts/lakebase/enable-e2e.ts +19 -3
- package/scripts/lakebase/scm-doctor.ts +1 -12
- package/scripts/lakebase/scm-workflow-state.ts +49 -0
- package/templates/project/common/.gitignore.base +8 -0
- package/templates/project/common/scripts/run-tests.sh +18 -1
|
@@ -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.42".length > 0) {
|
|
1634
|
+
cached = "0.2.42";
|
|
1635
1635
|
return cached;
|
|
1636
1636
|
}
|
|
1637
1637
|
cached = "unknown";
|
|
@@ -1866,7 +1866,9 @@ function resolveProfile(opts) {
|
|
|
1866
1866
|
function buildInvocation(args, opts) {
|
|
1867
1867
|
const base = opts.env ?? process.env;
|
|
1868
1868
|
const trimmedHost = effectiveHost(opts)?.replace(/\/+$/, "");
|
|
1869
|
-
const env =
|
|
1869
|
+
const env = { ...base };
|
|
1870
|
+
if (trimmedHost) env.DATABRICKS_HOST = trimmedHost;
|
|
1871
|
+
delete env.DATABRICKS_WORKSPACE_ID;
|
|
1870
1872
|
const profile = resolveProfile(opts);
|
|
1871
1873
|
const argv = profile && !opts.noProfile && !args.includes("--profile") ? [...args, "--profile", profile] : args;
|
|
1872
1874
|
return { argv, env, profile };
|
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.42",
|
|
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,
|
|
@@ -149,7 +149,13 @@ export function buildInvocation(args: string[], opts: DatabricksCliOptions): {
|
|
|
149
149
|
// so an in-project call pins the project workspace rather than the DEFAULT
|
|
150
150
|
// profile's default host. Trailing slash stripped for a stable value.
|
|
151
151
|
const trimmedHost = effectiveHost(opts)?.replace(/\/+$/, "");
|
|
152
|
-
const env: NodeJS.ProcessEnv =
|
|
152
|
+
const env: NodeJS.ProcessEnv = { ...base };
|
|
153
|
+
if (trimmedHost) env.DATABRICKS_HOST = trimmedHost;
|
|
154
|
+
// A stray DATABRICKS_WORKSPACE_ID (a bare numeric workspace ID, never a URL)
|
|
155
|
+
// makes the CLI's unified-host path dial https://<id> (issue #204.1). The
|
|
156
|
+
// workspace always resolves via the profile/host chain above, so scrub the
|
|
157
|
+
// stray var from the child env (the documented workaround, centralized).
|
|
158
|
+
delete env.DATABRICKS_WORKSPACE_ID;
|
|
153
159
|
const profile = resolveProfile(opts);
|
|
154
160
|
const argv =
|
|
155
161
|
profile && !opts.noProfile && !args.includes("--profile")
|
|
@@ -225,12 +225,27 @@ function runTestsE2eBlock(): string {
|
|
|
225
225
|
' export VITE_PROXY_TARGET="http://127.0.0.1:${E2E_BACKEND_PORT}"',
|
|
226
226
|
' echo "Local E2E free-port: backend :$E2E_BACKEND_PORT / client :$E2E_CLIENT_PORT"',
|
|
227
227
|
'fi',
|
|
228
|
+
// Stale-server hygiene (issue #197): CI=1 on the Playwright invocations below
|
|
229
|
+
// forces EVERY config vintage's reuseExistingServer guard OFF (old `!CI` and
|
|
230
|
+
// new `!!BASE_URL` alike), so the verify never reuses a stale server bound to
|
|
231
|
+
// an unmigrated branch (the false-500 misdiagnosis). A lingering uvicorn/vite
|
|
232
|
+
// on the conventional default ports still serves STALE schema to anything
|
|
233
|
+
// that reaches it, so clear it (TERM, not KILL) before the boot - never when
|
|
234
|
+
// the caller pre-set the ports (the deploy gate owns that server; BASE_URL).
|
|
235
|
+
'if [ -z "${BASE_URL:-}" ] && command -v lsof >/dev/null 2>&1; then',
|
|
236
|
+
' for p in "${E2E_BACKEND_PORT:-8000}" "${E2E_CLIENT_PORT:-5173}"; do',
|
|
237
|
+
' if lsof -iTCP:"$p" -sTCP:LISTEN -n -P >/dev/null 2>&1; then',
|
|
238
|
+
' echo "Local E2E: clearing a stale listener on :$p before the webServer boot"',
|
|
239
|
+
' lsof -ti tcp:"$p" 2>/dev/null | xargs kill 2>/dev/null || true',
|
|
240
|
+
' fi',
|
|
241
|
+
' done',
|
|
242
|
+
'fi',
|
|
228
243
|
'if [ -f "$REPO_ROOT/playwright.config.ts" ] || [ -f "$REPO_ROOT/playwright.config.js" ]; then',
|
|
229
244
|
' echo "Running Playwright E2E tests..."',
|
|
230
245
|
' if [ -f "$REPO_ROOT/package.json" ] && command -v npm >/dev/null 2>&1; then',
|
|
231
|
-
' (cd "$REPO_ROOT" && npm run test:e2e)',
|
|
246
|
+
' (cd "$REPO_ROOT" && CI=1 npm run test:e2e)',
|
|
232
247
|
" else",
|
|
233
|
-
' (cd "$REPO_ROOT" && npx --yes playwright test)',
|
|
248
|
+
' (cd "$REPO_ROOT" && CI=1 npx --yes playwright test)',
|
|
234
249
|
" fi",
|
|
235
250
|
// Python E2E: pytest-playwright + the shipped tests/e2e/conftest.py
|
|
236
251
|
// (live_server). Gated on the conftest + pyproject so it only fires for a
|
|
@@ -269,7 +284,8 @@ function runTestsE2eBlock(): string {
|
|
|
269
284
|
'if [ -f "$REPO_ROOT/client/playwright.config.ts" ] || [ -f "$REPO_ROOT/client/playwright.config.js" ] || [ -f "$REPO_ROOT/client/playwright.config.mjs" ]; then',
|
|
270
285
|
' echo "Running client Playwright E2E tests..."',
|
|
271
286
|
' (cd "$REPO_ROOT/client" && npx --yes playwright install chromium)',
|
|
272
|
-
|
|
287
|
+
// CI=1: no server reuse on any config vintage (see the stale-server note above).
|
|
288
|
+
' (cd "$REPO_ROOT/client" && CI=1 npx --yes playwright test --pass-with-no-tests)',
|
|
273
289
|
"fi",
|
|
274
290
|
"",
|
|
275
291
|
].join("\n");
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
// an error rather than performing a related-but-different remediation.
|
|
10
10
|
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
|
-
import { execFileSync } from "node:child_process";
|
|
13
12
|
import * as path from "node:path";
|
|
14
13
|
import {
|
|
15
14
|
getBranchByName,
|
|
@@ -25,6 +24,7 @@ import { recoverOrphans } from "./scm-recover-orphans.js";
|
|
|
25
24
|
import { abandonFeatureBranch } from "./scm-abandon-feature.js";
|
|
26
25
|
import {
|
|
27
26
|
readWorkflowState,
|
|
27
|
+
isGitTracked,
|
|
28
28
|
type ScmWorkflowState,
|
|
29
29
|
type TierTopology,
|
|
30
30
|
} from "./scm-workflow-state.js";
|
|
@@ -106,17 +106,6 @@ function leafOf(b: LakebaseBranchInfo): string {
|
|
|
106
106
|
return b.name.split("/").pop() ?? b.name;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
/** True iff `rel` (repo-relative) is git-tracked in projectDir. Best-effort: no
|
|
110
|
-
* git / not a repo -> false. */
|
|
111
|
-
function isGitTracked(projectDir: string, rel: string): boolean {
|
|
112
|
-
try {
|
|
113
|
-
execFileSync("git", ["ls-files", "--error-unmatch", "--", rel], { cwd: projectDir, stdio: "ignore" });
|
|
114
|
-
return true;
|
|
115
|
-
} catch {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
109
|
function worstOf(a: DoctorSeverity, b: DoctorSeverity): DoctorSeverity {
|
|
121
110
|
const order: DoctorSeverity[] = ["ok", "warn", "fail"];
|
|
122
111
|
return order[Math.max(order.indexOf(a), order.indexOf(b))] as DoctorSeverity;
|
|
@@ -13,6 +13,52 @@
|
|
|
13
13
|
|
|
14
14
|
import * as fs from "node:fs";
|
|
15
15
|
import * as path from "node:path";
|
|
16
|
+
import { execFileSync } from "node:child_process";
|
|
17
|
+
|
|
18
|
+
/** True iff `rel` (repo-relative) is git-tracked in projectDir. Best-effort: no
|
|
19
|
+
* git / not a repo -> false. Shared home (scm-doctor's Finding-28 check reads
|
|
20
|
+
* the same predicate). */
|
|
21
|
+
export function isGitTracked(projectDir: string, rel: string): boolean {
|
|
22
|
+
try {
|
|
23
|
+
execFileSync("git", ["ls-files", "--error-unmatch", "--", rel], { cwd: projectDir, stdio: "ignore" });
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Ensure the runtime SCM claim state is NOT git-tracked (issue #203 / Finding 28):
|
|
31
|
+
* a tracked .lakebase/workflow-state.json gets a branch-COMMITTED stale claim
|
|
32
|
+
* restored over the live one by every branch checkout / `git reset --hard
|
|
33
|
+
* origin/<tier>`, which then blocks the next feature's claim
|
|
34
|
+
* ("already-claimed-other") until a manual abandon+reclaim. Untrack it
|
|
35
|
+
* (index-only; the working file stays) and cover it in .gitignore, so the live
|
|
36
|
+
* claim survives checkouts. The committed .lakebase/kit-ref + scm-utils-ref stay
|
|
37
|
+
* tracked deliberately (CI resolves them). Idempotent + best-effort (non-git
|
|
38
|
+
* dirs / unwritable .gitignore skip silently – the state write itself still lands). */
|
|
39
|
+
function ensureWorkflowStateUntracked(projectDir: string): void {
|
|
40
|
+
const rel = ".lakebase/workflow-state.json";
|
|
41
|
+
try {
|
|
42
|
+
if (isGitTracked(projectDir, rel)) {
|
|
43
|
+
execFileSync("git", ["rm", "--cached", "--quiet", "--ignore-unmatch", "--", rel], { cwd: projectDir, stdio: "ignore" });
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
/* best-effort */
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const gitignore = path.join(projectDir, ".gitignore");
|
|
50
|
+
const existing = fs.existsSync(gitignore) ? fs.readFileSync(gitignore, "utf8") : "";
|
|
51
|
+
if (!existing.split("\n").some((l) => l.trim() === rel)) {
|
|
52
|
+
const sep = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
53
|
+
fs.appendFileSync(
|
|
54
|
+
gitignore,
|
|
55
|
+
`${sep}# Runtime SCM claim state (per working tree): a branch checkout must never restore\n# a stale committed claim over the live one (issue #203 / Finding 28).\n${rel}\n`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
/* best-effort */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
16
62
|
|
|
17
63
|
/** All SCM states, in canonical progression order. */
|
|
18
64
|
export const SCM_STATES = [
|
|
@@ -133,6 +179,9 @@ export function writeWorkflowState(
|
|
|
133
179
|
.join("\n");
|
|
134
180
|
throw new Error(`Refusing to write invalid SCM state:\n${summary}`);
|
|
135
181
|
}
|
|
182
|
+
// Self-heal on every write (issue #203): the live claim survives checkouts
|
|
183
|
+
// only while the state file is untracked.
|
|
184
|
+
ensureWorkflowStateUntracked(projectDir);
|
|
136
185
|
const dir = path.join(projectDir, ".lakebase");
|
|
137
186
|
fs.mkdirSync(dir, { recursive: true });
|
|
138
187
|
const target = stateFilePath(projectDir);
|
|
@@ -58,6 +58,14 @@ application-local.properties
|
|
|
58
58
|
.lakebase/kit-ref.local
|
|
59
59
|
.lakebase/scm-utils-ref.local
|
|
60
60
|
|
|
61
|
+
# The runtime SCM CLAIM state: per working tree, rewritten by every SCM transition
|
|
62
|
+
# (claim / pr-ready / ci-green / merged). If it is git-tracked, a branch checkout
|
|
63
|
+
# or `git reset --hard origin/<tier>` restores a branch-COMMITTED stale claim over
|
|
64
|
+
# the live one, and the next feature's claim then refuses "already-claimed-other"
|
|
65
|
+
# until a manual abandon+reclaim (issue #203 / Finding 28). Never track it; the
|
|
66
|
+
# committed kit-ref / scm-utils-ref pins above stay tracked deliberately.
|
|
67
|
+
.lakebase/workflow-state.json
|
|
68
|
+
|
|
61
69
|
# Per-agent local memory: each role subagent (memory: project) persists its own
|
|
62
70
|
# learnings here mid-run. It is local + churny (re-written every run), not feature
|
|
63
71
|
# code, so it must never be committed nor count as a dirty working tree , an
|
|
@@ -213,6 +213,20 @@ ensure_local_e2e_ports() {
|
|
|
213
213
|
export VITE_PROXY_TARGET="http://127.0.0.1:${E2E_BACKEND_PORT}"
|
|
214
214
|
echo "Local E2E free-port: backend :$E2E_BACKEND_PORT / client :$E2E_CLIENT_PORT"
|
|
215
215
|
fi
|
|
216
|
+
# Stale-server hygiene (issue #197): CI=1 (on the Playwright invocation below)
|
|
217
|
+
# means the webServer never REUSES a stale app, and free_port moved us off any
|
|
218
|
+
# collision - but a lingering uvicorn/vite on the conventional default ports (a
|
|
219
|
+
# prior E2E run, or a deploy whose teardown missed) still serves STALE schema to
|
|
220
|
+
# anything that reaches it. Clear it (TERM, not KILL) before the boot. Never
|
|
221
|
+
# when the caller pre-set the ports (the deploy gate owns that server).
|
|
222
|
+
if [ -z "${BASE_URL:-}" ] && command -v lsof >/dev/null 2>&1; then
|
|
223
|
+
for p in "${E2E_BACKEND_PORT:-8000}" "${E2E_CLIENT_PORT:-5173}"; do
|
|
224
|
+
if lsof -iTCP:"$p" -sTCP:LISTEN -n -P >/dev/null 2>&1; then
|
|
225
|
+
echo "Local E2E: clearing a stale listener on :$p before the webServer boot"
|
|
226
|
+
lsof -ti tcp:"$p" 2>/dev/null | xargs kill 2>/dev/null || true
|
|
227
|
+
fi
|
|
228
|
+
done
|
|
229
|
+
fi
|
|
216
230
|
}
|
|
217
231
|
|
|
218
232
|
if [ "$#" -eq 0 ] && [ -d "$REPO_ROOT/client/tests/e2e" ] \
|
|
@@ -240,8 +254,11 @@ if [ "$#" -eq 0 ] && [ -d "$REPO_ROOT/client/tests/e2e" ] \
|
|
|
240
254
|
# Use the client's OWN playwright bin (guaranteed present by the check/install above), not npx,
|
|
241
255
|
# so browser install + the run are deterministic under the deploy gate. install chromium first
|
|
242
256
|
# (playwright test does not auto-install browsers); a failing E2E fails the run (set -e).
|
|
257
|
+
# CI=1: force EVERY playwright.config vintage's reuseExistingServer guard OFF
|
|
258
|
+
# (old `!CI` and new `!!BASE_URL` alike) so the verify never reuses a stale
|
|
259
|
+
# server bound to an unmigrated branch (issue #197's false-500 misdiagnosis).
|
|
243
260
|
ensure_local_e2e_ports
|
|
244
|
-
( cd "$REPO_ROOT/client" && ./node_modules/.bin/playwright install chromium && npm run test:e2e )
|
|
261
|
+
( cd "$REPO_ROOT/client" && ./node_modules/.bin/playwright install chromium && CI=1 npm run test:e2e )
|
|
245
262
|
fi
|
|
246
263
|
fi
|
|
247
264
|
|