@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
@@ -1,22 +1,145 @@
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 "./chunk-55KERLWL.js";
4
+ } from "./chunk-MN3TJ3D5.js";
6
5
  import {
7
6
  discoverAllComponents
8
- } from "./chunk-ZKTFKHWN.js";
7
+ } from "./chunk-65WSVDV5.js";
9
8
  import "./chunk-D2CDBRNU.js";
10
9
  import {
11
10
  BRAND
12
- } from "./chunk-I34BC3CU.js";
11
+ } from "./chunk-32LIWN2P.js";
13
12
  import "./chunk-Z7EY4VHE.js";
14
13
 
15
14
  // src/commands/scan-generate.ts
16
15
  import { readFile, writeFile, access, mkdir } from "fs/promises";
17
16
  import { resolve, basename, dirname, relative, join } from "path";
17
+ import { execSync } from "child_process";
18
18
  import * as ts from "typescript";
19
19
  import pc from "picocolors";
20
+ async function findNearestAncestor(scanPath, targetFile) {
21
+ let dir = resolve(scanPath);
22
+ for (let i = 0; i < 20; i++) {
23
+ try {
24
+ await access(join(dir, targetFile));
25
+ return dir;
26
+ } catch {
27
+ const parent = dirname(dir);
28
+ if (parent === dir) break;
29
+ dir = parent;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ async function findNearestPackageJsonDir(scanPath) {
35
+ return findNearestAncestor(scanPath, "package.json");
36
+ }
37
+ async function detectPackageManagerForProject(projectDir) {
38
+ let dir = resolve(projectDir);
39
+ for (let i = 0; i < 20; i++) {
40
+ for (const [fileName, manager] of [
41
+ ["pnpm-lock.yaml", "pnpm"],
42
+ ["pnpm-workspace.yaml", "pnpm"],
43
+ ["yarn.lock", "yarn"],
44
+ ["package-lock.json", "npm"]
45
+ ]) {
46
+ try {
47
+ await access(join(dir, fileName));
48
+ return { manager, lockfileDir: dir };
49
+ } catch {
50
+ }
51
+ }
52
+ const parent = dirname(dir);
53
+ if (parent === dir) break;
54
+ dir = parent;
55
+ }
56
+ return { manager: "npm", lockfileDir: null };
57
+ }
58
+ function resolveCoreInstallCommand(manager) {
59
+ switch (manager) {
60
+ case "pnpm":
61
+ return "pnpm add -D @fragments-sdk/core";
62
+ case "yarn":
63
+ return "yarn add -D @fragments-sdk/core";
64
+ default:
65
+ return "npm install -D @fragments-sdk/core";
66
+ }
67
+ }
68
+ async function ensureCoreDependency(scanPath) {
69
+ const projectDir = await findNearestPackageJsonDir(scanPath);
70
+ if (!projectDir) return;
71
+ const pkgJsonPath = join(projectDir, "package.json");
72
+ try {
73
+ const pkgRaw = await readFile(pkgJsonPath, "utf-8");
74
+ const pkg = JSON.parse(pkgRaw);
75
+ const deps = pkg.dependencies || {};
76
+ const devDeps = pkg.devDependencies || {};
77
+ if (deps["@fragments-sdk/core"] || devDeps["@fragments-sdk/core"]) {
78
+ console.log(pc.dim(" \xB7 @fragments-sdk/core already installed"));
79
+ return;
80
+ }
81
+ const { manager } = await detectPackageManagerForProject(projectDir);
82
+ const cmd = resolveCoreInstallCommand(manager);
83
+ execSync(cmd, { cwd: projectDir, stdio: "ignore" });
84
+ console.log(pc.green(" \u2713 Installed @fragments-sdk/core"));
85
+ } catch (e) {
86
+ console.log(
87
+ pc.yellow(
88
+ ` \u26A0 Could not auto-install @fragments-sdk/core: ${e instanceof Error ? e.message : String(e)}`
89
+ )
90
+ );
91
+ }
92
+ }
93
+ async function excludeFragmentsFromTsconfig(scanPath) {
94
+ const projectDir = await findNearestPackageJsonDir(scanPath);
95
+ if (!projectDir) return;
96
+ let tsconfigPath = null;
97
+ let tsconfigName = "";
98
+ for (const name of ["tsconfig.app.json", "tsconfig.json"]) {
99
+ const candidate = join(projectDir, name);
100
+ try {
101
+ await access(candidate);
102
+ tsconfigPath = candidate;
103
+ tsconfigName = name;
104
+ break;
105
+ } catch {
106
+ }
107
+ }
108
+ if (!tsconfigPath) return;
109
+ try {
110
+ const raw = await readFile(tsconfigPath, "utf-8");
111
+ const parsed = ts.parseConfigFileTextToJson(tsconfigPath, raw);
112
+ if (parsed.error || !parsed.config) {
113
+ throw new Error(parsed.error?.messageText?.toString() ?? "Unable to parse tsconfig");
114
+ }
115
+ const config = parsed.config;
116
+ const exclude = Array.isArray(config.exclude) ? [...config.exclude] : [];
117
+ const ext = BRAND.fileExtension;
118
+ const alreadyExcluded = exclude.some(
119
+ (pattern) => pattern.includes(`*${ext}`) || pattern.includes("*.fragment.*") || pattern.includes("*.contract.*")
120
+ );
121
+ if (alreadyExcluded) {
122
+ console.log(
123
+ pc.dim(` \xB7 ${ext} already excluded in ${tsconfigName}`)
124
+ );
125
+ return;
126
+ }
127
+ exclude.push(`**/*${ext}`);
128
+ config.exclude = exclude;
129
+ const updated = `${JSON.stringify(config, null, 2)}
130
+ `;
131
+ await writeFile(tsconfigPath, updated, "utf-8");
132
+ console.log(
133
+ pc.green(` \u2713 Added ${ext} exclusion to ${tsconfigName}`)
134
+ );
135
+ } catch (e) {
136
+ console.log(
137
+ pc.yellow(
138
+ ` \u26A0 Could not update ${tsconfigName}: ${e instanceof Error ? e.message : String(e)}`
139
+ )
140
+ );
141
+ }
142
+ }
20
143
  async function scanGenerate(options) {
21
144
  const scanPath = resolve(options.scanPath);
22
145
  const generated = [];
@@ -168,13 +291,26 @@ Phase ${options.enrich ? "4" : "3"}: Generating fragment files...`));
168
291
  comp.sourcePath,
169
292
  componentBaseName
170
293
  );
171
- const content = generateFragmentWithTodos(
172
- comp.name,
173
- importPath,
174
- data,
175
- confidence,
176
- enrichment
177
- );
294
+ let content;
295
+ if (fragmentPath.endsWith(".contract.json")) {
296
+ content = generateContractJsonFromScan(
297
+ comp.name,
298
+ componentBaseName,
299
+ data,
300
+ confidence,
301
+ comp.sourcePath,
302
+ scanPath,
303
+ enrichment
304
+ );
305
+ } else {
306
+ content = generateFragmentWithTodos(
307
+ comp.name,
308
+ importPath,
309
+ data,
310
+ confidence,
311
+ enrichment
312
+ );
313
+ }
178
314
  await writeFile(fragmentPath, content, "utf-8");
179
315
  const relPath = relative(process.cwd(), fragmentPath);
180
316
  generated.push({
@@ -196,6 +332,10 @@ Phase ${options.enrich ? "4" : "3"}: Generating fragment files...`));
196
332
  console.log(pc.red(` \u2717 ${comp.name}: ${e instanceof Error ? e.message : String(e)}`));
197
333
  }
198
334
  }
