@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.
Files changed (78) hide show
  1. package/dist/scripts/index.cjs +2636 -2124
  2. package/dist/scripts/index.d.cts +1 -1
  3. package/dist/scripts/index.d.ts +1 -1
  4. package/dist/scripts/index.js +680 -178
  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 +2633 -2121
  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 +674 -172
  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 +14 -12
  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/.gitignore.base +7 -0
  57. package/templates/project/common/scripts/flyway-migrate.sh +54 -1
  58. package/templates/project/common/scripts/prepare-schema-diff.sh +5 -0
  59. package/templates/project/common/scripts/run-dev.sh +10 -1
  60. package/templates/project/common/scripts/run-tests.sh +6 -1
  61. package/templates/project/dotnet/.gitignore.extra +8 -0
  62. package/templates/project/dotnet/App.sln +56 -0
  63. package/templates/project/dotnet/Makefile +13 -0
  64. package/templates/project/dotnet/global.json +6 -0
  65. package/templates/project/dotnet/src/App/Data/AppDbContext.cs +12 -0
  66. package/templates/project/dotnet/src/App/Lakebase/DotEnv.cs +48 -0
  67. package/templates/project/dotnet/src/App/Lakebase/LakebaseConnection.cs +225 -0
  68. package/templates/project/dotnet/src/App/Program.cs +34 -0
  69. package/templates/project/dotnet/src/App/appsettings.json +9 -0
  70. package/templates/project/dotnet/tests/App.Tests/App.Tests.csproj +25 -0
  71. package/templates/project/dotnet/tests/App.Tests/HealthTests.cs +18 -0
  72. package/templates/project/dotnet/tests/App.Tests/OpenApiTests.cs +23 -0
  73. package/templates/project/dotnet-efcore/.config/dotnet-tools.json +13 -0
  74. package/templates/project/dotnet-efcore/src/App/App.csproj +22 -0
  75. package/templates/project/dotnet-efcore/src/App/Data/DesignTimeDbContextFactory.cs +24 -0
  76. package/templates/project/dotnet-efcore/src/App/Migrations/README.md +23 -0
  77. package/templates/project/dotnet-flyway/db/migrations/README.md +23 -0
  78. 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/infra-runner.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);
@@ -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.2.50".length > 0) {
397
- cached = "0.2.50";
396
+ if ("0.3.0".length > 0) {
397
+ cached = "0.3.0";
398
398
  return cached;
399
399
  }
400
400
  cached = "unknown";
@@ -511,16 +511,16 @@ function isAllSchemas(schema) {
511
511
  }
512
512
  function buildSchemaQuery(schema) {
513
513
  const cols = "c.table_schema, c.table_name, c.column_name, c.data_type";
514
- const join11 = "FROM information_schema.columns c JOIN pg_tables t ON c.table_name = t.tablename AND c.table_schema = t.schemaname ";
514
+ const join12 = "FROM information_schema.columns c JOIN pg_tables t ON c.table_name = t.tablename AND c.table_schema = t.schemaname ";
515
515
  if (isAllSchemas(schema)) {
516
516
  return {
517
- text: `SELECT ${cols} ` + join11 + `WHERE ${SYSTEM_SCHEMA_FILTER} ORDER BY c.table_schema, c.table_name, c.ordinal_position`,
517
+ text: `SELECT ${cols} ` + join12 + `WHERE ${SYSTEM_SCHEMA_FILTER} ORDER BY c.table_schema, c.table_name, c.ordinal_position`,
518
518
  values: []
519
519
  };
520
520
  }
521
521
  const one = (schema ?? "").trim() || "public";
522
522
  return {
523
- text: `SELECT ${cols} ` + join11 + "WHERE c.table_schema = $1 ORDER BY c.table_name, c.ordinal_position",
523
+ text: `SELECT ${cols} ` + join12 + "WHERE c.table_schema = $1 ORDER BY c.table_name, c.ordinal_position",
524
524
  values: [one]
525
525
  };
526
526
  }
@@ -684,25 +684,87 @@ function dbcli3(args) {
684
684
  }
685
685
 
686
686
  // scripts/lakebase/schema-migrate.ts
