@fragments-sdk/cli 0.11.1 → 0.12.1

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 (86) hide show
  1. package/dist/ai-client-I6MDWNYA.js +21 -0
  2. package/dist/bin.js +275 -368
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
  5. package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
  6. package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
  7. package/dist/chunk-GVDSFQ4E.js.map +1 -0
  8. package/dist/chunk-JJ2VRTBU.js +626 -0
  9. package/dist/chunk-JJ2VRTBU.js.map +1 -0
  10. package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
  11. package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
  12. package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
  13. package/dist/chunk-OQKMEFOS.js.map +1 -0
  14. package/dist/chunk-SXTKFDCR.js +104 -0
  15. package/dist/chunk-SXTKFDCR.js.map +1 -0
  16. package/dist/chunk-T5OMVL7E.js +443 -0
  17. package/dist/chunk-T5OMVL7E.js.map +1 -0
  18. package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
  19. package/dist/chunk-TPWGL2XS.js.map +1 -0
  20. package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
  21. package/dist/chunk-WFS63PCW.js.map +1 -0
  22. package/dist/core/index.js +9 -1
  23. package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
  24. package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
  25. package/dist/index.js +7 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/init-ZSX3NRCZ.js +636 -0
  28. package/dist/init-ZSX3NRCZ.js.map +1 -0
  29. package/dist/mcp-bin.js +2 -2
  30. package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
  31. package/dist/scan-generate-SYU4PYZD.js +1115 -0
  32. package/dist/scan-generate-SYU4PYZD.js.map +1 -0
  33. package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
  34. package/dist/{snapshot-SV2JOFZH.js → snapshot-XOISO2IS.js} +2 -2
  35. package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
  36. package/dist/static-viewer-5GXH2MGE.js.map +1 -0
  37. package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
  38. package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
  39. package/dist/{viewer-DLLJIMCK.js → viewer-7ZEAFBVN.js} +13 -13
  40. package/package.json +4 -4
  41. package/src/ai-client.ts +156 -0
  42. package/src/bin.ts +44 -2
  43. package/src/build.ts +95 -33
  44. package/src/commands/__tests__/drift-sync.test.ts +252 -0
  45. package/src/commands/__tests__/scan-generate.test.ts +497 -45
  46. package/src/commands/enhance.ts +11 -35
  47. package/src/commands/init.ts +288 -260
  48. package/src/commands/scan-generate.ts +740 -139
  49. package/src/commands/scan.ts +37 -32
  50. package/src/commands/setup.ts +143 -52
  51. package/src/commands/sync.ts +357 -0
  52. package/src/commands/validate.ts +43 -1
  53. package/src/core/component-extractor.test.ts +282 -0
  54. package/src/core/component-extractor.ts +1030 -0
  55. package/src/core/discovery.ts +93 -7
  56. package/src/service/enhance/props-extractor.ts +235 -13
  57. package/src/validators.ts +236 -0
  58. package/dist/chunk-5G3VZH43.js.map +0 -1
  59. package/dist/chunk-OQO55NKV.js.map +0 -1
  60. package/dist/chunk-WXSR2II7.js.map +0 -1
  61. package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
  62. package/dist/init-UFGK5TCN.js +0 -867
  63. package/dist/init-UFGK5TCN.js.map +0 -1
  64. package/dist/scan-generate-SJAN5MVI.js +0 -691
  65. package/dist/scan-generate-SJAN5MVI.js.map +0 -1
  66. package/src/ai.ts +0 -266
  67. package/src/commands/init-framework.ts +0 -414
  68. package/src/mcp/bin.ts +0 -36
  69. package/src/migrate/bin.ts +0 -114
  70. package/src/theme/index.ts +0 -77
  71. package/src/viewer/bin.ts +0 -86
  72. package/src/viewer/cli/health.ts +0 -256
  73. package/src/viewer/cli/index.ts +0 -33
  74. package/src/viewer/cli/scan.ts +0 -124
  75. package/src/viewer/cli/utils.ts +0 -174
  76. /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
  77. /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
  78. /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
  79. /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
  80. /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
  81. /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
  82. /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
  83. /package/dist/{snapshot-SV2JOFZH.js.map → snapshot-XOISO2IS.js.map} +0 -0
  84. /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
  85. /package/dist/{tokens-CE46OTMD.js.map → tokens-T6SIVUT5.js.map} +0 -0
  86. /package/dist/{viewer-DLLJIMCK.js.map → viewer-7ZEAFBVN.js.map} +0 -0
@@ -1,17 +1,20 @@
1
1
  import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
