@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
@@ -2581,7 +2581,7 @@ paginateRest.VERSION = VERSION5;
2581
2581
 
2582
2582
  // node_modules/@octokit/plugin-paginate-graphql/dist-bundle/index.js
2583
2583
  init_esm_shims();
2584
- var generateMessage = (path34, cursorValue) => `The cursor at "${path34.join(
2584
+ var generateMessage = (path37, cursorValue) => `The cursor at "${path37.join(
2585
2585
  ","
2586
2586
  )}" did not change its value "${cursorValue}" after a page transition. Please make sure your that your query is set up correctly.`;
2587
2587
  var MissingCursorChange = class extends Error {
@@ -2622,9 +2622,9 @@ function findPaginatedResourcePath(responseData) {
2622
2622
  }
2623
2623
  return paginatedResourcePath;
2624
2624
  }
2625
- var deepFindPathToProperty = (object, searchProp, path34 = []) => {
2625
+ var deepFindPathToProperty = (object, searchProp, path37 = []) => {
2626
2626
  for (const key of Object.keys(object)) {
2627
- const currentPath = [...path34, key];
2627
+ const currentPath = [...path37, key];
2628
2628
  const currentValue = object[key];
2629
2629
  if (isObject(currentValue)) {
2630
2630
  if (currentValue.hasOwnProperty(searchProp)) {
@@ -2642,12 +2642,12 @@ var deepFindPathToProperty = (object, searchProp, path34 = []) => {
2642
2642
  }
2643
2643
  return [];
2644
2644
  };
2645
- var get = (object, path34) => {
2646
- return path34.reduce((current, nextProperty) => current[nextProperty], object);
2645
+ var get = (object, path37) => {
2646
+ return path37.reduce((current, nextProperty) => current[nextProperty], object);
2647
2647
  };
2648
- var set = (object, path34, mutator) => {
2649
- const lastProperty = path34[path34.length - 1];
2650
- const parentPath = [...path34].slice(0, -1);
2648
+ var set = (object, path37, mutator) => {
2649
+ const lastProperty = path37[path37.length - 1];
2650
+ const parentPath = [...path37].slice(0, -1);
2651
2651
  const parent = get(object, parentPath);
2652
2652
  if (typeof mutator === "function") {
2653
2653
  parent[lastProperty] = mutator(parent[lastProperty]);
@@ -2699,22 +2699,22 @@ var mergeResponses = (response1, response2) => {
2699
2699
  if (Object.keys(response1).length === 0) {
2700
2700
  return Object.assign(response1, response2);
2701
2701
  }
2702
- const path34 = findPaginatedResourcePath(response1);
2703
- const nodesPath = [...path34, "nodes"];
2702
+ const path37 = findPaginatedResourcePath(response1);
2703
+ const nodesPath = [...path37, "nodes"];
2704
2704
  const newNodes = get(response2, nodesPath);
2705
2705
  if (newNodes) {
2706
2706
  set(response1, nodesPath, (values) => {
2707
2707
  return [...values, ...newNodes];
2708
2708
  });
2709
2709
  }
2710
- const edgesPath = [...path34, "edges"];
2710
+ const edgesPath = [...path37, "edges"];
2711
2711
  const newEdges = get(response2, edgesPath);
2712
2712
  if (newEdges) {
2713
2713
  set(response1, edgesPath, (values) => {
2714
2714
  return [...values, ...newEdges];
2715
2715
  });
2716
2716
  }
2717
- const pageInfoPath = [...path34, "pageInfo"];
2717
+ const pageInfoPath = [...path37, "pageInfo"];
2718
2718
  set(response1, pageInfoPath, get(response2, pageInfoPath));
2719
2719
  return response1;
2720
2720
  };
@@ -5135,7 +5135,7 @@ var triggers_notification_paths_default = [
5135
5135
  ];
5136
5136
  function routeMatcher(paths) {
5137
5137
  const regexes = paths.map(
5138
- (path34) => path34.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
5138
+ (path37) => path37.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
5139
5139
  );
5140
5140
  const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
5141
5141
  return new RegExp(regex2, "i");
@@ -8715,9 +8715,9 @@ function asBranchUid(s) {
8715
8715
  }
8716
8716
  return s;
8717
8717
  }
8718
- function branchNameFromResourcePath(path34) {
8719
- if (!path34.includes("/branches/")) return null;
8720
- const leaf = path34.split("/branches/").pop();
8718
+ function branchNameFromResourcePath(path37) {
8719
+ if (!path37.includes("/branches/")) return null;
8720
+ const leaf = path37.split("/branches/").pop();
8721
8721
  if (!leaf) return null;
8722
8722
  try {
8723
8723
  return asBranchName(leaf);
@@ -9725,8 +9725,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
9725
9725
  var cached;
9726
9726
  function substrateSelfVersion() {
9727
9727
  if (cached !== void 0) return cached;
9728
- if ("0.2.50".length > 0) {
9729
- cached = "0.2.50";
9728
+ if ("0.3.0".length > 0) {
9729
+ cached = "0.3.0";
9730
9730
  return cached;
9731
9731
  }
9732
9732
  cached = "unknown";
@@ -11314,16 +11314,16 @@ function isAllSchemas(schema) {
11314
11314
  }
11315
11315
  function buildSchemaQuery(schema) {
11316
11316
  const cols = "c.table_schema, c.table_name, c.column_name, c.data_type";
11317
- const join36 = "FROM information_schema.columns c JOIN pg_tables t ON c.table_name = t.tablename AND c.table_schema = t.schemaname ";
11317
+ const join37 = "FROM information_schema.columns c JOIN pg_tables t ON c.table_name = t.tablename AND c.table_schema = t.schemaname ";
11318
11318
  if (isAllSchemas(schema)) {
11319
11319
  return {
11320
- text: `SELECT ${cols} ` + join36 + `WHERE ${SYSTEM_SCHEMA_FILTER} ORDER BY c.table_schema, c.table_name, c.ordinal_position`,
11320
+ text: `SELECT ${cols} ` + join37 + `WHERE ${SYSTEM_SCHEMA_FILTER} ORDER BY c.table_schema, c.table_name, c.ordinal_position`,
11321
11321
  values: []
11322
11322
  };
11323
11323
  }
11324
11324
  const one = (schema ?? "").trim() || "public";
11325
11325
  return {
11326
- text: `SELECT ${cols} ` + join36 + "WHERE c.table_schema = $1 ORDER BY c.table_name, c.ordinal_position",
11326
+ text: `SELECT ${cols} ` + join37 + "WHERE c.table_schema = $1 ORDER BY c.table_name, c.ordinal_position",
11327
11327
  values: [one]
11328
11328
  };
11329
11329
  }
@@ -11988,6 +11988,20 @@ async function deployLanguageProject(args) {
11988
11988
  return;
11989
11989
  }
11990
11990
  const templatesDir = args.templatesDir ?? findTemplatesDir2();
11991
+ if (args.language === "dotnet") {
11992
+ const tool = args.migrationTool ?? "efcore";
11993
+ if (tool !== "efcore" && tool !== "flyway") {
11994
+ throw new Error(`Unsupported .NET migration tool: ${String(tool)} (expected efcore or flyway)`);
11995
+ }
11996
+ const base = path10.join(templatesDir, "dotnet");
11997
+ const overlay = path10.join(templatesDir, `dotnet-${tool}`);
11998
+ if (!fs11.existsSync(base) || !fs11.existsSync(overlay)) {
11999
+ throw new Error(`No template found for language: dotnet (${tool})`);
12000
+ }
12001
+ copyDirSubstituted(base, args.targetDir, { projectName: args.projectName });
12002
+ copyDirSubstituted(overlay, args.targetDir, { projectName: args.projectName });
12003
+ return;
12004
+ }
11991
12005
  const langSrc = path10.join(templatesDir, args.language);
11992
12006
  if (!fs11.existsSync(langSrc)) {
11993
12007
  throw new Error(`No template found for language: ${args.language}`);
@@ -12407,6 +12421,7 @@ async function scaffoldAll(args) {
12407
12421
  targetDir: args.targetDir,
12408
12422
  language,
12409
12423
  projectName,
12424
+ migrationTool: args.migrationTool,
12410
12425
  templatesDir: args.templatesDir,
12411
12426
  initializrClient: args.initializrClient,
12412
12427
  report
@@ -13633,7 +13648,7 @@ async function createProject(input, progress) {
13633
13648
  tierCount > 1 ? `${tierCount} tiers (prod${tierCount >= 2 ? " + staging" : ""}${tierCount >= 3 ? " + dev" : ""})` : null
13634
13649
  ].filter(Boolean);
13635
13650
  report(
13636
- `Setting up "${input.projectName}" , one-time provisioning, usually ~2-4 min. This creates: ${bits.join(", ")}. ${usesGithub ? "The slowest part is the runner setup; that wait is normal. " : ""}Then the Consort toolkit downloads on first use (~1-2 min, one-time). Progress:`
13651
+ `Setting up "${input.projectName}". This creates: ${bits.join(", ")}. The Consort toolkit downloads on first use. Progress:`
13637
13652
  );
13638
13653
  }
13639
13654
  report("Checking Databricks authentication...");
@@ -13688,7 +13703,7 @@ Last probe error:
13688
13703
  fs18.mkdirSync(projectDir, { recursive: true });
13689
13704
  await gitInit(projectDir);
13690
13705
  }
13691
- report("Creating Lakebase database (provisioning Postgres, ~30-60s)...", lakebaseProjectId);
13706
+ report("Creating Lakebase database (provisioning Postgres)...", lakebaseProjectId);
13692
13707
  await createLakebaseProject({ projectId: lakebaseProjectId, host });
13693
13708
  return await withLakebaseRollback(
13694
13709
  { projectId: lakebaseProjectId, host, report },
@@ -13704,6 +13719,7 @@ Last probe error:
13704
13719
  databricksHost: host,
13705
13720
  lakebaseProjectId,
13706
13721
  language,
13722
+ migrationTool: input.migrationTool,
13707
13723
  runnerType,
13708
13724
  skipCommands,
13709
13725
  clientFramework,
@@ -13768,7 +13784,7 @@ Last probe error:
13768
13784
  }
13769
13785
  }
13770
13786
  if (useGithub && runnerType === "self-hosted") {
13771
- report("Setting up the self-hosted CI runner (downloads + registers it, ~1-2 min , the slow step)...");
13787
+ report("Setting up the self-hosted CI runner: downloading, extracting, and registering it...");
13772
13788
  try {
13773
13789
  await setupRunner({
13774
13790
  fullRepoName,
@@ -13840,14 +13856,15 @@ Last probe error:
13840
13856
  }
13841
13857
  }
13842
13858
  report(
13843
- "Consort toolkit downloads on first use (one-time, ~1-2 min) , run `./scripts/lk --refresh`, or just your first `./scripts/lk` command, and it installs then; instant afterward."
13859
+ "The Consort toolkit downloads on first use , run `./scripts/lk --refresh`, or just your first `./scripts/lk` command, and it installs then."
13844
13860
  );
13845
13861
  }
13846
13862
  const langLabels = {
13847
13863
  java: "Java/Spring Boot",
13848
13864
  kotlin: "Kotlin/Spring Boot",
13849
13865
  python: "Python/FastAPI",
13850
- nodejs: "Node.js/Express"
13866
+ nodejs: "Node.js/Express",
13867
+ dotnet: `.NET/ASP.NET Core (${input.migrationTool === "flyway" ? "Flyway" : "EF Core"})`
13851
13868
  };
13852
13869
  const langLabel = langLabels[language] ?? language;
13853
13870
  report("Creating initial commit...");
@@ -14028,8 +14045,8 @@ function _testMakeBrownfieldFixture(opts) {
14028
14045
 
14029
14046
  // scripts/lakebase/infra-runner.ts
14030
14047
  init_esm_shims();
14031
- import * as fs27 from "fs";
14032
- import * as path28 from "path";
14048
+ import * as fs29 from "fs";
14049
+ import * as path31 from "path";
14033
14050
 
14034
14051
  // scripts/lakebase/schema-diff.ts
14035
14052
  init_esm_shims();
@@ -14225,18 +14242,84 @@ function dbcli6(args) {
14225
14242
 
14226
14243
  // scripts/lakebase/schema-migrate.ts
14227
14244
  init_esm_shims();
14228
- import * as fs26 from "fs";
14229
- import * as path27 from "path";
14245
+ import * as fs28 from "fs";
14246
+ import * as path30 from "path";
14230
14247
 
14231
14248
  // scripts/lakebase/migration-layout.ts
14232
14249
  init_esm_shims();
14250
+ import * as fs21 from "fs";
14251
+ import * as path21 from "path";
14252
+
14253
+ // scripts/lakebase/dotnet-project.ts
14254
+ init_esm_shims();
14233
14255
  import * as fs20 from "fs";
14234
14256
  import * as path20 from "path";
14257
+ var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
14258
+ var DOTNET_FLYWAY_DIR = path20.join("db", "migrations");
14259
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
14260
+ function safeReaddir(dir) {
14261
+ try {
14262
+ return fs20.readdirSync(dir, { withFileTypes: true });
14263
+ } catch {
14264
+ return [];
14265
+ }
14266
+ }
14267
+ function hasDotnetMarker(dir) {
14268
+ return safeReaddir(dir).some(
14269
+ (e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
14270
+ );
14271
+ }
14272
+ function findCsprojFiles(projectDir) {
14273
+ const found = [];
14274
+ const walk = (dir, depth) => {
14275
+ for (const e of safeReaddir(dir)) {
14276
+ const full = path20.join(dir, e.name);
14277
+ if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
14278
+ found.push(full);
14279
+ } else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
14280
+ walk(full, depth + 1);
14281
+ }
14282
+ }
14283
+ };
14284
+ walk(projectDir, 0);
14285
+ return found.sort();
14286
+ }
14287
+ function findEfCoreProject(projectDir) {
14288
+ for (const csproj of findCsprojFiles(projectDir)) {
14289
+ let content = "";
14290
+ try {
14291
+ content = fs20.readFileSync(csproj, "utf-8");
14292
+ } catch {
14293
+ continue;
14294
+ }
14295
+ if (content.includes(EF_DESIGN_PACKAGE)) {
14296
+ const dir = path20.dirname(csproj);
14297
+ return { csproj, projectDir: dir, migrationsDir: path20.join(dir, "Migrations") };
14298
+ }
14299
+ }
14300
+ return null;
14301
+ }
14302
+ function isDotnetProject(projectDir) {
14303
+ return hasDotnetMarker(projectDir);
14304
+ }
14305
+ function isDotnetFlywayProject(projectDir) {
14306
+ return isDotnetProject(projectDir) && fs20.existsSync(path20.join(projectDir, DOTNET_FLYWAY_DIR));
14307
+ }
14308
+ var MAVEN_FLYWAY_DIR = path20.join("src", "main", "resources", "db", "migration");
14309
+ function flywayMigrationsDir(projectDir) {
14310
+ const maven = path20.join(projectDir, MAVEN_FLYWAY_DIR);
14311
+ if (fs20.existsSync(maven) || fs20.existsSync(path20.join(projectDir, "pom.xml"))) return maven;
14312
+ if (isDotnetFlywayProject(projectDir)) return path20.join(projectDir, DOTNET_FLYWAY_DIR);
14313
+ return maven;
14314
+ }
14315
+
14316
+ // scripts/lakebase/migration-layout.ts
14235
14317
  var MIGRATION_LANGUAGES = [
14236
14318
  "java",
14237
14319
  "kotlin",
14238
14320
  "python",
14239
14321
  "nodejs",
14322
+ "dotnet",
14240
14323
  "unknown"
14241
14324
  ];
14242
14325
  var MIGRATION_DEFAULTS = {
@@ -14244,16 +14327,19 @@ var MIGRATION_DEFAULTS = {
14244
14327
  kotlin: { path: "src/main/resources/db/migration", pattern: /^V\d+.*\.sql$/i, glob: "*.sql" },
14245
14328
  python: { path: "alembic/versions", pattern: /^[0-9a-f][\w]*.*\.py$/i, glob: "*.py" },
14246
14329
  nodejs: { path: "migrations", pattern: /^\d+.*\.(js|ts)$/i, glob: "*.{js,ts}" },
14330
+ // .NET + Flyway; EF Core projects get their Migrations/ dir from
14331
+ // resolveMigrationLayout (see DOTNET_EFCORE_DEFAULTS).
14332
+ dotnet: { path: "db/migrations", pattern: /^V\d+.*\.sql$/i, glob: "*.sql" },
14247
14333
  unknown: { path: "src/main/resources/db/migration", pattern: /^V\d+.*\.sql$/i, glob: "*.sql" }
14248
14334
  };
14249
14335
  function detectLanguageAt(dir) {
14250
- if (fs20.existsSync(path20.join(dir, "pom.xml"))) {
14251
- const kotlinDir = path20.join(dir, "src", "main", "kotlin");
14252
- if (fs20.existsSync(kotlinDir)) {
14336
+ if (fs21.existsSync(path21.join(dir, "pom.xml"))) {
14337
+ const kotlinDir = path21.join(dir, "src", "main", "kotlin");
14338
+ if (fs21.existsSync(kotlinDir)) {
14253
14339
  return "kotlin";
14254
14340
  }
14255
14341
  try {
14256
- const pom = fs20.readFileSync(path20.join(dir, "pom.xml"), "utf-8");
14342
+ const pom = fs21.readFileSync(path21.join(dir, "pom.xml"), "utf-8");
14257
14343
  if (pom.includes("kotlin-maven-plugin")) {
14258
14344
  return "kotlin";
14259
14345
  }
@@ -14261,10 +14347,13 @@ function detectLanguageAt(dir) {
14261
14347
  }
14262
14348
  return "java";
14263
14349
  }
14264
- if (fs20.existsSync(path20.join(dir, "pyproject.toml")) || fs20.existsSync(path20.join(dir, "requirements.txt")) || fs20.existsSync(path20.join(dir, "alembic.ini"))) {
14350
+ if (fs21.existsSync(path21.join(dir, "pyproject.toml")) || fs21.existsSync(path21.join(dir, "requirements.txt")) || fs21.existsSync(path21.join(dir, "alembic.ini"))) {
14265
14351
  return "python";
14266
14352
  }
14267
- if (fs20.existsSync(path20.join(dir, "package.json"))) {
14353
+ if (hasDotnetMarker(dir)) {
14354
+ return "dotnet";
14355
+ }
14356
+ if (fs21.existsSync(path21.join(dir, "package.json"))) {
14268
14357
  return "nodejs";
14269
14358
  }
14270
14359
  return "unknown";
@@ -14285,14 +14374,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
14285
14374
  if (!rel) {
14286
14375
  return "unknown";
14287
14376
  }
14288
- const rootResolved = path20.resolve(projectDir);
14289
- let dir = path20.resolve(projectDir, rel);
14290
- while (dir === rootResolved || dir.startsWith(rootResolved + path20.sep)) {
14377
+ const rootResolved = path21.resolve(projectDir);
14378
+ let dir = path21.resolve(projectDir, rel);
14379
+ while (dir === rootResolved || dir.startsWith(rootResolved + path21.sep)) {
14291
14380
  const lang = detectLanguageAt(dir);
14292
14381
  if (lang !== "unknown") {
14293
14382
  return lang;
14294
14383
  }
14295
- const parent = path20.dirname(dir);
14384
+ const parent = path21.dirname(dir);
14296
14385
  if (parent === dir) {
14297
14386
  break;
14298
14387
  }
@@ -14311,10 +14400,33 @@ function compileMigrationPattern(src) {
14311
14400
  return void 0;
14312
14401
  }
14313
14402
  }
14403
+ var DOTNET_EFCORE_DEFAULTS = {
14404
+ path: "Migrations",
14405
+ pattern: /^\d{14}_(?!.*\.Designer\.cs$).+\.cs$/i,
14406
+ glob: "*.cs"
14407
+ };
14408
+ var DOTNET_LAYOUT_TTL_MS = 5e3;
14409
+ var dotnetLayoutCache = /* @__PURE__ */ new Map();
14410
+ function dotnetDefaults(projectDir) {
14411
+ if (!projectDir) return MIGRATION_DEFAULTS.dotnet;
14412
+ const key = path21.resolve(projectDir);
14413
+ const hit = dotnetLayoutCache.get(key);
14414
+ if (hit && Date.now() - hit.at < DOTNET_LAYOUT_TTL_MS) return hit.value;
14415
+ let value = MIGRATION_DEFAULTS.dotnet;
14416
+ if (!isDotnetFlywayProject(projectDir)) {
14417
+ const ef = findEfCoreProject(projectDir);
14418
+ if (ef) {
14419
+ const rel = path21.relative(projectDir, ef.migrationsDir).split(path21.sep).join("/");
14420
+ value = { ...DOTNET_EFCORE_DEFAULTS, path: rel };
14421
+ }
14422
+ }
14423
+ dotnetLayoutCache.set(key, { at: Date.now(), value });
14424
+ return value;
14425
+ }
14314
14426
  function resolveMigrationLayout(args) {
14315
14427
  const configuredMigrationPath = (args.migrationPath ?? "").trim();
14316
14428
  const language = resolveMigrationLanguage(args.projectDir, configuredMigrationPath, args.language);
14317
- const defaults = MIGRATION_DEFAULTS[language];
14429
+ const defaults = language === "dotnet" ? dotnetDefaults(args.projectDir) : MIGRATION_DEFAULTS[language];
14318
14430
  return {
14319
14431
  language,
14320
14432
  migrationPath: configuredMigrationPath || defaults.path,
@@ -14325,22 +14437,22 @@ function resolveMigrationLayout(args) {
14325
14437
 
14326
14438
  // scripts/lakebase/adapters/alembic-adapter.ts
14327
14439
  init_esm_shims();
14328
- import * as fs22 from "fs";
14329
- import * as path22 from "path";
14440
+ import * as fs23 from "fs";
14441
+ import * as path23 from "path";
14330
14442
 
14331
14443
  // scripts/lakebase/schema-migrate-runners/alembic.ts
14332
14444
  init_esm_shims();
14333
14445
  import { spawn as spawn5 } from "child_process";
14334
- import * as fs21 from "fs";
14335
- import * as path21 from "path";
14446
+ import * as fs22 from "fs";
14447
+ import * as path22 from "path";
14336
14448
  function resolveAlembicBin(projectDir) {
14337
14449
  const candidates = [
14338
- path21.join(projectDir, ".venv", "bin", "alembic"),
14339
- path21.join(projectDir, "venv", "bin", "alembic")
14450
+ path22.join(projectDir, ".venv", "bin", "alembic"),
14451
+ path22.join(projectDir, "venv", "bin", "alembic")
14340
14452
  ];
14341
14453
  for (const candidate of candidates) {
14342
14454
  try {
14343
- if (fs21.existsSync(candidate)) return candidate;
14455
+ if (fs22.existsSync(candidate)) return candidate;
14344
14456
  } catch {
14345
14457
  }
14346
14458
  }
@@ -14350,7 +14462,7 @@ function spawnAlembic(projectDir, args, dsn) {
14350
14462
  return new Promise((resolve2, reject) => {
14351
14463
  const bin = resolveAlembicBin(projectDir);
14352
14464
  const env = { ...process.env };
14353
- env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path21.delimiter);
14465
+ env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path22.delimiter);
14354
14466
  if (dsn) env.DATABASE_URL = dsn;
14355
14467
  const child = spawn5(bin, args, {
14356
14468
  cwd: projectDir,
@@ -14398,10 +14510,10 @@ async function createAlembicRevision(opts) {
14398
14510
  const m = stdout.match(/Generating\s+(\S+\.py)/);
14399
14511
  if (m) return m[1].trim();
14400
14512
  for (const rel of ["migrations/versions", "alembic/versions"]) {
14401
- const dir = path21.join(opts.projectDir, rel);
14402
- if (!fs21.existsSync(dir)) continue;
14403
- const hit = fs21.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
14404
- if (hit) return path21.join(dir, hit);
14513
+ const dir = path22.join(opts.projectDir, rel);
14514
+ if (!fs22.existsSync(dir)) continue;
14515
+ const hit = fs22.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
14516
+ if (hit) return path22.join(dir, hit);
14405
14517
  }
14406
14518
  throw new SchemaMigrationError(
14407
14519
  `alembic revision succeeded but the created file could not be located.
@@ -14536,15 +14648,15 @@ async function buildDsn2(args) {
14536
14648
  }
14537
14649
  function findVersionsDir(projectDir) {
14538
14650
  const candidates = [
14539
- path22.join(projectDir, "migrations", "versions"),
14540
- path22.join(projectDir, "alembic", "versions")
14651
+ path23.join(projectDir, "migrations", "versions"),
14652
+ path23.join(projectDir, "alembic", "versions")
14541
14653
  ];
14542
- return candidates.find((p) => fs22.existsSync(p));
14654
+ return candidates.find((p) => fs23.existsSync(p));
14543
14655
  }
14544
14656
  function listAlembicFiles(projectDir) {
14545
14657
  const dir = findVersionsDir(projectDir);
14546
14658
  if (!dir) return [];
14547
- const files = fs22.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
14659
+ const files = fs23.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
14548
14660
  return files.map((filename) => {
14549
14661
  const stem = filename.replace(/\.py$/, "");
14550
14662
  const sep4 = stem.indexOf("_");
@@ -14569,9 +14681,9 @@ var AlembicAdapter = {
14569
14681
  * here. Callers can still force-select via project.yaml#migration_tool.
14570
14682
  */
14571
14683
  detect(projectDir) {
14572
- if (fs22.existsSync(path22.join(projectDir, "alembic.ini"))) return true;
14573
- if (fs22.existsSync(path22.join(projectDir, "migrations", "env.py"))) return true;
14574
- if (fs22.existsSync(path22.join(projectDir, "alembic", "env.py"))) return true;
14684
+ if (fs23.existsSync(path23.join(projectDir, "alembic.ini"))) return true;
14685
+ if (fs23.existsSync(path23.join(projectDir, "migrations", "env.py"))) return true;
14686
+ if (fs23.existsSync(path23.join(projectDir, "alembic", "env.py"))) return true;
14575
14687
  return false;
14576
14688
  },
14577
14689
  async apply(args) {
@@ -14677,7 +14789,7 @@ var AlembicAdapter = {
14677
14789
  autogenerate: !!args.autogenerate,
14678
14790
  dsn
14679
14791
  });
14680
- return { status: "ok", version: revId, filename: path22.basename(created), path: created };
14792
+ return { status: "ok", version: revId, filename: path23.basename(created), path: created };
14681
14793
  } catch (err) {
14682
14794
  return {
14683
14795
  status: "error",
@@ -14694,7 +14806,7 @@ var AlembicAdapter = {
14694
14806
  if (heads.length <= 1) return { status: "noop", headsBefore: heads };
14695
14807
  if (args.dryRun) return { status: "ok", headsBefore: heads };
14696
14808
  const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
14697
- const mergeRevision = path22.basename(created).replace(/\.py$/, "").split("_")[0];
14809
+ const mergeRevision = path23.basename(created).replace(/\.py$/, "").split("_")[0];
14698
14810
  return { status: "ok", headsBefore: heads, mergeRevision, path: created };
14699
14811
  } catch (err) {
14700
14812
  return {
@@ -14709,13 +14821,13 @@ registerSchemaMigrationAdapter(AlembicAdapter);
14709
14821
 
14710
14822
  // scripts/lakebase/adapters/flyway-adapter.ts
14711
14823
  init_esm_shims();
14712
- import * as fs23 from "fs";
14713
- import * as path24 from "path";
14824
+ import * as fs24 from "fs";
14825
+ import * as path25 from "path";
14714
14826
 
14715
14827
  // scripts/lakebase/schema-migrate-runners/flyway.ts
14716
14828
  init_esm_shims();
14717
14829
  import { spawn as spawn6 } from "child_process";
14718
- import * as path23 from "path";
14830
+ import * as path24 from "path";
14719
14831
  function dsnToFlywayEnv(dsn) {
14720
14832
  const u = new URL(dsn);
14721
14833
  const user = decodeURIComponent(u.username);
@@ -14725,7 +14837,7 @@ function dsnToFlywayEnv(dsn) {
14725
14837
  return { url, user, password };
14726
14838
  }
14727
14839
  function migrationsLocation(projectDir) {
14728
- return `filesystem:${path23.join(projectDir, "src", "main", "resources", "db", "migration")}`;
14840
+ return `filesystem:${flywayMigrationsDir(projectDir)}`;
14729
14841
  }
14730
14842
  function runFlyway(ctx, args) {
14731
14843
  const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
@@ -14826,7 +14938,7 @@ async function statusFlyway(ctx) {
14826
14938
  if (state === "SUCCESS" || state === "BASELINE") {
14827
14939
  current = m.version;
14828
14940
  } else if (state === "PENDING") {
14829
- const filename = m.filepath ? path23.basename(m.filepath) : `V${m.version}__migration.sql`;
14941
+ const filename = m.filepath ? path24.basename(m.filepath) : `V${m.version}__migration.sql`;
14830
14942
  pending.push({
14831
14943
  version: m.version,
14832
14944
  filename,
@@ -14849,9 +14961,9 @@ async function buildDsn3(args) {
14849
14961
  return result.url;
14850
14962
  }
14851
14963
  function listFlywayFiles(projectDir) {
14852
- const dir = path24.join(projectDir, "src", "main", "resources", "db", "migration");
14853
- if (!fs23.existsSync(dir)) return [];
14854
- const files = fs23.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
14964
+ const dir = flywayMigrationsDir(projectDir);
14965
+ if (!fs24.existsSync(dir)) return [];
14966
+ const files = fs24.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
14855
14967
  return files.map((filename) => {
14856
14968
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
14857
14969
  const version = m[1];
@@ -14872,9 +14984,9 @@ function versionCompare(a, b) {
14872
14984
  }
14873
14985
  var FlywayAdapter = {
14874
14986
  id: "flyway",
14875
- languages: ["java", "kotlin"],
14987
+ languages: ["java", "kotlin", "dotnet"],
14876
14988
  detect(projectDir) {
14877
- return fs23.existsSync(path24.join(projectDir, "pom.xml"));
14989
+ return fs24.existsSync(path25.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
14878
14990
  },
14879
14991
  async apply(args) {
14880
14992
  const dsn = await buildDsn3(args);
@@ -14932,14 +15044,14 @@ var FlywayAdapter = {
14932
15044
  // optional-protocol shape makes this additive.
14933
15045
  async newMigration(args) {
14934
15046
  try {
14935
- const dir = path24.join(args.projectDir, "src", "main", "resources", "db", "migration");
14936
- fs23.mkdirSync(dir, { recursive: true });
15047
+ const dir = flywayMigrationsDir(args.projectDir);
15048
+ fs24.mkdirSync(dir, { recursive: true });
14937
15049
  const version = migrationTimestamp();
14938
15050
  const slug = migrationSlug2(args.slug);
14939
15051
  const filename = `V${version}__${slug}.sql`;
14940
- const full = path24.join(dir, filename);
14941
- if (fs23.existsSync(full)) throw new Error(`${filename} already exists`);
14942
- fs23.writeFileSync(
15052
+ const full = path25.join(dir, filename);
15053
+ if (fs24.existsSync(full)) throw new Error(`${filename} already exists`);
15054
+ fs24.writeFileSync(
14943
15055
  full,
14944
15056
  `-- V${version}: ${args.slug}
14945
15057
  -- Flyway migration (write your DDL/DML below).
@@ -14962,19 +15074,19 @@ registerSchemaMigrationAdapter(FlywayAdapter);
14962
15074
 
14963
15075
  // scripts/lakebase/adapters/knex-adapter.ts
14964
15076
  init_esm_shims();
14965
- import * as fs25 from "fs";
14966
- import * as path26 from "path";
15077
+ import * as fs26 from "fs";
15078
+ import * as path27 from "path";
14967
15079
 
14968
15080
  // scripts/lakebase/schema-migrate-runners/knex.ts
14969
15081
  init_esm_shims();
14970
15082
  import { spawn as spawn7 } from "child_process";
14971
- import * as fs24 from "fs";
14972
- import * as path25 from "path";
15083
+ import * as fs25 from "fs";
15084
+ import * as path26 from "path";
14973
15085
  var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
14974
15086
  function findKnexfile(projectDir) {
14975
15087
  for (const name of KNEXFILE_VARIANTS) {
14976
- const p = path25.join(projectDir, name);
14977
- if (fs24.existsSync(p)) return p;
15088
+ const p = path26.join(projectDir, name);
15089
+ if (fs25.existsSync(p)) return p;
14978
15090
  }
14979
15091
  return void 0;
14980
15092
  }
@@ -15126,9 +15238,9 @@ async function buildDsn4(args) {
15126
15238
  }
15127
15239
  var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
15128
15240
  function listKnexFiles(projectDir) {
15129
- const dir = path26.join(projectDir, "migrations");
15130
- if (!fs25.existsSync(dir)) return [];
15131
- const files = fs25.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
15241
+ const dir = path27.join(projectDir, "migrations");
15242
+ if (!fs26.existsSync(dir)) return [];
15243
+ const files = fs26.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
15132
15244
  return files.map((filename) => {
15133
15245
  const stem = filename.replace(/\.(js|ts)$/, "");
15134
15246
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -15148,7 +15260,7 @@ var KnexAdapter = {
15148
15260
  * project.yaml#migration_tool.
15149
15261
  */
15150
15262
  detect(projectDir) {
15151
- return KNEXFILE_VARIANTS2.some((name) => fs25.existsSync(path26.join(projectDir, name)));
15263
+ return KNEXFILE_VARIANTS2.some((name) => fs26.existsSync(path27.join(projectDir, name)));
15152
15264
  },
15153
15265
  async apply(args) {
15154
15266
  const dsn = await buildDsn4(args);
@@ -15221,9 +15333,9 @@ var KnexAdapter = {
15221
15333
  async newMigration(args) {
15222
15334
  try {
15223
15335
  const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
15224
- const stem = path26.basename(created).replace(/\.(js|ts)$/, "");
15336
+ const stem = path27.basename(created).replace(/\.(js|ts)$/, "");
15225
15337
  const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
15226
- return { status: "ok", version, filename: path26.basename(created), path: created };
15338
+ return { status: "ok", version, filename: path27.basename(created), path: created };
15227
15339
  } catch (err) {
15228
15340
  return {
15229
15341
  status: "error",
@@ -15237,6 +15349,362 @@ var KnexAdapter = {
15237
15349
  };
15238
15350
  registerSchemaMigrationAdapter(KnexAdapter);
15239
15351
 
15352
+ // scripts/lakebase/adapters/efcore-adapter.ts
15353
+ init_esm_shims();
15354
+ import * as path29 from "path";
15355
+
15356
+ // scripts/lakebase/schema-migrate-runners/efcore.ts
15357
+ init_esm_shims();
15358
+ import { spawn as spawn8 } from "child_process";
15359
+ import * as fs27 from "fs";
15360
+ import * as path28 from "path";
15361
+ import { Client as Client3 } from "pg";
15362
+ var MIGRATION_ID = /^(\d{14})_(.+)$/;
15363
+ function efVersion(id) {
15364
+ return MIGRATION_ID.exec(id)?.[1] ?? id;
15365
+ }
15366
+ function efDescription(id) {
15367
+ const name = MIGRATION_ID.exec(id)?.[2] ?? id;
15368
+ return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
15369
+ }
15370
+ function dsnToNpgsql(dsn) {
15371
+ const u = new URL(dsn);
15372
+ const parts = [
15373
+ `Host=${u.hostname}`,
15374
+ `Port=${u.port || "5432"}`,
15375
+ `Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
15376
+ ];
15377
+ if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
15378
+ if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
15379
+ const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
15380
+ const ssl = {
15381
+ disable: "Disable",
15382
+ allow: "Allow",
15383
+ prefer: "Prefer",
15384
+ require: "Require",
15385
+ "verify-ca": "VerifyCA",
15386
+ "verify-full": "VerifyFull"
15387
+ };
15388
+ parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
15389
+ const app = u.searchParams.get("application_name");
15390
+ if (app) parts.push(`Application Name=${app}`);
15391
+ return parts.map((p) => p.replace(/;/g, "")).join(";");
15392
+ }
15393
+ function parseEfJson(stdout) {
15394
+ const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
15395
+ if (!body) {
15396
+ throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
15397
+ }
15398
+ try {
15399
+ return JSON.parse(body);
15400
+ } catch (err) {
15401
+ throw new SchemaMigrationError(
15402
+ `dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
15403
+ Body (first 400 chars): ${body.slice(0, 400)}`
15404
+ );
15405
+ }
15406
+ }
15407
+ function requireProject(projectDir) {
15408
+ const project = findEfCoreProject(projectDir);
15409
+ if (!project) {
15410
+ throw new SchemaMigrationError(
15411
+ `No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
15412
+ );
15413
+ }
15414
+ return project;
15415
+ }
15416
+ function spawnDotnet(projectDir, args, env) {
15417
+ return new Promise((resolve2, reject) => {
15418
+ const child = spawn8("dotnet", args, {
15419
+ cwd: projectDir,
15420
+ env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
15421
+ stdio: ["ignore", "pipe", "pipe"]
15422
+ });
15423
+ let stdout = "";
15424
+ let stderr = "";
15425
+ child.stdout.on("data", (chunk) => {
15426
+ stdout += chunk.toString("utf8");
15427
+ });
15428
+ child.stderr.on("data", (chunk) => {
15429
+ stderr += chunk.toString("utf8");
15430
+ });
15431
+ child.on("error", (err) => {
15432
+ reject(
15433
+ new SchemaMigrationError(
15434
+ `Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
15435
+ err
15436
+ )
15437
+ );
15438
+ });
15439
+ child.on("close", (code) => {
15440
+ if (code === 0) {
15441
+ resolve2({ stdout, stderr });
15442
+ } else {
15443
+ reject(
15444
+ new SchemaMigrationError(
15445
+ `dotnet ${args.join(" ")} exited with code ${code}.
15446
+ stdout: ${stdout}
15447
+ stderr: ${stderr}`
15448
+ )
15449
+ );
15450
+ }
15451
+ });
15452
+ });
15453
+ }
15454
+ var restored = /* @__PURE__ */ new Set();
15455
+ async function ensureRestored(projectDir, csproj) {
15456
+ if (restored.has(projectDir)) return;
15457
+ if (fs27.existsSync(path28.join(projectDir, ".config", "dotnet-tools.json"))) {
15458
+ await spawnDotnet(projectDir, ["tool", "restore"], {});
15459
+ }
15460
+ await spawnDotnet(projectDir, ["restore", csproj], {});
15461
+ restored.add(projectDir);
15462
+ }
15463
+ async function runEf(projectDir, args, dsn, opts = {}) {
15464
+ const project = requireProject(projectDir);
15465
+ const csproj = path28.relative(projectDir, project.csproj) || project.csproj;
15466
+ await ensureRestored(projectDir, csproj);
15467
+ const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
15468
+ if (opts.noBuild) full.push("--no-build");
15469
+ const env = {};
15470
+ if (dsn) {
15471
+ env.DATABASE_URL = dsn;
15472
+ if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
15473
+ }
15474
+ return spawnDotnet(projectDir, full, env);
15475
+ }
15476
+ async function listEfMigrations(ctx, opts = {}) {
15477
+ const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
15478
+ const entries = parseEfJson(stdout);
15479
+ if (ctx.dsn && entries.some((e) => e.applied === null)) {
15480
+ const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
15481
+ throw new SchemaMigrationError(
15482
+ `dotnet ef could not read migration state from the database (check connectivity and credentials).
15483
+ ${warning || stderr}`.trim()
15484
+ );
15485
+ }
15486
+ return entries;
15487
+ }
15488
+ async function readEfHistory(dsn) {
15489
+ const client = new Client3({ connectionString: dsn, application_name: connectionApplicationName() });
15490
+ await client.connect();
15491
+ try {
15492
+ const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
15493
+ if (!exists.rows[0]?.present) return null;
15494
+ const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
15495
+ return r.rows.map((row) => row.MigrationId);
15496
+ } finally {
15497
+ await client.end();
15498
+ }
15499
+ }
15500
+ function toApplied(entries) {
15501
+ return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
15502
+ }
15503
+ async function statusEfcore(ctx) {
15504
+ const entries = await listEfMigrations(ctx);
15505
+ const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
15506
+ const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
15507
+ const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
15508
+ return { current, pending, tool: "efcore" };
15509
+ }
15510
+ async function applyEfcore(ctx) {
15511
+ const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
15512
+ await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
15513
+ const after = await listEfMigrations(ctx, { noBuild: true });
15514
+ const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
15515
+ return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
15516
+ }
15517
+ function resolveEfTarget(target, applied) {
15518
+ const t = target.trim();
15519
+ if (t === "0") return "0";
15520
+ const rel = /^-(\d+)$/.exec(t);
15521
+ if (rel) {
15522
+ const steps = Number(rel[1]);
15523
+ if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
15524
+ const index = applied.length - 1 - steps;
15525
+ if (index < -1) {
15526
+ throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
15527
+ }
15528
+ return index === -1 ? "0" : applied[index].id;
15529
+ }
15530
+ const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
15531
+ if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
15532
+ return match.id;
15533
+ }
15534
+ async function rollbackEfcore(ctx) {
15535
+ const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
15536
+ const targetId = resolveEfTarget(ctx.target, beforeEntries);
15537
+ await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
15538
+ const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
15539
+ return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
15540
+ }
15541
+ function efMigrationName(slug) {
15542
+ const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
15543
+ if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
15544
+ return /^[0-9]/.test(name) ? `M${name}` : name;
15545
+ }
15546
+ async function createEfcoreMigration(opts) {
15547
+ const project = requireProject(opts.projectDir);
15548
+ const name = efMigrationName(opts.slug);
15549
+ const existing = new Set(fs27.existsSync(project.migrationsDir) ? fs27.readdirSync(project.migrationsDir) : []);
15550
+ await runEf(opts.projectDir, ["migrations", "add", name]);
15551
+ const created = fs27.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
15552
+ if (created.length !== 1) {
15553
+ throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
15554
+ }
15555
+ return { id: created[0].replace(/\.cs$/, ""), path: path28.join(project.migrationsDir, created[0]) };
15556
+ }
15557
+ function localEfMigrationIds(projectDir) {
15558
+ const project = findEfCoreProject(projectDir);
15559
+ if (!project || !fs27.existsSync(project.migrationsDir)) return [];
15560
+ return fs27.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();
15561
+ }
15562
+ function efProductVersion(project) {
15563
+ try {
15564
+ const csproj = fs27.readFileSync(project.csproj, "utf-8");
15565
+ const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
15566
+ if (m) return m[1];
15567
+ } catch {
15568
+ }
15569
+ return "unknown";
15570
+ }
15571
+ async function stampEfcore(ctx) {
15572
+ const project = requireProject(ctx.projectDir);
15573
+ const ids = localEfMigrationIds(ctx.projectDir);
15574
+ if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
15575
+ const rev = ctx.revision.trim();
15576
+ const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
15577
+ if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
15578
+ const toStamp = ids.slice(0, end + 1);
15579
+ const productVersion = efProductVersion(project);
15580
+ const client = new Client3({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
15581
+ await client.connect();
15582
+ try {
15583
+ await client.query("BEGIN");
15584
+ await client.query(
15585
+ `CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
15586
+ "MigrationId" character varying(150) NOT NULL,
15587
+ "ProductVersion" character varying(32) NOT NULL,
15588
+ CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
15589
+ );
15590
+ await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
15591
+ for (const id of toStamp) {
15592
+ await client.query(
15593
+ `INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
15594
+ ON CONFLICT ("MigrationId") DO NOTHING`,
15595
+ [id, productVersion]
15596
+ );
15597
+ }
15598
+ await client.query("COMMIT");
15599
+ } catch (err) {
15600
+ await client.query("ROLLBACK").catch(() => void 0);
15601
+ throw err;
15602
+ } finally {
15603
+ await client.end();
15604
+ }
15605
+ return efVersion(toStamp[toStamp.length - 1]);
15606
+ }
15607
+
15608
+ // scripts/lakebase/adapters/efcore-adapter.ts
15609
+ async function buildDsn5(args) {
15610
+ const result = await getConnection({
15611
+ output: "dsn",
15612
+ instance: args.instance,
15613
+ branch: args.branch,
15614
+ database: args.database,
15615
+ endpointName: args.endpointName
15616
+ });
15617
+ return result.url;
15618
+ }
15619
+ function errMsg(err) {
15620
+ return err instanceof Error ? err.message : String(err);
15621
+ }
15622
+ function listEfCoreFiles(projectDir) {
15623
+ return localEfMigrationIds(projectDir).map((id) => ({
15624
+ version: efVersion(id),
15625
+ filename: `${id}.cs`,
15626
+ description: efDescription(id),
15627
+ type: "CSharp",
15628
+ tool: "efcore"
15629
+ }));
15630
+ }
15631
+ var EfCoreAdapter = {
15632
+ id: "efcore",
15633
+ languages: ["dotnet"],
15634
+ /** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
15635
+ detect(projectDir) {
15636
+ return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
15637
+ },
15638
+ async apply(args) {
15639
+ try {
15640
+ const dsn = await buildDsn5(args);
15641
+ const r = await applyEfcore({ projectDir: args.projectDir, dsn });
15642
+ return {
15643
+ applied_migrations: r.applied,
15644
+ status: r.alreadyAtLatest ? "noop" : "ok",
15645
+ tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
15646
+ };
15647
+ } catch (err) {
15648
+ return { applied_migrations: [], status: "error", error: errMsg(err) };
15649
+ }
15650
+ },
15651
+ async rollback(args) {
15652
+ try {
15653
+ const dsn = await buildDsn5(args);
15654
+ const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
15655
+ return {
15656
+ rolled_back: r.rolledBack,
15657
+ status: r.rolledBack.length === 0 ? "noop" : "ok",
15658
+ tool_specific: { tool: r.tool }
15659
+ };
15660
+ } catch (err) {
15661
+ return { rolled_back: [], status: "error", error: errMsg(err) };
15662
+ }
15663
+ },
15664
+ async stamp(args) {
15665
+ try {
15666
+ const dsn = await buildDsn5(args);
15667
+ const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
15668
+ return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
15669
+ } catch (err) {
15670
+ return { status: "error", stamped_revision: null, error: errMsg(err) };
15671
+ }
15672
+ },
15673
+ async status(args) {
15674
+ try {
15675
+ const dsn = await buildDsn5(args);
15676
+ const r = await statusEfcore({ projectDir: args.projectDir, dsn });
15677
+ return {
15678
+ applied_version: r.current ?? null,
15679
+ pending: r.pending,
15680
+ applied: [],
15681
+ status: "ok",
15682
+ tool_specific: { tool: r.tool }
15683
+ };
15684
+ } catch (err) {
15685
+ return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
15686
+ }
15687
+ },
15688
+ async list(args) {
15689
+ return { files: listEfCoreFiles(args.projectDir) };
15690
+ },
15691
+ // baseline: use stamp (EF has no separate baseline concept).
15692
+ async newMigration(args) {
15693
+ try {
15694
+ const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
15695
+ return {
15696
+ status: "ok",
15697
+ version: efVersion(created.id),
15698
+ filename: path29.basename(created.path),
15699
+ path: created.path
15700
+ };
15701
+ } catch (err) {
15702
+ return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
15703
+ }
15704
+ }
15705
+ };
15706
+ registerSchemaMigrationAdapter(EfCoreAdapter);
15707
+
15240
15708
  // scripts/lakebase/schema-migrate.ts
15241
15709
  var SchemaMigrationError = class extends Error {
15242
15710
  constructor(message, cause) {
@@ -15250,13 +15718,16 @@ function detectLanguage(projectDir) {
15250
15718
  const lang = resolveMigrationLanguage(projectDir);
15251
15719
  if (lang === "unknown") {
15252
15720
  throw new SchemaMigrationError(
15253
- `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.`
15721
+ `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.`
15254
15722
  );
15255
15723
  }
15256
15724
  return lang;
15257
15725
  }
15258
- function toolForLanguage(language) {
15726
+ function toolForLanguage(language, projectDir) {
15259
15727
  switch (language) {
15728
+ case "dotnet":
15729
+ if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
15730
+ return "efcore";
15260
15731
  case "java":
15261
15732
  case "kotlin":
15262
15733
  return "flyway";
@@ -15269,8 +15740,10 @@ function toolForLanguage(language) {
15269
15740
  function listSchemaMigrations(args = {}) {
15270
15741
  const projectDir = args.projectDir ?? process.cwd();
15271
15742
  const language = args.language ?? detectLanguage(projectDir);
15272
- const tool = toolForLanguage(language);
15743
+ const tool = toolForLanguage(language, projectDir);
15273
15744
  switch (tool) {
15745
+ case "efcore":
15746
+ return listEfCoreFiles(projectDir);
15274
15747
  case "flyway":
15275
15748
  return listFlywayMigrations(projectDir);
15276
15749
  case "alembic":
@@ -15280,9 +15753,9 @@ function listSchemaMigrations(args = {}) {
15280
15753
  }
15281
15754
  }
15282
15755
  function listFlywayMigrations(projectDir) {
15283
- const dir = path27.join(projectDir, "src", "main", "resources", "db", "migration");
15284
- if (!fs26.existsSync(dir)) return [];
15285
- const files = fs26.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
15756
+ const dir = flywayMigrationsDir(projectDir);
15757
+ if (!fs28.existsSync(dir)) return [];
15758
+ const files = fs28.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
15286
15759
  return files.map((filename) => {
15287
15760
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
15288
15761
  const version = m[1];
@@ -15292,12 +15765,12 @@ function listFlywayMigrations(projectDir) {
15292
15765
  }
15293
15766
  function listAlembicMigrations(projectDir) {
15294
15767
  const candidates = [
15295
- path27.join(projectDir, "migrations", "versions"),
15296
- path27.join(projectDir, "alembic", "versions")
15768
+ path30.join(projectDir, "migrations", "versions"),
15769
+ path30.join(projectDir, "alembic", "versions")
15297
15770
  ];
15298
- const dir = candidates.find((p) => fs26.existsSync(p));
15771
+ const dir = candidates.find((p) => fs28.existsSync(p));
15299
15772
  if (!dir) return [];
15300
- const files = fs26.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
15773
+ const files = fs28.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
15301
15774
  return files.map((filename) => {
15302
15775
  const stem = filename.replace(/\.py$/, "");
15303
15776
  const sep4 = stem.indexOf("_");
@@ -15307,9 +15780,9 @@ function listAlembicMigrations(projectDir) {
15307
15780
  }).sort((a, b) => a.filename.localeCompare(b.filename));
15308
15781
  }
15309
15782
  function listKnexMigrations(projectDir) {
15310
- const dir = path27.join(projectDir, "migrations");
15311
- if (!fs26.existsSync(dir)) return [];
15312
- const files = fs26.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
15783
+ const dir = path30.join(projectDir, "migrations");
15784
+ if (!fs28.existsSync(dir)) return [];
15785
+ const files = fs28.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
15313
15786
  return files.map((filename) => {
15314
15787
  const stem = filename.replace(/\.(js|ts)$/, "");
15315
15788
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -15331,7 +15804,7 @@ function versionCompare2(a, b) {
15331
15804
  return 0;
15332
15805
  }
15333
15806
  function adapterFor(projectDir, language) {
15334
- const override = language ? toolForLanguage(language) : void 0;
15807
+ const override = language ? toolForLanguage(language, projectDir) : void 0;
15335
15808
  return resolveSchemaMigrationAdapter(projectDir, override);
15336
15809
  }
15337
15810
  var TierMigrationRefusedError = class extends Error {
@@ -15485,8 +15958,8 @@ async function runInfraSuite(args) {
15485
15958
  duration_ms: Date.now() - start
15486
15959
  };
15487
15960
  if (args.junitOutput) {
15488
- fs27.mkdirSync(path28.dirname(args.junitOutput), { recursive: true });
15489
- fs27.writeFileSync(args.junitOutput, formatJUnit(result), "utf8");
15961
+ fs29.mkdirSync(path31.dirname(args.junitOutput), { recursive: true });
15962
+ fs29.writeFileSync(args.junitOutput, formatJUnit(result), "utf8");
15490
15963
  }
15491
15964
  return result;
15492
15965
  }
@@ -15532,8 +16005,8 @@ function escapeXml(s) {
15532
16005
 
15533
16006
  // scripts/lakebase/scm-claim-feature.ts
15534
16007
  init_esm_shims();
15535
- import * as fs28 from "fs";
15536
- import * as path29 from "path";
16008
+ import * as fs30 from "fs";
16009
+ import * as path32 from "path";
15537
16010
  var ScmClaimError = class extends Error {
15538
16011
  constructor(message, code) {
15539
16012
  super(message);
@@ -15590,7 +16063,7 @@ async function claimFeatureBranch(args) {
15590
16063
  const current = readWorkflowState(args.projectDir);
15591
16064
  if (!current) {
15592
16065
  throw new ScmClaimError(
15593
- `No SCM workflow state found at ${path29.join(args.projectDir, ".lakebase/workflow-state.json")}. Run lakebase-create-project to scaffold, or re-seed via the substrate.`,
16066
+ `No SCM workflow state found at ${path32.join(args.projectDir, ".lakebase/workflow-state.json")}. Run lakebase-create-project to scaffold, or re-seed via the substrate.`,
15594
16067
  "no-state-file"
15595
16068
  );
15596
16069
  }
@@ -15694,8 +16167,8 @@ function alreadyClaimedSentinel(state) {
15694
16167
  };
15695
16168
  }
15696
16169
  function workflowStateFileExists(projectDir) {
15697
- return fs28.existsSync(
15698
- path29.join(projectDir, ".lakebase/workflow-state.json")
16170
+ return fs30.existsSync(
16171
+ path32.join(projectDir, ".lakebase/workflow-state.json")
15699
16172
  );
15700
16173
  }
15701
16174
 
@@ -16217,12 +16690,12 @@ async function sync(args) {
16217
16690
 
16218
16691
  // scripts/lakebase/workflow-drift.ts
16219
16692
  init_esm_shims();
16220
- import * as fs29 from "fs";
16221
- import * as path30 from "path";
16693
+ import * as fs31 from "fs";
16694
+ import * as path33 from "path";
16222
16695
  function findKitTemplatesDir(start) {
16223
16696
  let dir = start;
16224
16697
  for (let i = 0; i < 6; i++) {
16225
- const candidate = path30.join(
16698
+ const candidate = path33.join(
16226
16699
  dir,
16227
16700
  "templates",
16228
16701
  "project",
@@ -16230,8 +16703,8 @@ function findKitTemplatesDir(start) {
16230
16703
  ".github",
16231
16704
  "workflows"
16232
16705
  );
16233
- if (fs29.existsSync(candidate)) return candidate;
16234
- const parent = path30.dirname(dir);
16706
+ if (fs31.existsSync(candidate)) return candidate;
16707
+ const parent = path33.dirname(dir);
16235
16708
  if (parent === dir) break;
16236
16709
  dir = parent;
16237
16710
  }
@@ -16255,13 +16728,13 @@ function unifiedDiff(name, projectContent, templateContent) {
16255
16728
  return out.join("\n");
16256
16729
  }
16257
16730
  function detectWorkflowDrift(args) {
16258
- const projectWorkflowsDir = path30.join(
16731
+ const projectWorkflowsDir = path33.join(
16259
16732
  args.projectDir,
16260
16733
  ".github",
16261
16734
  "workflows"
16262
16735
  );
16263
- const here = path30.dirname(new URL(import.meta.url).pathname);
16264
- const kitWorkflowsDir = args.kitDir ? path30.join(
16736
+ const here = path33.dirname(new URL(import.meta.url).pathname);
16737
+ const kitWorkflowsDir = args.kitDir ? path33.join(
16265
16738
  args.kitDir,
16266
16739
  "templates",
16267
16740
  "project",
@@ -16269,21 +16742,21 @@ function detectWorkflowDrift(args) {
16269
16742
  ".github",
16270
16743
  "workflows"
16271
16744
  ) : findKitTemplatesDir(here);
16272
- const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16273
- const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16745
+ const templateFiles = fs31.existsSync(kitWorkflowsDir) ? fs31.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16746
+ const projectFiles = fs31.existsSync(projectWorkflowsDir) ? fs31.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16274
16747
  const version = readKitVersion(kitWorkflowsDir);
16275
16748
  const seen = /* @__PURE__ */ new Set();
16276
16749
  const files = [];
16277
16750
  for (const name of templateFiles) {
16278
16751
  seen.add(name);
16279
- const projectPath2 = path30.join(projectWorkflowsDir, name);
16280
- const templatePath = path30.join(kitWorkflowsDir, name);
16281
- if (!fs29.existsSync(projectPath2)) {
16752
+ const projectPath2 = path33.join(projectWorkflowsDir, name);
16753
+ const templatePath = path33.join(kitWorkflowsDir, name);
16754
+ if (!fs31.existsSync(projectPath2)) {
16282
16755
  files.push({ name, status: "missing" });
16283
16756
  continue;
16284
16757
  }
16285
- const projectContent = fs29.readFileSync(projectPath2, "utf8");
16286
- const templateContent = applyPlaceholders(fs29.readFileSync(templatePath, "utf8"), version);
16758
+ const projectContent = fs31.readFileSync(projectPath2, "utf8");
16759
+ const templateContent = applyPlaceholders(fs31.readFileSync(templatePath, "utf8"), version);
16287
16760
  if (projectContent === templateContent) {
16288
16761
  files.push({ name, status: "unchanged" });
16289
16762
  } else {
@@ -16314,10 +16787,10 @@ function detectWorkflowDrift(args) {
16314
16787
  function readKitVersion(kitWorkflowsDir) {
16315
16788
  let dir = kitWorkflowsDir;
16316
16789
  for (let i = 0; i < 5; i++) {
16317
- dir = path30.dirname(dir);
16790
+ dir = path33.dirname(dir);
16318
16791
  }
16319
16792
  try {
16320
- const raw = fs29.readFileSync(path30.join(dir, "package.json"), "utf-8");
16793
+ const raw = fs31.readFileSync(path33.join(dir, "package.json"), "utf-8");
16321
16794
  const pkg = JSON.parse(raw);
16322
16795
  return typeof pkg.version === "string" ? pkg.version : "unknown";
16323
16796
  } catch {
@@ -16334,7 +16807,7 @@ var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
16334
16807
  function findKitCommandsDir(start) {
16335
16808
  let dir = start;
16336
16809
  for (let i = 0; i < 6; i++) {
16337
- const candidate = path30.join(
16810
+ const candidate = path33.join(
16338
16811
  dir,
16339
16812
  "templates",
16340
16813
  "project",
@@ -16342,8 +16815,8 @@ function findKitCommandsDir(start) {
16342
16815
  ".claude",
16343
16816
  "commands"
16344
16817
  );
16345
- if (fs29.existsSync(candidate)) return candidate;
16346
- const parent = path30.dirname(dir);
16818
+ if (fs31.existsSync(candidate)) return candidate;
16819
+ const parent = path33.dirname(dir);
16347
16820
  if (parent === dir) break;
16348
16821
  dir = parent;
16349
16822
  }
@@ -16356,24 +16829,24 @@ function parsePinnedVersion(content) {
16356
16829
  return m ? m[1] : void 0;
16357
16830
  }
16358
16831
  function detectCommandDrift(args) {
16359
- const projectCommandsDir = path30.join(args.projectDir, ".claude", "commands");
16360
- const here = path30.dirname(new URL(import.meta.url).pathname);
16361
- const kitCommandsDir = args.kitDir ? path30.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
16832
+ const projectCommandsDir = path33.join(args.projectDir, ".claude", "commands");
16833
+ const here = path33.dirname(new URL(import.meta.url).pathname);
16834
+ const kitCommandsDir = args.kitDir ? path33.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
16362
16835
  const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
16363
- const templateFiles = fs29.existsSync(kitCommandsDir) ? fs29.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16364
- const projectFiles = fs29.existsSync(projectCommandsDir) ? fs29.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16836
+ const templateFiles = fs31.existsSync(kitCommandsDir) ? fs31.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16837
+ const projectFiles = fs31.existsSync(projectCommandsDir) ? fs31.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16365
16838
  const seen = /* @__PURE__ */ new Set();
16366
16839
  const files = [];
16367
16840
  for (const name of templateFiles) {
16368
16841
  seen.add(name);
16369
- const projectPath2 = path30.join(projectCommandsDir, name);
16370
- const templatePath = path30.join(kitCommandsDir, name);
16371
- const templateRaw = fs29.readFileSync(templatePath, "utf8");
16372
- if (!fs29.existsSync(projectPath2)) {
16842
+ const projectPath2 = path33.join(projectCommandsDir, name);
16843
+ const templatePath = path33.join(kitCommandsDir, name);
16844
+ const templateRaw = fs31.readFileSync(templatePath, "utf8");
16845
+ if (!fs31.existsSync(projectPath2)) {
16373
16846
  files.push({ name, status: "missing", kit_version: kitVersion });
16374
16847
  continue;
16375
16848
  }
16376
- const projectContent = fs29.readFileSync(projectPath2, "utf8");
16849
+ const projectContent = fs31.readFileSync(projectPath2, "utf8");
16377
16850
  const pinned = parsePinnedVersion(projectContent);
16378
16851
  const versionForCompare = pinned ?? kitVersion;
16379
16852
  const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
@@ -16411,10 +16884,10 @@ function detectCommandDrift(args) {
16411
16884
  function readKitVersionFromCommandsDir(kitCommandsDir) {
16412
16885
  let dir = kitCommandsDir;
16413
16886
  for (let i = 0; i < 5; i++) {
16414
- dir = path30.dirname(dir);
16887
+ dir = path33.dirname(dir);
16415
16888
  }
16416
16889
  try {
16417
- const raw = fs29.readFileSync(path30.join(dir, "package.json"), "utf-8");
16890
+ const raw = fs31.readFileSync(path33.join(dir, "package.json"), "utf-8");
16418
16891
  const pkg = JSON.parse(raw);
16419
16892
  return typeof pkg.version === "string" ? pkg.version : "unknown";
16420
16893
  } catch {
@@ -16431,13 +16904,13 @@ function detectScaffoldedDrift(args) {
16431
16904
  };
16432
16905
  }
16433
16906
  function updateWorkflows(args) {
16434
- const projectWorkflowsDir = path30.join(
16907
+ const projectWorkflowsDir = path33.join(
16435
16908
  args.projectDir,
16436
16909
  ".github",
16437
16910
  "workflows"
16438
16911
  );
16439
- const here = path30.dirname(new URL(import.meta.url).pathname);
16440
- const kitWorkflowsDir = args.kitDir ? path30.join(
16912
+ const here = path33.dirname(new URL(import.meta.url).pathname);
16913
+ const kitWorkflowsDir = args.kitDir ? path33.join(
16441
16914
  args.kitDir,
16442
16915
  "templates",
16443
16916
  "project",
@@ -16448,22 +16921,22 @@ function updateWorkflows(args) {
16448
16921
  const substitute = args.substitute !== false;
16449
16922
  const dryRun = args.dryRun === true;
16450
16923
  const pruneExtras = args.pruneExtras === true;
16451
- const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16452
- const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16453
- if (!dryRun && templateFiles.length > 0 && !fs29.existsSync(projectWorkflowsDir)) {
16454
- fs29.mkdirSync(projectWorkflowsDir, { recursive: true });
16924
+ const templateFiles = fs31.existsSync(kitWorkflowsDir) ? fs31.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16925
+ const projectFiles = fs31.existsSync(projectWorkflowsDir) ? fs31.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16926
+ if (!dryRun && templateFiles.length > 0 && !fs31.existsSync(projectWorkflowsDir)) {
16927
+ fs31.mkdirSync(projectWorkflowsDir, { recursive: true });
16455
16928
  }
16456
16929
  const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
16457
16930
  const seen = /* @__PURE__ */ new Set();
16458
16931
  const files = [];
16459
16932
  for (const name of templateFiles) {
16460
16933
  seen.add(name);
16461
- const projectPath2 = path30.join(projectWorkflowsDir, name);
16462
- const templatePath = path30.join(kitWorkflowsDir, name);
16463
- const templateRaw = fs29.readFileSync(templatePath, "utf-8");
16934
+ const projectPath2 = path33.join(projectWorkflowsDir, name);
16935
+ const templatePath = path33.join(kitWorkflowsDir, name);
16936
+ const templateRaw = fs31.readFileSync(templatePath, "utf-8");
16464
16937
  const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
16465
- const existed = fs29.existsSync(projectPath2);
16466
- const current = existed ? fs29.readFileSync(projectPath2, "utf-8") : "";
16938
+ const existed = fs31.existsSync(projectPath2);
16939
+ const current = existed ? fs31.readFileSync(projectPath2, "utf-8") : "";
16467
16940
  let outcome;
16468
16941
  if (!existed) {
16469
16942
  outcome = "added";
@@ -16473,16 +16946,16 @@ function updateWorkflows(args) {
16473
16946
  outcome = "updated";
16474
16947
  }
16475
16948
  if (!dryRun && outcome !== "unchanged") {
16476
- fs29.writeFileSync(projectPath2, desired);
16949
+ fs31.writeFileSync(projectPath2, desired);
16477
16950
  }
16478
16951
  files.push({ name, outcome });
16479
16952
  }
16480
16953
  if (pruneExtras) {
16481
16954
  for (const name of projectFiles) {
16482
16955
  if (seen.has(name)) continue;
16483
- const projectPath2 = path30.join(projectWorkflowsDir, name);
16956
+ const projectPath2 = path33.join(projectWorkflowsDir, name);
16484
16957
  if (!dryRun) {
16485
- fs29.unlinkSync(projectPath2);
16958
+ fs31.unlinkSync(projectPath2);
16486
16959
  }
16487
16960
  files.push({ name, outcome: "removed" });
16488
16961
  }
@@ -17115,15 +17588,15 @@ function parentForTopology(t, defaultLeaf) {
17115
17588
 
17116
17589
  // scripts/lakebase/scm-doctor.ts
17117
17590
  init_esm_shims();
17118
- import * as fs30 from "fs";
17119
- import * as path31 from "path";
17591
+ import * as fs32 from "fs";
17592
+ import * as path34 from "path";
17120
17593
  var FEATURE_PREFIX = "feature/";
17121
17594
  var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
17122
17595
  function readEnv(projectDir) {
17123
- const envPath = path31.join(projectDir, ".env");
17596
+ const envPath = path34.join(projectDir, ".env");
17124
17597
  const out = /* @__PURE__ */ new Map();
17125
- if (!fs30.existsSync(envPath)) return out;
17126
- const lines = fs30.readFileSync(envPath, "utf8").split("\n");
17598
+ if (!fs32.existsSync(envPath)) return out;
17599
+ const lines = fs32.readFileSync(envPath, "utf8").split("\n");
17127
17600
  for (const line of lines) {
17128
17601
  const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
17129
17602
  if (m) out.set(m[1], m[2].replace(/^["']|["']$/g, ""));
@@ -17178,9 +17651,9 @@ async function runDoctor(args, deps = {}) {
17178
17651
  }
17179
17652
  for (const wf of ["pr.yml", "merge.yml"]) {
17180
17653
  try {
17181
- const p = path31.join(projectDir, ".github", "workflows", wf);
17182
- if (!fs30.existsSync(p)) continue;
17183
- const body = fs30.readFileSync(p, "utf8");
17654
+ const p = path34.join(projectDir, ".github", "workflows", wf);
17655
+ if (!fs32.existsSync(p)) continue;
17656
+ const body = fs32.readFileSync(p, "utf8");
17184
17657
  if (/lakebase-scm-utils#v\d/.test(body)) {
17185
17658
  findings.push({
17186
17659
  id: "ci-workflow-substrate-pin",
@@ -17395,7 +17868,7 @@ async function fixFinding(args) {
17395
17868
  );
17396
17869
  }
17397
17870
  const sanitized = sanitizeBranchName(branch);
17398
- const envFile = path31.join(args.projectDir, ".env");
17871
+ const envFile = path34.join(args.projectDir, ".env");
17399
17872
  updateEnvConnection({
17400
17873
  envPath: envFile,
17401
17874
  projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
@@ -17497,14 +17970,14 @@ function shellEscape3(s) {
17497
17970
 
17498
17971
  // scripts/lakebase/doctor.ts
17499
17972
  init_esm_shims();
17500
- import * as fs31 from "fs";
17501
- import * as path32 from "path";
17973
+ import * as fs33 from "fs";
17974
+ import * as path35 from "path";
17502
17975
  import * as cp6 from "child_process";
17503
17976
  function readEnvFile(projectDir) {
17504
- const envPath = path32.join(projectDir, ".env");
17505
- if (!fs31.existsSync(envPath)) return {};
17977
+ const envPath = path35.join(projectDir, ".env");
17978
+ if (!fs33.existsSync(envPath)) return {};
17506
17979
  const out = {};
17507
- for (const line of fs31.readFileSync(envPath, "utf8").split("\n")) {
17980
+ for (const line of fs33.readFileSync(envPath, "utf8").split("\n")) {
17508
17981
  const m = line.match(/^\s*([A-Z][A-Z0-9_]*)\s*=\s*(.*)\s*$/);
17509
17982
  if (!m) continue;
17510
17983
  let val = m[2];
@@ -17658,6 +18131,23 @@ async function checkPrereq(spec, run) {
17658
18131
  detail: { version: trimmed }
17659
18132
  };
17660
18133
  }
18134
+ var DOTNET_PREREQ = {
18135
+ name: "dotnet-sdk",
18136
+ cmd: "dotnet",
18137
+ versionArgs: ["--version"],
18138
+ minMajor: 10,
18139
+ label: ".NET SDK",
18140
+ hint: "Install the .NET 10 SDK (e.g. `brew install --cask dotnet-sdk` or https://dot.net). EF Core's dotnet-ef is restored per project from .config/dotnet-tools.json."
18141
+ };
18142
+ async function checkLanguagePrerequisites(projectDir, run = defaultVersionRunner) {
18143
+ let lang;
18144
+ try {
18145
+ lang = detectLanguage(projectDir);
18146
+ } catch {
18147
+ return [];
18148
+ }
18149
+ return lang === "dotnet" ? [await checkPrereq(DOTNET_PREREQ, run)] : [];
18150
+ }
17661
18151
  async function checkPrerequisites(run = defaultVersionRunner) {
17662
18152
  return Promise.all(PREREQS.map((spec) => checkPrereq(spec, run)));
17663
18153
  }
@@ -17759,7 +18249,7 @@ function checkEnv(projectDir) {
17759
18249
  name: "env-file",
17760
18250
  status: "warn",
17761
18251
  message: ".env not found",
17762
- detail: { projectDir, envPath: path32.join(projectDir, ".env") },
18252
+ detail: { projectDir, envPath: path35.join(projectDir, ".env") },
17763
18253
  hint: "Run `lakebase-branch sync-env`, or `git checkout <branch>` so the post-checkout hook writes .env (or copy .env.example). The kit stores connection METADATA only , no DB token (the app mints a short-lived credential at runtime)."
17764
18254
  };
17765
18255
  }
@@ -17989,6 +18479,7 @@ async function runDoctor2(args = {}) {
17989
18479
  const hooks = checkHooks(projectDir);
17990
18480
  const workflowDrift = checkWorkflowDrift(projectDir);
17991
18481
  const prereqs = await checkPrerequisites();
18482
+ const languagePrereqs = await checkLanguagePrerequisites(projectDir);
17992
18483
  const lakebaseEnabled = auth7.status === "ok" ? await checkLakebaseEnabled(profile) : {
17993
18484
  name: "lakebase-enabled",
17994
18485
  status: "skip",
@@ -17997,6 +18488,7 @@ async function runDoctor2(args = {}) {
17997
18488
  const checks = [
17998
18489
  cli,
17999
18490
  ...prereqs,
18491
+ ...languagePrereqs,
18000
18492
  auth7,
18001
18493
  identity,
18002
18494
  lakebaseEnabled,
@@ -18252,8 +18744,8 @@ async function listMigrationsOnBranch(args) {
18252
18744
 
18253
18745
  // scripts/git/commits.ts
18254
18746
  init_esm_shims();
18255
- import { existsSync as existsSync30 } from "fs";
18256
- import { join as join34 } from "path";
18747
+ import { existsSync as existsSync32 } from "fs";
18748
+ import { join as join35 } from "path";
18257
18749
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
18258
18750
  ".py",
18259
18751
  ".ts",
@@ -18356,7 +18848,7 @@ async function commitAllIfChanged(args) {
18356
18848
  await exec2("git add -A", { cwd: args.cwd });
18357
18849
  }
18358
18850
  for (const inc of args.include ?? []) {
18359
- if (existsSync30(join34(args.cwd, inc))) {
18851
+ if (existsSync32(join35(args.cwd, inc))) {
18360
18852
  await exec2(`git add -f -- ${shq(inc)}`, { cwd: args.cwd });
18361
18853
  }
18362
18854
  }
@@ -18498,14 +18990,14 @@ async function stashDropAll(args) {
18498
18990
 
18499
18991
  // scripts/git/rebase.ts
18500
18992
  init_esm_shims();
18501
- import * as fs32 from "fs";
18502
- import * as path33 from "path";
18993
+ import * as fs34 from "fs";
18994
+ import * as path36 from "path";
18503
18995
  async function abortRebase(args) {
18504
18996
  await exec2("git rebase --abort", { cwd: args.cwd });
18505
18997
  }
18506
18998
  async function isRebasing(args) {
18507
18999
  try {
18508
- return fs32.existsSync(path33.join(args.cwd, ".git/rebase-merge")) || fs32.existsSync(path33.join(args.cwd, ".git/rebase-apply"));
19000
+ return fs34.existsSync(path36.join(args.cwd, ".git/rebase-merge")) || fs34.existsSync(path36.join(args.cwd, ".git/rebase-apply"));
18509
19001
  } catch {
18510
19002
  return false;
18511
19003
  }
@@ -18725,6 +19217,9 @@ export {
18725
19217
  CI_TOKEN_REMINT_MARGIN_SECONDS,
18726
19218
  CONVENTION_TIER_DEFAULTS,
18727
19219
  DEFAULT_PROTECTED_TIER_NAMES,
19220
+ DOTNET_EFCORE_DEFAULTS,
19221
+ DOTNET_FLYWAY_DIR,
19222
+ EF_DESIGN_PACKAGE,
18728
19223
  FIXABLE_FINDING_IDS,
18729
19224
  GITHUB_SCOPES,
18730
19225
  GitHubPullRequestError,
@@ -18737,6 +19232,7 @@ export {
18737
19232
  LakebaseBranchError,
18738
19233
  LakebaseBranchTtlTooLongError,
18739
19234
  LakebaseProjectError,
19235
+ MAVEN_FLYWAY_DIR,
18740
19236
  MIGRATION_DEFAULTS,
18741
19237
  NODE_E2E_TEMPLATE_FILES,
18742
19238
  PLAYWRIGHT_TEMPLATE_FILES,
@@ -18873,9 +19369,12 @@ export {
18873
19369
  fastForwardBranch,
18874
19370
  featureBranchName,
18875
19371
  fetch2 as fetch,
19372
+ findCsprojFiles,
18876
19373
  findDefaultBranchName,
19374
+ findEfCoreProject,
18877
19375
  findHistoryRetentionDuration,
18878
19376
  fixFinding,
19377
+ flywayMigrationsDir,
18879
19378
  formatJUnit,
18880
19379
  formatOwnerRepo,
18881
19380
  formatSchemaDiffAsMarkdown,
@@ -18925,6 +19424,7 @@ export {
18925
19424
  gitInit,
18926
19425
  grantLakebasePermission,
18927
19426
  grantUcCatalogPermission,
19427
+ hasDotnetMarker,
18928
19428
  hasRemoteBranch,
18929
19429
  hasUpstream,
18930
19430
  inferTierTopology,
@@ -18934,6 +19434,8 @@ export {
18934
19434
  isAllSchemas,
18935
19435
  isCliEntry,
18936
19436
  isDirty,
19437
+ isDotnetFlywayProject,
19438
+ isDotnetProject,
18937
19439
  isForeignFeatureClaim,
18938
19440
  isGitTracked,
18939
19441
  isLongRunningTierBranch,