@databricks-solutions/lakebase-scm-utils 0.3.1 → 0.3.3
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/index.cjs +26 -9
- package/dist/scripts/index.js +27 -10
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +26 -9
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +27 -10
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +26 -9
- package/dist/scripts/lakebase/collapse-heads.cli.js +27 -10
- package/dist/scripts/lakebase/detect-language.cli.cjs +26 -9
- package/dist/scripts/lakebase/detect-language.cli.js +27 -10
- package/dist/scripts/lakebase/doctor.cli.cjs +26 -9
- package/dist/scripts/lakebase/doctor.cli.js +27 -10
- 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 +26 -9
- package/dist/scripts/lakebase/index.js +27 -10
- package/dist/scripts/lakebase/infra-runner.cli.cjs +26 -9
- package/dist/scripts/lakebase/infra-runner.cli.js +27 -10
- package/dist/scripts/lakebase/new-migration.cli.cjs +26 -9
- package/dist/scripts/lakebase/new-migration.cli.js +27 -10
- 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 +26 -9
- package/dist/scripts/lakebase/schema-migrate.cli.js +27 -10
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +26 -9
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +27 -10
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +26 -9
- package/dist/scripts/lakebase/scm-doctor.cli.js +27 -10
- package/dist/scripts/lakebase/scm-merge.cli.cjs +26 -9
- package/dist/scripts/lakebase/scm-merge.cli.js +27 -10
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +26 -9
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +28 -11
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/scripts/lakebase/schema-migrate-runners/alembic.ts +58 -14
- package/templates/project/client/playwright.config.ts +45 -10
- package/templates/project/python/app/database.py +6 -6
- package/templates/project/python/app/repositories/__init__.py +8 -0
- package/templates/project/python/app/repositories/session.py +28 -0
|
@@ -88240,8 +88240,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
88240
88240
|
var cached;
|
|
88241
88241
|
function substrateSelfVersion() {
|
|
88242
88242
|
if (cached !== void 0) return cached;
|
|
88243
|
-
if ("0.3.
|
|
88244
|
-
cached = "0.3.
|
|
88243
|
+
if ("0.3.3".length > 0) {
|
|
88244
|
+
cached = "0.3.3";
|
|
88245
88245
|
return cached;
|
|
88246
88246
|
}
|
|
88247
88247
|
cached = "unknown";
|
|
@@ -89118,26 +89118,43 @@ init_cjs_shims();
|
|
|
89118
89118
|
var import_node_child_process6 = require("child_process");
|
|
89119
89119
|
var fs9 = __toESM(require("fs"), 1);
|
|
89120
89120
|
var path7 = __toESM(require("path"), 1);
|
|
89121
|
-
|
|
89122
|
-
|
|
89121
|
+
var uvOnPath;
|
|
89122
|
+
function hasUv() {
|
|
89123
|
+
if (uvOnPath !== void 0) return uvOnPath;
|
|
89124
|
+
try {
|
|
89125
|
+
(0, import_node_child_process6.execFileSync)("uv", ["--version"], { stdio: "ignore" });
|
|
89126
|
+
uvOnPath = true;
|
|
89127
|
+
} catch {
|
|
89128
|
+
uvOnPath = false;
|
|
89129
|
+
}
|
|
89130
|
+
return uvOnPath;
|
|
89131
|
+
}
|
|
89132
|
+
function resolveAlembicInvocation(projectDir, uvAvailable = hasUv()) {
|
|
89133
|
+
const venvCandidates = [
|
|
89123
89134
|
path7.join(projectDir, ".venv", "bin", "alembic"),
|
|
89124
89135
|
path7.join(projectDir, "venv", "bin", "alembic")
|
|
89125
89136
|
];
|
|
89126
|
-
for (const candidate of
|
|
89137
|
+
for (const candidate of venvCandidates) {
|
|
89127
89138
|
try {
|
|
89128
|
-
if (fs9.existsSync(candidate)) return candidate;
|
|
89139
|
+
if (fs9.existsSync(candidate)) return { bin: candidate, prefixArgs: [] };
|
|
89129
89140
|
} catch {
|
|
89130
89141
|
}
|
|
89131
89142
|
}
|
|
89132
|
-
|
|
89143
|
+
try {
|
|
89144
|
+
if (uvAvailable && fs9.existsSync(path7.join(projectDir, "pyproject.toml"))) {
|
|
89145
|
+
return { bin: "uv", prefixArgs: ["run", "alembic"] };
|
|
89146
|
+
}
|
|
89147
|
+
} catch {
|
|
89148
|
+
}
|
|
89149
|
+
return { bin: "alembic", prefixArgs: [] };
|
|
89133
89150
|
}
|
|
89134
89151
|
function spawnAlembic(projectDir, args, dsn) {
|
|
89135
89152
|
return new Promise((resolve3, reject) => {
|
|
89136
|
-
const bin =
|
|
89153
|
+
const { bin, prefixArgs } = resolveAlembicInvocation(projectDir);
|
|
89137
89154
|
const env = { ...process.env };
|
|
89138
89155
|
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path7.delimiter);
|
|
89139
89156
|
if (dsn) env.DATABASE_URL = dsn;
|
|
89140
|
-
const child = (0, import_node_child_process6.spawn)(bin, args, {
|
|
89157
|
+
const child = (0, import_node_child_process6.spawn)(bin, [...prefixArgs, ...args], {
|
|
89141
89158
|
cwd: projectDir,
|
|
89142
89159
|
env,
|
|
89143
89160
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -9393,8 +9393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
9393
9393
|
var cached;
|
|
9394
9394
|
function substrateSelfVersion() {
|
|
9395
9395
|
if (cached !== void 0) return cached;
|
|
9396
|
-
if ("0.3.
|
|
9397
|
-
cached = "0.3.
|
|
9396
|
+
if ("0.3.3".length > 0) {
|
|
9397
|
+
cached = "0.3.3";
|
|
9398
9398
|
return cached;
|
|
9399
9399
|
}
|
|
9400
9400
|
cached = "unknown";
|
|
@@ -10268,29 +10268,46 @@ import * as path9 from "path";
|
|
|
10268
10268
|
|
|
10269
10269
|
// scripts/lakebase/schema-migrate-runners/alembic.ts
|
|
10270
10270
|
init_esm_shims();
|
|
10271
|
-
import { spawn } from "child_process";
|
|
10271
|
+
import { spawn, execFileSync as execFileSync6 } from "child_process";
|
|
10272
10272
|
import * as fs8 from "fs";
|
|
10273
10273
|
import * as path8 from "path";
|
|
10274
|
-
|
|
10275
|
-
|
|
10274
|
+
var uvOnPath;
|
|
10275
|
+
function hasUv() {
|
|
10276
|
+
if (uvOnPath !== void 0) return uvOnPath;
|
|
10277
|
+
try {
|
|
10278
|
+
execFileSync6("uv", ["--version"], { stdio: "ignore" });
|
|
10279
|
+
uvOnPath = true;
|
|
10280
|
+
} catch {
|
|
10281
|
+
uvOnPath = false;
|
|
10282
|
+
}
|
|
10283
|
+
return uvOnPath;
|
|
10284
|
+
}
|
|
10285
|
+
function resolveAlembicInvocation(projectDir, uvAvailable = hasUv()) {
|
|
10286
|
+
const venvCandidates = [
|
|
10276
10287
|
path8.join(projectDir, ".venv", "bin", "alembic"),
|
|
10277
10288
|
path8.join(projectDir, "venv", "bin", "alembic")
|
|
10278
10289
|
];
|
|
10279
|
-
for (const candidate of
|
|
10290
|
+
for (const candidate of venvCandidates) {
|
|
10280
10291
|
try {
|
|
10281
|
-
if (fs8.existsSync(candidate)) return candidate;
|
|
10292
|
+
if (fs8.existsSync(candidate)) return { bin: candidate, prefixArgs: [] };
|
|
10282
10293
|
} catch {
|
|
10283
10294
|
}
|
|
10284
10295
|
}
|
|
10285
|
-
|
|
10296
|
+
try {
|
|
10297
|
+
if (uvAvailable && fs8.existsSync(path8.join(projectDir, "pyproject.toml"))) {
|
|
10298
|
+
return { bin: "uv", prefixArgs: ["run", "alembic"] };
|
|
10299
|
+
}
|
|
10300
|
+
} catch {
|
|
10301
|
+
}
|
|
10302
|
+
return { bin: "alembic", prefixArgs: [] };
|
|
10286
10303
|
}
|
|
10287
10304
|
function spawnAlembic(projectDir, args, dsn) {
|
|
10288
10305
|
return new Promise((resolve3, reject) => {
|
|
10289
|
-
const bin =
|
|
10306
|
+
const { bin, prefixArgs } = resolveAlembicInvocation(projectDir);
|
|
10290
10307
|
const env = { ...process.env };
|
|
10291
10308
|
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path8.delimiter);
|
|
10292
10309
|
if (dsn) env.DATABASE_URL = dsn;
|
|
10293
|
-
const child = spawn(bin, args, {
|
|
10310
|
+
const child = spawn(bin, [...prefixArgs, ...args], {
|
|
10294
10311
|
cwd: projectDir,
|
|
10295
10312
|
env,
|
|
10296
10313
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -88825,8 +88825,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
88825
88825
|
var cached;
|
|
88826
88826
|
function substrateSelfVersion() {
|
|
88827
88827
|
if (cached !== void 0) return cached;
|
|
88828
|
-
if ("0.3.
|
|
88829
|
-
cached = "0.3.
|
|
88828
|
+
if ("0.3.3".length > 0) {
|
|
88829
|
+
cached = "0.3.3";
|
|
88830
88830
|
return cached;
|
|
88831
88831
|
}
|
|
88832
88832
|
cached = "unknown";
|
|
@@ -89010,26 +89010,43 @@ init_cjs_shims();
|
|
|
89010
89010
|
var import_node_child_process5 = require("child_process");
|
|
89011
89011
|
var fs8 = __toESM(require("fs"), 1);
|
|
89012
89012
|
var path7 = __toESM(require("path"), 1);
|
|
89013
|
-
|
|
89014
|
-
|
|
89013
|
+
var uvOnPath;
|
|
89014
|
+
function hasUv() {
|
|
89015
|
+
if (uvOnPath !== void 0) return uvOnPath;
|
|
89016
|
+
try {
|
|
89017
|
+
(0, import_node_child_process5.execFileSync)("uv", ["--version"], { stdio: "ignore" });
|
|
89018
|
+
uvOnPath = true;
|
|
89019
|
+
} catch {
|
|
89020
|
+
uvOnPath = false;
|
|
89021
|
+
}
|
|
89022
|
+
return uvOnPath;
|
|
89023
|
+
}
|
|
89024
|
+
function resolveAlembicInvocation(projectDir, uvAvailable = hasUv()) {
|
|
89025
|
+
const venvCandidates = [
|
|
89015
89026
|
path7.join(projectDir, ".venv", "bin", "alembic"),
|
|
89016
89027
|
path7.join(projectDir, "venv", "bin", "alembic")
|
|
89017
89028
|
];
|
|
89018
|
-
for (const candidate of
|
|
89029
|
+
for (const candidate of venvCandidates) {
|
|
89019
89030
|
try {
|
|
89020
|
-
if (fs8.existsSync(candidate)) return candidate;
|
|
89031
|
+
if (fs8.existsSync(candidate)) return { bin: candidate, prefixArgs: [] };
|
|
89021
89032
|
} catch {
|
|
89022
89033
|
}
|
|
89023
89034
|
}
|
|
89024
|
-
|
|
89035
|
+
try {
|
|
89036
|
+
if (uvAvailable && fs8.existsSync(path7.join(projectDir, "pyproject.toml"))) {
|
|
89037
|
+
return { bin: "uv", prefixArgs: ["run", "alembic"] };
|
|
89038
|
+
}
|
|
89039
|
+
} catch {
|
|
89040
|
+
}
|
|
89041
|
+
return { bin: "alembic", prefixArgs: [] };
|
|
89025
89042
|
}
|
|
89026
89043
|
function spawnAlembic(projectDir, args, dsn) {
|
|
89027
89044
|
return new Promise((resolve2, reject) => {
|
|
89028
|
-
const bin =
|
|
89045
|
+
const { bin, prefixArgs } = resolveAlembicInvocation(projectDir);
|
|
89029
89046
|
const env = { ...process.env };
|
|
89030
89047
|
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path7.delimiter);
|
|
89031
89048
|
if (dsn) env.DATABASE_URL = dsn;
|
|
89032
|
-
const child = (0, import_node_child_process5.spawn)(bin, args, {
|
|
89049
|
+
const child = (0, import_node_child_process5.spawn)(bin, [...prefixArgs, ...args], {
|
|
89033
89050
|
cwd: projectDir,
|
|
89034
89051
|
env,
|
|
89035
89052
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -9978,8 +9978,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
9978
9978
|
var cached;
|
|
9979
9979
|
function substrateSelfVersion() {
|
|
9980
9980
|
if (cached !== void 0) return cached;
|
|
9981
|
-
if ("0.3.
|
|
9982
|
-
cached = "0.3.
|
|
9981
|
+
if ("0.3.3".length > 0) {
|
|
9982
|
+
cached = "0.3.3";
|
|
9983
9983
|
return cached;
|
|
9984
9984
|
}
|
|
9985
9985
|
cached = "unknown";
|
|
@@ -10160,29 +10160,46 @@ import * as path9 from "path";
|
|
|
10160
10160
|
|
|
10161
10161
|
// scripts/lakebase/schema-migrate-runners/alembic.ts
|
|
10162
10162
|
init_esm_shims();
|
|
10163
|
-
import { spawn } from "child_process";
|
|
10163
|
+
import { spawn, execFileSync as execFileSync5 } from "child_process";
|
|
10164
10164
|
import * as fs7 from "fs";
|
|
10165
10165
|
import * as path8 from "path";
|
|
10166
|
-
|
|
10167
|
-
|
|
10166
|
+
var uvOnPath;
|
|
10167
|
+
function hasUv() {
|
|
10168
|
+
if (uvOnPath !== void 0) return uvOnPath;
|
|
10169
|
+
try {
|
|
10170
|
+
execFileSync5("uv", ["--version"], { stdio: "ignore" });
|
|
10171
|
+
uvOnPath = true;
|
|
10172
|
+
} catch {
|
|
10173
|
+
uvOnPath = false;
|
|
10174
|
+
}
|
|
10175
|
+
return uvOnPath;
|
|
10176
|
+
}
|
|
10177
|
+
function resolveAlembicInvocation(projectDir, uvAvailable = hasUv()) {
|
|
10178
|
+
const venvCandidates = [
|
|
10168
10179
|
path8.join(projectDir, ".venv", "bin", "alembic"),
|
|
10169
10180
|
path8.join(projectDir, "venv", "bin", "alembic")
|
|
10170
10181
|
];
|
|
10171
|
-
for (const candidate of
|
|
10182
|
+
for (const candidate of venvCandidates) {
|
|
10172
10183
|
try {
|
|
10173
|
-
if (fs7.existsSync(candidate)) return candidate;
|
|
10184
|
+
if (fs7.existsSync(candidate)) return { bin: candidate, prefixArgs: [] };
|
|
10174
10185
|
} catch {
|
|
10175
10186
|
}
|
|
10176
10187
|
}
|
|
10177
|
-
|
|
10188
|
+
try {
|
|
10189
|
+
if (uvAvailable && fs7.existsSync(path8.join(projectDir, "pyproject.toml"))) {
|
|
10190
|
+
return { bin: "uv", prefixArgs: ["run", "alembic"] };
|
|
10191
|
+
}
|
|
10192
|
+
} catch {
|
|
10193
|
+
}
|
|
10194
|
+
return { bin: "alembic", prefixArgs: [] };
|
|
10178
10195
|
}
|
|
10179
10196
|
function spawnAlembic(projectDir, args, dsn) {
|
|
10180
10197
|
return new Promise((resolve2, reject) => {
|
|
10181
|
-
const bin =
|
|
10198
|
+
const { bin, prefixArgs } = resolveAlembicInvocation(projectDir);
|
|
10182
10199
|
const env = { ...process.env };
|
|
10183
10200
|
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path8.delimiter);
|
|
10184
10201
|
if (dsn) env.DATABASE_URL = dsn;
|
|
10185
|
-
const child = spawn(bin, args, {
|
|
10202
|
+
const child = spawn(bin, [...prefixArgs, ...args], {
|
|
10186
10203
|
cwd: projectDir,
|
|
10187
10204
|
env,
|
|
10188
10205
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -79288,8 +79288,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79288
79288
|
var cached;
|
|
79289
79289
|
function substrateSelfVersion() {
|
|
79290
79290
|
if (cached !== void 0) return cached;
|
|
79291
|
-
if ("0.3.
|
|
79292
|
-
cached = "0.3.
|
|
79291
|
+
if ("0.3.3".length > 0) {
|
|
79292
|
+
cached = "0.3.3";
|
|
79293
79293
|
return cached;
|
|
79294
79294
|
}
|
|
79295
79295
|
cached = "unknown";
|
|
@@ -79547,26 +79547,43 @@ init_cjs_shims();
|
|
|
79547
79547
|
var import_node_child_process3 = require("child_process");
|
|
79548
79548
|
var fs7 = __toESM(require("fs"), 1);
|
|
79549
79549
|
var path5 = __toESM(require("path"), 1);
|
|
79550
|
-
|
|
79551
|
-
|
|
79550
|
+
var uvOnPath;
|
|
79551
|
+
function hasUv() {
|
|
79552
|
+
if (uvOnPath !== void 0) return uvOnPath;
|
|
79553
|
+
try {
|
|
79554
|
+
(0, import_node_child_process3.execFileSync)("uv", ["--version"], { stdio: "ignore" });
|
|
79555
|
+
uvOnPath = true;
|
|
79556
|
+
} catch {
|
|
79557
|
+
uvOnPath = false;
|
|
79558
|
+
}
|
|
79559
|
+
return uvOnPath;
|
|
79560
|
+
}
|
|
79561
|
+
function resolveAlembicInvocation(projectDir, uvAvailable = hasUv()) {
|
|
79562
|
+
const venvCandidates = [
|
|
79552
79563
|
path5.join(projectDir, ".venv", "bin", "alembic"),
|
|
79553
79564
|
path5.join(projectDir, "venv", "bin", "alembic")
|
|
79554
79565
|
];
|
|
79555
|
-
for (const candidate of
|
|
79566
|
+
for (const candidate of venvCandidates) {
|
|
79556
79567
|
try {
|
|
79557
|
-
if (fs7.existsSync(candidate)) return candidate;
|
|
79568
|
+
if (fs7.existsSync(candidate)) return { bin: candidate, prefixArgs: [] };
|
|
79558
79569
|
} catch {
|
|
79559
79570
|
}
|
|
79560
79571
|
}
|
|
79561
|
-
|
|
79572
|
+
try {
|
|
79573
|
+
if (uvAvailable && fs7.existsSync(path5.join(projectDir, "pyproject.toml"))) {
|
|
79574
|
+
return { bin: "uv", prefixArgs: ["run", "alembic"] };
|
|
79575
|
+
}
|
|
79576
|
+
} catch {
|
|
79577
|
+
}
|
|
79578
|
+
return { bin: "alembic", prefixArgs: [] };
|
|
79562
79579
|
}
|
|
79563
79580
|
function spawnAlembic(projectDir, args, dsn) {
|
|
79564
79581
|
return new Promise((resolve2, reject) => {
|
|
79565
|
-
const bin =
|
|
79582
|
+
const { bin, prefixArgs } = resolveAlembicInvocation(projectDir);
|
|
79566
79583
|
const env = { ...process.env };
|
|
79567
79584
|
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path5.delimiter);
|
|
79568
79585
|
if (dsn) env.DATABASE_URL = dsn;
|
|
79569
|
-
const child = (0, import_node_child_process3.spawn)(bin, args, {
|
|
79586
|
+
const child = (0, import_node_child_process3.spawn)(bin, [...prefixArgs, ...args], {
|
|
79570
79587
|
cwd: projectDir,
|
|
79571
79588
|
env,
|
|
79572
79589
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
389
389
|
var cached;
|
|
390
390
|
function substrateSelfVersion() {
|
|
391
391
|
if (cached !== void 0) return cached;
|
|
392
|
-
if ("0.3.
|
|
393
|
-
cached = "0.3.
|
|
392
|
+
if ("0.3.3".length > 0) {
|
|
393
|
+
cached = "0.3.3";
|
|
394
394
|
return cached;
|
|
395
395
|
}
|
|
396
396
|
cached = "unknown";
|
|
@@ -640,29 +640,46 @@ import * as fs7 from "fs";
|
|
|
640
640
|
import * as path6 from "path";
|
|
641
641
|
|
|
642
642
|
// scripts/lakebase/schema-migrate-runners/alembic.ts
|
|
643
|
-
import { spawn } from "child_process";
|
|
643
|
+
import { spawn, execFileSync as execFileSync3 } from "child_process";
|
|
644
644
|
import * as fs6 from "fs";
|
|
645
645
|
import * as path5 from "path";
|
|
646
|
-
|
|
647
|
-
|
|
646
|
+
var uvOnPath;
|
|
647
|
+
function hasUv() {
|
|
648
|
+
if (uvOnPath !== void 0) return uvOnPath;
|
|
649
|
+
try {
|
|
650
|
+
execFileSync3("uv", ["--version"], { stdio: "ignore" });
|
|
651
|
+
uvOnPath = true;
|
|
652
|
+
} catch {
|
|
653
|
+
uvOnPath = false;
|
|
654
|
+
}
|
|
655
|
+
return uvOnPath;
|
|
656
|
+
}
|
|
657
|
+
function resolveAlembicInvocation(projectDir, uvAvailable = hasUv()) {
|
|
658
|
+
const venvCandidates = [
|
|
648
659
|
path5.join(projectDir, ".venv", "bin", "alembic"),
|
|
649
660
|
path5.join(projectDir, "venv", "bin", "alembic")
|
|
650
661
|
];
|
|
651
|
-
for (const candidate of
|
|
662
|
+
for (const candidate of venvCandidates) {
|
|
652
663
|
try {
|
|
653
|
-
if (fs6.existsSync(candidate)) return candidate;
|
|
664
|
+
if (fs6.existsSync(candidate)) return { bin: candidate, prefixArgs: [] };
|
|
654
665
|
} catch {
|
|
655
666
|
}
|
|
656
667
|
}
|
|
657
|
-
|
|
668
|
+
try {
|
|
669
|
+
if (uvAvailable && fs6.existsSync(path5.join(projectDir, "pyproject.toml"))) {
|
|
670
|
+
return { bin: "uv", prefixArgs: ["run", "alembic"] };
|
|
671
|
+
}
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
return { bin: "alembic", prefixArgs: [] };
|
|
658
675
|
}
|
|
659
676
|
function spawnAlembic(projectDir, args, dsn) {
|
|
660
677
|
return new Promise((resolve2, reject) => {
|
|
661
|
-
const bin =
|
|
678
|
+
const { bin, prefixArgs } = resolveAlembicInvocation(projectDir);
|
|
662
679
|
const env = { ...process.env };
|
|
663
680
|
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path5.delimiter);
|
|
664
681
|
if (dsn) env.DATABASE_URL = dsn;
|
|
665
|
-
const child = spawn(bin, args, {
|
|
682
|
+
const child = spawn(bin, [...prefixArgs, ...args], {
|
|
666
683
|
cwd: projectDir,
|
|
667
684
|
env,
|
|
668
685
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2137,7 +2154,7 @@ async function reconcileTierBranch(args, deps = {}) {
|
|
|
2137
2154
|
// scripts/lakebase/scm-workflow-state.ts
|
|
2138
2155
|
import * as fs13 from "fs";
|
|
2139
2156
|
import * as path14 from "path";
|
|
2140
|
-
import { execFileSync as
|
|
2157
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
2141
2158
|
var SCM_STATES = [
|
|
2142
2159
|
"scaffold-complete",
|
|
2143
2160
|
"feature-claimed",
|
|
@@ -1667,8 +1667,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
1667
1667
|
var cached;
|
|
1668
1668
|
function substrateSelfVersion() {
|
|
1669
1669
|
if (cached !== void 0) return cached;
|
|
1670
|
-
if ("0.3.
|
|
1671
|
-
cached = "0.3.
|
|
1670
|
+
if ("0.3.3".length > 0) {
|
|
1671
|
+
cached = "0.3.3";
|
|
1672
1672
|
return cached;
|
|
1673
1673
|
}
|
|
1674
1674
|
cached = "unknown";
|
|
@@ -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.3.
|
|
1634
|
-
cached = "0.3.
|
|
1633
|
+
if ("0.3.3".length > 0) {
|
|
1634
|
+
cached = "0.3.3";
|
|
1635
1635
|
return cached;
|
|
1636
1636
|
}
|
|
1637
1637
|
cached = "unknown";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@databricks-solutions/lakebase-scm-utils",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// stdout/stderr is not load-bearing here, which keeps the runner robust
|
|
15
15
|
// to logger config drift in the consumer's `alembic.ini`.
|
|
16
16
|
|
|
17
|
-
import { spawn } from "node:child_process";
|
|
17
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
18
18
|
import * as fs from "node:fs";
|
|
19
19
|
import * as path from "node:path";
|
|
20
20
|
import {
|
|
@@ -32,26 +32,70 @@ interface RunnerCtx {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* runner's PATH. Spawning bare `alembic` fails with ENOENT in CI even
|
|
38
|
-
* after `uv sync` succeeded. Prefer the project-local venv when it
|
|
39
|
-
* exists; fall back to bare `alembic` for projects with a pre-activated
|
|
40
|
-
* shell venv or a system-wide install.
|
|
35
|
+
* Whether `uv` is on PATH. Memoized: resolution runs per alembic subcommand and
|
|
36
|
+
* the answer cannot change within a process.
|
|
41
37
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
let uvOnPath: boolean | undefined;
|
|
39
|
+
function hasUv(): boolean {
|
|
40
|
+
if (uvOnPath !== undefined) return uvOnPath;
|
|
41
|
+
try {
|
|
42
|
+
execFileSync("uv", ["--version"], { stdio: "ignore" });
|
|
43
|
+
uvOnPath = true;
|
|
44
|
+
} catch {
|
|
45
|
+
uvOnPath = false;
|
|
46
|
+
}
|
|
47
|
+
return uvOnPath;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve HOW to invoke alembic in a project: an executable plus the args that
|
|
52
|
+
* must PREFIX the alembic subcommand.
|
|
53
|
+
*
|
|
54
|
+
* uv-managed Python projects install alembic into a per-project `.venv/bin/alembic`,
|
|
55
|
+
* which is NOT on the runner's PATH, so that venv-local bin is preferred when it
|
|
56
|
+
* exists (it is also what CI has after `uv sync`). But a FRESH clone / rehydrated
|
|
57
|
+
* project has no `.venv` yet (nothing has run `uv sync`), and spawning bare
|
|
58
|
+
* `alembic` then fails ENOENT — the failure the `lk lakebase-new-migration` path
|
|
59
|
+
* hit on a never-synced project, before any test run created the venv. For such a
|
|
60
|
+
* uv project (pyproject.toml present and `uv` on PATH) invoke via `uv run alembic`,
|
|
61
|
+
* which auto-creates the venv and syncs the declared deps (alembic is a top-level
|
|
62
|
+
* dep) BEFORE running — exactly what scripts/run-tests.sh relies on. CI is
|
|
63
|
+
* unaffected: it hits the venv-local bin first. Last resort is bare `alembic`
|
|
64
|
+
* (a pre-activated shell venv or a system-wide install).
|
|
65
|
+
*/
|
|
66
|
+
export function resolveAlembicInvocation(
|
|
67
|
+
projectDir: string,
|
|
68
|
+
uvAvailable: boolean = hasUv()
|
|
69
|
+
): { bin: string; prefixArgs: string[] } {
|
|
70
|
+
const venvCandidates = [
|
|
44
71
|
path.join(projectDir, ".venv", "bin", "alembic"),
|
|
45
72
|
path.join(projectDir, "venv", "bin", "alembic"),
|
|
46
73
|
];
|
|
47
|
-
for (const candidate of
|
|
74
|
+
for (const candidate of venvCandidates) {
|
|
48
75
|
try {
|
|
49
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
76
|
+
if (fs.existsSync(candidate)) return { bin: candidate, prefixArgs: [] };
|
|
50
77
|
} catch {
|
|
51
78
|
// best-effort; keep checking
|
|
52
79
|
}
|
|
53
80
|
}
|
|
54
|
-
|
|
81
|
+
try {
|
|
82
|
+
if (uvAvailable && fs.existsSync(path.join(projectDir, "pyproject.toml"))) {
|
|
83
|
+
return { bin: "uv", prefixArgs: ["run", "alembic"] };
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// best-effort; fall through to bare alembic
|
|
87
|
+
}
|
|
88
|
+
return { bin: "alembic", prefixArgs: [] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Back-compat: the executable that would run alembic (the venv-local path or bare
|
|
93
|
+
* `alembic`). Prefer resolveAlembicInvocation, which also models the `uv run`
|
|
94
|
+
* prefix for a never-synced uv project.
|
|
95
|
+
*/
|
|
96
|
+
export function resolveAlembicBin(projectDir: string): string {
|
|
97
|
+
const { bin, prefixArgs } = resolveAlembicInvocation(projectDir);
|
|
98
|
+
return prefixArgs.length ? `${bin} ${prefixArgs.join(" ")}` : bin;
|
|
55
99
|
}
|
|
56
100
|
|
|
57
101
|
/**
|
|
@@ -65,7 +109,7 @@ function spawnAlembic(
|
|
|
65
109
|
dsn?: string
|
|
66
110
|
): Promise<{ stdout: string; stderr: string }> {
|
|
67
111
|
return new Promise((resolve, reject) => {
|
|
68
|
-
const bin =
|
|
112
|
+
const { bin, prefixArgs } = resolveAlembicInvocation(projectDir);
|
|
69
113
|
// Put the project root on PYTHONPATH so `app` is importable for EVERY alembic
|
|
70
114
|
// subcommand. `upgrade` runs env.py (which prepends sys.path), but `history`
|
|
71
115
|
// and other read-only commands do not, yet they still import every migration
|
|
@@ -74,7 +118,7 @@ function spawnAlembic(
|
|
|
74
118
|
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
75
119
|
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path.delimiter);
|
|
76
120
|
if (dsn) env.DATABASE_URL = dsn;
|
|
77
|
-
const child = spawn(bin, args, {
|
|
121
|
+
const child = spawn(bin, [...prefixArgs, ...args], {
|
|
78
122
|
cwd: projectDir,
|
|
79
123
|
env,
|
|
80
124
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1,21 +1,57 @@
|
|
|
1
1
|
import { defineConfig, devices } from "@playwright/test";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
|
|
6
|
+
// The backend lives at the project root, one level up from this client/ config
|
|
7
|
+
// (Playwright runs from client/, hence cwd:".." on the webServers below).
|
|
8
|
+
const ROOT = join(process.cwd(), "..");
|
|
9
|
+
|
|
5
10
|
// End-to-end tests drive the rendered SPA through a real browser against the
|
|
6
11
|
// real backend, backed by the paired Lakebase branch. Playwright boots BOTH
|
|
7
12
|
// servers: the backend FIRST (so Vite's /api proxy has a target), then Vite.
|
|
8
13
|
// Adapt the backend `command`/`url` to your stack if it is not Python/FastAPI.
|
|
9
14
|
//
|
|
10
|
-
// Resiliency: the ports are env-driven (E2E_BACKEND_PORT / E2E_CLIENT_PORT)
|
|
11
|
-
//
|
|
12
|
-
// left on 8000/5173 by a prior run
|
|
13
|
-
// "port already used".
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
// Resiliency: the ports are env-driven (E2E_BACKEND_PORT / E2E_CLIENT_PORT).
|
|
16
|
+
// run-tests.sh and CI pre-allocate FREE ports (scripts/port-utils.sh) and hand them
|
|
17
|
+
// in via those vars, so a stale server left on 8000/5173 by a prior run never makes
|
|
18
|
+
// webServer hard-fail with "port already used". When NOTHING hands them in — a direct
|
|
19
|
+
// `npx playwright test` (e.g. the build driver running a targeted spec, not through
|
|
20
|
+
// run-tests.sh) — resolve a free port HERE the SAME way, by sourcing that one shared
|
|
21
|
+
// free_port helper, so a direct run is just as collision-proof and never defaults onto
|
|
22
|
+
// a busy :8000 / :5173. One free-port owner (port-utils.sh), two entry points. The Vite
|
|
23
|
+
// proxy is pointed at the resolved backend port via VITE_PROXY_TARGET so /api + /health
|
|
24
|
+
// still reach the backend wherever it landed.
|
|
25
|
+
function resolvePort(envVar: string, fallback: number): string {
|
|
26
|
+
const fromEnv = process.env[envVar];
|
|
27
|
+
if (fromEnv) return fromEnv;
|
|
28
|
+
let resolved = String(fallback);
|
|
29
|
+
const portUtils = join(ROOT, "scripts", "port-utils.sh");
|
|
30
|
+
if (existsSync(portUtils)) {
|
|
31
|
+
try {
|
|
32
|
+
const out = execFileSync(
|
|
33
|
+
"bash",
|
|
34
|
+
["-c", `source ${JSON.stringify(portUtils)} && free_port ${fallback}`],
|
|
35
|
+
{ encoding: "utf8" }
|
|
36
|
+
).trim();
|
|
37
|
+
if (/^\d+$/.test(out)) resolved = out;
|
|
38
|
+
} catch {
|
|
39
|
+
// fall through to the conventional default
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// PERSIST the chosen port into the environment so Playwright's WORKER processes reuse it.
|
|
43
|
+
// Playwright evaluates this config in the main process AND re-evaluates it in each test
|
|
44
|
+
// worker. Without this, a worker re-runs resolvePort with E2E_CLIENT_PORT still unset, finds
|
|
45
|
+
// the port the main process picked (e.g. 5173) now TAKEN by the Vite webServer, allocates a
|
|
46
|
+
// DIFFERENT free port (5174), and points `baseURL` at 5174 where nothing listens — so the
|
|
47
|
+
// browser specs fail "not reachable" even though the app is healthy on the real port. Workers
|
|
48
|
+
// are spawned AFTER the main-process config load and inherit its process.env, so recording the
|
|
49
|
+
// resolution here makes every worker read it back via `fromEnv` above: resolve once, share it.
|
|
50
|
+
process.env[envVar] = resolved;
|
|
51
|
+
return resolved;
|
|
52
|
+
}
|
|
53
|
+
const BACKEND_PORT = resolvePort("E2E_BACKEND_PORT", 8000);
|
|
54
|
+
const CLIENT_PORT = resolvePort("E2E_CLIENT_PORT", 5173);
|
|
19
55
|
// Use 127.0.0.1, NOT localhost, and bind both servers to it (below). `localhost`
|
|
20
56
|
// is ambiguous: on macOS it resolves to IPv6 ::1 first, but uvicorn binds IPv4
|
|
21
57
|
// 127.0.0.1 only (its default) and Vite's dev server is likewise IPv4 — so a
|
|
@@ -31,7 +67,6 @@ const CLIENT_URL = `http://127.0.0.1:${CLIENT_PORT}`;
|
|
|
31
67
|
// one level up from this client/ config (Playwright runs from client/, hence cwd:".." below). Pick
|
|
32
68
|
// the migrate-before-serve command + health URL for the detected stack so E2E boots on ANY language
|
|
33
69
|
// , a nodejs scaffold otherwise inherited the Python (alembic/uvicorn) command and could not start.
|
|
34
|
-
const ROOT = join(process.cwd(), "..");
|
|
35
70
|
function backendWebServer(): { command: string; url: string } {
|
|
36
71
|
if (existsSync(join(ROOT, "pom.xml"))) {
|
|
37
72
|
// Java/Kotlin (Spring): flyway migrate, then boot on the resolved port.
|
|
@@ -118,9 +118,9 @@ class Base(DeclarativeBase):
|
|
|
118
118
|
pass
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
# The request-scoped session DEPENDENCY (`get_session`) lives in the repository layer,
|
|
122
|
+
# at `app/repositories/session.py` — NOT here. This module is pure infrastructure (engine,
|
|
123
|
+
# SessionLocal, Base); keeping the per-request session door out of it is what lets a
|
|
124
|
+
# service-backed feature's boundary stay off infrastructure (the layering contract). A route
|
|
125
|
+
# that needs a session depends on the session a SERVICE exposes (sourced from the repository
|
|
126
|
+
# layer), never `from app.database import ...` a session dependency.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Repository layer: the ONLY layer that touches the ORM / database session.
|
|
2
|
+
|
|
3
|
+
Each feature's repositories (data access for one aggregate) live here, alongside
|
|
4
|
+
the shared request-session door `session.get_session`. Business logic lives in the
|
|
5
|
+
service layer above; persistence lives only here. A service-backed feature's
|
|
6
|
+
boundary (routes) therefore never imports the DB session from infrastructure
|
|
7
|
+
(`app.database`) — it depends on a session the service exposes, sourced here.
|
|
8
|
+
"""
|