@fragments-sdk/cli 0.2.2
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/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fragments add - Scaffold a new component with fragment file
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { writeFile, mkdir, access } from 'node:fs/promises';
|
|
6
|
+
import { resolve, join, relative } from 'node:path';
|
|
7
|
+
import pc from 'picocolors';
|
|
8
|
+
import { BRAND } from '../core/index.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for add command
|
|
12
|
+
*/
|
|
13
|
+
export interface AddOptions {
|
|
14
|
+
/** Component category */
|
|
15
|
+
category?: string;
|
|
16
|
+
/** Output directory */
|
|
17
|
+
dir?: string;
|
|
18
|
+
/** Template to use (action, form-input, layout, display) */
|
|
19
|
+
template?: string;
|
|
20
|
+
/** Only generate fragment file, skip component stub */
|
|
21
|
+
component?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Result of add command
|
|
26
|
+
*/
|
|
27
|
+
export interface AddResult {
|
|
28
|
+
success: boolean;
|
|
29
|
+
componentPath?: string;
|
|
30
|
+
segmentPath: string;
|
|
31
|
+
indexPath?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Run the add command
|
|
36
|
+
*/
|
|
37
|
+
export async function add(
|
|
38
|
+
name: string | undefined,
|
|
39
|
+
options: AddOptions = {}
|
|
40
|
+
): Promise<AddResult> {
|
|
41
|
+
console.log(pc.cyan(`\n${BRAND.name} Component Scaffold\n`));
|
|
42
|
+
|
|
43
|
+
let componentName = name;
|
|
44
|
+
let category = options.category;
|
|
45
|
+
let template = options.template;
|
|
46
|
+
let dir = options.dir;
|
|
47
|
+
const generateComponent = options.component !== false;
|
|
48
|
+
|
|
49
|
+
// If any required values are missing, go interactive
|
|
50
|
+
if (!componentName || !category || !template || !dir) {
|
|
51
|
+
const { input, select } = await import('@inquirer/prompts');
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Component name
|
|
55
|
+
if (!componentName) {
|
|
56
|
+
componentName = await input({
|
|
57
|
+
message: 'Component name:',
|
|
58
|
+
validate: (value) => {
|
|
59
|
+
if (!value.trim()) return 'Component name is required';
|
|
60
|
+
if (!/^[A-Za-z][A-Za-z0-9]*$/.test(value)) return 'Use PascalCase (e.g., Button, TextField)';
|
|
61
|
+
return true;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Template type
|
|
67
|
+
if (!template) {
|
|
68
|
+
template = await select({
|
|
69
|
+
message: 'What type of component is this?',
|
|
70
|
+
choices: [
|
|
71
|
+
{ name: 'Display - Shows information or status', value: 'display' },
|
|
72
|
+
{ name: 'Action - Triggers an action (button, link)', value: 'action' },
|
|
73
|
+
{ name: 'Form Input - Accepts user input', value: 'form-input' },
|
|
74
|
+
{ name: 'Layout - Organizes content structure', value: 'layout' },
|
|
75
|
+
],
|
|
76
|
+
default: 'display',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Category
|
|
81
|
+
if (!category) {
|
|
82
|
+
category = await input({
|
|
83
|
+
message: 'Category:',
|
|
84
|
+
default: template === 'form-input' ? 'forms' : template === 'layout' ? 'layout' : 'components',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Output directory
|
|
89
|
+
if (!dir) {
|
|
90
|
+
dir = await input({
|
|
91
|
+
message: 'Output directory:',
|
|
92
|
+
default: 'src/components',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// User cancelled (Ctrl+C)
|
|
97
|
+
console.log(pc.dim('\nNo changes made.'));
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Normalize component name (PascalCase)
|
|
103
|
+
componentName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
|
|
104
|
+
|
|
105
|
+
// Apply defaults for non-interactive values
|
|
106
|
+
category = category || 'components';
|
|
107
|
+
template = template || 'display';
|
|
108
|
+
dir = dir || 'src/components';
|
|
109
|
+
|
|
110
|
+
// Determine output paths
|
|
111
|
+
const componentDir = resolve(process.cwd(), dir, componentName);
|
|
112
|
+
const componentFile = join(componentDir, `${componentName}.tsx`);
|
|
113
|
+
const segmentFile = join(componentDir, `${componentName}${BRAND.fileExtension}`);
|
|
114
|
+
const indexFile = join(componentDir, 'index.ts');
|
|
115
|
+
|
|
116
|
+
// Check if directory already exists
|
|
117
|
+
try {
|
|
118
|
+
await access(componentDir);
|
|
119
|
+
console.log(pc.yellow(`Directory already exists: ${relative(process.cwd(), componentDir)}`));
|
|
120
|
+
console.log(pc.dim('Use a different name or remove the existing directory.'));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
} catch {
|
|
123
|
+
// Directory doesn't exist, proceed
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Create directory
|
|
127
|
+
await mkdir(componentDir, { recursive: true });
|
|
128
|
+
|
|
129
|
+
// Generate component stub (unless --no-component)
|
|
130
|
+
if (generateComponent) {
|
|
131
|
+
const componentCode = generateComponentStub(componentName, template);
|
|
132
|
+
await writeFile(componentFile, componentCode);
|
|
133
|
+
console.log(`${pc.green('✓')} Created ${relative(process.cwd(), componentFile)}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Generate segment file
|
|
137
|
+
const segmentCode = generateSegmentStub(componentName, category, template);
|
|
138
|
+
await writeFile(segmentFile, segmentCode);
|
|
139
|
+
console.log(`${pc.green('✓')} Created ${relative(process.cwd(), segmentFile)}`);
|
|
140
|
+
|
|
141
|
+
// Generate index.ts
|
|
142
|
+
const indexCode = `export { ${componentName} } from './${componentName}.js';\n`;
|
|
143
|
+
await writeFile(indexFile, indexCode);
|
|
144
|
+
console.log(`${pc.green('✓')} Created ${relative(process.cwd(), indexFile)}`);
|
|
145
|
+
|
|
146
|
+
console.log(pc.green(`\n✓ Scaffolded ${componentName}\n`));
|
|
147
|
+
|
|
148
|
+
// Next steps
|
|
149
|
+
console.log(pc.dim('───────────────────────────────────────'));
|
|
150
|
+
console.log(pc.bold('\nNext steps:'));
|
|
151
|
+
if (generateComponent) {
|
|
152
|
+
console.log(` 1. Implement ${componentName}.tsx`);
|
|
153
|
+
}
|
|
154
|
+
console.log(` 2. Fill in usage.when and usage.whenNot in the fragment file`);
|
|
155
|
+
console.log(` 3. Add variants with different prop combinations`);
|
|
156
|
+
console.log(` 4. Run ${pc.cyan(`${BRAND.cliCommand} dev`)} to preview`);
|
|
157
|
+
console.log();
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
componentPath: generateComponent ? componentFile : undefined,
|
|
162
|
+
segmentPath: segmentFile,
|
|
163
|
+
indexPath: indexFile,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Generate a React component stub based on template type.
|
|
169
|
+
*/
|
|
170
|
+
function generateComponentStub(name: string, _template: string): string {
|
|
171
|
+
const propsInterface = `export interface ${name}Props {
|
|
172
|
+
/** Content to display */
|
|
173
|
+
children?: React.ReactNode;
|
|
174
|
+
/** Additional CSS classes */
|
|
175
|
+
className?: string;
|
|
176
|
+
}`;
|
|
177
|
+
|
|
178
|
+
return `import React from 'react';
|
|
179
|
+
|
|
180
|
+
${propsInterface}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* ${name} component
|
|
184
|
+
*
|
|
185
|
+
* TODO: Implement this component
|
|
186
|
+
*/
|
|
187
|
+
export function ${name}({ children, className }: ${name}Props) {
|
|
188
|
+
return (
|
|
189
|
+
<div className={className}>
|
|
190
|
+
{children}
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Generate a segment stub based on template type.
|
|
199
|
+
*/
|
|
200
|
+
function generateSegmentStub(name: string, category: string, template: string): string {
|
|
201
|
+
// Template-specific usage hints
|
|
202
|
+
const usageHints: Record<string, { when: string[]; whenNot: string[] }> = {
|
|
203
|
+
action: {
|
|
204
|
+
when: ['User needs to trigger an action', 'Form submission is required'],
|
|
205
|
+
whenNot: ['Navigation is needed (use Link)', 'Action is destructive without confirmation'],
|
|
206
|
+
},
|
|
207
|
+
'form-input': {
|
|
208
|
+
when: ['User needs to enter data', 'Form field is required'],
|
|
209
|
+
whenNot: ['Display-only data is shown', 'Rich text editing is needed'],
|
|
210
|
+
},
|
|
211
|
+
layout: {
|
|
212
|
+
when: ['Content needs to be organized', 'Responsive layout is required'],
|
|
213
|
+
whenNot: ['Simple inline content is displayed', 'Scroll container is needed'],
|
|
214
|
+
},
|
|
215
|
+
display: {
|
|
216
|
+
when: ['Information needs to be presented', 'Status or feedback is shown'],
|
|
217
|
+
whenNot: ['User interaction is required', 'Data input is needed'],
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const hints = usageHints[template] || usageHints.display;
|
|
222
|
+
|
|
223
|
+
// Template-specific scenario tags
|
|
224
|
+
const scenarioTagHints: Record<string, string[]> = {
|
|
225
|
+
action: ['action.primary', 'action.secondary', 'form.submit'],
|
|
226
|
+
'form-input': ['form.input', 'form.field', 'form.text'],
|
|
227
|
+
layout: ['layout.container', 'layout.section', 'content.group'],
|
|
228
|
+
display: ['display.info', 'display.status', 'content.text'],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const scenarioTags = scenarioTagHints[template] || scenarioTagHints.display;
|
|
232
|
+
|
|
233
|
+
return `import React from 'react';
|
|
234
|
+
import { defineSegment } from '@fragments/core';
|
|
235
|
+
import { ${name} } from './index.js';
|
|
236
|
+
|
|
237
|
+
export default defineSegment({
|
|
238
|
+
component: ${name},
|
|
239
|
+
|
|
240
|
+
meta: {
|
|
241
|
+
name: '${name}',
|
|
242
|
+
description: 'TODO: Add description',
|
|
243
|
+
category: '${category}',
|
|
244
|
+
status: 'experimental',
|
|
245
|
+
tags: ['${category}'],
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
usage: {
|
|
249
|
+
when: [
|
|
250
|
+
'${hints.when[0]}',
|
|
251
|
+
'${hints.when[1]}',
|
|
252
|
+
],
|
|
253
|
+
whenNot: [
|
|
254
|
+
'${hints.whenNot[0]}',
|
|
255
|
+
'${hints.whenNot[1]}',
|
|
256
|
+
],
|
|
257
|
+
guidelines: [
|
|
258
|
+
// TODO: Add best practices
|
|
259
|
+
],
|
|
260
|
+
accessibility: [
|
|
261
|
+
// TODO: Add accessibility guidelines
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
props: {
|
|
266
|
+
children: {
|
|
267
|
+
type: 'node',
|
|
268
|
+
description: 'Content to display',
|
|
269
|
+
},
|
|
270
|
+
className: {
|
|
271
|
+
type: 'string',
|
|
272
|
+
description: 'Additional CSS classes',
|
|
273
|
+
},
|
|
274
|
+
// TODO: Add more props
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
relations: [
|
|
278
|
+
// TODO: Add related components
|
|
279
|
+
// { component: 'RelatedComponent', relationship: 'alternative', note: 'Use for...' },
|
|
280
|
+
],
|
|
281
|
+
|
|
282
|
+
contract: {
|
|
283
|
+
propsSummary: [
|
|
284
|
+
'children: ReactNode - content to display',
|
|
285
|
+
'className: string - additional CSS classes',
|
|
286
|
+
// TODO: Add prop summaries
|
|
287
|
+
],
|
|
288
|
+
scenarioTags: [
|
|
289
|
+
'${scenarioTags[0]}',
|
|
290
|
+
'${scenarioTags[1]}',
|
|
291
|
+
// TODO: Add scenario tags for AI agent queries
|
|
292
|
+
],
|
|
293
|
+
a11yRules: [
|
|
294
|
+
// TODO: Add accessibility rule IDs
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
variants: [
|
|
299
|
+
{
|
|
300
|
+
name: 'Default',
|
|
301
|
+
description: 'Default ${name} appearance',
|
|
302
|
+
render: () => <${name}>Example content</${name}>,
|
|
303
|
+
},
|
|
304
|
+
// TODO: Add more variants
|
|
305
|
+
// {
|
|
306
|
+
// name: 'WithProps',
|
|
307
|
+
// description: '${name} with additional props',
|
|
308
|
+
// render: () => <${name} someProp="value">Content</${name}>,
|
|
309
|
+
// },
|
|
310
|
+
],
|
|
311
|
+
});
|
|
312
|
+
`;
|
|
313
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fragments audit - Scan all fragments and show compliance metrics
|
|
3
|
+
*
|
|
4
|
+
* Uses the /fragments/compliance endpoint to get real token compliance data
|
|
5
|
+
* for all components in the design system.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
import { BRAND } from '../core/index.js';
|
|
10
|
+
import { loadConfig } from '../core/node.js';
|
|
11
|
+
import {
|
|
12
|
+
createDevServerClient,
|
|
13
|
+
DevServerConnectionError,
|
|
14
|
+
type AuditResult,
|
|
15
|
+
} from '../shared/index.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for audit command
|
|
19
|
+
*/
|
|
20
|
+
export interface AuditOptions {
|
|
21
|
+
/** Path to config file */
|
|
22
|
+
config?: string;
|
|
23
|
+
/** Output JSON format */
|
|
24
|
+
json?: boolean;
|
|
25
|
+
/** Sort field: compliance, name, hardcoded */
|
|
26
|
+
sort?: 'compliance' | 'name' | 'hardcoded';
|
|
27
|
+
/** Dev server port */
|
|
28
|
+
port?: number | string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Summary of audit results
|
|
33
|
+
*/
|
|
34
|
+
export interface AuditSummary {
|
|
35
|
+
totalComponents: number;
|
|
36
|
+
averageCompliance: number;
|
|
37
|
+
worstOffenders: AuditResult[];
|
|
38
|
+
components: AuditResult[];
|
|
39
|
+
stats: {
|
|
40
|
+
totalHardcoded: number;
|
|
41
|
+
totalTokenMismatches: number;
|
|
42
|
+
totalProperties: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run the audit command
|
|
48
|
+
*/
|
|
49
|
+
export async function audit(options: AuditOptions = {}): Promise<AuditSummary> {
|
|
50
|
+
const { config: configPath, json = false, sort = 'compliance', port = 6006 } = options;
|
|
51
|
+
|
|
52
|
+
// Load config
|
|
53
|
+
await loadConfig(configPath);
|
|
54
|
+
|
|
55
|
+
const client = createDevServerClient(port);
|
|
56
|
+
const audits: AuditResult[] = [];
|
|
57
|
+
|
|
58
|
+
if (!json) {
|
|
59
|
+
console.log(pc.cyan(`\n${BRAND.name} Design System Audit\n`));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check if dev server is reachable
|
|
63
|
+
const isReachable = await client.ping();
|
|
64
|
+
if (!isReachable) {
|
|
65
|
+
throw new DevServerConnectionError(
|
|
66
|
+
`Cannot connect to dev server at http://localhost:${port}`,
|
|
67
|
+
`http://localhost:${port}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Fetch all fragments
|
|
72
|
+
const segments = await client.getSegments();
|
|
73
|
+
|
|
74
|
+
if (segments.length === 0) {
|
|
75
|
+
if (json) {
|
|
76
|
+
console.log(JSON.stringify({ error: 'No fragments found', components: [] }));
|
|
77
|
+
} else {
|
|
78
|
+
console.log(pc.yellow('No fragments found.\n'));
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
totalComponents: 0,
|
|
82
|
+
averageCompliance: 100,
|
|
83
|
+
worstOffenders: [],
|
|
84
|
+
components: [],
|
|
85
|
+
stats: {
|
|
86
|
+
totalHardcoded: 0,
|
|
87
|
+
totalTokenMismatches: 0,
|
|
88
|
+
totalProperties: 0,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!json) {
|
|
94
|
+
console.log(pc.dim(`Auditing ${segments.length} component(s)...\n`));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Audit each fragment
|
|
98
|
+
for (const seg of segments) {
|
|
99
|
+
try {
|
|
100
|
+
// Get real compliance from the server
|
|
101
|
+
const complianceResult = await client.getCompliance({
|
|
102
|
+
component: seg.name,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
audits.push({
|
|
106
|
+
name: seg.name,
|
|
107
|
+
category: seg.category || 'uncategorized',
|
|
108
|
+
compliance: complianceResult.compliance,
|
|
109
|
+
hardcoded: complianceResult.hardcoded,
|
|
110
|
+
tokenMismatches: complianceResult.violations.filter(
|
|
111
|
+
v => v.issue.includes('mismatch')
|
|
112
|
+
).length,
|
|
113
|
+
totalProperties: complianceResult.totalProperties,
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// Handle individual component errors
|
|
117
|
+
audits.push({
|
|
118
|
+
name: seg.name,
|
|
119
|
+
category: seg.category || 'uncategorized',
|
|
120
|
+
compliance: 0,
|
|
121
|
+
hardcoded: 0,
|
|
122
|
+
tokenMismatches: 0,
|
|
123
|
+
totalProperties: 0,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Sort results
|
|
129
|
+
audits.sort((a, b) => {
|
|
130
|
+
switch (sort) {
|
|
131
|
+
case 'name':
|
|
132
|
+
return a.name.localeCompare(b.name);
|
|
133
|
+
case 'hardcoded':
|
|
134
|
+
return b.hardcoded - a.hardcoded; // Most hardcoded first
|
|
135
|
+
case 'compliance':
|
|
136
|
+
default:
|
|
137
|
+
return a.compliance - b.compliance; // Worst first
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const summary: AuditSummary = {
|
|
142
|
+
totalComponents: audits.length,
|
|
143
|
+
averageCompliance: audits.length > 0
|
|
144
|
+
? Math.round(audits.reduce((sum, a) => sum + a.compliance, 0) / audits.length * 100) / 100
|
|
145
|
+
: 100,
|
|
146
|
+
worstOffenders: audits.slice(0, 5),
|
|
147
|
+
components: audits,
|
|
148
|
+
stats: {
|
|
149
|
+
totalHardcoded: audits.reduce((sum, a) => sum + a.hardcoded, 0),
|
|
150
|
+
totalTokenMismatches: audits.reduce((sum, a) => sum + a.tokenMismatches, 0),
|
|
151
|
+
totalProperties: audits.reduce((sum, a) => sum + a.totalProperties, 0),
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
if (json) {
|
|
156
|
+
// JSON output for CI/automation
|
|
157
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
158
|
+
} else {
|
|
159
|
+
// Table output for humans
|
|
160
|
+
console.log(pc.bold('Component'.padEnd(30) + 'Compliance'.padEnd(12) + 'Hardcoded'.padEnd(12) + 'Properties'));
|
|
161
|
+
console.log(pc.dim('─'.repeat(66)));
|
|
162
|
+
|
|
163
|
+
for (const auditItem of audits) {
|
|
164
|
+
const complianceColor = auditItem.compliance >= 90
|
|
165
|
+
? pc.green
|
|
166
|
+
: auditItem.compliance >= 70
|
|
167
|
+
? pc.yellow
|
|
168
|
+
: pc.red;
|
|
169
|
+
|
|
170
|
+
console.log(
|
|
171
|
+
auditItem.name.padEnd(30) +
|
|
172
|
+
complianceColor(`${auditItem.compliance}%`.padEnd(12)) +
|
|
173
|
+
String(auditItem.hardcoded).padEnd(12) +
|
|
174
|
+
String(auditItem.totalProperties)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(pc.dim('─'.repeat(66)));
|
|
179
|
+
|
|
180
|
+
console.log();
|
|
181
|
+
console.log(pc.bold('Summary:'));
|
|
182
|
+
console.log(` Total components: ${audits.length}`);
|
|
183
|
+
console.log(` Average compliance: ${summary.averageCompliance}%`);
|
|
184
|
+
console.log(` Total hardcoded values: ${summary.stats.totalHardcoded}`);
|
|
185
|
+
console.log(` Total properties checked: ${summary.stats.totalProperties}`);
|
|
186
|
+
|
|
187
|
+
if (audits.some(a => a.compliance < 100)) {
|
|
188
|
+
console.log();
|
|
189
|
+
console.log(pc.dim(`Run ${pc.cyan(`${BRAND.cliCommand} verify <component>`)} for detailed compliance info.`));
|
|
190
|
+
}
|
|
191
|
+
console.log();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return summary;
|
|
195
|
+
}
|