@fragments-sdk/cli 0.2.2
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/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
package/src/build.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import type {
|
|
4
|
+
SegmentsConfig,
|
|
5
|
+
CompiledSegmentsFile,
|
|
6
|
+
CompiledSegment,
|
|
7
|
+
CompiledRecipe,
|
|
8
|
+
} from "./core/index.js";
|
|
9
|
+
import { BRAND, compileRecipe } from "./core/index.js";
|
|
10
|
+
import type { RecipeDefinition } from "./core/index.js";
|
|
11
|
+
import {
|
|
12
|
+
discoverSegmentFiles,
|
|
13
|
+
discoverRecipeFiles,
|
|
14
|
+
parseSegmentFile,
|
|
15
|
+
loadSegmentFile,
|
|
16
|
+
generateRegistry,
|
|
17
|
+
generateContextMd,
|
|
18
|
+
} from "./core/node.js";
|
|
19
|
+
|
|
20
|
+
export interface BuildResult {
|
|
21
|
+
success: boolean;
|
|
22
|
+
outputPath: string;
|
|
23
|
+
segmentCount: number;
|
|
24
|
+
errors: Array<{ file: string; error: string }>;
|
|
25
|
+
warnings: Array<{ file: string; warning: string }>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build compiled fragments.json file for AI consumption.
|
|
30
|
+
*
|
|
31
|
+
* Uses AST parsing to extract metadata WITHOUT executing fragment files.
|
|
32
|
+
* This means the build works without any project dependencies installed.
|
|
33
|
+
*/
|
|
34
|
+
export async function buildSegments(
|
|
35
|
+
config: SegmentsConfig,
|
|
36
|
+
configDir: string
|
|
37
|
+
): Promise<BuildResult> {
|
|
38
|
+
const files = await discoverSegmentFiles(config, configDir);
|
|
39
|
+
const errors: Array<{ file: string; error: string }> = [];
|
|
40
|
+
const warnings: Array<{ file: string; warning: string }> = [];
|
|
41
|
+
const segments: CompiledSegmentsFile["segments"] = {};
|
|
42
|
+
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
try {
|
|
45
|
+
// Read file content as text
|
|
46
|
+
const content = await readFile(file.absolutePath, "utf-8");
|
|
47
|
+
|
|
48
|
+
// Parse using AST (no execution)
|
|
49
|
+
const parsed = parseSegmentFile(content, file.relativePath);
|
|
50
|
+
|
|
51
|
+
// Collect warnings
|
|
52
|
+
for (const warning of parsed.warnings) {
|
|
53
|
+
warnings.push({ file: file.relativePath, warning });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check for required fields
|
|
57
|
+
if (!parsed.meta.name) {
|
|
58
|
+
errors.push({
|
|
59
|
+
file: file.relativePath,
|
|
60
|
+
error: "Missing meta.name in fragment definition",
|
|
61
|
+
});
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Build compiled fragment from parsed metadata
|
|
66
|
+
const compiled: CompiledSegment = {
|
|
67
|
+
filePath: file.relativePath,
|
|
68
|
+
meta: {
|
|
69
|
+
name: parsed.meta.name,
|
|
70
|
+
description: parsed.meta.description ?? "",
|
|
71
|
+
category: parsed.meta.category ?? "Uncategorized",
|
|
72
|
+
status: parsed.meta.status,
|
|
73
|
+
tags: parsed.meta.tags,
|
|
74
|
+
since: parsed.meta.since,
|
|
75
|
+
figma: parsed.meta.figma,
|
|
76
|
+
},
|
|
77
|
+
usage: {
|
|
78
|
+
when: parsed.usage.when ?? [],
|
|
79
|
+
whenNot: parsed.usage.whenNot ?? [],
|
|
80
|
+
guidelines: parsed.usage.guidelines,
|
|
81
|
+
accessibility: parsed.usage.accessibility,
|
|
82
|
+
},
|
|
83
|
+
props: Object.fromEntries(
|
|
84
|
+
Object.entries(parsed.props).map(([name, prop]) => [
|
|
85
|
+
name,
|
|
86
|
+
{
|
|
87
|
+
type: prop.type ?? "custom",
|
|
88
|
+
description: prop.description ?? "",
|
|
89
|
+
default: prop.default,
|
|
90
|
+
required: prop.required,
|
|
91
|
+
values: prop.values,
|
|
92
|
+
constraints: prop.constraints,
|
|
93
|
+
},
|
|
94
|
+
])
|
|
95
|
+
),
|
|
96
|
+
relations: parsed.relations.map((rel) => ({
|
|
97
|
+
component: rel.component,
|
|
98
|
+
relationship: rel.relationship as
|
|
99
|
+
| "alternative"
|
|
100
|
+
| "sibling"
|
|
101
|
+
| "parent"
|
|
102
|
+
| "child"
|
|
103
|
+
| "composition",
|
|
104
|
+
note: rel.note,
|
|
105
|
+
})),
|
|
106
|
+
variants: parsed.variants.map((v) => ({
|
|
107
|
+
name: v.name,
|
|
108
|
+
description: v.description,
|
|
109
|
+
...(v.code && { code: v.code }),
|
|
110
|
+
...(v.figma && { figma: v.figma }),
|
|
111
|
+
...(v.args && { args: v.args }),
|
|
112
|
+
})),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
segments[parsed.meta.name] = compiled;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
errors.push({
|
|
118
|
+
file: file.relativePath,
|
|
119
|
+
error: error instanceof Error ? error.message : String(error),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Discover and compile recipe files
|
|
125
|
+
const recipes: Record<string, CompiledRecipe> = {};
|
|
126
|
+
try {
|
|
127
|
+
const recipeFiles = await discoverRecipeFiles(configDir, config.exclude);
|
|
128
|
+
for (const file of recipeFiles) {
|
|
129
|
+
try {
|
|
130
|
+
// loadSegmentFile uses esbuild to bundle+evaluate, returns default export
|
|
131
|
+
// CJS/ESM interop may double-wrap the default export
|
|
132
|
+
let raw = await loadSegmentFile(file.absolutePath) as unknown as Record<string, unknown> | null;
|
|
133
|
+
// Unwrap double-default from CJS interop
|
|
134
|
+
if (raw && 'default' in raw && typeof raw.default === 'object') {
|
|
135
|
+
raw = raw.default as Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
const def = raw;
|
|
138
|
+
if (def && typeof def === 'object' && 'name' in def && 'code' in def && 'components' in def) {
|
|
139
|
+
const compiled = compileRecipe(def as unknown as RecipeDefinition, file.relativePath);
|
|
140
|
+
recipes[compiled.name] = compiled;
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
warnings.push({
|
|
144
|
+
file: file.relativePath,
|
|
145
|
+
warning: `Failed to load recipe: ${error instanceof Error ? error.message : String(error)}`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
// Recipe discovery failure is non-fatal
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const output: CompiledSegmentsFile = {
|
|
154
|
+
version: "1.0.0",
|
|
155
|
+
generatedAt: new Date().toISOString(),
|
|
156
|
+
segments,
|
|
157
|
+
...(Object.keys(recipes).length > 0 && { recipes }),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const outputPath = resolve(configDir, config.outFile ?? BRAND.outFile);
|
|
161
|
+
await writeFile(outputPath, JSON.stringify(output));
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
success: errors.length === 0,
|
|
165
|
+
outputPath,
|
|
166
|
+
segmentCount: Object.keys(segments).length,
|
|
167
|
+
errors,
|
|
168
|
+
warnings,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Result of building the .fragments directory
|
|
174
|
+
*/
|
|
175
|
+
export interface FragmentsBuildResult {
|
|
176
|
+
success: boolean;
|
|
177
|
+
indexPath: string;
|
|
178
|
+
registryPath: string;
|
|
179
|
+
contextPath: string;
|
|
180
|
+
componentCount: number;
|
|
181
|
+
errors: Array<{ file: string; error: string }>;
|
|
182
|
+
warnings: Array<{ file: string; warning: string }>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Build the .fragments/ directory with index.json, registry.json, and context.md
|
|
187
|
+
*
|
|
188
|
+
* This generates:
|
|
189
|
+
* - .fragments/index.json - Minimal name → path mapping (tiny, for quick lookups)
|
|
190
|
+
* - .fragments/registry.json - Component index with paths and enrichment references
|
|
191
|
+
* - .fragments/context.md - AI-ready consolidated context file
|
|
192
|
+
*/
|
|
193
|
+
export async function buildFragmentsDir(
|
|
194
|
+
config: SegmentsConfig,
|
|
195
|
+
configDir: string
|
|
196
|
+
): Promise<FragmentsBuildResult> {
|
|
197
|
+
const fragmentsDir = join(configDir, BRAND.dataDir);
|
|
198
|
+
const componentsDir = join(fragmentsDir, BRAND.componentsDir);
|
|
199
|
+
|
|
200
|
+
// Create directories
|
|
201
|
+
await mkdir(fragmentsDir, { recursive: true });
|
|
202
|
+
await mkdir(componentsDir, { recursive: true });
|
|
203
|
+
|
|
204
|
+
// Generate registry with config options
|
|
205
|
+
const registryResult = await generateRegistry({
|
|
206
|
+
projectRoot: configDir,
|
|
207
|
+
componentPatterns: config.components || ["src/**/*.tsx", "src/**/*.ts"],
|
|
208
|
+
storyPatterns: config.include || ["src/**/*.stories.tsx"],
|
|
209
|
+
fragmentsDir,
|
|
210
|
+
registryOptions: config.registry || {},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const errors = [...registryResult.errors];
|
|
214
|
+
const warnings = [...registryResult.warnings];
|
|
215
|
+
|
|
216
|
+
// Write index.json (minimal name → path)
|
|
217
|
+
const indexPath = join(fragmentsDir, "index.json");
|
|
218
|
+
await writeFile(indexPath, JSON.stringify(registryResult.index, null, 2));
|
|
219
|
+
|
|
220
|
+
// Write registry.json (full metadata)
|
|
221
|
+
const registryPath = join(fragmentsDir, BRAND.registryFile);
|
|
222
|
+
await writeFile(registryPath, JSON.stringify(registryResult.registry, null, 2));
|
|
223
|
+
|
|
224
|
+
// Generate context.md - focus on semantic knowledge, skip props (AI can read source)
|
|
225
|
+
const contextResult = generateContextMd(registryResult.registry, {
|
|
226
|
+
format: "markdown",
|
|
227
|
+
compact: false,
|
|
228
|
+
include: {
|
|
229
|
+
props: false, // AI can read TypeScript directly
|
|
230
|
+
relations: true,
|
|
231
|
+
code: false,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Write context.md
|
|
236
|
+
const contextPath = join(fragmentsDir, BRAND.contextFile);
|
|
237
|
+
await writeFile(contextPath, contextResult.content);
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
success: errors.length === 0,
|
|
241
|
+
indexPath,
|
|
242
|
+
registryPath,
|
|
243
|
+
contextPath,
|
|
244
|
+
componentCount: registryResult.registry.componentCount,
|
|
245
|
+
errors,
|
|
246
|
+
warnings,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fragments a11y - Run accessibility checks on all component variants
|
|
3
|
+
*
|
|
4
|
+
* Uses the /fragments/a11y endpoint to get axe-core accessibility results
|
|
5
|
+
* for all components in the design system.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
import { BRAND } from '../core/index.js';
|
|
10
|
+
import { loadConfig } from '../core/node.js';
|
|
11
|
+
import {
|
|
12
|
+
createDevServerClient,
|
|
13
|
+
DevServerConnectionError,
|
|
14
|
+
} from '../shared/index.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for a11y command
|
|
18
|
+
*/
|
|
19
|
+
export interface A11yOptions {
|
|
20
|
+
/** Path to config file */
|
|
21
|
+
config?: string;
|
|
22
|
+
/** Output JSON format */
|
|
23
|
+
json?: boolean;
|
|
24
|
+
/** CI mode - exit code 1 if any critical/serious violations */
|
|
25
|
+
ci?: boolean;
|
|
26
|
+
/** Check specific component only */
|
|
27
|
+
component?: string;
|
|
28
|
+
/** Dev server port */
|
|
29
|
+
port?: number | string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A11y result for a single variant
|
|
34
|
+
*/
|
|
35
|
+
export interface A11yVariantResult {
|
|
36
|
+
/** Variant name */
|
|
37
|
+
variant: string;
|
|
38
|
+
/** Number of violations */
|
|
39
|
+
violations: number;
|
|
40
|
+
/** Number of passes */
|
|
41
|
+
passes: number;
|
|
42
|
+
/** Number of incomplete checks */
|
|
43
|
+
incomplete: number;
|
|
44
|
+
/** Violation summary by severity */
|
|
45
|
+
summary: {
|
|
46
|
+
total: number;
|
|
47
|
+
critical: number;
|
|
48
|
+
serious: number;
|
|
49
|
+
moderate: number;
|
|
50
|
+
minor: number;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A11y result for a component
|
|
56
|
+
*/
|
|
57
|
+
export interface A11yComponentResult {
|
|
58
|
+
/** Component name */
|
|
59
|
+
component: string;
|
|
60
|
+
/** Results for each variant */
|
|
61
|
+
results: A11yVariantResult[];
|
|
62
|
+
/** Overall status */
|
|
63
|
+
status: 'PASS' | 'WARN' | 'FAIL';
|
|
64
|
+
/** Total violations across all variants */
|
|
65
|
+
totalViolations: number;
|
|
66
|
+
/** Total critical violations */
|
|
67
|
+
totalCritical: number;
|
|
68
|
+
/** Total serious violations */
|
|
69
|
+
totalSerious: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Summary of a11y results
|
|
74
|
+
*/
|
|
75
|
+
export interface A11ySummary {
|
|
76
|
+
/** Total number of components */
|
|
77
|
+
totalComponents: number;
|
|
78
|
+
/** Number of accessible components (no critical/serious) */
|
|
79
|
+
accessibleComponents: number;
|
|
80
|
+
/** Percentage of accessible components */
|
|
81
|
+
accessiblePercent: number;
|
|
82
|
+
/** All component results */
|
|
83
|
+
components: A11yComponentResult[];
|
|
84
|
+
/** Total violations across all components */
|
|
85
|
+
totalViolations: number;
|
|
86
|
+
/** Total critical violations */
|
|
87
|
+
totalCritical: number;
|
|
88
|
+
/** Total serious violations */
|
|
89
|
+
totalSerious: number;
|
|
90
|
+
/** Total moderate violations */
|
|
91
|
+
totalModerate: number;
|
|
92
|
+
/** Total minor violations */
|
|
93
|
+
totalMinor: number;
|
|
94
|
+
/** Whether CI check passed (no critical/serious) */
|
|
95
|
+
passed: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Run the a11y command
|
|
100
|
+
*/
|
|
101
|
+
export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
|
|
102
|
+
const { config: configPath, json = false, ci = false, component, port = 6006 } = options;
|
|
103
|
+
|
|
104
|
+
// Load config
|
|
105
|
+
await loadConfig(configPath);
|
|
106
|
+
|
|
107
|
+
const client = createDevServerClient(port);
|
|
108
|
+
const componentResults: A11yComponentResult[] = [];
|
|
109
|
+
|
|
110
|
+
if (!json) {
|
|
111
|
+
console.log(pc.cyan(`\n${BRAND.name} Accessibility Report\n`));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if dev server is reachable
|
|
115
|
+
const isReachable = await client.ping();
|
|
116
|
+
if (!isReachable) {
|
|
117
|
+
throw new DevServerConnectionError(
|
|
118
|
+
`Cannot connect to dev server at http://localhost:${port}`,
|
|
119
|
+
`http://localhost:${port}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fetch all segments
|
|
124
|
+
const segments = await client.getSegments();
|
|
125
|
+
|
|
126
|
+
if (segments.length === 0) {
|
|
127
|
+
if (json) {
|
|
128
|
+
console.log(JSON.stringify({ error: 'No fragments found', components: [] }));
|
|
129
|
+
} else {
|
|
130
|
+
console.log(pc.yellow('No fragments found.\n'));
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
totalComponents: 0,
|
|
134
|
+
accessibleComponents: 0,
|
|
135
|
+
accessiblePercent: 100,
|
|
136
|
+
components: [],
|
|
137
|
+
totalViolations: 0,
|
|
138
|
+
totalCritical: 0,
|
|
139
|
+
totalSerious: 0,
|
|
140
|
+
totalModerate: 0,
|
|
141
|
+
totalMinor: 0,
|
|
142
|
+
passed: true,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Filter to specific component if requested
|
|
147
|
+
const componentsToCheck = component
|
|
148
|
+
? segments.filter(s => s.name.toLowerCase() === component.toLowerCase())
|
|
149
|
+
: segments;
|
|
150
|
+
|
|
151
|
+
if (component && componentsToCheck.length === 0) {
|
|
152
|
+
const error = `Component '${component}' not found. Available: ${segments.map(s => s.name).join(', ')}`;
|
|
153
|
+
if (json) {
|
|
154
|
+
console.log(JSON.stringify({ error }));
|
|
155
|
+
} else {
|
|
156
|
+
console.log(pc.red(error));
|
|
157
|
+
}
|
|
158
|
+
throw new Error(error);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!json) {
|
|
162
|
+
console.log(pc.dim(`Checking ${componentsToCheck.length} component(s) for accessibility issues...\n`));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check each component
|
|
166
|
+
for (const seg of componentsToCheck) {
|
|
167
|
+
try {
|
|
168
|
+
// Get a11y results from the server
|
|
169
|
+
const a11yResult = await client.getA11y(seg.name);
|
|
170
|
+
|
|
171
|
+
let totalViolations = 0;
|
|
172
|
+
let totalCritical = 0;
|
|
173
|
+
let totalSerious = 0;
|
|
174
|
+
|
|
175
|
+
for (const result of a11yResult.results) {
|
|
176
|
+
totalViolations += result.summary.total;
|
|
177
|
+
totalCritical += result.summary.critical;
|
|
178
|
+
totalSerious += result.summary.serious;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Determine status
|
|
182
|
+
let status: 'PASS' | 'WARN' | 'FAIL' = 'PASS';
|
|
183
|
+
if (totalCritical > 0 || totalSerious > 0) {
|
|
184
|
+
status = 'FAIL';
|
|
185
|
+
} else if (totalViolations > 0) {
|
|
186
|
+
status = 'WARN';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
componentResults.push({
|
|
190
|
+
component: seg.name,
|
|
191
|
+
results: a11yResult.results,
|
|
192
|
+
status,
|
|
193
|
+
totalViolations,
|
|
194
|
+
totalCritical,
|
|
195
|
+
totalSerious,
|
|
196
|
+
});
|
|
197
|
+
} catch (error) {
|
|
198
|
+
// Handle individual component errors
|
|
199
|
+
componentResults.push({
|
|
200
|
+
component: seg.name,
|
|
201
|
+
results: [],
|
|
202
|
+
status: 'FAIL',
|
|
203
|
+
totalViolations: 0,
|
|
204
|
+
totalCritical: 0,
|
|
205
|
+
totalSerious: 0,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Calculate summary
|
|
211
|
+
const accessibleComponents = componentResults.filter(c => c.status !== 'FAIL').length;
|
|
212
|
+
const totalViolations = componentResults.reduce((sum, c) => sum + c.totalViolations, 0);
|
|
213
|
+
const totalCritical = componentResults.reduce((sum, c) => sum + c.totalCritical, 0);
|
|
214
|
+
const totalSerious = componentResults.reduce((sum, c) => sum + c.totalSerious, 0);
|
|
215
|
+
|
|
216
|
+
// Calculate moderate and minor from all results
|
|
217
|
+
let totalModerate = 0;
|
|
218
|
+
let totalMinor = 0;
|
|
219
|
+
for (const comp of componentResults) {
|
|
220
|
+
for (const result of comp.results) {
|
|
221
|
+
totalModerate += result.summary.moderate;
|
|
222
|
+
totalMinor += result.summary.minor;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const summary: A11ySummary = {
|
|
227
|
+
totalComponents: componentResults.length,
|
|
228
|
+
accessibleComponents,
|
|
229
|
+
accessiblePercent: componentResults.length > 0
|
|
230
|
+
? Math.round((accessibleComponents / componentResults.length) * 100)
|
|
231
|
+
: 100,
|
|
232
|
+
components: componentResults,
|
|
233
|
+
totalViolations,
|
|
234
|
+
totalCritical,
|
|
235
|
+
totalSerious,
|
|
236
|
+
totalModerate,
|
|
237
|
+
totalMinor,
|
|
238
|
+
passed: totalCritical === 0 && totalSerious === 0,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
if (json) {
|
|
242
|
+
// JSON output for CI/automation
|
|
243
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
244
|
+
} else {
|
|
245
|
+
// Table output for humans
|
|
246
|
+
console.log(pc.bold(
|
|
247
|
+
'Component'.padEnd(20) +
|
|
248
|
+
'Variants'.padEnd(10) +
|
|
249
|
+
'Violations'.padEnd(12) +
|
|
250
|
+
'Critical'.padEnd(10) +
|
|
251
|
+
'Serious'.padEnd(10) +
|
|
252
|
+
'Status'
|
|
253
|
+
));
|
|
254
|
+
console.log(pc.dim('─'.repeat(72)));
|
|
255
|
+
|
|
256
|
+
for (const result of componentResults) {
|
|
257
|
+
const statusColor = result.status === 'PASS'
|
|
258
|
+
? pc.green
|
|
259
|
+
: result.status === 'WARN'
|
|
260
|
+
? pc.yellow
|
|
261
|
+
: pc.red;
|
|
262
|
+
|
|
263
|
+
const variantCount = result.results.length || 1;
|
|
264
|
+
|
|
265
|
+
console.log(
|
|
266
|
+
result.component.padEnd(20) +
|
|
267
|
+
String(variantCount).padEnd(10) +
|
|
268
|
+
String(result.totalViolations).padEnd(12) +
|
|
269
|
+
String(result.totalCritical).padEnd(10) +
|
|
270
|
+
String(result.totalSerious).padEnd(10) +
|
|
271
|
+
statusColor(result.status)
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
console.log(pc.dim('─'.repeat(72)));
|
|
276
|
+
|
|
277
|
+
console.log();
|
|
278
|
+
console.log(pc.bold('Summary:'));
|
|
279
|
+
console.log(` ${accessibleComponents}/${componentResults.length} components accessible (${summary.accessiblePercent}%)`);
|
|
280
|
+
console.log(` Total violations: ${totalViolations} (${totalCritical} critical, ${totalSerious} serious, ${totalModerate} moderate, ${totalMinor} minor)`);
|
|
281
|
+
|
|
282
|
+
if (!summary.passed) {
|
|
283
|
+
console.log();
|
|
284
|
+
console.log(pc.red('✗ Accessibility check failed - critical/serious violations found'));
|
|
285
|
+
} else if (totalViolations > 0) {
|
|
286
|
+
console.log();
|
|
287
|
+
console.log(pc.yellow('⚠ Minor/moderate violations found - consider fixing for better accessibility'));
|
|
288
|
+
} else {
|
|
289
|
+
console.log();
|
|
290
|
+
console.log(pc.green('✓ All components pass accessibility checks'));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// In CI mode, throw if there are critical/serious violations
|
|
297
|
+
if (ci && !summary.passed) {
|
|
298
|
+
throw new Error(`Accessibility check failed: ${totalCritical} critical, ${totalSerious} serious violations found`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return summary;
|
|
302
|
+
}
|