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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/scripts/index.cjs +2632 -2120
  2. package/dist/scripts/index.d.cts +1 -1
  3. package/dist/scripts/index.d.ts +1 -1
  4. package/dist/scripts/index.js +676 -174
  5. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2460 -2030
  6. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +508 -81
  7. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2429 -2007
  8. package/dist/scripts/lakebase/collapse-heads.cli.js +477 -58
  9. package/dist/scripts/lakebase/detect-language.cli.cjs +2441 -2016
  10. package/dist/scripts/lakebase/detect-language.cli.js +489 -67
  11. package/dist/scripts/lakebase/doctor.cli.cjs +2484 -2040
  12. package/dist/scripts/lakebase/doctor.cli.js +532 -91
  13. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  14. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  15. package/dist/scripts/lakebase/index.cjs +2629 -2117
  16. package/dist/scripts/lakebase/index.d.cts +59 -10
  17. package/dist/scripts/lakebase/index.d.ts +59 -10
  18. package/dist/scripts/lakebase/index.js +670 -168
  19. package/dist/scripts/lakebase/infra-runner.cli.cjs +2435 -2013
  20. package/dist/scripts/lakebase/infra-runner.cli.js +483 -64
  21. package/dist/scripts/lakebase/new-migration.cli.cjs +2429 -2007
  22. package/dist/scripts/lakebase/new-migration.cli.js +477 -58
  23. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  24. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  25. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2461 -2029
  26. package/dist/scripts/lakebase/schema-migrate.cli.js +509 -80
  27. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2460 -2030
  28. package/dist/scripts/lakebase/scm-claim-feature.cli.js +508 -81
  29. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2486 -2056
  30. package/dist/scripts/lakebase/scm-doctor.cli.js +537 -107
  31. package/dist/scripts/lakebase/scm-merge.cli.cjs +2443 -2021
  32. package/dist/scripts/lakebase/scm-merge.cli.js +494 -72
  33. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2464 -2034
  34. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +511 -84
  35. package/dist/scripts/util/index.cjs +2 -2
  36. package/dist/scripts/util/index.js +2 -2
  37. package/package.json +1 -1
  38. package/scripts/lakebase/adapters/efcore-adapter.ts +158 -0
  39. package/scripts/lakebase/adapters/flyway-adapter.ts +8 -5
  40. package/scripts/lakebase/collapse-heads.cli.ts +1 -1
  41. package/scripts/lakebase/create-project.ts +6 -2
  42. package/scripts/lakebase/detect-language.cli.ts +2 -2
  43. package/scripts/lakebase/doctor.ts +29 -0
  44. package/scripts/lakebase/dotnet-project.ts +112 -0
  45. package/scripts/lakebase/index.ts +1 -0
  46. package/scripts/lakebase/migration-layout.ts +43 -2
  47. package/scripts/lakebase/new-migration.cli.ts +1 -1
  48. package/scripts/lakebase/scaffold-language.ts +23 -2
  49. package/scripts/lakebase/scaffold.ts +4 -1
  50. package/scripts/lakebase/schema-migrate-runners/efcore.ts +399 -0
  51. package/scripts/lakebase/schema-migrate-runners/flyway.ts +6 -4
  52. package/scripts/lakebase/schema-migrate.cli.ts +3 -1
  53. package/scripts/lakebase/schema-migrate.ts +24 -10
  54. package/templates/project/common/.github/workflows/merge.yml +13 -1
  55. package/templates/project/common/.github/workflows/pr.yml +25 -5
  56. package/templates/project/common/scripts/flyway-migrate.sh +54 -1
  57. package/templates/project/common/scripts/prepare-schema-diff.sh +5 -0
  58. package/templates/project/common/scripts/run-dev.sh +10 -1
  59. package/templates/project/common/scripts/run-tests.sh +6 -1
  60. package/templates/project/dotnet/.gitignore.extra +8 -0
  61. package/templates/project/dotnet/App.sln +56 -0
  62. package/templates/project/dotnet/Makefile +13 -0
  63. package/templates/project/dotnet/global.json +6 -0
  64. package/templates/project/dotnet/src/App/Data/AppDbContext.cs +12 -0
  65. package/templates/project/dotnet/src/App/Lakebase/DotEnv.cs +48 -0
  66. package/templates/project/dotnet/src/App/Lakebase/LakebaseConnection.cs +225 -0
  67. package/templates/project/dotnet/src/App/Program.cs +34 -0
  68. package/templates/project/dotnet/src/App/appsettings.json +9 -0
  69. package/templates/project/dotnet/tests/App.Tests/App.Tests.csproj +25 -0
  70. package/templates/project/dotnet/tests/App.Tests/HealthTests.cs +18 -0
  71. package/templates/project/dotnet/tests/App.Tests/OpenApiTests.cs +23 -0
  72. package/templates/project/dotnet-efcore/.config/dotnet-tools.json +13 -0
  73. package/templates/project/dotnet-efcore/src/App/App.csproj +22 -0
  74. package/templates/project/dotnet-efcore/src/App/Data/DesignTimeDbContextFactory.cs +24 -0
  75. package/templates/project/dotnet-efcore/src/App/Migrations/README.md +23 -0
  76. package/templates/project/dotnet-flyway/db/migrations/README.md +23 -0
  77. package/templates/project/dotnet-flyway/src/App/App.csproj +17 -0
@@ -1913,9 +1913,9 @@ function asBranchUid(s) {
1913
1913
  }
1914
1914
  return s;
1915
1915
  }
