@fragments-sdk/cli 0.11.1 → 0.13.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 (89) hide show
  1. package/dist/ai-client-I6MDWNYA.js +21 -0
  2. package/dist/bin.js +419 -410
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-HRFUSSZI.js → chunk-3SOAPJDX.js} +2 -2
  5. package/dist/{chunk-D5PYOXEI.js → chunk-4K7EAQ5L.js} +148 -13
  6. package/dist/{chunk-D5PYOXEI.js.map → chunk-4K7EAQ5L.js.map} +1 -1
  7. package/dist/chunk-DXX6HADE.js +443 -0
  8. package/dist/chunk-DXX6HADE.js.map +1 -0
  9. package/dist/chunk-EYXVAMEX.js +626 -0
  10. package/dist/chunk-EYXVAMEX.js.map +1 -0
  11. package/dist/{chunk-ZM4ZQZWZ.js → chunk-FO6EBJWP.js} +39 -37
  12. package/dist/chunk-FO6EBJWP.js.map +1 -0
  13. package/dist/{chunk-OQO55NKV.js → chunk-QM7SVOGF.js} +120 -12
  14. package/dist/chunk-QM7SVOGF.js.map +1 -0
  15. package/dist/{chunk-5G3VZH43.js → chunk-RF3C6LGA.js} +281 -351
  16. package/dist/chunk-RF3C6LGA.js.map +1 -0
  17. package/dist/{chunk-WXSR2II7.js → chunk-SM674YAS.js} +58 -6
  18. package/dist/chunk-SM674YAS.js.map +1 -0
  19. package/dist/chunk-SXTKFDCR.js +104 -0
  20. package/dist/chunk-SXTKFDCR.js.map +1 -0
  21. package/dist/{chunk-PW7QTQA6.js → chunk-UV5JQV3R.js} +2 -2
  22. package/dist/core/index.js +13 -1
  23. package/dist/{discovery-NEOY4MPN.js → discovery-VSGC76JN.js} +3 -3
  24. package/dist/{generate-FBHSXR3D.js → generate-QZXOXYFW.js} +4 -4
  25. package/dist/index.js +7 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/init-XK6PRUE5.js +636 -0
  28. package/dist/init-XK6PRUE5.js.map +1 -0
  29. package/dist/mcp-bin.js +2 -2
  30. package/dist/{scan-CJF2DOQW.js → scan-CHQHXWVD.js} +6 -6
  31. package/dist/scan-generate-U3RFVDTX.js +1115 -0
  32. package/dist/scan-generate-U3RFVDTX.js.map +1 -0
  33. package/dist/{service-TQYWY65E.js → service-MMEKG4MZ.js} +3 -3
  34. package/dist/{snapshot-SV2JOFZH.js → snapshot-53TUR3HW.js} +2 -2
  35. package/dist/{static-viewer-NUBFPKWH.js → static-viewer-KKCR4KXR.js} +3 -3
  36. package/dist/static-viewer-KKCR4KXR.js.map +1 -0
  37. package/dist/{test-Z5LVO724.js → test-5UCKXYSC.js} +4 -4
  38. package/dist/{tokens-CE46OTMD.js → tokens-L46MK5AW.js} +5 -5
  39. package/dist/{viewer-DLLJIMCK.js → viewer-M2EQQSGE.js} +14 -14
  40. package/dist/viewer-M2EQQSGE.js.map +1 -0
  41. package/package.json +11 -9
  42. package/src/ai-client.ts +156 -0
  43. package/src/bin.ts +99 -2
  44. package/src/build.ts +95 -33
  45. package/src/commands/__tests__/drift-sync.test.ts +252 -0
  46. package/src/commands/__tests__/scan-generate.test.ts +497 -45
  47. package/src/commands/enhance.ts +11 -35
  48. package/src/commands/govern.ts +122 -0
  49. package/src/commands/init.ts +288 -260
  50. package/src/commands/scan-generate.ts +740 -139
  51. package/src/commands/scan.ts +37 -32
  52. package/src/commands/setup.ts +143 -52
  53. package/src/commands/sync.ts +357 -0
  54. package/src/commands/validate.ts +43 -1
  55. package/src/core/component-extractor.test.ts +282 -0
  56. package/src/core/component-extractor.ts +1030 -0
  57. package/src/core/discovery.ts +93 -7
  58. package/src/service/enhance/props-extractor.ts +235 -13
  59. package/src/validators.ts +236 -0
  60. package/src/viewer/vite-plugin.ts +1 -1
  61. package/dist/chunk-5G3VZH43.js.map +0 -1
  62. package/dist/chunk-OQO55NKV.js.map +0 -1
  63. package/dist/chunk-WXSR2II7.js.map +0 -1
  64. package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
  65. package/dist/init-UFGK5TCN.js +0 -867
  66. package/dist/init-UFGK5TCN.js.map +0 -1
  67. package/dist/scan-generate-SJAN5MVI.js +0 -691
  68. package/dist/scan-generate-SJAN5MVI.js.map +0 -1
  69. package/dist/viewer-DLLJIMCK.js.map +0 -1
  70. package/src/ai.ts +0 -266
  71. package/src/commands/init-framework.ts +0 -414
  72. package/src/mcp/bin.ts +0 -36
  73. package/src/migrate/bin.ts +0 -114
  74. package/src/theme/index.ts +0 -77
  75. package/src/viewer/bin.ts +0 -86
  76. package/src/viewer/cli/health.ts +0 -256
  77. package/src/viewer/cli/index.ts +0 -33
  78. package/src/viewer/cli/scan.ts +0 -124
  79. package/src/viewer/cli/utils.ts +0 -174
  80. /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
  81. /package/dist/{chunk-HRFUSSZI.js.map → chunk-3SOAPJDX.js.map} +0 -0
  82. /package/dist/{chunk-PW7QTQA6.js.map → chunk-UV5JQV3R.js.map} +0 -0
  83. /package/dist/{scan-CJF2DOQW.js.map → discovery-VSGC76JN.js.map} +0 -0
  84. /package/dist/{generate-FBHSXR3D.js.map → generate-QZXOXYFW.js.map} +0 -0
  85. /package/dist/{service-TQYWY65E.js.map → scan-CHQHXWVD.js.map} +0 -0
  86. /package/dist/{static-viewer-NUBFPKWH.js.map → service-MMEKG4MZ.js.map} +0 -0
  87. /package/dist/{snapshot-SV2JOFZH.js.map → snapshot-53TUR3HW.js.map} +0 -0
  88. /package/dist/{test-Z5LVO724.js.map → test-5UCKXYSC.js.map} +0 -0
  89. /package/dist/{tokens-CE46OTMD.js.map → tokens-L46MK5AW.js.map} +0 -0
