@databricks-solutions/lakebase-scm-utils 0.2.50 → 0.3.0
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 +2636 -2124
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +680 -178
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2460 -2030
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +508 -81
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +2429 -2007
- package/dist/scripts/lakebase/collapse-heads.cli.js +477 -58
- package/dist/scripts/lakebase/detect-language.cli.cjs +2441 -2016
- package/dist/scripts/lakebase/detect-language.cli.js +489 -67
- package/dist/scripts/lakebase/doctor.cli.cjs +2484 -2040
- package/dist/scripts/lakebase/doctor.cli.js +532 -91
- 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 +2633 -2121
- package/dist/scripts/lakebase/index.d.cts +59 -10
- package/dist/scripts/lakebase/index.d.ts +59 -10
- package/dist/scripts/lakebase/index.js +674 -172
- package/dist/scripts/lakebase/infra-runner.cli.cjs +2435 -2013
- package/dist/scripts/lakebase/infra-runner.cli.js +483 -64
- package/dist/scripts/lakebase/new-migration.cli.cjs +2429 -2007
- package/dist/scripts/lakebase/new-migration.cli.js +477 -58
- 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 +2461 -2029
- package/dist/scripts/lakebase/schema-migrate.cli.js +509 -80
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2460 -2030
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +508 -81
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +2486 -2056
- package/dist/scripts/lakebase/scm-doctor.cli.js +537 -107
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2443 -2021
- package/dist/scripts/lakebase/scm-merge.cli.js +494 -72
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2464 -2034
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +511 -84
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/scripts/lakebase/adapters/efcore-adapter.ts +158 -0
- package/scripts/lakebase/adapters/flyway-adapter.ts +8 -5
- package/scripts/lakebase/collapse-heads.cli.ts +1 -1
- package/scripts/lakebase/create-project.ts +14 -12
- package/scripts/lakebase/detect-language.cli.ts +2 -2
- package/scripts/lakebase/doctor.ts +29 -0
- package/scripts/lakebase/dotnet-project.ts +112 -0
- package/scripts/lakebase/index.ts +1 -0
- package/scripts/lakebase/migration-layout.ts +43 -2
- package/scripts/lakebase/new-migration.cli.ts +1 -1
- package/scripts/lakebase/scaffold-language.ts +23 -2
- package/scripts/lakebase/scaffold.ts +4 -1
- package/scripts/lakebase/schema-migrate-runners/efcore.ts +399 -0
- package/scripts/lakebase/schema-migrate-runners/flyway.ts +6 -4
- package/scripts/lakebase/schema-migrate.cli.ts +3 -1
- package/scripts/lakebase/schema-migrate.ts +24 -10
- package/templates/project/common/.github/workflows/merge.yml +13 -1
- package/templates/project/common/.github/workflows/pr.yml +25 -5
- package/templates/project/common/.gitignore.base +7 -0
- package/templates/project/common/scripts/flyway-migrate.sh +54 -1
- package/templates/project/common/scripts/prepare-schema-diff.sh +5 -0
- package/templates/project/common/scripts/run-dev.sh +10 -1
- package/templates/project/common/scripts/run-tests.sh +6 -1
- package/templates/project/dotnet/.gitignore.extra +8 -0
- package/templates/project/dotnet/App.sln +56 -0
- package/templates/project/dotnet/Makefile +13 -0
- package/templates/project/dotnet/global.json +6 -0
- package/templates/project/dotnet/src/App/Data/AppDbContext.cs +12 -0
- package/templates/project/dotnet/src/App/Lakebase/DotEnv.cs +48 -0
- package/templates/project/dotnet/src/App/Lakebase/LakebaseConnection.cs +225 -0
- package/templates/project/dotnet/src/App/Program.cs +34 -0
- package/templates/project/dotnet/src/App/appsettings.json +9 -0
- package/templates/project/dotnet/tests/App.Tests/App.Tests.csproj +25 -0
- package/templates/project/dotnet/tests/App.Tests/HealthTests.cs +18 -0
- package/templates/project/dotnet/tests/App.Tests/OpenApiTests.cs +23 -0
- package/templates/project/dotnet-efcore/.config/dotnet-tools.json +13 -0
- package/templates/project/dotnet-efcore/src/App/App.csproj +22 -0
- package/templates/project/dotnet-efcore/src/App/Data/DesignTimeDbContextFactory.cs +24 -0
- package/templates/project/dotnet-efcore/src/App/Migrations/README.md +23 -0
- package/templates/project/dotnet-flyway/db/migrations/README.md +23 -0
- package/templates/project/dotnet-flyway/src/App/App.csproj +17 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// scripts/lakebase/ci-resolve-branch.cli.ts
|
|
4
|
-
import * as
|
|
4
|
+
import * as fs13 from "fs";
|
|
5
5
|
|
|
6
6
|
// scripts/lakebase/databricks-cli.ts
|
|
7
7
|
import { execFile, execFileSync as execFileSync2 } from "child_process";
|
|
@@ -275,9 +275,9 @@ function asBranchUid(s) {
|
|
|
275
275
|
}
|
|
276
276
|
return s;
|
|
277
277
|
}
|
|
278
|
-
function branchNameFromResourcePath(
|
|
279
|
-
if (!
|
|
280
|
-
const leaf =
|
|
278
|
+
function branchNameFromResourcePath(path14) {
|
|
279
|
+
if (!path14.includes("/branches/")) return null;
|
|
280
|
+
const leaf = path14.split("/branches/").pop();
|
|
281
281
|
if (!leaf) return null;
|
|
282
282
|
try {
|
|
283
283
|
return asBranchName(leaf);
|
|
@@ -697,8 +697,8 @@ function dbcli4(args, host) {
|
|
|
697
697
|
}
|
|
698
698
|
|
|
699
699
|
// scripts/lakebase/schema-migrate.ts
|
|
700
|
-
import * as
|
|
701
|
-
import * as
|
|
700
|
+
import * as fs12 from "fs";
|
|
701
|
+
import * as path13 from "path";
|
|
702
702
|
|
|
703
703
|
// scripts/lakebase/get-connection.ts
|
|
704
704
|
import { createLakebasePool } from "@databricks/lakebase";
|
|
@@ -721,8 +721,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
721
721
|
var cached;
|
|
722
722
|
function substrateSelfVersion() {
|
|
723
723
|
if (cached !== void 0) return cached;
|
|
724
|
-
if ("0.
|
|
725
|
-
cached = "0.
|
|
724
|
+
if ("0.3.0".length > 0) {
|
|
725
|
+
cached = "0.3.0";
|
|
726
726
|
return cached;
|
|
727
727
|
}
|
|
728
728
|
cached = "unknown";
|
|
@@ -831,23 +831,88 @@ function dbcli5(args) {
|
|
|
831
831
|
}
|
|
832
832
|
|
|
833
833
|
// scripts/lakebase/migration-layout.ts
|
|
834
|
+
import * as fs5 from "fs";
|
|
835
|
+
import * as path4 from "path";
|
|
836
|
+
|
|
837
|
+
// scripts/lakebase/dotnet-project.ts
|
|
834
838
|
import * as fs4 from "fs";
|
|
835
839
|
import * as path3 from "path";
|
|
840
|
+
var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
|
|
841
|
+
var DOTNET_FLYWAY_DIR = path3.join("db", "migrations");
|
|
842
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
|
|
843
|
+
function safeReaddir(dir) {
|
|
844
|
+
try {
|
|
845
|
+
return fs4.readdirSync(dir, { withFileTypes: true });
|
|
846
|
+
} catch {
|
|
847
|
+
return [];
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
function hasDotnetMarker(dir) {
|
|
851
|
+
return safeReaddir(dir).some(
|
|
852
|
+
(e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
function findCsprojFiles(projectDir) {
|
|
856
|
+
const found = [];
|
|
857
|
+
const walk = (dir, depth) => {
|
|
858
|
+
for (const e of safeReaddir(dir)) {
|
|
859
|
+
const full = path3.join(dir, e.name);
|
|
860
|
+
if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
|
|
861
|
+
found.push(full);
|
|
862
|
+
} else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
|
|
863
|
+
walk(full, depth + 1);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
walk(projectDir, 0);
|
|
868
|
+
return found.sort();
|
|
869
|
+
}
|
|
870
|
+
function findEfCoreProject(projectDir) {
|
|
871
|
+
for (const csproj of findCsprojFiles(projectDir)) {
|
|
872
|
+
let content = "";
|
|
873
|
+
try {
|
|
874
|
+
content = fs4.readFileSync(csproj, "utf-8");
|
|
875
|
+
} catch {
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (content.includes(EF_DESIGN_PACKAGE)) {
|
|
879
|
+
const dir = path3.dirname(csproj);
|
|
880
|
+
return { csproj, projectDir: dir, migrationsDir: path3.join(dir, "Migrations") };
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
function isDotnetProject(projectDir) {
|
|
886
|
+
return hasDotnetMarker(projectDir);
|
|
887
|
+
}
|
|
888
|
+
function isDotnetFlywayProject(projectDir) {
|
|
889
|
+
return isDotnetProject(projectDir) && fs4.existsSync(path3.join(projectDir, DOTNET_FLYWAY_DIR));
|
|
890
|
+
}
|
|
891
|
+
var MAVEN_FLYWAY_DIR = path3.join("src", "main", "resources", "db", "migration");
|
|
892
|
+
function flywayMigrationsDir(projectDir) {
|
|
893
|
+
const maven = path3.join(projectDir, MAVEN_FLYWAY_DIR);
|
|
894
|
+
if (fs4.existsSync(maven) || fs4.existsSync(path3.join(projectDir, "pom.xml"))) return maven;
|
|
895
|
+
if (isDotnetFlywayProject(projectDir)) return path3.join(projectDir, DOTNET_FLYWAY_DIR);
|
|
896
|
+
return maven;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// scripts/lakebase/migration-layout.ts
|
|
836
900
|
var MIGRATION_LANGUAGES = [
|
|
837
901
|
"java",
|
|
838
902
|
"kotlin",
|
|
839
903
|
"python",
|
|
840
904
|
"nodejs",
|
|
905
|
+
"dotnet",
|
|
841
906
|
"unknown"
|
|
842
907
|
];
|
|
843
908
|
function detectLanguageAt(dir) {
|
|
844
|
-
if (
|
|
845
|
-
const kotlinDir =
|
|
846
|
-
if (
|
|
909
|
+
if (fs5.existsSync(path4.join(dir, "pom.xml"))) {
|
|
910
|
+
const kotlinDir = path4.join(dir, "src", "main", "kotlin");
|
|
911
|
+
if (fs5.existsSync(kotlinDir)) {
|
|
847
912
|
return "kotlin";
|
|
848
913
|
}
|
|
849
914
|
try {
|
|
850
|
-
const pom =
|
|
915
|
+
const pom = fs5.readFileSync(path4.join(dir, "pom.xml"), "utf-8");
|
|
851
916
|
if (pom.includes("kotlin-maven-plugin")) {
|
|
852
917
|
return "kotlin";
|
|
853
918
|
}
|
|
@@ -855,10 +920,13 @@ function detectLanguageAt(dir) {
|
|
|
855
920
|
}
|
|
856
921
|
return "java";
|
|
857
922
|
}
|
|
858
|
-
if (
|
|
923
|
+
if (fs5.existsSync(path4.join(dir, "pyproject.toml")) || fs5.existsSync(path4.join(dir, "requirements.txt")) || fs5.existsSync(path4.join(dir, "alembic.ini"))) {
|
|
859
924
|
return "python";
|
|
860
925
|
}
|
|
861
|
-
if (
|
|
926
|
+
if (hasDotnetMarker(dir)) {
|
|
927
|
+
return "dotnet";
|
|
928
|
+
}
|
|
929
|
+
if (fs5.existsSync(path4.join(dir, "package.json"))) {
|
|
862
930
|
return "nodejs";
|
|
863
931
|
}
|
|
864
932
|
return "unknown";
|
|
@@ -879,14 +947,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
|
|
|
879
947
|
if (!rel) {
|
|
880
948
|
return "unknown";
|
|
881
949
|
}
|
|
882
|
-
const rootResolved =
|
|
883
|
-
let dir =
|
|
884
|
-
while (dir === rootResolved || dir.startsWith(rootResolved +
|
|
950
|
+
const rootResolved = path4.resolve(projectDir);
|
|
951
|
+
let dir = path4.resolve(projectDir, rel);
|
|
952
|
+
while (dir === rootResolved || dir.startsWith(rootResolved + path4.sep)) {
|
|
885
953
|
const lang = detectLanguageAt(dir);
|
|
886
954
|
if (lang !== "unknown") {
|
|
887
955
|
return lang;
|
|
888
956
|
}
|
|
889
|
-
const parent =
|
|
957
|
+
const parent = path4.dirname(dir);
|
|
890
958
|
if (parent === dir) {
|
|
891
959
|
break;
|
|
892
960
|
}
|
|
@@ -896,21 +964,21 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
|
|
|
896
964
|
}
|
|
897
965
|
|
|
898
966
|
// scripts/lakebase/adapters/alembic-adapter.ts
|
|
899
|
-
import * as
|
|
900
|
-
import * as
|
|
967
|
+
import * as fs7 from "fs";
|
|
968
|
+
import * as path6 from "path";
|
|
901
969
|
|
|
902
970
|
// scripts/lakebase/schema-migrate-runners/alembic.ts
|
|
903
971
|
import { spawn } from "child_process";
|
|
904
|
-
import * as
|
|
905
|
-
import * as
|
|
972
|
+
import * as fs6 from "fs";
|
|
973
|
+
import * as path5 from "path";
|
|
906
974
|
function resolveAlembicBin(projectDir) {
|
|
907
975
|
const candidates = [
|
|
908
|
-
|
|
909
|
-
|
|
976
|
+
path5.join(projectDir, ".venv", "bin", "alembic"),
|
|
977
|
+
path5.join(projectDir, "venv", "bin", "alembic")
|
|
910
978
|
];
|
|
911
979
|
for (const candidate of candidates) {
|
|
912
980
|
try {
|
|
913
|
-
if (
|
|
981
|
+
if (fs6.existsSync(candidate)) return candidate;
|
|
914
982
|
} catch {
|
|
915
983
|
}
|
|
916
984
|
}
|
|
@@ -920,7 +988,7 @@ function spawnAlembic(projectDir, args, dsn) {
|
|
|
920
988
|
return new Promise((resolve2, reject) => {
|
|
921
989
|
const bin = resolveAlembicBin(projectDir);
|
|
922
990
|
const env = { ...process.env };
|
|
923
|
-
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(
|
|
991
|
+
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path5.delimiter);
|
|
924
992
|
if (dsn) env.DATABASE_URL = dsn;
|
|
925
993
|
const child = spawn(bin, args, {
|
|
926
994
|
cwd: projectDir,
|
|
@@ -968,10 +1036,10 @@ async function createAlembicRevision(opts) {
|
|
|
968
1036
|
const m = stdout.match(/Generating\s+(\S+\.py)/);
|
|
969
1037
|
if (m) return m[1].trim();
|
|
970
1038
|
for (const rel of ["migrations/versions", "alembic/versions"]) {
|
|
971
|
-
const dir =
|
|
972
|
-
if (!
|
|
973
|
-
const hit =
|
|
974
|
-
if (hit) return
|
|
1039
|
+
const dir = path5.join(opts.projectDir, rel);
|
|
1040
|
+
if (!fs6.existsSync(dir)) continue;
|
|
1041
|
+
const hit = fs6.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
|
|
1042
|
+
if (hit) return path5.join(dir, hit);
|
|
975
1043
|
}
|
|
976
1044
|
throw new SchemaMigrationError(
|
|
977
1045
|
`alembic revision succeeded but the created file could not be located.
|
|
@@ -1105,15 +1173,15 @@ async function buildDsn(args) {
|
|
|
1105
1173
|
}
|
|
1106
1174
|
function findVersionsDir(projectDir) {
|
|
1107
1175
|
const candidates = [
|
|
1108
|
-
|
|
1109
|
-
|
|
1176
|
+
path6.join(projectDir, "migrations", "versions"),
|
|
1177
|
+
path6.join(projectDir, "alembic", "versions")
|
|
1110
1178
|
];
|
|
1111
|
-
return candidates.find((p) =>
|
|
1179
|
+
return candidates.find((p) => fs7.existsSync(p));
|
|
1112
1180
|
}
|
|
1113
1181
|
function listAlembicFiles(projectDir) {
|
|
1114
1182
|
const dir = findVersionsDir(projectDir);
|
|
1115
1183
|
if (!dir) return [];
|
|
1116
|
-
const files =
|
|
1184
|
+
const files = fs7.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
|
|
1117
1185
|
return files.map((filename) => {
|
|
1118
1186
|
const stem = filename.replace(/\.py$/, "");
|
|
1119
1187
|
const sep2 = stem.indexOf("_");
|
|
@@ -1138,9 +1206,9 @@ var AlembicAdapter = {
|
|
|
1138
1206
|
* here. Callers can still force-select via project.yaml#migration_tool.
|
|
1139
1207
|
*/
|
|
1140
1208
|
detect(projectDir) {
|
|
1141
|
-
if (
|
|
1142
|
-
if (
|
|
1143
|
-
if (
|
|
1209
|
+
if (fs7.existsSync(path6.join(projectDir, "alembic.ini"))) return true;
|
|
1210
|
+
if (fs7.existsSync(path6.join(projectDir, "migrations", "env.py"))) return true;
|
|
1211
|
+
if (fs7.existsSync(path6.join(projectDir, "alembic", "env.py"))) return true;
|
|
1144
1212
|
return false;
|
|
1145
1213
|
},
|
|
1146
1214
|
async apply(args) {
|
|
@@ -1246,7 +1314,7 @@ var AlembicAdapter = {
|
|
|
1246
1314
|
autogenerate: !!args.autogenerate,
|
|
1247
1315
|
dsn
|
|
1248
1316
|
});
|
|
1249
|
-
return { status: "ok", version: revId, filename:
|
|
1317
|
+
return { status: "ok", version: revId, filename: path6.basename(created), path: created };
|
|
1250
1318
|
} catch (err) {
|
|
1251
1319
|
return {
|
|
1252
1320
|
status: "error",
|
|
@@ -1263,7 +1331,7 @@ var AlembicAdapter = {
|
|
|
1263
1331
|
if (heads.length <= 1) return { status: "noop", headsBefore: heads };
|
|
1264
1332
|
if (args.dryRun) return { status: "ok", headsBefore: heads };
|
|
1265
1333
|
const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
|
|
1266
|
-
const mergeRevision =
|
|
1334
|
+
const mergeRevision = path6.basename(created).replace(/\.py$/, "").split("_")[0];
|
|
1267
1335
|
return { status: "ok", headsBefore: heads, mergeRevision, path: created };
|
|
1268
1336
|
} catch (err) {
|
|
1269
1337
|
return {
|
|
@@ -1277,12 +1345,12 @@ var AlembicAdapter = {
|
|
|
1277
1345
|
registerSchemaMigrationAdapter(AlembicAdapter);
|
|
1278
1346
|
|
|
1279
1347
|
// scripts/lakebase/adapters/flyway-adapter.ts
|
|
1280
|
-
import * as
|
|
1281
|
-
import * as
|
|
1348
|
+
import * as fs8 from "fs";
|
|
1349
|
+
import * as path8 from "path";
|
|
1282
1350
|
|
|
1283
1351
|
// scripts/lakebase/schema-migrate-runners/flyway.ts
|
|
1284
1352
|
import { spawn as spawn2 } from "child_process";
|
|
1285
|
-
import * as
|
|
1353
|
+
import * as path7 from "path";
|
|
1286
1354
|
function dsnToFlywayEnv(dsn) {
|
|
1287
1355
|
const u = new URL(dsn);
|
|
1288
1356
|
const user = decodeURIComponent(u.username);
|
|
@@ -1292,7 +1360,7 @@ function dsnToFlywayEnv(dsn) {
|
|
|
1292
1360
|
return { url, user, password };
|
|
1293
1361
|
}
|
|
1294
1362
|
function migrationsLocation(projectDir) {
|
|
1295
|
-
return `filesystem:${
|
|
1363
|
+
return `filesystem:${flywayMigrationsDir(projectDir)}`;
|
|
1296
1364
|
}
|
|
1297
1365
|
function runFlyway(ctx, args) {
|
|
1298
1366
|
const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
|
|
@@ -1393,7 +1461,7 @@ async function statusFlyway(ctx) {
|
|
|
1393
1461
|
if (state === "SUCCESS" || state === "BASELINE") {
|
|
1394
1462
|
current = m.version;
|
|
1395
1463
|
} else if (state === "PENDING") {
|
|
1396
|
-
const filename = m.filepath ?
|
|
1464
|
+
const filename = m.filepath ? path7.basename(m.filepath) : `V${m.version}__migration.sql`;
|
|
1397
1465
|
pending.push({
|
|
1398
1466
|
version: m.version,
|
|
1399
1467
|
filename,
|
|
@@ -1416,9 +1484,9 @@ async function buildDsn2(args) {
|
|
|
1416
1484
|
return result.url;
|
|
1417
1485
|
}
|
|
1418
1486
|
function listFlywayFiles(projectDir) {
|
|
1419
|
-
const dir =
|
|
1420
|
-
if (!
|
|
1421
|
-
const files =
|
|
1487
|
+
const dir = flywayMigrationsDir(projectDir);
|
|
1488
|
+
if (!fs8.existsSync(dir)) return [];
|
|
1489
|
+
const files = fs8.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
|
|
1422
1490
|
return files.map((filename) => {
|
|
1423
1491
|
const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
|
|
1424
1492
|
const version = m[1];
|
|
@@ -1439,9 +1507,9 @@ function versionCompare(a, b) {
|
|
|
1439
1507
|
}
|
|
1440
1508
|
var FlywayAdapter = {
|
|
1441
1509
|
id: "flyway",
|
|
1442
|
-
languages: ["java", "kotlin"],
|
|
1510
|
+
languages: ["java", "kotlin", "dotnet"],
|
|
1443
1511
|
detect(projectDir) {
|
|
1444
|
-
return
|
|
1512
|
+
return fs8.existsSync(path8.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
|
|
1445
1513
|
},
|
|
1446
1514
|
async apply(args) {
|
|
1447
1515
|
const dsn = await buildDsn2(args);
|
|
@@ -1499,14 +1567,14 @@ var FlywayAdapter = {
|
|
|
1499
1567
|
// optional-protocol shape makes this additive.
|
|
1500
1568
|
async newMigration(args) {
|
|
1501
1569
|
try {
|
|
1502
|
-
const dir =
|
|
1503
|
-
|
|
1570
|
+
const dir = flywayMigrationsDir(args.projectDir);
|
|
1571
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1504
1572
|
const version = migrationTimestamp();
|
|
1505
1573
|
const slug = migrationSlug2(args.slug);
|
|
1506
1574
|
const filename = `V${version}__${slug}.sql`;
|
|
1507
|
-
const full =
|
|
1508
|
-
if (
|
|
1509
|
-
|
|
1575
|
+
const full = path8.join(dir, filename);
|
|
1576
|
+
if (fs8.existsSync(full)) throw new Error(`${filename} already exists`);
|
|
1577
|
+
fs8.writeFileSync(
|
|
1510
1578
|
full,
|
|
1511
1579
|
`-- V${version}: ${args.slug}
|
|
1512
1580
|
-- Flyway migration (write your DDL/DML below).
|
|
@@ -1528,18 +1596,18 @@ var FlywayAdapter = {
|
|
|
1528
1596
|
registerSchemaMigrationAdapter(FlywayAdapter);
|
|
1529
1597
|
|
|
1530
1598
|
// scripts/lakebase/adapters/knex-adapter.ts
|
|
1531
|
-
import * as
|
|
1532
|
-
import * as
|
|
1599
|
+
import * as fs10 from "fs";
|
|
1600
|
+
import * as path10 from "path";
|
|
1533
1601
|
|
|
1534
1602
|
// scripts/lakebase/schema-migrate-runners/knex.ts
|
|
1535
1603
|
import { spawn as spawn3 } from "child_process";
|
|
1536
|
-
import * as
|
|
1537
|
-
import * as
|
|
1604
|
+
import * as fs9 from "fs";
|
|
1605
|
+
import * as path9 from "path";
|
|
1538
1606
|
var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
1539
1607
|
function findKnexfile(projectDir) {
|
|
1540
1608
|
for (const name of KNEXFILE_VARIANTS) {
|
|
1541
|
-
const p =
|
|
1542
|
-
if (
|
|
1609
|
+
const p = path9.join(projectDir, name);
|
|
1610
|
+
if (fs9.existsSync(p)) return p;
|
|
1543
1611
|
}
|
|
1544
1612
|
return void 0;
|
|
1545
1613
|
}
|
|
@@ -1691,9 +1759,9 @@ async function buildDsn3(args) {
|
|
|
1691
1759
|
}
|
|
1692
1760
|
var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
1693
1761
|
function listKnexFiles(projectDir) {
|
|
1694
|
-
const dir =
|
|
1695
|
-
if (!
|
|
1696
|
-
const files =
|
|
1762
|
+
const dir = path10.join(projectDir, "migrations");
|
|
1763
|
+
if (!fs10.existsSync(dir)) return [];
|
|
1764
|
+
const files = fs10.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
|
|
1697
1765
|
return files.map((filename) => {
|
|
1698
1766
|
const stem = filename.replace(/\.(js|ts)$/, "");
|
|
1699
1767
|
const m = stem.match(/^(\d{14})_(.+)$/);
|
|
@@ -1713,7 +1781,7 @@ var KnexAdapter = {
|
|
|
1713
1781
|
* project.yaml#migration_tool.
|
|
1714
1782
|
*/
|
|
1715
1783
|
detect(projectDir) {
|
|
1716
|
-
return KNEXFILE_VARIANTS2.some((name) =>
|
|
1784
|
+
return KNEXFILE_VARIANTS2.some((name) => fs10.existsSync(path10.join(projectDir, name)));
|
|
1717
1785
|
},
|
|
1718
1786
|
async apply(args) {
|
|
1719
1787
|
const dsn = await buildDsn3(args);
|
|
@@ -1786,9 +1854,9 @@ var KnexAdapter = {
|
|
|
1786
1854
|
async newMigration(args) {
|
|
1787
1855
|
try {
|
|
1788
1856
|
const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
|
|
1789
|
-
const stem =
|
|
1857
|
+
const stem = path10.basename(created).replace(/\.(js|ts)$/, "");
|
|
1790
1858
|
const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
|
|
1791
|
-
return { status: "ok", version, filename:
|
|
1859
|
+
return { status: "ok", version, filename: path10.basename(created), path: created };
|
|
1792
1860
|
} catch (err) {
|
|
1793
1861
|
return {
|
|
1794
1862
|
status: "error",
|
|
@@ -1802,6 +1870,360 @@ var KnexAdapter = {
|
|
|
1802
1870
|
};
|
|
1803
1871
|
registerSchemaMigrationAdapter(KnexAdapter);
|
|
1804
1872
|
|
|
1873
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
1874
|
+
import * as path12 from "path";
|
|
1875
|
+
|
|
1876
|
+
// scripts/lakebase/schema-migrate-runners/efcore.ts
|
|
1877
|
+
import { spawn as spawn4 } from "child_process";
|
|
1878
|
+
import * as fs11 from "fs";
|
|
1879
|
+
import * as path11 from "path";
|
|
1880
|
+
import { Client as Client2 } from "pg";
|
|
1881
|
+
var MIGRATION_ID = /^(\d{14})_(.+)$/;
|
|
1882
|
+
function efVersion(id) {
|
|
1883
|
+
return MIGRATION_ID.exec(id)?.[1] ?? id;
|
|
1884
|
+
}
|
|
1885
|
+
function efDescription(id) {
|
|
1886
|
+
const name = MIGRATION_ID.exec(id)?.[2] ?? id;
|
|
1887
|
+
return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
1888
|
+
}
|
|
1889
|
+
function dsnToNpgsql(dsn) {
|
|
1890
|
+
const u = new URL(dsn);
|
|
1891
|
+
const parts = [
|
|
1892
|
+
`Host=${u.hostname}`,
|
|
1893
|
+
`Port=${u.port || "5432"}`,
|
|
1894
|
+
`Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
|
|
1895
|
+
];
|
|
1896
|
+
if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
|
|
1897
|
+
if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
|
|
1898
|
+
const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
|
|
1899
|
+
const ssl = {
|
|
1900
|
+
disable: "Disable",
|
|
1901
|
+
allow: "Allow",
|
|
1902
|
+
prefer: "Prefer",
|
|
1903
|
+
require: "Require",
|
|
1904
|
+
"verify-ca": "VerifyCA",
|
|
1905
|
+
"verify-full": "VerifyFull"
|
|
1906
|
+
};
|
|
1907
|
+
parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
|
|
1908
|
+
const app = u.searchParams.get("application_name");
|
|
1909
|
+
if (app) parts.push(`Application Name=${app}`);
|
|
1910
|
+
return parts.map((p) => p.replace(/;/g, "")).join(";");
|
|
1911
|
+
}
|
|
1912
|
+
function parseEfJson(stdout) {
|
|
1913
|
+
const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
|
|
1914
|
+
if (!body) {
|
|
1915
|
+
throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
|
|
1916
|
+
}
|
|
1917
|
+
try {
|
|
1918
|
+
return JSON.parse(body);
|
|
1919
|
+
} catch (err) {
|
|
1920
|
+
throw new SchemaMigrationError(
|
|
1921
|
+
`dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
|
|
1922
|
+
Body (first 400 chars): ${body.slice(0, 400)}`
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
function requireProject(projectDir) {
|
|
1927
|
+
const project = findEfCoreProject(projectDir);
|
|
1928
|
+
if (!project) {
|
|
1929
|
+
throw new SchemaMigrationError(
|
|
1930
|
+
`No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
return project;
|
|
1934
|
+
}
|
|
1935
|
+
function spawnDotnet(projectDir, args, env) {
|
|
1936
|
+
return new Promise((resolve2, reject) => {
|
|
1937
|
+
const child = spawn4("dotnet", args, {
|
|
1938
|
+
cwd: projectDir,
|
|
1939
|
+
env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
|
|
1940
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1941
|
+
});
|
|
1942
|
+
let stdout = "";
|
|
1943
|
+
let stderr = "";
|
|
1944
|
+
child.stdout.on("data", (chunk) => {
|
|
1945
|
+
stdout += chunk.toString("utf8");
|
|
1946
|
+
});
|
|
1947
|
+
child.stderr.on("data", (chunk) => {
|
|
1948
|
+
stderr += chunk.toString("utf8");
|
|
1949
|
+
});
|
|
1950
|
+
child.on("error", (err) => {
|
|
1951
|
+
reject(
|
|
1952
|
+
new SchemaMigrationError(
|
|
1953
|
+
`Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
|
|
1954
|
+
err
|
|
1955
|
+
)
|
|
1956
|
+
);
|
|
1957
|
+
});
|
|
1958
|
+
child.on("close", (code) => {
|
|
1959
|
+
if (code === 0) {
|
|
1960
|
+
resolve2({ stdout, stderr });
|
|
1961
|
+
} else {
|
|
1962
|
+
reject(
|
|
1963
|
+
new SchemaMigrationError(
|
|
1964
|
+
`dotnet ${args.join(" ")} exited with code ${code}.
|
|
1965
|
+
stdout: ${stdout}
|
|
1966
|
+
stderr: ${stderr}`
|
|
1967
|
+
)
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
var restored = /* @__PURE__ */ new Set();
|
|
1974
|
+
async function ensureRestored(projectDir, csproj) {
|
|
1975
|
+
if (restored.has(projectDir)) return;
|
|
1976
|
+
if (fs11.existsSync(path11.join(projectDir, ".config", "dotnet-tools.json"))) {
|
|
1977
|
+
await spawnDotnet(projectDir, ["tool", "restore"], {});
|
|
1978
|
+
}
|
|
1979
|
+
await spawnDotnet(projectDir, ["restore", csproj], {});
|
|
1980
|
+
restored.add(projectDir);
|
|
1981
|
+
}
|
|
1982
|
+
async function runEf(projectDir, args, dsn, opts = {}) {
|
|
1983
|
+
const project = requireProject(projectDir);
|
|
1984
|
+
const csproj = path11.relative(projectDir, project.csproj) || project.csproj;
|
|
1985
|
+
await ensureRestored(projectDir, csproj);
|
|
1986
|
+
const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
|
|
1987
|
+
if (opts.noBuild) full.push("--no-build");
|
|
1988
|
+
const env = {};
|
|
1989
|
+
if (dsn) {
|
|
1990
|
+
env.DATABASE_URL = dsn;
|
|
1991
|
+
if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
|
|
1992
|
+
}
|
|
1993
|
+
return spawnDotnet(projectDir, full, env);
|
|
1994
|
+
}
|
|
1995
|
+
async function listEfMigrations(ctx, opts = {}) {
|
|
1996
|
+
const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
|
|
1997
|
+
const entries = parseEfJson(stdout);
|
|
1998
|
+
if (ctx.dsn && entries.some((e) => e.applied === null)) {
|
|
1999
|
+
const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
|
|
2000
|
+
throw new SchemaMigrationError(
|
|
2001
|
+
`dotnet ef could not read migration state from the database (check connectivity and credentials).
|
|
2002
|
+
${warning || stderr}`.trim()
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
return entries;
|
|
2006
|
+
}
|
|
2007
|
+
async function readEfHistory(dsn) {
|
|
2008
|
+
const client = new Client2({ connectionString: dsn, application_name: connectionApplicationName() });
|
|
2009
|
+
await client.connect();
|
|
2010
|
+
try {
|
|
2011
|
+
const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
|
|
2012
|
+
if (!exists.rows[0]?.present) return null;
|
|
2013
|
+
const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
|
|
2014
|
+
return r.rows.map((row) => row.MigrationId);
|
|
2015
|
+
} finally {
|
|
2016
|
+
await client.end();
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
function toApplied(entries) {
|
|
2020
|
+
return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
|
|
2021
|
+
}
|
|
2022
|
+
async function statusEfcore(ctx) {
|
|
2023
|
+
const entries = await listEfMigrations(ctx);
|
|
2024
|
+
const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
|
|
2025
|
+
const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
|
|
2026
|
+
const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
|
|
2027
|
+
return { current, pending, tool: "efcore" };
|
|
2028
|
+
}
|
|
2029
|
+
async function applyEfcore(ctx) {
|
|
2030
|
+
const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
|
|
2031
|
+
await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
|
|
2032
|
+
const after = await listEfMigrations(ctx, { noBuild: true });
|
|
2033
|
+
const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
|
|
2034
|
+
return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
|
|
2035
|
+
}
|
|
2036
|
+
function resolveEfTarget(target, applied) {
|
|
2037
|
+
const t = target.trim();
|
|
2038
|
+
if (t === "0") return "0";
|
|
2039
|
+
const rel = /^-(\d+)$/.exec(t);
|
|
2040
|
+
if (rel) {
|
|
2041
|
+
const steps = Number(rel[1]);
|
|
2042
|
+
if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
|
|
2043
|
+
const index = applied.length - 1 - steps;
|
|
2044
|
+
if (index < -1) {
|
|
2045
|
+
throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
|
|
2046
|
+
}
|
|
2047
|
+
return index === -1 ? "0" : applied[index].id;
|
|
2048
|
+
}
|
|
2049
|
+
const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
|
|
2050
|
+
if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
|
|
2051
|
+
return match.id;
|
|
2052
|
+
}
|
|
2053
|
+
async function rollbackEfcore(ctx) {
|
|
2054
|
+
const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
|
|
2055
|
+
const targetId = resolveEfTarget(ctx.target, beforeEntries);
|
|
2056
|
+
await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
|
|
2057
|
+
const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
|
|
2058
|
+
return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
|
|
2059
|
+
}
|
|
2060
|
+
function efMigrationName(slug) {
|
|
2061
|
+
const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
2062
|
+
if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
|
|
2063
|
+
return /^[0-9]/.test(name) ? `M${name}` : name;
|
|
2064
|
+
}
|
|
2065
|
+
async function createEfcoreMigration(opts) {
|
|
2066
|
+
const project = requireProject(opts.projectDir);
|
|
2067
|
+
const name = efMigrationName(opts.slug);
|
|
2068
|
+
const existing = new Set(fs11.existsSync(project.migrationsDir) ? fs11.readdirSync(project.migrationsDir) : []);
|
|
2069
|
+
await runEf(opts.projectDir, ["migrations", "add", name]);
|
|
2070
|
+
const created = fs11.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
|
|
2071
|
+
if (created.length !== 1) {
|
|
2072
|
+
throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
|
|
2073
|
+
}
|
|
2074
|
+
return { id: created[0].replace(/\.cs$/, ""), path: path11.join(project.migrationsDir, created[0]) };
|
|
2075
|
+
}
|
|
2076
|
+
function localEfMigrationIds(projectDir) {
|
|
2077
|
+
const project = findEfCoreProject(projectDir);
|
|
2078
|
+
if (!project || !fs11.existsSync(project.migrationsDir)) return [];
|
|
2079
|
+
return fs11.readdirSync(project.migrationsDir).filter((f) => f.endsWith(".cs") && !f.endsWith(".Designer.cs") && MIGRATION_ID.test(f.slice(0, -3))).map((f) => f.slice(0, -3)).sort();
|
|
2080
|
+
}
|
|
2081
|
+
function efProductVersion(project) {
|
|
2082
|
+
try {
|
|
2083
|
+
const csproj = fs11.readFileSync(project.csproj, "utf-8");
|
|
2084
|
+
const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
|
|
2085
|
+
if (m) return m[1];
|
|
2086
|
+
} catch {
|
|
2087
|
+
}
|
|
2088
|
+
return "unknown";
|
|
2089
|
+
}
|
|
2090
|
+
async function stampEfcore(ctx) {
|
|
2091
|
+
const project = requireProject(ctx.projectDir);
|
|
2092
|
+
const ids = localEfMigrationIds(ctx.projectDir);
|
|
2093
|
+
if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
|
|
2094
|
+
const rev = ctx.revision.trim();
|
|
2095
|
+
const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
|
|
2096
|
+
if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
|
|
2097
|
+
const toStamp = ids.slice(0, end + 1);
|
|
2098
|
+
const productVersion = efProductVersion(project);
|
|
2099
|
+
const client = new Client2({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
|
|
2100
|
+
await client.connect();
|
|
2101
|
+
try {
|
|
2102
|
+
await client.query("BEGIN");
|
|
2103
|
+
await client.query(
|
|
2104
|
+
`CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
|
|
2105
|
+
"MigrationId" character varying(150) NOT NULL,
|
|
2106
|
+
"ProductVersion" character varying(32) NOT NULL,
|
|
2107
|
+
CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
|
|
2108
|
+
);
|
|
2109
|
+
await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
|
|
2110
|
+
for (const id of toStamp) {
|
|
2111
|
+
await client.query(
|
|
2112
|
+
`INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
|
|
2113
|
+
ON CONFLICT ("MigrationId") DO NOTHING`,
|
|
2114
|
+
[id, productVersion]
|
|
2115
|
+
);
|
|
2116
|
+
}
|
|
2117
|
+
await client.query("COMMIT");
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
2120
|
+
throw err;
|
|
2121
|
+
} finally {
|
|
2122
|
+
await client.end();
|
|
2123
|
+
}
|
|
2124
|
+
return efVersion(toStamp[toStamp.length - 1]);
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
2128
|
+
async function buildDsn4(args) {
|
|
2129
|
+
const result = await getConnection({
|
|
2130
|
+
output: "dsn",
|
|
2131
|
+
instance: args.instance,
|
|
2132
|
+
branch: args.branch,
|
|
2133
|
+
database: args.database,
|
|
2134
|
+
endpointName: args.endpointName
|
|
2135
|
+
});
|
|
2136
|
+
return result.url;
|
|
2137
|
+
}
|
|
2138
|
+
function errMsg(err) {
|
|
2139
|
+
return err instanceof Error ? err.message : String(err);
|
|
2140
|
+
}
|
|
2141
|
+
function listEfCoreFiles(projectDir) {
|
|
2142
|
+
return localEfMigrationIds(projectDir).map((id) => ({
|
|
2143
|
+
version: efVersion(id),
|
|
2144
|
+
filename: `${id}.cs`,
|
|
2145
|
+
description: efDescription(id),
|
|
2146
|
+
type: "CSharp",
|
|
2147
|
+
tool: "efcore"
|
|
2148
|
+
}));
|
|
2149
|
+
}
|
|
2150
|
+
var EfCoreAdapter = {
|
|
2151
|
+
id: "efcore",
|
|
2152
|
+
languages: ["dotnet"],
|
|
2153
|
+
/** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
|
|
2154
|
+
detect(projectDir) {
|
|
2155
|
+
return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
|
|
2156
|
+
},
|
|
2157
|
+
async apply(args) {
|
|
2158
|
+
try {
|
|
2159
|
+
const dsn = await buildDsn4(args);
|
|
2160
|
+
const r = await applyEfcore({ projectDir: args.projectDir, dsn });
|
|
2161
|
+
return {
|
|
2162
|
+
applied_migrations: r.applied,
|
|
2163
|
+
status: r.alreadyAtLatest ? "noop" : "ok",
|
|
2164
|
+
tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
|
|
2165
|
+
};
|
|
2166
|
+
} catch (err) {
|
|
2167
|
+
return { applied_migrations: [], status: "error", error: errMsg(err) };
|
|
2168
|
+
}
|
|
2169
|
+
},
|
|
2170
|
+
async rollback(args) {
|
|
2171
|
+
try {
|
|
2172
|
+
const dsn = await buildDsn4(args);
|
|
2173
|
+
const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
|
|
2174
|
+
return {
|
|
2175
|
+
rolled_back: r.rolledBack,
|
|
2176
|
+
status: r.rolledBack.length === 0 ? "noop" : "ok",
|
|
2177
|
+
tool_specific: { tool: r.tool }
|
|
2178
|
+
};
|
|
2179
|
+
} catch (err) {
|
|
2180
|
+
return { rolled_back: [], status: "error", error: errMsg(err) };
|
|
2181
|
+
}
|
|
2182
|
+
},
|
|
2183
|
+
async stamp(args) {
|
|
2184
|
+
try {
|
|
2185
|
+
const dsn = await buildDsn4(args);
|
|
2186
|
+
const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
|
|
2187
|
+
return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
|
|
2188
|
+
} catch (err) {
|
|
2189
|
+
return { status: "error", stamped_revision: null, error: errMsg(err) };
|
|
2190
|
+
}
|
|
2191
|
+
},
|
|
2192
|
+
async status(args) {
|
|
2193
|
+
try {
|
|
2194
|
+
const dsn = await buildDsn4(args);
|
|
2195
|
+
const r = await statusEfcore({ projectDir: args.projectDir, dsn });
|
|
2196
|
+
return {
|
|
2197
|
+
applied_version: r.current ?? null,
|
|
2198
|
+
pending: r.pending,
|
|
2199
|
+
applied: [],
|
|
2200
|
+
status: "ok",
|
|
2201
|
+
tool_specific: { tool: r.tool }
|
|
2202
|
+
};
|
|
2203
|
+
} catch (err) {
|
|
2204
|
+
return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
|
|
2205
|
+
}
|
|
2206
|
+
},
|
|
2207
|
+
async list(args) {
|
|
2208
|
+
return { files: listEfCoreFiles(args.projectDir) };
|
|
2209
|
+
},
|
|
2210
|
+
// baseline: use stamp (EF has no separate baseline concept).
|
|
2211
|
+
async newMigration(args) {
|
|
2212
|
+
try {
|
|
2213
|
+
const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
|
|
2214
|
+
return {
|
|
2215
|
+
status: "ok",
|
|
2216
|
+
version: efVersion(created.id),
|
|
2217
|
+
filename: path12.basename(created.path),
|
|
2218
|
+
path: created.path
|
|
2219
|
+
};
|
|
2220
|
+
} catch (err) {
|
|
2221
|
+
return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2225
|
+
registerSchemaMigrationAdapter(EfCoreAdapter);
|
|
2226
|
+
|
|
1805
2227
|
// scripts/lakebase/schema-migrate.ts
|
|
1806
2228
|
var SchemaMigrationError = class extends Error {
|
|
1807
2229
|
constructor(message, cause) {
|
|
@@ -1815,13 +2237,16 @@ function detectLanguage(projectDir) {
|
|
|
1815
2237
|
const lang = resolveMigrationLanguage(projectDir);
|
|
1816
2238
|
if (lang === "unknown") {
|
|
1817
2239
|
throw new SchemaMigrationError(
|
|
1818
|
-
`Could not detect project language in ${projectDir}. Expected one of: pom.xml (java/kotlin), pyproject.toml or alembic.ini (python), package.json (nodejs). Pass {language} explicitly to override.`
|
|
2240
|
+
`Could not detect project language in ${projectDir}. Expected one of: pom.xml (java/kotlin), pyproject.toml or alembic.ini (python), package.json (nodejs), *.sln or *.csproj (dotnet). Pass {language} explicitly to override.`
|
|
1819
2241
|
);
|
|
1820
2242
|
}
|
|
1821
2243
|
return lang;
|
|
1822
2244
|
}
|
|
1823
|
-
function toolForLanguage(language) {
|
|
2245
|
+
function toolForLanguage(language, projectDir) {
|
|
1824
2246
|
switch (language) {
|
|
2247
|
+
case "dotnet":
|
|
2248
|
+
if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
|
|
2249
|
+
return "efcore";
|
|
1825
2250
|
case "java":
|
|
1826
2251
|
case "kotlin":
|
|
1827
2252
|
return "flyway";
|
|
@@ -1834,8 +2259,10 @@ function toolForLanguage(language) {
|
|
|
1834
2259
|
function listSchemaMigrations(args = {}) {
|
|
1835
2260
|
const projectDir = args.projectDir ?? process.cwd();
|
|
1836
2261
|
const language = args.language ?? detectLanguage(projectDir);
|
|
1837
|
-
const tool = toolForLanguage(language);
|
|
2262
|
+
const tool = toolForLanguage(language, projectDir);
|
|
1838
2263
|
switch (tool) {
|
|
2264
|
+
case "efcore":
|
|
2265
|
+
return listEfCoreFiles(projectDir);
|
|
1839
2266
|
case "flyway":
|
|
1840
2267
|
return listFlywayMigrations(projectDir);
|
|
1841
2268
|
case "alembic":
|
|
@@ -1845,9 +2272,9 @@ function listSchemaMigrations(args = {}) {
|
|
|
1845
2272
|
}
|
|
1846
2273
|
}
|
|
1847
2274
|
function listFlywayMigrations(projectDir) {
|
|
1848
|
-
const dir =
|
|
1849
|
-
if (!
|
|
1850
|
-
const files =
|
|
2275
|
+
const dir = flywayMigrationsDir(projectDir);
|
|
2276
|
+
if (!fs12.existsSync(dir)) return [];
|
|
2277
|
+
const files = fs12.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
|
|
1851
2278
|
return files.map((filename) => {
|
|
1852
2279
|
const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
|
|
1853
2280
|
const version = m[1];
|
|
@@ -1857,12 +2284,12 @@ function listFlywayMigrations(projectDir) {
|
|
|
1857
2284
|
}
|
|
1858
2285
|
function listAlembicMigrations(projectDir) {
|
|
1859
2286
|
const candidates = [
|
|
1860
|
-
|
|
1861
|
-
|
|
2287
|
+
path13.join(projectDir, "migrations", "versions"),
|
|
2288
|
+
path13.join(projectDir, "alembic", "versions")
|
|
1862
2289
|
];
|
|
1863
|
-
const dir = candidates.find((p) =>
|
|
2290
|
+
const dir = candidates.find((p) => fs12.existsSync(p));
|
|
1864
2291
|
if (!dir) return [];
|
|
1865
|
-
const files =
|
|
2292
|
+
const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
|
|
1866
2293
|
return files.map((filename) => {
|
|
1867
2294
|
const stem = filename.replace(/\.py$/, "");
|
|
1868
2295
|
const sep2 = stem.indexOf("_");
|
|
@@ -1872,9 +2299,9 @@ function listAlembicMigrations(projectDir) {
|
|
|
1872
2299
|
}).sort((a, b) => a.filename.localeCompare(b.filename));
|
|
1873
2300
|
}
|
|
1874
2301
|
function listKnexMigrations(projectDir) {
|
|
1875
|
-
const dir =
|
|
1876
|
-
if (!
|
|
1877
|
-
const files =
|
|
2302
|
+
const dir = path13.join(projectDir, "migrations");
|
|
2303
|
+
if (!fs12.existsSync(dir)) return [];
|
|
2304
|
+
const files = fs12.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
|
|
1878
2305
|
return files.map((filename) => {
|
|
1879
2306
|
const stem = filename.replace(/\.(js|ts)$/, "");
|
|
1880
2307
|
const m = stem.match(/^(\d{14})_(.+)$/);
|
|
@@ -1896,7 +2323,7 @@ function versionCompare2(a, b) {
|
|
|
1896
2323
|
return 0;
|
|
1897
2324
|
}
|
|
1898
2325
|
function adapterFor(projectDir, language) {
|
|
1899
|
-
const override = language ? toolForLanguage(language) : void 0;
|
|
2326
|
+
const override = language ? toolForLanguage(language, projectDir) : void 0;
|
|
1900
2327
|
return resolveSchemaMigrationAdapter(projectDir, override);
|
|
1901
2328
|
}
|
|
1902
2329
|
function dbRevisionOrphaned(appliedRevision, localRevisionIds) {
|
|
@@ -2369,7 +2796,7 @@ async function main() {
|
|
|
2369
2796
|
return 2;
|
|
2370
2797
|
}
|
|
2371
2798
|
const ghEnvLines = emitGithubEnvScalar("LAKEBASE_BRANCH_NAME", result.lakebaseName) + emitGithubEnvScalar("LAKEBASE_BRANCH_PATH", result.branchPath) + emitGithubEnvScalar("LAKEBASE_BRANCH_STATUS", result.status) + emitGithubEnvScalar("LAKEBASE_BRANCH_SOURCE", result.source) + emitGithubEnvScalar("LAKEBASE_HOST", result.host) + emitGithubEnvScalar("LAKEBASE_USERNAME", result.email) + emitGithubEnvHeredoc("LAKEBASE_PASSWORD", result.token) + emitGithubEnvScalar("DATABASE_URL", result.databaseUrl) + emitGithubEnvScalar("JDBC_URL", result.jdbcUrl) + emitGithubEnvScalar("DB_USERNAME", result.email) + emitGithubEnvHeredoc("DB_PASSWORD", result.token) + emitGithubEnvScalar("SPRING_DATASOURCE_URL", result.jdbcUrl) + emitGithubEnvScalar("SPRING_DATASOURCE_USERNAME", result.email) + emitGithubEnvHeredoc("SPRING_DATASOURCE_PASSWORD", result.token);
|
|
2372
|
-
|
|
2799
|
+
fs13.appendFileSync(ghEnvFile, ghEnvLines, { encoding: "utf8" });
|
|
2373
2800
|
process.stdout.write(emitEvalLine("LAKEBASE_BRANCH_NAME", result.lakebaseName));
|
|
2374
2801
|
process.stdout.write(emitEvalLine("LAKEBASE_BRANCH_PATH", result.branchPath));
|
|
2375
2802
|
process.stdout.write(emitEvalLine("LAKEBASE_BRANCH_STATUS", result.status));
|