@fragments-sdk/cli 0.15.0 → 0.15.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 (118) hide show
  1. package/dist/{ai-client-I6MDWNYA.js → ai-client-LSLQGOMM.js} +1 -2
  2. package/dist/bin.js +463 -71
  3. package/dist/bin.js.map +1 -1
  4. package/dist/chunk-5JF26E55.js +1255 -0
  5. package/dist/chunk-5JF26E55.js.map +1 -0
  6. package/dist/{chunk-XJQ5BIWI.js → chunk-6SQPP47U.js} +30 -314
  7. package/dist/chunk-6SQPP47U.js.map +1 -0
  8. package/dist/{chunk-65WSVDV5.js → chunk-HQ6A6DTV.js} +1386 -1097
  9. package/dist/chunk-HQ6A6DTV.js.map +1 -0
  10. package/dist/chunk-MHIBEEW4.js +511 -0
  11. package/dist/chunk-MHIBEEW4.js.map +1 -0
  12. package/dist/{chunk-CZD3AD4Q.js → chunk-ONUP6Z4W.js} +17 -6
  13. package/dist/chunk-ONUP6Z4W.js.map +1 -0
  14. package/dist/{codebase-scanner-VOTPXRYW.js → codebase-scanner-MQHUZC2G.js} +1 -2
  15. package/dist/{converter-JLINP7CJ.js → converter-7XM3Y6NJ.js} +1 -2
  16. package/dist/{converter-JLINP7CJ.js.map → converter-7XM3Y6NJ.js.map} +1 -1
  17. package/dist/core/index.js +0 -1
  18. package/dist/create-IH4R45GE.js +806 -0
  19. package/dist/create-IH4R45GE.js.map +1 -0
  20. package/dist/{generate-A4FP5426.js → generate-PVOLUAAC.js} +3 -4
  21. package/dist/{generate-A4FP5426.js.map → generate-PVOLUAAC.js.map} +1 -1
  22. package/dist/{govern-scan-UCBZR6D6.js → govern-scan-OYFZYOQW.js} +142 -9
  23. package/dist/govern-scan-OYFZYOQW.js.map +1 -0
  24. package/dist/index.d.ts +2 -22
  25. package/dist/index.js +8 -7
  26. package/dist/index.js.map +1 -1
  27. package/dist/{init-HGSM35XA.js → init-SSGUSP7Z.js} +3 -4
  28. package/dist/{init-HGSM35XA.js.map → init-SSGUSP7Z.js.map} +1 -1
  29. package/dist/{init-cloud-MQ6GRJAZ.js → init-cloud-3DNKPWFB.js} +29 -4
  30. package/dist/{init-cloud-MQ6GRJAZ.js.map → init-cloud-3DNKPWFB.js.map} +1 -1
  31. package/dist/mcp-bin.js +1 -2
  32. package/dist/mcp-bin.js.map +1 -1
  33. package/dist/node-37AUE74M.js +65 -0
  34. package/dist/push-contracts-WY32TFP6.js +84 -0
  35. package/dist/push-contracts-WY32TFP6.js.map +1 -0
  36. package/dist/{scan-VNNKACG2.js → scan-PKSYSTRR.js} +5 -5
  37. package/dist/{scan-generate-TWRHNU5M.js → scan-generate-VY27PIOX.js} +8 -9
  38. package/dist/scan-generate-VY27PIOX.js.map +1 -0
  39. package/dist/{scanner-7LAZYPWZ.js → scanner-4KZNOXAK.js} +1 -2
  40. package/dist/{service-FHQU7YS7.js → service-QJGWUIVL.js} +16 -9
  41. package/dist/{snapshot-KQEQ6XHL.js → snapshot-WIJMEIFT.js} +1 -2
  42. package/dist/{snapshot-KQEQ6XHL.js.map → snapshot-WIJMEIFT.js.map} +1 -1
  43. package/dist/{static-viewer-63PG6FWY.js → static-viewer-7QIBQZRC.js} +1 -2
  44. package/dist/{test-UQYUCZIS.js → test-64Z5BKBA.js} +2 -3
  45. package/dist/{test-UQYUCZIS.js.map → test-64Z5BKBA.js.map} +1 -1
  46. package/dist/token-normalizer-TEPOVBPV.js +312 -0
  47. package/dist/token-normalizer-TEPOVBPV.js.map +1 -0
  48. package/dist/token-parser-32KOIOFN.js +22 -0
  49. package/dist/token-parser-32KOIOFN.js.map +1 -0
  50. package/dist/{tokens-6GYKDV6U.js → tokens-NZWFQIAB.js} +7 -7
  51. package/dist/{tokens-generate-VTZV5EEW.js → tokens-generate-5JQSJ27E.js} +1 -2
  52. package/dist/{tokens-generate-VTZV5EEW.js.map → tokens-generate-5JQSJ27E.js.map} +1 -1
  53. package/dist/tokens-push-HY3KO36V.js +148 -0
  54. package/dist/tokens-push-HY3KO36V.js.map +1 -0
  55. package/package.json +5 -3
  56. package/src/bin.ts +90 -0
  57. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +1 -1
  58. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +1 -1
  59. package/src/commands/__tests__/build-freshness.test.ts +231 -0
  60. package/src/commands/__tests__/create.test.ts +71 -0
  61. package/src/commands/__tests__/drift-sync.test.ts +1 -1
  62. package/src/commands/__tests__/govern.test.ts +258 -0
  63. package/src/commands/__tests__/init.test.ts +1 -1
  64. package/src/commands/__tests__/scan-generate.test.ts +1 -1
  65. package/src/commands/build.ts +54 -1
  66. package/src/commands/context.ts +1 -1
  67. package/src/commands/create.ts +536 -0
  68. package/src/commands/doctor.ts +3 -2
  69. package/src/commands/govern-scan.ts +187 -8
  70. package/src/commands/govern.ts +65 -2
  71. package/src/commands/init-cloud.ts +32 -4
  72. package/src/commands/push-contracts.ts +112 -0
  73. package/src/commands/scan-generate.ts +1 -1
  74. package/src/commands/scan.ts +13 -0
  75. package/src/commands/sync.ts +2 -2
  76. package/src/commands/tokens-push.ts +199 -0
  77. package/src/core/__tests__/token-resolver.test.ts +1 -1
  78. package/src/core/component-extractor.test.ts +1 -1
  79. package/src/core/drift-verifier.ts +1 -1
  80. package/src/core/extractor-adapter.ts +1 -1
  81. package/src/index.ts +3 -3
  82. package/src/migrate/fragment-to-contract.ts +2 -2
  83. package/src/service/index.ts +8 -0
  84. package/src/service/tailwind-v4-parser.ts +314 -0
  85. package/src/service/token-parser.ts +56 -0
  86. package/src/setup.ts +10 -39
  87. package/src/theme/__tests__/component-contrast.test.ts +2 -2
  88. package/src/theme/__tests__/serializer.test.ts +1 -1
  89. package/src/theme/generator.ts +16 -1
  90. package/src/theme/schema.ts +8 -0
  91. package/src/theme/serializer.ts +13 -9
  92. package/src/theme/types.ts +8 -0
  93. package/src/validators.ts +1 -2
  94. package/dist/chunk-65WSVDV5.js.map +0 -1
  95. package/dist/chunk-7WHVW72L.js +0 -2664
  96. package/dist/chunk-7WHVW72L.js.map +0 -1
  97. package/dist/chunk-CZD3AD4Q.js.map +0 -1
  98. package/dist/chunk-MN3TJ3D5.js +0 -695
  99. package/dist/chunk-MN3TJ3D5.js.map +0 -1
  100. package/dist/chunk-XJQ5BIWI.js.map +0 -1
  101. package/dist/chunk-Z7EY4VHE.js +0 -50
  102. package/dist/govern-scan-UCBZR6D6.js.map +0 -1
  103. package/dist/sass.node-4XJK6YBF.js +0 -130708
  104. package/dist/sass.node-4XJK6YBF.js.map +0 -1
  105. package/dist/scan-generate-TWRHNU5M.js.map +0 -1
  106. package/src/build.ts +0 -736
  107. package/src/core/auto-props.ts +0 -464
  108. package/src/core/component-extractor.ts +0 -1121
  109. package/src/core/token-resolver.ts +0 -155
  110. package/src/viewer/preview-adapter.ts +0 -116
  111. /package/dist/{ai-client-I6MDWNYA.js.map → ai-client-LSLQGOMM.js.map} +0 -0
  112. /package/dist/{chunk-Z7EY4VHE.js.map → codebase-scanner-MQHUZC2G.js.map} +0 -0
  113. /package/dist/{codebase-scanner-VOTPXRYW.js.map → node-37AUE74M.js.map} +0 -0
  114. /package/dist/{scan-VNNKACG2.js.map → scan-PKSYSTRR.js.map} +0 -0
  115. /package/dist/{scanner-7LAZYPWZ.js.map → scanner-4KZNOXAK.js.map} +0 -0
  116. /package/dist/{service-FHQU7YS7.js.map → service-QJGWUIVL.js.map} +0 -0
  117. /package/dist/{static-viewer-63PG6FWY.js.map → static-viewer-7QIBQZRC.js.map} +0 -0
  118. /package/dist/{tokens-6GYKDV6U.js.map → tokens-NZWFQIAB.js.map} +0 -0
@@ -132,6 +132,7 @@ async function discoverBlockFiles(configDir, exclude) {
132
132
  absolutePath: resolve2(configDir, relativePath)
133
133
  }));
134
134
  }
135
+ var discoverRecipeFiles = discoverBlockFiles;
135
136
  async function discoverFragmentFiles(config, configDir) {
136
137
  const defaultExcludes = [
137
138
  "**/*.test.stories.*",
@@ -346,6 +347,31 @@ async function discoverTokenFiles(configDir, patterns, exclude) {
346
347
  absolutePath: resolve2(configDir, relativePath)
347
348
  }));
348
349
  }