2
+ import {
3
+ createComponentExtractor
4
+ } from "./chunk-JJ2VRTBU.js";
2
5
  import {
3
6
  generateContextMd,
4
7
  generateRegistry,
5
8
  loadFragmentFile,
6
9
  parseFragmentFile
7
- } from "./chunk-HRFUSSZI.js";
10
+ } from "./chunk-AM4MRTMN.js";
8
11
  import {
9
12
  discoverBlockFiles,
10
13
  discoverComponentFiles,
11
14
  discoverFragmentFiles,
12
15
  discoverTokenFiles,
13
16
  extractComponentName
14
- } from "./chunk-WXSR2II7.js";
17
+ } from "./chunk-OQKMEFOS.js";
15
18
  import {
16
19
  BrowserPool,
17
20
  CaptureEngine,
@@ -21,7 +24,7 @@ import {
21
24
  formatMs,
22
25
  generateHtmlReport,
23
26
  getGrade
24
- } from "./chunk-D5PYOXEI.js";
27
+ } from "./chunk-LVWFOLUZ.js";
25
28
  import {
26
29
  BRAND,
27
30
  DEFAULTS,
@@ -30,7 +33,7 @@ import {
30
33
  fragmentDefinitionSchema,
31
34
  parseTokenFile,
32
35
  resolvePerformanceConfig
33
- } from "./chunk-OQO55NKV.js";
36
+ } from "./chunk-WFS63PCW.js";
34
37
 
35
38
  // src/service/snippet-validation.ts
36
39
  import ts from "typescript";
@@ -481,7 +484,52 @@ async function validateSnippetPolicy(config, configDir, options = {}) {
481
484
  return toValidationResult(policy, issues);
482
485
  }
483
486
 
487
+ // src/core/auto-props.ts
488
+ import { existsSync as existsSync2, statSync } from "fs";
489
+ import { dirname, extname, join as join2, resolve } from "path";
490
+ import ts2 from "typescript";
491
+ function isFile(filePath) {
492
+ if (!existsSync2(filePath)) return false;
493
+ try {
494
+ return statSync(filePath).isFile();
495
+ } catch {
496
+ return false;
497
+ }
498
+ }
499
+ function resolveModulePath(basePath) {
500
+ const candidates = [];
501
+ const extension = extname(basePath);
502
+ if (extension) {
503
+ candidates.push(basePath);
504
+ } else {
505
+ candidates.push(
506
+ `${basePath}.tsx`,
507
+ `${basePath}.ts`,
508
+ `${basePath}.jsx`,
509
+ `${basePath}.js`,
510
+ join2(basePath, "index.tsx"),
511
+ join2(basePath, "index.ts"),
512
+ join2(basePath, "index.jsx"),
513
+ join2(basePath, "index.js")
514
+ );
515
+ }
516
+ for (const candidate of candidates) {
517
+ if (isFile(candidate)) {
518
+ return resolve(candidate);
519
+ }
520
+ }
521
+ return null;
522
+ }
523
+ function resolveComponentSourcePath(fragmentFileAbsolutePath, componentImportPath) {
524
+ if (!componentImportPath) return null;
525
+ if (!componentImportPath.startsWith(".")) return null;
526
+ const fragmentDir = dirname(fragmentFileAbsolutePath);
527
+ const basePath = resolve(fragmentDir, componentImportPath);
528
+ return resolveModulePath(basePath);
529
+ }
530
+
484
531
  // src/validators.ts
532
+ import { readFile as readFile2 } from "fs/promises";
485
533
  async function validateSchema(config, configDir) {
486
534
  const files = await discoverFragmentFiles(config, configDir);
487
535
  const errors = [];
@@ -593,15 +641,162 @@ async function validateSnippets(config, configDir, options = {}) {
593
641
  warnings: snippetResult.warnings
594
642
  };
595
643
  }
