@buoy-design/cli 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +37 -21
  2. package/dist/cloud/config.d.ts +5 -0
  3. package/dist/cloud/config.d.ts.map +1 -1
  4. package/dist/cloud/config.js.map +1 -1
  5. package/dist/commands/ignore.d.ts +7 -3
  6. package/dist/commands/ignore.d.ts.map +1 -1
  7. package/dist/commands/ignore.js +81 -78
  8. package/dist/commands/ignore.js.map +1 -1
  9. package/dist/commands/index.d.ts +1 -0
  10. package/dist/commands/index.d.ts.map +1 -1
  11. package/dist/commands/index.js +1 -0
  12. package/dist/commands/index.js.map +1 -1
  13. package/dist/commands/login.d.ts.map +1 -1
  14. package/dist/commands/login.js +108 -33
  15. package/dist/commands/login.js.map +1 -1
  16. package/dist/commands/rescue.d.ts +3 -0
  17. package/dist/commands/rescue.d.ts.map +1 -0
  18. package/dist/commands/rescue.js +147 -0
  19. package/dist/commands/rescue.js.map +1 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/rescue/report.d.ts +9 -0
  24. package/dist/rescue/report.d.ts.map +1 -0
  25. package/dist/rescue/report.js +107 -0
  26. package/dist/rescue/report.js.map +1 -0
  27. package/dist/rescue/store.d.ts +9 -0
  28. package/dist/rescue/store.d.ts.map +1 -0
  29. package/dist/rescue/store.js +50 -0
  30. package/dist/rescue/store.js.map +1 -0
  31. package/dist/rescue/types.d.ts +76 -0
  32. package/dist/rescue/types.d.ts.map +1 -0
  33. package/dist/rescue/types.js +2 -0
  34. package/dist/rescue/types.js.map +1 -0
  35. package/dist/rescue/workflow.d.ts +37 -0
  36. package/dist/rescue/workflow.d.ts.map +1 -0
  37. package/dist/rescue/workflow.js +355 -0
  38. package/dist/rescue/workflow.js.map +1 -0
  39. package/dist/services/drift-analysis.d.ts +3 -1
  40. package/dist/services/drift-analysis.d.ts.map +1 -1
  41. package/dist/services/drift-analysis.js +179 -98
  42. package/dist/services/drift-analysis.js.map +1 -1
  43. package/dist/utils/upgrade-hints.d.ts +13 -2
  44. package/dist/utils/upgrade-hints.d.ts.map +1 -1
  45. package/dist/utils/upgrade-hints.js +43 -5
  46. package/dist/utils/upgrade-hints.js.map +1 -1
  47. package/package.json +3 -3
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { ScanOrchestrator } from "../scan/orchestrator.js";
12
12
  import { getSeverityWeight } from "@buoy-design/core";
13
- import { TailwindScanner, extractStaticClassStrings } from "@buoy-design/scanners";
13
+ import { TailwindScanner, extractStaticClassStrings, } from "@buoy-design/scanners";
14
14
  import { detectRepeatedPatterns, checkVariantConsistency, checkExampleCompliance, detectTokenUtilities, checkTokenUtilityUsage, } from "@buoy-design/core";
15
15
  import { glob } from "glob";
16
16
  import { minimatch } from "minimatch";