335
+ if (generated.length > 0) {
336
+ await ensureCoreDependency(scanPath);
337
+ await excludeFragmentsFromTsconfig(scanPath);
338
+ }
199
339
  const avgConfidence = generated.length > 0 ? Math.round(
200
340
  generated.reduce((sum, g) => sum + g.confidence, 0) / generated.length
201
341
  ) : 0;
@@ -450,12 +590,12 @@ Rules:
450
590
  }
451
591
  function buildEnrichmentUserPrompt(name, data) {
452
592
  const props = data.meta?.props ?? {};
453
- const localProps = Object.entries(props).filter(([_, p]) => p.source === "local");
593
+ const fragmentProps = getFragmentPropEntries(props);
454
594
  const composition = data.meta?.composition ?? null;
455
595
  const description = data.meta?.description || "";
456
596
  const category = inferCategoryFromMeta(
457
597
  name,
458
- Object.fromEntries(localProps)
598
+ Object.fromEntries(fragmentProps)
459
599
  );
460
600
  const lines = [
461
601
  `Component: ${name}`,
@@ -464,10 +604,10 @@ function buildEnrichmentUserPrompt(name, data) {
464
604
  if (description) {
465
605
  lines.push(`Description: ${description}`);
466
606
  }
467
- if (localProps.length > 0) {
607
+ if (fragmentProps.length > 0) {
468
608
  lines.push("");
469
609
  lines.push("Props:");
470
- for (const [propName, prop] of localProps) {
610
+ for (const [propName, prop] of fragmentProps) {
471
611
  let propLine = ` - ${propName}: ${prop.typeKind}`;
472
612
  if (prop.values && prop.values.length > 0) {
473
613
  propLine += ` (${prop.values.join(" | ")})`;
@@ -606,8 +746,8 @@ function calculateFieldConfidence(data, enrichment) {
606
746
  let score = 0;
607
747
  const todoFields = [];
608
748
  const props = data.meta?.props ?? {};
609
- const localEntries = Object.values(props).filter((p) => p.source === "local");
610
- const hasProps = localEntries.length > 0;
749
+ const fragmentPropEntries = getFragmentPropEntries(props);
750
+ const hasProps = fragmentPropEntries.length > 0;
611
751
  if (hasProps) {
612
752
  score += 30;
613
753
  }
@@ -616,10 +756,8 @@ function calculateFieldConfidence(data, enrichment) {
616
756
  } else {
617
757
  todoFields.push("meta.description");
618
758
  }
619
- const localProps = Object.fromEntries(
620
- Object.entries(props).filter(([_, p]) => p.source === "local")
621
- );
622
- const category = inferCategoryFromMeta(data.component.name, localProps);
759
+ const fragmentProps = Object.fromEntries(fragmentPropEntries);
760
+ const category = inferCategoryFromMeta(data.component.name, fragmentProps);
623
761
  if (category !== "Components") {
624
762
  score += 10;
625
763
  } else {
@@ -629,7 +767,7 @@ function calculateFieldConfidence(data, enrichment) {
629
767
  score += 25;
630
768
  }
631
769
  if (hasProps) {
632
- const allResolved = localEntries.every((p) => p.typeKind !== "custom");
770
+ const allResolved = fragmentPropEntries.every(([_, p]) => p.typeKind !== "custom");
633
771
  if (allResolved) {
634
772
  score += 10;
635
773
  }
@@ -638,7 +776,7 @@ function calculateFieldConfidence(data, enrichment) {
638
776
  score += 10;
639
777
  }
640
778
  if (hasProps) {
641
- const hasDefaults = localEntries.some((p) => p.default !== void 0);
779
+ const hasDefaults = fragmentPropEntries.some(([_, p]) => p.default !== void 0);
642
780
  if (hasDefaults) {
643
781
  score += 5;
644
782
  }
@@ -792,13 +930,55 @@ function inferAccessibilityFromMeta(props) {
792
930
  if (requirements.length > 0) accessibility.requirements = requirements;
793
931
  return accessibility;
794
932
  }
795
- function buildContractBlock(componentName, props, composition, accessibility) {
796
- const localEntries = Object.entries(props).filter(([_, p]) => p.source === "local");
797
- if (localEntries.length === 0 && !composition && !accessibility.requirements?.length) {
933
+ var INHERITED_PROP_PRIORITY = [
934
+ "htmlFor",
935
+ "type",
936
+ "value",
937
+ "defaultValue",
938
+ "checked",
939
+ "defaultChecked",
940
+ "disabled",
941
+ "required",
942
+ "placeholder",
943
+ "name",
944
+ "id",
945
+ "form",
946
+ "accept",
947
+ "multiple",
948
+ "min",
949
+ "max",
950
+ "step",
951
+ "pattern",
952
+ "role",
953
+ "children"
954
+ ];
955
+ function getFragmentPropEntries(props) {
956
+ const entries = Object.entries(props);
957
+ const localEntries = entries.filter(([_, prop]) => prop.source === "local");
958
+ if (localEntries.length > 0) {
959
+ return localEntries;
960
+ }
961
+ const prioritized = new Set(INHERITED_PROP_PRIORITY);
962
+ const inheritedEntries = entries.filter(([name, prop]) => {
963
+ if (prop.source !== "inherited") return false;
964
+ if (name === "key" || name === "ref" || name === "className" || name === "style") return false;
965
+ if (name.startsWith("on")) return false;
966
+ if (name.startsWith("aria-") || name.startsWith("data-")) return false;
967
+ return prioritized.has(name);
968
+ }).sort(([nameA], [nameB]) => {
969
+ const rankA = INHERITED_PROP_PRIORITY.indexOf(nameA);
970
+ const rankB = INHERITED_PROP_PRIORITY.indexOf(nameB);
971
+ return rankA - rankB;
972
+ });
973
+ return inheritedEntries.slice(0, 8);
974
+ }
975
+ function buildContractBlock(componentName, props, composition, accessibility, compoundPartRefs = []) {
976
+ const relevantEntries = getFragmentPropEntries(props);
977
+ if (relevantEntries.length === 0 && !composition && !accessibility.requirements?.length) {
798
978
  return null;
799
979
  }
800
980
  const contract = { propsSummary: [] };
801
- for (const [name, prop] of localEntries) {
981
+ for (const [name, prop] of relevantEntries) {
802
982
  let summary = name + ": ";
803
983
  if (prop.typeKind === "enum" && prop.values && prop.values.length > 0) {
804
984
  summary += prop.values.join(" | ");
@@ -825,7 +1005,8 @@ function buildContractBlock(componentName, props, composition, accessibility) {
825
1005
  contract.compoundChildren = children;
826
1006
  }
827
1007
  if (composition && composition.parts.length > 0) {
828
- const innerLines = composition.parts.map((part) => ` <${componentName}.${part.name}>...</${componentName}.${part.name}>`).join("\n");
1008
+ const resolvedParts = compoundPartRefs.length > 0 ? compoundPartRefs : composition.parts.map((part) => ({ partName: part.name, tagName: `${componentName}.${part.name}` }));
1009
+ const innerLines = resolvedParts.map((part) => ` <${part.tagName}>...</${part.tagName}>`).join("\n");
829
1010
  contract.canonicalUsage = [
830
1011
  `<${componentName}>
831
1012
  ${innerLines}
@@ -909,20 +1090,53 @@ function computeImportPath(fragmentDir, sourcePath, componentBaseName) {
909
1090
  function escapeQuotes(str) {
910
1091
  return str.replace(/'/g, "\\'");
911
1092
  }
1093
+ function resolveCompoundPartReferences(componentName, composition, exportNames) {
1094
+ if (!composition || composition.parts.length === 0) return [];
1095
+ return composition.parts.map((part) => {
1096
+ const directExportName = `${componentName}${part.name}`;
1097
+ if (exportNames.includes(directExportName)) {
1098
+ return {
1099
+ partName: part.name,
1100
+ tagName: directExportName,
1101
+ importName: directExportName
1102
+ };
1103
+ }
1104
+ return {
1105
+ partName: part.name,
1106
+ tagName: `${componentName}.${part.name}`
1107
+ };
1108
+ });
1109
+ }
912
1110
  function generateFragmentWithTodos(componentName, importPath, data, confidence, enrichment) {
913
1111
  const props = data.meta?.props ?? {};
914
1112
  const localProps = Object.fromEntries(
915
1113
  Object.entries(props).filter(([_, p]) => p.source === "local")
916
1114
  );
1115
+ const fragmentProps = Object.fromEntries(getFragmentPropEntries(props));
1116
+ const inferenceProps = Object.keys(localProps).length > 0 ? localProps : fragmentProps;
917
1117
  const composition = data.meta?.composition ?? null;
918
- const description = data.meta?.description || inferDescriptionFromMeta(componentName, localProps);
1118
+ const description = data.meta?.description || inferDescriptionFromMeta(componentName, inferenceProps);
919
1119
  const descriptionTodo = data.meta?.description ? "" : " // TODO: Review description";
920
- const category = inferCategoryFromMeta(componentName, localProps);
1120
+ const category = inferCategoryFromMeta(componentName, inferenceProps);
921
1121
  const categoryTodo = category === "Components" ? " // TODO: Set correct category" : "";
922
1122
  const status = inferStatus(data.component.sourcePath);
923
- const accessibility = inferAccessibilityFromMeta(localProps);
1123
+ const accessibility = inferAccessibilityFromMeta(inferenceProps);
1124
+ const exportNames = data.meta?.exports ?? [];
1125
+ const compoundPartRefs = resolveCompoundPartReferences(componentName, composition, exportNames);
1126
+ const importNames = Array.from(
1127
+ /* @__PURE__ */ new Set([
1128
+ componentName,
1129
+ ...compoundPartRefs.flatMap((part) => part.importName ? [part.importName] : [])
1130
+ ])
1131
+ );
924
1132
  const propsBlock = buildPropsBlockFromMeta(props);
925
- const contract = buildContractBlock(componentName, props, composition, accessibility);
1133
+ const contract = buildContractBlock(
1134
+ componentName,
1135
+ props,
1136
+ composition,
1137
+ accessibility,
1138
+ compoundPartRefs
1139
+ );
926
1140
  if (contract && enrichment) {
927
1141
  if (enrichment.a11yRules.length > 0) {
928
1142
  contract.a11yRules = enrichment.a11yRules;
@@ -950,17 +1164,17 @@ function generateFragmentWithTodos(componentName, importPath, data, confidence,
950
1164
  const variantsBlock = buildVariantsBlock(
951
1165
  componentName,
952
1166
  data.storyVariants,
953
- composition
1167
+ composition,
1168
+ compoundPartRefs
954
1169
  );
955
- const aiBlock = buildAIBlock(composition);
1170
+ const aiBlock = buildAIBlock(composition, compoundPartRefs);
956
1171
  const provenanceBlock = buildProvenanceBlock(confidence, props);
957
1172
  const compoundComment = composition && composition.parts.length > 0 ? `
958
1173
  // Compound sub-components detected: ${composition.parts.map((p) => p.name).join(", ")}` : "";
959
1174
  return `// Auto-generated by fragments init --scan | Confidence: ${confidence.score}/100
960
1175
  // ${confidence.todoFields.length} TODO(s) \u2014 search for "TODO:" and fill in human knowledge${compoundComment}
961
- import React from 'react';
962
1176
  import { defineFragment } from '@fragments-sdk/core';
963
- import { ${componentName} } from '${importPath}';
1177
+ import { ${importNames.join(", ")} } from '${importPath}';
964
1178
 
965
1179
  export default defineFragment({
966
1180
  component: ${componentName},
@@ -983,7 +1197,7 @@ ${provenanceBlock}});
983
1197
  `;
984
1198
  }
985
1199
  function buildPropsBlockFromMeta(props) {
986
- const entries = Object.entries(props).filter(([_, p]) => p.source === "local");
1200
+ const entries = getFragmentPropEntries(props);
987
1201
  if (entries.length === 0) return "{}";
988
1202
  const lines = entries.map(([name, prop]) => {
989
1203
  const type = prop.typeKind;
@@ -1009,12 +1223,12 @@ ${parts.join(",\n")},
1009
1223
  ${lines.join("\n")}
1010
1224
  }`;
1011
1225
  }
1012
- function buildVariantsBlock(componentName, storyVariants, composition) {
1226
+ function buildVariantsBlock(componentName, storyVariants, composition, compoundPartRefs = []) {
1013
1227
  const entries = [];
1014
1228
  const hasDefault = storyVariants.some((v) => v.name === "Default");
1015
1229
  if (!hasDefault) {
1016
1230
  if (composition && composition.pattern === "compound" && composition.parts.length > 0) {
1017
- entries.push(formatCompoundVariantEntry(componentName, composition));
1231
+ entries.push(formatCompoundVariantEntry(componentName, composition, compoundPartRefs));
1018
1232
  } else {
1019
1233
  entries.push(formatVariantEntry(componentName, "Default", `Default ${componentName}`, {}));
1020
1234
  }
@@ -1041,9 +1255,9 @@ function formatVariantEntry(componentName, name, description, args) {
1041
1255
  render: () => ${jsxCode},
1042
1256
  },`;
1043
1257
  }
1044
- function formatCompoundVariantEntry(componentName, composition) {
1045
- const parts = composition.parts;
1046
- const innerJsx = parts.map((part) => ` <${componentName}.${part.name}>...</${componentName}.${part.name}>`).join("\n");
1258
+ function formatCompoundVariantEntry(componentName, composition, compoundPartRefs = []) {
1259
+ const resolvedParts = compoundPartRefs.length > 0 ? compoundPartRefs : composition.parts.map((part) => ({ partName: part.name, tagName: `${componentName}.${part.name}` }));
1260
+ const innerJsx = resolvedParts.map((part) => ` <${part.tagName}>...</${part.tagName}>`).join("\n");
1047
1261
  const jsxCode = `<${componentName}>
1048
1262
  ${innerJsx}
1049
1263
  </${componentName}>`;
@@ -1056,12 +1270,16 @@ ${innerJsx}
1056
1270
  ),
1057
1271
  },`;
1058
1272
  }
1059
- function buildAIBlock(composition) {
1273
+ function buildAIBlock(composition, compoundPartRefs = []) {
1060
1274
  if (!composition || composition.parts.length === 0) return "";
1061
- const subComponents = composition.parts.map((p) => `'${p.name}'`).join(", ");
1275
+ const resolvedParts = compoundPartRefs.length > 0 ? compoundPartRefs : composition.parts.map((part) => ({
1276
+ partName: part.name,
1277
+ tagName: `Component.${part.name}`
1278
+ }));
1279
+ const subComponents = resolvedParts.map((part) => `'${part.importName ?? part.partName}'`).join(", ");
1062
1280
  const pattern = `
1063
1281
  <Component>
1064
- ${composition.parts.map((p) => ` <Component.${p.name}>...</Component.${p.name}>`).join("\n")}
1282
+ ${resolvedParts.map((part) => ` <${part.tagName}>...</${part.tagName}>`).join("\n")}
1065
1283
  </Component>`;
1066
1284
  return `
1067
1285
 
@@ -1100,6 +1318,73 @@ function buildJsxString(componentName, args) {
1100
1318
  }
1101
1319
  return `<${componentName}${propsStr} />`;
1102
1320
  }
1321
+ function generateContractJsonFromScan(componentName, _baseName, data, confidence, sourcePath, scanPath, enrichment) {
1322
+ const props = data.meta?.props ?? {};
1323
+ const localProps = Object.fromEntries(
1324
+ Object.entries(props).filter(([, p]) => p.source === "local")
1325
+ );
1326
+ const effectiveProps = Object.keys(localProps).length > 0 ? localProps : props;
1327
+ const composition = data.meta?.composition ?? null;
1328
+ const description = data.meta?.description || `${componentName} component`;
1329
+ const category = inferCategoryFromMeta(componentName, effectiveProps);
1330
+ const propsSchema = {};
1331
+ for (const [name, prop] of Object.entries(effectiveProps)) {
1332
+ propsSchema[name] = {
1333
+ type: prop.typeKind,
1334
+ description: prop.description ?? "",
1335
+ ...prop.values?.length && { values: prop.values },
1336
+ ...prop.default !== void 0 && { default: prop.default },
1337
+ ...prop.required && { required: true }
1338
+ };
1339
+ }
1340
+ const propsSummary = Object.entries(effectiveProps).map(([name, prop]) => {
1341
+ let summary = `${name}: `;
1342
+ if (prop.values?.length) {
1343
+ summary += prop.values.join("|");
1344
+ } else {
1345
+ summary += prop.typeKind;
1346
+ }
1347
+ if (prop.default !== void 0) summary += ` (default: ${prop.default})`;
1348
+ if (prop.required) summary += " (required)";
1349
+ return summary;
1350
+ });
1351
+ let ai;
1352
+ if (composition) {
1353
+ ai = {
1354
+ compositionPattern: composition.pattern,
1355
+ subComponents: composition.parts.map((p) => p.name),
1356
+ ...composition.required.length > 0 && { requiredChildren: composition.required }
1357
+ };
1358
+ }
1359
+ const relativeSourcePath = relative(scanPath, sourcePath);
1360
+ const usage = enrichment ? {
1361
+ when: enrichment.when ?? [],
1362
+ whenNot: enrichment.whenNot ?? [],
1363
+ ...enrichment.guidelines?.length && { guidelines: enrichment.guidelines }
1364
+ } : {
1365
+ when: [],
1366
+ whenNot: []
1367
+ };
1368
+ const contract = {
1369
+ $schema: "https://usefragments.com/schemas/contract.v1.json",
1370
+ name: componentName,
1371
+ description,
1372
+ category,
1373
+ sourcePath: relativeSourcePath,
1374
+ exportName: componentName,
1375
+ propsSummary,
1376
+ props: propsSchema,
1377
+ usage,
1378
+ ...ai && { ai },
1379
+ provenance: {
1380
+ source: "extracted",
1381
+ verified: confidence.score >= 70,
1382
+ frameworkSupport: "native",
1383
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString()
1384
+ }
1385
+ };
1386
+ return JSON.stringify(contract, null, 2) + "\n";
1387
+ }
1103
1388
  export {
1104
1389
  buildEnrichedUsageBlock,
1105
1390
  buildEnrichmentSystemPrompt,
@@ -1107,9 +1392,13 @@ export {
1107
1392
  calculateFieldConfidence,
1108
1393
  detectCompoundComponents,
1109
1394
  detectCompoundComponentsFromSource,
1395
+ detectPackageManagerForProject,
1396
+ ensureCoreDependency,
1397
+ excludeFragmentsFromTsconfig,
1110
1398
  extractComponentJSDoc,
1111
1399
  extractComponentJSDocFromSource,
1112
1400
  parseEnrichmentResponse,
1401
+ resolveCoreInstallCommand,
1113
1402
  scanGenerate
1114
1403
  };
1115
- //# sourceMappingURL=scan-generate-TFZVL3BT.js.map
1404
+ //# sourceMappingURL=scan-generate-TWRHNU5M.js.map