@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.
- package/dist/ai-client-I6MDWNYA.js +21 -0
- package/dist/bin.js +419 -410
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-HRFUSSZI.js → chunk-3SOAPJDX.js} +2 -2
- package/dist/{chunk-D5PYOXEI.js → chunk-4K7EAQ5L.js} +148 -13
- package/dist/{chunk-D5PYOXEI.js.map → chunk-4K7EAQ5L.js.map} +1 -1
- package/dist/chunk-DXX6HADE.js +443 -0
- package/dist/chunk-DXX6HADE.js.map +1 -0
- package/dist/chunk-EYXVAMEX.js +626 -0
- package/dist/chunk-EYXVAMEX.js.map +1 -0
- package/dist/{chunk-ZM4ZQZWZ.js → chunk-FO6EBJWP.js} +39 -37
- package/dist/chunk-FO6EBJWP.js.map +1 -0
- package/dist/{chunk-OQO55NKV.js → chunk-QM7SVOGF.js} +120 -12
- package/dist/chunk-QM7SVOGF.js.map +1 -0
- package/dist/{chunk-5G3VZH43.js → chunk-RF3C6LGA.js} +281 -351
- package/dist/chunk-RF3C6LGA.js.map +1 -0
- package/dist/{chunk-WXSR2II7.js → chunk-SM674YAS.js} +58 -6
- package/dist/chunk-SM674YAS.js.map +1 -0
- package/dist/chunk-SXTKFDCR.js +104 -0
- package/dist/chunk-SXTKFDCR.js.map +1 -0
- package/dist/{chunk-PW7QTQA6.js → chunk-UV5JQV3R.js} +2 -2
- package/dist/core/index.js +13 -1
- package/dist/{discovery-NEOY4MPN.js → discovery-VSGC76JN.js} +3 -3
- package/dist/{generate-FBHSXR3D.js → generate-QZXOXYFW.js} +4 -4
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/init-XK6PRUE5.js +636 -0
- package/dist/init-XK6PRUE5.js.map +1 -0
- package/dist/mcp-bin.js +2 -2
- package/dist/{scan-CJF2DOQW.js → scan-CHQHXWVD.js} +6 -6
- package/dist/scan-generate-U3RFVDTX.js +1115 -0
- package/dist/scan-generate-U3RFVDTX.js.map +1 -0
- package/dist/{service-TQYWY65E.js → service-MMEKG4MZ.js} +3 -3
- package/dist/{snapshot-SV2JOFZH.js → snapshot-53TUR3HW.js} +2 -2
- package/dist/{static-viewer-NUBFPKWH.js → static-viewer-KKCR4KXR.js} +3 -3
- package/dist/static-viewer-KKCR4KXR.js.map +1 -0
- package/dist/{test-Z5LVO724.js → test-5UCKXYSC.js} +4 -4
- package/dist/{tokens-CE46OTMD.js → tokens-L46MK5AW.js} +5 -5
- package/dist/{viewer-DLLJIMCK.js → viewer-M2EQQSGE.js} +14 -14
- package/dist/viewer-M2EQQSGE.js.map +1 -0
- package/package.json +11 -9
- package/src/ai-client.ts +156 -0
- package/src/bin.ts +99 -2
- package/src/build.ts +95 -33
- package/src/commands/__tests__/drift-sync.test.ts +252 -0
- package/src/commands/__tests__/scan-generate.test.ts +497 -45
- package/src/commands/enhance.ts +11 -35
- package/src/commands/govern.ts +122 -0
- package/src/commands/init.ts +288 -260
- package/src/commands/scan-generate.ts +740 -139
- package/src/commands/scan.ts +37 -32
- package/src/commands/setup.ts +143 -52
- package/src/commands/sync.ts +357 -0
- package/src/commands/validate.ts +43 -1
- package/src/core/component-extractor.test.ts +282 -0
- package/src/core/component-extractor.ts +1030 -0
- package/src/core/discovery.ts +93 -7
- package/src/service/enhance/props-extractor.ts +235 -13
- package/src/validators.ts +236 -0
- package/src/viewer/vite-plugin.ts +1 -1
- package/dist/chunk-5G3VZH43.js.map +0 -1
- package/dist/chunk-OQO55NKV.js.map +0 -1
- package/dist/chunk-WXSR2II7.js.map +0 -1
- package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
- package/dist/init-UFGK5TCN.js +0 -867
- package/dist/init-UFGK5TCN.js.map +0 -1
- package/dist/scan-generate-SJAN5MVI.js +0 -691
- package/dist/scan-generate-SJAN5MVI.js.map +0 -1
- package/dist/viewer-DLLJIMCK.js.map +0 -1
- package/src/ai.ts +0 -266
- package/src/commands/init-framework.ts +0 -414
- package/src/mcp/bin.ts +0 -36
- package/src/migrate/bin.ts +0 -114
- package/src/theme/index.ts +0 -77
- package/src/viewer/bin.ts +0 -86
- package/src/viewer/cli/health.ts +0 -256
- package/src/viewer/cli/index.ts +0 -33
- package/src/viewer/cli/scan.ts +0 -124
- package/src/viewer/cli/utils.ts +0 -174
- /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
- /package/dist/{chunk-HRFUSSZI.js.map → chunk-3SOAPJDX.js.map} +0 -0
- /package/dist/{chunk-PW7QTQA6.js.map → chunk-UV5JQV3R.js.map} +0 -0
- /package/dist/{scan-CJF2DOQW.js.map → discovery-VSGC76JN.js.map} +0 -0
- /package/dist/{generate-FBHSXR3D.js.map → generate-QZXOXYFW.js.map} +0 -0
- /package/dist/{service-TQYWY65E.js.map → scan-CHQHXWVD.js.map} +0 -0
- /package/dist/{static-viewer-NUBFPKWH.js.map → service-MMEKG4MZ.js.map} +0 -0
- /package/dist/{snapshot-SV2JOFZH.js.map → snapshot-53TUR3HW.js.map} +0 -0
- /package/dist/{test-Z5LVO724.js.map → test-5UCKXYSC.js.map} +0 -0
- /package/dist/{tokens-CE46OTMD.js.map → tokens-L46MK5AW.js.map} +0 -0
package/src/migrate/bin.ts
DELETED
|
@@ -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();
|
package/src/theme/index.ts
DELETED
|
@@ -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();
|
package/src/viewer/cli/health.ts
DELETED
|
@@ -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
|
-
}
|
package/src/viewer/cli/index.ts
DELETED
|
@@ -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
|
-
}
|
package/src/viewer/cli/scan.ts
DELETED
|
@@ -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
|
-
}
|