@fragments-sdk/cli 0.14.3 → 0.15.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 (135) hide show
  1. package/README.md +0 -3
  2. package/dist/bin.js +4290 -3754
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-TXFCEDOC.js → chunk-2WXKALIG.js} +2 -2
  5. package/dist/{chunk-I34BC3CU.js → chunk-32LIWN2P.js} +1006 -3
  6. package/dist/chunk-32LIWN2P.js.map +1 -0
  7. package/dist/{chunk-55KERLWL.js → chunk-65WSVDV5.js} +314 -89
  8. package/dist/chunk-65WSVDV5.js.map +1 -0
  9. package/dist/chunk-7DZC4YEV.js +294 -0
  10. package/dist/chunk-7DZC4YEV.js.map +1 -0
  11. package/dist/{chunk-LOYS64QS.js → chunk-7WHVW72L.js} +230 -19
  12. package/dist/chunk-7WHVW72L.js.map +1 -0
  13. package/dist/{chunk-PJT5IZ37.js → chunk-BJE3425I.js} +19 -52
  14. package/dist/{chunk-PJT5IZ37.js.map → chunk-BJE3425I.js.map} +1 -1
  15. package/dist/{chunk-5A6X2Y73.js → chunk-CZD3AD4Q.js} +12 -11
  16. package/dist/chunk-CZD3AD4Q.js.map +1 -0
  17. package/dist/{chunk-EYXVAMEX.js → chunk-MN3TJ3D5.js} +72 -3
  18. package/dist/chunk-MN3TJ3D5.js.map +1 -0
  19. package/dist/chunk-QCN35LJU.js +630 -0
  20. package/dist/chunk-QCN35LJU.js.map +1 -0
  21. package/dist/chunk-T47OLCSF.js +36 -0
  22. package/dist/chunk-T47OLCSF.js.map +1 -0
  23. package/dist/{chunk-APTQIBS5.js → chunk-XJQ5BIWI.js} +144 -1049
  24. package/dist/chunk-XJQ5BIWI.js.map +1 -0
  25. package/dist/codebase-scanner-VOTPXRYW.js +22 -0
  26. package/dist/converter-JLINP7CJ.js +34 -0
  27. package/dist/converter-JLINP7CJ.js.map +1 -0
  28. package/dist/core/index.js +43 -1
  29. package/dist/{generate-RYWIPDN2.js → generate-A4FP5426.js} +3 -4
  30. package/dist/{generate-RYWIPDN2.js.map → generate-A4FP5426.js.map} +1 -1
  31. package/dist/govern-scan-UCBZR6D6.js +280 -0
  32. package/dist/govern-scan-UCBZR6D6.js.map +1 -0
  33. package/dist/index.d.ts +2 -1
  34. package/dist/index.js +11 -11
  35. package/dist/{init-WRUSW7R5.js → init-HGSM35XA.js} +131 -128
  36. package/dist/init-HGSM35XA.js.map +1 -0
  37. package/dist/{init-cloud-REQ3XLHO.js → init-cloud-MQ6GRJAZ.js} +2 -2
  38. package/dist/mcp-bin.js +5 -36
  39. package/dist/mcp-bin.js.map +1 -1
  40. package/dist/scan-VNNKACG2.js +15 -0
  41. package/dist/{scan-generate-TFZVL3BT.js → scan-generate-TWRHNU5M.js} +335 -46
  42. package/dist/scan-generate-TWRHNU5M.js.map +1 -0
  43. package/dist/scanner-7LAZYPWZ.js +13 -0
  44. package/dist/{service-HKJ6B7P7.js → service-FHQU7YS7.js} +27 -23
  45. package/dist/{snapshot-C5DYIGIV.js → snapshot-KQEQ6XHL.js} +2 -2
  46. package/dist/{static-viewer-DUVC4UIM.js → static-viewer-63PG6FWY.js} +3 -3
  47. package/dist/static-viewer-63PG6FWY.js.map +1 -0
  48. package/dist/{test-JW7JIDFG.js → test-UQYUCZIS.js} +4 -6
  49. package/dist/{test-JW7JIDFG.js.map → test-UQYUCZIS.js.map} +1 -1
  50. package/dist/{tokens-KE73G5JC.js → tokens-6GYKDV6U.js} +6 -5
  51. package/dist/{tokens-KE73G5JC.js.map → tokens-6GYKDV6U.js.map} +1 -1
  52. package/dist/tokens-generate-VTZV5EEW.js +86 -0
  53. package/dist/tokens-generate-VTZV5EEW.js.map +1 -0
  54. package/package.json +6 -6
  55. package/src/bin.ts +210 -48
  56. package/src/build.ts +130 -6
  57. package/src/commands/__fixtures__/shadcn-label-wrapper/package.json +7 -0
  58. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +42 -0
  59. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.tsx +11 -0
  60. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +20 -0
  61. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.tsx +14 -0
  62. package/src/commands/__fixtures__/shadcn-label-wrapper/tsconfig.app.json +23 -0
  63. package/src/commands/__tests__/init.test.ts +113 -0
  64. package/src/commands/__tests__/scan-generate.test.ts +188 -69
  65. package/src/commands/__tests__/verify.test.ts +91 -0
  66. package/src/commands/discover.ts +151 -0
  67. package/src/commands/enhance.ts +3 -1
  68. package/src/commands/govern-scan.ts +386 -0
  69. package/src/commands/govern.ts +2 -2
  70. package/src/commands/init.ts +152 -28
  71. package/src/commands/inspect.ts +290 -0
  72. package/src/commands/migrate-contract.ts +85 -0
  73. package/src/commands/scan-generate.ts +438 -50
  74. package/src/commands/scan.ts +1 -0
  75. package/src/commands/setup.ts +27 -50
  76. package/src/commands/tokens-generate.ts +113 -0
  77. package/src/commands/verify.ts +195 -1
  78. package/src/core/__fixtures__/shadcn-input/input.tsx +7 -0
  79. package/src/core/__fixtures__/shadcn-input/tsconfig.json +14 -0
  80. package/src/core/__fixtures__/shadcn-label/label.tsx +11 -0
  81. package/src/core/__fixtures__/shadcn-label/primitive.tsx +14 -0
  82. package/src/core/__fixtures__/shadcn-label/tsconfig.json +14 -0
  83. package/src/core/__fixtures__/shadcn-radix-label/label.tsx +11 -0
  84. package/src/core/__fixtures__/shadcn-radix-label/node_modules/radix-ui/index.d.ts +12 -0
  85. package/src/core/__fixtures__/shadcn-radix-label/tsconfig.json +14 -0
  86. package/src/core/__tests__/contract-parity.test.ts +316 -0
  87. package/src/core/component-extractor.test.ts +39 -0
  88. package/src/core/component-extractor.ts +92 -1
  89. package/src/core/config.ts +2 -1
  90. package/src/core/discovery.ts +13 -2
  91. package/src/core/drift-verifier.ts +123 -0
  92. package/src/core/extractor-adapter.ts +80 -0
  93. package/src/mcp/__tests__/projectFields.test.ts +1 -1
  94. package/src/mcp/utils.ts +1 -50
  95. package/src/migrate/converter.ts +3 -3
  96. package/src/migrate/fragment-to-contract.ts +253 -0
  97. package/src/migrate/report.ts +1 -1
  98. package/src/scripts/token-benchmark.ts +121 -0
  99. package/src/service/__tests__/props-extractor.test.ts +94 -0
  100. package/src/service/__tests__/token-normalizer.test.ts +690 -0
  101. package/src/service/ast-utils.ts +4 -23
  102. package/src/service/babel-config.ts +23 -0
  103. package/src/service/enhance/converter.ts +61 -0
  104. package/src/service/enhance/props-extractor.ts +25 -8
  105. package/src/service/enhance/scanner.ts +5 -24
  106. package/src/service/snippet-validation.ts +9 -3
  107. package/src/service/token-normalizer.ts +510 -0
  108. package/src/shared/index.ts +1 -0
  109. package/src/shared/project-fields.ts +46 -0
  110. package/src/viewer/__tests__/viewer-integration.test.ts +8 -8
  111. package/src/viewer/preview-adapter.ts +116 -0
  112. package/src/viewer/style-utils.ts +27 -412
  113. package/src/viewer/vite-plugin.ts +2 -2
  114. package/dist/chunk-55KERLWL.js.map +0 -1
  115. package/dist/chunk-5A6X2Y73.js.map +0 -1
  116. package/dist/chunk-APTQIBS5.js.map +0 -1
  117. package/dist/chunk-EYXVAMEX.js.map +0 -1
  118. package/dist/chunk-I34BC3CU.js.map +0 -1
  119. package/dist/chunk-LOYS64QS.js.map +0 -1
  120. package/dist/chunk-ZKTFKHWN.js +0 -324
  121. package/dist/chunk-ZKTFKHWN.js.map +0 -1
  122. package/dist/discovery-VDANZAJ2.js +0 -28
  123. package/dist/init-WRUSW7R5.js.map +0 -1
  124. package/dist/scan-YJHQIRKG.js +0 -14
  125. package/dist/scan-generate-TFZVL3BT.js.map +0 -1
  126. package/dist/viewer-2TZS3NDL.js +0 -2730
  127. package/dist/viewer-2TZS3NDL.js.map +0 -1
  128. package/src/commands/dev.ts +0 -107
  129. /package/dist/{chunk-TXFCEDOC.js.map → chunk-2WXKALIG.js.map} +0 -0
  130. /package/dist/{discovery-VDANZAJ2.js.map → codebase-scanner-VOTPXRYW.js.map} +0 -0
  131. /package/dist/{init-cloud-REQ3XLHO.js.map → init-cloud-MQ6GRJAZ.js.map} +0 -0
  132. /package/dist/{scan-YJHQIRKG.js.map → scan-VNNKACG2.js.map} +0 -0
  133. /package/dist/{service-HKJ6B7P7.js.map → scanner-7LAZYPWZ.js.map} +0 -0
  134. /package/dist/{static-viewer-DUVC4UIM.js.map → service-FHQU7YS7.js.map} +0 -0
  135. /package/dist/{snapshot-C5DYIGIV.js.map → snapshot-KQEQ6XHL.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/service/enhance/scanner.ts","../src/service/babel-config.ts"],"sourcesContent":["/**\n * AST Scanner for Component Usage Analysis\n *\n * Scans TypeScript/TSX files to find:\n * - Component imports (named, default, aliased)\n * - Component usages in JSX with props\n */\n\nimport { parse } from \"@babel/parser\";\nimport { BABEL_PARSER_OPTIONS } from \"../babel-config.js\";\nimport _traverse from \"@babel/traverse\";\nimport * as t from \"@babel/types\";\n\n// Handle CommonJS/ESM interop\nconst traverse = (_traverse as unknown as { default: typeof _traverse }).default || _traverse;\nimport { readFile } from \"node:fs/promises\";\nimport type {\n ComponentImport,\n ComponentUsage,\n UsageProps,\n} from \"./types.js\";\n\n\n/**\n * Scan a file for component imports\n *\n * Handles:\n * - Named imports: import { Button } from './Button'\n * - Default imports: import Button from './Button'\n * - Aliased imports: import { Button as Btn } from './Button'\n * - Namespace imports: import * as Components from './index'\n */\nexport async function scanFileForImports(\n filePath: string\n): Promise<ComponentImport[]> {\n const imports: ComponentImport[] = [];\n\n try {\n const sourceCode = await readFile(filePath, \"utf-8\");\n const ast = parse(sourceCode, BABEL_PARSER_OPTIONS);\n\n traverse(ast, {\n ImportDeclaration(path) {\n const source = path.node.source.value;\n const line = path.node.loc?.start.line ?? 0;\n\n for (const specifier of path.node.specifiers) {\n if (t.isImportSpecifier(specifier)) {\n // Named import: import { Button } from './Button'\n // or aliased: import { Button as Btn } from './Button'\n const importedName = t.isIdentifier(specifier.imported)\n ? specifier.imported.name\n : specifier.imported.value;\n const localName = specifier.local.name;\n\n // Only track PascalCase names (likely components)\n if (isPascalCase(importedName)) {\n imports.push({\n componentName: importedName,\n localName,\n source,\n isDefault: false,\n filePath,\n line,\n });\n }\n } else if (t.isImportDefaultSpecifier(specifier)) {\n // Default import: import Button from './Button'\n const localName = specifier.local.name;\n\n // Only track PascalCase names (likely components)\n if (isPascalCase(localName)) {\n imports.push({\n componentName: localName,\n localName,\n source,\n isDefault: true,\n filePath,\n line,\n });\n }\n }\n // Skip namespace imports for now (import * as X)\n }\n },\n });\n } catch (error) {\n // Return empty array for files with parse errors\n // Caller can check if empty means \"no imports\" or \"parse error\"\n console.warn(`Failed to parse ${filePath}:`, (error as Error).message);\n return [];\n }\n\n return imports;\n}\n\n/**\n * Scan a file for component usages in JSX\n *\n * @param filePath - File to scan\n * @param componentNames - Map of localName -> componentName for components to track\n */\nexport async function scanFileForUsages(\n filePath: string,\n componentNames: Map<string, string>\n): Promise<ComponentUsage[]> {\n const usages: ComponentUsage[] = [];\n\n try {\n const sourceCode = await readFile(filePath, \"utf-8\");\n const lines = sourceCode.split(\"\\n\");\n const ast = parse(sourceCode, BABEL_PARSER_OPTIONS);\n\n traverse(ast, {\n JSXOpeningElement(path) {\n const elementName = getJSXElementName(path.node.name);\n if (!elementName) return;\n\n // Check if this is a tracked component (by local name)\n const componentName = componentNames.get(elementName);\n if (!componentName) return;\n\n const loc = path.node.loc;\n if (!loc) return;\n\n // Extract props\n const props = extractProps(path.node.attributes);\n\n // Get surrounding context (3 lines before and after)\n const contextLines = getContextLines(lines, loc.start.line - 1, 3);\n\n // Check if conditionally rendered\n const isConditional = checkIfConditional(path);\n\n // Get parent element name\n const parentElement = getParentElementName(path);\n\n usages.push({\n componentName,\n filePath,\n line: loc.start.line,\n column: loc.start.column,\n props,\n context: contextLines,\n parentElement,\n hasSpreadProps: props.spreads.length > 0,\n isConditional,\n });\n },\n });\n } catch (error) {\n console.warn(`Failed to parse ${filePath}:`, (error as Error).message);\n return [];\n }\n\n return usages;\n}\n\n/**\n * Scan a file for both imports and usages in a single pass\n * More efficient for scanning many files\n */\nexport async function scanFile(\n filePath: string,\n trackedComponents?: Set<string>\n): Promise<{ imports: ComponentImport[]; usages: ComponentUsage[] }> {\n const imports: ComponentImport[] = [];\n const usages: ComponentUsage[] = [];\n\n try {\n const sourceCode = await readFile(filePath, \"utf-8\");\n const lines = sourceCode.split(\"\\n\");\n const ast = parse(sourceCode, BABEL_PARSER_OPTIONS);\n\n // Track local names to component names mapping within this file\n const localToComponent = new Map<string, string>();\n\n traverse(ast, {\n ImportDeclaration(path) {\n const source = path.node.source.value;\n const line = path.node.loc?.start.line ?? 0;\n\n for (const specifier of path.node.specifiers) {\n if (t.isImportSpecifier(specifier)) {\n const importedName = t.isIdentifier(specifier.imported)\n ? specifier.imported.name\n : specifier.imported.value;\n const localName = specifier.local.name;\n\n if (isPascalCase(importedName)) {\n // If we're tracking specific components, only add those\n if (!trackedComponents || trackedComponents.has(importedName)) {\n imports.push({\n componentName: importedName,\n localName,\n source,\n isDefault: false,\n filePath,\n line,\n });\n localToComponent.set(localName, importedName);\n }\n }\n } else if (t.isImportDefaultSpecifier(specifier)) {\n const localName = specifier.local.name;\n\n if (isPascalCase(localName)) {\n if (!trackedComponents || trackedComponents.has(localName)) {\n imports.push({\n componentName: localName,\n localName,\n source,\n isDefault: true,\n filePath,\n line,\n });\n localToComponent.set(localName, localName);\n }\n }\n }\n }\n },\n\n JSXOpeningElement(path) {\n const elementName = getJSXElementName(path.node.name);\n if (!elementName) return;\n\n // Check if this is a tracked component (by local name)\n const componentName = localToComponent.get(elementName);\n if (!componentName) return;\n\n const loc = path.node.loc;\n if (!loc) return;\n\n const props = extractProps(path.node.attributes);\n const contextLines = getContextLines(lines, loc.start.line - 1, 3);\n const isConditional = checkIfConditional(path);\n const parentElement = getParentElementName(path);\n\n usages.push({\n componentName,\n filePath,\n line: loc.start.line,\n column: loc.start.column,\n props,\n context: contextLines,\n parentElement,\n hasSpreadProps: props.spreads.length > 0,\n isConditional,\n });\n },\n });\n } catch (error) {\n console.warn(`Failed to parse ${filePath}:`, (error as Error).message);\n return { imports: [], usages: [] };\n }\n\n return { imports, usages };\n}\n\n/**\n * Check if a string is PascalCase (likely a component name)\n */\nfunction isPascalCase(str: string): boolean {\n return /^[A-Z][a-zA-Z0-9]*$/.test(str);\n}\n\n/**\n * Get the element name from a JSX element name node\n */\nfunction getJSXElementName(\n name: t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName\n): string | null {\n if (t.isJSXIdentifier(name)) {\n return name.name;\n }\n if (t.isJSXMemberExpression(name)) {\n // Handle Component.SubComponent - return the full path\n const parts: string[] = [];\n let current: t.JSXMemberExpression | t.JSXIdentifier = name;\n while (t.isJSXMemberExpression(current)) {\n parts.unshift(current.property.name);\n current = current.object as t.JSXMemberExpression | t.JSXIdentifier;\n }\n if (t.isJSXIdentifier(current)) {\n parts.unshift(current.name);\n }\n return parts.join(\".\");\n }\n return null;\n}\n\n/**\n * Extract props from JSX attributes\n */\nfunction extractProps(\n attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[]\n): UsageProps {\n const result: UsageProps = {\n static: {},\n dynamic: [],\n spreads: [],\n };\n\n for (const attr of attributes) {\n if (t.isJSXSpreadAttribute(attr)) {\n // {...props} or {...otherProps}\n if (t.isIdentifier(attr.argument)) {\n result.spreads.push(attr.argument.name);\n } else {\n result.spreads.push(\"(expression)\");\n }\n } else if (t.isJSXAttribute(attr)) {\n const propName = t.isJSXIdentifier(attr.name)\n ? attr.name.name\n : `${attr.name.namespace.name}:${attr.name.name.name}`;\n\n const value = attr.value;\n\n if (value === null) {\n // Boolean shorthand: <Button disabled />\n result.static[propName] = true;\n } else if (t.isStringLiteral(value)) {\n // String literal: <Button variant=\"primary\" />\n result.static[propName] = value.value;\n } else if (t.isJSXExpressionContainer(value)) {\n const expr = value.expression;\n\n if (t.isStringLiteral(expr)) {\n result.static[propName] = expr.value;\n } else if (t.isNumericLiteral(expr)) {\n result.static[propName] = expr.value;\n } else if (t.isBooleanLiteral(expr)) {\n result.static[propName] = expr.value;\n } else if (t.isTemplateLiteral(expr) && expr.expressions.length === 0) {\n // Static template literal: variant={`primary`}\n result.static[propName] = expr.quasis.map((q) => q.value.raw).join(\"\");\n } else {\n // Dynamic expression\n result.dynamic.push(propName);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Get surrounding context lines\n */\nfunction getContextLines(\n lines: string[],\n centerLine: number,\n radius: number\n): string {\n const start = Math.max(0, centerLine - radius);\n const end = Math.min(lines.length, centerLine + radius + 1);\n return lines.slice(start, end).join(\"\\n\");\n}\n\n/**\n * Check if JSX element is conditionally rendered\n */\nfunction checkIfConditional(path: babel.NodePath<t.JSXOpeningElement>): boolean {\n let current: babel.NodePath | null = path.parentPath;\n\n while (current) {\n // Check for {condition && <Component />}\n if (\n current.isLogicalExpression() &&\n current.node.operator === \"&&\"\n ) {\n return true;\n }\n\n // Check for {condition ? <Component /> : null}\n if (current.isConditionalExpression()) {\n return true;\n }\n\n // Check for ternary in JSX expression container\n if (\n current.isJSXExpressionContainer() &&\n current.parentPath?.isJSXElement()\n ) {\n const expr = current.node.expression;\n if (\n t.isLogicalExpression(expr) ||\n t.isConditionalExpression(expr)\n ) {\n return true;\n }\n }\n\n current = current.parentPath;\n }\n\n return false;\n}\n\n/**\n * Get the parent JSX element name\n */\nfunction getParentElementName(\n path: babel.NodePath<t.JSXOpeningElement>\n): string | undefined {\n let current: babel.NodePath | null = path.parentPath;\n const currentElementName = getJSXElementName(path.node.name);\n\n while (current) {\n if (current.isJSXElement()) {\n const opening = current.node.openingElement;\n const name = getJSXElementName(opening.name);\n if (name && name !== currentElementName) {\n return name;\n }\n }\n current = current.parentPath;\n }\n\n return undefined;\n}\n\n// Type import for babel NodePath\nimport type * as babel from \"@babel/traverse\";\n","/**\n * Shared Babel parser configuration for React/TypeScript files.\n */\n\nimport type { ParserOptions } from \"@babel/parser\";\n\nexport const BABEL_PARSER_OPTIONS: ParserOptions = {\n sourceType: \"module\",\n plugins: [\n \"jsx\",\n \"typescript\",\n [\"decorators\", { decoratorsBeforeExport: true }],\n \"classProperties\",\n \"classPrivateProperties\",\n \"classPrivateMethods\",\n \"exportDefaultFrom\",\n \"exportNamespaceFrom\",\n \"dynamicImport\",\n \"nullishCoalescingOperator\",\n \"optionalChaining\",\n \"objectRestSpread\",\n ],\n};\n"],"mappings":"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","names":[]}
