@databricks-solutions/lakebase-scm-utils 0.2.51 → 0.3.1

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 (79) hide show
  1. package/README.md +10 -4
  2. package/dist/scripts/index.cjs +2813 -2244
  3. package/dist/scripts/index.d.cts +1 -1
  4. package/dist/scripts/index.d.ts +1 -1
  5. package/dist/scripts/index.js +857 -298
  6. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2460 -2030
  7. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +508 -81
  8. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2429 -2007
  9. package/dist/scripts/lakebase/collapse-heads.cli.js +477 -58
  10. package/dist/scripts/lakebase/detect-language.cli.cjs +2441 -2016
  11. package/dist/scripts/lakebase/detect-language.cli.js +489 -67
  12. package/dist/scripts/lakebase/doctor.cli.cjs +2484 -2040
  13. package/dist/scripts/lakebase/doctor.cli.js +532 -91
  14. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  15. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  16. package/dist/scripts/lakebase/index.cjs +2810 -2205
  17. package/dist/scripts/lakebase/index.d.cts +59 -10
  18. package/dist/scripts/lakebase/index.d.ts +59 -10
  19. package/dist/scripts/lakebase/index.js +851 -256
  20. package/dist/scripts/lakebase/infra-runner.cli.cjs +2435 -2013
  21. package/dist/scripts/lakebase/infra-runner.cli.js +483 -64
  22. package/dist/scripts/lakebase/new-migration.cli.cjs +2429 -2007
  23. package/dist/scripts/lakebase/new-migration.cli.js +477 -58
  24. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  25. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  26. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2461 -2029
  27. package/dist/scripts/lakebase/schema-migrate.cli.js +509 -80
  28. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2460 -2030
  29. package/dist/scripts/lakebase/scm-claim-feature.cli.js +508 -81
  30. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2486 -2056
  31. package/dist/scripts/lakebase/scm-doctor.cli.js +537 -107
  32. package/dist/scripts/lakebase/scm-merge.cli.cjs +2443 -2021
  33. package/dist/scripts/lakebase/scm-merge.cli.js +494 -72
  34. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2464 -2034
  35. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +511 -84
  36. package/dist/scripts/util/index.cjs +2 -2
  37. package/dist/scripts/util/index.js +2 -2
  38. package/package.json +1 -1
  39. package/scripts/lakebase/adapters/efcore-adapter.ts +158 -0
  40. package/scripts/lakebase/adapters/flyway-adapter.ts +8 -5
  41. package/scripts/lakebase/client-lockfile.ts +102 -0
  42. package/scripts/lakebase/collapse-heads.cli.ts +1 -1
  43. package/scripts/lakebase/create-project.ts +25 -2
  44. package/scripts/lakebase/detect-language.cli.ts +2 -2
  45. package/scripts/lakebase/doctor.ts +29 -0
  46. package/scripts/lakebase/dotnet-project.ts +112 -0
  47. package/scripts/lakebase/index.ts +1 -0
  48. package/scripts/lakebase/migration-layout.ts +43 -2
  49. package/scripts/lakebase/new-migration.cli.ts +1 -1
  50. package/scripts/lakebase/scaffold-language.ts +23 -2
  51. package/scripts/lakebase/scaffold.ts +4 -1
  52. package/scripts/lakebase/schema-migrate-runners/efcore.ts +399 -0
  53. package/scripts/lakebase/schema-migrate-runners/flyway.ts +6 -4
  54. package/scripts/lakebase/schema-migrate.cli.ts +3 -1
  55. package/scripts/lakebase/schema-migrate.ts +24 -10
  56. package/templates/project/common/.github/workflows/merge.yml +13 -1
  57. package/templates/project/common/.github/workflows/pr.yml +25 -5
  58. package/templates/project/common/scripts/flyway-migrate.sh +54 -1
  59. package/templates/project/common/scripts/prepare-schema-diff.sh +5 -0
  60. package/templates/project/common/scripts/run-dev.sh +10 -1
  61. package/templates/project/common/scripts/run-tests.sh +6 -1
  62. package/templates/project/dotnet/.gitignore.extra +8 -0
  63. package/templates/project/dotnet/App.sln +56 -0
  64. package/templates/project/dotnet/Makefile +13 -0
  65. package/templates/project/dotnet/global.json +6 -0
  66. package/templates/project/dotnet/src/App/Data/AppDbContext.cs +12 -0
  67. package/templates/project/dotnet/src/App/Lakebase/DotEnv.cs +48 -0
  68. package/templates/project/dotnet/src/App/Lakebase/LakebaseConnection.cs +225 -0
  69. package/templates/project/dotnet/src/App/Program.cs +34 -0
  70. package/templates/project/dotnet/src/App/appsettings.json +9 -0
  71. package/templates/project/dotnet/tests/App.Tests/App.Tests.csproj +25 -0
  72. package/templates/project/dotnet/tests/App.Tests/HealthTests.cs +18 -0
  73. package/templates/project/dotnet/tests/App.Tests/OpenApiTests.cs +23 -0
  74. package/templates/project/dotnet-efcore/.config/dotnet-tools.json +13 -0
  75. package/templates/project/dotnet-efcore/src/App/App.csproj +22 -0
  76. package/templates/project/dotnet-efcore/src/App/Data/DesignTimeDbContextFactory.cs +24 -0
  77. package/templates/project/dotnet-efcore/src/App/Migrations/README.md +23 -0
  78. package/templates/project/dotnet-flyway/db/migrations/README.md +23 -0
  79. package/templates/project/dotnet-flyway/src/App/App.csproj +17 -0
