@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/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fragments-sdk/cli",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "CLI, MCP server, and dev tools for Fragments design system",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"fragments": "./dist/bin.js",
|
|
8
|
+
"fragments-mcp": "./dist/mcp-bin.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"module": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./mcp": {
|
|
19
|
+
"types": "./dist/mcp/index.d.ts",
|
|
20
|
+
"import": "./dist/mcp/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@anthropic-ai/sdk": "^0.71.2",
|
|
32
|
+
"@babel/generator": "^7.23.6",
|
|
33
|
+
"@babel/parser": "^7.23.6",
|
|
34
|
+
"@babel/traverse": "^7.23.6",
|
|
35
|
+
"@babel/types": "^7.23.6",
|
|
36
|
+
"@figma/rest-api-spec": "^0.35.0",
|
|
37
|
+
"@hookform/resolvers": "^5.2.2",
|
|
38
|
+
"@inquirer/prompts": "^7.2.1",
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
40
|
+
"@monaco-editor/react": "^4.7.0",
|
|
41
|
+
"@storybook/csf": "^0.1.12",
|
|
42
|
+
"@tanstack/react-virtual": "^3.13.18",
|
|
43
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
44
|
+
"autoprefixer": "^10.4.20",
|
|
45
|
+
"axe-core": "^4.11.1",
|
|
46
|
+
"chokidar": "^4.0.3",
|
|
47
|
+
"clsx": "^2.1.1",
|
|
48
|
+
"commander": "^12.1.0",
|
|
49
|
+
"esbuild": "^0.24.0",
|
|
50
|
+
"fast-glob": "^3.3.3",
|
|
51
|
+
"html2canvas": "^1.4.1",
|
|
52
|
+
"jiti": "^2.6.1",
|
|
53
|
+
"monaco-editor": "^0.55.1",
|
|
54
|
+
"openai": "^6.16.0",
|
|
55
|
+
"picocolors": "^1.1.1",
|
|
56
|
+
"pixelmatch": "^5.3.0",
|
|
57
|
+
"pngjs": "^7.0.0",
|
|
58
|
+
"postcss": "^8.4.49",
|
|
59
|
+
"react-colorful": "^5.6.1",
|
|
60
|
+
"react-hook-form": "^7.71.0",
|
|
61
|
+
"react-live": "^4.1.6",
|
|
62
|
+
"shiki": "^3.21.0",
|
|
63
|
+
"tailwindcss": "^3.4.17",
|
|
64
|
+
"vite": "^6.0.0",
|
|
65
|
+
"vite-plugin-svgr": "^4.5.0",
|
|
66
|
+
"zod": "^3.24.1"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/babel__generator": "^7.6.8",
|
|
70
|
+
"@types/babel__traverse": "^7.20.5",
|
|
71
|
+
"@types/node": "^22.0.0",
|
|
72
|
+
"@types/pixelmatch": "^5.2.6",
|
|
73
|
+
"@types/pngjs": "^6.0.5",
|
|
74
|
+
"@types/react": "^18.3.0",
|
|
75
|
+
"@types/react-dom": "^18.3.0",
|
|
76
|
+
"react": "^18.3.0",
|
|
77
|
+
"react-dom": "^18.3.0",
|
|
78
|
+
"sass": "^1.83.0",
|
|
79
|
+
"tsup": "^8.3.5",
|
|
80
|
+
"typescript": "^5.7.2",
|
|
81
|
+
"vitest": "^2.1.8"
|
|
82
|
+
},
|
|
83
|
+
"peerDependencies": {
|
|
84
|
+
"playwright": "^1.40.0",
|
|
85
|
+
"react": ">=18",
|
|
86
|
+
"react-dom": ">=18",
|
|
87
|
+
"typescript": "^5.0.0"
|
|
88
|
+
},
|
|
89
|
+
"peerDependenciesMeta": {
|
|
90
|
+
"playwright": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
93
|
+
"react": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"react-dom": {
|
|
97
|
+
"optional": true
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"scripts": {
|
|
101
|
+
"build": "tsup",
|
|
102
|
+
"dev": "tsup --watch",
|
|
103
|
+
"test": "vitest run",
|
|
104
|
+
"typecheck": "tsc --noEmit",
|
|
105
|
+
"clean": "rm -rf dist"
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/ai.ts
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import type { CompiledSegment, CompiledSegmentsFile } from "./core/index.js";
|
|
2
|
+
import { generateContext } from "./core/index.js";
|
|
3
|
+
|
|
4
|
+
export interface AiSuggestOptions {
|
|
5
|
+
/** The user's prompt describing what they want to build */
|
|
6
|
+
prompt: string;
|
|
7
|
+
/** Optional additional context */
|
|
8
|
+
context?: string;
|
|
9
|
+
/** Compiled segments to use */
|
|
10
|
+
segments: CompiledSegment[];
|
|
11
|
+
/** Whether to stream output */
|
|
12
|
+
stream?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AiSuggestResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
response?: string;
|
|
18
|
+
error?: string;
|
|
19
|
+
tokensUsed?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Call AI API to get component suggestions based on design system context.
|
|
24
|
+
* Supports both Anthropic and OpenAI APIs via environment variables.
|
|
25
|
+
*/
|
|
26
|
+
export async function aiSuggest(options: AiSuggestOptions): Promise<AiSuggestResult> {
|
|
27
|
+
const { prompt, context, segments, stream = true } = options;
|
|
28
|
+
|
|
29
|
+
// Check for API keys
|
|
30
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
31
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
32
|
+
|
|
33
|
+
if (!anthropicKey && !openaiKey) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: "No API key found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable.",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Generate design system context
|
|
41
|
+
const { content: systemContext } = generateContext(segments, {
|
|
42
|
+
format: "markdown",
|
|
43
|
+
compact: false,
|
|
44
|
+
include: {
|
|
45
|
+
props: true,
|
|
46
|
+
variants: true,
|
|
47
|
+
usage: true,
|
|
48
|
+
code: true,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Build the system prompt
|
|
53
|
+
const systemPrompt = `You are a UI developer assistant helping to build interfaces using a specific design system.
|
|
54
|
+
|
|
55
|
+
You have access to the following design system components:
|
|
56
|
+
|
|
57
|
+
${systemContext}
|
|
58
|
+
|
|
59
|
+
When suggesting components:
|
|
60
|
+
1. ONLY use components from this design system - never suggest generic HTML or components not listed above
|
|
61
|
+
2. Explain WHY each component is appropriate for the use case
|
|
62
|
+
3. Show complete, ready-to-use code examples with correct props
|
|
63
|
+
4. Follow the "when to use" and "when NOT to use" guidelines for each component
|
|
64
|
+
5. Consider accessibility and best practices
|
|
65
|
+
6. If multiple approaches are valid, recommend the simplest one first
|
|
66
|
+
|
|
67
|
+
Format your response as:
|
|
68
|
+
1. A brief explanation of the approach
|
|
69
|
+
2. Code example(s) with comments
|
|
70
|
+
3. Any important notes about props or customization`;
|
|
71
|
+
|
|
72
|
+
// Build the user prompt
|
|
73
|
+
const userPrompt = context
|
|
74
|
+
? `${prompt}\n\nAdditional context: ${context}`
|
|
75
|
+
: prompt;
|
|
76
|
+
|
|
77
|
+
// Prefer Anthropic, fallback to OpenAI
|
|
78
|
+
if (anthropicKey) {
|
|
79
|
+
return callAnthropic(anthropicKey, systemPrompt, userPrompt, stream);
|
|
80
|
+
} else {
|
|
81
|
+
return callOpenAI(openaiKey!, systemPrompt, userPrompt, stream);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Call Anthropic's Claude API
|
|
87
|
+
*/
|
|
88
|
+
async function callAnthropic(
|
|
89
|
+
apiKey: string,
|
|
90
|
+
systemPrompt: string,
|
|
91
|
+
userPrompt: string,
|
|
92
|
+
stream: boolean
|
|
93
|
+
): Promise<AiSuggestResult> {
|
|
94
|
+
try {
|
|
95
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
"x-api-key": apiKey,
|
|
100
|
+
"anthropic-version": "2023-06-01",
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
model: "claude-sonnet-4-20250514",
|
|
104
|
+
max_tokens: 4096,
|
|
105
|
+
system: systemPrompt,
|
|
106
|
+
messages: [
|
|
107
|
+
{
|
|
108
|
+
role: "user",
|
|
109
|
+
content: userPrompt,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
stream: stream,
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const error = await response.text();
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
error: `Anthropic API error: ${response.status} - ${error}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (stream && response.body) {
|
|
125
|
+
// Stream the response
|
|
126
|
+
const reader = response.body.getReader();
|
|
127
|
+
const decoder = new TextDecoder();
|
|
128
|
+
let fullResponse = "";
|
|
129
|
+
|
|
130
|
+
while (true) {
|
|
131
|
+
const { done, value } = await reader.read();
|
|
132
|
+
if (done) break;
|
|
133
|
+
|
|
134
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
135
|
+
const lines = chunk.split("\n");
|
|
136
|
+
|
|
137
|
+
for (const line of lines) {
|
|
138
|
+
if (line.startsWith("data: ")) {
|
|
139
|
+
const data = line.slice(6);
|
|
140
|
+
if (data === "[DONE]") continue;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(data);
|
|
144
|
+
if (parsed.type === "content_block_delta" && parsed.delta?.text) {
|
|
145
|
+
process.stdout.write(parsed.delta.text);
|
|
146
|
+
fullResponse += parsed.delta.text;
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// Skip malformed JSON
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(); // Final newline
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
response: fullResponse,
|
|
159
|
+
};
|
|
160
|
+
} else {
|
|
161
|
+
const data = await response.json();
|
|
162
|
+
const text = data.content?.[0]?.text ?? "";
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
response: text,
|
|
167
|
+
tokensUsed: data.usage?.input_tokens + data.usage?.output_tokens,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
error: `Failed to call Anthropic API: ${error instanceof Error ? error.message : error}`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Call OpenAI's API
|
|
180
|
+
*/
|
|
181
|
+
async function callOpenAI(
|
|
182
|
+
apiKey: string,
|
|
183
|
+
systemPrompt: string,
|
|
184
|
+
userPrompt: string,
|
|
185
|
+
stream: boolean
|
|
186
|
+
): Promise<AiSuggestResult> {
|
|
187
|
+
try {
|
|
188
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: {
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
Authorization: `Bearer ${apiKey}`,
|
|
193
|
+
},
|
|
194
|
+
body: JSON.stringify({
|
|
195
|
+
model: "gpt-4o",
|
|
196
|
+
messages: [
|
|
197
|
+
{ role: "system", content: systemPrompt },
|
|
198
|
+
{ role: "user", content: userPrompt },
|
|
199
|
+
],
|
|
200
|
+
max_tokens: 4096,
|
|
201
|
+
stream: stream,
|
|
202
|
+
}),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
const error = await response.text();
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
error: `OpenAI API error: ${response.status} - ${error}`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (stream && response.body) {
|
|
214
|
+
// Stream the response
|
|
215
|
+
const reader = response.body.getReader();
|
|
216
|
+
const decoder = new TextDecoder();
|
|
217
|
+
let fullResponse = "";
|
|
218
|
+
|
|
219
|
+
while (true) {
|
|
220
|
+
const { done, value } = await reader.read();
|
|
221
|
+
if (done) break;
|
|
222
|
+
|
|
223
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
224
|
+
const lines = chunk.split("\n");
|
|
225
|
+
|
|
226
|
+
for (const line of lines) {
|
|
227
|
+
if (line.startsWith("data: ")) {
|
|
228
|
+
const data = line.slice(6);
|
|
229
|
+
if (data === "[DONE]") continue;
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const parsed = JSON.parse(data);
|
|
233
|
+
const content = parsed.choices?.[0]?.delta?.content;
|
|
234
|
+
if (content) {
|
|
235
|
+
process.stdout.write(content);
|
|
236
|
+
fullResponse += content;
|
|
237
|
+
}
|
|
238
|
+
} catch {
|
|
239
|
+
// Skip malformed JSON
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
console.log(); // Final newline
|
|
246
|
+
return {
|
|
247
|
+
success: true,
|
|
248
|
+
response: fullResponse,
|
|
249
|
+
};
|
|
250
|
+
} else {
|
|
251
|
+
const data = await response.json();
|
|
252
|
+
const text = data.choices?.[0]?.message?.content ?? "";
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
response: text,
|
|
257
|
+
tokensUsed: data.usage?.total_tokens,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
error: `Failed to call OpenAI API: ${error instanceof Error ? error.message : error}`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
package/src/analyze.ts
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: segments analyze
|
|
3
|
+
*
|
|
4
|
+
* Analyzes the design system and generates an HTML report with:
|
|
5
|
+
* - Component inventory
|
|
6
|
+
* - Documentation coverage
|
|
7
|
+
* - Quality insights
|
|
8
|
+
* - Actionable recommendations
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import pc from "picocolors";
|
|
15
|
+
import type { SegmentsConfig, CompiledSegmentsFile } from "./core/index.js";
|
|
16
|
+
import { BRAND, DEFAULTS } from "./core/index.js";
|
|
17
|
+
import {
|
|
18
|
+
analyzeDesignSystem,
|
|
19
|
+
generateHtmlReport,
|
|
20
|
+
getGrade,
|
|
21
|
+
getScoreColor,
|
|
22
|
+
type DesignSystemAnalytics,
|
|
23
|
+
} from "./service/index.js";
|
|
24
|
+
|
|
25
|
+
export interface AnalyzeOptions {
|
|
26
|
+
/** Output format */
|
|
27
|
+
format?: "html" | "json" | "console";
|
|
28
|
+
/** Output file path (default: segments-report.html) */
|
|
29
|
+
output?: string;
|
|
30
|
+
/** Open report in browser after generation */
|
|
31
|
+
open?: boolean;
|
|
32
|
+
/** CI mode - exit with appropriate code */
|
|
33
|
+
ci?: boolean;
|
|
34
|
+
/** Minimum score to pass in CI mode */
|
|
35
|
+
minScore?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AnalyzeResult {
|
|
39
|
+
success: boolean;
|
|
40
|
+
analytics: DesignSystemAnalytics;
|
|
41
|
+
outputPath?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run the analyze command
|
|
46
|
+
*/
|
|
47
|
+
export async function runAnalyzeCommand(
|
|
48
|
+
config: SegmentsConfig,
|
|
49
|
+
configDir: string,
|
|
50
|
+
options: AnalyzeOptions = {}
|
|
51
|
+
): Promise<AnalyzeResult> {
|
|
52
|
+
const format = options.format ?? "html";
|
|
53
|
+
const minScore = options.minScore ?? 0;
|
|
54
|
+
|
|
55
|
+
console.log(pc.cyan(`\n${BRAND.name} Analyzer\n`));
|
|
56
|
+
|
|
57
|
+
// Load compiled segments
|
|
58
|
+
const segmentsPath = join(configDir, config.outFile ?? "segments.json");
|
|
59
|
+
|
|
60
|
+
if (!existsSync(segmentsPath)) {
|
|
61
|
+
console.log(pc.red(`✗ No segments.json found. Run \`${BRAND.cliCommand} build\` first.\n`));
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
analytics: createEmptyAnalytics(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(pc.dim("Analyzing design system...\n"));
|
|
69
|
+
|
|
70
|
+
const content = await readFile(segmentsPath, "utf-8");
|
|
71
|
+
const data: CompiledSegmentsFile = JSON.parse(content);
|
|
72
|
+
|
|
73
|
+
// Run analysis
|
|
74
|
+
const analytics = analyzeDesignSystem(data);
|
|
75
|
+
|
|
76
|
+
// Print summary to console
|
|
77
|
+
printConsoleSummary(analytics);
|
|
78
|
+
|
|
79
|
+
// Generate output based on format
|
|
80
|
+
let outputPath: string | undefined;
|
|
81
|
+
|
|
82
|
+
if (format === "html" || format === "json") {
|
|
83
|
+
outputPath = options.output ?? getDefaultOutputPath(format, configDir);
|
|
84
|
+
|
|
85
|
+
// Ensure output directory exists
|
|
86
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
87
|
+
|
|
88
|
+
if (format === "html") {
|
|
89
|
+
const html = generateHtmlReport(analytics);
|
|
90
|
+
await writeFile(outputPath, html);
|
|
91
|
+
console.log(pc.green(`✓ Report generated: ${outputPath}\n`));
|
|
92
|
+
} else {
|
|
93
|
+
await writeFile(outputPath, JSON.stringify(analytics, null, 2));
|
|
94
|
+
console.log(pc.green(`✓ JSON report generated: ${outputPath}\n`));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Open in browser if requested
|
|
98
|
+
if (options.open && format === "html") {
|
|
99
|
+
await openInBrowser(outputPath);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// CI mode - check score
|
|
104
|
+
const passedCi = analytics.summary.overallScore >= minScore;
|
|
105
|
+
|
|
106
|
+
if (options.ci) {
|
|
107
|
+
if (passedCi) {
|
|
108
|
+
console.log(
|
|
109
|
+
pc.green(`✓ Score ${analytics.summary.overallScore} meets minimum threshold ${minScore}\n`)
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
console.log(
|
|
113
|
+
pc.red(
|
|
114
|
+
`✗ Score ${analytics.summary.overallScore} below minimum threshold ${minScore}\n`
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
success: !options.ci || passedCi,
|
|
122
|
+
analytics,
|
|
123
|
+
outputPath,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Print a summary to the console
|
|
129
|
+
*/
|
|
130
|
+
function printConsoleSummary(analytics: DesignSystemAnalytics): void {
|
|
131
|
+
const { summary, coverage, recommendations } = analytics;
|
|
132
|
+
const grade = getGrade(summary.overallScore);
|
|
133
|
+
|
|
134
|
+
// Score header
|
|
135
|
+
console.log(
|
|
136
|
+
pc.bold(
|
|
137
|
+
`Overall Score: ${colorizeScore(summary.overallScore)} (${grade})\n`
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Summary stats
|
|
142
|
+
console.log(pc.dim("Summary"));
|
|
143
|
+
console.log(` Components: ${pc.white(summary.totalComponents.toString())}`);
|
|
144
|
+
console.log(` Variants: ${pc.white(summary.totalVariants.toString())}`);
|
|
145
|
+
console.log(` Props: ${pc.white(summary.totalProps.toString())}`);
|
|
146
|
+
console.log(` Categories: ${pc.white(summary.categories.join(", "))}`);
|
|
147
|
+
console.log();
|
|
148
|
+
|
|
149
|
+
// Coverage
|
|
150
|
+
console.log(pc.dim("Coverage"));
|
|
151
|
+
console.log(` Description: ${formatCoverage(coverage.fields.description)}`);
|
|
152
|
+
console.log(` Usage when: ${formatCoverage(coverage.fields.usageWhen)}`);
|
|
153
|
+
console.log(` Usage whenNot:${formatCoverage(coverage.fields.usageWhenNot)}`);
|
|
154
|
+
console.log(` Guidelines: ${formatCoverage(coverage.fields.guidelines)}`);
|
|
155
|
+
console.log(` Relations: ${formatCoverage(coverage.fields.relations)}`);
|
|
156
|
+
console.log();
|
|
157
|
+
|
|
158
|
+
// Top recommendations
|
|
159
|
+
if (recommendations.length > 0) {
|
|
160
|
+
console.log(pc.dim("Top Recommendations"));
|
|
161
|
+
for (const rec of recommendations.slice(0, 3)) {
|
|
162
|
+
const priority = rec.priority === "high"
|
|
163
|
+
? pc.red(`[${rec.priority}]`)
|
|
164
|
+
: rec.priority === "medium"
|
|
165
|
+
? pc.yellow(`[${rec.priority}]`)
|
|
166
|
+
: pc.dim(`[${rec.priority}]`);
|
|
167
|
+
console.log(` ${priority} ${rec.title}`);
|
|
168
|
+
}
|
|
169
|
+
console.log();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Format coverage percentage with color
|
|
175
|
+
*/
|
|
176
|
+
function formatCoverage(field: { percentage: number; covered: number; total: number }): string {
|
|
177
|
+
const pct = colorizeScore(field.percentage);
|
|
178
|
+
return `${pct} (${field.covered}/${field.total})`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Colorize a score
|
|
183
|
+
*/
|
|
184
|
+
function colorizeScore(score: number): string {
|
|
185
|
+
if (score >= 80) return pc.green(`${score}%`);
|
|
186
|
+
if (score >= 60) return pc.yellow(`${score}%`);
|
|
187
|
+
return pc.red(`${score}%`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get default output path
|
|
192
|
+
*/
|
|
193
|
+
function getDefaultOutputPath(format: "html" | "json", configDir: string): string {
|
|
194
|
+
const filename = format === "html" ? "segments-report.html" : "segments-report.json";
|
|
195
|
+
return join(configDir, filename);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Open file in default browser
|
|
200
|
+
*/
|
|
201
|
+
async function openInBrowser(path: string): Promise<void> {
|
|
202
|
+
const { platform } = await import("node:os");
|
|
203
|
+
const { exec } = await import("node:child_process");
|
|
204
|
+
|
|
205
|
+
const os = platform();
|
|
206
|
+
const cmd = os === "darwin"
|
|
207
|
+
? `open "${path}"`
|
|
208
|
+
: os === "win32"
|
|
209
|
+
? `start "" "${path}"`
|
|
210
|
+
: `xdg-open "${path}"`;
|
|
211
|
+
|
|
212
|
+
exec(cmd);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Create empty analytics for error cases
|
|
217
|
+
*/
|
|
218
|
+
function createEmptyAnalytics(): DesignSystemAnalytics {
|
|
219
|
+
return {
|
|
220
|
+
analyzedAt: new Date(),
|
|
221
|
+
summary: {
|
|
222
|
+
totalComponents: 0,
|
|
223
|
+
totalVariants: 0,
|
|
224
|
+
totalProps: 0,
|
|
225
|
+
categories: [],
|
|
226
|
+
overallScore: 0,
|
|
227
|
+
},
|
|
228
|
+
inventory: {
|
|
229
|
+
byCategory: {},
|
|
230
|
+
byStatus: {},
|
|
231
|
+
byVariantCount: [],
|
|
232
|
+
byPropCount: [],
|
|
233
|
+
},
|
|
234
|
+
coverage: {
|
|
235
|
+
overall: 0,
|
|
236
|
+
fields: {
|
|
237
|
+
description: { covered: 0, total: 0, percentage: 0 },
|
|
238
|
+
usageWhen: { covered: 0, total: 0, percentage: 0 },
|
|
239
|
+
usageWhenNot: { covered: 0, total: 0, percentage: 0 },
|
|
240
|
+
guidelines: { covered: 0, total: 0, percentage: 0 },
|
|
241
|
+
accessibility: { covered: 0, total: 0, percentage: 0 },
|
|
242
|
+
relations: { covered: 0, total: 0, percentage: 0 },
|
|
243
|
+
propDescriptions: { covered: 0, total: 0, percentage: 0 },
|
|
244
|
+
propConstraints: { covered: 0, total: 0, percentage: 0 },
|
|
245
|
+
},
|
|
246
|
+
incomplete: [],
|
|
247
|
+
},
|
|
248
|
+
quality: {
|
|
249
|
+
missingWhenNot: [],
|
|
250
|
+
isolated: [],
|
|
251
|
+
deprecated: [],
|
|
252
|
+
fewVariants: [],
|
|
253
|
+
undocumentedProps: [],
|
|
254
|
+
unconstrainedProps: [],
|
|
255
|
+
},
|
|
256
|
+
distribution: {
|
|
257
|
+
variantsPerComponent: [],
|
|
258
|
+
propsPerComponent: [],
|
|
259
|
+
componentsPerCategory: [],
|
|
260
|
+
statusDistribution: [],
|
|
261
|
+
tagFrequency: [],
|
|
262
|
+
},
|
|
263
|
+
recommendations: [],
|
|
264
|
+
};
|
|
265
|
+
}
|