@atlashub/smartstack-mcp 1.9.0 → 1.13.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/dist/index.js +240 -140
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/migrations/seed-permissions.cs.hbs +0 -108
package/dist/index.js
CHANGED
|
@@ -1822,6 +1822,10 @@ var scaffoldExtensionTool = {
|
|
|
1822
1822
|
type: "boolean",
|
|
1823
1823
|
description: "For feature type: generate repository pattern"
|
|
1824
1824
|
},
|
|
1825
|
+
withSeedData: {
|
|
1826
|
+
type: "boolean",
|
|
1827
|
+
description: "For entity type: generate centralized SeedData file in Seeding/Data/{Domain}/"
|
|
1828
|
+
},
|
|
1825
1829
|
entityProperties: {
|
|
1826
1830
|
type: "array",
|
|
1827
1831
|
items: {
|
|
@@ -1986,8 +1990,12 @@ async function scaffoldFeature(name, options, structure, config, result, dryRun
|
|
|
1986
1990
|
result.instructions.push("---");
|
|
1987
1991
|
result.instructions.push(`## Summary: Generated ${generated.join(" + ")}`);
|
|
1988
1992
|
result.instructions.push("");
|
|
1993
|
+
const schema = options?.schema || (isClientExtension ? "extensions" : "core");
|
|
1994
|
+
const dbContextName = schema === "extensions" ? "ExtensionsDbContext" : "CoreDbContext";
|
|
1995
|
+
const dbContextInterface = schema === "extensions" ? "IExtensionsDbContext" : "ICoreDbContext";
|
|
1996
|
+
const migrationPrefix = schema === "extensions" ? "ext" : "core";
|
|
1989
1997
|
result.instructions.push("### Next Steps:");
|
|
1990
|
-
result.instructions.push(`1. Add DbSet to
|
|
1998
|
+
result.instructions.push(`1. Add DbSet to ${dbContextInterface} and ${dbContextName}: \`public DbSet<${name}> ${name}s => Set<${name}>();\``);
|
|
1991
1999
|
if (withRepository) {
|
|
1992
2000
|
result.instructions.push(`2. Register repository: \`services.AddScoped<I${name}Repository, ${name}Repository>();\``);
|
|
1993
2001
|
}
|
|
@@ -1995,8 +2003,8 @@ async function scaffoldFeature(name, options, structure, config, result, dryRun
|
|
|
1995
2003
|
if (withValidation) {
|
|
1996
2004
|
result.instructions.push(`${withRepository ? "4" : "3"}. Register validators: \`services.AddValidatorsFromAssemblyContaining<Create${name}DtoValidator>();\``);
|
|
1997
2005
|
}
|
|
1998
|
-
result.instructions.push(`${withRepository ? withValidation ? "5" : "4" : withValidation ? "4" : "3"}. Create migration: \`dotnet ef migrations add ${
|
|
1999
|
-
result.instructions.push(`${withRepository ? withValidation ? "6" : "5" : withValidation ? "5" : "4"}. Run migration: \`dotnet ef database update\``);
|
|
2006
|
+
result.instructions.push(`${withRepository ? withValidation ? "5" : "4" : withValidation ? "4" : "3"}. Create migration: \`dotnet ef migrations add ${migrationPrefix}_vX.X.X_XXX_Add${name} --context ${dbContextName}\``);
|
|
2007
|
+
result.instructions.push(`${withRepository ? withValidation ? "6" : "5" : withValidation ? "5" : "4"}. Run migration: \`dotnet ef database update --context ${dbContextName}\``);
|
|
2000
2008
|
if (!skipComponent) {
|
|
2001
2009
|
result.instructions.push(`Import component: \`import { ${name} } from './components/${name}';\``);
|
|
2002
2010
|
}
|
|
@@ -2325,17 +2333,136 @@ public class {{name}}Configuration : IEntityTypeConfiguration<{{name}}>
|
|
|
2325
2333
|
}
|
|
2326
2334
|
result.files.push({ path: entityFilePath, content: entityContent, type: "created" });
|
|
2327
2335
|
result.files.push({ path: configFilePath, content: configContent, type: "created" });
|
|
2328
|
-
|
|
2336
|
+
const dbContextName = schema === "extensions" ? "ExtensionsDbContext" : "CoreDbContext";
|
|
2337
|
+
const dbContextInterface = schema === "extensions" ? "IExtensionsDbContext" : "ICoreDbContext";
|
|
2338
|
+
const migrationPrefix = schema === "extensions" ? "ext" : "core";
|
|
2339
|
+
result.instructions.push(`Add DbSet to ${dbContextInterface}:`);
|
|
2340
|
+
result.instructions.push(`public DbSet<${name}> ${name}s => Set<${name}>();`);
|
|
2341
|
+
result.instructions.push("");
|
|
2342
|
+
result.instructions.push(`And implementation in ${dbContextName}:`);
|
|
2329
2343
|
result.instructions.push(`public DbSet<${name}> ${name}s => Set<${name}>();`);
|
|
2330
2344
|
result.instructions.push("");
|
|
2331
2345
|
result.instructions.push("Create migration:");
|
|
2332
|
-
result.instructions.push(`dotnet ef migrations add
|
|
2346
|
+
result.instructions.push(`dotnet ef migrations add ${migrationPrefix}_vX.X.X_XXX_Add${name} --context ${dbContextName}`);
|
|
2333
2347
|
result.instructions.push("");
|
|
2334
2348
|
result.instructions.push("Required fields from BaseEntity:");
|
|
2335
2349
|
result.instructions.push(`- Id (Guid), ${isSystemEntity ? "" : "TenantId (Guid), "}Code (string, lowercase)`);
|
|
2336
2350
|
result.instructions.push("- CreatedAt, UpdatedAt, CreatedBy, UpdatedBy (audit)");
|
|
2337
2351
|
result.instructions.push("- IsDeleted, DeletedAt, DeletedBy (soft delete)");
|
|
2338
2352
|
result.instructions.push("- RowVersion (concurrency)");
|
|
2353
|
+
if (options?.withSeedData) {
|
|
2354
|
+
result.instructions.push("");
|
|
2355
|
+
result.instructions.push("### Seed Data");
|
|
2356
|
+
await scaffoldSeedData(name, options, structure, config, result, dryRun);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
async function scaffoldSeedData(name, options, structure, config, result, dryRun = false) {
|
|
2360
|
+
const tablePrefix = options?.tablePrefix || "ref_";
|
|
2361
|
+
const domainMap = {
|
|
2362
|
+
"auth_": "Authorization",
|
|
2363
|
+
"nav_": "Navigation",
|
|
2364
|
+
"usr_": "User",
|
|
2365
|
+
"wkf_": "Communications",
|
|
2366
|
+
"cfg_": "Configuration",
|
|
2367
|
+
"ai_": "AI",
|
|
2368
|
+
"entra_": "Entra",
|
|
2369
|
+
"ref_": "Reference",
|
|
2370
|
+
"support_": "Support",
|
|
2371
|
+
"loc_": "Localization"
|
|
2372
|
+
};
|
|
2373
|
+
const domain = options?.seedDataDomain || domainMap[tablePrefix] || "Reference";
|
|
2374
|
+
const isSystemEntity = options?.isSystemEntity || false;
|
|
2375
|
+
const seedDataTemplate = `using SmartStack.Domain.{{domainNamespace}};
|
|
2376
|
+
|
|
2377
|
+
namespace SmartStack.Infrastructure.Persistence.Seeding.Data.{{domain}};
|
|
2378
|
+
|
|
2379
|
+
/// <summary>
|
|
2380
|
+
/// Donnees seed pour {{name}}.
|
|
2381
|
+
/// Centralise les IDs et donnees d'initialisation.
|
|
2382
|
+
/// </summary>
|
|
2383
|
+
public static class {{name}}SeedData
|
|
2384
|
+
{
|
|
2385
|
+
// ============================================================
|
|
2386
|
+
// IDs - Documenter chaque ID avec son role
|
|
2387
|
+
// ============================================================
|
|
2388
|
+
|
|
2389
|
+
/// <summary>ID exemple - A remplacer par vos IDs</summary>
|
|
2390
|
+
public static readonly Guid ExampleId = Guid.Parse("{{exampleGuid}}");
|
|
2391
|
+
|
|
2392
|
+
// ============================================================
|
|
2393
|
+
// CODES / CONSTANTS
|
|
2394
|
+
// ============================================================
|
|
2395
|
+
|
|
2396
|
+
public const string ExampleCode = "example";
|
|
2397
|
+
|
|
2398
|
+
// ============================================================
|
|
2399
|
+
// SEED DATA
|
|
2400
|
+
// ============================================================
|
|
2401
|
+
|
|
2402
|
+
/// <summary>
|
|
2403
|
+
/// Retourne toutes les donnees seed pour {{name}}.
|
|
2404
|
+
/// Appel\xE9 depuis {{name}}Configuration.HasData()
|
|
2405
|
+
/// </summary>
|
|
2406
|
+
public static object[] GetSeedData()
|
|
2407
|
+
{
|
|
2408
|
+
var seedDate = SeedConstants.SeedDate;
|
|
2409
|
+
|
|
2410
|
+
return new object[]
|
|
2411
|
+
{
|
|
2412
|
+
// Exemple - A remplacer par vos donnees
|
|
2413
|
+
new
|
|
2414
|
+
{
|
|
2415
|
+
Id = ExampleId,
|
|
2416
|
+
{{#unless isSystemEntity}}
|
|
2417
|
+
TenantId = (Guid?)null, // Seed data systeme sans tenant
|
|
2418
|
+
{{/unless}}
|
|
2419
|
+
Code = ExampleCode,
|
|
2420
|
+
// TODO: Ajouter les proprietes specifiques
|
|
2421
|
+
IsDeleted = false,
|
|
2422
|
+
CreatedAt = seedDate
|
|
2423
|
+
}
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
`;
|
|
2428
|
+
const exampleGuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
2429
|
+
const r = Math.random() * 16 | 0;
|
|
2430
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
2431
|
+
return v.toString(16);
|
|
2432
|
+
});
|
|
2433
|
+
const context = {
|
|
2434
|
+
name,
|
|
2435
|
+
domain,
|
|
2436
|
+
domainNamespace: domain,
|
|
2437
|
+
isSystemEntity,
|
|
2438
|
+
exampleGuid
|
|
2439
|
+
};
|
|
2440
|
+
const seedDataContent = Handlebars.compile(seedDataTemplate)(context);
|
|
2441
|
+
const projectRoot = config.smartstack.projectPath;
|
|
2442
|
+
const infraPath = structure.infrastructure || path8.join(projectRoot, "Infrastructure");
|
|
2443
|
+
const seedDataPath = path8.join(infraPath, "Persistence", "Seeding", "Data", domain);
|
|
2444
|
+
const seedDataFilePath = path8.join(seedDataPath, `${name}SeedData.cs`);
|
|
2445
|
+
validatePathSecurity(seedDataFilePath, projectRoot);
|
|
2446
|
+
if (!dryRun) {
|
|
2447
|
+
await ensureDirectory(seedDataPath);
|
|
2448
|
+
await writeText(seedDataFilePath, seedDataContent);
|
|
2449
|
+
}
|
|
2450
|
+
result.files.push({ path: seedDataFilePath, content: seedDataContent, type: "created" });
|
|
2451
|
+
result.instructions.push(`SeedData file generated in Seeding/Data/${domain}/`);
|
|
2452
|
+
result.instructions.push("");
|
|
2453
|
+
result.instructions.push("Update your Configuration to use centralized SeedData:");
|
|
2454
|
+
result.instructions.push("```csharp");
|
|
2455
|
+
result.instructions.push(`// In ${name}Configuration.cs`);
|
|
2456
|
+
result.instructions.push(`using SmartStack.Infrastructure.Persistence.Seeding.Data.${domain};`);
|
|
2457
|
+
result.instructions.push("");
|
|
2458
|
+
result.instructions.push(`builder.HasData(${name}SeedData.GetSeedData());`);
|
|
2459
|
+
result.instructions.push("```");
|
|
2460
|
+
result.instructions.push("");
|
|
2461
|
+
result.instructions.push("Pattern to follow:");
|
|
2462
|
+
result.instructions.push("1. Define public static readonly Guid IDs");
|
|
2463
|
+
result.instructions.push("2. Define const string codes");
|
|
2464
|
+
result.instructions.push("3. Implement GetSeedData() returning object[]");
|
|
2465
|
+
result.instructions.push("4. Reference other SeedData IDs for foreign keys");
|
|
2339
2466
|
}
|
|
2340
2467
|
async function scaffoldController(name, options, structure, config, result, dryRun = false) {
|
|
2341
2468
|
const namespace = options?.namespace || `${config.conventions.namespaces.api}.Controllers`;
|
|
@@ -2991,6 +3118,8 @@ public class Update{{name}}DtoValidator : AbstractValidator<Update{{name}}Dto>
|
|
|
2991
3118
|
}
|
|
2992
3119
|
async function scaffoldRepository(name, options, structure, config, result, dryRun = false) {
|
|
2993
3120
|
const isSystemEntity = options?.isSystemEntity || false;
|
|
3121
|
+
const schema = options?.schema || config.conventions.schemas.platform;
|
|
3122
|
+
const dbContextName = schema === "extensions" ? "ExtensionsDbContext" : "CoreDbContext";
|
|
2994
3123
|
const interfaceTemplate = `using System;
|
|
2995
3124
|
using System.Collections.Generic;
|
|
2996
3125
|
using System.Threading;
|
|
@@ -3042,9 +3171,9 @@ namespace ${config.conventions.namespaces.infrastructure}.Repositories;
|
|
|
3042
3171
|
/// </summary>
|
|
3043
3172
|
public class {{name}}Repository : I{{name}}Repository
|
|
3044
3173
|
{
|
|
3045
|
-
private readonly
|
|
3174
|
+
private readonly {{dbContextName}} _context;
|
|
3046
3175
|
|
|
3047
|
-
public {{name}}Repository(
|
|
3176
|
+
public {{name}}Repository({{dbContextName}} context)
|
|
3048
3177
|
{
|
|
3049
3178
|
_context = context;
|
|
3050
3179
|
}
|
|
@@ -3104,7 +3233,8 @@ public class {{name}}Repository : I{{name}}Repository
|
|
|
3104
3233
|
`;
|
|
3105
3234
|
const context = {
|
|
3106
3235
|
name,
|
|
3107
|
-
isSystemEntity
|
|
3236
|
+
isSystemEntity,
|
|
3237
|
+
dbContextName
|
|
3108
3238
|
};
|
|
3109
3239
|
const interfaceContent = Handlebars.compile(interfaceTemplate)(context);
|
|
3110
3240
|
const implementationContent = Handlebars.compile(implementationTemplate)(context);
|
|
@@ -3602,7 +3732,8 @@ async function handleSuggestMigration(args, config) {
|
|
|
3602
3732
|
const pascalDescription = toPascalCase(input.description);
|
|
3603
3733
|
const sequenceStr = sequence.toString().padStart(3, "0");
|
|
3604
3734
|
const migrationName = `${context}_v${version}_${sequenceStr}_${pascalDescription}`;
|
|
3605
|
-
const
|
|
3735
|
+
const dbContextName = context === "core" ? "CoreDbContext" : "ExtensionsDbContext";
|
|
3736
|
+
const command = `dotnet ef migrations add ${migrationName} --context ${dbContextName}`;
|
|
3606
3737
|
const lines = [];
|
|
3607
3738
|
lines.push("# Migration Name Suggestion");
|
|
3608
3739
|
lines.push("");
|
|
@@ -3620,11 +3751,17 @@ async function handleSuggestMigration(args, config) {
|
|
|
3620
3751
|
lines.push("");
|
|
3621
3752
|
lines.push(`| Part | Value | Description |`);
|
|
3622
3753
|
lines.push(`|------|-------|-------------|`);
|
|
3623
|
-
lines.push(`| Context | \`${context}\` |
|
|
3754
|
+
lines.push(`| Context | \`${context}\` | Migration prefix (core/extensions) |`);
|
|
3755
|
+
lines.push(`| DbContext | \`${dbContextName}\` | EF Core DbContext to use |`);
|
|
3756
|
+
lines.push(`| Schema | \`${context}\` | Database schema for tables |`);
|
|
3624
3757
|
lines.push(`| Version | \`v${version}\` | Semver version |`);
|
|
3625
3758
|
lines.push(`| Sequence | \`${sequenceStr}\` | Order in version |`);
|
|
3626
3759
|
lines.push(`| Description | \`${pascalDescription}\` | Migration description |`);
|
|
3627
3760
|
lines.push("");
|
|
3761
|
+
lines.push("> **Note**: Migrations are stored in separate history tables:");
|
|
3762
|
+
lines.push(`> - Core: \`core.__EFMigrationsHistory\``);
|
|
3763
|
+
lines.push(`> - Extensions: \`extensions.__EFMigrationsHistory\``);
|
|
3764
|
+
lines.push("");
|
|
3628
3765
|
if (existingMigrations.length > 0) {
|
|
3629
3766
|
lines.push("## Existing Migrations (last 5)");
|
|
3630
3767
|
lines.push("");
|
|
@@ -3638,7 +3775,7 @@ async function handleSuggestMigration(args, config) {
|
|
|
3638
3775
|
async function findExistingMigrations(structure, config, context) {
|
|
3639
3776
|
const migrations = [];
|
|
3640
3777
|
const infraPath = structure.infrastructure || path10.join(config.smartstack.projectPath, "Infrastructure");
|
|
3641
|
-
const migrationsPath = path10.join(infraPath, "Migrations");
|
|
3778
|
+
const migrationsPath = path10.join(infraPath, "Persistence", "Migrations");
|
|
3642
3779
|
try {
|
|
3643
3780
|
const migrationFiles = await findFiles("*.cs", { cwd: migrationsPath });
|
|
3644
3781
|
const migrationPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d+)_(\w+)\.cs$/;
|
|
@@ -3685,7 +3822,6 @@ function compareVersions2(a, b) {
|
|
|
3685
3822
|
}
|
|
3686
3823
|
|
|
3687
3824
|
// src/tools/generate-permissions.ts
|
|
3688
|
-
import Handlebars2 from "handlebars";
|
|
3689
3825
|
import path11 from "path";
|
|
3690
3826
|
var HTTP_METHOD_TO_ACTION = {
|
|
3691
3827
|
"GET": "read",
|
|
@@ -3702,17 +3838,21 @@ var generatePermissionsTool = {
|
|
|
3702
3838
|
name: "generate_permissions",
|
|
3703
3839
|
description: `Generate RBAC permissions for SmartStack controllers.
|
|
3704
3840
|
|
|
3705
|
-
|
|
3841
|
+
Analyzes NavRoute attributes and outputs HasData() C# code to add to PermissionConfiguration.cs.
|
|
3842
|
+
|
|
3843
|
+
IMPORTANT: This tool does NOT generate migrations with raw SQL (forbidden by SmartStack conventions).
|
|
3844
|
+
Instead, it outputs HasData() code that must be manually added to the Configuration file.
|
|
3706
3845
|
|
|
3707
3846
|
Example:
|
|
3708
3847
|
navRoute: "platform.administration.entra"
|
|
3709
|
-
|
|
3848
|
+
Outputs HasData() code for:
|
|
3849
|
+
- platform.administration.entra.*
|
|
3710
3850
|
- platform.administration.entra.read
|
|
3711
3851
|
- platform.administration.entra.create
|
|
3712
3852
|
- platform.administration.entra.update
|
|
3713
3853
|
- platform.administration.entra.delete
|
|
3714
3854
|
|
|
3715
|
-
|
|
3855
|
+
After adding to PermissionConfiguration.cs, run: dotnet ef migrations add <MigrationName>`,
|
|
3716
3856
|
inputSchema: {
|
|
3717
3857
|
type: "object",
|
|
3718
3858
|
properties: {
|
|
@@ -3730,15 +3870,10 @@ Can also generate EF Core migration to seed permissions in database.`,
|
|
|
3730
3870
|
default: true,
|
|
3731
3871
|
description: "Include standard CRUD actions (read, create, update, delete)"
|
|
3732
3872
|
},
|
|
3733
|
-
|
|
3873
|
+
includeWildcard: {
|
|
3734
3874
|
type: "boolean",
|
|
3735
3875
|
default: true,
|
|
3736
|
-
description: "
|
|
3737
|
-
},
|
|
3738
|
-
dryRun: {
|
|
3739
|
-
type: "boolean",
|
|
3740
|
-
default: false,
|
|
3741
|
-
description: "Preview without writing files or creating migration"
|
|
3876
|
+
description: "Include wildcard permission (e.g., personal.myspace.tenants.*)"
|
|
3742
3877
|
}
|
|
3743
3878
|
}
|
|
3744
3879
|
}
|
|
@@ -3749,8 +3884,7 @@ async function handleGeneratePermissions(args, config) {
|
|
|
3749
3884
|
navRoute: args.navRoute,
|
|
3750
3885
|
actions: args.actions || [],
|
|
3751
3886
|
includeStandardActions: args.includeStandardActions !== false,
|
|
3752
|
-
|
|
3753
|
-
dryRun: args.dryRun === true
|
|
3887
|
+
includeWildcard: args.includeWildcard !== false
|
|
3754
3888
|
};
|
|
3755
3889
|
const structure = await findSmartStackStructure(config.smartstack.projectPath);
|
|
3756
3890
|
if (!structure) {
|
|
@@ -3762,16 +3896,17 @@ async function handleGeneratePermissions(args, config) {
|
|
|
3762
3896
|
permissions = generatePermissionsForNavRoute(
|
|
3763
3897
|
options.navRoute,
|
|
3764
3898
|
options.actions || [],
|
|
3765
|
-
options.includeStandardActions || false
|
|
3899
|
+
options.includeStandardActions || false,
|
|
3900
|
+
options.includeWildcard || false
|
|
3766
3901
|
);
|
|
3767
|
-
report = `## Permissions
|
|
3902
|
+
report = `## Permissions for NavRoute: ${options.navRoute}
|
|
3768
3903
|
|
|
3769
3904
|
`;
|
|
3770
3905
|
report += formatPermissionsReport(permissions);
|
|
3771
3906
|
} else {
|
|
3772
3907
|
const scannedPermissions = await scanControllersForPermissions(structure.api || structure.root);
|
|
3773
3908
|
permissions = scannedPermissions;
|
|
3774
|
-
report = `## Permissions
|
|
3909
|
+
report = `## Permissions from All Controllers
|
|
3775
3910
|
|
|
3776
3911
|
`;
|
|
3777
3912
|
report += `Total controllers scanned: ${getUniqueNavRouteCount(permissions)}
|
|
@@ -3781,53 +3916,51 @@ async function handleGeneratePermissions(args, config) {
|
|
|
3781
3916
|
`;
|
|
3782
3917
|
report += formatPermissionsReport(permissions);
|
|
3783
3918
|
}
|
|
3784
|
-
|
|
3785
|
-
const migrationResult = await generatePermissionMigration(
|
|
3786
|
-
structure.api || structure.root,
|
|
3787
|
-
permissions,
|
|
3788
|
-
config
|
|
3789
|
-
);
|
|
3790
|
-
report += `
|
|
3919
|
+
report += `
|
|
3791
3920
|
|
|
3792
|
-
##
|
|
3921
|
+
## HasData() Code for PermissionConfiguration.cs
|
|
3793
3922
|
|
|
3794
3923
|
`;
|
|
3795
|
-
|
|
3924
|
+
report += `\u26A0\uFE0F **IMPORTANT**: Do NOT use migrationBuilder.Sql() - it's forbidden by SmartStack conventions.
|
|
3796
3925
|
`;
|
|
3797
|
-
|
|
3926
|
+
report += `Add this code to \`PermissionConfiguration.cs\` in the HasData() section, then create a migration.
|
|
3798
3927
|
|
|
3799
3928
|
`;
|
|
3800
|
-
|
|
3929
|
+
report += generateHasDataCode(permissions, options.navRoute || "custom");
|
|
3930
|
+
report += `
|
|
3931
|
+
|
|
3932
|
+
### Next Steps
|
|
3801
3933
|
|
|
3802
3934
|
`;
|
|
3803
|
-
|
|
3804
|
-
`;
|
|
3805
|
-
report += `2. Run: \`dotnet ef database update\`
|
|
3935
|
+
report += `1. Add the module ID variable in GetSeedData() method
|
|
3806
3936
|
`;
|
|
3807
|
-
|
|
3937
|
+
report += `2. Add the HasData entries to the return array
|
|
3808
3938
|
`;
|
|
3809
|
-
|
|
3810
|
-
report += `
|
|
3811
|
-
|
|
3812
|
-
## Dry Run Mode
|
|
3813
|
-
|
|
3939
|
+
report += `3. Run: \`dotnet ef migrations add <MigrationName> -o Persistence/Migrations\`
|
|
3814
3940
|
`;
|
|
3815
|
-
|
|
3941
|
+
report += `4. Run: \`dotnet ef database update\`
|
|
3816
3942
|
`;
|
|
3817
|
-
}
|
|
3818
3943
|
return report;
|
|
3819
3944
|
} catch (error) {
|
|
3820
3945
|
logger.error("Error generating permissions:", error);
|
|
3821
3946
|
throw error;
|
|
3822
3947
|
}
|
|
3823
3948
|
}
|
|
3824
|
-
function generatePermissionsForNavRoute(navRoute, customActions, includeStandardActions) {
|
|
3949
|
+
function generatePermissionsForNavRoute(navRoute, customActions, includeStandardActions, includeWildcard = true) {
|
|
3825
3950
|
const permissions = [];
|
|
3826
3951
|
const parts = navRoute.split(".");
|
|
3827
3952
|
const context = parts[0];
|
|
3828
3953
|
if (parts.length < 3) {
|
|
3829
3954
|
throw new Error(`Invalid NavRoute format: ${navRoute}. Expected format: context.application.module`);
|
|
3830
3955
|
}
|
|
3956
|
+
if (includeWildcard) {
|
|
3957
|
+
permissions.push({
|
|
3958
|
+
code: `${navRoute}.*`,
|
|
3959
|
+
name: formatPermissionName(navRoute, "Full Access"),
|
|
3960
|
+
description: `Full ${parts[parts.length - 1]} management`,
|
|
3961
|
+
category: context
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3831
3964
|
const actions = includeStandardActions ? [...STANDARD_ACTIONS, ...customActions] : customActions;
|
|
3832
3965
|
for (const action of actions) {
|
|
3833
3966
|
const code = `${navRoute}.${action}`;
|
|
@@ -3966,87 +4099,54 @@ function getUniqueNavRouteCount(permissions) {
|
|
|
3966
4099
|
);
|
|
3967
4100
|
return navRoutes.size;
|
|
3968
4101
|
}
|
|
3969
|
-
|
|
3970
|
-
const
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
try {
|
|
3978
|
-
template = await readText(templatePath);
|
|
3979
|
-
} catch {
|
|
3980
|
-
template = getSeedPermissionsTemplate();
|
|
3981
|
-
}
|
|
3982
|
-
const handlebars = Handlebars2.create();
|
|
3983
|
-
const compiled = handlebars.compile(template);
|
|
3984
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T.]/g, "").slice(0, 14);
|
|
3985
|
-
const migrationName = `SeedPermissions_${timestamp}`;
|
|
3986
|
-
const content = compiled({
|
|
3987
|
-
migrationName,
|
|
3988
|
-
permissions,
|
|
3989
|
-
timestamp
|
|
3990
|
-
});
|
|
3991
|
-
const migrationsPath = path11.join(
|
|
3992
|
-
backendRoot,
|
|
3993
|
-
"Infrastructure",
|
|
3994
|
-
"Data",
|
|
3995
|
-
"Migrations",
|
|
3996
|
-
"Core"
|
|
3997
|
-
);
|
|
3998
|
-
await ensureDirectory(migrationsPath);
|
|
3999
|
-
const filePath = path11.join(migrationsPath, `${migrationName}.cs`);
|
|
4000
|
-
validatePathSecurity(filePath, config.smartstack.projectPath);
|
|
4001
|
-
await writeText(filePath, content);
|
|
4002
|
-
logger.info(`Generated permission migration: ${filePath}`);
|
|
4003
|
-
return {
|
|
4004
|
-
filePath,
|
|
4005
|
-
migrationName
|
|
4006
|
-
};
|
|
4007
|
-
}
|
|
4008
|
-
function getSeedPermissionsTemplate() {
|
|
4009
|
-
return `using Microsoft.EntityFrameworkCore.Migrations;
|
|
4010
|
-
|
|
4011
|
-
namespace SmartStack.Infrastructure.Data.Migrations.Core;
|
|
4102
|
+
function generateHasDataCode(permissions, navRoute) {
|
|
4103
|
+
const parts = navRoute.split(".");
|
|
4104
|
+
const moduleName = parts.length >= 3 ? parts[parts.length - 1] : "custom";
|
|
4105
|
+
const moduleVarName = `${moduleName}ModuleId`;
|
|
4106
|
+
let code = "```csharp\n";
|
|
4107
|
+
code += `// 1. Add module ID variable (get from NavigationModuleSeedData.cs)
|
|
4108
|
+
`;
|
|
4109
|
+
code += `var ${moduleVarName} = Guid.Parse("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"); // TODO: Replace with actual ID
|
|
4012
4110
|
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
{
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
VALUES (
|
|
4028
|
-
NEWID(),
|
|
4029
|
-
'{{code}}',
|
|
4030
|
-
'{{name}}',
|
|
4031
|
-
'{{description}}',
|
|
4032
|
-
0,
|
|
4033
|
-
GETUTCDATE(),
|
|
4034
|
-
GETUTCDATE()
|
|
4035
|
-
);
|
|
4036
|
-
END
|
|
4037
|
-
");
|
|
4038
|
-
{{/each}}
|
|
4039
|
-
}
|
|
4040
|
-
|
|
4041
|
-
protected override void Down(MigrationBuilder migrationBuilder)
|
|
4042
|
-
{
|
|
4043
|
-
// Remove seeded permissions
|
|
4044
|
-
{{#each permissions}}
|
|
4045
|
-
migrationBuilder.Sql(@"DELETE FROM core.auth_Permissions WHERE Code = '{{code}}'");
|
|
4046
|
-
{{/each}}
|
|
4111
|
+
`;
|
|
4112
|
+
code += `// 2. Add these entries to the HasData() return array:
|
|
4113
|
+
`;
|
|
4114
|
+
for (const perm of permissions) {
|
|
4115
|
+
const isWildcard = perm.code.endsWith(".*");
|
|
4116
|
+
const action = perm.code.split(".").pop();
|
|
4117
|
+
const guidPlaceholder = generatePlaceholderGuid();
|
|
4118
|
+
if (isWildcard) {
|
|
4119
|
+
code += `new { Id = Guid.Parse("${guidPlaceholder}"), Path = "${perm.code}", Level = PermissionLevel.Module, IsWildcard = true, ModuleId = ${moduleVarName}, Description = "${perm.description}", CreatedAt = seedDate },
|
|
4120
|
+
`;
|
|
4121
|
+
} else {
|
|
4122
|
+
const actionEnum = getActionEnum(action || "read");
|
|
4123
|
+
code += `new { Id = Guid.Parse("${guidPlaceholder}"), Path = "${perm.code}", Level = PermissionLevel.Module, Action = PermissionAction.${actionEnum}, IsWildcard = false, ModuleId = ${moduleVarName}, Description = "${perm.description}", CreatedAt = seedDate },
|
|
4124
|
+
`;
|
|
4047
4125
|
}
|
|
4048
|
-
}
|
|
4126
|
+
}
|
|
4127
|
+
code += "```\n";
|
|
4128
|
+
code += "\n\u26A0\uFE0F **IMPORTANT**: Replace all GUIDs with randomly generated ones using:\n";
|
|
4129
|
+
code += "```powershell\n";
|
|
4130
|
+
code += `1..${permissions.length} | ForEach-Object { [guid]::NewGuid().ToString() }
|
|
4049
4131
|
`;
|
|
4132
|
+
code += "```\n";
|
|
4133
|
+
return code;
|
|
4134
|
+
}
|
|
4135
|
+
function generatePlaceholderGuid() {
|
|
4136
|
+
return "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
|
|
4137
|
+
}
|
|
4138
|
+
function getActionEnum(action) {
|
|
4139
|
+
const actionMap = {
|
|
4140
|
+
"read": "Read",
|
|
4141
|
+
"create": "Create",
|
|
4142
|
+
"update": "Update",
|
|
4143
|
+
"delete": "Delete",
|
|
4144
|
+
"assign": "Assign",
|
|
4145
|
+
"execute": "Execute",
|
|
4146
|
+
"export": "Export",
|
|
4147
|
+
"import": "Import"
|
|
4148
|
+
};
|
|
4149
|
+
return actionMap[action.toLowerCase()] || action.charAt(0).toUpperCase() + action.slice(1);
|
|
4050
4150
|
}
|
|
4051
4151
|
async function readDirectoryRecursive(dir) {
|
|
4052
4152
|
const files = [];
|
|
@@ -4066,7 +4166,7 @@ async function readDirectoryRecursive(dir) {
|
|
|
4066
4166
|
}
|
|
4067
4167
|
|
|
4068
4168
|
// src/tools/scaffold-tests.ts
|
|
4069
|
-
import
|
|
4169
|
+
import Handlebars2 from "handlebars";
|
|
4070
4170
|
import path12 from "path";
|
|
4071
4171
|
var scaffoldTestsTool = {
|
|
4072
4172
|
name: "scaffold_tests",
|
|
@@ -4146,15 +4246,15 @@ var scaffoldTestsTool = {
|
|
|
4146
4246
|
required: ["target", "name"]
|
|
4147
4247
|
}
|
|
4148
4248
|
};
|
|
4149
|
-
|
|
4249
|
+
Handlebars2.registerHelper("pascalCase", (str) => {
|
|
4150
4250
|
if (!str) return "";
|
|
4151
4251
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
4152
4252
|
});
|
|
4153
|
-
|
|
4253
|
+
Handlebars2.registerHelper("camelCase", (str) => {
|
|
4154
4254
|
if (!str) return "";
|
|
4155
4255
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
4156
4256
|
});
|
|
4157
|
-
|
|
4257
|
+
Handlebars2.registerHelper("unless", function(conditional, options) {
|
|
4158
4258
|
if (!conditional) {
|
|
4159
4259
|
return options.fn(this);
|
|
4160
4260
|
}
|
|
@@ -5496,7 +5596,7 @@ async function scaffoldEntityTests(name, options, testTypes, structure, config,
|
|
|
5496
5596
|
...options
|
|
5497
5597
|
};
|
|
5498
5598
|
if (testTypes.includes("unit")) {
|
|
5499
|
-
const content =
|
|
5599
|
+
const content = Handlebars2.compile(entityTestTemplate)(context);
|
|
5500
5600
|
const testPath = path12.join(structure.root, "Tests", "Unit", "Domain", `${name}Tests.cs`);
|
|
5501
5601
|
validatePathSecurity(testPath, structure.root);
|
|
5502
5602
|
if (!dryRun) {
|
|
@@ -5510,7 +5610,7 @@ async function scaffoldEntityTests(name, options, testTypes, structure, config,
|
|
|
5510
5610
|
});
|
|
5511
5611
|
}
|
|
5512
5612
|
if (testTypes.includes("security")) {
|
|
5513
|
-
const securityContent =
|
|
5613
|
+
const securityContent = Handlebars2.compile(securityTestTemplate)({
|
|
5514
5614
|
...context,
|
|
5515
5615
|
nameLower: name.charAt(0).toLowerCase() + name.slice(1),
|
|
5516
5616
|
apiNamespace: config.conventions.namespaces.api
|
|
@@ -5542,7 +5642,7 @@ async function scaffoldServiceTests(name, options, testTypes, structure, config,
|
|
|
5542
5642
|
...options
|
|
5543
5643
|
};
|
|
5544
5644
|
if (testTypes.includes("unit")) {
|
|
5545
|
-
const content =
|
|
5645
|
+
const content = Handlebars2.compile(serviceTestTemplate)(context);
|
|
5546
5646
|
const testPath = path12.join(structure.root, "Tests", "Unit", "Services", `${name}ServiceTests.cs`);
|
|
5547
5647
|
validatePathSecurity(testPath, structure.root);
|
|
5548
5648
|
if (!dryRun) {
|
|
@@ -5572,7 +5672,7 @@ async function scaffoldControllerTests(name, options, testTypes, structure, conf
|
|
|
5572
5672
|
...options
|
|
5573
5673
|
};
|
|
5574
5674
|
if (testTypes.includes("integration")) {
|
|
5575
|
-
const content =
|
|
5675
|
+
const content = Handlebars2.compile(controllerTestTemplate)(context);
|
|
5576
5676
|
const testPath = path12.join(structure.root, "Tests", "Integration", "Controllers", `${name}ControllerTests.cs`);
|
|
5577
5677
|
validatePathSecurity(testPath, structure.root);
|
|
5578
5678
|
if (!dryRun) {
|
|
@@ -5586,7 +5686,7 @@ async function scaffoldControllerTests(name, options, testTypes, structure, conf
|
|
|
5586
5686
|
});
|
|
5587
5687
|
}
|
|
5588
5688
|
if (testTypes.includes("security")) {
|
|
5589
|
-
const securityContent =
|
|
5689
|
+
const securityContent = Handlebars2.compile(securityTestTemplate)(context);
|
|
5590
5690
|
const securityPath = path12.join(structure.root, "Tests", "Security", `${name}SecurityTests.cs`);
|
|
5591
5691
|
validatePathSecurity(securityPath, structure.root);
|
|
5592
5692
|
if (!dryRun) {
|
|
@@ -5611,7 +5711,7 @@ async function scaffoldValidatorTests(name, options, testTypes, structure, confi
|
|
|
5611
5711
|
...options
|
|
5612
5712
|
};
|
|
5613
5713
|
if (testTypes.includes("unit")) {
|
|
5614
|
-
const content =
|
|
5714
|
+
const content = Handlebars2.compile(validatorTestTemplate)(context);
|
|
5615
5715
|
const testPath = path12.join(structure.root, "Tests", "Unit", "Validators", `${name}ValidatorTests.cs`);
|
|
5616
5716
|
validatePathSecurity(testPath, structure.root);
|
|
5617
5717
|
if (!dryRun) {
|
|
@@ -5638,7 +5738,7 @@ async function scaffoldRepositoryTests(name, options, testTypes, structure, conf
|
|
|
5638
5738
|
...options
|
|
5639
5739
|
};
|
|
5640
5740
|
if (testTypes.includes("integration")) {
|
|
5641
|
-
const content =
|
|
5741
|
+
const content = Handlebars2.compile(repositoryTestTemplate)(context);
|
|
5642
5742
|
const testPath = path12.join(structure.root, "Tests", "Integration", "Repositories", `${name}RepositoryTests.cs`);
|
|
5643
5743
|
validatePathSecurity(testPath, structure.root);
|
|
5644
5744
|
if (!dryRun) {
|
|
@@ -11337,7 +11437,7 @@ async function createServer() {
|
|
|
11337
11437
|
const server = new Server(
|
|
11338
11438
|
{
|
|
11339
11439
|
name: "smartstack-mcp",
|
|
11340
|
-
version: "1.
|
|
11440
|
+
version: "1.10.0"
|
|
11341
11441
|
},
|
|
11342
11442
|
{
|
|
11343
11443
|
capabilities: {
|