644
+ async function validateDrift(config, configDir, options = {}) {
645
+ const fragmentFiles = await discoverFragmentFiles(config, configDir);
646
+ const errors = [];
647
+ const warnings = [];
648
+ const reports = [];
649
+ if (fragmentFiles.length === 0) {
650
+ return { valid: true, errors, warnings, reports };
651
+ }
652
+ const extractor = createComponentExtractor(options.tsconfig);
653
+ try {
654
+ for (const file of fragmentFiles) {
655
+ try {
656
+ const fragment = await loadFragmentFile(file.absolutePath);
657
+ if (!fragment?.meta?.name) continue;
658
+ const fileContent = await readFile2(file.absolutePath, "utf-8");
659
+ const parsed = parseFragmentFile(fileContent, file.absolutePath);
660
+ if (!parsed.componentImport) continue;
661
+ const sourcePath = resolveComponentSourcePath(file.absolutePath, parsed.componentImport);
662
+ if (!sourcePath) continue;
663
+ const meta = extractor.extract(sourcePath, fragment.meta.name);
664
+ if (!meta) continue;
665
+ const drifts = diffProps(fragment.props, meta.props);
666
+ let compositionDrift = null;
667
+ const fragmentAi = fragment.ai;
668
+ if (meta.composition && !fragmentAi?.compositionPattern) {
669
+ compositionDrift = `Source has "${meta.composition.pattern}" composition but fragment has no ai.compositionPattern`;
670
+ } else if (!meta.composition && fragmentAi?.compositionPattern) {
671
+ compositionDrift = `Fragment declares "${fragmentAi.compositionPattern}" but source has no compound pattern`;
672
+ } else if (meta.composition && fragmentAi?.compositionPattern && meta.composition.pattern !== fragmentAi.compositionPattern) {
673
+ compositionDrift = `Composition pattern changed: fragment="${fragmentAi.compositionPattern}" source="${meta.composition.pattern}"`;
674
+ }
675
+ if (drifts.length > 0 || compositionDrift) {
676
+ const report2 = {
677
+ component: fragment.meta.name,
678
+ file: file.relativePath,
679
+ drifts,
680
+ compositionDrift
681
+ };
682
+ reports.push(report2);
683
+ for (const drift of drifts) {
684
+ if (drift.kind === "removed") {
685
+ errors.push({
686
+ file: file.relativePath,
687
+ message: `Prop "${drift.prop}" documented in fragment but removed from source`,
688
+ details: `Fragment: ${drift.fragment} | Source: (not found)`
689
+ });
690
+ } else if (drift.kind === "added") {
691
+ warnings.push({
692
+ file: file.relativePath,
693
+ message: `Prop "${drift.prop}" exists in source but not documented in fragment`
694
+ });
695
+ } else {
696
+ warnings.push({
697
+ file: file.relativePath,
698
+ message: `Prop "${drift.prop}" ${drift.kind.replace("_", " ")}: fragment=${drift.fragment} source=${drift.source}`
699
+ });
700
+ }
701
+ }
702
+ if (compositionDrift) {
703
+ warnings.push({
704
+ file: file.relativePath,
705
+ message: compositionDrift
706
+ });
707
+ }
708
+ }
709
+ } catch {
710
+ }
711
+ }
712
+ } finally {
713
+ extractor.dispose();
714
+ }
715
+ return {
716
+ valid: errors.length === 0,
717
+ errors,
718
+ warnings,
719
+ reports
720
+ };
721
+ }
722
+ function diffProps(fragmentProps, sourceProps) {
723
+ const drifts = [];
724
+ const localSourceProps = Object.fromEntries(
725
+ Object.entries(sourceProps).filter(([_, p]) => p.source === "local")
726
+ );
727
+ for (const [name, sourceProp] of Object.entries(localSourceProps)) {
728
+ if (!(name in fragmentProps)) {
729
+ drifts.push({
730
+ prop: name,
731
+ kind: "added",
732
+ source: sourceProp.type,
733
+ fragment: "(not documented)"
734
+ });
735
+ }
736
+ }
737
+ for (const [name, fragProp] of Object.entries(fragmentProps)) {
738
+ if (!(name in localSourceProps)) {
739
+ drifts.push({
740
+ prop: name,
741
+ kind: "removed",
742
+ source: "(not found)",
743
+ fragment: String(fragProp.type ?? "unknown")
744
+ });
745
+ continue;
746
+ }
747
+ const sourceProp = localSourceProps[name];
748
+ if (fragProp.type && fragProp.type !== sourceProp.typeKind) {
749
+ drifts.push({
750
+ prop: name,
751
+ kind: "type_changed",
752
+ source: sourceProp.typeKind,
753
+ fragment: String(fragProp.type)
754
+ });
755
+ }
756
+ if (fragProp.required !== void 0 && fragProp.required !== sourceProp.required) {
757
+ drifts.push({
758
+ prop: name,
759
+ kind: "required_changed",
760
+ source: String(sourceProp.required),
761
+ fragment: String(fragProp.required)
762
+ });
763
+ }
764
+ if (fragProp.values && sourceProp.values) {
765
+ const fragSet = new Set(fragProp.values);
766
+ const srcSet = new Set(sourceProp.values);
767
+ const added = sourceProp.values.filter((v) => !fragSet.has(v));
768
+ const removed = Array.from(fragProp.values).filter((v) => !srcSet.has(v));
769
+ if (added.length > 0 || removed.length > 0) {
770
+ drifts.push({
771
+ prop: name,
772
+ kind: "values_changed",
773
+ source: sourceProp.values.join(", "),
774
+ fragment: Array.from(fragProp.values).join(", ")
775
+ });
776
+ }
777
+ }
778
+ if (fragProp.default !== void 0 && sourceProp.default !== void 0) {
779
+ if (String(fragProp.default) !== sourceProp.default) {
780
+ drifts.push({
781
+ prop: name,
782
+ kind: "default_changed",
783
+ source: sourceProp.default,
784
+ fragment: String(fragProp.default)
785
+ });
786
+ }
787
+ }
788
+ }
789
+ return drifts;
790
+ }
596
791
 
597
792
  // src/build.ts
598
- import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
793
+ import { readFile as readFile3, writeFile, mkdir } from "fs/promises";
599
794
  import { resolve as resolve5, join as join5 } from "path";
600
795
  import { existsSync as existsSync6 } from "fs";
601
796
 
602
797
  // src/core/token-resolver.ts
