@atlashub/smartstack-cli 3.22.0 → 3.24.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/mcp-entry.mjs +143 -174
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/mcp-scaffolding/component.tsx.hbs +21 -1
- package/templates/skills/apex/SKILL.md +21 -0
- package/templates/skills/apex/references/smartstack-api.md +507 -0
- package/templates/skills/apex/references/smartstack-frontend.md +1081 -0
- package/templates/skills/apex/references/smartstack-layers.md +166 -20
- package/templates/skills/apex/steps/step-00-init.md +27 -14
- package/templates/skills/apex/steps/step-01-analyze.md +45 -3
- package/templates/skills/apex/steps/step-02-plan.md +5 -1
- package/templates/skills/apex/steps/step-03-execute.md +51 -9
- package/templates/skills/apex/steps/step-04-validate.md +251 -0
- package/templates/skills/apex/steps/step-05-examine.md +7 -0
- package/templates/skills/apex/steps/step-07-tests.md +48 -5
- package/templates/skills/business-analyse/_shared.md +6 -6
- package/templates/skills/business-analyse/patterns/suggestion-catalog.md +1 -1
- package/templates/skills/business-analyse/questionnaire/07-ui.md +3 -3
- package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +1 -1
- package/templates/skills/business-analyse/references/entity-architecture-decision.md +3 -3
- package/templates/skills/business-analyse/references/handoff-file-templates.md +13 -5
- package/templates/skills/business-analyse/references/spec-auto-inference.md +14 -14
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +2 -2
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +1 -1
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +2 -2
- package/templates/skills/business-analyse/steps/step-03b-ui.md +2 -1
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +15 -4
- package/templates/skills/business-analyse/templates/tpl-frd.md +2 -2
- package/templates/skills/business-analyse/templates-frd.md +2 -2
- package/templates/skills/ralph-loop/references/category-rules.md +45 -7
- package/templates/skills/ralph-loop/references/compact-loop.md +2 -2
- package/templates/skills/ralph-loop/references/core-seed-data.md +10 -0
- package/templates/skills/ralph-loop/steps/step-02-execute.md +110 -1
- package/templates/skills/validate-feature/steps/step-05-db-validation.md +86 -1
|
@@ -287,13 +287,24 @@ For each module, generate these SEPARATE frontend file entries (not one monolith
|
|
|
287
287
|
|
|
288
288
|
```json
|
|
289
289
|
"frontend": [
|
|
290
|
-
{ "path": "src/pages/{Mod}/{ListPage}Page.tsx", "type": "Page", "linkedWireframes": ["{module}-list"], "module": "{moduleCode}" },
|
|
291
|
-
{ "path": "src/pages/{Mod}/{
|
|
290
|
+
{ "path": "src/pages/{Context}/{App}/{Mod}/{ListPage}Page.tsx", "type": "Page", "linkedWireframes": ["{module}-list"], "module": "{moduleCode}", "skill": "/ui-components" },
|
|
291
|
+
{ "path": "src/pages/{Context}/{App}/{Mod}/{Entity}CreatePage.tsx", "type": "Page", "linkedWireframes": ["{module}-create"], "module": "{moduleCode}", "skill": "/ui-components", "fkFields": ["EmployeeId→Employee", "DepartmentId→Department"] },
|
|
292
|
+
{ "path": "src/pages/{Context}/{App}/{Mod}/{Entity}EditPage.tsx", "type": "Page", "linkedWireframes": ["{module}-edit"], "module": "{moduleCode}", "skill": "/ui-components", "fkFields": ["EmployeeId→Employee", "DepartmentId→Department"] },
|
|
293
|
+
{ "path": "src/pages/{Context}/{App}/{Mod}/{DetailPage}Page.tsx", "type": "Page", "linkedWireframes": ["{module}-detail"], "module": "{moduleCode}", "skill": "/ui-components" },
|
|
292
294
|
{ "path": "src/hooks/use{Module}.ts", "type": "Hook", "module": "{moduleCode}" },
|
|
293
|
-
{ "path": "src/i18n/{
|
|
295
|
+
{ "path": "src/i18n/locales/fr/{moduleLower}.json", "type": "I18n", "language": "fr", "module": "{moduleCode}" },
|
|
296
|
+
{ "path": "src/i18n/locales/en/{moduleLower}.json", "type": "I18n", "language": "en", "module": "{moduleCode}" },
|
|
297
|
+
{ "path": "src/i18n/locales/it/{moduleLower}.json", "type": "I18n", "language": "it", "module": "{moduleCode}" },
|
|
298
|
+
{ "path": "src/i18n/locales/de/{moduleLower}.json", "type": "I18n", "language": "de", "module": "{moduleCode}" }
|
|
294
299
|
]
|
|
295
300
|
```
|
|
296
301
|
|
|
302
|
+
**FK fields in forms:** Create/Edit page entries MUST include `"fkFields"` array listing FK properties and their target entities (format: `"PropertyId→TargetEntity"`). This tells ralph-loop to use `EntityLookup` component instead of plain text inputs. Derive FK fields from `analysis.entities[].relationships[]` where `type = "N:1"` or from properties ending in `Id` with a navigation property. See `smartstack-frontend.md` section 6 for the EntityLookup pattern.
|
|
303
|
+
|
|
304
|
+
**Page generation:** ALL pages MUST use `/ui-components` skill (indicated by `"skill"` field). NEVER write raw TSX without the skill patterns.
|
|
305
|
+
|
|
306
|
+
**I18n generation:** 4 JSON files per module with identical key structures. `{moduleLower}` = lowercase module code. Keys: actions, labels, columns, form, errors, validation, messages, empty. See `smartstack-frontend.md` template.
|
|
307
|
+
|
|
297
308
|
Additional per-section pages (dashboard, import, etc.) are separate entries.
|
|
298
309
|
|
|
299
310
|
**Route wiring task (MANDATORY — separate entry):**
|
|
@@ -344,7 +355,7 @@ Add to progress.txt after all module tasks.
|
|
|
344
355
|
> "application": [{"path": "src/Application/Services/{Ctx}/{App}/{Mod}/{Svc}Service.cs", "type": "Service|Dto|Validator", "linkedFRs": [], "linkedUCs": [], "module": "..."}],
|
|
345
356
|
> "infrastructure": [{"path": "src/Infrastructure/Persistence/Configurations/{Ctx}/{App}/{Mod}/{Entity}Configuration.cs", "type": "EFConfiguration", "linkedFRs": [], "module": "..."}],
|
|
346
357
|
> "api": [{"path": "src/API/Controllers/{CtxShort}/{App}/{Entity}Controller.cs", "type": "ApiController", "linkedUCs": [], "linkedFRs": [], "module": "..."}],
|
|
347
|
-
> "frontend": [{"path": "src/pages/{Mod}/{Page}Page.tsx", "type": "Page|Component|Hook|DashboardPage", "linkedUCs": [], "linkedWireframes": [], "module": "..."}],
|
|
358
|
+
> "frontend": [{"path": "src/pages/{Ctx}/{App}/{Mod}/{Page}Page.tsx", "type": "Page|Component|Hook|DashboardPage|I18n", "linkedUCs": [], "linkedWireframes": [], "module": "...", "skill": "/ui-components"}],
|
|
348
359
|
> "seedData": [{"path": "src/Infrastructure/Persistence/Seeding/Data/{Mod}/...SeedData.cs", "type": "SeedData", "category": "core|business", "source": "...", "module": "..."}],
|
|
349
360
|
> "tests": [{"path": "src/Tests/Unit/Domain/{Ctx}/{App}/{Mod}/{Entity}Tests.cs", "type": "UnitTests|IntegrationTests|SecurityTests", "linkedFRs": [], "linkedUCs": [], "module": "..."}]
|
|
350
361
|
> },
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
|
|
101
101
|
| Element | Type | Behavior | Permission |
|
|
102
102
|
|---------|------|----------|------------|
|
|
103
|
-
| [+ New] | Button |
|
|
104
|
-
| [E] | Icon |
|
|
103
|
+
| [+ New] | Button | Navigates to `/create` page | `{module}.create` |
|
|
104
|
+
| [E] | Icon | Navigates to `/:id/edit` page | `{module}.update` |
|
|
105
105
|
| [D] | Icon | Confirmation + delete | `{module}.delete` |
|
|
106
106
|
|
|
107
107
|
## 5. Validation and Messages
|
|
@@ -272,8 +272,8 @@
|
|
|
272
272
|
|
|
273
273
|
| Element | Type | Behavior | Permission |
|
|
274
274
|
|---------|------|----------|------------|
|
|
275
|
-
| [+ New] | Button |
|
|
276
|
-
| [E] | Icon |
|
|
275
|
+
| [+ New] | Button | Navigates to `/create` page | `{module}.create` |
|
|
276
|
+
| [E] | Icon | Navigates to `/:id/edit` page | `{module}.update` |
|
|
277
277
|
| [D] | Icon | Confirmation + delete | `{module}.delete` |
|
|
278
278
|
|
|
279
279
|
## 5. Validation and Messages
|
|
@@ -281,6 +281,8 @@ fi
|
|
|
281
281
|
- Swagger XML documentation
|
|
282
282
|
- Consistent route patterns: `api/{context}/{app}/{module}`
|
|
283
283
|
- Return DTOs, never domain entities
|
|
284
|
+
- **ALL GetAll endpoints MUST accept `?search=` query parameter** (enables EntityLookup on frontend)
|
|
285
|
+
- **GetAll returns paginated results:** `PaginatedResult<T>` with items, totalCount, page, pageSize
|
|
284
286
|
|
|
285
287
|
**Controller coverage (BLOCKING):**
|
|
286
288
|
- EVERY entity in the module MUST have a controller with CRUD endpoints (GET list, GET by id, POST, PUT, DELETE)
|
|
@@ -307,18 +309,28 @@ fi
|
|
|
307
309
|
3. **Wire routes to App.tsx (detect pattern: `contextRoutes` array OR JSX `<Route>`):**
|
|
308
310
|
- **Pattern A** (`contextRoutes: ContextRouteExtensions` in App.tsx): add to `contextRoutes.{context}[]` with RELATIVE paths → auto-injected into both standard + tenant trees
|
|
309
311
|
- **Pattern B** (JSX `<Route>` in App.tsx): insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
|
|
310
|
-
4. Create pages using
|
|
312
|
+
4. Create pages using **`/ui-components` skill** (MANDATORY for entity lists, grids, tables, dashboards, charts). The skill ensures CSS variables, EntityCard, SmartTable, loading/error/empty states, and responsive grid patterns.
|
|
311
313
|
5. Create preferences hook: `use{Module}Preferences.ts`
|
|
312
|
-
6. Generate i18n (4 languages: fr, en, it, de)
|
|
314
|
+
6. Generate i18n (4 languages: fr, en, it, de) — see I18n section below for detailed rules
|
|
313
315
|
7. `npm run typecheck` MUST pass (BLOCKING)
|
|
314
316
|
|
|
315
317
|
**Components:**
|
|
316
318
|
- Lists: `SmartTable` + `SmartFilter` (NOT HTML `<table>`)
|
|
317
319
|
- Grids: `EntityCard` (NOT custom `<div>` cards)
|
|
318
320
|
- Detail: `EntityDetailCard`, `StatusBadge`, tab layout
|
|
319
|
-
- Forms: `SmartForm` with FluentValidation-backed fields
|
|
321
|
+
- Forms: `SmartForm` with FluentValidation-backed fields — **MUST be full pages, NEVER modals**
|
|
322
|
+
- FK fields: `EntityLookup` for searchable entity selection (NEVER plain text for Guid FK)
|
|
320
323
|
- Dashboard: `StatCard`, Recharts components
|
|
321
324
|
|
|
325
|
+
**Form pages (CRITICAL — ZERO modals/popups/drawers):**
|
|
326
|
+
- Create form: `EntityCreatePage.tsx` with route `/{module}/create`
|
|
327
|
+
- Edit form: `EntityEditPage.tsx` with route `/{module}/:id/edit`
|
|
328
|
+
- ALL forms are full pages with their own URL — NEVER Modal/Dialog/Drawer/Popup
|
|
329
|
+
- Back button with `navigate(-1)` on every form page
|
|
330
|
+
- Use React.lazy() + `<Suspense fallback={<PageLoader />}>` for form page imports
|
|
331
|
+
- **Form tests (MANDATORY):** Co-located `EntityCreatePage.test.tsx` and `EntityEditPage.test.tsx`
|
|
332
|
+
→ Cover: rendering, validation, submit, pre-fill (edit), navigation, error handling
|
|
333
|
+
|
|
322
334
|
**Layout wrapper mapping:**
|
|
323
335
|
|
|
324
336
|
| Context | Layout | Route path |
|
|
@@ -355,17 +367,43 @@ Adding routes to clientRoutes[] instead of contextRoutes.{context}[] → MUST us
|
|
|
355
367
|
'00000000-0000-0000-0000-000000000000' → use dynamic ID from auth context or route params
|
|
356
368
|
const api = axios.create(...) → use @/services/api/apiClient (single instance)
|
|
357
369
|
useXxx with raw axios inside hooks → hooks MUST use the shared apiClient from @/services/api
|
|
370
|
+
<input type="text" value={...employeeId} → FK Guid fields MUST use EntityLookup (searchable select)
|
|
371
|
+
placeholder="Enter ID" / "Enter GUID" → EntityLookup provides search-based entity selection
|
|
372
|
+
<Modal>/<Dialog>/<Drawer>/<Popup> for forms → forms are FULL PAGES with own URL routes (/create, /:id/edit)
|
|
373
|
+
useState(showCreateModal/editDialog) → navigate to form page, NEVER toggle modal visibility
|
|
358
374
|
```
|
|
359
375
|
|
|
360
376
|
---
|
|
361
377
|
|
|
362
|
-
## I18n
|
|
378
|
+
## I18n (MANDATORY for frontend)
|
|
379
|
+
|
|
380
|
+
> **CRITICAL:** Every frontend module MUST have 4 translation files. Missing i18n = broken UI (untranslated labels, empty buttons).
|
|
381
|
+
|
|
382
|
+
**File path:** `src/i18n/locales/{lang}/{moduleLower}.json` (4 files: fr, en, it, de)
|
|
383
|
+
|
|
384
|
+
**JSON key structure (identical across all 4 languages):**
|
|
385
|
+
```json
|
|
386
|
+
{
|
|
387
|
+
"actions": { "create": "...", "edit": "...", "delete": "...", "save": "...", "cancel": "...", "search": "...", "filter": "...", "export": "...", "refresh": "..." },
|
|
388
|
+
"labels": { "title": "...", "description": "...", "status": "...", "createdAt": "...", "updatedAt": "..." },
|
|
389
|
+
"columns": { "name": "...", "status": "...", "date": "...", "actions": "..." },
|
|
390
|
+
"form": { "name": "...", "description": "...", "submit": "...", "required": "..." },
|
|
391
|
+
"errors": { "loadFailed": "...", "saveFailed": "...", "deleteFailed": "...", "notFound": "..." },
|
|
392
|
+
"validation": { "required": "...", "minLength": "...", "maxLength": "...", "invalid": "..." },
|
|
393
|
+
"messages": { "created": "...", "updated": "...", "deleted": "...", "confirmDelete": "..." },
|
|
394
|
+
"empty": { "title": "...", "description": "...", "action": "..." }
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Usage pattern in TSX:** `t('{moduleLower}:actions.create', 'Create')` — ALWAYS namespace prefix + fallback value.
|
|
363
399
|
|
|
364
400
|
**Rules:**
|
|
365
|
-
- 4 JSON files: fr, en, it, de
|
|
366
|
-
- Identical key structures across all languages
|
|
401
|
+
- 4 JSON files: fr, en, it, de — ALL mandatory (not just fr/en)
|
|
402
|
+
- Identical key structures across all languages — same keys, translated values
|
|
367
403
|
- All UI labels, validation messages, button text, empty states
|
|
368
|
-
- Depends on frontend
|
|
404
|
+
- Depends on frontend page completion (pages define which keys are needed)
|
|
405
|
+
- Add entity-specific keys under `labels`, `columns`, `form` for each entity field
|
|
406
|
+
- Reference `smartstack-frontend.md` for the complete template
|
|
369
407
|
|
|
370
408
|
---
|
|
371
409
|
|
|
@@ -99,8 +99,8 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
99
99
|
| `infrastructure` with migration | `Migrations/` folder has .cs files | No migration files |
|
|
100
100
|
| `infrastructure` with seed data | Seed data .cs files exist | 0 files created |
|
|
101
101
|
| `application` | Service + DTO + Validator files exist | 0 files created |
|
|
102
|
-
| `api` | Controller .cs files exist | 0 files created |
|
|
103
|
-
| `frontend` | .tsx page files exist | 0 files created |
|
|
102
|
+
| `api` | Controller .cs files exist + GetAll supports `?search=` | 0 files created OR GetAll without search param |
|
|
103
|
+
| `frontend` | .tsx page files exist + FK fields use EntityLookup (not plain text) | 0 files created OR FK Guid field as `<input type="text">` |
|
|
104
104
|
| `test` | Test project dir exists AND contains test .cs files | No test project or 0 test files |
|
|
105
105
|
| `validation` | `dotnet test` exit 0 AND `dotnet build` exit 0 | Either command fails |
|
|
106
106
|
|
|
@@ -1253,3 +1253,13 @@ public class {Module}DevDataSeeder : IDevDataSeeder
|
|
|
1253
1253
|
- Using `Guid.NewGuid()` for TenantId
|
|
1254
1254
|
- Omitting idempotency check (`AnyAsync`)
|
|
1255
1255
|
- Hardcoding TenantId inline (use `SeedConstants.DefaultTenantId`)
|
|
1256
|
+
|
|
1257
|
+
### CROSS-SCHEMA FK WARNING
|
|
1258
|
+
|
|
1259
|
+
> `SeedConstants.DefaultTenantId` MUST reference a tenant that EXISTS in `core.tenant_Tenants`.
|
|
1260
|
+
> The SmartStack platform seeds a default tenant during `InitializeSmartStackAsync()`.
|
|
1261
|
+
> If you use a custom GUID, ensure it is created BEFORE `DevDataSeeder` runs (Order >= 200).
|
|
1262
|
+
>
|
|
1263
|
+
> **Pipeline validation:**
|
|
1264
|
+
> - ralph-loop POST-CHECK warns if GUID not found in project config
|
|
1265
|
+
> - validate-feature step-05 verifies FK exists in real database via SQL query
|
|
@@ -63,7 +63,7 @@ Key category triggers:
|
|
|
63
63
|
|----------|---------------|
|
|
64
64
|
| `infrastructure` with `_migrationMeta` | Migration sequence: MCP suggest → add → update → build |
|
|
65
65
|
| `infrastructure` with seed data keywords | **MANDATORY**: Read `references/core-seed-data.md` |
|
|
66
|
-
| `frontend` | MCP-first: scaffold_api_client → scaffold_routes → pages |
|
|
66
|
+
| `frontend` | MCP-first: scaffold_api_client → scaffold_routes → `/ui-components` skill (pages) → form pages (create/edit = full pages, ZERO modals) → form tests → i18n (4 langues) |
|
|
67
67
|
| `test` | Generate via MCP → run → fix loop → 100% pass required |
|
|
68
68
|
| `validation` | Clean build → full test suite → MCP validate |
|
|
69
69
|
|
|
@@ -164,6 +164,43 @@ fi
|
|
|
164
164
|
- If `auth_Permissions` is empty → all authorization checks reject → 403 on every endpoint
|
|
165
165
|
- These are **foundational** — without them, ALL subsequent features (frontend, API, tests) are useless
|
|
166
166
|
|
|
167
|
+
**POST-CHECK: DefaultTenantId cross-schema FK validation (WARNING)**
|
|
168
|
+
|
|
169
|
+
After generating SeedConstants.cs and DevDataSeeder, verify that `DefaultTenantId` is not a phantom GUID that doesn't exist in `core.tenant_Tenants`:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Find SeedConstants.cs
|
|
173
|
+
SEED_CONSTANTS=$(find . -path "*/SeedConstants.cs" 2>/dev/null | head -1)
|
|
174
|
+
if [ -n "$SEED_CONSTANTS" ]; then
|
|
175
|
+
# Extract the DefaultTenantId GUID value
|
|
176
|
+
TENANT_GUID=$(grep -oP 'DefaultTenantId\s*=\s*Guid\.Parse\("([^"]+)"\)' "$SEED_CONSTANTS" | grep -oP '"[^"]+"' | tr -d '"')
|
|
177
|
+
|
|
178
|
+
if [ -z "$TENANT_GUID" ]; then
|
|
179
|
+
echo "⚠️ WARNING: SeedConstants.cs found but DefaultTenantId not defined"
|
|
180
|
+
echo "DevDataSeeder requires SeedConstants.DefaultTenantId for business seed data"
|
|
181
|
+
echo "Fix: Add 'public static readonly Guid DefaultTenantId = Guid.Parse(\"...\");' to SeedConstants.cs"
|
|
182
|
+
else
|
|
183
|
+
# Verify the GUID is referenced somewhere in platform config (not an invented value)
|
|
184
|
+
GUID_FOUND=$(grep -r "$TENANT_GUID" appsettings*.json src/ 2>/dev/null | grep -v SeedConstants)
|
|
185
|
+
|
|
186
|
+
if [ -z "$GUID_FOUND" ]; then
|
|
187
|
+
echo "⚠️ WARNING: SeedConstants.DefaultTenantId ($TENANT_GUID) not found in platform config"
|
|
188
|
+
echo "This GUID must exist in core.tenant_Tenants at runtime"
|
|
189
|
+
echo "Verify: The SmartStack platform seeds this tenant during InitializeSmartStackAsync()"
|
|
190
|
+
echo "If using a custom TenantId, ensure it's created before DevDataSeeder runs (Order >= 200)"
|
|
191
|
+
echo "validate-feature step-05 will verify this FK exists in real database"
|
|
192
|
+
else
|
|
193
|
+
echo "✓ SeedConstants.DefaultTenantId ($TENANT_GUID) found in platform config"
|
|
194
|
+
fi
|
|
195
|
+
fi
|
|
196
|
+
fi
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Why this matters:**
|
|
200
|
+
- If `DefaultTenantId` references a GUID not in `core.tenant_Tenants`, DevDataSeeder inserts orphan rows
|
|
201
|
+
- Business entities with invalid `TenantId` FK cause runtime 500 errors
|
|
202
|
+
- The tenant must be seeded by `InitializeSmartStackAsync()` BEFORE DevDataSeeder (Order >= 200) runs
|
|
203
|
+
|
|
167
204
|
### 6. Validate with MCP
|
|
168
205
|
|
|
169
206
|
```
|
|
@@ -206,6 +243,78 @@ npm run lint # ESLint — MUST exit 0
|
|
|
206
243
|
```
|
|
207
244
|
If FAIL → read errors → fix TSX/TS files → re-run → loop until pass.
|
|
208
245
|
|
|
246
|
+
**Frontend POST-CHECKs (BLOCKING for frontend category):**
|
|
247
|
+
```bash
|
|
248
|
+
# POST-CHECK: Forms must be full pages — ZERO modals/popups/drawers
|
|
249
|
+
PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null)
|
|
250
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
251
|
+
MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet)" $PAGE_FILES 2>/dev/null)
|
|
252
|
+
if [ -n "$MODAL_IMPORTS" ]; then
|
|
253
|
+
echo "BLOCKING: Form pages must NOT use Modal/Dialog/Drawer/Popup components"
|
|
254
|
+
echo "Create/Edit forms MUST be full pages with own URL routes (/create, /:id/edit)"
|
|
255
|
+
echo "$MODAL_IMPORTS"
|
|
256
|
+
exit 1
|
|
257
|
+
fi
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
# POST-CHECK: Create/Edit pages must exist for each module with a list page
|
|
261
|
+
LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
|
|
262
|
+
if [ -n "$LIST_PAGES" ]; then
|
|
263
|
+
for LP in $LIST_PAGES; do
|
|
264
|
+
DIR=$(dirname "$LP"); MOD=$(basename "$DIR")
|
|
265
|
+
if [ -z "$(find "$DIR" -name "*CreatePage.tsx" 2>/dev/null)" ]; then
|
|
266
|
+
echo "WARNING: Module $MOD has list page but no CreatePage"
|
|
267
|
+
fi
|
|
268
|
+
if [ -z "$(find "$DIR" -name "*EditPage.tsx" 2>/dev/null)" ]; then
|
|
269
|
+
echo "WARNING: Module $MOD has list page but no EditPage"
|
|
270
|
+
fi
|
|
271
|
+
done
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
# POST-CHECK: Form pages must have companion test files
|
|
275
|
+
FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test)
|
|
276
|
+
if [ -n "$FORM_PAGES" ]; then
|
|
277
|
+
for FP in $FORM_PAGES; do
|
|
278
|
+
TEST="${FP%.tsx}.test.tsx"
|
|
279
|
+
if [ ! -f "$TEST" ]; then
|
|
280
|
+
echo "BLOCKING: Form page missing test file: $FP → expected $TEST"
|
|
281
|
+
exit 1
|
|
282
|
+
fi
|
|
283
|
+
done
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
# POST-CHECK: FK fields must NOT be plain text inputs — use EntityLookup
|
|
287
|
+
FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test | grep -v node_modules)
|
|
288
|
+
if [ -n "$FORM_PAGES" ]; then
|
|
289
|
+
FK_TEXT_INPUTS=$(grep -Pn 'type=["\x27]text["\x27].*[a-z]+Id|value=\{[^}]*\.[a-z]+Id\}.*type=["\x27]text["\x27]' $FORM_PAGES 2>/dev/null)
|
|
290
|
+
if [ -n "$FK_TEXT_INPUTS" ]; then
|
|
291
|
+
echo "BLOCKING: FK Guid fields rendered as plain text inputs — MUST use EntityLookup"
|
|
292
|
+
echo "See smartstack-frontend.md section 6 for the EntityLookup pattern"
|
|
293
|
+
echo "$FK_TEXT_INPUTS"
|
|
294
|
+
exit 1
|
|
295
|
+
fi
|
|
296
|
+
FK_PLACEHOLDER=$(grep -Pn 'placeholder=["\x27].*[Ee]nter.*[Ii][Dd]|placeholder=["\x27].*[Gg][Uu][Ii][Dd]' $FORM_PAGES 2>/dev/null)
|
|
297
|
+
if [ -n "$FK_PLACEHOLDER" ]; then
|
|
298
|
+
echo "BLOCKING: Form placeholder asks user to enter ID/GUID — use EntityLookup instead"
|
|
299
|
+
echo "$FK_PLACEHOLDER"
|
|
300
|
+
exit 1
|
|
301
|
+
fi
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
# POST-CHECK: Backend GetAll endpoints must support ?search= for EntityLookup
|
|
305
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
306
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
307
|
+
for f in $CTRL_FILES; do
|
|
308
|
+
if grep -q "\[HttpGet\]" "$f" && grep -q "GetAll" "$f"; then
|
|
309
|
+
if ! grep -q "search" "$f"; then
|
|
310
|
+
echo "WARNING: Controller missing search parameter on GetAll: $f"
|
|
311
|
+
echo "GetAll MUST accept ?search= to enable EntityLookup on frontend"
|
|
312
|
+
fi
|
|
313
|
+
fi
|
|
314
|
+
done
|
|
315
|
+
fi
|
|
316
|
+
```
|
|
317
|
+
|
|
209
318
|
**Error resolution cycle (ALL categories):**
|
|
210
319
|
1. Read the FULL error output (not just first line)
|
|
211
320
|
2. Identify root cause: file path + line number
|
|
@@ -132,6 +132,65 @@ fi
|
|
|
132
132
|
|
|
133
133
|
---
|
|
134
134
|
|
|
135
|
+
## 5b. Verify Dev Seeding Configuration
|
|
136
|
+
|
|
137
|
+
Before checking seed data, verify that dev seeding is actually enabled:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Check 1: EnableDevSeeding in appsettings
|
|
141
|
+
APPSETTINGS_DEV=$(ls appsettings.Development.json 2>/dev/null || ls src/*Api*/appsettings.Development.json 2>/dev/null)
|
|
142
|
+
APPSETTINGS_BASE=$(ls appsettings.json 2>/dev/null || ls src/*Api*/appsettings.json 2>/dev/null)
|
|
143
|
+
|
|
144
|
+
SEEDING_EXPLICIT_TRUE=false
|
|
145
|
+
SEEDING_EXPLICIT_FALSE=false
|
|
146
|
+
|
|
147
|
+
# Check Development appsettings first (overrides base)
|
|
148
|
+
if [ -n "$APPSETTINGS_DEV" ]; then
|
|
149
|
+
if grep -q '"EnableDevSeeding".*true' "$APPSETTINGS_DEV" 2>/dev/null; then
|
|
150
|
+
SEEDING_EXPLICIT_TRUE=true
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# Check base appsettings
|
|
155
|
+
if [ -n "$APPSETTINGS_BASE" ] && [ "$SEEDING_EXPLICIT_TRUE" = "false" ]; then
|
|
156
|
+
if grep -q '"EnableDevSeeding".*false' "$APPSETTINGS_BASE" 2>/dev/null; then
|
|
157
|
+
SEEDING_EXPLICIT_FALSE=true
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# Check 2: Program.cs override pattern
|
|
162
|
+
PROGRAM_CS=$(find . -name "Program.cs" -path "*/Api/*" 2>/dev/null | head -1)
|
|
163
|
+
PROGRAM_OVERRIDE=false
|
|
164
|
+
if [ -n "$PROGRAM_CS" ]; then
|
|
165
|
+
if grep -q 'EnableDevSeeding.*IsDevelopment\|IsDevelopment.*EnableDevSeeding' "$PROGRAM_CS" 2>/dev/null; then
|
|
166
|
+
PROGRAM_OVERRIDE=true
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Evaluate
|
|
171
|
+
if [ "$SEEDING_EXPLICIT_FALSE" = "true" ] && [ "$PROGRAM_OVERRIDE" = "false" ]; then
|
|
172
|
+
echo "⚠️ WARNING: EnableDevSeeding is FALSE in appsettings.json and NO Program.cs override detected"
|
|
173
|
+
echo "DevDataSeeder will NOT execute — test data will be empty"
|
|
174
|
+
echo "Fix option 1: Add to appsettings.Development.json: \"EnableDevSeeding\": true"
|
|
175
|
+
echo "Fix option 2: Add to Program.cs: options.EnableDevSeeding = builder.Environment.IsDevelopment();"
|
|
176
|
+
SEEDING_ENABLED=false
|
|
177
|
+
elif [ "$PROGRAM_OVERRIDE" = "true" ]; then
|
|
178
|
+
echo "✓ EnableDevSeeding: Program.cs overrides to IsDevelopment() — active in Development"
|
|
179
|
+
SEEDING_ENABLED=true
|
|
180
|
+
else
|
|
181
|
+
echo "✓ EnableDevSeeding: explicitly enabled"
|
|
182
|
+
SEEDING_ENABLED=true
|
|
183
|
+
fi
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| Result | Meaning | Action |
|
|
187
|
+
|--------|---------|--------|
|
|
188
|
+
| Program.cs override detected | PASS | Dev seeding active in Development environment |
|
|
189
|
+
| Explicitly enabled | PASS | Dev seeding active |
|
|
190
|
+
| FALSE + no override | **WARNING** | DevDataSeeder won't run — seed data checks in section 6 will likely fail |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
135
194
|
## 6. Verify Seed Data Execution
|
|
136
195
|
|
|
137
196
|
```bash
|
|
@@ -182,6 +241,28 @@ if [ "$STARTED" = "true" ]; then
|
|
|
182
241
|
fi
|
|
183
242
|
fi
|
|
184
243
|
|
|
244
|
+
# --- DefaultTenantId cross-schema FK validation ---
|
|
245
|
+
# Verify that SeedConstants.DefaultTenantId exists in core.tenant_Tenants
|
|
246
|
+
SEED_CONSTANTS=$(find . -path "*/SeedConstants.cs" 2>/dev/null | head -1)
|
|
247
|
+
if [ -n "$SEED_CONSTANTS" ]; then
|
|
248
|
+
TENANT_GUID=$(grep -oP 'DefaultTenantId\s*=\s*Guid\.Parse\("([^"]+)"\)' "$SEED_CONSTANTS" | grep -oP '"[^"]+"' | tr -d '"')
|
|
249
|
+
|
|
250
|
+
if [ -n "$TENANT_GUID" ] && [ -n "$DB_NAME" ]; then
|
|
251
|
+
TENANT_EXISTS=$(sqlcmd -S "(localdb)\MSSQLLocalDB" -d "$DB_NAME" -Q "
|
|
252
|
+
SET NOCOUNT ON;
|
|
253
|
+
SELECT COUNT(*) FROM core.tenant_Tenants WHERE Id = '$TENANT_GUID'
|
|
254
|
+
" -h -1 2>/dev/null | tr -d ' ')
|
|
255
|
+
|
|
256
|
+
if [ "$TENANT_EXISTS" = "0" ]; then
|
|
257
|
+
echo "❌ FAIL: SeedConstants.DefaultTenantId ($TENANT_GUID) does NOT exist in core.tenant_Tenants"
|
|
258
|
+
echo "DevDataSeeder inserts rows with an invalid FK — causes runtime 500 errors"
|
|
259
|
+
echo "Fix: Use a TenantId that is seeded by InitializeSmartStackAsync()"
|
|
260
|
+
else
|
|
261
|
+
echo "✓ DefaultTenantId ($TENANT_GUID) exists in core.tenant_Tenants"
|
|
262
|
+
fi
|
|
263
|
+
fi
|
|
264
|
+
fi
|
|
265
|
+
|
|
185
266
|
# Cleanup
|
|
186
267
|
kill $SEED_PID 2>/dev/null
|
|
187
268
|
wait $SEED_PID 2>/dev/null
|
|
@@ -224,6 +305,8 @@ fi
|
|
|
224
305
|
| Pending model changes | PASS/FAIL | dotnet ef migrations has-pending |
|
|
225
306
|
| Migrations apply cleanly | PASS/FAIL | dotnet ef database update on temp DB |
|
|
226
307
|
| Integration tests (SQL Server) | PASS/FAIL | dotnet test --filter Integration |
|
|
308
|
+
| EnableDevSeeding active | PASS/WARN | Program.cs override or appsettings check |
|
|
309
|
+
| DefaultTenantId FK valid | PASS/FAIL | SeedConstants GUID exists in tenant_Tenants|
|
|
227
310
|
| Seed data accessible | PASS/WARN | API endpoints return seeded data |
|
|
228
311
|
| Temp DB cleanup | PASS/WARN | Database dropped |
|
|
229
312
|
|
|
@@ -231,7 +314,7 @@ DB Validation Result: {PASS / FAIL / SKIPPED}
|
|
|
231
314
|
```
|
|
232
315
|
|
|
233
316
|
**Interpretation:**
|
|
234
|
-
- **PASS**: All
|
|
317
|
+
- **PASS**: All 8 checks covered — migrations, SQL Server, LINQ→SQL, isolation, seeding config, tenant FK, seed data verified
|
|
235
318
|
- **FAIL**: One or more checks failed — fix before committing
|
|
236
319
|
- **SKIPPED**: LocalDB not available — tests ran on SQLite only (partial coverage)
|
|
237
320
|
|
|
@@ -248,3 +331,5 @@ DB Validation Result: {PASS / FAIL / SKIPPED}
|
|
|
248
331
|
| `Cannot insert the value NULL into column` | Missing NOT NULL | Add default value or make column nullable |
|
|
249
332
|
| `Login failed for user` | LocalDB auth | Run `sqllocaldb start MSSQLLocalDB` |
|
|
250
333
|
| `A network-related or instance-specific error` | LocalDB not running | Run `sqllocaldb start MSSQLLocalDB` |
|
|
334
|
+
| `DefaultTenantId does NOT exist in core.tenant_Tenants` | Phantom FK | Use a TenantId seeded by `InitializeSmartStackAsync()` |
|
|
335
|
+
| `EnableDevSeeding is FALSE` + empty tables | Seeding disabled | Add `options.EnableDevSeeding = builder.Environment.IsDevelopment()` in Program.cs |
|