@@ -1457,8 +1457,8 @@ var require_light = __commonJS({
1457
1457
 
1458
1458
  // scripts/lakebase/scm-doctor.cli.ts
1459
1459
  init_esm_shims();
1460
- import * as fs14 from "fs";
1461
- import * as path15 from "path";
1460
+ import * as fs16 from "fs";
1461
+ import * as path18 from "path";
1462
1462
 
1463
1463
  // scripts/util/cli-entry.ts
1464
1464
  init_esm_shims();
@@ -1484,8 +1484,8 @@ function isCliEntry(importMetaUrl) {
1484
1484
 
1485
1485
  // scripts/lakebase/scm-doctor.ts
1486
1486
  init_esm_shims();
1487
- import * as fs13 from "fs";
1488
- import * as path14 from "path";
1487
+ import * as fs15 from "fs";
1488
+ import * as path17 from "path";
1489
1489
 
1490
1490
  // scripts/lakebase/branch-utils.ts
1491
1491
  init_esm_shims();
@@ -1856,9 +1856,9 @@ function asBranchUid(s) {
1856
1856
  }
1857
1857
  return s;
1858
1858
  }
1859
- function branchNameFromResourcePath(path16) {
1860
- if (!path16.includes("/branches/")) return null;
1861
- const leaf = path16.split("/branches/").pop();
1859
+ function branchNameFromResourcePath(path19) {
1860
+ if (!path19.includes("/branches/")) return null;
1861
+ const leaf = path19.split("/branches/").pop();
1862
1862
  if (!leaf) return null;
1863
1863
  try {
1864
1864
  return asBranchName(leaf);
@@ -3175,7 +3175,7 @@ paginateRest.VERSION = VERSION5;
3175
3175
 
3176
3176
  // node_modules/@octokit/plugin-paginate-graphql/dist-bundle/index.js
3177
3177
  init_esm_shims();
3178
- var generateMessage = (path16, cursorValue) => `The cursor at "${path16.join(
3178
+ var generateMessage = (path19, cursorValue) => `The cursor at "${path19.join(
3179
3179
  ","
3180
3180
  )}" did not change its value "${cursorValue}" after a page transition. Please make sure your that your query is set up correctly.`;
3181
3181
  var MissingCursorChange = class extends Error {
@@ -3216,9 +3216,9 @@ function findPaginatedResourcePath(responseData) {
3216
3216
  }
3217
3217
  return paginatedResourcePath;
3218
3218
  }
3219
- var deepFindPathToProperty = (object, searchProp, path16 = []) => {
3219
+ var deepFindPathToProperty = (object, searchProp, path19 = []) => {
3220
3220
  for (const key of Object.keys(object)) {
3221
- const currentPath = [...path16, key];
3221
+ const currentPath = [...path19, key];
3222
3222
  const currentValue = object[key];
3223
3223
  if (isObject(currentValue)) {
3224
3224
  if (currentValue.hasOwnProperty(searchProp)) {
@@ -3236,12 +3236,12 @@ var deepFindPathToProperty = (object, searchProp, path16 = []) => {
3236
3236
  }
3237
3237
  return [];
3238
3238
  };
3239
- var get = (object, path16) => {
3240
- return path16.reduce((current, nextProperty) => current[nextProperty], object);
3239
+ var get = (object, path19) => {
3240
+ return path19.reduce((current, nextProperty) => current[nextProperty], object);
3241
3241
  };
3242
- var set = (object, path16, mutator) => {
3243
- const lastProperty = path16[path16.length - 1];
3244
- const parentPath = [...path16].slice(0, -1);
3242
+ var set = (object, path19, mutator) => {
3243
+ const lastProperty = path19[path19.length - 1];
3244
+ const parentPath = [...path19].slice(0, -1);
3245
3245
  const parent = get(object, parentPath);
3246
3246
  if (typeof mutator === "function") {
3247
3247
  parent[lastProperty] = mutator(parent[lastProperty]);
@@ -3293,22 +3293,22 @@ var mergeResponses = (response1, response2) => {
3293
3293
  if (Object.keys(response1).length === 0) {
3294
3294
  return Object.assign(response1, response2);
3295
3295
  }
3296
- const path16 = findPaginatedResourcePath(response1);
3297
- const nodesPath = [...path16, "nodes"];
3296
+ const path19 = findPaginatedResourcePath(response1);
3297
+ const nodesPath = [...path19, "nodes"];
3298
3298
  const newNodes = get(response2, nodesPath);
3299
3299
  if (newNodes) {
3300
3300
  set(response1, nodesPath, (values) => {
3301
3301
  return [...values, ...newNodes];
3302
3302
  });
3303
3303
  }
3304
- const edgesPath = [...path16, "edges"];
3304
+ const edgesPath = [...path19, "edges"];
3305
3305
  const newEdges = get(response2, edgesPath);
3306
3306
  if (newEdges) {
3307
3307
  set(response1, edgesPath, (values) => {
3308
3308
  return [...values, ...newEdges];
3309
3309
  });
3310
3310
  }
3311
- const pageInfoPath = [...path16, "pageInfo"];
3311
+ const pageInfoPath = [...path19, "pageInfo"];
3312
3312
  set(response1, pageInfoPath, get(response2, pageInfoPath));
3313
3313
  return response1;
3314
3314
  };
@@ -5729,7 +5729,7 @@ var triggers_notification_paths_default = [
5729
5729
  ];
5730
5730
  function routeMatcher(paths) {
5731
5731
  const regexes = paths.map(
5732
- (path16) => path16.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
5732
+ (path19) => path19.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
5733
5733
  );
5734
5734
  const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
5735
5735
  return new RegExp(regex2, "i");
@@ -9393,8 +9393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
9393
9393
  var cached;
9394
9394
  function substrateSelfVersion() {
9395
9395
  if (cached !== void 0) return cached;
9396
- if ("0.2.51".length > 0) {
9397
- cached = "0.2.51";
9396
+ if ("0.3.1".length > 0) {
9397
+ cached = "0.3.1";
9398
9398
  return cached;
9399
9399
  }
9400
9400
  cached = "unknown";
@@ -10123,28 +10123,94 @@ async function abandonFeatureBranch(args) {
10123
10123
 
10124
10124
  // scripts/lakebase/schema-migrate.ts
10125
10125
  init_esm_shims();
10126
- import * as fs12 from "fs";
10127
- import * as path13 from "path";
10126
+ import * as fs14 from "fs";
10127
+ import * as path16 from "path";
10128
10128
 
10129
10129
  // scripts/lakebase/migration-layout.ts
10130
10130
  init_esm_shims();
10131
+ import * as fs7 from "fs";
10132
+ import * as path7 from "path";
10133
+
10134
+ // scripts/lakebase/dotnet-project.ts
10135
+ init_esm_shims();
10131
10136
  import * as fs6 from "fs";
10132
10137
  import * as path6 from "path";
10138
+ var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
10139
+ var DOTNET_FLYWAY_DIR = path6.join("db", "migrations");
10140
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
10141
+ function safeReaddir(dir) {
10142
+ try {
10143
+ return fs6.readdirSync(dir, { withFileTypes: true });
10144
+ } catch {
10145
+ return [];
10146
+ }
10147
+ }
10148
+ function hasDotnetMarker(dir) {
10149
+ return safeReaddir(dir).some(
10150
+ (e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
10151
+ );
10152
+ }
10153
+ function findCsprojFiles(projectDir) {
10154
+ const found = [];
10155
+ const walk = (dir, depth) => {
10156
+ for (const e of safeReaddir(dir)) {
10157
+ const full = path6.join(dir, e.name);
10158
+ if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
10159
+ found.push(full);
10160
+ } else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
10161
+ walk(full, depth + 1);
10162
+ }
10163
+ }
10164
+ };
10165
+ walk(projectDir, 0);
10166
+ return found.sort();
10167
+ }
10168
+ function findEfCoreProject(projectDir) {
10169
+ for (const csproj of findCsprojFiles(projectDir)) {
10170
+ let content = "";
10171
+ try {
10172
+ content = fs6.readFileSync(csproj, "utf-8");
10173
+ } catch {
10174
+ continue;
10175
+ }
10176
+ if (content.includes(EF_DESIGN_PACKAGE)) {
10177
+ const dir = path6.dirname(csproj);
10178
+ return { csproj, projectDir: dir, migrationsDir: path6.join(dir, "Migrations") };
10179
+ }
10180
+ }
10181
+ return null;
10182
+ }
10183
+ function isDotnetProject(projectDir) {
10184
+ return hasDotnetMarker(projectDir);
10185
+ }
10186
+ function isDotnetFlywayProject(projectDir) {
10187
+ return isDotnetProject(projectDir) && fs6.existsSync(path6.join(projectDir, DOTNET_FLYWAY_DIR));
10188
+ }
10189
+ var MAVEN_FLYWAY_DIR = path6.join("src", "main", "resources", "db", "migration");
10190
+ function flywayMigrationsDir(projectDir) {
10191
+ const maven = path6.join(projectDir, MAVEN_FLYWAY_DIR);
10192
+ if (fs6.existsSync(maven) || fs6.existsSync(path6.join(projectDir, "pom.xml"))) return maven;
10193
+ if (isDotnetFlywayProject(projectDir)) return path6.join(projectDir, DOTNET_FLYWAY_DIR);
10194
+ return maven;
10195
+ }
10196
+
10197
+ // scripts/lakebase/migration-layout.ts
10133
10198
  var MIGRATION_LANGUAGES = [
10134
10199
  "java",
10135
10200
  "kotlin",
10136
10201
  "python",
10137
10202
  "nodejs",
10203
+ "dotnet",
10138
10204
  "unknown"
10139
10205
  ];
10140
10206
  function detectLanguageAt(dir) {
10141
- if (fs6.existsSync(path6.join(dir, "pom.xml"))) {
10142
- const kotlinDir = path6.join(dir, "src", "main", "kotlin");
10143
- if (fs6.existsSync(kotlinDir)) {
10207
+ if (fs7.existsSync(path7.join(dir, "pom.xml"))) {
10208
+ const kotlinDir = path7.join(dir, "src", "main", "kotlin");
10209
+ if (fs7.existsSync(kotlinDir)) {
10144
10210
  return "kotlin";
10145
10211
  }
10146
10212
  try {
10147
- const pom = fs6.readFileSync(path6.join(dir, "pom.xml"), "utf-8");
10213
+ const pom = fs7.readFileSync(path7.join(dir, "pom.xml"), "utf-8");
10148
10214
  if (pom.includes("kotlin-maven-plugin")) {
10149
10215
  return "kotlin";
10150
10216
  }
@@ -10152,10 +10218,13 @@ function detectLanguageAt(dir) {
10152
10218
  }
10153
10219
  return "java";
10154
10220
  }
10155
- if (fs6.existsSync(path6.join(dir, "pyproject.toml")) || fs6.existsSync(path6.join(dir, "requirements.txt")) || fs6.existsSync(path6.join(dir, "alembic.ini"))) {
10221
+ if (fs7.existsSync(path7.join(dir, "pyproject.toml")) || fs7.existsSync(path7.join(dir, "requirements.txt")) || fs7.existsSync(path7.join(dir, "alembic.ini"))) {
10156
10222
  return "python";
10157
10223
  }
10158
- if (fs6.existsSync(path6.join(dir, "package.json"))) {
10224
+ if (hasDotnetMarker(dir)) {
10225
+ return "dotnet";
10226
+ }
10227
+ if (fs7.existsSync(path7.join(dir, "package.json"))) {
10159
10228
  return "nodejs";
10160
10229
  }
10161
10230
  return "unknown";
@@ -10176,14 +10245,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
10176
10245
  if (!rel) {
10177
10246
  return "unknown";
10178
10247
  }
10179
- const rootResolved = path6.resolve(projectDir);
10180
- let dir = path6.resolve(projectDir, rel);
10181
- while (dir === rootResolved || dir.startsWith(rootResolved + path6.sep)) {
10248
+ const rootResolved = path7.resolve(projectDir);
10249
+ let dir = path7.resolve(projectDir, rel);
10250
+ while (dir === rootResolved || dir.startsWith(rootResolved + path7.sep)) {
10182
10251
  const lang = detectLanguageAt(dir);
10183
10252
  if (lang !== "unknown") {
10184
10253
  return lang;
10185
10254
  }
10186
- const parent = path6.dirname(dir);
10255
+ const parent = path7.dirname(dir);
10187
10256
  if (parent === dir) {
10188
10257
  break;
10189
10258
  }
@@ -10194,22 +10263,22 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
10194
10263
 
10195
10264
  // scripts/lakebase/adapters/alembic-adapter.ts
10196
10265
  init_esm_shims();
10197
- import * as fs8 from "fs";
10198
- import * as path8 from "path";
10266
+ import * as fs9 from "fs";
10267
+ import * as path9 from "path";
10199
10268
 
10200
10269
  // scripts/lakebase/schema-migrate-runners/alembic.ts
10201
10270
  init_esm_shims();
10202
10271
  import { spawn } from "child_process";
10203
- import * as fs7 from "fs";
10204
- import * as path7 from "path";
10272
+ import * as fs8 from "fs";
10273
+ import * as path8 from "path";
10205
10274
  function resolveAlembicBin(projectDir) {
10206
10275
  const candidates = [
10207
- path7.join(projectDir, ".venv", "bin", "alembic"),
10208
- path7.join(projectDir, "venv", "bin", "alembic")
10276
+ path8.join(projectDir, ".venv", "bin", "alembic"),
10277
+ path8.join(projectDir, "venv", "bin", "alembic")
10209
10278
  ];
10210
10279
  for (const candidate of candidates) {
10211
10280
  try {
10212
- if (fs7.existsSync(candidate)) return candidate;
10281
+ if (fs8.existsSync(candidate)) return candidate;
10213
10282
  } catch {
10214
10283
  }
10215
10284
  }
@@ -10219,7 +10288,7 @@ function spawnAlembic(projectDir, args, dsn) {
10219
10288
  return new Promise((resolve3, reject) => {
10220
10289
  const bin = resolveAlembicBin(projectDir);
10221
10290
  const env = { ...process.env };
10222
- env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path7.delimiter);
10291
+ env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path8.delimiter);
10223
10292
  if (dsn) env.DATABASE_URL = dsn;
10224
10293
  const child = spawn(bin, args, {
10225
10294
  cwd: projectDir,
@@ -10267,10 +10336,10 @@ async function createAlembicRevision(opts) {
10267
10336
  const m = stdout.match(/Generating\s+(\S+\.py)/);
10268
10337
  if (m) return m[1].trim();
10269
10338
  for (const rel of ["migrations/versions", "alembic/versions"]) {
10270
- const dir = path7.join(opts.projectDir, rel);
10271
- if (!fs7.existsSync(dir)) continue;
10272
- const hit = fs7.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
10273
- if (hit) return path7.join(dir, hit);
10339
+ const dir = path8.join(opts.projectDir, rel);
10340
+ if (!fs8.existsSync(dir)) continue;
10341
+ const hit = fs8.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
10342
+ if (hit) return path8.join(dir, hit);
10274
10343
  }
10275
10344
  throw new SchemaMigrationError(
10276
10345
  `alembic revision succeeded but the created file could not be located.
@@ -10405,15 +10474,15 @@ async function buildDsn(args) {
10405
10474
  }
10406
10475
  function findVersionsDir(projectDir) {
10407
10476
  const candidates = [
10408
- path8.join(projectDir, "migrations", "versions"),
10409
- path8.join(projectDir, "alembic", "versions")
10477
+ path9.join(projectDir, "migrations", "versions"),
10478
+ path9.join(projectDir, "alembic", "versions")
10410
10479
  ];
10411
- return candidates.find((p) => fs8.existsSync(p));
10480
+ return candidates.find((p) => fs9.existsSync(p));
10412
10481
  }
10413
10482
  function listAlembicFiles(projectDir) {
10414
10483
  const dir = findVersionsDir(projectDir);
10415
10484
  if (!dir) return [];
10416
- const files = fs8.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
10485
+ const files = fs9.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
10417
10486
  return files.map((filename) => {
10418
10487
  const stem = filename.replace(/\.py$/, "");
10419
10488
  const sep3 = stem.indexOf("_");
@@ -10438,9 +10507,9 @@ var AlembicAdapter = {
10438
10507
  * here. Callers can still force-select via project.yaml#migration_tool.
10439
10508
  */
10440
10509
  detect(projectDir) {
10441
- if (fs8.existsSync(path8.join(projectDir, "alembic.ini"))) return true;
10442
- if (fs8.existsSync(path8.join(projectDir, "migrations", "env.py"))) return true;
10443
- if (fs8.existsSync(path8.join(projectDir, "alembic", "env.py"))) return true;
10510
+ if (fs9.existsSync(path9.join(projectDir, "alembic.ini"))) return true;
10511
+ if (fs9.existsSync(path9.join(projectDir, "migrations", "env.py"))) return true;
10512
+ if (fs9.existsSync(path9.join(projectDir, "alembic", "env.py"))) return true;
10444
10513
  return false;
10445
10514
  },
10446
10515
  async apply(args) {
@@ -10546,7 +10615,7 @@ var AlembicAdapter = {
10546
10615
  autogenerate: !!args.autogenerate,
10547
10616
  dsn
10548
10617
  });
10549
- return { status: "ok", version: revId, filename: path8.basename(created), path: created };
10618
+ return { status: "ok", version: revId, filename: path9.basename(created), path: created };
10550
10619
  } catch (err) {
10551
10620
  return {
10552
10621
  status: "error",
@@ -10563,7 +10632,7 @@ var AlembicAdapter = {
10563
10632
  if (heads.length <= 1) return { status: "noop", headsBefore: heads };
10564
10633
  if (args.dryRun) return { status: "ok", headsBefore: heads };
10565
10634
  const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
10566
- const mergeRevision = path8.basename(created).replace(/\.py$/, "").split("_")[0];
10635
+ const mergeRevision = path9.basename(created).replace(/\.py$/, "").split("_")[0];
10567
10636
  return { status: "ok", headsBefore: heads, mergeRevision, path: created };
10568
10637
  } catch (err) {
10569
10638
  return {
@@ -10578,13 +10647,13 @@ registerSchemaMigrationAdapter(AlembicAdapter);
10578
10647
 
10579
10648
  // scripts/lakebase/adapters/flyway-adapter.ts
10580
10649
  init_esm_shims();
10581
- import * as fs9 from "fs";
10582
- import * as path10 from "path";
10650
+ import * as fs10 from "fs";
10651
+ import * as path11 from "path";
10583
10652
 
10584
10653
  // scripts/lakebase/schema-migrate-runners/flyway.ts
10585
10654
  init_esm_shims();
10586
10655
  import { spawn as spawn2 } from "child_process";
10587
- import * as path9 from "path";
10656
+ import * as path10 from "path";
10588
10657
  function dsnToFlywayEnv(dsn) {
10589
10658
  const u = new URL(dsn);
10590
10659
  const user = decodeURIComponent(u.username);
@@ -10594,7 +10663,7 @@ function dsnToFlywayEnv(dsn) {
10594
10663
  return { url, user, password };
10595
10664
  }
10596
10665
  function migrationsLocation(projectDir) {
10597
- return `filesystem:${path9.join(projectDir, "src", "main", "resources", "db", "migration")}`;
10666
+ return `filesystem:${flywayMigrationsDir(projectDir)}`;
10598
10667
  }
10599
10668
  function runFlyway(ctx, args) {
10600
10669
  const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
@@ -10695,7 +10764,7 @@ async function statusFlyway(ctx) {
10695
10764
  if (state === "SUCCESS" || state === "BASELINE") {
10696
10765
  current = m.version;
10697
10766
  } else if (state === "PENDING") {
10698
- const filename = m.filepath ? path9.basename(m.filepath) : `V${m.version}__migration.sql`;
10767
+ const filename = m.filepath ? path10.basename(m.filepath) : `V${m.version}__migration.sql`;
10699
10768
  pending.push({
10700
10769
  version: m.version,
10701
10770
  filename,
@@ -10718,9 +10787,9 @@ async function buildDsn2(args) {
10718
10787
  return result.url;
10719
10788
  }
10720
10789
  function listFlywayFiles(projectDir) {
10721
- const dir = path10.join(projectDir, "src", "main", "resources", "db", "migration");
10722
- if (!fs9.existsSync(dir)) return [];
10723
- const files = fs9.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
10790
+ const dir = flywayMigrationsDir(projectDir);
10791
+ if (!fs10.existsSync(dir)) return [];
10792
+ const files = fs10.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
10724
10793
  return files.map((filename) => {
10725
10794
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
10726
10795
  const version = m[1];
@@ -10741,9 +10810,9 @@ function versionCompare(a, b) {
10741
10810
  }
10742
10811
  var FlywayAdapter = {
10743
10812
  id: "flyway",
10744
- languages: ["java", "kotlin"],
10813
+ languages: ["java", "kotlin", "dotnet"],
10745
10814
  detect(projectDir) {
10746
- return fs9.existsSync(path10.join(projectDir, "pom.xml"));
10815
+ return fs10.existsSync(path11.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
10747
10816
  },
10748
10817
  async apply(args) {
10749
10818
  const dsn = await buildDsn2(args);
@@ -10801,14 +10870,14 @@ var FlywayAdapter = {
10801
10870
  // optional-protocol shape makes this additive.
10802
10871
  async newMigration(args) {
10803
10872
  try {
10804
- const dir = path10.join(args.projectDir, "src", "main", "resources", "db", "migration");
10805
- fs9.mkdirSync(dir, { recursive: true });
10873
+ const dir = flywayMigrationsDir(args.projectDir);
10874
+ fs10.mkdirSync(dir, { recursive: true });
10806
10875
  const version = migrationTimestamp();
10807
10876
  const slug = migrationSlug2(args.slug);
10808
10877
  const filename = `V${version}__${slug}.sql`;
10809
- const full = path10.join(dir, filename);
10810
- if (fs9.existsSync(full)) throw new Error(`${filename} already exists`);
10811
- fs9.writeFileSync(
10878
+ const full = path11.join(dir, filename);
10879
+ if (fs10.existsSync(full)) throw new Error(`${filename} already exists`);
10880
+ fs10.writeFileSync(
10812
10881
  full,
10813
10882
  `-- V${version}: ${args.slug}
10814
10883
  -- Flyway migration (write your DDL/DML below).
@@ -10831,19 +10900,19 @@ registerSchemaMigrationAdapter(FlywayAdapter);
10831
10900
 
10832
10901
  // scripts/lakebase/adapters/knex-adapter.ts
10833
10902
  init_esm_shims();
10834
- import * as fs11 from "fs";
10835
- import * as path12 from "path";
10903
+ import * as fs12 from "fs";
10904
+ import * as path13 from "path";
10836
10905
 
10837
10906
  // scripts/lakebase/schema-migrate-runners/knex.ts
10838
10907
  init_esm_shims();
10839
10908
  import { spawn as spawn3 } from "child_process";
10840
- import * as fs10 from "fs";
10841
- import * as path11 from "path";
10909
+ import * as fs11 from "fs";
10910
+ import * as path12 from "path";
10842
10911
  var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
10843
10912
  function findKnexfile(projectDir) {
10844
10913
  for (const name of KNEXFILE_VARIANTS) {
10845
- const p = path11.join(projectDir, name);
10846
- if (fs10.existsSync(p)) return p;
10914
+ const p = path12.join(projectDir, name);
10915
+ if (fs11.existsSync(p)) return p;
10847
10916
  }
10848
10917
  return void 0;
10849
10918
  }
@@ -10995,9 +11064,9 @@ async function buildDsn3(args) {
10995
11064
  }
10996
11065
  var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
10997
11066
  function listKnexFiles(projectDir) {
10998
- const dir = path12.join(projectDir, "migrations");
10999
- if (!fs11.existsSync(dir)) return [];
11000
- const files = fs11.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
11067
+ const dir = path13.join(projectDir, "migrations");
11068
+ if (!fs12.existsSync(dir)) return [];
11069
+ const files = fs12.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
11001
11070
  return files.map((filename) => {
11002
11071
  const stem = filename.replace(/\.(js|ts)$/, "");
11003
11072
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -11017,7 +11086,7 @@ var KnexAdapter = {
11017
11086
  * project.yaml#migration_tool.
11018
11087
  */
11019
11088
  detect(projectDir) {
11020
- return KNEXFILE_VARIANTS2.some((name) => fs11.existsSync(path12.join(projectDir, name)));
11089
+ return KNEXFILE_VARIANTS2.some((name) => fs12.existsSync(path13.join(projectDir, name)));
11021
11090
  },
11022
11091
  async apply(args) {
11023
11092
  const dsn = await buildDsn3(args);
@@ -11090,9 +11159,9 @@ var KnexAdapter = {
11090
11159
  async newMigration(args) {
11091
11160
  try {
11092
11161
  const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
11093
- const stem = path12.basename(created).replace(/\.(js|ts)$/, "");
11162
+ const stem = path13.basename(created).replace(/\.(js|ts)$/, "");
11094
11163
  const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
11095
- return { status: "ok", version, filename: path12.basename(created), path: created };
11164
+ return { status: "ok", version, filename: path13.basename(created), path: created };
11096
11165
  } catch (err) {
11097
11166
  return {
11098
11167
  status: "error",
@@ -11106,6 +11175,362 @@ var KnexAdapter = {
11106
11175
  };
11107
11176
  registerSchemaMigrationAdapter(KnexAdapter);
11108
11177
 
11178
+ // scripts/lakebase/adapters/efcore-adapter.ts
11179
+ init_esm_shims();
11180
+ import * as path15 from "path";
11181
+
11182
+ // scripts/lakebase/schema-migrate-runners/efcore.ts
11183
+ init_esm_shims();
11184
+ import { spawn as spawn4 } from "child_process";
11185
+ import * as fs13 from "fs";
11186
+ import * as path14 from "path";
11187
+ import { Client as Client2 } from "pg";
11188
+ var MIGRATION_ID = /^(\d{14})_(.+)$/;
11189
+ function efVersion(id) {
11190
+ return MIGRATION_ID.exec(id)?.[1] ?? id;
11191
+ }
11192
+ function efDescription(id) {
11193
+ const name = MIGRATION_ID.exec(id)?.[2] ?? id;
11194
+ return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
11195
+ }
11196
+ function dsnToNpgsql(dsn) {
11197
+ const u = new URL(dsn);
11198
+ const parts = [
11199
+ `Host=${u.hostname}`,
11200
+ `Port=${u.port || "5432"}`,
11201
+ `Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
11202
+ ];
11203
+ if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
11204
+ if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
11205
+ const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
11206
+ const ssl = {
11207
+ disable: "Disable",
11208
+ allow: "Allow",
11209
+ prefer: "Prefer",
11210
+ require: "Require",
11211
+ "verify-ca": "VerifyCA",
11212
+ "verify-full": "VerifyFull"
11213
+ };
11214
+ parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
11215
+ const app = u.searchParams.get("application_name");
11216
+ if (app) parts.push(`Application Name=${app}`);
11217
+ return parts.map((p) => p.replace(/;/g, "")).join(";");
11218
+ }
11219
+ function parseEfJson(stdout) {
11220
+ const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
11221
+ if (!body) {
11222
+ throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
11223
+ }
11224
+ try {
11225
+ return JSON.parse(body);
11226
+ } catch (err) {
11227
+ throw new SchemaMigrationError(
11228
+ `dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
11229
+ Body (first 400 chars): ${body.slice(0, 400)}`
11230
+ );
11231
+ }
11232
+ }
11233
+ function requireProject(projectDir) {
11234
+ const project = findEfCoreProject(projectDir);
11235
+ if (!project) {
11236
+ throw new SchemaMigrationError(
11237
+ `No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
11238
+ );
11239
+ }
11240
+ return project;
11241
+ }
11242
+ function spawnDotnet(projectDir, args, env) {
11243
+ return new Promise((resolve3, reject) => {
11244
+ const child = spawn4("dotnet", args, {
11245
+ cwd: projectDir,
11246
+ env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
11247
+ stdio: ["ignore", "pipe", "pipe"]
11248
+ });
11249
+ let stdout = "";
11250
+ let stderr = "";
11251
+ child.stdout.on("data", (chunk) => {
11252
+ stdout += chunk.toString("utf8");
11253
+ });
11254
+ child.stderr.on("data", (chunk) => {
11255
+ stderr += chunk.toString("utf8");
11256
+ });
11257
+ child.on("error", (err) => {
11258
+ reject(
11259
+ new SchemaMigrationError(
11260
+ `Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
11261
+ err
11262
+ )
11263
+ );
11264
+ });
11265
+ child.on("close", (code) => {
11266
+ if (code === 0) {
11267
+ resolve3({ stdout, stderr });
11268
+ } else {
11269
+ reject(
11270
+ new SchemaMigrationError(
11271
+ `dotnet ${args.join(" ")} exited with code ${code}.
11272
+ stdout: ${stdout}
11273
+ stderr: ${stderr}`
11274
+ )
11275
+ );
11276
+ }
11277
+ });
11278
+ });
11279
+ }
11280
+ var restored = /* @__PURE__ */ new Set();
11281
+ async function ensureRestored(projectDir, csproj) {
11282
+ if (restored.has(projectDir)) return;
11283
+ if (fs13.existsSync(path14.join(projectDir, ".config", "dotnet-tools.json"))) {
11284
+ await spawnDotnet(projectDir, ["tool", "restore"], {});
11285
+ }
11286
+ await spawnDotnet(projectDir, ["restore", csproj], {});
11287
+ restored.add(projectDir);
11288
+ }
11289
+ async function runEf(projectDir, args, dsn, opts = {}) {
11290
+ const project = requireProject(projectDir);
11291
+ const csproj = path14.relative(projectDir, project.csproj) || project.csproj;
11292
+ await ensureRestored(projectDir, csproj);
11293
+ const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
11294
+ if (opts.noBuild) full.push("--no-build");
11295
+ const env = {};
11296
+ if (dsn) {
11297
+ env.DATABASE_URL = dsn;
11298
+ if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
11299
+ }
11300
+ return spawnDotnet(projectDir, full, env);
11301
+ }
11302
+ async function listEfMigrations(ctx, opts = {}) {
11303
+ const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
11304
+ const entries = parseEfJson(stdout);
11305
+ if (ctx.dsn && entries.some((e) => e.applied === null)) {
11306
+ const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
11307
+ throw new SchemaMigrationError(
11308
+ `dotnet ef could not read migration state from the database (check connectivity and credentials).
11309
+ ${warning || stderr}`.trim()
11310
+ );
11311
+ }
11312
+ return entries;
11313
+ }
11314
+ async function readEfHistory(dsn) {
11315
+ const client = new Client2({ connectionString: dsn, application_name: connectionApplicationName() });
11316
+ await client.connect();
11317
+ try {
11318
+ const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
11319
+ if (!exists.rows[0]?.present) return null;
11320
+ const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
11321
+ return r.rows.map((row) => row.MigrationId);
11322
+ } finally {
11323
+ await client.end();
11324
+ }
11325
+ }
11326
+ function toApplied(entries) {
11327
+ return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
11328
+ }
11329
+ async function statusEfcore(ctx) {
11330
+ const entries = await listEfMigrations(ctx);
11331
+ const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
11332
+ const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
11333
+ const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
11334
+ return { current, pending, tool: "efcore" };
11335
+ }
11336
+ async function applyEfcore(ctx) {
11337
+ const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
11338
+ await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
11339
+ const after = await listEfMigrations(ctx, { noBuild: true });
11340
+ const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
11341
+ return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
11342
+ }
11343
+ function resolveEfTarget(target, applied) {
11344
+ const t = target.trim();
11345
+ if (t === "0") return "0";
11346
+ const rel = /^-(\d+)$/.exec(t);
11347
+ if (rel) {
11348
+ const steps = Number(rel[1]);
11349
+ if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
11350
+ const index = applied.length - 1 - steps;
11351
+ if (index < -1) {
11352
+ throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
11353
+ }
11354
+ return index === -1 ? "0" : applied[index].id;
11355
+ }
11356
+ const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
11357
+ if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
11358
+ return match.id;
11359
+ }
11360
+ async function rollbackEfcore(ctx) {
11361
+ const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
11362
+ const targetId = resolveEfTarget(ctx.target, beforeEntries);
11363
+ await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
11364
+ const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
11365
+ return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
11366
+ }
11367
+ function efMigrationName(slug) {
11368
+ const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
11369
+ if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
11370
+ return /^[0-9]/.test(name) ? `M${name}` : name;
11371
+ }
11372
+ async function createEfcoreMigration(opts) {
11373
+ const project = requireProject(opts.projectDir);
11374
+ const name = efMigrationName(opts.slug);
11375
+ const existing = new Set(fs13.existsSync(project.migrationsDir) ? fs13.readdirSync(project.migrationsDir) : []);
11376
+ await runEf(opts.projectDir, ["migrations", "add", name]);
11377
+ const created = fs13.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
11378
+ if (created.length !== 1) {
11379
+ throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
11380
+ }
11381
+ return { id: created[0].replace(/\.cs$/, ""), path: path14.join(project.migrationsDir, created[0]) };
11382
+ }
11383
+ function localEfMigrationIds(projectDir) {
11384
+ const project = findEfCoreProject(projectDir);
11385
+ if (!project || !fs13.existsSync(project.migrationsDir)) return [];
11386
+ return fs13.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();
11387
+ }
11388
+ function efProductVersion(project) {
11389
+ try {
11390
+ const csproj = fs13.readFileSync(project.csproj, "utf-8");
11391
+ const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
11392
+ if (m) return m[1];
11393
+ } catch {
11394
+ }
11395
+ return "unknown";
11396
+ }
11397
+ async function stampEfcore(ctx) {
11398
+ const project = requireProject(ctx.projectDir);
11399
+ const ids = localEfMigrationIds(ctx.projectDir);
11400
+ if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
11401
+ const rev = ctx.revision.trim();
11402
+ const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
11403
+ if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
11404
+ const toStamp = ids.slice(0, end + 1);
11405
+ const productVersion = efProductVersion(project);
11406
+ const client = new Client2({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
11407
+ await client.connect();
11408
+ try {
11409
+ await client.query("BEGIN");
11410
+ await client.query(
11411
+ `CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
11412
+ "MigrationId" character varying(150) NOT NULL,
11413
+ "ProductVersion" character varying(32) NOT NULL,
11414
+ CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
11415
+ );
11416
+ await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
11417
+ for (const id of toStamp) {
11418
+ await client.query(
11419
+ `INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
11420
+ ON CONFLICT ("MigrationId") DO NOTHING`,
11421
+ [id, productVersion]
11422
+ );
11423
+ }
11424
+ await client.query("COMMIT");
11425
+ } catch (err) {
11426
+ await client.query("ROLLBACK").catch(() => void 0);
11427
+ throw err;
11428
+ } finally {
11429
+ await client.end();
11430
+ }
11431
+ return efVersion(toStamp[toStamp.length - 1]);
11432
+ }
11433
+
11434
+ // scripts/lakebase/adapters/efcore-adapter.ts
11435
+ async function buildDsn4(args) {
11436
+ const result = await getConnection({
11437
+ output: "dsn",
11438
+ instance: args.instance,
11439
+ branch: args.branch,
11440
+ database: args.database,
11441
+ endpointName: args.endpointName
11442
+ });
11443
+ return result.url;
11444
+ }
11445
+ function errMsg(err) {
11446
+ return err instanceof Error ? err.message : String(err);
11447
+ }
11448
+ function listEfCoreFiles(projectDir) {
11449
+ return localEfMigrationIds(projectDir).map((id) => ({
11450
+ version: efVersion(id),
11451
+ filename: `${id}.cs`,
11452
+ description: efDescription(id),
11453
+ type: "CSharp",
11454
+ tool: "efcore"
11455
+ }));
11456
+ }
11457
+ var EfCoreAdapter = {
11458
+ id: "efcore",
11459
+ languages: ["dotnet"],
11460
+ /** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
11461
+ detect(projectDir) {
11462
+ return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
11463
+ },
11464
+ async apply(args) {
11465
+ try {
11466
+ const dsn = await buildDsn4(args);
11467
+ const r = await applyEfcore({ projectDir: args.projectDir, dsn });
11468
+ return {
11469
+ applied_migrations: r.applied,
11470
+ status: r.alreadyAtLatest ? "noop" : "ok",
11471
+ tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
11472
+ };
11473
+ } catch (err) {
11474
+ return { applied_migrations: [], status: "error", error: errMsg(err) };
11475
+ }
11476
+ },
11477
+ async rollback(args) {
11478
+ try {
11479
+ const dsn = await buildDsn4(args);
11480
+ const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
11481
+ return {
11482
+ rolled_back: r.rolledBack,
11483
+ status: r.rolledBack.length === 0 ? "noop" : "ok",
11484
+ tool_specific: { tool: r.tool }
11485
+ };
11486
+ } catch (err) {
11487
+ return { rolled_back: [], status: "error", error: errMsg(err) };
11488
+ }
11489
+ },
11490
+ async stamp(args) {
11491
+ try {
11492
+ const dsn = await buildDsn4(args);
11493
+ const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
11494
+ return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
11495
+ } catch (err) {
11496
+ return { status: "error", stamped_revision: null, error: errMsg(err) };
11497
+ }
11498
+ },
11499
+ async status(args) {
11500
+ try {
11501
+ const dsn = await buildDsn4(args);
11502
+ const r = await statusEfcore({ projectDir: args.projectDir, dsn });
11503
+ return {
11504
+ applied_version: r.current ?? null,
11505
+ pending: r.pending,
11506
+ applied: [],
11507
+ status: "ok",
11508
+ tool_specific: { tool: r.tool }
11509
+ };
11510
+ } catch (err) {
11511
+ return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
11512
+ }
11513
+ },
11514
+ async list(args) {
11515
+ return { files: listEfCoreFiles(args.projectDir) };
11516
+ },
11517
+ // baseline: use stamp (EF has no separate baseline concept).
11518
+ async newMigration(args) {
11519
+ try {
11520
+ const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
11521
+ return {
11522
+ status: "ok",
11523
+ version: efVersion(created.id),
11524
+ filename: path15.basename(created.path),
11525
+ path: created.path
11526
+ };
11527
+ } catch (err) {
11528
+ return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
11529
+ }
11530
+ }
11531
+ };
11532
+ registerSchemaMigrationAdapter(EfCoreAdapter);
11533
+
11109
11534
  // scripts/lakebase/schema-migrate.ts
11110
11535
  var SchemaMigrationError = class extends Error {
11111
11536
  constructor(message, cause) {
@@ -11119,13 +11544,16 @@ function detectLanguage(projectDir) {
11119
11544
  const lang = resolveMigrationLanguage(projectDir);
11120
11545
  if (lang === "unknown") {
11121
11546
  throw new SchemaMigrationError(
11122
- `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.`
11547
+ `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.`
11123
11548
  );
11124
11549
  }
11125
11550
  return lang;
11126
11551
  }
11127
- function toolForLanguage(language) {
11552
+ function toolForLanguage(language, projectDir) {
11128
11553
  switch (language) {
11554
+ case "dotnet":
11555
+ if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
11556
+ return "efcore";
11129
11557
  case "java":
11130
11558
  case "kotlin":
11131
11559
  return "flyway";
@@ -11138,8 +11566,10 @@ function toolForLanguage(language) {
11138
11566
  function listSchemaMigrations(args = {}) {
11139
11567
  const projectDir = args.projectDir ?? process.cwd();
11140
11568
  const language = args.language ?? detectLanguage(projectDir);
11141
- const tool = toolForLanguage(language);
11569
+ const tool = toolForLanguage(language, projectDir);
11142
11570
  switch (tool) {
11571
+ case "efcore":
11572
+ return listEfCoreFiles(projectDir);
11143
11573
  case "flyway":
11144
11574
  return listFlywayMigrations(projectDir);
11145
11575
  case "alembic":
@@ -11149,9 +11579,9 @@ function listSchemaMigrations(args = {}) {
11149
11579
  }
11150
11580
  }
11151
11581
  function listFlywayMigrations(projectDir) {
11152
- const dir = path13.join(projectDir, "src", "main", "resources", "db", "migration");
11153
- if (!fs12.existsSync(dir)) return [];
11154
- const files = fs12.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
11582
+ const dir = flywayMigrationsDir(projectDir);
11583
+ if (!fs14.existsSync(dir)) return [];
11584
+ const files = fs14.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
11155
11585
  return files.map((filename) => {
11156
11586
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
11157
11587
  const version = m[1];
@@ -11161,12 +11591,12 @@ function listFlywayMigrations(projectDir) {
11161
11591
  }
11162
11592
  function listAlembicMigrations(projectDir) {
11163
11593
  const candidates = [
11164
- path13.join(projectDir, "migrations", "versions"),
11165
- path13.join(projectDir, "alembic", "versions")
11594
+ path16.join(projectDir, "migrations", "versions"),
11595
+ path16.join(projectDir, "alembic", "versions")
11166
11596
  ];
11167
- const dir = candidates.find((p) => fs12.existsSync(p));
11597
+ const dir = candidates.find((p) => fs14.existsSync(p));
11168
11598
  if (!dir) return [];
11169
- const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
11599
+ const files = fs14.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
11170
11600
  return files.map((filename) => {
11171
11601
  const stem = filename.replace(/\.py$/, "");
11172
11602
  const sep3 = stem.indexOf("_");
@@ -11176,9 +11606,9 @@ function listAlembicMigrations(projectDir) {
11176
11606
  }).sort((a, b) => a.filename.localeCompare(b.filename));
11177
11607
  }
11178
11608
  function listKnexMigrations(projectDir) {
11179
- const dir = path13.join(projectDir, "migrations");
11180
- if (!fs12.existsSync(dir)) return [];
11181
- const files = fs12.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
11609
+ const dir = path16.join(projectDir, "migrations");
11610
+ if (!fs14.existsSync(dir)) return [];
11611
+ const files = fs14.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
11182
11612
  return files.map((filename) => {
11183
11613
  const stem = filename.replace(/\.(js|ts)$/, "");
11184
11614
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -11200,7 +11630,7 @@ function versionCompare2(a, b) {
11200
11630
  return 0;
11201
11631
  }
11202
11632
  function adapterFor(projectDir, language) {
11203
- const override = language ? toolForLanguage(language) : void 0;
11633
+ const override = language ? toolForLanguage(language, projectDir) : void 0;
11204
11634
  return resolveSchemaMigrationAdapter(projectDir, override);
11205
11635
  }
11206
11636
  function dbRevisionOrphaned(appliedRevision, localRevisionIds) {
@@ -11255,10 +11685,10 @@ async function collapseMigrationHeads(args) {
11255
11685
  var FEATURE_PREFIX = "feature/";
11256
11686
  var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
11257
11687
  function readEnv(projectDir) {
11258
- const envPath = path14.join(projectDir, ".env");
11688
+ const envPath = path17.join(projectDir, ".env");
11259
11689
  const out = /* @__PURE__ */ new Map();
11260
- if (!fs13.existsSync(envPath)) return out;
11261
- const lines = fs13.readFileSync(envPath, "utf8").split("\n");
11690
+ if (!fs15.existsSync(envPath)) return out;
11691
+ const lines = fs15.readFileSync(envPath, "utf8").split("\n");
11262
11692
  for (const line of lines) {
11263
11693
  const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
11264
11694
  if (m) out.set(m[1], m[2].replace(/^["']|["']$/g, ""));
@@ -11313,9 +11743,9 @@ async function runDoctor(args, deps = {}) {
11313
11743
  }
11314
11744
  for (const wf of ["pr.yml", "merge.yml"]) {
11315
11745
  try {
11316
- const p = path14.join(projectDir, ".github", "workflows", wf);
11317
- if (!fs13.existsSync(p)) continue;
11318
- const body = fs13.readFileSync(p, "utf8");
11746
+ const p = path17.join(projectDir, ".github", "workflows", wf);
11747
+ if (!fs15.existsSync(p)) continue;
11748
+ const body = fs15.readFileSync(p, "utf8");
11319
11749
  if (/lakebase-scm-utils#v\d/.test(body)) {
11320
11750
  findings.push({
11321
11751
  id: "ci-workflow-substrate-pin",
@@ -11530,7 +11960,7 @@ async function fixFinding(args) {
11530
11960
  );
11531
11961
  }
11532
11962
  const sanitized = sanitizeBranchName(branch);
11533
- const envFile = path14.join(args.projectDir, ".env");
11963
+ const envFile = path17.join(args.projectDir, ".env");
11534
11964
  updateEnvConnection({
11535
11965
  envPath: envFile,
11536
11966
  projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
@@ -11690,9 +12120,9 @@ Exit codes (--fix mode):
11690
12120
  3 = fix executed but the underlying command failed
11691
12121
  `;
11692
12122
  function readEnvProjectId(projectDir) {
11693
- const envPath = path15.join(projectDir, ".env");
11694
- if (!fs14.existsSync(envPath)) return void 0;
11695
- const lines = fs14.readFileSync(envPath, "utf8").split("\n");
12123
+ const envPath = path18.join(projectDir, ".env");
12124
+ if (!fs16.existsSync(envPath)) return void 0;
12125
+ const lines = fs16.readFileSync(envPath, "utf8").split("\n");
11696
12126
  for (const line of lines) {
11697
12127
  const m = line.match(/^\s*LAKEBASE_PROJECT_ID\s*=\s*(.+?)\s*$/);
11698
12128
  if (m) return m[1].replace(/^["']|["']$/g, "");
@@ -11752,7 +12182,7 @@ async function runScmDoctorCli(argv) {
11752
12182
  `);
11753
12183
  return 0;
11754
12184
  }
11755
- const projectDir = path15.resolve(args.projectDir ?? process.cwd());
12185
+ const projectDir = path18.resolve(args.projectDir ?? process.cwd());
11756
12186
  const instance = args.instance ?? readEnvProjectId(projectDir);
11757
12187
  if (args.fix) {
11758
12188
  if (!FIXABLE_FINDING_IDS.includes(args.fix)) {