@@ -1,20 +1,18 @@
1
1
  import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
2
2
  import {
3
3
  createComponentExtractor
4
- } from "./chunk-EYXVAMEX.js";
5
- import {
6
- generateContextMd,
7
- generateRegistry,
8
- loadFragmentFile,
9
- parseFragmentFile
10
- } from "./chunk-55KERLWL.js";
4
+ } from "./chunk-MN3TJ3D5.js";
11
5
  import {
12
6
  discoverBlockFiles,
13
7
  discoverComponentFiles,
14
8
  discoverFragmentFiles,
15
9
  discoverTokenFiles,
16
- extractComponentName
17
- } from "./chunk-ZKTFKHWN.js";
10
+ extractComponentName,
11
+ generateContextMd,
12
+ generateRegistry,
13
+ loadFragmentFile,
14
+ parseFragmentFile
15
+ } from "./chunk-65WSVDV5.js";
18
16
  import {
19
17
  BrowserPool,
20
18
  CaptureEngine,
@@ -24,16 +22,20 @@ import {
24
22
  formatMs,
25
23
  generateHtmlReport,
26
24
  getGrade
27
- } from "./chunk-APTQIBS5.js";
25
+ } from "./chunk-XJQ5BIWI.js";
28
26
  import {
29
27
  BRAND,
30
28
  DEFAULTS,
31
29
  classifyComplexity,
32
30
  compileBlock,
33
31
  fragmentDefinitionSchema,
32
+ isContractFile,
33
+ isDTCGFile,
34
+ parseComponentContract,
35
+ parseDTCGFile,
34
36
  parseTokenFile,
35
37
  resolvePerformanceConfig
36
- } from "./chunk-I34BC3CU.js";
38
+ } from "./chunk-32LIWN2P.js";
37
39
 
