@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
|
@@ -276,9 +276,9 @@ function asBranchUid(s) {
|
|
|
276
276
|
}
|
|
277
277
|
return s;
|
|
278
278
|
}
|
|
279
|
-
function branchNameFromResourcePath(
|
|
280
|
-
if (!
|
|
281
|
-
const leaf =
|
|
279
|
+
function branchNameFromResourcePath(path15) {
|
|
280
|
+
if (!path15.includes("/branches/")) return null;
|
|
281
|
+
const leaf = path15.split("/branches/").pop();
|
|
282
282
|
if (!leaf) return null;
|
|
283
283
|
try {
|
|
284
284
|
return asBranchName(leaf);
|
|
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
389
389
|
var cached;
|
|
390
390
|
function substrateSelfVersion() {
|
|
391
391
|
if (cached !== void 0) return cached;
|
|
392
|
-
if ("0.
|
|
393
|
-
cached = "0.
|
|
392
|
+
if ("0.3.0".length > 0) {
|
|
393
|
+
cached = "0.3.0";
|
|
394
394
|
return cached;
|
|
395
395
|
}
|
|
396
396
|
cached = "unknown";
|
|
@@ -499,27 +499,92 @@ function dbcli2(args) {
|
|
|
499
499
|
}
|
|
500
500
|
|
|
501
501
|
// scripts/lakebase/schema-migrate.ts
|
|
502
|
-
import * as
|
|
503
|
-
import * as
|
|
502
|
+
import * as fs12 from "fs";
|
|
503
|
+
import * as path13 from "path";
|
|
504
504
|
|
|
505
505
|
// scripts/lakebase/migration-layout.ts
|
|
506
|
+
import * as fs5 from "fs";
|
|
507
|
+
import * as path4 from "path";
|
|
508
|
+
|
|
509
|
+
// scripts/lakebase/dotnet-project.ts
|
|
506
510
|
import * as fs4 from "fs";
|
|
507
511
|
import * as path3 from "path";
|
|
512
|
+
var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
|
|
513
|
+
var DOTNET_FLYWAY_DIR = path3.join("db", "migrations");
|
|
514
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
|
|
515
|
+
function safeReaddir(dir) {
|
|
516
|
+
try {
|
|
517
|
+
return fs4.readdirSync(dir, { withFileTypes: true });
|
|
518
|
+
} catch {
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function hasDotnetMarker(dir) {
|
|
523
|
+
return safeReaddir(dir).some(
|
|
524
|
+
(e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
function findCsprojFiles(projectDir) {
|
|
528
|
+
const found = [];
|
|
529
|
+
const walk = (dir, depth) => {
|
|
530
|
+
for (const e of safeReaddir(dir)) {
|
|
531
|
+
const full = path3.join(dir, e.name);
|
|
532
|
+
if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
|
|
533
|
+
found.push(full);
|
|
534
|
+
} else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
|
|
535
|
+
walk(full, depth + 1);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
walk(projectDir, 0);
|
|
540
|
+
return found.sort();
|
|
541
|
+
}
|
|
542
|
+
function findEfCoreProject(projectDir) {
|
|
543
|
+
for (const csproj of findCsprojFiles(projectDir)) {
|
|
544
|
+
let content = "";
|
|
545
|
+
try {
|
|
546
|
+
content = fs4.readFileSync(csproj, "utf-8");
|
|
547
|
+
} catch {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (content.includes(EF_DESIGN_PACKAGE)) {
|
|
551
|
+
const dir = path3.dirname(csproj);
|
|
552
|
+
return { csproj, projectDir: dir, migrationsDir: path3.join(dir, "Migrations") };
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
function isDotnetProject(projectDir) {
|
|
558
|
+
return hasDotnetMarker(projectDir);
|
|
559
|
+
}
|
|
560
|
+
function isDotnetFlywayProject(projectDir) {
|
|
561
|
+
return isDotnetProject(projectDir) && fs4.existsSync(path3.join(projectDir, DOTNET_FLYWAY_DIR));
|
|
562
|
+
}
|
|
563
|
+
var MAVEN_FLYWAY_DIR = path3.join("src", "main", "resources", "db", "migration");
|
|
564
|
+
function flywayMigrationsDir(projectDir) {
|
|
565
|
+
const maven = path3.join(projectDir, MAVEN_FLYWAY_DIR);
|
|
566
|
+
if (fs4.existsSync(maven) || fs4.existsSync(path3.join(projectDir, "pom.xml"))) return maven;
|
|
567
|
+
if (isDotnetFlywayProject(projectDir)) return path3.join(projectDir, DOTNET_FLYWAY_DIR);
|
|
568
|
+
return maven;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// scripts/lakebase/migration-layout.ts
|
|
508
572
|
var MIGRATION_LANGUAGES = [
|
|
509
573
|
"java",
|
|
510
574
|
"kotlin",
|
|
511
575
|
"python",
|
|
512
576
|
"nodejs",
|
|
577
|
+
"dotnet",
|
|
513
578
|
"unknown"
|
|
514
579
|
];
|
|
515
580
|
function detectLanguageAt(dir) {
|
|
516
|
-
if (
|
|
517
|
-
const kotlinDir =
|
|
518
|
-
if (
|
|
581
|
+
if (fs5.existsSync(path4.join(dir, "pom.xml"))) {
|
|
582
|
+
const kotlinDir = path4.join(dir, "src", "main", "kotlin");
|
|
583
|
+
if (fs5.existsSync(kotlinDir)) {
|
|
519
584
|
return "kotlin";
|
|
520
585
|
}
|
|
521
586
|
try {
|
|
522
|
-
const pom =
|
|
587
|
+
const pom = fs5.readFileSync(path4.join(dir, "pom.xml"), "utf-8");
|
|
523
588
|
if (pom.includes("kotlin-maven-plugin")) {
|
|
524
589
|
return "kotlin";
|
|
525
590
|
}
|
|
@@ -527,10 +592,13 @@ function detectLanguageAt(dir) {
|
|
|
527
592
|
}
|
|
528
593
|
return "java";
|
|
529
594
|
}
|
|
530
|
-
if (
|
|
595
|
+
if (fs5.existsSync(path4.join(dir, "pyproject.toml")) || fs5.existsSync(path4.join(dir, "requirements.txt")) || fs5.existsSync(path4.join(dir, "alembic.ini"))) {
|
|
531
596
|
return "python";
|
|
532
597
|
}
|
|
533
|
-
if (
|
|
598
|
+
if (hasDotnetMarker(dir)) {
|
|
599
|
+
return "dotnet";
|
|
600
|
+
}
|
|
601
|
+
if (fs5.existsSync(path4.join(dir, "package.json"))) {
|
|
534
602
|
return "nodejs";
|
|
535
603
|
}
|
|
536
604
|
return "unknown";
|
|
@@ -551,14 +619,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
|
|
|
551
619
|
if (!rel) {
|
|
552
620
|
return "unknown";
|
|
553
621
|
}
|
|
554
|
-
const rootResolved =
|
|
555
|
-
let dir =
|
|
556
|
-
while (dir === rootResolved || dir.startsWith(rootResolved +
|
|
622
|
+
const rootResolved = path4.resolve(projectDir);
|
|
623
|
+
let dir = path4.resolve(projectDir, rel);
|
|
624
|
+
while (dir === rootResolved || dir.startsWith(rootResolved + path4.sep)) {
|
|
557
625
|
const lang = detectLanguageAt(dir);
|
|
558
626
|
if (lang !== "unknown") {
|
|
559
627
|
return lang;
|
|
560
628
|
}
|
|
561
|
-
const parent =
|
|
629
|
+
const parent = path4.dirname(dir);
|
|
562
630
|
if (parent === dir) {
|
|
563
631
|
break;
|
|
564
632
|
}
|
|
@@ -568,21 +636,21 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
|
|
|
568
636
|
}
|
|
569
637
|
|
|
570
638
|
// scripts/lakebase/adapters/alembic-adapter.ts
|
|
571
|
-
import * as
|
|
572
|
-
import * as
|
|
639
|
+
import * as fs7 from "fs";
|
|
640
|
+
import * as path6 from "path";
|
|
573
641
|
|
|
574
642
|
// scripts/lakebase/schema-migrate-runners/alembic.ts
|
|
575
643
|
import { spawn } from "child_process";
|
|
576
|
-
import * as
|
|
577
|
-
import * as
|
|
644
|
+
import * as fs6 from "fs";
|
|
645
|
+
import * as path5 from "path";
|
|
578
646
|
function resolveAlembicBin(projectDir) {
|
|
579
647
|
const candidates = [
|
|
580
|
-
|
|
581
|
-
|
|
648
|
+
path5.join(projectDir, ".venv", "bin", "alembic"),
|
|
649
|
+
path5.join(projectDir, "venv", "bin", "alembic")
|
|
582
650
|
];
|
|
583
651
|
for (const candidate of candidates) {
|
|
584
652
|
try {
|
|
585
|
-
if (
|
|
653
|
+
if (fs6.existsSync(candidate)) return candidate;
|
|
586
654
|
} catch {
|
|
587
655
|
}
|
|
588
656
|
}
|
|
@@ -592,7 +660,7 @@ function spawnAlembic(projectDir, args, dsn) {
|
|
|
592
660
|
return new Promise((resolve2, reject) => {
|
|
593
661
|
const bin = resolveAlembicBin(projectDir);
|
|
594
662
|
const env = { ...process.env };
|
|
595
|
-
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(
|
|
663
|
+
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path5.delimiter);
|
|
596
664
|
if (dsn) env.DATABASE_URL = dsn;
|
|
597
665
|
const child = spawn(bin, args, {
|
|
598
666
|
cwd: projectDir,
|
|
@@ -640,10 +708,10 @@ async function createAlembicRevision(opts) {
|
|
|
640
708
|
const m = stdout.match(/Generating\s+(\S+\.py)/);
|
|
641
709
|
if (m) return m[1].trim();
|
|
642
710
|
for (const rel of ["migrations/versions", "alembic/versions"]) {
|
|
643
|
-
const dir =
|
|
644
|
-
if (!
|
|
645
|
-
const hit =
|
|
646
|
-
if (hit) return
|
|
711
|
+
const dir = path5.join(opts.projectDir, rel);
|
|
712
|
+
if (!fs6.existsSync(dir)) continue;
|
|
713
|
+
const hit = fs6.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
|
|
714
|
+
if (hit) return path5.join(dir, hit);
|
|
647
715
|
}
|
|
648
716
|
throw new SchemaMigrationError(
|
|
649
717
|
`alembic revision succeeded but the created file could not be located.
|
|
@@ -777,15 +845,15 @@ async function buildDsn(args) {
|
|
|
777
845
|
}
|
|
778
846
|
function findVersionsDir(projectDir) {
|
|
779
847
|
const candidates = [
|
|
780
|
-
|
|
781
|
-
|
|
848
|
+
path6.join(projectDir, "migrations", "versions"),
|
|
849
|
+
path6.join(projectDir, "alembic", "versions")
|
|
782
850
|
];
|
|
783
|
-
return candidates.find((p) =>
|
|
851
|
+
return candidates.find((p) => fs7.existsSync(p));
|
|
784
852
|
}
|
|
785
853
|
function listAlembicFiles(projectDir) {
|
|
786
854
|
const dir = findVersionsDir(projectDir);
|
|
787
855
|
if (!dir) return [];
|
|
788
|
-
const files =
|
|
856
|
+
const files = fs7.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
|
|
789
857
|
return files.map((filename) => {
|
|
790
858
|
const stem = filename.replace(/\.py$/, "");
|
|
791
859
|
const sep2 = stem.indexOf("_");
|
|
@@ -810,9 +878,9 @@ var AlembicAdapter = {
|
|
|
810
878
|
* here. Callers can still force-select via project.yaml#migration_tool.
|
|
811
879
|
*/
|
|
812
880
|
detect(projectDir) {
|
|
813
|
-
if (
|
|
814
|
-
if (
|
|
815
|
-
if (
|
|
881
|
+
if (fs7.existsSync(path6.join(projectDir, "alembic.ini"))) return true;
|
|
882
|
+
if (fs7.existsSync(path6.join(projectDir, "migrations", "env.py"))) return true;
|
|
883
|
+
if (fs7.existsSync(path6.join(projectDir, "alembic", "env.py"))) return true;
|
|
816
884
|
return false;
|
|
817
885
|
},
|
|
818
886
|
async apply(args) {
|
|
@@ -918,7 +986,7 @@ var AlembicAdapter = {
|
|
|
918
986
|
autogenerate: !!args.autogenerate,
|
|
919
987
|
dsn
|
|
920
988
|
});
|
|
921
|
-
return { status: "ok", version: revId, filename:
|
|
989
|
+
return { status: "ok", version: revId, filename: path6.basename(created), path: created };
|
|
922
990
|
} catch (err) {
|
|
923
991
|
return {
|
|
924
992
|
status: "error",
|
|
@@ -935,7 +1003,7 @@ var AlembicAdapter = {
|
|
|
935
1003
|
if (heads.length <= 1) return { status: "noop", headsBefore: heads };
|
|
936
1004
|
if (args.dryRun) return { status: "ok", headsBefore: heads };
|
|
937
1005
|
const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
|
|
938
|
-
const mergeRevision =
|
|
1006
|
+
const mergeRevision = path6.basename(created).replace(/\.py$/, "").split("_")[0];
|
|
939
1007
|
return { status: "ok", headsBefore: heads, mergeRevision, path: created };
|
|
940
1008
|
} catch (err) {
|
|
941
1009
|
return {
|
|
@@ -949,12 +1017,12 @@ var AlembicAdapter = {
|
|
|
949
1017
|
registerSchemaMigrationAdapter(AlembicAdapter);
|
|
950
1018
|
|
|
951
1019
|
// scripts/lakebase/adapters/flyway-adapter.ts
|
|
952
|
-
import * as
|
|
953
|
-
import * as
|
|
1020
|
+
import * as fs8 from "fs";
|
|
1021
|
+
import * as path8 from "path";
|
|
954
1022
|
|
|
955
1023
|
// scripts/lakebase/schema-migrate-runners/flyway.ts
|
|
956
1024
|
import { spawn as spawn2 } from "child_process";
|
|
957
|
-
import * as
|
|
1025
|
+
import * as path7 from "path";
|
|
958
1026
|
function dsnToFlywayEnv(dsn) {
|
|
959
1027
|
const u = new URL(dsn);
|
|
960
1028
|
const user = decodeURIComponent(u.username);
|
|
@@ -964,7 +1032,7 @@ function dsnToFlywayEnv(dsn) {
|
|
|
964
1032
|
return { url, user, password };
|
|
965
1033
|
}
|
|
966
1034
|
function migrationsLocation(projectDir) {
|
|
967
|
-
return `filesystem:${
|
|
1035
|
+
return `filesystem:${flywayMigrationsDir(projectDir)}`;
|
|
968
1036
|
}
|
|
969
1037
|
function runFlyway(ctx, args) {
|
|
970
1038
|
const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
|
|
@@ -1065,7 +1133,7 @@ async function statusFlyway(ctx) {
|
|
|
1065
1133
|
if (state === "SUCCESS" || state === "BASELINE") {
|
|
1066
1134
|
current = m.version;
|
|
1067
1135
|
} else if (state === "PENDING") {
|
|
1068
|
-
const filename = m.filepath ?
|
|
1136
|
+
const filename = m.filepath ? path7.basename(m.filepath) : `V${m.version}__migration.sql`;
|
|
1069
1137
|
pending.push({
|
|
1070
1138
|
version: m.version,
|
|
1071
1139
|
filename,
|
|
@@ -1088,9 +1156,9 @@ async function buildDsn2(args) {
|
|
|
1088
1156
|
return result.url;
|
|
1089
1157
|
}
|
|
1090
1158
|
function listFlywayFiles(projectDir) {
|
|
1091
|
-
const dir =
|
|
1092
|
-
if (!
|
|
1093
|
-
const files =
|
|
1159
|
+
const dir = flywayMigrationsDir(projectDir);
|
|
1160
|
+
if (!fs8.existsSync(dir)) return [];
|
|
1161
|
+
const files = fs8.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
|
|
1094
1162
|
return files.map((filename) => {
|
|
1095
1163
|
const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
|
|
1096
1164
|
const version = m[1];
|
|
@@ -1111,9 +1179,9 @@ function versionCompare(a, b) {
|
|
|
1111
1179
|
}
|
|
1112
1180
|
var FlywayAdapter = {
|
|
1113
1181
|
id: "flyway",
|
|
1114
|
-
languages: ["java", "kotlin"],
|
|
1182
|
+
languages: ["java", "kotlin", "dotnet"],
|
|
1115
1183
|
detect(projectDir) {
|
|
1116
|
-
return
|
|
1184
|
+
return fs8.existsSync(path8.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
|
|
1117
1185
|
},
|
|
1118
1186
|
async apply(args) {
|
|
1119
1187
|
const dsn = await buildDsn2(args);
|
|
@@ -1171,14 +1239,14 @@ var FlywayAdapter = {
|
|
|
1171
1239
|
// optional-protocol shape makes this additive.
|
|
1172
1240
|
async newMigration(args) {
|
|
1173
1241
|
try {
|
|
1174
|
-
const dir =
|
|
1175
|
-
|
|
1242
|
+
const dir = flywayMigrationsDir(args.projectDir);
|
|
1243
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1176
1244
|
const version = migrationTimestamp();
|
|
1177
1245
|
const slug = migrationSlug2(args.slug);
|
|
1178
1246
|
const filename = `V${version}__${slug}.sql`;
|
|
1179
|
-
const full =
|
|
1180
|
-
if (
|
|
1181
|
-
|
|
1247
|
+
const full = path8.join(dir, filename);
|
|
1248
|
+
if (fs8.existsSync(full)) throw new Error(`${filename} already exists`);
|
|
1249
|
+
fs8.writeFileSync(
|
|
1182
1250
|
full,
|
|
1183
1251
|
`-- V${version}: ${args.slug}
|
|
1184
1252
|
-- Flyway migration (write your DDL/DML below).
|
|
@@ -1200,18 +1268,18 @@ var FlywayAdapter = {
|
|
|
1200
1268
|
registerSchemaMigrationAdapter(FlywayAdapter);
|
|
1201
1269
|
|
|
1202
1270
|
// scripts/lakebase/adapters/knex-adapter.ts
|
|
1203
|
-
import * as
|
|
1204
|
-
import * as
|
|
1271
|
+
import * as fs10 from "fs";
|
|
1272
|
+
import * as path10 from "path";
|
|
1205
1273
|
|
|
1206
1274
|
// scripts/lakebase/schema-migrate-runners/knex.ts
|
|
1207
1275
|
import { spawn as spawn3 } from "child_process";
|
|
1208
|
-
import * as
|
|
1209
|
-
import * as
|
|
1276
|
+
import * as fs9 from "fs";
|
|
1277
|
+
import * as path9 from "path";
|
|
1210
1278
|
var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
1211
1279
|
function findKnexfile(projectDir) {
|
|
1212
1280
|
for (const name of KNEXFILE_VARIANTS) {
|
|
1213
|
-
const p =
|
|
1214
|
-
if (
|
|
1281
|
+
const p = path9.join(projectDir, name);
|
|
1282
|
+
if (fs9.existsSync(p)) return p;
|
|
1215
1283
|
}
|
|
1216
1284
|
return void 0;
|
|
1217
1285
|
}
|
|
@@ -1363,9 +1431,9 @@ async function buildDsn3(args) {
|
|
|
1363
1431
|
}
|
|
1364
1432
|
var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
1365
1433
|
function listKnexFiles(projectDir) {
|
|
1366
|
-
const dir =
|
|
1367
|
-
if (!
|
|
1368
|
-
const files =
|
|
1434
|
+
const dir = path10.join(projectDir, "migrations");
|
|
1435
|
+
if (!fs10.existsSync(dir)) return [];
|
|
1436
|
+
const files = fs10.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
|
|
1369
1437
|
return files.map((filename) => {
|
|
1370
1438
|
const stem = filename.replace(/\.(js|ts)$/, "");
|
|
1371
1439
|
const m = stem.match(/^(\d{14})_(.+)$/);
|
|
@@ -1385,7 +1453,7 @@ var KnexAdapter = {
|
|
|
1385
1453
|
* project.yaml#migration_tool.
|
|
1386
1454
|
*/
|
|
1387
1455
|
detect(projectDir) {
|
|
1388
|
-
return KNEXFILE_VARIANTS2.some((name) =>
|
|
1456
|
+
return KNEXFILE_VARIANTS2.some((name) => fs10.existsSync(path10.join(projectDir, name)));
|
|
1389
1457
|
},
|
|
1390
1458
|
async apply(args) {
|
|
1391
1459
|
const dsn = await buildDsn3(args);
|
|
@@ -1458,9 +1526,9 @@ var KnexAdapter = {
|
|
|
1458
1526
|
async newMigration(args) {
|
|
1459
1527
|
try {
|
|
1460
1528
|
const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
|
|
1461
|
-
const stem =
|
|
1529
|
+
const stem = path10.basename(created).replace(/\.(js|ts)$/, "");
|
|
1462
1530
|
const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
|
|
1463
|
-
return { status: "ok", version, filename:
|
|
1531
|
+
return { status: "ok", version, filename: path10.basename(created), path: created };
|
|
1464
1532
|
} catch (err) {
|
|
1465
1533
|
return {
|
|
1466
1534
|
status: "error",
|
|
@@ -1474,6 +1542,360 @@ var KnexAdapter = {
|
|
|
1474
1542
|
};
|
|
1475
1543
|
registerSchemaMigrationAdapter(KnexAdapter);
|
|
1476
1544
|
|
|
1545
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
1546
|
+
import * as path12 from "path";
|
|
1547
|
+
|
|
1548
|
+
// scripts/lakebase/schema-migrate-runners/efcore.ts
|
|
1549
|
+
import { spawn as spawn4 } from "child_process";
|
|
1550
|
+
import * as fs11 from "fs";
|
|
1551
|
+
import * as path11 from "path";
|
|
1552
|
+
import { Client as Client2 } from "pg";
|
|
1553
|
+
var MIGRATION_ID = /^(\d{14})_(.+)$/;
|
|
1554
|
+
function efVersion(id) {
|
|
1555
|
+
return MIGRATION_ID.exec(id)?.[1] ?? id;
|
|
1556
|
+
}
|
|
1557
|
+
function efDescription(id) {
|
|
1558
|
+
const name = MIGRATION_ID.exec(id)?.[2] ?? id;
|
|
1559
|
+
return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
1560
|
+
}
|
|
1561
|
+
function dsnToNpgsql(dsn) {
|
|
1562
|
+
const u = new URL(dsn);
|
|
1563
|
+
const parts = [
|
|
1564
|
+
`Host=${u.hostname}`,
|
|
1565
|
+
`Port=${u.port || "5432"}`,
|
|
1566
|
+
`Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
|
|
1567
|
+
];
|
|
1568
|
+
if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
|
|
1569
|
+
if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
|
|
1570
|
+
const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
|
|
1571
|
+
const ssl = {
|
|
1572
|
+
disable: "Disable",
|
|
1573
|
+
allow: "Allow",
|
|
1574
|
+
prefer: "Prefer",
|
|
1575
|
+
require: "Require",
|
|
1576
|
+
"verify-ca": "VerifyCA",
|
|
1577
|
+
"verify-full": "VerifyFull"
|
|
1578
|
+
};
|
|
1579
|
+
parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
|
|
1580
|
+
const app = u.searchParams.get("application_name");
|
|
1581
|
+
if (app) parts.push(`Application Name=${app}`);
|
|
1582
|
+
return parts.map((p) => p.replace(/;/g, "")).join(";");
|
|
1583
|
+
}
|
|
1584
|
+
function parseEfJson(stdout) {
|
|
1585
|
+
const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
|
|
1586
|
+
if (!body) {
|
|
1587
|
+
throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
|
|
1588
|
+
}
|
|
1589
|
+
try {
|
|
1590
|
+
return JSON.parse(body);
|
|
1591
|
+
} catch (err) {
|
|
1592
|
+
throw new SchemaMigrationError(
|
|
1593
|
+
`dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
|
|
1594
|
+
Body (first 400 chars): ${body.slice(0, 400)}`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
function requireProject(projectDir) {
|
|
1599
|
+
const project = findEfCoreProject(projectDir);
|
|
1600
|
+
if (!project) {
|
|
1601
|
+
throw new SchemaMigrationError(
|
|
1602
|
+
`No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
return project;
|
|
1606
|
+
}
|
|
1607
|
+
function spawnDotnet(projectDir, args, env) {
|
|
1608
|
+
return new Promise((resolve2, reject) => {
|
|
1609
|
+
const child = spawn4("dotnet", args, {
|
|
1610
|
+
cwd: projectDir,
|
|
1611
|
+
env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
|
|
1612
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1613
|
+
});
|
|
1614
|
+
let stdout = "";
|
|
1615
|
+
let stderr = "";
|
|
1616
|
+
child.stdout.on("data", (chunk) => {
|
|
1617
|
+
stdout += chunk.toString("utf8");
|
|
1618
|
+
});
|
|
1619
|
+
child.stderr.on("data", (chunk) => {
|
|
1620
|
+
stderr += chunk.toString("utf8");
|
|
1621
|
+
});
|
|
1622
|
+
child.on("error", (err) => {
|
|
1623
|
+
reject(
|
|
1624
|
+
new SchemaMigrationError(
|
|
1625
|
+
`Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
|
|
1626
|
+
err
|
|
1627
|
+
)
|
|
1628
|
+
);
|
|
1629
|
+
});
|
|
1630
|
+
child.on("close", (code) => {
|
|
1631
|
+
if (code === 0) {
|
|
1632
|
+
resolve2({ stdout, stderr });
|
|
1633
|
+
} else {
|
|
1634
|
+
reject(
|
|
1635
|
+
new SchemaMigrationError(
|
|
1636
|
+
`dotnet ${args.join(" ")} exited with code ${code}.
|
|
1637
|
+
stdout: ${stdout}
|
|
1638
|
+
stderr: ${stderr}`
|
|
1639
|
+
)
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
var restored = /* @__PURE__ */ new Set();
|
|
1646
|
+
async function ensureRestored(projectDir, csproj) {
|
|
1647
|
+
if (restored.has(projectDir)) return;
|
|
1648
|
+
if (fs11.existsSync(path11.join(projectDir, ".config", "dotnet-tools.json"))) {
|
|
1649
|
+
await spawnDotnet(projectDir, ["tool", "restore"], {});
|
|
1650
|
+
}
|
|
1651
|
+
await spawnDotnet(projectDir, ["restore", csproj], {});
|
|
1652
|
+
restored.add(projectDir);
|
|
1653
|
+
}
|
|
1654
|
+
async function runEf(projectDir, args, dsn, opts = {}) {
|
|
1655
|
+
const project = requireProject(projectDir);
|
|
1656
|
+
const csproj = path11.relative(projectDir, project.csproj) || project.csproj;
|
|
1657
|
+
await ensureRestored(projectDir, csproj);
|
|
1658
|
+
const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
|
|
1659
|
+
if (opts.noBuild) full.push("--no-build");
|
|
1660
|
+
const env = {};
|
|
1661
|
+
if (dsn) {
|
|
1662
|
+
env.DATABASE_URL = dsn;
|
|
1663
|
+
if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
|
|
1664
|
+
}
|
|
1665
|
+
return spawnDotnet(projectDir, full, env);
|
|
1666
|
+
}
|
|
1667
|
+
async function listEfMigrations(ctx, opts = {}) {
|
|
1668
|
+
const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
|
|
1669
|
+
const entries = parseEfJson(stdout);
|
|
1670
|
+
if (ctx.dsn && entries.some((e) => e.applied === null)) {
|
|
1671
|
+
const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
|
|
1672
|
+
throw new SchemaMigrationError(
|
|
1673
|
+
`dotnet ef could not read migration state from the database (check connectivity and credentials).
|
|
1674
|
+
${warning || stderr}`.trim()
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
return entries;
|
|
1678
|
+
}
|
|
1679
|
+
async function readEfHistory(dsn) {
|
|
1680
|
+
const client = new Client2({ connectionString: dsn, application_name: connectionApplicationName() });
|
|
1681
|
+
await client.connect();
|
|
1682
|
+
try {
|
|
1683
|
+
const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
|
|
1684
|
+
if (!exists.rows[0]?.present) return null;
|
|
1685
|
+
const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
|
|
1686
|
+
return r.rows.map((row) => row.MigrationId);
|
|
1687
|
+
} finally {
|
|
1688
|
+
await client.end();
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
function toApplied(entries) {
|
|
1692
|
+
return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
|
|
1693
|
+
}
|
|
1694
|
+
async function statusEfcore(ctx) {
|
|
1695
|
+
const entries = await listEfMigrations(ctx);
|
|
1696
|
+
const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
|
|
1697
|
+
const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
|
|
1698
|
+
const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
|
|
1699
|
+
return { current, pending, tool: "efcore" };
|
|
1700
|
+
}
|
|
1701
|
+
async function applyEfcore(ctx) {
|
|
1702
|
+
const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
|
|
1703
|
+
await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
|
|
1704
|
+
const after = await listEfMigrations(ctx, { noBuild: true });
|
|
1705
|
+
const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
|
|
1706
|
+
return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
|
|
1707
|
+
}
|
|
1708
|
+
function resolveEfTarget(target, applied) {
|
|
1709
|
+
const t = target.trim();
|
|
1710
|
+
if (t === "0") return "0";
|
|
1711
|
+
const rel = /^-(\d+)$/.exec(t);
|
|
1712
|
+
if (rel) {
|
|
1713
|
+
const steps = Number(rel[1]);
|
|
1714
|
+
if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
|
|
1715
|
+
const index = applied.length - 1 - steps;
|
|
1716
|
+
if (index < -1) {
|
|
1717
|
+
throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
|
|
1718
|
+
}
|
|
1719
|
+
return index === -1 ? "0" : applied[index].id;
|
|
1720
|
+
}
|
|
1721
|
+
const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
|
|
1722
|
+
if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
|
|
1723
|
+
return match.id;
|
|
1724
|
+
}
|
|
1725
|
+
async function rollbackEfcore(ctx) {
|
|
1726
|
+
const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
|
|
1727
|
+
const targetId = resolveEfTarget(ctx.target, beforeEntries);
|
|
1728
|
+
await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
|
|
1729
|
+
const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
|
|
1730
|
+
return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
|
|
1731
|
+
}
|
|
1732
|
+
function efMigrationName(slug) {
|
|
1733
|
+
const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
1734
|
+
if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
|
|
1735
|
+
return /^[0-9]/.test(name) ? `M${name}` : name;
|
|
1736
|
+
}
|
|
1737
|
+
async function createEfcoreMigration(opts) {
|
|
1738
|
+
const project = requireProject(opts.projectDir);
|
|
1739
|
+
const name = efMigrationName(opts.slug);
|
|
1740
|
+
const existing = new Set(fs11.existsSync(project.migrationsDir) ? fs11.readdirSync(project.migrationsDir) : []);
|
|
1741
|
+
await runEf(opts.projectDir, ["migrations", "add", name]);
|
|
1742
|
+
const created = fs11.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
|
|
1743
|
+
if (created.length !== 1) {
|
|
1744
|
+
throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
|
|
1745
|
+
}
|
|
1746
|
+
return { id: created[0].replace(/\.cs$/, ""), path: path11.join(project.migrationsDir, created[0]) };
|
|
1747
|
+
}
|
|
1748
|
+
function localEfMigrationIds(projectDir) {
|
|
1749
|
+
const project = findEfCoreProject(projectDir);
|
|
1750
|
+
if (!project || !fs11.existsSync(project.migrationsDir)) return [];
|
|
1751
|
+
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();
|
|
1752
|
+
}
|
|
1753
|
+
function efProductVersion(project) {
|
|
1754
|
+
try {
|
|
1755
|
+
const csproj = fs11.readFileSync(project.csproj, "utf-8");
|
|
1756
|
+
const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
|
|
1757
|
+
if (m) return m[1];
|
|
1758
|
+
} catch {
|
|
1759
|
+
}
|
|
1760
|
+
return "unknown";
|
|
1761
|
+
}
|
|
1762
|
+
async function stampEfcore(ctx) {
|
|
1763
|
+
const project = requireProject(ctx.projectDir);
|
|
1764
|
+
const ids = localEfMigrationIds(ctx.projectDir);
|
|
1765
|
+
if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
|
|
1766
|
+
const rev = ctx.revision.trim();
|
|
1767
|
+
const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
|
|
1768
|
+
if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
|
|
1769
|
+
const toStamp = ids.slice(0, end + 1);
|
|
1770
|
+
const productVersion = efProductVersion(project);
|
|
1771
|
+
const client = new Client2({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
|
|
1772
|
+
await client.connect();
|
|
1773
|
+
try {
|
|
1774
|
+
await client.query("BEGIN");
|
|
1775
|
+
await client.query(
|
|
1776
|
+
`CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
|
|
1777
|
+
"MigrationId" character varying(150) NOT NULL,
|
|
1778
|
+
"ProductVersion" character varying(32) NOT NULL,
|
|
1779
|
+
CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
|
|
1780
|
+
);
|
|
1781
|
+
await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
|
|
1782
|
+
for (const id of toStamp) {
|
|
1783
|
+
await client.query(
|
|
1784
|
+
`INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
|
|
1785
|
+
ON CONFLICT ("MigrationId") DO NOTHING`,
|
|
1786
|
+
[id, productVersion]
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
await client.query("COMMIT");
|
|
1790
|
+
} catch (err) {
|
|
1791
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
1792
|
+
throw err;
|
|
1793
|
+
} finally {
|
|
1794
|
+
await client.end();
|
|
1795
|
+
}
|
|
1796
|
+
return efVersion(toStamp[toStamp.length - 1]);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
1800
|
+
async function buildDsn4(args) {
|
|
1801
|
+
const result = await getConnection({
|
|
1802
|
+
output: "dsn",
|
|
1803
|
+
instance: args.instance,
|
|
1804
|
+
branch: args.branch,
|
|
1805
|
+
database: args.database,
|
|
1806
|
+
endpointName: args.endpointName
|
|
1807
|
+
});
|
|
1808
|
+
return result.url;
|
|
1809
|
+
}
|
|
1810
|
+
function errMsg(err) {
|
|
1811
|
+
return err instanceof Error ? err.message : String(err);
|
|
1812
|
+
}
|
|
1813
|
+
function listEfCoreFiles(projectDir) {
|
|
1814
|
+
return localEfMigrationIds(projectDir).map((id) => ({
|
|
1815
|
+
version: efVersion(id),
|
|
1816
|
+
filename: `${id}.cs`,
|
|
1817
|
+
description: efDescription(id),
|
|
1818
|
+
type: "CSharp",
|
|
1819
|
+
tool: "efcore"
|
|
1820
|
+
}));
|
|
1821
|
+
}
|
|
1822
|
+
var EfCoreAdapter = {
|
|
1823
|
+
id: "efcore",
|
|
1824
|
+
languages: ["dotnet"],
|
|
1825
|
+
/** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
|
|
1826
|
+
detect(projectDir) {
|
|
1827
|
+
return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
|
|
1828
|
+
},
|
|
1829
|
+
async apply(args) {
|
|
1830
|
+
try {
|
|
1831
|
+
const dsn = await buildDsn4(args);
|
|
1832
|
+
const r = await applyEfcore({ projectDir: args.projectDir, dsn });
|
|
1833
|
+
return {
|
|
1834
|
+
applied_migrations: r.applied,
|
|
1835
|
+
status: r.alreadyAtLatest ? "noop" : "ok",
|
|
1836
|
+
tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
|
|
1837
|
+
};
|
|
1838
|
+
} catch (err) {
|
|
1839
|
+
return { applied_migrations: [], status: "error", error: errMsg(err) };
|
|
1840
|
+
}
|
|
1841
|
+
},
|
|
1842
|
+
async rollback(args) {
|
|
1843
|
+
try {
|
|
1844
|
+
const dsn = await buildDsn4(args);
|
|
1845
|
+
const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
|
|
1846
|
+
return {
|
|
1847
|
+
rolled_back: r.rolledBack,
|
|
1848
|
+
status: r.rolledBack.length === 0 ? "noop" : "ok",
|
|
1849
|
+
tool_specific: { tool: r.tool }
|
|
1850
|
+
};
|
|
1851
|
+
} catch (err) {
|
|
1852
|
+
return { rolled_back: [], status: "error", error: errMsg(err) };
|
|
1853
|
+
}
|
|
1854
|
+
},
|
|
1855
|
+
async stamp(args) {
|
|
1856
|
+
try {
|
|
1857
|
+
const dsn = await buildDsn4(args);
|
|
1858
|
+
const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
|
|
1859
|
+
return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
|
|
1860
|
+
} catch (err) {
|
|
1861
|
+
return { status: "error", stamped_revision: null, error: errMsg(err) };
|
|
1862
|
+
}
|
|
1863
|
+
},
|
|
1864
|
+
async status(args) {
|
|
1865
|
+
try {
|
|
1866
|
+
const dsn = await buildDsn4(args);
|
|
1867
|
+
const r = await statusEfcore({ projectDir: args.projectDir, dsn });
|
|
1868
|
+
return {
|
|
1869
|
+
applied_version: r.current ?? null,
|
|
1870
|
+
pending: r.pending,
|
|
1871
|
+
applied: [],
|
|
1872
|
+
status: "ok",
|
|
1873
|
+
tool_specific: { tool: r.tool }
|
|
1874
|
+
};
|
|
1875
|
+
} catch (err) {
|
|
1876
|
+
return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
|
|
1877
|
+
}
|
|
1878
|
+
},
|
|
1879
|
+
async list(args) {
|
|
1880
|
+
return { files: listEfCoreFiles(args.projectDir) };
|
|
1881
|
+
},
|
|
1882
|
+
// baseline: use stamp (EF has no separate baseline concept).
|
|
1883
|
+
async newMigration(args) {
|
|
1884
|
+
try {
|
|
1885
|
+
const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
|
|
1886
|
+
return {
|
|
1887
|
+
status: "ok",
|
|
1888
|
+
version: efVersion(created.id),
|
|
1889
|
+
filename: path12.basename(created.path),
|
|
1890
|
+
path: created.path
|
|
1891
|
+
};
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
registerSchemaMigrationAdapter(EfCoreAdapter);
|
|
1898
|
+
|
|
1477
1899
|
// scripts/lakebase/schema-migrate.ts
|
|
1478
1900
|
var SchemaMigrationError = class extends Error {
|
|
1479
1901
|
constructor(message, cause) {
|
|
@@ -1487,13 +1909,16 @@ function detectLanguage(projectDir) {
|
|
|
1487
1909
|
const lang = resolveMigrationLanguage(projectDir);
|
|
1488
1910
|
if (lang === "unknown") {
|
|
1489
1911
|
throw new SchemaMigrationError(
|
|
1490
|
-
`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.`
|
|
1912
|
+
`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.`
|
|
1491
1913
|
);
|
|
1492
1914
|
}
|
|
1493
1915
|
return lang;
|
|
1494
1916
|
}
|
|
1495
|
-
function toolForLanguage(language) {
|
|
1917
|
+
function toolForLanguage(language, projectDir) {
|
|
1496
1918
|
switch (language) {
|
|
1919
|
+
case "dotnet":
|
|
1920
|
+
if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
|
|
1921
|
+
return "efcore";
|
|
1497
1922
|
case "java":
|
|
1498
1923
|
case "kotlin":
|
|
1499
1924
|
return "flyway";
|
|
@@ -1506,8 +1931,10 @@ function toolForLanguage(language) {
|
|
|
1506
1931
|
function listSchemaMigrations(args = {}) {
|
|
1507
1932
|
const projectDir = args.projectDir ?? process.cwd();
|
|
1508
1933
|
const language = args.language ?? detectLanguage(projectDir);
|
|
1509
|
-
const tool = toolForLanguage(language);
|
|
1934
|
+
const tool = toolForLanguage(language, projectDir);
|
|
1510
1935
|
switch (tool) {
|
|
1936
|
+
case "efcore":
|
|
1937
|
+
return listEfCoreFiles(projectDir);
|
|
1511
1938
|
case "flyway":
|
|
1512
1939
|
return listFlywayMigrations(projectDir);
|
|
1513
1940
|
case "alembic":
|
|
@@ -1517,9 +1944,9 @@ function listSchemaMigrations(args = {}) {
|
|
|
1517
1944
|
}
|
|
1518
1945
|
}
|
|
1519
1946
|
function listFlywayMigrations(projectDir) {
|
|
1520
|
-
const dir =
|
|
1521
|
-
if (!
|
|
1522
|
-
const files =
|
|
1947
|
+
const dir = flywayMigrationsDir(projectDir);
|
|
1948
|
+
if (!fs12.existsSync(dir)) return [];
|
|
1949
|
+
const files = fs12.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
|
|
1523
1950
|
return files.map((filename) => {
|
|
1524
1951
|
const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
|
|
1525
1952
|
const version = m[1];
|
|
@@ -1529,12 +1956,12 @@ function listFlywayMigrations(projectDir) {
|
|
|
1529
1956
|
}
|
|
1530
1957
|
function listAlembicMigrations(projectDir) {
|
|
1531
1958
|
const candidates = [
|
|
1532
|
-
|
|
1533
|
-
|
|
1959
|
+
path13.join(projectDir, "migrations", "versions"),
|
|
1960
|
+
path13.join(projectDir, "alembic", "versions")
|
|
1534
1961
|
];
|
|
1535
|
-
const dir = candidates.find((p) =>
|
|
1962
|
+
const dir = candidates.find((p) => fs12.existsSync(p));
|
|
1536
1963
|
if (!dir) return [];
|
|
1537
|
-
const files =
|
|
1964
|
+
const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
|
|
1538
1965
|
return files.map((filename) => {
|
|
1539
1966
|
const stem = filename.replace(/\.py$/, "");
|
|
1540
1967
|
const sep2 = stem.indexOf("_");
|
|
@@ -1544,9 +1971,9 @@ function listAlembicMigrations(projectDir) {
|
|
|
1544
1971
|
}).sort((a, b) => a.filename.localeCompare(b.filename));
|
|
1545
1972
|
}
|
|
1546
1973
|
function listKnexMigrations(projectDir) {
|
|
1547
|
-
const dir =
|
|
1548
|
-
if (!
|
|
1549
|
-
const files =
|
|
1974
|
+
const dir = path13.join(projectDir, "migrations");
|
|
1975
|
+
if (!fs12.existsSync(dir)) return [];
|
|
1976
|
+
const files = fs12.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
|
|
1550
1977
|
return files.map((filename) => {
|
|
1551
1978
|
const stem = filename.replace(/\.(js|ts)$/, "");
|
|
1552
1979
|
const m = stem.match(/^(\d{14})_(.+)$/);
|
|
@@ -1568,7 +1995,7 @@ function versionCompare2(a, b) {
|
|
|
1568
1995
|
return 0;
|
|
1569
1996
|
}
|
|
1570
1997
|
function adapterFor(projectDir, language) {
|
|
1571
|
-
const override = language ? toolForLanguage(language) : void 0;
|
|
1998
|
+
const override = language ? toolForLanguage(language, projectDir) : void 0;
|
|
1572
1999
|
return resolveSchemaMigrationAdapter(projectDir, override);
|
|
1573
2000
|
}
|
|
1574
2001
|
function dbRevisionOrphaned(appliedRevision, localRevisionIds) {
|
|
@@ -1708,8 +2135,8 @@ async function reconcileTierBranch(args, deps = {}) {
|
|
|
1708
2135
|
}
|
|
1709
2136
|
|
|
1710
2137
|
// scripts/lakebase/scm-workflow-state.ts
|
|
1711
|
-
import * as
|
|
1712
|
-
import * as
|
|
2138
|
+
import * as fs13 from "fs";
|
|
2139
|
+
import * as path14 from "path";
|
|
1713
2140
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1714
2141
|
var SCM_STATES = [
|
|
1715
2142
|
"scaffold-complete",
|
|
@@ -1724,12 +2151,12 @@ var STATE_INDEX = SCM_STATES.reduce(
|
|
|
1724
2151
|
);
|
|
1725
2152
|
var STATE_FILE_REL = ".lakebase/workflow-state.json";
|
|
1726
2153
|
function stateFilePath(projectDir) {
|
|
1727
|
-
return
|
|
2154
|
+
return path14.join(projectDir, STATE_FILE_REL);
|
|
1728
2155
|
}
|
|
1729
2156
|
function readWorkflowState(projectDir) {
|
|
1730
2157
|
const p = stateFilePath(projectDir);
|
|
1731
|
-
if (!
|
|
1732
|
-
const raw =
|
|
2158
|
+
if (!fs13.existsSync(p)) return null;
|
|
2159
|
+
const raw = fs13.readFileSync(p, "utf8");
|
|
1733
2160
|
let parsed;
|
|
1734
2161
|
try {
|
|
1735
2162
|
parsed = JSON.parse(raw);
|