@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.
Files changed (77) hide show
  1. package/dist/scripts/index.cjs +2632 -2120
  2. package/dist/scripts/index.d.cts +1 -1
  3. package/dist/scripts/index.d.ts +1 -1
  4. package/dist/scripts/index.js +676 -174
  5. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2460 -2030
  6. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +508 -81
  7. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2429 -2007
  8. package/dist/scripts/lakebase/collapse-heads.cli.js +477 -58
  9. package/dist/scripts/lakebase/detect-language.cli.cjs +2441 -2016
  10. package/dist/scripts/lakebase/detect-language.cli.js +489 -67
  11. package/dist/scripts/lakebase/doctor.cli.cjs +2484 -2040
  12. package/dist/scripts/lakebase/doctor.cli.js +532 -91
  13. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  14. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  15. package/dist/scripts/lakebase/index.cjs +2629 -2117
  16. package/dist/scripts/lakebase/index.d.cts +59 -10
  17. package/dist/scripts/lakebase/index.d.ts +59 -10
  18. package/dist/scripts/lakebase/index.js +670 -168
  19. package/dist/scripts/lakebase/infra-runner.cli.cjs +2435 -2013
  20. package/dist/scripts/lakebase/infra-runner.cli.js +483 -64
  21. package/dist/scripts/lakebase/new-migration.cli.cjs +2429 -2007
  22. package/dist/scripts/lakebase/new-migration.cli.js +477 -58
  23. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  24. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  25. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2461 -2029
  26. package/dist/scripts/lakebase/schema-migrate.cli.js +509 -80
  27. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2460 -2030
  28. package/dist/scripts/lakebase/scm-claim-feature.cli.js +508 -81
  29. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2486 -2056
  30. package/dist/scripts/lakebase/scm-doctor.cli.js +537 -107
  31. package/dist/scripts/lakebase/scm-merge.cli.cjs +2443 -2021
  32. package/dist/scripts/lakebase/scm-merge.cli.js +494 -72
  33. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2464 -2034
  34. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +511 -84
  35. package/dist/scripts/util/index.cjs +2 -2
  36. package/dist/scripts/util/index.js +2 -2
  37. package/package.json +1 -1
  38. package/scripts/lakebase/adapters/efcore-adapter.ts +158 -0
  39. package/scripts/lakebase/adapters/flyway-adapter.ts +8 -5
  40. package/scripts/lakebase/collapse-heads.cli.ts +1 -1
  41. package/scripts/lakebase/create-project.ts +6 -2
  42. package/scripts/lakebase/detect-language.cli.ts +2 -2
  43. package/scripts/lakebase/doctor.ts +29 -0
  44. package/scripts/lakebase/dotnet-project.ts +112 -0
  45. package/scripts/lakebase/index.ts +1 -0
  46. package/scripts/lakebase/migration-layout.ts +43 -2
  47. package/scripts/lakebase/new-migration.cli.ts +1 -1
  48. package/scripts/lakebase/scaffold-language.ts +23 -2
  49. package/scripts/lakebase/scaffold.ts +4 -1
  50. package/scripts/lakebase/schema-migrate-runners/efcore.ts +399 -0
  51. package/scripts/lakebase/schema-migrate-runners/flyway.ts +6 -4
  52. package/scripts/lakebase/schema-migrate.cli.ts +3 -1
  53. package/scripts/lakebase/schema-migrate.ts +24 -10
  54. package/templates/project/common/.github/workflows/merge.yml +13 -1
  55. package/templates/project/common/.github/workflows/pr.yml +25 -5
  56. package/templates/project/common/scripts/flyway-migrate.sh +54 -1
  57. package/templates/project/common/scripts/prepare-schema-diff.sh +5 -0
  58. package/templates/project/common/scripts/run-dev.sh +10 -1
  59. package/templates/project/common/scripts/run-tests.sh +6 -1
  60. package/templates/project/dotnet/.gitignore.extra +8 -0
  61. package/templates/project/dotnet/App.sln +56 -0
  62. package/templates/project/dotnet/Makefile +13 -0
  63. package/templates/project/dotnet/global.json +6 -0
  64. package/templates/project/dotnet/src/App/Data/AppDbContext.cs +12 -0
  65. package/templates/project/dotnet/src/App/Lakebase/DotEnv.cs +48 -0
  66. package/templates/project/dotnet/src/App/Lakebase/LakebaseConnection.cs +225 -0
  67. package/templates/project/dotnet/src/App/Program.cs +34 -0
  68. package/templates/project/dotnet/src/App/appsettings.json +9 -0
  69. package/templates/project/dotnet/tests/App.Tests/App.Tests.csproj +25 -0
  70. package/templates/project/dotnet/tests/App.Tests/HealthTests.cs +18 -0
  71. package/templates/project/dotnet/tests/App.Tests/OpenApiTests.cs +23 -0
  72. package/templates/project/dotnet-efcore/.config/dotnet-tools.json +13 -0
  73. package/templates/project/dotnet-efcore/src/App/App.csproj +22 -0
  74. package/templates/project/dotnet-efcore/src/App/Data/DesignTimeDbContextFactory.cs +24 -0
  75. package/templates/project/dotnet-efcore/src/App/Migrations/README.md +23 -0
  76. package/templates/project/dotnet-flyway/db/migrations/README.md +23 -0
  77. 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 fs10 from "fs";
