@fragments-sdk/cli 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/bin.js +996 -79
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
  4. package/dist/chunk-6JBGU74P.js.map +1 -0
  5. package/dist/chunk-7OPWMLOE.js +1625 -0
  6. package/dist/chunk-7OPWMLOE.js.map +1 -0
  7. package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
  8. package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
  9. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
  12. package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
  13. package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
  14. package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
  15. package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +15 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
  20. package/dist/mcp-bin.js +8 -220
  21. package/dist/mcp-bin.js.map +1 -1
  22. package/dist/scan-WY23TJCP.js +12 -0
  23. package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
  24. package/dist/static-viewer-GBR7YNF3.js +12 -0
  25. package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
  26. package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
  27. package/dist/viewer-SUFOISZM.js +1822 -0
  28. package/dist/viewer-SUFOISZM.js.map +1 -0
  29. package/package.json +6 -5
  30. package/src/bin.ts +31 -0
  31. package/src/build.ts +147 -13
  32. package/src/cli-commands.ts +18 -0
  33. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  34. package/src/commands/a11y-report.ts +625 -0
  35. package/src/commands/a11y.ts +168 -14
  36. package/src/commands/build.ts +16 -0
  37. package/src/commands/graph.ts +274 -0
  38. package/src/core/auto-props.ts +464 -0
  39. package/src/core/composition.ts +64 -1
  40. package/src/core/graph-extractor.test.ts +542 -0
  41. package/src/core/graph-extractor.ts +601 -0
  42. package/src/core/importAnalyzer.ts +5 -0
  43. package/src/core/schema.ts +2 -0
  44. package/src/core/types.ts +3 -1
  45. package/src/index.ts +4 -0
  46. package/src/mcp/server.ts +13 -220
  47. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  48. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  49. package/src/theme/contrast.test.ts +331 -0
  50. package/src/theme/contrast.ts +246 -0
  51. package/src/theme/generator.ts +213 -1
  52. package/src/theme/index.ts +16 -0
  53. package/src/theme/types.ts +51 -0
  54. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  55. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  56. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  57. package/src/viewer/components/ActionCapture.tsx +1 -1
  58. package/src/viewer/components/ActionsPanel.tsx +142 -183
  59. package/src/viewer/components/App.tsx +276 -183
  60. package/src/viewer/components/BottomPanel.tsx +40 -80
  61. package/src/viewer/components/CodePanel.tsx +9 -87
  62. package/src/viewer/components/CommandPalette.tsx +117 -74
  63. package/src/viewer/components/ComponentGraph.tsx +143 -126
  64. package/src/viewer/components/ComponentHeader.tsx +46 -43
  65. package/src/viewer/components/ContractPanel.tsx +124 -117
  66. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  67. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  68. package/src/viewer/components/FragmentEditor.tsx +126 -63
  69. package/src/viewer/components/HealthDashboard.tsx +146 -171
  70. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  71. package/src/viewer/components/Icons.tsx +151 -98
  72. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  73. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  74. package/src/viewer/components/IsolatedRender.tsx +12 -6
  75. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  76. package/src/viewer/components/LandingPage.tsx +285 -305
  77. package/src/viewer/components/Layout.tsx +12 -10
  78. package/src/viewer/components/LeftSidebar.tsx +103 -155
  79. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  80. package/src/viewer/components/PreviewArea.tsx +113 -44
  81. package/src/viewer/components/PreviewFrameHost.tsx +36 -6
  82. package/src/viewer/components/PreviewPane.tsx +2 -3
  83. package/src/viewer/components/PreviewToolbar.tsx +109 -105
  84. package/src/viewer/components/PropsEditor.tsx +154 -74
  85. package/src/viewer/components/PropsTable.tsx +95 -82
  86. package/src/viewer/components/RelationsSection.tsx +71 -40
  87. package/src/viewer/components/ResizablePanel.tsx +158 -55
  88. package/src/viewer/components/RightSidebar.tsx +46 -56
  89. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  90. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  91. package/src/viewer/components/StoryRenderer.tsx +4 -11
  92. package/src/viewer/components/Toast.tsx +3 -67
  93. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  94. package/src/viewer/components/UsageSection.tsx +26 -26
  95. package/src/viewer/components/VariantMatrix.tsx +140 -47
  96. package/src/viewer/components/VariantTabs.tsx +24 -68
  97. package/src/viewer/components/ViewportSelector.tsx +121 -114
  98. package/src/viewer/constants/ui.ts +23 -22
  99. package/src/viewer/entry.tsx +8 -3
  100. package/src/viewer/index.ts +3 -6
  101. package/src/viewer/preview-frame.html +43 -18
  102. package/src/viewer/server.ts +7 -16
  103. package/src/viewer/styles/globals.css +46 -85
  104. package/src/viewer/utils/a11y-fixes.ts +53 -30
  105. package/dist/chunk-ICAIQ57V.js.map +0 -1
  106. package/dist/chunk-U4GQ2JTD.js +0 -832
  107. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  108. package/dist/scan-ESEXV7LF.js +0 -12
  109. package/dist/static-viewer-O37MJ5B6.js +0 -12
  110. package/dist/viewer-YDGFDTK5.js +0 -11104
  111. package/dist/viewer-YDGFDTK5.js.map +0 -1
  112. package/src/viewer/postcss.config.js +0 -6
  113. package/src/viewer/tailwind.config.js +0 -37
  114. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  115. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  116. /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
  117. /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
  118. /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
  119. /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
  120. /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
  121. /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
  122. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
  123. /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
  124. /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