@@ -1,114 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * CLI entry point for migration tool.
4
- *
5
- * Can be run standalone or integrated with the main fragments CLI.
6
- */
7
-
8
- import { Command } from "commander";
9
- import pc from "picocolors";
10
- import { readFileSync } from "node:fs";
11
- import { fileURLToPath } from "node:url";
12
- import { dirname, join } from "node:path";
13
- import { BRAND } from "../core/index.js";
14
- import { migrate } from "./migrate.js";
15
-
16
- const __dirname = dirname(fileURLToPath(import.meta.url));
17
- const pkg = JSON.parse(readFileSync(join(__dirname, "../../package.json"), "utf-8")) as { version: string };
18
-
19
- const program = new Command();
20
-
21
- program
22
- .name("fragments-migrate")
23
- .description(`${BRAND.name} Storybook Migration Tool`)
24
- .version(pkg.version);
25
-
26
- program
27
- .command("migrate")
28
- .description("Migrate Storybook stories to Fragments")
29
- .argument("<source>", "Source directory or file containing .stories.tsx files")
30
- .option("-o, --output <dir>", "Output directory (default: same as source)")
31
- .option("--dry-run", "Show what would be generated without writing files")
32
- .option("--overwrite", "Overwrite existing fragment files")
33
- .option("--report", "Generate HTML migration report")
34
- .option("--report-path <path>", "Path for the migration report")
35
- .option("-v, --verbose", "Verbose output")
36
- .action(async (source, options) => {
37
- try {
38
- console.log(pc.cyan(`\n${BRAND.name} Migration Tool\n`));
39
- console.log(pc.dim("Discovering Storybook files...\n"));
40
-
41
- const report = await migrate({
42
- from: source,
43
- to: options.output,
44
- dryRun: options.dryRun,
45
- overwrite: options.overwrite,
46
- report: options.report,
47
- reportPath: options.reportPath,
48
- verbose: options.verbose,
49
- });
50
-
51
- // Print results
52
- if (report.totalStoryFiles === 0) {
53
- console.log(pc.yellow("No story files found.\n"));
54
- console.log(pc.dim("Looking for: *.stories.tsx, *.stories.ts, *.stories.jsx, *.stories.js"));
55
- return;
56
- }
57
-
58
- console.log(pc.dim(`Found ${report.totalStoryFiles} story file(s)\n`));
59
-
60
- // Print each conversion
61
- for (const result of report.results) {
62
- if (result.success) {
63
- console.log(`${pc.green("✓")} ${pc.bold(result.componentName)} → ${result.outputFile}`);
64
- console.log(pc.dim(` ${result.variantCount} variant(s), ${result.todos.length} TODO(s)`));
65
- } else {
66
- console.log(`${pc.red("✗")} ${result.sourceFile}`);
67
- for (const warning of result.warnings) {
68
- console.log(pc.dim(` ${warning}`));
69
- }
70
- }
71
- }
72
-
73
- console.log();
74
-
75
- // Summary
76
- if (report.successfulConversions > 0) {
77
- console.log(pc.green(`✓ Converted ${report.successfulConversions} component(s)`));
78
- }
79
- if (report.failedConversions > 0) {
80
- console.log(pc.red(`✗ Failed ${report.failedConversions} conversion(s)`));
81
- }
82
- console.log(pc.dim(` ${report.totalVariants} variant(s) total`));
83
- console.log(pc.dim(` ${report.totalTodos} TODO(s) need attention`));
84
-
85
- if (options.dryRun) {
86
- console.log(pc.yellow("\n[Dry run - no files were written]"));
87
- }
88
-
89
- if (options.report) {
90
- const reportPath = options.reportPath ?? `${source}/migration-report.html`;
91
- console.log(pc.cyan(`\n📊 Report generated: ${reportPath}`));
92
- }
93
-
94
- // Next steps
95
- console.log(pc.dim("\n───────────────────────────────────────"));
96
- console.log(pc.bold("\nNext steps:"));
97
- console.log(` 1. Review generated ${BRAND.fileExtension} files`);
98
- console.log(` 2. Fill in TODO comments (usage.when, usage.whenNot, etc.)`);
99
- console.log(` 3. Run ${pc.cyan(`${BRAND.cliCommand} build`)} to compile`);
100
- console.log(` 4. Run ${pc.cyan(`${BRAND.cliCommand} dev`)} to view your design system`);
101
- console.log();
102
- } catch (error) {
103
- console.error(pc.red("Error:"), error instanceof Error ? error.message : error);
104
- process.exit(1);
105
- }
106
- });
107
-
108
- // Also support direct execution without subcommand
109
- if (process.argv.length > 2 && !process.argv[2].startsWith("-") && process.argv[2] !== "migrate") {
110
- // Insert "migrate" as the command
111
- process.argv.splice(2, 0, "migrate");
112
- }
113
-
114
- program.parse();
@@ -1,77 +0,0 @@
1
- /**
2
- * Theme Customization Module
3
- *
4
- * Provides theme configuration, validation, serialization, and generation
5
- */
6
-
7
- // Types
8
- export type {
9
- ThemeConfig,
10
- ThemeColors,
11
- ThemeSurfaces,
12
- ThemeText,
13
- ThemeBorders,
14
- ThemeTypography,
15
- ThemeRadius,
16
- ThemeShadows,
17
- ThemeDarkMode,
18
- TokenOutputFormat,
19
- TokenGeneratorOptions,
20
- TokenGeneratorResult,
21
- PresetName,
22
- ContrastPair,
23
- ContrastWarning,
24
- ContrastValidationResult,
25
- } from "./types.js";
26
-
27
- // Schema validation
28
- export {
29
- themeConfigSchema,
30
- themeColorsSchema,
31
- themeSurfacesSchema,
32
- themeTextSchema,
33
- themeBordersSchema,
34
- themeTypographySchema,
35
- themeRadiusSchema,
36
- themeShadowsSchema,
37
- themeDarkModeSchema,
38
- validateThemeConfig,
39
- } from "./schema.js";
40
- export type { ThemeValidationResult } from "./schema.js";
41
-
42
- // Serialization
43
- export {
44
- encodeThemeToUrl,
45
- decodeThemeFromUrl,
46
- compressTheme,
47
- decompressTheme,
48
- } from "./serializer.js";
49
-
50
- // Generation
51
- export {
52
- generateScssTokens,
53
- generateCssTokens,
54
- generateTokenFiles,
55
- validateContrast,
56
- } from "./generator.js";
57
-
58
- // Contrast utilities
59
- export {
60
- parseColor,
61
- relativeLuminance,
62
- contrastRatio,
63
- meetsAA,
64
- meetsAAA,
65
- rgbToHex,
66
- suggestFix,
67
- } from "./contrast.js";
68
- export type { RGB } from "./contrast.js";
69
-
70
- // Presets
71
- export {
72
- DEFAULT_PRESET,
73
- PRESETS,
74
- getPreset,
75
- listPresets,
76
- isValidPresetName,
77
- } from "./presets.js";
package/src/viewer/bin.ts DELETED
@@ -1,86 +0,0 @@
1
- #!/usr/bin/env node
2
- import pc from 'picocolors';
3
- import { BRAND } from '../core/index.js';
4
- import { createDevServer } from './server.js';
5
- import { runCommand } from './cli/index.js';
6
-
7
- async function startDevServer(): Promise<void> {
8
- const args = process.argv.slice(2);
9
- const portArg = args.find((arg) => arg.startsWith('--port='));
10
- const port = portArg ? parseInt(portArg.split('=')[1], 10) : 6006;
11
- const noOpen = args.includes('--no-open');
12
-
13
- console.log(pc.cyan(`\n${BRAND.name} Dev Server\n`));
14
- console.log(pc.dim('Starting development server...\n'));
15
-
16
- try {
17
- const server = await createDevServer({
18
- port,
19
- open: !noOpen,
20
- });
21
-
22
- const address = server.httpServer?.address();
23
- const actualPort = typeof address === 'object' && address ? address.port : port;
24
-
25
- console.log(pc.green(`✓ Server running at http://localhost:${actualPort}\n`));
26
- console.log(pc.dim('Press Ctrl+C to stop\n'));
27
- } catch (error) {
28
- console.error(pc.red('Failed to start server:'), error);
29
- process.exit(1);
30
- }
31
- }
32
-
33
- function showHelp(): void {
34
- console.log(pc.cyan(`\n${BRAND.name} CLI\n`));
35
- console.log(`Usage: ${BRAND.cliCommand} <command> [options]\n`);
36
- console.log('Commands:');
37
- console.log(' dev Start the development server (default)');
38
- console.log(' scan <dir> Scan codebase for component usage');
39
- console.log(' health Generate design system health report');
40
- console.log('');
41
- console.log('Dev Server Options:');
42
- console.log(' --port=<port> Port to run on (default: 6006)');
43
- console.log(' --no-open Don\'t open browser automatically');
44
- console.log('');
45
- console.log('Scan Options:');
46
- console.log(' --format=<fmt> Output format: table, json (default: table)');
47
- console.log(' --component=<n> Filter to specific component');
48
- console.log(' --verbose Show detailed usage info');
49
- console.log('');
50
- console.log('Health Options:');
51
- console.log(' --format=<fmt> Output format: table, json, markdown (default: table)');
52
- console.log(' --scan=<dir> Include codebase usage scan');
53
- console.log(' --ci CI mode: exit 1 if score below threshold');
54
- console.log(' --threshold=<n> Minimum score for CI mode (default: 80)');
55
- console.log('');
56
- }
57
-
58
- async function main(): Promise<void> {
59
- const args = process.argv.slice(2);
60
- const command = args[0];
61
-
62
- // Show help
63
- if (command === '--help' || command === '-h' || command === 'help') {
64
- showHelp();
65
- return;
66
- }
67
-
68
- // Route to subcommands
69
- if (command === 'scan' || command === 'health') {
70
- await runCommand(command, args.slice(1));
71
- return;
72
- }
73
-
74
- // Default: start dev server (handles 'dev' command or no command)
75
- if (!command || command === 'dev' || command.startsWith('--')) {
76
- await startDevServer();
77
- return;
78
- }
79
-
80
- // Unknown command
81
- console.error(pc.red(`Unknown command: ${command}`));
82
- console.log(pc.dim(`Run "${BRAND.cliCommand} --help" for usage information.\n`));
83
- process.exit(1);
84
- }
85
-
86
- main();
@@ -1,256 +0,0 @@
1
- /**
2
- * Health Command
3
- * Generates a comprehensive design system health report
4
- */
5
-
6
- import pc from 'picocolors';
7
- import fs from 'fs';
8
- import path from 'path';
9
- import { BRAND } from '../../core/index.js';
10
- import { loadConfig } from '../../core/node.js';
11
- import { scanForUsages } from '../intelligence/usageScanner.js';
12
- import { generateHealthReport, formatHealthReportAsMarkdown, type HealthReport } from '../intelligence/healthReport.js';
13
- import {
14
- parseArgs,
15
- createSpinner,
16
- formatScore,
17
- printHeader,
18
- printKV,
19
- printWarning,
20
- printSuccess,
21
- formatList,
22
- } from './utils.js';
23
-
24
- type OutputFormat = 'table' | 'json' | 'markdown';
25
-
26
- interface HealthCommandOptions {
27
- format: OutputFormat;
28
- scanDir?: string;
29
- ci: boolean;
30
- threshold: number;
31
- }
32
-
33
- function parseOptions(args: string[]): HealthCommandOptions {
34
- const { options } = parseArgs(args);
35
-
36
- return {
37
- format: (options.format as OutputFormat) || 'table',
38
- scanDir: options.scan as string | undefined,
39
- ci: !!options.ci,
40
- threshold: options.threshold ? parseInt(options.threshold as string, 10) : 80,
41
- };
42
- }
43
-
44
- /**
45
- * Load fragments from the project
46
- */
47
- async function loadFragments(): Promise<Array<{ path: string; fragment: any }>> {
48
- const configResult = await loadConfig();
49
-
50
- if (!configResult) {
51
- throw new Error('No fragments.config.ts found. Run this command from a Fragments project.');
52
- }
53
-
54
- const { config } = configResult;
55
-
56
- // Try to load compiled fragments
57
- const fragmentsPath = path.resolve(process.cwd(), config.outFile || `${BRAND.dataDir}/${BRAND.outFile}`);
58
-
59
- if (fs.existsSync(fragmentsPath)) {
60
- const data = JSON.parse(fs.readFileSync(fragmentsPath, 'utf-8'));
61
- return Object.entries(data.fragments || {}).map(([name, fragment]: [string, any]) => ({
62
- path: fragment.filePath || name,
63
- fragment: {
64
- meta: fragment.meta || { name, description: '', category: '' },
65
- usage: fragment.usage || { when: [], whenNot: [] },
66
- props: fragment.props || {},
67
- variants: fragment.variants || [],
68
- relations: fragment.relations,
69
- contract: fragment.contract,
70
- _generated: fragment._generated,
71
- component: () => null, // Placeholder
72
- },
73
- }));
74
- }
75
-
76
- // Fallback: return empty if no compiled fragments
77
- console.log(pc.yellow(`No compiled fragments found. Run \`${BRAND.cliCommand} build\` first for complete analysis.`));
78
- return [];
79
- }
80
-
81
- function formatGrade(grade: string): string {
82
- switch (grade) {
83
- case 'A':
84
- return pc.green(pc.bold(grade));
85
- case 'B':
86
- return pc.cyan(grade);
87
- case 'C':
88
- return pc.yellow(grade);
89
- case 'D':
90
- return pc.magenta(grade);
91
- case 'F':
92
- return pc.red(pc.bold(grade));
93
- default:
94
- return grade;
95
- }
96
- }
97
-
98
- function formatTableOutput(report: HealthReport): void {
99
- console.log(pc.cyan(`\n${BRAND.name} Health Report\n`));
100
- console.log(pc.dim(`Generated: ${new Date(report.generatedAt).toLocaleString()}\n`));
101
-
102
- // Overall score
103
- const scoreColor = report.overallScore >= 80 ? pc.green : report.overallScore >= 60 ? pc.yellow : pc.red;
104
- console.log(` ${pc.bold('Overall Score:')} ${scoreColor(pc.bold(`${report.overallScore}%`))}\n`);
105
-
106
- // Grade summary
107
- console.log(pc.bold(' Category Grades:\n'));
108
- console.log(` Usage: ${formatGrade(report.grades.usage)} (${report.usage.adoptionRate}% adoption)`);
109
- console.log(` Style Drift: ${formatGrade(report.grades.drift)} (${report.styleDrift.averageCompliance}% compliance)`);
110
- console.log(` Documentation: ${formatGrade(report.grades.documentation)} (${report.documentation.documentationRate}% documented)`);
111
- console.log(` Token Usage: ${formatGrade(report.grades.tokens)} (${report.tokens.tokenizationRate}% tokenized)`);
112
- console.log('');
113
-
114
- // Usage section
115
- printHeader('Usage Statistics');
116
- printKV('Total Components', report.usage.totalComponents.toString());
117
- printKV('Used Components', report.usage.usedComponents.toString());
118
- printKV('Adoption Rate', formatScore(report.usage.adoptionRate));
119
-
120
- if (report.usage.mostUsed.length > 0) {
121
- console.log(pc.dim('\n Most Used:'));
122
- for (const item of report.usage.mostUsed.slice(0, 5)) {
123
- console.log(` ${pc.cyan(item.component)}: ${item.count} uses`);
124
- }
125
- }
126
-
127
- if (report.usage.unusedComponents.length > 0 && report.usage.unusedComponents.length <= 5) {
128
- console.log(pc.dim('\n Unused:'));
129
- for (const name of report.usage.unusedComponents) {
130
- printWarning(name);
131
- }
132
- } else if (report.usage.unusedComponents.length > 5) {
133
- printWarning(`${report.usage.unusedComponents.length} unused components`);
134
- }
135
-
136
- // Style Drift section
137
- printHeader('Style Drift Analysis');
138
- printKV('Components with Drift', report.styleDrift.componentsWithDrift.toString());
139
- printKV('Total Drifts', report.styleDrift.totalDrifts.toString());
140
- printKV('Average Compliance', formatScore(report.styleDrift.averageCompliance));
141
-
142
- if (report.styleDrift.bySeverity.high > 0) {
143
- console.log('');
144
- printWarning(`${report.styleDrift.bySeverity.high} high-severity drift(s) - affects brand identity`);
145
- }
146
-
147
- if (report.styleDrift.worstOffenders.length > 0) {
148
- console.log(pc.dim('\n Needs Attention:'));
149
- for (const item of report.styleDrift.worstOffenders.slice(0, 3)) {
150
- console.log(` ${pc.red('!')} ${item.component} (${item.variant}): ${item.driftCount} drifts`);
151
- }
152
- }
153
-
154
- // Documentation section
155
- printHeader('Documentation Coverage');
156
- printKV('Documented', `${report.documentation.documented} components`);
157
- printKV('Coverage', formatScore(report.documentation.documentationRate));
158
-
159
- if (report.documentation.missingDescriptions.length > 0) {
160
- printWarning(`${report.documentation.missingDescriptions.length} missing descriptions`);
161
- }
162
-
163
- if (report.documentation.missingUsage.length > 0) {
164
- printWarning(`${report.documentation.missingUsage.length} missing usage guidelines`);
165
- }
166
-
167
- // Token section
168
- printHeader('Design Token Usage');
169
- printKV('Token Compliance', formatScore(report.tokens.complianceScore));
170
- printKV('Tokenized Values', report.tokens.tokenizedValues.toString());
171
- printKV('Hardcoded Values', report.tokens.hardcodedValues.toString(), report.tokens.hardcodedValues > 0 ? pc.yellow : undefined);
172
-
173
- // Recommendations
174
- if (report.recommendations.length > 0) {
175
- printHeader('Recommendations');
176
- console.log('');
177
- for (const rec of report.recommendations) {
178
- console.log(` ${pc.yellow('→')} ${rec}`);
179
- }
180
- }
181
-
182
- console.log('');
183
- }
184
-
185
- function formatJsonOutput(report: HealthReport): void {
186
- console.log(JSON.stringify(report, null, 2));
187
- }
188
-
189
- function formatMarkdownOutput(report: HealthReport): void {
190
- console.log(formatHealthReportAsMarkdown(report));
191
- }
192
-
193
- /**
194
- * Run the health command
195
- */
196
- export async function healthCommand(args: string[]): Promise<void> {
197
- const options = parseOptions(args);
198
-
199
- const spinner = createSpinner('Analyzing design system health...');
200
- spinner.start();
201
-
202
- try {
203
- // Load fragments
204
- spinner.update('Loading fragments...');
205
- const fragments = await loadFragments();
206
-
207
- if (fragments.length === 0) {
208
- spinner.stop(false, 'No fragments found');
209
- console.log(pc.yellow(`\nNo components to analyze. Create fragment files or run \`${BRAND.cliCommand} build\`.\n`));
210
- return;
211
- }
212
-
213
- // Run usage scan if directory provided
214
- let usageScan;
215
- if (options.scanDir) {
216
- spinner.update(`Scanning ${options.scanDir} for component usage...`);
217
- usageScan = await scanForUsages({ directory: options.scanDir });
218
- }
219
-
220
- // Generate report
221
- spinner.update('Generating health report...');
222
- const report = generateHealthReport({
223
- fragments,
224
- usageScan,
225
- // Note: Drift reports would require running the viewer and extracting styles
226
- // For CLI, we focus on what can be statically analyzed
227
- });
228
-
229
- spinner.stop(true, 'Health analysis complete');
230
-
231
- // Output
232
- switch (options.format) {
233
- case 'json':
234
- formatJsonOutput(report);
235
- break;
236
- case 'markdown':
237
- formatMarkdownOutput(report);
238
- break;
239
- default:
240
- formatTableOutput(report);
241
- }
242
-
243
- // CI mode: exit with error if below threshold
244
- if (options.ci) {
245
- if (report.overallScore < options.threshold) {
246
- console.log(pc.red(`\nCI Check Failed: Score ${report.overallScore}% is below threshold ${options.threshold}%\n`));
247
- process.exit(1);
248
- } else {
249
- console.log(pc.green(`\nCI Check Passed: Score ${report.overallScore}% meets threshold ${options.threshold}%\n`));
250
- }
251
- }
252
- } catch (error) {
253
- spinner.stop(false, 'Health analysis failed');
254
- throw error;
255
- }
256
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * CLI Command Router
3
- * Routes subcommands to their respective handlers
4
- */
5
-
6
- import pc from 'picocolors';
7
- import { scanCommand } from './scan.js';
8
- import { healthCommand } from './health.js';
9
-
10
- export type CommandName = 'scan' | 'health';
11
-
12
- /**
13
- * Run a CLI command with the given arguments
14
- */
15
- export async function runCommand(command: CommandName, args: string[]): Promise<void> {
16
- try {
17
- switch (command) {
18
- case 'scan':
19
- await scanCommand(args);
20
- break;
21
- case 'health':
22
- await healthCommand(args);
23
- break;
24
- default:
25
- console.error(pc.red(`Unknown command: ${command}`));
26
- process.exit(1);
27
- }
28
- } catch (error) {
29
- const message = error instanceof Error ? error.message : String(error);
30
- console.error(pc.red(`\nError: ${message}\n`));
31
- process.exit(1);
32
- }
33
- }
@@ -1,124 +0,0 @@
1
- /**
2
- * Scan Command
3
- * Scans a codebase for component usage
4
- */
5
-
6
- import pc from 'picocolors';
7
- import path from 'path';
8
- import { scanForUsages, type FullScanResult, type UsageScanResult } from '../intelligence/usageScanner.js';
9
- import { parseArgs, createSpinner, formatTable, formatDuration, printHeader, printKV } from './utils.js';
10
-
11
- type OutputFormat = 'table' | 'json';
12
-
13
- interface ScanCommandOptions {
14
- format: OutputFormat;
15
- component?: string;
16
- verbose: boolean;
17
- }
18
-
19
- function parseOptions(args: string[]): { directory: string; options: ScanCommandOptions } {
20
- const { positional, options } = parseArgs(args);
21
-
22
- return {
23
- directory: positional[0] || '.',
24
- options: {
25
- format: (options.format as OutputFormat) || 'table',
26
- component: options.component as string | undefined,
27
- verbose: !!options.verbose || !!options.v,
28
- },
29
- };
30
- }
31
-
32
- function formatTableOutput(result: FullScanResult, options: ScanCommandOptions): void {
33
- const { results, summary } = result;
34
-
35
- printHeader('Component Usage Scan');
36
- printKV('Directory scanned', path.resolve(result.results[0]?.usages[0]?.file || '.').split(path.sep).slice(0, -1).join(path.sep) || '.');
37
- printKV('Files scanned', summary.totalFiles.toString());
38
- printKV('Files with usage', summary.filesWithUsage.toString());
39
- printKV('Scan time', formatDuration(summary.scanTimeMs));
40
- console.log('');
41
-
42
- if (results.length === 0) {
43
- console.log(pc.yellow(' No component usages found.\n'));
44
- return;
45
- }
46
-
47
- // Summary table
48
- const headers = ['Component', 'Files', 'Total Uses'];
49
- const rows = results.map((r) => [
50
- r.component,
51
- String(r.usages.length),
52
- String(r.totalUsages),
53
- ]);
54
-
55
- console.log(formatTable(headers, rows));
56
- console.log('');
57
-
58
- // Verbose: show file details
59
- if (options.verbose) {
60
- console.log(pc.bold('\nDetailed Usage:\n'));
61
-
62
- for (const r of results) {
63
- console.log(pc.cyan(` ${r.component}`));
64
- for (const usage of r.usages) {
65
- const relativePath = path.relative(process.cwd(), usage.file);
66
- const importIcon = usage.importType === 'named' ? '{}' : usage.importType === 'default' ? '=>' : '*';
67
- console.log(pc.dim(` ${importIcon} ${relativePath}:${usage.line} (${usage.usageCount} uses)`));
68
- }
69
- console.log('');
70
- }
71
- }
72
-
73
- // Summary footer
74
- console.log(pc.dim('─'.repeat(50)));
75
- console.log(` ${pc.bold(String(summary.totalComponents))} components used ${pc.bold(String(summary.totalUsages))} times across ${pc.bold(String(summary.filesWithUsage))} files`);
76
- console.log('');
77
- }
78
-
79
- function formatJsonOutput(result: FullScanResult): void {
80
- const output = {
81
- summary: result.summary,
82
- components: result.results.map((r) => ({
83
- name: r.component,
84
- totalUsages: r.totalUsages,
85
- fileCount: r.usages.length,
86
- usages: r.usages.map((u) => ({
87
- file: path.relative(process.cwd(), u.file),
88
- line: u.line,
89
- importType: u.importType,
90
- usageCount: u.usageCount,
91
- })),
92
- })),
93
- };
94
-
95
- console.log(JSON.stringify(output, null, 2));
96
- }
97
-
98
- /**
99
- * Run the scan command
100
- */
101
- export async function scanCommand(args: string[]): Promise<void> {
102
- const { directory, options } = parseOptions(args);
103
-
104
- const spinner = createSpinner(`Scanning ${directory} for component usage...`);
105
- spinner.start();
106
-
107
- try {
108
- const result = await scanForUsages({
109
- directory,
110
- components: options.component ? [options.component] : undefined,
111
- });
112
-
113
- spinner.stop(true, `Scanned ${result.summary.totalFiles} files in ${formatDuration(result.summary.scanTimeMs)}`);
114
-
115
- if (options.format === 'json') {
116
- formatJsonOutput(result);
117
- } else {
118
- formatTableOutput(result, options);
119
- }
120
- } catch (error) {
121
- spinner.stop(false, 'Scan failed');
122
- throw error;
123
- }
124
- }