@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.
Files changed (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. 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
+ }