@atlashub/smartstack-cli 3.34.0 → 3.36.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.
- package/.documentation/init.html +409 -0
- package/dist/index.js +35 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +118 -70
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -2
- package/templates/mcp-scaffolding/controller.cs.hbs +5 -1
- package/templates/skills/apex/SKILL.md +6 -3
- package/templates/skills/apex/references/post-checks.md +225 -0
- package/templates/skills/apex/references/smartstack-api.md +29 -1
- package/templates/skills/apex/references/smartstack-frontend.md +27 -0
- package/templates/skills/apex/references/smartstack-layers.md +18 -2
- package/templates/skills/apex/steps/step-00-init.md +77 -1
- package/templates/skills/apex/steps/step-01-analyze.md +21 -0
- package/templates/skills/apex/steps/step-03-execute.md +94 -5
- package/templates/skills/apex/steps/step-04-examine.md +7 -1
- package/templates/skills/business-analyse/SKILL.md +4 -3
- package/templates/skills/business-analyse/_shared.md +9 -0
- package/templates/skills/business-analyse/schemas/application-schema.json +13 -0
- package/templates/skills/business-analyse/steps/step-00-init.md +190 -34
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +129 -10
- package/templates/skills/business-analyse/steps/step-01b-applications.md +184 -13
- package/templates/skills/business-analyse/steps/step-03c-compile.md +14 -2
- package/templates/skills/business-analyse/steps/step-03d-validate.md +5 -1
- package/templates/skills/documentation/SKILL.md +175 -9
- package/templates/skills/efcore/steps/squash/step-03-create.md +6 -4
- package/templates/skills/gitflow/_shared.md +3 -1
- package/templates/skills/gitflow/steps/step-pr.md +34 -0
- package/templates/skills/ralph-loop/SKILL.md +31 -2
- package/templates/skills/ralph-loop/references/category-rules.md +29 -0
- package/templates/skills/ralph-loop/references/compact-loop.md +85 -2
- package/templates/skills/ralph-loop/references/section-splitting.md +439 -0
- package/templates/skills/ralph-loop/references/team-orchestration.md +331 -14
- package/templates/skills/ralph-loop/steps/step-00-init.md +4 -0
- package/templates/skills/ralph-loop/steps/step-01-task.md +27 -0
- package/templates/skills/ralph-loop/steps/step-02-execute.md +206 -1
- package/templates/skills/ralph-loop/steps/step-05-report.md +19 -0
- package/scripts/health-check.sh +0 -168
- package/scripts/postinstall.js +0 -18
package/dist/mcp-entry.mjs
CHANGED
|
@@ -26396,6 +26396,7 @@ var init_config = __esm({
|
|
|
26396
26396
|
],
|
|
26397
26397
|
customTablePrefixes: [],
|
|
26398
26398
|
scopeTypes: ["Core", "Extension", "Partner", "Community"],
|
|
26399
|
+
// Incremental: {context}_v{version}_{sequence}_{Description} | Squash: {context}_v{version}
|
|
26399
26400
|
migrationFormat: "{context}_v{version}_{sequence}_{Description}",
|
|
26400
26401
|
namespaces: {
|
|
26401
26402
|
// Empty = auto-detect from .csproj files
|
|
@@ -26847,32 +26848,39 @@ async function validateMigrationNaming(structure, _config, result) {
|
|
|
26847
26848
|
return;
|
|
26848
26849
|
}
|
|
26849
26850
|
const migrationFiles = await findFiles("*.cs", { cwd: structure.migrations });
|
|
26850
|
-
const
|
|
26851
|
+
const incrementalPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d{3})_(.+)\.cs$/;
|
|
26852
|
+
const squashPattern = /^(\w+)_v(\d+\.\d+\.\d+)\.cs$/;
|
|
26851
26853
|
const designerPattern = /\.Designer\.cs$/;
|
|
26852
26854
|
for (const file of migrationFiles) {
|
|
26853
26855
|
const fileName = path8.basename(file);
|
|
26854
26856
|
if (designerPattern.test(fileName) || fileName.includes("ModelSnapshot")) {
|
|
26855
26857
|
continue;
|
|
26856
26858
|
}
|
|
26857
|
-
if (!
|
|
26859
|
+
if (!incrementalPattern.test(fileName) && !squashPattern.test(fileName)) {
|
|
26858
26860
|
result.errors.push({
|
|
26859
26861
|
type: "error",
|
|
26860
26862
|
category: "migrations",
|
|
26861
26863
|
message: `Migration "${fileName}" does not follow naming convention`,
|
|
26862
26864
|
file: path8.relative(structure.root, file),
|
|
26863
|
-
suggestion: `Expected format: {context}_v{version}_{sequence}_{Description}.cs (
|
|
26865
|
+
suggestion: `Expected format: {context}_v{version}_{sequence}_{Description}.cs (incremental) or {context}_v{version}.cs (squash)`
|
|
26864
26866
|
});
|
|
26865
26867
|
}
|
|
26866
26868
|
}
|
|
26867
|
-
const
|
|
26869
|
+
const isValidMigration = (f) => (incrementalPattern.test(f) || squashPattern.test(f)) && !f.includes("Designer");
|
|
26870
|
+
const getVersion = (f) => {
|
|
26871
|
+
const inc = incrementalPattern.exec(f);
|
|
26872
|
+
if (inc) return inc[2];
|
|
26873
|
+
const sq = squashPattern.exec(f);
|
|
26874
|
+
if (sq) return sq[2];
|
|
26875
|
+
return "0.0.0";
|
|
26876
|
+
};
|
|
26877
|
+
const orderedMigrations = migrationFiles.map((f) => path8.basename(f)).filter(isValidMigration).sort();
|
|
26868
26878
|
for (let i = 1; i < orderedMigrations.length; i++) {
|
|
26869
26879
|
const prev = orderedMigrations[i - 1];
|
|
26870
26880
|
const curr = orderedMigrations[i];
|
|
26871
|
-
const
|
|
26872
|
-
const
|
|
26873
|
-
if (
|
|
26874
|
-
const prevVersion = prevMatch[2];
|
|
26875
|
-
const currVersion = currMatch[2];
|
|
26881
|
+
const prevVersion = getVersion(prev);
|
|
26882
|
+
const currVersion = getVersion(curr);
|
|
26883
|
+
if (prevVersion !== "0.0.0" && currVersion !== "0.0.0") {
|
|
26876
26884
|
if (currVersion < prevVersion) {
|
|
26877
26885
|
result.warnings.push({
|
|
26878
26886
|
type: "warning",
|
|
@@ -27176,7 +27184,6 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
27176
27184
|
const navRouteMatch = content.match(/\[NavRoute\s*\(\s*"([^"]+)"(?:\s*,\s*Suffix\s*=\s*"([^"]+)")?\s*\)\]/);
|
|
27177
27185
|
if (navRouteMatch) {
|
|
27178
27186
|
const routePath = navRouteMatch[1];
|
|
27179
|
-
const suffix = navRouteMatch[2];
|
|
27180
27187
|
const parts = routePath.split(".");
|
|
27181
27188
|
if (parts.length < 2) {
|
|
27182
27189
|
result.warnings.push({
|
|
@@ -27197,26 +27204,14 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
27197
27204
|
suggestion: 'NavRoute paths must be lowercase (e.g., "platform.administration.users")'
|
|
27198
27205
|
});
|
|
27199
27206
|
}
|
|
27200
|
-
const expectedRoute = `api/${routePath.replace(/\./g, "/")}${suffix ? `/${suffix}` : ""}`;
|
|
27201
27207
|
const routeAttrMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
|
|
27202
27208
|
if (routeAttrMatch) {
|
|
27203
|
-
|
|
27204
|
-
|
|
27205
|
-
result.errors.push({
|
|
27206
|
-
type: "error",
|
|
27207
|
-
category: "controllers",
|
|
27208
|
-
message: `Controller "${fileName}" has [Route("${actualRoute}")] that doesn't match NavRoute "${routePath}". Expected [Route("${expectedRoute}")]`,
|
|
27209
|
-
file: path8.relative(structure.root, file),
|
|
27210
|
-
suggestion: `Change [Route] to [Route("${expectedRoute}")] to match the NavRoute convention`
|
|
27211
|
-
});
|
|
27212
|
-
}
|
|
27213
|
-
} else {
|
|
27214
|
-
result.warnings.push({
|
|
27215
|
-
type: "warning",
|
|
27209
|
+
result.errors.push({
|
|
27210
|
+
type: "error",
|
|
27216
27211
|
category: "controllers",
|
|
27217
|
-
message: `Controller "${fileName}" has [
|
|
27212
|
+
message: `Controller "${fileName}" has BOTH [Route("${routeAttrMatch[1]}")] and [NavRoute("${routePath}")]. Only [NavRoute] should be used.`,
|
|
27218
27213
|
file: path8.relative(structure.root, file),
|
|
27219
|
-
suggestion: `
|
|
27214
|
+
suggestion: `Remove [Route("${routeAttrMatch[1]}")] \u2014 [NavRoute("${routePath}")] resolves the route dynamically from the database at startup`
|
|
27220
27215
|
});
|
|
27221
27216
|
}
|
|
27222
27217
|
}
|
|
@@ -28276,26 +28271,34 @@ async function handleCheckMigrations(args, config2) {
|
|
|
28276
28271
|
async function parseMigrations(migrationsPath, rootPath) {
|
|
28277
28272
|
const files = await findFiles("*.cs", { cwd: migrationsPath });
|
|
28278
28273
|
const migrations = [];
|
|
28279
|
-
const
|
|
28274
|
+
const incrementalPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d{3})_(.+)\.cs$/;
|
|
28275
|
+
const squashPattern = /^(\w+)_v(\d+\.\d+\.\d+)\.cs$/;
|
|
28280
28276
|
for (const file of files) {
|
|
28281
28277
|
const fileName = path9.basename(file);
|
|
28282
28278
|
if (fileName.includes(".Designer.") || fileName.includes("ModelSnapshot")) {
|
|
28283
28279
|
continue;
|
|
28284
28280
|
}
|
|
28285
|
-
const
|
|
28286
|
-
|
|
28281
|
+
const incrementalMatch = incrementalPattern.exec(fileName);
|
|
28282
|
+
const squashMatch = squashPattern.exec(fileName);
|
|
28283
|
+
if (incrementalMatch) {
|
|
28287
28284
|
migrations.push({
|
|
28288
28285
|
name: fileName.replace(".cs", ""),
|
|
28289
|
-
context:
|
|
28290
|
-
|
|
28291
|
-
|
|
28292
|
-
|
|
28293
|
-
|
|
28294
|
-
|
|
28295
|
-
|
|
28286
|
+
context: incrementalMatch[1],
|
|
28287
|
+
version: incrementalMatch[2],
|
|
28288
|
+
sequence: incrementalMatch[3],
|
|
28289
|
+
description: incrementalMatch[4],
|
|
28290
|
+
file: path9.relative(rootPath, file),
|
|
28291
|
+
applied: true
|
|
28292
|
+
});
|
|
28293
|
+
} else if (squashMatch) {
|
|
28294
|
+
migrations.push({
|
|
28295
|
+
name: fileName.replace(".cs", ""),
|
|
28296
|
+
context: squashMatch[1],
|
|
28297
|
+
version: squashMatch[2],
|
|
28298
|
+
sequence: "000",
|
|
28299
|
+
description: "Squash",
|
|
28296
28300
|
file: path9.relative(rootPath, file),
|
|
28297
28301
|
applied: true
|
|
28298
|
-
// We'd need DB connection to check this
|
|
28299
28302
|
});
|
|
28300
28303
|
} else {
|
|
28301
28304
|
migrations.push({
|
|
@@ -28322,7 +28325,7 @@ function checkNamingConventions(result, _config) {
|
|
|
28322
28325
|
type: "naming",
|
|
28323
28326
|
description: `Migration "${migration.name}" does not follow naming convention`,
|
|
28324
28327
|
files: [migration.file],
|
|
28325
|
-
resolution: `Rename to format: {context}_v{version}_{sequence}_{Description} (
|
|
28328
|
+
resolution: `Rename to format: {context}_v{version}_{sequence}_{Description} (incremental) or {context}_v{version} (squash)`
|
|
28326
28329
|
});
|
|
28327
28330
|
}
|
|
28328
28331
|
if (migration.version === "0.0.0") {
|
|
@@ -28330,7 +28333,7 @@ function checkNamingConventions(result, _config) {
|
|
|
28330
28333
|
type: "naming",
|
|
28331
28334
|
description: `Migration "${migration.name}" missing version number`,
|
|
28332
28335
|
files: [migration.file],
|
|
28333
|
-
resolution: `Use format: {context}_v{version}_{sequence}_{Description} where version is semver (1.0.0, 1.2.0, etc.)`
|
|
28336
|
+
resolution: `Use format: {context}_v{version}_{sequence}_{Description} (incremental) or {context}_v{version} (squash) where version is semver (1.0.0, 1.2.0, etc.)`
|
|
28334
28337
|
});
|
|
28335
28338
|
}
|
|
28336
28339
|
if (migration.version !== "0.0.0" && !parseSemver(migration.version)) {
|
|
@@ -28438,7 +28441,7 @@ function generateSuggestions(result) {
|
|
|
28438
28441
|
}
|
|
28439
28442
|
if (result.conflicts.some((c) => c.type === "naming")) {
|
|
28440
28443
|
result.suggestions.push(
|
|
28441
|
-
"Use convention: {context}_v{version}_{sequence}_{Description} for
|
|
28444
|
+
"Use convention: {context}_v{version}_{sequence}_{Description} for incremental migrations (e.g., core_v1.0.0_001_CreateAuthUsers) or {context}_v{version} for squash migrations (e.g., core_v1.0.0)"
|
|
28442
28445
|
);
|
|
28443
28446
|
}
|
|
28444
28447
|
if (result.conflicts.some((c) => c.type === "order")) {
|
|
@@ -52792,19 +52795,24 @@ async function handleSuggestMigration(args, config2) {
|
|
|
52792
52795
|
} else {
|
|
52793
52796
|
version2 = version2 || "1.0.0";
|
|
52794
52797
|
}
|
|
52795
|
-
|
|
52796
|
-
if (
|
|
52797
|
-
|
|
52798
|
+
let migrationName;
|
|
52799
|
+
if (input.squash) {
|
|
52800
|
+
migrationName = `${context}_v${version2}`;
|
|
52801
|
+
} else {
|
|
52802
|
+
const pascalDescription = toPascalCase(sanitizedDescription);
|
|
52803
|
+
if (!pascalDescription || !/^[A-Z][a-zA-Z0-9]*$/.test(pascalDescription)) {
|
|
52804
|
+
throw new Error(`Invalid migration description after PascalCase conversion: "${pascalDescription}"`);
|
|
52805
|
+
}
|
|
52806
|
+
const sequenceStr = sequence.toString().padStart(3, "0");
|
|
52807
|
+
migrationName = `${context}_v${version2}_${sequenceStr}_${pascalDescription}`;
|
|
52798
52808
|
}
|
|
52799
|
-
const sequenceStr = sequence.toString().padStart(3, "0");
|
|
52800
|
-
const migrationName = `${context}_v${version2}_${sequenceStr}_${pascalDescription}`;
|
|
52801
52809
|
const dbContextName = context === "core" ? "CoreDbContext" : "ExtensionsDbContext";
|
|
52802
52810
|
const outputPath = context === "extensions" ? "Persistence/Migrations/Extensions" : "Persistence/Migrations";
|
|
52803
52811
|
const command = `dotnet ef migrations add ${migrationName} --context ${dbContextName} --project ../SmartStack.Infrastructure -o ${outputPath}`;
|
|
52804
52812
|
const lines = [];
|
|
52805
52813
|
lines.push("# Migration Name Suggestion");
|
|
52806
52814
|
lines.push("");
|
|
52807
|
-
lines.push(
|
|
52815
|
+
lines.push(`## Suggested Name (${input.squash ? "Squash" : "Incremental"})`);
|
|
52808
52816
|
lines.push("```");
|
|
52809
52817
|
lines.push(migrationName);
|
|
52810
52818
|
lines.push("```");
|
|
@@ -52822,8 +52830,13 @@ async function handleSuggestMigration(args, config2) {
|
|
|
52822
52830
|
lines.push(`| DbContext | \`${dbContextName}\` | EF Core DbContext to use |`);
|
|
52823
52831
|
lines.push(`| Schema | \`${context}\` | Database schema for tables |`);
|
|
52824
52832
|
lines.push(`| Version | \`v${version2}\` | Semver version |`);
|
|
52825
|
-
|
|
52826
|
-
|
|
52833
|
+
if (!input.squash) {
|
|
52834
|
+
const sequenceStr = sequence.toString().padStart(3, "0");
|
|
52835
|
+
lines.push(`| Sequence | \`${sequenceStr}\` | Order in version |`);
|
|
52836
|
+
lines.push(`| Description | \`${toPascalCase(sanitizedDescription)}\` | Migration description |`);
|
|
52837
|
+
} else {
|
|
52838
|
+
lines.push(`| Mode | \`squash\` | Consolidated baseline (no sequence/description) |`);
|
|
52839
|
+
}
|
|
52827
52840
|
lines.push("");
|
|
52828
52841
|
lines.push("> **Note**: Migrations are stored in separate history tables:");
|
|
52829
52842
|
lines.push(`> - Core: \`core.__EFMigrationsHistory\``);
|
|
@@ -52845,15 +52858,17 @@ async function findExistingMigrations(structure, config2, context) {
|
|
|
52845
52858
|
const migrationsPath = path12.join(infraPath, "Persistence", "Migrations");
|
|
52846
52859
|
try {
|
|
52847
52860
|
const migrationFiles = await findFiles("*.cs", { cwd: migrationsPath });
|
|
52848
|
-
const
|
|
52861
|
+
const incrementalPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d+)_(\w+)\.cs$/;
|
|
52862
|
+
const squashPattern = /^(\w+)_v(\d+\.\d+\.\d+)\.cs$/;
|
|
52849
52863
|
for (const file of migrationFiles) {
|
|
52850
52864
|
const fileName = path12.basename(file);
|
|
52851
52865
|
if (fileName.includes(".Designer.") || fileName.includes("ModelSnapshot")) {
|
|
52852
52866
|
continue;
|
|
52853
52867
|
}
|
|
52854
|
-
const
|
|
52855
|
-
|
|
52856
|
-
|
|
52868
|
+
const incrementalMatch = fileName.match(incrementalPattern);
|
|
52869
|
+
const squashMatch = fileName.match(squashPattern);
|
|
52870
|
+
if (incrementalMatch) {
|
|
52871
|
+
const [, ctx, ver, seq, desc] = incrementalMatch;
|
|
52857
52872
|
if (ctx === context || !context) {
|
|
52858
52873
|
migrations.push({
|
|
52859
52874
|
name: fileName.replace(".cs", ""),
|
|
@@ -52863,6 +52878,17 @@ async function findExistingMigrations(structure, config2, context) {
|
|
|
52863
52878
|
description: desc
|
|
52864
52879
|
});
|
|
52865
52880
|
}
|
|
52881
|
+
} else if (squashMatch) {
|
|
52882
|
+
const [, ctx, ver] = squashMatch;
|
|
52883
|
+
if (ctx === context || !context) {
|
|
52884
|
+
migrations.push({
|
|
52885
|
+
name: fileName.replace(".cs", ""),
|
|
52886
|
+
context: ctx,
|
|
52887
|
+
version: ver,
|
|
52888
|
+
sequence: 0,
|
|
52889
|
+
description: "Squash"
|
|
52890
|
+
});
|
|
52891
|
+
}
|
|
52866
52892
|
}
|
|
52867
52893
|
}
|
|
52868
52894
|
migrations.sort((a, b) => {
|
|
@@ -52895,7 +52921,7 @@ var init_suggest_migration = __esm({
|
|
|
52895
52921
|
init_logger();
|
|
52896
52922
|
suggestMigrationTool = {
|
|
52897
52923
|
name: "suggest_migration",
|
|
52898
|
-
description: "Suggest a migration name following SmartStack conventions ({context}_v{version}_{sequence}_{Description})",
|
|
52924
|
+
description: "Suggest a migration name following SmartStack conventions ({context}_v{version}_{sequence}_{Description} for incremental, {context}_v{version} for squash)",
|
|
52899
52925
|
inputSchema: {
|
|
52900
52926
|
type: "object",
|
|
52901
52927
|
properties: {
|
|
@@ -52911,6 +52937,10 @@ var init_suggest_migration = __esm({
|
|
|
52911
52937
|
version: {
|
|
52912
52938
|
type: "string",
|
|
52913
52939
|
description: 'Semver version (e.g., "1.0.0", "1.2.0"). If not provided, uses latest from existing migrations.'
|
|
52940
|
+
},
|
|
52941
|
+
squash: {
|
|
52942
|
+
type: "boolean",
|
|
52943
|
+
description: "If true, generates squash format: {context}_v{version} (no sequence/description). Used before merge to consolidate feature migrations."
|
|
52914
52944
|
}
|
|
52915
52945
|
},
|
|
52916
52946
|
required: ["description"]
|
|
@@ -52919,7 +52949,8 @@ var init_suggest_migration = __esm({
|
|
|
52919
52949
|
SuggestMigrationInputSchema2 = external_exports.object({
|
|
52920
52950
|
description: external_exports.string().min(3, "Migration description must be at least 3 characters").max(100, "Migration description must be at most 100 characters").describe("Description of what the migration does"),
|
|
52921
52951
|
context: external_exports.enum(["core", "extensions"]).optional().describe("DbContext name (default: auto-detected from project config)"),
|
|
52922
|
-
version: external_exports.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format (e.g., "1.0.0")').optional().describe('Semver version (e.g., "1.0.0")')
|
|
52952
|
+
version: external_exports.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format (e.g., "1.0.0")').optional().describe('Semver version (e.g., "1.0.0")'),
|
|
52953
|
+
squash: external_exports.boolean().optional().describe("If true, generates squash format: {context}_v{version} (no sequence/description)")
|
|
52923
52954
|
});
|
|
52924
52955
|
}
|
|
52925
52956
|
});
|
|
@@ -57631,7 +57662,6 @@ async function discoverNavRoutes(structure, scope, warnings) {
|
|
|
57631
57662
|
permissions.push(match2[1]);
|
|
57632
57663
|
}
|
|
57633
57664
|
const fullNavRoute = suffix ? `${navRoute}.${suffix}` : navRoute;
|
|
57634
|
-
const expectedRoute = `api/${navRouteToUrlPath(navRoute)}${suffix ? `/${toKebabCase(suffix)}` : ""}`;
|
|
57635
57665
|
routes.push({
|
|
57636
57666
|
navRoute: fullNavRoute,
|
|
57637
57667
|
apiPath: `/api/${navRouteToUrlPath(navRoute)}${suffix ? `/${toKebabCase(suffix)}` : ""}`,
|
|
@@ -57642,12 +57672,9 @@ async function discoverNavRoutes(structure, scope, warnings) {
|
|
|
57642
57672
|
});
|
|
57643
57673
|
const routeAttrMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
|
|
57644
57674
|
if (routeAttrMatch && warnings) {
|
|
57645
|
-
|
|
57646
|
-
|
|
57647
|
-
|
|
57648
|
-
`WARNING: ${controllerName}Controller has [Route("${actualRoute}")] that doesn't match NavRoute "${navRoute}". Expected [Route("${expectedRoute}")]`
|
|
57649
|
-
);
|
|
57650
|
-
}
|
|
57675
|
+
warnings.push(
|
|
57676
|
+
`WARNING: ${controllerName}Controller has [Route("${routeAttrMatch[1]}")] alongside [NavRoute]. Remove [Route] \u2014 NavRoute resolves routes dynamically from the database.`
|
|
57677
|
+
);
|
|
57651
57678
|
}
|
|
57652
57679
|
}
|
|
57653
57680
|
} catch {
|
|
@@ -64918,8 +64945,9 @@ builder.HasOne(e => e.User)
|
|
|
64918
64945
|
|
|
64919
64946
|
### Naming Format
|
|
64920
64947
|
|
|
64921
|
-
Migrations
|
|
64948
|
+
Migrations follow two naming patterns depending on context:
|
|
64922
64949
|
|
|
64950
|
+
**Incremental (during development):**
|
|
64923
64951
|
\`\`\`
|
|
64924
64952
|
${migrationFormat}
|
|
64925
64953
|
\`\`\`
|
|
@@ -64931,30 +64959,50 @@ ${migrationFormat}
|
|
|
64931
64959
|
| \`{sequence}\` | Order in version | \`001\`, \`002\` |
|
|
64932
64960
|
| \`{Description}\` | Action (PascalCase) | \`CreateAuthUsers\` |
|
|
64933
64961
|
|
|
64962
|
+
**Squash (before merge, consolidated baseline):**
|
|
64963
|
+
\`\`\`
|
|
64964
|
+
{context}_v{version}
|
|
64965
|
+
\`\`\`
|
|
64966
|
+
|
|
64967
|
+
The squash format has no sequence or description \u2014 it represents the complete state at a version.
|
|
64968
|
+
|
|
64934
64969
|
**Examples:**
|
|
64935
|
-
- \`core_v1.0.0_001_InitialSchema.cs\`
|
|
64936
|
-
- \`core_v1.0.0_002_CreateAuthUsers.cs\`
|
|
64937
|
-
- \`core_v1.2.0_001_AddUserProfiles.cs\`
|
|
64938
|
-
- \`extensions_v1.0.0_001_AddClientFeatures.cs\`
|
|
64970
|
+
- \`core_v1.0.0_001_InitialSchema.cs\` (incremental)
|
|
64971
|
+
- \`core_v1.0.0_002_CreateAuthUsers.cs\` (incremental)
|
|
64972
|
+
- \`core_v1.2.0_001_AddUserProfiles.cs\` (incremental)
|
|
64973
|
+
- \`extensions_v1.0.0_001_AddClientFeatures.cs\` (incremental)
|
|
64974
|
+
- \`core_v1.0.0.cs\` (squash \u2014 consolidated baseline)
|
|
64939
64975
|
|
|
64940
64976
|
### Creating Migrations
|
|
64941
64977
|
|
|
64942
64978
|
\`\`\`bash
|
|
64943
|
-
# Create
|
|
64979
|
+
# Create an incremental migration
|
|
64944
64980
|
dotnet ef migrations add core_v1.0.0_001_InitialSchema
|
|
64945
64981
|
|
|
64946
64982
|
# With context specified
|
|
64947
64983
|
dotnet ef migrations add core_v1.2.0_001_AddUserProfiles --context ApplicationDbContext
|
|
64948
64984
|
\`\`\`
|
|
64949
64985
|
|
|
64986
|
+
### Squash Workflow
|
|
64987
|
+
|
|
64988
|
+
Before creating a PR, squash all feature migrations into one:
|
|
64989
|
+
|
|
64990
|
+
\`\`\`bash
|
|
64991
|
+
# Run /efcore squash \u2014 produces: core_v1.0.0 (no sequence/description)
|
|
64992
|
+
dotnet ef migrations add core_v1.0.0 --context CoreDbContext
|
|
64993
|
+
\`\`\`
|
|
64994
|
+
|
|
64995
|
+
After a squash at \`core_v1.0.0\`, the next incremental migration starts at \`core_v1.0.0_001_*\`.
|
|
64996
|
+
|
|
64950
64997
|
### Migration Rules
|
|
64951
64998
|
|
|
64952
|
-
1. **One migration per feature** -
|
|
64999
|
+
1. **One migration per feature** - Squash before merge to ensure a single migration per feature branch
|
|
64953
65000
|
2. **Version-based naming** - Use semver (v1.0.0, v1.2.0) to link migrations to releases
|
|
64954
|
-
3. **Sequence numbers** - Use NNN (001, 002, etc.) for migrations in the same version
|
|
65001
|
+
3. **Sequence numbers** - Use NNN (001, 002, etc.) for incremental migrations in the same version
|
|
64955
65002
|
4. **Context prefix** - Use \`core_\` for platform tables, \`extensions_\` for client extensions
|
|
64956
|
-
5. **Descriptive names** - Use clear PascalCase descriptions (CreateAuthUsers, AddUserProfiles, etc.)
|
|
65003
|
+
5. **Descriptive names** - Use clear PascalCase descriptions for incremental (CreateAuthUsers, AddUserProfiles, etc.)
|
|
64957
65004
|
6. **Schema must be specified** - All tables must specify their schema in ToTable()
|
|
65005
|
+
7. **Squash before PR** - Feature branches must have exactly 1 migration (squashed) before creating a PR
|
|
64958
65006
|
|
|
64959
65007
|
---
|
|
64960
65008
|
|