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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +10 -4
  2. package/dist/scripts/index.cjs +2813 -2244
  3. package/dist/scripts/index.d.cts +1 -1
  4. package/dist/scripts/index.d.ts +1 -1
  5. package/dist/scripts/index.js +857 -298
  6. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2460 -2030
  7. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +508 -81
  8. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2429 -2007
  9. package/dist/scripts/lakebase/collapse-heads.cli.js +477 -58
  10. package/dist/scripts/lakebase/detect-language.cli.cjs +2441 -2016
  11. package/dist/scripts/lakebase/detect-language.cli.js +489 -67
  12. package/dist/scripts/lakebase/doctor.cli.cjs +2484 -2040
  13. package/dist/scripts/lakebase/doctor.cli.js +532 -91
  14. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  15. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  16. package/dist/scripts/lakebase/index.cjs +2810 -2205
  17. package/dist/scripts/lakebase/index.d.cts +59 -10
  18. package/dist/scripts/lakebase/index.d.ts +59 -10
  19. package/dist/scripts/lakebase/index.js +851 -256
  20. package/dist/scripts/lakebase/infra-runner.cli.cjs +2435 -2013
  21. package/dist/scripts/lakebase/infra-runner.cli.js +483 -64
  22. package/dist/scripts/lakebase/new-migration.cli.cjs +2429 -2007
  23. package/dist/scripts/lakebase/new-migration.cli.js +477 -58
  24. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  25. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  26. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2461 -2029
  27. package/dist/scripts/lakebase/schema-migrate.cli.js +509 -80
  28. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2460 -2030
  29. package/dist/scripts/lakebase/scm-claim-feature.cli.js +508 -81
  30. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2486 -2056
  31. package/dist/scripts/lakebase/scm-doctor.cli.js +537 -107
  32. package/dist/scripts/lakebase/scm-merge.cli.cjs +2443 -2021
  33. package/dist/scripts/lakebase/scm-merge.cli.js +494 -72
  34. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2464 -2034
  35. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +511 -84
  36. package/dist/scripts/util/index.cjs +2 -2
  37. package/dist/scripts/util/index.js +2 -2
  38. package/package.json +1 -1
  39. package/scripts/lakebase/adapters/efcore-adapter.ts +158 -0
  40. package/scripts/lakebase/adapters/flyway-adapter.ts +8 -5
  41. package/scripts/lakebase/client-lockfile.ts +102 -0
  42. package/scripts/lakebase/collapse-heads.cli.ts +1 -1
  43. package/scripts/lakebase/create-project.ts +25 -2
  44. package/scripts/lakebase/detect-language.cli.ts +2 -2
  45. package/scripts/lakebase/doctor.ts +29 -0
  46. package/scripts/lakebase/dotnet-project.ts +112 -0
  47. package/scripts/lakebase/index.ts +1 -0
  48. package/scripts/lakebase/migration-layout.ts +43 -2
  49. package/scripts/lakebase/new-migration.cli.ts +1 -1
  50. package/scripts/lakebase/scaffold-language.ts +23 -2
  51. package/scripts/lakebase/scaffold.ts +4 -1
  52. package/scripts/lakebase/schema-migrate-runners/efcore.ts +399 -0
  53. package/scripts/lakebase/schema-migrate-runners/flyway.ts +6 -4
  54. package/scripts/lakebase/schema-migrate.cli.ts +3 -1
  55. package/scripts/lakebase/schema-migrate.ts +24 -10
  56. package/templates/project/common/.github/workflows/merge.yml +13 -1
  57. package/templates/project/common/.github/workflows/pr.yml +25 -5
  58. package/templates/project/common/scripts/flyway-migrate.sh +54 -1
  59. package/templates/project/common/scripts/prepare-schema-diff.sh +5 -0
  60. package/templates/project/common/scripts/run-dev.sh +10 -1
  61. package/templates/project/common/scripts/run-tests.sh +6 -1
  62. package/templates/project/dotnet/.gitignore.extra +8 -0
  63. package/templates/project/dotnet/App.sln +56 -0
  64. package/templates/project/dotnet/Makefile +13 -0
  65. package/templates/project/dotnet/global.json +6 -0
  66. package/templates/project/dotnet/src/App/Data/AppDbContext.cs +12 -0
  67. package/templates/project/dotnet/src/App/Lakebase/DotEnv.cs +48 -0
  68. package/templates/project/dotnet/src/App/Lakebase/LakebaseConnection.cs +225 -0
  69. package/templates/project/dotnet/src/App/Program.cs +34 -0
  70. package/templates/project/dotnet/src/App/appsettings.json +9 -0
  71. package/templates/project/dotnet/tests/App.Tests/App.Tests.csproj +25 -0
  72. package/templates/project/dotnet/tests/App.Tests/HealthTests.cs +18 -0
  73. package/templates/project/dotnet/tests/App.Tests/OpenApiTests.cs +23 -0
  74. package/templates/project/dotnet-efcore/.config/dotnet-tools.json +13 -0
  75. package/templates/project/dotnet-efcore/src/App/App.csproj +22 -0
  76. package/templates/project/dotnet-efcore/src/App/Data/DesignTimeDbContextFactory.cs +24 -0
  77. package/templates/project/dotnet-efcore/src/App/Migrations/README.md +23 -0
  78. package/templates/project/dotnet-flyway/db/migrations/README.md +23 -0
  79. package/templates/project/dotnet-flyway/src/App/App.csproj +17 -0
@@ -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(path37) {
1917
+ if (!path37.includes("/branches/")) return null;
1918
+ const leaf = path37.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.1".length > 0) {
2525
+ cached = "0.3.1";
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 = (path37, cursorValue) => `The cursor at "${path37.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, path37 = []) => {
5107
5107
  for (const key of Object.keys(object)) {
5108
- const currentPath = [...path33, key];
5108
+ const currentPath = [...path37, 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, path37) => {
5127
+ return path37.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, path37, mutator) => {
5130
+ const lastProperty = path37[path37.length - 1];
5131
+ const parentPath = [...path37].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 path37 = findPaginatedResourcePath(response1);
5184
+ const nodesPath = [...path37, "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 = [...path37, "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 = [...path37, "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
+ (path37) => path37.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 join36 = "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} ` + join36 + `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} ` + join36 + "WHERE c.table_schema = $1 ORDER BY c.table_name, c.ordinal_position",
10957
10957
  values: [one]
10958
10958
  };
10959
10959
  }
@@ -11113,8 +11113,8 @@ async function withLakebaseRollback(opts, fn) {
11113
11113
 
11114
11114
  // scripts/lakebase/create-project.ts
11115
11115
  init_esm_shims();
11116
- import * as fs18 from "fs";
11117
- import * as path18 from "path";
11116
+ import * as fs19 from "fs";
11117
+ import * as path19 from "path";
11118
11118
 
11119
11119
  // scripts/lakebase/project-verify.ts
11120
11120
  init_esm_shims();
@@ -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
@@ -12462,18 +12477,100 @@ function enableE2eForProject(args) {
12462
12477
  };
12463
12478
  }
12464
12479
 
12465
- // scripts/lakebase/enable-infra.ts
12480
+ // scripts/lakebase/client-lockfile.ts
12466
12481
  init_esm_shims();
12467
12482
  import * as fs15 from "fs";
12468
12483
  import * as path14 from "path";
12484
+
12485
+ // scripts/util/proxy-env.ts
12486
+ init_esm_shims();
12487
+ var PROXY_ENV_KEYS = [
12488
+ "HTTP_PROXY",
12489
+ "HTTPS_PROXY",
12490
+ "NO_PROXY",
12491
+ "http_proxy",
12492
+ "https_proxy",
12493
+ "no_proxy",
12494
+ "npm_config_proxy",
12495
+ "npm_config_https_proxy",
12496
+ "npm_config_no_proxy",
12497
+ "npm_config_registry"
12498
+ ];
12499
+ function proxyEnvSubset(source = process.env) {
12500
+ const out = {};
12501
+ for (const key of PROXY_ENV_KEYS) {
12502
+ const v = source[key];
12503
+ if (v !== void 0 && v !== "") {
12504
+ out[key] = v;
12505
+ }
12506
+ }
12507
+ return out;
12508
+ }
12509
+ function withProxyEnv(base = {}) {
12510
+ const out = {};
12511
+ const inherited = proxyEnvSubset();
12512
+ for (const [k, v] of Object.entries(inherited)) {
12513
+ out[k] = v;
12514
+ }
12515
+ for (const [k, v] of Object.entries(base)) {
12516
+ if (v !== void 0) out[k] = v;
12517
+ }
12518
+ return out;
12519
+ }
12520
+
12521
+ // scripts/lakebase/client-lockfile.ts
12522
+ var PROXY_HOST = "npm-proxy.cloud.databricks.com";
12523
+ var PUBLIC_HOST = "registry.npmjs.org";
12524
+ function normalizeLockfileRegistry(lockText) {
12525
+ let swaps = 0;
12526
+ const text = lockText.replace(new RegExp(PROXY_HOST.replace(/\./g, "\\."), "g"), () => {
12527
+ swaps++;
12528
+ return PUBLIC_HOST;
12529
+ });
12530
+ return { text, swaps };
12531
+ }
12532
+ async function ensureClientLockfile(args) {
12533
+ const clientDir = path14.join(args.projectDir, args.clientDir ?? "client");
12534
+ const pkg = path14.join(clientDir, "package.json");
12535
+ if (!fs15.existsSync(pkg)) {
12536
+ return { generated: false, swaps: 0, reason: "no client package.json" };
12537
+ }
12538
+ const lockPath = path14.join(clientDir, "package-lock.json");
12539
+ try {
12540
+ await exec2("npm install --package-lock-only", {
12541
+ cwd: clientDir,
12542
+ env: withProxyEnv(),
12543
+ timeout: 18e4
12544
+ });
12545
+ } catch (err) {
12546
+ const msg = err instanceof Error ? err.message : String(err);
12547
+ return { generated: fs15.existsSync(lockPath), swaps: 0, reason: `npm resolve failed: ${msg}` };
12548
+ }
12549
+ if (!fs15.existsSync(lockPath)) {
12550
+ return { generated: false, swaps: 0, reason: "npm wrote no package-lock.json" };
12551
+ }
12552
+ try {
12553
+ const { text, swaps } = normalizeLockfileRegistry(fs15.readFileSync(lockPath, "utf8"));
12554
+ if (swaps > 0) fs15.writeFileSync(lockPath, text);
12555
+ return { generated: true, swaps };
12556
+ } catch (err) {
12557
+ const msg = err instanceof Error ? err.message : String(err);
12558
+ return { generated: true, swaps: 0, reason: `lock normalize failed: ${msg}` };
12559
+ }
12560
+ }
12561
+
12562
+ // scripts/lakebase/enable-infra.ts
12563
+ init_esm_shims();
12564
+ import * as fs16 from "fs";
12565
+ import * as path15 from "path";
12469
12566
  var RUN_TESTS_INFRA_MARKER = "# Run Lakebase [Infra]-tag suite when wired";
12470
12567
  function addInfraToPackageJson(args) {
12471
- const pkgPath = path14.join(args.projectDir, "package.json");
12472
- if (!fs15.existsSync(pkgPath)) {
12568
+ const pkgPath = path15.join(args.projectDir, "package.json");
12569
+ if (!fs16.existsSync(pkgPath)) {
12473
12570
  return { patched: false, scriptAdded: false };
12474
12571
  }
12475
12572
  const scriptValue = args.scriptValue ?? "npx --yes lakebase-infra-runner";
12476
- const raw = fs15.readFileSync(pkgPath, "utf8");
12573
+ const raw = fs16.readFileSync(pkgPath, "utf8");
12477
12574
  const pkg = JSON.parse(raw);
12478
12575
  const scripts = pkg.scripts ?? {};
12479
12576
  let scriptAdded = false;
@@ -12484,16 +12581,16 @@ function addInfraToPackageJson(args) {
12484
12581
  pkg.scripts = scripts;
12485
12582
  if (scriptAdded) {
12486
12583
  const trailing = raw.endsWith("\n") ? "\n" : "";
12487
- fs15.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + trailing, "utf8");
12584
+ fs16.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + trailing, "utf8");
12488
12585
  }
12489
12586
  return { patched: true, scriptAdded };
12490
12587
  }