1916
- function branchNameFromResourcePath(path33) {
1917
- if (!path33.includes("/branches/")) return null;
1918
- const leaf = path33.split("/branches/").pop();
1916
+ function branchNameFromResourcePath(path36) {
1917
+ if (!path36.includes("/branches/")) return null;
1918
+ const leaf = path36.split("/branches/").pop();
1919
1919
  if (!leaf) return null;
1920
1920
  try {
1921
1921
  return asBranchName(leaf);
@@ -2521,8 +2521,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
2521
2521
  var cached;
2522
2522
  function substrateSelfVersion() {
2523
2523
  if (cached !== void 0) return cached;
2524
- if ("0.2.51".length > 0) {
2525
- cached = "0.2.51";
2524
+ if ("0.3.0".length > 0) {
2525
+ cached = "0.3.0";
2526
2526
  return cached;
2527
2527
  }
2528
2528
  cached = "unknown";
@@ -5062,7 +5062,7 @@ paginateRest.VERSION = VERSION5;
5062
5062
 
5063
5063
  // node_modules/@octokit/plugin-paginate-graphql/dist-bundle/index.js
5064
5064
  init_esm_shims();
5065
- var generateMessage = (path33, cursorValue) => `The cursor at "${path33.join(
5065
+ var generateMessage = (path36, cursorValue) => `The cursor at "${path36.join(
5066
5066
  ","
5067
5067
  )}" did not change its value "${cursorValue}" after a page transition. Please make sure your that your query is set up correctly.`;
5068
5068
  var MissingCursorChange = class extends Error {
@@ -5103,9 +5103,9 @@ function findPaginatedResourcePath(responseData) {
5103
5103
  }
5104
5104
  return paginatedResourcePath;
5105
5105
  }
5106
- var deepFindPathToProperty = (object, searchProp, path33 = []) => {
5106
+ var deepFindPathToProperty = (object, searchProp, path36 = []) => {
5107
5107
  for (const key of Object.keys(object)) {
5108
- const currentPath = [...path33, key];
5108
+ const currentPath = [...path36, key];
5109
5109
  const currentValue = object[key];
5110
5110
  if (isObject(currentValue)) {
5111
5111
  if (currentValue.hasOwnProperty(searchProp)) {
@@ -5123,12 +5123,12 @@ var deepFindPathToProperty = (object, searchProp, path33 = []) => {
5123
5123
  }
5124
5124
  return [];
5125
5125
  };
5126
- var get = (object, path33) => {
5127
- return path33.reduce((current, nextProperty) => current[nextProperty], object);
5126
+ var get = (object, path36) => {
5127
+ return path36.reduce((current, nextProperty) => current[nextProperty], object);
5128
5128
  };
5129
- var set = (object, path33, mutator) => {
5130
- const lastProperty = path33[path33.length - 1];
5131
- const parentPath = [...path33].slice(0, -1);
5129
+ var set = (object, path36, mutator) => {
5130
+ const lastProperty = path36[path36.length - 1];
5131
+ const parentPath = [...path36].slice(0, -1);
5132
5132
  const parent = get(object, parentPath);
5133
5133
  if (typeof mutator === "function") {
5134
5134
  parent[lastProperty] = mutator(parent[lastProperty]);
@@ -5180,22 +5180,22 @@ var mergeResponses = (response1, response2) => {
5180
5180
  if (Object.keys(response1).length === 0) {
5181
5181
  return Object.assign(response1, response2);
5182
5182
  }
5183
- const path33 = findPaginatedResourcePath(response1);
5184
- const nodesPath = [...path33, "nodes"];
5183
+ const path36 = findPaginatedResourcePath(response1);
5184
+ const nodesPath = [...path36, "nodes"];
5185
5185
  const newNodes = get(response2, nodesPath);
5186
5186
  if (newNodes) {
5187
5187
  set(response1, nodesPath, (values) => {
5188
5188
  return [...values, ...newNodes];
5189
5189
  });
5190
5190
  }
5191
- const edgesPath = [...path33, "edges"];
5191
+ const edgesPath = [...path36, "edges"];
5192
5192
  const newEdges = get(response2, edgesPath);
5193
5193
  if (newEdges) {
5194
5194
  set(response1, edgesPath, (values) => {
5195
5195
  return [...values, ...newEdges];
5196
5196
  });
5197
5197
  }
5198
- const pageInfoPath = [...path33, "pageInfo"];
5198
+ const pageInfoPath = [...path36, "pageInfo"];
5199
5199
  set(response1, pageInfoPath, get(response2, pageInfoPath));
5200
5200
  return response1;
5201
5201
  };
@@ -7616,7 +7616,7 @@ var triggers_notification_paths_default = [
7616
7616
  ];
7617
7617
  function routeMatcher(paths) {
7618
7618
  const regexes = paths.map(
7619
- (path33) => path33.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
7619
+ (path36) => path36.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
7620
7620
  );
7621
7621
  const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
7622
7622
  return new RegExp(regex2, "i");
@@ -10944,16 +10944,16 @@ function isAllSchemas(schema) {
10944
10944
  }
10945
10945
  function buildSchemaQuery(schema) {
10946
10946
  const cols = "c.table_schema, c.table_name, c.column_name, c.data_type";
10947
- const join34 = "FROM information_schema.columns c JOIN pg_tables t ON c.table_name = t.tablename AND c.table_schema = t.schemaname ";
10947
+ const join35 = "FROM information_schema.columns c JOIN pg_tables t ON c.table_name = t.tablename AND c.table_schema = t.schemaname ";
10948
10948
  if (isAllSchemas(schema)) {
10949
10949
  return {
10950
- text: `SELECT ${cols} ` + join34 + `WHERE ${SYSTEM_SCHEMA_FILTER} ORDER BY c.table_schema, c.table_name, c.ordinal_position`,
10950
+ text: `SELECT ${cols} ` + join35 + `WHERE ${SYSTEM_SCHEMA_FILTER} ORDER BY c.table_schema, c.table_name, c.ordinal_position`,
10951
10951
  values: []
10952
10952
  };
10953
10953
  }
10954
10954
  const one = (schema ?? "").trim() || "public";
10955
10955
  return {
10956
- text: `SELECT ${cols} ` + join34 + "WHERE c.table_schema = $1 ORDER BY c.table_name, c.ordinal_position",
10956
+ text: `SELECT ${cols} ` + join35 + "WHERE c.table_schema = $1 ORDER BY c.table_name, c.ordinal_position",
10957
10957
  values: [one]
10958
10958
  };
10959
10959
  }
@@ -11711,6 +11711,20 @@ async function deployLanguageProject(args) {
11711
11711
  return;
11712
11712
  }
11713
11713
  const templatesDir = args.templatesDir ?? findTemplatesDir2();
11714
+ if (args.language === "dotnet") {
11715
+ const tool = args.migrationTool ?? "efcore";
11716
+ if (tool !== "efcore" && tool !== "flyway") {
11717
+ throw new Error(`Unsupported .NET migration tool: ${String(tool)} (expected efcore or flyway)`);
11718
+ }
11719
+ const base = path10.join(templatesDir, "dotnet");
11720
+ const overlay = path10.join(templatesDir, `dotnet-${tool}`);
11721
+ if (!fs11.existsSync(base) || !fs11.existsSync(overlay)) {
11722
+ throw new Error(`No template found for language: dotnet (${tool})`);
11723
+ }
11724
+ copyDirSubstituted(base, args.targetDir, { projectName: args.projectName });
11725
+ copyDirSubstituted(overlay, args.targetDir, { projectName: args.projectName });
11726
+ return;
11727
+ }
11714
11728
  const langSrc = path10.join(templatesDir, args.language);
11715
11729
  if (!fs11.existsSync(langSrc)) {
11716
11730
  throw new Error(`No template found for language: ${args.language}`);
@@ -12130,6 +12144,7 @@ async function scaffoldAll(args) {
12130
12144
  targetDir: args.targetDir,
12131
12145
  language,
12132
12146
  projectName,
12147
+ migrationTool: args.migrationTool,
12133
12148
  templatesDir: args.templatesDir,
12134
12149
  initializrClient: args.initializrClient,
12135
12150
  report
@@ -13550,6 +13565,7 @@ Last probe error:
13550
13565
  databricksHost: host,
13551
13566
  lakebaseProjectId,
13552
13567
  language,
13568
+ migrationTool: input.migrationTool,
13553
13569
  runnerType,
13554
13570
  skipCommands,
13555
13571
  clientFramework,
@@ -13693,7 +13709,8 @@ Last probe error:
13693
13709
  java: "Java/Spring Boot",
13694
13710
  kotlin: "Kotlin/Spring Boot",
13695
13711
  python: "Python/FastAPI",
13696
- nodejs: "Node.js/Express"
13712
+ nodejs: "Node.js/Express",
13713
+ dotnet: `.NET/ASP.NET Core (${input.migrationTool === "flyway" ? "Flyway" : "EF Core"})`
13697
13714
  };
13698
13715
  const langLabel = langLabels[language] ?? language;
13699
13716
  report("Creating initial commit...");
@@ -13874,8 +13891,8 @@ function _testMakeBrownfieldFixture(opts) {
13874
13891
 
13875
13892
  // scripts/lakebase/infra-runner.ts
13876
13893
  init_esm_shims();
13877
- import * as fs27 from "fs";
13878
- import * as path28 from "path";
13894
+ import * as fs29 from "fs";
13895
+ import * as path31 from "path";
13879
13896
 
13880
13897
  // scripts/lakebase/schema-diff.ts
13881
13898
  init_esm_shims();
@@ -14071,18 +14088,84 @@ function dbcli6(args) {
14071
14088
 
14072
14089
  // scripts/lakebase/schema-migrate.ts
14073
14090
  init_esm_shims();
14074
- import * as fs26 from "fs";
14075
- import * as path27 from "path";
14091
+ import * as fs28 from "fs";
14092
+ import * as path30 from "path";
14076
14093
 
14077
14094
  // scripts/lakebase/migration-layout.ts
14078
14095
  init_esm_shims();
14096
+ import * as fs21 from "fs";
14097
+ import * as path21 from "path";
14098
+
14099
+ // scripts/lakebase/dotnet-project.ts
14100
+ init_esm_shims();
14079
14101
  import * as fs20 from "fs";
14080
14102
  import * as path20 from "path";
14103
+ var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
14104
+ var DOTNET_FLYWAY_DIR = path20.join("db", "migrations");
14105
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
14106
+ function safeReaddir(dir) {
14107
+ try {
14108
+ return fs20.readdirSync(dir, { withFileTypes: true });
14109
+ } catch {
14110
+ return [];
14111
+ }
14112
+ }
14113
+ function hasDotnetMarker(dir) {
14114
+ return safeReaddir(dir).some(
14115
+ (e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
14116
+ );
14117
+ }
14118
+ function findCsprojFiles(projectDir) {
14119
+ const found = [];
14120
+ const walk = (dir, depth) => {
14121
+ for (const e of safeReaddir(dir)) {
14122
+ const full = path20.join(dir, e.name);
14123
+ if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
14124
+ found.push(full);
14125
+ } else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
14126
+ walk(full, depth + 1);
14127
+ }
14128
+ }
14129
+ };
14130
+ walk(projectDir, 0);
14131
+ return found.sort();
14132
+ }
14133
+ function findEfCoreProject(projectDir) {
14134
+ for (const csproj of findCsprojFiles(projectDir)) {
14135
+ let content = "";
14136
+ try {
14137
+ content = fs20.readFileSync(csproj, "utf-8");
14138
+ } catch {
14139
+ continue;
14140
+ }
14141
+ if (content.includes(EF_DESIGN_PACKAGE)) {
14142
+ const dir = path20.dirname(csproj);
14143
+ return { csproj, projectDir: dir, migrationsDir: path20.join(dir, "Migrations") };
14144
+ }
14145
+ }
14146
+ return null;
14147
+ }
14148
+ function isDotnetProject(projectDir) {
14149
+ return hasDotnetMarker(projectDir);
14150
+ }
14151
+ function isDotnetFlywayProject(projectDir) {
14152
+ return isDotnetProject(projectDir) && fs20.existsSync(path20.join(projectDir, DOTNET_FLYWAY_DIR));
14153
+ }
14154
+ var MAVEN_FLYWAY_DIR = path20.join("src", "main", "resources", "db", "migration");
14155
+ function flywayMigrationsDir(projectDir) {
14156
+ const maven = path20.join(projectDir, MAVEN_FLYWAY_DIR);
14157
+ if (fs20.existsSync(maven) || fs20.existsSync(path20.join(projectDir, "pom.xml"))) return maven;
14158
+ if (isDotnetFlywayProject(projectDir)) return path20.join(projectDir, DOTNET_FLYWAY_DIR);
14159
+ return maven;
14160
+ }
14161
+
14162
+ // scripts/lakebase/migration-layout.ts
14081
14163
  var MIGRATION_LANGUAGES = [
14082
14164
  "java",
14083
14165
  "kotlin",
14084
14166
  "python",
14085
14167
  "nodejs",
14168
+ "dotnet",
14086
14169
  "unknown"
14087
14170
  ];
14088
14171
  var MIGRATION_DEFAULTS = {
@@ -14090,16 +14173,19 @@ var MIGRATION_DEFAULTS = {
14090
14173
  kotlin: { path: "src/main/resources/db/migration", pattern: /^V\d+.*\.sql$/i, glob: "*.sql" },
14091
14174
  python: { path: "alembic/versions", pattern: /^[0-9a-f][\w]*.*\.py$/i, glob: "*.py" },
14092
14175
  nodejs: { path: "migrations", pattern: /^\d+.*\.(js|ts)$/i, glob: "*.{js,ts}" },
14176
+ // .NET + Flyway; EF Core projects get their Migrations/ dir from
14177
+ // resolveMigrationLayout (see DOTNET_EFCORE_DEFAULTS).
14178
+ dotnet: { path: "db/migrations", pattern: /^V\d+.*\.sql$/i, glob: "*.sql" },
14093
14179
  unknown: { path: "src/main/resources/db/migration", pattern: /^V\d+.*\.sql$/i, glob: "*.sql" }
14094
14180
  };
14095
14181
  function detectLanguageAt(dir) {
14096
- if (fs20.existsSync(path20.join(dir, "pom.xml"))) {
14097
- const kotlinDir = path20.join(dir, "src", "main", "kotlin");
14098
- if (fs20.existsSync(kotlinDir)) {
14182
+ if (fs21.existsSync(path21.join(dir, "pom.xml"))) {
14183
+ const kotlinDir = path21.join(dir, "src", "main", "kotlin");
14184
+ if (fs21.existsSync(kotlinDir)) {
14099
14185
  return "kotlin";
14100
14186
  }
14101
14187
  try {
14102
- const pom = fs20.readFileSync(path20.join(dir, "pom.xml"), "utf-8");
14188
+ const pom = fs21.readFileSync(path21.join(dir, "pom.xml"), "utf-8");
14103
14189
  if (pom.includes("kotlin-maven-plugin")) {
14104
14190
  return "kotlin";
14105
14191
  }
@@ -14107,10 +14193,13 @@ function detectLanguageAt(dir) {
14107
14193
  }
14108
14194
  return "java";
14109
14195
  }
14110
- if (fs20.existsSync(path20.join(dir, "pyproject.toml")) || fs20.existsSync(path20.join(dir, "requirements.txt")) || fs20.existsSync(path20.join(dir, "alembic.ini"))) {
14196
+ if (fs21.existsSync(path21.join(dir, "pyproject.toml")) || fs21.existsSync(path21.join(dir, "requirements.txt")) || fs21.existsSync(path21.join(dir, "alembic.ini"))) {
14111
14197
  return "python";
14112
14198
  }
14113
- if (fs20.existsSync(path20.join(dir, "package.json"))) {
14199
+ if (hasDotnetMarker(dir)) {
14200
+ return "dotnet";
14201
+ }
14202
+ if (fs21.existsSync(path21.join(dir, "package.json"))) {
14114
14203
  return "nodejs";
14115
14204
  }
14116
14205
  return "unknown";
@@ -14131,14 +14220,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
14131
14220
  if (!rel) {
14132
14221
  return "unknown";
14133
14222
  }
14134
- const rootResolved = path20.resolve(projectDir);
14135
- let dir = path20.resolve(projectDir, rel);
14136
- while (dir === rootResolved || dir.startsWith(rootResolved + path20.sep)) {
14223
+ const rootResolved = path21.resolve(projectDir);
14224
+ let dir = path21.resolve(projectDir, rel);
14225
+ while (dir === rootResolved || dir.startsWith(rootResolved + path21.sep)) {
14137
14226
  const lang = detectLanguageAt(dir);
14138
14227
  if (lang !== "unknown") {
14139
14228
  return lang;
14140
14229
  }
14141
- const parent = path20.dirname(dir);
14230
+ const parent = path21.dirname(dir);
14142
14231
  if (parent === dir) {
14143
14232
  break;
14144
14233
  }
@@ -14157,10 +14246,33 @@ function compileMigrationPattern(src) {
14157
14246
  return void 0;
14158
14247
  }
14159
14248
  }
14249
+ var DOTNET_EFCORE_DEFAULTS = {
14250
+ path: "Migrations",
14251
+ pattern: /^\d{14}_(?!.*\.Designer\.cs$).+\.cs$/i,
14252
+ glob: "*.cs"
14253
+ };
14254
+ var DOTNET_LAYOUT_TTL_MS = 5e3;
14255
+ var dotnetLayoutCache = /* @__PURE__ */ new Map();
14256
+ function dotnetDefaults(projectDir) {
14257
+ if (!projectDir) return MIGRATION_DEFAULTS.dotnet;
14258
+ const key = path21.resolve(projectDir);
14259
+ const hit = dotnetLayoutCache.get(key);
14260
+ if (hit && Date.now() - hit.at < DOTNET_LAYOUT_TTL_MS) return hit.value;
14261
+ let value = MIGRATION_DEFAULTS.dotnet;
14262
+ if (!isDotnetFlywayProject(projectDir)) {
14263
+ const ef = findEfCoreProject(projectDir);
14264
+ if (ef) {
14265
+ const rel = path21.relative(projectDir, ef.migrationsDir).split(path21.sep).join("/");
14266
+ value = { ...DOTNET_EFCORE_DEFAULTS, path: rel };
14267
+ }
14268
+ }
14269
+ dotnetLayoutCache.set(key, { at: Date.now(), value });
14270
+ return value;
14271
+ }
14160
14272
  function resolveMigrationLayout(args) {
14161
14273
  const configuredMigrationPath = (args.migrationPath ?? "").trim();
14162
14274
  const language = resolveMigrationLanguage(args.projectDir, configuredMigrationPath, args.language);
14163
- const defaults = MIGRATION_DEFAULTS[language];
14275
+ const defaults = language === "dotnet" ? dotnetDefaults(args.projectDir) : MIGRATION_DEFAULTS[language];
14164
14276
  return {
14165
14277
  language,
14166
14278
  migrationPath: configuredMigrationPath || defaults.path,
@@ -14171,22 +14283,22 @@ function resolveMigrationLayout(args) {
14171
14283
 
14172
14284
  // scripts/lakebase/adapters/alembic-adapter.ts
14173
14285
  init_esm_shims();
14174
- import * as fs22 from "fs";
14175
- import * as path22 from "path";
14286
+ import * as fs23 from "fs";
14287
+ import * as path23 from "path";
14176
14288
 
14177
14289
  // scripts/lakebase/schema-migrate-runners/alembic.ts
14178
14290
  init_esm_shims();
14179
14291
  import { spawn as spawn5 } from "child_process";
14180
- import * as fs21 from "fs";
14181
- import * as path21 from "path";
14292
+ import * as fs22 from "fs";
14293
+ import * as path22 from "path";
14182
14294
  function resolveAlembicBin(projectDir) {
14183
14295
  const candidates = [
14184
- path21.join(projectDir, ".venv", "bin", "alembic"),
14185
- path21.join(projectDir, "venv", "bin", "alembic")
14296
+ path22.join(projectDir, ".venv", "bin", "alembic"),
14297
+ path22.join(projectDir, "venv", "bin", "alembic")
14186
14298
  ];
14187
14299
  for (const candidate of candidates) {
14188
14300
  try {
14189
- if (fs21.existsSync(candidate)) return candidate;
14301
+ if (fs22.existsSync(candidate)) return candidate;
14190
14302
  } catch {
14191
14303
  }
14192
14304
  }
@@ -14196,7 +14308,7 @@ function spawnAlembic(projectDir, args, dsn) {
14196
14308
  return new Promise((resolve2, reject) => {
14197
14309
  const bin = resolveAlembicBin(projectDir);
14198
14310
  const env = { ...process.env };
14199
- env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path21.delimiter);
14311
+ env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path22.delimiter);
14200
14312
  if (dsn) env.DATABASE_URL = dsn;
14201
14313
  const child = spawn5(bin, args, {
14202
14314
  cwd: projectDir,
@@ -14244,10 +14356,10 @@ async function createAlembicRevision(opts) {
14244
14356
  const m = stdout.match(/Generating\s+(\S+\.py)/);
14245
14357
  if (m) return m[1].trim();
14246
14358
  for (const rel of ["migrations/versions", "alembic/versions"]) {
14247
- const dir = path21.join(opts.projectDir, rel);
14248
- if (!fs21.existsSync(dir)) continue;
14249
- const hit = fs21.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
14250
- if (hit) return path21.join(dir, hit);
14359
+ const dir = path22.join(opts.projectDir, rel);
14360
+ if (!fs22.existsSync(dir)) continue;
14361
+ const hit = fs22.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
14362
+ if (hit) return path22.join(dir, hit);
14251
14363
  }
14252
14364
  throw new SchemaMigrationError(
14253
14365
  `alembic revision succeeded but the created file could not be located.
@@ -14382,15 +14494,15 @@ async function buildDsn2(args) {
14382
14494
  }
14383
14495
  function findVersionsDir(projectDir) {
14384
14496
  const candidates = [
14385
- path22.join(projectDir, "migrations", "versions"),
14386
- path22.join(projectDir, "alembic", "versions")
14497
+ path23.join(projectDir, "migrations", "versions"),
14498
+ path23.join(projectDir, "alembic", "versions")
14387
14499
  ];
14388
- return candidates.find((p) => fs22.existsSync(p));
14500
+ return candidates.find((p) => fs23.existsSync(p));
14389
14501
  }
14390
14502
  function listAlembicFiles(projectDir) {
14391
14503
  const dir = findVersionsDir(projectDir);
14392
14504
  if (!dir) return [];
14393
- const files = fs22.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
14505
+ const files = fs23.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
14394
14506
  return files.map((filename) => {
14395
14507
  const stem = filename.replace(/\.py$/, "");
14396
14508
  const sep4 = stem.indexOf("_");
@@ -14415,9 +14527,9 @@ var AlembicAdapter = {
14415
14527
  * here. Callers can still force-select via project.yaml#migration_tool.
14416
14528
  */
14417
14529
  detect(projectDir) {
14418
- if (fs22.existsSync(path22.join(projectDir, "alembic.ini"))) return true;
14419
- if (fs22.existsSync(path22.join(projectDir, "migrations", "env.py"))) return true;
14420
- if (fs22.existsSync(path22.join(projectDir, "alembic", "env.py"))) return true;
14530
+ if (fs23.existsSync(path23.join(projectDir, "alembic.ini"))) return true;
14531
+ if (fs23.existsSync(path23.join(projectDir, "migrations", "env.py"))) return true;
14532
+ if (fs23.existsSync(path23.join(projectDir, "alembic", "env.py"))) return true;
14421
14533
  return false;
14422
14534
  },
14423
14535
  async apply(args) {
@@ -14523,7 +14635,7 @@ var AlembicAdapter = {
14523
14635
  autogenerate: !!args.autogenerate,
14524
14636
  dsn
14525
14637
  });
14526
- return { status: "ok", version: revId, filename: path22.basename(created), path: created };
14638
+ return { status: "ok", version: revId, filename: path23.basename(created), path: created };
14527
14639
  } catch (err) {
14528
14640
  return {
14529
14641
  status: "error",
@@ -14540,7 +14652,7 @@ var AlembicAdapter = {
14540
14652
  if (heads.length <= 1) return { status: "noop", headsBefore: heads };
14541
14653
  if (args.dryRun) return { status: "ok", headsBefore: heads };
14542
14654
  const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
14543
- const mergeRevision = path22.basename(created).replace(/\.py$/, "").split("_")[0];
14655
+ const mergeRevision = path23.basename(created).replace(/\.py$/, "").split("_")[0];
14544
14656
  return { status: "ok", headsBefore: heads, mergeRevision, path: created };
14545
14657
  } catch (err) {
14546
14658
  return {
@@ -14555,13 +14667,13 @@ registerSchemaMigrationAdapter(AlembicAdapter);
14555
14667
 
14556
14668
  // scripts/lakebase/adapters/flyway-adapter.ts
14557
14669
  init_esm_shims();
14558
- import * as fs23 from "fs";
14559
- import * as path24 from "path";
14670
+ import * as fs24 from "fs";
14671
+ import * as path25 from "path";
14560
14672
 
14561
14673
  // scripts/lakebase/schema-migrate-runners/flyway.ts
14562
14674
  init_esm_shims();
14563
14675
  import { spawn as spawn6 } from "child_process";
14564
- import * as path23 from "path";
14676
+ import * as path24 from "path";
14565
14677
  function dsnToFlywayEnv(dsn) {
14566
14678
  const u = new URL(dsn);
14567
14679
  const user = decodeURIComponent(u.username);
@@ -14571,7 +14683,7 @@ function dsnToFlywayEnv(dsn) {
14571
14683
  return { url, user, password };
14572
14684
  }
14573
14685
  function migrationsLocation(projectDir) {
14574
- return `filesystem:${path23.join(projectDir, "src", "main", "resources", "db", "migration")}`;
14686
+ return `filesystem:${flywayMigrationsDir(projectDir)}`;
14575
14687
  }
14576
14688
  function runFlyway(ctx, args) {
14577
14689
  const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
@@ -14672,7 +14784,7 @@ async function statusFlyway(ctx) {
14672
14784
  if (state === "SUCCESS" || state === "BASELINE") {
14673
14785
  current = m.version;
14674
14786
  } else if (state === "PENDING") {
14675
- const filename = m.filepath ? path23.basename(m.filepath) : `V${m.version}__migration.sql`;
14787
+ const filename = m.filepath ? path24.basename(m.filepath) : `V${m.version}__migration.sql`;
14676
14788
  pending.push({
14677
14789
  version: m.version,
14678
14790
  filename,
@@ -14695,9 +14807,9 @@ async function buildDsn3(args) {
14695
14807
  return result.url;
14696
14808
  }
14697
14809
  function listFlywayFiles(projectDir) {
14698
- const dir = path24.join(projectDir, "src", "main", "resources", "db", "migration");
14699
- if (!fs23.existsSync(dir)) return [];
14700
- const files = fs23.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
14810
+ const dir = flywayMigrationsDir(projectDir);
14811
+ if (!fs24.existsSync(dir)) return [];
14812
+ const files = fs24.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
14701
14813
  return files.map((filename) => {
14702
14814
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
14703
14815
  const version = m[1];
@@ -14718,9 +14830,9 @@ function versionCompare(a, b) {
14718
14830
  }
14719
14831
  var FlywayAdapter = {
14720
14832
  id: "flyway",
14721
- languages: ["java", "kotlin"],
14833
+ languages: ["java", "kotlin", "dotnet"],
14722
14834
  detect(projectDir) {
14723
- return fs23.existsSync(path24.join(projectDir, "pom.xml"));
14835
+ return fs24.existsSync(path25.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
14724
14836
  },
14725
14837
  async apply(args) {
14726
14838
  const dsn = await buildDsn3(args);
@@ -14778,14 +14890,14 @@ var FlywayAdapter = {
14778
14890
  // optional-protocol shape makes this additive.
14779
14891
  async newMigration(args) {
14780
14892
  try {
14781
- const dir = path24.join(args.projectDir, "src", "main", "resources", "db", "migration");
14782
- fs23.mkdirSync(dir, { recursive: true });
14893
+ const dir = flywayMigrationsDir(args.projectDir);
14894
+ fs24.mkdirSync(dir, { recursive: true });
14783
14895
  const version = migrationTimestamp();
14784
14896
  const slug = migrationSlug2(args.slug);
14785
14897
  const filename = `V${version}__${slug}.sql`;
14786
- const full = path24.join(dir, filename);
14787
- if (fs23.existsSync(full)) throw new Error(`${filename} already exists`);
14788
- fs23.writeFileSync(
14898
+ const full = path25.join(dir, filename);
14899
+ if (fs24.existsSync(full)) throw new Error(`${filename} already exists`);
14900
+ fs24.writeFileSync(
14789
14901
  full,
14790
14902
  `-- V${version}: ${args.slug}
14791
14903
  -- Flyway migration (write your DDL/DML below).
@@ -14808,19 +14920,19 @@ registerSchemaMigrationAdapter(FlywayAdapter);
14808
14920
 
14809
14921
  // scripts/lakebase/adapters/knex-adapter.ts
14810
14922
  init_esm_shims();
14811
- import * as fs25 from "fs";
14812
- import * as path26 from "path";
14923
+ import * as fs26 from "fs";
14924
+ import * as path27 from "path";
14813
14925
 
14814
14926
  // scripts/lakebase/schema-migrate-runners/knex.ts
14815
14927
  init_esm_shims();
14816
14928
  import { spawn as spawn7 } from "child_process";
14817
- import * as fs24 from "fs";
14818
- import * as path25 from "path";
14929
+ import * as fs25 from "fs";
14930
+ import * as path26 from "path";
14819
14931
  var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
14820
14932
  function findKnexfile(projectDir) {
14821
14933
  for (const name of KNEXFILE_VARIANTS) {
14822
- const p = path25.join(projectDir, name);
14823
- if (fs24.existsSync(p)) return p;
14934
+ const p = path26.join(projectDir, name);
14935
+ if (fs25.existsSync(p)) return p;
14824
14936
  }
14825
14937
  return void 0;
14826
14938
  }
@@ -14972,9 +15084,9 @@ async function buildDsn4(args) {
14972
15084
  }
14973
15085
  var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
14974
15086
  function listKnexFiles(projectDir) {
14975
- const dir = path26.join(projectDir, "migrations");
14976
- if (!fs25.existsSync(dir)) return [];
14977
- const files = fs25.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
15087
+ const dir = path27.join(projectDir, "migrations");
15088
+ if (!fs26.existsSync(dir)) return [];
15089
+ const files = fs26.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
14978
15090
  return files.map((filename) => {
14979
15091
  const stem = filename.replace(/\.(js|ts)$/, "");
14980
15092
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -14994,7 +15106,7 @@ var KnexAdapter = {
14994
15106
  * project.yaml#migration_tool.
14995
15107
  */
14996
15108
  detect(projectDir) {
14997
- return KNEXFILE_VARIANTS2.some((name) => fs25.existsSync(path26.join(projectDir, name)));
15109
+ return KNEXFILE_VARIANTS2.some((name) => fs26.existsSync(path27.join(projectDir, name)));
14998
15110
  },
14999
15111
  async apply(args) {
15000
15112
  const dsn = await buildDsn4(args);
@@ -15067,9 +15179,9 @@ var KnexAdapter = {
15067
15179
  async newMigration(args) {
15068
15180
  try {
15069
15181
  const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
15070
- const stem = path26.basename(created).replace(/\.(js|ts)$/, "");
15182
+ const stem = path27.basename(created).replace(/\.(js|ts)$/, "");
15071
15183
  const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
15072
- return { status: "ok", version, filename: path26.basename(created), path: created };
15184
+ return { status: "ok", version, filename: path27.basename(created), path: created };
15073
15185
  } catch (err) {
15074
15186
  return {
15075
15187
  status: "error",
@@ -15083,6 +15195,362 @@ var KnexAdapter = {
15083
15195
  };
15084
15196
  registerSchemaMigrationAdapter(KnexAdapter);
15085
15197
 
15198
+ // scripts/lakebase/adapters/efcore-adapter.ts
15199
+ init_esm_shims();
15200
+ import * as path29 from "path";
15201
+
15202
+ // scripts/lakebase/schema-migrate-runners/efcore.ts
15203
+ init_esm_shims();
15204
+ import { spawn as spawn8 } from "child_process";
15205
+ import * as fs27 from "fs";
15206
+ import * as path28 from "path";
15207
+ import { Client as Client3 } from "pg";
15208
+ var MIGRATION_ID = /^(\d{14})_(.+)$/;
15209
+ function efVersion(id) {
15210
+ return MIGRATION_ID.exec(id)?.[1] ?? id;
15211
+ }
15212
+ function efDescription(id) {
15213
+ const name = MIGRATION_ID.exec(id)?.[2] ?? id;
15214
+ return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
15215
+ }
15216
+ function dsnToNpgsql(dsn) {
15217
+ const u = new URL(dsn);
15218
+ const parts = [
15219
+ `Host=${u.hostname}`,
15220
+ `Port=${u.port || "5432"}`,
15221
+ `Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
15222
+ ];
15223
+ if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
15224
+ if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
15225
+ const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
15226
+ const ssl = {
15227
+ disable: "Disable",
15228
+ allow: "Allow",
15229
+ prefer: "Prefer",
15230
+ require: "Require",
15231
+ "verify-ca": "VerifyCA",
15232
+ "verify-full": "VerifyFull"
15233
+ };
15234
+ parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
15235
+ const app = u.searchParams.get("application_name");
15236
+ if (app) parts.push(`Application Name=${app}`);
15237
+ return parts.map((p) => p.replace(/;/g, "")).join(";");
15238
+ }
15239
+ function parseEfJson(stdout) {
15240
+ const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
15241
+ if (!body) {
15242
+ throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
15243
+ }
15244
+ try {
15245
+ return JSON.parse(body);
15246
+ } catch (err) {
15247
+ throw new SchemaMigrationError(
15248
+ `dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
15249
+ Body (first 400 chars): ${body.slice(0, 400)}`
15250
+ );
15251
+ }
15252
+ }
15253
+ function requireProject(projectDir) {
15254
+ const project = findEfCoreProject(projectDir);
15255
+ if (!project) {
15256
+ throw new SchemaMigrationError(
15257
+ `No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
15258
+ );
15259
+ }
15260
+ return project;
15261
+ }
15262
+ function spawnDotnet(projectDir, args, env) {
15263
+ return new Promise((resolve2, reject) => {
15264
+ const child = spawn8("dotnet", args, {
15265
+ cwd: projectDir,
15266
+ env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
15267
+ stdio: ["ignore", "pipe", "pipe"]
15268
+ });
15269
+ let stdout = "";
15270
+ let stderr = "";
15271
+ child.stdout.on("data", (chunk) => {
15272
+ stdout += chunk.toString("utf8");
15273
+ });
15274
+ child.stderr.on("data", (chunk) => {
15275
+ stderr += chunk.toString("utf8");
15276
+ });
15277
+ child.on("error", (err) => {
15278
+ reject(
15279
+ new SchemaMigrationError(
15280
+ `Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
15281
+ err
15282
+ )
15283
+ );
15284
+ });
15285
+ child.on("close", (code) => {
15286
+ if (code === 0) {
15287
+ resolve2({ stdout, stderr });
15288
+ } else {
15289
+ reject(
15290
+ new SchemaMigrationError(
15291
+ `dotnet ${args.join(" ")} exited with code ${code}.
15292
+ stdout: ${stdout}
15293
+ stderr: ${stderr}`
15294
+ )
15295
+ );
15296
+ }
15297
+ });
15298
+ });
15299
+ }
15300
+ var restored = /* @__PURE__ */ new Set();
15301
+ async function ensureRestored(projectDir, csproj) {
15302
+ if (restored.has(projectDir)) return;
15303
+ if (fs27.existsSync(path28.join(projectDir, ".config", "dotnet-tools.json"))) {
15304
+ await spawnDotnet(projectDir, ["tool", "restore"], {});
15305
+ }
15306
+ await spawnDotnet(projectDir, ["restore", csproj], {});
15307
+ restored.add(projectDir);
15308
+ }
15309
+ async function runEf(projectDir, args, dsn, opts = {}) {
15310
+ const project = requireProject(projectDir);
15311
+ const csproj = path28.relative(projectDir, project.csproj) || project.csproj;
15312
+ await ensureRestored(projectDir, csproj);
15313
+ const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
15314
+ if (opts.noBuild) full.push("--no-build");
15315
+ const env = {};
15316
+ if (dsn) {
15317
+ env.DATABASE_URL = dsn;
15318
+ if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
15319
+ }
15320
+ return spawnDotnet(projectDir, full, env);
15321
+ }
15322
+ async function listEfMigrations(ctx, opts = {}) {
15323
+ const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
15324
+ const entries = parseEfJson(stdout);
15325
+ if (ctx.dsn && entries.some((e) => e.applied === null)) {
15326
+ const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
15327
+ throw new SchemaMigrationError(
15328
+ `dotnet ef could not read migration state from the database (check connectivity and credentials).
15329
+ ${warning || stderr}`.trim()
15330
+ );
15331
+ }
15332
+ return entries;
15333
+ }
15334
+ async function readEfHistory(dsn) {
15335
+ const client = new Client3({ connectionString: dsn, application_name: connectionApplicationName() });
15336
+ await client.connect();
15337
+ try {
15338
+ const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
15339
+ if (!exists.rows[0]?.present) return null;
15340
+ const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
15341
+ return r.rows.map((row) => row.MigrationId);
15342
+ } finally {
15343
+ await client.end();
15344
+ }
15345
+ }
15346
+ function toApplied(entries) {
15347
+ return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
15348
+ }
15349
+ async function statusEfcore(ctx) {
15350
+ const entries = await listEfMigrations(ctx);
15351
+ const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
15352
+ const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
15353
+ const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
15354
+ return { current, pending, tool: "efcore" };
15355
+ }
15356
+ async function applyEfcore(ctx) {
15357
+ const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
15358
+ await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
15359
+ const after = await listEfMigrations(ctx, { noBuild: true });
15360
+ const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
15361
+ return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
15362
+ }
15363
+ function resolveEfTarget(target, applied) {
15364
+ const t = target.trim();
15365
+ if (t === "0") return "0";
15366
+ const rel = /^-(\d+)$/.exec(t);
15367
+ if (rel) {
15368
+ const steps = Number(rel[1]);
15369
+ if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
15370
+ const index = applied.length - 1 - steps;
15371
+ if (index < -1) {
15372
+ throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
15373
+ }
15374
+ return index === -1 ? "0" : applied[index].id;
15375
+ }
15376
+ const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
15377
+ if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
15378
+ return match.id;
15379
+ }
15380
+ async function rollbackEfcore(ctx) {
15381
+ const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
15382
+ const targetId = resolveEfTarget(ctx.target, beforeEntries);
15383
+ await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
15384
+ const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
15385
+ return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
15386
+ }
15387
+ function efMigrationName(slug) {
15388
+ const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
15389
+ if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
15390
+ return /^[0-9]/.test(name) ? `M${name}` : name;
15391
+ }
15392
+ async function createEfcoreMigration(opts) {
15393
+ const project = requireProject(opts.projectDir);
15394
+ const name = efMigrationName(opts.slug);
15395
+ const existing = new Set(fs27.existsSync(project.migrationsDir) ? fs27.readdirSync(project.migrationsDir) : []);
15396
+ await runEf(opts.projectDir, ["migrations", "add", name]);
15397
+ const created = fs27.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
15398
+ if (created.length !== 1) {
15399
+ throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
15400
+ }
15401
+ return { id: created[0].replace(/\.cs$/, ""), path: path28.join(project.migrationsDir, created[0]) };
15402
+ }
15403
+ function localEfMigrationIds(projectDir) {
15404
+ const project = findEfCoreProject(projectDir);
15405
+ if (!project || !fs27.existsSync(project.migrationsDir)) return [];
15406
+ 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();
15407
+ }
15408
+ function efProductVersion(project) {
15409
+ try {
15410
+ const csproj = fs27.readFileSync(project.csproj, "utf-8");
15411
+ const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
15412
+ if (m) return m[1];
15413
+ } catch {
15414
+ }
15415
+ return "unknown";
15416
+ }
15417
+ async function stampEfcore(ctx) {
15418
+ const project = requireProject(ctx.projectDir);
15419
+ const ids = localEfMigrationIds(ctx.projectDir);
15420
+ if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
15421
+ const rev = ctx.revision.trim();
15422
+ const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
15423
+ if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
15424
+ const toStamp = ids.slice(0, end + 1);
15425
+ const productVersion = efProductVersion(project);
15426
+ const client = new Client3({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
15427
+ await client.connect();
15428
+ try {
15429
+ await client.query("BEGIN");
15430
+ await client.query(
15431
+ `CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
15432
+ "MigrationId" character varying(150) NOT NULL,
15433
+ "ProductVersion" character varying(32) NOT NULL,
15434
+ CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
15435
+ );
15436
+ await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
15437
+ for (const id of toStamp) {
15438
+ await client.query(
15439
+ `INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
15440
+ ON CONFLICT ("MigrationId") DO NOTHING`,
15441
+ [id, productVersion]
15442
+ );
15443
+ }
15444
+ await client.query("COMMIT");
15445
+ } catch (err) {
15446
+ await client.query("ROLLBACK").catch(() => void 0);
15447
+ throw err;
15448
+ } finally {
15449
+ await client.end();
15450
+ }
15451
+ return efVersion(toStamp[toStamp.length - 1]);
15452
+ }
15453
+
15454
+ // scripts/lakebase/adapters/efcore-adapter.ts
15455
+ async function buildDsn5(args) {
15456
+ const result = await getConnection({
15457
+ output: "dsn",
15458
+ instance: args.instance,
15459
+ branch: args.branch,
15460
+ database: args.database,
15461
+ endpointName: args.endpointName
15462
+ });
15463
+ return result.url;
15464
+ }
15465
+ function errMsg(err) {
15466
+ return err instanceof Error ? err.message : String(err);
15467
+ }
15468
+ function listEfCoreFiles(projectDir) {
15469
+ return localEfMigrationIds(projectDir).map((id) => ({
15470
+ version: efVersion(id),
15471
+ filename: `${id}.cs`,
15472
+ description: efDescription(id),
15473
+ type: "CSharp",
15474
+ tool: "efcore"
15475
+ }));
15476
+ }
15477
+ var EfCoreAdapter = {
15478
+ id: "efcore",
15479
+ languages: ["dotnet"],
15480
+ /** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
15481
+ detect(projectDir) {
15482
+ return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
15483
+ },
15484
+ async apply(args) {
15485
+ try {
15486
+ const dsn = await buildDsn5(args);
15487
+ const r = await applyEfcore({ projectDir: args.projectDir, dsn });
15488
+ return {
15489
+ applied_migrations: r.applied,
15490
+ status: r.alreadyAtLatest ? "noop" : "ok",
15491
+ tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
15492
+ };
15493
+ } catch (err) {
15494
+ return { applied_migrations: [], status: "error", error: errMsg(err) };
15495
+ }
15496
+ },
15497
+ async rollback(args) {
15498
+ try {
15499
+ const dsn = await buildDsn5(args);
15500
+ const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
15501
+ return {
15502
+ rolled_back: r.rolledBack,
15503
+ status: r.rolledBack.length === 0 ? "noop" : "ok",
15504
+ tool_specific: { tool: r.tool }
15505
+ };
15506
+ } catch (err) {
15507
+ return { rolled_back: [], status: "error", error: errMsg(err) };
15508
+ }
15509
+ },
15510
+ async stamp(args) {
15511
+ try {
15512
+ const dsn = await buildDsn5(args);
15513
+ const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
15514
+ return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
15515
+ } catch (err) {
15516
+ return { status: "error", stamped_revision: null, error: errMsg(err) };
15517
+ }
15518
+ },
15519
+ async status(args) {
15520
+ try {
15521
+ const dsn = await buildDsn5(args);
15522
+ const r = await statusEfcore({ projectDir: args.projectDir, dsn });
15523
+ return {
15524
+ applied_version: r.current ?? null,
15525
+ pending: r.pending,
15526
+ applied: [],
15527
+ status: "ok",
15528
+ tool_specific: { tool: r.tool }
15529
+ };
15530
+ } catch (err) {
15531
+ return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
15532
+ }
15533
+ },
15534
+ async list(args) {
15535
+ return { files: listEfCoreFiles(args.projectDir) };
15536
+ },
15537
+ // baseline: use stamp (EF has no separate baseline concept).
15538
+ async newMigration(args) {
15539
+ try {
15540
+ const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
15541
+ return {
15542
+ status: "ok",
15543
+ version: efVersion(created.id),
15544
+ filename: path29.basename(created.path),
15545
+ path: created.path
15546
+ };
15547
+ } catch (err) {
15548
+ return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
15549
+ }
15550
+ }
15551
+ };
15552
+ registerSchemaMigrationAdapter(EfCoreAdapter);
15553
+
15086
15554
  // scripts/lakebase/schema-migrate.ts
15087
15555
  var SchemaMigrationError = class extends Error {
15088
15556
  constructor(message, cause) {
@@ -15096,13 +15564,16 @@ function detectLanguage(projectDir) {
15096
15564
  const lang = resolveMigrationLanguage(projectDir);
15097
15565
  if (lang === "unknown") {
15098
15566
  throw new SchemaMigrationError(
15099
- `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.`
15567
+ `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.`
15100
15568
  );
15101
15569
  }
15102
15570
  return lang;
15103
15571
  }
15104
- function toolForLanguage(language) {
15572
+ function toolForLanguage(language, projectDir) {
15105
15573
  switch (language) {
15574
+ case "dotnet":
15575
+ if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
15576
+ return "efcore";
15106
15577
  case "java":
15107
15578
  case "kotlin":
15108
15579
  return "flyway";
@@ -15115,8 +15586,10 @@ function toolForLanguage(language) {
15115
15586
  function listSchemaMigrations(args = {}) {
15116
15587
  const projectDir = args.projectDir ?? process.cwd();
15117
15588
  const language = args.language ?? detectLanguage(projectDir);
15118
- const tool = toolForLanguage(language);
15589
+ const tool = toolForLanguage(language, projectDir);
15119
15590
  switch (tool) {
15591
+ case "efcore":
15592
+ return listEfCoreFiles(projectDir);
15120
15593
  case "flyway":
15121
15594
  return listFlywayMigrations(projectDir);
15122
15595
  case "alembic":
@@ -15126,9 +15599,9 @@ function listSchemaMigrations(args = {}) {
15126
15599
  }
15127
15600
  }
15128
15601
  function listFlywayMigrations(projectDir) {
15129
- const dir = path27.join(projectDir, "src", "main", "resources", "db", "migration");
15130
- if (!fs26.existsSync(dir)) return [];
15131
- const files = fs26.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
15602
+ const dir = flywayMigrationsDir(projectDir);
15603
+ if (!fs28.existsSync(dir)) return [];
15604
+ const files = fs28.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
15132
15605
  return files.map((filename) => {
15133
15606
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
15134
15607
  const version = m[1];
@@ -15138,12 +15611,12 @@ function listFlywayMigrations(projectDir) {
15138
15611
  }
15139
15612
  function listAlembicMigrations(projectDir) {
15140
15613
  const candidates = [
15141
- path27.join(projectDir, "migrations", "versions"),
15142
- path27.join(projectDir, "alembic", "versions")
15614
+ path30.join(projectDir, "migrations", "versions"),
15615
+ path30.join(projectDir, "alembic", "versions")
15143
15616
  ];
15144
- const dir = candidates.find((p) => fs26.existsSync(p));
15617
+ const dir = candidates.find((p) => fs28.existsSync(p));
15145
15618
  if (!dir) return [];
15146
- const files = fs26.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
15619
+ const files = fs28.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
15147
15620
  return files.map((filename) => {
15148
15621
  const stem = filename.replace(/\.py$/, "");
15149
15622
  const sep4 = stem.indexOf("_");
@@ -15153,9 +15626,9 @@ function listAlembicMigrations(projectDir) {
15153
15626
  }).sort((a, b) => a.filename.localeCompare(b.filename));
15154
15627
  }
15155
15628
  function listKnexMigrations(projectDir) {
15156
- const dir = path27.join(projectDir, "migrations");
15157
- if (!fs26.existsSync(dir)) return [];
15158
- const files = fs26.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
15629
+ const dir = path30.join(projectDir, "migrations");
15630
+ if (!fs28.existsSync(dir)) return [];
15631
+ const files = fs28.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
15159
15632
  return files.map((filename) => {
15160
15633
  const stem = filename.replace(/\.(js|ts)$/, "");
15161
15634
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -15177,7 +15650,7 @@ function versionCompare2(a, b) {
15177
15650
  return 0;
15178
15651
  }
15179
15652
  function adapterFor(projectDir, language) {
15180
- const override = language ? toolForLanguage(language) : void 0;
15653
+ const override = language ? toolForLanguage(language, projectDir) : void 0;
15181
15654
  return resolveSchemaMigrationAdapter(projectDir, override);
15182
15655
  }
15183
15656
  var TierMigrationRefusedError = class extends Error {
@@ -15331,8 +15804,8 @@ async function runInfraSuite(args) {
15331
15804
  duration_ms: Date.now() - start
15332
15805
  };
15333
15806
  if (args.junitOutput) {
15334
- fs27.mkdirSync(path28.dirname(args.junitOutput), { recursive: true });
15335
- fs27.writeFileSync(args.junitOutput, formatJUnit(result), "utf8");
15807
+ fs29.mkdirSync(path31.dirname(args.junitOutput), { recursive: true });
15808
+ fs29.writeFileSync(args.junitOutput, formatJUnit(result), "utf8");
15336
15809
  }
15337
15810
  return result;
15338
15811
  }
@@ -15378,8 +15851,8 @@ function escapeXml(s) {
15378
15851
 
15379
15852
  // scripts/lakebase/scm-claim-feature.ts
15380
15853
  init_esm_shims();
15381
- import * as fs28 from "fs";
15382
- import * as path29 from "path";
15854
+ import * as fs30 from "fs";
15855
+ import * as path32 from "path";
15383
15856
  var ScmClaimError = class extends Error {
15384
15857
  constructor(message, code) {
15385
15858
  super(message);
@@ -15436,7 +15909,7 @@ async function claimFeatureBranch(args) {
15436
15909
  const current = readWorkflowState(args.projectDir);
15437
15910
  if (!current) {
15438
15911
  throw new ScmClaimError(
15439
- `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.`,
15912
+ `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.`,
15440
15913
  "no-state-file"
15441
15914
  );
15442
15915
  }
@@ -15540,8 +16013,8 @@ function alreadyClaimedSentinel(state) {
15540
16013
  };
15541
16014
  }
15542
16015
  function workflowStateFileExists(projectDir) {
15543
- return fs28.existsSync(
15544
- path29.join(projectDir, ".lakebase/workflow-state.json")
16016
+ return fs30.existsSync(
16017
+ path32.join(projectDir, ".lakebase/workflow-state.json")
15545
16018
  );
15546
16019
  }
15547
16020
 
@@ -16029,12 +16502,12 @@ async function pushCurrentBranchForPr(args) {
16029
16502
 
16030
16503
  // scripts/lakebase/workflow-drift.ts
16031
16504
  init_esm_shims();
16032
- import * as fs29 from "fs";
16033
- import * as path30 from "path";
16505
+ import * as fs31 from "fs";
16506
+ import * as path33 from "path";
16034
16507
  function findKitTemplatesDir(start) {
16035
16508
  let dir = start;
16036
16509
  for (let i = 0; i < 6; i++) {
16037
- const candidate = path30.join(
16510
+ const candidate = path33.join(
16038
16511
  dir,
16039
16512
  "templates",
16040
16513
  "project",
@@ -16042,8 +16515,8 @@ function findKitTemplatesDir(start) {
16042
16515
  ".github",
16043
16516
  "workflows"
16044
16517
  );
16045
- if (fs29.existsSync(candidate)) return candidate;
16046
- const parent = path30.dirname(dir);
16518
+ if (fs31.existsSync(candidate)) return candidate;
16519
+ const parent = path33.dirname(dir);
16047
16520
  if (parent === dir) break;
16048
16521
  dir = parent;
16049
16522
  }
@@ -16067,13 +16540,13 @@ function unifiedDiff(name, projectContent, templateContent) {
16067
16540
  return out.join("\n");
16068
16541
  }
16069
16542
  function detectWorkflowDrift(args) {
16070
- const projectWorkflowsDir = path30.join(
16543
+ const projectWorkflowsDir = path33.join(
16071
16544
  args.projectDir,
16072
16545
  ".github",
16073
16546
  "workflows"
16074
16547
  );
16075
- const here = path30.dirname(new URL(import.meta.url).pathname);
16076
- const kitWorkflowsDir = args.kitDir ? path30.join(
16548
+ const here = path33.dirname(new URL(import.meta.url).pathname);
16549
+ const kitWorkflowsDir = args.kitDir ? path33.join(
16077
16550
  args.kitDir,
16078
16551
  "templates",
16079
16552
  "project",
@@ -16081,21 +16554,21 @@ function detectWorkflowDrift(args) {
16081
16554
  ".github",
16082
16555
  "workflows"
16083
16556
  ) : findKitTemplatesDir(here);
16084
- const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16085
- const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16557
+ const templateFiles = fs31.existsSync(kitWorkflowsDir) ? fs31.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16558
+ const projectFiles = fs31.existsSync(projectWorkflowsDir) ? fs31.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16086
16559
  const version = readKitVersion(kitWorkflowsDir);
16087
16560
  const seen = /* @__PURE__ */ new Set();
16088
16561
  const files = [];
16089
16562
  for (const name of templateFiles) {
16090
16563
  seen.add(name);
16091
- const projectPath2 = path30.join(projectWorkflowsDir, name);
16092
- const templatePath = path30.join(kitWorkflowsDir, name);
16093
- if (!fs29.existsSync(projectPath2)) {
16564
+ const projectPath2 = path33.join(projectWorkflowsDir, name);
16565
+ const templatePath = path33.join(kitWorkflowsDir, name);
16566
+ if (!fs31.existsSync(projectPath2)) {
16094
16567
  files.push({ name, status: "missing" });
16095
16568
  continue;
16096
16569
  }
16097
- const projectContent = fs29.readFileSync(projectPath2, "utf8");
16098
- const templateContent = applyPlaceholders(fs29.readFileSync(templatePath, "utf8"), version);
16570
+ const projectContent = fs31.readFileSync(projectPath2, "utf8");
16571
+ const templateContent = applyPlaceholders(fs31.readFileSync(templatePath, "utf8"), version);
16099
16572
  if (projectContent === templateContent) {
16100
16573
  files.push({ name, status: "unchanged" });
16101
16574
  } else {
@@ -16126,10 +16599,10 @@ function detectWorkflowDrift(args) {
16126
16599
  function readKitVersion(kitWorkflowsDir) {
16127
16600
  let dir = kitWorkflowsDir;
16128
16601
  for (let i = 0; i < 5; i++) {
16129
- dir = path30.dirname(dir);
16602
+ dir = path33.dirname(dir);
16130
16603
  }
16131
16604
  try {
16132
- const raw = fs29.readFileSync(path30.join(dir, "package.json"), "utf-8");
16605
+ const raw = fs31.readFileSync(path33.join(dir, "package.json"), "utf-8");
16133
16606
  const pkg = JSON.parse(raw);
16134
16607
  return typeof pkg.version === "string" ? pkg.version : "unknown";
16135
16608
  } catch {
@@ -16146,7 +16619,7 @@ var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
16146
16619
  function findKitCommandsDir(start) {
16147
16620
  let dir = start;
16148
16621
  for (let i = 0; i < 6; i++) {
16149
- const candidate = path30.join(
16622
+ const candidate = path33.join(
16150
16623
  dir,
16151
16624
  "templates",
16152
16625
  "project",
@@ -16154,8 +16627,8 @@ function findKitCommandsDir(start) {
16154
16627
  ".claude",
16155
16628
  "commands"
16156
16629
  );
16157
- if (fs29.existsSync(candidate)) return candidate;
16158
- const parent = path30.dirname(dir);
16630
+ if (fs31.existsSync(candidate)) return candidate;
16631
+ const parent = path33.dirname(dir);
16159
16632
  if (parent === dir) break;
16160
16633
  dir = parent;
16161
16634
  }
@@ -16168,24 +16641,24 @@ function parsePinnedVersion(content) {
16168
16641
  return m ? m[1] : void 0;
16169
16642
  }
16170
16643
  function detectCommandDrift(args) {
16171
- const projectCommandsDir = path30.join(args.projectDir, ".claude", "commands");
16172
- const here = path30.dirname(new URL(import.meta.url).pathname);
16173
- const kitCommandsDir = args.kitDir ? path30.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
16644
+ const projectCommandsDir = path33.join(args.projectDir, ".claude", "commands");
16645
+ const here = path33.dirname(new URL(import.meta.url).pathname);
16646
+ const kitCommandsDir = args.kitDir ? path33.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
16174
16647
  const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
16175
- const templateFiles = fs29.existsSync(kitCommandsDir) ? fs29.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16176
- const projectFiles = fs29.existsSync(projectCommandsDir) ? fs29.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16648
+ const templateFiles = fs31.existsSync(kitCommandsDir) ? fs31.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16649
+ const projectFiles = fs31.existsSync(projectCommandsDir) ? fs31.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16177
16650
  const seen = /* @__PURE__ */ new Set();
16178
16651
  const files = [];
16179
16652
  for (const name of templateFiles) {
16180
16653
  seen.add(name);
16181
- const projectPath2 = path30.join(projectCommandsDir, name);
16182
- const templatePath = path30.join(kitCommandsDir, name);
16183
- const templateRaw = fs29.readFileSync(templatePath, "utf8");
16184
- if (!fs29.existsSync(projectPath2)) {
16654
+ const projectPath2 = path33.join(projectCommandsDir, name);
16655
+ const templatePath = path33.join(kitCommandsDir, name);
16656
+ const templateRaw = fs31.readFileSync(templatePath, "utf8");
16657
+ if (!fs31.existsSync(projectPath2)) {
16185
16658
  files.push({ name, status: "missing", kit_version: kitVersion });
16186
16659
  continue;
16187
16660
  }
16188
- const projectContent = fs29.readFileSync(projectPath2, "utf8");
16661
+ const projectContent = fs31.readFileSync(projectPath2, "utf8");
16189
16662
  const pinned = parsePinnedVersion(projectContent);
16190
16663
  const versionForCompare = pinned ?? kitVersion;
16191
16664
  const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
@@ -16223,10 +16696,10 @@ function detectCommandDrift(args) {
16223
16696
  function readKitVersionFromCommandsDir(kitCommandsDir) {
16224
16697
  let dir = kitCommandsDir;
16225
16698
  for (let i = 0; i < 5; i++) {
16226
- dir = path30.dirname(dir);
16699
+ dir = path33.dirname(dir);
16227
16700
  }
16228
16701
  try {
16229
- const raw = fs29.readFileSync(path30.join(dir, "package.json"), "utf-8");
16702
+ const raw = fs31.readFileSync(path33.join(dir, "package.json"), "utf-8");
16230
16703
  const pkg = JSON.parse(raw);
16231
16704
  return typeof pkg.version === "string" ? pkg.version : "unknown";
16232
16705
  } catch {
@@ -16243,13 +16716,13 @@ function detectScaffoldedDrift(args) {
16243
16716
  };
16244
16717
  }
16245
16718
  function updateWorkflows(args) {
16246
- const projectWorkflowsDir = path30.join(
16719
+ const projectWorkflowsDir = path33.join(
16247
16720
  args.projectDir,
16248
16721
  ".github",
16249
16722
  "workflows"
16250
16723
  );
16251
- const here = path30.dirname(new URL(import.meta.url).pathname);
16252
- const kitWorkflowsDir = args.kitDir ? path30.join(
16724
+ const here = path33.dirname(new URL(import.meta.url).pathname);
16725
+ const kitWorkflowsDir = args.kitDir ? path33.join(
16253
16726
  args.kitDir,
16254
16727
  "templates",
16255
16728
  "project",
@@ -16260,22 +16733,22 @@ function updateWorkflows(args) {
16260
16733
  const substitute = args.substitute !== false;
16261
16734
  const dryRun = args.dryRun === true;
16262
16735
  const pruneExtras = args.pruneExtras === true;
16263
- const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16264
- const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16265
- if (!dryRun && templateFiles.length > 0 && !fs29.existsSync(projectWorkflowsDir)) {
16266
- fs29.mkdirSync(projectWorkflowsDir, { recursive: true });
16736
+ const templateFiles = fs31.existsSync(kitWorkflowsDir) ? fs31.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16737
+ const projectFiles = fs31.existsSync(projectWorkflowsDir) ? fs31.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16738
+ if (!dryRun && templateFiles.length > 0 && !fs31.existsSync(projectWorkflowsDir)) {
16739
+ fs31.mkdirSync(projectWorkflowsDir, { recursive: true });
16267
16740
  }
16268
16741
  const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
16269
16742
  const seen = /* @__PURE__ */ new Set();
16270
16743
  const files = [];
16271
16744
  for (const name of templateFiles) {
16272
16745
  seen.add(name);
16273
- const projectPath2 = path30.join(projectWorkflowsDir, name);
16274
- const templatePath = path30.join(kitWorkflowsDir, name);
16275
- const templateRaw = fs29.readFileSync(templatePath, "utf-8");
16746
+ const projectPath2 = path33.join(projectWorkflowsDir, name);
16747
+ const templatePath = path33.join(kitWorkflowsDir, name);
16748
+ const templateRaw = fs31.readFileSync(templatePath, "utf-8");
16276
16749
  const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
16277
- const existed = fs29.existsSync(projectPath2);
16278
- const current = existed ? fs29.readFileSync(projectPath2, "utf-8") : "";
16750
+ const existed = fs31.existsSync(projectPath2);
16751
+ const current = existed ? fs31.readFileSync(projectPath2, "utf-8") : "";
16279
16752
  let outcome;
16280
16753
  if (!existed) {
16281
16754
  outcome = "added";
@@ -16285,16 +16758,16 @@ function updateWorkflows(args) {
16285
16758
  outcome = "updated";
16286
16759
  }
16287
16760
  if (!dryRun && outcome !== "unchanged") {
16288
- fs29.writeFileSync(projectPath2, desired);
16761
+ fs31.writeFileSync(projectPath2, desired);
16289
16762
  }
16290
16763
  files.push({ name, outcome });
16291
16764
  }
16292
16765
  if (pruneExtras) {
16293
16766
  for (const name of projectFiles) {
16294
16767
  if (seen.has(name)) continue;
16295
- const projectPath2 = path30.join(projectWorkflowsDir, name);
16768
+ const projectPath2 = path33.join(projectWorkflowsDir, name);
16296
16769
  if (!dryRun) {
16297
- fs29.unlinkSync(projectPath2);
16770
+ fs31.unlinkSync(projectPath2);
16298
16771
  }
16299
16772
  files.push({ name, outcome: "removed" });
16300
16773
  }
@@ -16892,15 +17365,15 @@ function parentForTopology(t, defaultLeaf) {
16892
17365
 
16893
17366
  // scripts/lakebase/scm-doctor.ts
16894
17367
  init_esm_shims();
16895
- import * as fs30 from "fs";
16896
- import * as path31 from "path";
17368
+ import * as fs32 from "fs";
17369
+ import * as path34 from "path";
16897
17370
  var FEATURE_PREFIX = "feature/";
16898
17371
  var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
16899
17372
  function readEnv(projectDir) {
16900
- const envPath = path31.join(projectDir, ".env");
17373
+ const envPath = path34.join(projectDir, ".env");
16901
17374
  const out = /* @__PURE__ */ new Map();
16902
- if (!fs30.existsSync(envPath)) return out;
16903
- const lines = fs30.readFileSync(envPath, "utf8").split("\n");
17375
+ if (!fs32.existsSync(envPath)) return out;
17376
+ const lines = fs32.readFileSync(envPath, "utf8").split("\n");
16904
17377
  for (const line of lines) {
16905
17378
  const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
16906
17379
  if (m) out.set(m[1], m[2].replace(/^["']|["']$/g, ""));
@@ -16955,9 +17428,9 @@ async function runDoctor(args, deps = {}) {
16955
17428
  }
16956
17429
  for (const wf of ["pr.yml", "merge.yml"]) {
16957
17430
  try {
16958
- const p = path31.join(projectDir, ".github", "workflows", wf);
16959
- if (!fs30.existsSync(p)) continue;
16960
- const body = fs30.readFileSync(p, "utf8");
17431
+ const p = path34.join(projectDir, ".github", "workflows", wf);
17432
+ if (!fs32.existsSync(p)) continue;
17433
+ const body = fs32.readFileSync(p, "utf8");
16961
17434
  if (/lakebase-scm-utils#v\d/.test(body)) {
16962
17435
  findings.push({
16963
17436
  id: "ci-workflow-substrate-pin",
@@ -17172,7 +17645,7 @@ async function fixFinding(args) {
17172
17645
  );
17173
17646
  }
17174
17647
  const sanitized = sanitizeBranchName(branch);
17175
- const envFile = path31.join(args.projectDir, ".env");
17648
+ const envFile = path34.join(args.projectDir, ".env");
17176
17649
  updateEnvConnection({
17177
17650
  envPath: envFile,
17178
17651
  projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
@@ -17274,14 +17747,14 @@ function shellEscape3(s) {
17274
17747
 
17275
17748
  // scripts/lakebase/doctor.ts
17276
17749
  init_esm_shims();
17277
- import * as fs31 from "fs";
17278
- import * as path32 from "path";
17750
+ import * as fs33 from "fs";
17751
+ import * as path35 from "path";
17279
17752
  import * as cp6 from "child_process";
17280
17753
  function readEnvFile(projectDir) {
17281
- const envPath = path32.join(projectDir, ".env");
17282
- if (!fs31.existsSync(envPath)) return {};
17754
+ const envPath = path35.join(projectDir, ".env");
17755
+ if (!fs33.existsSync(envPath)) return {};
17283
17756
  const out = {};
17284
- for (const line of fs31.readFileSync(envPath, "utf8").split("\n")) {
17757
+ for (const line of fs33.readFileSync(envPath, "utf8").split("\n")) {
17285
17758
  const m = line.match(/^\s*([A-Z][A-Z0-9_]*)\s*=\s*(.*)\s*$/);
17286
17759
  if (!m) continue;
17287
17760
  let val = m[2];
@@ -17435,6 +17908,23 @@ async function checkPrereq(spec, run) {
17435
17908
  detail: { version: trimmed }
17436
17909
  };
17437
17910
  }
17911
+ var DOTNET_PREREQ = {
17912
+ name: "dotnet-sdk",
17913
+ cmd: "dotnet",
17914
+ versionArgs: ["--version"],
17915
+ minMajor: 10,
17916
+ label: ".NET SDK",
17917
+ 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."
17918
+ };
17919
+ async function checkLanguagePrerequisites(projectDir, run = defaultVersionRunner) {
17920
+ let lang;
17921
+ try {
17922
+ lang = detectLanguage(projectDir);
17923
+ } catch {
17924
+ return [];
17925
+ }
17926
+ return lang === "dotnet" ? [await checkPrereq(DOTNET_PREREQ, run)] : [];
17927
+ }
17438
17928
  async function checkPrerequisites(run = defaultVersionRunner) {
17439
17929
  return Promise.all(PREREQS.map((spec) => checkPrereq(spec, run)));
17440
17930
  }
@@ -17536,7 +18026,7 @@ function checkEnv(projectDir) {
17536
18026
  name: "env-file",
17537
18027
  status: "warn",
17538
18028
  message: ".env not found",
17539
- detail: { projectDir, envPath: path32.join(projectDir, ".env") },
18029
+ detail: { projectDir, envPath: path35.join(projectDir, ".env") },
17540
18030
  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)."
17541
18031
  };
17542
18032
  }
@@ -17766,6 +18256,7 @@ async function runDoctor2(args = {}) {
17766
18256
  const hooks = checkHooks(projectDir);
17767
18257
  const workflowDrift = checkWorkflowDrift(projectDir);
17768
18258
  const prereqs = await checkPrerequisites();
18259
+ const languagePrereqs = await checkLanguagePrerequisites(projectDir);
17769
18260
  const lakebaseEnabled = auth7.status === "ok" ? await checkLakebaseEnabled(profile) : {
17770
18261
  name: "lakebase-enabled",
17771
18262
  status: "skip",
@@ -17774,6 +18265,7 @@ async function runDoctor2(args = {}) {
17774
18265
  const checks = [
17775
18266
  cli,
17776
18267
  ...prereqs,
18268
+ ...languagePrereqs,
17777
18269
  auth7,
17778
18270
  identity,
17779
18271
  lakebaseEnabled,
@@ -17956,12 +18448,16 @@ function isUcMissingError(msg) {
17956
18448
  export {
17957
18449
  CONVENTION_TIER_DEFAULTS,
17958
18450
  DEFAULT_PROTECTED_TIER_NAMES,
18451
+ DOTNET_EFCORE_DEFAULTS,
18452
+ DOTNET_FLYWAY_DIR,
18453
+ EF_DESIGN_PACKAGE,
17959
18454
  FIXABLE_FINDING_IDS,
17960
18455
  InitializrNetworkError,
17961
18456
  InitializrParseError,
17962
18457
  LakebaseBranchError,
17963
18458
  LakebaseBranchTtlTooLongError,
17964
18459
  LakebaseProjectError,
18460
+ MAVEN_FLYWAY_DIR,
17965
18461
  MIGRATION_DEFAULTS,
17966
18462
  NODE_E2E_TEMPLATE_FILES,
17967
18463
  PLAYWRIGHT_TEMPLATE_FILES,
@@ -18058,9 +18554,12 @@ export {
18058
18554
  ensureSchemaAndVolume,
18059
18555
  extractPullNumber,
18060
18556
  featureBranchName,
18557
+ findCsprojFiles,
18061
18558
  findDefaultBranchName,
18559
+ findEfCoreProject,
18062
18560
  findHistoryRetentionDuration,
18063
18561
  fixFinding,
18562
+ flywayMigrationsDir,
18064
18563
  formatJUnit,
18065
18564
  formatSchemaDiffAsMarkdown,
18066
18565
  generateAppYaml,
@@ -18082,11 +18581,14 @@ export {
18082
18581
  getTargetNames,
18083
18582
  grantLakebasePermission,
18084
18583
  grantUcCatalogPermission,
18584
+ hasDotnetMarker,
18085
18585
  inferTierTopology,
18086
18586
  initWorkflowState,
18087
18587
  installHooks,
18088
18588
  installPlaywright,
18089
18589
  isAllSchemas,
18590
+ isDotnetFlywayProject,
18591
+ isDotnetProject,
18090
18592
  isForeignFeatureClaim,
18091
18593
  isGitTracked,
18092
18594
  isLongRunningTierBranch,