687
- import * as fs9 from "fs";
688
- import * as path9 from "path";
687
+ import * as fs11 from "fs";
688
+ import * as path12 from "path";
689
+
690
+ // scripts/lakebase/dotnet-project.ts
691
+ import * as fs4 from "fs";
692
+ import * as path3 from "path";
693
+ var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
694
+ var DOTNET_FLYWAY_DIR = path3.join("db", "migrations");
695
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
696
+ function safeReaddir(dir) {
697
+ try {
698
+ return fs4.readdirSync(dir, { withFileTypes: true });
699
+ } catch {
700
+ return [];
701
+ }
702
+ }
703
+ function hasDotnetMarker(dir) {
704
+ return safeReaddir(dir).some(
705
+ (e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
706
+ );
707
+ }
708
+ function findCsprojFiles(projectDir) {
709
+ const found = [];
710
+ const walk = (dir, depth) => {
711
+ for (const e of safeReaddir(dir)) {
712
+ const full = path3.join(dir, e.name);
713
+ if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
714
+ found.push(full);
715
+ } else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
716
+ walk(full, depth + 1);
717
+ }
718
+ }
719
+ };
720
+ walk(projectDir, 0);
721
+ return found.sort();
722
+ }
723
+ function findEfCoreProject(projectDir) {
724
+ for (const csproj of findCsprojFiles(projectDir)) {
725
+ let content = "";
726
+ try {
727
+ content = fs4.readFileSync(csproj, "utf-8");
728
+ } catch {
729
+ continue;
730
+ }
731
+ if (content.includes(EF_DESIGN_PACKAGE)) {
732
+ const dir = path3.dirname(csproj);
733
+ return { csproj, projectDir: dir, migrationsDir: path3.join(dir, "Migrations") };
734
+ }
735
+ }
736
+ return null;
737
+ }
738
+ function isDotnetProject(projectDir) {
739
+ return hasDotnetMarker(projectDir);
740
+ }
741
+ function isDotnetFlywayProject(projectDir) {
742
+ return isDotnetProject(projectDir) && fs4.existsSync(path3.join(projectDir, DOTNET_FLYWAY_DIR));
743
+ }
744
+ var MAVEN_FLYWAY_DIR = path3.join("src", "main", "resources", "db", "migration");
745
+ function flywayMigrationsDir(projectDir) {
746
+ const maven = path3.join(projectDir, MAVEN_FLYWAY_DIR);
747
+ if (fs4.existsSync(maven) || fs4.existsSync(path3.join(projectDir, "pom.xml"))) return maven;
748
+ if (isDotnetFlywayProject(projectDir)) return path3.join(projectDir, DOTNET_FLYWAY_DIR);
749
+ return maven;
750
+ }
689
751
 
690
752
  // scripts/lakebase/adapters/alembic-adapter.ts
691
- import * as fs5 from "fs";
692
- import * as path4 from "path";
753
+ import * as fs6 from "fs";
754
+ import * as path5 from "path";
693
755
 
694
756
  // scripts/lakebase/schema-migrate-runners/alembic.ts
695
757
  import { spawn } from "child_process";
