@atlashub/smartstack-cli 1.35.0 → 1.37.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/package.json +1 -1
- package/templates/skills/_shared.md +7 -7
- package/templates/skills/application/steps/step-01-navigation.md +226 -43
- package/templates/skills/application/steps/step-03-roles.md +160 -38
- package/templates/skills/application/steps/step-04-backend.md +126 -19
- package/templates/skills/application/steps/step-05-frontend.md +4 -1
- package/templates/skills/application/templates-backend.md +8 -8
- package/templates/skills/application/templates-frontend.md +8 -8
- package/templates/skills/application/templates-seed.md +200 -1
- package/templates/skills/gitflow/_shared.md +188 -53
- package/templates/skills/gitflow/phases/abort.md +28 -16
- package/templates/skills/gitflow/phases/cleanup.md +13 -9
- package/templates/skills/gitflow/phases/status.md +16 -17
- package/templates/skills/gitflow/steps/step-commit.md +11 -5
- package/templates/skills/gitflow/steps/step-finish.md +43 -33
- package/templates/skills/gitflow/steps/step-init.md +7 -2
- package/templates/skills/gitflow/steps/step-merge.md +24 -10
- package/templates/skills/gitflow/steps/step-pr.md +42 -28
- package/templates/skills/gitflow/steps/step-start.md +19 -13
- package/templates/skills/gitflow/templates/config.json +7 -4
- package/templates/skills/ralph-loop/SKILL.md +57 -11
- package/templates/skills/ralph-loop/steps/step-00-init.md +170 -30
- package/templates/skills/ralph-loop/steps/step-01-task.md +243 -40
- package/templates/skills/ralph-loop/steps/step-02-execute.md +142 -24
- package/templates/skills/ralph-loop/steps/step-03-commit.md +140 -36
- package/templates/skills/ralph-loop/steps/step-04-check.md +128 -44
- package/templates/skills/ralph-loop/steps/step-05-report.md +175 -88
|
@@ -80,15 +80,15 @@ Args:
|
|
|
80
80
|
|
|
81
81
|
### 4. Parse MCP Response
|
|
82
82
|
|
|
83
|
-
The tool generates:
|
|
84
|
-
- `Domain/{EntityName}.cs` - Entity with BaseEntity
|
|
85
|
-
- `Infrastructure/Persistence/Configurations/{EntityName}Configuration.cs` - EF Config
|
|
86
|
-
- `Application/
|
|
87
|
-
- `
|
|
88
|
-
- `Api/Controllers/{EntityName}Controller.cs` - REST Controller
|
|
89
|
-
- `Application/DTOs/{EntityName}
|
|
90
|
-
- `Application/DTOs/Create{EntityName}Dto.cs` - Create request
|
|
91
|
-
- `Application/DTOs/Update{EntityName}Dto.cs` - Update request
|
|
83
|
+
The tool generates (paths organized by navRoute hierarchy `{context}.{application}.{module}`):
|
|
84
|
+
- `Domain/{Context}/{Application}/{Module}/{EntityName}.cs` - Entity with BaseEntity
|
|
85
|
+
- `Infrastructure/Persistence/Configurations/{Module}/{EntityName}Configuration.cs` - EF Config
|
|
86
|
+
- `Application/Common/Interfaces/I{EntityName}Service.cs` - Service Interface
|
|
87
|
+
- `Infrastructure/Services/{Module}/{EntityName}Service.cs` - Service Implementation
|
|
88
|
+
- `Api/Controllers/{Context}/{EntityName}Controller.cs` - REST Controller
|
|
89
|
+
- `Application/{Context}/{Application}/{Module}/DTOs/{EntityName}ResponseDto.cs` - Response DTO
|
|
90
|
+
- `Application/{Context}/{Application}/{Module}/DTOs/Create{EntityName}Dto.cs` - Create request
|
|
91
|
+
- `Application/{Context}/{Application}/{Module}/DTOs/Update{EntityName}Dto.cs` - Update request
|
|
92
92
|
|
|
93
93
|
### 5. Present Output to User
|
|
94
94
|
|
|
@@ -96,20 +96,20 @@ The tool generates:
|
|
|
96
96
|
## Backend Code Generated
|
|
97
97
|
|
|
98
98
|
### Domain Layer
|
|
99
|
-
- `Domain/{EntityName}.cs`
|
|
99
|
+
- `Domain/{Context}/{Application}/{Module}/{EntityName}.cs`
|
|
100
100
|
|
|
101
101
|
### Infrastructure Layer
|
|
102
|
-
- `Persistence/Configurations/{EntityName}Configuration.cs`
|
|
102
|
+
- `Persistence/Configurations/{Module}/{EntityName}Configuration.cs`
|
|
103
|
+
- `Services/{Module}/{EntityName}Service.cs`
|
|
103
104
|
|
|
104
105
|
### Application Layer
|
|
105
|
-
- `
|
|
106
|
-
- `
|
|
107
|
-
- `DTOs/{EntityName}Dto.cs`
|
|
108
|
-
- `DTOs/
|
|
109
|
-
- `DTOs/Update{EntityName}Dto.cs`
|
|
106
|
+
- `Common/Interfaces/I{EntityName}Service.cs`
|
|
107
|
+
- `{Context}/{Application}/{Module}/DTOs/{EntityName}ResponseDto.cs`
|
|
108
|
+
- `{Context}/{Application}/{Module}/DTOs/Create{EntityName}Dto.cs`
|
|
109
|
+
- `{Context}/{Application}/{Module}/DTOs/Update{EntityName}Dto.cs`
|
|
110
110
|
|
|
111
111
|
### API Layer
|
|
112
|
-
- `Controllers/{EntityName}Controller.cs`
|
|
112
|
+
- `Controllers/{Context}/{EntityName}Controller.cs`
|
|
113
113
|
- NavRoute: `{full_path}`
|
|
114
114
|
- Endpoints:
|
|
115
115
|
- GET /api/{code} - List all
|
|
@@ -124,7 +124,110 @@ The tool generates:
|
|
|
124
124
|
3. Run: `dotnet ef migrations add core_vX.X.X_XXX_Add{EntityName}`
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
### 6.
|
|
127
|
+
### 6. Entity Seeding (Optional)
|
|
128
|
+
|
|
129
|
+
> Ask the user if they want to seed initial data for the entity.
|
|
130
|
+
> This creates runtime seed data (applied at startup via DevDataSeeder), following the same
|
|
131
|
+
> architecture as core (SeedData provider pattern).
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
questions:
|
|
135
|
+
- header: "Seed Data"
|
|
136
|
+
question: "Would you like to generate initial seed data for {EntityName}?"
|
|
137
|
+
options:
|
|
138
|
+
- label: "Yes - Generate SeedData provider"
|
|
139
|
+
description: "Creates {EntityName}SeedData.cs + registers in DevDataSeeder (same pattern as core)"
|
|
140
|
+
- label: "No - Skip seeding"
|
|
141
|
+
description: "Entity starts empty (can add seed data later)"
|
|
142
|
+
multiSelect: false
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### If User Selects YES:
|
|
146
|
+
|
|
147
|
+
**Reference:** See `templates-seed.md` section "TEMPLATE: ENTITY SEED DATA (SeedData Provider)"
|
|
148
|
+
|
|
149
|
+
**Generate the following files:**
|
|
150
|
+
|
|
151
|
+
1. **`Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs`**
|
|
152
|
+
|
|
153
|
+
Follow the SmartStack.app pattern (same architecture as core):
|
|
154
|
+
- Static class with `internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()`
|
|
155
|
+
- Use deterministic GUIDs (NEVER `Guid.NewGuid()`)
|
|
156
|
+
- Include 3-5 sample entities with varied, realistic data
|
|
157
|
+
- Internal record class `{EntityName}SeedItem` with all entity properties
|
|
158
|
+
- For multi-tenant entities: organize by tenant using `GetTenant1{EntityName}s()` helper methods
|
|
159
|
+
- Reference existing tenant/user IDs from `TenantSeedData`/`UserSeedData` for foreign keys
|
|
160
|
+
|
|
161
|
+
```csharp
|
|
162
|
+
// Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs
|
|
163
|
+
|
|
164
|
+
public static class {EntityName}SeedData
|
|
165
|
+
{
|
|
166
|
+
public static readonly Guid Sample1Id = Guid.Parse("...");
|
|
167
|
+
public static readonly Guid Sample2Id = Guid.Parse("...");
|
|
168
|
+
public static readonly Guid Sample3Id = Guid.Parse("...");
|
|
169
|
+
|
|
170
|
+
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
171
|
+
{
|
|
172
|
+
return new[]
|
|
173
|
+
{
|
|
174
|
+
new {EntityName}SeedItem { Id = Sample1Id, /* ... */ },
|
|
175
|
+
new {EntityName}SeedItem { Id = Sample2Id, /* ... */ },
|
|
176
|
+
new {EntityName}SeedItem { Id = Sample3Id, /* ... */ }
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
internal class {EntityName}SeedItem
|
|
182
|
+
{
|
|
183
|
+
public Guid Id { get; init; }
|
|
184
|
+
// ... all required entity properties
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
2. **Update `Infrastructure/Persistence/Seeding/DevDataSeeder.cs`**
|
|
189
|
+
- Add `using` for the new SeedData namespace
|
|
190
|
+
- Add `await Seed{EntityName}sAsync(cancellationToken);` in `SeedAsync()` method
|
|
191
|
+
- Add private seeding method:
|
|
192
|
+
|
|
193
|
+
```csharp
|
|
194
|
+
private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
|
|
195
|
+
{
|
|
196
|
+
_logger.LogInformation("Seeding demo {EntityName}s...");
|
|
197
|
+
|
|
198
|
+
var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
|
|
199
|
+
if (existingCount > 0)
|
|
200
|
+
{
|
|
201
|
+
_logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
var createdCount = 0;
|
|
206
|
+
foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
|
|
207
|
+
{
|
|
208
|
+
var entity = {EntityName}.Create(/* map seedItem properties */);
|
|
209
|
+
typeof({EntityName}).GetProperty("Id")?.SetValue(entity, seedItem.Id);
|
|
210
|
+
_context.{EntityName}s.Add(entity);
|
|
211
|
+
createdCount++;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (createdCount > 0)
|
|
215
|
+
await _context.SaveChangesAsync(cancellationToken);
|
|
216
|
+
|
|
217
|
+
_logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### If User Selects NO:
|
|
222
|
+
|
|
223
|
+
Skip this section and proceed to storing entity info.
|
|
224
|
+
|
|
225
|
+
```markdown
|
|
226
|
+
> Skipping entity seed data. You can generate it later using the SeedData provider pattern
|
|
227
|
+
> documented in templates-seed.md.
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 7. Store Entity Info
|
|
128
231
|
|
|
129
232
|
Store entity information for frontend generation:
|
|
130
233
|
|
|
@@ -132,6 +235,7 @@ Store entity information for frontend generation:
|
|
|
132
235
|
{entity_name} = "{EntityName}"
|
|
133
236
|
{entity_code} = "{code}"
|
|
134
237
|
{api_route} = "/api/{code}"
|
|
238
|
+
{has_seed_data} = true/false // Whether SeedData provider was generated
|
|
135
239
|
```
|
|
136
240
|
|
|
137
241
|
---
|
|
@@ -168,6 +272,7 @@ This ensures:
|
|
|
168
272
|
If MCP returns successfully:
|
|
169
273
|
- Display all generated files
|
|
170
274
|
- Show DbSet and DI registration instructions
|
|
275
|
+
- Ask about entity seeding (section 6)
|
|
171
276
|
- Store entity info for frontend
|
|
172
277
|
- Proceed to step-05-frontend.md
|
|
173
278
|
|
|
@@ -186,6 +291,7 @@ If MCP call fails:
|
|
|
186
291
|
- Entity, Service, Controller generated
|
|
187
292
|
- DTOs generated
|
|
188
293
|
- NavRoute attribute included
|
|
294
|
+
- Entity seeding offered to user (SeedData.cs + DevDataSeeder if accepted)
|
|
189
295
|
- Entity info stored for frontend
|
|
190
296
|
- Proceeded to step-05-frontend.md
|
|
191
297
|
|
|
@@ -199,4 +305,5 @@ If MCP call fails:
|
|
|
199
305
|
|
|
200
306
|
## NEXT STEP
|
|
201
307
|
|
|
202
|
-
After
|
|
308
|
+
After backend code is generated and entity seeding is handled,
|
|
309
|
+
proceed to `./step-05-frontend.md`
|
|
@@ -129,9 +129,12 @@ Repeat for en, it, de with appropriate translations.
|
|
|
129
129
|
```markdown
|
|
130
130
|
## Frontend Code Generated
|
|
131
131
|
|
|
132
|
-
###
|
|
132
|
+
### Pages
|
|
133
133
|
- `pages/{context}/{application}/{module}/{EntityName}Page.tsx`
|
|
134
134
|
- `pages/{context}/{application}/{module}/{EntityName}ListView.tsx`
|
|
135
|
+
|
|
136
|
+
### Components
|
|
137
|
+
- `components/{context}/{module}/{EntityName}.tsx`
|
|
135
138
|
- `hooks/use{EntityName}.ts`
|
|
136
139
|
|
|
137
140
|
### API Client
|
|
@@ -49,7 +49,7 @@ public static class Erp
|
|
|
49
49
|
```csharp
|
|
50
50
|
// src/SmartStack.Application/Common/Interfaces/I{Module}Service.cs
|
|
51
51
|
|
|
52
|
-
using SmartStack.Application.$MODULE_PASCAL.
|
|
52
|
+
using SmartStack.Application.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;
|
|
53
53
|
|
|
54
54
|
namespace SmartStack.Application.Common.Interfaces;
|
|
55
55
|
|
|
@@ -88,8 +88,8 @@ public interface I$MODULE_PASCALService
|
|
|
88
88
|
using Microsoft.EntityFrameworkCore;
|
|
89
89
|
using Microsoft.Extensions.Logging;
|
|
90
90
|
using SmartStack.Application.Common.Interfaces;
|
|
91
|
-
using SmartStack.Application.$MODULE_PASCAL.
|
|
92
|
-
using SmartStack.Domain.$MODULE_PASCAL;
|
|
91
|
+
using SmartStack.Application.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;
|
|
92
|
+
using SmartStack.Domain.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
|
|
93
93
|
|
|
94
94
|
namespace SmartStack.Infrastructure.Services.$MODULE_PASCAL;
|
|
95
95
|
|
|
@@ -269,9 +269,9 @@ public class $MODULE_PASCALService : I$MODULE_PASCALService
|
|
|
269
269
|
## TEMPLATE: DTOs
|
|
270
270
|
|
|
271
271
|
```csharp
|
|
272
|
-
// src/SmartStack.Application/$MODULE_PASCAL/
|
|
272
|
+
// src/SmartStack.Application/$CONTEXT_PASCAL/$APPLICATION_PASCAL/$MODULE_PASCAL/DTOs/$ENTITY_PASCALDto.cs
|
|
273
273
|
|
|
274
|
-
namespace SmartStack.Application.$MODULE_PASCAL.
|
|
274
|
+
namespace SmartStack.Application.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;
|
|
275
275
|
|
|
276
276
|
public record $ENTITY_PASCALDto
|
|
277
277
|
{
|
|
@@ -310,11 +310,11 @@ public record $ENTITY_PASCALQueryParameters
|
|
|
310
310
|
## TEMPLATE: DOMAIN ENTITY
|
|
311
311
|
|
|
312
312
|
```csharp
|
|
313
|
-
// src/SmartStack.Domain/$MODULE_PASCAL/$ENTITY_PASCAL.cs
|
|
313
|
+
// src/SmartStack.Domain/$CONTEXT_PASCAL/$APPLICATION_PASCAL/$MODULE_PASCAL/$ENTITY_PASCAL.cs
|
|
314
314
|
|
|
315
315
|
using SmartStack.Domain.Common;
|
|
316
316
|
|
|
317
|
-
namespace SmartStack.Domain.$MODULE_PASCAL;
|
|
317
|
+
namespace SmartStack.Domain.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
|
|
318
318
|
|
|
319
319
|
public class $ENTITY_PASCAL : BaseEntity
|
|
320
320
|
{
|
|
@@ -363,7 +363,7 @@ public class $ENTITY_PASCAL : BaseEntity
|
|
|
363
363
|
|
|
364
364
|
using Microsoft.EntityFrameworkCore;
|
|
365
365
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|
366
|
-
using SmartStack.Domain.$MODULE_PASCAL;
|
|
366
|
+
using SmartStack.Domain.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
|
|
367
367
|
|
|
368
368
|
namespace SmartStack.Infrastructure.Persistence.Configurations.$MODULE_PASCAL;
|
|
369
369
|
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
web/smartstack-web/src/
|
|
11
|
-
├── pages/$CONTEXT/$MODULE/
|
|
11
|
+
├── pages/$CONTEXT/$APPLICATION/$MODULE/
|
|
12
12
|
│ ├── $MODULE_PASCALPage.tsx # Main page (list)
|
|
13
13
|
│ ├── $MODULE_PASCALDetailPage.tsx # Detail page
|
|
14
14
|
│ └── Create$MODULE_PASCALPage.tsx # Create page
|
|
15
|
-
├── components/$MODULE/
|
|
15
|
+
├── components/$CONTEXT/$MODULE/
|
|
16
16
|
│ ├── $MODULE_PASCALListView.tsx # Reusable list component
|
|
17
17
|
│ ├── $MODULE_PASCALForm.tsx # CRUD form
|
|
18
18
|
│ └── $MODULE_PASCALFilters.tsx # Filters
|
|
@@ -35,10 +35,10 @@ web/smartstack-web/src/
|
|
|
35
35
|
## TEMPLATE: MAIN PAGE
|
|
36
36
|
|
|
37
37
|
```tsx
|
|
38
|
-
// pages/$CONTEXT/$MODULE/$MODULE_PASCALPage.tsx
|
|
38
|
+
// pages/$CONTEXT/$APPLICATION/$MODULE/$MODULE_PASCALPage.tsx
|
|
39
39
|
|
|
40
40
|
import { useTranslation } from 'react-i18next';
|
|
41
|
-
import { $MODULE_PASCALListView } from '@/components/$MODULE/$MODULE_PASCALListView';
|
|
41
|
+
import { $MODULE_PASCALListView } from '@/components/$CONTEXT/$MODULE/$MODULE_PASCALListView';
|
|
42
42
|
|
|
43
43
|
export function $MODULE_PASCALPage() {
|
|
44
44
|
const { t } = useTranslation(['$module', 'common']);
|
|
@@ -59,7 +59,7 @@ export function $MODULE_PASCALPage() {
|
|
|
59
59
|
## TEMPLATE: LIST VIEW (Reusable component)
|
|
60
60
|
|
|
61
61
|
```tsx
|
|
62
|
-
// components/$MODULE/$MODULE_PASCALListView.tsx
|
|
62
|
+
// components/$CONTEXT/$MODULE/$MODULE_PASCALListView.tsx
|
|
63
63
|
|
|
64
64
|
import { useState, useEffect } from 'react';
|
|
65
65
|
import { useNavigate } from 'react-router-dom';
|
|
@@ -491,9 +491,9 @@ React Router v7 **requires** nested routes for multi-module applications.
|
|
|
491
491
|
```tsx
|
|
492
492
|
// Add to App.tsx
|
|
493
493
|
|
|
494
|
-
import { $MODULE_PASCALPage } from '@/pages/$CONTEXT/$MODULE/$MODULE_PASCALPage';
|
|
495
|
-
import { $MODULE_PASCALDetailPage } from '@/pages/$CONTEXT/$MODULE/$MODULE_PASCALDetailPage';
|
|
496
|
-
import { Create$MODULE_PASCALPage } from '@/pages/$CONTEXT/$MODULE/Create$MODULE_PASCALPage';
|
|
494
|
+
import { $MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/$MODULE_PASCALPage';
|
|
495
|
+
import { $MODULE_PASCALDetailPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage';
|
|
496
|
+
import { Create$MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/Create$MODULE_PASCALPage';
|
|
497
497
|
|
|
498
498
|
// In routes - NESTED STRUCTURE
|
|
499
499
|
<Route path="$APPLICATION">
|
|
@@ -605,12 +605,209 @@ $ORDER = 1
|
|
|
605
605
|
|
|
606
606
|
---
|
|
607
607
|
|
|
608
|
+
## TEMPLATE: ENTITY SEED DATA (SeedData Provider)
|
|
609
|
+
|
|
610
|
+
> **Usage:** Runtime seed data for domain entities (Products, Orders, Tickets, etc.)
|
|
611
|
+
> **When:** User wants initial data for development/testing
|
|
612
|
+
> **Mechanism:** Loaded at startup by DevDataSeeder, NOT via EF Core HasData/migrations
|
|
613
|
+
> **Architecture:** Identical in core AND extensions
|
|
614
|
+
|
|
615
|
+
### Two Seeding Mechanisms
|
|
616
|
+
|
|
617
|
+
| Mechanism | When to Use | Applied Via |
|
|
618
|
+
|-----------|-------------|-------------|
|
|
619
|
+
| **HasData()** in Configuration.cs | System entities (Navigation, Permissions, Roles) | EF Core migrations |
|
|
620
|
+
| **SeedData.cs + DevDataSeeder** | Domain entities (Tickets, Products, Orders) | Application startup |
|
|
621
|
+
|
|
622
|
+
**Rule:** Navigation, Permission, and RolePermission seeds use HasData().
|
|
623
|
+
Domain entity seeds use the SeedData provider pattern below.
|
|
624
|
+
|
|
625
|
+
### SeedData Class Pattern
|
|
626
|
+
|
|
627
|
+
```csharp
|
|
628
|
+
// Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs
|
|
629
|
+
|
|
630
|
+
using SmartStack.Domain.{Context}.{Application}.{Module};
|
|
631
|
+
|
|
632
|
+
namespace SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
|
|
633
|
+
|
|
634
|
+
/// <summary>
|
|
635
|
+
/// Demo {EntityName} data for development and testing.
|
|
636
|
+
/// </summary>
|
|
637
|
+
public static class {EntityName}SeedData
|
|
638
|
+
{
|
|
639
|
+
// ============================================================
|
|
640
|
+
// DETERMINISTIC IDs - for referencing in other seed data
|
|
641
|
+
// ============================================================
|
|
642
|
+
|
|
643
|
+
public static readonly Guid Sample1Id = Guid.Parse("$SEED_GUID_1");
|
|
644
|
+
public static readonly Guid Sample2Id = Guid.Parse("$SEED_GUID_2");
|
|
645
|
+
public static readonly Guid Sample3Id = Guid.Parse("$SEED_GUID_3");
|
|
646
|
+
|
|
647
|
+
/// <summary>
|
|
648
|
+
/// Returns all demo {EntityName} entities.
|
|
649
|
+
/// </summary>
|
|
650
|
+
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
651
|
+
{
|
|
652
|
+
return new[]
|
|
653
|
+
{
|
|
654
|
+
new {EntityName}SeedItem
|
|
655
|
+
{
|
|
656
|
+
Id = Sample1Id,
|
|
657
|
+
// ... entity properties with realistic sample data
|
|
658
|
+
CreatedByUserId = UserSeedData.SystemUserId
|
|
659
|
+
},
|
|
660
|
+
new {EntityName}SeedItem
|
|
661
|
+
{
|
|
662
|
+
Id = Sample2Id,
|
|
663
|
+
// ... varied data
|
|
664
|
+
CreatedByUserId = UserSeedData.SystemUserId
|
|
665
|
+
},
|
|
666
|
+
new {EntityName}SeedItem
|
|
667
|
+
{
|
|
668
|
+
Id = Sample3Id,
|
|
669
|
+
// ... varied data
|
|
670
|
+
CreatedByUserId = UserSeedData.SystemUserId
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/// <summary>
|
|
677
|
+
/// Seed item DTO for {EntityName} (avoids domain factory methods during seeding).
|
|
678
|
+
/// </summary>
|
|
679
|
+
internal class {EntityName}SeedItem
|
|
680
|
+
{
|
|
681
|
+
public Guid Id { get; init; }
|
|
682
|
+
// ... all required properties matching the domain entity
|
|
683
|
+
public Guid CreatedByUserId { get; init; }
|
|
684
|
+
}
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### Multi-Tenant Entity Seed Pattern
|
|
688
|
+
|
|
689
|
+
For entities belonging to a tenant, organize by tenant:
|
|
690
|
+
|
|
691
|
+
```csharp
|
|
692
|
+
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
693
|
+
{
|
|
694
|
+
var items = new List<{EntityName}SeedItem>();
|
|
695
|
+
|
|
696
|
+
items.AddRange(GetTenant1{EntityName}s());
|
|
697
|
+
items.AddRange(GetTenant2{EntityName}s());
|
|
698
|
+
|
|
699
|
+
return items;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private static IEnumerable<{EntityName}SeedItem> GetTenant1{EntityName}s()
|
|
703
|
+
{
|
|
704
|
+
var tenantId = TenantSeedData.AcmeCorporationId;
|
|
705
|
+
var userId = TenantMembershipSeedData.AcmeOwnerId;
|
|
706
|
+
|
|
707
|
+
return new[]
|
|
708
|
+
{
|
|
709
|
+
new {EntityName}SeedItem
|
|
710
|
+
{
|
|
711
|
+
Id = Guid.Parse("$GUID"),
|
|
712
|
+
TenantId = tenantId,
|
|
713
|
+
CreatedByUserId = userId,
|
|
714
|
+
// ... properties
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### DevDataSeeder Registration Pattern
|
|
721
|
+
|
|
722
|
+
```csharp
|
|
723
|
+
// In Infrastructure/Persistence/Seeding/DevDataSeeder.cs
|
|
724
|
+
|
|
725
|
+
// 1. Add using statement
|
|
726
|
+
using SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
|
|
727
|
+
|
|
728
|
+
// 2. Add call in SeedAsync() method (after existing seed calls)
|
|
729
|
+
public async Task SeedAsync(CancellationToken cancellationToken = default)
|
|
730
|
+
{
|
|
731
|
+
// ... existing seed calls ...
|
|
732
|
+
|
|
733
|
+
await Seed{EntityName}sAsync(cancellationToken); // <-- ADD THIS
|
|
734
|
+
|
|
735
|
+
// ... summary logging ...
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// 3. Add private seeding method
|
|
739
|
+
private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
|
|
740
|
+
{
|
|
741
|
+
_logger.LogInformation("Seeding demo {EntityName}s...");
|
|
742
|
+
|
|
743
|
+
var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
|
|
744
|
+
if (existingCount > 0)
|
|
745
|
+
{
|
|
746
|
+
_logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
var createdCount = 0;
|
|
751
|
+
|
|
752
|
+
foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
|
|
753
|
+
{
|
|
754
|
+
var entity = {EntityName}.Create(
|
|
755
|
+
// Map seedItem properties to factory method parameters
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
// Set deterministic ID (factory generates random GUID)
|
|
759
|
+
typeof({EntityName}).GetProperty("Id")?.SetValue(entity, seedItem.Id);
|
|
760
|
+
|
|
761
|
+
_context.{EntityName}s.Add(entity);
|
|
762
|
+
createdCount++;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (createdCount > 0)
|
|
766
|
+
{
|
|
767
|
+
await _context.SaveChangesAsync(cancellationToken);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
_logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
|
|
771
|
+
}
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### Seed Data GUID Generation
|
|
775
|
+
|
|
776
|
+
```csharp
|
|
777
|
+
// Option 1: Hardcoded descriptive GUIDs (preferred for small sets)
|
|
778
|
+
public static readonly Guid ProductAlphaId = Guid.Parse("aa000001-0000-0000-0000-000000000001");
|
|
779
|
+
public static readonly Guid ProductBetaId = Guid.Parse("aa000001-0000-0000-0000-000000000002");
|
|
780
|
+
|
|
781
|
+
// Option 2: SHA256-based for larger sets
|
|
782
|
+
private static Guid GenerateEntityGuid(string uniqueKey)
|
|
783
|
+
{
|
|
784
|
+
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
785
|
+
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{EntityName}-{uniqueKey}"));
|
|
786
|
+
return new Guid(hash.Take(16).ToArray());
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Best Practices
|
|
791
|
+
|
|
792
|
+
| Practice | Description |
|
|
793
|
+
|----------|-------------|
|
|
794
|
+
| Deterministic IDs | NEVER use `Guid.NewGuid()` - seeds must be idempotent |
|
|
795
|
+
| Idempotent | Always check `if exists` before creating |
|
|
796
|
+
| Use factory methods | Call `Entity.Create(...)` not `new Entity()` |
|
|
797
|
+
| Set ID via reflection | Factory methods generate random IDs; override after creation |
|
|
798
|
+
| Realistic data | Use plausible names, descriptions, amounts |
|
|
799
|
+
| Cover all states | Include entities in different statuses (active, closed, etc.) |
|
|
800
|
+
| Reference existing seeds | Use IDs from TenantSeedData, UserSeedData for foreign keys |
|
|
801
|
+
| SaveChanges per batch | Call SaveChanges after each entity group |
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
608
805
|
## SEED CHECKLIST
|
|
609
806
|
|
|
610
807
|
| Check | Status |
|
|
611
808
|
|-------|--------|
|
|
612
809
|
| ☐ Deterministic GUID (not NewGuid) | |
|
|
613
|
-
| ☐ 4 languages for each entity | |
|
|
810
|
+
| ☐ 4 languages for each navigation entity | |
|
|
614
811
|
| ☐ Index translations continue existing sequence | |
|
|
615
812
|
| ☐ Route aligned with permission path | |
|
|
616
813
|
| ☐ DisplayOrder consistent | |
|
|
@@ -619,6 +816,8 @@ $ORDER = 1
|
|
|
619
816
|
| ☐ Resource permissions if sub-resources (Level 5) | |
|
|
620
817
|
| ☐ Bulk Operations permissions created | |
|
|
621
818
|
| ☐ RolePermissions assigned | |
|
|
819
|
+
| ☐ Entity SeedData.cs created (if user opted in) | |
|
|
820
|
+
| ☐ DevDataSeeder.cs updated (if user opted in) | |
|
|
622
821
|
|
|
623
822
|
---
|
|
624
823
|
|