603
- import { resolve, dirname, basename } from "path";
604
- import { existsSync as existsSync2, readdirSync } from "fs";
798
+ import { resolve as resolve2, dirname as dirname2, basename } from "path";
799
+ import { existsSync as existsSync3, readdirSync } from "fs";
605
800
  function roundRgbValues(value) {
606
801
  return value.replace(
607
802
  /rgb\(([^)]+)\)/g,
@@ -647,7 +842,7 @@ async function resolveTokensWithSass(unresolvedTokens, tokensDir) {
647
842
  :root { @include vars.fui-css-variables; }
648
843
  `;
649
844
  const compiled = sass.compileString(scssSource, {
650
- loadPaths: [tokensDir, dirname(tokensDir)],
845
+ loadPaths: [tokensDir, dirname2(tokensDir)],
651
846
  style: "expanded",
652
847
  // Suppress sass deprecation warnings during build
653
848
  logger: { warn() {
@@ -673,15 +868,15 @@ async function resolveTokensWithSass(unresolvedTokens, tokensDir) {
673
868
  function findVariablesFile(tokensDir) {
674
869
  const candidates = ["_variables.scss", "variables.scss"];
675
870
  for (const name of candidates) {
676
- const path = resolve(tokensDir, name);
677
- if (existsSync2(path)) {
871
+ const path = resolve2(tokensDir, name);
872
+ if (existsSync3(path)) {
678
873
  return path;
679
874
  }
680
875
  }
681
876
  try {
682
877
  const files = readdirSync(tokensDir).filter((f) => f.endsWith(".scss"));
683
878
  for (const file of files) {
684
- const path = resolve(tokensDir, file);
879
+ const path = resolve2(tokensDir, file);
685
880
  if (file.includes("variables") || file.includes("tokens")) {
686
881
  return path;
687
882
  }
@@ -691,312 +886,6 @@ function findVariablesFile(tokensDir) {
691
886
  return null;
692
887
  }
693
888
 
694
- // src/core/auto-props.ts
695
- import { existsSync as existsSync3, statSync } from "fs";
696
- import { dirname as dirname2, extname, join as join2, resolve as resolve2 } from "path";
697
- import ts2 from "typescript";
698
- function toPosixPath(filePath) {
699
- return filePath.replace(/\\/g, "/");
700
- }
701
- function isFile(filePath) {
702
- if (!existsSync3(filePath)) return false;
703
- try {
704
- return statSync(filePath).isFile();
705
- } catch {
706
- return false;
707
- }
708
- }
709
- function resolveModulePath(basePath) {
710
- const candidates = [];
711
- const extension = extname(basePath);
712
- if (extension) {
713
- candidates.push(basePath);
714
- } else {
715
- candidates.push(
716
- `${basePath}.tsx`,
717
- `${basePath}.ts`,
718
- `${basePath}.jsx`,
719
- `${basePath}.js`,
720
- join2(basePath, "index.tsx"),
721
- join2(basePath, "index.ts"),
722
- join2(basePath, "index.jsx"),
723
- join2(basePath, "index.js")
724
- );
725
- }
726
- for (const candidate of candidates) {
727
- if (isFile(candidate)) {
728
- return resolve2(candidate);
729
- }
730
- }
731
- return null;
732
- }
733
- function resolveComponentSourcePath(fragmentFileAbsolutePath, componentImportPath) {
734
- if (!componentImportPath) return null;
735
- if (!componentImportPath.startsWith(".")) return null;
736
- const fragmentDir = dirname2(fragmentFileAbsolutePath);
737
- const basePath = resolve2(fragmentDir, componentImportPath);
738
- return resolveModulePath(basePath);
739
- }
740
- function collectTopLevelDeclarations(sourceFile) {
741
- const typeDeclarations = /* @__PURE__ */ new Map();
742
- const functionDeclarations = /* @__PURE__ */ new Map();
743
- const variableDeclarations = /* @__PURE__ */ new Map();
744
- for (const node of sourceFile.statements) {
745
- if (ts2.isInterfaceDeclaration(node)) {
746
- typeDeclarations.set(node.name.text, node);
747
- continue;
748
- }
749
- if (ts2.isTypeAliasDeclaration(node)) {
750
- typeDeclarations.set(node.name.text, node);
751
- continue;
752
- }
753
- if (ts2.isFunctionDeclaration(node) && node.name) {
754
- functionDeclarations.set(node.name.text, node);
755
- continue;
756
- }
757
- if (ts2.isVariableStatement(node)) {
758
- for (const declaration of node.declarationList.declarations) {
759
- if (ts2.isIdentifier(declaration.name)) {
760
- variableDeclarations.set(declaration.name.text, declaration);
761
- }
762
- }
763
- }
764
- }
765
- return { typeDeclarations, functionDeclarations, variableDeclarations };
766
- }
767
- function readDefaultValue(expression) {
768
- if (ts2.isStringLiteral(expression) || ts2.isNoSubstitutionTemplateLiteral(expression)) {
769
- return expression.text;
770
- }
771
- if (ts2.isNumericLiteral(expression)) {
772
- return Number(expression.text);
773
- }
774
- if (expression.kind === ts2.SyntaxKind.TrueKeyword) return true;
775
- if (expression.kind === ts2.SyntaxKind.FalseKeyword) return false;
776
- if (expression.kind === ts2.SyntaxKind.NullKeyword) return null;
777
- if (ts2.isPrefixUnaryExpression(expression) && expression.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(expression.operand)) {
778
- return -Number(expression.operand.text);
779
- }
780
- return void 0;
781
- }
782
- function extractDefaultValues(componentNode) {
783
- const defaults = {};
784
- if (!componentNode?.parameters?.length) return defaults;
785
- const firstParam = componentNode.parameters[0];
786
- if (!ts2.isObjectBindingPattern(firstParam.name)) return defaults;
787
- for (const element of firstParam.name.elements) {
788
- let propName = null;
789
- if (element.propertyName) {
790
- if (ts2.isIdentifier(element.propertyName) || ts2.isStringLiteral(element.propertyName)) {
791
- propName = element.propertyName.text;
792
- }
793
- } else if (ts2.isIdentifier(element.name)) {
794
- propName = element.name.text;
795
- }
796
- if (!propName || !element.initializer) continue;
797
- const value = readDefaultValue(element.initializer);
798
- if (value !== void 0) {
799
- defaults[propName] = value;
800
- }
801
- }
802
- return defaults;
803
- }
804
- function isNullishType(type) {
805
- return (type.flags & ts2.TypeFlags.Null) !== 0 || (type.flags & ts2.TypeFlags.Undefined) !== 0 || (type.flags & ts2.TypeFlags.Void) !== 0;
806
- }
807
- function isBooleanLikeType(type) {
808
- return (type.flags & ts2.TypeFlags.BooleanLike) !== 0 || type.flags === ts2.TypeFlags.BooleanLiteral;
809
- }
810
- function inferPropType(type, checker) {
811
- const typeText = checker.typeToString(type, void 0, ts2.TypeFormatFlags.NoTruncation);
812
- if (typeText.includes("ReactNode")) {
813
- return { type: "node" };
814
- }
815
- if (typeText.includes("ReactElement") || typeText.includes("JSX.Element")) {
816
- return { type: "element" };
817
- }
818
- if (type.getCallSignatures().length > 0) {
819
- return { type: "function" };
820
- }
821
- if (checker.isArrayType(type) || checker.isTupleType(type)) {
822
- return { type: "array" };
823
- }
824
- if (type.isUnion()) {
825
- const nonNullableTypes = type.types.filter((unionType) => !isNullishType(unionType));
826
- if (nonNullableTypes.length === 1) {
827
- return inferPropType(nonNullableTypes[0], checker);
828
- }
829
- const stringLiteralValues = nonNullableTypes.filter((unionType) => (unionType.flags & ts2.TypeFlags.StringLiteral) !== 0).map((unionType) => unionType.value);
830
- if (stringLiteralValues.length > 0 && stringLiteralValues.length === nonNullableTypes.length) {
831
- return { type: "enum", values: stringLiteralValues };
832
- }
833
- if (nonNullableTypes.every((unionType) => isBooleanLikeType(unionType))) {
834
- return { type: "boolean" };
835
- }
836
- return { type: "union" };
837
- }
838
- if ((type.flags & ts2.TypeFlags.StringLike) !== 0) {
839
- return { type: "string" };
840
- }
841
- if ((type.flags & ts2.TypeFlags.NumberLike) !== 0) {
842
- return { type: "number" };
843
- }
844
- if ((type.flags & ts2.TypeFlags.BooleanLike) !== 0) {
845
- return { type: "boolean" };
846
- }
847
- if ((type.flags & ts2.TypeFlags.Object) !== 0) {
848
- return { type: "object" };
849
- }
850
- return { type: "custom" };
851
- }
852
- function resolveComponentSignature(exportName, declarations, sourceFile) {
853
- const visitedNames = /* @__PURE__ */ new Set();
854
- const typeNodeFromFunction = (node) => ({
855
- propsTypeNode: node.parameters[0]?.type ?? null,
856
- componentNode: node
857
- });
858
- const resolveFromExpression = (expression) => {
859
- if (ts2.isParenthesizedExpression(expression)) {
860
- return resolveFromExpression(expression.expression);
861
- }
862
- if (ts2.isAsExpression(expression) || ts2.isTypeAssertionExpression(expression)) {
863
- return resolveFromExpression(expression.expression);
864
- }
865
- if (ts2.isArrowFunction(expression) || ts2.isFunctionExpression(expression)) {
866
- return typeNodeFromFunction(expression);
867
- }
868
- if (ts2.isIdentifier(expression)) {
869
- return resolveFromIdentifier(expression.text);
870
- }
871
- if (ts2.isCallExpression(expression)) {
872
- if (ts2.isPropertyAccessExpression(expression.expression) && expression.expression.name.text === "forwardRef") {
873
- const forwardRefPropsType = expression.typeArguments?.[1] ?? null;
874
- const innerArg = expression.arguments[0];
875
- const inner = innerArg && (ts2.isArrowFunction(innerArg) || ts2.isFunctionExpression(innerArg)) ? typeNodeFromFunction(innerArg) : innerArg && ts2.isIdentifier(innerArg) ? resolveFromIdentifier(innerArg.text) : { propsTypeNode: null, componentNode: null };
876
- return {
877
- propsTypeNode: forwardRefPropsType ?? inner.propsTypeNode,
878
- componentNode: inner.componentNode
879
- };
880
- }
881
- if (ts2.isPropertyAccessExpression(expression.expression) && expression.expression.name.text === "memo" && expression.arguments[0]) {
882
- return resolveFromExpression(expression.arguments[0]);
883
- }
884
- if (ts2.isPropertyAccessExpression(expression.expression) && expression.expression.expression.getText(sourceFile) === "Object" && expression.expression.name.text === "assign" && expression.arguments[0]) {
885
- return resolveFromExpression(expression.arguments[0]);
886
- }
887
- }
888
- return { propsTypeNode: null, componentNode: null };
889
- };
890
- const resolveFromVariable = (declaration) => {
891
- if (declaration.type && ts2.isTypeReferenceNode(declaration.type) && declaration.type.typeArguments?.length) {
892
- const typeName = declaration.type.typeName.getText(sourceFile);
893
- if (typeName.includes("FC") || typeName.includes("FunctionComponent")) {
894
- const componentNode = declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer)) ? declaration.initializer : null;
895
- return {
896
- propsTypeNode: declaration.type.typeArguments[0] ?? null,
897
- componentNode
898
- };
899
- }
900
- }
901
- if (declaration.initializer) {
902
- return resolveFromExpression(declaration.initializer);
903
- }
904
- return { propsTypeNode: null, componentNode: null };
905
- };
906
- const resolveFromIdentifier = (name) => {
907
- if (!name || visitedNames.has(name)) {
908
- return { propsTypeNode: null, componentNode: null };
909
- }
910
- visitedNames.add(name);
911
- const functionDeclaration = declarations.functionDeclarations.get(name);
912
- if (functionDeclaration) {
913
- return typeNodeFromFunction(functionDeclaration);
914
- }
915
- const variableDeclaration = declarations.variableDeclarations.get(name);
916
- if (variableDeclaration) {
917
- return resolveFromVariable(variableDeclaration);
918
- }
919
- return { propsTypeNode: null, componentNode: null };
920
- };
921
- return resolveFromIdentifier(exportName);
922
- }
923
- function extractCustomPropsFromComponentFile(componentFilePath, exportName) {
924
- const warnings = [];
925
- const resolvedPath = resolve2(componentFilePath);
926
- if (!existsSync3(resolvedPath)) {
927
- return {
928
- props: {},
929
- warnings: [`Component file not found: ${resolvedPath}`],
930
- resolved: false
931
- };
932
- }
933
- const compilerOptions = {
934
- target: ts2.ScriptTarget.ESNext,
935
- module: ts2.ModuleKind.ESNext,
936
- moduleResolution: ts2.ModuleResolutionKind.Bundler,
937
- jsx: ts2.JsxEmit.ReactJSX,
938
- allowSyntheticDefaultImports: true,
939
- esModuleInterop: true,
940
- skipLibCheck: true,
941
- strict: false,
942
- noEmit: true
943
- };
944
- const program = ts2.createProgram([resolvedPath], compilerOptions);
945
- const sourceFile = program.getSourceFile(resolvedPath);
946
- if (!sourceFile) {
947
- return {
948
- props: {},
949
- warnings: [`Unable to parse component source: ${resolvedPath}`],
950
- resolved: false
951
- };
952
- }
953
- const checker = program.getTypeChecker();
954
- const declarations = collectTopLevelDeclarations(sourceFile);
955
- const signature = resolveComponentSignature(exportName, declarations, sourceFile);
956
- if (!signature.propsTypeNode) {
957
- return {
958
- props: {},
959
- warnings: [`Unable to resolve props type for export: ${exportName}`],
960
- resolved: false
961
- };
962
- }
963
- const propsType = checker.getTypeFromTypeNode(signature.propsTypeNode);
964
- const defaultValues = extractDefaultValues(signature.componentNode);
965
- const sourceFilePath = toPosixPath(sourceFile.fileName);
966
- const extractedProps = {};
967
- for (const symbol of checker.getPropertiesOfType(propsType)) {
968
- const propName = symbol.getName();
969
- if (propName.startsWith("_") || propName.startsWith("$")) {
970
- continue;
971
- }
972
- const declarationsForSymbol = symbol.getDeclarations() ?? [];
973
- const localDeclarations = declarationsForSymbol.filter(
974
- (declaration) => toPosixPath(declaration.getSourceFile().fileName) === sourceFilePath
975
- );
976
- if (localDeclarations.length === 0) {
977
- continue;
978
- }
979
- const referenceNode = localDeclarations[0];
980
- const inferredType = inferPropType(checker.getTypeOfSymbolAtLocation(symbol, referenceNode), checker);
981
- const description = ts2.displayPartsToString(symbol.getDocumentationComment(checker)).trim();
982
- extractedProps[propName] = {
983
- type: inferredType.type,
984
- description,
985
- required: (symbol.getFlags() & ts2.SymbolFlags.Optional) === 0,
986
- ...inferredType.values && { values: inferredType.values },
987
- ...defaultValues[propName] !== void 0 && { default: defaultValues[propName] }
988
- };
989
- }
990
- if (Object.keys(extractedProps).length === 0) {
991
- warnings.push(`Resolved props type for ${exportName}, but no local custom props were found`);
992
- }
993
- return {
994
- props: extractedProps,
995
- warnings,
996
- resolved: true
997
- };
998
- }
999
-
1000
889
  // src/core/graph-extractor.ts
1001
890
  import ts3 from "typescript";
1002
891
  import { readFileSync, existsSync as existsSync4 } from "fs";
@@ -1645,13 +1534,13 @@ function normalizeParsedProps(parsedProps) {
1645
1534
  }
1646
1535
  function mergeDocumentedAndAutoProps(documentedProps, autoProps) {
1647
1536
  return Object.fromEntries(
1648
- Object.keys(autoProps).map((name) => {
1537
+ Object.keys(autoProps).filter((name) => autoProps[name].source === "local" || name in documentedProps).map((name) => {
1649
1538
  const documented = documentedProps[name];
1650
1539
  const auto = autoProps[name];
1651
1540
  return [
1652
1541
  name,
1653
1542
  {
1654
- type: auto.type,
1543
+ type: auto.typeKind,
1655
1544
  description: documented?.description ?? auto.description ?? "",
1656
1545
  default: auto.default !== void 0 ? auto.default : documented?.default,
1657
1546
  required: auto.required,
@@ -1662,14 +1551,37 @@ function mergeDocumentedAndAutoProps(documentedProps, autoProps) {
1662
1551
  })
1663
1552
  );
1664
1553
  }
1554
+ function compilePropsSummary(props) {
1555
+ return Object.entries(props).filter(([_, p]) => p.source === "local").map(([name, prop]) => {
1556
+ let summary = name + ": ";
1557
+ if (prop.values && prop.values.length > 0) {
1558
+ summary += prop.values.join("|");
1559
+ } else {
1560
+ summary += prop.typeKind;
1561
+ }
1562
+ if (prop.default !== void 0) {
1563
+ summary += ` (default: ${prop.default})`;
1564
+ }
1565
+ if (prop.required) {
1566
+ summary += " (required)";
1567
+ }
1568
+ return summary;
1569
+ });
1570
+ }
1665
1571
  async function buildFragments(config, configDir) {
1666
1572
  const files = await discoverFragmentFiles(config, configDir);
1667
1573
  const errors = [];
1668
1574
  const warnings = [];
1669
1575
  const fragments = {};
1576
+ const tsconfigCandidates = [
1577
+ resolve5(configDir, "tsconfig.json"),
1578
+ resolve5(configDir, "..", "tsconfig.json")
1579
+ ];
1580
+ const tsconfigPath = tsconfigCandidates.find((p) => existsSync6(p));
1581
+ const extractor = createComponentExtractor(tsconfigPath);
1670
1582
  for (const file of files) {
1671
1583
  try {
1672
- const content = await readFile2(file.absolutePath, "utf-8");
1584
+ const content = await readFile3(file.absolutePath, "utf-8");
1673
1585
  if (!content.includes("defineFragment")) {
1674
1586
  warnings.push({
1675
1587
  file: file.relativePath,
@@ -1695,34 +1607,32 @@ async function buildFragments(config, configDir) {
1695
1607
  file.absolutePath,
1696
1608
  parsed.componentImport
1697
1609
  );
1610
+ let extractedMeta = null;
1698
1611
  if (componentExportName && componentSourcePath) {
1699
- const autoPropsResult = extractCustomPropsFromComponentFile(
1700
- componentSourcePath,
1701
- componentExportName
1702
- );
1703
- for (const warning of autoPropsResult.warnings) {
1704
- warnings.push({ file: file.relativePath, warning });
1612
+ try {
1613
+ extractedMeta = extractor.extract(componentSourcePath, componentExportName);
1614
+ } catch {
1705
1615
  }
1706
- const hasAutoProps = Object.keys(autoPropsResult.props).length > 0;
1707
- if (autoPropsResult.resolved && hasAutoProps) {
1708
- const removedDocumentedProps = Object.keys(documentedProps).filter(
1709
- (propName) => !(propName in autoPropsResult.props)
1710
- );
1711
- if (removedDocumentedProps.length > 0) {
1616
+ if (extractedMeta) {
1617
+ const autoProps = extractedMeta.props;
1618
+ const hasAutoProps = Object.keys(autoProps).length > 0;
1619
+ if (hasAutoProps) {
1620
+ const removedDocumentedProps = Object.keys(documentedProps).filter(
1621
+ (propName) => !(propName in autoProps)
1622
+ );
1623
+ if (removedDocumentedProps.length > 0) {
1624
+ warnings.push({
1625
+ file: file.relativePath,
1626
+ warning: `Removed ${removedDocumentedProps.length} documented props not present in source API: ${removedDocumentedProps.join(", ")}`
1627
+ });
1628
+ }
1629
+ mergedProps = mergeDocumentedAndAutoProps(documentedProps, autoProps);
1630
+ } else if (Object.keys(documentedProps).length > 0) {
1712
1631
  warnings.push({
1713
1632
  file: file.relativePath,
1714
- warning: `Removed ${removedDocumentedProps.length} documented props not present in source API: ${removedDocumentedProps.join(", ")}`
1633
+ warning: "Auto-props extraction returned no props; falling back to documented props"
1715
1634
  });
1716
1635
  }
1717
- mergedProps = mergeDocumentedAndAutoProps(
1718
- documentedProps,
1719
- autoPropsResult.props
1720
- );
1721
- } else if (autoPropsResult.resolved && !hasAutoProps && Object.keys(documentedProps).length > 0) {
1722
- warnings.push({
1723
- file: file.relativePath,
1724
- warning: "Auto-props extraction returned no custom props; falling back to documented props"
1725
- });
1726
1636
  }
1727
1637
  } else if (!componentExportName) {
1728
1638
  warnings.push({
@@ -1735,6 +1645,23 @@ async function buildFragments(config, configDir) {
1735
1645
  warning: `Unable to resolve component source path from import: ${parsed.componentImport ?? "unknown"}`
1736
1646
  });
1737
1647
  }
1648
+ let contract = parsed.contract;
1649
+ if (!contract?.propsSummary && extractedMeta) {
1650
+ const summary = compilePropsSummary(extractedMeta.props);
1651
+ if (summary.length > 0) {
1652
+ contract = { ...contract, propsSummary: summary };
1653
+ }
1654
+ }
1655
+ let ai = parsed.ai;
1656
+ if (extractedMeta?.composition) {
1657
+ const comp = extractedMeta.composition;
1658
+ ai = {
1659
+ compositionPattern: comp.pattern,
1660
+ subComponents: comp.parts.map((p) => p.name),
1661
+ ...ai
1662
+ // Manually authored ai fields take precedence
1663
+ };
1664
+ }
1738
1665
  const compiled = {
1739
1666
  filePath: file.relativePath,
1740
1667
  meta: {
@@ -1766,10 +1693,10 @@ async function buildFragments(config, configDir) {
1766
1693
  ...v.figma && { figma: v.figma },
1767
1694
  ...v.args && { args: v.args }
1768
1695
  })),
1769
- // Include AI metadata if present
1770
- ...parsed.ai && { ai: parsed.ai },
1771
- // Include contract metadata if present
1772
- ...parsed.contract && { contract: parsed.contract }
1696
+ // Include AI metadata (auto-enriched or manual)
1697
+ ...ai && { ai },
1698
+ // Include contract metadata (auto-compiled or manual)
1699
+ ...contract && { contract }
1773
1700
  };
1774
1701
  fragments[parsed.meta.name] = compiled;
1775
1702
  } catch (error) {
@@ -1779,6 +1706,7 @@ async function buildFragments(config, configDir) {
1779
1706
  });
1780
1707
  }
1781
1708
  }
1709
+ extractor.dispose();
1782
1710
  const blocks = {};
1783
1711
  try {
1784
1712
  const blockFiles = await discoverBlockFiles(configDir, config.exclude);
@@ -1812,7 +1740,7 @@ async function buildFragments(config, configDir) {
1812
1740
  let total = 0;
1813
1741
  const fileContents = [];
1814
1742
  for (const file of tokenFiles) {
1815
- const content = await readFile2(file.absolutePath, "utf-8");
1743
+ const content = await readFile3(file.absolutePath, "utf-8");
1816
1744
  fileContents.push({ content, path: file.relativePath });
1817
1745
  }
1818
1746
  const allContent = fileContents.map((f) => f.content).join("\n");
@@ -1869,7 +1797,7 @@ async function buildFragments(config, configDir) {
1869
1797
  const pkgJsonPath = resolve5(configDir, "package.json");
1870
1798
  if (existsSync6(pkgJsonPath)) {
1871
1799
  try {
1872
- const pkg = JSON.parse(await readFile2(pkgJsonPath, "utf-8"));
1800
+ const pkg = JSON.parse(await readFile3(pkgJsonPath, "utf-8"));
1873
1801
  if (pkg.name) packageName = pkg.name;
1874
1802
  } catch {
1875
1803
  }
@@ -2344,7 +2272,7 @@ ${BRAND.name} Diff
2344
2272
 
2345
2273
  // src/analyze.ts
2346
2274
  import { existsSync as existsSync7 } from "fs";
2347
- import { readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
2275
+ import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
2348
2276
  import { join as join6, dirname as dirname4 } from "path";
2349
2277
  import pc3 from "picocolors";
2350
2278
  async function runAnalyzeCommand(config, configDir, options = {}) {
@@ -2363,7 +2291,7 @@ ${BRAND.name} Analyzer
2363
2291
  };
2364
2292
  }
2365
2293
  console.log(pc3.dim("Analyzing design system...\n"));
2366
- const content = await readFile3(fragmentsPath, "utf-8");
2294
+ const content = await readFile4(fragmentsPath, "utf-8");
2367
2295
  const data = JSON.parse(content);
2368
2296
  const analytics = analyzeDesignSystem(data);
2369
2297
  printConsoleSummary(analytics);
@@ -2508,10 +2436,12 @@ function createEmptyAnalytics() {
2508
2436
  }
2509
2437
 
2510
2438
  export {
2439
+ resolveComponentSourcePath,
2511
2440
  validateSchema,
2512
2441
  validateCoverage,
2513
2442
  validateAll,
2514
2443
  validateSnippets,
2444
+ validateDrift,
2515
2445
  measureBundleSizes,
2516
2446
  toPerformanceData,
2517
2447
  buildFragments,
@@ -2520,4 +2450,4 @@ export {
2520
2450
  runDiffCommand,
2521
2451
  runAnalyzeCommand
2522
2452
  };
2523
- //# sourceMappingURL=chunk-5G3VZH43.js.map
2453
+ //# sourceMappingURL=chunk-GVDSFQ4E.js.map