@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,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fragments storygen - Generate Storybook stories from segment definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { writeFile, mkdir } 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
|
+
import type { SegmentDefinition } from '../core/index.js';
|
|
10
|
+
import { loadConfig, discoverSegmentFiles, loadSegmentFile } from '../core/node.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for storygen command
|
|
14
|
+
*/
|
|
15
|
+
export interface StorygenOptions {
|
|
16
|
+
/** Path to config file */
|
|
17
|
+
config?: string;
|
|
18
|
+
/** Output directory */
|
|
19
|
+
output?: string;
|
|
20
|
+
/** Watch for segment changes and regenerate */
|
|
21
|
+
watch?: boolean;
|
|
22
|
+
/** Story format (csf3) */
|
|
23
|
+
format?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Result of storygen command
|
|
28
|
+
*/
|
|
29
|
+
export interface StorygenResult {
|
|
30
|
+
success: boolean;
|
|
31
|
+
generated: number;
|
|
32
|
+
outputDir: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run the storygen command
|
|
37
|
+
*/
|
|
38
|
+
export async function storygen(options: StorygenOptions = {}): Promise<StorygenResult> {
|
|
39
|
+
const { config: configPath, output = '.storybook/generated', watch = false } = options;
|
|
40
|
+
|
|
41
|
+
const { config, configDir } = await loadConfig(configPath);
|
|
42
|
+
|
|
43
|
+
console.log(pc.cyan(`\n${BRAND.name} Story Generator\n`));
|
|
44
|
+
|
|
45
|
+
// Discover segment files
|
|
46
|
+
const segmentFiles = await discoverSegmentFiles(config, configDir);
|
|
47
|
+
|
|
48
|
+
if (segmentFiles.length === 0) {
|
|
49
|
+
console.log(pc.yellow('No segment files found.\n'));
|
|
50
|
+
return { success: true, generated: 0, outputDir: output };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const outputDir = resolve(configDir, output);
|
|
54
|
+
await mkdir(outputDir, { recursive: true });
|
|
55
|
+
|
|
56
|
+
let generated = 0;
|
|
57
|
+
|
|
58
|
+
// Generate function for a single segment
|
|
59
|
+
const generateStory = async (file: { absolutePath: string; relativePath: string }) => {
|
|
60
|
+
try {
|
|
61
|
+
const segment = await loadSegmentFile(file.absolutePath);
|
|
62
|
+
if (!segment) return false;
|
|
63
|
+
|
|
64
|
+
const storyContent = generateCSF3Story(segment, file.relativePath);
|
|
65
|
+
const storyName = `${segment.meta.name}.stories.tsx`;
|
|
66
|
+
const storyPath = join(outputDir, storyName);
|
|
67
|
+
|
|
68
|
+
await writeFile(storyPath, storyContent);
|
|
69
|
+
console.log(`${pc.green('✓')} Generated ${storyName}`);
|
|
70
|
+
return true;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.log(`${pc.red('✗')} Failed: ${file.relativePath} - ${error instanceof Error ? error.message : error}`);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Initial generation
|
|
78
|
+
console.log(pc.dim(`Generating stories to ${relative(process.cwd(), outputDir)}/\n`));
|
|
79
|
+
|
|
80
|
+
for (const file of segmentFiles) {
|
|
81
|
+
if (await generateStory(file)) {
|
|
82
|
+
generated++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log();
|
|
87
|
+
console.log(pc.green(`✓ Generated ${generated} story file(s)\n`));
|
|
88
|
+
|
|
89
|
+
// Watch mode
|
|
90
|
+
if (watch) {
|
|
91
|
+
console.log(pc.dim('Watching for segment changes... (Ctrl+C to stop)\n'));
|
|
92
|
+
|
|
93
|
+
const chokidar = await import('chokidar');
|
|
94
|
+
const patterns = segmentFiles.map(f => f.absolutePath);
|
|
95
|
+
|
|
96
|
+
const watcher = chokidar.watch(patterns, {
|
|
97
|
+
ignoreInitial: true,
|
|
98
|
+
awaitWriteFinish: { stabilityThreshold: 100 },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
watcher.on('change', async (changedPath: string) => {
|
|
102
|
+
const file = segmentFiles.find(f => f.absolutePath === changedPath);
|
|
103
|
+
if (file) {
|
|
104
|
+
console.log(pc.dim(`\nChanged: ${relative(process.cwd(), changedPath)}`));
|
|
105
|
+
await generateStory(file);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
watcher.on('add', async (addedPath: string) => {
|
|
110
|
+
console.log(pc.dim(`\nAdded: ${relative(process.cwd(), addedPath)}`));
|
|
111
|
+
// Re-discover to get the new file's relative path
|
|
112
|
+
const newFiles = await discoverSegmentFiles(config, configDir);
|
|
113
|
+
const file = newFiles.find(f => f.absolutePath === addedPath);
|
|
114
|
+
if (file) {
|
|
115
|
+
segmentFiles.push(file);
|
|
116
|
+
await generateStory(file);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Keep process alive
|
|
121
|
+
await new Promise(() => {});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { success: true, generated, outputDir };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Generate a CSF3 story file from a segment definition
|
|
129
|
+
*/
|
|
130
|
+
function generateCSF3Story(segment: SegmentDefinition, relativePath: string): string {
|
|
131
|
+
const { meta, variants, props, usage } = segment;
|
|
132
|
+
const componentName = meta.name;
|
|
133
|
+
|
|
134
|
+
// Build argTypes from props
|
|
135
|
+
const argTypes: string[] = [];
|
|
136
|
+
if (props) {
|
|
137
|
+
for (const [propName, propDef] of Object.entries(props)) {
|
|
138
|
+
let controlType = 'text';
|
|
139
|
+
let controlOptions = '';
|
|
140
|
+
|
|
141
|
+
if (propDef.type === 'enum' && propDef.values) {
|
|
142
|
+
controlType = 'select';
|
|
143
|
+
controlOptions = `,\n options: ${JSON.stringify(propDef.values)}`;
|
|
144
|
+
} else if (propDef.type === 'boolean') {
|
|
145
|
+
controlType = 'boolean';
|
|
146
|
+
} else if (propDef.type === 'number') {
|
|
147
|
+
controlType = 'number';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
argTypes.push(` ${propName}: {
|
|
151
|
+
control: '${controlType}'${controlOptions},
|
|
152
|
+
description: ${JSON.stringify(propDef.description || '')},
|
|
153
|
+
${propDef.default !== undefined ? `defaultValue: ${JSON.stringify(propDef.default)},` : ''}
|
|
154
|
+
}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Build story exports from variants
|
|
159
|
+
const storyExports: string[] = [];
|
|
160
|
+
for (const variant of variants) {
|
|
161
|
+
const storyName = variant.name.replace(/[^a-zA-Z0-9]/g, '');
|
|
162
|
+
storyExports.push(`
|
|
163
|
+
export const ${storyName}: Story = {
|
|
164
|
+
name: ${JSON.stringify(variant.name)},
|
|
165
|
+
${variant.description ? `parameters: { docs: { description: { story: ${JSON.stringify(variant.description)} } } },` : ''}
|
|
166
|
+
};`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Build usage description
|
|
170
|
+
const usageDoc = usage
|
|
171
|
+
? `When to use:
|
|
172
|
+
${usage.when?.map(w => `- ${w}`).join('\n') || ''}
|
|
173
|
+
|
|
174
|
+
When not to use:
|
|
175
|
+
${usage.whenNot?.map(w => `- ${w}`).join('\n') || ''}`
|
|
176
|
+
: '';
|
|
177
|
+
|
|
178
|
+
return `/**
|
|
179
|
+
* Auto-generated Storybook stories from ${relativePath}
|
|
180
|
+
*
|
|
181
|
+
* DO NOT EDIT - regenerate with: segments storygen
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
185
|
+
import { ${componentName} } from '${relativePath.replace(/\.segment\.tsx$/, '/index.js')}';
|
|
186
|
+
|
|
187
|
+
const meta: Meta<typeof ${componentName}> = {
|
|
188
|
+
title: '${meta.category ? `${meta.category.charAt(0).toUpperCase() + meta.category.slice(1)}/` : ''}${componentName}',
|
|
189
|
+
component: ${componentName},
|
|
190
|
+
tags: ['autodocs'],
|
|
191
|
+
parameters: {
|
|
192
|
+
docs: {
|
|
193
|
+
description: {
|
|
194
|
+
component: ${JSON.stringify(meta.description || '')},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
argTypes: {
|
|
199
|
+
${argTypes.join(',\n')}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export default meta;
|
|
204
|
+
type Story = StoryObj<typeof meta>;
|
|
205
|
+
${storyExports.join('\n')}
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Tokens Command
|
|
3
|
+
*
|
|
4
|
+
* Discover and list design tokens from CSS/SCSS files.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* fragments tokens # List all tokens
|
|
8
|
+
* fragments tokens --json # Output as JSON
|
|
9
|
+
* fragments tokens --categories # Group by category
|
|
10
|
+
* fragments tokens --theme dark # Filter by theme
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import pc from "picocolors";
|
|
14
|
+
import { BRAND } from "../core/index.js";
|
|
15
|
+
import type { DesignToken, TokenCategory, TokenConfig } from "../core/index.js";
|
|
16
|
+
import { loadConfig } from "../core/node.js";
|
|
17
|
+
import { parseTokenFiles, createTokenRegistry } from "../service/index.js";
|
|
18
|
+
|
|
19
|
+
export interface TokensCommandOptions {
|
|
20
|
+
config?: string;
|
|
21
|
+
json?: boolean;
|
|
22
|
+
categories?: boolean;
|
|
23
|
+
theme?: string;
|
|
24
|
+
category?: string;
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TokensCommandResult {
|
|
29
|
+
success: boolean;
|
|
30
|
+
tokenCount: number;
|
|
31
|
+
errors: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Run the tokens command
|
|
36
|
+
*/
|
|
37
|
+
export async function tokens(
|
|
38
|
+
options: TokensCommandOptions
|
|
39
|
+
): Promise<TokensCommandResult> {
|
|
40
|
+
const errors: string[] = [];
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
console.log(pc.cyan(`\n${BRAND.name} Token Discovery\n`));
|
|
44
|
+
|
|
45
|
+
// Load config
|
|
46
|
+
const { config, configDir } = await loadConfig(options.config);
|
|
47
|
+
|
|
48
|
+
// Check for token configuration
|
|
49
|
+
if (!config.tokens || !config.tokens.include || config.tokens.include.length === 0) {
|
|
50
|
+
console.log(pc.yellow("No token configuration found.\n"));
|
|
51
|
+
console.log(pc.dim("Add 'tokens' config to fragments.config.ts:"));
|
|
52
|
+
console.log(pc.dim(`
|
|
53
|
+
tokens: {
|
|
54
|
+
include: ['src/styles/theme.scss', 'src/styles/variables.css'],
|
|
55
|
+
themeSelectors: {
|
|
56
|
+
':root': 'default',
|
|
57
|
+
'[data-theme="dark"]': 'dark',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
`));
|
|
61
|
+
return { success: false, tokenCount: 0, errors: ["No token configuration"] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(pc.dim(`Scanning files: ${config.tokens.include.join(", ")}\n`));
|
|
65
|
+
|
|
66
|
+
// Parse token files
|
|
67
|
+
const parseResult = await parseTokenFiles(config.tokens, configDir);
|
|
68
|
+
|
|
69
|
+
if (parseResult.errors.length > 0) {
|
|
70
|
+
console.log(pc.yellow("Parse errors:"));
|
|
71
|
+
for (const err of parseResult.errors) {
|
|
72
|
+
console.log(pc.red(` ${err.file}: ${err.message}`));
|
|
73
|
+
errors.push(`${err.file}: ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
console.log();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (parseResult.warnings.length > 0 && options.verbose) {
|
|
79
|
+
console.log(pc.yellow("Warnings:"));
|
|
80
|
+
for (const warning of parseResult.warnings) {
|
|
81
|
+
console.log(pc.dim(` ${warning}`));
|
|
82
|
+
}
|
|
83
|
+
console.log();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let tokens = parseResult.tokens;
|
|
87
|
+
|
|
88
|
+
// Filter by theme if specified
|
|
89
|
+
if (options.theme) {
|
|
90
|
+
tokens = tokens.filter(
|
|
91
|
+
(t) => t.theme === options.theme || t.theme === "default"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Filter by category if specified
|
|
96
|
+
if (options.category) {
|
|
97
|
+
tokens = tokens.filter((t) => t.category === options.category);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (tokens.length === 0) {
|
|
101
|
+
console.log(pc.yellow("No tokens found.\n"));
|
|
102
|
+
console.log(pc.dim("Make sure your CSS files contain CSS custom properties (--token-name: value;)"));
|
|
103
|
+
return { success: true, tokenCount: 0, errors };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Output based on format
|
|
107
|
+
if (options.json) {
|
|
108
|
+
outputJson(tokens, parseResult.parseTimeMs);
|
|
109
|
+
} else if (options.categories) {
|
|
110
|
+
outputByCategory(tokens, parseResult.parseTimeMs);
|
|
111
|
+
} else {
|
|
112
|
+
outputList(tokens, parseResult.parseTimeMs, options.verbose);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { success: true, tokenCount: tokens.length, errors };
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
118
|
+
console.error(pc.red("Error:"), message);
|
|
119
|
+
errors.push(message);
|
|
120
|
+
return { success: false, tokenCount: 0, errors };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Output tokens as JSON
|
|
126
|
+
*/
|
|
127
|
+
function outputJson(tokens: DesignToken[], parseTimeMs: number): void {
|
|
128
|
+
const output = {
|
|
129
|
+
tokens,
|
|
130
|
+
meta: {
|
|
131
|
+
totalTokens: tokens.length,
|
|
132
|
+
parseTimeMs,
|
|
133
|
+
discoveredAt: new Date().toISOString(),
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
console.log(JSON.stringify(output, null, 2));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Output tokens grouped by category
|
|
142
|
+
*/
|
|
143
|
+
function outputByCategory(tokens: DesignToken[], parseTimeMs: number): void {
|
|
144
|
+
// Group by category
|
|
145
|
+
const byCategory = new Map<TokenCategory, DesignToken[]>();
|
|
146
|
+
|
|
147
|
+
for (const token of tokens) {
|
|
148
|
+
const list = byCategory.get(token.category) || [];
|
|
149
|
+
list.push(token);
|
|
150
|
+
byCategory.set(token.category, list);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Sort categories alphabetically
|
|
154
|
+
const categories = Array.from(byCategory.keys()).sort();
|
|
155
|
+
|
|
156
|
+
for (const category of categories) {
|
|
157
|
+
const categoryTokens = byCategory.get(category) || [];
|
|
158
|
+
console.log(pc.bold(`${category} (${categoryTokens.length})`));
|
|
159
|
+
console.log(pc.dim("─".repeat(40)));
|
|
160
|
+
|
|
161
|
+
for (const token of categoryTokens.slice(0, 10)) {
|
|
162
|
+
const levelLabel = token.level === 1 ? "base" : token.level === 2 ? "semantic" : "component";
|
|
163
|
+
console.log(` ${pc.cyan(token.name)}`);
|
|
164
|
+
console.log(` ${pc.dim("Value:")} ${token.resolvedValue}`);
|
|
165
|
+
console.log(` ${pc.dim("Level:")} ${levelLabel} ${pc.dim(`(${token.theme})`)}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (categoryTokens.length > 10) {
|
|
169
|
+
console.log(pc.dim(` ... and ${categoryTokens.length - 10} more`));
|
|
170
|
+
}
|
|
171
|
+
console.log();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Summary
|
|
175
|
+
console.log(pc.dim("─".repeat(40)));
|
|
176
|
+
console.log(pc.green(`✓ Found ${tokens.length} token(s) in ${categories.length} categories`));
|
|
177
|
+
console.log(pc.dim(` Parsed in ${parseTimeMs.toFixed(1)}ms\n`));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Output tokens as a simple list
|
|
182
|
+
*/
|
|
183
|
+
function outputList(tokens: DesignToken[], parseTimeMs: number, verbose?: boolean): void {
|
|
184
|
+
// Group by theme first
|
|
185
|
+
const byTheme = new Map<string, DesignToken[]>();
|
|
186
|
+
|
|
187
|
+
for (const token of tokens) {
|
|
188
|
+
const list = byTheme.get(token.theme) || [];
|
|
189
|
+
list.push(token);
|
|
190
|
+
byTheme.set(token.theme, list);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const themes = Array.from(byTheme.keys()).sort();
|
|
194
|
+
|
|
195
|
+
for (const theme of themes) {
|
|
196
|
+
const themeTokens = byTheme.get(theme) || [];
|
|
197
|
+
|
|
198
|
+
if (themes.length > 1) {
|
|
199
|
+
console.log(pc.bold(`Theme: ${theme} (${themeTokens.length} tokens)`));
|
|
200
|
+
console.log(pc.dim("─".repeat(50)));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Sort tokens by name
|
|
204
|
+
themeTokens.sort((a, b) => a.name.localeCompare(b.name));
|
|
205
|
+
|
|
206
|
+
// Table header
|
|
207
|
+
console.log(
|
|
208
|
+
pc.dim(
|
|
209
|
+
`${"Token Name".padEnd(32)} ${"Value".padEnd(20)} ${"Category".padEnd(12)}`
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
console.log(pc.dim("─".repeat(70)));
|
|
213
|
+
|
|
214
|
+
const displayTokens = verbose ? themeTokens : themeTokens.slice(0, 30);
|
|
215
|
+
|
|
216
|
+
for (const token of displayTokens) {
|
|
217
|
+
const name = token.name.length > 30 ? token.name.slice(0, 27) + "..." : token.name;
|
|
218
|
+
const value = token.resolvedValue.length > 18 ? token.resolvedValue.slice(0, 15) + "..." : token.resolvedValue;
|
|
219
|
+
|
|
220
|
+
console.log(
|
|
221
|
+
`${pc.cyan(name.padEnd(32))} ${value.padEnd(20)} ${pc.dim(token.category.padEnd(12))}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!verbose && themeTokens.length > 30) {
|
|
226
|
+
console.log(pc.dim(`\n ... and ${themeTokens.length - 30} more (use --verbose to show all)`));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Summary
|
|
233
|
+
console.log(pc.dim("─".repeat(50)));
|
|
234
|
+
console.log(pc.green(`✓ Found ${tokens.length} token(s)`));
|
|
235
|
+
|
|
236
|
+
// Category breakdown
|
|
237
|
+
const categoryCounts: Record<string, number> = {};
|
|
238
|
+
for (const token of tokens) {
|
|
239
|
+
categoryCounts[token.category] = (categoryCounts[token.category] || 0) + 1;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const breakdown = Object.entries(categoryCounts)
|
|
243
|
+
.sort((a, b) => b[1] - a[1])
|
|
244
|
+
.map(([cat, count]) => `${cat}: ${count}`)
|
|
245
|
+
.join(", ");
|
|
246
|
+
|
|
247
|
+
console.log(pc.dim(` ${breakdown}`));
|
|
248
|
+
console.log(pc.dim(` Parsed in ${parseTimeMs.toFixed(1)}ms\n`));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default tokens;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fragments validate - Validate segment files
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import pc from 'picocolors';
|
|
6
|
+
import { BRAND } from '../core/index.js';
|
|
7
|
+
import { loadConfig } from '../core/node.js';
|
|
8
|
+
import { validateSchema, validateCoverage, validateAll } from '../validators.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for validate command
|
|
12
|
+
*/
|
|
13
|
+
export interface ValidateOptions {
|
|
14
|
+
/** Path to config file */
|
|
15
|
+
config?: string;
|
|
16
|
+
/** Validate segment schema only */
|
|
17
|
+
schema?: boolean;
|
|
18
|
+
/** Validate coverage only */
|
|
19
|
+
coverage?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Result of validate command
|
|
24
|
+
*/
|
|
25
|
+
export interface ValidateResult {
|
|
26
|
+
valid: boolean;
|
|
27
|
+
errors: Array<{
|
|
28
|
+
file: string;
|
|
29
|
+
message: string;
|
|
30
|
+
details?: string;
|
|
31
|
+
}>;
|
|
32
|
+
warnings: Array<{
|
|
33
|
+
file: string;
|
|
34
|
+
message: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Run the validate command
|
|
40
|
+
*/
|
|
41
|
+
export async function validate(options: ValidateOptions = {}): Promise<ValidateResult> {
|
|
42
|
+
const { config, configDir } = await loadConfig(options.config);
|
|
43
|
+
|
|
44
|
+
console.log(pc.cyan(`\n${BRAND.name} Validator\n`));
|
|
45
|
+
|
|
46
|
+
let result: ValidateResult;
|
|
47
|
+
if (options.schema) {
|
|
48
|
+
console.log(pc.dim('Running schema validation...\n'));
|
|
49
|
+
result = await validateSchema(config, configDir);
|
|
50
|
+
} else if (options.coverage) {
|
|
51
|
+
console.log(pc.dim('Running coverage validation...\n'));
|
|
52
|
+
result = await validateCoverage(config, configDir);
|
|
53
|
+
} else {
|
|
54
|
+
console.log(pc.dim('Running all validations...\n'));
|
|
55
|
+
result = await validateAll(config, configDir);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Print errors
|
|
59
|
+
if (result.errors.length > 0) {
|
|
60
|
+
console.log(pc.red(pc.bold('Errors:')));
|
|
61
|
+
for (const error of result.errors) {
|
|
62
|
+
console.log(` ${pc.red('✗')} ${pc.bold(error.file)}`);
|
|
63
|
+
console.log(` ${error.message}`);
|
|
64
|
+
if (error.details) {
|
|
65
|
+
console.log(pc.dim(` ${error.details}`));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Print warnings
|
|
72
|
+
if (result.warnings.length > 0) {
|
|
73
|
+
console.log(pc.yellow(pc.bold('Warnings:')));
|
|
74
|
+
for (const warning of result.warnings) {
|
|
75
|
+
console.log(` ${pc.yellow('⚠')} ${pc.bold(warning.file)}`);
|
|
76
|
+
console.log(` ${warning.message}`);
|
|
77
|
+
}
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Summary
|
|
82
|
+
if (result.valid && result.warnings.length === 0) {
|
|
83
|
+
console.log(pc.green('✓ All validations passed\n'));
|
|
84
|
+
} else if (result.valid) {
|
|
85
|
+
console.log(
|
|
86
|
+
pc.yellow(`⚠ Passed with ${result.warnings.length} warning(s)\n`)
|
|
87
|
+
);
|
|
88
|
+
} else {
|
|
89
|
+
console.log(pc.red(`✗ Failed with ${result.errors.length} error(s)\n`));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return result;
|
|
93
|
+
}
|