@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
@@ -0,0 +1,225 @@
1
+ using System.Diagnostics;
2
+ using System.Text.Json;
3
+ using Microsoft.EntityFrameworkCore;
4
+ using Microsoft.Extensions.DependencyInjection.Extensions;
5
+ using Npgsql;
6
+
7
+ namespace App.Lakebase;
8
+
9
+ /// <summary>
10
+ /// Builds the app's Postgres connection. Credentials are minted at RUNTIME, never
11
+ /// stored in .env: the data source is built from connection METADATA
12
+ /// (LAKEBASE_HOST, LAKEBASE_PROJECT_ID, LAKEBASE_BRANCH_ID, DB_USERNAME) and a
13
+ /// periodic password provider injects a short-lived Lakebase credential minted by
14
+ /// the databricks CLI (the same credential source as the Python and Node templates).
15
+ ///
16
+ /// An explicit DATABASE_URL (CI secret, Docker, the kit's ephemeral-verify DSN)
17
+ /// OVERRIDES this and is used verbatim. postgresql:// URLs and Npgsql
18
+ /// "Host=...;" strings are both accepted.
19
+ /// </summary>
20
+ public static class LakebaseConnection
21
+ {
22
+ // Lakebase credentials live ~1h; re-mint after 40 min so a new physical
23
+ // connection never presents an expired token.
24
+ private static readonly TimeSpan MintTtl = TimeSpan.FromMinutes(40);
25
+ private static readonly SemaphoreSlim MintLock = new(1, 1);
26
+ private static string? _cachedToken;
27
+ private static DateTime _mintedAt = DateTime.MinValue;
28
+
29
+ /// <summary>
30
+ /// Registers one process-wide NpgsqlDataSource and a DbContext that uses it.
31
+ /// Never build a data source per DbContext: that creates a new pool and mints a
32
+ /// new credential on every request.
33
+ /// </summary>
34
+ public static IServiceCollection AddLakebaseDbContext<TContext>(this IServiceCollection services)
35
+ where TContext : DbContext
36
+ {
37
+ services.TryAddSingleton(_ => CreateDataSource());
38
+ services.AddDbContext<TContext>((sp, options) =>
39
+ options.UseNpgsql(sp.GetRequiredService<NpgsqlDataSource>()));
40
+ return services;
41
+ }
42
+
43
+ /// <summary>Data source for the app (and the EF Core design-time factory).</summary>
44
+ public static NpgsqlDataSource CreateDataSource()
45
+ {
46
+ var explicitUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
47
+ if (!string.IsNullOrWhiteSpace(explicitUrl))
48
+ return NpgsqlDataSource.Create(Normalize(new NpgsqlConnectionStringBuilder(ToNpgsql(explicitUrl))).ConnectionString);
49
+
50
+ var host = Env("LAKEBASE_HOST") ?? Env("DB_HOST");
51
+ var endpoint = EndpointPathFromEnv();
52
+ var builder = new NpgsqlConnectionStringBuilder
53
+ {
54
+ Host = host ?? "localhost",
55
+ Port = 5432,
56
+ Database = Env("DB_NAME") ?? "databricks_postgres",
57
+ };
58
+
59
+ if (host is null || endpoint is null)
60
+ return NpgsqlDataSource.Create(Normalize(builder).ConnectionString); // pure-local fallback
61
+
62
+ // Resolve the user without failing here: design-time commands such as
63
+ // `dotnet ef migrations add` build a data source but never connect, so
64
+ // an offline or expired CLI session must only fail when a connection
65
+ // is actually opened.
66
+ Exception? userError = null;
67
+ var user = Env("DB_USERNAME");
68
+ if (user is null)
69
+ {
70
+ try { user = CurrentUser(); }
71
+ catch (Exception ex) { userError = ex; }
72
+ }
73
+
74
+ builder.Username = user ?? "unresolved-lakebase-user";
75
+ var dataSourceBuilder = new NpgsqlDataSourceBuilder(Normalize(builder).ConnectionString);
76
+ dataSourceBuilder.UsePeriodicPasswordProvider(
77
+ async (_, ct) => userError is null
78
+ ? await MintTokenAsync(endpoint, ct)
79
+ : throw new InvalidOperationException(
80
+ "Could not resolve the Lakebase user. Set DB_USERNAME in .env or run 'databricks auth login'.", userError),
81
+ successRefreshInterval: TimeSpan.FromMinutes(5),
82
+ failureRefreshInterval: TimeSpan.FromSeconds(10));
83
+ return dataSourceBuilder.Build();
84
+ }
85
+
86
+ /// <summary>projects/&lt;id&gt;/branches/&lt;branch&gt;/endpoints/&lt;endpoint&gt;, or null without metadata.</summary>
87
+ public static string? EndpointPathFromEnv()
88
+ {
89
+ var project = Env("LAKEBASE_PROJECT_ID");
90
+ var branch = Env("LAKEBASE_BRANCH_ID");
91
+ if (project is null || branch is null)
92
+ return null;
93
+ return $"projects/{project}/branches/{branch}/endpoints/{Env("LAKEBASE_ENDPOINT") ?? "primary"}";
94
+ }
95
+
96
+ /// <summary>Convert a postgresql:// URL to an Npgsql connection string; pass Npgsql strings through.</summary>
97
+ public static string ToNpgsql(string databaseUrl)
98
+ {
99
+ var raw = databaseUrl.Trim();
100
+ if (!raw.StartsWith("postgres://", StringComparison.OrdinalIgnoreCase)
101
+ && !raw.StartsWith("postgresql://", StringComparison.OrdinalIgnoreCase))
102
+ return raw;
103
+
104
+ var uri = new Uri(raw);
105
+ var builder = new NpgsqlConnectionStringBuilder
106
+ {
107
+ Host = uri.Host,
108
+ Port = uri.Port > 0 ? uri.Port : 5432,
109
+ Database = Uri.UnescapeDataString(uri.AbsolutePath.TrimStart('/')),
110
+ };
111
+ var userInfo = uri.UserInfo.Split(':', 2);
112
+ if (userInfo[0].Length > 0)
113
+ builder.Username = Uri.UnescapeDataString(userInfo[0]);
114
+ if (userInfo.Length > 1)
115
+ builder.Password = Uri.UnescapeDataString(userInfo[1]);
116
+
117
+ foreach (var pair in uri.Query.TrimStart('?').Split('&', StringSplitOptions.RemoveEmptyEntries))
118
+ {
119
+ var kv = pair.Split('=', 2);
120
+ var value = kv.Length > 1 ? Uri.UnescapeDataString(kv[1]) : "";
121
+ switch (kv[0].ToLowerInvariant())
122
+ {
123
+ case "sslmode":
124
+ builder.SslMode = value.ToLowerInvariant() switch
125
+ {
126
+ "disable" => SslMode.Disable,
127
+ "allow" => SslMode.Allow,
128
+ "prefer" => SslMode.Prefer,
129
+ "verify-ca" => SslMode.VerifyCA,
130
+ "verify-full" => SslMode.VerifyFull,
131
+ _ => SslMode.Require,
132
+ };
133
+ break;
134
+ case "application_name":
135
+ builder.ApplicationName = value;
136
+ break;
137
+ }
138
+ }
139
+
140
+ return builder.ConnectionString;
141
+ }
142
+
143
+ // TLS on and a non-empty application_name, so every connection is
144
+ // identifiable in pg_stat_activity.
145
+ private static NpgsqlConnectionStringBuilder Normalize(NpgsqlConnectionStringBuilder builder)
146
+ {
147
+ var weakTls = builder.SslMode is SslMode.Disable or SslMode.Allow or SslMode.Prefer;
148
+ if (weakTls && builder.Host != "localhost")
149
+ builder.SslMode = SslMode.Require;
150
+ if (string.IsNullOrEmpty(builder.ApplicationName))
151
+ {
152
+ var consort = Env("CONSORT_VERSION");
153
+ builder.ApplicationName = Env("PGAPPNAME")
154
+ ?? (consort is not null ? $"consort/{consort}" : "scm-utils/{{LAKEBASE_SCM_UTILS_VERSION}}");
155
+ }
156
+
157
+ return builder;
158
+ }
159
+
160
+ private static async Task<string> MintTokenAsync(string endpoint, CancellationToken ct)
161
+ {
162
+ await MintLock.WaitAsync(ct);
163
+ try
164
+ {
165
+ if (_cachedToken is not null && DateTime.UtcNow - _mintedAt < MintTtl)
166
+ return _cachedToken;
167
+
168
+ var json = await RunDatabricksAsync(["postgres", "generate-database-credential", endpoint, "-o", "json"], ct);
169
+ using var doc = JsonDocument.Parse(json);
170
+ _cachedToken = doc.RootElement.GetProperty("token").GetString()
171
+ ?? throw new InvalidOperationException($"generate-database-credential returned no token for {endpoint}");
172
+ _mintedAt = DateTime.UtcNow;
173
+ return _cachedToken;
174
+ }
175
+ finally
176
+ {
177
+ MintLock.Release();
178
+ }
179
+ }
180
+
181
+ private static string CurrentUser()
182
+ {
183
+ var json = RunDatabricksAsync(["current-user", "me", "-o", "json"], CancellationToken.None).GetAwaiter().GetResult();
184
+ using var doc = JsonDocument.Parse(json);
185
+ return doc.RootElement.GetProperty("userName").GetString()
186
+ ?? throw new InvalidOperationException("databricks current-user me returned no userName; set DB_USERNAME in .env");
187
+ }
188
+
189
+ private static async Task<string> RunDatabricksAsync(IEnumerable<string> args, CancellationToken ct)
190
+ {
191
+ var psi = new ProcessStartInfo("databricks")
192
+ {
193
+ RedirectStandardOutput = true,
194
+ RedirectStandardError = true,
195
+ UseShellExecute = false,
196
+ };
197
+ foreach (var arg in args)
198
+ psi.ArgumentList.Add(arg);
199
+ if (Env("DATABRICKS_CONFIG_PROFILE") is { } profile)
200
+ {
201
+ psi.ArgumentList.Add("--profile");
202
+ psi.ArgumentList.Add(profile);
203
+ }
204
+
205
+ using var process = Process.Start(psi)
206
+ ?? throw new InvalidOperationException("Could not start the databricks CLI. Is it installed and on PATH?");
207
+ var stdout = process.StandardOutput.ReadToEndAsync(ct);
208
+ var stderr = process.StandardError.ReadToEndAsync(ct);
209
+ await process.WaitForExitAsync(ct);
210
+ if (process.ExitCode != 0)
211
+ {
212
+ var err = (await stderr).Trim();
213
+ var hint = err.Contains("auth login", StringComparison.OrdinalIgnoreCase)
214
+ || err.Contains("refresh token", StringComparison.OrdinalIgnoreCase)
215
+ ? " Run 'databricks auth login' to re-authenticate."
216
+ : "";
217
+ throw new InvalidOperationException($"databricks {string.Join(' ', psi.ArgumentList)} failed: {err}{hint}");
218
+ }
219
+
220
+ return await stdout;
221
+ }
222
+
223
+ private static string? Env(string name)
224
+ => Environment.GetEnvironmentVariable(name) is { Length: > 0 } value ? value : null;
225
+ }
@@ -0,0 +1,34 @@
1
+ using App.Data;
2
+ using App.Lakebase;
3
+
4
+ // Load .env from the repo root so the Lakebase connection metadata written by
5
+ // the post-checkout hook is available however the app is started. Variables
6
+ // already set in the environment (CI secrets, Docker --env) always win.
7
+ DotEnv.LoadFromRepoRoot();
8
+
9
+ var builder = WebApplication.CreateBuilder(args);
10
+
11
+ // One process-wide data source: a single connection pool whose password is a
12
+ // short-lived Lakebase credential refreshed in the background.
13
+ builder.Services.AddLakebaseDbContext<AppDbContext>();
14
+
15
+ // OpenAPI description of the endpoints, browsable at /docs (like the Node template).
16
+ builder.Services.AddOpenApi();
17
+
18
+ var app = builder.Build();
19
+
20
+ app.MapOpenApi("/openapi.json");
21
+ app.UseSwaggerUI(options =>
22
+ {
23
+ options.RoutePrefix = "docs";
24
+ options.SwaggerEndpoint("/openapi.json", "API");
25
+ });
26
+
27
+ app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
28
+
29
+ // Register API endpoints above this line.
30
+
31
+ app.Run();
32
+
33
+ // Exposed for WebApplicationFactory<Program> in tests.
34
+ public partial class Program;
@@ -0,0 +1,9 @@
1
+ {
2
+ "Logging": {
3
+ "LogLevel": {
4
+ "Default": "Information",
5
+ "Microsoft.AspNetCore": "Warning"
6
+ }
7
+ },
8
+ "AllowedHosts": "*"
9
+ }
@@ -0,0 +1,25 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>net10.0</TargetFramework>
5
+ <ImplicitUsings>enable</ImplicitUsings>
6
+ <Nullable>enable</Nullable>
7
+ <IsPackable>false</IsPackable>
8
+ </PropertyGroup>
9
+
10
+ <ItemGroup>
11
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.12" />
12
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
13
+ <PackageReference Include="xunit" Version="2.9.3" />
14
+ <PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
15
+ </ItemGroup>
16
+
17
+ <ItemGroup>
18
+ <Using Include="Xunit" />
19
+ </ItemGroup>
20
+
21
+ <ItemGroup>
22
+ <ProjectReference Include="..\..\src\App\App.csproj" />
23
+ </ItemGroup>
24
+
25
+ </Project>
@@ -0,0 +1,18 @@
1
+ using System.Net;
2
+ using System.Net.Http.Json;
3
+ using Microsoft.AspNetCore.Mvc.Testing;
4
+
5
+ namespace App.Tests;
6
+
7
+ public class HealthTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
8
+ {
9
+ [Fact]
10
+ public async Task Health_ReturnsOk()
11
+ {
12
+ var response = await factory.CreateClient().GetAsync("/health");
13
+
14
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
15
+ var body = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();
16
+ Assert.Equal("ok", body!["status"]);
17
+ }
18
+ }
@@ -0,0 +1,23 @@
1
+ using System.Net;
2
+ using Microsoft.AspNetCore.Mvc.Testing;
3
+
4
+ namespace App.Tests;
5
+
6
+ public class OpenApiTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
7
+ {
8
+ [Fact]
9
+ public async Task OpenApiDocument_ListsEndpoints()
10
+ {
11
+ var body = await factory.CreateClient().GetStringAsync("/openapi.json");
12
+
13
+ Assert.Contains("\"/health\"", body);
14
+ }
15
+
16
+ [Fact]
17
+ public async Task Docs_ServesSwaggerUi()
18
+ {
19
+ var response = await factory.CreateClient().GetAsync("/docs/index.html");
20
+
21
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
22
+ }
23
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 1,
3
+ "isRoot": true,
4
+ "tools": {
5
+ "dotnet-ef": {
6
+ "version": "10.0.4",
7
+ "commands": [
8
+ "dotnet-ef"
9
+ ],
10
+ "rollForward": false
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,22 @@
1
+ <Project Sdk="Microsoft.NET.Sdk.Web">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>net10.0</TargetFramework>
5
+ <Nullable>enable</Nullable>
6
+ <ImplicitUsings>enable</ImplicitUsings>
7
+ <RootNamespace>App</RootNamespace>
8
+ <!-- {{PROJECT_NAME}} -->
9
+ </PropertyGroup>
10
+
11
+ <ItemGroup>
12
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
13
+ <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" />
14
+ <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.3.0" />
15
+ <!-- Marks this project as the EF Core migrations owner for lakebase-schema-migrate. -->
16
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.4">
17
+ <PrivateAssets>all</PrivateAssets>
18
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19
+ </PackageReference>
20
+ </ItemGroup>
21
+
22
+ </Project>
@@ -0,0 +1,24 @@
1
+ using App.Lakebase;
2
+ using Microsoft.EntityFrameworkCore;
3
+ using Microsoft.EntityFrameworkCore.Design;
4
+
5
+ namespace App.Data;
6
+
7
+ /// <summary>
8
+ /// Used by `dotnet ef` (and the kit's lakebase-schema-migrate) to build the
9
+ /// DbContext without starting the web host. It reads the same connection
10
+ /// settings as the app: DATABASE_URL when set (the kit passes the target
11
+ /// branch DSN this way, so no password appears on a command line), otherwise
12
+ /// the Lakebase metadata in .env with a runtime-minted credential.
13
+ /// </summary>
14
+ public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
15
+ {
16
+ public AppDbContext CreateDbContext(string[] args)
17
+ {
18
+ DotEnv.LoadFromRepoRoot();
19
+ var options = new DbContextOptionsBuilder<AppDbContext>()
20
+ .UseNpgsql(LakebaseConnection.CreateDataSource())
21
+ .Options;
22
+ return new AppDbContext(options);
23
+ }
24
+ }
@@ -0,0 +1,23 @@
1
+ # EF Core migrations
2
+
3
+ Create a migration with the kit's tool-agnostic command (run in the project
4
+ root):
5
+
6
+ ```bash
7
+ ./scripts/lk lakebase-new-migration --name "<description>"
8
+ ```
9
+
10
+ This runs `dotnet ef migrations add`, which generates the migration from the
11
+ difference between your `AppDbContext` model and the last snapshot. Review the
12
+ generated `Up()` / `Down()` before committing. You can also run
13
+ `dotnet ef migrations add <Name> --project src/App` directly.
14
+
15
+ EF Core names migrations `<UTC timestamp>_<Name>.cs`, so sibling feature
16
+ branches never collide on an id. Commit the migration, its `.Designer.cs`, and
17
+ the updated `AppDbContextModelSnapshot.cs` together.
18
+
19
+ You do not run migrations by hand: the kit applies them to each paired Lakebase
20
+ branch through CI (`pr.yml` on the feature branch, `merge.yml` on staging /
21
+ production) via `lakebase-schema-migrate`, which passes the branch connection to
22
+ `dotnet ef` in `DATABASE_URL` (read by `Data/DesignTimeDbContextFactory.cs`).
23
+ To migrate your paired branch locally, run `./scripts/flyway-migrate.sh`.
@@ -0,0 +1,23 @@
1
+ # Flyway migrations
2
+
3
+ Create a migration with the kit's tool-agnostic command (run in the project
4
+ root):
5
+
6
+ ```bash
7
+ ./scripts/lk lakebase-new-migration --name "<description>"
8
+ ```
9
+
10
+ This writes a timestamp-versioned skeleton (`V<timestamp>__<slug>.sql`, a UTC
11
+ `YYYYMMDDHHMMSS` version); then author the DDL/DML. Timestamps are globally
12
+ unique and sort chronologically, so sibling feature branches never collide on a
13
+ version. Flyway runs with `-baselineOnMigrate=true -baselineVersion=0`, so every
14
+ `V<n>` file is applied as a pending migration.
15
+
16
+ Flyway owns the schema; EF Core is used only as the runtime ORM. Keep your
17
+ `AppDbContext` model in step with the SQL here, and do not add EF Core
18
+ migrations to this project.
19
+
20
+ You do not run migrations by hand: the kit applies them to each paired Lakebase
21
+ branch through CI (`pr.yml` on the feature branch, `merge.yml` on staging /
22
+ production) via `lakebase-schema-migrate`. To migrate your paired branch
23
+ locally, run `./scripts/flyway-migrate.sh`.
@@ -0,0 +1,17 @@
1
+ <Project Sdk="Microsoft.NET.Sdk.Web">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>net10.0</TargetFramework>
5
+ <Nullable>enable</Nullable>
6
+ <ImplicitUsings>enable</ImplicitUsings>
7
+ <RootNamespace>App</RootNamespace>
8
+ <!-- {{PROJECT_NAME}} -->
9
+ </PropertyGroup>
10
+
11
+ <ItemGroup>
12
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
13
+ <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" />
14
+ <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.3.0" />
15
+ </ItemGroup>
16
+
17
+ </Project>