@databricks-solutions/lakebase-scm-utils 0.2.51 → 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 +2632 -2120
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +676 -174
- 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 +2629 -2117
- package/dist/scripts/lakebase/index.d.cts +59 -10
- package/dist/scripts/lakebase/index.d.ts +59 -10
- package/dist/scripts/lakebase/index.js +670 -168
- 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 +6 -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);
|
|
@@ -393,8 +393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
393
393
|
var cached;
|
|
394
394
|
function substrateSelfVersion() {
|
|
395
395
|
if (cached !== void 0) return cached;
|
|
396
|
-
if ("0.
|
|
397
|
-
cached = "0.
|
|
396
|
+
if ("0.3.0".length > 0) {
|
|
397
|
+
cached = "0.3.0";
|
|
398
398
|
return cached;
|
|
399
399
|
}
|
|
400
400
|
cached = "unknown";
|
|
@@ -503,23 +503,88 @@ function dbcli2(args) {
|
|
|
503
503
|
}
|
|
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(projectDir2) {
|
|
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(projectDir2, 0);
|
|
540
|
+
return found.sort();
|
|
541
|
+
}
|
|
542
|
+
function findEfCoreProject(projectDir2) {
|
|
543
|
+
for (const csproj of findCsprojFiles(projectDir2)) {
|
|
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(projectDir2) {
|
|
558
|
+
return hasDotnetMarker(projectDir2);
|
|
559
|
+
}
|
|
560
|
+
function isDotnetFlywayProject(projectDir2) {
|
|
561
|
+
return isDotnetProject(projectDir2) && fs4.existsSync(path3.join(projectDir2, DOTNET_FLYWAY_DIR));
|
|
562
|
+
}
|
|
563
|
+
var MAVEN_FLYWAY_DIR = path3.join("src", "main", "resources", "db", "migration");
|
|
564
|
+
function flywayMigrationsDir(projectDir2) {
|
|
565
|
+
const maven = path3.join(projectDir2, MAVEN_FLYWAY_DIR);
|
|
566
|
+
if (fs4.existsSync(maven) || fs4.existsSync(path3.join(projectDir2, "pom.xml"))) return maven;
|
|
567
|
+
if (isDotnetFlywayProject(projectDir2)) return path3.join(projectDir2, 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(projectDir2, 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(projectDir2);
|
|
623
|
+
let dir = path4.resolve(projectDir2, 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(projectDir2, 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(projectDir2) {
|
|
579
647
|
const candidates = [
|
|
580
|
-
|
|
581
|
-
|
|
648
|
+
path5.join(projectDir2, ".venv", "bin", "alembic"),
|
|
649
|
+
path5.join(projectDir2, "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(projectDir2, args, dsn) {
|
|
|
592
660
|
return new Promise((resolve2, reject) => {
|
|
593
661
|
const bin = resolveAlembicBin(projectDir2);
|
|
594
662
|
const env = { ...process.env };
|
|
595
|
-
env.PYTHONPATH = [projectDir2, process.env.PYTHONPATH].filter(Boolean).join(
|
|
663
|
+
env.PYTHONPATH = [projectDir2, 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: projectDir2,
|
|
@@ -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.
|
|
@@ -754,15 +822,15 @@ async function buildDsn(args) {
|
|
|
754
822
|
}
|
|
755
823
|
function findVersionsDir(projectDir2) {
|
|
756
824
|
const candidates = [
|
|
757
|
-
|
|
758
|
-
|
|
825
|
+
path6.join(projectDir2, "migrations", "versions"),
|
|
826
|
+
path6.join(projectDir2, "alembic", "versions")
|
|
759
827
|
];
|
|
760
|
-
return candidates.find((p) =>
|
|
828
|
+
return candidates.find((p) => fs7.existsSync(p));
|
|
761
829
|
}
|
|
762
830
|
function listAlembicFiles(projectDir2) {
|
|
763
831
|
const dir = findVersionsDir(projectDir2);
|
|
764
832
|
if (!dir) return [];
|
|
765
|
-
const files =
|
|
833
|
+
const files = fs7.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
|
|
766
834
|
return files.map((filename) => {
|
|
767
835
|
const stem = filename.replace(/\.py$/, "");
|
|
768
836
|
const sep2 = stem.indexOf("_");
|
|
@@ -787,9 +855,9 @@ var AlembicAdapter = {
|
|
|
787
855
|
* here. Callers can still force-select via project.yaml#migration_tool.
|
|
788
856
|
*/
|
|
789
857
|
detect(projectDir2) {
|
|
790
|
-
if (
|
|
791
|
-
if (
|
|
792
|
-
if (
|
|
858
|
+
if (fs7.existsSync(path6.join(projectDir2, "alembic.ini"))) return true;
|
|
859
|
+
if (fs7.existsSync(path6.join(projectDir2, "migrations", "env.py"))) return true;
|
|
860
|
+
if (fs7.existsSync(path6.join(projectDir2, "alembic", "env.py"))) return true;
|
|
793
861
|
return false;
|
|
794
862
|
},
|
|
795
863
|
async apply(args) {
|
|
@@ -895,7 +963,7 @@ var AlembicAdapter = {
|
|
|
895
963
|
autogenerate: !!args.autogenerate,
|
|
896
964
|
dsn
|
|
897
965
|
});
|
|
898
|
-
return { status: "ok", version: revId, filename:
|
|
966
|
+
return { status: "ok", version: revId, filename: path6.basename(created), path: created };
|
|
899
967
|
} catch (err) {
|
|
900
968
|
return {
|
|
901
969
|
status: "error",
|
|
@@ -912,7 +980,7 @@ var AlembicAdapter = {
|
|
|
912
980
|
if (heads.length <= 1) return { status: "noop", headsBefore: heads };
|
|
913
981
|
if (args.dryRun) return { status: "ok", headsBefore: heads };
|
|
914
982
|
const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
|
|
915
|
-
const mergeRevision =
|
|
983
|
+
const mergeRevision = path6.basename(created).replace(/\.py$/, "").split("_")[0];
|
|
916
984
|
return { status: "ok", headsBefore: heads, mergeRevision, path: created };
|
|
917
985
|
} catch (err) {
|
|
918
986
|
return {
|
|
@@ -926,12 +994,12 @@ var AlembicAdapter = {
|
|
|
926
994
|
registerSchemaMigrationAdapter(AlembicAdapter);
|
|
927
995
|
|
|
928
996
|
// scripts/lakebase/adapters/flyway-adapter.ts
|
|
929
|
-
import * as
|
|
930
|
-
import * as
|
|
997
|
+
import * as fs8 from "fs";
|
|
998
|
+
import * as path8 from "path";
|
|
931
999
|
|
|
932
1000
|
// scripts/lakebase/schema-migrate-runners/flyway.ts
|
|
933
1001
|
import { spawn as spawn2 } from "child_process";
|
|
934
|
-
import * as
|
|
1002
|
+
import * as path7 from "path";
|
|
935
1003
|
function dsnToFlywayEnv(dsn) {
|
|
936
1004
|
const u = new URL(dsn);
|
|
937
1005
|
const user = decodeURIComponent(u.username);
|
|
@@ -941,7 +1009,7 @@ function dsnToFlywayEnv(dsn) {
|
|
|
941
1009
|
return { url, user, password };
|
|
942
1010
|
}
|
|
943
1011
|
function migrationsLocation(projectDir2) {
|
|
944
|
-
return `filesystem:${
|
|
1012
|
+
return `filesystem:${flywayMigrationsDir(projectDir2)}`;
|
|
945
1013
|
}
|
|
946
1014
|
function runFlyway(ctx, args) {
|
|
947
1015
|
const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
|
|
@@ -1042,7 +1110,7 @@ async function statusFlyway(ctx) {
|
|
|
1042
1110
|
if (state === "SUCCESS" || state === "BASELINE") {
|
|
1043
1111
|
current = m.version;
|
|
1044
1112
|
} else if (state === "PENDING") {
|
|
1045
|
-
const filename = m.filepath ?
|
|
1113
|
+
const filename = m.filepath ? path7.basename(m.filepath) : `V${m.version}__migration.sql`;
|
|
1046
1114
|
pending.push({
|
|
1047
1115
|
version: m.version,
|
|
1048
1116
|
filename,
|
|
@@ -1065,9 +1133,9 @@ async function buildDsn2(args) {
|
|
|
1065
1133
|
return result.url;
|
|
1066
1134
|
}
|
|
1067
1135
|
function listFlywayFiles(projectDir2) {
|
|
1068
|
-
const dir =
|
|
1069
|
-
if (!
|
|
1070
|
-
const files =
|
|
1136
|
+
const dir = flywayMigrationsDir(projectDir2);
|
|
1137
|
+
if (!fs8.existsSync(dir)) return [];
|
|
1138
|
+
const files = fs8.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
|
|
1071
1139
|
return files.map((filename) => {
|
|
1072
1140
|
const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
|
|
1073
1141
|
const version = m[1];
|
|
@@ -1088,9 +1156,9 @@ function versionCompare(a, b) {
|
|
|
1088
1156
|
}
|
|
1089
1157
|
var FlywayAdapter = {
|
|
1090
1158
|
id: "flyway",
|
|
1091
|
-
languages: ["java", "kotlin"],
|
|
1159
|
+
languages: ["java", "kotlin", "dotnet"],
|
|
1092
1160
|
detect(projectDir2) {
|
|
1093
|
-
return
|
|
1161
|
+
return fs8.existsSync(path8.join(projectDir2, "pom.xml")) || isDotnetFlywayProject(projectDir2);
|
|
1094
1162
|
},
|
|
1095
1163
|
async apply(args) {
|
|
1096
1164
|
const dsn = await buildDsn2(args);
|
|
@@ -1148,14 +1216,14 @@ var FlywayAdapter = {
|
|
|
1148
1216
|
// optional-protocol shape makes this additive.
|
|
1149
1217
|
async newMigration(args) {
|
|
1150
1218
|
try {
|
|
1151
|
-
const dir =
|
|
1152
|
-
|
|
1219
|
+
const dir = flywayMigrationsDir(args.projectDir);
|
|
1220
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1153
1221
|
const version = migrationTimestamp();
|
|
1154
1222
|
const slug = migrationSlug2(args.slug);
|
|
1155
1223
|
const filename = `V${version}__${slug}.sql`;
|
|
1156
|
-
const full =
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1224
|
+
const full = path8.join(dir, filename);
|
|
1225
|
+
if (fs8.existsSync(full)) throw new Error(`${filename} already exists`);
|
|
1226
|
+
fs8.writeFileSync(
|
|
1159
1227
|
full,
|
|
1160
1228
|
`-- V${version}: ${args.slug}
|
|
1161
1229
|
-- Flyway migration (write your DDL/DML below).
|
|
@@ -1177,18 +1245,18 @@ var FlywayAdapter = {
|
|
|
1177
1245
|
registerSchemaMigrationAdapter(FlywayAdapter);
|
|
1178
1246
|
|
|
1179
1247
|
// scripts/lakebase/adapters/knex-adapter.ts
|
|
1180
|
-
import * as
|
|
1181
|
-
import * as
|
|
1248
|
+
import * as fs10 from "fs";
|
|
1249
|
+
import * as path10 from "path";
|
|
1182
1250
|
|
|
1183
1251
|
// scripts/lakebase/schema-migrate-runners/knex.ts
|
|
1184
1252
|
import { spawn as spawn3 } from "child_process";
|
|
1185
|
-
import * as
|
|
1186
|
-
import * as
|
|
1253
|
+
import * as fs9 from "fs";
|
|
1254
|
+
import * as path9 from "path";
|
|
1187
1255
|
var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
1188
1256
|
function findKnexfile(projectDir2) {
|
|
1189
1257
|
for (const name of KNEXFILE_VARIANTS) {
|
|
1190
|
-
const p =
|
|
1191
|
-
if (
|
|
1258
|
+
const p = path9.join(projectDir2, name);
|
|
1259
|
+
if (fs9.existsSync(p)) return p;
|
|
1192
1260
|
}
|
|
1193
1261
|
return void 0;
|
|
1194
1262
|
}
|
|
@@ -1340,9 +1408,9 @@ async function buildDsn3(args) {
|
|
|
1340
1408
|
}
|
|
1341
1409
|
var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
1342
1410
|
function listKnexFiles(projectDir2) {
|
|
1343
|
-
const dir =
|
|
1344
|
-
if (!
|
|
1345
|
-
const files =
|
|
1411
|
+
const dir = path10.join(projectDir2, "migrations");
|
|
1412
|
+
if (!fs10.existsSync(dir)) return [];
|
|
1413
|
+
const files = fs10.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
|
|
1346
1414
|
return files.map((filename) => {
|
|
1347
1415
|
const stem = filename.replace(/\.(js|ts)$/, "");
|
|
1348
1416
|
const m = stem.match(/^(\d{14})_(.+)$/);
|
|
@@ -1362,7 +1430,7 @@ var KnexAdapter = {
|
|
|
1362
1430
|
* project.yaml#migration_tool.
|
|
1363
1431
|
*/
|
|
1364
1432
|
detect(projectDir2) {
|
|
1365
|
-
return KNEXFILE_VARIANTS2.some((name) =>
|
|
1433
|
+
return KNEXFILE_VARIANTS2.some((name) => fs10.existsSync(path10.join(projectDir2, name)));
|
|
1366
1434
|
},
|
|
1367
1435
|
async apply(args) {
|
|
1368
1436
|
const dsn = await buildDsn3(args);
|
|
@@ -1435,9 +1503,9 @@ var KnexAdapter = {
|
|
|
1435
1503
|
async newMigration(args) {
|
|
1436
1504
|
try {
|
|
1437
1505
|
const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
|
|
1438
|
-
const stem =
|
|
1506
|
+
const stem = path10.basename(created).replace(/\.(js|ts)$/, "");
|
|
1439
1507
|
const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
|
|
1440
|
-
return { status: "ok", version, filename:
|
|
1508
|
+
return { status: "ok", version, filename: path10.basename(created), path: created };
|
|
1441
1509
|
} catch (err) {
|
|
1442
1510
|
return {
|
|
1443
1511
|
status: "error",
|
|
@@ -1451,6 +1519,360 @@ var KnexAdapter = {
|
|
|
1451
1519
|
};
|
|
1452
1520
|
registerSchemaMigrationAdapter(KnexAdapter);
|
|
1453
1521
|
|
|
1522
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
1523
|
+
import * as path12 from "path";
|
|
1524
|
+
|
|
1525
|
+
// scripts/lakebase/schema-migrate-runners/efcore.ts
|
|
1526
|
+
import { spawn as spawn4 } from "child_process";
|
|
1527
|
+
import * as fs11 from "fs";
|
|
1528
|
+
import * as path11 from "path";
|
|
1529
|
+
import { Client as Client2 } from "pg";
|
|
1530
|
+
var MIGRATION_ID = /^(\d{14})_(.+)$/;
|
|
1531
|
+
function efVersion(id) {
|
|
1532
|
+
return MIGRATION_ID.exec(id)?.[1] ?? id;
|
|
1533
|
+
}
|
|
1534
|
+
function efDescription(id) {
|
|
1535
|
+
const name = MIGRATION_ID.exec(id)?.[2] ?? id;
|
|
1536
|
+
return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
1537
|
+
}
|
|
1538
|
+
function dsnToNpgsql(dsn) {
|
|
1539
|
+
const u = new URL(dsn);
|
|
1540
|
+
const parts = [
|
|
1541
|
+
`Host=${u.hostname}`,
|
|
1542
|
+
`Port=${u.port || "5432"}`,
|
|
1543
|
+
`Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
|
|
1544
|
+
];
|
|
1545
|
+
if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
|
|
1546
|
+
if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
|
|
1547
|
+
const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
|
|
1548
|
+
const ssl = {
|
|
1549
|
+
disable: "Disable",
|
|
1550
|
+
allow: "Allow",
|
|
1551
|
+
prefer: "Prefer",
|
|
1552
|
+
require: "Require",
|
|
1553
|
+
"verify-ca": "VerifyCA",
|
|
1554
|
+
"verify-full": "VerifyFull"
|
|
1555
|
+
};
|
|
1556
|
+
parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
|
|
1557
|
+
const app = u.searchParams.get("application_name");
|
|
1558
|
+
if (app) parts.push(`Application Name=${app}`);
|
|
1559
|
+
return parts.map((p) => p.replace(/;/g, "")).join(";");
|
|
1560
|
+
}
|
|
1561
|
+
function parseEfJson(stdout) {
|
|
1562
|
+
const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
|
|
1563
|
+
if (!body) {
|
|
1564
|
+
throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
|
|
1565
|
+
}
|
|
1566
|
+
try {
|
|
1567
|
+
return JSON.parse(body);
|
|
1568
|
+
} catch (err) {
|
|
1569
|
+
throw new SchemaMigrationError(
|
|
1570
|
+
`dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
|
|
1571
|
+
Body (first 400 chars): ${body.slice(0, 400)}`
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
function requireProject(projectDir2) {
|
|
1576
|
+
const project = findEfCoreProject(projectDir2);
|
|
1577
|
+
if (!project) {
|
|
1578
|
+
throw new SchemaMigrationError(
|
|
1579
|
+
`No EF Core project found under ${projectDir2}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
return project;
|
|
1583
|
+
}
|
|
1584
|
+
function spawnDotnet(projectDir2, args, env) {
|
|
1585
|
+
return new Promise((resolve2, reject) => {
|
|
1586
|
+
const child = spawn4("dotnet", args, {
|
|
1587
|
+
cwd: projectDir2,
|
|
1588
|
+
env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
|
|
1589
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1590
|
+
});
|
|
1591
|
+
let stdout = "";
|
|
1592
|
+
let stderr = "";
|
|
1593
|
+
child.stdout.on("data", (chunk) => {
|
|
1594
|
+
stdout += chunk.toString("utf8");
|
|
1595
|
+
});
|
|
1596
|
+
child.stderr.on("data", (chunk) => {
|
|
1597
|
+
stderr += chunk.toString("utf8");
|
|
1598
|
+
});
|
|
1599
|
+
child.on("error", (err) => {
|
|
1600
|
+
reject(
|
|
1601
|
+
new SchemaMigrationError(
|
|
1602
|
+
`Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
|
|
1603
|
+
err
|
|
1604
|
+
)
|
|
1605
|
+
);
|
|
1606
|
+
});
|
|
1607
|
+
child.on("close", (code) => {
|
|
1608
|
+
if (code === 0) {
|
|
1609
|
+
resolve2({ stdout, stderr });
|
|
1610
|
+
} else {
|
|
1611
|
+
reject(
|
|
1612
|
+
new SchemaMigrationError(
|
|
1613
|
+
`dotnet ${args.join(" ")} exited with code ${code}.
|
|
1614
|
+
stdout: ${stdout}
|
|
1615
|
+
stderr: ${stderr}`
|
|
1616
|
+
)
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
var restored = /* @__PURE__ */ new Set();
|
|
1623
|
+
async function ensureRestored(projectDir2, csproj) {
|
|
1624
|
+
if (restored.has(projectDir2)) return;
|
|
1625
|
+
if (fs11.existsSync(path11.join(projectDir2, ".config", "dotnet-tools.json"))) {
|
|
1626
|
+
await spawnDotnet(projectDir2, ["tool", "restore"], {});
|
|
1627
|
+
}
|
|
1628
|
+
await spawnDotnet(projectDir2, ["restore", csproj], {});
|
|
1629
|
+
restored.add(projectDir2);
|
|
1630
|
+
}
|
|
1631
|
+
async function runEf(projectDir2, args, dsn, opts = {}) {
|
|
1632
|
+
const project = requireProject(projectDir2);
|
|
1633
|
+
const csproj = path11.relative(projectDir2, project.csproj) || project.csproj;
|
|
1634
|
+
await ensureRestored(projectDir2, csproj);
|
|
1635
|
+
const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
|
|
1636
|
+
if (opts.noBuild) full.push("--no-build");
|
|
1637
|
+
const env = {};
|
|
1638
|
+
if (dsn) {
|
|
1639
|
+
env.DATABASE_URL = dsn;
|
|
1640
|
+
if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
|
|
1641
|
+
}
|
|
1642
|
+
return spawnDotnet(projectDir2, full, env);
|
|
1643
|
+
}
|
|
1644
|
+
async function listEfMigrations(ctx, opts = {}) {
|
|
1645
|
+
const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
|
|
1646
|
+
const entries = parseEfJson(stdout);
|
|
1647
|
+
if (ctx.dsn && entries.some((e) => e.applied === null)) {
|
|
1648
|
+
const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
|
|
1649
|
+
throw new SchemaMigrationError(
|
|
1650
|
+
`dotnet ef could not read migration state from the database (check connectivity and credentials).
|
|
1651
|
+
${warning || stderr}`.trim()
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
return entries;
|
|
1655
|
+
}
|
|
1656
|
+
async function readEfHistory(dsn) {
|
|
1657
|
+
const client = new Client2({ connectionString: dsn, application_name: connectionApplicationName() });
|
|
1658
|
+
await client.connect();
|
|
1659
|
+
try {
|
|
1660
|
+
const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
|
|
1661
|
+
if (!exists.rows[0]?.present) return null;
|
|
1662
|
+
const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
|
|
1663
|
+
return r.rows.map((row) => row.MigrationId);
|
|
1664
|
+
} finally {
|
|
1665
|
+
await client.end();
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
function toApplied(entries) {
|
|
1669
|
+
return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
|
|
1670
|
+
}
|
|
1671
|
+
async function statusEfcore(ctx) {
|
|
1672
|
+
const entries = await listEfMigrations(ctx);
|
|
1673
|
+
const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
|
|
1674
|
+
const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
|
|
1675
|
+
const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
|
|
1676
|
+
return { current, pending, tool: "efcore" };
|
|
1677
|
+
}
|
|
1678
|
+
async function applyEfcore(ctx) {
|
|
1679
|
+
const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
|
|
1680
|
+
await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
|
|
1681
|
+
const after = await listEfMigrations(ctx, { noBuild: true });
|
|
1682
|
+
const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
|
|
1683
|
+
return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
|
|
1684
|
+
}
|
|
1685
|
+
function resolveEfTarget(target, applied) {
|
|
1686
|
+
const t = target.trim();
|
|
1687
|
+
if (t === "0") return "0";
|
|
1688
|
+
const rel = /^-(\d+)$/.exec(t);
|
|
1689
|
+
if (rel) {
|
|
1690
|
+
const steps = Number(rel[1]);
|
|
1691
|
+
if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
|
|
1692
|
+
const index = applied.length - 1 - steps;
|
|
1693
|
+
if (index < -1) {
|
|
1694
|
+
throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
|
|
1695
|
+
}
|
|
1696
|
+
return index === -1 ? "0" : applied[index].id;
|
|
1697
|
+
}
|
|
1698
|
+
const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
|
|
1699
|
+
if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
|
|
1700
|
+
return match.id;
|
|
1701
|
+
}
|
|
1702
|
+
async function rollbackEfcore(ctx) {
|
|
1703
|
+
const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
|
|
1704
|
+
const targetId = resolveEfTarget(ctx.target, beforeEntries);
|
|
1705
|
+
await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
|
|
1706
|
+
const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
|
|
1707
|
+
return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
|
|
1708
|
+
}
|
|
1709
|
+
function efMigrationName(slug) {
|
|
1710
|
+
const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
1711
|
+
if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
|
|
1712
|
+
return /^[0-9]/.test(name) ? `M${name}` : name;
|
|
1713
|
+
}
|
|
1714
|
+
async function createEfcoreMigration(opts) {
|
|
1715
|
+
const project = requireProject(opts.projectDir);
|
|
1716
|
+
const name = efMigrationName(opts.slug);
|
|
1717
|
+
const existing = new Set(fs11.existsSync(project.migrationsDir) ? fs11.readdirSync(project.migrationsDir) : []);
|
|
1718
|
+
await runEf(opts.projectDir, ["migrations", "add", name]);
|
|
1719
|
+
const created = fs11.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
|
|
1720
|
+
if (created.length !== 1) {
|
|
1721
|
+
throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
|
|
1722
|
+
}
|
|
1723
|
+
return { id: created[0].replace(/\.cs$/, ""), path: path11.join(project.migrationsDir, created[0]) };
|
|
1724
|
+
}
|
|
1725
|
+
function localEfMigrationIds(projectDir2) {
|
|
1726
|
+
const project = findEfCoreProject(projectDir2);
|
|
1727
|
+
if (!project || !fs11.existsSync(project.migrationsDir)) return [];
|
|
1728
|
+
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();
|
|
1729
|
+
}
|
|
1730
|
+
function efProductVersion(project) {
|
|
1731
|
+
try {
|
|
1732
|
+
const csproj = fs11.readFileSync(project.csproj, "utf-8");
|
|
1733
|
+
const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
|
|
1734
|
+
if (m) return m[1];
|
|
1735
|
+
} catch {
|
|
1736
|
+
}
|
|
1737
|
+
return "unknown";
|
|
1738
|
+
}
|
|
1739
|
+
async function stampEfcore(ctx) {
|
|
1740
|
+
const project = requireProject(ctx.projectDir);
|
|
1741
|
+
const ids = localEfMigrationIds(ctx.projectDir);
|
|
1742
|
+
if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
|
|
1743
|
+
const rev = ctx.revision.trim();
|
|
1744
|
+
const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
|
|
1745
|
+
if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
|
|
1746
|
+
const toStamp = ids.slice(0, end + 1);
|
|
1747
|
+
const productVersion = efProductVersion(project);
|
|
1748
|
+
const client = new Client2({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
|
|
1749
|
+
await client.connect();
|
|
1750
|
+
try {
|
|
1751
|
+
await client.query("BEGIN");
|
|
1752
|
+
await client.query(
|
|
1753
|
+
`CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
|
|
1754
|
+
"MigrationId" character varying(150) NOT NULL,
|
|
1755
|
+
"ProductVersion" character varying(32) NOT NULL,
|
|
1756
|
+
CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
|
|
1757
|
+
);
|
|
1758
|
+
await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
|
|
1759
|
+
for (const id of toStamp) {
|
|
1760
|
+
await client.query(
|
|
1761
|
+
`INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
|
|
1762
|
+
ON CONFLICT ("MigrationId") DO NOTHING`,
|
|
1763
|
+
[id, productVersion]
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
await client.query("COMMIT");
|
|
1767
|
+
} catch (err) {
|
|
1768
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
1769
|
+
throw err;
|
|
1770
|
+
} finally {
|
|
1771
|
+
await client.end();
|
|
1772
|
+
}
|
|
1773
|
+
return efVersion(toStamp[toStamp.length - 1]);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
1777
|
+
async function buildDsn4(args) {
|
|
1778
|
+
const result = await getConnection({
|
|
1779
|
+
output: "dsn",
|
|
1780
|
+
instance: args.instance,
|
|
1781
|
+
branch: args.branch,
|
|
1782
|
+
database: args.database,
|
|
1783
|
+
endpointName: args.endpointName
|
|
1784
|
+
});
|
|
1785
|
+
return result.url;
|
|
1786
|
+
}
|
|
1787
|
+
function errMsg(err) {
|
|
1788
|
+
return err instanceof Error ? err.message : String(err);
|
|
1789
|
+
}
|
|
1790
|
+
function listEfCoreFiles(projectDir2) {
|
|
1791
|
+
return localEfMigrationIds(projectDir2).map((id) => ({
|
|
1792
|
+
version: efVersion(id),
|
|
1793
|
+
filename: `${id}.cs`,
|
|
1794
|
+
description: efDescription(id),
|
|
1795
|
+
type: "CSharp",
|
|
1796
|
+
tool: "efcore"
|
|
1797
|
+
}));
|
|
1798
|
+
}
|
|
1799
|
+
var EfCoreAdapter = {
|
|
1800
|
+
id: "efcore",
|
|
1801
|
+
languages: ["dotnet"],
|
|
1802
|
+
/** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
|
|
1803
|
+
detect(projectDir2) {
|
|
1804
|
+
return isDotnetProject(projectDir2) && findEfCoreProject(projectDir2) !== null;
|
|
1805
|
+
},
|
|
1806
|
+
async apply(args) {
|
|
1807
|
+
try {
|
|
1808
|
+
const dsn = await buildDsn4(args);
|
|
1809
|
+
const r = await applyEfcore({ projectDir: args.projectDir, dsn });
|
|
1810
|
+
return {
|
|
1811
|
+
applied_migrations: r.applied,
|
|
1812
|
+
status: r.alreadyAtLatest ? "noop" : "ok",
|
|
1813
|
+
tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
|
|
1814
|
+
};
|
|
1815
|
+
} catch (err) {
|
|
1816
|
+
return { applied_migrations: [], status: "error", error: errMsg(err) };
|
|
1817
|
+
}
|
|
1818
|
+
},
|
|
1819
|
+
async rollback(args) {
|
|
1820
|
+
try {
|
|
1821
|
+
const dsn = await buildDsn4(args);
|
|
1822
|
+
const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
|
|
1823
|
+
return {
|
|
1824
|
+
rolled_back: r.rolledBack,
|
|
1825
|
+
status: r.rolledBack.length === 0 ? "noop" : "ok",
|
|
1826
|
+
tool_specific: { tool: r.tool }
|
|
1827
|
+
};
|
|
1828
|
+
} catch (err) {
|
|
1829
|
+
return { rolled_back: [], status: "error", error: errMsg(err) };
|
|
1830
|
+
}
|
|
1831
|
+
},
|
|
1832
|
+
async stamp(args) {
|
|
1833
|
+
try {
|
|
1834
|
+
const dsn = await buildDsn4(args);
|
|
1835
|
+
const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
|
|
1836
|
+
return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
|
|
1837
|
+
} catch (err) {
|
|
1838
|
+
return { status: "error", stamped_revision: null, error: errMsg(err) };
|
|
1839
|
+
}
|
|
1840
|
+
},
|
|
1841
|
+
async status(args) {
|
|
1842
|
+
try {
|
|
1843
|
+
const dsn = await buildDsn4(args);
|
|
1844
|
+
const r = await statusEfcore({ projectDir: args.projectDir, dsn });
|
|
1845
|
+
return {
|
|
1846
|
+
applied_version: r.current ?? null,
|
|
1847
|
+
pending: r.pending,
|
|
1848
|
+
applied: [],
|
|
1849
|
+
status: "ok",
|
|
1850
|
+
tool_specific: { tool: r.tool }
|
|
1851
|
+
};
|
|
1852
|
+
} catch (err) {
|
|
1853
|
+
return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
|
|
1854
|
+
}
|
|
1855
|
+
},
|
|
1856
|
+
async list(args) {
|
|
1857
|
+
return { files: listEfCoreFiles(args.projectDir) };
|
|
1858
|
+
},
|
|
1859
|
+
// baseline: use stamp (EF has no separate baseline concept).
|
|
1860
|
+
async newMigration(args) {
|
|
1861
|
+
try {
|
|
1862
|
+
const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
|
|
1863
|
+
return {
|
|
1864
|
+
status: "ok",
|
|
1865
|
+
version: efVersion(created.id),
|
|
1866
|
+
filename: path12.basename(created.path),
|
|
1867
|
+
path: created.path
|
|
1868
|
+
};
|
|
1869
|
+
} catch (err) {
|
|
1870
|
+
return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
registerSchemaMigrationAdapter(EfCoreAdapter);
|
|
1875
|
+
|
|
1454
1876
|
// scripts/lakebase/schema-migrate.ts
|
|
1455
1877
|
var SchemaMigrationError = class extends Error {
|
|
1456
1878
|
constructor(message, cause) {
|
|
@@ -1464,7 +1886,7 @@ function detectLanguage(projectDir2) {
|
|
|
1464
1886
|
const lang = resolveMigrationLanguage(projectDir2);
|
|
1465
1887
|
if (lang === "unknown") {
|
|
1466
1888
|
throw new SchemaMigrationError(
|
|
1467
|
-
`Could not detect project language in ${projectDir2}. Expected one of: pom.xml (java/kotlin), pyproject.toml or alembic.ini (python), package.json (nodejs). Pass {language} explicitly to override.`
|
|
1889
|
+
`Could not detect project language in ${projectDir2}. 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.`
|
|
1468
1890
|
);
|
|
1469
1891
|
}
|
|
1470
1892
|
return lang;
|
|
@@ -1496,7 +1918,7 @@ function printHelpAndExit() {
|
|
|
1496
1918
|
Usage:
|
|
1497
1919
|
lakebase-detect-language [--project-dir <path>]
|
|
1498
1920
|
|
|
1499
|
-
Output (stdout): one of "java", "python", "nodejs"
|
|
1921
|
+
Output (stdout): one of "java", "kotlin", "python", "nodejs", "dotnet"
|
|
1500
1922
|
Exits 1 with an error message on stderr if no marker found.
|
|
1501
1923
|
`
|
|
1502
1924
|
);
|