@atlashub/smartstack-cli 3.35.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.
@@ -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 migrationPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d{3})_(.+)\.cs$/;
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 (!migrationPattern.test(fileName)) {
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 (e.g., core_v1.0.0_001_CreateAuthUsers.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 orderedMigrations = migrationFiles.map((f) => path8.basename(f)).filter((f) => migrationPattern.test(f) && !f.includes("Designer")).sort();
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 prevMatch = migrationPattern.exec(prev);
26872
- const currMatch = migrationPattern.exec(curr);
26873
- if (prevMatch && currMatch) {
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",
@@ -28263,26 +28271,34 @@ async function handleCheckMigrations(args, config2) {
28263
28271
  async function parseMigrations(migrationsPath, rootPath) {
28264
28272
  const files = await findFiles("*.cs", { cwd: migrationsPath });
28265
28273
  const migrations = [];
28266
- const pattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d{3})_(.+)\.cs$/;
28274
+ const incrementalPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d{3})_(.+)\.cs$/;
28275
+ const squashPattern = /^(\w+)_v(\d+\.\d+\.\d+)\.cs$/;
28267
28276
  for (const file of files) {
28268
28277
  const fileName = path9.basename(file);
28269
28278
  if (fileName.includes(".Designer.") || fileName.includes("ModelSnapshot")) {
28270
28279
  continue;
28271
28280
  }
28272
- const match2 = pattern.exec(fileName);
28273
- if (match2) {
28281
+ const incrementalMatch = incrementalPattern.exec(fileName);
28282
+ const squashMatch = squashPattern.exec(fileName);
28283
+ if (incrementalMatch) {
28284
+ migrations.push({
28285
+ name: fileName.replace(".cs", ""),
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) {
28274
28294
  migrations.push({
28275
28295
  name: fileName.replace(".cs", ""),
28276
- context: match2[1],
28277
- // DbContext (core, extensions, etc.)
28278
- version: match2[2],
28279
- // Semver version (1.0.0, 1.2.0, etc.)
28280
- sequence: match2[3],
28281
- // Sequence number (001, 002, etc.)
28282
- description: match2[4],
28296
+ context: squashMatch[1],
28297
+ version: squashMatch[2],
28298
+ sequence: "000",
28299
+ description: "Squash",
28283
28300
  file: path9.relative(rootPath, file),
28284
28301
  applied: true
28285
- // We'd need DB connection to check this
28286
28302
  });
28287
28303
  } else {
28288
28304
  migrations.push({
@@ -28309,7 +28325,7 @@ function checkNamingConventions(result, _config) {
28309
28325
  type: "naming",
28310
28326
  description: `Migration "${migration.name}" does not follow naming convention`,
28311
28327
  files: [migration.file],
28312
- resolution: `Rename to format: {context}_v{version}_{sequence}_{Description} (e.g., core_v1.0.0_001_CreateAuthUsers)`
28328
+ resolution: `Rename to format: {context}_v{version}_{sequence}_{Description} (incremental) or {context}_v{version} (squash)`
28313
28329
  });
28314
28330
  }
28315
28331
  if (migration.version === "0.0.0") {
@@ -28317,7 +28333,7 @@ function checkNamingConventions(result, _config) {
28317
28333
  type: "naming",
28318
28334
  description: `Migration "${migration.name}" missing version number`,
28319
28335
  files: [migration.file],
28320
- 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.)`
28321
28337
  });
28322
28338
  }
28323
28339
  if (migration.version !== "0.0.0" && !parseSemver(migration.version)) {
@@ -28425,7 +28441,7 @@ function generateSuggestions(result) {
28425
28441
  }
28426
28442
  if (result.conflicts.some((c) => c.type === "naming")) {
28427
28443
  result.suggestions.push(
28428
- "Use convention: {context}_v{version}_{sequence}_{Description} for migration naming (e.g., core_v1.0.0_001_CreateAuthUsers)"
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)"
28429
28445
  );
28430
28446
  }
28431
28447
  if (result.conflicts.some((c) => c.type === "order")) {
@@ -52779,19 +52795,24 @@ async function handleSuggestMigration(args, config2) {
52779
52795
  } else {
52780
52796
  version2 = version2 || "1.0.0";
52781
52797
  }
52782
- const pascalDescription = toPascalCase(sanitizedDescription);
52783
- if (!pascalDescription || !/^[A-Z][a-zA-Z0-9]*$/.test(pascalDescription)) {
52784
- throw new Error(`Invalid migration description after PascalCase conversion: "${pascalDescription}"`);
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}`;
52785
52808
  }
52786
- const sequenceStr = sequence.toString().padStart(3, "0");
52787
- const migrationName = `${context}_v${version2}_${sequenceStr}_${pascalDescription}`;
52788
52809
  const dbContextName = context === "core" ? "CoreDbContext" : "ExtensionsDbContext";
52789
52810
  const outputPath = context === "extensions" ? "Persistence/Migrations/Extensions" : "Persistence/Migrations";
52790
52811
  const command = `dotnet ef migrations add ${migrationName} --context ${dbContextName} --project ../SmartStack.Infrastructure -o ${outputPath}`;
52791
52812
  const lines = [];
52792
52813
  lines.push("# Migration Name Suggestion");
52793
52814
  lines.push("");
52794
- lines.push("## Suggested Name");
52815
+ lines.push(`## Suggested Name (${input.squash ? "Squash" : "Incremental"})`);
52795
52816
  lines.push("```");
52796
52817
  lines.push(migrationName);
52797
52818
  lines.push("```");
@@ -52809,8 +52830,13 @@ async function handleSuggestMigration(args, config2) {
52809
52830
  lines.push(`| DbContext | \`${dbContextName}\` | EF Core DbContext to use |`);
52810
52831
  lines.push(`| Schema | \`${context}\` | Database schema for tables |`);
52811
52832
  lines.push(`| Version | \`v${version2}\` | Semver version |`);
52812
- lines.push(`| Sequence | \`${sequenceStr}\` | Order in version |`);
52813
- lines.push(`| Description | \`${pascalDescription}\` | Migration description |`);
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
+ }
52814
52840
  lines.push("");
52815
52841
  lines.push("> **Note**: Migrations are stored in separate history tables:");
52816
52842
  lines.push(`> - Core: \`core.__EFMigrationsHistory\``);
@@ -52832,15 +52858,17 @@ async function findExistingMigrations(structure, config2, context) {
52832
52858
  const migrationsPath = path12.join(infraPath, "Persistence", "Migrations");
52833
52859
  try {
52834
52860
  const migrationFiles = await findFiles("*.cs", { cwd: migrationsPath });
52835
- const migrationPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d+)_(\w+)\.cs$/;
52861
+ const incrementalPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d+)_(\w+)\.cs$/;
52862
+ const squashPattern = /^(\w+)_v(\d+\.\d+\.\d+)\.cs$/;
52836
52863
  for (const file of migrationFiles) {
52837
52864
  const fileName = path12.basename(file);
52838
52865
  if (fileName.includes(".Designer.") || fileName.includes("ModelSnapshot")) {
52839
52866
  continue;
52840
52867
  }
52841
- const match2 = fileName.match(migrationPattern);
52842
- if (match2) {
52843
- const [, ctx, ver, seq, desc] = match2;
52868
+ const incrementalMatch = fileName.match(incrementalPattern);
52869
+ const squashMatch = fileName.match(squashPattern);
52870
+ if (incrementalMatch) {
52871
+ const [, ctx, ver, seq, desc] = incrementalMatch;
52844
52872
  if (ctx === context || !context) {
52845
52873
  migrations.push({
52846
52874
  name: fileName.replace(".cs", ""),
@@ -52850,6 +52878,17 @@ async function findExistingMigrations(structure, config2, context) {
52850
52878
  description: desc
52851
52879
  });
52852
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
+ }
52853
52892
  }
52854
52893
  }
52855
52894
  migrations.sort((a, b) => {
@@ -52882,7 +52921,7 @@ var init_suggest_migration = __esm({
52882
52921
  init_logger();
52883
52922
  suggestMigrationTool = {
52884
52923
  name: "suggest_migration",
52885
- 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)",
52886
52925
  inputSchema: {
52887
52926
  type: "object",
52888
52927
  properties: {
@@ -52898,6 +52937,10 @@ var init_suggest_migration = __esm({
52898
52937
  version: {
52899
52938
  type: "string",
52900
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."
52901
52944
  }
52902
52945
  },
52903
52946
  required: ["description"]
@@ -52906,7 +52949,8 @@ var init_suggest_migration = __esm({
52906
52949
  SuggestMigrationInputSchema2 = external_exports.object({
52907
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"),
52908
52951
  context: external_exports.enum(["core", "extensions"]).optional().describe("DbContext name (default: auto-detected from project config)"),
52909
- 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)")
52910
52954
  });
52911
52955
  }
52912
52956
  });
@@ -64901,8 +64945,9 @@ builder.HasOne(e => e.User)
64901
64945
 
64902
64946
  ### Naming Format
64903
64947
 
64904
- Migrations MUST follow this naming pattern:
64948
+ Migrations follow two naming patterns depending on context:
64905
64949
 
64950
+ **Incremental (during development):**
64906
64951
  \`\`\`
64907
64952
  ${migrationFormat}
64908
64953
  \`\`\`
@@ -64914,30 +64959,50 @@ ${migrationFormat}
64914
64959
  | \`{sequence}\` | Order in version | \`001\`, \`002\` |
64915
64960
  | \`{Description}\` | Action (PascalCase) | \`CreateAuthUsers\` |
64916
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
+
64917
64969
  **Examples:**
64918
- - \`core_v1.0.0_001_InitialSchema.cs\`
64919
- - \`core_v1.0.0_002_CreateAuthUsers.cs\`
64920
- - \`core_v1.2.0_001_AddUserProfiles.cs\`
64921
- - \`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)
64922
64975
 
64923
64976
  ### Creating Migrations
64924
64977
 
64925
64978
  \`\`\`bash
64926
- # Create a new migration
64979
+ # Create an incremental migration
64927
64980
  dotnet ef migrations add core_v1.0.0_001_InitialSchema
64928
64981
 
64929
64982
  # With context specified
64930
64983
  dotnet ef migrations add core_v1.2.0_001_AddUserProfiles --context ApplicationDbContext
64931
64984
  \`\`\`
64932
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
+
64933
64997
  ### Migration Rules
64934
64998
 
64935
- 1. **One migration per feature** - Group related changes in a single migration
64999
+ 1. **One migration per feature** - Squash before merge to ensure a single migration per feature branch
64936
65000
  2. **Version-based naming** - Use semver (v1.0.0, v1.2.0) to link migrations to releases
64937
- 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
64938
65002
  4. **Context prefix** - Use \`core_\` for platform tables, \`extensions_\` for client extensions
64939
- 5. **Descriptive names** - Use clear PascalCase descriptions (CreateAuthUsers, AddUserProfiles, etc.)
65003
+ 5. **Descriptive names** - Use clear PascalCase descriptions for incremental (CreateAuthUsers, AddUserProfiles, etc.)
64940
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
64941
65006
 
64942
65007
  ---
64943
65008