5
- import * as path10 from "path";
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(path11) {
284
- if (!path11.includes("/branches/")) return null;
285
- const leaf = path11.split("/branches/").pop();
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.2.51".length > 0) {
431
- cached = "0.2.51";
430
+ if ("0.3.0".length > 0) {
431
+ cached = "0.3.0";
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 (fs4.existsSync(path3.join(dir, "pom.xml"))) {
551
- const kotlinDir = path3.join(dir, "src", "main", "kotlin");
552
- if (fs4.existsSync(kotlinDir)) {
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 = fs4.readFileSync(path3.join(dir, "pom.xml"), "utf-8");
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 (fs4.existsSync(path3.join(dir, "pyproject.toml")) || fs4.existsSync(path3.join(dir, "requirements.txt")) || fs4.existsSync(path3.join(dir, "alembic.ini"))) {
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 (fs4.existsSync(path3.join(dir, "package.json"))) {
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 = path3.resolve(projectDir);
589
- let dir = path3.resolve(projectDir, rel);
590
- while (dir === rootResolved || dir.startsWith(rootResolved + path3.sep)) {
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 = path3.dirname(dir);
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 fs6 from "fs";
606
- import * as path5 from "path";
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 fs5 from "fs";
611
- import * as path4 from "path";
678
+ import * as fs6 from "fs";
679
+ import * as path5 from "path";
612
680
  function resolveAlembicBin(projectDir) {
613
681
  const candidates = [
614
- path4.join(projectDir, ".venv", "bin", "alembic"),
615
- path4.join(projectDir, "venv", "bin", "alembic")
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 (fs5.existsSync(candidate)) return candidate;
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(path4.delimiter);
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 = path4.join(opts.projectDir, rel);
678
- if (!fs5.existsSync(dir)) continue;
679
- const hit = fs5.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
680
- if (hit) return path4.join(dir, hit);
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
- path5.join(projectDir, "migrations", "versions"),
815
- path5.join(projectDir, "alembic", "versions")
882
+ path6.join(projectDir, "migrations", "versions"),
883
+ path6.join(projectDir, "alembic", "versions")
816
884
  ];
817
- return candidates.find((p) => fs6.existsSync(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 = fs6.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
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 (fs6.existsSync(path5.join(projectDir, "alembic.ini"))) return true;
848
- if (fs6.existsSync(path5.join(projectDir, "migrations", "env.py"))) return true;
849
- if (fs6.existsSync(path5.join(projectDir, "alembic", "env.py"))) return true;
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: path5.basename(created), path: created };
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 = path5.basename(created).replace(/\.py$/, "").split("_")[0];
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 fs7 from "fs";
987
- import * as path7 from "path";
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 path6 from "path";
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:${path6.join(projectDir, "src", "main", "resources", "db", "migration")}`;
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 ? path6.basename(m.filepath) : `V${m.version}__migration.sql`;
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 = path7.join(projectDir, "src", "main", "resources", "db", "migration");
1126
- if (!fs7.existsSync(dir)) return [];
1127
- const files = fs7.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
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 fs7.existsSync(path7.join(projectDir, "pom.xml"));
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 = path7.join(args.projectDir, "src", "main", "resources", "db", "migration");
1209
- fs7.mkdirSync(dir, { recursive: true });
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 = path7.join(dir, filename);
1214
- if (fs7.existsSync(full)) throw new Error(`${filename} already exists`);
1215
- fs7.writeFileSync(
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 fs9 from "fs";
1238
- import * as path9 from "path";
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 fs8 from "fs";
1243
- import * as path8 from "path";
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 = path8.join(projectDir, name);
1248
- if (fs8.existsSync(p)) return p;
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 = path9.join(projectDir, "migrations");
1401
- if (!fs9.existsSync(dir)) return [];
1402
- const files = fs9.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
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) => fs9.existsSync(path9.join(projectDir, 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 = path9.basename(created).replace(/\.(js|ts)$/, "");
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: path9.basename(created), path: created };
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 = path10.join(projectDir, "src", "main", "resources", "db", "migration");
1555
- if (!fs10.existsSync(dir)) return [];
1556
- const files = fs10.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
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
- path10.join(projectDir, "migrations", "versions"),
1567
- path10.join(projectDir, "alembic", "versions")
1993
+ path13.join(projectDir, "migrations", "versions"),
1994
+ path13.join(projectDir, "alembic", "versions")
1568
1995
  ];
1569
- const dir = candidates.find((p) => fs10.existsSync(p));
1996
+ const dir = candidates.find((p) => fs12.existsSync(p));
1570
1997
  if (!dir) return [];
1571
- const files = fs10.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
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 = path10.join(projectDir, "migrations");
1582
- if (!fs10.existsSync(dir)) return [];
1583
- const files = fs10.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
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) {