@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.
- package/README.md +10 -4
- package/dist/scripts/index.cjs +2813 -2244
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +857 -298
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2460 -2030
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +508 -81
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +2429 -2007
- package/dist/scripts/lakebase/collapse-heads.cli.js +477 -58
- package/dist/scripts/lakebase/detect-language.cli.cjs +2441 -2016
- package/dist/scripts/lakebase/detect-language.cli.js +489 -67
- package/dist/scripts/lakebase/doctor.cli.cjs +2484 -2040
- package/dist/scripts/lakebase/doctor.cli.js +532 -91
- package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
- package/dist/scripts/lakebase/get-connection.cli.js +2 -2
- package/dist/scripts/lakebase/index.cjs +2810 -2205
- package/dist/scripts/lakebase/index.d.cts +59 -10
- package/dist/scripts/lakebase/index.d.ts +59 -10
- package/dist/scripts/lakebase/index.js +851 -256
- package/dist/scripts/lakebase/infra-runner.cli.cjs +2435 -2013
- package/dist/scripts/lakebase/infra-runner.cli.js +483 -64
- package/dist/scripts/lakebase/new-migration.cli.cjs +2429 -2007
- package/dist/scripts/lakebase/new-migration.cli.js +477 -58
- package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +2461 -2029
- package/dist/scripts/lakebase/schema-migrate.cli.js +509 -80
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2460 -2030
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +508 -81
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +2486 -2056
- package/dist/scripts/lakebase/scm-doctor.cli.js +537 -107
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2443 -2021
- package/dist/scripts/lakebase/scm-merge.cli.js +494 -72
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2464 -2034
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +511 -84
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/scripts/lakebase/adapters/efcore-adapter.ts +158 -0
- package/scripts/lakebase/adapters/flyway-adapter.ts +8 -5
- package/scripts/lakebase/client-lockfile.ts +102 -0
- package/scripts/lakebase/collapse-heads.cli.ts +1 -1
- package/scripts/lakebase/create-project.ts +25 -2
- package/scripts/lakebase/detect-language.cli.ts +2 -2
- package/scripts/lakebase/doctor.ts +29 -0
- package/scripts/lakebase/dotnet-project.ts +112 -0
- package/scripts/lakebase/index.ts +1 -0
- package/scripts/lakebase/migration-layout.ts +43 -2
- package/scripts/lakebase/new-migration.cli.ts +1 -1
- package/scripts/lakebase/scaffold-language.ts +23 -2
- package/scripts/lakebase/scaffold.ts +4 -1
- package/scripts/lakebase/schema-migrate-runners/efcore.ts +399 -0
- package/scripts/lakebase/schema-migrate-runners/flyway.ts +6 -4
- package/scripts/lakebase/schema-migrate.cli.ts +3 -1
- package/scripts/lakebase/schema-migrate.ts +24 -10
- package/templates/project/common/.github/workflows/merge.yml +13 -1
- package/templates/project/common/.github/workflows/pr.yml +25 -5
- package/templates/project/common/scripts/flyway-migrate.sh +54 -1
- package/templates/project/common/scripts/prepare-schema-diff.sh +5 -0
- package/templates/project/common/scripts/run-dev.sh +10 -1
- package/templates/project/common/scripts/run-tests.sh +6 -1
- package/templates/project/dotnet/.gitignore.extra +8 -0
- package/templates/project/dotnet/App.sln +56 -0
- package/templates/project/dotnet/Makefile +13 -0
- package/templates/project/dotnet/global.json +6 -0
- package/templates/project/dotnet/src/App/Data/AppDbContext.cs +12 -0
- package/templates/project/dotnet/src/App/Lakebase/DotEnv.cs +48 -0
- package/templates/project/dotnet/src/App/Lakebase/LakebaseConnection.cs +225 -0
- package/templates/project/dotnet/src/App/Program.cs +34 -0
- package/templates/project/dotnet/src/App/appsettings.json +9 -0
- package/templates/project/dotnet/tests/App.Tests/App.Tests.csproj +25 -0
- package/templates/project/dotnet/tests/App.Tests/HealthTests.cs +18 -0
- package/templates/project/dotnet/tests/App.Tests/OpenApiTests.cs +23 -0
- package/templates/project/dotnet-efcore/.config/dotnet-tools.json +13 -0
- package/templates/project/dotnet-efcore/src/App/App.csproj +22 -0
- package/templates/project/dotnet-efcore/src/App/Data/DesignTimeDbContextFactory.cs +24 -0
- package/templates/project/dotnet-efcore/src/App/Migrations/README.md +23 -0
- package/templates/project/dotnet-flyway/db/migrations/README.md +23 -0
- package/templates/project/dotnet-flyway/src/App/App.csproj +17 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// scripts/lakebase/scm-claim-feature.cli.ts
|
|
4
|
-
import * as
|
|
4
|
+
import * as path17 from "path";
|
|
5
5
|
|
|
6
6
|
// scripts/util/cli-entry.ts
|
|
7
7
|
import { realpathSync } from "fs";
|
|
@@ -452,9 +452,9 @@ function asBranchUid(s) {
|
|
|
452
452
|
}
|
|
453
453
|
return s;
|
|
454
454
|
}
|
|
455
|
-
function branchNameFromResourcePath(
|
|
456
|
-
if (!
|
|
457
|
-
const leaf =
|
|
455
|
+
function branchNameFromResourcePath(path18) {
|
|
456
|
+
if (!path18.includes("/branches/")) return null;
|
|
457
|
+
const leaf = path18.split("/branches/").pop();
|
|
458
458
|
if (!leaf) return null;
|
|
459
459
|
try {
|
|
460
460
|
return asBranchName(leaf);
|
|
@@ -864,8 +864,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
864
864
|
var cached;
|
|
865
865
|
function substrateSelfVersion() {
|
|
866
866
|
if (cached !== void 0) return cached;
|
|
867
|
-
if ("0.
|
|
868
|
-
cached = "0.
|
|
867
|
+
if ("0.3.1".length > 0) {
|
|
868
|
+
cached = "0.3.1";
|
|
869
869
|
return cached;
|
|
870
870
|
}
|
|
871
871
|
cached = "unknown";
|
|
@@ -1745,27 +1745,92 @@ function alreadyClaimedSentinel(state) {
|
|
|
1745
1745
|
}
|
|
1746
1746
|
|
|
1747
1747
|
// scripts/lakebase/schema-migrate.ts
|
|
1748
|
-
import * as
|
|
1749
|
-
import * as
|
|
1748
|
+
import * as fs15 from "fs";
|
|
1749
|
+
import * as path16 from "path";
|
|
1750
1750
|
|
|
1751
1751
|
// scripts/lakebase/migration-layout.ts
|
|
1752
|
+
import * as fs8 from "fs";
|
|
1753
|
+
import * as path7 from "path";
|
|
1754
|
+
|
|
1755
|
+
// scripts/lakebase/dotnet-project.ts
|
|
1752
1756
|
import * as fs7 from "fs";
|
|
1753
1757
|
import * as path6 from "path";
|
|
1758
|
+
var EF_DESIGN_PACKAGE = "Microsoft.EntityFrameworkCore.Design";
|
|
1759
|
+
var DOTNET_FLYWAY_DIR = path6.join("db", "migrations");
|
|
1760
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", "client", "tests", "test"]);
|
|
1761
|
+
function safeReaddir(dir) {
|
|
1762
|
+
try {
|
|
1763
|
+
return fs7.readdirSync(dir, { withFileTypes: true });
|
|
1764
|
+
} catch {
|
|
1765
|
+
return [];
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function hasDotnetMarker(dir) {
|
|
1769
|
+
return safeReaddir(dir).some(
|
|
1770
|
+
(e) => e.isFile() && /\.(sln|slnx|csproj)$/i.test(e.name)
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
function findCsprojFiles(projectDir) {
|
|
1774
|
+
const found = [];
|
|
1775
|
+
const walk = (dir, depth) => {
|
|
1776
|
+
for (const e of safeReaddir(dir)) {
|
|
1777
|
+
const full = path6.join(dir, e.name);
|
|
1778
|
+
if (e.isFile() && e.name.toLowerCase().endsWith(".csproj")) {
|
|
1779
|
+
found.push(full);
|
|
1780
|
+
} else if (e.isDirectory() && depth < 2 && !SKIP_DIRS.has(e.name.toLowerCase()) && !e.name.startsWith(".")) {
|
|
1781
|
+
walk(full, depth + 1);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1785
|
+
walk(projectDir, 0);
|
|
1786
|
+
return found.sort();
|
|
1787
|
+
}
|
|
1788
|
+
function findEfCoreProject(projectDir) {
|
|
1789
|
+
for (const csproj of findCsprojFiles(projectDir)) {
|
|
1790
|
+
let content = "";
|
|
1791
|
+
try {
|
|
1792
|
+
content = fs7.readFileSync(csproj, "utf-8");
|
|
1793
|
+
} catch {
|
|
1794
|
+
continue;
|
|
1795
|
+
}
|
|
1796
|
+
if (content.includes(EF_DESIGN_PACKAGE)) {
|
|
1797
|
+
const dir = path6.dirname(csproj);
|
|
1798
|
+
return { csproj, projectDir: dir, migrationsDir: path6.join(dir, "Migrations") };
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return null;
|
|
1802
|
+
}
|
|
1803
|
+
function isDotnetProject(projectDir) {
|
|
1804
|
+
return hasDotnetMarker(projectDir);
|
|
1805
|
+
}
|
|
1806
|
+
function isDotnetFlywayProject(projectDir) {
|
|
1807
|
+
return isDotnetProject(projectDir) && fs7.existsSync(path6.join(projectDir, DOTNET_FLYWAY_DIR));
|
|
1808
|
+
}
|
|
1809
|
+
var MAVEN_FLYWAY_DIR = path6.join("src", "main", "resources", "db", "migration");
|
|
1810
|
+
function flywayMigrationsDir(projectDir) {
|
|
1811
|
+
const maven = path6.join(projectDir, MAVEN_FLYWAY_DIR);
|
|
1812
|
+
if (fs7.existsSync(maven) || fs7.existsSync(path6.join(projectDir, "pom.xml"))) return maven;
|
|
1813
|
+
if (isDotnetFlywayProject(projectDir)) return path6.join(projectDir, DOTNET_FLYWAY_DIR);
|
|
1814
|
+
return maven;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// scripts/lakebase/migration-layout.ts
|
|
1754
1818
|
var MIGRATION_LANGUAGES = [
|
|
1755
1819
|
"java",
|
|
1756
1820
|
"kotlin",
|
|
1757
1821
|
"python",
|
|
1758
1822
|
"nodejs",
|
|
1823
|
+
"dotnet",
|
|
1759
1824
|
"unknown"
|
|
1760
1825
|
];
|
|
1761
1826
|
function detectLanguageAt(dir) {
|
|
1762
|
-
if (
|
|
1763
|
-
const kotlinDir =
|
|
1764
|
-
if (
|
|
1827
|
+
if (fs8.existsSync(path7.join(dir, "pom.xml"))) {
|
|
1828
|
+
const kotlinDir = path7.join(dir, "src", "main", "kotlin");
|
|
1829
|
+
if (fs8.existsSync(kotlinDir)) {
|
|
1765
1830
|
return "kotlin";
|
|
1766
1831
|
}
|
|
1767
1832
|
try {
|
|
1768
|
-
const pom =
|
|
1833
|
+
const pom = fs8.readFileSync(path7.join(dir, "pom.xml"), "utf-8");
|
|
1769
1834
|
if (pom.includes("kotlin-maven-plugin")) {
|
|
1770
1835
|
return "kotlin";
|
|
1771
1836
|
}
|
|
@@ -1773,10 +1838,13 @@ function detectLanguageAt(dir) {
|
|
|
1773
1838
|
}
|
|
1774
1839
|
return "java";
|
|
1775
1840
|
}
|
|
1776
|
-
if (
|
|
1841
|
+
if (fs8.existsSync(path7.join(dir, "pyproject.toml")) || fs8.existsSync(path7.join(dir, "requirements.txt")) || fs8.existsSync(path7.join(dir, "alembic.ini"))) {
|
|
1777
1842
|
return "python";
|
|
1778
1843
|
}
|
|
1779
|
-
if (
|
|
1844
|
+
if (hasDotnetMarker(dir)) {
|
|
1845
|
+
return "dotnet";
|
|
1846
|
+
}
|
|
1847
|
+
if (fs8.existsSync(path7.join(dir, "package.json"))) {
|
|
1780
1848
|
return "nodejs";
|
|
1781
1849
|
}
|
|
1782
1850
|
return "unknown";
|
|
@@ -1797,14 +1865,14 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
|
|
|
1797
1865
|
if (!rel) {
|
|
1798
1866
|
return "unknown";
|
|
1799
1867
|
}
|
|
1800
|
-
const rootResolved =
|
|
1801
|
-
let dir =
|
|
1802
|
-
while (dir === rootResolved || dir.startsWith(rootResolved +
|
|
1868
|
+
const rootResolved = path7.resolve(projectDir);
|
|
1869
|
+
let dir = path7.resolve(projectDir, rel);
|
|
1870
|
+
while (dir === rootResolved || dir.startsWith(rootResolved + path7.sep)) {
|
|
1803
1871
|
const lang = detectLanguageAt(dir);
|
|
1804
1872
|
if (lang !== "unknown") {
|
|
1805
1873
|
return lang;
|
|
1806
1874
|
}
|
|
1807
|
-
const parent =
|
|
1875
|
+
const parent = path7.dirname(dir);
|
|
1808
1876
|
if (parent === dir) {
|
|
1809
1877
|
break;
|
|
1810
1878
|
}
|
|
@@ -1814,21 +1882,21 @@ function resolveMigrationLanguage(projectDir, configuredMigrationPath, override)
|
|
|
1814
1882
|
}
|
|
1815
1883
|
|
|
1816
1884
|
// scripts/lakebase/adapters/alembic-adapter.ts
|
|
1817
|
-
import * as
|
|
1818
|
-
import * as
|
|
1885
|
+
import * as fs10 from "fs";
|
|
1886
|
+
import * as path9 from "path";
|
|
1819
1887
|
|
|
1820
1888
|
// scripts/lakebase/schema-migrate-runners/alembic.ts
|
|
1821
1889
|
import { spawn } from "child_process";
|
|
1822
|
-
import * as
|
|
1823
|
-
import * as
|
|
1890
|
+
import * as fs9 from "fs";
|
|
1891
|
+
import * as path8 from "path";
|
|
1824
1892
|
function resolveAlembicBin(projectDir) {
|
|
1825
1893
|
const candidates = [
|
|
1826
|
-
|
|
1827
|
-
|
|
1894
|
+
path8.join(projectDir, ".venv", "bin", "alembic"),
|
|
1895
|
+
path8.join(projectDir, "venv", "bin", "alembic")
|
|
1828
1896
|
];
|
|
1829
1897
|
for (const candidate of candidates) {
|
|
1830
1898
|
try {
|
|
1831
|
-
if (
|
|
1899
|
+
if (fs9.existsSync(candidate)) return candidate;
|
|
1832
1900
|
} catch {
|
|
1833
1901
|
}
|
|
1834
1902
|
}
|
|
@@ -1838,7 +1906,7 @@ function spawnAlembic(projectDir, args, dsn) {
|
|
|
1838
1906
|
return new Promise((resolve3, reject) => {
|
|
1839
1907
|
const bin = resolveAlembicBin(projectDir);
|
|
1840
1908
|
const env = { ...process.env };
|
|
1841
|
-
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(
|
|
1909
|
+
env.PYTHONPATH = [projectDir, process.env.PYTHONPATH].filter(Boolean).join(path8.delimiter);
|
|
1842
1910
|
if (dsn) env.DATABASE_URL = dsn;
|
|
1843
1911
|
const child = spawn(bin, args, {
|
|
1844
1912
|
cwd: projectDir,
|
|
@@ -1886,10 +1954,10 @@ async function createAlembicRevision(opts) {
|
|
|
1886
1954
|
const m = stdout.match(/Generating\s+(\S+\.py)/);
|
|
1887
1955
|
if (m) return m[1].trim();
|
|
1888
1956
|
for (const rel of ["migrations/versions", "alembic/versions"]) {
|
|
1889
|
-
const dir =
|
|
1890
|
-
if (!
|
|
1891
|
-
const hit =
|
|
1892
|
-
if (hit) return
|
|
1957
|
+
const dir = path8.join(opts.projectDir, rel);
|
|
1958
|
+
if (!fs9.existsSync(dir)) continue;
|
|
1959
|
+
const hit = fs9.readdirSync(dir).find((f) => f.startsWith(`${opts.revId}_`) && f.endsWith(".py"));
|
|
1960
|
+
if (hit) return path8.join(dir, hit);
|
|
1893
1961
|
}
|
|
1894
1962
|
throw new SchemaMigrationError(
|
|
1895
1963
|
`alembic revision succeeded but the created file could not be located.
|
|
@@ -2023,15 +2091,15 @@ async function buildDsn(args) {
|
|
|
2023
2091
|
}
|
|
2024
2092
|
function findVersionsDir(projectDir) {
|
|
2025
2093
|
const candidates = [
|
|
2026
|
-
|
|
2027
|
-
|
|
2094
|
+
path9.join(projectDir, "migrations", "versions"),
|
|
2095
|
+
path9.join(projectDir, "alembic", "versions")
|
|
2028
2096
|
];
|
|
2029
|
-
return candidates.find((p) =>
|
|
2097
|
+
return candidates.find((p) => fs10.existsSync(p));
|
|
2030
2098
|
}
|
|
2031
2099
|
function listAlembicFiles(projectDir) {
|
|
2032
2100
|
const dir = findVersionsDir(projectDir);
|
|
2033
2101
|
if (!dir) return [];
|
|
2034
|
-
const files =
|
|
2102
|
+
const files = fs10.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
|
|
2035
2103
|
return files.map((filename) => {
|
|
2036
2104
|
const stem = filename.replace(/\.py$/, "");
|
|
2037
2105
|
const sep2 = stem.indexOf("_");
|
|
@@ -2056,9 +2124,9 @@ var AlembicAdapter = {
|
|
|
2056
2124
|
* here. Callers can still force-select via project.yaml#migration_tool.
|
|
2057
2125
|
*/
|
|
2058
2126
|
detect(projectDir) {
|
|
2059
|
-
if (
|
|
2060
|
-
if (
|
|
2061
|
-
if (
|
|
2127
|
+
if (fs10.existsSync(path9.join(projectDir, "alembic.ini"))) return true;
|
|
2128
|
+
if (fs10.existsSync(path9.join(projectDir, "migrations", "env.py"))) return true;
|
|
2129
|
+
if (fs10.existsSync(path9.join(projectDir, "alembic", "env.py"))) return true;
|
|
2062
2130
|
return false;
|
|
2063
2131
|
},
|
|
2064
2132
|
async apply(args) {
|
|
@@ -2164,7 +2232,7 @@ var AlembicAdapter = {
|
|
|
2164
2232
|
autogenerate: !!args.autogenerate,
|
|
2165
2233
|
dsn
|
|
2166
2234
|
});
|
|
2167
|
-
return { status: "ok", version: revId, filename:
|
|
2235
|
+
return { status: "ok", version: revId, filename: path9.basename(created), path: created };
|
|
2168
2236
|
} catch (err) {
|
|
2169
2237
|
return {
|
|
2170
2238
|
status: "error",
|
|
@@ -2181,7 +2249,7 @@ var AlembicAdapter = {
|
|
|
2181
2249
|
if (heads.length <= 1) return { status: "noop", headsBefore: heads };
|
|
2182
2250
|
if (args.dryRun) return { status: "ok", headsBefore: heads };
|
|
2183
2251
|
const created = await mergeAlembicHeads(args.projectDir, args.message ?? "merge heads");
|
|
2184
|
-
const mergeRevision =
|
|
2252
|
+
const mergeRevision = path9.basename(created).replace(/\.py$/, "").split("_")[0];
|
|
2185
2253
|
return { status: "ok", headsBefore: heads, mergeRevision, path: created };
|
|
2186
2254
|
} catch (err) {
|
|
2187
2255
|
return {
|
|
@@ -2195,12 +2263,12 @@ var AlembicAdapter = {
|
|
|
2195
2263
|
registerSchemaMigrationAdapter(AlembicAdapter);
|
|
2196
2264
|
|
|
2197
2265
|
// scripts/lakebase/adapters/flyway-adapter.ts
|
|
2198
|
-
import * as
|
|
2199
|
-
import * as
|
|
2266
|
+
import * as fs11 from "fs";
|
|
2267
|
+
import * as path11 from "path";
|
|
2200
2268
|
|
|
2201
2269
|
// scripts/lakebase/schema-migrate-runners/flyway.ts
|
|
2202
2270
|
import { spawn as spawn2 } from "child_process";
|
|
2203
|
-
import * as
|
|
2271
|
+
import * as path10 from "path";
|
|
2204
2272
|
function dsnToFlywayEnv(dsn) {
|
|
2205
2273
|
const u = new URL(dsn);
|
|
2206
2274
|
const user = decodeURIComponent(u.username);
|
|
@@ -2210,7 +2278,7 @@ function dsnToFlywayEnv(dsn) {
|
|
|
2210
2278
|
return { url, user, password };
|
|
2211
2279
|
}
|
|
2212
2280
|
function migrationsLocation(projectDir) {
|
|
2213
|
-
return `filesystem:${
|
|
2281
|
+
return `filesystem:${flywayMigrationsDir(projectDir)}`;
|
|
2214
2282
|
}
|
|
2215
2283
|
function runFlyway(ctx, args) {
|
|
2216
2284
|
const { url, user, password } = dsnToFlywayEnv(ctx.dsn);
|
|
@@ -2311,7 +2379,7 @@ async function statusFlyway(ctx) {
|
|
|
2311
2379
|
if (state === "SUCCESS" || state === "BASELINE") {
|
|
2312
2380
|
current = m.version;
|
|
2313
2381
|
} else if (state === "PENDING") {
|
|
2314
|
-
const filename = m.filepath ?
|
|
2382
|
+
const filename = m.filepath ? path10.basename(m.filepath) : `V${m.version}__migration.sql`;
|
|
2315
2383
|
pending.push({
|
|
2316
2384
|
version: m.version,
|
|
2317
2385
|
filename,
|
|
@@ -2334,9 +2402,9 @@ async function buildDsn2(args) {
|
|
|
2334
2402
|
return result.url;
|
|
2335
2403
|
}
|
|
2336
2404
|
function listFlywayFiles(projectDir) {
|
|
2337
|
-
const dir =
|
|
2338
|
-
if (!
|
|
2339
|
-
const files =
|
|
2405
|
+
const dir = flywayMigrationsDir(projectDir);
|
|
2406
|
+
if (!fs11.existsSync(dir)) return [];
|
|
2407
|
+
const files = fs11.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
|
|
2340
2408
|
return files.map((filename) => {
|
|
2341
2409
|
const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
|
|
2342
2410
|
const version = m[1];
|
|
@@ -2357,9 +2425,9 @@ function versionCompare(a, b) {
|
|
|
2357
2425
|
}
|
|
2358
2426
|
var FlywayAdapter = {
|
|
2359
2427
|
id: "flyway",
|
|
2360
|
-
languages: ["java", "kotlin"],
|
|
2428
|
+
languages: ["java", "kotlin", "dotnet"],
|
|
2361
2429
|
detect(projectDir) {
|
|
2362
|
-
return
|
|
2430
|
+
return fs11.existsSync(path11.join(projectDir, "pom.xml")) || isDotnetFlywayProject(projectDir);
|
|
2363
2431
|
},
|
|
2364
2432
|
async apply(args) {
|
|
2365
2433
|
const dsn = await buildDsn2(args);
|
|
@@ -2417,14 +2485,14 @@ var FlywayAdapter = {
|
|
|
2417
2485
|
// optional-protocol shape makes this additive.
|
|
2418
2486
|
async newMigration(args) {
|
|
2419
2487
|
try {
|
|
2420
|
-
const dir =
|
|
2421
|
-
|
|
2488
|
+
const dir = flywayMigrationsDir(args.projectDir);
|
|
2489
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
2422
2490
|
const version = migrationTimestamp();
|
|
2423
2491
|
const slug = migrationSlug2(args.slug);
|
|
2424
2492
|
const filename = `V${version}__${slug}.sql`;
|
|
2425
|
-
const full =
|
|
2426
|
-
if (
|
|
2427
|
-
|
|
2493
|
+
const full = path11.join(dir, filename);
|
|
2494
|
+
if (fs11.existsSync(full)) throw new Error(`${filename} already exists`);
|
|
2495
|
+
fs11.writeFileSync(
|
|
2428
2496
|
full,
|
|
2429
2497
|
`-- V${version}: ${args.slug}
|
|
2430
2498
|
-- Flyway migration (write your DDL/DML below).
|
|
@@ -2446,18 +2514,18 @@ var FlywayAdapter = {
|
|
|
2446
2514
|
registerSchemaMigrationAdapter(FlywayAdapter);
|
|
2447
2515
|
|
|
2448
2516
|
// scripts/lakebase/adapters/knex-adapter.ts
|
|
2449
|
-
import * as
|
|
2450
|
-
import * as
|
|
2517
|
+
import * as fs13 from "fs";
|
|
2518
|
+
import * as path13 from "path";
|
|
2451
2519
|
|
|
2452
2520
|
// scripts/lakebase/schema-migrate-runners/knex.ts
|
|
2453
2521
|
import { spawn as spawn3 } from "child_process";
|
|
2454
|
-
import * as
|
|
2455
|
-
import * as
|
|
2522
|
+
import * as fs12 from "fs";
|
|
2523
|
+
import * as path12 from "path";
|
|
2456
2524
|
var KNEXFILE_VARIANTS = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
2457
2525
|
function findKnexfile(projectDir) {
|
|
2458
2526
|
for (const name of KNEXFILE_VARIANTS) {
|
|
2459
|
-
const p =
|
|
2460
|
-
if (
|
|
2527
|
+
const p = path12.join(projectDir, name);
|
|
2528
|
+
if (fs12.existsSync(p)) return p;
|
|
2461
2529
|
}
|
|
2462
2530
|
return void 0;
|
|
2463
2531
|
}
|
|
@@ -2609,9 +2677,9 @@ async function buildDsn3(args) {
|
|
|
2609
2677
|
}
|
|
2610
2678
|
var KNEXFILE_VARIANTS2 = ["knexfile.js", "knexfile.ts", "knexfile.mjs", "knexfile.cjs"];
|
|
2611
2679
|
function listKnexFiles(projectDir) {
|
|
2612
|
-
const dir =
|
|
2613
|
-
if (!
|
|
2614
|
-
const files =
|
|
2680
|
+
const dir = path13.join(projectDir, "migrations");
|
|
2681
|
+
if (!fs13.existsSync(dir)) return [];
|
|
2682
|
+
const files = fs13.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
|
|
2615
2683
|
return files.map((filename) => {
|
|
2616
2684
|
const stem = filename.replace(/\.(js|ts)$/, "");
|
|
2617
2685
|
const m = stem.match(/^(\d{14})_(.+)$/);
|
|
@@ -2631,7 +2699,7 @@ var KnexAdapter = {
|
|
|
2631
2699
|
* project.yaml#migration_tool.
|
|
2632
2700
|
*/
|
|
2633
2701
|
detect(projectDir) {
|
|
2634
|
-
return KNEXFILE_VARIANTS2.some((name) =>
|
|
2702
|
+
return KNEXFILE_VARIANTS2.some((name) => fs13.existsSync(path13.join(projectDir, name)));
|
|
2635
2703
|
},
|
|
2636
2704
|
async apply(args) {
|
|
2637
2705
|
const dsn = await buildDsn3(args);
|
|
@@ -2704,9 +2772,9 @@ var KnexAdapter = {
|
|
|
2704
2772
|
async newMigration(args) {
|
|
2705
2773
|
try {
|
|
2706
2774
|
const created = await createKnexMigration({ projectDir: args.projectDir, slug: migrationSlug2(args.slug) });
|
|
2707
|
-
const stem =
|
|
2775
|
+
const stem = path13.basename(created).replace(/\.(js|ts)$/, "");
|
|
2708
2776
|
const version = stem.match(/^(\d{14})_/)?.[1] ?? stem;
|
|
2709
|
-
return { status: "ok", version, filename:
|
|
2777
|
+
return { status: "ok", version, filename: path13.basename(created), path: created };
|
|
2710
2778
|
} catch (err) {
|
|
2711
2779
|
return {
|
|
2712
2780
|
status: "error",
|
|
@@ -2720,6 +2788,360 @@ var KnexAdapter = {
|
|
|
2720
2788
|
};
|
|
2721
2789
|
registerSchemaMigrationAdapter(KnexAdapter);
|
|
2722
2790
|
|
|
2791
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
2792
|
+
import * as path15 from "path";
|
|
2793
|
+
|
|
2794
|
+
// scripts/lakebase/schema-migrate-runners/efcore.ts
|
|
2795
|
+
import { spawn as spawn4 } from "child_process";
|
|
2796
|
+
import * as fs14 from "fs";
|
|
2797
|
+
import * as path14 from "path";
|
|
2798
|
+
import { Client as Client2 } from "pg";
|
|
2799
|
+
var MIGRATION_ID = /^(\d{14})_(.+)$/;
|
|
2800
|
+
function efVersion(id) {
|
|
2801
|
+
return MIGRATION_ID.exec(id)?.[1] ?? id;
|
|
2802
|
+
}
|
|
2803
|
+
function efDescription(id) {
|
|
2804
|
+
const name = MIGRATION_ID.exec(id)?.[2] ?? id;
|
|
2805
|
+
return name.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
2806
|
+
}
|
|
2807
|
+
function dsnToNpgsql(dsn) {
|
|
2808
|
+
const u = new URL(dsn);
|
|
2809
|
+
const parts = [
|
|
2810
|
+
`Host=${u.hostname}`,
|
|
2811
|
+
`Port=${u.port || "5432"}`,
|
|
2812
|
+
`Database=${decodeURIComponent(u.pathname.replace(/^\//, "")) || "databricks_postgres"}`
|
|
2813
|
+
];
|
|
2814
|
+
if (u.username) parts.push(`Username=${decodeURIComponent(u.username)}`);
|
|
2815
|
+
if (u.password) parts.push(`Password=${decodeURIComponent(u.password)}`);
|
|
2816
|
+
const sslmode = (u.searchParams.get("sslmode") ?? "require").toLowerCase();
|
|
2817
|
+
const ssl = {
|
|
2818
|
+
disable: "Disable",
|
|
2819
|
+
allow: "Allow",
|
|
2820
|
+
prefer: "Prefer",
|
|
2821
|
+
require: "Require",
|
|
2822
|
+
"verify-ca": "VerifyCA",
|
|
2823
|
+
"verify-full": "VerifyFull"
|
|
2824
|
+
};
|
|
2825
|
+
parts.push(`SSL Mode=${ssl[sslmode] ?? "Require"}`);
|
|
2826
|
+
const app = u.searchParams.get("application_name");
|
|
2827
|
+
if (app) parts.push(`Application Name=${app}`);
|
|
2828
|
+
return parts.map((p) => p.replace(/;/g, "")).join(";");
|
|
2829
|
+
}
|
|
2830
|
+
function parseEfJson(stdout) {
|
|
2831
|
+
const body = stdout.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice("data:".length)).join("\n").trim();
|
|
2832
|
+
if (!body) {
|
|
2833
|
+
throw new SchemaMigrationError(`dotnet ef returned no JSON payload: ${stdout.slice(0, 400)}`);
|
|
2834
|
+
}
|
|
2835
|
+
try {
|
|
2836
|
+
return JSON.parse(body);
|
|
2837
|
+
} catch (err) {
|
|
2838
|
+
throw new SchemaMigrationError(
|
|
2839
|
+
`dotnet ef JSON parse failed: ${err instanceof Error ? err.message : String(err)}.
|
|
2840
|
+
Body (first 400 chars): ${body.slice(0, 400)}`
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
function requireProject(projectDir) {
|
|
2845
|
+
const project = findEfCoreProject(projectDir);
|
|
2846
|
+
if (!project) {
|
|
2847
|
+
throw new SchemaMigrationError(
|
|
2848
|
+
`No EF Core project found under ${projectDir}. Expected a *.csproj that references ${EF_DESIGN_PACKAGE}.`
|
|
2849
|
+
);
|
|
2850
|
+
}
|
|
2851
|
+
return project;
|
|
2852
|
+
}
|
|
2853
|
+
function spawnDotnet(projectDir, args, env) {
|
|
2854
|
+
return new Promise((resolve3, reject) => {
|
|
2855
|
+
const child = spawn4("dotnet", args, {
|
|
2856
|
+
cwd: projectDir,
|
|
2857
|
+
env: { ...process.env, DOTNET_NOLOGO: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1", ...env },
|
|
2858
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2859
|
+
});
|
|
2860
|
+
let stdout = "";
|
|
2861
|
+
let stderr = "";
|
|
2862
|
+
child.stdout.on("data", (chunk) => {
|
|
2863
|
+
stdout += chunk.toString("utf8");
|
|
2864
|
+
});
|
|
2865
|
+
child.stderr.on("data", (chunk) => {
|
|
2866
|
+
stderr += chunk.toString("utf8");
|
|
2867
|
+
});
|
|
2868
|
+
child.on("error", (err) => {
|
|
2869
|
+
reject(
|
|
2870
|
+
new SchemaMigrationError(
|
|
2871
|
+
`Could not spawn dotnet. Is the .NET SDK installed and on PATH? ${err.message}`,
|
|
2872
|
+
err
|
|
2873
|
+
)
|
|
2874
|
+
);
|
|
2875
|
+
});
|
|
2876
|
+
child.on("close", (code) => {
|
|
2877
|
+
if (code === 0) {
|
|
2878
|
+
resolve3({ stdout, stderr });
|
|
2879
|
+
} else {
|
|
2880
|
+
reject(
|
|
2881
|
+
new SchemaMigrationError(
|
|
2882
|
+
`dotnet ${args.join(" ")} exited with code ${code}.
|
|
2883
|
+
stdout: ${stdout}
|
|
2884
|
+
stderr: ${stderr}`
|
|
2885
|
+
)
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2888
|
+
});
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
var restored = /* @__PURE__ */ new Set();
|
|
2892
|
+
async function ensureRestored(projectDir, csproj) {
|
|
2893
|
+
if (restored.has(projectDir)) return;
|
|
2894
|
+
if (fs14.existsSync(path14.join(projectDir, ".config", "dotnet-tools.json"))) {
|
|
2895
|
+
await spawnDotnet(projectDir, ["tool", "restore"], {});
|
|
2896
|
+
}
|
|
2897
|
+
await spawnDotnet(projectDir, ["restore", csproj], {});
|
|
2898
|
+
restored.add(projectDir);
|
|
2899
|
+
}
|
|
2900
|
+
async function runEf(projectDir, args, dsn, opts = {}) {
|
|
2901
|
+
const project = requireProject(projectDir);
|
|
2902
|
+
const csproj = path14.relative(projectDir, project.csproj) || project.csproj;
|
|
2903
|
+
await ensureRestored(projectDir, csproj);
|
|
2904
|
+
const full = ["ef", ...args, "--project", csproj, "--prefix-output"];
|
|
2905
|
+
if (opts.noBuild) full.push("--no-build");
|
|
2906
|
+
const env = {};
|
|
2907
|
+
if (dsn) {
|
|
2908
|
+
env.DATABASE_URL = dsn;
|
|
2909
|
+
if (process.env.LAKEBASE_EFCORE_CONNECTION_ARG === "1") full.push("--connection", dsnToNpgsql(dsn));
|
|
2910
|
+
}
|
|
2911
|
+
return spawnDotnet(projectDir, full, env);
|
|
2912
|
+
}
|
|
2913
|
+
async function listEfMigrations(ctx, opts = {}) {
|
|
2914
|
+
const { stdout, stderr } = await runEf(ctx.projectDir, ["migrations", "list", "--json"], ctx.dsn, opts);
|
|
2915
|
+
const entries = parseEfJson(stdout);
|
|
2916
|
+
if (ctx.dsn && entries.some((e) => e.applied === null)) {
|
|
2917
|
+
const warning = stdout.split(/\r?\n/).filter((l) => /^(warn|error):/.test(l)).join("\n");
|
|
2918
|
+
throw new SchemaMigrationError(
|
|
2919
|
+
`dotnet ef could not read migration state from the database (check connectivity and credentials).
|
|
2920
|
+
${warning || stderr}`.trim()
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
return entries;
|
|
2924
|
+
}
|
|
2925
|
+
async function readEfHistory(dsn) {
|
|
2926
|
+
const client = new Client2({ connectionString: dsn, application_name: connectionApplicationName() });
|
|
2927
|
+
await client.connect();
|
|
2928
|
+
try {
|
|
2929
|
+
const exists = await client.query(`SELECT to_regclass('"__EFMigrationsHistory"') IS NOT NULL AS present`);
|
|
2930
|
+
if (!exists.rows[0]?.present) return null;
|
|
2931
|
+
const r = await client.query(`SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "MigrationId"`);
|
|
2932
|
+
return r.rows.map((row) => row.MigrationId);
|
|
2933
|
+
} finally {
|
|
2934
|
+
await client.end();
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
function toApplied(entries) {
|
|
2938
|
+
return entries.map((e) => ({ version: efVersion(e.id), description: efDescription(e.id) }));
|
|
2939
|
+
}
|
|
2940
|
+
async function statusEfcore(ctx) {
|
|
2941
|
+
const entries = await listEfMigrations(ctx);
|
|
2942
|
+
const history = await (ctx.readHistory ?? readEfHistory)(ctx.dsn);
|
|
2943
|
+
const current = history && history.length > 0 ? efVersion(history[history.length - 1]) : void 0;
|
|
2944
|
+
const pending = entries.filter((e) => e.applied !== true).map((e) => ({ version: efVersion(e.id), filename: `${e.id}.cs`, description: efDescription(e.id) }));
|
|
2945
|
+
return { current, pending, tool: "efcore" };
|
|
2946
|
+
}
|
|
2947
|
+
async function applyEfcore(ctx) {
|
|
2948
|
+
const before = new Set((await listEfMigrations(ctx)).filter((e) => e.applied).map((e) => e.id));
|
|
2949
|
+
await runEf(ctx.projectDir, ["database", "update"], ctx.dsn, { noBuild: true });
|
|
2950
|
+
const after = await listEfMigrations(ctx, { noBuild: true });
|
|
2951
|
+
const applied = toApplied(after.filter((e) => e.applied && !before.has(e.id)));
|
|
2952
|
+
return { applied, alreadyAtLatest: applied.length === 0, tool: "efcore" };
|
|
2953
|
+
}
|
|
2954
|
+
function resolveEfTarget(target, applied) {
|
|
2955
|
+
const t = target.trim();
|
|
2956
|
+
if (t === "0") return "0";
|
|
2957
|
+
const rel = /^-(\d+)$/.exec(t);
|
|
2958
|
+
if (rel) {
|
|
2959
|
+
const steps = Number(rel[1]);
|
|
2960
|
+
if (steps < 1) throw new SchemaMigrationError(`Invalid rollback step: ${target}`);
|
|
2961
|
+
const index = applied.length - 1 - steps;
|
|
2962
|
+
if (index < -1) {
|
|
2963
|
+
throw new SchemaMigrationError(`Cannot roll back ${steps} migration(s); only ${applied.length} applied.`);
|
|
2964
|
+
}
|
|
2965
|
+
return index === -1 ? "0" : applied[index].id;
|
|
2966
|
+
}
|
|
2967
|
+
const match = applied.find((e) => e.id === t || efVersion(e.id) === t || e.name === t);
|
|
2968
|
+
if (!match) throw new SchemaMigrationError(`Rollback target '${target}' is not an applied migration.`);
|
|
2969
|
+
return match.id;
|
|
2970
|
+
}
|
|
2971
|
+
async function rollbackEfcore(ctx) {
|
|
2972
|
+
const beforeEntries = (await listEfMigrations(ctx)).filter((e) => e.applied);
|
|
2973
|
+
const targetId = resolveEfTarget(ctx.target, beforeEntries);
|
|
2974
|
+
await runEf(ctx.projectDir, ["database", "update", targetId], ctx.dsn, { noBuild: true });
|
|
2975
|
+
const stillApplied = new Set((await listEfMigrations(ctx, { noBuild: true })).filter((e) => e.applied).map((e) => e.id));
|
|
2976
|
+
return { rolledBack: toApplied(beforeEntries.filter((e) => !stillApplied.has(e.id)).reverse()), tool: "efcore" };
|
|
2977
|
+
}
|
|
2978
|
+
function efMigrationName(slug) {
|
|
2979
|
+
const name = slug.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
2980
|
+
if (!name) throw new SchemaMigrationError(`Cannot derive a migration name from '${slug}'.`);
|
|
2981
|
+
return /^[0-9]/.test(name) ? `M${name}` : name;
|
|
2982
|
+
}
|
|
2983
|
+
async function createEfcoreMigration(opts) {
|
|
2984
|
+
const project = requireProject(opts.projectDir);
|
|
2985
|
+
const name = efMigrationName(opts.slug);
|
|
2986
|
+
const existing = new Set(fs14.existsSync(project.migrationsDir) ? fs14.readdirSync(project.migrationsDir) : []);
|
|
2987
|
+
await runEf(opts.projectDir, ["migrations", "add", name]);
|
|
2988
|
+
const created = fs14.readdirSync(project.migrationsDir).filter((f) => !existing.has(f) && MIGRATION_ID.test(f.replace(/\.cs$/, "")) && !f.endsWith(".Designer.cs"));
|
|
2989
|
+
if (created.length !== 1) {
|
|
2990
|
+
throw new SchemaMigrationError(`Expected one new migration file for ${name}, found: ${created.join(", ") || "none"}`);
|
|
2991
|
+
}
|
|
2992
|
+
return { id: created[0].replace(/\.cs$/, ""), path: path14.join(project.migrationsDir, created[0]) };
|
|
2993
|
+
}
|
|
2994
|
+
function localEfMigrationIds(projectDir) {
|
|
2995
|
+
const project = findEfCoreProject(projectDir);
|
|
2996
|
+
if (!project || !fs14.existsSync(project.migrationsDir)) return [];
|
|
2997
|
+
return fs14.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();
|
|
2998
|
+
}
|
|
2999
|
+
function efProductVersion(project) {
|
|
3000
|
+
try {
|
|
3001
|
+
const csproj = fs14.readFileSync(project.csproj, "utf-8");
|
|
3002
|
+
const m = new RegExp(`${EF_DESIGN_PACKAGE.replace(/\./g, "\\.")}"\\s+Version="([^"]+)"`).exec(csproj);
|
|
3003
|
+
if (m) return m[1];
|
|
3004
|
+
} catch {
|
|
3005
|
+
}
|
|
3006
|
+
return "unknown";
|
|
3007
|
+
}
|
|
3008
|
+
async function stampEfcore(ctx) {
|
|
3009
|
+
const project = requireProject(ctx.projectDir);
|
|
3010
|
+
const ids = localEfMigrationIds(ctx.projectDir);
|
|
3011
|
+
if (ids.length === 0) throw new SchemaMigrationError("No local EF Core migrations to stamp.");
|
|
3012
|
+
const rev = ctx.revision.trim();
|
|
3013
|
+
const end = rev === "head" ? ids.length - 1 : ids.findIndex((id) => id === rev || efVersion(id) === rev);
|
|
3014
|
+
if (end < 0) throw new SchemaMigrationError(`Stamp revision '${ctx.revision}' is not a local migration.`);
|
|
3015
|
+
const toStamp = ids.slice(0, end + 1);
|
|
3016
|
+
const productVersion = efProductVersion(project);
|
|
3017
|
+
const client = new Client2({ connectionString: ctx.dsn, application_name: connectionApplicationName() });
|
|
3018
|
+
await client.connect();
|
|
3019
|
+
try {
|
|
3020
|
+
await client.query("BEGIN");
|
|
3021
|
+
await client.query(
|
|
3022
|
+
`CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
|
|
3023
|
+
"MigrationId" character varying(150) NOT NULL,
|
|
3024
|
+
"ProductVersion" character varying(32) NOT NULL,
|
|
3025
|
+
CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId"))`
|
|
3026
|
+
);
|
|
3027
|
+
await client.query(`DELETE FROM "__EFMigrationsHistory" WHERE NOT ("MigrationId" = ANY($1::text[]))`, [toStamp]);
|
|
3028
|
+
for (const id of toStamp) {
|
|
3029
|
+
await client.query(
|
|
3030
|
+
`INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($1, $2)
|
|
3031
|
+
ON CONFLICT ("MigrationId") DO NOTHING`,
|
|
3032
|
+
[id, productVersion]
|
|
3033
|
+
);
|
|
3034
|
+
}
|
|
3035
|
+
await client.query("COMMIT");
|
|
3036
|
+
} catch (err) {
|
|
3037
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
3038
|
+
throw err;
|
|
3039
|
+
} finally {
|
|
3040
|
+
await client.end();
|
|
3041
|
+
}
|
|
3042
|
+
return efVersion(toStamp[toStamp.length - 1]);
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
// scripts/lakebase/adapters/efcore-adapter.ts
|
|
3046
|
+
async function buildDsn4(args) {
|
|
3047
|
+
const result = await getConnection({
|
|
3048
|
+
output: "dsn",
|
|
3049
|
+
instance: args.instance,
|
|
3050
|
+
branch: args.branch,
|
|
3051
|
+
database: args.database,
|
|
3052
|
+
endpointName: args.endpointName
|
|
3053
|
+
});
|
|
3054
|
+
return result.url;
|
|
3055
|
+
}
|
|
3056
|
+
function errMsg(err) {
|
|
3057
|
+
return err instanceof Error ? err.message : String(err);
|
|
3058
|
+
}
|
|
3059
|
+
function listEfCoreFiles(projectDir) {
|
|
3060
|
+
return localEfMigrationIds(projectDir).map((id) => ({
|
|
3061
|
+
version: efVersion(id),
|
|
3062
|
+
filename: `${id}.cs`,
|
|
3063
|
+
description: efDescription(id),
|
|
3064
|
+
type: "CSharp",
|
|
3065
|
+
tool: "efcore"
|
|
3066
|
+
}));
|
|
3067
|
+
}
|
|
3068
|
+
var EfCoreAdapter = {
|
|
3069
|
+
id: "efcore",
|
|
3070
|
+
languages: ["dotnet"],
|
|
3071
|
+
/** A .NET project (root marker) with a *.csproj that references Microsoft.EntityFrameworkCore.Design. */
|
|
3072
|
+
detect(projectDir) {
|
|
3073
|
+
return isDotnetProject(projectDir) && findEfCoreProject(projectDir) !== null;
|
|
3074
|
+
},
|
|
3075
|
+
async apply(args) {
|
|
3076
|
+
try {
|
|
3077
|
+
const dsn = await buildDsn4(args);
|
|
3078
|
+
const r = await applyEfcore({ projectDir: args.projectDir, dsn });
|
|
3079
|
+
return {
|
|
3080
|
+
applied_migrations: r.applied,
|
|
3081
|
+
status: r.alreadyAtLatest ? "noop" : "ok",
|
|
3082
|
+
tool_specific: { alreadyAtLatest: r.alreadyAtLatest, tool: r.tool }
|
|
3083
|
+
};
|
|
3084
|
+
} catch (err) {
|
|
3085
|
+
return { applied_migrations: [], status: "error", error: errMsg(err) };
|
|
3086
|
+
}
|
|
3087
|
+
},
|
|
3088
|
+
async rollback(args) {
|
|
3089
|
+
try {
|
|
3090
|
+
const dsn = await buildDsn4(args);
|
|
3091
|
+
const r = await rollbackEfcore({ projectDir: args.projectDir, dsn, target: args.target });
|
|
3092
|
+
return {
|
|
3093
|
+
rolled_back: r.rolledBack,
|
|
3094
|
+
status: r.rolledBack.length === 0 ? "noop" : "ok",
|
|
3095
|
+
tool_specific: { tool: r.tool }
|
|
3096
|
+
};
|
|
3097
|
+
} catch (err) {
|
|
3098
|
+
return { rolled_back: [], status: "error", error: errMsg(err) };
|
|
3099
|
+
}
|
|
3100
|
+
},
|
|
3101
|
+
async stamp(args) {
|
|
3102
|
+
try {
|
|
3103
|
+
const dsn = await buildDsn4(args);
|
|
3104
|
+
const stamped = await stampEfcore({ projectDir: args.projectDir, dsn, revision: args.revision });
|
|
3105
|
+
return { status: "ok", stamped_revision: stamped, tool_specific: { tool: "efcore" } };
|
|
3106
|
+
} catch (err) {
|
|
3107
|
+
return { status: "error", stamped_revision: null, error: errMsg(err) };
|
|
3108
|
+
}
|
|
3109
|
+
},
|
|
3110
|
+
async status(args) {
|
|
3111
|
+
try {
|
|
3112
|
+
const dsn = await buildDsn4(args);
|
|
3113
|
+
const r = await statusEfcore({ projectDir: args.projectDir, dsn });
|
|
3114
|
+
return {
|
|
3115
|
+
applied_version: r.current ?? null,
|
|
3116
|
+
pending: r.pending,
|
|
3117
|
+
applied: [],
|
|
3118
|
+
status: "ok",
|
|
3119
|
+
tool_specific: { tool: r.tool }
|
|
3120
|
+
};
|
|
3121
|
+
} catch (err) {
|
|
3122
|
+
return { applied_version: null, pending: [], applied: [], status: "error", error: errMsg(err) };
|
|
3123
|
+
}
|
|
3124
|
+
},
|
|
3125
|
+
async list(args) {
|
|
3126
|
+
return { files: listEfCoreFiles(args.projectDir) };
|
|
3127
|
+
},
|
|
3128
|
+
// baseline: use stamp (EF has no separate baseline concept).
|
|
3129
|
+
async newMigration(args) {
|
|
3130
|
+
try {
|
|
3131
|
+
const created = await createEfcoreMigration({ projectDir: args.projectDir, slug: args.slug });
|
|
3132
|
+
return {
|
|
3133
|
+
status: "ok",
|
|
3134
|
+
version: efVersion(created.id),
|
|
3135
|
+
filename: path15.basename(created.path),
|
|
3136
|
+
path: created.path
|
|
3137
|
+
};
|
|
3138
|
+
} catch (err) {
|
|
3139
|
+
return { status: "error", version: "", filename: "", path: "", error: errMsg(err) };
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
registerSchemaMigrationAdapter(EfCoreAdapter);
|
|
3144
|
+
|
|
2723
3145
|
// scripts/lakebase/schema-migrate.ts
|
|
2724
3146
|
var SchemaMigrationError = class extends Error {
|
|
2725
3147
|
constructor(message, cause) {
|
|
@@ -2733,13 +3155,16 @@ function detectLanguage(projectDir) {
|
|
|
2733
3155
|
const lang = resolveMigrationLanguage(projectDir);
|
|
2734
3156
|
if (lang === "unknown") {
|
|
2735
3157
|
throw new SchemaMigrationError(
|
|
2736
|
-
`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.`
|
|
3158
|
+
`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.`
|
|
2737
3159
|
);
|
|
2738
3160
|
}
|
|
2739
3161
|
return lang;
|
|
2740
3162
|
}
|
|
2741
|
-
function toolForLanguage(language) {
|
|
3163
|
+
function toolForLanguage(language, projectDir) {
|
|
2742
3164
|
switch (language) {
|
|
3165
|
+
case "dotnet":
|
|
3166
|
+
if (projectDir && isDotnetFlywayProject(projectDir)) return "flyway";
|
|
3167
|
+
return "efcore";
|
|
2743
3168
|
case "java":
|
|
2744
3169
|
case "kotlin":
|
|
2745
3170
|
return "flyway";
|
|
@@ -2752,8 +3177,10 @@ function toolForLanguage(language) {
|
|
|
2752
3177
|
function listSchemaMigrations(args = {}) {
|
|
2753
3178
|
const projectDir = args.projectDir ?? process.cwd();
|
|
2754
3179
|
const language = args.language ?? detectLanguage(projectDir);
|
|
2755
|
-
const tool = toolForLanguage(language);
|
|
3180
|
+
const tool = toolForLanguage(language, projectDir);
|
|
2756
3181
|
switch (tool) {
|
|
3182
|
+
case "efcore":
|
|
3183
|
+
return listEfCoreFiles(projectDir);
|
|
2757
3184
|
case "flyway":
|
|
2758
3185
|
return listFlywayMigrations(projectDir);
|
|
2759
3186
|
case "alembic":
|
|
@@ -2763,9 +3190,9 @@ function listSchemaMigrations(args = {}) {
|
|
|
2763
3190
|
}
|
|
2764
3191
|
}
|
|
2765
3192
|
function listFlywayMigrations(projectDir) {
|
|
2766
|
-
const dir =
|
|
2767
|
-
if (!
|
|
2768
|
-
const files =
|
|
3193
|
+
const dir = flywayMigrationsDir(projectDir);
|
|
3194
|
+
if (!fs15.existsSync(dir)) return [];
|
|
3195
|
+
const files = fs15.readdirSync(dir).filter((f) => /^V\d+(\.\d+)*__.+\.sql$/.test(f));
|
|
2769
3196
|
return files.map((filename) => {
|
|
2770
3197
|
const m = filename.match(/^V(\d+(?:\.\d+)*)__(.+)\.sql$/);
|
|
2771
3198
|
const version = m[1];
|
|
@@ -2775,12 +3202,12 @@ function listFlywayMigrations(projectDir) {
|
|
|
2775
3202
|
}
|
|
2776
3203
|
function listAlembicMigrations(projectDir) {
|
|
2777
3204
|
const candidates = [
|
|
2778
|
-
|
|
2779
|
-
|
|
3205
|
+
path16.join(projectDir, "migrations", "versions"),
|
|
3206
|
+
path16.join(projectDir, "alembic", "versions")
|
|
2780
3207
|
];
|
|
2781
|
-
const dir = candidates.find((p) =>
|
|
3208
|
+
const dir = candidates.find((p) => fs15.existsSync(p));
|
|
2782
3209
|
if (!dir) return [];
|
|
2783
|
-
const files =
|
|
3210
|
+
const files = fs15.readdirSync(dir).filter((f) => f.endsWith(".py") && !f.startsWith("__"));
|
|
2784
3211
|
return files.map((filename) => {
|
|
2785
3212
|
const stem = filename.replace(/\.py$/, "");
|
|
2786
3213
|
const sep2 = stem.indexOf("_");
|
|
@@ -2790,9 +3217,9 @@ function listAlembicMigrations(projectDir) {
|
|
|
2790
3217
|
}).sort((a, b) => a.filename.localeCompare(b.filename));
|
|
2791
3218
|
}
|
|
2792
3219
|
function listKnexMigrations(projectDir) {
|
|
2793
|
-
const dir =
|
|
2794
|
-
if (!
|
|
2795
|
-
const files =
|
|
3220
|
+
const dir = path16.join(projectDir, "migrations");
|
|
3221
|
+
if (!fs15.existsSync(dir)) return [];
|
|
3222
|
+
const files = fs15.readdirSync(dir).filter((f) => (f.endsWith(".js") || f.endsWith(".ts")) && !f.startsWith("."));
|
|
2796
3223
|
return files.map((filename) => {
|
|
2797
3224
|
const stem = filename.replace(/\.(js|ts)$/, "");
|
|
2798
3225
|
const m = stem.match(/^(\d{14})_(.+)$/);
|
|
@@ -2814,7 +3241,7 @@ function versionCompare2(a, b) {
|
|
|
2814
3241
|
return 0;
|
|
2815
3242
|
}
|
|
2816
3243
|
function adapterFor(projectDir, language) {
|
|
2817
|
-
const override = language ? toolForLanguage(language) : void 0;
|
|
3244
|
+
const override = language ? toolForLanguage(language, projectDir) : void 0;
|
|
2818
3245
|
return resolveSchemaMigrationAdapter(projectDir, override);
|
|
2819
3246
|
}
|
|
2820
3247
|
function dbRevisionOrphaned(appliedRevision, localRevisionIds) {
|
|
@@ -3008,7 +3435,7 @@ ${HELP}
|
|
|
3008
3435
|
);
|
|
3009
3436
|
return 2;
|
|
3010
3437
|
}
|
|
3011
|
-
const projectDir =
|
|
3438
|
+
const projectDir = path17.resolve(args.projectDir ?? process.cwd());
|
|
3012
3439
|
try {
|
|
3013
3440
|
const result = await claimFeatureBranch({
|
|
3014
3441
|
projectDir,
|