@polymorphism-tech/morph-spec 2.4.0 → 3.0.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/CLAUDE.md +158 -26
- package/LICENSE +72 -72
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +8 -0
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-deploy.md +529 -0
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -227
- package/content/.claude/commands/morph-troubleshoot.md +122 -122
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/infra/azure-deploy-specialist.md +699 -0
- package/content/.claude/skills/level-0-meta/README.md +7 -0
- package/content/.claude/skills/{checklists → level-0-meta}/morph-checklist.md +117 -117
- package/content/.claude/skills/level-1-workflows/README.md +7 -0
- package/content/.claude/skills/{workflows → level-1-workflows}/morph-replicate.md +213 -213
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-clarify.md +131 -131
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-design.md +213 -205
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-setup.md +106 -92
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-tasks.md +164 -164
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-uiux.md +169 -138
- package/content/.claude/skills/level-2-domains/README.md +14 -0
- package/content/.claude/skills/{specialists → level-2-domains/quality}/testing-specialist.md +126 -126
- package/content/.claude/skills/level-3-technologies/README.md +7 -0
- package/content/.claude/skills/level-4-patterns/README.md +7 -0
- package/content/.claude/skills/specialists/prompt-engineer.md +189 -0
- package/content/.claude/skills/specialists/seo-growth-hacker.md +320 -0
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +742 -358
- package/content/.morph/config/config.template.json +33 -0
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/enforcement-pipeline.md +668 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -158
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -95
- package/content/.morph/examples/scheduled-reports/spec.md +267 -267
- package/content/.morph/examples/state-v3.json +188 -188
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +158 -0
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/hooks/task-completed.js +73 -0
- package/content/.morph/hooks/teammate-idle.js +68 -0
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -220
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-teams-workflow.md +474 -0
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/CONTEXT-FEATURE.md +276 -0
- package/content/.morph/templates/CONTEXT.md +170 -0
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/clarify-questions.md +159 -159
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -74
- package/content/.morph/templates/contracts/Entities.cs +25 -25
- package/content/.morph/templates/contracts/Queries.cs +74 -74
- package/content/.morph/templates/contracts/README.md +74 -74
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/azure-pipelines-deploy.yml +480 -0
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -426
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/templates.md +418 -418
- package/package.json +1 -1
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +83 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -193
- package/src/commands/create-story.js +351 -351
- package/src/commands/deploy.js +780 -0
- package/src/commands/detect-agents.js +34 -6
- package/src/commands/detect.js +104 -104
- package/src/commands/generate-context.js +40 -0
- package/src/commands/generate.js +149 -149
- package/src/commands/lint-fluent.js +352 -352
- package/src/commands/rollback-phase.js +185 -185
- package/src/commands/session-summary.js +291 -291
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/troubleshoot.js +222 -222
- package/src/commands/validate-blazor-state.js +210 -210
- package/src/commands/validate-blazor.js +156 -156
- package/src/commands/validate-css.js +84 -84
- package/src/commands/validate-phase.js +221 -221
- package/src/lib/blazor-concurrency-analyzer.js +288 -288
- package/src/lib/blazor-state-validator.js +291 -291
- package/src/lib/blazor-validator.js +374 -374
- package/src/lib/context-generator.js +513 -0
- package/src/lib/css-validator.js +352 -352
- package/src/lib/design-system-detector.js +187 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/design-system-scaffolder.js +299 -0
- package/src/lib/hook-executor.js +256 -0
- package/src/lib/learning-system.js +520 -520
- package/src/lib/mockup-generator.js +366 -366
- package/src/lib/spec-validator.js +258 -0
- package/src/lib/standards-context-injector.js +287 -0
- package/src/lib/team-orchestrator.js +322 -0
- package/src/lib/troubleshoot-grep.js +194 -194
- package/src/lib/troubleshoot-index.js +144 -144
- package/src/lib/ui-detector.js +350 -350
- package/src/lib/validation-runner.js +65 -13
- package/src/lib/validators/architecture-validator.js +387 -387
- package/src/lib/validators/design-system-validator.js +231 -0
- package/src/lib/validators/package-validator.js +360 -360
- package/src/lib/validators/ui-contrast-validator.js +422 -422
- package/src/utils/file-copier.js +9 -1
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- /package/content/.claude/skills/{checklists → level-0-meta}/code-review.md +0 -0
- /package/content/.claude/skills/{checklists → level-0-meta}/simulation-checklist.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/ai-agents}/ai-system-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/po-pm-advisor.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/standards-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/dotnet-senior.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ef-modeler.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/hangfire-orchestrator.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ms-agent-expert.md +0 -0
- /package/content/.claude/skills/{stacks/dotnet-blazor.md → level-2-domains/frontend/blazor-builder.md} +0 -0
- /package/content/.claude/skills/{stacks/dotnet-nextjs.md → level-2-domains/frontend/nextjs-expert.md} +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/frontend}/ui-ux-designer.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/infrastructure}/azure-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/bicep-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/container-specialist.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/devops-engineer.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/asaas-financial.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/azure-identity.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/clerk-auth.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/resend-email.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/quality}/code-analyzer.md +0 -0
package/src/lib/css-validator.js
CHANGED
|
@@ -1,352 +1,352 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC CSS Validator Library
|
|
3
|
-
*
|
|
4
|
-
* Validates that CSS classes used in Razor files exist in CSS files.
|
|
5
|
-
* Helps catch missing CSS definitions before runtime.
|
|
6
|
-
*
|
|
7
|
-
* @module css-validator
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { readFileSync, existsSync } from 'fs';
|
|
11
|
-
import { glob } from 'glob';
|
|
12
|
-
import { join, relative } from 'path';
|
|
13
|
-
|
|
14
|
-
// ============================================================================
|
|
15
|
-
// CSS CLASS EXTRACTION
|
|
16
|
-
// ============================================================================
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Extract CSS class names from a CSS file content
|
|
20
|
-
* @param {string} content - CSS file content
|
|
21
|
-
* @returns {Set<string>} Set of class names defined in the CSS
|
|
22
|
-
*/
|
|
23
|
-
export function extractCssClasses(content) {
|
|
24
|
-
const classes = new Set();
|
|
25
|
-
|
|
26
|
-
// Match class selectors: .class-name
|
|
27
|
-
// Handles: .class, .class:hover, .class::before, .class.other-class
|
|
28
|
-
const classRegex = /\.([a-zA-Z_-][a-zA-Z0-9_-]*)/g;
|
|
29
|
-
|
|
30
|
-
let match;
|
|
31
|
-
while ((match = classRegex.exec(content)) !== null) {
|
|
32
|
-
classes.add(match[1]);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return classes;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Extract CSS classes used in Razor file content
|
|
40
|
-
* @param {string} content - Razor file content
|
|
41
|
-
* @returns {Array<{className: string, line: number}>} Array of class usages with line numbers
|
|
42
|
-
*/
|
|
43
|
-
export function extractRazorClasses(content) {
|
|
44
|
-
const usages = [];
|
|
45
|
-
const lines = content.split('\n');
|
|
46
|
-
|
|
47
|
-
lines.forEach((line, index) => {
|
|
48
|
-
// Match class="..." or class='...'
|
|
49
|
-
const classAttrRegex = /class\s*=\s*["']([^"']+)["']/gi;
|
|
50
|
-
|
|
51
|
-
// Match @class="..." (Blazor conditional)
|
|
52
|
-
const blazorClassRegex = /@class\s*=\s*["']([^"']+)["']/gi;
|
|
53
|
-
|
|
54
|
-
// Match Class="..." (component parameter)
|
|
55
|
-
const componentClassRegex = /\bClass\s*=\s*["']([^"']+)["']/gi;
|
|
56
|
-
|
|
57
|
-
// Match Style with class-like patterns in CSS modules
|
|
58
|
-
const cssModuleRegex = /styles\.([a-zA-Z_-][a-zA-Z0-9_-]*)/gi;
|
|
59
|
-
|
|
60
|
-
for (const regex of [classAttrRegex, blazorClassRegex, componentClassRegex]) {
|
|
61
|
-
let match;
|
|
62
|
-
while ((match = regex.exec(line)) !== null) {
|
|
63
|
-
const classString = match[1];
|
|
64
|
-
|
|
65
|
-
// Split by whitespace to get individual classes
|
|
66
|
-
const classes = classString.split(/\s+/).filter(c => c.length > 0);
|
|
67
|
-
|
|
68
|
-
for (const className of classes) {
|
|
69
|
-
// Skip Blazor expressions like @(condition ? "class1" : "class2")
|
|
70
|
-
if (className.startsWith('@') || className.includes('(')) continue;
|
|
71
|
-
|
|
72
|
-
// Skip interpolated strings
|
|
73
|
-
if (className.includes('{') || className.includes('}')) continue;
|
|
74
|
-
|
|
75
|
-
// Skip conditional ternary results (partial matches)
|
|
76
|
-
if (className === '?' || className === ':') continue;
|
|
77
|
-
|
|
78
|
-
usages.push({
|
|
79
|
-
className: className.trim(),
|
|
80
|
-
line: index + 1
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// CSS modules
|
|
87
|
-
let cssModuleMatch;
|
|
88
|
-
while ((cssModuleMatch = cssModuleRegex.exec(line)) !== null) {
|
|
89
|
-
usages.push({
|
|
90
|
-
className: cssModuleMatch[1],
|
|
91
|
-
line: index + 1
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
return usages;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ============================================================================
|
|
100
|
-
// VALIDATION
|
|
101
|
-
// ============================================================================
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Known utility class prefixes from common frameworks
|
|
105
|
-
* These are excluded from validation as they come from external libraries
|
|
106
|
-
*/
|
|
107
|
-
const UTILITY_PREFIXES = [
|
|
108
|
-
// Tailwind CSS
|
|
109
|
-
'bg-', 'text-', 'font-', 'p-', 'px-', 'py-', 'pt-', 'pb-', 'pl-', 'pr-',
|
|
110
|
-
'm-', 'mx-', 'my-', 'mt-', 'mb-', 'ml-', 'mr-', 'w-', 'h-', 'min-', 'max-',
|
|
111
|
-
'flex', 'grid', 'block', 'inline', 'hidden', 'overflow-', 'rounded-',
|
|
112
|
-
'border-', 'shadow-', 'opacity-', 'z-', 'gap-', 'space-', 'justify-',
|
|
113
|
-
'items-', 'content-', 'self-', 'col-', 'row-', 'order-', 'animate-',
|
|
114
|
-
'transition-', 'duration-', 'ease-', 'delay-', 'scale-', 'rotate-',
|
|
115
|
-
'translate-', 'skew-', 'origin-', 'cursor-', 'select-', 'resize-',
|
|
116
|
-
'list-', 'appearance-', 'pointer-', 'fill-', 'stroke-', 'object-',
|
|
117
|
-
'aspect-', 'columns-', 'break-', 'box-', 'float-', 'clear-', 'isolation-',
|
|
118
|
-
'mix-blend-', 'bg-blend-', 'filter-', 'backdrop-', 'blur-', 'brightness-',
|
|
119
|
-
'contrast-', 'drop-shadow-', 'grayscale-', 'hue-rotate-', 'invert-',
|
|
120
|
-
'saturate-', 'sepia-', 'table-', 'caption-', 'border-collapse-', 'outline-',
|
|
121
|
-
'ring-', 'placeholder-', 'caret-', 'accent-', 'scroll-', 'snap-', 'touch-',
|
|
122
|
-
'will-change-', 'contain-',
|
|
123
|
-
|
|
124
|
-
// Bootstrap
|
|
125
|
-
'container', 'row', 'col-', 'btn-', 'alert-', 'badge-', 'card-', 'nav-',
|
|
126
|
-
'navbar-', 'dropdown-', 'modal-', 'form-', 'input-', 'table-', 'list-',
|
|
127
|
-
'd-', 'align-', 'justify-', 'order-', 'offset-', 'g-', 'gy-', 'gx-',
|
|
128
|
-
|
|
129
|
-
// Fluent UI Blazor (internal classes)
|
|
130
|
-
'fluent-', 'fui-',
|
|
131
|
-
|
|
132
|
-
// MudBlazor (internal classes)
|
|
133
|
-
'mud-', 'MudBlazor',
|
|
134
|
-
|
|
135
|
-
// Common framework classes
|
|
136
|
-
'sr-only', 'not-sr-only', 'disabled', 'active', 'show', 'fade', 'collapse'
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Check if a class name is a utility class from a known framework
|
|
141
|
-
* @param {string} className - Class name to check
|
|
142
|
-
* @returns {boolean} True if it's a utility class
|
|
143
|
-
*/
|
|
144
|
-
function isUtilityClass(className) {
|
|
145
|
-
return UTILITY_PREFIXES.some(prefix => className.startsWith(prefix));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Validate CSS classes in a project directory
|
|
150
|
-
* @param {string} projectPath - Path to the project directory
|
|
151
|
-
* @param {Object} options - Validation options
|
|
152
|
-
* @param {boolean} options.verbose - Show detailed output
|
|
153
|
-
* @param {boolean} options.includeUtilities - Include utility classes in validation
|
|
154
|
-
* @param {string[]} options.cssGlob - Glob patterns for CSS files
|
|
155
|
-
* @param {string[]} options.razorGlob - Glob patterns for Razor files
|
|
156
|
-
* @returns {Promise<Object>} Validation results
|
|
157
|
-
*/
|
|
158
|
-
export async function validateCssClasses(projectPath, options = {}) {
|
|
159
|
-
const {
|
|
160
|
-
verbose = false,
|
|
161
|
-
includeUtilities = false,
|
|
162
|
-
cssGlob = ['**/*.css'],
|
|
163
|
-
razorGlob = ['**/*.razor']
|
|
164
|
-
} = options;
|
|
165
|
-
|
|
166
|
-
// Find all CSS files
|
|
167
|
-
const cssFiles = [];
|
|
168
|
-
for (const pattern of cssGlob) {
|
|
169
|
-
const files = await glob(pattern, {
|
|
170
|
-
cwd: projectPath,
|
|
171
|
-
ignore: ['**/node_modules/**', '**/bin/**', '**/obj/**', '**/wwwroot/lib/**']
|
|
172
|
-
});
|
|
173
|
-
cssFiles.push(...files);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Find all Razor files
|
|
177
|
-
const razorFiles = [];
|
|
178
|
-
for (const pattern of razorGlob) {
|
|
179
|
-
const files = await glob(pattern, {
|
|
180
|
-
cwd: projectPath,
|
|
181
|
-
ignore: ['**/node_modules/**', '**/bin/**', '**/obj/**']
|
|
182
|
-
});
|
|
183
|
-
razorFiles.push(...files);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Extract all defined CSS classes
|
|
187
|
-
const definedClasses = new Set();
|
|
188
|
-
const cssFileDetails = [];
|
|
189
|
-
|
|
190
|
-
for (const file of cssFiles) {
|
|
191
|
-
const fullPath = join(projectPath, file);
|
|
192
|
-
if (!existsSync(fullPath)) continue;
|
|
193
|
-
|
|
194
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
195
|
-
const classes = extractCssClasses(content);
|
|
196
|
-
|
|
197
|
-
cssFileDetails.push({
|
|
198
|
-
file,
|
|
199
|
-
classCount: classes.size
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
for (const cls of classes) {
|
|
203
|
-
definedClasses.add(cls);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Extract all used CSS classes from Razor files
|
|
208
|
-
const usedClasses = new Map(); // className -> [{file, line}]
|
|
209
|
-
const razorFileDetails = [];
|
|
210
|
-
|
|
211
|
-
for (const file of razorFiles) {
|
|
212
|
-
const fullPath = join(projectPath, file);
|
|
213
|
-
if (!existsSync(fullPath)) continue;
|
|
214
|
-
|
|
215
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
216
|
-
const usages = extractRazorClasses(content);
|
|
217
|
-
|
|
218
|
-
razorFileDetails.push({
|
|
219
|
-
file,
|
|
220
|
-
usageCount: usages.length
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
for (const usage of usages) {
|
|
224
|
-
if (!usedClasses.has(usage.className)) {
|
|
225
|
-
usedClasses.set(usage.className, []);
|
|
226
|
-
}
|
|
227
|
-
usedClasses.get(usage.className).push({
|
|
228
|
-
file,
|
|
229
|
-
line: usage.line
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Find missing classes
|
|
235
|
-
const missingClasses = [];
|
|
236
|
-
|
|
237
|
-
for (const [className, locations] of usedClasses) {
|
|
238
|
-
// Skip utility classes unless explicitly included
|
|
239
|
-
if (!includeUtilities && isUtilityClass(className)) continue;
|
|
240
|
-
|
|
241
|
-
// Skip if class is defined
|
|
242
|
-
if (definedClasses.has(className)) continue;
|
|
243
|
-
|
|
244
|
-
missingClasses.push({
|
|
245
|
-
className,
|
|
246
|
-
locations
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Sort missing classes by number of usages (most used first)
|
|
251
|
-
missingClasses.sort((a, b) => b.locations.length - a.locations.length);
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
summary: {
|
|
255
|
-
cssFilesScanned: cssFiles.length,
|
|
256
|
-
razorFilesScanned: razorFiles.length,
|
|
257
|
-
definedClasses: definedClasses.size,
|
|
258
|
-
usedClasses: usedClasses.size,
|
|
259
|
-
missingClasses: missingClasses.length
|
|
260
|
-
},
|
|
261
|
-
cssFiles: cssFileDetails,
|
|
262
|
-
razorFiles: razorFileDetails,
|
|
263
|
-
definedClasses: Array.from(definedClasses).sort(),
|
|
264
|
-
missingClasses,
|
|
265
|
-
valid: missingClasses.length === 0
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Format validation results for CLI output
|
|
271
|
-
* @param {Object} results - Validation results from validateCssClasses
|
|
272
|
-
* @param {boolean} verbose - Show detailed output
|
|
273
|
-
* @returns {string} Formatted output
|
|
274
|
-
*/
|
|
275
|
-
export function formatValidationResults(results, verbose = false) {
|
|
276
|
-
const lines = [];
|
|
277
|
-
|
|
278
|
-
// Header
|
|
279
|
-
lines.push('');
|
|
280
|
-
lines.push('CSS Validation Results');
|
|
281
|
-
lines.push('='.repeat(50));
|
|
282
|
-
lines.push('');
|
|
283
|
-
|
|
284
|
-
// Summary
|
|
285
|
-
lines.push(`CSS files scanned: ${results.summary.cssFilesScanned}`);
|
|
286
|
-
lines.push(`Razor files scanned: ${results.summary.razorFilesScanned}`);
|
|
287
|
-
lines.push(`Classes defined: ${results.summary.definedClasses}`);
|
|
288
|
-
lines.push(`Classes used: ${results.summary.usedClasses}`);
|
|
289
|
-
lines.push(`Missing classes: ${results.summary.missingClasses}`);
|
|
290
|
-
lines.push('');
|
|
291
|
-
|
|
292
|
-
if (results.valid) {
|
|
293
|
-
lines.push('All CSS classes are defined.');
|
|
294
|
-
return lines.join('\n');
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Missing classes
|
|
298
|
-
lines.push('Missing CSS Classes:');
|
|
299
|
-
lines.push('-'.repeat(50));
|
|
300
|
-
|
|
301
|
-
for (const missing of results.missingClasses) {
|
|
302
|
-
lines.push(`\n .${missing.className}`);
|
|
303
|
-
|
|
304
|
-
if (verbose) {
|
|
305
|
-
for (const loc of missing.locations) {
|
|
306
|
-
lines.push(` - ${loc.file}:${loc.line}`);
|
|
307
|
-
}
|
|
308
|
-
} else {
|
|
309
|
-
// Show first 3 locations
|
|
310
|
-
const shown = missing.locations.slice(0, 3);
|
|
311
|
-
for (const loc of shown) {
|
|
312
|
-
lines.push(` - ${loc.file}:${loc.line}`);
|
|
313
|
-
}
|
|
314
|
-
if (missing.locations.length > 3) {
|
|
315
|
-
lines.push(` ... and ${missing.locations.length - 3} more`);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
lines.push('');
|
|
321
|
-
|
|
322
|
-
return lines.join('\n');
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Generate a CSS stub file with missing class definitions
|
|
327
|
-
* @param {Object} results - Validation results
|
|
328
|
-
* @returns {string} CSS file content with stub classes
|
|
329
|
-
*/
|
|
330
|
-
export function generateCssStub(results) {
|
|
331
|
-
if (results.valid) {
|
|
332
|
-
return '/* No missing classes */';
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const lines = [
|
|
336
|
-
'/* ==========================================================================',
|
|
337
|
-
' MISSING CSS CLASSES - Generated by MORPH-SPEC validate-css',
|
|
338
|
-
' Add these classes to your design-system.css or appropriate CSS file',
|
|
339
|
-
' ========================================================================== */',
|
|
340
|
-
''
|
|
341
|
-
];
|
|
342
|
-
|
|
343
|
-
for (const missing of results.missingClasses) {
|
|
344
|
-
lines.push(`/* Used in: ${missing.locations.map(l => `${l.file}:${l.line}`).slice(0, 3).join(', ')}${missing.locations.length > 3 ? '...' : ''} */`);
|
|
345
|
-
lines.push(`.${missing.className} {`);
|
|
346
|
-
lines.push(' /* TODO: Add styles */');
|
|
347
|
-
lines.push('}');
|
|
348
|
-
lines.push('');
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return lines.join('\n');
|
|
352
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC CSS Validator Library
|
|
3
|
+
*
|
|
4
|
+
* Validates that CSS classes used in Razor files exist in CSS files.
|
|
5
|
+
* Helps catch missing CSS definitions before runtime.
|
|
6
|
+
*
|
|
7
|
+
* @module css-validator
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync } from 'fs';
|
|
11
|
+
import { glob } from 'glob';
|
|
12
|
+
import { join, relative } from 'path';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// CSS CLASS EXTRACTION
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extract CSS class names from a CSS file content
|
|
20
|
+
* @param {string} content - CSS file content
|
|
21
|
+
* @returns {Set<string>} Set of class names defined in the CSS
|
|
22
|
+
*/
|
|
23
|
+
export function extractCssClasses(content) {
|
|
24
|
+
const classes = new Set();
|
|
25
|
+
|
|
26
|
+
// Match class selectors: .class-name
|
|
27
|
+
// Handles: .class, .class:hover, .class::before, .class.other-class
|
|
28
|
+
const classRegex = /\.([a-zA-Z_-][a-zA-Z0-9_-]*)/g;
|
|
29
|
+
|
|
30
|
+
let match;
|
|
31
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
32
|
+
classes.add(match[1]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return classes;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract CSS classes used in Razor file content
|
|
40
|
+
* @param {string} content - Razor file content
|
|
41
|
+
* @returns {Array<{className: string, line: number}>} Array of class usages with line numbers
|
|
42
|
+
*/
|
|
43
|
+
export function extractRazorClasses(content) {
|
|
44
|
+
const usages = [];
|
|
45
|
+
const lines = content.split('\n');
|
|
46
|
+
|
|
47
|
+
lines.forEach((line, index) => {
|
|
48
|
+
// Match class="..." or class='...'
|
|
49
|
+
const classAttrRegex = /class\s*=\s*["']([^"']+)["']/gi;
|
|
50
|
+
|
|
51
|
+
// Match @class="..." (Blazor conditional)
|
|
52
|
+
const blazorClassRegex = /@class\s*=\s*["']([^"']+)["']/gi;
|
|
53
|
+
|
|
54
|
+
// Match Class="..." (component parameter)
|
|
55
|
+
const componentClassRegex = /\bClass\s*=\s*["']([^"']+)["']/gi;
|
|
56
|
+
|
|
57
|
+
// Match Style with class-like patterns in CSS modules
|
|
58
|
+
const cssModuleRegex = /styles\.([a-zA-Z_-][a-zA-Z0-9_-]*)/gi;
|
|
59
|
+
|
|
60
|
+
for (const regex of [classAttrRegex, blazorClassRegex, componentClassRegex]) {
|
|
61
|
+
let match;
|
|
62
|
+
while ((match = regex.exec(line)) !== null) {
|
|
63
|
+
const classString = match[1];
|
|
64
|
+
|
|
65
|
+
// Split by whitespace to get individual classes
|
|
66
|
+
const classes = classString.split(/\s+/).filter(c => c.length > 0);
|
|
67
|
+
|
|
68
|
+
for (const className of classes) {
|
|
69
|
+
// Skip Blazor expressions like @(condition ? "class1" : "class2")
|
|
70
|
+
if (className.startsWith('@') || className.includes('(')) continue;
|
|
71
|
+
|
|
72
|
+
// Skip interpolated strings
|
|
73
|
+
if (className.includes('{') || className.includes('}')) continue;
|
|
74
|
+
|
|
75
|
+
// Skip conditional ternary results (partial matches)
|
|
76
|
+
if (className === '?' || className === ':') continue;
|
|
77
|
+
|
|
78
|
+
usages.push({
|
|
79
|
+
className: className.trim(),
|
|
80
|
+
line: index + 1
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// CSS modules
|
|
87
|
+
let cssModuleMatch;
|
|
88
|
+
while ((cssModuleMatch = cssModuleRegex.exec(line)) !== null) {
|
|
89
|
+
usages.push({
|
|
90
|
+
className: cssModuleMatch[1],
|
|
91
|
+
line: index + 1
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return usages;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// VALIDATION
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Known utility class prefixes from common frameworks
|
|
105
|
+
* These are excluded from validation as they come from external libraries
|
|
106
|
+
*/
|
|
107
|
+
const UTILITY_PREFIXES = [
|
|
108
|
+
// Tailwind CSS
|
|
109
|
+
'bg-', 'text-', 'font-', 'p-', 'px-', 'py-', 'pt-', 'pb-', 'pl-', 'pr-',
|
|
110
|
+
'm-', 'mx-', 'my-', 'mt-', 'mb-', 'ml-', 'mr-', 'w-', 'h-', 'min-', 'max-',
|
|
111
|
+
'flex', 'grid', 'block', 'inline', 'hidden', 'overflow-', 'rounded-',
|
|
112
|
+
'border-', 'shadow-', 'opacity-', 'z-', 'gap-', 'space-', 'justify-',
|
|
113
|
+
'items-', 'content-', 'self-', 'col-', 'row-', 'order-', 'animate-',
|
|
114
|
+
'transition-', 'duration-', 'ease-', 'delay-', 'scale-', 'rotate-',
|
|
115
|
+
'translate-', 'skew-', 'origin-', 'cursor-', 'select-', 'resize-',
|
|
116
|
+
'list-', 'appearance-', 'pointer-', 'fill-', 'stroke-', 'object-',
|
|
117
|
+
'aspect-', 'columns-', 'break-', 'box-', 'float-', 'clear-', 'isolation-',
|
|
118
|
+
'mix-blend-', 'bg-blend-', 'filter-', 'backdrop-', 'blur-', 'brightness-',
|
|
119
|
+
'contrast-', 'drop-shadow-', 'grayscale-', 'hue-rotate-', 'invert-',
|
|
120
|
+
'saturate-', 'sepia-', 'table-', 'caption-', 'border-collapse-', 'outline-',
|
|
121
|
+
'ring-', 'placeholder-', 'caret-', 'accent-', 'scroll-', 'snap-', 'touch-',
|
|
122
|
+
'will-change-', 'contain-',
|
|
123
|
+
|
|
124
|
+
// Bootstrap
|
|
125
|
+
'container', 'row', 'col-', 'btn-', 'alert-', 'badge-', 'card-', 'nav-',
|
|
126
|
+
'navbar-', 'dropdown-', 'modal-', 'form-', 'input-', 'table-', 'list-',
|
|
127
|
+
'd-', 'align-', 'justify-', 'order-', 'offset-', 'g-', 'gy-', 'gx-',
|
|
128
|
+
|
|
129
|
+
// Fluent UI Blazor (internal classes)
|
|
130
|
+
'fluent-', 'fui-',
|
|
131
|
+
|
|
132
|
+
// MudBlazor (internal classes)
|
|
133
|
+
'mud-', 'MudBlazor',
|
|
134
|
+
|
|
135
|
+
// Common framework classes
|
|
136
|
+
'sr-only', 'not-sr-only', 'disabled', 'active', 'show', 'fade', 'collapse'
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if a class name is a utility class from a known framework
|
|
141
|
+
* @param {string} className - Class name to check
|
|
142
|
+
* @returns {boolean} True if it's a utility class
|
|
143
|
+
*/
|
|
144
|
+
function isUtilityClass(className) {
|
|
145
|
+
return UTILITY_PREFIXES.some(prefix => className.startsWith(prefix));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate CSS classes in a project directory
|
|
150
|
+
* @param {string} projectPath - Path to the project directory
|
|
151
|
+
* @param {Object} options - Validation options
|
|
152
|
+
* @param {boolean} options.verbose - Show detailed output
|
|
153
|
+
* @param {boolean} options.includeUtilities - Include utility classes in validation
|
|
154
|
+
* @param {string[]} options.cssGlob - Glob patterns for CSS files
|
|
155
|
+
* @param {string[]} options.razorGlob - Glob patterns for Razor files
|
|
156
|
+
* @returns {Promise<Object>} Validation results
|
|
157
|
+
*/
|
|
158
|
+
export async function validateCssClasses(projectPath, options = {}) {
|
|
159
|
+
const {
|
|
160
|
+
verbose = false,
|
|
161
|
+
includeUtilities = false,
|
|
162
|
+
cssGlob = ['**/*.css'],
|
|
163
|
+
razorGlob = ['**/*.razor']
|
|
164
|
+
} = options;
|
|
165
|
+
|
|
166
|
+
// Find all CSS files
|
|
167
|
+
const cssFiles = [];
|
|
168
|
+
for (const pattern of cssGlob) {
|
|
169
|
+
const files = await glob(pattern, {
|
|
170
|
+
cwd: projectPath,
|
|
171
|
+
ignore: ['**/node_modules/**', '**/bin/**', '**/obj/**', '**/wwwroot/lib/**']
|
|
172
|
+
});
|
|
173
|
+
cssFiles.push(...files);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Find all Razor files
|
|
177
|
+
const razorFiles = [];
|
|
178
|
+
for (const pattern of razorGlob) {
|
|
179
|
+
const files = await glob(pattern, {
|
|
180
|
+
cwd: projectPath,
|
|
181
|
+
ignore: ['**/node_modules/**', '**/bin/**', '**/obj/**']
|
|
182
|
+
});
|
|
183
|
+
razorFiles.push(...files);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Extract all defined CSS classes
|
|
187
|
+
const definedClasses = new Set();
|
|
188
|
+
const cssFileDetails = [];
|
|
189
|
+
|
|
190
|
+
for (const file of cssFiles) {
|
|
191
|
+
const fullPath = join(projectPath, file);
|
|
192
|
+
if (!existsSync(fullPath)) continue;
|
|
193
|
+
|
|
194
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
195
|
+
const classes = extractCssClasses(content);
|
|
196
|
+
|
|
197
|
+
cssFileDetails.push({
|
|
198
|
+
file,
|
|
199
|
+
classCount: classes.size
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
for (const cls of classes) {
|
|
203
|
+
definedClasses.add(cls);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Extract all used CSS classes from Razor files
|
|
208
|
+
const usedClasses = new Map(); // className -> [{file, line}]
|
|
209
|
+
const razorFileDetails = [];
|
|
210
|
+
|
|
211
|
+
for (const file of razorFiles) {
|
|
212
|
+
const fullPath = join(projectPath, file);
|
|
213
|
+
if (!existsSync(fullPath)) continue;
|
|
214
|
+
|
|
215
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
216
|
+
const usages = extractRazorClasses(content);
|
|
217
|
+
|
|
218
|
+
razorFileDetails.push({
|
|
219
|
+
file,
|
|
220
|
+
usageCount: usages.length
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
for (const usage of usages) {
|
|
224
|
+
if (!usedClasses.has(usage.className)) {
|
|
225
|
+
usedClasses.set(usage.className, []);
|
|
226
|
+
}
|
|
227
|
+
usedClasses.get(usage.className).push({
|
|
228
|
+
file,
|
|
229
|
+
line: usage.line
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Find missing classes
|
|
235
|
+
const missingClasses = [];
|
|
236
|
+
|
|
237
|
+
for (const [className, locations] of usedClasses) {
|
|
238
|
+
// Skip utility classes unless explicitly included
|
|
239
|
+
if (!includeUtilities && isUtilityClass(className)) continue;
|
|
240
|
+
|
|
241
|
+
// Skip if class is defined
|
|
242
|
+
if (definedClasses.has(className)) continue;
|
|
243
|
+
|
|
244
|
+
missingClasses.push({
|
|
245
|
+
className,
|
|
246
|
+
locations
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Sort missing classes by number of usages (most used first)
|
|
251
|
+
missingClasses.sort((a, b) => b.locations.length - a.locations.length);
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
summary: {
|
|
255
|
+
cssFilesScanned: cssFiles.length,
|
|
256
|
+
razorFilesScanned: razorFiles.length,
|
|
257
|
+
definedClasses: definedClasses.size,
|
|
258
|
+
usedClasses: usedClasses.size,
|
|
259
|
+
missingClasses: missingClasses.length
|
|
260
|
+
},
|
|
261
|
+
cssFiles: cssFileDetails,
|
|
262
|
+
razorFiles: razorFileDetails,
|
|
263
|
+
definedClasses: Array.from(definedClasses).sort(),
|
|
264
|
+
missingClasses,
|
|
265
|
+
valid: missingClasses.length === 0
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Format validation results for CLI output
|
|
271
|
+
* @param {Object} results - Validation results from validateCssClasses
|
|
272
|
+
* @param {boolean} verbose - Show detailed output
|
|
273
|
+
* @returns {string} Formatted output
|
|
274
|
+
*/
|
|
275
|
+
export function formatValidationResults(results, verbose = false) {
|
|
276
|
+
const lines = [];
|
|
277
|
+
|
|
278
|
+
// Header
|
|
279
|
+
lines.push('');
|
|
280
|
+
lines.push('CSS Validation Results');
|
|
281
|
+
lines.push('='.repeat(50));
|
|
282
|
+
lines.push('');
|
|
283
|
+
|
|
284
|
+
// Summary
|
|
285
|
+
lines.push(`CSS files scanned: ${results.summary.cssFilesScanned}`);
|
|
286
|
+
lines.push(`Razor files scanned: ${results.summary.razorFilesScanned}`);
|
|
287
|
+
lines.push(`Classes defined: ${results.summary.definedClasses}`);
|
|
288
|
+
lines.push(`Classes used: ${results.summary.usedClasses}`);
|
|
289
|
+
lines.push(`Missing classes: ${results.summary.missingClasses}`);
|
|
290
|
+
lines.push('');
|
|
291
|
+
|
|
292
|
+
if (results.valid) {
|
|
293
|
+
lines.push('All CSS classes are defined.');
|
|
294
|
+
return lines.join('\n');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Missing classes
|
|
298
|
+
lines.push('Missing CSS Classes:');
|
|
299
|
+
lines.push('-'.repeat(50));
|
|
300
|
+
|
|
301
|
+
for (const missing of results.missingClasses) {
|
|
302
|
+
lines.push(`\n .${missing.className}`);
|
|
303
|
+
|
|
304
|
+
if (verbose) {
|
|
305
|
+
for (const loc of missing.locations) {
|
|
306
|
+
lines.push(` - ${loc.file}:${loc.line}`);
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
// Show first 3 locations
|
|
310
|
+
const shown = missing.locations.slice(0, 3);
|
|
311
|
+
for (const loc of shown) {
|
|
312
|
+
lines.push(` - ${loc.file}:${loc.line}`);
|
|
313
|
+
}
|
|
314
|
+
if (missing.locations.length > 3) {
|
|
315
|
+
lines.push(` ... and ${missing.locations.length - 3} more`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
lines.push('');
|
|
321
|
+
|
|
322
|
+
return lines.join('\n');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Generate a CSS stub file with missing class definitions
|
|
327
|
+
* @param {Object} results - Validation results
|
|
328
|
+
* @returns {string} CSS file content with stub classes
|
|
329
|
+
*/
|
|
330
|
+
export function generateCssStub(results) {
|
|
331
|
+
if (results.valid) {
|
|
332
|
+
return '/* No missing classes */';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const lines = [
|
|
336
|
+
'/* ==========================================================================',
|
|
337
|
+
' MISSING CSS CLASSES - Generated by MORPH-SPEC validate-css',
|
|
338
|
+
' Add these classes to your design-system.css or appropriate CSS file',
|
|
339
|
+
' ========================================================================== */',
|
|
340
|
+
''
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
for (const missing of results.missingClasses) {
|
|
344
|
+
lines.push(`/* Used in: ${missing.locations.map(l => `${l.file}:${l.line}`).slice(0, 3).join(', ')}${missing.locations.length > 3 ? '...' : ''} */`);
|
|
345
|
+
lines.push(`.${missing.className} {`);
|
|
346
|
+
lines.push(' /* TODO: Add styles */');
|
|
347
|
+
lines.push('}');
|
|
348
|
+
lines.push('');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return lines.join('\n');
|
|
352
|
+
}
|