38
40
  // src/service/snippet-validation.ts
39
41
  import ts from "typescript";
@@ -369,8 +371,10 @@ function sortAndFilterBatch(files, componentStart, componentLimit) {
369
371
  const getComponentName = (relativePath) => {
370
372
  const normalized = relativePath.replace(/\\/g, "/");
371
373
  const fileName = normalized.split("/").pop() ?? normalized;
372
- if (fileName.endsWith(BRAND.fileExtension)) {
373
- return fileName.slice(0, -BRAND.fileExtension.length);
374
+ for (const ext of [BRAND.fileExtension, ".fragment.tsx", ".fragment.ts"]) {
375
+ if (fileName.endsWith(ext)) {
376
+ return fileName.slice(0, -ext.length);
377
+ }
374
378
  }
375
379
  return extractComponentName(relativePath);
376
380
  };
@@ -428,7 +432,9 @@ async function validateSnippetPolicy(config, configDir, options = {}) {
428
432
  const policy = normalizePolicy(config.snippets, options);
429
433
  const issues = [];
430
434
  const discovered = await discoverFragmentFiles(config, configDir);
431
- const fragmentFiles = discovered.filter((file) => file.relativePath.endsWith(BRAND.fileExtension));
435
+ const fragmentFiles = discovered.filter(
436
+ (file) => file.relativePath.endsWith(".fragment.tsx") || file.relativePath.endsWith(".fragment.ts")
437
+ );
432
438
  const batchResult = sortAndFilterBatch(fragmentFiles, policy.componentStart, policy.componentLimit);
433
439
  if (batchResult.warning) {
434
440
  issues.push({ file: "snippets", message: batchResult.warning });
@@ -886,6 +892,111 @@ function findVariablesFile(tokensDir) {
886
892
  return null;
887
893
  }
888
894
 
895
+ // src/core/extractor-adapter.ts
896
+ var ReactExtractorAdapter = class {
897
+ extractor;
898
+ verificationLevel = "full";
899
+ constructor(tsconfigPath) {
900
+ this.extractor = createComponentExtractor(tsconfigPath);
901
+ }
902
+ canHandle(framework) {
903
+ return framework === "react";
904
+ }
905
+ extract(sourcePath, exportName) {
906
+ return this.extractor.extract(sourcePath, exportName);
907
+ }
908
+ dispose() {
909
+ this.extractor.dispose();
910
+ }
911
+ };
912
+ var NoopExtractorAdapter = class {
913
+ verificationLevel = "none";
914
+ canHandle() {
915
+ return true;
916
+ }
917
+ extract() {
918
+ return null;
919
+ }
920
+ dispose() {
921
+ }
922
+ };
923
+ function createExtractorAdapter(framework, tsconfigPath) {
924
+ if (framework === "react") {
925
+ return new ReactExtractorAdapter(tsconfigPath);
926
+ }
927
+ return new NoopExtractorAdapter();
928
+ }
929
+
930
+ // src/core/drift-verifier.ts
931
+ function verifyContractDrift(contract, extracted) {
932
+ const contractPropNames = new Set(Object.keys(contract.props));
933
+ const sourcePropNames = new Set(
934
+ Object.entries(extracted.props).filter(([, p]) => p.source === "local").map(([name]) => name)
935
+ );
936
+ const removedProps = [];
937
+ const undocumentedProps = [];
938
+ const typeMismatches = [];
939
+ const defaultMismatches = [];
940
+ for (const name of contractPropNames) {
941
+ if (!sourcePropNames.has(name)) {
942
+ removedProps.push(name);
943
+ }
944
+ }
945
+ for (const name of sourcePropNames) {
946
+ if (!contractPropNames.has(name)) {
947
+ undocumentedProps.push(name);
948
+ }
949
+ }
950
+ for (const name of contractPropNames) {
951
+ const contractProp = contract.props[name];
952
+ const sourceProp = extracted.props[name];
953
+ if (!sourceProp || sourceProp.source !== "local") continue;
954
+ if (contractProp.type !== sourceProp.typeKind) {
955
+ typeMismatches.push({
956
+ prop: name,
957
+ contract: contractProp.type,
958
+ source: sourceProp.typeKind
959
+ });
960
+ }
961
+ const contractDefault = contractProp.default;
962
+ const sourceDefault = sourceProp.default;
963
+ if (contractDefault !== void 0 && sourceDefault !== void 0) {
964
+ if (String(contractDefault) !== String(sourceDefault)) {
965
+ defaultMismatches.push({
966
+ prop: name,
967
+ contract: contractDefault,
968
+ source: sourceDefault
969
+ });
970
+ }
971
+ }
972
+ }
973
+ const isClean = removedProps.length === 0 && undocumentedProps.length === 0 && typeMismatches.length === 0 && defaultMismatches.length === 0;
974
+ return {
975
+ componentName: contract.meta.name,
976
+ removedProps,
977
+ undocumentedProps,
978
+ typeMismatches,
979
+ defaultMismatches,
980
+ isClean
981
+ };
982
+ }
983
+ function formatDriftReport(report2) {
984
+ const lines = [`Drift detected for ${report2.componentName}:`];
985
+ if (report2.removedProps.length > 0) {
986
+ lines.push(` Removed from source: ${report2.removedProps.join(", ")}`);
987
+ }
988
+ if (report2.undocumentedProps.length > 0) {
989
+ lines.push(` Missing from contract: ${report2.undocumentedProps.join(", ")}`);
990
+ }
991
+ for (const m of report2.typeMismatches) {
992
+ lines.push(` Type mismatch: ${m.prop} (contract: ${m.contract}, source: ${m.source})`);
993
+ }
994
+ for (const m of report2.defaultMismatches) {
995
+ lines.push(` Default mismatch: ${m.prop} (contract: ${String(m.contract)}, source: ${String(m.source)})`);
996
+ }
997
+ return lines.join("\n");
998
+ }
999
+
889
1000
  // src/core/graph-extractor.ts
890
1001
  import ts3 from "typescript";
891
1002
  import { readFileSync, existsSync as existsSync4 } from "fs";
@@ -1573,6 +1684,7 @@ async function buildFragments(config, configDir) {
1573
1684
  const errors = [];
1574
1685
  const warnings = [];
1575
1686
  const fragments = {};
1687
+ const contractSourcedNames = /* @__PURE__ */ new Set();
1576
1688
  const tsconfigCandidates = [
1577
1689
  resolve5(configDir, "tsconfig.json"),
1578
1690
  resolve5(configDir, "..", "tsconfig.json")
@@ -1582,6 +1694,86 @@ async function buildFragments(config, configDir) {
1582
1694
  for (const file of files) {
1583
1695
  try {
1584
1696
  const content = await readFile3(file.absolutePath, "utf-8");
1697
+ if (isContractFile(file.absolutePath)) {
1698
+ try {
1699
+ const parsed2 = parseComponentContract(content, file.relativePath);
1700
+ const framework = parsed2.framework ?? config.framework ?? "react";
1701
+ const contractAdapter = createExtractorAdapter(framework, tsconfigPath);
1702
+ let extractedMeta2 = null;
1703
+ if (parsed2.sourcePath && parsed2.exportName && contractAdapter.canHandle(framework)) {
1704
+ try {
1705
+ const absSourcePath = resolve5(configDir, parsed2.sourcePath);
1706
+ extractedMeta2 = contractAdapter.extract(absSourcePath, parsed2.exportName);
1707
+ } catch {
1708
+ }
1709
+ }
1710
+ let mergedProps2 = parsed2.props;
1711
+ if (extractedMeta2 && Object.keys(extractedMeta2.props).length > 0) {
1712
+ mergedProps2 = mergeDocumentedAndAutoProps(mergedProps2, extractedMeta2.props);
1713
+ }
1714
+ let contractPropsSummary = parsed2.propsSummary;
1715
+ if ((!contractPropsSummary || contractPropsSummary.length === 0) && extractedMeta2) {
1716
+ contractPropsSummary = compilePropsSummary(extractedMeta2.props);
1717
+ }
1718
+ let ai2 = parsed2.ai;
1719
+ if (extractedMeta2?.composition) {
1720
+ const comp = extractedMeta2.composition;
1721
+ ai2 = {
1722
+ compositionPattern: comp.pattern,
1723
+ subComponents: comp.parts.map((p) => p.name),
1724
+ ...ai2
1725
+ };
1726
+ }
1727
+ if (parsed2.provenance?.verified && extractedMeta2) {
1728
+ const drift = verifyContractDrift(parsed2, extractedMeta2);
1729
+ if (!drift.isClean) {
1730
+ errors.push({
1731
+ file: file.relativePath,
1732
+ error: formatDriftReport(drift)
1733
+ });
1734
+ contractAdapter.dispose();
1735
+ continue;
1736
+ }
1737
+ }
1738
+ const compiled2 = {
1739
+ filePath: file.relativePath,
1740
+ meta: {
1741
+ name: parsed2.meta.name,
1742
+ description: parsed2.meta.description,
1743
+ category: parsed2.meta.category,
1744
+ tags: parsed2.meta.tags,
1745
+ status: parsed2.meta.status,
1746
+ figma: parsed2.meta.figma
1747
+ },
1748
+ usage: parsed2.usage,
1749
+ props: mergedProps2,
1750
+ relations: parsed2.relations,
1751
+ variants: parsed2.variants,
1752
+ ...parsed2.contract && { contract: parsed2.contract },
1753
+ ...ai2 && { ai: ai2 },
1754
+ framework: parsed2.framework,
1755
+ propsSummary: contractPropsSummary,
1756
+ sourcePath: parsed2.sourcePath,
1757
+ exportName: parsed2.exportName,
1758
+ provenance: parsed2.provenance
1759
+ };
1760
+ fragments[compiled2.meta.name] = compiled2;
1761
+ contractSourcedNames.add(compiled2.meta.name);
1762
+ contractAdapter.dispose();
1763
+ } catch (error) {
1764
+ errors.push({
1765
+ file: file.relativePath,
1766
+ error: `Contract parse error: ${error instanceof Error ? error.message : String(error)}`
1767
+ });
1768
+ }
1769
+ continue;
1770
+ }
1771
+ if (file.absolutePath.endsWith(".fragment.tsx") || file.absolutePath.endsWith(".fragment.ts")) {
1772
+ warnings.push({
1773
+ file: file.relativePath,
1774
+ warning: "Deprecated format: .fragment.tsx \u2014 run `fragments migrate-contract` to generate .contract.json"
1775
+ });
1776
+ }
1585
1777
  if (!content.includes("defineFragment")) {
1586
1778
  warnings.push({
1587
1779
  file: file.relativePath,
@@ -1696,9 +1888,21 @@ async function buildFragments(config, configDir) {
1696
1888
  // Include AI metadata (auto-enriched or manual)
1697
1889
  ...ai && { ai },
1698
1890
  // Include contract metadata (auto-compiled or manual)
1699
- ...contract && { contract }
1891
+ ...contract && { contract },
1892
+ // Provenance from TSX path
1893
+ provenance: {
1894
+ source: extractedMeta ? "extracted" : "manual",
1895
+ verified: !!extractedMeta
1896
+ }
1700
1897
  };
1701
- fragments[parsed.meta.name] = compiled;
1898
+ if (contractSourcedNames.has(parsed.meta.name)) {
1899
+ warnings.push({
1900
+ file: file.relativePath,
1901
+ warning: `Duplicate: "${parsed.meta.name}" already loaded from .contract.json \u2014 skipping .fragment.tsx`
1902
+ });
1903
+ } else {
1904
+ fragments[parsed.meta.name] = compiled;
1905
+ }
1702
1906
  } catch (error) {
1703
1907
  errors.push({
1704
1908
  file: file.relativePath,
@@ -1745,8 +1949,15 @@ async function buildFragments(config, configDir) {
1745
1949
  }
1746
1950
  const allContent = fileContents.map((f) => f.content).join("\n");
1747
1951
  for (const { content, path } of fileContents) {
1748
- const parsed = parseTokenFile(allContent, path);
1749
- const fileParsed = parseTokenFile(content, path);
1952
+ let fileParsed;
1953
+ let parsed;
1954
+ if (isDTCGFile(path)) {
1955
+ fileParsed = parseDTCGFile(content, path);
1956
+ parsed = fileParsed;
1957
+ } else {
1958
+ parsed = parseTokenFile(allContent, path);
1959
+ fileParsed = parseTokenFile(content, path);
1960
+ }
1750
1961
  prefix = fileParsed.prefix;
1751
1962
  total += fileParsed.total;
1752
1963
  for (const [cat, catTokens] of Object.entries(fileParsed.categories)) {
@@ -2450,4 +2661,4 @@ export {
2450
2661
  runDiffCommand,
2451
2662
  runAnalyzeCommand
2452
2663
  };
2453
- //# sourceMappingURL=chunk-LOYS64QS.js.map
2664
+ //# sourceMappingURL=chunk-7WHVW72L.js.map