@atlashub/smartstack-mcp 1.13.0 → 1.14.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 +209 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -558,7 +558,7 @@ var ConfigSchema = z.object({
|
|
|
558
558
|
});
|
|
559
559
|
var ValidateConventionsInputSchema = z.object({
|
|
560
560
|
path: z.string().optional().describe("Project path to validate (default: SmartStack.app path)"),
|
|
561
|
-
checks: z.array(z.enum(["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "all"])).default(["all"]).describe("Types of checks to perform")
|
|
561
|
+
checks: z.array(z.enum(["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "all"])).default(["all"]).describe("Types of checks to perform")
|
|
562
562
|
});
|
|
563
563
|
var CheckMigrationsInputSchema = z.object({
|
|
564
564
|
projectPath: z.string().optional().describe("EF Core project path"),
|
|
@@ -914,7 +914,7 @@ var validateConventionsTool = {
|
|
|
914
914
|
type: "array",
|
|
915
915
|
items: {
|
|
916
916
|
type: "string",
|
|
917
|
-
enum: ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "all"]
|
|
917
|
+
enum: ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "all"]
|
|
918
918
|
},
|
|
919
919
|
description: "Types of checks to perform",
|
|
920
920
|
default: ["all"]
|
|
@@ -925,7 +925,7 @@ var validateConventionsTool = {
|
|
|
925
925
|
async function handleValidateConventions(args, config) {
|
|
926
926
|
const input = ValidateConventionsInputSchema.parse(args);
|
|
927
927
|
const projectPath = input.path || config.smartstack.projectPath;
|
|
928
|
-
const checks = input.checks.includes("all") ? ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers"] : input.checks;
|
|
928
|
+
const checks = input.checks.includes("all") ? ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs"] : input.checks;
|
|
929
929
|
logger.info("Validating conventions", { projectPath, checks });
|
|
930
930
|
const result = {
|
|
931
931
|
valid: true,
|
|
@@ -955,6 +955,12 @@ async function handleValidateConventions(args, config) {
|
|
|
955
955
|
if (checks.includes("controllers")) {
|
|
956
956
|
await validateControllerRoutes(structure, config, result);
|
|
957
957
|
}
|
|
958
|
+
if (checks.includes("layouts")) {
|
|
959
|
+
await validateLayouts(structure, config, result);
|
|
960
|
+
}
|
|
961
|
+
if (checks.includes("tabs")) {
|
|
962
|
+
await validateTabs(structure, config, result);
|
|
963
|
+
}
|
|
958
964
|
result.valid = result.errors.length === 0;
|
|
959
965
|
result.summary = generateSummary(result, checks);
|
|
960
966
|
return formatResult(result);
|
|
@@ -1385,6 +1391,132 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
1385
1391
|
});
|
|
1386
1392
|
}
|
|
1387
1393
|
}
|
|
1394
|
+
async function validateLayouts(structure, _config, result) {
|
|
1395
|
+
if (!structure.web) {
|
|
1396
|
+
result.warnings.push({
|
|
1397
|
+
type: "warning",
|
|
1398
|
+
category: "layouts",
|
|
1399
|
+
message: "Web project not found, skipping layout validation"
|
|
1400
|
+
});
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
const layoutFiles = await findFiles("**/layouts/**/*.tsx", { cwd: structure.web });
|
|
1404
|
+
if (layoutFiles.length === 0) {
|
|
1405
|
+
result.warnings.push({
|
|
1406
|
+
type: "warning",
|
|
1407
|
+
category: "layouts",
|
|
1408
|
+
message: "No layout files found in web/src/layouts/"
|
|
1409
|
+
});
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
for (const file of layoutFiles) {
|
|
1413
|
+
const content = await readText(file);
|
|
1414
|
+
const fileName = path6.basename(file);
|
|
1415
|
+
const lines = content.split("\n");
|
|
1416
|
+
let hasMaxWidth = false;
|
|
1417
|
+
for (const line of lines) {
|
|
1418
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;
|
|
1419
|
+
if (/className.*max-w-/.test(line)) {
|
|
1420
|
+
hasMaxWidth = true;
|
|
1421
|
+
break;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
if (hasMaxWidth) {
|
|
1425
|
+
result.errors.push({
|
|
1426
|
+
type: "error",
|
|
1427
|
+
category: "layouts",
|
|
1428
|
+
message: `Layout "${fileName}" uses max-w-* constraint which limits content width`,
|
|
1429
|
+
file: path6.relative(structure.root, file),
|
|
1430
|
+
suggestion: "Remove max-w-* class. Content should occupy full available width."
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
let hasStandardPadding = false;
|
|
1434
|
+
for (const line of lines) {
|
|
1435
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;
|
|
1436
|
+
if (/className.*lg:px-10/.test(line)) {
|
|
1437
|
+
hasStandardPadding = true;
|
|
1438
|
+
break;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
if (!hasStandardPadding) {
|
|
1442
|
+
result.warnings.push({
|
|
1443
|
+
type: "warning",
|
|
1444
|
+
category: "layouts",
|
|
1445
|
+
message: `Layout "${fileName}" may be missing standard horizontal padding`,
|
|
1446
|
+
file: path6.relative(structure.root, file),
|
|
1447
|
+
suggestion: "Use lg:px-10 for consistent horizontal padding across layouts"
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
let hasScrollPattern = false;
|
|
1451
|
+
for (const line of lines) {
|
|
1452
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;
|
|
1453
|
+
if (/className.*h-full/.test(line) && /className.*overflow-auto/.test(line)) {
|
|
1454
|
+
hasScrollPattern = true;
|
|
1455
|
+
break;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
if (!hasScrollPattern) {
|
|
1459
|
+
result.warnings.push({
|
|
1460
|
+
type: "warning",
|
|
1461
|
+
category: "layouts",
|
|
1462
|
+
message: `Layout "${fileName}" may be missing scroll container pattern`,
|
|
1463
|
+
file: path6.relative(structure.root, file),
|
|
1464
|
+
suggestion: "Use h-full overflow-auto for proper internal scrolling"
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
async function validateTabs(structure, _config, result) {
|
|
1470
|
+
if (!structure.web) {
|
|
1471
|
+
result.warnings.push({
|
|
1472
|
+
type: "warning",
|
|
1473
|
+
category: "tabs",
|
|
1474
|
+
message: "Web project not found, skipping tab validation"
|
|
1475
|
+
});
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
const pageFiles = await findFiles("**/pages/**/*.tsx", { cwd: structure.web });
|
|
1479
|
+
let tabPagesCount = 0;
|
|
1480
|
+
let hookUsageCount = 0;
|
|
1481
|
+
for (const file of pageFiles) {
|
|
1482
|
+
const content = await readText(file);
|
|
1483
|
+
const fileName = path6.basename(file);
|
|
1484
|
+
const lines = content.split("\n");
|
|
1485
|
+
let hasTabPattern = false;
|
|
1486
|
+
for (const line of lines) {
|
|
1487
|
+
const trimmed = line.trim();
|
|
1488
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*") || trimmed === "") {
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
if (/<Tabs|<TabList|activeTab\s*[,=})]|setActiveTab\s*[({]/.test(line)) {
|
|
1492
|
+
hasTabPattern = true;
|
|
1493
|
+
break;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
if (hasTabPattern) {
|
|
1497
|
+
tabPagesCount++;
|
|
1498
|
+
const usesHook = content.includes("useTabNavigation");
|
|
1499
|
+
if (usesHook) {
|
|
1500
|
+
hookUsageCount++;
|
|
1501
|
+
} else {
|
|
1502
|
+
result.errors.push({
|
|
1503
|
+
type: "error",
|
|
1504
|
+
category: "tabs",
|
|
1505
|
+
message: `Page "${fileName}" has tabs but doesn't use useTabNavigation hook`,
|
|
1506
|
+
file: path6.relative(structure.root, file),
|
|
1507
|
+
suggestion: "Use useTabNavigation hook to sync tab state with URL: const { activeTab, setActiveTab } = useTabNavigation(defaultTab, VALID_TABS)"
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (tabPagesCount > 0) {
|
|
1513
|
+
result.warnings.push({
|
|
1514
|
+
type: "warning",
|
|
1515
|
+
category: "tabs",
|
|
1516
|
+
message: `Tab summary: ${hookUsageCount}/${tabPagesCount} pages with tabs use useTabNavigation hook`
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1388
1520
|
function generateSummary(result, checks) {
|
|
1389
1521
|
const parts = [];
|
|
1390
1522
|
parts.push(`Checks performed: ${checks.join(", ")}`);
|
|
@@ -10468,6 +10600,76 @@ All tenant-related tables use the \`tenant_\` prefix:
|
|
|
10468
10600
|
|
|
10469
10601
|
---
|
|
10470
10602
|
|
|
10603
|
+
## 10. Frontend Conventions
|
|
10604
|
+
|
|
10605
|
+
### Layout Standards
|
|
10606
|
+
|
|
10607
|
+
All context layouts MUST follow the AdminLayout pattern for consistent user experience.
|
|
10608
|
+
|
|
10609
|
+
#### Standard Structure
|
|
10610
|
+
|
|
10611
|
+
\`\`\`tsx
|
|
10612
|
+
<main className={\`pt-16 transition-all duration-300 \${
|
|
10613
|
+
showSidebar ? (isCollapsed ? 'lg:pl-16' : 'lg:pl-64') : ''
|
|
10614
|
+
}\`}>
|
|
10615
|
+
<div className="p-4 sm:p-6 lg:px-10 h-full overflow-auto">
|
|
10616
|
+
<Outlet />
|
|
10617
|
+
</div>
|
|
10618
|
+
</main>
|
|
10619
|
+
\`\`\`
|
|
10620
|
+
|
|
10621
|
+
#### Layout Rules
|
|
10622
|
+
|
|
10623
|
+
| Rule | Description |
|
|
10624
|
+
|------|-------------|
|
|
10625
|
+
| No \`max-w-*\` | Content MUST occupy full available width |
|
|
10626
|
+
| Horizontal padding | Use \`lg:px-10\` for consistent padding |
|
|
10627
|
+
| Scroll container | Use \`h-full overflow-auto\` for internal scrolling |
|
|
10628
|
+
| Sidebar conditional | \`showSidebar = !!currentAppCode\` |
|
|
10629
|
+
|
|
10630
|
+
### Tab Navigation with URL
|
|
10631
|
+
|
|
10632
|
+
Pages with tabs MUST synchronize tab state with URL for shareable links and browser navigation.
|
|
10633
|
+
|
|
10634
|
+
#### useTabNavigation Hook
|
|
10635
|
+
|
|
10636
|
+
\`\`\`tsx
|
|
10637
|
+
import { useTabNavigation } from '@/hooks/useTabNavigation';
|
|
10638
|
+
|
|
10639
|
+
// 1. Define valid tabs
|
|
10640
|
+
const VALID_TABS = ['info', 'settings', 'permissions'] as const;
|
|
10641
|
+
type TabId = typeof VALID_TABS[number];
|
|
10642
|
+
|
|
10643
|
+
// 2. Use the hook
|
|
10644
|
+
const { activeTab, setActiveTab } = useTabNavigation<TabId>('info', VALID_TABS);
|
|
10645
|
+
|
|
10646
|
+
// 3. Use in JSX
|
|
10647
|
+
<button onClick={() => setActiveTab('settings')}>Settings</button>
|
|
10648
|
+
\`\`\`
|
|
10649
|
+
|
|
10650
|
+
#### URL Behavior
|
|
10651
|
+
|
|
10652
|
+
| URL | Active Tab |
|
|
10653
|
+
|-----|------------|
|
|
10654
|
+
| \`/page\` | Default tab (e.g., \`info\`) |
|
|
10655
|
+
| \`/page?tab=settings\` | \`settings\` |
|
|
10656
|
+
| \`/page?tab=invalid\` | Default tab (fallback) |
|
|
10657
|
+
|
|
10658
|
+
#### Hook Characteristics
|
|
10659
|
+
|
|
10660
|
+
- URL is clean for default tab (no \`?tab=\` parameter)
|
|
10661
|
+
- Invalid tabs fallback to default
|
|
10662
|
+
- Uses \`replace: true\` to avoid polluting browser history
|
|
10663
|
+
|
|
10664
|
+
### Frontend Validation Checks
|
|
10665
|
+
|
|
10666
|
+
| Check | Description |
|
|
10667
|
+
|-------|-------------|
|
|
10668
|
+
| \`layouts\` | Verify no \`max-w-*\` in content wrapper, standard padding present |
|
|
10669
|
+
| \`tabs\` | Verify \`useTabNavigation\` hook usage for pages with tabs |
|
|
10670
|
+
|
|
10671
|
+
---
|
|
10672
|
+
|
|
10471
10673
|
## Quick Reference
|
|
10472
10674
|
|
|
10473
10675
|
| Category | Convention | Example |
|
|
@@ -10500,6 +10702,10 @@ All tenant-related tables use the \`tenant_\` prefix:
|
|
|
10500
10702
|
| Validation | \`withValidation: true\` | FluentValidation validators |
|
|
10501
10703
|
| Repository | \`withRepository: true\` | Interface + Implementation |
|
|
10502
10704
|
| Migration naming | \`suggest_migration\` | {context}_v{version}_{seq}_{Desc} |
|
|
10705
|
+
| Layout wrapper | No \`max-w-*\` | Content fills available width |
|
|
10706
|
+
| Layout padding | \`lg:px-10\` | Standard horizontal padding |
|
|
10707
|
+
| Tab navigation | \`useTabNavigation\` hook | Sync tabs with URL |
|
|
10708
|
+
| Tab URL | \`?tab=name\` | Shareable deep links to tabs |
|
|
10503
10709
|
|
|
10504
10710
|
---
|
|
10505
10711
|
|