@@ -0,0 +1,1625 @@
1
+ import { createRequire as __banner_createRequire } from 'module'; const require = __banner_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-NWQ4CJOQ.js";
12
+ import {
13
+ discoverBlockFiles,
14
+ discoverComponentFiles,
15
+ discoverSegmentFiles,
16
+ discoverTokenFiles,
17
+ extractComponentName,
18
+ generateContextMd,
19
+ generateRegistry,
20
+ loadSegmentFile,
21
+ parseSegmentFile
22
+ } from "./chunk-CVXKXVOY.js";
23
+ import {
24
+ compileBlock,
25
+ parseTokenFile
26
+ } from "./chunk-TJ34N7C7.js";
27
+ import {
28
+ BRAND,
29
+ DEFAULTS,
30
+ segmentDefinitionSchema
31
+ } from "./chunk-6JBGU74P.js";
32
+
33
+ // src/validators.ts
34
+ async function validateSchema(config, configDir) {
35
+ const files = await discoverSegmentFiles(config, configDir);
36
+ const errors = [];
37
+ const warnings = [];
38
+ for (const file of files) {
39
+ try {
40
+ const segment = await loadSegmentFile(file.absolutePath);
41
+ if (!segment) {
42
+ errors.push({
43
+ file: file.relativePath,
44
+ message: "No default export found",
45
+ details: `Segment files must have a default export from defineSegment()`
46
+ });
47
+ continue;
48
+ }
49
+ const result = segmentDefinitionSchema.safeParse(segment);
50
+ if (!result.success) {
51
+ const details = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
52
+ errors.push({
53
+ file: file.relativePath,
54
+ message: "Invalid segment schema",
55
+ details
56
+ });
57
+ }
58
+ } catch (error) {
59
+ errors.push({
60
+ file: file.relativePath,
61
+ message: "Failed to load segment file",
62
+ details: error instanceof Error ? error.message : String(error)
63
+ });
64
+ }
65
+ }
66
+ return {
67
+ valid: errors.length === 0,
68
+ errors,
69
+ warnings
70
+ };
71
+ }
72
+ async function validateCoverage(config, configDir) {
73
+ const segmentFiles = await discoverSegmentFiles(config, configDir);
74
+ const componentFiles = await discoverComponentFiles(config, configDir);
75
+ const errors = [];
76
+ const warnings = [];
77
+ const documentedComponents = /* @__PURE__ */ new Set();
78
+ for (const file of segmentFiles) {
79
+ try {
80
+ const segment = await loadSegmentFile(file.absolutePath);
81
+ if (segment?.meta?.name) {
82
+ documentedComponents.add(segment.meta.name);
83
+ }
84
+ } catch {
85
+ }
86
+ }
87
+ for (const file of componentFiles) {
88
+ const componentName = extractComponentName(file.relativePath);
89
+ const segmentPath = file.relativePath.replace(
90
+ /\.(tsx?|jsx?)$/,
91
+ BRAND.fileExtension
92
+ );
93
+ const hasSegmentFile = segmentFiles.some(
94
+ (s) => s.relativePath === segmentPath
95
+ );
96
+ if (!hasSegmentFile && !documentedComponents.has(componentName)) {
97
+ warnings.push({
98
+ file: file.relativePath,
99
+ message: `Component "${componentName}" has no segment documentation`
100
+ });
101
+ }
102
+ }
103
+ return {
104
+ valid: errors.length === 0,
105
+ errors,
106
+ warnings
107
+ };
108
+ }
109
+ async function validateAll(config, configDir) {
110
+ const [schemaResult, coverageResult] = await Promise.all([
111
+ validateSchema(config, configDir),
112
+ validateCoverage(config, configDir)
113
+ ]);
114
+ return {
115
+ valid: schemaResult.valid && coverageResult.valid,
116
+ errors: [...schemaResult.errors, ...coverageResult.errors],
117
+ warnings: [...schemaResult.warnings, ...coverageResult.warnings]
118
+ };
119
+ }
120
+
121
+ // src/build.ts
122
+ import { readFile, writeFile, mkdir } from "fs/promises";
123
+ import { resolve as resolve3, join as join3 } from "path";
124
+ import { existsSync as existsSync3 } from "fs";
125
+
126
+ // src/core/auto-props.ts
127
+ import { existsSync, statSync } from "fs";
128
+ import { dirname, extname, join, resolve } from "path";
129
+ import ts from "typescript";
130
+ function toPosixPath(filePath) {
131
+ return filePath.replace(/\\/g, "/");
132
+ }
133
+ function isFile(filePath) {
134
+ if (!existsSync(filePath)) return false;
135
+ try {
136
+ return statSync(filePath).isFile();
137
+ } catch {
138
+ return false;
139
+ }
140
+ }
141
+ function resolveModulePath(basePath) {
142
+ const candidates = [];
143
+ const extension = extname(basePath);
144
+ if (extension) {
145
+ candidates.push(basePath);
146
+ } else {
147
+ candidates.push(
148
+ `${basePath}.tsx`,
149
+ `${basePath}.ts`,
150
+ `${basePath}.jsx`,
151
+ `${basePath}.js`,
152
+ join(basePath, "index.tsx"),
153
+ join(basePath, "index.ts"),
154
+ join(basePath, "index.jsx"),
155
+ join(basePath, "index.js")
156
+ );
157
+ }
158
+ for (const candidate of candidates) {
159
+ if (isFile(candidate)) {
160
+ return resolve(candidate);
161
+ }
162
+ }
163
+ return null;
164
+ }
165
+ function resolveComponentSourcePath(segmentFileAbsolutePath, componentImportPath) {
166
+ if (!componentImportPath) return null;
167
+ if (!componentImportPath.startsWith(".")) return null;
168
+ const segmentDir = dirname(segmentFileAbsolutePath);
169
+ const basePath = resolve(segmentDir, componentImportPath);
170
+ return resolveModulePath(basePath);
171
+ }
172
+ function collectTopLevelDeclarations(sourceFile) {
173
+ const typeDeclarations = /* @__PURE__ */ new Map();
174
+ const functionDeclarations = /* @__PURE__ */ new Map();
175
+ const variableDeclarations = /* @__PURE__ */ new Map();
176
+ for (const node of sourceFile.statements) {
177
+ if (ts.isInterfaceDeclaration(node)) {
178
+ typeDeclarations.set(node.name.text, node);
179
+ continue;
180
+ }
181
+ if (ts.isTypeAliasDeclaration(node)) {
182
+ typeDeclarations.set(node.name.text, node);
183
+ continue;
184
+ }
185
+ if (ts.isFunctionDeclaration(node) && node.name) {
186
+ functionDeclarations.set(node.name.text, node);
187
+ continue;
188
+ }
189
+ if (ts.isVariableStatement(node)) {
190
+ for (const declaration of node.declarationList.declarations) {
191
+ if (ts.isIdentifier(declaration.name)) {
192
+ variableDeclarations.set(declaration.name.text, declaration);
193
+ }
194
+ }
195
+ }
196
+ }
197
+ return { typeDeclarations, functionDeclarations, variableDeclarations };
198
+ }
199
+ function readDefaultValue(expression) {
200
+ if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {
201
+ return expression.text;
202
+ }
203
+ if (ts.isNumericLiteral(expression)) {
204
+ return Number(expression.text);
205
+ }
206
+ if (expression.kind === ts.SyntaxKind.TrueKeyword) return true;
207
+ if (expression.kind === ts.SyntaxKind.FalseKeyword) return false;
208
+ if (expression.kind === ts.SyntaxKind.NullKeyword) return null;
209
+ if (ts.isPrefixUnaryExpression(expression) && expression.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(expression.operand)) {
210
+ return -Number(expression.operand.text);
211
+ }
212
+ return void 0;
213
+ }
214
+ function extractDefaultValues(componentNode) {
215
+ const defaults = {};
216
+ if (!componentNode?.parameters?.length) return defaults;
217
+ const firstParam = componentNode.parameters[0];
218
+ if (!ts.isObjectBindingPattern(firstParam.name)) return defaults;
219
+ for (const element of firstParam.name.elements) {
220
+ let propName = null;
221
+ if (element.propertyName) {
222
+ if (ts.isIdentifier(element.propertyName) || ts.isStringLiteral(element.propertyName)) {
223
+ propName = element.propertyName.text;
224
+ }
225
+ } else if (ts.isIdentifier(element.name)) {
226
+ propName = element.name.text;
227
+ }
228
+ if (!propName || !element.initializer) continue;
229
+ const value = readDefaultValue(element.initializer);
230
+ if (value !== void 0) {
231
+ defaults[propName] = value;
232
+ }
233
+ }
234
+ return defaults;
235
+ }
236
+ function isNullishType(type) {
237
+ return (type.flags & ts.TypeFlags.Null) !== 0 || (type.flags & ts.TypeFlags.Undefined) !== 0 || (type.flags & ts.TypeFlags.Void) !== 0;
238
+ }
239
+ function isBooleanLikeType(type) {
240
+ return (type.flags & ts.TypeFlags.BooleanLike) !== 0 || type.flags === ts.TypeFlags.BooleanLiteral;
241
+ }
242
+ function inferPropType(type, checker) {
243
+ const typeText = checker.typeToString(type, void 0, ts.TypeFormatFlags.NoTruncation);
244
+ if (typeText.includes("ReactNode")) {
245
+ return { type: "node" };
246
+ }
247
+ if (typeText.includes("ReactElement") || typeText.includes("JSX.Element")) {
248
+ return { type: "element" };
249
+ }
250
+ if (type.getCallSignatures().length > 0) {
251
+ return { type: "function" };
252
+ }
253
+ if (checker.isArrayType(type) || checker.isTupleType(type)) {
254
+ return { type: "array" };
255
+ }
256
+ if (type.isUnion()) {
257
+ const nonNullableTypes = type.types.filter((unionType) => !isNullishType(unionType));
258
+ if (nonNullableTypes.length === 1) {
259
+ return inferPropType(nonNullableTypes[0], checker);
260
+ }
261
+ const stringLiteralValues = nonNullableTypes.filter((unionType) => (unionType.flags & ts.TypeFlags.StringLiteral) !== 0).map((unionType) => unionType.value);
262
+ if (stringLiteralValues.length > 0 && stringLiteralValues.length === nonNullableTypes.length) {
263
+ return { type: "enum", values: stringLiteralValues };
264
+ }
265
+ if (nonNullableTypes.every((unionType) => isBooleanLikeType(unionType))) {
266
+ return { type: "boolean" };
267
+ }
268
+ return { type: "union" };
269
+ }
270
+ if ((type.flags & ts.TypeFlags.StringLike) !== 0) {
271
+ return { type: "string" };
272
+ }
273
+ if ((type.flags & ts.TypeFlags.NumberLike) !== 0) {
274
+ return { type: "number" };
275
+ }
276
+ if ((type.flags & ts.TypeFlags.BooleanLike) !== 0) {
277
+ return { type: "boolean" };
278
+ }
279
+ if ((type.flags & ts.TypeFlags.Object) !== 0) {
280
+ return { type: "object" };
281
+ }
282
+ return { type: "custom" };
283
+ }
284
+ function resolveComponentSignature(exportName, declarations, sourceFile) {
285
+ const visitedNames = /* @__PURE__ */ new Set();
286
+ const typeNodeFromFunction = (node) => ({
287
+ propsTypeNode: node.parameters[0]?.type ?? null,
288
+ componentNode: node
289
+ });
290
+ const resolveFromExpression = (expression) => {
291
+ if (ts.isParenthesizedExpression(expression)) {
292
+ return resolveFromExpression(expression.expression);
293
+ }
294
+ if (ts.isAsExpression(expression) || ts.isTypeAssertionExpression(expression)) {
295
+ return resolveFromExpression(expression.expression);
296
+ }
297
+ if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
298
+ return typeNodeFromFunction(expression);
299
+ }
300
+ if (ts.isIdentifier(expression)) {
301
+ return resolveFromIdentifier(expression.text);
302
+ }
303
+ if (ts.isCallExpression(expression)) {
304
+ if (ts.isPropertyAccessExpression(expression.expression) && expression.expression.name.text === "forwardRef") {
305
+ const forwardRefPropsType = expression.typeArguments?.[1] ?? null;
306
+ const innerArg = expression.arguments[0];
307
+ const inner = innerArg && (ts.isArrowFunction(innerArg) || ts.isFunctionExpression(innerArg)) ? typeNodeFromFunction(innerArg) : innerArg && ts.isIdentifier(innerArg) ? resolveFromIdentifier(innerArg.text) : { propsTypeNode: null, componentNode: null };
308
+ return {
309
+ propsTypeNode: forwardRefPropsType ?? inner.propsTypeNode,
310
+ componentNode: inner.componentNode
311
+ };
312
+ }
313
+ if (ts.isPropertyAccessExpression(expression.expression) && expression.expression.name.text === "memo" && expression.arguments[0]) {
314
+ return resolveFromExpression(expression.arguments[0]);
315
+ }
316
+ if (ts.isPropertyAccessExpression(expression.expression) && expression.expression.expression.getText(sourceFile) === "Object" && expression.expression.name.text === "assign" && expression.arguments[0]) {
317
+ return resolveFromExpression(expression.arguments[0]);
318
+ }
319
+ }
320
+ return { propsTypeNode: null, componentNode: null };
321
+ };
322
+ const resolveFromVariable = (declaration) => {
323
+ if (declaration.type && ts.isTypeReferenceNode(declaration.type) && declaration.type.typeArguments?.length) {
324
+ const typeName = declaration.type.typeName.getText(sourceFile);
325
+ if (typeName.includes("FC") || typeName.includes("FunctionComponent")) {
326
+ const componentNode = declaration.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer)) ? declaration.initializer : null;
327
+ return {
328
+ propsTypeNode: declaration.type.typeArguments[0] ?? null,
329
+ componentNode
330
+ };
331
+ }
332
+ }
333
+ if (declaration.initializer) {
334
+ return resolveFromExpression(declaration.initializer);
335
+ }
336
+ return { propsTypeNode: null, componentNode: null };
337
+ };
338
+ const resolveFromIdentifier = (name) => {
339
+ if (!name || visitedNames.has(name)) {
340
+ return { propsTypeNode: null, componentNode: null };
341
+ }
342
+ visitedNames.add(name);
343
+ const functionDeclaration = declarations.functionDeclarations.get(name);
344
+ if (functionDeclaration) {
345
+ return typeNodeFromFunction(functionDeclaration);
346
+ }
347
+ const variableDeclaration = declarations.variableDeclarations.get(name);
348
+ if (variableDeclaration) {
349
+ return resolveFromVariable(variableDeclaration);
350
+ }
351
+ return { propsTypeNode: null, componentNode: null };
352
+ };
353
+ return resolveFromIdentifier(exportName);
354
+ }
355
+ function extractCustomPropsFromComponentFile(componentFilePath, exportName) {
356
+ const warnings = [];
357
+ const resolvedPath = resolve(componentFilePath);
358
+ if (!existsSync(resolvedPath)) {
359
+ return {
360
+ props: {},
361
+ warnings: [`Component file not found: ${resolvedPath}`],
362
+ resolved: false
363
+ };
364
+ }
365
+ const compilerOptions = {
366
+ target: ts.ScriptTarget.ESNext,
367
+ module: ts.ModuleKind.ESNext,
368
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
369
+ jsx: ts.JsxEmit.ReactJSX,
370
+ allowSyntheticDefaultImports: true,
371
+ esModuleInterop: true,
372
+ skipLibCheck: true,
373
+ strict: false,
374
+ noEmit: true
375
+ };
376
+ const program = ts.createProgram([resolvedPath], compilerOptions);
377
+ const sourceFile = program.getSourceFile(resolvedPath);
378
+ if (!sourceFile) {
379
+ return {
380
+ props: {},
381
+ warnings: [`Unable to parse component source: ${resolvedPath}`],
382
+ resolved: false
383
+ };
384
+ }
385
+ const checker = program.getTypeChecker();
386
+ const declarations = collectTopLevelDeclarations(sourceFile);
387
+ const signature = resolveComponentSignature(exportName, declarations, sourceFile);
388
+ if (!signature.propsTypeNode) {
389
+ return {
390
+ props: {},
391
+ warnings: [`Unable to resolve props type for export: ${exportName}`],
392
+ resolved: false
393
+ };
394
+ }
395
+ const propsType = checker.getTypeFromTypeNode(signature.propsTypeNode);
396
+ const defaultValues = extractDefaultValues(signature.componentNode);
397
+ const sourceFilePath = toPosixPath(sourceFile.fileName);
398
+ const extractedProps = {};
399
+ for (const symbol of checker.getPropertiesOfType(propsType)) {
400
+ const propName = symbol.getName();
401
+ if (propName.startsWith("_") || propName.startsWith("$")) {
402
+ continue;
403
+ }
404
+ const declarationsForSymbol = symbol.getDeclarations() ?? [];
405
+ const localDeclarations = declarationsForSymbol.filter(
406
+ (declaration) => toPosixPath(declaration.getSourceFile().fileName) === sourceFilePath
407
+ );
408
+ if (localDeclarations.length === 0) {
409
+ continue;
410
+ }
411
+ const referenceNode = localDeclarations[0];
412
+ const inferredType = inferPropType(checker.getTypeOfSymbolAtLocation(symbol, referenceNode), checker);
413
+ const description = ts.displayPartsToString(symbol.getDocumentationComment(checker)).trim();
414
+ extractedProps[propName] = {
415
+ type: inferredType.type,
416
+ description,
417
+ required: (symbol.getFlags() & ts.SymbolFlags.Optional) === 0,
418
+ ...inferredType.values && { values: inferredType.values },
419
+ ...defaultValues[propName] !== void 0 && { default: defaultValues[propName] }
420
+ };
421
+ }
422
+ if (Object.keys(extractedProps).length === 0) {
423
+ warnings.push(`Resolved props type for ${exportName}, but no local custom props were found`);
424
+ }
425
+ return {
426
+ props: extractedProps,
427
+ warnings,
428
+ resolved: true
429
+ };
430
+ }
431
+
432
+ // src/core/graph-extractor.ts
433
+ import ts2 from "typescript";
434
+ import { readFileSync, existsSync as existsSync2 } from "fs";
435
+ import { join as join2 } from "path";
436
+ import { readdirSync } from "fs";
437
+ import { EDGE_TYPE_WEIGHTS, computeHealthFromData } from "@fragments-sdk/context/graph";
438
+ async function buildComponentGraph(segments, blocks, componentDir, options) {
439
+ const knownComponents = new Set(Object.keys(segments));
440
+ const allEdges = [];
441
+ const autoDetected = /* @__PURE__ */ new Map();
442
+ const warnings = [];
443
+ if (!options?.skipSourceAnalysis) {
444
+ const sourceEdges = extractImportAndHookEdges(componentDir, knownComponents);
445
+ allEdges.push(...sourceEdges);
446
+ const subComponentResults = extractSubComponents(componentDir, knownComponents);
447
+ for (const [name, subs] of subComponentResults) {
448
+ autoDetected.set(name, {
449
+ ...autoDetected.get(name),
450
+ subComponents: subs,
451
+ compositionPattern: subs.length > 0 ? "compound" : "simple"
452
+ });
453
+ }
454
+ }
455
+ const jsxEdges = extractJsxUsageEdges(segments, knownComponents);
456
+ allEdges.push(...jsxEdges);
457
+ const blockEdges = extractBlockEdges(blocks);
458
+ allEdges.push(...blockEdges);
459
+ const relationEdges = extractRelationEdges(segments);
460
+ allEdges.push(...relationEdges);
461
+ const requiredChildrenMap = inferRequiredChildren(segments, autoDetected);
462
+ for (const [name, children] of requiredChildrenMap) {
463
+ const existing = autoDetected.get(name) ?? {};
464
+ autoDetected.set(name, { ...existing, requiredChildren: children });
465
+ }
466
+ const patternsMap = generateCommonPatterns(segments, autoDetected);
467
+ for (const [name, patterns] of patternsMap) {
468
+ const existing = autoDetected.get(name) ?? {};
469
+ autoDetected.set(name, { ...existing, commonPatterns: patterns });
470
+ }
471
+ const mergedEdges = mergeAndDeduplicate(allEdges);
472
+ const nodes = Object.entries(segments).map(([name, segment]) => {
473
+ const detected = autoDetected.get(name);
474
+ return {
475
+ name,
476
+ category: segment.meta.category,
477
+ status: segment.meta.status ?? "stable",
478
+ compositionPattern: segment.ai?.compositionPattern ?? detected?.compositionPattern,
479
+ subComponents: segment.ai?.subComponents ?? detected?.subComponents
480
+ };
481
+ });
482
+ const blockIndex = /* @__PURE__ */ new Map();
483
+ for (const [blockName, block] of Object.entries(blocks)) {
484
+ for (const comp of block.components) {
485
+ const existing = blockIndex.get(comp);
486
+ if (existing) existing.push(blockName);
487
+ else blockIndex.set(comp, [blockName]);
488
+ }
489
+ }
490
+ const health = computeHealthFromData(nodes, mergedEdges, blockIndex);
491
+ for (const [name, segment] of Object.entries(segments)) {
492
+ const detected = autoDetected.get(name);
493
+ if (!detected) continue;
494
+ if (segment.ai?.subComponents && detected.subComponents) {
495
+ const declared = new Set(segment.ai.subComponents);
496
+ const found = new Set(detected.subComponents);
497
+ const missing = detected.subComponents.filter((s) => !declared.has(s));
498
+ const extra = segment.ai.subComponents.filter((s) => !found.has(s));
499
+ if (missing.length > 0) {
500
+ warnings.push(
501
+ `${name}: declares ${declared.size} subComponents but code has ${found.size}. Missing from declaration: ${missing.join(", ")}`
502
+ );
503
+ }
504
+ if (extra.length > 0) {
505
+ warnings.push(
506
+ `${name}: declares subComponents [${extra.join(", ")}] not found in Object.assign`
507
+ );
508
+ }
509
+ }
510
+ }
511
+ return {
512
+ graph: { nodes, edges: mergedEdges, health },
513
+ autoDetected,
514
+ warnings
515
+ };
516
+ }
517
+ function extractImportAndHookEdges(componentDir, knownComponents) {
518
+ const edges = [];
519
+ for (const componentName of knownComponents) {
520
+ const indexPath = findComponentIndex(componentDir, componentName);
521
+ if (!indexPath) continue;
522
+ let sourceText;
523
+ try {
524
+ sourceText = readFileSync(indexPath, "utf-8");
525
+ } catch {
526
+ continue;
527
+ }
528
+ const sourceFile = ts2.createSourceFile(
529
+ indexPath,
530
+ sourceText,
531
+ ts2.ScriptTarget.Latest,
532
+ true,
533
+ indexPath.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS
534
+ );
535
+ const visitNode = (node) => {
536
+ if (ts2.isImportDeclaration(node)) {
537
+ const moduleSpecifier = node.moduleSpecifier;
538
+ if (ts2.isStringLiteral(moduleSpecifier)) {
539
+ const importPath = moduleSpecifier.text;
540
+ if (importPath.startsWith(".") || importPath.startsWith("/")) {
541
+ const clause = node.importClause;
542
+ if (clause) {
543
+ if (clause.name && isPascalCase(clause.name.text) && knownComponents.has(clause.name.text)) {
544
+ edges.push({
545
+ source: componentName,
546
+ target: clause.name.text,
547
+ type: "imports",
548
+ weight: EDGE_TYPE_WEIGHTS["imports"],
549
+ provenance: `source:${componentName}/index.tsx`
550
+ });
551
+ }
552
+ if (clause.namedBindings && ts2.isNamedImports(clause.namedBindings)) {
553
+ for (const element of clause.namedBindings.elements) {
554
+ const name = element.name.text;
555
+ if (isPascalCase(name) && knownComponents.has(name) && name !== componentName) {
556
+ edges.push({
557
+ source: componentName,
558
+ target: name,
559
+ type: "imports",
560
+ weight: EDGE_TYPE_WEIGHTS["imports"],
561
+ provenance: `source:${componentName}/index.tsx`
562
+ });
563
+ }
564
+ }
565
+ }
566
+ }
567
+ }
568
+ }
569
+ }
570
+ if (ts2.isCallExpression(node) && ts2.isIdentifier(node.expression)) {
571
+ const callName = node.expression.text;
572
+ const hookMatch = callName.match(/^use([A-Z][a-zA-Z]*)$/);
573
+ if (hookMatch) {
574
+ const hookTarget = hookMatch[1];
575
+ if (knownComponents.has(hookTarget) && hookTarget !== componentName) {
576
+ edges.push({
577
+ source: componentName,
578
+ target: hookTarget,
579
+ type: "hook-depends",
580
+ weight: EDGE_TYPE_WEIGHTS["hook-depends"],
581
+ provenance: `source:${componentName}/index.tsx`
582
+ });
583
+ }
584
+ }
585
+ }
586
+ ts2.forEachChild(node, visitNode);
587
+ };
588
+ ts2.forEachChild(sourceFile, visitNode);
589
+ }
590
+ return edges;
591
+ }
592
+ function extractSubComponents(componentDir, knownComponents) {
593
+ const result = /* @__PURE__ */ new Map();
594
+ for (const componentName of knownComponents) {
595
+ const indexPath = findComponentIndex(componentDir, componentName);
596
+ if (!indexPath) continue;
597
+ let sourceText;
598
+ try {
599
+ sourceText = readFileSync(indexPath, "utf-8");
600
+ } catch {
601
+ continue;
602
+ }
603
+ if (!sourceText.includes("Object.assign")) continue;
604
+ const sourceFile = ts2.createSourceFile(
605
+ indexPath,
606
+ sourceText,
607
+ ts2.ScriptTarget.Latest,
608
+ true,
609
+ indexPath.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS
610
+ );
611
+ const subComponents = [];
612
+ const visitNode = (node) => {
613
+ if (ts2.isCallExpression(node) && ts2.isPropertyAccessExpression(node.expression) && ts2.isIdentifier(node.expression.expression) && node.expression.expression.text === "Object" && node.expression.name.text === "assign" && node.arguments.length >= 2) {
614
+ const propsArg = node.arguments[1];
615
+ if (ts2.isObjectLiteralExpression(propsArg)) {
616
+ for (const prop of propsArg.properties) {
617
+ if (ts2.isShorthandPropertyAssignment(prop)) {
618
+ subComponents.push(prop.name.text);
619
+ } else if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
620
+ subComponents.push(prop.name.text);
621
+ }
622
+ }
623
+ }
624
+ }
625
+ ts2.forEachChild(node, visitNode);
626
+ };
627
+ ts2.forEachChild(sourceFile, visitNode);
628
+ if (subComponents.length > 0) {
629
+ result.set(componentName, subComponents);
630
+ }
631
+ }
632
+ return result;
633
+ }
634
+ function extractJsxUsageEdges(segments, knownComponents) {
635
+ const edges = [];
636
+ const jsxTagRegex = /<([A-Z][a-zA-Z]*(?:\.[A-Z][a-zA-Z]*)?)/g;
637
+ for (const [name, segment] of Object.entries(segments)) {
638
+ const usedComponents = /* @__PURE__ */ new Set();
639
+ for (const variant of segment.variants) {
640
+ if (!variant.code) continue;
641
+ let match;
642
+ jsxTagRegex.lastIndex = 0;
643
+ while ((match = jsxTagRegex.exec(variant.code)) !== null) {
644
+ let tagName = match[1];
645
+ if (tagName.includes(".")) {
646
+ tagName = tagName.split(".")[0];
647
+ }
648
+ if (knownComponents.has(tagName) && tagName !== name) {
649
+ usedComponents.add(tagName);
650
+ }
651
+ }
652
+ }
653
+ for (const target of usedComponents) {
654
+ edges.push({
655
+ source: name,
656
+ target,
657
+ type: "renders",
658
+ weight: EDGE_TYPE_WEIGHTS["renders"],
659
+ provenance: `variant:${name}`
660
+ });
661
+ }
662
+ }
663
+ return edges;
664
+ }
665
+ function extractBlockEdges(blocks) {
666
+ const edges = [];
667
+ for (const [blockName, block] of Object.entries(blocks)) {
668
+ const components = block.components;
669
+ for (let i = 0; i < components.length; i++) {
670
+ for (let j = i + 1; j < components.length; j++) {
671
+ edges.push({
672
+ source: components[i],
673
+ target: components[j],
674
+ type: "composes",
675
+ weight: EDGE_TYPE_WEIGHTS["composes"],
676
+ provenance: `block:${blockName}`
677
+ });
678
+ }
679
+ }
680
+ }
681
+ return edges;
682
+ }
683
+ function extractRelationEdges(segments) {
684
+ const edges = [];
685
+ const relationToEdgeType = {
686
+ parent: "parent-of",
687
+ child: "parent-of",
688
+ // reversed: if A declares child B, edge is A parent-of B
689
+ composition: "composes",
690
+ alternative: "alternative-to",
691
+ sibling: "sibling-of"
692
+ };
693
+ for (const [name, segment] of Object.entries(segments)) {
694
+ if (!segment.relations) continue;
695
+ for (const rel of segment.relations) {
696
+ const edgeType = relationToEdgeType[rel.relationship];
697
+ if (!edgeType) continue;
698
+ let source;
699
+ let target;
700
+ if (rel.relationship === "parent") {
701
+ source = rel.component;
702
+ target = name;
703
+ } else {
704
+ source = name;
705
+ target = rel.component;
706
+ }
707
+ edges.push({
708
+ source,
709
+ target,
710
+ type: edgeType,
711
+ weight: EDGE_TYPE_WEIGHTS[edgeType],
712
+ note: rel.note,
713
+ provenance: "relation"
714
+ });
715
+ }
716
+ }
717
+ return edges;
718
+ }
719
+ function inferRequiredChildren(segments, autoDetected) {
720
+ const result = /* @__PURE__ */ new Map();
721
+ for (const [name, segment] of Object.entries(segments)) {
722
+ const detected = autoDetected.get(name);
723
+ const subs = detected?.subComponents ?? segment.ai?.subComponents;
724
+ if (!subs || subs.length === 0) continue;
725
+ const variantsWithCode = segment.variants.filter((v) => v.code);
726
+ if (variantsWithCode.length === 0) continue;
727
+ const required = [];
728
+ for (const sub of subs) {
729
+ const inAll = variantsWithCode.every((v) => {
730
+ const patterns = [
731
+ new RegExp(`<${name}\\.${sub}[\\s/>]`),
732
+ new RegExp(`<${sub}[\\s/>]`)
733
+ ];
734
+ return patterns.some((p) => p.test(v.code));
735
+ });
736
+ if (inAll) required.push(sub);
737
+ }
738
+ if (required.length > 0) {
739
+ result.set(name, required);
740
+ }
741
+ }
742
+ return result;
743
+ }
744
+ function generateCommonPatterns(segments, autoDetected) {
745
+ const result = /* @__PURE__ */ new Map();
746
+ for (const [name, segment] of Object.entries(segments)) {
747
+ const detected = autoDetected.get(name);
748
+ const subs = detected?.subComponents ?? segment.ai?.subComponents;
749
+ if (!subs || subs.length === 0) continue;
750
+ const firstVariant = segment.variants.find((v) => v.code);
751
+ if (!firstVariant?.code) continue;
752
+ const usedSubs = [];
753
+ for (const sub of subs) {
754
+ const patterns = [
755
+ new RegExp(`<${name}\\.${sub}`),
756
+ new RegExp(`<${sub}[\\s/>]`)
757
+ ];
758
+ if (patterns.some((p) => p.test(firstVariant.code))) {
759
+ usedSubs.push(sub);
760
+ }
761
+ }
762
+ if (usedSubs.length > 0) {
763
+ const pattern = `<${name}>
764
+ ${usedSubs.map((s) => ` <${name}.${s}>...</${name}.${s}>`).join("\n")}
765
+ </${name}>`;
766
+ result.set(name, [pattern]);
767
+ }
768
+ }
769
+ return result;
770
+ }
771
+ function mergeAndDeduplicate(edges) {
772
+ const edgeMap = /* @__PURE__ */ new Map();
773
+ for (const edge of edges) {
774
+ const key = `${edge.source}\u2192${edge.target}:${edge.type}`;
775
+ const existing = edgeMap.get(key);
776
+ if (!existing || edge.weight > existing.weight) {
777
+ edgeMap.set(key, edge);
778
+ }
779
+ }
780
+ return [...edgeMap.values()];
781
+ }
782
+ function isPascalCase(name) {
783
+ return /^[A-Z][a-zA-Z0-9]*$/.test(name);
784
+ }
785
+ function findComponentIndex(componentDir, componentName) {
786
+ const candidates = [
787
+ join2(componentDir, componentName, "index.tsx"),
788
+ join2(componentDir, componentName, "index.ts"),
789
+ join2(componentDir, componentName, `${componentName}.tsx`),
790
+ join2(componentDir, componentName, `${componentName}.ts`)
791
+ ];
792
+ for (const candidate of candidates) {
793
+ if (existsSync2(candidate)) {
794
+ return candidate;
795
+ }
796
+ }
797
+ try {
798
+ const entries = readdirSync(componentDir, { withFileTypes: true });
799
+ for (const entry of entries) {
800
+ if (entry.isDirectory() && entry.name === componentName) {
801
+ const subCandidates = [
802
+ join2(componentDir, entry.name, "index.tsx"),
803
+ join2(componentDir, entry.name, "index.ts")
804
+ ];
805
+ for (const sc of subCandidates) {
806
+ if (existsSync2(sc)) return sc;
807
+ }
808
+ }
809
+ }
810
+ } catch {
811
+ }
812
+ return null;
813
+ }
814
+
815
+ // src/build.ts
816
+ import { serializeGraph } from "@fragments-sdk/context/graph";
817
+ function normalizeParsedProps(parsedProps) {
818
+ return Object.fromEntries(
819
+ Object.entries(parsedProps).map(([name, prop]) => [
820
+ name,
821
+ {
822
+ type: prop.type ?? "custom",
823
+ description: prop.description ?? "",
824
+ default: prop.default,
825
+ required: prop.required,
826
+ values: prop.values,
827
+ constraints: prop.constraints
828
+ }
829
+ ])
830
+ );
831
+ }
832
+ function mergeDocumentedAndAutoProps(documentedProps, autoProps) {
833
+ return Object.fromEntries(
834
+ Object.keys(autoProps).map((name) => {
835
+ const documented = documentedProps[name];
836
+ const auto = autoProps[name];
837
+ return [
838
+ name,
839
+ {
840
+ type: auto.type,
841
+ description: documented?.description ?? auto.description ?? "",
842
+ default: auto.default !== void 0 ? auto.default : documented?.default,
843
+ required: auto.required,
844
+ values: auto.values ?? documented?.values,
845
+ constraints: documented?.constraints
846
+ }
847
+ ];
848
+ })
849
+ );
850
+ }
851
+ async function buildSegments(config, configDir) {
852
+ const files = await discoverSegmentFiles(config, configDir);
853
+ const errors = [];
854
+ const warnings = [];
855
+ const segments = {};
856
+ for (const file of files) {
857
+ try {
858
+ const content = await readFile(file.absolutePath, "utf-8");
859
+ const parsed = parseSegmentFile(content, file.relativePath);
860
+ for (const warning of parsed.warnings) {
861
+ warnings.push({ file: file.relativePath, warning });
862
+ }
863
+ if (!parsed.meta.name) {
864
+ errors.push({
865
+ file: file.relativePath,
866
+ error: "Missing meta.name in fragment definition"
867
+ });
868
+ continue;
869
+ }
870
+ const documentedProps = normalizeParsedProps(parsed.props);
871
+ let mergedProps = documentedProps;
872
+ const componentExportName = parsed.componentName ?? parsed.meta.name;
873
+ const componentSourcePath = resolveComponentSourcePath(
874
+ file.absolutePath,
875
+ parsed.componentImport
876
+ );
877
+ if (componentExportName && componentSourcePath) {
878
+ const autoPropsResult = extractCustomPropsFromComponentFile(
879
+ componentSourcePath,
880
+ componentExportName
881
+ );
882
+ for (const warning of autoPropsResult.warnings) {
883
+ warnings.push({ file: file.relativePath, warning });
884
+ }
885
+ const hasAutoProps = Object.keys(autoPropsResult.props).length > 0;
886
+ if (autoPropsResult.resolved && hasAutoProps) {
887
+ const removedDocumentedProps = Object.keys(documentedProps).filter(
888
+ (propName) => !(propName in autoPropsResult.props)
889
+ );
890
+ if (removedDocumentedProps.length > 0) {
891
+ warnings.push({
892
+ file: file.relativePath,
893
+ warning: `Removed ${removedDocumentedProps.length} documented props not present in source API: ${removedDocumentedProps.join(", ")}`
894
+ });
895
+ }
896
+ mergedProps = mergeDocumentedAndAutoProps(
897
+ documentedProps,
898
+ autoPropsResult.props
899
+ );
900
+ } else if (autoPropsResult.resolved && !hasAutoProps && Object.keys(documentedProps).length > 0) {
901
+ warnings.push({
902
+ file: file.relativePath,
903
+ warning: "Auto-props extraction returned no custom props; falling back to documented props"
904
+ });
905
+ }
906
+ } else if (!componentExportName) {
907
+ warnings.push({
908
+ file: file.relativePath,
909
+ warning: "Unable to resolve component export name for auto-props extraction"
910
+ });
911
+ } else if (!componentSourcePath) {
912
+ warnings.push({
913
+ file: file.relativePath,
914
+ warning: `Unable to resolve component source path from import: ${parsed.componentImport ?? "unknown"}`
915
+ });
916
+ }
917
+ const compiled = {
918
+ filePath: file.relativePath,
919
+ meta: {
920
+ name: parsed.meta.name,
921
+ description: parsed.meta.description ?? "",
922
+ category: parsed.meta.category ?? "Uncategorized",
923
+ status: parsed.meta.status,
924
+ tags: parsed.meta.tags,
925
+ since: parsed.meta.since,
926
+ figma: parsed.meta.figma
927
+ },
928
+ usage: {
929
+ when: parsed.usage.when ?? [],
930
+ whenNot: parsed.usage.whenNot ?? [],
931
+ guidelines: parsed.usage.guidelines,
932
+ accessibility: parsed.usage.accessibility
933
+ },
934
+ props: mergedProps,
935
+ relations: parsed.relations.map((rel) => ({
936
+ component: rel.component,
937
+ relationship: rel.relationship,
938
+ note: rel.note
939
+ })),
940
+ variants: parsed.variants.map((v) => ({
941
+ name: v.name,
942
+ description: v.description,
943
+ ...v.code && { code: v.code },
944
+ ...v.figma && { figma: v.figma },
945
+ ...v.args && { args: v.args }
946
+ })),
947
+ // Include AI metadata if present
948
+ ...parsed.ai && { ai: parsed.ai }
949
+ };
950
+ segments[parsed.meta.name] = compiled;
951
+ } catch (error) {
952
+ errors.push({
953
+ file: file.relativePath,
954
+ error: error instanceof Error ? error.message : String(error)
955
+ });
956
+ }
957
+ }
958
+ const blocks = {};
959
+ try {
960
+ const blockFiles = await discoverBlockFiles(configDir, config.exclude);
961
+ for (const file of blockFiles) {
962
+ try {
963
+ let raw = await loadSegmentFile(file.absolutePath);
964
+ if (raw && "default" in raw && typeof raw.default === "object") {
965
+ raw = raw.default;
966
+ }
967
+ const def = raw;
968
+ if (def && typeof def === "object" && "name" in def && "code" in def && "components" in def) {
969
+ const compiled = compileBlock(def, file.relativePath);
970
+ blocks[compiled.name] = compiled;
971
+ }
972
+ } catch (error) {
973
+ warnings.push({
974
+ file: file.relativePath,
975
+ warning: `Failed to load block: ${error instanceof Error ? error.message : String(error)}`
976
+ });
977
+ }
978
+ }
979
+ } catch {
980
+ }
981
+ let tokens;
982
+ try {
983
+ const tokenPatterns = config.tokens?.include;
984
+ const tokenFiles = await discoverTokenFiles(configDir, tokenPatterns, config.exclude);
985
+ if (tokenFiles.length > 0) {
986
+ const mergedCategories = {};
987
+ let prefix = "--";
988
+ let total = 0;
989
+ for (const file of tokenFiles) {
990
+ const content = await readFile(file.absolutePath, "utf-8");
991
+ const parsed = parseTokenFile(content, file.relativePath);
992
+ prefix = parsed.prefix;
993
+ total += parsed.total;
994
+ for (const [cat, catTokens] of Object.entries(parsed.categories)) {
995
+ if (!mergedCategories[cat]) {
996
+ mergedCategories[cat] = [];
997
+ }
998
+ for (const t of catTokens) {
999
+ if (!mergedCategories[cat].some((e) => e.name === t.name)) {
1000
+ mergedCategories[cat].push({ name: t.name, description: t.description });
1001
+ }
1002
+ }
1003
+ }
1004
+ }
1005
+ if (total > 0) {
1006
+ tokens = { prefix, total, categories: mergedCategories };
1007
+ }
1008
+ }
1009
+ } catch {
1010
+ }
1011
+ let packageName;
1012
+ const pkgJsonPath = resolve3(configDir, "package.json");
1013
+ if (existsSync3(pkgJsonPath)) {
1014
+ try {
1015
+ const pkg = JSON.parse(await readFile(pkgJsonPath, "utf-8"));
1016
+ if (pkg.name) packageName = pkg.name;
1017
+ } catch {
1018
+ }
1019
+ }
1020
+ const componentDir = resolve3(configDir, "src", "components");
1021
+ let graphData;
1022
+ try {
1023
+ const graphResult = await buildComponentGraph(segments, blocks, componentDir);
1024
+ for (const [name, segment] of Object.entries(segments)) {
1025
+ const detected = graphResult.autoDetected.get(name);
1026
+ if (!detected) continue;
1027
+ if (!segment.ai) segment.ai = {};
1028
+ if (!segment.ai.subComponents && detected.subComponents) {
1029
+ segment.ai.subComponents = detected.subComponents;
1030
+ }
1031
+ if (!segment.ai.compositionPattern && detected.compositionPattern) {
1032
+ segment.ai.compositionPattern = detected.compositionPattern;
1033
+ }
1034
+ if (!segment.ai.commonPatterns && detected.commonPatterns) {
1035
+ segment.ai.commonPatterns = detected.commonPatterns;
1036
+ }
1037
+ if (!segment.ai.requiredChildren && detected.requiredChildren) {
1038
+ segment.ai.requiredChildren = detected.requiredChildren;
1039
+ }
1040
+ }
1041
+ for (const w of graphResult.warnings) {
1042
+ warnings.push({ file: "graph", warning: w });
1043
+ }
1044
+ graphData = serializeGraph(graphResult.graph);
1045
+ } catch (error) {
1046
+ warnings.push({
1047
+ file: "graph",
1048
+ warning: `Graph extraction failed: ${error instanceof Error ? error.message : String(error)}`
1049
+ });
1050
+ }
1051
+ const output = {
1052
+ version: "1.0.0",
1053
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1054
+ ...packageName && { packageName },
1055
+ segments,
1056
+ ...Object.keys(blocks).length > 0 && { blocks },
1057
+ ...tokens && { tokens },
1058
+ ...graphData && { graph: graphData }
1059
+ };
1060
+ const outputPath = resolve3(configDir, config.outFile ?? BRAND.outFile);
1061
+ await writeFile(outputPath, JSON.stringify(output));
1062
+ return {
1063
+ success: errors.length === 0,
1064
+ outputPath,
1065
+ segmentCount: Object.keys(segments).length,
1066
+ errors,
1067
+ warnings
1068
+ };
1069
+ }
1070
+ async function buildFragmentsDir(config, configDir) {
1071
+ const fragmentsDir = join3(configDir, BRAND.dataDir);
1072
+ const componentsDir = join3(fragmentsDir, BRAND.componentsDir);
1073
+ await mkdir(fragmentsDir, { recursive: true });
1074
+ await mkdir(componentsDir, { recursive: true });
1075
+ const registryResult = await generateRegistry({
1076
+ projectRoot: configDir,
1077
+ componentPatterns: config.components || ["src/**/*.tsx", "src/**/*.ts"],
1078
+ storyPatterns: config.include || ["src/**/*.stories.tsx"],
1079
+ fragmentsDir,
1080
+ registryOptions: config.registry || {}
1081
+ });
1082
+ const errors = [...registryResult.errors];
1083
+ const warnings = [...registryResult.warnings];
1084
+ const indexPath = join3(fragmentsDir, "index.json");
1085
+ await writeFile(indexPath, JSON.stringify(registryResult.index, null, 2));
1086
+ const registryPath = join3(fragmentsDir, BRAND.registryFile);
1087
+ await writeFile(registryPath, JSON.stringify(registryResult.registry, null, 2));
1088
+ const contextResult = generateContextMd(registryResult.registry, {
1089
+ format: "markdown",
1090
+ compact: false,
1091
+ include: {
1092
+ props: false,
1093
+ // AI can read TypeScript directly
1094
+ relations: true,
1095
+ code: false
1096
+ }
1097
+ });
1098
+ const contextPath = join3(fragmentsDir, BRAND.contextFile);
1099
+ await writeFile(contextPath, contextResult.content);
1100
+ return {
1101
+ success: errors.length === 0,
1102
+ indexPath,
1103
+ registryPath,
1104
+ contextPath,
1105
+ componentCount: registryResult.registry.componentCount,
1106
+ errors,
1107
+ warnings
1108
+ };
1109
+ }
1110
+
1111
+ // src/screenshot.ts
1112
+ import pc from "picocolors";
1113
+ async function runScreenshotCommand(config, configDir, options = {}) {
1114
+ const startTime = Date.now();
1115
+ const errors = [];
1116
+ const storage = new StorageManager({
1117
+ projectRoot: configDir,
1118
+ viewport: options.width && options.height ? { width: options.width, height: options.height } : config.screenshots?.viewport
1119
+ });
1120
+ await storage.initialize();
1121
+ const segmentFiles = await discoverSegmentFiles(config, configDir);
1122
+ if (segmentFiles.length === 0) {
1123
+ console.log(pc.yellow("No segment files found."));
1124
+ return {
1125
+ success: true,
1126
+ captured: 0,
1127
+ skipped: 0,
1128
+ errors: [],
1129
+ totalTimeMs: Date.now() - startTime
1130
+ };
1131
+ }
1132
+ const segments = [];
1133
+ for (const file of segmentFiles) {
1134
+ try {
1135
+ const segment = await loadSegmentFile(file.absolutePath);
1136
+ if (segment) {
1137
+ segments.push({ path: file.relativePath, segment });
1138
+ }
1139
+ } catch (error) {
1140
+ errors.push({
1141
+ component: file.relativePath,
1142
+ variant: "",
1143
+ error: error instanceof Error ? error.message : String(error)
1144
+ });
1145
+ }
1146
+ }
1147
+ const filteredSegments = options.component ? segments.filter((s) => s.segment.meta.name === options.component) : segments;
1148
+ if (options.component && filteredSegments.length === 0) {
1149
+ console.log(pc.yellow(`Component "${options.component}" not found.`));
1150
+ return {
1151
+ success: false,
1152
+ captured: 0,
1153
+ skipped: 0,
1154
+ errors: [],
1155
+ totalTimeMs: Date.now() - startTime
1156
+ };
1157
+ }
1158
+ const variantsToCapture = [];
1159
+ for (const { segment } of filteredSegments) {
1160
+ const variants = options.variant ? segment.variants.filter((v) => v.name === options.variant) : segment.variants;
1161
+ for (const variant of variants) {
1162
+ variantsToCapture.push({
1163
+ component: segment.meta.name,
1164
+ variant: variant.name,
1165
+ render: variant.render
1166
+ });
1167
+ }
1168
+ }
1169
+ if (variantsToCapture.length === 0) {
1170
+ console.log(pc.yellow("No variants to capture."));
1171
+ return {
1172
+ success: true,
1173
+ captured: 0,
1174
+ skipped: 0,
1175
+ errors: [],
1176
+ totalTimeMs: Date.now() - startTime
1177
+ };
1178
+ }
1179
+ const theme = options.theme ?? DEFAULTS.theme;
1180
+ const viewport = {
1181
+ width: options.width ?? config.screenshots?.viewport?.width ?? DEFAULTS.viewport.width,
1182
+ height: options.height ?? config.screenshots?.viewport?.height ?? DEFAULTS.viewport.height
1183
+ };
1184
+ console.log(pc.cyan(`
1185
+ ${BRAND.name} Screenshot
1186
+ `));
1187
+ console.log(pc.dim(`Capturing variants (theme: ${theme}, viewport: ${viewport.width}x${viewport.height}):
1188
+ `));
1189
+ const pool = new BrowserPool({
1190
+ viewport
1191
+ });
1192
+ const viewerPort = DEFAULTS.port;
1193
+ const baseUrl = `http://localhost:${viewerPort}`;
1194
+ const captureEngine = new CaptureEngine(pool, baseUrl);
1195
+ let captured = 0;
1196
+ let skipped = 0;
1197
+ const captureOptions = {
1198
+ theme,
1199
+ viewport,
1200
+ delay: config.screenshots?.delay ?? DEFAULTS.captureDelayMs
1201
+ };
1202
+ try {
1203
+ console.log(pc.dim("Starting browser..."));
1204
+ await pool.warmup();
1205
+ console.log(pc.dim("Browser ready.\n"));
1206
+ for (const { component, variant } of variantsToCapture) {
1207
+ const hasExisting = storage.hasBaseline(component, variant, theme);
1208
+ if (hasExisting && !options.update) {
1209
+ console.log(` ${pc.dim("\u25CB")} ${component}/${variant} ${pc.dim("(skipped)")}`);
1210
+ skipped++;
1211
+ continue;
1212
+ }
1213
+ try {
1214
+ const screenshot = await captureEngine.captureVariant(
1215
+ component,
1216
+ variant,
1217
+ captureOptions
1218
+ );
1219
+ await storage.saveBaseline(screenshot);
1220
+ const totalTime = screenshot.metadata.renderTimeMs + screenshot.metadata.captureTimeMs;
1221
+ console.log(
1222
+ ` ${pc.green("\u2713")} ${component}/${variant} ${pc.dim(formatMs(totalTime))}`
1223
+ );
1224
+ captured++;
1225
+ } catch (error) {
1226
+ const errorMsg = error instanceof Error ? error.message : String(error);
1227
+ console.log(` ${pc.red("\u2717")} ${component}/${variant} ${pc.dim(errorMsg)}`);
1228
+ errors.push({ component, variant, error: errorMsg });
1229
+ }
1230
+ }
1231
+ } finally {
1232
+ await pool.shutdown();
1233
+ }
1234
+ const totalTimeMs = Date.now() - startTime;
1235
+ console.log();
1236
+ if (errors.length === 0) {
1237
+ console.log(pc.green(`\u2713 Captured ${captured} screenshot(s) in ${formatMs(totalTimeMs)}`));
1238
+ } else {
1239
+ console.log(pc.yellow(`\u26A0 Captured ${captured} screenshot(s) with ${errors.length} error(s)`));
1240
+ }
1241
+ if (skipped > 0) {
1242
+ console.log(pc.dim(` ${skipped} skipped (use --update to recapture)`));
1243
+ }
1244
+ console.log(pc.dim(` Stored in ${storage.screenshotsDirPath}
1245
+ `));
1246
+ return {
1247
+ success: errors.length === 0,
1248
+ captured,
1249
+ skipped,
1250
+ errors,
1251
+ totalTimeMs
1252
+ };
1253
+ }
1254
+
1255
+ // src/diff.ts
1256
+ import pc2 from "picocolors";
1257
+ async function runDiffCommand(config, configDir, options = {}) {
1258
+ const startTime = Date.now();
1259
+ const results = [];
1260
+ const storage = new StorageManager({
1261
+ projectRoot: configDir,
1262
+ viewport: config.screenshots?.viewport
1263
+ });
1264
+ await storage.initialize();
1265
+ const threshold = options.threshold ?? config.screenshots?.threshold ?? DEFAULTS.diffThreshold;
1266
+ const diffEngine = new DiffEngine(threshold);
1267
+ const segmentFiles = await discoverSegmentFiles(config, configDir);
1268
+ if (segmentFiles.length === 0) {
1269
+ console.log(pc2.yellow("No segment files found."));
1270
+ return {
1271
+ success: true,
1272
+ total: 0,
1273
+ passed: 0,
1274
+ failed: 0,
1275
+ missing: 0,
1276
+ results: [],
1277
+ totalTimeMs: Date.now() - startTime
1278
+ };
1279
+ }
1280
+ const segments = [];
1281
+ for (const file of segmentFiles) {
1282
+ try {
1283
+ const segment = await loadSegmentFile(file.absolutePath);
1284
+ if (segment) {
1285
+ segments.push({ path: file.relativePath, segment });
1286
+ }
1287
+ } catch {
1288
+ }
1289
+ }
1290
+ const filteredSegments = options.component ? segments.filter((s) => s.segment.meta.name === options.component) : segments;
1291
+ if (options.component && filteredSegments.length === 0) {
1292
+ console.log(pc2.yellow(`Component "${options.component}" not found.`));
1293
+ return {
1294
+ success: false,
1295
+ total: 0,
1296
+ passed: 0,
1297
+ failed: 0,
1298
+ missing: 0,
1299
+ results: [],
1300
+ totalTimeMs: Date.now() - startTime
1301
+ };
1302
+ }
1303
+ const variantsToDiff = [];
1304
+ for (const { segment } of filteredSegments) {
1305
+ const variants = options.variant ? segment.variants.filter((v) => v.name === options.variant) : segment.variants;
1306
+ for (const variant of variants) {
1307
+ variantsToDiff.push({
1308
+ component: segment.meta.name,
1309
+ variant: variant.name
1310
+ });
1311
+ }
1312
+ }
1313
+ if (variantsToDiff.length === 0) {
1314
+ console.log(pc2.yellow("No variants to compare."));
1315
+ return {
1316
+ success: true,
1317
+ total: 0,
1318
+ passed: 0,
1319
+ failed: 0,
1320
+ missing: 0,
1321
+ results: [],
1322
+ totalTimeMs: Date.now() - startTime
1323
+ };
1324
+ }
1325
+ const theme = options.theme ?? DEFAULTS.theme;
1326
+ const viewport = config.screenshots?.viewport ?? DEFAULTS.viewport;
1327
+ console.log(pc2.cyan(`
1328
+ ${BRAND.name} Diff
1329
+ `));
1330
+ console.log(pc2.dim(`Comparing against baselines (theme: ${theme}, threshold: ${threshold}%):
1331
+ `));
1332
+ const pool = new BrowserPool({
1333
+ viewport
1334
+ });
1335
+ const viewerPort = DEFAULTS.port;
1336
+ const baseUrl = `http://localhost:${viewerPort}`;
1337
+ const captureEngine = new CaptureEngine(pool, baseUrl);
1338
+ let passed = 0;
1339
+ let failed = 0;
1340
+ let missing = 0;
1341
+ const captureOptions = {
1342
+ theme,
1343
+ viewport,
1344
+ delay: config.screenshots?.delay ?? DEFAULTS.captureDelayMs
1345
+ };
1346
+ try {
1347
+ await pool.warmup();
1348
+ for (const { component, variant } of variantsToDiff) {
1349
+ const baseline = await storage.loadBaseline(component, variant, theme);
1350
+ if (!baseline) {
1351
+ console.log(
1352
+ ` ${pc2.yellow("?")} ${component}/${variant} ${pc2.dim("(no baseline)")}`
1353
+ );
1354
+ missing++;
1355
+ continue;
1356
+ }
1357
+ try {
1358
+ const current = await captureEngine.captureVariant(
1359
+ component,
1360
+ variant,
1361
+ captureOptions
1362
+ );
1363
+ if (diffEngine.areIdentical(current, baseline)) {
1364
+ console.log(` ${pc2.green("\u2713")} ${component}/${variant} ${pc2.dim("0.0%")}`);
1365
+ results.push({
1366
+ component,
1367
+ variant,
1368
+ theme,
1369
+ result: {
1370
+ matches: true,
1371
+ diffPercentage: 0,
1372
+ diffPixelCount: 0,
1373
+ totalPixels: current.viewport.width * current.viewport.height,
1374
+ changedRegions: [],
1375
+ diffTimeMs: 0
1376
+ }
1377
+ });
1378
+ passed++;
1379
+ continue;
1380
+ }
1381
+ const diffResult = diffEngine.compare(current, baseline, { threshold });
1382
+ if (diffResult.matches) {
1383
+ console.log(
1384
+ ` ${pc2.green("\u2713")} ${component}/${variant} ${pc2.dim(`${diffResult.diffPercentage}%`)}`
1385
+ );
1386
+ passed++;
1387
+ } else {
1388
+ let diffImagePath;
1389
+ if (diffResult.diffImage) {
1390
+ diffImagePath = await storage.saveDiff(
1391
+ component,
1392
+ variant,
1393
+ theme,
1394
+ diffResult.diffImage
1395
+ );
1396
+ }
1397
+ console.log(
1398
+ ` ${pc2.red("\u2717")} ${component}/${variant} ${pc2.yellow(`${diffResult.diffPercentage}%`)}` + (diffImagePath ? pc2.dim(` \u2192 ${diffImagePath}`) : "")
1399
+ );
1400
+ failed++;
1401
+ results.push({
1402
+ component,
1403
+ variant,
1404
+ theme,
1405
+ result: diffResult,
1406
+ diffImagePath
1407
+ });
1408
+ continue;
1409
+ }
1410
+ results.push({
1411
+ component,
1412
+ variant,
1413
+ theme,
1414
+ result: diffResult
1415
+ });
1416
+ } catch (error) {
1417
+ const errorMsg = error instanceof Error ? error.message : String(error);
1418
+ console.log(` ${pc2.red("!")} ${component}/${variant} ${pc2.dim(errorMsg)}`);
1419
+ failed++;
1420
+ }
1421
+ }
1422
+ } finally {
1423
+ await pool.shutdown();
1424
+ }
1425
+ const totalTimeMs = Date.now() - startTime;
1426
+ const total = passed + failed + missing;
1427
+ console.log();
1428
+ if (failed === 0 && missing === 0) {
1429
+ console.log(pc2.green(`\u2713 All ${passed} variant(s) match baselines`));
1430
+ } else if (failed > 0) {
1431
+ console.log(pc2.red(`\u2717 ${failed} variant(s) differ from baselines`));
1432
+ }
1433
+ if (missing > 0) {
1434
+ console.log(pc2.yellow(` ${missing} variant(s) have no baseline (run \`${BRAND.cliCommand} screenshot\`)`));
1435
+ }
1436
+ console.log(pc2.dim(` Completed in ${formatMs(totalTimeMs)}
1437
+ `));
1438
+ const success = failed === 0;
1439
+ return {
1440
+ success,
1441
+ total,
1442
+ passed,
1443
+ failed,
1444
+ missing,
1445
+ results,
1446
+ totalTimeMs
1447
+ };
1448
+ }
1449
+
1450
+ // src/analyze.ts
1451
+ import { existsSync as existsSync4 } from "fs";
1452
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1453
+ import { join as join4, dirname as dirname2 } from "path";
1454
+ import pc3 from "picocolors";
1455
+ async function runAnalyzeCommand(config, configDir, options = {}) {
1456
+ const format = options.format ?? "html";
1457
+ const minScore = options.minScore ?? 0;
1458
+ console.log(pc3.cyan(`
1459
+ ${BRAND.name} Analyzer
1460
+ `));
1461
+ const segmentsPath = join4(configDir, config.outFile ?? "segments.json");
1462
+ if (!existsSync4(segmentsPath)) {
1463
+ console.log(pc3.red(`\u2717 No segments.json found. Run \`${BRAND.cliCommand} build\` first.
1464
+ `));
1465
+ return {
1466
+ success: false,
1467
+ analytics: createEmptyAnalytics()
1468
+ };
1469
+ }
1470
+ console.log(pc3.dim("Analyzing design system...\n"));
1471
+ const content = await readFile2(segmentsPath, "utf-8");
1472
+ const data = JSON.parse(content);
1473
+ const analytics = analyzeDesignSystem(data);
1474
+ printConsoleSummary(analytics);
1475
+ let outputPath;
1476
+ if (format === "html" || format === "json") {
1477
+ outputPath = options.output ?? getDefaultOutputPath(format, configDir);
1478
+ await mkdir2(dirname2(outputPath), { recursive: true });
1479
+ if (format === "html") {
1480
+ const html = generateHtmlReport(analytics);
1481
+ await writeFile2(outputPath, html);
1482
+ console.log(pc3.green(`\u2713 Report generated: ${outputPath}
1483
+ `));
1484
+ } else {
1485
+ await writeFile2(outputPath, JSON.stringify(analytics, null, 2));
1486
+ console.log(pc3.green(`\u2713 JSON report generated: ${outputPath}
1487
+ `));
1488
+ }
1489
+ if (options.open && format === "html") {
1490
+ await openInBrowser(outputPath);
1491
+ }
1492
+ }
1493
+ const passedCi = analytics.summary.overallScore >= minScore;
1494
+ if (options.ci) {
1495
+ if (passedCi) {
1496
+ console.log(
1497
+ pc3.green(`\u2713 Score ${analytics.summary.overallScore} meets minimum threshold ${minScore}
1498
+ `)
1499
+ );
1500
+ } else {
1501
+ console.log(
1502
+ pc3.red(
1503
+ `\u2717 Score ${analytics.summary.overallScore} below minimum threshold ${minScore}
1504
+ `
1505
+ )
1506
+ );
1507
+ }
1508
+ }
1509
+ return {
1510
+ success: !options.ci || passedCi,
1511
+ analytics,
1512
+ outputPath
1513
+ };
1514
+ }
1515
+ function printConsoleSummary(analytics) {
1516
+ const { summary, coverage, recommendations } = analytics;
1517
+ const grade = getGrade(summary.overallScore);
1518
+ console.log(
1519
+ pc3.bold(
1520
+ `Overall Score: ${colorizeScore(summary.overallScore)} (${grade})
1521
+ `
1522
+ )
1523
+ );
1524
+ console.log(pc3.dim("Summary"));
1525
+ console.log(` Components: ${pc3.white(summary.totalComponents.toString())}`);
1526
+ console.log(` Variants: ${pc3.white(summary.totalVariants.toString())}`);
1527
+ console.log(` Props: ${pc3.white(summary.totalProps.toString())}`);
1528
+ console.log(` Categories: ${pc3.white(summary.categories.join(", "))}`);
1529
+ console.log();
1530
+ console.log(pc3.dim("Coverage"));
1531
+ console.log(` Description: ${formatCoverage(coverage.fields.description)}`);
1532
+ console.log(` Usage when: ${formatCoverage(coverage.fields.usageWhen)}`);
1533
+ console.log(` Usage whenNot:${formatCoverage(coverage.fields.usageWhenNot)}`);
1534
+ console.log(` Guidelines: ${formatCoverage(coverage.fields.guidelines)}`);
1535
+ console.log(` Relations: ${formatCoverage(coverage.fields.relations)}`);
1536
+ console.log();
1537
+ if (recommendations.length > 0) {
1538
+ console.log(pc3.dim("Top Recommendations"));
1539
+ for (const rec of recommendations.slice(0, 3)) {
1540
+ const priority = rec.priority === "high" ? pc3.red(`[${rec.priority}]`) : rec.priority === "medium" ? pc3.yellow(`[${rec.priority}]`) : pc3.dim(`[${rec.priority}]`);
1541
+ console.log(` ${priority} ${rec.title}`);
1542
+ }
1543
+ console.log();
1544
+ }
1545
+ }
1546
+ function formatCoverage(field) {
1547
+ const pct = colorizeScore(field.percentage);
1548
+ return `${pct} (${field.covered}/${field.total})`;
1549
+ }
1550
+ function colorizeScore(score) {
1551
+ if (score >= 80) return pc3.green(`${score}%`);
1552
+ if (score >= 60) return pc3.yellow(`${score}%`);
1553
+ return pc3.red(`${score}%`);
1554
+ }
1555
+ function getDefaultOutputPath(format, configDir) {
1556
+ const filename = format === "html" ? "segments-report.html" : "segments-report.json";
1557
+ return join4(configDir, filename);
1558
+ }
1559
+ async function openInBrowser(path) {
1560
+ const { platform } = await import("os");
1561
+ const { exec } = await import("child_process");
1562
+ const os = platform();
1563
+ const cmd = os === "darwin" ? `open "${path}"` : os === "win32" ? `start "" "${path}"` : `xdg-open "${path}"`;
1564
+ exec(cmd);
1565
+ }
1566
+ function createEmptyAnalytics() {
1567
+ return {
1568
+ analyzedAt: /* @__PURE__ */ new Date(),
1569
+ summary: {
1570
+ totalComponents: 0,
1571
+ totalVariants: 0,
1572
+ totalProps: 0,
1573
+ categories: [],
1574
+ overallScore: 0
1575
+ },
1576
+ inventory: {
1577
+ byCategory: {},
1578
+ byStatus: {},
1579
+ byVariantCount: [],
1580
+ byPropCount: []
1581
+ },
1582
+ coverage: {
1583
+ overall: 0,
1584
+ fields: {
1585
+ description: { covered: 0, total: 0, percentage: 0 },
1586
+ usageWhen: { covered: 0, total: 0, percentage: 0 },
1587
+ usageWhenNot: { covered: 0, total: 0, percentage: 0 },
1588
+ guidelines: { covered: 0, total: 0, percentage: 0 },
1589
+ accessibility: { covered: 0, total: 0, percentage: 0 },
1590
+ relations: { covered: 0, total: 0, percentage: 0 },
1591
+ propDescriptions: { covered: 0, total: 0, percentage: 0 },
1592
+ propConstraints: { covered: 0, total: 0, percentage: 0 }
1593
+ },
1594
+ incomplete: []
1595
+ },
1596
+ quality: {
1597
+ missingWhenNot: [],
1598
+ isolated: [],
1599
+ deprecated: [],
1600
+ fewVariants: [],
1601
+ undocumentedProps: [],
1602
+ unconstrainedProps: []
1603
+ },
1604
+ distribution: {
1605
+ variantsPerComponent: [],
1606
+ propsPerComponent: [],
1607
+ componentsPerCategory: [],
1608
+ statusDistribution: [],
1609
+ tagFrequency: []
1610
+ },
1611
+ recommendations: []
1612
+ };
1613
+ }
1614
+
1615
+ export {
1616
+ validateSchema,
1617
+ validateCoverage,
1618
+ validateAll,
1619
+ buildSegments,
1620
+ buildFragmentsDir,
1621
+ runScreenshotCommand,
1622
+ runDiffCommand,
1623
+ runAnalyzeCommand
1624
+ };
1625
+ //# sourceMappingURL=chunk-7OPWMLOE.js.map