@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.
Files changed (71) hide show
  1. package/dist/scripts/github/index.cjs +3 -1
  2. package/dist/scripts/github/index.js +3 -1
  3. package/dist/scripts/github/pr.cli.cjs +3 -1
  4. package/dist/scripts/github/pr.cli.js +3 -1
  5. package/dist/scripts/index.cjs +66 -21
  6. package/dist/scripts/index.d.cts +1 -1
  7. package/dist/scripts/index.d.ts +1 -1
  8. package/dist/scripts/index.js +59 -15
  9. package/dist/scripts/lakebase/branch.cli.cjs +3 -1
  10. package/dist/scripts/lakebase/branch.cli.js +3 -1
  11. package/dist/scripts/lakebase/ci-app-endpoint.cli.cjs +3 -1
  12. package/dist/scripts/lakebase/ci-app-endpoint.cli.js +3 -1
  13. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +5 -3
  14. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +5 -3
  15. package/dist/scripts/lakebase/collapse-heads.cli.cjs +5 -3
  16. package/dist/scripts/lakebase/collapse-heads.cli.js +5 -3
  17. package/dist/scripts/lakebase/cut-backup.cli.cjs +3 -1
  18. package/dist/scripts/lakebase/cut-backup.cli.js +3 -1
  19. package/dist/scripts/lakebase/cut-tier.cli.cjs +3 -1
  20. package/dist/scripts/lakebase/cut-tier.cli.js +3 -1
  21. package/dist/scripts/lakebase/detect-language.cli.cjs +5 -3
  22. package/dist/scripts/lakebase/detect-language.cli.js +5 -3
  23. package/dist/scripts/lakebase/doctor.cli.cjs +5 -3
  24. package/dist/scripts/lakebase/doctor.cli.js +5 -3
  25. package/dist/scripts/lakebase/get-connection.cli.cjs +5 -3
  26. package/dist/scripts/lakebase/get-connection.cli.js +5 -3
  27. package/dist/scripts/lakebase/index.cjs +66 -21
  28. package/dist/scripts/lakebase/index.d.cts +5 -1
  29. package/dist/scripts/lakebase/index.d.ts +5 -1
  30. package/dist/scripts/lakebase/index.js +59 -15
  31. package/dist/scripts/lakebase/infra-runner.cli.cjs +5 -3
  32. package/dist/scripts/lakebase/infra-runner.cli.js +5 -3
  33. package/dist/scripts/lakebase/new-migration.cli.cjs +5 -3
  34. package/dist/scripts/lakebase/new-migration.cli.js +5 -3
  35. package/dist/scripts/lakebase/schema-diff.cli.cjs +5 -3
  36. package/dist/scripts/lakebase/schema-diff.cli.js +5 -3
  37. package/dist/scripts/lakebase/schema-migrate.cli.cjs +5 -3
  38. package/dist/scripts/lakebase/schema-migrate.cli.js +5 -3
  39. package/dist/scripts/lakebase/scm-abandon-feature.cli.cjs +37 -1
  40. package/dist/scripts/lakebase/scm-abandon-feature.cli.js +37 -1
  41. package/dist/scripts/lakebase/scm-adopt-state.cli.cjs +37 -1
  42. package/dist/scripts/lakebase/scm-adopt-state.cli.js +37 -1
  43. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +45 -9
  44. package/dist/scripts/lakebase/scm-claim-feature.cli.js +39 -3
  45. package/dist/scripts/lakebase/scm-cleanup.cli.cjs +3 -1
  46. package/dist/scripts/lakebase/scm-cleanup.cli.js +3 -1
  47. package/dist/scripts/lakebase/scm-doctor.cli.cjs +56 -29
  48. package/dist/scripts/lakebase/scm-doctor.cli.js +50 -23
  49. package/dist/scripts/lakebase/scm-feature-branch.cli.cjs +1 -0
  50. package/dist/scripts/lakebase/scm-feature-branch.cli.js +1 -0
  51. package/dist/scripts/lakebase/scm-merge.cli.cjs +45 -9
  52. package/dist/scripts/lakebase/scm-merge.cli.js +39 -3
  53. package/dist/scripts/lakebase/scm-prepare-pr.cli.cjs +34 -0
  54. package/dist/scripts/lakebase/scm-prepare-pr.cli.js +34 -0
  55. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +6 -3
  56. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +6 -3
  57. package/dist/scripts/lakebase/scm-recover-orphans.cli.cjs +37 -1
  58. package/dist/scripts/lakebase/scm-recover-orphans.cli.js +37 -1
  59. package/dist/scripts/lakebase/scm-state.cli.cjs +5 -4
  60. package/dist/scripts/lakebase/scm-state.cli.js +4 -3
  61. package/dist/scripts/lakebase/scm-wait-ci.cli.cjs +34 -0
  62. package/dist/scripts/lakebase/scm-wait-ci.cli.js +34 -0
  63. package/dist/scripts/util/index.cjs +5 -3
  64. package/dist/scripts/util/index.js +5 -3
  65. package/package.json +1 -1
  66. package/scripts/lakebase/databricks-cli.ts +7 -1
  67. package/scripts/lakebase/enable-e2e.ts +19 -3
  68. package/scripts/lakebase/scm-doctor.ts +1 -12
  69. package/scripts/lakebase/scm-workflow-state.ts +49 -0
  70. package/templates/project/common/.gitignore.base +8 -0
  71. 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.41".length > 0) {
1634
- cached = "0.2.41";
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 = trimmedHost ? { ...base, DATABRICKS_HOST: trimmedHost } : base;
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.41",
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 = trimmedHost ? { ...base, DATABRICKS_HOST: trimmedHost } : base;
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
- ' (cd "$REPO_ROOT/client" && npx --yes playwright test --pass-with-no-tests)',
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