@@ -94,7 +94,10 @@ export function applyPromoteRules(drifts, rules, onWarning) {
94
94
  export function applyEnforceRules(drifts, rules, onWarning) {
95
95
  if (rules.length === 0)
96
96
  return drifts;
97
- const promoteRules = rules.map((rule) => ({ ...rule, to: "critical" }));
97
+ const promoteRules = rules.map((rule) => ({
98
+ ...rule,
99
+ to: "critical",
100
+ }));
98
101
  return applyPromoteRules(drifts, promoteRules, onWarning);
99
102
  }
100
103
  function ruleMatches(d, rule, onWarning) {
@@ -313,11 +316,11 @@ const ENTRY_POINT_PATTERNS = [
313
316
  function isEntryPointComponent(component) {
314
317
  const source = component.source;
315
318
  // Only file-based sources (react, vue, svelte) have a path field
316
- if (source.type === 'figma' || source.type === 'storybook')
319
+ if (source.type === "figma" || source.type === "storybook")
317
320
  return false;
318
321
  // Prefix with "/" so regexes match both "app/foo/page.tsx" and "/app/foo/page.tsx"
319
- const location = `/${source.path || ''}`;
320
- return ENTRY_POINT_PATTERNS.some(pattern => pattern.test(location));
322
+ const location = `/${source.path || ""}`;
323
+ return ENTRY_POINT_PATTERNS.some((pattern) => pattern.test(location));
321
324
  }
322
325
  /**
323
326
  * Known CSS variable prefixes that are framework-internal implementation details.
@@ -327,37 +330,25 @@ function isEntryPointComponent(component) {
327
330
  const FRAMEWORK_INTERNAL_PREFIXES = {
328
331
  // Tailwind CSS — utility internals and plugin variables
329
332
  tailwind: [
330
- '--tw-', // all Tailwind internals (ring, shadow, gradient, transform, filter, prose, etc.)
333
+ "--tw-", // all Tailwind internals (ring, shadow, gradient, transform, filter, prose, etc.)
331
334
  ],
332
335
  // Radix UI — component internal sizing/animation variables
333
- radix: [
334
- '--radix-',
335
- ],
336
+ radix: ["--radix-"],
336
337
  // Chakra UI — generated theme CSS variables
337
- chakra: [
338
- '--chakra-',
339
- ],
338
+ chakra: ["--chakra-"],
340
339
  // Mantine — theme system variables
341
- mantine: [
342
- '--mantine-',
343
- ],
340
+ mantine: ["--mantine-"],
344
341
  // Material UI — theme CSS variables
345
- mui: [
346
- '--mui-',
347
- '--md-',
348
- '--joy-',
349
- ],
342
+ mui: ["--mui-", "--md-", "--joy-"],
350
343
  // Ant Design — component theme variables
351
- antd: [
352
- '--ant-',
353
- ],
344
+ antd: ["--ant-"],
354
345
  };
355
346
  /**
356
347
  * Check if a token name matches a known framework-internal CSS variable prefix.
357
348
  * These tokens are consumed by the framework itself, not by user components.
358
349
  */
359
350
  function isFrameworkInternalToken(tokenName, detectedFrameworks) {
360
- const name = tokenName.startsWith('--') ? tokenName : `--${tokenName}`;
351
+ const name = tokenName.startsWith("--") ? tokenName : `--${tokenName}`;
361
352
  for (const [framework, prefixes] of Object.entries(FRAMEWORK_INTERNAL_PREFIXES)) {
362
353
  // Match detected framework names loosely (e.g., "tailwindcss" matches "tailwind")
363
354
  const isDetected = [...detectedFrameworks].some((f) => f.includes(framework) || framework.includes(f));
@@ -395,7 +386,9 @@ export class DriftAnalysisService {
395
386
  });
396
387
  // Step 2: Scan tokens (before analysis, so suggestions can be generated)
397
388
  onProgress?.("Scanning tokens...");
398
- const { tokens: scannedTokens } = await orchestrator.scanTokens({ onProgress });
389
+ const { tokens: scannedTokens } = await orchestrator.scanTokens({
390
+ onProgress,
391
+ });
399
392
  // Step 2.1: Run semantic diff analysis
400
393
  onProgress?.("Analyzing drift...");
401
394
  const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
@@ -414,7 +407,10 @@ export class DriftAnalysisService {
414
407
  const detector = new ProjectDetector(this.projectRoot);
415
408
  const projectInfo = await detector.detect();
416
409
  if (projectInfo.frameworks.length > 0) {
417
- const sprawlDrift = engine.checkFrameworkSprawl(projectInfo.frameworks.map((f) => ({ name: f.name, version: f.version })));
410
+ const sprawlDrift = engine.checkFrameworkSprawl(projectInfo.frameworks.map((f) => ({
411
+ name: f.name,
412
+ version: f.version,
413
+ })));
418
414
  if (sprawlDrift) {
419
415
  drifts.push(...applySeverityOverrides([sprawlDrift], this.config.drift.severity));
420
416
  onProgress?.(`Framework sprawl detected: ${projectInfo.frameworks.map((f) => f.name).join(", ")}`);
@@ -482,7 +478,7 @@ export class DriftAnalysisService {
482
478
  await this.scanComponentAsValueUsages(componentUsageMap, componentNames);
483
479
  // Fix 1: Exempt entry point components (pages, routes, layouts)
484
480
  // These are rendered by the framework router, not imported by other components
485
- const nonEntryPointComponents = components.filter(c => !isEntryPointComponent(c));
481
+ const nonEntryPointComponents = components.filter((c) => !isEntryPointComponent(c));
486
482
  // Check for unused components (excluding entry points)
487
483
  const unusedComponentDrifts = engine.checkUnusedComponents(nonEntryPointComponents, componentUsageMap);
488
484
  if (unusedComponentDrifts.length > 0) {
@@ -496,7 +492,7 @@ export class DriftAnalysisService {
496
492
  const detectedNames = new Set(projectInfo.frameworks.map((f) => f.name.toLowerCase()));
497
493
  // Tailwind and CSS libraries are source configs, not component frameworks — detect from config/deps/tokens
498
494
  if (this.config.sources.tailwind?.enabled)
499
- detectedNames.add('tailwind');
495
+ detectedNames.add("tailwind");
500
496
  for (const tokenFile of projectInfo.tokens) {
501
497
  detectedNames.add(tokenFile.type.toLowerCase());
502
498
  }
@@ -515,7 +511,8 @@ export class DriftAnalysisService {
515
511
  const canonicalPatterns = this.config.sources.tokens?.canonical ?? [];
516
512
  const classifiedComponents = classifyComponents(components);
517
513
  const classifiedTokens = classifyTokens(scannedTokens, canonicalPatterns);
518
- if (classifiedComponents.canonical.length > 0 && classifiedComponents.code.length > 0) {
514
+ if (classifiedComponents.canonical.length > 0 &&
515
+ classifiedComponents.code.length > 0) {
519
516
  onProgress?.(`Comparing ${classifiedComponents.code.length} code components against ${classifiedComponents.canonical.length} design components...`);
520
517
  const componentDiff = engine.compareComponents(classifiedComponents.code, classifiedComponents.canonical);
521
518
  if (componentDiff.drifts.length > 0) {
@@ -523,7 +520,8 @@ export class DriftAnalysisService {
523
520
  onProgress?.(`Found ${componentDiff.drifts.length} cross-source component issues`);
524
521
  }
525
522
  }
526
- if (classifiedTokens.canonical.length > 0 && classifiedTokens.code.length > 0) {
523
+ if (classifiedTokens.canonical.length > 0 &&
524
+ classifiedTokens.code.length > 0) {
527
525
  onProgress?.(`Comparing ${classifiedTokens.code.length} code tokens against ${classifiedTokens.canonical.length} design tokens...`);
528
526
  const tokenDiff = engine.compareTokens(classifiedTokens.code, classifiedTokens.canonical);
529
527
  if (tokenDiff.drifts.length > 0) {
@@ -560,7 +558,8 @@ export class DriftAnalysisService {
560
558
  }
561
559
  }
562
560
  // Step 2.7: Phase 4.1 - Cross-Variant Consistency Checking
563
- const variantCheckEnabled = checkVariants ?? this.config.drift?.types?.["value-divergence"]?.checkVariants;
561
+ const variantCheckEnabled = checkVariants ??
562
+ this.config.drift?.types?.["value-divergence"]?.checkVariants;
564
563
  if (variantCheckEnabled) {
565
564
  onProgress?.("Checking variant consistency...");
566
565
  const variantDrifts = checkVariantConsistency(components);
@@ -570,7 +569,8 @@ export class DriftAnalysisService {
570
569
  }
571
570
  }
572
571
  // Step 2.8: Phase 4.2 - Token Utility Function Detection
573
- const tokenUtilityCheckEnabled = checkTokenUtilities ?? this.config.drift?.types?.["hardcoded-value"]?.checkUtilities;
572
+ const tokenUtilityCheckEnabled = checkTokenUtilities ??
573
+ this.config.drift?.types?.["hardcoded-value"]?.checkUtilities;
574
574
  if (tokenUtilityCheckEnabled) {
575
575
  onProgress?.("Detecting token utility functions...");
576
576
  const utilityAnalysis = detectTokenUtilities(components);
@@ -584,7 +584,8 @@ export class DriftAnalysisService {
584
584
  }
585
585
  }
586
586
  // Step 2.9: Phase 4.3 - Example Code vs Production Code Analysis
587
- const exampleCheckEnabled = checkExamples ?? this.config.drift?.types?.["missing-documentation"]?.checkExamples;
587
+ const exampleCheckEnabled = checkExamples ??
588
+ this.config.drift?.types?.["missing-documentation"]?.checkExamples;
588
589
  if (exampleCheckEnabled) {
589
590
  onProgress?.("Analyzing example code compliance...");
590
591
  const exampleDrifts = checkExampleCompliance(components);
@@ -621,7 +622,7 @@ export class DriftAnalysisService {
621
622
  let ignoredCount = 0;
622
623
  if (!includeIgnored) {
623
624
  const { loadIgnoreList, filterIgnored } = await import("../commands/ignore.js");
624
- const ignoreList = await loadIgnoreList();
625
+ const ignoreList = await loadIgnoreList(this.projectRoot);
625
626
  const filtered = filterIgnored(drifts, ignoreList);
626
627
  drifts = filtered.newDrifts;
627
628
  ignoredCount = filtered.ignoredCount;
@@ -633,6 +634,7 @@ export class DriftAnalysisService {
633
634
  drifts,
634
635
  components,
635
636
  tokenCount: scannedTokens.length,
637
+ tokens: scannedTokens,
636
638
  ignoredCount,
637
639
  summary: calculateDriftSummary(drifts),
638
640
  };
@@ -649,7 +651,15 @@ export class DriftAnalysisService {
649
651
  }
650
652
  };
651
653
  const { react, nextjs, vue, svelte, angular, webcomponent, templates, tailwind, tokens, } = this.config.sources;
652
- for (const source of [react, nextjs, vue, svelte, angular, webcomponent, templates]) {
654
+ for (const source of [
655
+ react,
656
+ nextjs,
657
+ vue,
658
+ svelte,
659
+ angular,
660
+ webcomponent,
661
+ templates,
662
+ ]) {
653
663
  if (!source?.enabled)
654
664
  continue;
655
665
  addMany(include, source.include);
@@ -674,8 +684,15 @@ export class DriftAnalysisService {
674
684
  if (aliasMap.size === 0)
675
685
  return;
676
686
  const usageGlobs = this.getUsageCollectorGlobs();
677
- const include = usageGlobs.include ?? ["**/*.{ts,tsx,js,jsx,vue,svelte,astro,html,mdx,md}"];
678
- const exclude = usageGlobs.exclude ?? ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**"];
687
+ const include = usageGlobs.include ?? [
688
+ "**/*.{ts,tsx,js,jsx,vue,svelte,astro,html,mdx,md}",
689
+ ];
690
+ const exclude = usageGlobs.exclude ?? [
691
+ "**/node_modules/**",
692
+ "**/dist/**",
693
+ "**/build/**",
694
+ "**/.next/**",
695
+ ];
679
696
  const files = await glob(include, {
680
697
  cwd: this.projectRoot,
681
698
  ignore: exclude,
@@ -826,24 +843,32 @@ export class DriftAnalysisService {
826
843
  */
827
844
  async scanBarrelReExports(componentUsageMap) {
828
845
  const cwd = this.projectRoot;
829
- const barrelFiles = await glob('**/index.{ts,tsx,js,jsx}', {
846
+ const barrelFiles = await glob("**/index.{ts,tsx,js,jsx}", {
830
847
  cwd,
831
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**'],
848
+ ignore: [
849
+ "**/node_modules/**",
850
+ "**/dist/**",
851
+ "**/build/**",
852
+ "**/.next/**",
853
+ ],
832
854
  nodir: true,
833
855
  maxDepth: 6,
834
856
  });
835
857
  for (const barrelFile of barrelFiles.slice(0, 200)) {
836
858
  try {
837
- const content = await readFile(resolve(cwd, barrelFile), 'utf-8');
859
+ const content = await readFile(resolve(cwd, barrelFile), "utf-8");
838
860
  // Check for named re-exports: export { Button, Card } from './components'
839
861
  const namedPattern = /export\s*\{\s*([^}]+)\s*\}\s*from/g;
840
862
  let match;
841
863
  while ((match = namedPattern.exec(content)) !== null) {
842
- const names = match[1].split(',').map(n => {
864
+ const names = match[1]
865
+ .split(",")
866
+ .map((n) => {
843
867
  // Handle "default as Name" and "Name as Alias"
844
868
  const parts = n.trim().split(/\s+as\s+/);
845
- return (parts[1] || parts[0] || '').trim();
846
- }).filter(n => n && /^[A-Z]/.test(n)); // Only PascalCase (component names)
869
+ return (parts[1] || parts[0] || "").trim();
870
+ })
871
+ .filter((n) => n && /^[A-Z]/.test(n)); // Only PascalCase (component names)
847
872
  for (const name of names) {
848
873
  componentUsageMap.set(name, (componentUsageMap.get(name) || 0) + 1);
849
874
  }
@@ -859,8 +884,8 @@ export class DriftAnalysisService {
859
884
  const wildcardPattern = /export\s*\*\s*from\s*['"]\.\/([^'"]+)['"]/g;
860
885
  while ((match = wildcardPattern.exec(content)) !== null) {
861
886
  const moduleName = match[1];
862
- const segments = moduleName.split('/');
863
- const last = segments[segments.length - 1] || '';
887
+ const segments = moduleName.split("/");
888
+ const last = segments[segments.length - 1] || "";
864
889
  if (/^[A-Z]/.test(last)) {
865
890
  componentUsageMap.set(last, (componentUsageMap.get(last) || 0) + 1);
866
891
  }
@@ -877,23 +902,30 @@ export class DriftAnalysisService {
877
902
  */
878
903
  async scanDynamicImports(componentUsageMap) {
879
904
  const cwd = this.projectRoot;
880
- const sourceFiles = await glob('**/*.{tsx,jsx,ts,js}', {
905
+ const sourceFiles = await glob("**/*.{tsx,jsx,ts,js}", {
881
906
  cwd,
882
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/*.test.*', '**/*.spec.*'],
907
+ ignore: [
908
+ "**/node_modules/**",
909
+ "**/dist/**",
910
+ "**/build/**",
911
+ "**/.next/**",
912
+ "**/*.test.*",
913
+ "**/*.spec.*",
914
+ ],
883
915
  nodir: true,
884
916
  maxDepth: 6,
885
917
  });
886
918
  for (const file of sourceFiles.slice(0, 200)) {
887
919
  try {
888
- const content = await readFile(resolve(cwd, file), 'utf-8');
889
- if (!content.includes('import('))
920
+ const content = await readFile(resolve(cwd, file), "utf-8");
921
+ if (!content.includes("import("))
890
922
  continue; // Quick check before regex
891
923
  const dynamicPattern = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
892
924
  let match;
893
925
  while ((match = dynamicPattern.exec(content)) !== null) {
894
926
  const modulePath = match[1];
895
- const segments = modulePath.split('/');
896
- const last = (segments[segments.length - 1] || '').replace(/\.(tsx?|jsx?)$/, '');
927
+ const segments = modulePath.split("/");
928
+ const last = (segments[segments.length - 1] || "").replace(/\.(tsx?|jsx?)$/, "");
897
929
  if (/^[A-Z]/.test(last)) {
898
930
  componentUsageMap.set(last, (componentUsageMap.get(last) || 0) + 1);
899
931
  }
@@ -914,9 +946,14 @@ export class DriftAnalysisService {
914
946
  if (knownComponents.length === 0)
915
947
  return;
916
948
  const cwd = this.projectRoot;
917
- const templateFiles = await glob('**/*.{vue,svelte,html}', {
949
+ const templateFiles = await glob("**/*.{vue,svelte,html}", {
918
950
  cwd,
919
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**'],
951
+ ignore: [
952
+ "**/node_modules/**",
953
+ "**/dist/**",
954
+ "**/build/**",
955
+ "**/.next/**",
956
+ ],
920
957
  nodir: true,
921
958
  maxDepth: 8,
922
959
  });
@@ -924,15 +961,15 @@ export class DriftAnalysisService {
924
961
  // Build a kebab-case lookup for Vue/Angular (MyComponent -> my-component)
925
962
  const kebabMap = new Map();
926
963
  for (const name of knownComponents) {
927
- const kebab = name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
964
+ const kebab = name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
928
965
  kebabMap.set(kebab, name);
929
966
  // Angular selectors often use app- prefix: ButtonComponent -> app-button
930
- const angularSelector = `app-${kebab.replace(/-component$/, '')}`;
967
+ const angularSelector = `app-${kebab.replace(/-component$/, "")}`;
931
968
  kebabMap.set(angularSelector, name);
932
969
  }
933
970
  for (const file of templateFiles.slice(0, 300)) {
934
971
  try {
935
- const content = await readFile(resolve(cwd, file), 'utf-8');
972
+ const content = await readFile(resolve(cwd, file), "utf-8");
936
973
  // Match PascalCase component tags: <MyComponent or <MyComponent>
937
974
  const pascalPattern = /<([A-Z][a-zA-Z0-9]*)\s*/g;
938
975
  let match;
@@ -963,16 +1000,21 @@ export class DriftAnalysisService {
963
1000
  */
964
1001
  async scanAutoRegistration(componentUsageMap) {
965
1002
  const cwd = this.projectRoot;
966
- const sourceFiles = await glob('**/*.{ts,js,tsx,jsx}', {
1003
+ const sourceFiles = await glob("**/*.{ts,js,tsx,jsx}", {
967
1004
  cwd,
968
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**'],
1005
+ ignore: [
1006
+ "**/node_modules/**",
1007
+ "**/dist/**",
1008
+ "**/build/**",
1009
+ "**/.next/**",
1010
+ ],
969
1011
  nodir: true,
970
1012
  maxDepth: 4,
971
1013
  });
972
1014
  for (const file of sourceFiles.slice(0, 100)) {
973
1015
  try {
974
- const content = await readFile(resolve(cwd, file), 'utf-8');
975
- if (!content.includes('.component('))
1016
+ const content = await readFile(resolve(cwd, file), "utf-8");
1017
+ if (!content.includes(".component("))
976
1018
  continue;
977
1019
  // Match: app.component('Name', ...) or Vue.component('Name', ...)
978
1020
  const pattern = /\.component\(\s*['"]([A-Z][a-zA-Z0-9]*)['"]/g;
@@ -993,16 +1035,16 @@ export class DriftAnalysisService {
993
1035
  */
994
1036
  async scanNgModuleDeclarations(componentUsageMap) {
995
1037
  const cwd = this.projectRoot;
996
- const moduleFiles = await glob('**/*.module.ts', {
1038
+ const moduleFiles = await glob("**/*.module.ts", {
997
1039
  cwd,
998
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**'],
1040
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
999
1041
  nodir: true,
1000
1042
  maxDepth: 8,
1001
1043
  });
1002
1044
  for (const file of moduleFiles.slice(0, 50)) {
1003
1045
  try {
1004
- const content = await readFile(resolve(cwd, file), 'utf-8');
1005
- if (!content.includes('declarations'))
1046
+ const content = await readFile(resolve(cwd, file), "utf-8");
1047
+ if (!content.includes("declarations"))
1006
1048
  continue;
1007
1049
  // Match declarations: [Component1, Component2, ...]
1008
1050
  const declMatch = content.match(/declarations\s*:\s*\[([\s\S]*?)\]/);
@@ -1015,7 +1057,7 @@ export class DriftAnalysisService {
1015
1057
  }
1016
1058
  }
1017
1059
  // Also match imports: [...] and exports: [...] arrays
1018
- for (const key of ['imports', 'exports']) {
1060
+ for (const key of ["imports", "exports"]) {
1019
1061
  const match = content.match(new RegExp(`${key}\\s*:\\s*\\[([\\s\\S]*?)\\]`));
1020
1062
  if (match) {
1021
1063
  const names = match[1].match(/\b([A-Z][a-zA-Z]+(?:Component|Module))\b/g);
@@ -1038,23 +1080,26 @@ export class DriftAnalysisService {
1038
1080
  */
1039
1081
  async scanStoryFileUsages(componentUsageMap) {
1040
1082
  const cwd = this.projectRoot;
1041
- const storyFiles = await glob('**/*.stories.{ts,tsx,js,jsx}', {
1083
+ const storyFiles = await glob("**/*.stories.{ts,tsx,js,jsx}", {
1042
1084
  cwd,
1043
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**'],
1085
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
1044
1086
  nodir: true,
1045
1087
  maxDepth: 8,
1046
1088
  });
1047
1089
  for (const file of storyFiles.slice(0, 200)) {
1048
1090
  try {
1049
- const content = await readFile(resolve(cwd, file), 'utf-8');
1091
+ const content = await readFile(resolve(cwd, file), "utf-8");
1050
1092
  // Match named imports: import { Button, Card } from '...'
1051
1093
  const importPattern = /import\s*\{\s*([^}]+)\s*\}\s*from/g;
1052
1094
  let match;
1053
1095
  while ((match = importPattern.exec(content)) !== null) {
1054
- const names = match[1].split(',').map(n => {
1096
+ const names = match[1]
1097
+ .split(",")
1098
+ .map((n) => {
1055
1099
  const parts = n.trim().split(/\s+as\s+/);
1056
- return (parts[0] || '').trim();
1057
- }).filter(n => n && /^[A-Z]/.test(n));
1100
+ return (parts[0] || "").trim();
1101
+ })
1102
+ .filter((n) => n && /^[A-Z]/.test(n));
1058
1103
  for (const name of names) {
1059
1104
  componentUsageMap.set(name, (componentUsageMap.get(name) || 0) + 1);
1060
1105
  }
@@ -1084,15 +1129,22 @@ export class DriftAnalysisService {
1084
1129
  */
1085
1130
  async scanWebComponentRegistrations(componentUsageMap) {
1086
1131
  const cwd = this.projectRoot;
1087
- const files = await glob('**/*.{ts,js}', {
1132
+ const files = await glob("**/*.{ts,js}", {
1088
1133
  cwd,
1089
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/*.d.ts', '**/*.spec.*', '**/*.test.*'],
1134
+ ignore: [
1135
+ "**/node_modules/**",
1136
+ "**/dist/**",
1137
+ "**/build/**",
1138
+ "**/*.d.ts",
1139
+ "**/*.spec.*",
1140
+ "**/*.test.*",
1141
+ ],
1090
1142
  nodir: true,
1091
1143
  maxDepth: 8,
1092
1144
  });
1093
1145
  for (const file of files.slice(0, 500)) {
1094
1146
  try {
1095
- const content = await readFile(resolve(cwd, file), 'utf-8');
1147
+ const content = await readFile(resolve(cwd, file), "utf-8");
1096
1148
  // Match customElements.define('tag-name', ClassName)
1097
1149
  const definePattern = /customElements\.define\s*\(\s*['"][^'"]+['"]\s*,\s*([A-Z][a-zA-Z0-9]*)/g;
1098
1150
  let match;
@@ -1121,7 +1173,10 @@ export class DriftAnalysisService {
1121
1173
  async scanNuxtAutoImports(componentUsageMap, componentNames) {
1122
1174
  const cwd = this.projectRoot;
1123
1175
  // Check for Nuxt config
1124
- const nuxtConfigs = await glob('nuxt.config.{ts,js,mjs}', { cwd, nodir: true });
1176
+ const nuxtConfigs = await glob("nuxt.config.{ts,js,mjs}", {
1177
+ cwd,
1178
+ nodir: true,
1179
+ });
1125
1180
  if (nuxtConfigs.length === 0)
1126
1181
  return;
1127
1182
  // In Nuxt, all components in components/ are auto-imported
@@ -1136,23 +1191,26 @@ export class DriftAnalysisService {
1136
1191
  */
1137
1192
  async scanTestFileUsages(componentUsageMap) {
1138
1193
  const cwd = this.projectRoot;
1139
- const testFiles = await glob('**/*.{test,spec}.{ts,tsx,js,jsx}', {
1194
+ const testFiles = await glob("**/*.{test,spec}.{ts,tsx,js,jsx}", {
1140
1195
  cwd,
1141
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**'],
1196
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
1142
1197
  nodir: true,
1143
1198
  maxDepth: 8,
1144
1199
  });
1145
1200
  for (const file of testFiles.slice(0, 300)) {
1146
1201
  try {
1147
- const content = await readFile(resolve(cwd, file), 'utf-8');
1202
+ const content = await readFile(resolve(cwd, file), "utf-8");
1148
1203
  // Match named imports: import { Button, Card } from '...'
1149
1204
  const importPattern = /import\s*\{\s*([^}]+)\s*\}\s*from/g;
1150
1205
  let match;
1151
1206
  while ((match = importPattern.exec(content)) !== null) {
1152
- const names = match[1].split(',').map(n => {
1207
+ const names = match[1]
1208
+ .split(",")
1209
+ .map((n) => {
1153
1210
  const parts = n.trim().split(/\s+as\s+/);
1154
- return (parts[0] || '').trim();
1155
- }).filter(n => n && /^[A-Z]/.test(n));
1211
+ return (parts[0] || "").trim();
1212
+ })
1213
+ .filter((n) => n && /^[A-Z]/.test(n));
1156
1214
  for (const name of names) {
1157
1215
  componentUsageMap.set(name, (componentUsageMap.get(name) || 0) + 1);
1158
1216
  }
@@ -1177,15 +1235,21 @@ export class DriftAnalysisService {
1177
1235
  */
1178
1236
  async scanHOCWrapperUsages(componentUsageMap) {
1179
1237
  const cwd = this.projectRoot;
1180
- const sourceFiles = await glob('**/*.{ts,tsx,js,jsx}', {
1238
+ const sourceFiles = await glob("**/*.{ts,tsx,js,jsx}", {
1181
1239
  cwd,
1182
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/*.d.ts'],
1240
+ ignore: [
1241
+ "**/node_modules/**",
1242
+ "**/dist/**",
1243
+ "**/build/**",
1244
+ "**/.next/**",
1245
+ "**/*.d.ts",
1246
+ ],
1183
1247
  nodir: true,
1184
1248
  maxDepth: 8,
1185
1249
  });
1186
1250
  for (const file of sourceFiles.slice(0, 500)) {
1187
1251
  try {
1188
- const content = await readFile(resolve(cwd, file), 'utf-8');
1252
+ const content = await readFile(resolve(cwd, file), "utf-8");
1189
1253
  // forwardRef(Component) / React.forwardRef(Component)
1190
1254
  const forwardRefPattern = /(?:React\.)?forwardRef\s*[(<]\s*(?:function\s+)?([A-Z][a-zA-Z0-9]*)/g;
1191
1255
  let match;
@@ -1251,31 +1315,37 @@ export class DriftAnalysisService {
1251
1315
  async scanPackageExports(componentUsageMap) {
1252
1316
  const cwd = this.projectRoot;
1253
1317
  try {
1254
- const pkgContent = await readFile(resolve(cwd, 'package.json'), 'utf-8');
1318
+ const pkgContent = await readFile(resolve(cwd, "package.json"), "utf-8");
1255
1319
  const pkg = JSON.parse(pkgContent);
1256
1320
  // Detect if this is a component library by checking for:
1257
1321
  // - exports field with ./ entries
1258
1322
  // - main/module pointing to index file
1259
1323
  // - "react" or "vue" in peerDependencies (library pattern)
1260
1324
  const hasPeerReact = pkg.peerDependencies?.react || pkg.peerDependencies?.vue;
1261
- const hasExports = pkg.exports && typeof pkg.exports === 'object';
1262
- const mainEntry = pkg.main || pkg.module || '';
1325
+ const hasExports = pkg.exports && typeof pkg.exports === "object";
1326
+ const mainEntry = pkg.main || pkg.module || "";
1263
1327
  const isLibraryPattern = hasPeerReact && (hasExports || mainEntry);
1264
1328
  if (!isLibraryPattern)
1265
1329
  return;
1266
1330
  // Scan the root barrel file(s) for exports — these are the public API
1267
- const rootBarrels = await glob('src/index.{ts,tsx,js,jsx}', { cwd, nodir: true });
1331
+ const rootBarrels = await glob("src/index.{ts,tsx,js,jsx}", {
1332
+ cwd,
1333
+ nodir: true,
1334
+ });
1268
1335
  for (const barrel of rootBarrels) {
1269
1336
  try {
1270
- const content = await readFile(resolve(cwd, barrel), 'utf-8');
1337
+ const content = await readFile(resolve(cwd, barrel), "utf-8");
1271
1338
  // Named exports: export { Button, Card } from '...'
1272
1339
  const namedPattern = /export\s*\{\s*([^}]+)\s*\}\s*from/g;
1273
1340
  let match;
1274
1341
  while ((match = namedPattern.exec(content)) !== null) {
1275
- const names = match[1].split(',').map(n => {
1342
+ const names = match[1]
1343
+ .split(",")
1344
+ .map((n) => {
1276
1345
  const parts = n.trim().split(/\s+as\s+/);
1277
- return (parts[1] || parts[0] || '').trim();
1278
- }).filter(n => n && /^[A-Z]/.test(n));
1346
+ return (parts[1] || parts[0] || "").trim();
1347
+ })
1348
+ .filter((n) => n && /^[A-Z]/.test(n));
1279
1349
  for (const name of names) {
1280
1350
  componentUsageMap.set(name, (componentUsageMap.get(name) || 0) + 1);
1281
1351
  }
@@ -1289,8 +1359,8 @@ export class DriftAnalysisService {
1289
1359
  const wildcardPattern = /export\s*\*\s*from\s*['"]\.\/([^'"]+)['"]/g;
1290
1360
  while ((match = wildcardPattern.exec(content)) !== null) {
1291
1361
  const moduleName = match[1];
1292
- const segments = moduleName.split('/');
1293
- const last = segments[segments.length - 1] || '';
1362
+ const segments = moduleName.split("/");
1363
+ const last = segments[segments.length - 1] || "";
1294
1364
  if (/^[A-Z]/.test(last)) {
1295
1365
  componentUsageMap.set(last, (componentUsageMap.get(last) || 0) + 1);
1296
1366
  }
@@ -1321,9 +1391,15 @@ export class DriftAnalysisService {
1321
1391
  if (knownComponents.length === 0)
1322
1392
  return;
1323
1393
  const cwd = this.projectRoot;
1324
- const sourceFiles = await glob('**/*.{tsx,jsx,ts,js}', {
1394
+ const sourceFiles = await glob("**/*.{tsx,jsx,ts,js}", {
1325
1395
  cwd,
1326
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/*.d.ts'],
1396
+ ignore: [
1397
+ "**/node_modules/**",
1398
+ "**/dist/**",
1399
+ "**/build/**",
1400
+ "**/.next/**",
1401
+ "**/*.d.ts",
1402
+ ],
1327
1403
  nodir: true,
1328
1404
  maxDepth: 8,
1329
1405
  });
@@ -1331,7 +1407,7 @@ export class DriftAnalysisService {
1331
1407
  const knownSet = new Set(knownComponents);
1332
1408
  for (const file of sourceFiles.slice(0, 500)) {
1333
1409
  try {
1334
- const content = await readFile(resolve(cwd, file), 'utf-8');
1410
+ const content = await readFile(resolve(cwd, file), "utf-8");
1335
1411
  // Match component names used as values in these contexts:
1336
1412
  // 1. JSX prop value: ={ComponentName} or ={ComponentName}
1337
1413
  // 2. Object property: : ComponentName, or : ComponentName}
@@ -1361,7 +1437,12 @@ export class DriftAnalysisService {
1361
1437
  const cwd = this.projectRoot;
1362
1438
  // Find all source files
1363
1439
  const patterns = ["**/*.tsx", "**/*.jsx", "**/*.vue", "**/*.svelte"];
1364
- const ignore = ["**/node_modules/**", "**/dist/**", "**/.next/**", "**/build/**"];
1440
+ const ignore = [
1441
+ "**/node_modules/**",
1442
+ "**/dist/**",
1443
+ "**/.next/**",
1444
+ "**/build/**",
1445
+ ];
1365
1446
  const files = await glob(patterns, { cwd, ignore, absolute: true });
1366
1447
  for (const file of files) {
1367
1448
  try {