@atlashub/smartstack-cli 4.32.0 → 4.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.documentation/index.html +2 -2
- package/.documentation/init.html +358 -174
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +271 -44
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/mcp-scaffolding/controller.cs.hbs +54 -128
- package/templates/project/README.md +19 -0
- package/templates/project/claude-md/api.CLAUDE.md.template +315 -0
- package/templates/project/claude-md/application.CLAUDE.md.template +181 -0
- package/templates/project/claude-md/domain.CLAUDE.md.template +125 -0
- package/templates/project/claude-md/infrastructure.CLAUDE.md.template +168 -0
- package/templates/project/claude-md/root.CLAUDE.md.template +339 -0
- package/templates/project/claude-md/web.CLAUDE.md.template +339 -0
- package/templates/skills/apex/SKILL.md +16 -10
- package/templates/skills/apex/_shared.md +1 -1
- package/templates/skills/apex/references/checks/architecture-checks.sh +154 -0
- package/templates/skills/apex/references/checks/backend-checks.sh +194 -0
- package/templates/skills/apex/references/checks/frontend-checks.sh +448 -0
- package/templates/skills/apex/references/checks/infrastructure-checks.sh +255 -0
- package/templates/skills/apex/references/checks/security-checks.sh +153 -0
- package/templates/skills/apex/references/checks/seed-checks.sh +536 -0
- package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +49 -192
- package/templates/skills/apex/references/post-checks.md +124 -2156
- package/templates/skills/apex/references/smartstack-api.md +160 -957
- package/templates/skills/apex/references/smartstack-frontend.md +134 -1022
- package/templates/skills/apex/references/smartstack-layers.md +12 -6
- package/templates/skills/apex/steps/step-00-init.md +81 -238
- package/templates/skills/apex/steps/step-03-execute.md +25 -752
- package/templates/skills/apex/steps/step-03a-layer0-domain.md +118 -0
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +91 -0
- package/templates/skills/apex/steps/step-03c-layer2-backend.md +240 -0
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +300 -0
- package/templates/skills/apex/steps/step-03e-layer4-devdata.md +44 -0
- package/templates/skills/apex/steps/step-04-examine.md +70 -150
- package/templates/skills/application/references/frontend-i18n-and-output.md +2 -2
- package/templates/skills/application/references/frontend-route-naming.md +5 -1
- package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +49 -198
- package/templates/skills/application/references/frontend-verification.md +11 -11
- package/templates/skills/application/steps/step-05-frontend.md +26 -15
- package/templates/skills/application/templates-frontend.md +4 -0
- package/templates/skills/cli-app-sync/SKILL.md +2 -2
- package/templates/skills/cli-app-sync/references/comparison-map.md +1 -1
- package/templates/skills/controller/references/controller-code-templates.md +70 -67
- package/templates/skills/controller/references/mcp-scaffold-workflow.md +5 -1
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# POST-CHECK: Frontend — CSS, Forms, Components, I18n
|
|
5
|
+
# C3a, C3-C7, C9, C11, C24-C27, C36-C37, C49, C52: Page structure, forms, i18n, routing
|
|
6
|
+
|
|
7
|
+
FAIL=false
|
|
8
|
+
|
|
9
|
+
# POST-CHECK C3a: Frontend must not be empty if Layer 3 was planned (BLOCKING)
|
|
10
|
+
APP_TSX=$(find web/ src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
11
|
+
if [ -n "$APP_TSX" ]; then
|
|
12
|
+
# Check for DynamicRouter pattern (v3.7+)
|
|
13
|
+
COMPONENT_REGISTRY=$(find web/ src/ -name "componentRegistry.generated.ts" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
14
|
+
if [ -n "$COMPONENT_REGISTRY" ]; then
|
|
15
|
+
echo "OK: componentRegistry.generated.ts found (DynamicRouter pattern)"
|
|
16
|
+
else
|
|
17
|
+
EMPTY_ROUTES=$(grep -P "applicationRoutes.*=\s*\{[\s/]*\}" "$APP_TSX" 2>/dev/null || true)
|
|
18
|
+
if [ -n "$EMPTY_ROUTES" ]; then
|
|
19
|
+
echo "BLOCKING: applicationRoutes in App.tsx is empty — Layer 3 frontend was NOT executed"
|
|
20
|
+
echo "Expected: at least one application key with route definitions"
|
|
21
|
+
echo "Fix: Run Layer 3 (scaffold_routes + scaffold_extension)"
|
|
22
|
+
FAIL=true
|
|
23
|
+
fi
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
PAGE_COUNT=$(find web/ src/ -path "*/pages/*" -name "*.tsx" -not -path "*/node_modules/*" 2>/dev/null | wc -l)
|
|
27
|
+
if [ "$PAGE_COUNT" -eq 0 ]; then
|
|
28
|
+
echo "BLOCKING: No page components found in pages/ directory"
|
|
29
|
+
echo "Fix: Generate pages via scaffold_extension or /ui-components"
|
|
30
|
+
FAIL=true
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
NAV_ROUTES=$(find web/ src/ -name "navRoutes.generated.ts" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
34
|
+
if [ -z "$NAV_ROUTES" ]; then
|
|
35
|
+
echo "BLOCKING: navRoutes.generated.ts not found — scaffold_routes was never called"
|
|
36
|
+
echo "Fix: Run scaffold_routes(source: 'controllers', outputFormat: 'componentRegistry')"
|
|
37
|
+
FAIL=true
|
|
38
|
+
fi
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# POST-CHECK C3: Translation files must exist for all 4 languages (if frontend)
|
|
42
|
+
TSX_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null || true)
|
|
43
|
+
if [ -n "$TSX_FILES" ]; then
|
|
44
|
+
NAMESPACES=$(grep -ohP "useTranslation\(\[?'([^']+)" $TSX_FILES 2>/dev/null | sed "s/.*'//" | sort -u || true)
|
|
45
|
+
for NS in $NAMESPACES; do
|
|
46
|
+
for LANG in fr en it de; do
|
|
47
|
+
if [ ! -f "src/i18n/locales/$LANG/$NS.json" ]; then
|
|
48
|
+
echo "WARNING: Missing translation file: src/i18n/locales/$LANG/$NS.json"
|
|
49
|
+
fi
|
|
50
|
+
done
|
|
51
|
+
done
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# POST-CHECK C4: Forms must be full pages with routes — ZERO modals/popups/drawers/slide-overs
|
|
55
|
+
PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null || true)
|
|
56
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
57
|
+
MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet|SlideOver|Overlay)" $PAGE_FILES 2>/dev/null || true)
|
|
58
|
+
if [ -n "$MODAL_IMPORTS" ]; then
|
|
59
|
+
echo "WARNING: Form pages must NOT use Modal/Dialog/Drawer/Popup/SlideOver components"
|
|
60
|
+
echo "$MODAL_IMPORTS"
|
|
61
|
+
FAIL=true
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
FORM_STATE=$(grep -Pn "useState.*(?:isOpen|showModal|showDialog|showCreate|showEdit|showForm|isCreating|isEditing|showDrawer|showPanel|showSlideOver|selectedEntity|editingEntity)" $PAGE_FILES 2>/dev/null || true)
|
|
65
|
+
if [ -n "$FORM_STATE" ]; then
|
|
66
|
+
echo "WARNING: Inline form state detected — forms embedded in ListPage as drawers/panels"
|
|
67
|
+
echo "Create/Edit forms MUST be separate page components with their own URL routes"
|
|
68
|
+
echo "$FORM_STATE"
|
|
69
|
+
FAIL=true
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [ "$FAIL" = true ]; then
|
|
73
|
+
echo ""
|
|
74
|
+
echo "Fix: Create EntityCreatePage.tsx with route /create and EntityEditPage.tsx with route /:id/edit"
|
|
75
|
+
echo "NEVER embed create/edit forms as inline drawers, panels, or slide-overs in ListPage"
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# POST-CHECK C5: Create/Edit pages must exist as separate route pages (CRITICAL)
|
|
80
|
+
LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test || true)
|
|
81
|
+
if [ -n "$LIST_PAGES" ]; then
|
|
82
|
+
FAIL_C5=false
|
|
83
|
+
for LIST_PAGE in $LIST_PAGES; do
|
|
84
|
+
PAGE_DIR=$(dirname "$LIST_PAGE")
|
|
85
|
+
MODULE_NAME=$(basename "$PAGE_DIR")
|
|
86
|
+
|
|
87
|
+
HAS_CREATE_NAV=$(grep -P "navigate\(.*['/]create" "$LIST_PAGE" 2>/dev/null || true)
|
|
88
|
+
HAS_EDIT_NAV=$(grep -P "navigate\(.*['/]edit|navigate\(.*/:id/edit" "$LIST_PAGE" 2>/dev/null || true)
|
|
89
|
+
|
|
90
|
+
CREATE_PAGE=$(find "$PAGE_DIR" -name "*CreatePage.tsx" 2>/dev/null || true)
|
|
91
|
+
if [ -z "$CREATE_PAGE" ]; then
|
|
92
|
+
if [ -n "$HAS_CREATE_NAV" ]; then
|
|
93
|
+
echo "CRITICAL: Module $MODULE_NAME ListPage navigates to /create but CreatePage does NOT exist"
|
|
94
|
+
echo " Dead link: $HAS_CREATE_NAV"
|
|
95
|
+
echo " Fix: Create ${MODULE_NAME}CreatePage.tsx in $PAGE_DIR"
|
|
96
|
+
FAIL_C5=true
|
|
97
|
+
fi
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
EDIT_PAGE=$(find "$PAGE_DIR" -name "*EditPage.tsx" 2>/dev/null || true)
|
|
101
|
+
if [ -z "$EDIT_PAGE" ]; then
|
|
102
|
+
if [ -n "$HAS_EDIT_NAV" ]; then
|
|
103
|
+
echo "CRITICAL: Module $MODULE_NAME ListPage navigates to /:id/edit but EditPage does NOT exist"
|
|
104
|
+
echo " Dead link: $HAS_EDIT_NAV"
|
|
105
|
+
echo " Fix: Create ${MODULE_NAME}EditPage.tsx in $PAGE_DIR"
|
|
106
|
+
FAIL_C5=true
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
done
|
|
110
|
+
|
|
111
|
+
if [ "$FAIL_C5" = true ]; then
|
|
112
|
+
echo ""
|
|
113
|
+
echo "CRITICAL: Create/Edit pages are MISSING but ListPage buttons link to them."
|
|
114
|
+
echo "Users will see white screen / 404 when clicking Create or Edit buttons."
|
|
115
|
+
echo "Fix: Generate form pages using /ui-components skill patterns (smartstack-frontend.md section 3b)"
|
|
116
|
+
FAIL=true
|
|
117
|
+
fi
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# POST-CHECK C6: Form pages must have companion test files
|
|
121
|
+
PAGE_FILES=$(find src/pages/ -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null || true)
|
|
122
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
123
|
+
ALL_TESTS=$(find src/pages/ -name "*.test.tsx" 2>/dev/null || true)
|
|
124
|
+
if [ -z "$ALL_TESTS" ]; then
|
|
125
|
+
echo "WARNING: No frontend test files found in src/pages/"
|
|
126
|
+
echo "Every form page MUST have a companion .test.tsx file"
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test || true)
|
|
131
|
+
if [ -n "$FORM_PAGES" ]; then
|
|
132
|
+
for FORM_PAGE in $FORM_PAGES; do
|
|
133
|
+
TEST_FILE="${FORM_PAGE%.tsx}.test.tsx"
|
|
134
|
+
if [ ! -f "$TEST_FILE" ]; then
|
|
135
|
+
echo "WARNING: Form page missing test file: $FORM_PAGE"
|
|
136
|
+
echo "Expected: $TEST_FILE"
|
|
137
|
+
echo "All form pages MUST have companion test files (rendering, validation, submit, navigation)"
|
|
138
|
+
fi
|
|
139
|
+
done
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# POST-CHECK C7: FK fields must use EntityLookup — NO <input>, NO <select> (WARNING)
|
|
143
|
+
ALL_PAGES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
|
|
144
|
+
if [ -n "$ALL_PAGES" ]; then
|
|
145
|
+
FAIL_C7=false
|
|
146
|
+
|
|
147
|
+
FK_INPUTS=$(grep -Pn '<input[^>]*(?:name|value)=["\x27{][^>]*[a-zA-Z]Id["\x27}]' $ALL_PAGES 2>/dev/null | grep -Pv 'type=["\x27]hidden["\x27]' || true)
|
|
148
|
+
if [ -n "$FK_INPUTS" ]; then
|
|
149
|
+
echo "WARNING: FK fields rendered as <input> — MUST use EntityLookup component"
|
|
150
|
+
echo "$FK_INPUTS"
|
|
151
|
+
FAIL_C7=true
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
FK_SELECTS=$(grep -Pn '<select[^>]*value=\{[^}]*[a-zA-Z]Id\b' $ALL_PAGES 2>/dev/null || true)
|
|
155
|
+
if [ -n "$FK_SELECTS" ]; then
|
|
156
|
+
echo "WARNING: FK fields rendered as <select> dropdown — MUST use EntityLookup component"
|
|
157
|
+
echo "A <select> loaded from API state is NOT a valid substitute for EntityLookup."
|
|
158
|
+
echo "EntityLookup provides: debounced search, paginated results, display name resolution."
|
|
159
|
+
echo "$FK_SELECTS"
|
|
160
|
+
FAIL_C7=true
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
FK_SELECT_ONCHANGE=$(grep -Pn 'onChange=.*[a-zA-Z]Id[^a-zA-Z].*e\.target\.value' $ALL_PAGES 2>/dev/null || true)
|
|
164
|
+
if [ -n "$FK_SELECT_ONCHANGE" ]; then
|
|
165
|
+
echo "WARNING: FK field set via e.target.value (select/input pattern) — use EntityLookup onChange(id)"
|
|
166
|
+
echo "$FK_SELECT_ONCHANGE"
|
|
167
|
+
FAIL_C7=true
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
FK_PLACEHOLDER=$(grep -Pn 'placeholder=["\x27].*(?:[Ee]nter.*[Ii][Dd]|[Gg][Uu][Ii][Dd]|[Ss]elect.*[Ee]mployee|[Ss]elect.*[Dd]epartment|[Ss]elect.*[Pp]osition|[Ss]elect.*[Pp]roject|[Ss]elect.*[Cc]ategory)' $ALL_PAGES 2>/dev/null || true)
|
|
171
|
+
if [ -n "$FK_PLACEHOLDER" ]; then
|
|
172
|
+
echo "WARNING: Form has placeholder for FK field selection — use EntityLookup search instead"
|
|
173
|
+
echo "$FK_PLACEHOLDER"
|
|
174
|
+
FAIL_C7=true
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
FK_OPTIONS=$(grep -Pn '<option[^>]*value=\{[^}]*\.id\}' $ALL_PAGES 2>/dev/null || true)
|
|
178
|
+
if [ -n "$FK_OPTIONS" ]; then
|
|
179
|
+
echo "WARNING: <option> with entity .id value detected — this is a FK <select> anti-pattern"
|
|
180
|
+
echo "Replace the entire <select>/<option> block with <EntityLookup />"
|
|
181
|
+
echo "$FK_OPTIONS"
|
|
182
|
+
FAIL_C7=true
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
if [ "$FAIL_C7" = true ]; then
|
|
186
|
+
echo ""
|
|
187
|
+
echo "Fix: Replace ALL FK fields with <EntityLookup /> from @/components/ui/EntityLookup"
|
|
188
|
+
echo "See smartstack-frontend.md section 6 for the EntityLookup pattern"
|
|
189
|
+
echo "FORBIDDEN for FK Guid fields: <input>, <select>, <option>, e.target.value"
|
|
190
|
+
echo "REQUIRED: <EntityLookup apiEndpoint={...} mapOption={...} value={...} onChange={...} />"
|
|
191
|
+
fi
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# POST-CHECK C9: No hardcoded Tailwind colors in generated pages (WARNING)
|
|
195
|
+
ALL_PAGES=$(find src/pages/ src/components/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
|
|
196
|
+
if [ -n "$ALL_PAGES" ]; then
|
|
197
|
+
HARDCODED=$(grep -Pn '(bg|text|border)-(?!\[)(red|blue|green|gray|white|black|slate|zinc|neutral|stone)-' $ALL_PAGES 2>/dev/null || true)
|
|
198
|
+
if [ -n "$HARDCODED" ]; then
|
|
199
|
+
echo "WARNING: Pages MUST use CSS variables instead of hardcoded Tailwind colors"
|
|
200
|
+
echo "SmartStack uses a theme system — hardcoded colors break dark mode and custom themes"
|
|
201
|
+
echo ""
|
|
202
|
+
echo "Fix mapping:"
|
|
203
|
+
echo " bg-white → bg-[var(--bg-card)]"
|
|
204
|
+
echo " bg-gray-50 → bg-[var(--bg-primary)]"
|
|
205
|
+
echo " text-gray-900 → text-[var(--text-primary)]"
|
|
206
|
+
echo " text-gray-500 → text-[var(--text-secondary)]"
|
|
207
|
+
echo " border-gray-200 → border-[var(--border-color)]"
|
|
208
|
+
echo " bg-blue-600 → bg-[var(--color-accent-500)]"
|
|
209
|
+
echo " text-blue-600 → text-[var(--color-accent-500)]"
|
|
210
|
+
echo " text-red-500 → text-[var(--error-text)]"
|
|
211
|
+
echo " bg-green-500 → bg-[var(--success-bg)]"
|
|
212
|
+
echo ""
|
|
213
|
+
echo "See references/smartstack-frontend.md section 4 for full variable reference"
|
|
214
|
+
echo ""
|
|
215
|
+
echo "$HARDCODED"
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# POST-CHECK C11: Frontend routes must use kebab-case (BLOCKING)
|
|
220
|
+
APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
221
|
+
if [ -n "$APP_TSX" ]; then
|
|
222
|
+
FE_PATHS=$(grep -oP "path:\s*['\"]([^'\"]+)['\"]" "$APP_TSX" | grep -oP "['\"][^'\"]+['\"]" | tr -d "'" | tr -d '"')
|
|
223
|
+
for FE_PATH in $FE_PATHS; do
|
|
224
|
+
for SEG in $(echo "$FE_PATH" | tr '/' '\n'); do
|
|
225
|
+
echo "$SEG" | grep -qP '^:' && continue
|
|
226
|
+
if echo "$SEG" | grep -qP '^[a-z]{10,}$'; then
|
|
227
|
+
SEED_MATCH=$(echo "$SEED_ROUTES" | tr '/' '\n' | grep -P "^[a-z]+-[a-z]+" | tr -d '-' | grep -x "$SEG" || true)
|
|
228
|
+
if [ -n "$SEED_MATCH" ]; then
|
|
229
|
+
echo "BLOCKING: Frontend route segment '$SEG' appears to be missing hyphens"
|
|
230
|
+
echo "Seed data uses kebab-case (e.g., 'human-resources') but frontend has '$SEG'"
|
|
231
|
+
echo "Fix: Use kebab-case in App.tsx route paths to match seed data exactly"
|
|
232
|
+
FAIL=true
|
|
233
|
+
fi
|
|
234
|
+
fi
|
|
235
|
+
done
|
|
236
|
+
done
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
# POST-CHECK C24: i18n must use separate JSON files per language (not embedded in index.ts)
|
|
240
|
+
TSX_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
|
|
241
|
+
if [ -n "$TSX_FILES" ]; then
|
|
242
|
+
if [ ! -d "src/i18n/locales" ]; then
|
|
243
|
+
echo "WARNING: Missing src/i18n/locales/ directory"
|
|
244
|
+
echo "Translations must be in separate JSON files: src/i18n/locales/{fr,en,it,de}/{module}.json"
|
|
245
|
+
echo "NEVER embed translations in src/i18n/index.ts or a single TypeScript file"
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
I18N_INDEX=$(find src/i18n/ -maxdepth 1 -name "index.ts" -o -name "index.tsx" -o -name "config.ts" 2>/dev/null || true)
|
|
249
|
+
if [ -n "$I18N_INDEX" ]; then
|
|
250
|
+
EMBEDDED=$(grep -Pn '^\s*(resources|translations)\s*[:=]\s*\{' $I18N_INDEX 2>/dev/null || true)
|
|
251
|
+
if [ -n "$EMBEDDED" ]; then
|
|
252
|
+
echo "WARNING: Translations embedded in i18n config file — must be in separate JSON files"
|
|
253
|
+
echo "Found embedded translations in:"
|
|
254
|
+
echo "$EMBEDDED"
|
|
255
|
+
echo ""
|
|
256
|
+
echo "Fix: Move translations to src/i18n/locales/{fr,en,it,de}/{module}.json"
|
|
257
|
+
echo "The i18n config should import from locales/ directory, not contain inline translations"
|
|
258
|
+
fi
|
|
259
|
+
fi
|
|
260
|
+
|
|
261
|
+
for LANG in fr en it de; do
|
|
262
|
+
if [ ! -d "src/i18n/locales/$LANG" ]; then
|
|
263
|
+
echo "WARNING: Missing language directory: src/i18n/locales/$LANG/"
|
|
264
|
+
echo "SmartStack requires 4 languages: fr, en, it, de"
|
|
265
|
+
fi
|
|
266
|
+
done
|
|
267
|
+
|
|
268
|
+
for LANG in fr en it de; do
|
|
269
|
+
JSON_COUNT=$(find "src/i18n/locales/$LANG" -name "*.json" 2>/dev/null | wc -l)
|
|
270
|
+
if [ "$JSON_COUNT" -eq 0 ]; then
|
|
271
|
+
echo "WARNING: No translation JSON files in src/i18n/locales/$LANG/"
|
|
272
|
+
echo "Each module must have a {module}.json file per language"
|
|
273
|
+
fi
|
|
274
|
+
done
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# POST-CHECK C25: Pages must use useTranslation hook (no hardcoded user-facing strings)
|
|
278
|
+
PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
|
|
279
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
280
|
+
TOTAL_PAGES=$(echo "$PAGE_FILES" | wc -l)
|
|
281
|
+
I18N_PAGES=$(grep -l "useTranslation" $PAGE_FILES 2>/dev/null | wc -l)
|
|
282
|
+
if [ "$TOTAL_PAGES" -gt 0 ] && [ "$I18N_PAGES" -eq 0 ]; then
|
|
283
|
+
echo "WARNING: No page files use useTranslation — all user-facing text must be translated"
|
|
284
|
+
echo "Found $TOTAL_PAGES page files but 0 use useTranslation"
|
|
285
|
+
echo ""
|
|
286
|
+
echo "Fix: Import and use useTranslation in every page component:"
|
|
287
|
+
echo " const { t } = useTranslation(['{module}']);"
|
|
288
|
+
echo " t('{module}:title', 'Fallback text')"
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
HARDCODED_TEXT=$(grep -Pn '>\s*(Create|Edit|Delete|Save|Cancel|Search|Loading|Error|No data|Not found|Submit|Back|Actions|Name|Status|Description)\s*<' $PAGE_FILES 2>/dev/null | grep -v '{t(' | head -10 || true)
|
|
292
|
+
if [ -n "$HARDCODED_TEXT" ]; then
|
|
293
|
+
echo "WARNING: Possible hardcoded user-facing strings detected in JSX"
|
|
294
|
+
echo "All user-facing text MUST use t('namespace:key', 'Fallback')"
|
|
295
|
+
echo "$HARDCODED_TEXT"
|
|
296
|
+
fi
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# POST-CHECK C26: List/Detail pages must include DocToggleButton (documentation panel)
|
|
300
|
+
LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
|
|
301
|
+
if [ -n "$LIST_PAGES" ]; then
|
|
302
|
+
MISSING_DOC=0
|
|
303
|
+
for PAGE in $LIST_PAGES; do
|
|
304
|
+
if ! grep -q "DocToggleButton" "$PAGE" 2>/dev/null; then
|
|
305
|
+
echo "WARNING: Page missing DocToggleButton: $PAGE"
|
|
306
|
+
echo " Import: import { DocToggleButton } from '@/components/docs/DocToggleButton';"
|
|
307
|
+
echo " Place in header actions: <DocToggleButton />"
|
|
308
|
+
MISSING_DOC=$((MISSING_DOC + 1))
|
|
309
|
+
fi
|
|
310
|
+
done
|
|
311
|
+
if [ "$MISSING_DOC" -gt 0 ]; then
|
|
312
|
+
echo ""
|
|
313
|
+
echo "WARNING: $MISSING_DOC pages missing DocToggleButton — users cannot access inline documentation"
|
|
314
|
+
echo "See smartstack-frontend.md section 7 for placement pattern"
|
|
315
|
+
fi
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
# POST-CHECK C27: Module documentation must be generated (doc-data.ts)
|
|
319
|
+
TSX_PAGES=$(find src/pages/ -name "*.tsx" -not -name "*.test.*" 2>/dev/null | grep -v node_modules | grep -v "docs/" || true)
|
|
320
|
+
DOC_DATA=$(find src/pages/docs/ -name "doc-data.ts" 2>/dev/null || true)
|
|
321
|
+
if [ -n "$TSX_PAGES" ] && [ -z "$DOC_DATA" ]; then
|
|
322
|
+
echo "WARNING: Frontend pages exist but no documentation generated"
|
|
323
|
+
echo "Fix: Invoke /documentation {module} --type user to generate doc-data.ts"
|
|
324
|
+
echo "The DocToggleButton in page headers will link to this documentation"
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
# POST-CHECK C36: Frontend navigate() calls must have matching route definitions (CRITICAL)
|
|
328
|
+
PAGE_FILES=$(find web/ -name "*.tsx" -path "*/pages/*" ! -name "*.test.tsx" 2>/dev/null || true)
|
|
329
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
330
|
+
NAV_TARGETS=$(grep -oP "navigate\(['\"]([^'\"]+)['\"]" $PAGE_FILES 2>/dev/null | grep -oP "['\"][^'\"]+['\"]" | tr -d "'" | tr -d '"' | sort -u || true)
|
|
331
|
+
APP_FILES=$(find web/ -name "App.tsx" -o -name "routes.tsx" -o -name "applicationRoutes*.tsx" -o -name "clientRoutes*.tsx" -o -name "componentRegistry.generated.ts" 2>/dev/null || true)
|
|
332
|
+
if [ -n "$APP_FILES" ] && [ -n "$NAV_TARGETS" ]; then
|
|
333
|
+
ROUTE_PATHS=$(grep -oP "path:\s*['\"]([^'\"]+)['\"]" $APP_FILES 2>/dev/null | grep -oP "['\"][^'\"]+['\"]" | tr -d "'" | tr -d '"' | sort -u || true)
|
|
334
|
+
for TARGET in $NAV_TARGETS; do
|
|
335
|
+
if echo "$TARGET" | grep -qP '^(:|/api|http|-[0-9])'; then continue; fi
|
|
336
|
+
LAST_SEG=$(echo "$TARGET" | grep -oP '[a-z][-a-z0-9]*$' || true)
|
|
337
|
+
if [ -z "$LAST_SEG" ]; then continue; fi
|
|
338
|
+
FOUND=$(echo "$ROUTE_PATHS" | grep -F "$LAST_SEG" 2>/dev/null || true)
|
|
339
|
+
if [ -z "$FOUND" ]; then
|
|
340
|
+
SEG_PASCAL=$(echo "$LAST_SEG" | sed -r 's/(^|-)([a-z])/\U\2/g')
|
|
341
|
+
PAGE_EXISTS=$(find web/ -name "${SEG_PASCAL}Page.tsx" -o -name "${SEG_PASCAL}ListPage.tsx" -o -name "${SEG_PASCAL}sPage.tsx" 2>/dev/null || true)
|
|
342
|
+
if [ -z "$PAGE_EXISTS" ]; then
|
|
343
|
+
SOURCE_FILE=$(grep -rl "navigate(['\"].*${LAST_SEG}" $PAGE_FILES 2>/dev/null | head -1 || true)
|
|
344
|
+
echo "CRITICAL: Dead link detected — navigate('$TARGET') in $SOURCE_FILE"
|
|
345
|
+
echo " Route segment '$LAST_SEG' has no matching route in App.tsx and no page component"
|
|
346
|
+
echo " Fix: Either create the page component + route, or remove the navigate() button"
|
|
347
|
+
FAIL=true
|
|
348
|
+
fi
|
|
349
|
+
fi
|
|
350
|
+
done
|
|
351
|
+
fi
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
# POST-CHECK C37: Detail page tabs must NOT navigate() — content switches locally (CRITICAL)
|
|
355
|
+
DETAIL_PAGES=$(find src/ web/ -name "*DetailPage.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
|
|
356
|
+
if [ -n "$DETAIL_PAGES" ]; then
|
|
357
|
+
FAIL_C37=false
|
|
358
|
+
for DP in $DETAIL_PAGES; do
|
|
359
|
+
HAS_TABS=$(grep -P "useState.*activeTab|setActiveTab" "$DP" 2>/dev/null || true)
|
|
360
|
+
if [ -z "$HAS_TABS" ]; then continue; fi
|
|
361
|
+
|
|
362
|
+
TAB_NAVIGATE=$(grep -Pn "navigate\(" "$DP" 2>/dev/null | grep -v "navigate\(\s*['\"]edit['\"]" | grep -v "navigate\(\s*-1\s*\)" | grep -v "navigate\(\s*['\`].*/:id/edit" | grep -v "//" || true)
|
|
363
|
+
if [ -n "$TAB_NAVIGATE" ]; then
|
|
364
|
+
RELATIVE_NAV=$(echo "$TAB_NAVIGATE" | grep -P "navigate\(['\"\`]\.\./" 2>/dev/null || true)
|
|
365
|
+
if [ -n "$RELATIVE_NAV" ]; then
|
|
366
|
+
echo "CRITICAL: Detail page tabs use navigate() instead of local content switching: $DP"
|
|
367
|
+
echo " Tab click handlers MUST only call setActiveTab() — render content inline"
|
|
368
|
+
echo " Found navigate() calls (likely in tab handlers):"
|
|
369
|
+
echo "$RELATIVE_NAV"
|
|
370
|
+
echo ""
|
|
371
|
+
echo " Fix: Remove navigate() from tab handlers. Render sub-resource content inline:"
|
|
372
|
+
echo " {activeTab === 'leaves' && <LeaveRequestsTable employeeId={entity.id} />}"
|
|
373
|
+
echo " See smartstack-frontend.md section 3 'Tab Behavior Rules' for the correct pattern."
|
|
374
|
+
FAIL_C37=true
|
|
375
|
+
fi
|
|
376
|
+
fi
|
|
377
|
+
done
|
|
378
|
+
if [ "$FAIL_C37" = true ]; then
|
|
379
|
+
FAIL=true
|
|
380
|
+
fi
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
# POST-CHECK C49: Route Ordering in App.tsx — static before dynamic (BLOCKING)
|
|
384
|
+
APP_TSX=$(find web/ src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
385
|
+
if [ -n "$APP_TSX" ]; then
|
|
386
|
+
ROUTE_PATHS=$(grep -oP "path:\s*'([^']+)'" "$APP_TSX" | sed "s/path: '//;s/'//")
|
|
387
|
+
FAIL_C49=false
|
|
388
|
+
PREV_PREFIX=""
|
|
389
|
+
PREV_IS_DYNAMIC=false
|
|
390
|
+
|
|
391
|
+
while IFS= read -r ROUTE; do
|
|
392
|
+
[ -z "$ROUTE" ] && continue
|
|
393
|
+
PREFIX=$(echo "$ROUTE" | sed 's|/[^/]*$||')
|
|
394
|
+
LAST_SEGMENT=$(echo "$ROUTE" | grep -oP '[^/]+$')
|
|
395
|
+
IS_DYNAMIC=$(echo "$LAST_SEGMENT" | grep -c '^:')
|
|
396
|
+
|
|
397
|
+
if [ "$PREV_IS_DYNAMIC" = true ] && [ "$IS_DYNAMIC" -eq 0 ] && [ "$PREFIX" = "$PREV_PREFIX" ]; then
|
|
398
|
+
echo "BLOCKING: Static route '$ROUTE' is AFTER a dynamic route with prefix '$PREFIX' in App.tsx"
|
|
399
|
+
echo " React Router will match the dynamic route first → '$ROUTE' is unreachable (404)"
|
|
400
|
+
echo " Fix: Move static routes BEFORE dynamic routes within the same prefix group"
|
|
401
|
+
FAIL_C49=true
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
PREV_PREFIX="$PREFIX"
|
|
405
|
+
if [ "$IS_DYNAMIC" -gt 0 ]; then
|
|
406
|
+
PREV_IS_DYNAMIC=true
|
|
407
|
+
else
|
|
408
|
+
PREV_IS_DYNAMIC=false
|
|
409
|
+
fi
|
|
410
|
+
done <<< "$ROUTE_PATHS"
|
|
411
|
+
|
|
412
|
+
if [ "$FAIL_C49" = true ]; then
|
|
413
|
+
FAIL=true
|
|
414
|
+
fi
|
|
415
|
+
fi
|
|
416
|
+
|
|
417
|
+
# POST-CHECK C52: Frontend route paths must include module segment (BLOCKING)
|
|
418
|
+
APP_TSX=$(find . -path "*/src/App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
419
|
+
if [ -n "$APP_TSX" ]; then
|
|
420
|
+
ROUTE_PATHS=$(grep -oP "path:\s*'[^']+'" "$APP_TSX" | grep -v "^path: ''" | grep -v ":id" | grep -v "create" | grep -v "edit" | sed "s/path: '//;s/'//")
|
|
421
|
+
|
|
422
|
+
NAV_SEED_FILES=$(find src/ -name "*NavigationSeedData.cs" -o -name "*NavigationModuleSeedData.cs" 2>/dev/null)
|
|
423
|
+
if [ -n "$NAV_SEED_FILES" ]; then
|
|
424
|
+
MODULE_CODES=$(grep -oP 'Code\s*=\s*"\K[^"]+' $NAV_SEED_FILES 2>/dev/null | sort -u || true)
|
|
425
|
+
|
|
426
|
+
for route in $ROUTE_PATHS; do
|
|
427
|
+
if [ -z "$route" ]; then continue; fi
|
|
428
|
+
|
|
429
|
+
if ! echo "$route" | grep -q "/"; then
|
|
430
|
+
IS_MODULE=false
|
|
431
|
+
for mod in $MODULE_CODES; do
|
|
432
|
+
if [ "$route" = "$mod" ]; then IS_MODULE=true; break; fi
|
|
433
|
+
done
|
|
434
|
+
if [ "$IS_MODULE" = false ]; then
|
|
435
|
+
echo "BLOCKING: Frontend route '$route' is missing module segment"
|
|
436
|
+
echo " Expected: '{module}/{route}' (e.g., 'employee-management/$route')"
|
|
437
|
+
echo " Backend navigation seed data defines routes with full hierarchy: /app/module/section"
|
|
438
|
+
echo " Frontend routes must match: module/section (relative to app root)"
|
|
439
|
+
FAIL=true
|
|
440
|
+
fi
|
|
441
|
+
fi
|
|
442
|
+
done
|
|
443
|
+
fi
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
if [ "$FAIL" = true ]; then
|
|
447
|
+
exit 1
|
|
448
|
+
fi
|