@databricks-solutions/lakebase-scm-utils 0.2.40 → 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/.github/workflows/merge.yml +14 -5
- package/templates/project/common/.github/workflows/pr.yml +14 -5
- package/templates/project/common/.gitignore.base +8 -0
- package/templates/project/common/scripts/lk +66 -33
- 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);
|
|
@@ -72,7 +72,16 @@ jobs:
|
|
|
72
72
|
# the version this project was scaffolded from, when .lakebase/scm-utils-ref is absent.
|
|
73
73
|
run: |
|
|
74
74
|
SCM_UTILS_REF="$(tr -d '[:space:]' < .lakebase/scm-utils-ref 2>/dev/null || true)"
|
|
75
|
-
|
|
75
|
+
SCM_UTILS_REF="${SCM_UTILS_REF:-v{{LAKEBASE_SCM_UTILS_VERSION}}}"
|
|
76
|
+
echo "SCM_UTILS_REF=${SCM_UTILS_REF}" >> "$GITHUB_ENV"
|
|
77
|
+
# Registry-first: version-tag refs install the published tarball from the
|
|
78
|
+
# npm registry (seconds); branch/SHA refs keep the GitHub source form
|
|
79
|
+
# (their only source).
|
|
80
|
+
case "$SCM_UTILS_REF" in
|
|
81
|
+
v[0-9]*) echo "SCM_UTILS_NPX_PKG=@databricks-solutions/lakebase-scm-utils@${SCM_UTILS_REF#v}" >> "$GITHUB_ENV" ;;
|
|
82
|
+
[0-9]*) echo "SCM_UTILS_NPX_PKG=@databricks-solutions/lakebase-scm-utils@${SCM_UTILS_REF}" >> "$GITHUB_ENV" ;;
|
|
83
|
+
*) echo "SCM_UTILS_NPX_PKG=github:databricks-solutions/lakebase-scm-utils#${SCM_UTILS_REF}" >> "$GITHUB_ENV" ;;
|
|
84
|
+
esac
|
|
76
85
|
|
|
77
86
|
- name: Detect project language
|
|
78
87
|
id: detect-lang
|
|
@@ -84,7 +93,7 @@ jobs:
|
|
|
84
93
|
# this project.
|
|
85
94
|
run: |
|
|
86
95
|
LANG="$(npx --yes \
|
|
87
|
-
--package=
|
|
96
|
+
--package="${SCM_UTILS_NPX_PKG}" \
|
|
88
97
|
lakebase-detect-language)"
|
|
89
98
|
echo "lang=$LANG" >> $GITHUB_OUTPUT
|
|
90
99
|
echo "Detected language: $LANG"
|
|
@@ -268,7 +277,7 @@ jobs:
|
|
|
268
277
|
echo " instance: $LAKEBASE_PROJECT_ID"
|
|
269
278
|
echo " source: $DEFAULT_NAME"
|
|
270
279
|
echo " backup: $SNAPSHOT_NAME"
|
|
271
|
-
if CREATE_OUT="$(npx --yes --package=
|
|
280
|
+
if CREATE_OUT="$(npx --yes --package="${SCM_UTILS_NPX_PKG}" \
|
|
272
281
|
lakebase-cut-backup \
|
|
273
282
|
--instance "$LAKEBASE_PROJECT_ID" \
|
|
274
283
|
--source "$DEFAULT_NAME" \
|
|
@@ -312,7 +321,7 @@ jobs:
|
|
|
312
321
|
# is the promotion opt-in (allowTier) + verifies the tier reached head,
|
|
313
322
|
# so a partial promotion fails the job instead of leaving git ahead of
|
|
314
323
|
# the Lakebase schema. Single-tier (default branch) also goes through it.
|
|
315
|
-
npx --yes --package=
|
|
324
|
+
npx --yes --package="${SCM_UTILS_NPX_PKG}" \
|
|
316
325
|
lakebase-schema-migrate apply-tier \
|
|
317
326
|
--instance "$LAKEBASE_PROJECT_ID" \
|
|
318
327
|
--branch "$LAKEBASE_BRANCH_NAME" \
|
|
@@ -394,7 +403,7 @@ jobs:
|
|
|
394
403
|
echo " substrate ref: ${SCM_UTILS_REF}"
|
|
395
404
|
echo " instance: $LAKEBASE_PROJECT_ID"
|
|
396
405
|
echo " branch: $LAKEBASE_BRANCH_NAME"
|
|
397
|
-
npx --yes --package=
|
|
406
|
+
npx --yes --package="${SCM_UTILS_NPX_PKG}" \
|
|
398
407
|
lakebase-schema-migrate status \
|
|
399
408
|
--instance "$LAKEBASE_PROJECT_ID" \
|
|
400
409
|
--branch "$LAKEBASE_BRANCH_NAME" \
|
|
@@ -31,7 +31,16 @@ jobs:
|
|
|
31
31
|
# the version this project was scaffolded from, when .lakebase/scm-utils-ref is absent.
|
|
32
32
|
run: |
|
|
33
33
|
SCM_UTILS_REF="$(tr -d '[:space:]' < .lakebase/scm-utils-ref 2>/dev/null || true)"
|
|
34
|
-
|
|
34
|
+
SCM_UTILS_REF="${SCM_UTILS_REF:-v{{LAKEBASE_SCM_UTILS_VERSION}}}"
|
|
35
|
+
echo "SCM_UTILS_REF=${SCM_UTILS_REF}" >> "$GITHUB_ENV"
|
|
36
|
+
# Registry-first: version-tag refs install the published tarball from the
|
|
37
|
+
# npm registry (seconds); branch/SHA refs keep the GitHub source form
|
|
38
|
+
# (their only source).
|
|
39
|
+
case "$SCM_UTILS_REF" in
|
|
40
|
+
v[0-9]*) echo "SCM_UTILS_NPX_PKG=@databricks-solutions/lakebase-scm-utils@${SCM_UTILS_REF#v}" >> "$GITHUB_ENV" ;;
|
|
41
|
+
[0-9]*) echo "SCM_UTILS_NPX_PKG=@databricks-solutions/lakebase-scm-utils@${SCM_UTILS_REF}" >> "$GITHUB_ENV" ;;
|
|
42
|
+
*) echo "SCM_UTILS_NPX_PKG=github:databricks-solutions/lakebase-scm-utils#${SCM_UTILS_REF}" >> "$GITHUB_ENV" ;;
|
|
43
|
+
esac
|
|
35
44
|
|
|
36
45
|
- name: Detect project language
|
|
37
46
|
id: detect-lang
|
|
@@ -43,7 +52,7 @@ jobs:
|
|
|
43
52
|
# this project.
|
|
44
53
|
run: |
|
|
45
54
|
LANG="$(npx --yes \
|
|
46
|
-
--package=
|
|
55
|
+
--package="${SCM_UTILS_NPX_PKG}" \
|
|
47
56
|
lakebase-detect-language)"
|
|
48
57
|
echo "lang=$LANG" >> $GITHUB_OUTPUT
|
|
49
58
|
echo "Detected language: $LANG"
|
|
@@ -343,7 +352,7 @@ jobs:
|
|
|
343
352
|
echo " substrate ref: ${SCM_UTILS_REF}"
|
|
344
353
|
echo " instance: $LAKEBASE_PROJECT_ID"
|
|
345
354
|
echo " branch: $LAKEBASE_BRANCH_NAME"
|
|
346
|
-
npx --yes --package=
|
|
355
|
+
npx --yes --package="${SCM_UTILS_NPX_PKG}" \
|
|
347
356
|
lakebase-schema-migrate apply \
|
|
348
357
|
--instance "$LAKEBASE_PROJECT_ID" \
|
|
349
358
|
--branch "$LAKEBASE_BRANCH_NAME" \
|
|
@@ -469,7 +478,7 @@ jobs:
|
|
|
469
478
|
# `|| URL=""` clause keeps the workflow green and the env var
|
|
470
479
|
# unset so the downstream Playwright step degrades cleanly.
|
|
471
480
|
URL="$(npx --yes \
|
|
472
|
-
--package=
|
|
481
|
+
--package="${SCM_UTILS_NPX_PKG}" \
|
|
473
482
|
lakebase-ci-app-endpoint \
|
|
474
483
|
--instance "$LAKEBASE_PROJECT_ID" \
|
|
475
484
|
--branch "$CI_BRANCH")" || URL=""
|
|
@@ -590,7 +599,7 @@ jobs:
|
|
|
590
599
|
if [ -n "$PRODUCTION_DATASOURCE_URL" ] && [ -n "$BRANCH" ]; then
|
|
591
600
|
echo "### Schema diff: \`${BRANCH}\` vs parent (\`${PR_BASE_REF}\`)" >> "$MD"
|
|
592
601
|
echo "" >> "$MD"
|
|
593
|
-
if ! npx --yes --package=
|
|
602
|
+
if ! npx --yes --package="${SCM_UTILS_NPX_PKG}" \
|
|
594
603
|
lakebase-schema-diff \
|
|
595
604
|
--instance "$LAKEBASE_PROJECT_ID" \
|
|
596
605
|
--branch "$BRANCH" \
|
|
@@ -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
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# lk , run a substrate/SCM or SFTDD CLI fast, without the npx tax.
|
|
3
3
|
#
|
|
4
|
-
# `npx --package
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# `npx --package=<pkg>@<version> <bin>` re-resolves on EVERY call (~3.5s even
|
|
5
|
+
# warm). This shim resolves a package ONCE per ref into a shared cache and runs
|
|
6
|
+
# the bin via `node dist/...` (~0.09s). Cold-cache installs pull the published
|
|
7
|
+
# tarball from the npm registry for version-tag refs (seconds; GitHub is the
|
|
8
|
+
# fallback) and clone+install from GitHub for branch/SHA refs (their only source).
|
|
7
9
|
#
|
|
8
10
|
# Two packages back a scaffolded project, and lk routes each bin to its owner so
|
|
9
11
|
# each runs from its OWN dist (no cross-package node_modules paths):
|
|
@@ -110,27 +112,20 @@ resolve_pkg() {
|
|
|
110
112
|
node -e 'const fs=require("fs"),p=require("path"),r=process.argv[1];let b={};try{b=(require(r+"/package.json").bin)||{}}catch(e){process.stderr.write("lk: cannot read package.json at "+r+"\n");process.exit(1)}const m=Object.values(b).filter(x=>!fs.existsSync(p.join(r,x)));if(m.length){process.stderr.write("lk: "+r+" dist is INCOMPLETE , "+m.length+"/"+Object.keys(b).length+" bin(s) have no file (e.g. "+m.slice(0,3).join(", ")+").\n");process.exit(1)}' "$1"
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
# SHOW WHAT IS BEING INSTALLED , not just a clock. npm's progress bar is TTY-only
|
|
126
|
-
# (silent when piped to a log for the relay), but `--loglevel=http` prints one line
|
|
127
|
-
# per package fetched (`npm http fetch GET 200 …/<pkg>/-/<pkg>-<ver>.tgz`) , exactly
|
|
128
|
-
# "what is being installed". We run npm to a temp log and FORWARD its new lines
|
|
129
|
-
# (prefixed `lk:` so the narrator relays them) every few seconds as they land; only
|
|
130
|
-
# when npm is briefly between fetches (e.g. the initial git clone) do we print an
|
|
131
|
-
# elapsed-time line. So the relay never goes silent and never looks like a timeout.
|
|
115
|
+
# npm_install_with_progress <spec>: install <spec> into $tmp in the background
|
|
116
|
+
# while narrating. `--omit=dev` skips the build-toolchain devDeps (tsup/vitest/
|
|
117
|
+
# typescript/esbuild) , the kit ships prebuilt dist/, so consumers never build;
|
|
118
|
+
# `--no-audit --no-fund` drop registry round-trips. SHOW WHAT IS BEING INSTALLED,
|
|
119
|
+
# not just a clock: npm's progress bar is TTY-only (silent when piped to a log
|
|
120
|
+
# for the relay), but `--loglevel=http` prints one line per package fetched, so
|
|
121
|
+
# we FORWARD its new lines (prefixed `lk:`) every few seconds as they land; only
|
|
122
|
+
# when npm is briefly between fetches (e.g. a git clone) do we print an elapsed
|
|
123
|
+
# line. The relay never goes silent and never looks like a timeout. Returns
|
|
124
|
+
# npm's exit status.
|
|
125
|
+
npm_install_with_progress() {
|
|
126
|
+
local spec="$1"
|
|
132
127
|
local npmlog; npmlog="$(mktemp)"
|
|
133
|
-
npm install --loglevel=http --no-save --prefer-online --omit=dev --no-audit --no-fund --prefix "$tmp" "$
|
|
128
|
+
npm install --loglevel=http --no-save --prefer-online --omit=dev --no-audit --no-fund --prefix "$tmp" "$spec" > "$npmlog" 2>&1 &
|
|
134
129
|
local npm_pid=$! start last cur
|
|
135
130
|
start=$(date +%s); last=0
|
|
136
131
|
while kill -0 "$npm_pid" 2>/dev/null; do
|
|
@@ -140,22 +135,60 @@ resolve_pkg() {
|
|
|
140
135
|
tail -c "+$((last + 1))" "$npmlog" | sed 's/^/lk: /' >&2 # forward the real npm progress
|
|
141
136
|
last="$cur"
|
|
142
137
|
else
|
|
143
|
-
echo "lk: … still installing the Consort TOOLKIT itself (the kit — one-time per version, cached + reused across projects) ($(( $(date +%s) - start ))s elapsed
|
|
138
|
+
echo "lk: … still installing the Consort TOOLKIT itself (the kit — one-time per version, cached + reused across projects) ($(( $(date +%s) - start ))s elapsed)…" >&2
|
|
144
139
|
fi
|
|
145
140
|
done
|
|
146
|
-
# Flush any final lines npm wrote between the last poll and exit, then drop the
|
|
147
|
-
# (all branches below are covered , no leaked temp on success OR failure).
|
|
141
|
+
# Flush any final lines npm wrote between the last poll and exit, then drop the
|
|
142
|
+
# log (all branches below are covered , no leaked temp on success OR failure).
|
|
148
143
|
cur=$(wc -c < "$npmlog" 2>/dev/null || echo 0)
|
|
149
144
|
[ "$cur" -gt "$last" ] && tail -c "+$((last + 1))" "$npmlog" | sed 's/^/lk: /' >&2
|
|
150
145
|
rm -f "$npmlog"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
146
|
+
wait "$npm_pid"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# finalize_install <committish> <sha>: promote a completed $tmp install into the
|
|
150
|
+
# version-keyed cache slot.
|
|
151
|
+
finalize_install() {
|
|
152
|
+
local committish="$1" sha="$2"
|
|
153
|
+
rm -rf "$cache"
|
|
154
|
+
if mv "$tmp" "$cache" 2>/dev/null; then
|
|
155
|
+
[ -n "$sha" ] && printf '%s\n' "$sha" > "${cache}/.pkg-sha" 2>/dev/null || true
|
|
156
|
+
echo "lk: Consort toolkit ready (${pkg}@${committish}; subsequent commands are instant)." >&2
|
|
157
|
+
return 0
|
|
158
|
+
fi
|
|
159
|
+
rm -rf "$tmp"; return 1
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
install_pkg() {
|
|
163
|
+
local committish="$1" sha="$2" tmp
|
|
164
|
+
mkdir -p "$cache_root"
|
|
165
|
+
tmp="$(mktemp -d "${cache_root}/.tmp.XXXXXX")"
|
|
166
|
+
# Tell the consumer what is happening: without narration a cold-cache install
|
|
167
|
+
# looks hung (a GitHub install takes a couple of minutes the FIRST time a ref
|
|
168
|
+
# is cached; a registry install is seconds).
|
|
169
|
+
echo "lk: Downloading the Consort toolkit (${pkg}@${committish}) , one-time for this version. Later commands are instant." >&2
|
|
170
|
+
# Registry-first for immutable version tags (v0.2.40 -> ${pkg}@0.2.40): a
|
|
171
|
+
# registry tarball lands in seconds (no git clone, no build). GitHub stays the
|
|
172
|
+
# fallback (e.g. a tag not yet published) and is the only path for branch/SHA
|
|
173
|
+
# refs, which the registry never has.
|
|
174
|
+
local ver=""
|
|
175
|
+
if [ "$is_sha" -eq 0 ]; then
|
|
176
|
+
case "$ref" in
|
|
177
|
+
v[0-9]*) ver="${ref#v}" ;;
|
|
178
|
+
[0-9]*) ver="$ref" ;;
|
|
179
|
+
esac
|
|
180
|
+
fi
|
|
181
|
+
if [ -n "$ver" ]; then
|
|
182
|
+
if npm_install_with_progress "${pkg}@${ver}" && [ -d "${tmp}/node_modules/${pkg}/dist" ]; then
|
|
183
|
+
finalize_install "$committish" "$sha"
|
|
184
|
+
return $?
|
|
157
185
|
fi
|
|
158
|
-
|
|
186
|
+
echo "lk: registry install of ${pkg}@${ver} did not complete; falling back to GitHub (${gh}#${committish})." >&2
|
|
187
|
+
rm -rf "$tmp"; tmp="$(mktemp -d "${cache_root}/.tmp.XXXXXX")"
|
|
188
|
+
fi
|
|
189
|
+
if npm_install_with_progress "${gh}#${committish}" && [ -d "${tmp}/node_modules/${pkg}/dist" ]; then
|
|
190
|
+
finalize_install "$committish" "$sha"
|
|
191
|
+
return $?
|
|
159
192
|
fi
|
|
160
193
|
rm -rf "$tmp"; return 1
|
|
161
194
|
}
|
|
@@ -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
|
|