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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/scripts/index.cjs +2636 -2124
  2. package/dist/scripts/index.d.cts +1 -1
  3. package/dist/scripts/index.d.ts +1 -1
  4. package/dist/scripts/index.js +680 -178
  5. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2460 -2030
  6. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +508 -81
  7. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2429 -2007
  8. package/dist/scripts/lakebase/collapse-heads.cli.js +477 -58
  9. package/dist/scripts/lakebase/detect-language.cli.cjs +2441 -2016
  10. package/dist/scripts/lakebase/detect-language.cli.js +489 -67
  11. package/dist/scripts/lakebase/doctor.cli.cjs +2484 -2040
  12. package/dist/scripts/lakebase/doctor.cli.js +532 -91
  13. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  14. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  15. package/dist/scripts/lakebase/index.cjs +2633 -2121
  16. package/dist/scripts/lakebase/index.d.cts +59 -10
  17. package/dist/scripts/lakebase/index.d.ts +59 -10
  18. package/dist/scripts/lakebase/index.js +674 -172
  19. package/dist/scripts/lakebase/infra-runner.cli.cjs +2435 -2013
  20. package/dist/scripts/lakebase/infra-runner.cli.js +483 -64
  21. package/dist/scripts/lakebase/new-migration.cli.cjs +2429 -2007
  22. package/dist/scripts/lakebase/new-migration.cli.js +477 -58
  23. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  24. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  25. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2461 -2029
  26. package/dist/scripts/lakebase/schema-migrate.cli.js +509 -80
  27. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2460 -2030
  28. package/dist/scripts/lakebase/scm-claim-feature.cli.js +508 -81
  29. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2486 -2056
  30. package/dist/scripts/lakebase/scm-doctor.cli.js +537 -107
  31. package/dist/scripts/lakebase/scm-merge.cli.cjs +2443 -2021
  32. package/dist/scripts/lakebase/scm-merge.cli.js +494 -72
  33. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2464 -2034
  34. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +511 -84
  35. package/dist/scripts/util/index.cjs +2 -2
  36. package/dist/scripts/util/index.js +2 -2
  37. package/package.json +1 -1
  38. package/scripts/lakebase/adapters/efcore-adapter.ts +158 -0
  39. package/scripts/lakebase/adapters/flyway-adapter.ts +8 -5
  40. package/scripts/lakebase/collapse-heads.cli.ts +1 -1
  41. package/scripts/lakebase/create-project.ts +14 -12
  42. package/scripts/lakebase/detect-language.cli.ts +2 -2
  43. package/scripts/lakebase/doctor.ts +29 -0
  44. package/scripts/lakebase/dotnet-project.ts +112 -0
  45. package/scripts/lakebase/index.ts +1 -0
  46. package/scripts/lakebase/migration-layout.ts +43 -2
  47. package/scripts/lakebase/new-migration.cli.ts +1 -1
  48. package/scripts/lakebase/scaffold-language.ts +23 -2
  49. package/scripts/lakebase/scaffold.ts +4 -1
  50. package/scripts/lakebase/schema-migrate-runners/efcore.ts +399 -0
  51. package/scripts/lakebase/schema-migrate-runners/flyway.ts +6 -4
  52. package/scripts/lakebase/schema-migrate.cli.ts +3 -1
  53. package/scripts/lakebase/schema-migrate.ts +24 -10
  54. package/templates/project/common/.github/workflows/merge.yml +13 -1
  55. package/templates/project/common/.github/workflows/pr.yml +25 -5
  56. package/templates/project/common/.gitignore.base +7 -0
  57. package/templates/project/common/scripts/flyway-migrate.sh +54 -1
  58. package/templates/project/common/scripts/prepare-schema-diff.sh +5 -0
  59. package/templates/project/common/scripts/run-dev.sh +10 -1
  60. package/templates/project/common/scripts/run-tests.sh +6 -1
  61. package/templates/project/dotnet/.gitignore.extra +8 -0
  62. package/templates/project/dotnet/App.sln +56 -0
  63. package/templates/project/dotnet/Makefile +13 -0
  64. package/templates/project/dotnet/global.json +6 -0
  65. package/templates/project/dotnet/src/App/Data/AppDbContext.cs +12 -0
  66. package/templates/project/dotnet/src/App/Lakebase/DotEnv.cs +48 -0
  67. package/templates/project/dotnet/src/App/Lakebase/LakebaseConnection.cs +225 -0
  68. package/templates/project/dotnet/src/App/Program.cs +34 -0
  69. package/templates/project/dotnet/src/App/appsettings.json +9 -0
  70. package/templates/project/dotnet/tests/App.Tests/App.Tests.csproj +25 -0
  71. package/templates/project/dotnet/tests/App.Tests/HealthTests.cs +18 -0
  72. package/templates/project/dotnet/tests/App.Tests/OpenApiTests.cs +23 -0
  73. package/templates/project/dotnet-efcore/.config/dotnet-tools.json +13 -0
  74. package/templates/project/dotnet-efcore/src/App/App.csproj +22 -0
  75. package/templates/project/dotnet-efcore/src/App/Data/DesignTimeDbContextFactory.cs +24 -0
  76. package/templates/project/dotnet-efcore/src/App/Migrations/README.md +23 -0
  77. package/templates/project/dotnet-flyway/db/migrations/README.md +23 -0
  78. package/templates/project/dotnet-flyway/src/App/App.csproj +17 -0
@@ -276,9 +276,9 @@ function asBranchUid(s) {
276
276
  }
277
277
  return s;
278
278
  }
