@databricks-solutions/lakebase-scm-utils 0.2.51 → 0.3.1
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/README.md +10 -4
- package/dist/scripts/index.cjs +2813 -2244
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +857 -298
- 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 +2810 -2205
- package/dist/scripts/lakebase/index.d.cts +59 -10
- package/dist/scripts/lakebase/index.d.ts +59 -10
- package/dist/scripts/lakebase/index.js +851 -256
- 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/client-lockfile.ts +102 -0
- package/scripts/lakebase/collapse-heads.cli.ts +1 -1
- package/scripts/lakebase/create-project.ts +25 -2
- 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/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,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// scripts/lakebase/schema-migrate.ts
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
4
|
+
import * as fs12 from "fs";
|
|
5
|
+
import * as path13 from "path";
|
|
6
6
|
|
|
7
7
|
// scripts/lakebase/databricks-cli.ts
|
|
8
8
|
import { execFile, execFileSync as execFileSync2 } from "child_process";
|
|
@@ -280,9 +280,9 @@ function asBranchUid(s) {
|
|
|
280
280
|
}
|
|
281
281
|
return s;
|
|
282
282
|
}
|
|
283
|
-
function branchNameFromResourcePath(
|
|
284
|
-
if (!
|
|
285
|
-
const leaf =
|
|
283
|
+
function branchNameFromResourcePath(path14) {
|
|
284
|
+
if (!path14.includes("/branches/")) return null;
|
|
285
|
+
const leaf = path14.split("/branches/").pop();
|
|
286
286
|
if (!leaf) return null;
|
|
287
287
|
try {
|
|
288
288
|
return asBranchName(leaf);
|
|
@@ -427,8 +427,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
427
427
|
var cached;
|
|
428
428
|
function substrateSelfVersion() {
|
|
429
429
|
if (cached !== void 0) return cached;
|
|
430
|
-
if ("0.
|
|
431
|
-
cached = "0.
|
|
430
|
+
if ("0.3.1".length > 0) {
|
|
431
|
+
cached = "0.3.1";
|
|
432
432
|
return cached;
|
|
433
433
|
}
|
|
434
434
|
cached = "unknown";
|
|
@@ -537,23 +537,88 @@ function dbcli2(args) {
|
|
|
537
537
|
}
|
|
538
538
|
|
|
539
539
|
// scripts/lakebase/migration-layout.ts
|
|
540
|
+
import * as fs5 from "fs";
|
|
541
|
+
import * as path4 from "path";
|
|
542
|
+
|
|
543
|
+
// scripts/lakebase/dotnet-project.ts
|
|
540
544
|
import * as fs4 from "fs";
|
|
541
545
|
import * as path3 from "path";
|
|
546
|
+
var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
|
|
547
|
+
var DOTNET_FLYWAY_DIR = path3.join("db", "migrations");
|
|
548
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
|
|
549
|
+
function safeReaddir(dir) {
|
|
550
|
+
try {
|
|
551
|
+
return fs4.readdirSync(dir, { withFileTypes: true });
|
|
552
|
+
} catch {
|
|
553
|
+
return [];
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function hasDotnetMarker(dir) {
|
|
557
|
+
return safeReaddir(dir).some(
|
|
558
|
+
(e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
function findCsprojFiles(projectDir) {
|
|
562
|
+
const found = [];
|
|
563
|
+
const walk = (dir, depth) => {
|
|
564
|
+
for (const e of safeReaddir(dir)) {
|
|
565
|
+
const full = path3.join(dir, e.name);
|
|
566
|
+
if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
|
|
567
|
+
found.push(full);
|
|
568
|
+
} else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
|
|
569
|
+
walk(full, depth + 1);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
walk(projectDir, 0);
|
|
574
|
+
return found.sort();
|
|
575
|
+
}
|
|
576
|
+
function findEfCoreProject(projectDir) {
|
|
577
|
+
for (const csproj of findCsprojFiles(projectDir)) {
|
|
578
|
+
let content = "";
|
|
579
|
+
try {
|
|
580
|
+
content = fs4.readFileSync(csproj, "utf-8");
|
|
581
|
+
} catch {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (content.includes(EF_DESIGN_PACKAGE)) {
|
|
585
|
+
const dir = path3.dirname(csproj);
|
|
586
|
+
return { csproj, projectDir: dir, migrationsDir: path3.join(dir, "Migrations") };
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
function isDotnetProject(projectDir) {
|
|
592
|
+
return hasDotnetMarker(projectDir);
|
|
593
|
+
}
|
|
594
|
+
function isDotnetFlywayProject(projectDir) {
|
|
595
|
+
return isDotnetProject(projectDir) && fs4.existsSync(path3.join(projectDir, DOTNET_FLYWAY_DIR));
|
|
596
|
+
}
|
|
597
|
+
var MAVEN_FLYWAY_DIR = path3.join("src", "main", "resources", "db", "migration");
|
|
598
|
+
function flywayMigrationsDir(projectDir) {
|
|
599
|
+
const maven = path3.join(projectDir, MAVEN_FLYWAY_DIR);
|
|
600
|
+
if (fs4.existsSync(maven) || fs4.existsSync(path3.join(projectDir, "pom.xml"))) return maven;
|
|
601
|
+
if (isDotnetFlywayProject(projectDir)) return path3.join(projectDir, DOTNET_FLYWAY_DIR);
|
|
602
|
+
return maven;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// scripts/lakebase/migration-layout.ts
|
|
542
606
|
var MIGRATION_LANGUAGES = [
|
|
543
607
|
"java",
|
|
544
608
|
"kotlin",
|
|
545
609
|
"python",
|
|
546
610
|
"nodejs",
|
|
611
|
+
"dotnet",
|
|
547
612
|
"unknown"
|
|
548
613
|
];
|
|
549
614
|
function detectLanguageAt(dir) {
|
|
550
|
-
if (
|
|
551
|
-
const kotlinDir =
|
|
552
|
-
if (
|
|
615
|
+
if (fs5.existsSync(path4.join(dir, "pom.xml"))) {
|
|
616
|
+
const kotlinDir = path4.join(dir, "src", "main", "kotlin");
|
|
617
|
+
if (fs5.existsSync(kotlinDir)) {
|
|
553
618
|
return "kotlin";
|
|
554
619
|
}
|
|
555
620
|
try {
|
|
556
|
-
const pom =
|
|
621
|
+
const pom = fs5.readFileSync(path4.join(dir, "pom.xml"), "utf-8");
|
|
557
622
|
if (pom.includes("kotlin-maven-plugin")) {
|
|
558
623
|
return "kotlin";
|
|
559
624
|
}
|
|
@@ -561,10 +626,13 @@ function detectLanguageAt(dir) {
|
|
|
561
626
|
}
|
|
562
627
|
return "java";
|
|
563
628
|
}
|
|
564
|
-
if (
|
|
629
|
+
if (fs5.existsSync(path4.join(dir, "pyproject.toml")) || fs5.existsSync(path4.join(dir, "requirements.txt")) || fs5.existsSync(path4.join(dir, "alembic.ini"))) {
|
|
565
630
|
return "python";
|
|
566
631
|
}
|
|
567
|
-
if (
|
|
632
|
+
if (hasDotnetMarker(dir)) {
|
|
633
|
+
return "dotnet";
|
|
634
|
+
}
|
|
635
|
+
if (fs5.existsSync(path4.join(dir, "package.json"))) {
|
|
568
636
|
return "nodejs";
|
|
569
637
|
}
|
|
570
638
|
return "unknown";
|
|
@@ -585,14 +653,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
|
|
|
585
653
|
if (!rel) {
|
|
586
654
|
return "unknown";
|
|
587
655
|
}
|
|
588
|
-
const rootResolved =
|
|
589
|
-
let dir =
|
|
590
|
-
while (dir === rootResolved || dir.startsWith(rootResolved +
|
|
656
|
+
const rootResolved = path4.resolve(projectDir);
|
|
657
|
+
let dir = path4.resolve(projectDir, rel);
|
|
658
|
+
while (dir === rootResolved || dir.startsWith(rootResolved + path4.sep)) {
|
|
591
659
|
const lang = detectLanguageAt(dir);
|
|
592
660
|
if (lang !== "unknown") {
|
|
593
661
|
return lang;
|
|
594
662
|
}
|
|
595
|
-
const parent =
|
|
663
|
+
const parent = path4.dirname(dir);
|
|
596
664
|
if (parent === dir) {
|
|
597
665
|
break;
|
|
598
666
|
}
|
|
@@ -602,21 +670,21 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
|
|
|
602
670
|
}
|
|
603
671
|
|
|
604
672
|
// scripts/lakebase/adapters/alembic-adapter.ts
|
|
605
|
-
import * as
|
|
606
|
-
import * as
|
|
673
|
+
import * as fs7 from "fs";
|
|
674
|
+
import * as path6 from "path";
|
|
607
675
|
|
|
608
676
|
// scripts/lakebase/schema-migrate-runners/alembic.ts
|
|
609
677
|
import { spawn } from "child_process";
|
|
610
|
-
import * as
|
|
611
|
-
import * as
|
|
678
|
+
import * as fs6 from "fs";
|
|
679
|
+
import * as path5 from "path";
|
|
612
680
|
function resolveAlembicBin(projectDir) {
|
|
613
681
|
const candidates = [
|
|
614
|
-
|
|
615
|
-
|
|
682
|
+
path5.join(projectDir, ".venv", "bin", "alembic"),
|
|
683
|
+
path5.join(projectDir, "venv", "bin", "alembic")
|
|
616
684
|
];
|
|
617
685
|
for (const candidate of candidates) {
|
|
618
686
|
try {
|
|
619
|
-
if (
|
|
687
|
+
if (fs6.existsSync(candidate)) return candidate;
|
|
620
688
|
} catch {
|
|
621
689
|
}
|
|
622
690
|
}
|
|
@@ -626,7 +694,7 @@ function spawnAlembic(projectDir, args, dsn) {
|
|
|
626
694
|
return new Promise((resolve2, reject) => {
|
|
627
695
|
const bin = resolveAlembicBin(projectDir);
|
|
628
696
|
const env = { ...process.env };
|
|
629
|
-
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(
|
|
697
|
+
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path5.delimiter);
|
|
630
698
|
if (dsn) env.DATABASE_URL = dsn;
|
|
631
699
|
const child = spawn(bin, args, {
|
|
632
700
|
cwd: projectDir,
|
|
@@ -674,10 +742,10 @@ async function createAlembicRevision(opts) {
|
|
|
674
742
|
const m = stdout.match(/Generating\s+(\S+\.py)/);
|
|
675
743
|
if (m) return m[1].trim();
|
|
676
744
|
for (const rel of ["migrations/versions", "alembic/versions"]) {
|
|
677
|
-
const dir =
|
|
678
|
-
if (!
|
|
679
|
-
const hit =
|
|
680
|
-
if (hit) return
|
|
745
|
+
const dir = path5.join(opts.projectDir, rel);
|
|
746
|
+
if (!fs6.existsSync(dir)) continue;
|
|
747
|
+
const hit = fs6.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
|
|
748
|
+
if (hit) return path5.join(dir, hit);
|
|
681
749
|
}
|
|
682
750
|
throw new SchemaMigrationError(
|
|
683
751
|
`alembic revision succeeded but the created file could not be located.
|
|
@@ -811,15 +879,15 @@ async function buildDsn(args) {
|
|
|
811
879
|
}
|
|
812
880
|
function findVersionsDir(projectDir) {
|
|
813
881
|
const candidates = [
|
|
814
|
-
|
|
815
|
-
|
|
882
|
+
path6.join(projectDir, "migrations", "versions"),
|
|
883
|
+
path6.join(projectDir, "alembic", "versions")
|
|
816
884
|
];
|
|
817
|
-
return candidates.find((p) =>
|
|
885
|
+
return candidates.find((p) => fs7.existsSync(p));
|
|
818
886
|
}
|
|
819
887
|
function listAlembicFiles(projectDir) {
|
|
820
888
|
const dir = findVersionsDir(projectDir);
|
|
821
889
|
if (!dir) return [];
|
|
822
|
-
const files =
|
|
890
|
+
const files = fs7.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
|
|
823
891
|
return files.map((filename) => {
|
|
824
892
|
const stem = filename.replace(/\.py$/, "");
|
|
825
893
|
const sep2 = stem.indexOf("_");
|
|
@@ -844,9 +912,9 @@ var AlembicAdapter = {
|
|
|
844
912
|
* here. Callers can still force-select via project.yaml#migration_tool.
|
|
845
913
|
*/
|
|
846
914
|
detect(projectDir) {
|
|
847
|
-
if (
|
|
848
|
-
if (
|
|
849
|
-
if (
|
|
915
|
+
if (fs7.existsSync(path6.join(projectDir, "alembic.ini"))) return true;
|
|
916
|
+
if (fs7.existsSync(path6.join(projectDir, "migrations", "env.py"))) return true;
|
|
917
|
+
if (fs7.existsSync(path6.join(projectDir, "alembic", "env.py"))) return true;
|
|
850
918
|
return false;
|
|
851
919
|
},
|
|
852
920
|
async apply(args) {
|
|
@@ -952,7 +1020,7 @@ var AlembicAdapter = {
|
|
|
952
1020
|
autogenerate: !!args.autogenerate,
|
|
953
1021
|
dsn
|
|
954
1022
|
});
|
|
955
|
-
return { status: "ok", version: revId, filename:
|
|
1023
|
+
return { status: "ok", version: revId, filename: path6.basename(created), path: created };
|
|
956
1024
|
} catch (err) {
|
|
957
1025
|
return {
|
|
958
1026
|
status: "error",
|
|
@@ -969,7 +1037,7 @@ var AlembicAdapter = {
|
|
|
969
1037
|
if (heads.length <= 1) return { status: "noop", headsBefore: heads };
|
|
970
1038
|
if (args.dryRun) return { status: "ok", headsBefore: heads };
|
|
971
1039
|
const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
|
|
972
|
-
const mergeRevision =
|
|
1040
|
+
const mergeRevision = path6.basename(created).replace(/\.py$/, "").split("_")[0];
|
|
973
1041
|
return { status: "ok", headsBefore: heads, mergeRevision, path: created };
|
|
974
1042
|
} catch (err) {
|
|
975
1043
|
return {
|
|
@@ -983,12 +1051,12 @@ var AlembicAdapter = {
|
|
|
983
1051
|
registerSchemaMigrationAdapter(AlembicAdapter);
|
|
984
1052
|
|
|
985
1053
|
// scripts/lakebase/adapters/flyway-adapter.ts
|
|
986
|
-
import * as
|
|
987
|
-
import * as
|
|
1054
|
+
import * as fs8 from "fs";
|
|
1055
|
+
import * as path8 from "path";
|
|
988
1056
|
|
|
989
1057
|
// scripts/lakebase/schema-migrate-runners/flyway.ts
|
|
990
1058
|
import { spawn as spawn2 } from "child_process";
|
|
991
|
-
import * as
|
|
1059
|
+
import * as path7 from "path";
|
|
992
1060
|
function dsnToFlywayEnv(dsn) {
|
|
993
1061
|
const u = new URL(dsn);
|
|
994
1062
|
const user = decodeURIComponent(u.username);
|
|
@@ -998,7 +1066,7 @@ function dsnToFlywayEnv(dsn) {
|
|
|
998
1066
|
return { url, user, password };
|
|
999
1067
|
}
|
|
1000
1068
|
function migrationsLocation(projectDir) {
|
|
1001
|
-
return `filesystem:${
|
|
1069
|
+
return `filesystem:${flywayMigrationsDir(projectDir)}`;
|
|
1002
1070
|
}
|
|
1003
1071
|
function runFlyway(ctx, args) {
|
|
1004
1072
|
const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
|
|
@@ -1099,7 +1167,7 @@ async function statusFlyway(ctx) {
|
|
|
1099
1167
|
if (state === "SUCCESS" || state === "BASELINE") {
|
|
1100
1168
|
current = m.version;
|
|
1101
1169
|
} else if (state === "PENDING") {
|
|
1102
|
-
const filename = m.filepath ?
|
|
1170
|
+
const filename = m.filepath ? path7.basename(m.filepath) : `V${m.version}__migration.sql`;
|
|
1103
1171
|
pending.push({
|
|
1104
1172
|
version: m.version,
|
|
1105
1173
|
filename,
|
|
@@ -1122,9 +1190,9 @@ async function buildDsn2(args) {
|
|
|
1122
1190
|
return result.url;
|
|
1123
1191
|
}
|
|
1124
1192
|
function listFlywayFiles(projectDir) {
|
|
1125
|
-
const dir =
|
|
1126
|
-
if (!
|
|
1127
|
-
const files =
|
|
1193
|
+
const dir = flywayMigrationsDir(projectDir);
|
|
1194
|
+
if (!fs8.existsSync(dir)) return [];
|
|
1195
|
+
const files = fs8.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
|
|
1128
1196
|
return files.map((filename) => {
|
|
1129
1197
|
const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
|
|
1130
1198
|
const version = m[1];
|
|
@@ -1145,9 +1213,9 @@ function versionCompare(a, b) {
|
|
|
1145
1213
|
}
|
|
1146
1214
|
var FlywayAdapter = {
|
|
1147
1215
|
id: "flyway",
|
|
1148
|
-
languages: ["java", "kotlin"],
|
|
1216
|
+
languages: ["java", "kotlin", "dotnet"],
|
|
1149
1217
|
detect(projectDir) {
|
|
1150
|
-
return
|
|
1218
|
+
return fs8.existsSync(path8.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
|
|
1151
1219
|
},
|
|
1152
1220
|
async apply(args) {
|
|
1153
1221
|
const dsn = await buildDsn2(args);
|
|
@@ -1205,14 +1273,14 @@ var FlywayAdapter = {
|
|
|
1205
1273
|
// optional-protocol shape makes this additive.
|
|
1206
1274
|
async newMigration(args) {
|
|
1207
1275
|
try {
|
|
1208
|
-
const dir =
|
|
1209
|
-
|
|
1276
|
+
const dir = flywayMigrationsDir(args.projectDir);
|
|
1277
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1210
1278
|
const version = migrationTimestamp();
|
|
1211
1279
|
const slug = migrationSlug2(args.slug);
|
|
1212
1280
|
const filename = `V${version}__${slug}.sql`;
|
|
1213
|
-
const full =
|
|
1214
|
-
if (
|
|
1215
|
-
|
|
1281
|
+
const full = path8.join(dir, filename);
|
|
1282
|
+
if (fs8.existsSync(full)) throw new Error(`${filename} already exists`);
|
|
1283
|
+
fs8.writeFileSync(
|
|
1216
1284
|
full,
|
|
1217
1285
|
`-- V${version}: ${args.slug}
|
|
1218
1286
|
-- Flyway migration (write your DDL/DML below).
|
|
@@ -1234,18 +1302,18 @@ var FlywayAdapter = {
|
|
|
1234
1302
|
registerSchemaMigrationAdapter(FlywayAdapter);
|
|
1235
1303
|
|
|
1236
1304
|
// scripts/lakebase/adapters/knex-adapter.ts
|
|
1237
|
-
import * as
|
|
1238
|
-
import * as
|
|
1305
|
+
import * as fs10 from "fs";
|
|
1306
|
+
import * as path10 from "path";
|
|
1239
1307
|
|
|
1240
1308
|
// scripts/lakebase/schema-migrate-runners/knex.ts
|
|
1241
1309
|
import { spawn as spawn3 } from "child_process";
|
|
1242
|
-
import * as
|
|
1243
|
-
import * as
|
|
1310
|
+
import * as fs9 from "fs";
|
|
1311
|
+
import * as path9 from "path";
|
|
1244
1312
|
var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
1245
1313
|
function findKnexfile(projectDir) {
|
|
1246
1314
|
for (const name of KNEXFILE_VARIANTS) {
|
|
1247
|
-
const p =
|
|
1248
|
-
if (
|
|
1315
|
+
const p = path9.join(projectDir, name);
|
|
1316
|
+
if (fs9.existsSync(p)) return p;
|
|
1249
1317
|
}
|
|
1250
1318
|
return void 0;
|
|
1251
1319
|
}
|
|
@@ -1397,9 +1465,9 @@ async function buildDsn3(args) {
|
|
|
1397
1465
|
}
|
|
1398
1466
|
var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
1399
1467
|
function listKnexFiles(projectDir) {
|
|
1400
|
-
const dir =
|
|
1401
|
-
if (!
|
|
1402
|
-
const files =
|
|
1468
|
+
const dir = path10.join(projectDir, "migrations");
|
|
1469
|
+
if (!fs10.existsSync(dir)) return [];
|
|
1470
|
+
const files = fs10.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
|
|
1403
1471
|
return files.map((filename) => {
|
|
1404
1472
|
const stem = filename.replace(/\.(js|ts)$/, "");
|
|
1405
1473
|
const m = stem.match(/^(\d{14})_(.+)$/);
|
|
@@ -1419,7 +1487,7 @@ var KnexAdapter = {
|
|
|
1419
1487
|
* project.yaml#migration_tool.
|
|
1420
1488
|
*/
|
|
1421
1489
|
detect(projectDir) {
|
|
1422
|
-
return KNEXFILE_VARIANTS2.some((name) =>
|
|
1490
|
+
return KNEXFILE_VARIANTS2.some((name) => fs10.existsSync(path10.join(projectDir, name)));
|
|
1423
1491
|
},
|
|
1424
1492
|
async apply(args) {
|
|
1425
1493
|
const dsn = await buildDsn3(args);
|
|
@@ -1492,9 +1560,9 @@ var KnexAdapter = {
|
|
|
1492
1560
|
async newMigration(args) {
|
|
1493
1561
|
try {
|
|
1494
1562
|
const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
|
|
1495
|
-
const stem =
|
|
1563
|
+
const stem = path10.basename(created).replace(/\.(js|ts)$/, "");
|
|
1496
1564
|
const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
|
|
1497
|
-
return { status: "ok", version, filename:
|
|
1565
|
+
return { status: "ok", version, filename: path10.basename(created), path: created };
|
|
1498
1566
|
} catch (err) {
|
|
1499
1567
|
return {
|
|
1500
1568
|
status: "error",
|
|
@@ -1508,6 +1576,360 @@ var KnexAdapter = {
|
|
|
1508
1576
|
};
|
|
1509
1577
|
registerSchemaMigrationAdapter(KnexAdapter);
|
|
1510
1578
|
|
|
1579
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
1580
|
+
import * as path12 from "path";
|
|
1581
|
+
|
|
1582
|
+
// scripts/lakebase/schema-migrate-runners/efcore.ts
|
|
1583
|
+
import { spawn as spawn4 } from "child_process";
|
|
1584
|
+
import * as fs11 from "fs";
|
|
1585
|
+
import * as path11 from "path";
|
|
1586
|
+
import { Client as Client2 } from "pg";
|
|
1587
|
+
var MIGRATION_ID = /^(\d{14})_(.+)$/;
|
|
1588
|
+
function efVersion(id) {
|
|
1589
|
+
return MIGRATION_ID.exec(id)?.[1] ?? id;
|
|
1590
|
+
}
|
|
1591
|
+
function efDescription(id) {
|
|
1592
|
+
const name = MIGRATION_ID.exec(id)?.[2] ?? id;
|
|
1593
|
+
return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
1594
|
+
}
|
|
1595
|
+
function dsnToNpgsql(dsn) {
|
|
1596
|
+
const u = new URL(dsn);
|
|
1597
|
+
const parts = [
|
|
1598
|
+
`Host=${u.hostname}`,
|
|
1599
|
+
`Port=${u.port || "5432"}`,
|
|
1600
|
+
`Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
|
|
1601
|
+
];
|
|
1602
|
+
if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
|
|
1603
|
+
if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
|
|
1604
|
+
const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
|
|
1605
|
+
const ssl = {
|
|
1606
|
+
disable: "Disable",
|
|
1607
|
+
allow: "Allow",
|
|
1608
|
+
prefer: "Prefer",
|
|
1609
|
+
require: "Require",
|
|
1610
|
+
"verify-ca": "VerifyCA",
|
|
1611
|
+
"verify-full": "VerifyFull"
|
|
1612
|
+
};
|
|
1613
|
+
parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
|
|
1614
|
+
const app = u.searchParams.get("application_name");
|
|
1615
|
+
if (app) parts.push(`Application Name=${app}`);
|
|
1616
|
+
return parts.map((p) => p.replace(/;/g, "")).join(";");
|
|
1617
|
+
}
|
|
1618
|
+
function parseEfJson(stdout) {
|
|
1619
|
+
const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
|
|
1620
|
+
if (!body) {
|
|
1621
|
+
throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
|
|
1622
|
+
}
|
|
1623
|
+
try {
|
|
1624
|
+
return JSON.parse(body);
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
throw new SchemaMigrationError(
|
|
1627
|
+
`dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
|
|
1628
|
+
Body (first 400 chars): ${body.slice(0, 400)}`
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
function requireProject(projectDir) {
|
|
1633
|
+
const project = findEfCoreProject(projectDir);
|
|
1634
|
+
if (!project) {
|
|
1635
|
+
throw new SchemaMigrationError(
|
|
1636
|
+
`No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
return project;
|
|
1640
|
+
}
|
|
1641
|
+
function spawnDotnet(projectDir, args, env) {
|
|
1642
|
+
return new Promise((resolve2, reject) => {
|
|
1643
|
+
const child = spawn4("dotnet", args, {
|
|
1644
|
+
cwd: projectDir,
|
|
1645
|
+
env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
|
|
1646
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1647
|
+
});
|
|
1648
|
+
let stdout = "";
|
|
1649
|
+
let stderr = "";
|
|
1650
|
+
child.stdout.on("data", (chunk) => {
|
|
1651
|
+
stdout += chunk.toString("utf8");
|
|
1652
|
+
});
|
|
1653
|
+
child.stderr.on("data", (chunk) => {
|
|
1654
|
+
stderr += chunk.toString("utf8");
|
|
1655
|
+
});
|
|
1656
|
+
child.on("error", (err) => {
|
|
1657
|
+
reject(
|
|
1658
|
+
new SchemaMigrationError(
|
|
1659
|
+
`Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
|
|
1660
|
+
err
|
|
1661
|
+
)
|
|
1662
|
+
);
|
|
1663
|
+
});
|
|
1664
|
+
child.on("close", (code) => {
|
|
1665
|
+
if (code === 0) {
|
|
1666
|
+
resolve2({ stdout, stderr });
|
|
1667
|
+
} else {
|
|
1668
|
+
reject(
|
|
1669
|
+
new SchemaMigrationError(
|
|
1670
|
+
`dotnet ${args.join(" ")} exited with code ${code}.
|
|
1671
|
+
stdout: ${stdout}
|
|
1672
|
+
stderr: ${stderr}`
|
|
1673
|
+
)
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
var restored = /* @__PURE__ */ new Set();
|
|
1680
|
+
async function ensureRestored(projectDir, csproj) {
|
|
1681
|
+
if (restored.has(projectDir)) return;
|
|
1682
|
+
if (fs11.existsSync(path11.join(projectDir, ".config", "dotnet-tools.json"))) {
|
|
1683
|
+
await spawnDotnet(projectDir, ["tool", "restore"], {});
|
|
1684
|
+
}
|
|
1685
|
+
await spawnDotnet(projectDir, ["restore", csproj], {});
|
|
1686
|
+
restored.add(projectDir);
|
|
1687
|
+
}
|
|
1688
|
+
async function runEf(projectDir, args, dsn, opts = {}) {
|
|
1689
|
+
const project = requireProject(projectDir);
|
|
1690
|
+
const csproj = path11.relative(projectDir, project.csproj) || project.csproj;
|
|
1691
|
+
await ensureRestored(projectDir, csproj);
|
|
1692
|
+
const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
|
|
1693
|
+
if (opts.noBuild) full.push("--no-build");
|
|
1694
|
+
const env = {};
|
|
1695
|
+
if (dsn) {
|
|
1696
|
+
env.DATABASE_URL = dsn;
|
|
1697
|
+
if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
|
|
1698
|
+
}
|
|
1699
|
+
return spawnDotnet(projectDir, full, env);
|
|
1700
|
+
}
|
|
1701
|
+
async function listEfMigrations(ctx, opts = {}) {
|
|
1702
|
+
const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
|
|
1703
|
+
const entries = parseEfJson(stdout);
|
|
1704
|
+
if (ctx.dsn && entries.some((e) => e.applied === null)) {
|
|
1705
|
+
const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
|
|
1706
|
+
throw new SchemaMigrationError(
|
|
1707
|
+
`dotnet ef could not read migration state from the database (check connectivity and credentials).
|
|
1708
|
+
${warning || stderr}`.trim()
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
return entries;
|
|
1712
|
+
}
|
|
1713
|
+
async function readEfHistory(dsn) {
|
|
1714
|
+
const client = new Client2({ connectionString: dsn, application_name: connectionApplicationName() });
|
|
1715
|
+
await client.connect();
|
|
1716
|
+
try {
|
|
1717
|
+
const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
|
|
1718
|
+
if (!exists.rows[0]?.present) return null;
|
|
1719
|
+
const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
|
|
1720
|
+
return r.rows.map((row) => row.MigrationId);
|
|
1721
|
+
} finally {
|
|
1722
|
+
await client.end();
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
function toApplied(entries) {
|
|
1726
|
+
return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
|
|
1727
|
+
}
|
|
1728
|
+
async function statusEfcore(ctx) {
|
|
1729
|
+
const entries = await listEfMigrations(ctx);
|
|
1730
|
+
const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
|
|
1731
|
+
const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
|
|
1732
|
+
const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
|
|
1733
|
+
return { current, pending, tool: "efcore" };
|
|
1734
|
+
}
|
|
1735
|
+
async function applyEfcore(ctx) {
|
|
1736
|
+
const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
|
|
1737
|
+
await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
|
|
1738
|
+
const after = await listEfMigrations(ctx, { noBuild: true });
|
|
1739
|
+
const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
|
|
1740
|
+
return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
|
|
1741
|
+
}
|
|
1742
|
+
function resolveEfTarget(target, applied) {
|
|
1743
|
+
const t = target.trim();
|
|
1744
|
+
if (t === "0") return "0";
|
|
1745
|
+
const rel = /^-(\d+)$/.exec(t);
|
|
1746
|
+
if (rel) {
|
|
1747
|
+
const steps = Number(rel[1]);
|
|
1748
|
+
if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
|
|
1749
|
+
const index = applied.length - 1 - steps;
|
|
1750
|
+
if (index < -1) {
|
|
1751
|
+
throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
|
|
1752
|
+
}
|
|
1753
|
+
return index === -1 ? "0" : applied[index].id;
|
|
1754
|
+
}
|
|
1755
|
+
const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
|
|
1756
|
+
if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
|
|
1757
|
+
return match.id;
|
|
1758
|
+
}
|
|
1759
|
+
async function rollbackEfcore(ctx) {
|
|
1760
|
+
const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
|
|
1761
|
+
const targetId = resolveEfTarget(ctx.target, beforeEntries);
|
|
1762
|
+
await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
|
|
1763
|
+
const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
|
|
1764
|
+
return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
|
|
1765
|
+
}
|
|
1766
|
+
function efMigrationName(slug) {
|
|
1767
|
+
const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
1768
|
+
if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
|
|
1769
|
+
return /^[0-9]/.test(name) ? `M${name}` : name;
|
|
1770
|
+
}
|
|
1771
|
+
async function createEfcoreMigration(opts) {
|
|
1772
|
+
const project = requireProject(opts.projectDir);
|
|
1773
|
+
const name = efMigrationName(opts.slug);
|
|
1774
|
+
const existing = new Set(fs11.existsSync(project.migrationsDir) ? fs11.readdirSync(project.migrationsDir) : []);
|
|
1775
|
+
await runEf(opts.projectDir, ["migrations", "add", name]);
|
|
1776
|
+
const created = fs11.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
|
|
1777
|
+
if (created.length !== 1) {
|
|
1778
|
+
throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
|
|
1779
|
+
}
|
|
1780
|
+
return { id: created[0].replace(/\.cs$/, ""), path: path11.join(project.migrationsDir, created[0]) };
|
|
1781
|
+
}
|
|
1782
|
+
function localEfMigrationIds(projectDir) {
|
|
1783
|
+
const project = findEfCoreProject(projectDir);
|
|
1784
|
+
if (!project || !fs11.existsSync(project.migrationsDir)) return [];
|
|
1785
|
+
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();
|
|
1786
|
+
}
|
|
1787
|
+
function efProductVersion(project) {
|
|
1788
|
+
try {
|
|
1789
|
+
const csproj = fs11.readFileSync(project.csproj, "utf-8");
|
|
1790
|
+
const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
|
|
1791
|
+
if (m) return m[1];
|
|
1792
|
+
} catch {
|
|
1793
|
+
}
|
|
1794
|
+
return "unknown";
|
|
1795
|
+
}
|
|
1796
|
+
async function stampEfcore(ctx) {
|
|
1797
|
+
const project = requireProject(ctx.projectDir);
|
|
1798
|
+
const ids = localEfMigrationIds(ctx.projectDir);
|
|
1799
|
+
if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
|
|
1800
|
+
const rev = ctx.revision.trim();
|
|
1801
|
+
const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
|
|
1802
|
+
if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
|
|
1803
|
+
const toStamp = ids.slice(0, end + 1);
|
|
1804
|
+
const productVersion = efProductVersion(project);
|
|
1805
|
+
const client = new Client2({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
|
|
1806
|
+
await client.connect();
|
|
1807
|
+
try {
|
|
1808
|
+
await client.query("BEGIN");
|
|
1809
|
+
await client.query(
|
|
1810
|
+
`CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
|
|
1811
|
+
"MigrationId" character varying(150) NOT NULL,
|
|
1812
|
+
"ProductVersion" character varying(32) NOT NULL,
|
|
1813
|
+
CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
|
|
1814
|
+
);
|
|
1815
|
+
await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
|
|
1816
|
+
for (const id of toStamp) {
|
|
1817
|
+
await client.query(
|
|
1818
|
+
`INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
|
|
1819
|
+
ON CONFLICT ("MigrationId") DO NOTHING`,
|
|
1820
|
+
[id, productVersion]
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
await client.query("COMMIT");
|
|
1824
|
+
} catch (err) {
|
|
1825
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
1826
|
+
throw err;
|
|
1827
|
+
} finally {
|
|
1828
|
+
await client.end();
|
|
1829
|
+
}
|
|
1830
|
+
return efVersion(toStamp[toStamp.length - 1]);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
1834
|
+
async function buildDsn4(args) {
|
|
1835
|
+
const result = await getConnection({
|
|
1836
|
+
output: "dsn",
|
|
1837
|
+
instance: args.instance,
|
|
1838
|
+
branch: args.branch,
|
|
1839
|
+
database: args.database,
|
|
1840
|
+
endpointName: args.endpointName
|
|
1841
|
+
});
|
|
1842
|
+
return result.url;
|
|
1843
|
+
}
|
|
1844
|
+
function errMsg(err) {
|
|
1845
|
+
return err instanceof Error ? err.message : String(err);
|
|
1846
|
+
}
|
|
1847
|
+
function listEfCoreFiles(projectDir) {
|
|
1848
|
+
return localEfMigrationIds(projectDir).map((id) => ({
|
|
1849
|
+
version: efVersion(id),
|
|
1850
|
+
filename: `${id}.cs`,
|
|
1851
|
+
description: efDescription(id),
|
|
1852
|
+
type: "CSharp",
|
|
1853
|
+
tool: "efcore"
|
|
1854
|
+
}));
|
|
1855
|
+
}
|
|
1856
|
+
var EfCoreAdapter = {
|
|
1857
|
+
id: "efcore",
|
|
1858
|
+
languages: ["dotnet"],
|
|
1859
|
+
/** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
|
|
1860
|
+
detect(projectDir) {
|
|
1861
|
+
return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
|
|
1862
|
+
},
|
|
1863
|
+
async apply(args) {
|
|
1864
|
+
try {
|
|
1865
|
+
const dsn = await buildDsn4(args);
|
|
1866
|
+
const r = await applyEfcore({ projectDir: args.projectDir, dsn });
|
|
1867
|
+
return {
|
|
1868
|
+
applied_migrations: r.applied,
|
|
1869
|
+
status: r.alreadyAtLatest ? "noop" : "ok",
|
|
1870
|
+
tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
|
|
1871
|
+
};
|
|
1872
|
+
} catch (err) {
|
|
1873
|
+
return { applied_migrations: [], status: "error", error: errMsg(err) };
|
|
1874
|
+
}
|
|
1875
|
+
},
|
|
1876
|
+
async rollback(args) {
|
|
1877
|
+
try {
|
|
1878
|
+
const dsn = await buildDsn4(args);
|
|
1879
|
+
const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
|
|
1880
|
+
return {
|
|
1881
|
+
rolled_back: r.rolledBack,
|
|
1882
|
+
status: r.rolledBack.length === 0 ? "noop" : "ok",
|
|
1883
|
+
tool_specific: { tool: r.tool }
|
|
1884
|
+
};
|
|
1885
|
+
} catch (err) {
|
|
1886
|
+
return { rolled_back: [], status: "error", error: errMsg(err) };
|
|
1887
|
+
}
|
|
1888
|
+
},
|
|
1889
|
+
async stamp(args) {
|
|
1890
|
+
try {
|
|
1891
|
+
const dsn = await buildDsn4(args);
|
|
1892
|
+
const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
|
|
1893
|
+
return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
|
|
1894
|
+
} catch (err) {
|
|
1895
|
+
return { status: "error", stamped_revision: null, error: errMsg(err) };
|
|
1896
|
+
}
|
|
1897
|
+
},
|
|
1898
|
+
async status(args) {
|
|
1899
|
+
try {
|
|
1900
|
+
const dsn = await buildDsn4(args);
|
|
1901
|
+
const r = await statusEfcore({ projectDir: args.projectDir, dsn });
|
|
1902
|
+
return {
|
|
1903
|
+
applied_version: r.current ?? null,
|
|
1904
|
+
pending: r.pending,
|
|
1905
|
+
applied: [],
|
|
1906
|
+
status: "ok",
|
|
1907
|
+
tool_specific: { tool: r.tool }
|
|
1908
|
+
};
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
|
|
1911
|
+
}
|
|
1912
|
+
},
|
|
1913
|
+
async list(args) {
|
|
1914
|
+
return { files: listEfCoreFiles(args.projectDir) };
|
|
1915
|
+
},
|
|
1916
|
+
// baseline: use stamp (EF has no separate baseline concept).
|
|
1917
|
+
async newMigration(args) {
|
|
1918
|
+
try {
|
|
1919
|
+
const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
|
|
1920
|
+
return {
|
|
1921
|
+
status: "ok",
|
|
1922
|
+
version: efVersion(created.id),
|
|
1923
|
+
filename: path12.basename(created.path),
|
|
1924
|
+
path: created.path
|
|
1925
|
+
};
|
|
1926
|
+
} catch (err) {
|
|
1927
|
+
return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
registerSchemaMigrationAdapter(EfCoreAdapter);
|
|
1932
|
+
|
|
1511
1933
|
// scripts/lakebase/schema-migrate.ts
|
|
1512
1934
|
var SchemaMigrationError = class extends Error {
|
|
1513
1935
|
constructor(message, cause) {
|
|
@@ -1521,13 +1943,16 @@ function detectLanguage(projectDir) {
|
|
|
1521
1943
|
const lang = resolveMigrationLanguage(projectDir);
|
|
1522
1944
|
if (lang === "unknown") {
|
|
1523
1945
|
throw new SchemaMigrationError(
|
|
1524
|
-
`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.`
|
|
1946
|
+
`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.`
|
|
1525
1947
|
);
|
|
1526
1948
|
}
|
|
1527
1949
|
return lang;
|
|
1528
1950
|
}
|
|
1529
|
-
function toolForLanguage(language) {
|
|
1951
|
+
function toolForLanguage(language, projectDir) {
|
|
1530
1952
|
switch (language) {
|
|
1953
|
+
case "dotnet":
|
|
1954
|
+
if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
|
|
1955
|
+
return "efcore";
|
|
1531
1956
|
case "java":
|
|
1532
1957
|
case "kotlin":
|
|
1533
1958
|
return "flyway";
|
|
@@ -1540,8 +1965,10 @@ function toolForLanguage(language) {
|
|
|
1540
1965
|
function listSchemaMigrations(args = {}) {
|
|
1541
1966
|
const projectDir = args.projectDir ?? process.cwd();
|
|
1542
1967
|
const language = args.language ?? detectLanguage(projectDir);
|
|
1543
|
-
const tool = toolForLanguage(language);
|
|
1968
|
+
const tool = toolForLanguage(language, projectDir);
|
|
1544
1969
|
switch (tool) {
|
|
1970
|
+
case "efcore":
|
|
1971
|
+
return listEfCoreFiles(projectDir);
|
|
1545
1972
|
case "flyway":
|
|
1546
1973
|
return listFlywayMigrations(projectDir);
|
|
1547
1974
|
case "alembic":
|
|
@@ -1551,9 +1978,9 @@ function listSchemaMigrations(args = {}) {
|
|
|
1551
1978
|
}
|
|
1552
1979
|
}
|
|
1553
1980
|
function listFlywayMigrations(projectDir) {
|
|
1554
|
-
const dir =
|
|
1555
|
-
if (!
|
|
1556
|
-
const files =
|
|
1981
|
+
const dir = flywayMigrationsDir(projectDir);
|
|
1982
|
+
if (!fs12.existsSync(dir)) return [];
|
|
1983
|
+
const files = fs12.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
|
|
1557
1984
|
return files.map((filename) => {
|
|
1558
1985
|
const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
|
|
1559
1986
|
const version = m[1];
|
|
@@ -1563,12 +1990,12 @@ function listFlywayMigrations(projectDir) {
|
|
|
1563
1990
|
}
|
|
1564
1991
|
function listAlembicMigrations(projectDir) {
|
|
1565
1992
|
const candidates = [
|
|
1566
|
-
|
|
1567
|
-
|
|
1993
|
+
path13.join(projectDir, "migrations", "versions"),
|
|
1994
|
+
path13.join(projectDir, "alembic", "versions")
|
|
1568
1995
|
];
|
|
1569
|
-
const dir = candidates.find((p) =>
|
|
1996
|
+
const dir = candidates.find((p) => fs12.existsSync(p));
|
|
1570
1997
|
if (!dir) return [];
|
|
1571
|
-
const files =
|
|
1998
|
+
const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
|
|
1572
1999
|
return files.map((filename) => {
|
|
1573
2000
|
const stem = filename.replace(/\.py$/, "");
|
|
1574
2001
|
const sep2 = stem.indexOf("_");
|
|
@@ -1578,9 +2005,9 @@ function listAlembicMigrations(projectDir) {
|
|
|
1578
2005
|
}).sort((a, b) => a.filename.localeCompare(b.filename));
|
|
1579
2006
|
}
|
|
1580
2007
|
function listKnexMigrations(projectDir) {
|
|
1581
|
-
const dir =
|
|
1582
|
-
if (!
|
|
1583
|
-
const files =
|
|
2008
|
+
const dir = path13.join(projectDir, "migrations");
|
|
2009
|
+
if (!fs12.existsSync(dir)) return [];
|
|
2010
|
+
const files = fs12.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
|
|
1584
2011
|
return files.map((filename) => {
|
|
1585
2012
|
const stem = filename.replace(/\.(js|ts)$/, "");
|
|
1586
2013
|
const m = stem.match(/^(\d{14})_(.+)$/);
|
|
@@ -1602,7 +2029,7 @@ function versionCompare2(a, b) {
|
|
|
1602
2029
|
return 0;
|
|
1603
2030
|
}
|
|
1604
2031
|
function adapterFor(projectDir, language) {
|
|
1605
|
-
const override = language ? toolForLanguage(language) : void 0;
|
|
2032
|
+
const override = language ? toolForLanguage(language, projectDir) : void 0;
|
|
1606
2033
|
return resolveSchemaMigrationAdapter(projectDir, override);
|
|
1607
2034
|
}
|
|
1608
2035
|
var TierMigrationRefusedError = class extends Error {
|
|
@@ -1775,7 +2202,7 @@ Common flags (for apply, rollback, status):
|
|
|
1775
2202
|
--instance <id> Lakebase project id (required)
|
|
1776
2203
|
--branch <name> Branch to migrate against (required)
|
|
1777
2204
|
--project-dir <dir> Project root (default: cwd)
|
|
1778
|
-
--language <lang> java | kotlin | python | nodejs (default: auto-detect)
|
|
2205
|
+
--language <lang> java | kotlin | python | nodejs | dotnet (default: auto-detect)
|
|
1779
2206
|
--database <db> Database name (default: $PGDATABASE or "databricks_postgres")
|
|
1780
2207
|
--endpoint <name> Endpoint identifier on the branch (default: "primary")
|
|
1781
2208
|
|
|
@@ -1796,6 +2223,8 @@ Language support today:
|
|
|
1796
2223
|
python (alembic) Full: apply, rollback, status, list
|
|
1797
2224
|
java, kotlin Full: apply, status, list (rollback unsupported in Flyway Community Edition)
|
|
1798
2225
|
nodejs (knex) Full: apply, rollback, status, list
|
|
2226
|
+
dotnet (efcore) Full: apply, rollback, status, list (stamp via the adapter)
|
|
2227
|
+
dotnet (flyway) Full: apply, status, list (db/migrations/; rollback unsupported)
|
|
1799
2228
|
`;
|
|
1800
2229
|
}
|
|
1801
2230
|
function printJson(result, pretty) {
|