@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,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // scripts/lakebase/scm-claim-feature.cli.ts
4
- import * as path14 from "path";
4
+ import * as path17 from "path";
5
5
 
6
6
  // scripts/util/cli-entry.ts
7
7
  import { realpathSync } from "fs";
@@ -452,9 +452,9 @@ function asBranchUid(s) {
452
452
  }
453
453
  return s;
454
454
  }
455
- function branchNameFromResourcePath(path15) {
456
- if (!path15.includes("/branches/")) return null;
457
- const leaf = path15.split("/branches/").pop();
455
+ function branchNameFromResourcePath(path18) {
456
+ if (!path18.includes("/branches/")) return null;
457
+ const leaf = path18.split("/branches/").pop();
458
458
  if (!leaf) return null;
459
459
  try {
460
460
  return asBranchName(leaf);
@@ -864,8 +864,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
864
864
  var cached;
865
865
  function substrateSelfVersion() {
866
866
  if (cached !== void 0) return cached;
867
- if ("0.2.50".length > 0) {
868
- cached = "0.2.50";
867
+ if ("0.3.0".length > 0) {
868
+ cached = "0.3.0";
869
869
  return cached;
870
870
  }
871
871
  cached = "unknown";
@@ -1745,27 +1745,92 @@ function alreadyClaimedSentinel(state) {
1745
1745
  }
1746
1746
 
1747
1747
  // scripts/lakebase/schema-migrate.ts
1748
- import * as fs13 from "fs";
1749
- import * as path13 from "path";
1748
+ import * as fs15 from "fs";
1749
+ import * as path16 from "path";
1750
1750
 
1751
1751
  // scripts/lakebase/migration-layout.ts
1752
+ import * as fs8 from "fs";
1753
+ import * as path7 from "path";
1754
+
1755
+ // scripts/lakebase/dotnet-project.ts
1752
1756
  import * as fs7 from "fs";
1753
1757
  import * as path6 from "path";
1758
+ var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
1759
+ var DOTNET_FLYWAY_DIR = path6.join("db", "migrations");
1760
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
1761
+ function safeReaddir(dir) {
1762
+ try {
1763
+ return fs7.readdirSync(dir, { withFileTypes: true });
1764
+ } catch {
1765
+ return [];
1766
+ }
1767
+ }
1768
+ function hasDotnetMarker(dir) {
1769
+ return safeReaddir(dir).some(
1770
+ (e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
1771
+ );
1772
+ }
1773
+ function findCsprojFiles(projectDir) {
1774
+ const found = [];
1775
+ const walk = (dir, depth) => {
1776
+ for (const e of safeReaddir(dir)) {
1777
+ const full = path6.join(dir, e.name);
1778
+ if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
1779
+ found.push(full);
1780
+ } else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
1781
+ walk(full, depth + 1);
1782
+ }
1783
+ }
1784
+ };
1785
+ walk(projectDir, 0);
1786
+ return found.sort();
1787
+ }
1788
+ function findEfCoreProject(projectDir) {
1789
+ for (const csproj of findCsprojFiles(projectDir)) {
1790
+ let content = "";
1791
+ try {
1792
+ content = fs7.readFileSync(csproj, "utf-8");
1793
+ } catch {
1794
+ continue;
1795
+ }
1796
+ if (content.includes(EF_DESIGN_PACKAGE)) {
1797
+ const dir = path6.dirname(csproj);
1798
+ return { csproj, projectDir: dir, migrationsDir: path6.join(dir, "Migrations") };
1799
+ }
1800
+ }
1801
+ return null;
1802
+ }
1803
+ function isDotnetProject(projectDir) {
1804
+ return hasDotnetMarker(projectDir);
1805
+ }
1806
+ function isDotnetFlywayProject(projectDir) {
1807
+ return isDotnetProject(projectDir) && fs7.existsSync(path6.join(projectDir, DOTNET_FLYWAY_DIR));
1808
+ }
1809
+ var MAVEN_FLYWAY_DIR = path6.join("src", "main", "resources", "db", "migration");
1810
+ function flywayMigrationsDir(projectDir) {
1811
+ const maven = path6.join(projectDir, MAVEN_FLYWAY_DIR);
1812
+ if (fs7.existsSync(maven) || fs7.existsSync(path6.join(projectDir, "pom.xml"))) return maven;
1813
+ if (isDotnetFlywayProject(projectDir)) return path6.join(projectDir, DOTNET_FLYWAY_DIR);
1814
+ return maven;
1815
+ }
1816
+
1817
+ // scripts/lakebase/migration-layout.ts
1754
1818
  var MIGRATION_LANGUAGES = [
1755
1819
  "java",
1756
1820
  "kotlin",
1757
1821
  "python",
1758
1822
  "nodejs",
1823
+ "dotnet",
1759
1824
  "unknown"
1760
1825
  ];
1761
1826
  function detectLanguageAt(dir) {
1762
- if (fs7.existsSync(path6.join(dir, "pom.xml"))) {
1763
- const kotlinDir = path6.join(dir, "src", "main", "kotlin");
1764
- if (fs7.existsSync(kotlinDir)) {
1827
+ if (fs8.existsSync(path7.join(dir, "pom.xml"))) {
1828
+ const kotlinDir = path7.join(dir, "src", "main", "kotlin");
1829
+ if (fs8.existsSync(kotlinDir)) {
1765
1830
  return "kotlin";
1766
1831
  }
1767
1832
  try {
1768
- const pom = fs7.readFileSync(path6.join(dir, "pom.xml"), "utf-8");
1833
+ const pom = fs8.readFileSync(path7.join(dir, "pom.xml"), "utf-8");
1769
1834
  if (pom.includes("kotlin-maven-plugin")) {
1770
1835
  return "kotlin";
1771
1836
  }
@@ -1773,10 +1838,13 @@ function detectLanguageAt(dir) {
1773
1838
  }
1774
1839
  return "java";
1775
1840
  }
1776
- if (fs7.existsSync(path6.join(dir, "pyproject.toml")) || fs7.existsSync(path6.join(dir, "requirements.txt")) || fs7.existsSync(path6.join(dir, "alembic.ini"))) {
1841
+ if (fs8.existsSync(path7.join(dir, "pyproject.toml")) || fs8.existsSync(path7.join(dir, "requirements.txt")) || fs8.existsSync(path7.join(dir, "alembic.ini"))) {
1777
1842
  return "python";
1778
1843
  }
1779
- if (fs7.existsSync(path6.join(dir, "package.json"))) {
1844
+ if (hasDotnetMarker(dir)) {
1845
+ return "dotnet";
1846
+ }
1847
+ if (fs8.existsSync(path7.join(dir, "package.json"))) {
1780
1848
  return "nodejs";
1781
1849
  }
1782
1850
  return "unknown";
@@ -1797,14 +1865,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
1797
1865
  if (!rel) {
1798
1866
  return "unknown";
1799
1867
  }
1800
- const rootResolved = path6.resolve(projectDir);
1801
- let dir = path6.resolve(projectDir, rel);
1802
- while (dir === rootResolved || dir.startsWith(rootResolved + path6.sep)) {
1868
+ const rootResolved = path7.resolve(projectDir);
1869
+ let dir = path7.resolve(projectDir, rel);
1870
+ while (dir === rootResolved || dir.startsWith(rootResolved + path7.sep)) {
1803
1871
  const lang = detectLanguageAt(dir);
1804
1872
  if (lang !== "unknown") {
1805
1873
  return lang;
1806
1874
  }
1807
- const parent = path6.dirname(dir);
1875
+ const parent = path7.dirname(dir);
1808
1876
  if (parent === dir) {
1809
1877
  break;
1810
1878
  }
@@ -1814,21 +1882,21 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
1814
1882
  }
1815
1883
 
1816
1884
  // scripts/lakebase/adapters/alembic-adapter.ts
1817
- import * as fs9 from "fs";
1818
- import * as path8 from "path";
1885
+ import * as fs10 from "fs";
1886
+ import * as path9 from "path";
1819
1887
 
1820
1888
  // scripts/lakebase/schema-migrate-runners/alembic.ts
1821
1889
  import { spawn } from "child_process";
1822
- import * as fs8 from "fs";
1823
- import * as path7 from "path";
1890
+ import * as fs9 from "fs";
1891
+ import * as path8 from "path";
1824
1892
  function resolveAlembicBin(projectDir) {
1825
1893
  const candidates = [
1826
- path7.join(projectDir, ".venv", "bin", "alembic"),
1827
- path7.join(projectDir, "venv", "bin", "alembic")
1894
+ path8.join(projectDir, ".venv", "bin", "alembic"),
1895
+ path8.join(projectDir, "venv", "bin", "alembic")
1828
1896
  ];
1829
1897
  for (const candidate of candidates) {
1830
1898
  try {
1831
- if (fs8.existsSync(candidate)) return candidate;
1899
+ if (fs9.existsSync(candidate)) return candidate;
1832
1900
  } catch {
1833
1901
  }
1834
1902
  }
@@ -1838,7 +1906,7 @@ function spawnAlembic(projectDir, args, dsn) {
1838
1906
  return new Promise((resolve3, reject) => {
1839
1907
  const bin = resolveAlembicBin(projectDir);
1840
1908
  const env = { ...process.env };
1841
- env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path7.delimiter);
1909
+ env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path8.delimiter);
1842
1910
  if (dsn) env.DATABASE_URL = dsn;
1843
1911
  const child = spawn(bin, args, {
1844
1912
  cwd: projectDir,
@@ -1886,10 +1954,10 @@ async function createAlembicRevision(opts) {
1886
1954
  const m = stdout.match(/Generating\s+(\S+\.py)/);
1887
1955
  if (m) return m[1].trim();
1888
1956
  for (const rel of ["migrations/versions", "alembic/versions"]) {
1889
- const dir = path7.join(opts.projectDir, rel);
1890
- if (!fs8.existsSync(dir)) continue;
1891
- const hit = fs8.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
1892
- if (hit) return path7.join(dir, hit);
1957
+ const dir = path8.join(opts.projectDir, rel);
1958
+ if (!fs9.existsSync(dir)) continue;
1959
+ const hit = fs9.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
1960
+ if (hit) return path8.join(dir, hit);
1893
1961
  }
1894
1962
  throw new SchemaMigrationError(
1895
1963
  `alembic revision succeeded but the created file could not be located.
@@ -2023,15 +2091,15 @@ async function buildDsn(args) {
2023
2091
  }
2024
2092
  function findVersionsDir(projectDir) {
2025
2093
  const candidates = [
2026
- path8.join(projectDir, "migrations", "versions"),
2027
- path8.join(projectDir, "alembic", "versions")
2094
+ path9.join(projectDir, "migrations", "versions"),
2095
+ path9.join(projectDir, "alembic", "versions")
2028
2096
  ];
2029
- return candidates.find((p) => fs9.existsSync(p));
2097
+ return candidates.find((p) => fs10.existsSync(p));
2030
2098
  }
2031
2099
  function listAlembicFiles(projectDir) {
2032
2100
  const dir = findVersionsDir(projectDir);
2033
2101
  if (!dir) return [];
2034
- const files = fs9.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
2102
+ const files = fs10.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
2035
2103
  return files.map((filename) => {
2036
2104
  const stem = filename.replace(/\.py$/, "");
2037
2105
  const sep2 = stem.indexOf("_");
@@ -2056,9 +2124,9 @@ var AlembicAdapter = {
2056
2124
  * here. Callers can still force-select via project.yaml#migration_tool.
2057
2125
  */
2058
2126
  detect(projectDir) {
2059
- if (fs9.existsSync(path8.join(projectDir, "alembic.ini"))) return true;
2060
- if (fs9.existsSync(path8.join(projectDir, "migrations", "env.py"))) return true;
2061
- if (fs9.existsSync(path8.join(projectDir, "alembic", "env.py"))) return true;
2127
+ if (fs10.existsSync(path9.join(projectDir, "alembic.ini"))) return true;
2128
+ if (fs10.existsSync(path9.join(projectDir, "migrations", "env.py"))) return true;
2129
+ if (fs10.existsSync(path9.join(projectDir, "alembic", "env.py"))) return true;
2062
2130
  return false;
2063
2131
  },
2064
2132
  async apply(args) {
@@ -2164,7 +2232,7 @@ var AlembicAdapter = {
2164
2232
  autogenerate: !!args.autogenerate,
2165
2233
  dsn
2166
2234
  });
2167
- return { status: "ok", version: revId, filename: path8.basename(created), path: created };
2235
+ return { status: "ok", version: revId, filename: path9.basename(created), path: created };
2168
2236
  } catch (err) {
2169
2237
  return {
2170
2238
  status: "error",
@@ -2181,7 +2249,7 @@ var AlembicAdapter = {
2181
2249
  if (heads.length <= 1) return { status: "noop", headsBefore: heads };
2182
2250
  if (args.dryRun) return { status: "ok", headsBefore: heads };
2183
2251
  const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
2184
- const mergeRevision = path8.basename(created).replace(/\.py$/, "").split("_")[0];
2252
+ const mergeRevision = path9.basename(created).replace(/\.py$/, "").split("_")[0];
2185
2253
  return { status: "ok", headsBefore: heads, mergeRevision, path: created };
2186
2254
  } catch (err) {
2187
2255
  return {
@@ -2195,12 +2263,12 @@ var AlembicAdapter = {
2195
2263
  registerSchemaMigrationAdapter(AlembicAdapter);
2196
2264
 
2197
2265
  // scripts/lakebase/adapters/flyway-adapter.ts
2198
- import * as fs10 from "fs";
2199
- import * as path10 from "path";
2266
+ import * as fs11 from "fs";
2267
+ import * as path11 from "path";
2200
2268
 
2201
2269
  // scripts/lakebase/schema-migrate-runners/flyway.ts
2202
2270
  import { spawn as spawn2 } from "child_process";
2203
- import * as path9 from "path";
2271
+ import * as path10 from "path";
2204
2272
  function dsnToFlywayEnv(dsn) {
2205
2273
  const u = new URL(dsn);
2206
2274
  const user = decodeURIComponent(u.username);
@@ -2210,7 +2278,7 @@ function dsnToFlywayEnv(dsn) {
2210
2278
  return { url, user, password };
2211
2279
  }
2212
2280
  function migrationsLocation(projectDir) {
2213
- return `filesystem:${path9.join(projectDir, "src", "main", "resources", "db", "migration")}`;
2281
+ return `filesystem:${flywayMigrationsDir(projectDir)}`;
2214
2282
  }
2215
2283
  function runFlyway(ctx, args) {
2216
2284
  const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
@@ -2311,7 +2379,7 @@ async function statusFlyway(ctx) {
2311
2379
  if (state === "SUCCESS" || state === "BASELINE") {
2312
2380
  current = m.version;
2313
2381
  } else if (state === "PENDING") {
2314
- const filename = m.filepath ? path9.basename(m.filepath) : `V${m.version}__migration.sql`;
2382
+ const filename = m.filepath ? path10.basename(m.filepath) : `V${m.version}__migration.sql`;
2315
2383
  pending.push({
2316
2384
  version: m.version,
2317
2385
  filename,
@@ -2334,9 +2402,9 @@ async function buildDsn2(args) {
2334
2402
  return result.url;
2335
2403
  }
2336
2404
  function listFlywayFiles(projectDir) {
2337
- const dir = path10.join(projectDir, "src", "main", "resources", "db", "migration");
2338
- if (!fs10.existsSync(dir)) return [];
2339
- const files = fs10.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
2405
+ const dir = flywayMigrationsDir(projectDir);
2406
+ if (!fs11.existsSync(dir)) return [];
2407
+ const files = fs11.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
2340
2408
  return files.map((filename) => {
2341
2409
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
2342
2410
  const version = m[1];
@@ -2357,9 +2425,9 @@ function versionCompare(a, b) {
2357
2425
  }
2358
2426
  var FlywayAdapter = {
2359
2427
  id: "flyway",
2360
- languages: ["java", "kotlin"],
2428
+ languages: ["java", "kotlin", "dotnet"],
2361
2429
  detect(projectDir) {
2362
- return fs10.existsSync(path10.join(projectDir, "pom.xml"));
2430
+ return fs11.existsSync(path11.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
2363
2431
  },
2364
2432
  async apply(args) {
2365
2433
  const dsn = await buildDsn2(args);
@@ -2417,14 +2485,14 @@ var FlywayAdapter = {
2417
2485
  // optional-protocol shape makes this additive.
2418
2486
  async newMigration(args) {
2419
2487
  try {
2420
- const dir = path10.join(args.projectDir, "src", "main", "resources", "db", "migration");
2421
- fs10.mkdirSync(dir, { recursive: true });
2488
+ const dir = flywayMigrationsDir(args.projectDir);
2489
+ fs11.mkdirSync(dir, { recursive: true });
2422
2490
  const version = migrationTimestamp();
2423
2491
  const slug = migrationSlug2(args.slug);
2424
2492
  const filename = `V${version}__${slug}.sql`;
2425
- const full = path10.join(dir, filename);
2426
- if (fs10.existsSync(full)) throw new Error(`${filename} already exists`);
2427
- fs10.writeFileSync(
2493
+ const full = path11.join(dir, filename);
2494
+ if (fs11.existsSync(full)) throw new Error(`${filename} already exists`);
2495
+ fs11.writeFileSync(
2428
2496
  full,
2429
2497
  `-- V${version}: ${args.slug}
2430
2498
  -- Flyway migration (write your DDL/DML below).
@@ -2446,18 +2514,18 @@ var FlywayAdapter = {
2446
2514
  registerSchemaMigrationAdapter(FlywayAdapter);
2447
2515
 
2448
2516
  // scripts/lakebase/adapters/knex-adapter.ts
2449
- import * as fs12 from "fs";
2450
- import * as path12 from "path";
2517
+ import * as fs13 from "fs";
2518
+ import * as path13 from "path";
2451
2519
 
2452
2520
  // scripts/lakebase/schema-migrate-runners/knex.ts
2453
2521
  import { spawn as spawn3 } from "child_process";
2454
- import * as fs11 from "fs";
2455
- import * as path11 from "path";
2522
+ import * as fs12 from "fs";
2523
+ import * as path12 from "path";
2456
2524
  var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
2457
2525
  function findKnexfile(projectDir) {
2458
2526
  for (const name of KNEXFILE_VARIANTS) {
2459
- const p = path11.join(projectDir, name);
2460
- if (fs11.existsSync(p)) return p;
2527
+ const p = path12.join(projectDir, name);
2528
+ if (fs12.existsSync(p)) return p;
2461
2529
  }
2462
2530
  return void 0;
2463
2531
  }
@@ -2609,9 +2677,9 @@ async function buildDsn3(args) {
2609
2677
  }
2610
2678
  var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
2611
2679
  function listKnexFiles(projectDir) {
2612
- const dir = path12.join(projectDir, "migrations");
2613
- if (!fs12.existsSync(dir)) return [];
2614
- const files = fs12.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
2680
+ const dir = path13.join(projectDir, "migrations");
2681
+ if (!fs13.existsSync(dir)) return [];
2682
+ const files = fs13.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
2615
2683
  return files.map((filename) => {
2616
2684
  const stem = filename.replace(/\.(js|ts)$/, "");
2617
2685
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -2631,7 +2699,7 @@ var KnexAdapter = {
2631
2699
  * project.yaml#migration_tool.
2632
2700
  */
2633
2701
  detect(projectDir) {
2634
- return KNEXFILE_VARIANTS2.some((name) => fs12.existsSync(path12.join(projectDir, name)));
2702
+ return KNEXFILE_VARIANTS2.some((name) => fs13.existsSync(path13.join(projectDir, name)));
2635
2703
  },
2636
2704
  async apply(args) {
2637
2705
  const dsn = await buildDsn3(args);
@@ -2704,9 +2772,9 @@ var KnexAdapter = {
2704
2772
  async newMigration(args) {
2705
2773
  try {
2706
2774
  const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
2707
- const stem = path12.basename(created).replace(/\.(js|ts)$/, "");
2775
+ const stem = path13.basename(created).replace(/\.(js|ts)$/, "");
2708
2776
  const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
2709
- return { status: "ok", version, filename: path12.basename(created), path: created };
2777
+ return { status: "ok", version, filename: path13.basename(created), path: created };
2710
2778
  } catch (err) {
2711
2779
  return {
2712
2780
  status: "error",
@@ -2720,6 +2788,360 @@ var KnexAdapter = {
2720
2788
  };
2721
2789
  registerSchemaMigrationAdapter(KnexAdapter);
2722
2790
 
2791
+ // scripts/lakebase/adapters/efcore-adapter.ts
2792
+ import * as path15 from "path";
2793
+
2794
+ // scripts/lakebase/schema-migrate-runners/efcore.ts
2795
+ import { spawn as spawn4 } from "child_process";
2796
+ import * as fs14 from "fs";
2797
+ import * as path14 from "path";
2798
+ import { Client as Client2 } from "pg";
2799
+ var MIGRATION_ID = /^(\d{14})_(.+)$/;
2800
+ function efVersion(id) {
2801
+ return MIGRATION_ID.exec(id)?.[1] ?? id;
2802
+ }
2803
+ function efDescription(id) {
2804
+ const name = MIGRATION_ID.exec(id)?.[2] ?? id;
2805
+ return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
2806
+ }
2807
+ function dsnToNpgsql(dsn) {
2808
+ const u = new URL(dsn);
2809
+ const parts = [
2810
+ `Host=${u.hostname}`,
2811
+ `Port=${u.port || "5432"}`,
2812
+ `Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
2813
+ ];
2814
+ if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
2815
+ if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
2816
+ const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
2817
+ const ssl = {
2818
+ disable: "Disable",
2819
+ allow: "Allow",
2820
+ prefer: "Prefer",
2821
+ require: "Require",
2822
+ "verify-ca": "VerifyCA",
2823
+ "verify-full": "VerifyFull"
2824
+ };
2825
+ parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
2826
+ const app = u.searchParams.get("application_name");
2827
+ if (app) parts.push(`Application Name=${app}`);
2828
+ return parts.map((p) => p.replace(/;/g, "")).join(";");
2829
+ }
2830
+ function parseEfJson(stdout) {
2831
+ const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
2832
+ if (!body) {
2833
+ throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
2834
+ }
2835
+ try {
2836
+ return JSON.parse(body);
2837
+ } catch (err) {
2838
+ throw new SchemaMigrationError(
2839
+ `dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
2840
+ Body (first 400 chars): ${body.slice(0, 400)}`
2841
+ );
2842
+ }
2843
+ }
2844
+ function requireProject(projectDir) {
2845
+ const project = findEfCoreProject(projectDir);
2846
+ if (!project) {
2847
+ throw new SchemaMigrationError(
2848
+ `No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
2849
+ );
2850
+ }
2851
+ return project;
2852
+ }
2853
+ function spawnDotnet(projectDir, args, env) {
2854
+ return new Promise((resolve3, reject) => {
2855
+ const child = spawn4("dotnet", args, {
2856
+ cwd: projectDir,
2857
+ env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
2858
+ stdio: ["ignore", "pipe", "pipe"]
2859
+ });
2860
+ let stdout = "";
2861
+ let stderr = "";
2862
+ child.stdout.on("data", (chunk) => {
2863
+ stdout += chunk.toString("utf8");
2864
+ });
2865
+ child.stderr.on("data", (chunk) => {
2866
+ stderr += chunk.toString("utf8");
2867
+ });
2868
+ child.on("error", (err) => {
2869
+ reject(
2870
+ new SchemaMigrationError(
2871
+ `Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
2872
+ err
2873
+ )
2874
+ );
2875
+ });
2876
+ child.on("close", (code) => {
2877
+ if (code === 0) {
2878
+ resolve3({ stdout, stderr });
2879
+ } else {
2880
+ reject(
2881
+ new SchemaMigrationError(
2882
+ `dotnet ${args.join(" ")} exited with code ${code}.
2883
+ stdout: ${stdout}
2884
+ stderr: ${stderr}`
2885
+ )
2886
+ );
2887
+ }
2888
+ });
2889
+ });
2890
+ }
2891
+ var restored = /* @__PURE__ */ new Set();
2892
+ async function ensureRestored(projectDir, csproj) {
2893
+ if (restored.has(projectDir)) return;
2894
+ if (fs14.existsSync(path14.join(projectDir, ".config", "dotnet-tools.json"))) {
2895
+ await spawnDotnet(projectDir, ["tool", "restore"], {});
2896
+ }
2897
+ await spawnDotnet(projectDir, ["restore", csproj], {});
2898
+ restored.add(projectDir);
2899
+ }
2900
+ async function runEf(projectDir, args, dsn, opts = {}) {
2901
+ const project = requireProject(projectDir);
2902
+ const csproj = path14.relative(projectDir, project.csproj) || project.csproj;
2903
+ await ensureRestored(projectDir, csproj);
2904
+ const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
2905
+ if (opts.noBuild) full.push("--no-build");
2906
+ const env = {};
2907
+ if (dsn) {
2908
+ env.DATABASE_URL = dsn;
2909
+ if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
2910
+ }
2911
+ return spawnDotnet(projectDir, full, env);
2912
+ }
2913
+ async function listEfMigrations(ctx, opts = {}) {
2914
+ const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
2915
+ const entries = parseEfJson(stdout);
2916
+ if (ctx.dsn && entries.some((e) => e.applied === null)) {
2917
+ const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
2918
+ throw new SchemaMigrationError(
2919
+ `dotnet ef could not read migration state from the database (check connectivity and credentials).
2920
+ ${warning || stderr}`.trim()
2921
+ );
2922
+ }
2923
+ return entries;
2924
+ }
2925
+ async function readEfHistory(dsn) {
2926
+ const client = new Client2({ connectionString: dsn, application_name: connectionApplicationName() });
2927
+ await client.connect();
2928
+ try {
2929
+ const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
2930
+ if (!exists.rows[0]?.present) return null;
2931
+ const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
2932
+ return r.rows.map((row) => row.MigrationId);
2933
+ } finally {
2934
+ await client.end();
2935
+ }
2936
+ }
2937
+ function toApplied(entries) {
2938
+ return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
2939
+ }
2940
+ async function statusEfcore(ctx) {
2941
+ const entries = await listEfMigrations(ctx);
2942
+ const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
2943
+ const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
2944
+ const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
2945
+ return { current, pending, tool: "efcore" };
2946
+ }
2947
+ async function applyEfcore(ctx) {
2948
+ const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
2949
+ await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
2950
+ const after = await listEfMigrations(ctx, { noBuild: true });
2951
+ const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
2952
+ return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
2953
+ }
2954
+ function resolveEfTarget(target, applied) {
2955
+ const t = target.trim();
2956
+ if (t === "0") return "0";
2957
+ const rel = /^-(\d+)$/.exec(t);
2958
+ if (rel) {
2959
+ const steps = Number(rel[1]);
2960
+ if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
2961
+ const index = applied.length - 1 - steps;
2962
+ if (index < -1) {
2963
+ throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
2964
+ }
2965
+ return index === -1 ? "0" : applied[index].id;
2966
+ }
2967
+ const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
2968
+ if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
2969
+ return match.id;
2970
+ }
2971
+ async function rollbackEfcore(ctx) {
2972
+ const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
2973
+ const targetId = resolveEfTarget(ctx.target, beforeEntries);
2974
+ await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
2975
+ const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
2976
+ return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
2977
+ }
2978
+ function efMigrationName(slug) {
2979
+ const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
2980
+ if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
2981
+ return /^[0-9]/.test(name) ? `M${name}` : name;
2982
+ }
2983
+ async function createEfcoreMigration(opts) {
2984
+ const project = requireProject(opts.projectDir);
2985
+ const name = efMigrationName(opts.slug);
2986
+ const existing = new Set(fs14.existsSync(project.migrationsDir) ? fs14.readdirSync(project.migrationsDir) : []);
2987
+ await runEf(opts.projectDir, ["migrations", "add", name]);
2988
+ const created = fs14.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
2989
+ if (created.length !== 1) {
2990
+ throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
2991
+ }
2992
+ return { id: created[0].replace(/\.cs$/, ""), path: path14.join(project.migrationsDir, created[0]) };
2993
+ }
2994
+ function localEfMigrationIds(projectDir) {
2995
+ const project = findEfCoreProject(projectDir);
2996
+ if (!project || !fs14.existsSync(project.migrationsDir)) return [];
2997
+ return fs14.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();
2998
+ }
2999
+ function efProductVersion(project) {
3000
+ try {
3001
+ const csproj = fs14.readFileSync(project.csproj, "utf-8");
3002
+ const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
3003
+ if (m) return m[1];
3004
+ } catch {
3005
+ }
3006
+ return "unknown";
3007
+ }
3008
+ async function stampEfcore(ctx) {
3009
+ const project = requireProject(ctx.projectDir);
3010
+ const ids = localEfMigrationIds(ctx.projectDir);
3011
+ if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
3012
+ const rev = ctx.revision.trim();
3013
+ const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
3014
+ if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
3015
+ const toStamp = ids.slice(0, end + 1);
3016
+ const productVersion = efProductVersion(project);
3017
+ const client = new Client2({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
3018
+ await client.connect();
3019
+ try {
3020
+ await client.query("BEGIN");
3021
+ await client.query(
3022
+ `CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
3023
+ "MigrationId" character varying(150) NOT NULL,
3024
+ "ProductVersion" character varying(32) NOT NULL,
3025
+ CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
3026
+ );
3027
+ await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
3028
+ for (const id of toStamp) {
3029
+ await client.query(
3030
+ `INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
3031
+ ON CONFLICT ("MigrationId") DO NOTHING`,
3032
+ [id, productVersion]
3033
+ );
3034
+ }
3035
+ await client.query("COMMIT");
3036
+ } catch (err) {
3037
+ await client.query("ROLLBACK").catch(() => void 0);
3038
+ throw err;
3039
+ } finally {
3040
+ await client.end();
3041
+ }
3042
+ return efVersion(toStamp[toStamp.length - 1]);
3043
+ }
3044
+
3045
+ // scripts/lakebase/adapters/efcore-adapter.ts
3046
+ async function buildDsn4(args) {
3047
+ const result = await getConnection({
3048
+ output: "dsn",
3049
+ instance: args.instance,
3050
+ branch: args.branch,
3051
+ database: args.database,
3052
+ endpointName: args.endpointName
3053
+ });
3054
+ return result.url;
3055
+ }
3056
+ function errMsg(err) {
3057
+ return err instanceof Error ? err.message : String(err);
3058
+ }
3059
+ function listEfCoreFiles(projectDir) {
3060
+ return localEfMigrationIds(projectDir).map((id) => ({
3061
+ version: efVersion(id),
3062
+ filename: `${id}.cs`,
3063
+ description: efDescription(id),
3064
+ type: "CSharp",
3065
+ tool: "efcore"
3066
+ }));
3067
+ }
3068
+ var EfCoreAdapter = {
3069
+ id: "efcore",
3070
+ languages: ["dotnet"],
3071
+ /** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
3072
+ detect(projectDir) {
3073
+ return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
3074
+ },
3075
+ async apply(args) {
3076
+ try {
3077
+ const dsn = await buildDsn4(args);
3078
+ const r = await applyEfcore({ projectDir: args.projectDir, dsn });
3079
+ return {
3080
+ applied_migrations: r.applied,
3081
+ status: r.alreadyAtLatest ? "noop" : "ok",
3082
+ tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
3083
+ };
3084
+ } catch (err) {
3085
+ return { applied_migrations: [], status: "error", error: errMsg(err) };
3086
+ }
3087
+ },
3088
+ async rollback(args) {
3089
+ try {
3090
+ const dsn = await buildDsn4(args);
3091
+ const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
3092
+ return {
3093
+ rolled_back: r.rolledBack,
3094
+ status: r.rolledBack.length === 0 ? "noop" : "ok",
3095
+ tool_specific: { tool: r.tool }
3096
+ };
3097
+ } catch (err) {
3098
+ return { rolled_back: [], status: "error", error: errMsg(err) };
3099
+ }
3100
+ },
3101
+ async stamp(args) {
3102
+ try {
3103
+ const dsn = await buildDsn4(args);
3104
+ const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
3105
+ return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
3106
+ } catch (err) {
3107
+ return { status: "error", stamped_revision: null, error: errMsg(err) };
3108
+ }
3109
+ },
3110
+ async status(args) {
3111
+ try {
3112
+ const dsn = await buildDsn4(args);
3113
+ const r = await statusEfcore({ projectDir: args.projectDir, dsn });
3114
+ return {
3115
+ applied_version: r.current ?? null,
3116
+ pending: r.pending,
3117
+ applied: [],
3118
+ status: "ok",
3119
+ tool_specific: { tool: r.tool }
3120
+ };
3121
+ } catch (err) {
3122
+ return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
3123
+ }
3124
+ },
3125
+ async list(args) {
3126
+ return { files: listEfCoreFiles(args.projectDir) };
3127
+ },
3128
+ // baseline: use stamp (EF has no separate baseline concept).
3129
+ async newMigration(args) {
3130
+ try {
3131
+ const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
3132
+ return {
3133
+ status: "ok",
3134
+ version: efVersion(created.id),
3135
+ filename: path15.basename(created.path),
3136
+ path: created.path
3137
+ };
3138
+ } catch (err) {
3139
+ return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
3140
+ }
3141
+ }
3142
+ };
3143
+ registerSchemaMigrationAdapter(EfCoreAdapter);
3144
+
2723
3145
  // scripts/lakebase/schema-migrate.ts
2724
3146
  var SchemaMigrationError = class extends Error {
2725
3147
  constructor(message, cause) {
@@ -2733,13 +3155,16 @@ function detectLanguage(projectDir) {
2733
3155
  const lang = resolveMigrationLanguage(projectDir);
2734
3156
  if (lang === "unknown") {
2735
3157
  throw new SchemaMigrationError(
2736
- `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.`
3158
+ `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.`
2737
3159
  );
2738
3160
  }
2739
3161
  return lang;
2740
3162
  }
2741
- function toolForLanguage(language) {
3163
+ function toolForLanguage(language, projectDir) {
2742
3164
  switch (language) {
3165
+ case "dotnet":
3166
+ if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
3167
+ return "efcore";
2743
3168
  case "java":
2744
3169
  case "kotlin":
2745
3170
  return "flyway";
@@ -2752,8 +3177,10 @@ function toolForLanguage(language) {
2752
3177
  function listSchemaMigrations(args = {}) {
2753
3178
  const projectDir = args.projectDir ?? process.cwd();
2754
3179
  const language = args.language ?? detectLanguage(projectDir);
2755
- const tool = toolForLanguage(language);
3180
+ const tool = toolForLanguage(language, projectDir);
2756
3181
  switch (tool) {
3182
+ case "efcore":
3183
+ return listEfCoreFiles(projectDir);
2757
3184
  case "flyway":
2758
3185
  return listFlywayMigrations(projectDir);
2759
3186
  case "alembic":
@@ -2763,9 +3190,9 @@ function listSchemaMigrations(args = {}) {
2763
3190
  }
2764
3191
  }
2765
3192
  function listFlywayMigrations(projectDir) {
2766
- const dir = path13.join(projectDir, "src", "main", "resources", "db", "migration");
2767
- if (!fs13.existsSync(dir)) return [];
2768
- const files = fs13.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
3193
+ const dir = flywayMigrationsDir(projectDir);
3194
+ if (!fs15.existsSync(dir)) return [];
3195
+ const files = fs15.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
2769
3196
  return files.map((filename) => {
2770
3197
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
2771
3198
  const version = m[1];
@@ -2775,12 +3202,12 @@ function listFlywayMigrations(projectDir) {
2775
3202
  }
2776
3203
  function listAlembicMigrations(projectDir) {
2777
3204
  const candidates = [
2778
- path13.join(projectDir, "migrations", "versions"),
2779
- path13.join(projectDir, "alembic", "versions")
3205
+ path16.join(projectDir, "migrations", "versions"),
3206
+ path16.join(projectDir, "alembic", "versions")
2780
3207
  ];
2781
- const dir = candidates.find((p) => fs13.existsSync(p));
3208
+ const dir = candidates.find((p) => fs15.existsSync(p));
2782
3209
  if (!dir) return [];
2783
- const files = fs13.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
3210
+ const files = fs15.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
2784
3211
  return files.map((filename) => {
2785
3212
  const stem = filename.replace(/\.py$/, "");
2786
3213
  const sep2 = stem.indexOf("_");
@@ -2790,9 +3217,9 @@ function listAlembicMigrations(projectDir) {
2790
3217
  }).sort((a, b) => a.filename.localeCompare(b.filename));
2791
3218
  }
2792
3219
  function listKnexMigrations(projectDir) {
2793
- const dir = path13.join(projectDir, "migrations");
2794
- if (!fs13.existsSync(dir)) return [];
2795
- const files = fs13.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
3220
+ const dir = path16.join(projectDir, "migrations");
3221
+ if (!fs15.existsSync(dir)) return [];
3222
+ const files = fs15.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
2796
3223
  return files.map((filename) => {
2797
3224
  const stem = filename.replace(/\.(js|ts)$/, "");
2798
3225
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -2814,7 +3241,7 @@ function versionCompare2(a, b) {
2814
3241
  return 0;
2815
3242
  }
2816
3243
  function adapterFor(projectDir, language) {
2817
- const override = language ? toolForLanguage(language) : void 0;
3244
+ const override = language ? toolForLanguage(language, projectDir) : void 0;
2818
3245
  return resolveSchemaMigrationAdapter(projectDir, override);
2819
3246
  }
2820
3247
  function dbRevisionOrphaned(appliedRevision, localRevisionIds) {
@@ -3008,7 +3435,7 @@ ${HELP}
3008
3435
  );
3009
3436
  return 2;
3010
3437
  }
3011
- const projectDir = path14.resolve(args.projectDir ?? process.cwd());
3438
+ const projectDir = path17.resolve(args.projectDir ?? process.cwd());
3012
3439
  try {
3013
3440
  const result = await claimFeatureBranch({
3014
3441
  projectDir,