279
- function branchNameFromResourcePath(path12) {
280
- if (!path12.includes("/branches/")) return null;
281
- const leaf = path12.split("/branches/").pop();
279
+ function branchNameFromResourcePath(path15) {
280
+ if (!path15.includes("/branches/")) return null;
281
+ const leaf = path15.split("/branches/").pop();
282
282
  if (!leaf) return null;
283
283
  try {
284
284
  return asBranchName(leaf);
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
389
389
  var cached;
390
390
  function substrateSelfVersion() {
391
391
  if (cached !== void 0) return cached;
392
- if ("0.2.50".length > 0) {
393
- cached = "0.2.50";
392
+ if ("0.3.0".length > 0) {
393
+ cached = "0.3.0";
394
394
  return cached;
395
395
  }
396
396
  cached = "unknown";
@@ -499,27 +499,92 @@ function dbcli2(args) {
499
499
  }
500
500
 
501
501
  // scripts/lakebase/schema-migrate.ts
502
- import * as fs10 from "fs";
503
- import * as path10 from "path";
502
+ import * as fs12 from "fs";
503
+ import * as path13 from "path";
504
504
 
505
505
  // scripts/lakebase/migration-layout.ts
506
+ import * as fs5 from "fs";
507
+ import * as path4 from "path";
508
+
509
+ // scripts/lakebase/dotnet-project.ts
506
510
  import * as fs4 from "fs";
507
511
  import * as path3 from "path";
512
+ var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
513
+ var DOTNET_FLYWAY_DIR = path3.join("db", "migrations");
514
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
515
+ function safeReaddir(dir) {
516
+ try {
517
+ return fs4.readdirSync(dir, { withFileTypes: true });
518
+ } catch {
519
+ return [];
520
+ }
521
+ }
522
+ function hasDotnetMarker(dir) {
523
+ return safeReaddir(dir).some(
524
+ (e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
525
+ );
526
+ }
527
+ function findCsprojFiles(projectDir) {
528
+ const found = [];
529
+ const walk = (dir, depth) => {
530
+ for (const e of safeReaddir(dir)) {
531
+ const full = path3.join(dir, e.name);
532
+ if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
533
+ found.push(full);
534
+ } else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
535
+ walk(full, depth + 1);
536
+ }
537
+ }
538
+ };
539
+ walk(projectDir, 0);
540
+ return found.sort();
541
+ }
542
+ function findEfCoreProject(projectDir) {
543
+ for (const csproj of findCsprojFiles(projectDir)) {
544
+ let content = "";
545
+ try {
546
+ content = fs4.readFileSync(csproj, "utf-8");
547
+ } catch {
548
+ continue;
549
+ }
550
+ if (content.includes(EF_DESIGN_PACKAGE)) {
551
+ const dir = path3.dirname(csproj);
552
+ return { csproj, projectDir: dir, migrationsDir: path3.join(dir, "Migrations") };
553
+ }
554
+ }
555
+ return null;
556
+ }
557
+ function isDotnetProject(projectDir) {
558
+ return hasDotnetMarker(projectDir);
559
+ }
560
+ function isDotnetFlywayProject(projectDir) {
561
+ return isDotnetProject(projectDir) && fs4.existsSync(path3.join(projectDir, DOTNET_FLYWAY_DIR));
562
+ }
563
+ var MAVEN_FLYWAY_DIR = path3.join("src", "main", "resources", "db", "migration");
564
+ function flywayMigrationsDir(projectDir) {
565
+ const maven = path3.join(projectDir, MAVEN_FLYWAY_DIR);
566
+ if (fs4.existsSync(maven) || fs4.existsSync(path3.join(projectDir, "pom.xml"))) return maven;
567
+ if (isDotnetFlywayProject(projectDir)) return path3.join(projectDir, DOTNET_FLYWAY_DIR);
568
+ return maven;
569
+ }
570
+
571
+ // scripts/lakebase/migration-layout.ts
508
572
  var MIGRATION_LANGUAGES = [
509
573
  "java",
510
574
  "kotlin",
511
575
  "python",
512
576
  "nodejs",
577
+ "dotnet",
513
578
  "unknown"
514
579
  ];
515
580
  function detectLanguageAt(dir) {
516
- if (fs4.existsSync(path3.join(dir, "pom.xml"))) {
517
- const kotlinDir = path3.join(dir, "src", "main", "kotlin");
518
- if (fs4.existsSync(kotlinDir)) {
581
+ if (fs5.existsSync(path4.join(dir, "pom.xml"))) {
582
+ const kotlinDir = path4.join(dir, "src", "main", "kotlin");
583
+ if (fs5.existsSync(kotlinDir)) {
519
584
  return "kotlin";
520
585
  }
521
586
  try {
522
- const pom = fs4.readFileSync(path3.join(dir, "pom.xml"), "utf-8");
587
+ const pom = fs5.readFileSync(path4.join(dir, "pom.xml"), "utf-8");
523
588
  if (pom.includes("kotlin-maven-plugin")) {
524
589
  return "kotlin";
525
590
  }
@@ -527,10 +592,13 @@ function detectLanguageAt(dir) {
527
592
  }
528
593
  return "java";
529
594
  }
530
- if (fs4.existsSync(path3.join(dir, "pyproject.toml")) || fs4.existsSync(path3.join(dir, "requirements.txt")) || fs4.existsSync(path3.join(dir, "alembic.ini"))) {
595
+ if (fs5.existsSync(path4.join(dir, "pyproject.toml")) || fs5.existsSync(path4.join(dir, "requirements.txt")) || fs5.existsSync(path4.join(dir, "alembic.ini"))) {
531
596
  return "python";
532
597
  }
533
- if (fs4.existsSync(path3.join(dir, "package.json"))) {
598
+ if (hasDotnetMarker(dir)) {
599
+ return "dotnet";
600
+ }
601
+ if (fs5.existsSync(path4.join(dir, "package.json"))) {
534
602
  return "nodejs";
535
603
  }
536
604
  return "unknown";
@@ -551,14 +619,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
551
619
  if (!rel) {
552
620
  return "unknown";
553
621
  }
554
- const rootResolved = path3.resolve(projectDir);
555
- let dir = path3.resolve(projectDir, rel);
556
- while (dir === rootResolved || dir.startsWith(rootResolved + path3.sep)) {
622
+ const rootResolved = path4.resolve(projectDir);
623
+ let dir = path4.resolve(projectDir, rel);
624
+ while (dir === rootResolved || dir.startsWith(rootResolved + path4.sep)) {
557
625
  const lang = detectLanguageAt(dir);
558
626
  if (lang !== "unknown") {
559
627
  return lang;
560
628
  }
561
- const parent = path3.dirname(dir);
629
+ const parent = path4.dirname(dir);
562
630
  if (parent === dir) {
563
631
  break;
564
632
  }
@@ -568,21 +636,21 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
568
636
  }
569
637
 
570
638
  // scripts/lakebase/adapters/alembic-adapter.ts
571
- import * as fs6 from "fs";
572
- import * as path5 from "path";
639
+ import * as fs7 from "fs";
640
+ import * as path6 from "path";
573
641
 
574
642
  // scripts/lakebase/schema-migrate-runners/alembic.ts
575
643
  import { spawn } from "child_process";
576
- import * as fs5 from "fs";
577
- import * as path4 from "path";
644
+ import * as fs6 from "fs";
645
+ import * as path5 from "path";
578
646
  function resolveAlembicBin(projectDir) {
579
647
  const candidates = [
580
- path4.join(projectDir, ".venv", "bin", "alembic"),
581
- path4.join(projectDir, "venv", "bin", "alembic")
648
+ path5.join(projectDir, ".venv", "bin", "alembic"),
649
+ path5.join(projectDir, "venv", "bin", "alembic")
582
650
  ];
583
651
  for (const candidate of candidates) {
584
652
  try {
585
- if (fs5.existsSync(candidate)) return candidate;
653
+ if (fs6.existsSync(candidate)) return candidate;
586
654
  } catch {
587
655
  }
588
656
  }
@@ -592,7 +660,7 @@ function spawnAlembic(projectDir, args, dsn) {
592
660
  return new Promise((resolve2, reject) => {
593
661
  const bin = resolveAlembicBin(projectDir);
594
662
  const env = { ...process.env };
595
- env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path4.delimiter);
663
+ env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path5.delimiter);
596
664
  if (dsn) env.DATABASE_URL = dsn;
597
665
  const child = spawn(bin, args, {
598
666
  cwd: projectDir,
@@ -640,10 +708,10 @@ async function createAlembicRevision(opts) {
640
708
  const m = stdout.match(/Generating\s+(\S+\.py)/);
641
709
  if (m) return m[1].trim();
642
710
  for (const rel of ["migrations/versions", "alembic/versions"]) {
643
- const dir = path4.join(opts.projectDir, rel);
644
- if (!fs5.existsSync(dir)) continue;
645
- const hit = fs5.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
646
- if (hit) return path4.join(dir, hit);
711
+ const dir = path5.join(opts.projectDir, rel);
712
+ if (!fs6.existsSync(dir)) continue;
713
+ const hit = fs6.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
714
+ if (hit) return path5.join(dir, hit);
647
715
  }
648
716
  throw new SchemaMigrationError(
649
717
  `alembic revision succeeded but the created file could not be located.
@@ -777,15 +845,15 @@ async function buildDsn(args) {
777
845
  }
778
846
  function findVersionsDir(projectDir) {
779
847
  const candidates = [
780
- path5.join(projectDir, "migrations", "versions"),
781
- path5.join(projectDir, "alembic", "versions")
848
+ path6.join(projectDir, "migrations", "versions"),
849
+ path6.join(projectDir, "alembic", "versions")
782
850
  ];
783
- return candidates.find((p) => fs6.existsSync(p));
851
+ return candidates.find((p) => fs7.existsSync(p));
784
852
  }
785
853
  function listAlembicFiles(projectDir) {
786
854
  const dir = findVersionsDir(projectDir);
787
855
  if (!dir) return [];
788
- const files = fs6.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
856
+ const files = fs7.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
789
857
  return files.map((filename) => {
790
858
  const stem = filename.replace(/\.py$/, "");
791
859
  const sep2 = stem.indexOf("_");
@@ -810,9 +878,9 @@ var AlembicAdapter = {
810
878
  * here. Callers can still force-select via project.yaml#migration_tool.
811
879
  */
812
880
  detect(projectDir) {
813
- if (fs6.existsSync(path5.join(projectDir, "alembic.ini"))) return true;
814
- if (fs6.existsSync(path5.join(projectDir, "migrations", "env.py"))) return true;
815
- if (fs6.existsSync(path5.join(projectDir, "alembic", "env.py"))) return true;
881
+ if (fs7.existsSync(path6.join(projectDir, "alembic.ini"))) return true;
882
+ if (fs7.existsSync(path6.join(projectDir, "migrations", "env.py"))) return true;
883
+ if (fs7.existsSync(path6.join(projectDir, "alembic", "env.py"))) return true;
816
884
  return false;
817
885
  },
818
886
  async apply(args) {
@@ -918,7 +986,7 @@ var AlembicAdapter = {
918
986
  autogenerate: !!args.autogenerate,
919
987
  dsn
920
988
  });
921
- return { status: "ok", version: revId, filename: path5.basename(created), path: created };
989
+ return { status: "ok", version: revId, filename: path6.basename(created), path: created };
922
990
  } catch (err) {
923
991
  return {
924
992
  status: "error",
@@ -935,7 +1003,7 @@ var AlembicAdapter = {
935
1003
  if (heads.length <= 1) return { status: "noop", headsBefore: heads };
936
1004
  if (args.dryRun) return { status: "ok", headsBefore: heads };
937
1005
  const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
938
- const mergeRevision = path5.basename(created).replace(/\.py$/, "").split("_")[0];
1006
+ const mergeRevision = path6.basename(created).replace(/\.py$/, "").split("_")[0];
939
1007
  return { status: "ok", headsBefore: heads, mergeRevision, path: created };
940
1008
  } catch (err) {
941
1009
  return {
@@ -949,12 +1017,12 @@ var AlembicAdapter = {
949
1017
  registerSchemaMigrationAdapter(AlembicAdapter);
950
1018
 
951
1019
  // scripts/lakebase/adapters/flyway-adapter.ts
952
- import * as fs7 from "fs";
953
- import * as path7 from "path";
1020
+ import * as fs8 from "fs";
1021
+ import * as path8 from "path";
954
1022
 
955
1023
  // scripts/lakebase/schema-migrate-runners/flyway.ts
956
1024
  import { spawn as spawn2 } from "child_process";
957
- import * as path6 from "path";
1025
+ import * as path7 from "path";
958
1026
  function dsnToFlywayEnv(dsn) {
959
1027
  const u = new URL(dsn);
960
1028
  const user = decodeURIComponent(u.username);
@@ -964,7 +1032,7 @@ function dsnToFlywayEnv(dsn) {
964
1032
  return { url, user, password };
965
1033
  }
966
1034
  function migrationsLocation(projectDir) {
967
- return `filesystem:${path6.join(projectDir, "src", "main", "resources", "db", "migration")}`;
1035
+ return `filesystem:${flywayMigrationsDir(projectDir)}`;
968
1036
  }
969
1037
  function runFlyway(ctx, args) {
970
1038
  const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
@@ -1065,7 +1133,7 @@ async function statusFlyway(ctx) {
1065
1133
  if (state === "SUCCESS" || state === "BASELINE") {
1066
1134
  current = m.version;
1067
1135
  } else if (state === "PENDING") {
1068
- const filename = m.filepath ? path6.basename(m.filepath) : `V${m.version}__migration.sql`;
1136
+ const filename = m.filepath ? path7.basename(m.filepath) : `V${m.version}__migration.sql`;
1069
1137
  pending.push({
1070
1138
  version: m.version,
1071
1139
  filename,
@@ -1088,9 +1156,9 @@ async function buildDsn2(args) {
1088
1156
  return result.url;
1089
1157
  }
1090
1158
  function listFlywayFiles(projectDir) {
1091
- const dir = path7.join(projectDir, "src", "main", "resources", "db", "migration");
1092
- if (!fs7.existsSync(dir)) return [];
1093
- const files = fs7.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
1159
+ const dir = flywayMigrationsDir(projectDir);
1160
+ if (!fs8.existsSync(dir)) return [];
1161
+ const files = fs8.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
1094
1162
  return files.map((filename) => {
1095
1163
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
1096
1164
  const version = m[1];
@@ -1111,9 +1179,9 @@ function versionCompare(a, b) {
1111
1179
  }
1112
1180
  var FlywayAdapter = {
1113
1181
  id: "flyway",
1114
- languages: ["java", "kotlin"],
1182
+ languages: ["java", "kotlin", "dotnet"],
1115
1183
  detect(projectDir) {
1116
- return fs7.existsSync(path7.join(projectDir, "pom.xml"));
1184
+ return fs8.existsSync(path8.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
1117
1185
  },
1118
1186
  async apply(args) {
1119
1187
  const dsn = await buildDsn2(args);
@@ -1171,14 +1239,14 @@ var FlywayAdapter = {
1171
1239
  // optional-protocol shape makes this additive.
1172
1240
  async newMigration(args) {
1173
1241
  try {
1174
- const dir = path7.join(args.projectDir, "src", "main", "resources", "db", "migration");
1175
- fs7.mkdirSync(dir, { recursive: true });
1242
+ const dir = flywayMigrationsDir(args.projectDir);
1243
+ fs8.mkdirSync(dir, { recursive: true });
1176
1244
  const version = migrationTimestamp();
1177
1245
  const slug = migrationSlug2(args.slug);
1178
1246
  const filename = `V${version}__${slug}.sql`;
1179
- const full = path7.join(dir, filename);
1180
- if (fs7.existsSync(full)) throw new Error(`${filename} already exists`);
1181
- fs7.writeFileSync(
1247
+ const full = path8.join(dir, filename);
1248
+ if (fs8.existsSync(full)) throw new Error(`${filename} already exists`);
1249
+ fs8.writeFileSync(
1182
1250
  full,
1183
1251
  `-- V${version}: ${args.slug}
1184
1252
  -- Flyway migration (write your DDL/DML below).
@@ -1200,18 +1268,18 @@ var FlywayAdapter = {
1200
1268
  registerSchemaMigrationAdapter(FlywayAdapter);
1201
1269
 
1202
1270
  // scripts/lakebase/adapters/knex-adapter.ts
1203
- import * as fs9 from "fs";
1204
- import * as path9 from "path";
1271
+ import * as fs10 from "fs";
1272
+ import * as path10 from "path";
1205
1273
 
1206
1274
  // scripts/lakebase/schema-migrate-runners/knex.ts
1207
1275
  import { spawn as spawn3 } from "child_process";
1208
- import * as fs8 from "fs";
1209
- import * as path8 from "path";
1276
+ import * as fs9 from "fs";
1277
+ import * as path9 from "path";
1210
1278
  var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
1211
1279
  function findKnexfile(projectDir) {
1212
1280
  for (const name of KNEXFILE_VARIANTS) {
1213
- const p = path8.join(projectDir, name);
1214
- if (fs8.existsSync(p)) return p;
1281
+ const p = path9.join(projectDir, name);
1282
+ if (fs9.existsSync(p)) return p;
1215
1283
  }
1216
1284
  return void 0;
1217
1285
  }
@@ -1363,9 +1431,9 @@ async function buildDsn3(args) {
1363
1431
  }
1364
1432
  var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
1365
1433
  function listKnexFiles(projectDir) {
1366
- const dir = path9.join(projectDir, "migrations");
1367
- if (!fs9.existsSync(dir)) return [];
1368
- const files = fs9.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
1434
+ const dir = path10.join(projectDir, "migrations");
1435
+ if (!fs10.existsSync(dir)) return [];
1436
+ const files = fs10.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
1369
1437
  return files.map((filename) => {
1370
1438
  const stem = filename.replace(/\.(js|ts)$/, "");
1371
1439
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -1385,7 +1453,7 @@ var KnexAdapter = {
1385
1453
  * project.yaml#migration_tool.
1386
1454
  */
1387
1455
  detect(projectDir) {
1388
- return KNEXFILE_VARIANTS2.some((name) => fs9.existsSync(path9.join(projectDir, name)));
1456
+ return KNEXFILE_VARIANTS2.some((name) => fs10.existsSync(path10.join(projectDir, name)));
1389
1457
  },
1390
1458
  async apply(args) {
1391
1459
  const dsn = await buildDsn3(args);
@@ -1458,9 +1526,9 @@ var KnexAdapter = {
1458
1526
  async newMigration(args) {
1459
1527
  try {
1460
1528
  const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
1461
- const stem = path9.basename(created).replace(/\.(js|ts)$/, "");
1529
+ const stem = path10.basename(created).replace(/\.(js|ts)$/, "");
1462
1530
  const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
1463
- return { status: "ok", version, filename: path9.basename(created), path: created };
1531
+ return { status: "ok", version, filename: path10.basename(created), path: created };
1464
1532
  } catch (err) {
1465
1533
  return {
1466
1534
  status: "error",
@@ -1474,6 +1542,360 @@ var KnexAdapter = {
1474
1542
  };
1475
1543
  registerSchemaMigrationAdapter(KnexAdapter);
1476
1544
 
1545
+ // scripts/lakebase/adapters/efcore-adapter.ts
1546
+ import * as path12 from "path";
1547
+
1548
+ // scripts/lakebase/schema-migrate-runners/efcore.ts
1549
+ import { spawn as spawn4 } from "child_process";
1550
+ import * as fs11 from "fs";
1551
+ import * as path11 from "path";
1552
+ import { Client as Client2 } from "pg";
1553
+ var MIGRATION_ID = /^(\d{14})_(.+)$/;
1554
+ function efVersion(id) {
1555
+ return MIGRATION_ID.exec(id)?.[1] ?? id;
1556
+ }
1557
+ function efDescription(id) {
1558
+ const name = MIGRATION_ID.exec(id)?.[2] ?? id;
1559
+ return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
1560
+ }
1561
+ function dsnToNpgsql(dsn) {
1562
+ const u = new URL(dsn);
1563
+ const parts = [
1564
+ `Host=${u.hostname}`,
1565
+ `Port=${u.port || "5432"}`,
1566
+ `Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
1567
+ ];
1568
+ if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
1569
+ if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
1570
+ const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
1571
+ const ssl = {
1572
+ disable: "Disable",
1573
+ allow: "Allow",
1574
+ prefer: "Prefer",
1575
+ require: "Require",
1576
+ "verify-ca": "VerifyCA",
1577
+ "verify-full": "VerifyFull"
1578
+ };
1579
+ parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
1580
+ const app = u.searchParams.get("application_name");
1581
+ if (app) parts.push(`Application Name=${app}`);
1582
+ return parts.map((p) => p.replace(/;/g, "")).join(";");
1583
+ }
1584
+ function parseEfJson(stdout) {
1585
+ const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
1586
+ if (!body) {
1587
+ throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
1588
+ }
1589
+ try {
1590
+ return JSON.parse(body);
1591
+ } catch (err) {
1592
+ throw new SchemaMigrationError(
1593
+ `dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
1594
+ Body (first 400 chars): ${body.slice(0, 400)}`
1595
+ );
1596
+ }
1597
+ }
1598
+ function requireProject(projectDir) {
1599
+ const project = findEfCoreProject(projectDir);
1600
+ if (!project) {
1601
+ throw new SchemaMigrationError(
1602
+ `No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
1603
+ );
1604
+ }
1605
+ return project;
1606
+ }
1607
+ function spawnDotnet(projectDir, args, env) {
1608
+ return new Promise((resolve2, reject) => {
1609
+ const child = spawn4("dotnet", args, {
1610
+ cwd: projectDir,
1611
+ env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
1612
+ stdio: ["ignore", "pipe", "pipe"]
1613
+ });
1614
+ let stdout = "";
1615
+ let stderr = "";
1616
+ child.stdout.on("data", (chunk) => {
1617
+ stdout += chunk.toString("utf8");
1618
+ });
1619
+ child.stderr.on("data", (chunk) => {
1620
+ stderr += chunk.toString("utf8");
1621
+ });
1622
+ child.on("error", (err) => {
1623
+ reject(
1624
+ new SchemaMigrationError(
1625
+ `Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
1626
+ err
1627
+ )
1628
+ );
1629
+ });
1630
+ child.on("close", (code) => {
1631
+ if (code === 0) {
1632
+ resolve2({ stdout, stderr });
1633
+ } else {
1634
+ reject(
1635
+ new SchemaMigrationError(
1636
+ `dotnet ${args.join(" ")} exited with code ${code}.
1637
+ stdout: ${stdout}
1638
+ stderr: ${stderr}`
1639
+ )
1640
+ );
1641
+ }
1642
+ });
1643
+ });
1644
+ }
1645
+ var restored = /* @__PURE__ */ new Set();
1646
+ async function ensureRestored(projectDir, csproj) {
1647
+ if (restored.has(projectDir)) return;
1648
+ if (fs11.existsSync(path11.join(projectDir, ".config", "dotnet-tools.json"))) {
1649
+ await spawnDotnet(projectDir, ["tool", "restore"], {});
1650
+ }
1651
+ await spawnDotnet(projectDir, ["restore", csproj], {});
1652
+ restored.add(projectDir);
1653
+ }
1654
+ async function runEf(projectDir, args, dsn, opts = {}) {
1655
+ const project = requireProject(projectDir);
1656
+ const csproj = path11.relative(projectDir, project.csproj) || project.csproj;
1657
+ await ensureRestored(projectDir, csproj);
1658
+ const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
1659
+ if (opts.noBuild) full.push("--no-build");
1660
+ const env = {};
1661
+ if (dsn) {
1662
+ env.DATABASE_URL = dsn;
1663
+ if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
1664
+ }
1665
+ return spawnDotnet(projectDir, full, env);
1666
+ }
1667
+ async function listEfMigrations(ctx, opts = {}) {
1668
+ const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
1669
+ const entries = parseEfJson(stdout);
1670
+ if (ctx.dsn && entries.some((e) => e.applied === null)) {
1671
+ const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
1672
+ throw new SchemaMigrationError(
1673
+ `dotnet ef could not read migration state from the database (check connectivity and credentials).
1674
+ ${warning || stderr}`.trim()
1675
+ );
1676
+ }
1677
+ return entries;
1678
+ }
1679
+ async function readEfHistory(dsn) {
1680
+ const client = new Client2({ connectionString: dsn, application_name: connectionApplicationName() });
1681
+ await client.connect();
1682
+ try {
1683
+ const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
1684
+ if (!exists.rows[0]?.present) return null;
1685
+ const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
1686
+ return r.rows.map((row) => row.MigrationId);
1687
+ } finally {
1688
+ await client.end();
1689
+ }
1690
+ }
1691
+ function toApplied(entries) {
1692
+ return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
1693
+ }
1694
+ async function statusEfcore(ctx) {
1695
+ const entries = await listEfMigrations(ctx);
1696
+ const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
1697
+ const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
1698
+ const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
1699
+ return { current, pending, tool: "efcore" };
1700
+ }
1701
+ async function applyEfcore(ctx) {
1702
+ const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
1703
+ await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
1704
+ const after = await listEfMigrations(ctx, { noBuild: true });
1705
+ const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
1706
+ return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
1707
+ }
1708
+ function resolveEfTarget(target, applied) {
1709
+ const t = target.trim();
1710
+ if (t === "0") return "0";
1711
+ const rel = /^-(\d+)$/.exec(t);
1712
+ if (rel) {
1713
+ const steps = Number(rel[1]);
1714
+ if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
1715
+ const index = applied.length - 1 - steps;
1716
+ if (index < -1) {
1717
+ throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
1718
+ }
1719
+ return index === -1 ? "0" : applied[index].id;
1720
+ }
1721
+ const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
1722
+ if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
1723
+ return match.id;
1724
+ }
1725
+ async function rollbackEfcore(ctx) {
1726
+ const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
1727
+ const targetId = resolveEfTarget(ctx.target, beforeEntries);
1728
+ await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
1729
+ const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
1730
+ return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
1731
+ }
1732
+ function efMigrationName(slug) {
1733
+ const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
1734
+ if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
1735
+ return /^[0-9]/.test(name) ? `M${name}` : name;
1736
+ }
1737
+ async function createEfcoreMigration(opts) {
1738
+ const project = requireProject(opts.projectDir);
1739
+ const name = efMigrationName(opts.slug);
1740
+ const existing = new Set(fs11.existsSync(project.migrationsDir) ? fs11.readdirSync(project.migrationsDir) : []);
1741
+ await runEf(opts.projectDir, ["migrations", "add", name]);
1742
+ const created = fs11.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
1743
+ if (created.length !== 1) {
1744
+ throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
1745
+ }
1746
+ return { id: created[0].replace(/\.cs$/, ""), path: path11.join(project.migrationsDir, created[0]) };
1747
+ }
1748
+ function localEfMigrationIds(projectDir) {
1749
+ const project = findEfCoreProject(projectDir);
1750
+ if (!project || !fs11.existsSync(project.migrationsDir)) return [];
1751
+ return fs11.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();
1752
+ }
1753
+ function efProductVersion(project) {
1754
+ try {
1755
+ const csproj = fs11.readFileSync(project.csproj, "utf-8");
1756
+ const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
1757
+ if (m) return m[1];
1758
+ } catch {
1759
+ }
1760
+ return "unknown";
1761
+ }
1762
+ async function stampEfcore(ctx) {
1763
+ const project = requireProject(ctx.projectDir);
1764
+ const ids = localEfMigrationIds(ctx.projectDir);
1765
+ if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
1766
+ const rev = ctx.revision.trim();
1767
+ const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
1768
+ if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
1769
+ const toStamp = ids.slice(0, end + 1);
1770
+ const productVersion = efProductVersion(project);
1771
+ const client = new Client2({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
1772
+ await client.connect();
1773
+ try {
1774
+ await client.query("BEGIN");
1775
+ await client.query(
1776
+ `CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
1777
+ "MigrationId" character varying(150) NOT NULL,
1778
+ "ProductVersion" character varying(32) NOT NULL,
1779
+ CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
1780
+ );
1781
+ await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
1782
+ for (const id of toStamp) {
1783
+ await client.query(
1784
+ `INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
1785
+ ON CONFLICT ("MigrationId") DO NOTHING`,
1786
+ [id, productVersion]
1787
+ );
1788
+ }
1789
+ await client.query("COMMIT");
1790
+ } catch (err) {
1791
+ await client.query("ROLLBACK").catch(() => void 0);
1792
+ throw err;
1793
+ } finally {
1794
+ await client.end();
1795
+ }
1796
+ return efVersion(toStamp[toStamp.length - 1]);
1797
+ }
1798
+
1799
+ // scripts/lakebase/adapters/efcore-adapter.ts
1800
+ async function buildDsn4(args) {
1801
+ const result = await getConnection({
1802
+ output: "dsn",
1803
+ instance: args.instance,
1804
+ branch: args.branch,
1805
+ database: args.database,
1806
+ endpointName: args.endpointName
1807
+ });
1808
+ return result.url;
1809
+ }
1810
+ function errMsg(err) {
1811
+ return err instanceof Error ? err.message : String(err);
1812
+ }
1813
+ function listEfCoreFiles(projectDir) {
1814
+ return localEfMigrationIds(projectDir).map((id) => ({
1815
+ version: efVersion(id),
1816
+ filename: `${id}.cs`,
1817
+ description: efDescription(id),
1818
+ type: "CSharp",
1819
+ tool: "efcore"
1820
+ }));
1821
+ }
1822
+ var EfCoreAdapter = {
1823
+ id: "efcore",
1824
+ languages: ["dotnet"],
1825
+ /** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
1826
+ detect(projectDir) {
1827
+ return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
1828
+ },
1829
+ async apply(args) {
1830
+ try {
1831
+ const dsn = await buildDsn4(args);
1832
+ const r = await applyEfcore({ projectDir: args.projectDir, dsn });
1833
+ return {
1834
+ applied_migrations: r.applied,
1835
+ status: r.alreadyAtLatest ? "noop" : "ok",
1836
+ tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
1837
+ };
1838
+ } catch (err) {
1839
+ return { applied_migrations: [], status: "error", error: errMsg(err) };
1840
+ }
1841
+ },
1842
+ async rollback(args) {
1843
+ try {
1844
+ const dsn = await buildDsn4(args);
1845
+ const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
1846
+ return {
1847
+ rolled_back: r.rolledBack,
1848
+ status: r.rolledBack.length === 0 ? "noop" : "ok",
1849
+ tool_specific: { tool: r.tool }
1850
+ };
1851
+ } catch (err) {
1852
+ return { rolled_back: [], status: "error", error: errMsg(err) };
1853
+ }
1854
+ },
1855
+ async stamp(args) {
1856
+ try {
1857
+ const dsn = await buildDsn4(args);
1858
+ const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
1859
+ return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
1860
+ } catch (err) {
1861
+ return { status: "error", stamped_revision: null, error: errMsg(err) };
1862
+ }
1863
+ },
1864
+ async status(args) {
1865
+ try {
1866
+ const dsn = await buildDsn4(args);
1867
+ const r = await statusEfcore({ projectDir: args.projectDir, dsn });
1868
+ return {
1869
+ applied_version: r.current ?? null,
1870
+ pending: r.pending,
1871
+ applied: [],
1872
+ status: "ok",
1873
+ tool_specific: { tool: r.tool }
1874
+ };
1875
+ } catch (err) {
1876
+ return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
1877
+ }
1878
+ },
1879
+ async list(args) {
1880
+ return { files: listEfCoreFiles(args.projectDir) };
1881
+ },
1882
+ // baseline: use stamp (EF has no separate baseline concept).
1883
+ async newMigration(args) {
1884
+ try {
1885
+ const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
1886
+ return {
1887
+ status: "ok",
1888
+ version: efVersion(created.id),
1889
+ filename: path12.basename(created.path),
1890
+ path: created.path
1891
+ };
1892
+ } catch (err) {
1893
+ return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
1894
+ }
1895
+ }
1896
+ };
1897
+ registerSchemaMigrationAdapter(EfCoreAdapter);
1898
+
1477
1899
  // scripts/lakebase/schema-migrate.ts
1478
1900
  var SchemaMigrationError = class extends Error {
1479
1901
  constructor(message, cause) {
@@ -1487,13 +1909,16 @@ function detectLanguage(projectDir) {
1487
1909
  const lang = resolveMigrationLanguage(projectDir);
1488
1910
  if (lang === "unknown") {
1489
1911
  throw new SchemaMigrationError(
1490
- `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.`
1912
+ `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.`
1491
1913
  );
1492
1914
  }
1493
1915
  return lang;
1494
1916
  }
1495
- function toolForLanguage(language) {
1917
+ function toolForLanguage(language, projectDir) {
1496
1918
  switch (language) {
1919
+ case "dotnet":
1920
+ if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
1921
+ return "efcore";
1497
1922
  case "java":
1498
1923
  case "kotlin":
1499
1924
  return "flyway";
@@ -1506,8 +1931,10 @@ function toolForLanguage(language) {
1506
1931
  function listSchemaMigrations(args = {}) {
1507
1932
  const projectDir = args.projectDir ?? process.cwd();
1508
1933
  const language = args.language ?? detectLanguage(projectDir);
1509
- const tool = toolForLanguage(language);
1934
+ const tool = toolForLanguage(language, projectDir);
1510
1935
  switch (tool) {
1936
+ case "efcore":
1937
+ return listEfCoreFiles(projectDir);
1511
1938
  case "flyway":
1512
1939
  return listFlywayMigrations(projectDir);
1513
1940
  case "alembic":
@@ -1517,9 +1944,9 @@ function listSchemaMigrations(args = {}) {
1517
1944
  }
1518
1945
  }
1519
1946
  function listFlywayMigrations(projectDir) {
1520
- const dir = path10.join(projectDir, "src", "main", "resources", "db", "migration");
1521
- if (!fs10.existsSync(dir)) return [];
1522
- const files = fs10.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
1947
+ const dir = flywayMigrationsDir(projectDir);
1948
+ if (!fs12.existsSync(dir)) return [];
1949
+ const files = fs12.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
1523
1950
  return files.map((filename) => {
1524
1951
  const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
1525
1952
  const version = m[1];
@@ -1529,12 +1956,12 @@ function listFlywayMigrations(projectDir) {
1529
1956
  }
1530
1957
  function listAlembicMigrations(projectDir) {
1531
1958
  const candidates = [
1532
- path10.join(projectDir, "migrations", "versions"),
1533
- path10.join(projectDir, "alembic", "versions")
1959
+ path13.join(projectDir, "migrations", "versions"),
1960
+ path13.join(projectDir, "alembic", "versions")
1534
1961
  ];
1535
- const dir = candidates.find((p) => fs10.existsSync(p));
1962
+ const dir = candidates.find((p) => fs12.existsSync(p));
1536
1963
  if (!dir) return [];
1537
- const files = fs10.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
1964
+ const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
1538
1965
  return files.map((filename) => {
1539
1966
  const stem = filename.replace(/\.py$/, "");
1540
1967
  const sep2 = stem.indexOf("_");
@@ -1544,9 +1971,9 @@ function listAlembicMigrations(projectDir) {
1544
1971
  }).sort((a, b) => a.filename.localeCompare(b.filename));
1545
1972
  }
1546
1973
  function listKnexMigrations(projectDir) {
1547
- const dir = path10.join(projectDir, "migrations");
1548
- if (!fs10.existsSync(dir)) return [];
1549
- const files = fs10.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
1974
+ const dir = path13.join(projectDir, "migrations");
1975
+ if (!fs12.existsSync(dir)) return [];
1976
+ const files = fs12.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
1550
1977
  return files.map((filename) => {
1551
1978
  const stem = filename.replace(/\.(js|ts)$/, "");
1552
1979
  const m = stem.match(/^(\d{14})_(.+)$/);
@@ -1568,7 +1995,7 @@ function versionCompare2(a, b) {
1568
1995
  return 0;
1569
1996
  }
1570
1997
  function adapterFor(projectDir, language) {
1571
- const override = language ? toolForLanguage(language) : void 0;
1998
+ const override = language ? toolForLanguage(language, projectDir) : void 0;
1572
1999
  return resolveSchemaMigrationAdapter(projectDir, override);
1573
2000
  }
1574
2001
  function dbRevisionOrphaned(appliedRevision, localRevisionIds) {
@@ -1708,8 +2135,8 @@ async function reconcileTierBranch(args, deps = {}) {
1708
2135
  }
1709
2136
 
1710
2137
  // scripts/lakebase/scm-workflow-state.ts
1711
- import * as fs11 from "fs";
1712
- import * as path11 from "path";
2138
+ import * as fs13 from "fs";
2139
+ import * as path14 from "path";
1713
2140
  import { execFileSync as execFileSync3 } from "child_process";
1714
2141
  var SCM_STATES = [
1715
2142
  "scaffold-complete",
@@ -1724,12 +2151,12 @@ var STATE_INDEX = SCM_STATES.reduce(
1724
2151
  );
1725
2152
  var STATE_FILE_REL = ".lakebase/workflow-state.json";
1726
2153
  function stateFilePath(projectDir) {
1727
- return path11.join(projectDir, STATE_FILE_REL);
2154
+ return path14.join(projectDir, STATE_FILE_REL);
1728
2155
  }
1729
2156
  function readWorkflowState(projectDir) {
1730
2157
  const p = stateFilePath(projectDir);
1731
- if (!fs11.existsSync(p)) return null;
1732
- const raw = fs11.readFileSync(p, "utf8");
2158
+ if (!fs13.existsSync(p)) return null;
2159
+ const raw = fs13.readFileSync(p, "utf8");
1733
2160
  let parsed;
1734
2161
  try {
1735
2162
  parsed = JSON.parse(raw);