12491
12588
  function addInfraToRunTestsScript(args) {
12492
- const scriptPath = path14.join(args.projectDir, "scripts", "run-tests.sh");
12493
- if (!fs15.existsSync(scriptPath)) {
12589
+ const scriptPath = path15.join(args.projectDir, "scripts", "run-tests.sh");
12590
+ if (!fs16.existsSync(scriptPath)) {
12494
12591
  return { patched: false, inserted: false };
12495
12592
  }
12496
- const original = fs15.readFileSync(scriptPath, "utf8");
12593
+ const original = fs16.readFileSync(scriptPath, "utf8");
12497
12594
  if (original.includes(RUN_TESTS_INFRA_MARKER)) {
12498
12595
  return { patched: true, inserted: false };
12499
12596
  }
@@ -12509,7 +12606,7 @@ function addInfraToRunTestsScript(args) {
12509
12606
  "fi",
12510
12607
  ""
12511
12608
  ].join("\n");
12512
- fs15.writeFileSync(scriptPath, trimmed + block, "utf8");
12609
+ fs16.writeFileSync(scriptPath, trimmed + block, "utf8");
12513
12610
  return { patched: true, inserted: true };
12514
12611
  }
12515
12612
  function enableInfraForProject(args) {
@@ -12523,9 +12620,9 @@ function enableInfraForProject(args) {
12523
12620
 
12524
12621
  // scripts/lakebase/runner-setup.ts
12525
12622
  init_esm_shims();
12526
- import * as fs16 from "fs";
12623
+ import * as fs17 from "fs";
12527
12624
  import * as os from "os";
12528
- import * as path15 from "path";
12625
+ import * as path16 from "path";
12529
12626
  import * as cp4 from "child_process";
12530
12627
  import * as tar from "tar";
12531
12628
  import findJavaHome from "find-java-home";
@@ -12612,28 +12709,28 @@ var RUNNER_OS = process.platform === "darwin" ? "osx" : "linux";
12612
12709
  var RUNNER_ARCHIVE = `actions-runner-${RUNNER_OS}-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz`;
12613
12710
  var RUNNER_URL = `https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/${RUNNER_ARCHIVE}`;
12614
12711
  function cacheDir() {
12615
- return path15.join(os.homedir(), ".cache", "github-actions-runner");
12712
+ return path16.join(os.homedir(), ".cache", "github-actions-runner");
12616
12713
  }
12617
12714
  function runnersDir() {
12618
- return path15.join(os.homedir(), ".lakebase", "runners");
12715
+ return path16.join(os.homedir(), ".lakebase", "runners");
12619
12716
  }
12620
12717
  function runnerDir(projectName) {
12621
- return path15.join(runnersDir(), projectName);
12718
+ return path16.join(runnersDir(), projectName);
12622
12719
  }
12623
12720
  function runnerName(projectName) {
12624
12721
  return `lakebase-${projectName}`;
12625
12722
  }
12626
12723
  async function ensureCachedArchive() {
12627
12724
  const dir = cacheDir();
12628
- fs16.mkdirSync(dir, { recursive: true });
12629
- const cachedPath = path15.join(dir, RUNNER_ARCHIVE);
12630
- if (fs16.existsSync(cachedPath)) return cachedPath;
12725
+ fs17.mkdirSync(dir, { recursive: true });
12726
+ const cachedPath = path16.join(dir, RUNNER_ARCHIVE);
12727
+ if (fs17.existsSync(cachedPath)) return cachedPath;
12631
12728
  const response = await fetch(RUNNER_URL);
12632
12729
  if (!response.ok) {
12633
12730
  throw new Error(`Failed to download runner: HTTP ${response.status}`);
12634
12731
  }
12635
12732
  const buffer = Buffer.from(await response.arrayBuffer());
12636
- fs16.writeFileSync(cachedPath, buffer);
12733
+ fs17.writeFileSync(cachedPath, buffer);
12637
12734
  return cachedPath;
12638
12735
  }
12639
12736
  async function resolveJavaHome() {
@@ -12643,9 +12740,9 @@ async function resolveJavaHome() {
12643
12740
  });
12644
12741
  }
12645
12742
  function isRunning(projectName) {
12646
- const pidFile = path15.join(runnerDir(projectName), ".pid");
12647
- if (!fs16.existsSync(pidFile)) return false;
12648
- const pid = parseInt(fs16.readFileSync(pidFile, "utf-8").trim(), 10);
12743
+ const pidFile = path16.join(runnerDir(projectName), ".pid");
12744
+ if (!fs17.existsSync(pidFile)) return false;
12745
+ const pid = parseInt(fs17.readFileSync(pidFile, "utf-8").trim(), 10);
12649
12746
  if (!pid) return false;
12650
12747
  try {
12651
12748
  process.kill(pid, 0);
@@ -12656,23 +12753,23 @@ function isRunning(projectName) {
12656
12753
  }
12657
12754
  function getRunnerInfo(projectName) {
12658
12755
  const dir = runnerDir(projectName);
12659
- if (!fs16.existsSync(dir)) return void 0;
12660
- const pidFile = path15.join(dir, ".pid");
12756
+ if (!fs17.existsSync(dir)) return void 0;
12757
+ const pidFile = path16.join(dir, ".pid");
12661
12758
  let pid;
12662
- if (fs16.existsSync(pidFile)) {
12663
- pid = parseInt(fs16.readFileSync(pidFile, "utf-8").trim(), 10);
12759
+ if (fs17.existsSync(pidFile)) {
12760
+ pid = parseInt(fs17.readFileSync(pidFile, "utf-8").trim(), 10);
12664
12761
  }
12665
12762
  return { name: runnerName(projectName), dir, pid, online: isRunning(projectName) };
12666
12763
  }
12667
12764
  var lastRunnerPid;
12668
12765
  function stopRunner(projectName) {
12669
12766
  const dir = runnerDir(projectName);
12670
- const pidFile = path15.join(dir, ".pid");
12767
+ const pidFile = path16.join(dir, ".pid");
12671
12768
  let pid = lastRunnerPid;
12672
- if (fs16.existsSync(pidFile)) {
12673
- pid = parseInt(fs16.readFileSync(pidFile, "utf-8").trim(), 10);
12769
+ if (fs17.existsSync(pidFile)) {
12770
+ pid = parseInt(fs17.readFileSync(pidFile, "utf-8").trim(), 10);
12674
12771
  try {
12675
- fs16.unlinkSync(pidFile);
12772
+ fs17.unlinkSync(pidFile);
12676
12773
  } catch {
12677
12774
  }
12678
12775
  }
@@ -12685,7 +12782,7 @@ function stopRunner(projectName) {
12685
12782
  } catch {
12686
12783
  }
12687
12784
  }
12688
- } else if (fs16.existsSync(dir)) {
12785
+ } else if (fs17.existsSync(dir)) {
12689
12786
  try {
12690
12787
  cp4.execSync(`pkill -9 -f "${dir.replace(/\//g, "\\/")}.*Runner" 2>/dev/null || true`, {
12691
12788
  timeout: KIT_TIMEOUTS.cmdShort
@@ -12695,16 +12792,16 @@ function stopRunner(projectName) {
12695
12792
  }
12696
12793
  lastRunnerPid = void 0;
12697
12794
  for (const stale of ["_diag/pages", "_work/_temp", "_work/_actions"]) {
12698
- const full = path15.join(dir, stale);
12699
- if (fs16.existsSync(full)) {
12795
+ const full = path16.join(dir, stale);
12796
+ if (fs17.existsSync(full)) {
12700
12797
  try {
12701
- fs16.rmSync(full, { recursive: true, force: true });
12798
+ fs17.rmSync(full, { recursive: true, force: true });
12702
12799
  } catch {
12703
12800
  }
12704
12801
  }
12705
12802
  }
12706
12803
  try {
12707
- fs16.mkdirSync(path15.join(dir, "_diag", "pages"), { recursive: true });
12804
+ fs17.mkdirSync(path16.join(dir, "_diag", "pages"), { recursive: true });
12708
12805
  } catch {
12709
12806
  }
12710
12807
  }
@@ -12720,24 +12817,24 @@ function resetRunnerConfig(dir, projectName) {
12720
12817
  ];
12721
12818
  for (const f of stateFiles) {
12722
12819
  try {
12723
- fs16.unlinkSync(path15.join(dir, f));
12820
+ fs17.unlinkSync(path16.join(dir, f));
12724
12821
  } catch {
12725
12822
  }
12726
12823
  }
12727
12824
  if (process.platform === "darwin") {
12728
- const plist = path15.join(
12825
+ const plist = path16.join(
12729
12826
  os.homedir(),
12730
12827
  "Library",
12731
12828
  "LaunchAgents",
12732
12829
  `actions.runner.${projectName}.plist`
12733
12830
  );
12734
- if (fs16.existsSync(plist)) {
12831
+ if (fs17.existsSync(plist)) {
12735
12832
  try {
12736
12833
  cp4.execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
12737
12834
  } catch {
12738
12835
  }
12739
12836
  try {
12740
- fs16.unlinkSync(plist);
12837
+ fs17.unlinkSync(plist);
12741
12838
  } catch {
12742
12839
  }
12743
12840
  }
@@ -12751,24 +12848,24 @@ async function setupRunner(args) {
12751
12848
  stopRunner(args.projectName);
12752
12849
  report("Downloading runner binary...");
12753
12850
  const archive = await ensureCachedArchive();
12754
- fs16.mkdirSync(dir, { recursive: true });
12755
- if (!fs16.existsSync(path15.join(dir, "config.sh"))) {
12851
+ fs17.mkdirSync(dir, { recursive: true });
12852
+ if (!fs17.existsSync(path16.join(dir, "config.sh"))) {
12756
12853
  report("Extracting runner...");
12757
12854
  await tar.extract({ file: archive, cwd: dir });
12758
12855
  }
12759
- const diagPages = path15.join(dir, "_diag", "pages");
12760
- if (fs16.existsSync(diagPages)) {
12761
- fs16.rmSync(diagPages, { recursive: true, force: true });
12762
- fs16.mkdirSync(diagPages, { recursive: true });
12856
+ const diagPages = path16.join(dir, "_diag", "pages");
12857
+ if (fs17.existsSync(diagPages)) {
12858
+ fs17.rmSync(diagPages, { recursive: true, force: true });
12859
+ fs17.mkdirSync(diagPages, { recursive: true });
12763
12860
  }
12764
- const runnerFile = path15.join(dir, ".runner");
12765
- let needsConfig = !fs16.existsSync(runnerFile);
12861
+ const runnerFile = path16.join(dir, ".runner");
12862
+ let needsConfig = !fs17.existsSync(runnerFile);
12766
12863
  if (needsConfig) {
12767
12864
  resetRunnerConfig(dir, args.projectName);
12768
12865
  } else {
12769
12866
  let urlMismatch = false;
12770
12867
  try {
12771
- const runnerJson = JSON.parse(fs16.readFileSync(runnerFile, "utf-8"));
12868
+ const runnerJson = JSON.parse(fs17.readFileSync(runnerFile, "utf-8"));
12772
12869
  const configuredUrl = runnerJson.gitHubUrl || runnerJson.serverUrl || runnerJson.agentUrl || "";
12773
12870
  const expectedUrl = `https://github.com/${args.fullRepoName}`;
12774
12871
  urlMismatch = !!configuredUrl && !configuredUrl.startsWith(expectedUrl);
@@ -12829,7 +12926,7 @@ async function setupRunner(args) {
12829
12926
  child.unref();
12830
12927
  lastRunnerPid = child.pid;
12831
12928
  if (child.pid) {
12832
- fs16.writeFileSync(path15.join(dir, ".pid"), String(child.pid));
12929
+ fs17.writeFileSync(path16.join(dir, ".pid"), String(child.pid));
12833
12930
  }
12834
12931
  report("Waiting for runner to come online...");
12835
12932
  let online = false;
@@ -12861,14 +12958,14 @@ async function removeRunner(args) {
12861
12958
  } catch {
12862
12959
  }
12863
12960
  try {
12864
- fs16.rmSync(dir, { recursive: true, force: true });
12961
+ fs17.rmSync(dir, { recursive: true, force: true });
12865
12962
  } catch {
12866
12963
  }
12867
12964
  }
12868
12965
 
12869
12966
  // scripts/util/ci-secrets.ts
12870
12967
  init_esm_shims();
12871
- import * as path16 from "path";
12968
+ import * as path17 from "path";
12872
12969
 
12873
12970
  // scripts/github/secrets.ts
12874
12971
  init_esm_shims();
@@ -13091,7 +13188,7 @@ async function ensureCiSecretsFresh(args) {
13091
13188
  return { action: "ok", reason: "CI token is current." };
13092
13189
  }
13093
13190
  async function ensureCiSecretsFreshFromEnv(projectDir, opts) {
13094
- const envPath = path16.join(projectDir, ".env");
13191
+ const envPath = path17.join(projectDir, ".env");
13095
13192
  const databricksHost = readEnvVar(envPath, "DATABRICKS_HOST");
13096
13193
  const lakebaseProjectId = readEnvVar(envPath, "LAKEBASE_PROJECT_ID");
13097
13194
  if (!databricksHost || !lakebaseProjectId) {
@@ -13118,8 +13215,8 @@ async function ensureCiSecretsFreshFromEnv(projectDir, opts) {
13118
13215
 
13119
13216
  // scripts/lakebase/scm-workflow-state.ts
13120
13217
  init_esm_shims();
13121
- import * as fs17 from "fs";
13122
- import * as path17 from "path";
13218
+ import * as fs18 from "fs";
13219
+ import * as path18 from "path";
13123
13220
  import { execFileSync as execFileSync6 } from "child_process";
13124
13221
  function isGitTracked(projectDir, rel) {
13125
13222
  try {
@@ -13138,11 +13235,11 @@ function ensureWorkflowStateUntracked(projectDir) {
13138
13235
  } catch {
13139
13236
  }
13140
13237
  try {
13141
- const gitignore = path17.join(projectDir, ".gitignore");
13142
- const existing = fs17.existsSync(gitignore) ? fs17.readFileSync(gitignore, "utf8") : "";
13238
+ const gitignore = path18.join(projectDir, ".gitignore");
13239
+ const existing = fs18.existsSync(gitignore) ? fs18.readFileSync(gitignore, "utf8") : "";
13143
13240
  if (!existing.split("\n").some((l) => l.trim() === rel)) {
13144
13241
  const sep4 = existing === "" || existing.endsWith("\n") ? "" : "\n";
13145
- fs17.appendFileSync(
13242
+ fs18.appendFileSync(
13146
13243
  gitignore,
13147
13244
  `${sep4}# Runtime SCM claim state (per working tree): a branch checkout must never restore
13148
13245
  # a stale committed claim over the live one (issue #203 / Finding 28).
@@ -13166,12 +13263,12 @@ var STATE_INDEX = SCM_STATES.reduce(
13166
13263
  );
13167
13264
  var STATE_FILE_REL = ".lakebase/workflow-state.json";
13168
13265
  function stateFilePath(projectDir) {
13169
- return path17.join(projectDir, STATE_FILE_REL);
13266
+ return path18.join(projectDir, STATE_FILE_REL);
13170
13267
  }
13171
13268
  function readWorkflowState(projectDir) {
13172
13269
  const p = stateFilePath(projectDir);
13173
- if (!fs17.existsSync(p)) return null;
13174
- const raw = fs17.readFileSync(p, "utf8");
13270
+ if (!fs18.existsSync(p)) return null;
13271
+ const raw = fs18.readFileSync(p, "utf8");
13175
13272
  let parsed;
13176
13273
  try {
13177
13274
  parsed = JSON.parse(raw);
@@ -13206,14 +13303,14 @@ function writeWorkflowState(projectDir, state) {
13206
13303
  ${summary}`);
13207
13304
  }
13208
13305
  ensureWorkflowStateUntracked(projectDir);
13209
- const dir = path17.join(projectDir, ".lakebase");
13210
- fs17.mkdirSync(dir, { recursive: true });
13306
+ const dir = path18.join(projectDir, ".lakebase");
13307
+ fs18.mkdirSync(dir, { recursive: true });
13211
13308
  const target = stateFilePath(projectDir);
13212
13309
  const tmp = `${target}.tmp`;
13213
13310
  const ordered = orderForOutput(result.value);
13214
- fs17.writeFileSync(tmp, `${JSON.stringify(ordered, null, 2)}
13311
+ fs18.writeFileSync(tmp, `${JSON.stringify(ordered, null, 2)}
13215
13312
  `, "utf8");
13216
- fs17.renameSync(tmp, target);
13313
+ fs18.renameSync(tmp, target);
13217
13314
  }
13218
13315
  function initWorkflowState(args) {
13219
13316
  return {
@@ -13437,7 +13534,7 @@ function orderForOutput(state) {
13437
13534
  async function createProject(input, progress) {
13438
13535
  const report = progress ?? (() => {
13439
13536
  });
13440
- const projectDir = path18.join(input.parentDir, input.projectName);
13537
+ const projectDir = path19.join(input.parentDir, input.projectName);
13441
13538
  const lakebaseProjectId = input.projectName;
13442
13539
  const host = input.databricksHost.replace(/\/+$/, "");
13443
13540
  const useGithub = input.createGithubRepo !== false;
@@ -13462,7 +13559,7 @@ async function createProject(input, progress) {
13462
13559
  useGithub,
13463
13560
  githubOwner: input.githubOwner,
13464
13561
  tiers,
13465
- dirExists: (p) => fs18.existsSync(p),
13562
+ dirExists: (p) => fs19.existsSync(p),
13466
13563
  dirIsEmpty
13467
13564
  });
13468
13565
  if (!v.ok) throw new Error(v.reason);
@@ -13531,7 +13628,7 @@ Last probe error:
13531
13628
  });
13532
13629
  } else {
13533
13630
  report("Creating local project directory...", projectDir);
13534
- fs18.mkdirSync(projectDir, { recursive: true });
13631
+ fs19.mkdirSync(projectDir, { recursive: true });
13535
13632
  await gitInit(projectDir);
13536
13633
  }
13537
13634
  report("Creating Lakebase database (provisioning Postgres)...", lakebaseProjectId);
@@ -13550,6 +13647,7 @@ Last probe error:
13550
13647
  databricksHost: host,
13551
13648
  lakebaseProjectId,
13552
13649
  language,
13650
+ migrationTool: input.migrationTool,
13553
13651
  runnerType,
13554
13652
  skipCommands,
13555
13653
  clientFramework,
@@ -13664,9 +13762,9 @@ Last probe error:
13664
13762
  const scmRef = envRef || (ver !== "unknown" ? `v${ver}` : "");
13665
13763
  if (scmRef) {
13666
13764
  try {
13667
- const dir = path18.join(projectDir, ".lakebase");
13668
- fs18.mkdirSync(dir, { recursive: true });
13669
- fs18.writeFileSync(path18.join(dir, "scm-utils-ref"), `${scmRef}
13765
+ const dir = path19.join(projectDir, ".lakebase");
13766
+ fs19.mkdirSync(dir, { recursive: true });
13767
+ fs19.writeFileSync(path19.join(dir, "scm-utils-ref"), `${scmRef}
13670
13768
  `, "utf8");
13671
13769
  } catch (err) {
13672
13770
  warnings.push(`Substrate ref pin failed (advisory): ${err instanceof Error ? err.message : String(err)}.`);
@@ -13677,9 +13775,9 @@ Last probe error:
13677
13775
  const kitRef = process.env.LAKEBASE_KIT_REF?.trim();
13678
13776
  if (kitRef) {
13679
13777
  try {
13680
- const dir = path18.join(projectDir, ".lakebase");
13681
- fs18.mkdirSync(dir, { recursive: true });
13682
- fs18.writeFileSync(path18.join(dir, "kit-ref"), `${kitRef}
13778
+ const dir = path19.join(projectDir, ".lakebase");
13779
+ fs19.mkdirSync(dir, { recursive: true });
13780
+ fs19.writeFileSync(path19.join(dir, "kit-ref"), `${kitRef}
13683
13781
  `, "utf8");
13684
13782
  } catch (err) {
13685
13783
  warnings.push(`Kit ref pin failed (advisory): ${err instanceof Error ? err.message : String(err)}.`);
@@ -13693,9 +13791,21 @@ Last probe error:
13693
13791
  java: "Java/Spring Boot",
13694
13792
  kotlin: "Kotlin/Spring Boot",
13695
13793
  python: "Python/FastAPI",
13696
- nodejs: "Node.js/Express"
13794
+ nodejs: "Node.js/Express",
13795
+ dotnet: `.NET/ASP.NET Core (${input.migrationTool === "flyway" ? "Flyway" : "EF Core"})`
13697
13796
  };
13698
13797
  const langLabel = langLabels[language] ?? language;
13798
+ if (clientFramework !== "none") {
13799
+ report("Generating client package-lock.json (registry-normalized)...");
13800
+ const lock = await ensureClientLockfile({ projectDir });
13801
+ if (lock.generated) {
13802
+ report(` wrote client/package-lock.json${lock.swaps > 0 ? ` (normalized ${lock.swaps} proxy URL(s) to the public registry)` : ""}`);
13803
+ } else {
13804
+ const m = `Could not generate client/package-lock.json (${lock.reason ?? "unknown"}). Generate it with: (cd client && npm install) \u2014 the lock is committed on first push.`;
13805
+ warnings.push(m);
13806
+ report(`WARNING: ${m}`);
13807
+ }
13808
+ }
13699
13809
  report("Creating initial commit...");
13700
13810
  await commitAndPush({
13701
13811
  projectDir,
@@ -13760,17 +13870,17 @@ Last probe error:
13760
13870
  // scripts/lakebase/adopt-lakebase-project.ts
13761
13871
  init_esm_shims();
13762
13872
  import * as cp5 from "child_process";
13763
- import * as fs19 from "fs";
13764
- import * as path19 from "path";
13873
+ import * as fs20 from "fs";
13874
+ import * as path20 from "path";
13765
13875
  async function adoptLakebaseProject(args) {
13766
13876
  const warnings = [];
13767
13877
  const filesWritten = [];
13768
13878
  const dryRun = args.dryRun === true;
13769
13879
  const preserveExistingEnv = args.preserveExistingEnv !== false;
13770
- if (!fs19.existsSync(args.projectDir)) {
13880
+ if (!fs20.existsSync(args.projectDir)) {
13771
13881
  throw new Error(`adoptLakebaseProject: project directory does not exist: ${args.projectDir}`);
13772
13882
  }
13773
- if (!fs19.existsSync(path19.join(args.projectDir, ".git"))) {
13883
+ if (!fs20.existsSync(path20.join(args.projectDir, ".git"))) {
13774
13884
  throw new Error(
13775
13885
  `adoptLakebaseProject: ${args.projectDir} is not a git repo. Run \`git init\` first, or pass an existing repo path.`
13776
13886
  );
@@ -13836,9 +13946,9 @@ async function adoptLakebaseProject(args) {
13836
13946
  };
13837
13947
  }
13838
13948
  function assertEnvCompatibility(projectDir, expectedProjectId) {
13839
- const envPath = path19.join(projectDir, ".env");
13840
- if (!fs19.existsSync(envPath)) return;
13841
- const content = fs19.readFileSync(envPath, "utf8");
13949
+ const envPath = path20.join(projectDir, ".env");
13950
+ if (!fs20.existsSync(envPath)) return;
13951
+ const content = fs20.readFileSync(envPath, "utf8");
13842
13952
  const match = content.match(/^LAKEBASE_PROJECT_ID\s*=\s*(.+?)\s*$/m);
13843
13953
  if (!match) return;
13844
13954
  const existing = match[1].trim().replace(/^['"]|['"]$/g, "");
@@ -13849,10 +13959,10 @@ function assertEnvCompatibility(projectDir, expectedProjectId) {
13849
13959
  }
13850
13960
  }
13851
13961
  function assertAdoptionPreflight(args) {
13852
- if (!fs19.existsSync(args.projectDir)) {
13962
+ if (!fs20.existsSync(args.projectDir)) {
13853
13963
  throw new Error(`assertAdoptionPreflight: project directory does not exist: ${args.projectDir}`);
13854
13964
  }
13855
- if (!fs19.existsSync(path19.join(args.projectDir, ".git"))) {
13965
+ if (!fs20.existsSync(path20.join(args.projectDir, ".git"))) {
13856
13966
  throw new Error(
13857
13967
  `assertAdoptionPreflight: ${args.projectDir} is not a git repo.`
13858
13968
  );
@@ -13862,11 +13972,11 @@ function assertAdoptionPreflight(args) {
13862
13972
  }
13863
13973
  }
13864
13974
  function _testMakeBrownfieldFixture(opts) {
13865
- fs19.mkdirSync(opts.dir, { recursive: true });
13975
+ fs20.mkdirSync(opts.dir, { recursive: true });
13866
13976
  cp5.execSync("git init --quiet", { cwd: opts.dir, stdio: "pipe" });
13867
13977
  if (opts.packageJson) {
13868
- fs19.writeFileSync(
13869
- path19.join(opts.dir, "package.json"),
13978
+ fs20.writeFileSync(
13979
+ path20.join(opts.dir, "package.json"),
13870
13980
  JSON.stringify(opts.packageJson, null, 2) + "\n"
13871
13981
  );
13872
13982
  }
@@ -13874,8 +13984,8 @@ function _testMakeBrownfieldFixture(opts) {
13874
13984
 
13875
13985
  // scripts/lakebase/infra-runner.ts
13876
13986
  init_esm_shims();
13877
- import * as fs27 from "fs";
13878
- import * as path28 from "path";
13987
+ import * as fs30 from "fs";
13988
+ import * as path32 from "path";
13879
13989
 
13880
13990
  // scripts/lakebase/schema-diff.ts
13881
13991
  init_esm_shims();
@@ -14071,18 +14181,84 @@ function dbcli6(args) {
14071
14181
 
14072
14182
  // scripts/lakebase/schema-migrate.ts
14073
14183
  init_esm_shims();
14074
- import * as fs26 from "fs";
14075
- import * as path27 from "path";
14184
+ import * as fs29 from "fs";
14185
+ import * as path31 from "path";
14076
14186
 
14077
14187
  // scripts/lakebase/migration-layout.ts
14078
14188
  init_esm_shims();
14079
- import * as fs20 from "fs";
14080
- import * as path20 from "path";
14189
+ import * as fs22 from "fs";
14190
+ import * as path22 from "path";
14191
+
14192
+ // scripts/lakebase/dotnet-project.ts
14193
+ init_esm_shims();
14194
+ import * as fs21 from "fs";
14195
+ import * as path21 from "path";
14196
+ var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
14197
+ var DOTNET_FLYWAY_DIR = path21.join("db", "migrations");
14198
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
14199
+ function safeReaddir(dir) {
14200
+ try {
14201
+ return fs21.readdirSync(dir, { withFileTypes: true });
14202
+ } catch {
14203
+ return [];
14204
+ }
14205
+ }
14206
+ function hasDotnetMarker(dir) {
14207
+ return safeReaddir(dir).some(
14208
+ (e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
14209
+ );
14210
+ }
14211
+ function findCsprojFiles(projectDir) {
14212
+ const found = [];
14213
+ const walk = (dir, depth) => {
14214
+ for (const e of safeReaddir(dir)) {
14215
+ const full = path21.join(dir, e.name);
14216
+ if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
14217
+ found.push(full);
14218
+ } else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
14219
+ walk(full, depth + 1);
14220
+ }
14221
+ }
14222
+ };
14223
+ walk(projectDir, 0);
14224
+ return found.sort();
14225
+ }
14226
+ function findEfCoreProject(projectDir) {
14227
+ for (const csproj of findCsprojFiles(projectDir)) {
14228
+ let content = "";
14229
+ try {
14230
+ content = fs21.readFileSync(csproj, "utf-8");
14231
+ } catch {
14232
+ continue;
14233
+ }
14234
+ if (content.includes(EF_DESIGN_PACKAGE)) {
14235
+ const dir = path21.dirname(csproj);
14236
+ return { csproj, projectDir: dir, migrationsDir: path21.join(dir, "Migrations") };
14237
+ }
14238
+ }
14239
+ return null;
14240
+ }
14241
+ function isDotnetProject(projectDir) {
14242
+ return hasDotnetMarker(projectDir);
14243
+ }
14244
+ function isDotnetFlywayProject(projectDir) {
14245
+ return isDotnetProject(projectDir) && fs21.existsSync(path21.join(projectDir, DOTNET_FLYWAY_DIR));
14246
+ }
14247
+ var MAVEN_FLYWAY_DIR = path21.join("src", "main", "resources", "db", "migration");
14248
+ function flywayMigrationsDir(projectDir) {
14249
+ const maven = path21.join(projectDir, MAVEN_FLYWAY_DIR);
14250
+ if (fs21.existsSync(maven) || fs21.existsSync(path21.join(projectDir, "pom.xml"))) return maven;
14251
+ if (isDotnetFlywayProject(projectDir)) return path21.join(projectDir, DOTNET_FLYWAY_DIR);
14252
+ return maven;
14253
+ }
14254
+
14255
+ // scripts/lakebase/migration-layout.ts
14081
14256
  var MIGRATION_LANGUAGES = [
14082
14257
  "java",
14083
14258
  "kotlin",
14084
14259
  "python",
14085
14260
  "nodejs",
14261
+ "dotnet",
14086
14262
  "unknown"
14087
14263
  ];
14088
14264
  var MIGRATION_DEFAULTS = {
@@ -14090,16 +14266,19 @@ var MIGRATION_DEFAULTS = {
14090
14266
  kotlin: { path: "src/main/resources/db/migration", pattern: /^V\d+.*\.sql$/i, glob: "*.sql" },
14091
14267
  python: { path: "alembic/versions", pattern: /^[0-9a-f][\w]*.*\.py$/i, glob: "*.py" },
14092
14268
  nodejs: { path: "migrations", pattern: /^\d+.*\.(js|ts)$/i, glob: "*.{js,ts}" },
14269
+ // .NET + Flyway; EF Core projects get their Migrations/ dir from
14270
+ // resolveMigrationLayout (see DOTNET_EFCORE_DEFAULTS).
14271
+ dotnet: { path: "db/migrations", pattern: /^V\d+.*\.sql$/i, glob: "*.sql" },
14093
14272
  unknown: { path: "src/main/resources/db/migration", pattern: /^V\d+.*\.sql$/i, glob: "*.sql" }
14094
14273
  };
14095
14274
  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)) {
14275
+ if (fs22.existsSync(path22.join(dir, "pom.xml"))) {
14276
+ const kotlinDir = path22.join(dir, "src", "main", "kotlin");
14277
+ if (fs22.existsSync(kotlinDir)) {
14099
14278
  return "kotlin";
14100
14279
  }
14101
14280
  try {
14102
- const pom = fs20.readFileSync(path20.join(dir, "pom.xml"), "utf-8");
14281
+ const pom = fs22.readFileSync(path22.join(dir, "pom.xml"), "utf-8");
14103
14282
  if (pom.includes("kotlin-maven-plugin")) {
14104
14283
  return "kotlin";
14105
14284
  }
@@ -14107,10 +14286,13 @@ function detectLanguageAt(dir) {
14107
14286
  }
14108
14287
  return "java";
14109
14288
  }
14110
- if (fs20.existsSync(path20.join(dir, "pyproject.toml")) || fs20.existsSync(path20.join(dir, "requirements.txt")) || fs20.existsSync(path20.join(dir, "alembic.ini"))) {
14289
+ if (fs22.existsSync(path22.join(dir, "pyproject.toml")) || fs22.existsSync(path22.join(dir, "requirements.txt")) || fs22.existsSync(path22.join(dir, "alembic.ini"))) {
14111
14290
  return "python";
14112
14291
  }
14113
- if (fs20.existsSync(path20.join(dir, "package.json"))) {
14292
+ if (hasDotnetMarker(dir)) {
14293
+ return "dotnet";
14294
+ }
14295
+ if (fs22.existsSync(path22.join(dir, "package.json"))) {
14114
14296
  return "nodejs";
14115
14297
  }
14116
14298
  return "unknown";
@@ -14131,14 +14313,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
14131
14313
  if (!rel) {
14132
14314
  return "unknown";
14133
14315
  }
14134
- const rootResolved = path20.resolve(projectDir);
14135
- let dir = path20.resolve(projectDir, rel);
14136
- while (dir === rootResolved || dir.startsWith(rootResolved + path20.sep)) {
14316
+ const rootResolved = path22.resolve(projectDir);
14317
+ let dir = path22.resolve(projectDir, rel);
14318
+ while (dir === rootResolved || dir.startsWith(rootResolved + path22.sep)) {
14137
14319
  const lang = detectLanguageAt(dir);
14138
14320
  if (lang !== "unknown") {
14139
14321
  return lang;
14140
14322
  }
14141
- const parent = path20.dirname(dir);
14323
+ const parent = path22.dirname(dir);
14142
14324
  if (parent === dir) {
14143
14325
  break;
14144
14326
  }
@@ -14157,10 +14339,33 @@ function compileMigrationPattern(src) {
14157
14339
  return void 0;
14158
14340
  }
14159
14341
  }
14342
+ var DOTNET_EFCORE_DEFAULTS = {
14343
+ path: "Migrations",
14344
+ pattern: /^\d{14}_(?!.*\.Designer\.cs$).+\.cs$/i,
14345
+ glob: "*.cs"
14346
+ };
14347
+ var DOTNET_LAYOUT_TTL_MS = 5e3;
14348
+ var dotnetLayoutCache = /* @__PURE__ */ new Map();
14349
+ function dotnetDefaults(projectDir) {
14350
+ if (!projectDir) return MIGRATION_DEFAULTS.dotnet;
14351
+ const key = path22.resolve(projectDir);
14352
+ const hit = dotnetLayoutCache.get(key);
14353
+ if (hit && Date.now() - hit.at < DOTNET_LAYOUT_TTL_MS) return hit.value;
14354
+ let value = MIGRATION_DEFAULTS.dotnet;
14355
+ if (!isDotnetFlywayProject(projectDir)) {
14356
+ const ef = findEfCoreProject(projectDir);
14357
+ if (ef) {
14358
+ const rel = path22.relative(projectDir, ef.migrationsDir).split(path22.sep).join("/");
14359
+ value = { ...DOTNET_EFCORE_DEFAULTS, path: rel };
14360
+ }
14361
+ }
14362
+ dotnetLayoutCache.set(key, { at: Date.now(), value });
14363
+ return value;
14364
+ }
14160
14365
  function resolveMigrationLayout(args) {
14161
14366
  const configuredMigrationPath = (args.migrationPath ?? "").trim();
14162
14367
  const language = resolveMigrationLanguage(args.projectDir, configuredMigrationPath, args.language);
14163
- const defaults = MIGRATION_DEFAULTS[language];
14368
+ const defaults = language === "dotnet" ? dotnetDefaults(args.projectDir) : MIGRATION_DEFAULTS[language];
14164
14369
  return {
14165
14370
  language,
14166
14371
  migrationPath: configuredMigrationPath || defaults.path,
@@ -14171,22 +14376,22 @@ function resolveMigrationLayout(args) {
14171
14376
 
14172
14377
  // scripts/lakebase/adapters/alembic-adapter.ts
14173
14378
  init_esm_shims();
14174
- import * as fs22 from "fs";
14175
- import * as path22 from "path";
14379
+ import * as fs24 from "fs";
14380
+ import * as path24 from "path";
14176
14381
 
14177
14382
  // scripts/lakebase/schema-migrate-runners/alembic.ts
14178
14383
  init_esm_shims();
14179
14384
  import { spawn as spawn5 } from "child_process";
14180
- import * as fs21 from "fs";
14181
- import * as path21 from "path";
14385
+ import * as fs23 from "fs";
14386
+ import * as path23 from "path";
14182
14387
  function resolveAlembicBin(projectDir) {
14183
14388
  const candidates = [
14184
- path21.join(projectDir, ".venv", "bin", "alembic"),
14185
- path21.join(projectDir, "venv", "bin", "alembic")
14389
+ path23.join(projectDir, ".venv", "bin", "alembic"),
14390
+ path23.join(projectDir, "venv", "bin", "alembic")
14186
14391
  ];
14187
14392
  for (const candidate of candidates) {
14188
14393
  try {
14189
- if (fs21.existsSync(candidate)) return candidate;
14394
+ if (fs23.existsSync(candidate)) return candidate;
14190
14395
  } catch {
14191
14396
  }
14192
14397
  }
@@ -14196,7 +14401,7 @@ function spawnAlembic(projectDir, args, dsn) {
14196
14401
  return new Promise((resolve2, reject) => {
14197
14402
  const bin = resolveAlembicBin(projectDir);
14198
14403
  const env = { ...process.env };
14199
- env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path21.delimiter);
14404
+ env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path23.delimiter);
14200
14405
  if (dsn) env.DATABASE_URL = dsn;
14201
14406
  const child = spawn5(bin, args, {
14202
14407
  cwd: projectDir,
@@ -14244,10 +14449,10 @@ async function createAlembicRevision(opts) {
14244
14449
  const m = stdout.match(/Generating\s+(\S+\.py)/);
14245
14450
  if (m) return m[1].trim();
14246
14451
  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);
14452
+ const dir = path23.join(opts.projectDir, rel);
14453
+ if (!fs23.existsSync(dir)) continue;
14454
+ const hit = fs23.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
14455
+ if (hit) return path23.join(dir, hit);
14251
14456
  }
14252
14457
  throw new SchemaMigrationError(
14253
14458
  `alembic revision succeeded but the created file could not be located.
@@ -14382,15 +14587,15 @@ async function buildDsn2(args) {
14382
14587
  }
14383
14588
  function findVersionsDir(projectDir) {
14384
14589
  const candidates = [
14385
- path22.join(projectDir, "migrations", "versions"),
14386
- path22.join(projectDir, "alembic", "versions")
14590
+ path24.join(projectDir, "migrations", "versions"),
14591
+ path24.join(projectDir, "alembic", "versions")
14387
14592
  ];
14388
- return candidates.find((p) => fs22.existsSync(p));
14593
+ return candidates.find((p) => fs24.existsSync(p));
14389
14594
  }
14390
14595
  function listAlembicFiles(projectDir) {
14391
14596
  const dir = findVersionsDir(projectDir);
14392
14597
  if (!dir) return [];
14393
- const files = fs22.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
14598
+ const files = fs24.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
14394
14599
  return files.map((filename) => {
14395
14600
  const stem = filename.replace(/\.py$/, "");
14396
14601
  const sep4 = stem.indexOf("_");
@@ -14415,9 +14620,9 @@ var AlembicAdapter = {
14415
14620
  * here. Callers can still force-select via project.yaml#migration_tool.
14416
14621
  */
14417
14622
  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;
14623
+ if (fs24.existsSync(path24.join(projectDir, "alembic.ini"))) return true;
14624
+ if (fs24.existsSync(path24.join(projectDir, "migrations", "env.py"))) return true;
14625
+ if (fs24.existsSync(path24.join(projectDir, "alembic", "env.py"))) return true;
14421
14626
  return false;
14422
14627
  },
14423
14628
  async apply(args) {
@@ -14523,7 +14728,7 @@ var AlembicAdapter = {
14523
14728
  autogenerate: !!args.autogenerate,
14524
14729
  dsn
14525
14730
  });
14526
- return { status: "ok", version: revId, filename: path22.basename(created), path: created };
14731
+ return { status: "ok", version: revId, filename: path24.basename(created), path: created };
14527
14732
  } catch (err) {
14528
14733
  return {
14529
14734
  status: "error",
@@ -14540,7 +14745,7 @@ var AlembicAdapter = {
14540
14745
  if (heads.length <= 1) return { status: "noop", headsBefore: heads };
14541
14746
  if (args.dryRun) return { status: "ok", headsBefore: heads };
14542
14747
  const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
14543
- const mergeRevision = path22.basename(created).replace(/\.py$/, "").split("_")[0];
14748
+ const mergeRevision = path24.basename(created).replace(/\.py$/, "").split("_")[0];
14544
14749
  return { status: "ok", headsBefore: heads, mergeRevision, path: created };
14545
14750
  } catch (err) {
14546
14751
  return {
@@ -14555,13 +14760,13 @@ registerSchemaMigrationAdapter(AlembicAdapter);
14555
14760
 
14556
14761
  // scripts/lakebase/adapters/flyway-adapter.ts
14557
14762
  init_esm_shims();
14558
- import * as fs23 from "fs";
14559
- import * as path24 from "path";
14763
+ import * as fs25 from "fs";
14764
+ import * as path26 from "path";
14560
14765
 
14561
14766
  // scripts/lakebase/schema-migrate-runners/flyway.ts
14562
14767
  init_esm_shims();
14563
14768
  import { spawn as spawn6 } from "child_process";
14564
- import * as path23 from "path";
14769
+ import * as path25 from "path";
14565
14770
  function dsnToFlywayEnv(dsn) {
14566
14771
  const u = new URL(dsn);
14567
14772
  const user = decodeURIComponent(u.username);
@@ -14571,7 +14776,7 @@ function dsnToFlywayEnv(dsn) {
14571
14776
  return { url, user, password };
14572
14777
  }
14573
14778
  function migrationsLocation(projectDir) {
14574
- return `filesystem:${path23.join(projectDir, "src", "main", "resources", "db", "migration")}`;
14779
+ return `filesystem:${flywayMigrationsDir(projectDir)}`;
14575
14780
  }
14576
14781
  function runFlyway(ctx, args) {
14577
14782
  const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
@@ -14672,7 +14877,7 @@ async function statusFlyway(ctx) {
14672
14877
  if (state === "SUCCESS" || state === "BASELINE") {
14673
14878
  current = m.version;
14674
14879
  } else if (state === "PENDING") {
14675
- const filename = m.filepath ? path23.basename(m.filepath) : `V${m.version}__migration.sql`;
14880
+ const filename = m.filepath ? path25.basename(m.filepath) : `V${m.version}__migration.sql`;
14676
14881
  pending.push({
14677
14882
  version: m.version,
14678
14883
  filename,
@@ -14695,9 +14900,9 @@ async function buildDsn3(args) {
14695
14900
  return result.url;
14696
14901
  }
14697
14902
  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));
14903
+ const dir = flywayMigrationsDir(projectDir);
14904
+ if (!fs25.existsSync(dir)) return [];
14905
+ const files = fs25.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
14701
14906
  return files.map((filename) => {
14702
14907
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
14703
14908
  const version = m[1];
@@ -14718,9 +14923,9 @@ function versionCompare(a, b) {
14718
14923
  }
14719
14924
  var FlywayAdapter = {
14720
14925
  id: "flyway",
14721
- languages: ["java", "kotlin"],
14926
+ languages: ["java", "kotlin", "dotnet"],
14722
14927
  detect(projectDir) {
14723
- return fs23.existsSync(path24.join(projectDir, "pom.xml"));
14928
+ return fs25.existsSync(path26.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
14724
14929
  },
14725
14930
  async apply(args) {
14726
14931
  const dsn = await buildDsn3(args);
@@ -14778,14 +14983,14 @@ var FlywayAdapter = {
14778
14983
  // optional-protocol shape makes this additive.
14779
14984
  async newMigration(args) {
14780
14985
  try {
14781
- const dir = path24.join(args.projectDir, "src", "main", "resources", "db", "migration");
14782
- fs23.mkdirSync(dir, { recursive: true });
14986
+ const dir = flywayMigrationsDir(args.projectDir);
14987
+ fs25.mkdirSync(dir, { recursive: true });
14783
14988
  const version = migrationTimestamp();
14784
14989
  const slug = migrationSlug2(args.slug);
14785
14990
  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(
14991
+ const full = path26.join(dir, filename);
14992
+ if (fs25.existsSync(full)) throw new Error(`${filename} already exists`);
14993
+ fs25.writeFileSync(
14789
14994
  full,
14790
14995
  `-- V${version}: ${args.slug}
14791
14996
  -- Flyway migration (write your DDL/DML below).
@@ -14808,19 +15013,19 @@ registerSchemaMigrationAdapter(FlywayAdapter);
14808
15013
 
14809
15014
  // scripts/lakebase/adapters/knex-adapter.ts
14810
15015
  init_esm_shims();
14811
- import * as fs25 from "fs";
14812
- import * as path26 from "path";
15016
+ import * as fs27 from "fs";
15017
+ import * as path28 from "path";
14813
15018
 
14814
15019
  // scripts/lakebase/schema-migrate-runners/knex.ts
14815
15020
  init_esm_shims();
14816
15021
  import { spawn as spawn7 } from "child_process";
14817
- import * as fs24 from "fs";
14818
- import * as path25 from "path";
15022
+ import * as fs26 from "fs";
15023
+ import * as path27 from "path";
14819
15024
  var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
14820
15025
  function findKnexfile(projectDir) {
14821
15026
  for (const name of KNEXFILE_VARIANTS) {
14822
- const p = path25.join(projectDir, name);
14823
- if (fs24.existsSync(p)) return p;
15027
+ const p = path27.join(projectDir, name);
15028
+ if (fs26.existsSync(p)) return p;
14824
15029
  }
14825
15030
  return void 0;
14826
15031
  }
@@ -14972,9 +15177,9 @@ async function buildDsn4(args) {
14972
15177
  }
14973
15178
  var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
14974
15179
  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("."));
15180
+ const dir = path28.join(projectDir, "migrations");
15181
+ if (!fs27.existsSync(dir)) return [];
15182
+ const files = fs27.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
14978
15183
  return files.map((filename) => {
14979
15184
  const stem = filename.replace(/\.(js|ts)$/, "");
14980
15185
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -14994,7 +15199,7 @@ var KnexAdapter = {
14994
15199
  * project.yaml#migration_tool.
14995
15200
  */
14996
15201
  detect(projectDir) {
14997
- return KNEXFILE_VARIANTS2.some((name) => fs25.existsSync(path26.join(projectDir, name)));
15202
+ return KNEXFILE_VARIANTS2.some((name) => fs27.existsSync(path28.join(projectDir, name)));
14998
15203
  },
14999
15204
  async apply(args) {
15000
15205
  const dsn = await buildDsn4(args);
@@ -15067,9 +15272,9 @@ var KnexAdapter = {
15067
15272
  async newMigration(args) {
15068
15273
  try {
15069
15274
  const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
15070
- const stem = path26.basename(created).replace(/\.(js|ts)$/, "");
15275
+ const stem = path28.basename(created).replace(/\.(js|ts)$/, "");
15071
15276
  const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
15072
- return { status: "ok", version, filename: path26.basename(created), path: created };
15277
+ return { status: "ok", version, filename: path28.basename(created), path: created };
15073
15278
  } catch (err) {
15074
15279
  return {
15075
15280
  status: "error",
@@ -15083,6 +15288,362 @@ var KnexAdapter = {
15083
15288
  };
15084
15289
  registerSchemaMigrationAdapter(KnexAdapter);
15085
15290
 
15291
+ // scripts/lakebase/adapters/efcore-adapter.ts
15292
+ init_esm_shims();
15293
+ import * as path30 from "path";
15294
+
15295
+ // scripts/lakebase/schema-migrate-runners/efcore.ts
15296
+ init_esm_shims();
15297
+ import { spawn as spawn8 } from "child_process";
15298
+ import * as fs28 from "fs";
15299
+ import * as path29 from "path";
15300
+ import { Client as Client3 } from "pg";
15301
+ var MIGRATION_ID = /^(\d{14})_(.+)$/;
15302
+ function efVersion(id) {
15303
+ return MIGRATION_ID.exec(id)?.[1] ?? id;
15304
+ }
15305
+ function efDescription(id) {
15306
+ const name = MIGRATION_ID.exec(id)?.[2] ?? id;
15307
+ return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
15308
+ }
15309
+ function dsnToNpgsql(dsn) {
15310
+ const u = new URL(dsn);
15311
+ const parts = [
15312
+ `Host=${u.hostname}`,
15313
+ `Port=${u.port || "5432"}`,
15314
+ `Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
15315
+ ];
15316
+ if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
15317
+ if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
15318
+ const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
15319
+ const ssl = {
15320
+ disable: "Disable",
15321
+ allow: "Allow",
15322
+ prefer: "Prefer",
15323
+ require: "Require",
15324
+ "verify-ca": "VerifyCA",
15325
+ "verify-full": "VerifyFull"
15326
+ };
15327
+ parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
15328
+ const app = u.searchParams.get("application_name");
15329
+ if (app) parts.push(`Application Name=${app}`);
15330
+ return parts.map((p) => p.replace(/;/g, "")).join(";");
15331
+ }
15332
+ function parseEfJson(stdout) {
15333
+ const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
15334
+ if (!body) {
15335
+ throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
15336
+ }
15337
+ try {
15338
+ return JSON.parse(body);
15339
+ } catch (err) {
15340
+ throw new SchemaMigrationError(
15341
+ `dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
15342
+ Body (first 400 chars): ${body.slice(0, 400)}`
15343
+ );
15344
+ }
15345
+ }
15346
+ function requireProject(projectDir) {
15347
+ const project = findEfCoreProject(projectDir);
15348
+ if (!project) {
15349
+ throw new SchemaMigrationError(
15350
+ `No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
15351
+ );
15352
+ }
15353
+ return project;
15354
+ }
15355
+ function spawnDotnet(projectDir, args, env) {
15356
+ return new Promise((resolve2, reject) => {
15357
+ const child = spawn8("dotnet", args, {
15358
+ cwd: projectDir,
15359
+ env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
15360
+ stdio: ["ignore", "pipe", "pipe"]
15361
+ });
15362
+ let stdout = "";
15363
+ let stderr = "";
15364
+ child.stdout.on("data", (chunk) => {
15365
+ stdout += chunk.toString("utf8");
15366
+ });
15367
+ child.stderr.on("data", (chunk) => {
15368
+ stderr += chunk.toString("utf8");
15369
+ });
15370
+ child.on("error", (err) => {
15371
+ reject(
15372
+ new SchemaMigrationError(
15373
+ `Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
15374
+ err
15375
+ )
15376
+ );
15377
+ });
15378
+ child.on("close", (code) => {
15379
+ if (code === 0) {
15380
+ resolve2({ stdout, stderr });
15381
+ } else {
15382
+ reject(
15383
+ new SchemaMigrationError(
15384
+ `dotnet ${args.join(" ")} exited with code ${code}.
15385
+ stdout: ${stdout}
15386
+ stderr: ${stderr}`
15387
+ )
15388
+ );
15389
+ }
15390
+ });
15391
+ });
15392
+ }
15393
+ var restored = /* @__PURE__ */ new Set();
15394
+ async function ensureRestored(projectDir, csproj) {
15395
+ if (restored.has(projectDir)) return;
15396
+ if (fs28.existsSync(path29.join(projectDir, ".config", "dotnet-tools.json"))) {
15397
+ await spawnDotnet(projectDir, ["tool", "restore"], {});
15398
+ }
15399
+ await spawnDotnet(projectDir, ["restore", csproj], {});
15400
+ restored.add(projectDir);
15401
+ }
15402
+ async function runEf(projectDir, args, dsn, opts = {}) {
15403
+ const project = requireProject(projectDir);
15404
+ const csproj = path29.relative(projectDir, project.csproj) || project.csproj;
15405
+ await ensureRestored(projectDir, csproj);
15406
+ const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
15407
+ if (opts.noBuild) full.push("--no-build");
15408
+ const env = {};
15409
+ if (dsn) {
15410
+ env.DATABASE_URL = dsn;
15411
+ if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
15412
+ }
15413
+ return spawnDotnet(projectDir, full, env);
15414
+ }
15415
+ async function listEfMigrations(ctx, opts = {}) {
15416
+ const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
15417
+ const entries = parseEfJson(stdout);
15418
+ if (ctx.dsn && entries.some((e) => e.applied === null)) {
15419
+ const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
15420
+ throw new SchemaMigrationError(
15421
+ `dotnet ef could not read migration state from the database (check connectivity and credentials).
15422
+ ${warning || stderr}`.trim()
15423
+ );
15424
+ }
15425
+ return entries;
15426
+ }
15427
+ async function readEfHistory(dsn) {
15428
+ const client = new Client3({ connectionString: dsn, application_name: connectionApplicationName() });
15429
+ await client.connect();
15430
+ try {
15431
+ const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
15432
+ if (!exists.rows[0]?.present) return null;
15433
+ const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
15434
+ return r.rows.map((row) => row.MigrationId);
15435
+ } finally {
15436
+ await client.end();
15437
+ }
15438
+ }
15439
+ function toApplied(entries) {
15440
+ return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
15441
+ }
15442
+ async function statusEfcore(ctx) {
15443
+ const entries = await listEfMigrations(ctx);
15444
+ const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
15445
+ const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
15446
+ const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
15447
+ return { current, pending, tool: "efcore" };
15448
+ }
15449
+ async function applyEfcore(ctx) {
15450
+ const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
15451
+ await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
15452
+ const after = await listEfMigrations(ctx, { noBuild: true });
15453
+ const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
15454
+ return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
15455
+ }
15456
+ function resolveEfTarget(target, applied) {
15457
+ const t = target.trim();
15458
+ if (t === "0") return "0";
15459
+ const rel = /^-(\d+)$/.exec(t);
15460
+ if (rel) {
15461
+ const steps = Number(rel[1]);
15462
+ if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
15463
+ const index = applied.length - 1 - steps;
15464
+ if (index < -1) {
15465
+ throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
15466
+ }
15467
+ return index === -1 ? "0" : applied[index].id;
15468
+ }
15469
+ const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
15470
+ if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
15471
+ return match.id;
15472
+ }
15473
+ async function rollbackEfcore(ctx) {
15474
+ const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
15475
+ const targetId = resolveEfTarget(ctx.target, beforeEntries);
15476
+ await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
15477
+ const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
15478
+ return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
15479
+ }
15480
+ function efMigrationName(slug) {
15481
+ const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
15482
+ if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
15483
+ return /^[0-9]/.test(name) ? `M${name}` : name;
15484
+ }
15485
+ async function createEfcoreMigration(opts) {
15486
+ const project = requireProject(opts.projectDir);
15487
+ const name = efMigrationName(opts.slug);
15488
+ const existing = new Set(fs28.existsSync(project.migrationsDir) ? fs28.readdirSync(project.migrationsDir) : []);
15489
+ await runEf(opts.projectDir, ["migrations", "add", name]);
15490
+ const created = fs28.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
15491
+ if (created.length !== 1) {
15492
+ throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
15493
+ }
15494
+ return { id: created[0].replace(/\.cs$/, ""), path: path29.join(project.migrationsDir, created[0]) };
15495
+ }
15496
+ function localEfMigrationIds(projectDir) {
15497
+ const project = findEfCoreProject(projectDir);
15498
+ if (!project || !fs28.existsSync(project.migrationsDir)) return [];
15499
+ return fs28.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();
15500
+ }
15501
+ function efProductVersion(project) {
15502
+ try {
15503
+ const csproj = fs28.readFileSync(project.csproj, "utf-8");
15504
+ const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
15505
+ if (m) return m[1];
15506
+ } catch {
15507
+ }
15508
+ return "unknown";
15509
+ }
15510
+ async function stampEfcore(ctx) {
15511
+ const project = requireProject(ctx.projectDir);
15512
+ const ids = localEfMigrationIds(ctx.projectDir);
15513
+ if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
15514
+ const rev = ctx.revision.trim();
15515
+ const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
15516
+ if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
15517
+ const toStamp = ids.slice(0, end + 1);
15518
+ const productVersion = efProductVersion(project);
15519
+ const client = new Client3({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
15520
+ await client.connect();
15521
+ try {
15522
+ await client.query("BEGIN");
15523
+ await client.query(
15524
+ `CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
15525
+ "MigrationId" character varying(150) NOT NULL,
15526
+ "ProductVersion" character varying(32) NOT NULL,
15527
+ CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
15528
+ );
15529
+ await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
15530
+ for (const id of toStamp) {
15531
+ await client.query(
15532
+ `INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
15533
+ ON CONFLICT ("MigrationId") DO NOTHING`,
15534
+ [id, productVersion]
15535
+ );
15536
+ }
15537
+ await client.query("COMMIT");
15538
+ } catch (err) {
15539
+ await client.query("ROLLBACK").catch(() => void 0);
15540
+ throw err;
15541
+ } finally {
15542
+ await client.end();
15543
+ }
15544
+ return efVersion(toStamp[toStamp.length - 1]);
15545
+ }
15546
+
15547
+ // scripts/lakebase/adapters/efcore-adapter.ts
15548
+ async function buildDsn5(args) {
15549
+ const result = await getConnection({
15550
+ output: "dsn",
15551
+ instance: args.instance,
15552
+ branch: args.branch,
15553
+ database: args.database,
15554
+ endpointName: args.endpointName
15555
+ });
15556
+ return result.url;
15557
+ }
15558
+ function errMsg(err) {
15559
+ return err instanceof Error ? err.message : String(err);
15560
+ }
15561
+ function listEfCoreFiles(projectDir) {
15562
+ return localEfMigrationIds(projectDir).map((id) => ({
15563
+ version: efVersion(id),
15564
+ filename: `${id}.cs`,
15565
+ description: efDescription(id),
15566
+ type: "CSharp",
15567
+ tool: "efcore"
15568
+ }));
15569
+ }
15570
+ var EfCoreAdapter = {
15571
+ id: "efcore",
15572
+ languages: ["dotnet"],
15573
+ /** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
15574
+ detect(projectDir) {
15575
+ return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
15576
+ },
15577
+ async apply(args) {
15578
+ try {
15579
+ const dsn = await buildDsn5(args);
15580
+ const r = await applyEfcore({ projectDir: args.projectDir, dsn });
15581
+ return {
15582
+ applied_migrations: r.applied,
15583
+ status: r.alreadyAtLatest ? "noop" : "ok",
15584
+ tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
15585
+ };
15586
+ } catch (err) {
15587
+ return { applied_migrations: [], status: "error", error: errMsg(err) };
15588
+ }
15589
+ },
15590
+ async rollback(args) {
15591
+ try {
15592
+ const dsn = await buildDsn5(args);
15593
+ const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
15594
+ return {
15595
+ rolled_back: r.rolledBack,
15596
+ status: r.rolledBack.length === 0 ? "noop" : "ok",
15597
+ tool_specific: { tool: r.tool }
15598
+ };
15599
+ } catch (err) {
15600
+ return { rolled_back: [], status: "error", error: errMsg(err) };
15601
+ }
15602
+ },
15603
+ async stamp(args) {
15604
+ try {
15605
+ const dsn = await buildDsn5(args);
15606
+ const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
15607
+ return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
15608
+ } catch (err) {
15609
+ return { status: "error", stamped_revision: null, error: errMsg(err) };
15610
+ }
15611
+ },
15612
+ async status(args) {
15613
+ try {
15614
+ const dsn = await buildDsn5(args);
15615
+ const r = await statusEfcore({ projectDir: args.projectDir, dsn });
15616
+ return {
15617
+ applied_version: r.current ?? null,
15618
+ pending: r.pending,
15619
+ applied: [],
15620
+ status: "ok",
15621
+ tool_specific: { tool: r.tool }
15622
+ };
15623
+ } catch (err) {
15624
+ return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
15625
+ }
15626
+ },
15627
+ async list(args) {
15628
+ return { files: listEfCoreFiles(args.projectDir) };
15629
+ },
15630
+ // baseline: use stamp (EF has no separate baseline concept).
15631
+ async newMigration(args) {
15632
+ try {
15633
+ const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
15634
+ return {
15635
+ status: "ok",
15636
+ version: efVersion(created.id),
15637
+ filename: path30.basename(created.path),
15638
+ path: created.path
15639
+ };
15640
+ } catch (err) {
15641
+ return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
15642
+ }
15643
+ }
15644
+ };
15645
+ registerSchemaMigrationAdapter(EfCoreAdapter);
15646
+
15086
15647
  // scripts/lakebase/schema-migrate.ts
15087
15648
  var SchemaMigrationError = class extends Error {
15088
15649
  constructor(message, cause) {
@@ -15096,13 +15657,16 @@ function detectLanguage(projectDir) {
15096
15657
  const lang = resolveMigrationLanguage(projectDir);
15097
15658
  if (lang === "unknown") {
15098
15659
  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.`
15660
+ `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
15661
  );
15101
15662
  }
15102
15663
  return lang;
15103
15664
  }
15104
- function toolForLanguage(language) {
15665
+ function toolForLanguage(language, projectDir) {
15105
15666
  switch (language) {
15667
+ case "dotnet":
15668
+ if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
15669
+ return "efcore";
15106
15670
  case "java":
15107
15671
  case "kotlin":
15108
15672
  return "flyway";
@@ -15115,8 +15679,10 @@ function toolForLanguage(language) {
15115
15679
  function listSchemaMigrations(args = {}) {
15116
15680
  const projectDir = args.projectDir ?? process.cwd();
15117
15681
  const language = args.language ?? detectLanguage(projectDir);
15118
- const tool = toolForLanguage(language);
15682
+ const tool = toolForLanguage(language, projectDir);
15119
15683
  switch (tool) {
15684
+ case "efcore":
15685
+ return listEfCoreFiles(projectDir);
15120
15686
  case "flyway":
15121
15687
  return listFlywayMigrations(projectDir);
15122
15688
  case "alembic":
@@ -15126,9 +15692,9 @@ function listSchemaMigrations(args = {}) {
15126
15692
  }
15127
15693
  }
15128
15694
  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));
15695
+ const dir = flywayMigrationsDir(projectDir);
15696
+ if (!fs29.existsSync(dir)) return [];
15697
+ const files = fs29.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
15132
15698
  return files.map((filename) => {
15133
15699
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
15134
15700
  const version = m[1];
@@ -15138,12 +15704,12 @@ function listFlywayMigrations(projectDir) {
15138
15704
  }
15139
15705
  function listAlembicMigrations(projectDir) {
15140
15706
  const candidates = [
15141
- path27.join(projectDir, "migrations", "versions"),
15142
- path27.join(projectDir, "alembic", "versions")
15707
+ path31.join(projectDir, "migrations", "versions"),
15708
+ path31.join(projectDir, "alembic", "versions")
15143
15709
  ];
15144
- const dir = candidates.find((p) => fs26.existsSync(p));
15710
+ const dir = candidates.find((p) => fs29.existsSync(p));
15145
15711
  if (!dir) return [];
15146
- const files = fs26.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
15712
+ const files = fs29.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
15147
15713
  return files.map((filename) => {
15148
15714
  const stem = filename.replace(/\.py$/, "");
15149
15715
  const sep4 = stem.indexOf("_");
@@ -15153,9 +15719,9 @@ function listAlembicMigrations(projectDir) {
15153
15719
  }).sort((a, b) => a.filename.localeCompare(b.filename));
15154
15720
  }
15155
15721
  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("."));
15722
+ const dir = path31.join(projectDir, "migrations");
15723
+ if (!fs29.existsSync(dir)) return [];
15724
+ const files = fs29.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
15159
15725
  return files.map((filename) => {
15160
15726
  const stem = filename.replace(/\.(js|ts)$/, "");
15161
15727
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -15177,7 +15743,7 @@ function versionCompare2(a, b) {
15177
15743
  return 0;
15178
15744
  }
15179
15745
  function adapterFor(projectDir, language) {
15180
- const override = language ? toolForLanguage(language) : void 0;
15746
+ const override = language ? toolForLanguage(language, projectDir) : void 0;
15181
15747
  return resolveSchemaMigrationAdapter(projectDir, override);
15182
15748
  }
15183
15749
  var TierMigrationRefusedError = class extends Error {
@@ -15331,8 +15897,8 @@ async function runInfraSuite(args) {
15331
15897
  duration_ms: Date.now() - start
15332
15898
  };
15333
15899
  if (args.junitOutput) {
15334
- fs27.mkdirSync(path28.dirname(args.junitOutput), { recursive: true });
15335
- fs27.writeFileSync(args.junitOutput, formatJUnit(result), "utf8");
15900
+ fs30.mkdirSync(path32.dirname(args.junitOutput), { recursive: true });
15901
+ fs30.writeFileSync(args.junitOutput, formatJUnit(result), "utf8");
15336
15902
  }
15337
15903
  return result;
15338
15904
  }
@@ -15378,8 +15944,8 @@ function escapeXml(s) {
15378
15944
 
15379
15945
  // scripts/lakebase/scm-claim-feature.ts
15380
15946
  init_esm_shims();
15381
- import * as fs28 from "fs";
15382
- import * as path29 from "path";
15947
+ import * as fs31 from "fs";
15948
+ import * as path33 from "path";
15383
15949
  var ScmClaimError = class extends Error {
15384
15950
  constructor(message, code) {
15385
15951
  super(message);
@@ -15436,7 +16002,7 @@ async function claimFeatureBranch(args) {
15436
16002
  const current = readWorkflowState(args.projectDir);
15437
16003
  if (!current) {
15438
16004
  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.`,
16005
+ `No SCM workflow state found at ${path33.join(args.projectDir, ".lakebase/workflow-state.json")}. Run lakebase-create-project to scaffold, or re-seed via the substrate.`,
15440
16006
  "no-state-file"
15441
16007
  );
15442
16008
  }
@@ -15540,8 +16106,8 @@ function alreadyClaimedSentinel(state) {
15540
16106
  };
15541
16107
  }
15542
16108
  function workflowStateFileExists(projectDir) {
15543
- return fs28.existsSync(
15544
- path29.join(projectDir, ".lakebase/workflow-state.json")
16109
+ return fs31.existsSync(
16110
+ path33.join(projectDir, ".lakebase/workflow-state.json")
15545
16111
  );
15546
16112
  }
15547
16113
 
@@ -16029,12 +16595,12 @@ async function pushCurrentBranchForPr(args) {
16029
16595
 
16030
16596
  // scripts/lakebase/workflow-drift.ts
16031
16597
  init_esm_shims();
16032
- import * as fs29 from "fs";
16033
- import * as path30 from "path";
16598
+ import * as fs32 from "fs";
16599
+ import * as path34 from "path";
16034
16600
  function findKitTemplatesDir(start) {
16035
16601
  let dir = start;
16036
16602
  for (let i = 0; i < 6; i++) {
16037
- const candidate = path30.join(
16603
+ const candidate = path34.join(
16038
16604
  dir,
16039
16605
  "templates",
16040
16606
  "project",
@@ -16042,8 +16608,8 @@ function findKitTemplatesDir(start) {
16042
16608
  ".github",
16043
16609
  "workflows"
16044
16610
  );
16045
- if (fs29.existsSync(candidate)) return candidate;
16046
- const parent = path30.dirname(dir);
16611
+ if (fs32.existsSync(candidate)) return candidate;
16612
+ const parent = path34.dirname(dir);
16047
16613
  if (parent === dir) break;
16048
16614
  dir = parent;
16049
16615
  }
@@ -16067,13 +16633,13 @@ function unifiedDiff(name, projectContent, templateContent) {
16067
16633
  return out.join("\n");
16068
16634
  }
16069
16635
  function detectWorkflowDrift(args) {
16070
- const projectWorkflowsDir = path30.join(
16636
+ const projectWorkflowsDir = path34.join(
16071
16637
  args.projectDir,
16072
16638
  ".github",
16073
16639
  "workflows"
16074
16640
  );
16075
- const here = path30.dirname(new URL(import.meta.url).pathname);
16076
- const kitWorkflowsDir = args.kitDir ? path30.join(
16641
+ const here = path34.dirname(new URL(import.meta.url).pathname);
16642
+ const kitWorkflowsDir = args.kitDir ? path34.join(
16077
16643
  args.kitDir,
16078
16644
  "templates",
16079
16645
  "project",
@@ -16081,21 +16647,21 @@ function detectWorkflowDrift(args) {
16081
16647
  ".github",
16082
16648
  "workflows"
16083
16649
  ) : 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")) : [];
16650
+ const templateFiles = fs32.existsSync(kitWorkflowsDir) ? fs32.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16651
+ const projectFiles = fs32.existsSync(projectWorkflowsDir) ? fs32.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16086
16652
  const version = readKitVersion(kitWorkflowsDir);
16087
16653
  const seen = /* @__PURE__ */ new Set();
16088
16654
  const files = [];
16089
16655
  for (const name of templateFiles) {
16090
16656
  seen.add(name);
16091
- const projectPath2 = path30.join(projectWorkflowsDir, name);
16092
- const templatePath = path30.join(kitWorkflowsDir, name);
16093
- if (!fs29.existsSync(projectPath2)) {
16657
+ const projectPath2 = path34.join(projectWorkflowsDir, name);
16658
+ const templatePath = path34.join(kitWorkflowsDir, name);
16659
+ if (!fs32.existsSync(projectPath2)) {
16094
16660
  files.push({ name, status: "missing" });
16095
16661
  continue;
16096
16662
  }
16097
- const projectContent = fs29.readFileSync(projectPath2, "utf8");
16098
- const templateContent = applyPlaceholders(fs29.readFileSync(templatePath, "utf8"), version);
16663
+ const projectContent = fs32.readFileSync(projectPath2, "utf8");
16664
+ const templateContent = applyPlaceholders(fs32.readFileSync(templatePath, "utf8"), version);
16099
16665
  if (projectContent === templateContent) {
16100
16666
  files.push({ name, status: "unchanged" });
16101
16667
  } else {
@@ -16126,10 +16692,10 @@ function detectWorkflowDrift(args) {
16126
16692
  function readKitVersion(kitWorkflowsDir) {
16127
16693
  let dir = kitWorkflowsDir;
16128
16694
  for (let i = 0; i < 5; i++) {
16129
- dir = path30.dirname(dir);
16695
+ dir = path34.dirname(dir);
16130
16696
  }
16131
16697
  try {
16132
- const raw = fs29.readFileSync(path30.join(dir, "package.json"), "utf-8");
16698
+ const raw = fs32.readFileSync(path34.join(dir, "package.json"), "utf-8");
16133
16699
  const pkg = JSON.parse(raw);
16134
16700
  return typeof pkg.version === "string" ? pkg.version : "unknown";
16135
16701
  } catch {
@@ -16146,7 +16712,7 @@ var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
16146
16712
  function findKitCommandsDir(start) {
16147
16713
  let dir = start;
16148
16714
  for (let i = 0; i < 6; i++) {
16149
- const candidate = path30.join(
16715
+ const candidate = path34.join(
16150
16716
  dir,
16151
16717
  "templates",
16152
16718
  "project",
@@ -16154,8 +16720,8 @@ function findKitCommandsDir(start) {
16154
16720
  ".claude",
16155
16721
  "commands"
16156
16722
  );
16157
- if (fs29.existsSync(candidate)) return candidate;
16158
- const parent = path30.dirname(dir);
16723
+ if (fs32.existsSync(candidate)) return candidate;
16724
+ const parent = path34.dirname(dir);
16159
16725
  if (parent === dir) break;
16160
16726
  dir = parent;
16161
16727
  }
@@ -16168,24 +16734,24 @@ function parsePinnedVersion(content) {
16168
16734
  return m ? m[1] : void 0;
16169
16735
  }
16170
16736
  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);
16737
+ const projectCommandsDir = path34.join(args.projectDir, ".claude", "commands");
16738
+ const here = path34.dirname(new URL(import.meta.url).pathname);
16739
+ const kitCommandsDir = args.kitDir ? path34.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
16174
16740
  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)) : [];
16741
+ const templateFiles = fs32.existsSync(kitCommandsDir) ? fs32.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16742
+ const projectFiles = fs32.existsSync(projectCommandsDir) ? fs32.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
16177
16743
  const seen = /* @__PURE__ */ new Set();
16178
16744
  const files = [];
16179
16745
  for (const name of templateFiles) {
16180
16746
  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)) {
16747
+ const projectPath2 = path34.join(projectCommandsDir, name);
16748
+ const templatePath = path34.join(kitCommandsDir, name);
16749
+ const templateRaw = fs32.readFileSync(templatePath, "utf8");
16750
+ if (!fs32.existsSync(projectPath2)) {
16185
16751
  files.push({ name, status: "missing", kit_version: kitVersion });
16186
16752
  continue;
16187
16753
  }
16188
- const projectContent = fs29.readFileSync(projectPath2, "utf8");
16754
+ const projectContent = fs32.readFileSync(projectPath2, "utf8");
16189
16755
  const pinned = parsePinnedVersion(projectContent);
16190
16756
  const versionForCompare = pinned ?? kitVersion;
16191
16757
  const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
@@ -16223,10 +16789,10 @@ function detectCommandDrift(args) {
16223
16789
  function readKitVersionFromCommandsDir(kitCommandsDir) {
16224
16790
  let dir = kitCommandsDir;
16225
16791
  for (let i = 0; i < 5; i++) {
16226
- dir = path30.dirname(dir);
16792
+ dir = path34.dirname(dir);
16227
16793
  }
16228
16794
  try {
16229
- const raw = fs29.readFileSync(path30.join(dir, "package.json"), "utf-8");
16795
+ const raw = fs32.readFileSync(path34.join(dir, "package.json"), "utf-8");
16230
16796
  const pkg = JSON.parse(raw);
16231
16797
  return typeof pkg.version === "string" ? pkg.version : "unknown";
16232
16798
  } catch {
@@ -16243,13 +16809,13 @@ function detectScaffoldedDrift(args) {
16243
16809
  };
16244
16810
  }
16245
16811
  function updateWorkflows(args) {
16246
- const projectWorkflowsDir = path30.join(
16812
+ const projectWorkflowsDir = path34.join(
16247
16813
  args.projectDir,
16248
16814
  ".github",
16249
16815
  "workflows"
16250
16816
  );
16251
- const here = path30.dirname(new URL(import.meta.url).pathname);
16252
- const kitWorkflowsDir = args.kitDir ? path30.join(
16817
+ const here = path34.dirname(new URL(import.meta.url).pathname);
16818
+ const kitWorkflowsDir = args.kitDir ? path34.join(
16253
16819
  args.kitDir,
16254
16820
  "templates",
16255
16821
  "project",
@@ -16260,22 +16826,22 @@ function updateWorkflows(args) {
16260
16826
  const substitute = args.substitute !== false;
16261
16827
  const dryRun = args.dryRun === true;
16262
16828
  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 });
16829
+ const templateFiles = fs32.existsSync(kitWorkflowsDir) ? fs32.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16830
+ const projectFiles = fs32.existsSync(projectWorkflowsDir) ? fs32.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
16831
+ if (!dryRun && templateFiles.length > 0 && !fs32.existsSync(projectWorkflowsDir)) {
16832
+ fs32.mkdirSync(projectWorkflowsDir, { recursive: true });
16267
16833
  }
16268
16834
  const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
16269
16835
  const seen = /* @__PURE__ */ new Set();
16270
16836
  const files = [];
16271
16837
  for (const name of templateFiles) {
16272
16838
  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");
16839
+ const projectPath2 = path34.join(projectWorkflowsDir, name);
16840
+ const templatePath = path34.join(kitWorkflowsDir, name);
16841
+ const templateRaw = fs32.readFileSync(templatePath, "utf-8");
16276
16842
  const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
16277
- const existed = fs29.existsSync(projectPath2);
16278
- const current = existed ? fs29.readFileSync(projectPath2, "utf-8") : "";
16843
+ const existed = fs32.existsSync(projectPath2);
16844
+ const current = existed ? fs32.readFileSync(projectPath2, "utf-8") : "";
16279
16845
  let outcome;
16280
16846
  if (!existed) {
16281
16847
  outcome = "added";
@@ -16285,16 +16851,16 @@ function updateWorkflows(args) {
16285
16851
  outcome = "updated";
16286
16852
  }
16287
16853
  if (!dryRun && outcome !== "unchanged") {
16288
- fs29.writeFileSync(projectPath2, desired);
16854
+ fs32.writeFileSync(projectPath2, desired);
16289
16855
  }
16290
16856
  files.push({ name, outcome });
16291
16857
  }
16292
16858
  if (pruneExtras) {
16293
16859
  for (const name of projectFiles) {
16294
16860
  if (seen.has(name)) continue;
16295
- const projectPath2 = path30.join(projectWorkflowsDir, name);
16861
+ const projectPath2 = path34.join(projectWorkflowsDir, name);
16296
16862
  if (!dryRun) {
16297
- fs29.unlinkSync(projectPath2);
16863
+ fs32.unlinkSync(projectPath2);
16298
16864
  }
16299
16865
  files.push({ name, outcome: "removed" });
16300
16866
  }
@@ -16892,15 +17458,15 @@ function parentForTopology(t, defaultLeaf) {
16892
17458
 
16893
17459
  // scripts/lakebase/scm-doctor.ts
16894
17460
  init_esm_shims();
16895
- import * as fs30 from "fs";
16896
- import * as path31 from "path";
17461
+ import * as fs33 from "fs";
17462
+ import * as path35 from "path";
16897
17463
  var FEATURE_PREFIX = "feature/";
16898
17464
  var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
16899
17465
  function readEnv(projectDir) {
16900
- const envPath = path31.join(projectDir, ".env");
17466
+ const envPath = path35.join(projectDir, ".env");
16901
17467
  const out = /* @__PURE__ */ new Map();
16902
- if (!fs30.existsSync(envPath)) return out;
16903
- const lines = fs30.readFileSync(envPath, "utf8").split("\n");
17468
+ if (!fs33.existsSync(envPath)) return out;
17469
+ const lines = fs33.readFileSync(envPath, "utf8").split("\n");
16904
17470
  for (const line of lines) {
16905
17471
  const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
16906
17472
  if (m) out.set(m[1], m[2].replace(/^["']|["']$/g, ""));
@@ -16955,9 +17521,9 @@ async function runDoctor(args, deps = {}) {
16955
17521
  }
16956
17522
  for (const wf of ["pr.yml", "merge.yml"]) {
16957
17523
  try {
16958
- const p = path31.join(projectDir, ".github", "workflows", wf);
16959
- if (!fs30.existsSync(p)) continue;
16960
- const body = fs30.readFileSync(p, "utf8");
17524
+ const p = path35.join(projectDir, ".github", "workflows", wf);
17525
+ if (!fs33.existsSync(p)) continue;
17526
+ const body = fs33.readFileSync(p, "utf8");
16961
17527
  if (/lakebase-scm-utils#v\d/.test(body)) {
16962
17528
  findings.push({
16963
17529
  id: "ci-workflow-substrate-pin",
@@ -17172,7 +17738,7 @@ async function fixFinding(args) {
17172
17738
  );
17173
17739
  }
17174
17740
  const sanitized = sanitizeBranchName(branch);
17175
- const envFile = path31.join(args.projectDir, ".env");
17741
+ const envFile = path35.join(args.projectDir, ".env");
17176
17742
  updateEnvConnection({
17177
17743
  envPath: envFile,
17178
17744
  projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
@@ -17274,14 +17840,14 @@ function shellEscape3(s) {
17274
17840
 
17275
17841
  // scripts/lakebase/doctor.ts
17276
17842
  init_esm_shims();
17277
- import * as fs31 from "fs";
17278
- import * as path32 from "path";
17843
+ import * as fs34 from "fs";
17844
+ import * as path36 from "path";
17279
17845
  import * as cp6 from "child_process";
17280
17846
  function readEnvFile(projectDir) {
17281
- const envPath = path32.join(projectDir, ".env");
17282
- if (!fs31.existsSync(envPath)) return {};
17847
+ const envPath = path36.join(projectDir, ".env");
17848
+ if (!fs34.existsSync(envPath)) return {};
17283
17849
  const out = {};
17284
- for (const line of fs31.readFileSync(envPath, "utf8").split("\n")) {
17850
+ for (const line of fs34.readFileSync(envPath, "utf8").split("\n")) {
17285
17851
  const m = line.match(/^\s*([A-Z][A-Z0-9_]*)\s*=\s*(.*)\s*$/);
17286
17852
  if (!m) continue;
17287
17853
  let val = m[2];
@@ -17435,6 +18001,23 @@ async function checkPrereq(spec, run) {
17435
18001
  detail: { version: trimmed }
17436
18002
  };
17437
18003
  }
18004
+ var DOTNET_PREREQ = {
18005
+ name: "dotnet-sdk",
18006
+ cmd: "dotnet",
18007
+ versionArgs: ["--version"],
18008
+ minMajor: 10,
18009
+ label: ".NET SDK",
18010
+ 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."
18011
+ };
18012
+ async function checkLanguagePrerequisites(projectDir, run = defaultVersionRunner) {
18013
+ let lang;
18014
+ try {
18015
+ lang = detectLanguage(projectDir);
18016
+ } catch {
18017
+ return [];
18018
+ }
18019
+ return lang === "dotnet" ? [await checkPrereq(DOTNET_PREREQ, run)] : [];
18020
+ }
17438
18021
  async function checkPrerequisites(run = defaultVersionRunner) {
17439
18022
  return Promise.all(PREREQS.map((spec) => checkPrereq(spec, run)));
17440
18023
  }
@@ -17536,7 +18119,7 @@ function checkEnv(projectDir) {
17536
18119
  name: "env-file",
17537
18120
  status: "warn",
17538
18121
  message: ".env not found",
17539
- detail: { projectDir, envPath: path32.join(projectDir, ".env") },
18122
+ detail: { projectDir, envPath: path36.join(projectDir, ".env") },
17540
18123
  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
18124
  };
17542
18125
  }
@@ -17766,6 +18349,7 @@ async function runDoctor2(args = {}) {
17766
18349
  const hooks = checkHooks(projectDir);
17767
18350
  const workflowDrift = checkWorkflowDrift(projectDir);
17768
18351
  const prereqs = await checkPrerequisites();
18352
+ const languagePrereqs = await checkLanguagePrerequisites(projectDir);
17769
18353
  const lakebaseEnabled = auth7.status === "ok" ? await checkLakebaseEnabled(profile) : {
17770
18354
  name: "lakebase-enabled",
17771
18355
  status: "skip",
@@ -17774,6 +18358,7 @@ async function runDoctor2(args = {}) {
17774
18358
  const checks = [
17775
18359
  cli,
17776
18360
  ...prereqs,
18361
+ ...languagePrereqs,
17777
18362
  auth7,
17778
18363
  identity,
17779
18364
  lakebaseEnabled,
@@ -17956,12 +18541,16 @@ function isUcMissingError(msg) {
17956
18541
  export {
17957
18542
  CONVENTION_TIER_DEFAULTS,
17958
18543
  DEFAULT_PROTECTED_TIER_NAMES,
18544
+ DOTNET_EFCORE_DEFAULTS,
18545
+ DOTNET_FLYWAY_DIR,
18546
+ EF_DESIGN_PACKAGE,
17959
18547
  FIXABLE_FINDING_IDS,
17960
18548
  InitializrNetworkError,
17961
18549
  InitializrParseError,
17962
18550
  LakebaseBranchError,
17963
18551
  LakebaseBranchTtlTooLongError,
17964
18552
  LakebaseProjectError,
18553
+ MAVEN_FLYWAY_DIR,
17965
18554
  MIGRATION_DEFAULTS,
17966
18555
  NODE_E2E_TEMPLATE_FILES,
17967
18556
  PLAYWRIGHT_TEMPLATE_FILES,
@@ -18058,9 +18647,12 @@ export {
18058
18647
  ensureSchemaAndVolume,
18059
18648
  extractPullNumber,
18060
18649
  featureBranchName,
18650
+ findCsprojFiles,
18061
18651
  findDefaultBranchName,
18652
+ findEfCoreProject,
18062
18653
  findHistoryRetentionDuration,
18063
18654
  fixFinding,
18655
+ flywayMigrationsDir,
18064
18656
  formatJUnit,
18065
18657
  formatSchemaDiffAsMarkdown,
18066
18658
  generateAppYaml,
@@ -18082,11 +18674,14 @@ export {
18082
18674
  getTargetNames,
18083
18675
  grantLakebasePermission,
18084
18676
  grantUcCatalogPermission,
18677
+ hasDotnetMarker,
18085
18678
  inferTierTopology,
18086
18679
  initWorkflowState,
18087
18680
  installHooks,
18088
18681
  installPlaywright,
18089
18682
  isAllSchemas,
18683
+ isDotnetFlywayProject,
18684
+ isDotnetProject,
18090
18685
  isForeignFeatureClaim,
18091
18686
  isGitTracked,
18092
18687
  isLongRunningTierBranch,