@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
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
BrowserPool,
|
|
4
|
+
CaptureEngine,
|
|
5
|
+
DiffEngine,
|
|
6
|
+
StorageManager,
|
|
7
|
+
analyzeDesignSystem,
|
|
8
|
+
formatMs,
|
|
9
|
+
generateHtmlReport,
|
|
10
|
+
getGrade
|
|
11
|
+
} from "./chunk-MUZ6CM66.js";
|
|
12
|
+
import {
|
|
13
|
+
discoverComponentFiles,
|
|
14
|
+
discoverRecipeFiles,
|
|
15
|
+
discoverSegmentFiles,
|
|
16
|
+
extractComponentName,
|
|
17
|
+
generateContextMd,
|
|
18
|
+
generateRegistry,
|
|
19
|
+
loadSegmentFile,
|
|
20
|
+
parseSegmentFile
|
|
21
|
+
} from "./chunk-OAENNG3G.js";
|
|
22
|
+
import {
|
|
23
|
+
compileRecipe
|
|
24
|
+
} from "./chunk-LY2CFFPY.js";
|
|
25
|
+
import {
|
|
26
|
+
BRAND,
|
|
27
|
+
DEFAULTS,
|
|
28
|
+
segmentDefinitionSchema
|
|
29
|
+
} from "./chunk-XHNKNI6J.js";
|
|
30
|
+
|
|
31
|
+
// src/validators.ts
|
|
32
|
+
async function validateSchema(config, configDir) {
|
|
33
|
+
const files = await discoverSegmentFiles(config, configDir);
|
|
34
|
+
const errors = [];
|
|
35
|
+
const warnings = [];
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
try {
|
|
38
|
+
const segment = await loadSegmentFile(file.absolutePath);
|
|
39
|
+
if (!segment) {
|
|
40
|
+
errors.push({
|
|
41
|
+
file: file.relativePath,
|
|
42
|
+
message: "No default export found",
|
|
43
|
+
details: `Segment files must have a default export from defineSegment()`
|
|
44
|
+
});
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const result = segmentDefinitionSchema.safeParse(segment);
|
|
48
|
+
if (!result.success) {
|
|
49
|
+
const details = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
|
|
50
|
+
errors.push({
|
|
51
|
+
file: file.relativePath,
|
|
52
|
+
message: "Invalid segment schema",
|
|
53
|
+
details
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
errors.push({
|
|
58
|
+
file: file.relativePath,
|
|
59
|
+
message: "Failed to load segment file",
|
|
60
|
+
details: error instanceof Error ? error.message : String(error)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
valid: errors.length === 0,
|
|
66
|
+
errors,
|
|
67
|
+
warnings
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function validateCoverage(config, configDir) {
|
|
71
|
+
const segmentFiles = await discoverSegmentFiles(config, configDir);
|
|
72
|
+
const componentFiles = await discoverComponentFiles(config, configDir);
|
|
73
|
+
const errors = [];
|
|
74
|
+
const warnings = [];
|
|
75
|
+
const documentedComponents = /* @__PURE__ */ new Set();
|
|
76
|
+
for (const file of segmentFiles) {
|
|
77
|
+
try {
|
|
78
|
+
const segment = await loadSegmentFile(file.absolutePath);
|
|
79
|
+
if (segment?.meta?.name) {
|
|
80
|
+
documentedComponents.add(segment.meta.name);
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const file of componentFiles) {
|
|
86
|
+
const componentName = extractComponentName(file.relativePath);
|
|
87
|
+
const segmentPath = file.relativePath.replace(
|
|
88
|
+
/\.(tsx?|jsx?)$/,
|
|
89
|
+
BRAND.fileExtension
|
|
90
|
+
);
|
|
91
|
+
const hasSegmentFile = segmentFiles.some(
|
|
92
|
+
(s) => s.relativePath === segmentPath
|
|
93
|
+
);
|
|
94
|
+
if (!hasSegmentFile && !documentedComponents.has(componentName)) {
|
|
95
|
+
warnings.push({
|
|
96
|
+
file: file.relativePath,
|
|
97
|
+
message: `Component "${componentName}" has no segment documentation`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
valid: errors.length === 0,
|
|
103
|
+
errors,
|
|
104
|
+
warnings
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function validateAll(config, configDir) {
|
|
108
|
+
const [schemaResult, coverageResult] = await Promise.all([
|
|
109
|
+
validateSchema(config, configDir),
|
|
110
|
+
validateCoverage(config, configDir)
|
|
111
|
+
]);
|
|
112
|
+
return {
|
|
113
|
+
valid: schemaResult.valid && coverageResult.valid,
|
|
114
|
+
errors: [...schemaResult.errors, ...coverageResult.errors],
|
|
115
|
+
warnings: [...schemaResult.warnings, ...coverageResult.warnings]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/build.ts
|
|
120
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
121
|
+
import { resolve, join } from "path";
|
|
122
|
+
async function buildSegments(config, configDir) {
|
|
123
|
+
const files = await discoverSegmentFiles(config, configDir);
|
|
124
|
+
const errors = [];
|
|
125
|
+
const warnings = [];
|
|
126
|
+
const segments = {};
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
try {
|
|
129
|
+
const content = await readFile(file.absolutePath, "utf-8");
|
|
130
|
+
const parsed = parseSegmentFile(content, file.relativePath);
|
|
131
|
+
for (const warning of parsed.warnings) {
|
|
132
|
+
warnings.push({ file: file.relativePath, warning });
|
|
133
|
+
}
|
|
134
|
+
if (!parsed.meta.name) {
|
|
135
|
+
errors.push({
|
|
136
|
+
file: file.relativePath,
|
|
137
|
+
error: "Missing meta.name in fragment definition"
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const compiled = {
|
|
142
|
+
filePath: file.relativePath,
|
|
143
|
+
meta: {
|
|
144
|
+
name: parsed.meta.name,
|
|
145
|
+
description: parsed.meta.description ?? "",
|
|
146
|
+
category: parsed.meta.category ?? "Uncategorized",
|
|
147
|
+
status: parsed.meta.status,
|
|
148
|
+
tags: parsed.meta.tags,
|
|
149
|
+
since: parsed.meta.since,
|
|
150
|
+
figma: parsed.meta.figma
|
|
151
|
+
},
|
|
152
|
+
usage: {
|
|
153
|
+
when: parsed.usage.when ?? [],
|
|
154
|
+
whenNot: parsed.usage.whenNot ?? [],
|
|
155
|
+
guidelines: parsed.usage.guidelines,
|
|
156
|
+
accessibility: parsed.usage.accessibility
|
|
157
|
+
},
|
|
158
|
+
props: Object.fromEntries(
|
|
159
|
+
Object.entries(parsed.props).map(([name, prop]) => [
|
|
160
|
+
name,
|
|
161
|
+
{
|
|
162
|
+
type: prop.type ?? "custom",
|
|
163
|
+
description: prop.description ?? "",
|
|
164
|
+
default: prop.default,
|
|
165
|
+
required: prop.required,
|
|
166
|
+
values: prop.values,
|
|
167
|
+
constraints: prop.constraints
|
|
168
|
+
}
|
|
169
|
+
])
|
|
170
|
+
),
|
|
171
|
+
relations: parsed.relations.map((rel) => ({
|
|
172
|
+
component: rel.component,
|
|
173
|
+
relationship: rel.relationship,
|
|
174
|
+
note: rel.note
|
|
175
|
+
})),
|
|
176
|
+
variants: parsed.variants.map((v) => ({
|
|
177
|
+
name: v.name,
|
|
178
|
+
description: v.description,
|
|
179
|
+
...v.code && { code: v.code },
|
|
180
|
+
...v.figma && { figma: v.figma },
|
|
181
|
+
...v.args && { args: v.args }
|
|
182
|
+
}))
|
|
183
|
+
};
|
|
184
|
+
segments[parsed.meta.name] = compiled;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
errors.push({
|
|
187
|
+
file: file.relativePath,
|
|
188
|
+
error: error instanceof Error ? error.message : String(error)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const recipes = {};
|
|
193
|
+
try {
|
|
194
|
+
const recipeFiles = await discoverRecipeFiles(configDir, config.exclude);
|
|
195
|
+
for (const file of recipeFiles) {
|
|
196
|
+
try {
|
|
197
|
+
let raw = await loadSegmentFile(file.absolutePath);
|
|
198
|
+
if (raw && "default" in raw && typeof raw.default === "object") {
|
|
199
|
+
raw = raw.default;
|
|
200
|
+
}
|
|
201
|
+
const def = raw;
|
|
202
|
+
if (def && typeof def === "object" && "name" in def && "code" in def && "components" in def) {
|
|
203
|
+
const compiled = compileRecipe(def, file.relativePath);
|
|
204
|
+
recipes[compiled.name] = compiled;
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
warnings.push({
|
|
208
|
+
file: file.relativePath,
|
|
209
|
+
warning: `Failed to load recipe: ${error instanceof Error ? error.message : String(error)}`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
const output = {
|
|
216
|
+
version: "1.0.0",
|
|
217
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
218
|
+
segments,
|
|
219
|
+
...Object.keys(recipes).length > 0 && { recipes }
|
|
220
|
+
};
|
|
221
|
+
const outputPath = resolve(configDir, config.outFile ?? BRAND.outFile);
|
|
222
|
+
await writeFile(outputPath, JSON.stringify(output));
|
|
223
|
+
return {
|
|
224
|
+
success: errors.length === 0,
|
|
225
|
+
outputPath,
|
|
226
|
+
segmentCount: Object.keys(segments).length,
|
|
227
|
+
errors,
|
|
228
|
+
warnings
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async function buildFragmentsDir(config, configDir) {
|
|
232
|
+
const fragmentsDir = join(configDir, BRAND.dataDir);
|
|
233
|
+
const componentsDir = join(fragmentsDir, BRAND.componentsDir);
|
|
234
|
+
await mkdir(fragmentsDir, { recursive: true });
|
|
235
|
+
await mkdir(componentsDir, { recursive: true });
|
|
236
|
+
const registryResult = await generateRegistry({
|
|
237
|
+
projectRoot: configDir,
|
|
238
|
+
componentPatterns: config.components || ["src/**/*.tsx", "src/**/*.ts"],
|
|
239
|
+
storyPatterns: config.include || ["src/**/*.stories.tsx"],
|
|
240
|
+
fragmentsDir,
|
|
241
|
+
registryOptions: config.registry || {}
|
|
242
|
+
});
|
|
243
|
+
const errors = [...registryResult.errors];
|
|
244
|
+
const warnings = [...registryResult.warnings];
|
|
245
|
+
const indexPath = join(fragmentsDir, "index.json");
|
|
246
|
+
await writeFile(indexPath, JSON.stringify(registryResult.index, null, 2));
|
|
247
|
+
const registryPath = join(fragmentsDir, BRAND.registryFile);
|
|
248
|
+
await writeFile(registryPath, JSON.stringify(registryResult.registry, null, 2));
|
|
249
|
+
const contextResult = generateContextMd(registryResult.registry, {
|
|
250
|
+
format: "markdown",
|
|
251
|
+
compact: false,
|
|
252
|
+
include: {
|
|
253
|
+
props: false,
|
|
254
|
+
// AI can read TypeScript directly
|
|
255
|
+
relations: true,
|
|
256
|
+
code: false
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
const contextPath = join(fragmentsDir, BRAND.contextFile);
|
|
260
|
+
await writeFile(contextPath, contextResult.content);
|
|
261
|
+
return {
|
|
262
|
+
success: errors.length === 0,
|
|
263
|
+
indexPath,
|
|
264
|
+
registryPath,
|
|
265
|
+
contextPath,
|
|
266
|
+
componentCount: registryResult.registry.componentCount,
|
|
267
|
+
errors,
|
|
268
|
+
warnings
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/screenshot.ts
|
|
273
|
+
import pc from "picocolors";
|
|
274
|
+
async function runScreenshotCommand(config, configDir, options = {}) {
|
|
275
|
+
const startTime = Date.now();
|
|
276
|
+
const errors = [];
|
|
277
|
+
const storage = new StorageManager({
|
|
278
|
+
projectRoot: configDir,
|
|
279
|
+
viewport: options.width && options.height ? { width: options.width, height: options.height } : config.screenshots?.viewport
|
|
280
|
+
});
|
|
281
|
+
await storage.initialize();
|
|
282
|
+
const segmentFiles = await discoverSegmentFiles(config, configDir);
|
|
283
|
+
if (segmentFiles.length === 0) {
|
|
284
|
+
console.log(pc.yellow("No segment files found."));
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
captured: 0,
|
|
288
|
+
skipped: 0,
|
|
289
|
+
errors: [],
|
|
290
|
+
totalTimeMs: Date.now() - startTime
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const segments = [];
|
|
294
|
+
for (const file of segmentFiles) {
|
|
295
|
+
try {
|
|
296
|
+
const segment = await loadSegmentFile(file.absolutePath);
|
|
297
|
+
if (segment) {
|
|
298
|
+
segments.push({ path: file.relativePath, segment });
|
|
299
|
+
}
|
|
300
|
+
} catch (error) {
|
|
301
|
+
errors.push({
|
|
302
|
+
component: file.relativePath,
|
|
303
|
+
variant: "",
|
|
304
|
+
error: error instanceof Error ? error.message : String(error)
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const filteredSegments = options.component ? segments.filter((s) => s.segment.meta.name === options.component) : segments;
|
|
309
|
+
if (options.component && filteredSegments.length === 0) {
|
|
310
|
+
console.log(pc.yellow(`Component "${options.component}" not found.`));
|
|
311
|
+
return {
|
|
312
|
+
success: false,
|
|
313
|
+
captured: 0,
|
|
314
|
+
skipped: 0,
|
|
315
|
+
errors: [],
|
|
316
|
+
totalTimeMs: Date.now() - startTime
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const variantsToCapture = [];
|
|
320
|
+
for (const { segment } of filteredSegments) {
|
|
321
|
+
const variants = options.variant ? segment.variants.filter((v) => v.name === options.variant) : segment.variants;
|
|
322
|
+
for (const variant of variants) {
|
|
323
|
+
variantsToCapture.push({
|
|
324
|
+
component: segment.meta.name,
|
|
325
|
+
variant: variant.name,
|
|
326
|
+
render: variant.render
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (variantsToCapture.length === 0) {
|
|
331
|
+
console.log(pc.yellow("No variants to capture."));
|
|
332
|
+
return {
|
|
333
|
+
success: true,
|
|
334
|
+
captured: 0,
|
|
335
|
+
skipped: 0,
|
|
336
|
+
errors: [],
|
|
337
|
+
totalTimeMs: Date.now() - startTime
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
const theme = options.theme ?? DEFAULTS.theme;
|
|
341
|
+
const viewport = {
|
|
342
|
+
width: options.width ?? config.screenshots?.viewport?.width ?? DEFAULTS.viewport.width,
|
|
343
|
+
height: options.height ?? config.screenshots?.viewport?.height ?? DEFAULTS.viewport.height
|
|
344
|
+
};
|
|
345
|
+
console.log(pc.cyan(`
|
|
346
|
+
${BRAND.name} Screenshot
|
|
347
|
+
`));
|
|
348
|
+
console.log(pc.dim(`Capturing variants (theme: ${theme}, viewport: ${viewport.width}x${viewport.height}):
|
|
349
|
+
`));
|
|
350
|
+
const pool = new BrowserPool({
|
|
351
|
+
viewport
|
|
352
|
+
});
|
|
353
|
+
const viewerPort = DEFAULTS.port;
|
|
354
|
+
const baseUrl = `http://localhost:${viewerPort}`;
|
|
355
|
+
const captureEngine = new CaptureEngine(pool, baseUrl);
|
|
356
|
+
let captured = 0;
|
|
357
|
+
let skipped = 0;
|
|
358
|
+
const captureOptions = {
|
|
359
|
+
theme,
|
|
360
|
+
viewport,
|
|
361
|
+
delay: config.screenshots?.delay ?? DEFAULTS.captureDelayMs
|
|
362
|
+
};
|
|
363
|
+
try {
|
|
364
|
+
console.log(pc.dim("Starting browser..."));
|
|
365
|
+
await pool.warmup();
|
|
366
|
+
console.log(pc.dim("Browser ready.\n"));
|
|
367
|
+
for (const { component, variant } of variantsToCapture) {
|
|
368
|
+
const hasExisting = storage.hasBaseline(component, variant, theme);
|
|
369
|
+
if (hasExisting && !options.update) {
|
|
370
|
+
console.log(` ${pc.dim("\u25CB")} ${component}/${variant} ${pc.dim("(skipped)")}`);
|
|
371
|
+
skipped++;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
const screenshot = await captureEngine.captureVariant(
|
|
376
|
+
component,
|
|
377
|
+
variant,
|
|
378
|
+
captureOptions
|
|
379
|
+
);
|
|
380
|
+
await storage.saveBaseline(screenshot);
|
|
381
|
+
const totalTime = screenshot.metadata.renderTimeMs + screenshot.metadata.captureTimeMs;
|
|
382
|
+
console.log(
|
|
383
|
+
` ${pc.green("\u2713")} ${component}/${variant} ${pc.dim(formatMs(totalTime))}`
|
|
384
|
+
);
|
|
385
|
+
captured++;
|
|
386
|
+
} catch (error) {
|
|
387
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
388
|
+
console.log(` ${pc.red("\u2717")} ${component}/${variant} ${pc.dim(errorMsg)}`);
|
|
389
|
+
errors.push({ component, variant, error: errorMsg });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} finally {
|
|
393
|
+
await pool.shutdown();
|
|
394
|
+
}
|
|
395
|
+
const totalTimeMs = Date.now() - startTime;
|
|
396
|
+
console.log();
|
|
397
|
+
if (errors.length === 0) {
|
|
398
|
+
console.log(pc.green(`\u2713 Captured ${captured} screenshot(s) in ${formatMs(totalTimeMs)}`));
|
|
399
|
+
} else {
|
|
400
|
+
console.log(pc.yellow(`\u26A0 Captured ${captured} screenshot(s) with ${errors.length} error(s)`));
|
|
401
|
+
}
|
|
402
|
+
if (skipped > 0) {
|
|
403
|
+
console.log(pc.dim(` ${skipped} skipped (use --update to recapture)`));
|
|
404
|
+
}
|
|
405
|
+
console.log(pc.dim(` Stored in ${storage.screenshotsDirPath}
|
|
406
|
+
`));
|
|
407
|
+
return {
|
|
408
|
+
success: errors.length === 0,
|
|
409
|
+
captured,
|
|
410
|
+
skipped,
|
|
411
|
+
errors,
|
|
412
|
+
totalTimeMs
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/diff.ts
|
|
417
|
+
import pc2 from "picocolors";
|
|
418
|
+
async function runDiffCommand(config, configDir, options = {}) {
|
|
419
|
+
const startTime = Date.now();
|
|
420
|
+
const results = [];
|
|
421
|
+
const storage = new StorageManager({
|
|
422
|
+
projectRoot: configDir,
|
|
423
|
+
viewport: config.screenshots?.viewport
|
|
424
|
+
});
|
|
425
|
+
await storage.initialize();
|
|
426
|
+
const threshold = options.threshold ?? config.screenshots?.threshold ?? DEFAULTS.diffThreshold;
|
|
427
|
+
const diffEngine = new DiffEngine(threshold);
|
|
428
|
+
const segmentFiles = await discoverSegmentFiles(config, configDir);
|
|
429
|
+
if (segmentFiles.length === 0) {
|
|
430
|
+
console.log(pc2.yellow("No segment files found."));
|
|
431
|
+
return {
|
|
432
|
+
success: true,
|
|
433
|
+
total: 0,
|
|
434
|
+
passed: 0,
|
|
435
|
+
failed: 0,
|
|
436
|
+
missing: 0,
|
|
437
|
+
results: [],
|
|
438
|
+
totalTimeMs: Date.now() - startTime
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
const segments = [];
|
|
442
|
+
for (const file of segmentFiles) {
|
|
443
|
+
try {
|
|
444
|
+
const segment = await loadSegmentFile(file.absolutePath);
|
|
445
|
+
if (segment) {
|
|
446
|
+
segments.push({ path: file.relativePath, segment });
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const filteredSegments = options.component ? segments.filter((s) => s.segment.meta.name === options.component) : segments;
|
|
452
|
+
if (options.component && filteredSegments.length === 0) {
|
|
453
|
+
console.log(pc2.yellow(`Component "${options.component}" not found.`));
|
|
454
|
+
return {
|
|
455
|
+
success: false,
|
|
456
|
+
total: 0,
|
|
457
|
+
passed: 0,
|
|
458
|
+
failed: 0,
|
|
459
|
+
missing: 0,
|
|
460
|
+
results: [],
|
|
461
|
+
totalTimeMs: Date.now() - startTime
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const variantsToDiff = [];
|
|
465
|
+
for (const { segment } of filteredSegments) {
|
|
466
|
+
const variants = options.variant ? segment.variants.filter((v) => v.name === options.variant) : segment.variants;
|
|
467
|
+
for (const variant of variants) {
|
|
468
|
+
variantsToDiff.push({
|
|
469
|
+
component: segment.meta.name,
|
|
470
|
+
variant: variant.name
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (variantsToDiff.length === 0) {
|
|
475
|
+
console.log(pc2.yellow("No variants to compare."));
|
|
476
|
+
return {
|
|
477
|
+
success: true,
|
|
478
|
+
total: 0,
|
|
479
|
+
passed: 0,
|
|
480
|
+
failed: 0,
|
|
481
|
+
missing: 0,
|
|
482
|
+
results: [],
|
|
483
|
+
totalTimeMs: Date.now() - startTime
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
const theme = options.theme ?? DEFAULTS.theme;
|
|
487
|
+
const viewport = config.screenshots?.viewport ?? DEFAULTS.viewport;
|
|
488
|
+
console.log(pc2.cyan(`
|
|
489
|
+
${BRAND.name} Diff
|
|
490
|
+
`));
|
|
491
|
+
console.log(pc2.dim(`Comparing against baselines (theme: ${theme}, threshold: ${threshold}%):
|
|
492
|
+
`));
|
|
493
|
+
const pool = new BrowserPool({
|
|
494
|
+
viewport
|
|
495
|
+
});
|
|
496
|
+
const viewerPort = DEFAULTS.port;
|
|
497
|
+
const baseUrl = `http://localhost:${viewerPort}`;
|
|
498
|
+
const captureEngine = new CaptureEngine(pool, baseUrl);
|
|
499
|
+
let passed = 0;
|
|
500
|
+
let failed = 0;
|
|
501
|
+
let missing = 0;
|
|
502
|
+
const captureOptions = {
|
|
503
|
+
theme,
|
|
504
|
+
viewport,
|
|
505
|
+
delay: config.screenshots?.delay ?? DEFAULTS.captureDelayMs
|
|
506
|
+
};
|
|
507
|
+
try {
|
|
508
|
+
await pool.warmup();
|
|
509
|
+
for (const { component, variant } of variantsToDiff) {
|
|
510
|
+
const baseline = await storage.loadBaseline(component, variant, theme);
|
|
511
|
+
if (!baseline) {
|
|
512
|
+
console.log(
|
|
513
|
+
` ${pc2.yellow("?")} ${component}/${variant} ${pc2.dim("(no baseline)")}`
|
|
514
|
+
);
|
|
515
|
+
missing++;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
const current = await captureEngine.captureVariant(
|
|
520
|
+
component,
|
|
521
|
+
variant,
|
|
522
|
+
captureOptions
|
|
523
|
+
);
|
|
524
|
+
if (diffEngine.areIdentical(current, baseline)) {
|
|
525
|
+
console.log(` ${pc2.green("\u2713")} ${component}/${variant} ${pc2.dim("0.0%")}`);
|
|
526
|
+
results.push({
|
|
527
|
+
component,
|
|
528
|
+
variant,
|
|
529
|
+
theme,
|
|
530
|
+
result: {
|
|
531
|
+
matches: true,
|
|
532
|
+
diffPercentage: 0,
|
|
533
|
+
diffPixelCount: 0,
|
|
534
|
+
totalPixels: current.viewport.width * current.viewport.height,
|
|
535
|
+
changedRegions: [],
|
|
536
|
+
diffTimeMs: 0
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
passed++;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const diffResult = diffEngine.compare(current, baseline, { threshold });
|
|
543
|
+
if (diffResult.matches) {
|
|
544
|
+
console.log(
|
|
545
|
+
` ${pc2.green("\u2713")} ${component}/${variant} ${pc2.dim(`${diffResult.diffPercentage}%`)}`
|
|
546
|
+
);
|
|
547
|
+
passed++;
|
|
548
|
+
} else {
|
|
549
|
+
let diffImagePath;
|
|
550
|
+
if (diffResult.diffImage) {
|
|
551
|
+
diffImagePath = await storage.saveDiff(
|
|
552
|
+
component,
|
|
553
|
+
variant,
|
|
554
|
+
theme,
|
|
555
|
+
diffResult.diffImage
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
console.log(
|
|
559
|
+
` ${pc2.red("\u2717")} ${component}/${variant} ${pc2.yellow(`${diffResult.diffPercentage}%`)}` + (diffImagePath ? pc2.dim(` \u2192 ${diffImagePath}`) : "")
|
|
560
|
+
);
|
|
561
|
+
failed++;
|
|
562
|
+
results.push({
|
|
563
|
+
component,
|
|
564
|
+
variant,
|
|
565
|
+
theme,
|
|
566
|
+
result: diffResult,
|
|
567
|
+
diffImagePath
|
|
568
|
+
});
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
results.push({
|
|
572
|
+
component,
|
|
573
|
+
variant,
|
|
574
|
+
theme,
|
|
575
|
+
result: diffResult
|
|
576
|
+
});
|
|
577
|
+
} catch (error) {
|
|
578
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
579
|
+
console.log(` ${pc2.red("!")} ${component}/${variant} ${pc2.dim(errorMsg)}`);
|
|
580
|
+
failed++;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
} finally {
|
|
584
|
+
await pool.shutdown();
|
|
585
|
+
}
|
|
586
|
+
const totalTimeMs = Date.now() - startTime;
|
|
587
|
+
const total = passed + failed + missing;
|
|
588
|
+
console.log();
|
|
589
|
+
if (failed === 0 && missing === 0) {
|
|
590
|
+
console.log(pc2.green(`\u2713 All ${passed} variant(s) match baselines`));
|
|
591
|
+
} else if (failed > 0) {
|
|
592
|
+
console.log(pc2.red(`\u2717 ${failed} variant(s) differ from baselines`));
|
|
593
|
+
}
|
|
594
|
+
if (missing > 0) {
|
|
595
|
+
console.log(pc2.yellow(` ${missing} variant(s) have no baseline (run \`${BRAND.cliCommand} screenshot\`)`));
|
|
596
|
+
}
|
|
597
|
+
console.log(pc2.dim(` Completed in ${formatMs(totalTimeMs)}
|
|
598
|
+
`));
|
|
599
|
+
const success = failed === 0;
|
|
600
|
+
return {
|
|
601
|
+
success,
|
|
602
|
+
total,
|
|
603
|
+
passed,
|
|
604
|
+
failed,
|
|
605
|
+
missing,
|
|
606
|
+
results,
|
|
607
|
+
totalTimeMs
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/analyze.ts
|
|
612
|
+
import { existsSync } from "fs";
|
|
613
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
614
|
+
import { join as join2, dirname } from "path";
|
|
615
|
+
import pc3 from "picocolors";
|
|
616
|
+
async function runAnalyzeCommand(config, configDir, options = {}) {
|
|
617
|
+
const format = options.format ?? "html";
|
|
618
|
+
const minScore = options.minScore ?? 0;
|
|
619
|
+
console.log(pc3.cyan(`
|
|
620
|
+
${BRAND.name} Analyzer
|
|
621
|
+
`));
|
|
622
|
+
const segmentsPath = join2(configDir, config.outFile ?? "segments.json");
|
|
623
|
+
if (!existsSync(segmentsPath)) {
|
|
624
|
+
console.log(pc3.red(`\u2717 No segments.json found. Run \`${BRAND.cliCommand} build\` first.
|
|
625
|
+
`));
|
|
626
|
+
return {
|
|
627
|
+
success: false,
|
|
628
|
+
analytics: createEmptyAnalytics()
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
console.log(pc3.dim("Analyzing design system...\n"));
|
|
632
|
+
const content = await readFile2(segmentsPath, "utf-8");
|
|
633
|
+
const data = JSON.parse(content);
|
|
634
|
+
const analytics = analyzeDesignSystem(data);
|
|
635
|
+
printConsoleSummary(analytics);
|
|
636
|
+
let outputPath;
|
|
637
|
+
if (format === "html" || format === "json") {
|
|
638
|
+
outputPath = options.output ?? getDefaultOutputPath(format, configDir);
|
|
639
|
+
await mkdir2(dirname(outputPath), { recursive: true });
|
|
640
|
+
if (format === "html") {
|
|
641
|
+
const html = generateHtmlReport(analytics);
|
|
642
|
+
await writeFile2(outputPath, html);
|
|
643
|
+
console.log(pc3.green(`\u2713 Report generated: ${outputPath}
|
|
644
|
+
`));
|
|
645
|
+
} else {
|
|
646
|
+
await writeFile2(outputPath, JSON.stringify(analytics, null, 2));
|
|
647
|
+
console.log(pc3.green(`\u2713 JSON report generated: ${outputPath}
|
|
648
|
+
`));
|
|
649
|
+
}
|
|
650
|
+
if (options.open && format === "html") {
|
|
651
|
+
await openInBrowser(outputPath);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const passedCi = analytics.summary.overallScore >= minScore;
|
|
655
|
+
if (options.ci) {
|
|
656
|
+
if (passedCi) {
|
|
657
|
+
console.log(
|
|
658
|
+
pc3.green(`\u2713 Score ${analytics.summary.overallScore} meets minimum threshold ${minScore}
|
|
659
|
+
`)
|
|
660
|
+
);
|
|
661
|
+
} else {
|
|
662
|
+
console.log(
|
|
663
|
+
pc3.red(
|
|
664
|
+
`\u2717 Score ${analytics.summary.overallScore} below minimum threshold ${minScore}
|
|
665
|
+
`
|
|
666
|
+
)
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
success: !options.ci || passedCi,
|
|
672
|
+
analytics,
|
|
673
|
+
outputPath
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function printConsoleSummary(analytics) {
|
|
677
|
+
const { summary, coverage, recommendations } = analytics;
|
|
678
|
+
const grade = getGrade(summary.overallScore);
|
|
679
|
+
console.log(
|
|
680
|
+
pc3.bold(
|
|
681
|
+
`Overall Score: ${colorizeScore(summary.overallScore)} (${grade})
|
|
682
|
+
`
|
|
683
|
+
)
|
|
684
|
+
);
|
|
685
|
+
console.log(pc3.dim("Summary"));
|
|
686
|
+
console.log(` Components: ${pc3.white(summary.totalComponents.toString())}`);
|
|
687
|
+
console.log(` Variants: ${pc3.white(summary.totalVariants.toString())}`);
|
|
688
|
+
console.log(` Props: ${pc3.white(summary.totalProps.toString())}`);
|
|
689
|
+
console.log(` Categories: ${pc3.white(summary.categories.join(", "))}`);
|
|
690
|
+
console.log();
|
|
691
|
+
console.log(pc3.dim("Coverage"));
|
|
692
|
+
console.log(` Description: ${formatCoverage(coverage.fields.description)}`);
|
|
693
|
+
console.log(` Usage when: ${formatCoverage(coverage.fields.usageWhen)}`);
|
|
694
|
+
console.log(` Usage whenNot:${formatCoverage(coverage.fields.usageWhenNot)}`);
|
|
695
|
+
console.log(` Guidelines: ${formatCoverage(coverage.fields.guidelines)}`);
|
|
696
|
+
console.log(` Relations: ${formatCoverage(coverage.fields.relations)}`);
|
|
697
|
+
console.log();
|
|
698
|
+
if (recommendations.length > 0) {
|
|
699
|
+
console.log(pc3.dim("Top Recommendations"));
|
|
700
|
+
for (const rec of recommendations.slice(0, 3)) {
|
|
701
|
+
const priority = rec.priority === "high" ? pc3.red(`[${rec.priority}]`) : rec.priority === "medium" ? pc3.yellow(`[${rec.priority}]`) : pc3.dim(`[${rec.priority}]`);
|
|
702
|
+
console.log(` ${priority} ${rec.title}`);
|
|
703
|
+
}
|
|
704
|
+
console.log();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function formatCoverage(field) {
|
|
708
|
+
const pct = colorizeScore(field.percentage);
|
|
709
|
+
return `${pct} (${field.covered}/${field.total})`;
|
|
710
|
+
}
|
|
711
|
+
function colorizeScore(score) {
|
|
712
|
+
if (score >= 80) return pc3.green(`${score}%`);
|
|
713
|
+
if (score >= 60) return pc3.yellow(`${score}%`);
|
|
714
|
+
return pc3.red(`${score}%`);
|
|
715
|
+
}
|
|
716
|
+
function getDefaultOutputPath(format, configDir) {
|
|
717
|
+
const filename = format === "html" ? "segments-report.html" : "segments-report.json";
|
|
718
|
+
return join2(configDir, filename);
|
|
719
|
+
}
|
|
720
|
+
async function openInBrowser(path) {
|
|
721
|
+
const { platform } = await import("os");
|
|
722
|
+
const { exec } = await import("child_process");
|
|
723
|
+
const os = platform();
|
|
724
|
+
const cmd = os === "darwin" ? `open "${path}"` : os === "win32" ? `start "" "${path}"` : `xdg-open "${path}"`;
|
|
725
|
+
exec(cmd);
|
|
726
|
+
}
|
|
727
|
+
function createEmptyAnalytics() {
|
|
728
|
+
return {
|
|
729
|
+
analyzedAt: /* @__PURE__ */ new Date(),
|
|
730
|
+
summary: {
|
|
731
|
+
totalComponents: 0,
|
|
732
|
+
totalVariants: 0,
|
|
733
|
+
totalProps: 0,
|
|
734
|
+
categories: [],
|
|
735
|
+
overallScore: 0
|
|
736
|
+
},
|
|
737
|
+
inventory: {
|
|
738
|
+
byCategory: {},
|
|
739
|
+
byStatus: {},
|
|
740
|
+
byVariantCount: [],
|
|
741
|
+
byPropCount: []
|
|
742
|
+
},
|
|
743
|
+
coverage: {
|
|
744
|
+
overall: 0,
|
|
745
|
+
fields: {
|
|
746
|
+
description: { covered: 0, total: 0, percentage: 0 },
|
|
747
|
+
usageWhen: { covered: 0, total: 0, percentage: 0 },
|
|
748
|
+
usageWhenNot: { covered: 0, total: 0, percentage: 0 },
|
|
749
|
+
guidelines: { covered: 0, total: 0, percentage: 0 },
|
|
750
|
+
accessibility: { covered: 0, total: 0, percentage: 0 },
|
|
751
|
+
relations: { covered: 0, total: 0, percentage: 0 },
|
|
752
|
+
propDescriptions: { covered: 0, total: 0, percentage: 0 },
|
|
753
|
+
propConstraints: { covered: 0, total: 0, percentage: 0 }
|
|
754
|
+
},
|
|
755
|
+
incomplete: []
|
|
756
|
+
},
|
|
757
|
+
quality: {
|
|
758
|
+
missingWhenNot: [],
|
|
759
|
+
isolated: [],
|
|
760
|
+
deprecated: [],
|
|
761
|
+
fewVariants: [],
|
|
762
|
+
undocumentedProps: [],
|
|
763
|
+
unconstrainedProps: []
|
|
764
|
+
},
|
|
765
|
+
distribution: {
|
|
766
|
+
variantsPerComponent: [],
|
|
767
|
+
propsPerComponent: [],
|
|
768
|
+
componentsPerCategory: [],
|
|
769
|
+
statusDistribution: [],
|
|
770
|
+
tagFrequency: []
|
|
771
|
+
},
|
|
772
|
+
recommendations: []
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
export {
|
|
777
|
+
validateSchema,
|
|
778
|
+
validateCoverage,
|
|
779
|
+
validateAll,
|
|
780
|
+
buildSegments,
|
|
781
|
+
buildFragmentsDir,
|
|
782
|
+
runScreenshotCommand,
|
|
783
|
+
runDiffCommand,
|
|
784
|
+
runAnalyzeCommand
|
|
785
|
+
};
|
|
786
|
+
//# sourceMappingURL=chunk-4FDQSGKX.js.map
|