@databricks-solutions/lakebase-scm-utils 0.3.9 → 0.3.11
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-e-E4aP4j.d.cts → pr-D-eayWyJ.d.cts} +59 -1
- package/dist/{pr-e-E4aP4j.d.ts → pr-D-eayWyJ.d.ts} +59 -1
- package/dist/scripts/git/index.cjs +30 -14
- package/dist/scripts/git/index.d.cts +32 -1
- package/dist/scripts/git/index.d.ts +32 -1
- package/dist/scripts/git/index.js +28 -14
- package/dist/scripts/github/index.d.cts +2 -60
- package/dist/scripts/github/index.d.ts +2 -60
- package/dist/scripts/index.cjs +301 -98
- package/dist/scripts/index.d.cts +4 -4
- package/dist/scripts/index.d.ts +4 -4
- package/dist/scripts/index.js +296 -98
- package/dist/scripts/lakebase/branch.cli.cjs +55 -10
- package/dist/scripts/lakebase/branch.cli.js +55 -10
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +59 -24
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +59 -24
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +16 -14
- package/dist/scripts/lakebase/collapse-heads.cli.js +16 -14
- package/dist/scripts/lakebase/cut-backup.cli.cjs +43 -10
- package/dist/scripts/lakebase/cut-backup.cli.js +43 -10
- package/dist/scripts/lakebase/cut-tier.cli.cjs +43 -10
- package/dist/scripts/lakebase/cut-tier.cli.js +43 -10
- package/dist/scripts/lakebase/detect-language.cli.cjs +16 -14
- package/dist/scripts/lakebase/detect-language.cli.js +16 -14
- package/dist/scripts/lakebase/doctor.cli.cjs +16 -14
- package/dist/scripts/lakebase/doctor.cli.js +16 -14
- package/dist/scripts/lakebase/get-connection.cli.cjs +16 -14
- package/dist/scripts/lakebase/get-connection.cli.js +16 -14
- package/dist/scripts/lakebase/index.cjs +313 -99
- package/dist/scripts/lakebase/index.d.cts +97 -2
- package/dist/scripts/lakebase/index.d.ts +97 -2
- package/dist/scripts/lakebase/index.js +310 -99
- package/dist/scripts/lakebase/infra-runner.cli.cjs +16 -14
- package/dist/scripts/lakebase/infra-runner.cli.js +16 -14
- package/dist/scripts/lakebase/new-migration.cli.cjs +16 -14
- package/dist/scripts/lakebase/new-migration.cli.js +16 -14
- package/dist/scripts/lakebase/schema-diff.cli.cjs +16 -14
- package/dist/scripts/lakebase/schema-diff.cli.js +16 -14
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +16 -14
- package/dist/scripts/lakebase/schema-migrate.cli.js +16 -14
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +59 -24
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +59 -24
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +59 -24
- package/dist/scripts/lakebase/scm-doctor.cli.js +59 -24
- package/dist/scripts/lakebase/scm-merge.cli.cjs +16 -14
- package/dist/scripts/lakebase/scm-merge.cli.js +16 -14
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +16 -14
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +16 -14
- package/dist/scripts/lakebase/scm-recover-orphans.cli.cjs +43 -10
- package/dist/scripts/lakebase/scm-recover-orphans.cli.js +43 -10
- package/dist/scripts/lakebase/snapshot.cli.cjs +418 -0
- package/dist/scripts/lakebase/snapshot.cli.d.cts +1 -0
- package/dist/scripts/lakebase/snapshot.cli.d.ts +1 -0
- package/dist/scripts/lakebase/snapshot.cli.js +395 -0
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +2 -1
- package/scripts/git/commit-push.ts +60 -14
- package/scripts/lakebase/branch-create.ts +93 -14
- package/scripts/lakebase/branch.cli.ts +15 -0
- package/scripts/lakebase/create-preflight.ts +213 -1
- package/scripts/lakebase/create-project.ts +98 -30
- package/scripts/lakebase/get-connection.ts +29 -12
- package/scripts/lakebase/snapshot.cli.ts +117 -0
- package/scripts/lakebase/snapshot.ts +85 -0
- package/templates/project/common/scripts/run-tests.sh +43 -2
- package/templates/project/python/tests/conftest.py +22 -0
- package/templates/project/common/tests/e2e/__pycache__/conftest.cpython-314.pyc +0 -0
- package/templates/project/python/tests/__pycache__/conftest.cpython-314.pyc +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Lakebase SNAPSHOT primitives — the fast verify-branch substrate.
|
|
2
|
+
//
|
|
3
|
+
// A snapshot is an immutable point-in-time copy of a branch's data. Capture the
|
|
4
|
+
// committed schema ONCE (create), then branch copy-on-write from it per verify
|
|
5
|
+
// cycle (createBranch({ fromSnapshot })) instead of forking + `alembic upgrade
|
|
6
|
+
// head` every time. Because the snapshot is a fixed point, the per-cycle branch
|
|
7
|
+
// never goes stale. "Restore" is not an in-place op in Lakebase — you restore by
|
|
8
|
+
// branching from the snapshot (see createBranch's fromSnapshot path).
|
|
9
|
+
//
|
|
10
|
+
// API (verified): POST .../snapshots?snapshot_id=<id> with spec.source_branch
|
|
11
|
+
// (+ optional source_branch_time | source_branch_lsn for PITR, + one of
|
|
12
|
+
// no_expiry | ttl | expire_time). Mirrors the one-databricks-CLI-wrapper rule.
|
|
13
|
+
import { runDatabricks } from "./databricks-cli.js";
|
|
14
|
+
import { projectPath } from "./branch-utils.js";
|
|
15
|
+
import { KIT_TIMEOUTS } from "./kit-config.js";
|
|
16
|
+
|
|
17
|
+
/** Full branch resource path from a bare leaf or an already-full path. */
|
|
18
|
+
function branchResource(instance: string, branch: string): string {
|
|
19
|
+
return branch.includes("/") ? branch : `${projectPath(instance)}/branches/${branch}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Full snapshot resource path from a bare id or an already-full path. */
|
|
23
|
+
function snapshotResource(instance: string, snapshot: string): string {
|
|
24
|
+
return snapshot.includes("/") ? snapshot : `${projectPath(instance)}/snapshots/${snapshot}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SnapshotSpec {
|
|
28
|
+
source_branch: string;
|
|
29
|
+
source_branch_time?: string;
|
|
30
|
+
source_branch_lsn?: string;
|
|
31
|
+
no_expiry?: boolean;
|
|
32
|
+
ttl?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CreateSnapshotArgs {
|
|
36
|
+
instance: string;
|
|
37
|
+
/** Client-chosen snapshot id (becomes the resource leaf). */
|
|
38
|
+
snapshot: string;
|
|
39
|
+
/** Branch to snapshot — bare leaf (e.g. "staging") or full path. */
|
|
40
|
+
sourceBranch: string;
|
|
41
|
+
/** PITR: snapshot the branch AS OF this ISO timestamp (source_branch_time). */
|
|
42
|
+
sourceBranchTime?: string;
|
|
43
|
+
/** PITR: snapshot AS OF this LSN (source_branch_lsn). Exclusive with sourceBranchTime. */
|
|
44
|
+
sourceBranchLsn?: string;
|
|
45
|
+
/** TTL string ("604800s"). Mutually exclusive with noExpiry. */
|
|
46
|
+
ttl?: string;
|
|
47
|
+
/** Never auto-expire. Default true when ttl is unset (the API requires one). */
|
|
48
|
+
noExpiry?: boolean;
|
|
49
|
+
host?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Build the create-snapshot `spec` (pure — unit-testable without the CLI). */
|
|
53
|
+
export function buildSnapshotSpec(args: CreateSnapshotArgs): SnapshotSpec {
|
|
54
|
+
if (args.sourceBranchTime && args.sourceBranchLsn) {
|
|
55
|
+
throw new Error("sourceBranchTime and sourceBranchLsn are mutually exclusive — pass at most one point-in-time.");
|
|
56
|
+
}
|
|
57
|
+
if (args.ttl && args.noExpiry === true) {
|
|
58
|
+
throw new Error(`Cannot set both ttl ("${args.ttl}") and noExpiry: true — they are mutually exclusive.`);
|
|
59
|
+
}
|
|
60
|
+
const spec: SnapshotSpec = { source_branch: branchResource(args.instance, args.sourceBranch) };
|
|
61
|
+
if (args.sourceBranchTime) spec.source_branch_time = args.sourceBranchTime;
|
|
62
|
+
else if (args.sourceBranchLsn) spec.source_branch_lsn = args.sourceBranchLsn;
|
|
63
|
+
if (args.ttl) spec.ttl = args.ttl;
|
|
64
|
+
else if (args.noExpiry ?? true) spec.no_expiry = true;
|
|
65
|
+
return spec;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Create an immutable snapshot of a branch (waits for the operation to complete). */
|
|
69
|
+
export async function createSnapshot(args: CreateSnapshotArgs): Promise<string> {
|
|
70
|
+
const spec = buildSnapshotSpec(args);
|
|
71
|
+
return runDatabricks(
|
|
72
|
+
["postgres", "create-snapshot", projectPath(args.instance), args.snapshot, "--json", JSON.stringify({ spec })],
|
|
73
|
+
{ host: args.host, timeout: KIT_TIMEOUTS.cliCreateBranch },
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function deleteSnapshot(args: { instance: string; snapshot: string; host?: string }): Promise<string> {
|
|
78
|
+
return runDatabricks(["postgres", "delete-snapshot", snapshotResource(args.instance, args.snapshot)], {
|
|
79
|
+
host: args.host,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function listSnapshots(args: { instance: string; host?: string }): Promise<string> {
|
|
84
|
+
return runDatabricks(["postgres", "list-snapshots", projectPath(args.instance), "-o", "json"], { host: args.host });
|
|
85
|
+
}
|
|
@@ -155,6 +155,27 @@ else
|
|
|
155
155
|
exit 1
|
|
156
156
|
fi
|
|
157
157
|
|
|
158
|
+
# NOTE: a per-cycle client AC-scope (vitest -t / playwright --grep from SFTDD_TEST_IDS) was tried and
|
|
159
|
+
# REVERTED (live-proven harmful, 2026-10-24): scoping the per-cycle client pass to the current AC's
|
|
160
|
+
# @T specs stops the FULL client suite from running per cycle, which is what detects SUPERSESSION — a
|
|
161
|
+
# prior test asserting an element the current story removed (e.g. S2 split the combined
|
|
162
|
+
# `tracking-not-tracked` cell into batch/serial). Without that in-cycle RED the driver never refactors
|
|
163
|
+
# the superseded test, so it surfaces LATER as a deploy-verify HALT instead of a smooth in-cycle
|
|
164
|
+
# refactor. The client Vitest is cheap (jsdom) and the expensive Playwright E2E is already cost-gated
|
|
165
|
+
# by cycle layer below, so the full per-cycle client run stays — it is load-bearing, not waste.
|
|
166
|
+
#
|
|
167
|
+
# EXCEPTION — #2 parallel-lane scope (gated): in a SCOPED-LANE drive (CONSORT_SCOPE_AC set, i.e. a
|
|
168
|
+
# parallel per-AC lane / the contract story's serial-first AC), the cross-story supersession is owned by
|
|
169
|
+
# the DETERMINISTIC hoist sweep, NOT by this reactive full-client-run. So there the client pass is scoped
|
|
170
|
+
# to THIS AC's @T specs (it must not re-run the cross-story suite + re-trigger supersession wave-by-wave
|
|
171
|
+
# on the lanes' critical path). FLEET drives (no CONSORT_SCOPE_AC) are UNCHANGED — full client suite,
|
|
172
|
+
# supersession detection intact. The gate is what makes the reverted scope safe to re-introduce here.
|
|
173
|
+
SFTDD_CLIENT_GREP=""
|
|
174
|
+
if [ -n "${CONSORT_SCOPE_AC:-}" ] && [ -n "${SFTDD_TEST_IDS:-}" ]; then
|
|
175
|
+
SFTDD_CLIENT_GREP="@($(echo "$SFTDD_TEST_IDS" | tr -s ' ' '|' | sed 's/^|//;s/|$//'))"
|
|
176
|
+
echo "Scoped-lane client pass (CONSORT_SCOPE_AC=$CONSORT_SCOPE_AC): client tests scoped to $SFTDD_CLIENT_GREP"
|
|
177
|
+
fi
|
|
178
|
+
|
|
158
179
|
# React SPA client unit tests (Vitest + Testing Library). Only on a full run
|
|
159
180
|
# (no positional path arg, so a per-cycle backend-layer invocation does not drag
|
|
160
181
|
# in the client suite), and only when a client/ workspace is present. This is the
|
|
@@ -192,7 +213,11 @@ if [ "$#" -eq 0 ] && [ -f "$REPO_ROOT/client/package.json" ]; then
|
|
|
192
213
|
echo "Running client strict typecheck (tsc --noEmit, matching CI's build)..."
|
|
193
214
|
( cd "$REPO_ROOT/client" && npm run typecheck --if-present )
|
|
194
215
|
echo "Running client unit tests (Vitest)..."
|
|
195
|
-
|
|
216
|
+
if [ -n "$SFTDD_CLIENT_GREP" ]; then
|
|
217
|
+
( cd "$REPO_ROOT/client" && npm test -- -t "$SFTDD_CLIENT_GREP" )
|
|
218
|
+
else
|
|
219
|
+
( cd "$REPO_ROOT/client" && npm test )
|
|
220
|
+
fi
|
|
196
221
|
fi
|
|
197
222
|
|
|
198
223
|
# React SPA client E2E (Playwright). The client's end-to-end suite (client/tests/e2e/*.spec.ts) MUST run
|
|
@@ -273,7 +298,23 @@ if [ "$#" -eq 0 ] && [ -d "$REPO_ROOT/client/tests/e2e" ] \
|
|
|
273
298
|
# (old `!CI` and new `!!BASE_URL` alike) so the verify never reuses a stale
|
|
274
299
|
# server bound to an unmigrated branch (issue #197's false-500 misdiagnosis).
|
|
275
300
|
ensure_local_e2e_ports
|
|
276
|
-
|
|
301
|
+
e2e_rc=0
|
|
302
|
+
# #2 scoped-lane: scope the E2E to THIS AC's @T specs (gated on CONSORT_SCOPE_AC; fleet runs full).
|
|
303
|
+
if [ -n "$SFTDD_CLIENT_GREP" ]; then
|
|
304
|
+
( cd "$REPO_ROOT/client" && ./node_modules/.bin/playwright install chromium && CI=1 npm run test:e2e -- --grep "$SFTDD_CLIENT_GREP" ) || e2e_rc=$?
|
|
305
|
+
else
|
|
306
|
+
( cd "$REPO_ROOT/client" && ./node_modules/.bin/playwright install chromium && CI=1 npm run test:e2e ) || e2e_rc=$?
|
|
307
|
+
fi
|
|
308
|
+
# #7 within-cycle reap: Playwright's webServer SHOULD self-tear-down, but a killed/detached run
|
|
309
|
+
# leaves a uvicorn (+ vite) on the E2E ports that otherwise lingers until the NEXT cycle's
|
|
310
|
+
# scan-reaper — so a heavy-E2E cycle's repeated runs pile up. Reap them here, right after the run.
|
|
311
|
+
# NEVER when BASE_URL is set (the deploy gate owns that server, same guard as ensure_local_e2e_ports).
|
|
312
|
+
if [ -z "${BASE_URL:-}" ] && command -v lsof >/dev/null 2>&1; then
|
|
313
|
+
for p in "${E2E_BACKEND_PORT:-}" "${E2E_CLIENT_PORT:-}"; do
|
|
314
|
+
[ -n "$p" ] && lsof -ti tcp:"$p" 2>/dev/null | xargs kill 2>/dev/null || true
|
|
315
|
+
done
|
|
316
|
+
fi
|
|
317
|
+
[ "$e2e_rc" -eq 0 ] || exit "$e2e_rc"
|
|
277
318
|
fi
|
|
278
319
|
fi
|
|
279
320
|
|
|
@@ -270,6 +270,28 @@ def pytest_collection_modifyitems(config, items):
|
|
|
270
270
|
import os
|
|
271
271
|
import re
|
|
272
272
|
|
|
273
|
+
# DEFER superseded cross-story tests off the serial-first AC's verify (SFTDD_DEFER_FILES, set
|
|
274
|
+
# by the deferred serial-first AC of a parallel-lane fan-out). These prior-story tests fail only
|
|
275
|
+
# because THIS AC's drop migration superseded them; the coordinator's supersession sweep retargets
|
|
276
|
+
# them ONCE right after this AC, and the final feature-deploy verify (no defer) re-runs them
|
|
277
|
+
# retargeted. Deselected by FILE because the migration pass runs them regardless of the testid
|
|
278
|
+
# scope below, and they are typically cross-story / untagged (so always-KEPT by that scope).
|
|
279
|
+
defer_raw = (os.environ.get("SFTDD_DEFER_FILES") or "").strip()
|
|
280
|
+
if defer_raw:
|
|
281
|
+
defer_files = {f for f in re.split(r"[,\s]+", defer_raw) if f.endswith(".py")}
|
|
282
|
+
if defer_files:
|
|
283
|
+
kept, dropped = [], []
|
|
284
|
+
for item in items:
|
|
285
|
+
nodefile = item.nodeid.split("::", 1)[0].replace(os.sep, "/")
|
|
286
|
+
fspath = str(getattr(item, "fspath", "") or "").replace(os.sep, "/")
|
|
287
|
+
if any(nodefile == df or nodefile.endswith("/" + df) or fspath.endswith("/" + df) for df in defer_files):
|
|
288
|
+
dropped.append(item)
|
|
289
|
+
else:
|
|
290
|
+
kept.append(item)
|
|
291
|
+
if dropped:
|
|
292
|
+
config.hook.pytest_deselected(items=dropped)
|
|
293
|
+
items[:] = kept
|
|
294
|
+
|
|
273
295
|
raw = (os.environ.get("SFTDD_TEST_IDS") or "").strip()
|
|
274
296
|
if not raw:
|
|
275
297
|
return
|
|
Binary file
|
|
Binary file
|