696
- import * as fs4 from "fs";
697
- import * as path3 from "path";
758
+ import * as fs5 from "fs";
759
+ import * as path4 from "path";
698
760
  function resolveAlembicBin(projectDir) {
699
761
  const candidates = [
700
- path3.join(projectDir, ".venv", "bin", "alembic"),
701
- path3.join(projectDir, "venv", "bin", "alembic")
762
+ path4.join(projectDir, ".venv", "bin", "alembic"),
763
+ path4.join(projectDir, "venv", "bin", "alembic")
702
764
  ];
703
765
  for (const candidate of candidates) {
704
766
  try {
705
- if (fs4.existsSync(candidate)) return candidate;
767
+ if (fs5.existsSync(candidate)) return candidate;
706
768
  } catch {
707
769
  }
708
770
  }
@@ -712,7 +774,7 @@ function spawnAlembic(projectDir, args, dsn) {
712
774
  return new Promise((resolve, reject) => {
713
775
  const bin = resolveAlembicBin(projectDir);
714
776
  const env = { ...process.env };
715
- env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path3.delimiter);
777
+ env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path4.delimiter);
716
778
  if (dsn) env.DATABASE_URL = dsn;
717
779
  const child = spawn(bin, args, {
718
780
  cwd: projectDir,
@@ -760,10 +822,10 @@ async function createAlembicRevision(opts) {
760
822
  const m = stdout.match(/Generating\s+(\S+\.py)/);
761
823
  if (m) return m[1].trim();
762
824
  for (const rel of ["migrations/versions", "alembic/versions"]) {
763
- const dir = path3.join(opts.projectDir, rel);
764
- if (!fs4.existsSync(dir)) continue;
765
- const hit = fs4.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
766
- if (hit) return path3.join(dir, hit);
825
+ const dir = path4.join(opts.projectDir, rel);
826
+ if (!fs5.existsSync(dir)) continue;
827
+ const hit = fs5.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
828
+ if (hit) return path4.join(dir, hit);
767
829
  }
768
830
  throw new SchemaMigrationError(
769
831
  `alembic revision succeeded but the created file could not be located.
@@ -897,15 +959,15 @@ async function buildDsn(args) {
897
959
  }
898
960
  function findVersionsDir(projectDir) {
899
961
  const candidates = [
900
- path4.join(projectDir, "migrations", "versions"),
901
- path4.join(projectDir, "alembic", "versions")
962
+ path5.join(projectDir, "migrations", "versions"),
963
+ path5.join(projectDir, "alembic", "versions")
902
964
  ];
903
- return candidates.find((p) => fs5.existsSync(p));
965
+ return candidates.find((p) => fs6.existsSync(p));
904
966
  }
905
967
  function listAlembicFiles(projectDir) {
906
968
  const dir = findVersionsDir(projectDir);
907
969
  if (!dir) return [];
908
- const files = fs5.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
970
+ const files = fs6.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
909
971
  return files.map((filename) => {
910
972
  const stem = filename.replace(/\.py$/, "");
911
973
  const sep = stem.indexOf("_");
@@ -930,9 +992,9 @@ var AlembicAdapter = {
930
992
  * here. Callers can still force-select via project.yaml#migration_tool.
931
993
  */
932
994
  detect(projectDir) {
933
- if (fs5.existsSync(path4.join(projectDir, "alembic.ini"))) return true;
934
- if (fs5.existsSync(path4.join(projectDir, "migrations", "env.py"))) return true;
935
- if (fs5.existsSync(path4.join(projectDir, "alembic", "env.py"))) return true;
995
+ if (fs6.existsSync(path5.join(projectDir, "alembic.ini"))) return true;
996
+ if (fs6.existsSync(path5.join(projectDir, "migrations", "env.py"))) return true;
997
+ if (fs6.existsSync(path5.join(projectDir, "alembic", "env.py"))) return true;
936
998
  return false;
937
999
  },
938
1000
  async apply(args) {
@@ -1038,7 +1100,7 @@ var AlembicAdapter = {
1038
1100
  autogenerate: !!args.autogenerate,
1039
1101
  dsn
1040
1102
  });
1041
- return { status: "ok", version: revId, filename: path4.basename(created), path: created };
1103
+ return { status: "ok", version: revId, filename: path5.basename(created), path: created };
1042
1104
  } catch (err) {
1043
1105
  return {
1044
1106
  status: "error",
@@ -1055,7 +1117,7 @@ var AlembicAdapter = {
1055
1117
  if (heads.length <= 1) return { status: "noop", headsBefore: heads };
1056
1118
  if (args.dryRun) return { status: "ok", headsBefore: heads };
1057
1119
  const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
1058
- const mergeRevision = path4.basename(created).replace(/\.py$/, "").split("_")[0];
1120
+ const mergeRevision = path5.basename(created).replace(/\.py$/, "").split("_")[0];
1059
1121
  return { status: "ok", headsBefore: heads, mergeRevision, path: created };
1060
1122
  } catch (err) {
1061
1123
  return {
@@ -1069,12 +1131,12 @@ var AlembicAdapter = {
1069
1131
  registerSchemaMigrationAdapter(AlembicAdapter);
1070
1132
 
1071
1133
  // scripts/lakebase/adapters/flyway-adapter.ts
1072
- import * as fs6 from "fs";
1073
- import * as path6 from "path";
1134
+ import * as fs7 from "fs";
1135
+ import * as path7 from "path";
1074
1136
 
1075
1137
  // scripts/lakebase/schema-migrate-runners/flyway.ts
1076
1138
  import { spawn as spawn2 } from "child_process";
1077
- import * as path5 from "path";
1139
+ import * as path6 from "path";
1078
1140
  function dsnToFlywayEnv(dsn) {
1079
1141
  const u = new URL(dsn);
1080
1142
  const user = decodeURIComponent(u.username);
@@ -1084,7 +1146,7 @@ function dsnToFlywayEnv(dsn) {
1084
1146
  return { url, user, password };
1085
1147
  }
1086
1148
  function migrationsLocation(projectDir) {
1087
- return `filesystem:${path5.join(projectDir, "src", "main", "resources", "db", "migration")}`;
1149
+ return `filesystem:${flywayMigrationsDir(projectDir)}`;
1088
1150
  }
1089
1151
  function runFlyway(ctx, args) {
1090
1152
  const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
@@ -1185,7 +1247,7 @@ async function statusFlyway(ctx) {
1185
1247
  if (state === "SUCCESS" || state === "BASELINE") {
1186
1248
  current = m.version;
1187
1249
  } else if (state === "PENDING") {
1188
- const filename = m.filepath ? path5.basename(m.filepath) : `V${m.version}__migration.sql`;
1250
+ const filename = m.filepath ? path6.basename(m.filepath) : `V${m.version}__migration.sql`;
1189
1251
  pending.push({
1190
1252
  version: m.version,
1191
1253
  filename,
@@ -1208,9 +1270,9 @@ async function buildDsn2(args) {
1208
1270
  return result.url;
1209
1271
  }
1210
1272
  function listFlywayFiles(projectDir) {
1211
- const dir = path6.join(projectDir, "src", "main", "resources", "db", "migration");
1212
- if (!fs6.existsSync(dir)) return [];
1213
- const files = fs6.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
1273
+ const dir = flywayMigrationsDir(projectDir);
1274
+ if (!fs7.existsSync(dir)) return [];
1275
+ const files = fs7.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
1214
1276
  return files.map((filename) => {
1215
1277
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
1216
1278
  const version = m[1];
@@ -1231,9 +1293,9 @@ function versionCompare(a, b) {
1231
1293
  }
1232
1294
  var FlywayAdapter = {
1233
1295
  id: "flyway",
1234
- languages: ["java", "kotlin"],
1296
+ languages: ["java", "kotlin", "dotnet"],
1235
1297
  detect(projectDir) {
1236
- return fs6.existsSync(path6.join(projectDir, "pom.xml"));
1298
+ return fs7.existsSync(path7.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
1237
1299
  },
1238
1300
  async apply(args) {
1239
1301
  const dsn = await buildDsn2(args);
@@ -1291,14 +1353,14 @@ var FlywayAdapter = {
1291
1353
  // optional-protocol shape makes this additive.
1292
1354
  async newMigration(args) {
1293
1355
  try {
1294
- const dir = path6.join(args.projectDir, "src", "main", "resources", "db", "migration");
1295
- fs6.mkdirSync(dir, { recursive: true });
1356
+ const dir = flywayMigrationsDir(args.projectDir);
1357
+ fs7.mkdirSync(dir, { recursive: true });
1296
1358
  const version = migrationTimestamp();
1297
1359
  const slug = migrationSlug2(args.slug);
1298
1360
  const filename = `V${version}__${slug}.sql`;
1299
- const full = path6.join(dir, filename);
1300
- if (fs6.existsSync(full)) throw new Error(`${filename} already exists`);
1301
- fs6.writeFileSync(
1361
+ const full = path7.join(dir, filename);
1362
+ if (fs7.existsSync(full)) throw new Error(`${filename} already exists`);
1363
+ fs7.writeFileSync(
1302
1364
  full,
1303
1365
  `-- V${version}: ${args.slug}
1304
1366
  -- Flyway migration (write your DDL/DML below).
@@ -1320,18 +1382,18 @@ var FlywayAdapter = {
1320
1382
  registerSchemaMigrationAdapter(FlywayAdapter);
1321
1383
 
1322
1384
  // scripts/lakebase/adapters/knex-adapter.ts
1323
- import * as fs8 from "fs";
1324
- import * as path8 from "path";
1385
+ import * as fs9 from "fs";
1386
+ import * as path9 from "path";
1325
1387
 
1326
1388
  // scripts/lakebase/schema-migrate-runners/knex.ts
1327
1389
  import { spawn as spawn3 } from "child_process";
1328
- import * as fs7 from "fs";
1329
- import * as path7 from "path";
1390
+ import * as fs8 from "fs";
1391
+ import * as path8 from "path";
1330
1392
  var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
1331
1393
  function findKnexfile(projectDir) {
1332
1394
  for (const name of KNEXFILE_VARIANTS) {
1333
- const p = path7.join(projectDir, name);
1334
- if (fs7.existsSync(p)) return p;
1395
+ const p = path8.join(projectDir, name);
1396
+ if (fs8.existsSync(p)) return p;
1335
1397
  }
1336
1398
  return void 0;
1337
1399
  }
@@ -1483,9 +1545,9 @@ async function buildDsn3(args) {
1483
1545
  }
1484
1546
  var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
1485
1547
  function listKnexFiles(projectDir) {
1486
- const dir = path8.join(projectDir, "migrations");
1487
- if (!fs8.existsSync(dir)) return [];
1488
- const files = fs8.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
1548
+ const dir = path9.join(projectDir, "migrations");
1549
+ if (!fs9.existsSync(dir)) return [];
1550
+ const files = fs9.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
1489
1551
  return files.map((filename) => {
1490
1552
  const stem = filename.replace(/\.(js|ts)$/, "");
1491
1553
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -1505,7 +1567,7 @@ var KnexAdapter = {
1505
1567
  * project.yaml#migration_tool.
1506
1568
  */
1507
1569
  detect(projectDir) {
1508
- return KNEXFILE_VARIANTS2.some((name) => fs8.existsSync(path8.join(projectDir, name)));
1570
+ return KNEXFILE_VARIANTS2.some((name) => fs9.existsSync(path9.join(projectDir, name)));
1509
1571
  },
1510
1572
  async apply(args) {
1511
1573
  const dsn = await buildDsn3(args);
@@ -1578,9 +1640,9 @@ var KnexAdapter = {
1578
1640
  async newMigration(args) {
1579
1641
  try {
1580
1642
  const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
1581
- const stem = path8.basename(created).replace(/\.(js|ts)$/, "");
1643
+ const stem = path9.basename(created).replace(/\.(js|ts)$/, "");
1582
1644
  const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
1583
- return { status: "ok", version, filename: path8.basename(created), path: created };
1645
+ return { status: "ok", version, filename: path9.basename(created), path: created };
1584
1646
  } catch (err) {
1585
1647
  return {
1586
1648
  status: "error",
@@ -1594,6 +1656,360 @@ var KnexAdapter = {
1594
1656
  };
1595
1657
  registerSchemaMigrationAdapter(KnexAdapter);
1596
1658
 
1659
+ // scripts/lakebase/adapters/efcore-adapter.ts
1660
+ import * as path11 from "path";
1661
+
1662
+ // scripts/lakebase/schema-migrate-runners/efcore.ts
1663
+ import { spawn as spawn4 } from "child_process";
1664
+ import * as fs10 from "fs";
1665
+ import * as path10 from "path";
1666
+ import { Client as Client3 } from "pg";
1667
+ var MIGRATION_ID = /^(\d{14})_(.+)$/;
1668
+ function efVersion(id) {
1669
+ return MIGRATION_ID.exec(id)?.[1] ?? id;
1670
+ }
1671
+ function efDescription(id) {
1672
+ const name = MIGRATION_ID.exec(id)?.[2] ?? id;
1673
+ return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
1674
+ }
1675
+ function dsnToNpgsql(dsn) {
1676
+ const u = new URL(dsn);
1677
+ const parts = [
1678
+ `Host=${u.hostname}`,
1679
+ `Port=${u.port || "5432"}`,
1680
+ `Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
1681
+ ];
1682
+ if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
1683
+ if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
1684
+ const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
1685
+ const ssl = {
1686
+ disable: "Disable",
1687
+ allow: "Allow",
1688
+ prefer: "Prefer",
1689
+ require: "Require",
1690
+ "verify-ca": "VerifyCA",
1691
+ "verify-full": "VerifyFull"
1692
+ };
1693
+ parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
1694
+ const app = u.searchParams.get("application_name");
1695
+ if (app) parts.push(`Application Name=${app}`);
1696
+ return parts.map((p) => p.replace(/;/g, "")).join(";");
1697
+ }
1698
+ function parseEfJson(stdout) {
1699
+ const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
1700
+ if (!body) {
1701
+ throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
1702
+ }
1703
+ try {
1704
+ return JSON.parse(body);
1705
+ } catch (err) {
1706
+ throw new SchemaMigrationError(
1707
+ `dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
1708
+ Body (first 400 chars): ${body.slice(0, 400)}`
1709
+ );
1710
+ }
1711
+ }
1712
+ function requireProject(projectDir) {
1713
+ const project = findEfCoreProject(projectDir);
1714
+ if (!project) {
1715
+ throw new SchemaMigrationError(
1716
+ `No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
1717
+ );
1718
+ }
1719
+ return project;
1720
+ }
1721
+ function spawnDotnet(projectDir, args, env) {
1722
+ return new Promise((resolve, reject) => {
1723
+ const child = spawn4("dotnet", args, {
1724
+ cwd: projectDir,
1725
+ env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
1726
+ stdio: ["ignore", "pipe", "pipe"]
1727
+ });
1728
+ let stdout = "";
1729
+ let stderr = "";
1730
+ child.stdout.on("data", (chunk) => {
1731
+ stdout += chunk.toString("utf8");
1732
+ });
1733
+ child.stderr.on("data", (chunk) => {
1734
+ stderr += chunk.toString("utf8");
1735
+ });
1736
+ child.on("error", (err) => {
1737
+ reject(
1738
+ new SchemaMigrationError(
1739
+ `Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
1740
+ err
1741
+ )
1742
+ );
1743
+ });
1744
+ child.on("close", (code) => {
1745
+ if (code === 0) {
1746
+ resolve({ stdout, stderr });
1747
+ } else {
1748
+ reject(
1749
+ new SchemaMigrationError(
1750
+ `dotnet ${args.join(" ")} exited with code ${code}.
1751
+ stdout: ${stdout}
1752
+ stderr: ${stderr}`
1753
+ )
1754
+ );
1755
+ }
1756
+ });
1757
+ });
1758
+ }
1759
+ var restored = /* @__PURE__ */ new Set();
1760
+ async function ensureRestored(projectDir, csproj) {
1761
+ if (restored.has(projectDir)) return;
1762
+ if (fs10.existsSync(path10.join(projectDir, ".config", "dotnet-tools.json"))) {
1763
+ await spawnDotnet(projectDir, ["tool", "restore"], {});
1764
+ }
1765
+ await spawnDotnet(projectDir, ["restore", csproj], {});
1766
+ restored.add(projectDir);
1767
+ }
1768
+ async function runEf(projectDir, args, dsn, opts = {}) {
1769
+ const project = requireProject(projectDir);
1770
+ const csproj = path10.relative(projectDir, project.csproj) || project.csproj;
1771
+ await ensureRestored(projectDir, csproj);
1772
+ const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
1773
+ if (opts.noBuild) full.push("--no-build");
1774
+ const env = {};
1775
+ if (dsn) {
1776
+ env.DATABASE_URL = dsn;
1777
+ if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
1778
+ }
1779
+ return spawnDotnet(projectDir, full, env);
1780
+ }
1781
+ async function listEfMigrations(ctx, opts = {}) {
1782
+ const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
1783
+ const entries = parseEfJson(stdout);
1784
+ if (ctx.dsn && entries.some((e) => e.applied === null)) {
1785
+ const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
1786
+ throw new SchemaMigrationError(
1787
+ `dotnet ef could not read migration state from the database (check connectivity and credentials).
1788
+ ${warning || stderr}`.trim()
1789
+ );
1790
+ }
1791
+ return entries;
1792
+ }
1793
+ async function readEfHistory(dsn) {
1794
+ const client = new Client3({ connectionString: dsn, application_name: connectionApplicationName() });
1795
+ await client.connect();
1796
+ try {
1797
+ const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
1798
+ if (!exists.rows[0]?.present) return null;
1799
+ const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
1800
+ return r.rows.map((row) => row.MigrationId);
1801
+ } finally {
1802
+ await client.end();
1803
+ }
1804
+ }
1805
+ function toApplied(entries) {
1806
+ return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
1807
+ }
1808
+ async function statusEfcore(ctx) {
1809
+ const entries = await listEfMigrations(ctx);
1810
+ const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
1811
+ const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
1812
+ const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
1813
+ return { current, pending, tool: "efcore" };
1814
+ }
1815
+ async function applyEfcore(ctx) {
1816
+ const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
1817
+ await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
1818
+ const after = await listEfMigrations(ctx, { noBuild: true });
1819
+ const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
1820
+ return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
1821
+ }
1822
+ function resolveEfTarget(target, applied) {
1823
+ const t = target.trim();
1824
+ if (t === "0") return "0";
1825
+ const rel = /^-(\d+)$/.exec(t);
1826
+ if (rel) {
1827
+ const steps = Number(rel[1]);
1828
+ if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
1829
+ const index = applied.length - 1 - steps;
1830
+ if (index < -1) {
1831
+ throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
1832
+ }
1833
+ return index === -1 ? "0" : applied[index].id;
1834
+ }
1835
+ const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
1836
+ if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
1837
+ return match.id;
1838
+ }
1839
+ async function rollbackEfcore(ctx) {
1840
+ const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
1841
+ const targetId = resolveEfTarget(ctx.target, beforeEntries);
1842
+ await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
1843
+ const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
1844
+ return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
1845
+ }
1846
+ function efMigrationName(slug) {
1847
+ const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
1848
+ if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
1849
+ return /^[0-9]/.test(name) ? `M${name}` : name;
1850
+ }
1851
+ async function createEfcoreMigration(opts) {
1852
+ const project = requireProject(opts.projectDir);
1853
+ const name = efMigrationName(opts.slug);
1854
+ const existing = new Set(fs10.existsSync(project.migrationsDir) ? fs10.readdirSync(project.migrationsDir) : []);
1855
+ await runEf(opts.projectDir, ["migrations", "add", name]);
1856
+ const created = fs10.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
1857
+ if (created.length !== 1) {
1858
+ throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
1859
+ }
1860
+ return { id: created[0].replace(/\.cs$/, ""), path: path10.join(project.migrationsDir, created[0]) };
1861
+ }
1862
+ function localEfMigrationIds(projectDir) {
1863
+ const project = findEfCoreProject(projectDir);
1864
+ if (!project || !fs10.existsSync(project.migrationsDir)) return [];
1865
+ return fs10.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();
1866
+ }
1867
+ function efProductVersion(project) {
1868
+ try {
1869
+ const csproj = fs10.readFileSync(project.csproj, "utf-8");
1870
+ const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
1871
+ if (m) return m[1];
1872
+ } catch {
1873
+ }
1874
+ return "unknown";
1875
+ }
1876
+ async function stampEfcore(ctx) {
1877
+ const project = requireProject(ctx.projectDir);
1878
+ const ids = localEfMigrationIds(ctx.projectDir);
1879
+ if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
1880
+ const rev = ctx.revision.trim();
1881
+ const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
1882
+ if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
1883
+ const toStamp = ids.slice(0, end + 1);
1884
+ const productVersion = efProductVersion(project);
1885
+ const client = new Client3({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
1886
+ await client.connect();
1887
+ try {
1888
+ await client.query("BEGIN");
1889
+ await client.query(
1890
+ `CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
1891
+ "MigrationId" character varying(150) NOT NULL,
1892
+ "ProductVersion" character varying(32) NOT NULL,
1893
+ CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
1894
+ );
1895
+ await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
1896
+ for (const id of toStamp) {
1897
+ await client.query(
1898
+ `INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
1899
+ ON CONFLICT ("MigrationId") DO NOTHING`,
1900
+ [id, productVersion]
1901
+ );
1902
+ }
1903
+ await client.query("COMMIT");
1904
+ } catch (err) {
1905
+ await client.query("ROLLBACK").catch(() => void 0);
1906
+ throw err;
1907
+ } finally {
1908
+ await client.end();
1909
+ }
1910
+ return efVersion(toStamp[toStamp.length - 1]);
1911
+ }
1912
+
1913
+ // scripts/lakebase/adapters/efcore-adapter.ts
1914
+ async function buildDsn4(args) {
1915
+ const result = await getConnection({
1916
+ output: "dsn",
1917
+ instance: args.instance,
1918
+ branch: args.branch,
1919
+ database: args.database,
1920
+ endpointName: args.endpointName
1921
+ });
1922
+ return result.url;
1923
+ }
1924
+ function errMsg(err) {
1925
+ return err instanceof Error ? err.message : String(err);
1926
+ }
1927
+ function listEfCoreFiles(projectDir) {
1928
+ return localEfMigrationIds(projectDir).map((id) => ({
1929
+ version: efVersion(id),
1930
+ filename: `${id}.cs`,
1931
+ description: efDescription(id),
1932
+ type: "CSharp",
1933
+ tool: "efcore"
1934
+ }));
1935
+ }
1936
+ var EfCoreAdapter = {
1937
+ id: "efcore",
1938
+ languages: ["dotnet"],
1939
+ /** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
1940
+ detect(projectDir) {
1941
+ return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
1942
+ },
1943
+ async apply(args) {
1944
+ try {
1945
+ const dsn = await buildDsn4(args);
1946
+ const r = await applyEfcore({ projectDir: args.projectDir, dsn });
1947
+ return {
1948
+ applied_migrations: r.applied,
1949
+ status: r.alreadyAtLatest ? "noop" : "ok",
1950
+ tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
1951
+ };
1952
+ } catch (err) {
1953
+ return { applied_migrations: [], status: "error", error: errMsg(err) };
1954
+ }
1955
+ },
1956
+ async rollback(args) {
1957
+ try {
1958
+ const dsn = await buildDsn4(args);
1959
+ const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
1960
+ return {
1961
+ rolled_back: r.rolledBack,
1962
+ status: r.rolledBack.length === 0 ? "noop" : "ok",
1963
+ tool_specific: { tool: r.tool }
1964
+ };
1965
+ } catch (err) {
1966
+ return { rolled_back: [], status: "error", error: errMsg(err) };
1967
+ }
1968
+ },
1969
+ async stamp(args) {
1970
+ try {
1971
+ const dsn = await buildDsn4(args);
1972
+ const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
1973
+ return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
1974
+ } catch (err) {
1975
+ return { status: "error", stamped_revision: null, error: errMsg(err) };
1976
+ }
1977
+ },
1978
+ async status(args) {
1979
+ try {
1980
+ const dsn = await buildDsn4(args);
1981
+ const r = await statusEfcore({ projectDir: args.projectDir, dsn });
1982
+ return {
1983
+ applied_version: r.current ?? null,
1984
+ pending: r.pending,
1985
+ applied: [],
1986
+ status: "ok",
1987
+ tool_specific: { tool: r.tool }
1988
+ };
1989
+ } catch (err) {
1990
+ return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
1991
+ }
1992
+ },
1993
+ async list(args) {
1994
+ return { files: listEfCoreFiles(args.projectDir) };
1995
+ },
1996
+ // baseline: use stamp (EF has no separate baseline concept).
1997
+ async newMigration(args) {
1998
+ try {
1999
+ const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
2000
+ return {
2001
+ status: "ok",
2002
+ version: efVersion(created.id),
2003
+ filename: path11.basename(created.path),
2004
+ path: created.path
2005
+ };
2006
+ } catch (err) {
2007
+ return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
2008
+ }
2009
+ }
2010
+ };
2011
+ registerSchemaMigrationAdapter(EfCoreAdapter);
2012
+
1597
2013
  // scripts/lakebase/schema-migrate.ts
1598
2014
  var SchemaMigrationError = class extends Error {
1599
2015
  constructor(message, cause) {
@@ -1603,8 +2019,11 @@ var SchemaMigrationError = class extends Error {
1603
2019
  }
1604
2020
  cause;
1605
2021
  };
1606
- function toolForLanguage(language) {
2022
+ function toolForLanguage(language, projectDir) {
1607
2023
  switch (language) {
2024
+ case "dotnet":
2025
+ if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
2026
+ return "efcore";
1608
2027
  case "java":
1609
2028
  case "kotlin":
1610
2029
  return "flyway";
@@ -1615,7 +2034,7 @@ function toolForLanguage(language) {
1615
2034
  }
1616
2035
  }
1617
2036
  function adapterFor(projectDir, language) {
1618
- const override = language ? toolForLanguage(language) : void 0;
2037
+ const override = language ? toolForLanguage(language, projectDir) : void 0;
1619
2038
  return resolveSchemaMigrationAdapter(projectDir, override);
1620
2039
  }
1621
2040
  async function schemaMigrationStatus(args) {
@@ -1688,8 +2107,8 @@ async function runInfraSuite(args) {
1688
2107
  duration_ms: Date.now() - start
1689
2108
  };
1690
2109
  if (args.junitOutput) {
1691
- fs10.mkdirSync(path10.dirname(args.junitOutput), { recursive: true });
1692
- fs10.writeFileSync(args.junitOutput, formatJUnit(result), "utf8");
2110
+ fs12.mkdirSync(path13.dirname(args.junitOutput), { recursive: true });
2111
+ fs12.writeFileSync(args.junitOutput, formatJUnit(result), "utf8");
1693
2112
  }
1694
2113
  return result;
1695
2114
  }