350
+ async function discoverInstalledFragments(projectRoot) {
351
+ const pkgJsonPath = resolve2(projectRoot, "package.json");
352
+ if (!existsSync2(pkgJsonPath)) return [];
353
+ const pkgJson = JSON.parse(await readFile(pkgJsonPath, "utf-8"));
354
+ const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
355
+ const results = [];
356
+ for (const depName of Object.keys(allDeps)) {
357
+ const depDir = resolve2(projectRoot, "node_modules", depName);
358
+ const depPkgPath = resolve2(depDir, "package.json");
359
+ if (!existsSync2(depPkgPath)) continue;
360
+ const depPkg = JSON.parse(await readFile(depPkgPath, "utf-8"));
361
+ if (!depPkg.fragments) continue;
362
+ const files = await fg(
363
+ [`src/**/*${BRAND.fileExtension}`, "src/**/*.stories.tsx"],
364
+ { cwd: depDir, ignore: ["**/node_modules/**"], absolute: false }
365
+ );
366
+ for (const rel of files) {
367
+ results.push({
368
+ relativePath: `${depName}/${rel}`,
369
+ absolutePath: resolve2(depDir, rel)
370
+ });
371
+ }
372
+ }
373
+ return results;
374
+ }
349
375
  async function discoverAllComponents(configDir, options = {}) {
350
376
  const componentsMap = /* @__PURE__ */ new Map();
351
377
  const sourceComponents = await discoverComponentsFromSource(
@@ -369,1269 +395,1532 @@ async function discoverAllComponents(configDir, options = {}) {
369
395
  return Array.from(componentsMap.values()).sort((a, b) => a.name.localeCompare(b.name));
370
396
  }
371
397
 
372
- // src/core/generators/typescript-extractor.ts
373
- import ts from "typescript";
374
- import { readFileSync } from "fs";
375
- function extractPropsFromFile(filePath) {
376
- const sourceText = readFileSync(filePath, "utf-8");
377
- return extractPropsFromSource(sourceText, filePath);
398
+ // src/core/loader.ts
399
+ import { unlink } from "fs/promises";
400
+ import { dirname as dirname3, basename as basename2, join } from "path";
401
+ import { pathToFileURL } from "url";
402
+ import { build } from "esbuild";
403
+ var DEFINE_FRAGMENT_SHIM = `
404
+ export function defineFragment(def) {
405
+ return def;
378
406
  }
379
- function extractPropsFromSource(sourceText, fileName = "component.tsx") {
407
+ export function defineBlock(def) {
408
+ return def;
409
+ }
410
+ export function defineRecipe(def) {
411
+ return def;
412
+ }
413
+ `;
414
+ function createFragmentsCoreShimPlugin() {
415
+ return {
416
+ name: BRAND.vitePluginNamespace,
417
+ setup(build2) {
418
+ build2.onResolve({ filter: /^@fragments-sdk\/cli\/core$/ }, (args) => {
419
+ return {
420
+ path: args.path,
421
+ namespace: BRAND.vitePluginNamespace
422
+ };
423
+ });
424
+ build2.onLoad({ filter: /.*/, namespace: BRAND.vitePluginNamespace }, () => {
425
+ return {
426
+ contents: DEFINE_FRAGMENT_SHIM,
427
+ loader: "js"
428
+ };
429
+ });
430
+ }
431
+ };
432
+ }
433
+ async function loadFragmentFile(absolutePath) {
434
+ const unwrapFragmentExport = (value) => {
435
+ if (!value) return null;
436
+ const asObject = (v) => v && typeof v === "object" ? v : null;
437
+ const isFragmentLike = (v) => {
438
+ const obj = asObject(v);
439
+ return !!obj && ("component" in obj || "meta" in obj || "variants" in obj);
440
+ };
441
+ if (isFragmentLike(value)) {
442
+ return value;
443
+ }
444
+ const first = asObject(value)?.default;
445
+ if (isFragmentLike(first)) {
446
+ return first;
447
+ }
448
+ const second = asObject(first)?.default;
449
+ if (isFragmentLike(second)) {
450
+ return second;
451
+ }
452
+ return value ?? null;
453
+ };
454
+ const ext = absolutePath.split(".").pop()?.toLowerCase();
455
+ const needsTransform = ext === "tsx" || ext === "ts" || ext === "jsx";
456
+ if (!needsTransform) {
457
+ const fileUrl = pathToFileURL(absolutePath).href;
458
+ const module = await import(fileUrl);
459
+ return unwrapFragmentExport(module.default ?? null);
460
+ }
461
+ const sourceDir = dirname3(absolutePath);
462
+ const baseName = basename2(absolutePath, `.${ext}`);
463
+ const tempFile = join(sourceDir, `.${baseName}.fragments-temp-${Date.now()}.mjs`);
464
+ try {
465
+ await build({
466
+ entryPoints: [absolutePath],
467
+ outfile: tempFile,
468
+ bundle: true,
469
+ format: "esm",
470
+ target: "es2022",
471
+ jsx: "automatic",
472
+ platform: "node",
473
+ plugins: [createFragmentsCoreShimPlugin()],
474
+ // Externalize all node_modules - we only need fragment metadata, not component code
475
+ packages: "external",
476
+ // Also explicitly list patterns for nested imports
477
+ external: [
478
+ // React and its subpaths
479
+ "react",
480
+ "react-dom",
481
+ "react/*",
482
+ "react-dom/*"
483
+ ],
484
+ // Don't emit sourcemaps for temp files
485
+ sourcemap: false,
486
+ // Suppress warnings
487
+ logLevel: "silent",
488
+ // Use loader to ignore style imports
489
+ loader: {
490
+ ".scss": "empty",
491
+ ".css": "empty",
492
+ ".svg": "empty",
493
+ ".png": "empty",
494
+ ".jpg": "empty",
495
+ ".jpeg": "empty",
496
+ ".gif": "empty",
497
+ ".woff": "empty",
498
+ ".woff2": "empty",
499
+ ".ttf": "empty",
500
+ ".eot": "empty"
501
+ }
502
+ });
503
+ const fileUrl = pathToFileURL(tempFile).href;
504
+ const module = await import(fileUrl);
505
+ return unwrapFragmentExport(module.default ?? null);
506
+ } finally {
507
+ try {
508
+ await unlink(tempFile);
509
+ } catch {
510
+ }
511
+ }
512
+ }
513
+ async function loadFragmentFiles(absolutePaths) {
514
+ const results = /* @__PURE__ */ new Map();
515
+ await Promise.all(
516
+ absolutePaths.map(async (path) => {
517
+ try {
518
+ const fragment = await loadFragmentFile(path);
519
+ if (fragment) {
520
+ results.set(path, fragment);
521
+ } else {
522
+ results.set(path, new Error("No default export found"));
523
+ }
524
+ } catch (error) {
525
+ results.set(
526
+ path,
527
+ error instanceof Error ? error : new Error(String(error))
528
+ );
529
+ }
530
+ })
531
+ );
532
+ return results;
533
+ }
534
+
535
+ // src/core/parser.ts
536
+ import ts from "typescript";
537
+ function parseFragmentFile(fileContent, filePath) {
538
+ const warnings = [];
380
539
  const sourceFile = ts.createSourceFile(
381
- fileName,
382
- sourceText,
540
+ filePath ?? "fragment.tsx",
541
+ fileContent,
383
542
  ts.ScriptTarget.Latest,
384
543
  true,
385
- fileName.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
544
+ ts.ScriptKind.TSX
386
545
  );
387
- const result = {
388
- componentName: "",
389
- props: {},
390
- exports: [],
391
- imports: []
546
+ const imports = extractImports(sourceFile);
547
+ const defineFragmentCall = findDefineFragmentCall(sourceFile);
548
+ if (!defineFragmentCall) {
549
+ warnings.push("No defineFragment() call found");
550
+ return {
551
+ componentImport: null,
552
+ componentName: null,
553
+ meta: {},
554
+ usage: { when: [], whenNot: [] },
555
+ props: {},
556
+ variants: [],
557
+ relations: [],
558
+ warnings
559
+ };
560
+ }
561
+ const arg = defineFragmentCall.arguments[0];
562
+ if (!arg || !ts.isObjectLiteralExpression(arg)) {
563
+ warnings.push("defineFragment() argument is not an object literal");
564
+ return {
565
+ componentImport: null,
566
+ componentName: null,
567
+ meta: {},
568
+ usage: { when: [], whenNot: [] },
569
+ props: {},
570
+ variants: [],
571
+ relations: [],
572
+ warnings
573
+ };
574
+ }
575
+ const componentProp = findProperty(arg, "component");
576
+ let componentName = null;
577
+ let componentImport = null;
578
+ if (componentProp && ts.isIdentifier(componentProp)) {
579
+ componentName = componentProp.text;
580
+ componentImport = imports.get(componentName) ?? null;
581
+ }
582
+ const meta = extractMeta(arg, warnings);
583
+ const usage = extractUsage(arg, warnings);
584
+ const props = extractProps(arg, warnings);
585
+ const variants = extractVariants(arg, sourceFile, warnings);
586
+ const relations = extractRelations(arg, warnings);
587
+ const ai = extractAIMetadata(arg, warnings);
588
+ const contract = extractContractMetadata(arg);
589
+ return {
590
+ componentImport,
591
+ componentName,
592
+ meta,
593
+ usage,
594
+ props,
595
+ variants,
596
+ relations,
597
+ ai,
598
+ contract,
599
+ warnings
392
600
  };
393
- const propsInterfaces = /* @__PURE__ */ new Map();
394
- const componentExports = [];
395
- const defaultExports = /* @__PURE__ */ new Set();
396
- const importedModules = [];
601
+ }
602
+ function extractImports(sourceFile) {
603
+ const imports = /* @__PURE__ */ new Map();
397
604
  ts.forEachChild(sourceFile, (node) => {
398
605
  if (ts.isImportDeclaration(node)) {
399
606
  const moduleSpecifier = node.moduleSpecifier;
400
607
  if (ts.isStringLiteral(moduleSpecifier)) {
401
- importedModules.push(moduleSpecifier.text);
402
- }
403
- }
404
- if (ts.isInterfaceDeclaration(node)) {
405
- const name = node.name.text;
406
- if (name.endsWith("Props")) {
407
- propsInterfaces.set(name, node);
408
- }
409
- }
410
- if (ts.isTypeAliasDeclaration(node)) {
411
- const name = node.name.text;
412
- if (name.endsWith("Props")) {
413
- propsInterfaces.set(name, node);
414
- }
415
- }
416
- if (ts.isFunctionDeclaration(node) && node.name) {
417
- const hasExport = node.modifiers?.some(
418
- (m) => m.kind === ts.SyntaxKind.ExportKeyword
419
- );
420
- const hasDefault = node.modifiers?.some(
421
- (m) => m.kind === ts.SyntaxKind.DefaultKeyword
422
- );
423
- if (hasExport) {
424
- componentExports.push(node.name.text);
425
- if (hasDefault) {
426
- defaultExports.add(node.name.text);
427
- }
428
- }
429
- }
430
- if (ts.isVariableStatement(node)) {
431
- const hasExport = node.modifiers?.some(
432
- (m) => m.kind === ts.SyntaxKind.ExportKeyword
433
- );
434
- if (hasExport) {
435
- for (const decl of node.declarationList.declarations) {
436
- if (ts.isIdentifier(decl.name)) {
437
- componentExports.push(decl.name.text);
608
+ const modulePath = moduleSpecifier.text;
609
+ const importClause = node.importClause;
610
+ if (importClause) {
611
+ if (importClause.name) {
612
+ imports.set(importClause.name.text, modulePath);
613
+ }
614
+ const namedBindings = importClause.namedBindings;
615
+ if (namedBindings && ts.isNamedImports(namedBindings)) {
616
+ for (const element of namedBindings.elements) {
617
+ imports.set(element.name.text, modulePath);
618
+ }
438
619
  }
439
620
  }
440
621
  }
441
622
  }
442
- if (ts.isExportDeclaration(node) && node.exportClause) {
443
- if (ts.isNamedExports(node.exportClause)) {
444
- for (const element of node.exportClause.elements) {
445
- componentExports.push(element.name.text);
446
- }
623
+ });
624
+ return imports;
625
+ }
626
+ function findDefineFragmentCall(sourceFile) {
627
+ let result = null;
628
+ function visit(node) {
629
+ if (ts.isCallExpression(node)) {
630
+ const expression = node.expression;
631
+ if (ts.isIdentifier(expression) && expression.text === "defineFragment") {
632
+ result = node;
633
+ return;
447
634
  }
448
635
  }
449
- });
450
- result.exports = componentExports;
451
- result.imports = importedModules;
452
- const mainComponent = componentExports.find(
453
- (name) => /^[A-Z]/.test(name) && !name.endsWith("Props")
454
- );
455
- if (!mainComponent) {
456
- return null;
457
- }
458
- result.componentName = mainComponent;
459
- result.isDefaultExport = defaultExports.has(mainComponent);
460
- const propsInterfaceName = `${mainComponent}Props`;
461
- const propsInterface = propsInterfaces.get(propsInterfaceName);
462
- if (propsInterface) {
463
- result.propsInterfaceName = propsInterfaceName;
464
- result.props = extractPropsFromInterface(propsInterface, sourceFile);
636
+ ts.forEachChild(node, visit);
465
637
  }
638
+ visit(sourceFile);
466
639
  return result;
467
640
  }
468
- function extractPropsFromInterface(node, sourceFile) {
469
- const props = {};
470
- if (ts.isInterfaceDeclaration(node)) {
471
- for (const member of node.members) {
472
- if (ts.isPropertySignature(member) && member.name) {
473
- const propName = member.name.getText(sourceFile);
474
- const prop = extractPropFromMember(member, sourceFile);
475
- if (prop) {
476
- props[propName] = prop;
477
- }
641
+ function findProperty(obj, name) {
642
+ for (const prop of obj.properties) {
643
+ if (ts.isPropertyAssignment(prop)) {
644
+ const propName = prop.name;
645
+ if (ts.isIdentifier(propName) && propName.text === name) {
646
+ return prop.initializer;
478
647
  }
479
648
  }
480
649
  }
481
- if (ts.isTypeAliasDeclaration(node)) {
482
- const typeNode = node.type;
483
- if (ts.isTypeLiteralNode(typeNode)) {
484
- for (const member of typeNode.members) {
485
- if (ts.isPropertySignature(member) && member.name) {
486
- const propName = member.name.getText(sourceFile);
487
- const prop = extractPropFromMember(member, sourceFile);
488
- if (prop) {
489
- props[propName] = prop;
490
- }
491
- }
492
- }
650
+ return null;
651
+ }
652
+ function extractMeta(arg, warnings) {
653
+ const metaProp = findProperty(arg, "meta");
654
+ if (!metaProp || !ts.isObjectLiteralExpression(metaProp)) {
655
+ warnings.push("No meta object found");
656
+ return {};
657
+ }
658
+ const meta = {};
659
+ const name = extractStringProperty(metaProp, "name");
660
+ if (name) meta.name = name;
661
+ const description = extractStringProperty(metaProp, "description");
662
+ if (description) meta.description = description;
663
+ const category = extractStringProperty(metaProp, "category");
664
+ if (category) meta.category = category;
665
+ const status = extractStringProperty(metaProp, "status");
666
+ if (status) meta.status = status;
667
+ const since = extractStringProperty(metaProp, "since");
668
+ if (since) meta.since = since;
669
+ const figma = extractStringProperty(metaProp, "figma");
670
+ if (figma) meta.figma = figma;
671
+ const tags = extractStringArray(metaProp, "tags");
672
+ if (tags.length > 0) meta.tags = tags;
673
+ const depsProp = findProperty(metaProp, "dependencies");
674
+ if (depsProp && ts.isArrayLiteralExpression(depsProp)) {
675
+ const deps = extractLiteralValue(depsProp);
676
+ if (Array.isArray(deps) && deps.length > 0) {
677
+ meta.dependencies = deps;
493
678
  }
494
679
  }
495
- return props;
680
+ return meta;
496
681
  }
497
- function extractPropFromMember(member, sourceFile) {
498
- const entry = {};
499
- entry.required = !member.questionToken;
500
- if (member.type) {
501
- const typeInfo = parseTypeNode(member.type, sourceFile);
502
- entry.type = typeInfo.type;
503
- entry.typeKind = typeInfo.typeKind;
504
- if (typeInfo.options) {
505
- entry.options = typeInfo.options;
506
- }
682
+ function extractUsage(arg, warnings) {
683
+ const usageProp = findProperty(arg, "usage");
684
+ if (!usageProp || !ts.isObjectLiteralExpression(usageProp)) {
685
+ return { when: [], whenNot: [] };
507
686
  }
508
- const jsDocComment = getJSDocComment(member);
509
- if (jsDocComment) {
510
- entry.description = jsDocComment;
687
+ return {
688
+ when: extractStringArray(usageProp, "when"),
689
+ whenNot: extractStringArray(usageProp, "whenNot"),
690
+ guidelines: extractStringArray(usageProp, "guidelines"),
691
+ accessibility: extractStringArray(usageProp, "accessibility")
692
+ };
693
+ }
694
+ function extractProps(arg, warnings) {
695
+ const propsProp = findProperty(arg, "props");
696
+ if (!propsProp || !ts.isObjectLiteralExpression(propsProp)) {
697
+ return {};
511
698
  }
512
- const defaultValue = getJSDocDefault(member);
513
- if (defaultValue !== void 0) {
514
- entry.default = defaultValue;
699
+ const props = {};
700
+ for (const prop of propsProp.properties) {
701
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
702
+ const propName = prop.name.text;
703
+ const propValue = prop.initializer;
704
+ if (ts.isObjectLiteralExpression(propValue)) {
705
+ props[propName] = extractPropDefinition(propValue);
706
+ }
707
+ }
515
708
  }
516
- return entry;
709
+ return props;
517
710
  }
518
- function parseTypeNode(typeNode, sourceFile) {
519
- if (typeNode.kind === ts.SyntaxKind.StringKeyword) {
520
- return { type: "string", typeKind: "string" };
521
- }
522
- if (typeNode.kind === ts.SyntaxKind.NumberKeyword) {
523
- return { type: "number", typeKind: "number" };
711
+ function extractPropDefinition(obj) {
712
+ const def = {};
713
+ const type = extractStringProperty(obj, "type");
714
+ if (type) def.type = type;
715
+ const description = extractStringProperty(obj, "description");
716
+ if (description) def.description = description;
717
+ const values = extractStringArray(obj, "values");
718
+ if (values.length > 0) def.values = values;
719
+ const required = extractBooleanProperty(obj, "required");
720
+ if (required !== null) def.required = required;
721
+ const defaultProp = findProperty(obj, "default");
722
+ if (defaultProp) {
723
+ def.default = extractLiteralValue(defaultProp);
524
724
  }
525
- if (typeNode.kind === ts.SyntaxKind.BooleanKeyword) {
526
- return { type: "boolean", typeKind: "boolean" };
725
+ const constraints = extractStringArray(obj, "constraints");
726
+ if (constraints.length > 0) def.constraints = constraints;
727
+ return def;
728
+ }
729
+ function extractVariants(arg, sourceFile, warnings) {
730
+ const variantsProp = findProperty(arg, "variants");
731
+ if (!variantsProp || !ts.isArrayLiteralExpression(variantsProp)) {
732
+ return [];
527
733
  }
528
- if (ts.isUnionTypeNode(typeNode)) {
529
- const options = [];
530
- let allLiterals = true;
531
- for (const subType of typeNode.types) {
532
- if (ts.isLiteralTypeNode(subType)) {
533
- if (ts.isStringLiteral(subType.literal)) {
534
- options.push(subType.literal.text);
535
- } else if (subType.literal.kind === ts.SyntaxKind.TrueKeyword) {
536
- options.push("true");
537
- } else if (subType.literal.kind === ts.SyntaxKind.FalseKeyword) {
538
- options.push("false");
539
- } else {
540
- allLiterals = false;
734
+ const variants = [];
735
+ for (const element of variantsProp.elements) {
736
+ if (ts.isObjectLiteralExpression(element)) {
737
+ const name = extractStringProperty(element, "name");
738
+ const description = extractStringProperty(element, "description");
739
+ if (name) {
740
+ const variant = {
741
+ name,
742
+ description: description ?? ""
743
+ };
744
+ const codeProp = findProperty(element, "code");
745
+ if (codeProp && (ts.isStringLiteral(codeProp) || ts.isNoSubstitutionTemplateLiteral(codeProp))) {
746
+ variant.code = codeProp.text;
541
747
  }
542
- } else {
543
- allLiterals = false;
748
+ const renderProp = findProperty(element, "render");
749
+ if (renderProp && !variant.code) {
750
+ variant.code = extractRenderCode(renderProp, sourceFile);
751
+ }
752
+ const figma = extractStringProperty(element, "figma");
753
+ if (figma) {
754
+ variant.figma = figma;
755
+ }
756
+ const argsProp = findProperty(element, "args");
757
+ if (argsProp && ts.isObjectLiteralExpression(argsProp)) {
758
+ const argsValue = extractLiteralValue(argsProp);
759
+ if (argsValue && typeof argsValue === "object" && !Array.isArray(argsValue)) {
760
+ variant.args = argsValue;
761
+ }
762
+ }
763
+ variants.push(variant);
544
764
  }
545
765
  }
546
- if (allLiterals && options.length > 0) {
547
- return {
548
- type: options.map((o) => `"${o}"`).join(" | "),
549
- typeKind: "enum",
550
- options
551
- };
552
- }
553
- return {
554
- type: typeNode.getText(sourceFile),
555
- typeKind: "union"
556
- };
557
- }
558
- if (ts.isFunctionTypeNode(typeNode)) {
559
- return {
560
- type: typeNode.getText(sourceFile),
561
- typeKind: "function"
562
- };
563
766
  }
564
- if (ts.isArrayTypeNode(typeNode)) {
565
- return {
566
- type: typeNode.getText(sourceFile),
567
- typeKind: "array"
568
- };
767
+ return variants;
768
+ }
769
+ function dedent(str) {
770
+ const lines = str.split("\n");
771
+ if (lines.length <= 1) {
772
+ return str;
569
773
  }
570
- if (ts.isTypeReferenceNode(typeNode)) {
571
- const typeName = typeNode.typeName.getText(sourceFile);
572
- if (typeName === "ReactNode" || typeName === "React.ReactNode") {
573
- return { type: "ReactNode", typeKind: "node" };
574
- }
575
- if (typeName === "ReactElement" || typeName === "React.ReactElement") {
576
- return { type: "ReactElement", typeKind: "element" };
577
- }
578
- if (typeName === "JSX.Element") {
579
- return { type: "JSX.Element", typeKind: "element" };
774
+ const firstLineIndent = lines[0].match(/^(\s*)/)?.[1].length ?? 0;
775
+ const startIndex = firstLineIndent === 0 ? 1 : 0;
776
+ let minIndent = Infinity;
777
+ for (let i = startIndex; i < lines.length; i++) {
778
+ const line = lines[i];
779
+ if (line.trim() === "") continue;
780
+ const match = line.match(/^(\s*)/);
781
+ if (match) {
782
+ minIndent = Math.min(minIndent, match[1].length);
580
783
  }
581
- return {
582
- type: typeNode.getText(sourceFile),
583
- typeKind: "object"
584
- };
585
784
  }
586
- if (ts.isTypeLiteralNode(typeNode)) {
587
- return {
588
- type: typeNode.getText(sourceFile),
589
- typeKind: "object"
590
- };
785
+ if (minIndent === Infinity || minIndent === 0) {
786
+ return str;
591
787
  }
592
- return {
593
- type: typeNode.getText(sourceFile),
594
- typeKind: "unknown"
595
- };
596
- }
597
- function getJSDocComment(node) {
598
- const jsDocTags = ts.getJSDocTags(node);
599
- const jsDoc = node.jsDoc;
600
- if (jsDoc && jsDoc.length > 0) {
601
- const comment = jsDoc[0].comment;
602
- if (typeof comment === "string") {
603
- return comment;
604
- }
605
- if (Array.isArray(comment)) {
606
- return comment.map((c) => typeof c === "string" ? c : c.text).join("");
788
+ return lines.map((line, index) => {
789
+ if (index === 0 && firstLineIndent === 0) {
790
+ return line;
607
791
  }
608
- }
609
- return void 0;
792
+ return line.slice(minIndent);
793
+ }).join("\n");
610
794
  }
611
- function getJSDocDefault(node) {
612
- const jsDocTags = ts.getJSDocTags(node);
613
- for (const tag of jsDocTags) {
614
- if (tag.tagName.text === "default") {
615
- const comment = tag.comment;
616
- if (typeof comment === "string") {
617
- try {
618
- return JSON.parse(comment);
619
- } catch {
620
- return comment;
621
- }
622
- }
795
+ function extractRenderCode(renderProp, sourceFile) {
796
+ if (ts.isArrowFunction(renderProp)) {
797
+ const body = renderProp.body;
798
+ const start = body.getStart(sourceFile);
799
+ const end = body.getEnd();
800
+ let code = sourceFile.text.substring(start, end).trim();
801
+ if (code.startsWith("(") && code.endsWith(")")) {
802
+ code = code.slice(1, -1).trim();
623
803
  }
804
+ code = dedent(code);
805
+ return code;
624
806
  }
625
807
  return void 0;
626
808
  }
627
-
628
- // src/core/generators/registry.ts
629
- import { readFileSync as readFileSync2 } from "fs";
630
- import { relative, dirname as dirname3, basename as basename2, join } from "path";
631
- import fg2 from "fast-glob";
632
- async function generateRegistry(options) {
633
- const {
634
- projectRoot,
635
- componentPatterns = ["src/**/*.tsx", "src/**/*.ts"],
636
- storyPatterns = ["src/**/*.stories.tsx", "src/**/*.stories.ts"],
637
- fragmentsDir = join(projectRoot, BRAND.dataDir),
638
- registryOptions = {}
639
- } = options;
640
- const {
641
- requireStory = false,
642
- publicOnly = false,
643
- categoryDepth = 1,
644
- includeProps = false,
645
- embedFragments = false
646
- } = registryOptions;
647
- const errors = [];
648
- const warnings = [];
649
- const storyFiles = await fg2(storyPatterns, {
650
- cwd: projectRoot,
651
- ignore: ["**/node_modules/**"],
652
- absolute: true
653
- });
654
- const storyMap = /* @__PURE__ */ new Map();
655
- for (const storyPath of storyFiles) {
656
- const storyDir = dirname3(storyPath);
657
- const storyBase = basename2(storyPath).replace(/\.stories\.(tsx?|jsx?)$/, "");
658
- storyMap.set(`${storyDir}/${storyBase}`, storyPath);
659
- }
660
- const componentFiles = await fg2(componentPatterns, {
661
- cwd: projectRoot,
662
- ignore: [
663
- "**/node_modules/**",
664
- "**/*.stories.*",
665
- "**/*.test.*",
666
- "**/*.spec.*",
667
- "**/*.d.ts"
668
- ],
669
- absolute: true
670
- });
671
- const fragmentPattern = join(
672
- BRAND.dataDir,
673
- BRAND.componentsDir,
674
- `*${BRAND.fileExtension}`
675
- );
676
- const fragmentFiles = await fg2(fragmentPattern, {
677
- cwd: projectRoot,
678
- absolute: true
679
- });
680
- const fragmentMap = /* @__PURE__ */ new Map();
681
- for (const fragmentPath of fragmentFiles) {
682
- const fragmentName = basename2(fragmentPath).replace(BRAND.fileExtension, "");
683
- try {
684
- const content = readFileSync2(fragmentPath, "utf-8");
685
- const fragment = JSON.parse(content);
686
- fragmentMap.set(fragmentName, {
687
- path: relative(projectRoot, fragmentPath),
688
- fragment
689
- });
690
- } catch (e) {
691
- errors.push({
692
- file: fragmentPath,
693
- error: `Failed to parse fragment: ${e instanceof Error ? e.message : String(e)}`
694
- });
695
- }
809
+ function extractRelations(arg, warnings) {
810
+ const relationsProp = findProperty(arg, "relations");
811
+ if (!relationsProp || !ts.isArrayLiteralExpression(relationsProp)) {
812
+ return [];
696
813
  }
697
- const components = {};
698
- const indexComponents = {};
699
- const categories = {};
700
- for (const filePath of componentFiles) {
701
- try {
702
- const extracted = extractPropsFromFile(filePath);
703
- if (!extracted || !extracted.componentName) {
704
- continue;
705
- }
706
- const componentName = extracted.componentName;
707
- const relativePath = relative(projectRoot, filePath);
708
- if (publicOnly && !extracted.exports.includes(componentName)) {
709
- continue;
710
- }
711
- const componentDir = dirname3(filePath);
712
- const baseNameWithoutExt = basename2(filePath).replace(/\.(tsx?|jsx?)$/, "");
713
- const storyPath = storyMap.get(`${componentDir}/${baseNameWithoutExt}`);
714
- if (requireStory && !storyPath) {
715
- continue;
716
- }
717
- const fragmentData = fragmentMap.get(componentName);
718
- const category = fragmentData?.fragment?.meta?.status ? void 0 : getCategoryFromPath(relativePath, categoryDepth);
719
- const hasEnrichment = fragmentData ? hasRealEnrichment(fragmentData.fragment) : false;
720
- const entry = {
721
- path: relativePath
722
- };
723
- if (storyPath) {
724
- entry.storyPath = relative(projectRoot, storyPath);
725
- }
726
- if (fragmentData) {
727
- entry.fragmentPath = fragmentData.path;
728
- if (hasEnrichment) {
729
- entry.hasEnrichment = true;
730
- }
731
- if (fragmentData.fragment.description) {
732
- entry.description = fragmentData.fragment.description;
733
- }
734
- if (fragmentData.fragment.meta?.status) {
735
- entry.status = fragmentData.fragment.meta.status;
736
- }
737
- if (embedFragments) {
738
- entry.fragment = fragmentData.fragment;
739
- }
740
- }
741
- if (category) {
742
- entry.category = category;
743
- if (!categories[category]) {
744
- categories[category] = [];
745
- }
746
- categories[category].push(componentName);
747
- }
748
- if (includeProps && extracted.props) {
749
- entry.props = extracted.props;
750
- }
751
- if (extracted.exports.length > 1) {
752
- entry.exports = extracted.exports;
814
+ const relations = [];
815
+ for (const element of relationsProp.elements) {
816
+ if (ts.isObjectLiteralExpression(element)) {
817
+ const component = extractStringProperty(element, "component");
818
+ const relationship = extractStringProperty(element, "relationship");
819
+ const note = extractStringProperty(element, "note");
820
+ if (component && relationship) {
821
+ relations.push({
822
+ component,
823
+ relationship,
824
+ note: note ?? ""
825
+ });
753
826
  }
754
- components[componentName] = entry;
755
- indexComponents[componentName] = relativePath;
756
- } catch (e) {
757
- errors.push({
758
- file: filePath,
759
- error: `Failed to extract component: ${e instanceof Error ? e.message : String(e)}`
760
- });
761
827
  }
762
828
  }
763
- const componentCount = Object.keys(components).length;
764
- const registry = {
765
- $schema: "https://fragments.dev/schema/registry-v1.json",
766
- version: "1.0",
767
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
768
- componentCount,
769
- components,
770
- categories
771
- };
772
- const index = {
773
- version: "1.0",
774
- generatedAt: registry.generatedAt,
775
- components: indexComponents,
776
- categories
777
- };
778
- return { registry, index, errors, warnings };
829
+ return relations;
779
830
  }
780
- function getCategoryFromPath(relativePath, depth = 1) {
781
- const parts = relativePath.split("/");
782
- const componentsIndex = parts.findIndex((p) => p === "components");
783
- if (componentsIndex === -1) {
831
+ function extractAIMetadata(arg, warnings) {
832
+ const aiProp = findProperty(arg, "ai");
833
+ if (!aiProp || !ts.isObjectLiteralExpression(aiProp)) {
784
834
  return void 0;
785
835
  }
786
- const afterComponents = parts.slice(componentsIndex + 1);
787
- if (afterComponents.length <= 1) {
788
- return void 0;
836
+ const ai = {};
837
+ const compositionPattern = extractStringProperty(aiProp, "compositionPattern");
838
+ if (compositionPattern && ["compound", "simple", "controlled"].includes(compositionPattern)) {
839
+ ai.compositionPattern = compositionPattern;
789
840
  }
790
- const folderName = afterComponents[0];
791
- const fileName = afterComponents[afterComponents.length - 1].replace(/\.(tsx?|jsx?)$/, "");
792
- if (afterComponents.length === 2 && folderName === fileName) {
793
- return void 0;
841
+ const subComponents = extractStringArray(aiProp, "subComponents");
842
+ if (subComponents.length > 0) {
843
+ ai.subComponents = subComponents;
794
844
  }
795
- const categoryParts = afterComponents.slice(0, Math.min(depth, afterComponents.length - 1));
796
- const lastCategoryPart = categoryParts[categoryParts.length - 1];
797
- if (lastCategoryPart === fileName) {
798
- categoryParts.pop();
845
+ const requiredChildren = extractStringArray(aiProp, "requiredChildren");
846
+ if (requiredChildren.length > 0) {
847
+ ai.requiredChildren = requiredChildren;
799
848
  }
800
- if (categoryParts.length === 0) {
801
- return void 0;
849
+ const commonPatterns = extractStringArray(aiProp, "commonPatterns");
850
+ if (commonPatterns.length > 0) {
851
+ ai.commonPatterns = commonPatterns;
802
852
  }
803
- return categoryParts.join("/");
853
+ if (Object.keys(ai).length > 0) {
854
+ return ai;
855
+ }
856
+ return void 0;
804
857
  }
805
- function hasRealEnrichment(fragment) {
806
- if (fragment.description && fragment.description.length > 20) {
807
- return true;
858
+ function extractContractMetadata(arg) {
859
+ const contractProp = findProperty(arg, "contract");
860
+ if (!contractProp || !ts.isObjectLiteralExpression(contractProp)) {
861
+ return void 0;
808
862
  }
809
- if (fragment.usage?.when && fragment.usage.when.length > 0) {
810
- return true;
863
+ const contract = {};
864
+ const propsSummary = extractStringArray(contractProp, "propsSummary");
865
+ if (propsSummary.length > 0) {
866
+ contract.propsSummary = propsSummary;
811
867
  }
812
- if (fragment.usage?.doNot && fragment.usage.doNot.length > 0) {
813
- return true;
868
+ const a11yRules = extractStringArray(contractProp, "a11yRules");
869
+ if (a11yRules.length > 0) {
870
+ contract.a11yRules = a11yRules;
814
871
  }
815
- if (fragment.usage?.patterns && fragment.usage.patterns.length > 0) {
816
- return true;
872
+ if (Object.keys(contract).length > 0) {
873
+ return contract;
817
874
  }
818
- if (fragment.accessibility?.requirements && fragment.accessibility.requirements.length > 0) {
819
- return true;
875
+ return void 0;
876
+ }
877
+ function extractStringProperty(obj, name) {
878
+ const prop = findProperty(obj, name);
879
+ if (prop && ts.isStringLiteral(prop)) {
880
+ return prop.text;
820
881
  }
821
- if (fragment.figma?.nodeId || fragment.figma?.variants) {
822
- return true;
882
+ if (prop && ts.isNoSubstitutionTemplateLiteral(prop)) {
883
+ return prop.text;
823
884
  }
824
- if (fragment.related?.similar && fragment.related.similar.length > 0) {
825
- return true;
885
+ return null;
886
+ }
887
+ function extractBooleanProperty(obj, name) {
888
+ const prop = findProperty(obj, name);
889
+ if (prop) {
890
+ if (prop.kind === ts.SyntaxKind.TrueKeyword) return true;
891
+ if (prop.kind === ts.SyntaxKind.FalseKeyword) return false;
826
892
  }
827
- return false;
893
+ return null;
828
894
  }
829
-
830
- // src/core/generators/context.ts
831
- function generateContextMd(registry, options = {}) {
832
- const {
833
- format = "markdown",
834
- compact = false,
835
- include = { props: true, relations: true, code: false }
836
- } = options;
837
- if (format === "json") {
838
- return generateJsonContext(registry, include, compact);
895
+ function extractStringArray(obj, name) {
896
+ const prop = findProperty(obj, name);
897
+ if (!prop || !ts.isArrayLiteralExpression(prop)) {
898
+ return [];
839
899
  }
840
- return generateMarkdownContext(registry, include, compact);
900
+ const result = [];
901
+ for (const element of prop.elements) {
902
+ if (ts.isStringLiteral(element)) {
903
+ result.push(element.text);
904
+ } else if (ts.isNoSubstitutionTemplateLiteral(element)) {
905
+ result.push(element.text);
906
+ }
907
+ }
908
+ return result;
841
909
  }
842
- function generateMarkdownContext(registry, include, compact) {
843
- const lines = [];
844
- const componentNames = Object.keys(registry.components).sort();
845
- const componentCount = componentNames.length;
846
- lines.push("# Component Library Context");
847
- lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} | Components: ${componentCount}`);
848
- lines.push("");
849
- lines.push("## Quick Reference");
850
- lines.push("");
851
- lines.push("| Component | Path | Category | Status |");
852
- lines.push("|-----------|------|----------|--------|");
853
- for (const name of componentNames) {
854
- const entry = registry.components[name];
855
- const status = entry.status || "stable";
856
- const category = entry.category || "-";
857
- lines.push(`| ${name} | ${entry.path} | ${category} | ${status} |`);
910
+ function extractLiteralValue(expr) {
911
+ if (ts.isStringLiteral(expr)) {
912
+ return expr.text;
858
913
  }
859
- lines.push("");
860
- if (compact) {
861
- const content2 = lines.join("\n");
862
- return {
863
- content: content2,
864
- tokenEstimate: estimateTokens(content2),
865
- componentCount
914
+ if (ts.isNumericLiteral(expr)) {
915
+ return Number(expr.text);
916
+ }
917
+ if (expr.kind === ts.SyntaxKind.TrueKeyword) {
918
+ return true;
919
+ }
920
+ if (expr.kind === ts.SyntaxKind.FalseKeyword) {
921
+ return false;
922
+ }
923
+ if (expr.kind === ts.SyntaxKind.NullKeyword) {
924
+ return null;
925
+ }
926
+ if (ts.isArrayLiteralExpression(expr)) {
927
+ return expr.elements.map(extractLiteralValue);
928
+ }
929
+ if (ts.isObjectLiteralExpression(expr)) {
930
+ const obj = {};
931
+ for (const prop of expr.properties) {
932
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
933
+ obj[prop.name.text] = extractLiteralValue(prop.initializer);
934
+ }
935
+ }
936
+ return obj;
937
+ }
938
+ return void 0;
939
+ }
940
+
941
+ // src/core/previewLoader.ts
942
+ import { existsSync as existsSync3 } from "fs";
943
+ import { join as join2, resolve as resolve3 } from "path";
944
+ var PREVIEW_FILES = [
945
+ "preview.tsx",
946
+ "preview.ts",
947
+ "preview.jsx",
948
+ "preview.js"
949
+ ];
950
+ function findPreviewConfigPath(storybookDir) {
951
+ for (const fileName of PREVIEW_FILES) {
952
+ const filePath = join2(storybookDir, fileName);
953
+ if (existsSync3(filePath)) {
954
+ return filePath;
955
+ }
956
+ }
957
+ return null;
958
+ }
959
+ function findStorybookDir(projectRoot) {
960
+ const possiblePaths = [
961
+ join2(projectRoot, ".storybook"),
962
+ join2(projectRoot, "storybook")
963
+ ];
964
+ for (const dir of possiblePaths) {
965
+ if (existsSync3(dir)) {
966
+ return dir;
967
+ }
968
+ }
969
+ return null;
970
+ }
971
+ async function loadPreviewConfig(previewPath) {
972
+ try {
973
+ const fileUrl = new URL(`file://${resolve3(previewPath)}`);
974
+ const module = await import(fileUrl.href);
975
+ const config = {
976
+ decorators: module.decorators ?? module.default?.decorators ?? [],
977
+ parameters: module.parameters ?? module.default?.parameters ?? {},
978
+ globalTypes: module.globalTypes ?? module.default?.globalTypes ?? {},
979
+ args: module.args ?? module.default?.args ?? {},
980
+ argTypes: module.argTypes ?? module.default?.argTypes ?? {},
981
+ loaders: module.loaders ?? module.default?.loaders ?? []
866
982
  };
983
+ return config;
984
+ } catch (error) {
985
+ console.warn(
986
+ `[Fragments] Failed to load preview config from ${previewPath}:`,
987
+ error instanceof Error ? error.message : error
988
+ );
989
+ return {};
867
990
  }
868
- lines.push("---");
869
- lines.push("");
870
- for (const name of componentNames) {
871
- const entry = registry.components[name];
872
- const fragment = entry.fragment;
873
- lines.push(`## ${name}`);
874
- lines.push(`**Path:** \`${entry.path}\``);
875
- if (entry.category) {
876
- lines.push(`**Category:** ${entry.category} | **Status:** ${entry.status || "stable"}`);
991
+ }
992
+ async function autoLoadPreviewConfig(projectRoot) {
993
+ const storybookDir = findStorybookDir(projectRoot);
994
+ if (!storybookDir) {
995
+ return {};
996
+ }
997
+ const previewPath = findPreviewConfigPath(storybookDir);
998
+ if (!previewPath) {
999
+ return {};
1000
+ }
1001
+ return loadPreviewConfig(previewPath);
1002
+ }
1003
+ function generatePreviewModule(previewPath) {
1004
+ if (!previewPath) {
1005
+ return `
1006
+ export const decorators = [];
1007
+ export const parameters = {};
1008
+ export const globalTypes = {};
1009
+ export const args = {};
1010
+ export const argTypes = {};
1011
+ export const loaders = [];
1012
+
1013
+ export default {
1014
+ decorators,
1015
+ parameters,
1016
+ globalTypes,
1017
+ args,
1018
+ argTypes,
1019
+ loaders,
1020
+ };
1021
+ `;
1022
+ }
1023
+ return `
1024
+ import * as preview from "${previewPath}";
1025
+
1026
+ export const decorators = preview.decorators ?? preview.default?.decorators ?? [];
1027
+ export const parameters = preview.parameters ?? preview.default?.parameters ?? {};
1028
+ export const globalTypes = preview.globalTypes ?? preview.default?.globalTypes ?? {};
1029
+ export const args = preview.args ?? preview.default?.args ?? {};
1030
+ export const argTypes = preview.argTypes ?? preview.default?.argTypes ?? {};
1031
+ export const loaders = preview.loaders ?? preview.default?.loaders ?? [];
1032
+
1033
+ export default {
1034
+ decorators,
1035
+ parameters,
1036
+ globalTypes,
1037
+ args,
1038
+ argTypes,
1039
+ loaders,
1040
+ };
1041
+ `;
1042
+ }
1043
+
1044
+ // src/core/generators/typescript-extractor.ts
1045
+ import ts2 from "typescript";
1046
+ import { readFileSync } from "fs";
1047
+ function extractPropsFromFile(filePath) {
1048
+ const sourceText = readFileSync(filePath, "utf-8");
1049
+ return extractPropsFromSource(sourceText, filePath);
1050
+ }
1051
+ function extractPropsFromSource(sourceText, fileName = "component.tsx") {
1052
+ const sourceFile = ts2.createSourceFile(
1053
+ fileName,
1054
+ sourceText,
1055
+ ts2.ScriptTarget.Latest,
1056
+ true,
1057
+ fileName.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS
1058
+ );
1059
+ const result = {
1060
+ componentName: "",
1061
+ props: {},
1062
+ exports: [],
1063
+ imports: []
1064
+ };
1065
+ const propsInterfaces = /* @__PURE__ */ new Map();
1066
+ const componentExports = [];
1067
+ const defaultExports = /* @__PURE__ */ new Set();
1068
+ const importedModules = [];
1069
+ ts2.forEachChild(sourceFile, (node) => {
1070
+ if (ts2.isImportDeclaration(node)) {
1071
+ const moduleSpecifier = node.moduleSpecifier;
1072
+ if (ts2.isStringLiteral(moduleSpecifier)) {
1073
+ importedModules.push(moduleSpecifier.text);
1074
+ }
877
1075
  }
878
- lines.push("");
879
- if (entry.description || fragment?.description) {
880
- lines.push("### Description");
881
- lines.push(entry.description || fragment?.description || "");
882
- lines.push("");
1076
+ if (ts2.isInterfaceDeclaration(node)) {
1077
+ const name = node.name.text;
1078
+ if (name.endsWith("Props")) {
1079
+ propsInterfaces.set(name, node);
1080
+ }
883
1081
  }
884
- if (fragment?.usage) {
885
- if (fragment.usage.when && fragment.usage.when.length > 0) {
886
- lines.push("### When to Use");
887
- for (const when of fragment.usage.when) {
888
- lines.push(`- ${when}`);
889
- }
890
- lines.push("");
1082
+ if (ts2.isTypeAliasDeclaration(node)) {
1083
+ const name = node.name.text;
1084
+ if (name.endsWith("Props")) {
1085
+ propsInterfaces.set(name, node);
891
1086
  }
892
- if (fragment.usage.doNot && fragment.usage.doNot.length > 0) {
893
- lines.push("### Do Not");
894
- for (const doNotItem of fragment.usage.doNot) {
895
- if (typeof doNotItem === "string") {
896
- lines.push(`- ${doNotItem}`);
897
- } else {
898
- const item = doNotItem;
899
- if (item.instead) {
900
- const alternativePath = resolveComponentPath(item.instead, registry);
901
- if (alternativePath) {
902
- lines.push(`- ${item.text} \u2192 use **${item.instead}** (\`${alternativePath}\`)`);
903
- } else {
904
- lines.push(`- ${item.text} \u2192 use **${item.instead}**`);
905
- }
906
- } else {
907
- lines.push(`- ${item.text}`);
908
- }
909
- }
1087
+ }
1088
+ if (ts2.isFunctionDeclaration(node) && node.name) {
1089
+ const hasExport = node.modifiers?.some(
1090
+ (m) => m.kind === ts2.SyntaxKind.ExportKeyword
1091
+ );
1092
+ const hasDefault = node.modifiers?.some(
1093
+ (m) => m.kind === ts2.SyntaxKind.DefaultKeyword
1094
+ );
1095
+ if (hasExport) {
1096
+ componentExports.push(node.name.text);
1097
+ if (hasDefault) {
1098
+ defaultExports.add(node.name.text);
910
1099
  }
911
- lines.push("");
912
1100
  }
913
- if (include.code && fragment.usage.patterns && fragment.usage.patterns.length > 0) {
914
- lines.push("### Patterns");
915
- for (const pattern of fragment.usage.patterns) {
916
- lines.push(`**${pattern.name}**`);
917
- if (pattern.description) {
918
- lines.push(pattern.description);
1101
+ }
1102
+ if (ts2.isVariableStatement(node)) {
1103
+ const hasExport = node.modifiers?.some(
1104
+ (m) => m.kind === ts2.SyntaxKind.ExportKeyword
1105
+ );
1106
+ if (hasExport) {
1107
+ for (const decl of node.declarationList.declarations) {
1108
+ if (ts2.isIdentifier(decl.name)) {
1109
+ componentExports.push(decl.name.text);
919
1110
  }
920
- lines.push("```tsx");
921
- lines.push(pattern.code);
922
- lines.push("```");
923
- lines.push("");
924
1111
  }
925
1112
  }
926
1113
  }
927
- if (include.props && entry.props && Object.keys(entry.props).length > 0) {
928
- lines.push("### Props");
929
- lines.push("| Prop | Type | Default | Description |");
930
- lines.push("|------|------|---------|-------------|");
931
- for (const [propName, prop] of Object.entries(entry.props)) {
932
- const type = formatPropType(prop);
933
- const defaultVal = prop.default !== void 0 ? `\`${JSON.stringify(prop.default)}\`` : "-";
934
- const desc = prop.description || "-";
935
- const required = prop.required ? " (required)" : "";
936
- lines.push(`| ${propName}${required} | ${type} | ${defaultVal} | ${desc} |`);
937
- }
938
- lines.push("");
939
- }
940
- if (fragment?.accessibility) {
941
- lines.push("### Accessibility");
942
- if (fragment.accessibility.role) {
943
- lines.push(`**Role:** ${fragment.accessibility.role}`);
944
- }
945
- if (fragment.accessibility.requirements && fragment.accessibility.requirements.length > 0) {
946
- for (const req of fragment.accessibility.requirements) {
947
- lines.push(`- ${req}`);
1114
+ if (ts2.isExportDeclaration(node) && node.exportClause) {
1115
+ if (ts2.isNamedExports(node.exportClause)) {
1116
+ for (const element of node.exportClause.elements) {
1117
+ componentExports.push(element.name.text);
948
1118
  }
949
1119
  }
950
- if (fragment.accessibility.keyboard) {
951
- lines.push("");
952
- lines.push("**Keyboard:**");
953
- for (const [key, action] of Object.entries(fragment.accessibility.keyboard)) {
954
- lines.push(`- \`${key}\`: ${action}`);
1120
+ }
1121
+ });
1122
+ result.exports = componentExports;
1123
+ result.imports = importedModules;
1124
+ const mainComponent = componentExports.find(
1125
+ (name) => /^[A-Z]/.test(name) && !name.endsWith("Props")
1126
+ );
1127
+ if (!mainComponent) {
1128
+ return null;
1129
+ }
1130
+ result.componentName = mainComponent;
1131
+ result.isDefaultExport = defaultExports.has(mainComponent);
1132
+ const propsInterfaceName = `${mainComponent}Props`;
1133
+ const propsInterface = propsInterfaces.get(propsInterfaceName);
1134
+ if (propsInterface) {
1135
+ result.propsInterfaceName = propsInterfaceName;
1136
+ result.props = extractPropsFromInterface(propsInterface, sourceFile);
1137
+ }
1138
+ return result;
1139
+ }
1140
+ function extractPropsFromInterface(node, sourceFile) {
1141
+ const props = {};
1142
+ if (ts2.isInterfaceDeclaration(node)) {
1143
+ for (const member of node.members) {
1144
+ if (ts2.isPropertySignature(member) && member.name) {
1145
+ const propName = member.name.getText(sourceFile);
1146
+ const prop = extractPropFromMember(member, sourceFile);
1147
+ if (prop) {
1148
+ props[propName] = prop;
955
1149
  }
956
1150
  }
957
- lines.push("");
958
1151
  }
959
- if (include.relations && fragment?.related) {
960
- lines.push("### Related");
961
- if (fragment.related.similar && fragment.related.similar.length > 0) {
962
- const resolved = fragment.related.similar.map((comp) => {
963
- const path = resolveComponentPath(comp, registry);
964
- return path ? `${comp} (\`${path}\`)` : comp;
965
- });
966
- lines.push(`- **Similar:** ${resolved.join(", ")}`);
967
- }
968
- if (fragment.related.composedWith && fragment.related.composedWith.length > 0) {
969
- const resolved = fragment.related.composedWith.map((comp) => {
970
- const path = resolveComponentPath(comp, registry);
971
- return path ? `${comp} (\`${path}\`)` : comp;
972
- });
973
- lines.push(`- **Composed with:** ${resolved.join(", ")}`);
974
- }
975
- if (fragment.related.usedIn && fragment.related.usedIn.length > 0) {
976
- const resolved = fragment.related.usedIn.map((comp) => {
977
- const path = resolveComponentPath(comp, registry);
978
- return path ? `${comp} (\`${path}\`)` : comp;
979
- });
980
- lines.push(`- **Used in:** ${resolved.join(", ")}`);
1152
+ }
1153
+ if (ts2.isTypeAliasDeclaration(node)) {
1154
+ const typeNode = node.type;
1155
+ if (ts2.isTypeLiteralNode(typeNode)) {
1156
+ for (const member of typeNode.members) {
1157
+ if (ts2.isPropertySignature(member) && member.name) {
1158
+ const propName = member.name.getText(sourceFile);
1159
+ const prop = extractPropFromMember(member, sourceFile);
1160
+ if (prop) {
1161
+ props[propName] = prop;
1162
+ }
1163
+ }
981
1164
  }
982
- lines.push("");
983
1165
  }
984
- lines.push("---");
985
- lines.push("");
986
1166
  }
987
- const content = lines.join("\n");
988
- return {
989
- content,
990
- tokenEstimate: estimateTokens(content),
991
- componentCount
992
- };
1167
+ return props;
993
1168
  }
994
- function generateJsonContext(registry, include, compact) {
995
- const componentNames = Object.keys(registry.components).sort();
996
- const componentCount = componentNames.length;
997
- const components = {};
998
- for (const name of componentNames) {
999
- const entry = registry.components[name];
1000
- const fragment = entry.fragment;
1001
- const component = {
1002
- path: entry.path
1003
- };
1004
- if (entry.category) component.category = entry.category;
1005
- if (entry.status) component.status = entry.status;
1006
- if (entry.description || fragment?.description) {
1007
- component.description = entry.description || fragment?.description;
1169
+ function extractPropFromMember(member, sourceFile) {
1170
+ const entry = {};
1171
+ entry.required = !member.questionToken;
1172
+ if (member.type) {
1173
+ const typeInfo = parseTypeNode(member.type, sourceFile);
1174
+ entry.type = typeInfo.type;
1175
+ entry.typeKind = typeInfo.typeKind;
1176
+ if (typeInfo.options) {
1177
+ entry.options = typeInfo.options;
1008
1178
  }
1009
- if (!compact && fragment?.usage) {
1010
- if (fragment.usage.when && fragment.usage.when.length > 0) {
1011
- component.whenToUse = fragment.usage.when;
1012
- }
1013
- if (fragment.usage.doNot && fragment.usage.doNot.length > 0) {
1014
- component.doNot = fragment.usage.doNot.map((item) => {
1015
- if (typeof item === "string") {
1016
- return { text: item };
1017
- }
1018
- const doNotItem = item;
1019
- const result = {
1020
- text: doNotItem.text
1021
- };
1022
- if (doNotItem.instead) {
1023
- result.instead = doNotItem.instead;
1024
- result.insteadPath = resolveComponentPath(doNotItem.instead, registry);
1025
- }
1026
- return result;
1027
- });
1179
+ }
1180
+ const jsDocComment = getJSDocComment(member);
1181
+ if (jsDocComment) {
1182
+ entry.description = jsDocComment;
1183
+ }
1184
+ const defaultValue = getJSDocDefault(member);
1185
+ if (defaultValue !== void 0) {
1186
+ entry.default = defaultValue;
1187
+ }
1188
+ return entry;
1189
+ }
1190
+ function parseTypeNode(typeNode, sourceFile) {
1191
+ if (typeNode.kind === ts2.SyntaxKind.StringKeyword) {
1192
+ return { type: "string", typeKind: "string" };
1193
+ }
1194
+ if (typeNode.kind === ts2.SyntaxKind.NumberKeyword) {
1195
+ return { type: "number", typeKind: "number" };
1196
+ }
1197
+ if (typeNode.kind === ts2.SyntaxKind.BooleanKeyword) {
1198
+ return { type: "boolean", typeKind: "boolean" };
1199
+ }
1200
+ if (ts2.isUnionTypeNode(typeNode)) {
1201
+ const options = [];
1202
+ let allLiterals = true;
1203
+ for (const subType of typeNode.types) {
1204
+ if (ts2.isLiteralTypeNode(subType)) {
1205
+ if (ts2.isStringLiteral(subType.literal)) {
1206
+ options.push(subType.literal.text);
1207
+ } else if (subType.literal.kind === ts2.SyntaxKind.TrueKeyword) {
1208
+ options.push("true");
1209
+ } else if (subType.literal.kind === ts2.SyntaxKind.FalseKeyword) {
1210
+ options.push("false");
1211
+ } else {
1212
+ allLiterals = false;
1213
+ }
1214
+ } else {
1215
+ allLiterals = false;
1028
1216
  }
1029
1217
  }
1030
- if (!compact && include.props && entry.props) {
1031
- component.props = {};
1032
- for (const [propName, prop] of Object.entries(entry.props)) {
1033
- component.props[propName] = {
1034
- type: formatPropType(prop)
1035
- };
1036
- if (prop.required) component.props[propName].required = true;
1037
- if (prop.default !== void 0) component.props[propName].default = prop.default;
1038
- if (prop.description) component.props[propName].description = prop.description;
1039
- }
1218
+ if (allLiterals && options.length > 0) {
1219
+ return {
1220
+ type: options.map((o) => `"${o}"`).join(" | "),
1221
+ typeKind: "enum",
1222
+ options
1223
+ };
1040
1224
  }
1041
- if (!compact && include.relations && fragment?.related) {
1042
- component.related = {};
1043
- if (fragment.related.similar) {
1044
- component.related.similar = fragment.related.similar.map((name2) => ({
1045
- name: name2,
1046
- path: resolveComponentPath(name2, registry)
1047
- }));
1048
- }
1049
- if (fragment.related.composedWith) {
1050
- component.related.composedWith = fragment.related.composedWith.map((name2) => ({
1051
- name: name2,
1052
- path: resolveComponentPath(name2, registry)
1053
- }));
1054
- }
1055
- if (fragment.related.usedIn) {
1056
- component.related.usedIn = fragment.related.usedIn.map((name2) => ({
1057
- name: name2,
1058
- path: resolveComponentPath(name2, registry)
1059
- }));
1060
- }
1225
+ return {
1226
+ type: typeNode.getText(sourceFile),
1227
+ typeKind: "union"
1228
+ };
1229
+ }
1230
+ if (ts2.isFunctionTypeNode(typeNode)) {
1231
+ return {
1232
+ type: typeNode.getText(sourceFile),
1233
+ typeKind: "function"
1234
+ };
1235
+ }
1236
+ if (ts2.isArrayTypeNode(typeNode)) {
1237
+ return {
1238
+ type: typeNode.getText(sourceFile),
1239
+ typeKind: "array"
1240
+ };
1241
+ }
1242
+ if (ts2.isTypeReferenceNode(typeNode)) {
1243
+ const typeName = typeNode.typeName.getText(sourceFile);
1244
+ if (typeName === "ReactNode" || typeName === "React.ReactNode") {
1245
+ return { type: "ReactNode", typeKind: "node" };
1061
1246
  }
1062
- components[name] = component;
1247
+ if (typeName === "ReactElement" || typeName === "React.ReactElement") {
1248
+ return { type: "ReactElement", typeKind: "element" };
1249
+ }
1250
+ if (typeName === "JSX.Element") {
1251
+ return { type: "JSX.Element", typeKind: "element" };
1252
+ }
1253
+ return {
1254
+ type: typeNode.getText(sourceFile),
1255
+ typeKind: "object"
1256
+ };
1063
1257
  }
1064
- const output = {
1065
- version: "1.0",
1066
- generatedAt: registry.generatedAt,
1067
- componentCount,
1068
- categories: registry.categories,
1069
- components
1070
- };
1071
- const content = JSON.stringify(output, null, 2);
1072
- return {
1073
- content,
1074
- tokenEstimate: estimateTokens(content),
1075
- componentCount
1076
- };
1077
- }
1078
- function resolveComponentPath(componentName, registry) {
1079
- return registry.components[componentName]?.path;
1080
- }
1081
- function formatPropType(prop) {
1082
- if (prop.options && prop.options.length > 0) {
1083
- return prop.options.map((o) => `"${o}"`).join(" \\| ");
1258
+ if (ts2.isTypeLiteralNode(typeNode)) {
1259
+ return {
1260
+ type: typeNode.getText(sourceFile),
1261
+ typeKind: "object"
1262
+ };
1084
1263
  }
1085
- return prop.type || "unknown";
1086
- }
1087
- function estimateTokens(text) {
1088
- return Math.ceil(text.length / 4);
1089
- }
1090
-
1091
- // src/core/loader.ts
1092
- import { unlink } from "fs/promises";
1093
- import { dirname as dirname4, basename as basename3, join as join2 } from "path";
1094
- import { pathToFileURL } from "url";
1095
- import { build } from "esbuild";
1096
- var DEFINE_FRAGMENT_SHIM = `
1097
- export function defineFragment(def) {
1098
- return def;
1099
- }
1100
- export function defineBlock(def) {
1101
- return def;
1102
- }
1103
- export function defineRecipe(def) {
1104
- return def;
1105
- }
1106
- `;
1107
- function createFragmentsCoreShimPlugin() {
1108
1264
  return {
1109
- name: BRAND.vitePluginNamespace,
1110
- setup(build2) {
1111
- build2.onResolve({ filter: /^@fragments-sdk\/cli\/core$/ }, (args) => {
1112
- return {
1113
- path: args.path,
1114
- namespace: BRAND.vitePluginNamespace
1115
- };
1116
- });
1117
- build2.onLoad({ filter: /.*/, namespace: BRAND.vitePluginNamespace }, () => {
1118
- return {
1119
- contents: DEFINE_FRAGMENT_SHIM,
1120
- loader: "js"
1121
- };
1122
- });
1123
- }
1265
+ type: typeNode.getText(sourceFile),
1266
+ typeKind: "unknown"
1124
1267
  };
1125
1268
  }
1126
- async function loadFragmentFile(absolutePath) {
1127
- const unwrapFragmentExport = (value) => {
1128
- if (!value) return null;
1129
- const asObject = (v) => v && typeof v === "object" ? v : null;
1130
- const isFragmentLike = (v) => {
1131
- const obj = asObject(v);
1132
- return !!obj && ("component" in obj || "meta" in obj || "variants" in obj);
1133
- };
1134
- if (isFragmentLike(value)) {
1135
- return value;
1269
+ function getJSDocComment(node) {
1270
+ const jsDocTags = ts2.getJSDocTags(node);
1271
+ const jsDoc = node.jsDoc;
1272
+ if (jsDoc && jsDoc.length > 0) {
1273
+ const comment = jsDoc[0].comment;
1274
+ if (typeof comment === "string") {
1275
+ return comment;
1136
1276
  }
1137
- const first = asObject(value)?.default;
1138
- if (isFragmentLike(first)) {
1139
- return first;
1277
+ if (Array.isArray(comment)) {
1278
+ return comment.map((c) => typeof c === "string" ? c : c.text).join("");
1140
1279
  }
1141
- const second = asObject(first)?.default;
1142
- if (isFragmentLike(second)) {
1143
- return second;
1144
- }
1145
- return value ?? null;
1146
- };
1147
- const ext = absolutePath.split(".").pop()?.toLowerCase();
1148
- const needsTransform = ext === "tsx" || ext === "ts" || ext === "jsx";
1149
- if (!needsTransform) {
1150
- const fileUrl = pathToFileURL(absolutePath).href;
1151
- const module = await import(fileUrl);
1152
- return unwrapFragmentExport(module.default ?? null);
1153
- }
1154
- const sourceDir = dirname4(absolutePath);
1155
- const baseName = basename3(absolutePath, `.${ext}`);
1156
- const tempFile = join2(sourceDir, `.${baseName}.fragments-temp-${Date.now()}.mjs`);
1157
- try {
1158
- await build({
1159
- entryPoints: [absolutePath],
1160
- outfile: tempFile,
1161
- bundle: true,
1162
- format: "esm",
1163
- target: "es2022",
1164
- jsx: "automatic",
1165
- platform: "node",
1166
- plugins: [createFragmentsCoreShimPlugin()],
1167
- // Externalize all node_modules - we only need fragment metadata, not component code
1168
- packages: "external",
1169
- // Also explicitly list patterns for nested imports
1170
- external: [
1171
- // React and its subpaths
1172
- "react",
1173
- "react-dom",
1174
- "react/*",
1175
- "react-dom/*"
1176
- ],
1177
- // Don't emit sourcemaps for temp files
1178
- sourcemap: false,
1179
- // Suppress warnings
1180
- logLevel: "silent",
1181
- // Use loader to ignore style imports
1182
- loader: {
1183
- ".scss": "empty",
1184
- ".css": "empty",
1185
- ".svg": "empty",
1186
- ".png": "empty",
1187
- ".jpg": "empty",
1188
- ".jpeg": "empty",
1189
- ".gif": "empty",
1190
- ".woff": "empty",
1191
- ".woff2": "empty",
1192
- ".ttf": "empty",
1193
- ".eot": "empty"
1194
- }
1195
- });
1196
- const fileUrl = pathToFileURL(tempFile).href;
1197
- const module = await import(fileUrl);
1198
- return unwrapFragmentExport(module.default ?? null);
1199
- } finally {
1200
- try {
1201
- await unlink(tempFile);
1202
- } catch {
1280
+ }
1281
+ return void 0;
1282
+ }
1283
+ function getJSDocDefault(node) {
1284
+ const jsDocTags = ts2.getJSDocTags(node);
1285
+ for (const tag of jsDocTags) {
1286
+ if (tag.tagName.text === "default") {
1287
+ const comment = tag.comment;
1288
+ if (typeof comment === "string") {
1289
+ try {
1290
+ return JSON.parse(comment);
1291
+ } catch {
1292
+ return comment;
1293
+ }
1294
+ }
1203
1295
  }
1204
1296
  }
1297
+ return void 0;
1205
1298
  }
1206
1299
 
1207
- // src/core/parser.ts
1208
- import ts2 from "typescript";
1209
- function parseFragmentFile(fileContent, filePath) {
1300
+ // src/core/generators/registry.ts
1301
+ import { readFileSync as readFileSync2 } from "fs";
1302
+ import { relative, dirname as dirname4, basename as basename3, join as join3 } from "path";
1303
+ import fg2 from "fast-glob";
1304
+ async function generateRegistry(options) {
1305
+ const {
1306
+ projectRoot,
1307
+ componentPatterns = ["src/**/*.tsx", "src/**/*.ts"],
1308
+ storyPatterns = ["src/**/*.stories.tsx", "src/**/*.stories.ts"],
1309
+ fragmentsDir = join3(projectRoot, BRAND.dataDir),
1310
+ registryOptions = {}
1311
+ } = options;
1312
+ const {
1313
+ requireStory = false,
1314
+ publicOnly = false,
1315
+ categoryDepth = 1,
1316
+ includeProps = false,
1317
+ embedFragments = false
1318
+ } = registryOptions;
1319
+ const errors = [];
1210
1320
  const warnings = [];
1211
- const sourceFile = ts2.createSourceFile(
1212
- filePath ?? "fragment.tsx",
1213
- fileContent,
1214
- ts2.ScriptTarget.Latest,
1215
- true,
1216
- ts2.ScriptKind.TSX
1217
- );
1218
- const imports = extractImports(sourceFile);
1219
- const defineFragmentCall = findDefineFragmentCall(sourceFile);
1220
- if (!defineFragmentCall) {
1221
- warnings.push("No defineFragment() call found");
1222
- return {
1223
- componentImport: null,
1224
- componentName: null,
1225
- meta: {},
1226
- usage: { when: [], whenNot: [] },
1227
- props: {},
1228
- variants: [],
1229
- relations: [],
1230
- warnings
1231
- };
1232
- }
1233
- const arg = defineFragmentCall.arguments[0];
1234
- if (!arg || !ts2.isObjectLiteralExpression(arg)) {
1235
- warnings.push("defineFragment() argument is not an object literal");
1236
- return {
1237
- componentImport: null,
1238
- componentName: null,
1239
- meta: {},
1240
- usage: { when: [], whenNot: [] },
1241
- props: {},
1242
- variants: [],
1243
- relations: [],
1244
- warnings
1245
- };
1321
+ const storyFiles = await fg2(storyPatterns, {
1322
+ cwd: projectRoot,
1323
+ ignore: ["**/node_modules/**"],
1324
+ absolute: true
1325
+ });
1326
+ const storyMap = /* @__PURE__ */ new Map();
1327
+ for (const storyPath of storyFiles) {
1328
+ const storyDir = dirname4(storyPath);
1329
+ const storyBase = basename3(storyPath).replace(/\.stories\.(tsx?|jsx?)$/, "");
1330
+ storyMap.set(`${storyDir}/${storyBase}`, storyPath);
1246
1331
  }
1247
- const componentProp = findProperty(arg, "component");
1248
- let componentName = null;
1249
- let componentImport = null;
1250
- if (componentProp && ts2.isIdentifier(componentProp)) {
1251
- componentName = componentProp.text;
1252
- componentImport = imports.get(componentName) ?? null;
1332
+ const componentFiles = await fg2(componentPatterns, {
1333
+ cwd: projectRoot,
1334
+ ignore: [
1335
+ "**/node_modules/**",
1336
+ "**/*.stories.*",
1337
+ "**/*.test.*",
1338
+ "**/*.spec.*",
1339
+ "**/*.d.ts"
1340
+ ],
1341
+ absolute: true
1342
+ });
1343
+ const fragmentPattern = join3(
1344
+ BRAND.dataDir,
1345
+ BRAND.componentsDir,
1346
+ `*${BRAND.fileExtension}`
1347
+ );
1348
+ const fragmentFiles = await fg2(fragmentPattern, {
1349
+ cwd: projectRoot,
1350
+ absolute: true
1351
+ });
1352
+ const fragmentMap = /* @__PURE__ */ new Map();
1353
+ for (const fragmentPath of fragmentFiles) {
1354
+ const fragmentName = basename3(fragmentPath).replace(BRAND.fileExtension, "");
1355
+ try {
1356
+ const content = readFileSync2(fragmentPath, "utf-8");
1357
+ const fragment = JSON.parse(content);
1358
+ fragmentMap.set(fragmentName, {
1359
+ path: relative(projectRoot, fragmentPath),
1360
+ fragment
1361
+ });
1362
+ } catch (e) {
1363
+ errors.push({
1364
+ file: fragmentPath,
1365
+ error: `Failed to parse fragment: ${e instanceof Error ? e.message : String(e)}`
1366
+ });
1367
+ }
1253
1368
  }
1254
- const meta = extractMeta(arg, warnings);
1255
- const usage = extractUsage(arg, warnings);
1256
- const props = extractProps(arg, warnings);
1257
- const variants = extractVariants(arg, sourceFile, warnings);
1258
- const relations = extractRelations(arg, warnings);
1259
- const ai = extractAIMetadata(arg, warnings);
1260
- const contract = extractContractMetadata(arg);
1261
- return {
1262
- componentImport,
1263
- componentName,
1264
- meta,
1265
- usage,
1266
- props,
1267
- variants,
1268
- relations,
1269
- ai,
1270
- contract,
1271
- warnings
1272
- };
1273
- }
1274
- function extractImports(sourceFile) {
1275
- const imports = /* @__PURE__ */ new Map();
1276
- ts2.forEachChild(sourceFile, (node) => {
1277
- if (ts2.isImportDeclaration(node)) {
1278
- const moduleSpecifier = node.moduleSpecifier;
1279
- if (ts2.isStringLiteral(moduleSpecifier)) {
1280
- const modulePath = moduleSpecifier.text;
1281
- const importClause = node.importClause;
1282
- if (importClause) {
1283
- if (importClause.name) {
1284
- imports.set(importClause.name.text, modulePath);
1285
- }
1286
- const namedBindings = importClause.namedBindings;
1287
- if (namedBindings && ts2.isNamedImports(namedBindings)) {
1288
- for (const element of namedBindings.elements) {
1289
- imports.set(element.name.text, modulePath);
1290
- }
1291
- }
1369
+ const components = {};
1370
+ const indexComponents = {};
1371
+ const categories = {};
1372
+ for (const filePath of componentFiles) {
1373
+ try {
1374
+ const extracted = extractPropsFromFile(filePath);
1375
+ if (!extracted || !extracted.componentName) {
1376
+ continue;
1377
+ }
1378
+ const componentName = extracted.componentName;
1379
+ const relativePath = relative(projectRoot, filePath);
1380
+ if (publicOnly && !extracted.exports.includes(componentName)) {
1381
+ continue;
1382
+ }
1383
+ const componentDir = dirname4(filePath);
1384
+ const baseNameWithoutExt = basename3(filePath).replace(/\.(tsx?|jsx?)$/, "");
1385
+ const storyPath = storyMap.get(`${componentDir}/${baseNameWithoutExt}`);
1386
+ if (requireStory && !storyPath) {
1387
+ continue;
1388
+ }
1389
+ const fragmentData = fragmentMap.get(componentName);
1390
+ const category = fragmentData?.fragment?.meta?.status ? void 0 : getCategoryFromPath(relativePath, categoryDepth);
1391
+ const hasEnrichment = fragmentData ? hasRealEnrichment(fragmentData.fragment) : false;
1392
+ const entry = {
1393
+ path: relativePath
1394
+ };
1395
+ if (storyPath) {
1396
+ entry.storyPath = relative(projectRoot, storyPath);
1397
+ }
1398
+ if (fragmentData) {
1399
+ entry.fragmentPath = fragmentData.path;
1400
+ if (hasEnrichment) {
1401
+ entry.hasEnrichment = true;
1402
+ }
1403
+ if (fragmentData.fragment.description) {
1404
+ entry.description = fragmentData.fragment.description;
1405
+ }
1406
+ if (fragmentData.fragment.meta?.status) {
1407
+ entry.status = fragmentData.fragment.meta.status;
1408
+ }
1409
+ if (embedFragments) {
1410
+ entry.fragment = fragmentData.fragment;
1292
1411
  }
1293
1412
  }
1294
- }
1295
- });
1296
- return imports;
1297
- }
1298
- function findDefineFragmentCall(sourceFile) {
1299
- let result = null;
1300
- function visit(node) {
1301
- if (ts2.isCallExpression(node)) {
1302
- const expression = node.expression;
1303
- if (ts2.isIdentifier(expression) && expression.text === "defineFragment") {
1304
- result = node;
1305
- return;
1413
+ if (category) {
1414
+ entry.category = category;
1415
+ if (!categories[category]) {
1416
+ categories[category] = [];
1417
+ }
1418
+ categories[category].push(componentName);
1306
1419
  }
1420
+ if (includeProps && extracted.props) {
1421
+ entry.props = extracted.props;
1422
+ }
1423
+ if (extracted.exports.length > 1) {
1424
+ entry.exports = extracted.exports;
1425
+ }
1426
+ components[componentName] = entry;
1427
+ indexComponents[componentName] = relativePath;
1428
+ } catch (e) {
1429
+ errors.push({
1430
+ file: filePath,
1431
+ error: `Failed to extract component: ${e instanceof Error ? e.message : String(e)}`
1432
+ });
1307
1433
  }
1308
- ts2.forEachChild(node, visit);
1309
1434
  }
1310
- visit(sourceFile);
1311
- return result;
1435
+ const componentCount = Object.keys(components).length;
1436
+ const registry = {
1437
+ $schema: "https://fragments.dev/schema/registry-v1.json",
1438
+ version: "1.0",
1439
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1440
+ componentCount,
1441
+ components,
1442
+ categories
1443
+ };
1444
+ const index = {
1445
+ version: "1.0",
1446
+ generatedAt: registry.generatedAt,
1447
+ components: indexComponents,
1448
+ categories
1449
+ };
1450
+ return { registry, index, errors, warnings };
1312
1451
  }
1313
- function findProperty(obj, name) {
1314
- for (const prop of obj.properties) {
1315
- if (ts2.isPropertyAssignment(prop)) {
1316
- const propName = prop.name;
1317
- if (ts2.isIdentifier(propName) && propName.text === name) {
1318
- return prop.initializer;
1319
- }
1320
- }
1452
+ function getCategoryFromPath(relativePath, depth = 1) {
1453
+ const parts = relativePath.split("/");
1454
+ const componentsIndex = parts.findIndex((p) => p === "components");
1455
+ if (componentsIndex === -1) {
1456
+ return void 0;
1457
+ }
1458
+ const afterComponents = parts.slice(componentsIndex + 1);
1459
+ if (afterComponents.length <= 1) {
1460
+ return void 0;
1461
+ }
1462
+ const folderName = afterComponents[0];
1463
+ const fileName = afterComponents[afterComponents.length - 1].replace(/\.(tsx?|jsx?)$/, "");
1464
+ if (afterComponents.length === 2 && folderName === fileName) {
1465
+ return void 0;
1466
+ }
1467
+ const categoryParts = afterComponents.slice(0, Math.min(depth, afterComponents.length - 1));
1468
+ const lastCategoryPart = categoryParts[categoryParts.length - 1];
1469
+ if (lastCategoryPart === fileName) {
1470
+ categoryParts.pop();
1321
1471
  }
1322
- return null;
1472
+ if (categoryParts.length === 0) {
1473
+ return void 0;
1474
+ }
1475
+ return categoryParts.join("/");
1323
1476
  }
1324
- function extractMeta(arg, warnings) {
1325
- const metaProp = findProperty(arg, "meta");
1326
- if (!metaProp || !ts2.isObjectLiteralExpression(metaProp)) {
1327
- warnings.push("No meta object found");
1328
- return {};
1477
+ function hasRealEnrichment(fragment) {
1478
+ if (fragment.description && fragment.description.length > 20) {
1479
+ return true;
1329
1480
  }
1330
- const meta = {};
1331
- const name = extractStringProperty(metaProp, "name");
1332
- if (name) meta.name = name;
1333
- const description = extractStringProperty(metaProp, "description");
1334
- if (description) meta.description = description;
1335
- const category = extractStringProperty(metaProp, "category");
1336
- if (category) meta.category = category;
1337
- const status = extractStringProperty(metaProp, "status");
1338
- if (status) meta.status = status;
1339
- const since = extractStringProperty(metaProp, "since");
1340
- if (since) meta.since = since;
1341
- const figma = extractStringProperty(metaProp, "figma");
1342
- if (figma) meta.figma = figma;
1343
- const tags = extractStringArray(metaProp, "tags");
1344
- if (tags.length > 0) meta.tags = tags;
1345
- const depsProp = findProperty(metaProp, "dependencies");
1346
- if (depsProp && ts2.isArrayLiteralExpression(depsProp)) {
1347
- const deps = extractLiteralValue(depsProp);
1348
- if (Array.isArray(deps) && deps.length > 0) {
1349
- meta.dependencies = deps;
1350
- }
1481
+ if (fragment.usage?.when && fragment.usage.when.length > 0) {
1482
+ return true;
1351
1483
  }
1352
- return meta;
1353
- }
1354
- function extractUsage(arg, warnings) {
1355
- const usageProp = findProperty(arg, "usage");
1356
- if (!usageProp || !ts2.isObjectLiteralExpression(usageProp)) {
1357
- return { when: [], whenNot: [] };
1484
+ if (fragment.usage?.doNot && fragment.usage.doNot.length > 0) {
1485
+ return true;
1358
1486
  }
1359
- return {
1360
- when: extractStringArray(usageProp, "when"),
1361
- whenNot: extractStringArray(usageProp, "whenNot"),
1362
- guidelines: extractStringArray(usageProp, "guidelines"),
1363
- accessibility: extractStringArray(usageProp, "accessibility")
1364
- };
1365
- }
1366
- function extractProps(arg, warnings) {
1367
- const propsProp = findProperty(arg, "props");
1368
- if (!propsProp || !ts2.isObjectLiteralExpression(propsProp)) {
1369
- return {};
1487
+ if (fragment.usage?.patterns && fragment.usage.patterns.length > 0) {
1488
+ return true;
1370
1489
  }
1371
- const props = {};
1372
- for (const prop of propsProp.properties) {
1373
- if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
1374
- const propName = prop.name.text;
1375
- const propValue = prop.initializer;
1376
- if (ts2.isObjectLiteralExpression(propValue)) {
1377
- props[propName] = extractPropDefinition(propValue);
1378
- }
1379
- }
1490
+ if (fragment.accessibility?.requirements && fragment.accessibility.requirements.length > 0) {
1491
+ return true;
1380
1492
  }
1381
- return props;
1493
+ if (fragment.figma?.nodeId || fragment.figma?.variants) {
1494
+ return true;
1495
+ }
1496
+ if (fragment.related?.similar && fragment.related.similar.length > 0) {
1497
+ return true;
1498
+ }
1499
+ return false;
1382
1500
  }
1383
- function extractPropDefinition(obj) {
1384
- const def = {};
1385
- const type = extractStringProperty(obj, "type");
1386
- if (type) def.type = type;
1387
- const description = extractStringProperty(obj, "description");
1388
- if (description) def.description = description;
1389
- const values = extractStringArray(obj, "values");
1390
- if (values.length > 0) def.values = values;
1391
- const required = extractBooleanProperty(obj, "required");
1392
- if (required !== null) def.required = required;
1393
- const defaultProp = findProperty(obj, "default");
1394
- if (defaultProp) {
1395
- def.default = extractLiteralValue(defaultProp);
1501
+ function resolveComponentPath(componentName, registry) {
1502
+ const entry = registry.components[componentName];
1503
+ return entry?.path;
1504
+ }
1505
+ function getComponentsByCategory(category, registry) {
1506
+ return registry.categories?.[category] || [];
1507
+ }
1508
+
1509
+ // src/core/generators/context.ts
1510
+ function generateContextMd(registry, options = {}) {
1511
+ const {
1512
+ format = "markdown",
1513
+ compact = false,
1514
+ include = { props: true, relations: true, code: false }
1515
+ } = options;
1516
+ if (format === "json") {
1517
+ return generateJsonContext(registry, include, compact);
1396
1518
  }
1397
- const constraints = extractStringArray(obj, "constraints");
1398
- if (constraints.length > 0) def.constraints = constraints;
1399
- return def;
1519
+ return generateMarkdownContext(registry, include, compact);
1400
1520
  }
1401
- function extractVariants(arg, sourceFile, warnings) {
1402
- const variantsProp = findProperty(arg, "variants");
1403
- if (!variantsProp || !ts2.isArrayLiteralExpression(variantsProp)) {
1404
- return [];
1521
+ function generateMarkdownContext(registry, include, compact) {
1522
+ const lines = [];
1523
+ const componentNames = Object.keys(registry.components).sort();
1524
+ const componentCount = componentNames.length;
1525
+ lines.push("# Component Library Context");
1526
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} | Components: ${componentCount}`);
1527
+ lines.push("");
1528
+ lines.push("## Quick Reference");
1529
+ lines.push("");
1530
+ lines.push("| Component | Path | Category | Status |");
1531
+ lines.push("|-----------|------|----------|--------|");
1532
+ for (const name of componentNames) {
1533
+ const entry = registry.components[name];
1534
+ const status = entry.status || "stable";
1535
+ const category = entry.category || "-";
1536
+ lines.push(`| ${name} | ${entry.path} | ${category} | ${status} |`);
1405
1537
  }
1406
- const variants = [];
1407
- for (const element of variantsProp.elements) {
1408
- if (ts2.isObjectLiteralExpression(element)) {
1409
- const name = extractStringProperty(element, "name");
1410
- const description = extractStringProperty(element, "description");
1411
- if (name) {
1412
- const variant = {
1413
- name,
1414
- description: description ?? ""
1415
- };
1416
- const codeProp = findProperty(element, "code");
1417
- if (codeProp && (ts2.isStringLiteral(codeProp) || ts2.isNoSubstitutionTemplateLiteral(codeProp))) {
1418
- variant.code = codeProp.text;
1419
- }
1420
- const renderProp = findProperty(element, "render");
1421
- if (renderProp && !variant.code) {
1422
- variant.code = extractRenderCode(renderProp, sourceFile);
1538
+ lines.push("");
1539
+ if (compact) {
1540
+ const content2 = lines.join("\n");
1541
+ return {
1542
+ content: content2,
1543
+ tokenEstimate: estimateTokens(content2),
1544
+ componentCount
1545
+ };
1546
+ }
1547
+ lines.push("---");
1548
+ lines.push("");
1549
+ for (const name of componentNames) {
1550
+ const entry = registry.components[name];
1551
+ const fragment = entry.fragment;
1552
+ lines.push(`## ${name}`);
1553
+ lines.push(`**Path:** \`${entry.path}\``);
1554
+ if (entry.category) {
1555
+ lines.push(`**Category:** ${entry.category} | **Status:** ${entry.status || "stable"}`);
1556
+ }
1557
+ lines.push("");
1558
+ if (entry.description || fragment?.description) {
1559
+ lines.push("### Description");
1560
+ lines.push(entry.description || fragment?.description || "");
1561
+ lines.push("");
1562
+ }
1563
+ if (fragment?.usage) {
1564
+ if (fragment.usage.when && fragment.usage.when.length > 0) {
1565
+ lines.push("### When to Use");
1566
+ for (const when of fragment.usage.when) {
1567
+ lines.push(`- ${when}`);
1423
1568
  }
1424
- const figma = extractStringProperty(element, "figma");
1425
- if (figma) {
1426
- variant.figma = figma;
1569
+ lines.push("");
1570
+ }
1571
+ if (fragment.usage.doNot && fragment.usage.doNot.length > 0) {
1572
+ lines.push("### Do Not");
1573
+ for (const doNotItem of fragment.usage.doNot) {
1574
+ if (typeof doNotItem === "string") {
1575
+ lines.push(`- ${doNotItem}`);
1576
+ } else {
1577
+ const item = doNotItem;
1578
+ if (item.instead) {
1579
+ const alternativePath = resolveComponentPath2(item.instead, registry);
1580
+ if (alternativePath) {
1581
+ lines.push(`- ${item.text} \u2192 use **${item.instead}** (\`${alternativePath}\`)`);
1582
+ } else {
1583
+ lines.push(`- ${item.text} \u2192 use **${item.instead}**`);
1584
+ }
1585
+ } else {
1586
+ lines.push(`- ${item.text}`);
1587
+ }
1588
+ }
1427
1589
  }
1428
- const argsProp = findProperty(element, "args");
1429
- if (argsProp && ts2.isObjectLiteralExpression(argsProp)) {
1430
- const argsValue = extractLiteralValue(argsProp);
1431
- if (argsValue && typeof argsValue === "object" && !Array.isArray(argsValue)) {
1432
- variant.args = argsValue;
1590
+ lines.push("");
1591
+ }
1592
+ if (include.code && fragment.usage.patterns && fragment.usage.patterns.length > 0) {
1593
+ lines.push("### Patterns");
1594
+ for (const pattern of fragment.usage.patterns) {
1595
+ lines.push(`**${pattern.name}**`);
1596
+ if (pattern.description) {
1597
+ lines.push(pattern.description);
1433
1598
  }
1599
+ lines.push("```tsx");
1600
+ lines.push(pattern.code);
1601
+ lines.push("```");
1602
+ lines.push("");
1434
1603
  }
1435
- variants.push(variant);
1436
1604
  }
1437
1605
  }
1606
+ if (include.props && entry.props && Object.keys(entry.props).length > 0) {
1607
+ lines.push("### Props");
1608
+ lines.push("| Prop | Type | Default | Description |");
1609
+ lines.push("|------|------|---------|-------------|");
1610
+ for (const [propName, prop] of Object.entries(entry.props)) {
1611
+ const type = formatPropType(prop);
1612
+ const defaultVal = prop.default !== void 0 ? `\`${JSON.stringify(prop.default)}\`` : "-";
1613
+ const desc = prop.description || "-";
1614
+ const required = prop.required ? " (required)" : "";
1615
+ lines.push(`| ${propName}${required} | ${type} | ${defaultVal} | ${desc} |`);
1616
+ }
1617
+ lines.push("");
1618
+ }
1619
+ if (fragment?.accessibility) {
1620
+ lines.push("### Accessibility");
1621
+ if (fragment.accessibility.role) {
1622
+ lines.push(`**Role:** ${fragment.accessibility.role}`);
1623
+ }
1624
+ if (fragment.accessibility.requirements && fragment.accessibility.requirements.length > 0) {
1625
+ for (const req of fragment.accessibility.requirements) {
1626
+ lines.push(`- ${req}`);
1627
+ }
1628
+ }
1629
+ if (fragment.accessibility.keyboard) {
1630
+ lines.push("");
1631
+ lines.push("**Keyboard:**");
1632
+ for (const [key, action] of Object.entries(fragment.accessibility.keyboard)) {
1633
+ lines.push(`- \`${key}\`: ${action}`);
1634
+ }
1635
+ }
1636
+ lines.push("");
1637
+ }
1638
+ if (include.relations && fragment?.related) {
1639
+ lines.push("### Related");
1640
+ if (fragment.related.similar && fragment.related.similar.length > 0) {
1641
+ const resolved = fragment.related.similar.map((comp) => {
1642
+ const path = resolveComponentPath2(comp, registry);
1643
+ return path ? `${comp} (\`${path}\`)` : comp;
1644
+ });
1645
+ lines.push(`- **Similar:** ${resolved.join(", ")}`);
1646
+ }
1647
+ if (fragment.related.composedWith && fragment.related.composedWith.length > 0) {
1648
+ const resolved = fragment.related.composedWith.map((comp) => {
1649
+ const path = resolveComponentPath2(comp, registry);
1650
+ return path ? `${comp} (\`${path}\`)` : comp;
1651
+ });
1652
+ lines.push(`- **Composed with:** ${resolved.join(", ")}`);
1653
+ }
1654
+ if (fragment.related.usedIn && fragment.related.usedIn.length > 0) {
1655
+ const resolved = fragment.related.usedIn.map((comp) => {
1656
+ const path = resolveComponentPath2(comp, registry);
1657
+ return path ? `${comp} (\`${path}\`)` : comp;
1658
+ });
1659
+ lines.push(`- **Used in:** ${resolved.join(", ")}`);
1660
+ }
1661
+ lines.push("");
1662
+ }
1663
+ lines.push("---");
1664
+ lines.push("");
1438
1665
  }
1439
- return variants;
1666
+ const content = lines.join("\n");
1667
+ return {
1668
+ content,
1669
+ tokenEstimate: estimateTokens(content),
1670
+ componentCount
1671
+ };
1440
1672
  }
1441
- function dedent(str) {
1442
- const lines = str.split("\n");
1443
- if (lines.length <= 1) {
1444
- return str;
1445
- }
1446
- const firstLineIndent = lines[0].match(/^(\s*)/)?.[1].length ?? 0;
1447
- const startIndex = firstLineIndent === 0 ? 1 : 0;
1448
- let minIndent = Infinity;
1449
- for (let i = startIndex; i < lines.length; i++) {
1450
- const line = lines[i];
1451
- if (line.trim() === "") continue;
1452
- const match = line.match(/^(\s*)/);
1453
- if (match) {
1454
- minIndent = Math.min(minIndent, match[1].length);
1673
+ function generateJsonContext(registry, include, compact) {
1674
+ const componentNames = Object.keys(registry.components).sort();
1675
+ const componentCount = componentNames.length;
1676
+ const components = {};
1677
+ for (const name of componentNames) {
1678
+ const entry = registry.components[name];
1679
+ const fragment = entry.fragment;
1680
+ const component = {
1681
+ path: entry.path
1682
+ };
1683
+ if (entry.category) component.category = entry.category;
1684
+ if (entry.status) component.status = entry.status;
1685
+ if (entry.description || fragment?.description) {
1686
+ component.description = entry.description || fragment?.description;
1455
1687
  }
1456
- }
1457
- if (minIndent === Infinity || minIndent === 0) {
1458
- return str;
1459
- }
1460
- return lines.map((line, index) => {
1461
- if (index === 0 && firstLineIndent === 0) {
1462
- return line;
1688
+ if (!compact && fragment?.usage) {
1689
+ if (fragment.usage.when && fragment.usage.when.length > 0) {
1690
+ component.whenToUse = fragment.usage.when;
1691
+ }
1692
+ if (fragment.usage.doNot && fragment.usage.doNot.length > 0) {
1693
+ component.doNot = fragment.usage.doNot.map((item) => {
1694
+ if (typeof item === "string") {
1695
+ return { text: item };
1696
+ }
1697
+ const doNotItem = item;
1698
+ const result = {
1699
+ text: doNotItem.text
1700
+ };
1701
+ if (doNotItem.instead) {
1702
+ result.instead = doNotItem.instead;
1703
+ result.insteadPath = resolveComponentPath2(doNotItem.instead, registry);
1704
+ }
1705
+ return result;
1706
+ });
1707
+ }
1463
1708
  }
1464
- return line.slice(minIndent);
1465
- }).join("\n");
1466
- }
1467
- function extractRenderCode(renderProp, sourceFile) {
1468
- if (ts2.isArrowFunction(renderProp)) {
1469
- const body = renderProp.body;
1470
- const start = body.getStart(sourceFile);
1471
- const end = body.getEnd();
1472
- let code = sourceFile.text.substring(start, end).trim();
1473
- if (code.startsWith("(") && code.endsWith(")")) {
1474
- code = code.slice(1, -1).trim();
1709
+ if (!compact && include.props && entry.props) {
1710
+ component.props = {};
1711
+ for (const [propName, prop] of Object.entries(entry.props)) {
1712
+ component.props[propName] = {
1713
+ type: formatPropType(prop)
1714
+ };
1715
+ if (prop.required) component.props[propName].required = true;
1716
+ if (prop.default !== void 0) component.props[propName].default = prop.default;
1717
+ if (prop.description) component.props[propName].description = prop.description;
1718
+ }
1475
1719
  }
1476
- code = dedent(code);
1477
- return code;
1478
- }
1479
- return void 0;
1480
- }
1481
- function extractRelations(arg, warnings) {
1482
- const relationsProp = findProperty(arg, "relations");
1483
- if (!relationsProp || !ts2.isArrayLiteralExpression(relationsProp)) {
1484
- return [];
1485
- }
1486
- const relations = [];
1487
- for (const element of relationsProp.elements) {
1488
- if (ts2.isObjectLiteralExpression(element)) {
1489
- const component = extractStringProperty(element, "component");
1490
- const relationship = extractStringProperty(element, "relationship");
1491
- const note = extractStringProperty(element, "note");
1492
- if (component && relationship) {
1493
- relations.push({
1494
- component,
1495
- relationship,
1496
- note: note ?? ""
1497
- });
1720
+ if (!compact && include.relations && fragment?.related) {
1721
+ component.related = {};
1722
+ if (fragment.related.similar) {
1723
+ component.related.similar = fragment.related.similar.map((name2) => ({
1724
+ name: name2,
1725
+ path: resolveComponentPath2(name2, registry)
1726
+ }));
1727
+ }
1728
+ if (fragment.related.composedWith) {
1729
+ component.related.composedWith = fragment.related.composedWith.map((name2) => ({
1730
+ name: name2,
1731
+ path: resolveComponentPath2(name2, registry)
1732
+ }));
1733
+ }
1734
+ if (fragment.related.usedIn) {
1735
+ component.related.usedIn = fragment.related.usedIn.map((name2) => ({
1736
+ name: name2,
1737
+ path: resolveComponentPath2(name2, registry)
1738
+ }));
1498
1739
  }
1499
1740
  }
1741
+ components[name] = component;
1500
1742
  }
1501
- return relations;
1743
+ const output = {
1744
+ version: "1.0",
1745
+ generatedAt: registry.generatedAt,
1746
+ componentCount,
1747
+ categories: registry.categories,
1748
+ components
1749
+ };
1750
+ const content = JSON.stringify(output, null, 2);
1751
+ return {
1752
+ content,
1753
+ tokenEstimate: estimateTokens(content),
1754
+ componentCount
1755
+ };
1502
1756
  }
1503
- function extractAIMetadata(arg, warnings) {
1504
- const aiProp = findProperty(arg, "ai");
1505
- if (!aiProp || !ts2.isObjectLiteralExpression(aiProp)) {
1506
- return void 0;
1507
- }
1508
- const ai = {};
1509
- const compositionPattern = extractStringProperty(aiProp, "compositionPattern");
1510
- if (compositionPattern && ["compound", "simple", "controlled"].includes(compositionPattern)) {
1511
- ai.compositionPattern = compositionPattern;
1512
- }
1513
- const subComponents = extractStringArray(aiProp, "subComponents");
1514
- if (subComponents.length > 0) {
1515
- ai.subComponents = subComponents;
1516
- }
1517
- const requiredChildren = extractStringArray(aiProp, "requiredChildren");
1518
- if (requiredChildren.length > 0) {
1519
- ai.requiredChildren = requiredChildren;
1520
- }
1521
- const commonPatterns = extractStringArray(aiProp, "commonPatterns");
1522
- if (commonPatterns.length > 0) {
1523
- ai.commonPatterns = commonPatterns;
1524
- }
1525
- if (Object.keys(ai).length > 0) {
1526
- return ai;
1527
- }
1528
- return void 0;
1757
+ function resolveComponentPath2(componentName, registry) {
1758
+ return registry.components[componentName]?.path;
1529
1759
  }
1530
- function extractContractMetadata(arg) {
1531
- const contractProp = findProperty(arg, "contract");
1532
- if (!contractProp || !ts2.isObjectLiteralExpression(contractProp)) {
1533
- return void 0;
1534
- }
1535
- const contract = {};
1536
- const propsSummary = extractStringArray(contractProp, "propsSummary");
1537
- if (propsSummary.length > 0) {
1538
- contract.propsSummary = propsSummary;
1539
- }
1540
- const a11yRules = extractStringArray(contractProp, "a11yRules");
1541
- if (a11yRules.length > 0) {
1542
- contract.a11yRules = a11yRules;
1543
- }
1544
- if (Object.keys(contract).length > 0) {
1545
- return contract;
1760
+ function formatPropType(prop) {
1761
+ if (prop.options && prop.options.length > 0) {
1762
+ return prop.options.map((o) => `"${o}"`).join(" \\| ");
1546
1763
  }
1547
- return void 0;
1764
+ return prop.type || "unknown";
1548
1765
  }
1549
- function extractStringProperty(obj, name) {
1550
- const prop = findProperty(obj, name);
1551
- if (prop && ts2.isStringLiteral(prop)) {
1552
- return prop.text;
1553
- }
1554
- if (prop && ts2.isNoSubstitutionTemplateLiteral(prop)) {
1555
- return prop.text;
1556
- }
1557
- return null;
1766
+ function estimateTokens(text) {
1767
+ return Math.ceil(text.length / 4);
1558
1768
  }
1559
- function extractBooleanProperty(obj, name) {
1560
- const prop = findProperty(obj, name);
1561
- if (prop) {
1562
- if (prop.kind === ts2.SyntaxKind.TrueKeyword) return true;
1563
- if (prop.kind === ts2.SyntaxKind.FalseKeyword) return false;
1769
+
1770
+ // src/core/importAnalyzer.ts
1771
+ import ts3 from "typescript";
1772
+ import { readFileSync as readFileSync3 } from "fs";
1773
+ import { dirname as dirname5, basename as basename4 } from "path";
1774
+ function extractComponentNameFromPath(filePath) {
1775
+ const fileName = basename4(filePath);
1776
+ if (fileName === "index.tsx" || fileName === "index.ts" || fileName === "index.jsx" || fileName === "index.js") {
1777
+ const parentDir = basename4(dirname5(filePath));
1778
+ return parentDir;
1564
1779
  }
1565
- return null;
1780
+ return fileName.replace(/\.(tsx?|jsx?)$/, "").replace(/\.stories$/, "").replace(/\.fragment$/, "").replace(/\.fragment$/, "");
1566
1781
  }
1567
- function extractStringArray(obj, name) {
1568
- const prop = findProperty(obj, name);
1569
- if (!prop || !ts2.isArrayLiteralExpression(prop)) {
1570
- return [];
1782
+ function isComponentImportPath(importPath) {
1783
+ if (!importPath.startsWith(".") && !importPath.startsWith("/")) {
1784
+ return false;
1571
1785
  }
1572
- const result = [];
1573
- for (const element of prop.elements) {
1574
- if (ts2.isStringLiteral(element)) {
1575
- result.push(element.text);
1576
- } else if (ts2.isNoSubstitutionTemplateLiteral(element)) {
1577
- result.push(element.text);
1786
+ const skipPatterns = [
1787
+ /\/hooks\//,
1788
+ /\/utils\//,
1789
+ /\/helpers\//,
1790
+ /\/lib\//,
1791
+ /\/types\//,
1792
+ /\/constants\//,
1793
+ /\/styles\//,
1794
+ /\/context\//,
1795
+ /\.css$/,
1796
+ /\.scss$/,
1797
+ /\.less$/,
1798
+ /\.json$/
1799
+ ];
1800
+ for (const pattern of skipPatterns) {
1801
+ if (pattern.test(importPath)) {
1802
+ return false;
1578
1803
  }
1579
1804
  }
1580
- return result;
1805
+ return true;
1581
1806
  }
1582
- function extractLiteralValue(expr) {
1583
- if (ts2.isStringLiteral(expr)) {
1584
- return expr.text;
1585
- }
1586
- if (ts2.isNumericLiteral(expr)) {
1587
- return Number(expr.text);
1588
- }
1589
- if (expr.kind === ts2.SyntaxKind.TrueKeyword) {
1590
- return true;
1591
- }
1592
- if (expr.kind === ts2.SyntaxKind.FalseKeyword) {
1593
- return false;
1594
- }
1595
- if (expr.kind === ts2.SyntaxKind.NullKeyword) {
1596
- return null;
1597
- }
1598
- if (ts2.isArrayLiteralExpression(expr)) {
1599
- return expr.elements.map(extractLiteralValue);
1807
+ function isPascalCase(name) {
1808
+ return /^[A-Z][a-zA-Z0-9]*$/.test(name);
1809
+ }
1810
+ function analyzeFileImports(filePath) {
1811
+ const componentName = extractComponentNameFromPath(filePath);
1812
+ const imports = [];
1813
+ let sourceText;
1814
+ try {
1815
+ sourceText = readFileSync3(filePath, "utf-8");
1816
+ } catch {
1817
+ return { filePath, componentName, imports };
1600
1818
  }
1601
- if (ts2.isObjectLiteralExpression(expr)) {
1602
- const obj = {};
1603
- for (const prop of expr.properties) {
1604
- if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
1605
- obj[prop.name.text] = extractLiteralValue(prop.initializer);
1819
+ const sourceFile = ts3.createSourceFile(
1820
+ filePath,
1821
+ sourceText,
1822
+ ts3.ScriptTarget.Latest,
1823
+ true,
1824
+ filePath.endsWith(".tsx") || filePath.endsWith(".jsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS
1825
+ );
1826
+ ts3.forEachChild(sourceFile, (node) => {
1827
+ if (ts3.isImportDeclaration(node)) {
1828
+ const moduleSpecifier = node.moduleSpecifier;
1829
+ if (!ts3.isStringLiteral(moduleSpecifier)) return;
1830
+ const importPath = moduleSpecifier.text;
1831
+ if (!isComponentImportPath(importPath)) return;
1832
+ const importClause = node.importClause;
1833
+ if (!importClause) return;
1834
+ if (importClause.name) {
1835
+ const name = importClause.name.text;
1836
+ if (isPascalCase(name)) {
1837
+ imports.push({
1838
+ name,
1839
+ path: importPath,
1840
+ isDefault: true,
1841
+ isNamespace: false
1842
+ });
1843
+ }
1844
+ }
1845
+ const namedBindings = importClause.namedBindings;
1846
+ if (namedBindings) {
1847
+ if (ts3.isNamedImports(namedBindings)) {
1848
+ for (const element of namedBindings.elements) {
1849
+ const name = element.name.text;
1850
+ if (isPascalCase(name)) {
1851
+ imports.push({
1852
+ name,
1853
+ path: importPath,
1854
+ isDefault: false,
1855
+ isNamespace: false
1856
+ });
1857
+ }
1858
+ }
1859
+ } else if (ts3.isNamespaceImport(namedBindings)) {
1860
+ const name = namedBindings.name.text;
1861
+ imports.push({
1862
+ name,
1863
+ path: importPath,
1864
+ isDefault: false,
1865
+ isNamespace: true
1866
+ });
1867
+ }
1868
+ }
1869
+ }
1870
+ });
1871
+ return { filePath, componentName, imports };
1872
+ }
1873
+ function buildImportGraph(filePaths) {
1874
+ const importedBy = /* @__PURE__ */ new Map();
1875
+ for (const filePath of filePaths) {
1876
+ const result = analyzeFileImports(filePath);
1877
+ for (const imp of result.imports) {
1878
+ if (imp.isNamespace) continue;
1879
+ const importedComponent = imp.name;
1880
+ const importingComponent = result.componentName;
1881
+ if (importedComponent === importingComponent) continue;
1882
+ const existing = importedBy.get(importedComponent) || [];
1883
+ if (!existing.includes(importingComponent)) {
1884
+ existing.push(importingComponent);
1885
+ importedBy.set(importedComponent, existing);
1606
1886
  }
1607
1887
  }
1608
- return obj;
1609
1888
  }
1610
- return void 0;
1889
+ return importedBy;
1890
+ }
1891
+ function getImportedBy(componentName, importGraph) {
1892
+ return importGraph.get(componentName) || [];
1611
1893
  }
1612
-
1613
- // src/core/previewLoader.ts
1614
- import { existsSync as existsSync4 } from "fs";
1615
- import { join as join3, resolve as resolve3 } from "path";
1616
-
1617
- // src/core/importAnalyzer.ts
1618
- import ts3 from "typescript";
1619
- import { readFileSync as readFileSync3 } from "fs";
1620
- import { dirname as dirname5, basename as basename4 } from "path";
1621
1894
 
1622
1895
  export {
1623
1896
  findConfigFile,
1624
1897
  loadConfig,
1625
1898
  discoverBlockFiles,
1899
+ discoverRecipeFiles,
1626
1900
  discoverFragmentFiles,
1627
1901
  discoverComponentFiles,
1628
1902
  extractComponentName,
1903
+ discoverComponentsFromSource,
1904
+ discoverComponentsFromBarrel,
1629
1905
  discoverTokenFiles,
1906
+ discoverInstalledFragments,
1630
1907
  discoverAllComponents,
1631
1908
  loadFragmentFile,
1909
+ loadFragmentFiles,
1632
1910
  parseFragmentFile,
1911
+ findPreviewConfigPath,
1912
+ findStorybookDir,
1913
+ loadPreviewConfig,
1914
+ autoLoadPreviewConfig,
1915
+ generatePreviewModule,
1633
1916
  extractPropsFromFile,
1917
+ extractPropsFromSource,
1634
1918
  generateRegistry,
1635
- generateContextMd
1919
+ resolveComponentPath,
1920
+ getComponentsByCategory,
1921
+ generateContextMd,
1922
+ analyzeFileImports,
1923
+ buildImportGraph,
1924
+ getImportedBy
1636
1925
  };
1637
- //# sourceMappingURL=chunk-65WSVDV5.js.map
1926
+ //# sourceMappingURL=chunk-HQ6A6DTV.js.map