@shohojdhara/atomix 0.4.8 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/atomix.config.ts +58 -1
  2. package/dist/atomix.css +148 -120
  3. package/dist/atomix.css.map +1 -1
  4. package/dist/atomix.min.css +1 -1
  5. package/dist/atomix.min.css.map +1 -1
  6. package/dist/charts.d.ts +33 -0
  7. package/dist/charts.js +1227 -122
  8. package/dist/charts.js.map +1 -1
  9. package/dist/core.d.ts +33 -10
  10. package/dist/core.js +1052 -41
  11. package/dist/core.js.map +1 -1
  12. package/dist/forms.d.ts +33 -0
  13. package/dist/forms.js +2086 -1035
  14. package/dist/forms.js.map +1 -1
  15. package/dist/heavy.d.ts +42 -1
  16. package/dist/heavy.js +1620 -600
  17. package/dist/heavy.js.map +1 -1
  18. package/dist/index.d.ts +441 -270
  19. package/dist/index.esm.js +1900 -638
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/index.js +1935 -670
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.min.js +1 -1
  24. package/dist/index.min.js.map +1 -1
  25. package/package.json +6 -3
  26. package/scripts/atomix-cli.js +148 -4
  27. package/scripts/cli/__tests__/basic.test.js +3 -2
  28. package/scripts/cli/__tests__/clean.test.js +278 -0
  29. package/scripts/cli/__tests__/component-validator.test.js +433 -0
  30. package/scripts/cli/__tests__/generator.test.js +613 -0
  31. package/scripts/cli/__tests__/glass-motion.test.js +256 -0
  32. package/scripts/cli/__tests__/integration.test.js +719 -108
  33. package/scripts/cli/__tests__/migrate.test.js +74 -0
  34. package/scripts/cli/__tests__/security.test.js +206 -0
  35. package/scripts/cli/__tests__/test-setup.js +3 -1
  36. package/scripts/cli/__tests__/theme-bridge.test.js +507 -0
  37. package/scripts/cli/__tests__/token-provider.test.js +361 -0
  38. package/scripts/cli/__tests__/utils.test.js +5 -5
  39. package/scripts/cli/commands/benchmark.js +105 -0
  40. package/scripts/cli/commands/build-theme.js +4 -1
  41. package/scripts/cli/commands/clean.js +109 -0
  42. package/scripts/cli/commands/doctor.js +88 -0
  43. package/scripts/cli/commands/generate.js +135 -14
  44. package/scripts/cli/commands/init.js +45 -18
  45. package/scripts/cli/commands/migrate.js +106 -0
  46. package/scripts/cli/commands/sync-tokens.js +206 -0
  47. package/scripts/cli/commands/theme-bridge.js +248 -0
  48. package/scripts/cli/commands/tokens.js +157 -0
  49. package/scripts/cli/commands/validate.js +194 -0
  50. package/scripts/cli/internal/ai-engine.js +156 -0
  51. package/scripts/cli/internal/component-validator.js +443 -0
  52. package/scripts/cli/internal/config-loader.js +162 -0
  53. package/scripts/cli/internal/filesystem.js +102 -2
  54. package/scripts/cli/internal/generator.js +359 -39
  55. package/scripts/cli/internal/glass-generator.js +398 -0
  56. package/scripts/cli/internal/hook-generator.js +369 -0
  57. package/scripts/cli/internal/hooks.js +61 -0
  58. package/scripts/cli/internal/itcss-generator.js +565 -0
  59. package/scripts/cli/internal/motion-generator.js +679 -0
  60. package/scripts/cli/internal/template-engine.js +301 -0
  61. package/scripts/cli/internal/theme-bridge.js +664 -0
  62. package/scripts/cli/internal/tokens/engine.js +122 -0
  63. package/scripts/cli/internal/tokens/provider.js +34 -0
  64. package/scripts/cli/internal/tokens/providers/figma.js +50 -0
  65. package/scripts/cli/internal/tokens/providers/style-dictionary.js +48 -0
  66. package/scripts/cli/internal/tokens/providers/w3c.js +48 -0
  67. package/scripts/cli/internal/tokens/token-provider.js +443 -0
  68. package/scripts/cli/internal/tokens/token-validator.js +513 -0
  69. package/scripts/cli/internal/validator.js +276 -0
  70. package/scripts/cli/internal/wizard.js +60 -6
  71. package/scripts/cli/mappings.js +23 -0
  72. package/scripts/cli/migration-tools.js +164 -94
  73. package/scripts/cli/plugins/style-dictionary.js +46 -0
  74. package/scripts/cli/templates/README.md +525 -95
  75. package/scripts/cli/templates/common-templates.js +40 -14
  76. package/scripts/cli/templates/components/react-component.ts +282 -0
  77. package/scripts/cli/templates/config/project-config.ts +112 -0
  78. package/scripts/cli/templates/hooks/use-component.ts +477 -0
  79. package/scripts/cli/templates/index.js +19 -4
  80. package/scripts/cli/templates/index.ts +171 -0
  81. package/scripts/cli/templates/next-templates.js +72 -0
  82. package/scripts/cli/templates/react-templates.js +70 -126
  83. package/scripts/cli/templates/scss-templates.js +35 -35
  84. package/scripts/cli/templates/stories/storybook-story.ts +241 -0
  85. package/scripts/cli/templates/styles/scss-component.ts +255 -0
  86. package/scripts/cli/templates/tests/vitest-test.ts +229 -0
  87. package/scripts/cli/templates/token-templates.js +337 -1
  88. package/scripts/cli/templates/tokens/token-generators.ts +1088 -0
  89. package/scripts/cli/templates/types/component-types.ts +145 -0
  90. package/scripts/cli/templates/utils/testing-utils.ts +144 -0
  91. package/scripts/cli/templates/vanilla-templates.js +39 -0
  92. package/scripts/cli/token-manager.js +8 -2
  93. package/scripts/cli/utils/cache-manager.js +240 -0
  94. package/scripts/cli/utils/detector.js +46 -0
  95. package/scripts/cli/utils/diagnostics.js +289 -0
  96. package/scripts/cli/utils/error.js +45 -3
  97. package/scripts/cli/utils/helpers.js +24 -0
  98. package/scripts/cli/utils/logger.js +1 -1
  99. package/scripts/cli/utils/security.js +302 -0
  100. package/scripts/cli/utils/telemetry.js +115 -0
  101. package/scripts/cli/utils/validation.js +4 -38
  102. package/scripts/cli/utils.js +46 -0
  103. package/src/components/Accordion/Accordion.stories.tsx +0 -18
  104. package/src/components/Accordion/Accordion.test.tsx +0 -17
  105. package/src/components/Accordion/Accordion.tsx +0 -4
  106. package/src/components/AtomixGlass/AtomixGlass.tsx +102 -2
  107. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +125 -12
  108. package/src/components/AtomixGlass/PerformanceDashboard.tsx +219 -0
  109. package/src/components/AtomixGlass/README.md +25 -10
  110. package/src/components/AtomixGlass/animation-system.ts +578 -0
  111. package/src/components/AtomixGlass/shader-utils.ts +3 -0
  112. package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +653 -0
  113. package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +95 -0
  114. package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +212 -0
  115. package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +348 -0
  116. package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +410 -0
  117. package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +436 -0
  118. package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +264 -0
  119. package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +247 -0
  120. package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +418 -0
  121. package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +402 -0
  122. package/src/components/AtomixGlass/stories/Overview.stories.tsx +157 -6
  123. package/src/components/AtomixGlass/stories/Playground.stories.tsx +658 -93
  124. package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +335 -0
  125. package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +441 -0
  126. package/src/components/AtomixGlass/stories/argTypes.ts +384 -0
  127. package/src/components/AtomixGlass/stories/shared-components.tsx +91 -1
  128. package/src/components/AtomixGlass/stories/types.ts +127 -0
  129. package/src/components/Avatar/Avatar.tsx +1 -1
  130. package/src/components/Button/Button.stories.disabled-link.tsx +10 -0
  131. package/src/components/Button/Button.stories.tsx +10 -0
  132. package/src/components/Button/Button.test.tsx +16 -11
  133. package/src/components/Button/Button.tsx +4 -4
  134. package/src/components/Card/Card.tsx +1 -1
  135. package/src/components/Dropdown/Dropdown.tsx +12 -12
  136. package/src/components/Form/Select.tsx +62 -3
  137. package/src/components/Modal/Modal.tsx +14 -3
  138. package/src/components/Navigation/Navbar/Navbar.tsx +44 -0
  139. package/src/components/Slider/Slider.stories.tsx +3 -3
  140. package/src/components/Slider/Slider.tsx +38 -0
  141. package/src/components/Steps/Steps.tsx +3 -3
  142. package/src/components/Tabs/Tabs.tsx +77 -8
  143. package/src/components/Testimonial/Testimonial.tsx +1 -1
  144. package/src/components/TypedButton/TypedButton.stories.tsx +59 -0
  145. package/src/components/TypedButton/TypedButton.tsx +39 -0
  146. package/src/components/TypedButton/index.ts +2 -0
  147. package/src/components/VideoPlayer/VideoPlayer.tsx +11 -4
  148. package/src/lib/composables/index.ts +4 -7
  149. package/src/lib/composables/types.ts +45 -0
  150. package/src/lib/composables/useAccordion.ts +0 -7
  151. package/src/lib/composables/useAtomixGlass.ts +144 -5
  152. package/src/lib/composables/useChartExport.ts +3 -13
  153. package/src/lib/composables/useDropdown.ts +66 -0
  154. package/src/lib/composables/useFocusTrap.ts +80 -0
  155. package/src/lib/composables/usePerformanceMonitor.ts +448 -0
  156. package/src/lib/composables/useResponsiveGlass.presets.ts +192 -0
  157. package/src/lib/composables/useResponsiveGlass.ts +441 -0
  158. package/src/lib/composables/useTooltip.ts +16 -0
  159. package/src/lib/composables/useTypedButton.ts +66 -0
  160. package/src/lib/config/index.ts +62 -5
  161. package/src/lib/constants/components.ts +55 -0
  162. package/src/lib/theme/devtools/__tests__/useHistory.test.tsx +150 -0
  163. package/src/lib/theme/tokens/centralized-tokens.ts +120 -0
  164. package/src/lib/theme/utils/__tests__/domUtils.test.ts +101 -0
  165. package/src/lib/types/components.ts +37 -11
  166. package/src/lib/types/glass.ts +35 -0
  167. package/src/lib/types/index.ts +1 -0
  168. package/src/lib/utils/displacement-generator.ts +1 -1
  169. package/src/styles/01-settings/_settings.testtypecheck.scss +53 -0
  170. package/src/styles/01-settings/_settings.typedbutton.scss +53 -0
  171. package/src/styles/06-components/_components.testbutton.scss +212 -0
  172. package/src/styles/06-components/_components.testtypecheck.scss +212 -0
  173. package/src/styles/06-components/_components.typedbutton.scss +212 -0
  174. package/src/styles/99-utilities/_index.scss +1 -0
  175. package/src/styles/99-utilities/_utilities.text.scss +1 -1
  176. package/src/styles/99-utilities/_utilities.touch-target.scss +36 -0
  177. package/src/styles/06-components/old.chart.styles.scss +0 -2788
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Token Provider & Validator Tests
3
+ * Tests for Phase 1: Enhanced Design Token Integration
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach } from 'vitest';
7
+ import {
8
+ TokenProvider,
9
+ TOKEN_FORMATS,
10
+ TOKEN_CATEGORIES,
11
+ tokenProvider
12
+ } from '../internal/tokens/token-provider.js';
13
+ import {
14
+ TokenValidator,
15
+ VALIDATION_RULES,
16
+ SEVERITY,
17
+ tokenValidator
18
+ } from '../internal/tokens/token-validator.js';
19
+
20
+ describe('TokenProvider', () => {
21
+ let provider;
22
+
23
+ beforeEach(() => {
24
+ provider = new TokenProvider();
25
+ });
26
+
27
+ describe('Initialization', () => {
28
+ it('should create a new TokenProvider instance', () => {
29
+ expect(provider).toBeInstanceOf(TokenProvider);
30
+ });
31
+
32
+ it('should have default options', () => {
33
+ expect(provider.tokenPath).toBe('./design-tokens');
34
+ expect(provider.format).toBe(TOKEN_FORMATS.JSON);
35
+ expect(provider.tokens).toEqual({});
36
+ });
37
+
38
+ it('should accept custom options', () => {
39
+ const customProvider = new TokenProvider({
40
+ tokenPath: './custom-tokens',
41
+ format: TOKEN_FORMATS.CSS
42
+ });
43
+
44
+ expect(customProvider.tokenPath).toBe('./custom-tokens');
45
+ expect(customProvider.format).toBe(TOKEN_FORMATS.CSS);
46
+ });
47
+ });
48
+
49
+ describe('Token Loading', () => {
50
+ it('should throw error when file not found', async () => {
51
+ await expect(provider.loadTokens('./non-existent.json'))
52
+ .rejects
53
+ .toThrow('Token file not found');
54
+ });
55
+
56
+ it('should categorize tokens correctly', () => {
57
+ const categorizeToken = provider.categorizeToken.bind(provider);
58
+
59
+ expect(categorizeToken('color-primary')).toBe(TOKEN_CATEGORIES.COLOR);
60
+ expect(categorizeToken('space-sm')).toBe(TOKEN_CATEGORIES.SPACING);
61
+ expect(categorizeToken('font-size-lg')).toBe(TOKEN_CATEGORIES.TYPOGRAPHY);
62
+ expect(categorizeToken('shadow-md')).toBe(TOKEN_CATEGORIES.SHADOW);
63
+ expect(categorizeToken('radius-lg')).toBe(TOKEN_CATEGORIES.RADIUS);
64
+ expect(categorizeToken('duration-fast')).toBe(TOKEN_CATEGORIES.ANIMATION);
65
+ });
66
+
67
+ it('should infer token types from values', () => {
68
+ const inferTokenType = provider.inferTokenType.bind(provider);
69
+
70
+ expect(inferTokenType('#ff0000')).toBe('color');
71
+ expect(inferTokenType('rgb(255, 0, 0)')).toBe('color');
72
+ expect(inferTokenType('hsl(0, 100%, 50%)')).toBe('color');
73
+ expect(inferTokenType('16px')).toBe('dimension');
74
+ expect(inferTokenType('1rem')).toBe('dimension');
75
+ expect(inferTokenType('300ms')).toBe('duration');
76
+ expect(inferTokenType('0.5s')).toBe('duration');
77
+ expect(inferTokenType(42)).toBe('number');
78
+ });
79
+ });
80
+
81
+ describe('Token Merging', () => {
82
+ it('should merge multiple token sets', () => {
83
+ const existing = {
84
+ color: { primary: { value: '#000' } },
85
+ spacing: { sm: { value: '8px' } }
86
+ };
87
+
88
+ const newTokens = {
89
+ color: { secondary: { value: '#fff' } },
90
+ typography: { base: { value: '16px' } }
91
+ };
92
+
93
+ const merged = provider.mergeTokens(existing, newTokens);
94
+
95
+ expect(merged.color.primary).toBeDefined();
96
+ expect(merged.color.secondary).toBeDefined();
97
+ expect(merged.spacing.sm).toBeDefined();
98
+ expect(merged.typography.base).toBeDefined();
99
+ });
100
+ });
101
+
102
+ describe('Token Export', () => {
103
+ beforeEach(() => {
104
+ provider.tokens = {
105
+ color: {
106
+ primary: { value: '#007bff', type: 'color' },
107
+ secondary: { value: '#6c757d', type: 'color' }
108
+ },
109
+ spacing: {
110
+ sm: { value: '8px', type: 'dimension' },
111
+ lg: { value: '16px', type: 'dimension' }
112
+ }
113
+ };
114
+ });
115
+
116
+ it('should export to JSON format', () => {
117
+ const json = provider.exportTokens(TOKEN_FORMATS.JSON, { pretty: false });
118
+ const parsed = JSON.parse(json);
119
+
120
+ expect(parsed.color.primary.value).toBe('#007bff');
121
+ expect(parsed.spacing.lg.value).toBe('16px');
122
+ });
123
+
124
+ it('should export to CSS custom properties', () => {
125
+ const css = provider.exportTokens(TOKEN_FORMATS.CSS, {
126
+ selector: ':root',
127
+ prefix: 'atomix'
128
+ });
129
+
130
+ expect(css).toContain(':root {');
131
+ expect(css).toContain('--atomix-color-primary: #007bff;');
132
+ expect(css).toContain('--atomix-spacing-lg: 16px;');
133
+ });
134
+
135
+ it('should export to W3C DTCG format', () => {
136
+ const dtcg = provider.exportTokens(TOKEN_FORMATS.W3C_DTCG);
137
+
138
+ expect(dtcg.$schema).toBe('https://design-tokens.org/schema.json');
139
+ expect(dtcg.tokens.color.primary.value).toBe('#007bff');
140
+ });
141
+ });
142
+ });
143
+
144
+ describe('TokenValidator', () => {
145
+ let validator;
146
+
147
+ beforeEach(() => {
148
+ validator = new TokenValidator();
149
+ });
150
+
151
+ describe('Initialization', () => {
152
+ it('should create a new TokenValidator instance', () => {
153
+ expect(validator).toBeInstanceOf(TokenValidator);
154
+ });
155
+
156
+ it('should register all built-in rules', () => {
157
+ expect(validator.rules.size).toBeGreaterThan(0);
158
+ expect(validator.rules.has('COLOR_CONTRAST')).toBe(true);
159
+ expect(validator.rules.has('SEMANTIC_NAMING')).toBe(true);
160
+ expect(validator.rules.has('NO_HARDCODED_COLORS')).toBe(true);
161
+ });
162
+
163
+ it('should have all rules enabled by default', () => {
164
+ expect(validator.enabledRules.length).toBeGreaterThan(0);
165
+ });
166
+ });
167
+
168
+ describe('Validation Rules', () => {
169
+ it('should validate color contrast', () => {
170
+ const tokens = {
171
+ color: {
172
+ text: { value: '#999999' },
173
+ background: { value: '#ffffff' }
174
+ }
175
+ };
176
+
177
+ const result = validator.validate(tokens);
178
+
179
+ // Low contrast should be flagged
180
+ const contrastIssue = result.issues.find(i => i.rule === 'color-contrast');
181
+ expect(contrastIssue).toBeDefined();
182
+ expect(contrastIssue.severity).toBe(SEVERITY.ERROR);
183
+ });
184
+
185
+ it('should detect semantic naming issues', () => {
186
+ const tokens = {
187
+ color: {
188
+ blue: { value: '#0000ff' },
189
+ red: { value: '#ff0000' }
190
+ }
191
+ };
192
+
193
+ const result = validator.validate(tokens);
194
+
195
+ const namingIssue = result.issues.find(i => i.rule === 'semantic-naming');
196
+ expect(namingIssue).toBeDefined();
197
+ expect(namingIssue.severity).toBe(SEVERITY.WARNING);
198
+ });
199
+
200
+ it('should pass semantic naming for brand colors', () => {
201
+ const tokens = {
202
+ color: {
203
+ brandBlue: { value: '#0000ff' },
204
+ primaryRed: { value: '#ff0000' }
205
+ }
206
+ };
207
+
208
+ const result = validator.validate(tokens);
209
+
210
+ // Filter out token-completeness warnings which are expected
211
+ const namingIssue = result.issues.find(i => i.rule === 'semantic-naming');
212
+ // Should not flag brand colors or primary/secondary prefixed colors
213
+ expect(namingIssue).toBeUndefined();
214
+ });
215
+
216
+ it('should detect hardcoded colors in code', () => {
217
+ const codeContent = `
218
+ const styles = {
219
+ color: '#ff0000',
220
+ backgroundColor: 'rgb(0, 123, 255)',
221
+ borderColor: 'hsl(120, 100%, 50%)'
222
+ };
223
+ `;
224
+
225
+ const result = validator.validate({}, { codeContent });
226
+
227
+ const hardcodedIssue = result.issues.find(i => i.rule === 'no-hardcoded-colors');
228
+ expect(hardcodedIssue).toBeDefined();
229
+ expect(hardcodedIssue.matches).toHaveLength(3);
230
+ });
231
+
232
+ it('should validate token completeness', () => {
233
+ const tokens = {
234
+ color: { primary: { value: '#000' } }
235
+ // Missing spacing and typography
236
+ };
237
+
238
+ const result = validator.validate(tokens);
239
+
240
+ const completenessIssues = result.issues.filter(i => i.rule === 'token-completeness');
241
+ expect(completenessIssues.length).toBeGreaterThan(0);
242
+ });
243
+
244
+ it('should detect duplicate tokens', () => {
245
+ const tokens = {
246
+ color: {
247
+ primary: { value: '#007bff' },
248
+ main: { value: '#007bff' }
249
+ },
250
+ spacing: {
251
+ sm: { value: '8px' },
252
+ small: { value: '8px' }
253
+ }
254
+ };
255
+
256
+ const result = validator.validate(tokens);
257
+
258
+ const duplicateIssues = result.issues.filter(i => i.rule === 'duplicate-detection');
259
+ expect(duplicateIssues.length).toBeGreaterThan(0);
260
+ });
261
+ });
262
+
263
+ describe('Rule Management', () => {
264
+ it('should toggle rules on/off', () => {
265
+ const initialCount = validator.enabledRules.length;
266
+
267
+ validator.toggleRule('COLOR_CONTRAST', false);
268
+ expect(validator.enabledRules).not.toContain('COLOR_CONTRAST');
269
+
270
+ validator.toggleRule('COLOR_CONTRAST', true);
271
+ expect(validator.enabledRules).toContain('COLOR_CONTRAST');
272
+ });
273
+
274
+ it('should allow registering custom rules', () => {
275
+ const customRule = {
276
+ name: 'custom-rule',
277
+ description: 'Custom validation rule',
278
+ severity: SEVERITY.INFO,
279
+ validate: (tokens) => []
280
+ };
281
+
282
+ validator.registerRule('CUSTOM_RULE', customRule);
283
+
284
+ expect(validator.rules.has('CUSTOM_RULE')).toBe(true);
285
+ });
286
+
287
+ it('should reject invalid rules', () => {
288
+ expect(() => {
289
+ validator.registerRule('INVALID', { name: 'test' });
290
+ }).toThrow('must have name, validate, and severity');
291
+ });
292
+ });
293
+
294
+ describe('Validation Report', () => {
295
+ it('should generate formatted report', () => {
296
+ const tokens = {
297
+ color: {
298
+ lowContrast: { value: '#ccc' },
299
+ background: { value: '#fff' }
300
+ }
301
+ };
302
+
303
+ const result = validator.validate(tokens);
304
+ const report = validator.getReport(result);
305
+
306
+ expect(report).toContain('Token Validation Report');
307
+ expect(report).toContain('Status:');
308
+ expect(report).toContain('Summary:');
309
+ expect(report).toContain('Errors:');
310
+ });
311
+ });
312
+ });
313
+
314
+ describe('Integration Tests', () => {
315
+ it('should load and validate tokens together', async () => {
316
+ const provider = new TokenProvider();
317
+ const validator = new TokenValidator();
318
+
319
+ // Simulate loading tokens
320
+ provider.tokens = {
321
+ color: {
322
+ primary: { value: '#007bff', type: 'color' },
323
+ success: { value: '#22c55e', type: 'color' }
324
+ },
325
+ spacing: {
326
+ 4: { value: '1rem', type: 'dimension' },
327
+ 8: { value: '2rem', type: 'dimension' }
328
+ }
329
+ };
330
+
331
+ const tokens = provider.getAllTokens();
332
+ const result = validator.validate(tokens);
333
+
334
+ expect(result).toBeDefined();
335
+ expect(typeof result.valid).toBe('boolean');
336
+ expect(result.summary).toBeDefined();
337
+ });
338
+
339
+ it('should validate component with loaded tokens', () => {
340
+ const provider = new TokenProvider();
341
+ const validator = new TokenValidator();
342
+
343
+ provider.tokens = {
344
+ color: {
345
+ primary: { value: '#007bff' }
346
+ }
347
+ };
348
+
349
+ const cleanCode = `
350
+ const Component = () => {
351
+ return <div style={{ color: 'var(--color-primary)' }} />;
352
+ };
353
+ `;
354
+
355
+ const result = validator.validateComponent(cleanCode, provider.getAllTokens());
356
+
357
+ // Should not have hardcoded color errors
358
+ const hardcodedIssue = result.issues.find(i => i.rule === 'no-hardcoded-colors');
359
+ expect(hardcodedIssue).toBeUndefined();
360
+ });
361
+ });
@@ -30,13 +30,13 @@ describe('CLI Utils', () => {
30
30
  it('should accept valid relative paths', () => {
31
31
  const result = validatePath('./src/components', tempDir);
32
32
  expect(result.isValid).toBe(true);
33
- expect(result.safePath).toContain('src/components');
33
+ expect(result.safePath != null ? result.safePath : '').toMatch(/src\/components/);
34
34
  });
35
35
 
36
36
  it('should reject paths outside project directory', () => {
37
37
  const result = validatePath('../../etc/passwd', tempDir);
38
38
  expect(result.isValid).toBe(false);
39
- expect(result.error).toContain('outside the project directory');
39
+ expect(result.error).toMatch(/outside|traversal/);
40
40
  });
41
41
 
42
42
  it('should reject sensitive files', () => {
@@ -54,7 +54,7 @@ describe('CLI Utils', () => {
54
54
  it('should normalize paths correctly', () => {
55
55
  const result = validatePath('./src/../src/components', tempDir);
56
56
  expect(result.isValid).toBe(true);
57
- expect(result.safePath).toContain('src/components');
57
+ expect(result.safePath != null ? result.safePath : '').toMatch(/src\/components/);
58
58
  });
59
59
  });
60
60
 
@@ -69,8 +69,8 @@ describe('CLI Utils', () => {
69
69
  });
70
70
 
71
71
  it('should reject invalid names', () => {
72
- const invalidNames = ['button', 'button-primary', 'Button-Primary', '123Button', ''];
73
-
72
+ const invalidNames = ['123Button', '', '1', 'Component', 'React'];
73
+
74
74
  invalidNames.forEach(name => {
75
75
  const result = validateComponentName(name);
76
76
  expect(result.isValid).toBe(false);
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Atomix CLI - Benchmark Command
3
+ * Profiles CLI speed and provides performance metrics
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import { logger } from '../utils/logger.js';
8
+ import { telemetry } from '../utils/telemetry.js';
9
+
10
+ /**
11
+ * Benchmark action handler
12
+ */
13
+ export async function benchmarkAction(options) {
14
+ logger.info(chalk.blue('📊 CLI Performance Benchmark...'));
15
+
16
+ const logs = await telemetry.getLogs();
17
+
18
+ if (logs.length === 0) {
19
+ logger.info('Benchmark collects metrics from previous CLI runs.');
20
+ logger.warn('No performance data found. Run some commands first!');
21
+ return;
22
+ }
23
+
24
+ // Calculate stats
25
+ const stats = calculateStats(logs);
26
+
27
+ // Display results
28
+ displayBenchmarkResults(stats);
29
+ }
30
+
31
+ /**
32
+ * Calculate performance statistics from logs
33
+ */
34
+ function calculateStats(logs) {
35
+ const byCommand = {};
36
+
37
+ logs.forEach(log => {
38
+ if (!byCommand[log.command]) {
39
+ byCommand[log.command] = {
40
+ name: log.command,
41
+ runs: 0,
42
+ successes: 0,
43
+ totalDuration: 0,
44
+ min: Infinity,
45
+ max: -Infinity,
46
+ durations: []
47
+ };
48
+ }
49
+
50
+ const cmd = byCommand[log.command];
51
+ cmd.runs++;
52
+ if (log.success) cmd.successes++;
53
+ cmd.totalDuration += log.duration;
54
+ cmd.min = Math.min(cmd.min, log.duration);
55
+ cmd.max = Math.max(cmd.max, log.duration);
56
+ cmd.durations.push(log.duration);
57
+ });
58
+
59
+ // Calculate averages and percentiles
60
+ Object.values(byCommand).forEach(cmd => {
61
+ cmd.avg = parseFloat((cmd.totalDuration / cmd.runs).toFixed(2));
62
+
63
+ // Simple 95th percentile
64
+ cmd.durations.sort((a, b) => a - b);
65
+ const p95Index = Math.floor(cmd.durations.length * 0.95);
66
+ cmd.p95 = cmd.durations[p95Index];
67
+ });
68
+
69
+ return Object.values(byCommand);
70
+ }
71
+
72
+ /**
73
+ * Display benchmark results in a clean table
74
+ */
75
+ function displayBenchmarkResults(stats) {
76
+ console.log('\n' + chalk.bold.underline('CLI Performance Metrics (Local)'));
77
+ console.log(chalk.gray('Last 100 commands recorded\n'));
78
+
79
+ // Header
80
+ console.log(
81
+ chalk.bold(
82
+ 'Command'.padEnd(15) +
83
+ 'Runs'.padEnd(8) +
84
+ 'Avg (ms)'.padEnd(12) +
85
+ 'P95 (ms)'.padEnd(12) +
86
+ 'Success Rate'
87
+ )
88
+ );
89
+ console.log(chalk.gray('-'.repeat(60)));
90
+
91
+ stats.forEach(s => {
92
+ const successRate = ((s.successes / s.runs) * 100).toFixed(0) + '%';
93
+ const color = s.avg < 1000 ? chalk.green : s.avg < 2000 ? chalk.yellow : chalk.red;
94
+
95
+ console.log(
96
+ s.name.padEnd(15) +
97
+ s.runs.toString().padEnd(8) +
98
+ color(s.avg.toString().padEnd(12)) +
99
+ color(s.p95.toString().padEnd(12)) +
100
+ (s.successes === s.runs ? chalk.green(successRate) : chalk.yellow(successRate))
101
+ );
102
+ });
103
+
104
+ console.log('\n' + chalk.blue('Performance Budget: ') + chalk.bold('< 2000ms per command'));
105
+ }
@@ -33,7 +33,9 @@ export async function buildThemeAction(themePath, options) {
33
33
  );
34
34
  }
35
35
 
36
- const sanitizedOutput = sanitizeInput(options.output || './dist');
36
+ // Default output directory; may overwrite app build—use -o dist-theme for custom themes.
37
+ const outputDir = options.output ?? './dist';
38
+ const sanitizedOutput = sanitizeInput(outputDir);
37
39
  const outputValidation = filesystem.validatePath(sanitizedOutput);
38
40
 
39
41
  if (!outputValidation.isValid) {
@@ -99,6 +101,7 @@ export async function buildThemeAction(themePath, options) {
99
101
  await performBuild();
100
102
  });
101
103
 
104
+ // Only allowed direct success exit: clean shutdown in watch mode.
102
105
  process.on('SIGINT', () => {
103
106
  watcher.close();
104
107
  process.exit(0);
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Atomix CLI Clean Command
3
+ * Safely clean build artifacts and cache files
4
+ */
5
+
6
+ import { logger } from '../utils/logger.js';
7
+ import { AtomixCLIError } from '../utils/error.js';
8
+ import { cacheManager } from '../utils/cache-manager.js';
9
+
10
+ /**
11
+ * Action logic for cleaning build artifacts
12
+ * @param {object} options - Command options
13
+ * @param {boolean} options.all - Clean node_modules as well
14
+ * @param {boolean} options.cache - Only clean cache directories
15
+ * @param {boolean} options.dryRun - Preview without deleting
16
+ * @param {boolean} options.verbose - Show detailed output
17
+ */
18
+ export async function cleanAction(options) {
19
+ const spinner = logger.spinner('Analyzing project...').start();
20
+
21
+ try {
22
+ // Step 1: Identify what can be cleaned
23
+ spinner.text = 'Scanning for cleanable files...';
24
+ const targets = await cacheManager.identifyTargets(options);
25
+
26
+ // Filter out protected files
27
+ const safeTargets = targets.filter(target => {
28
+ if (cacheManager.isProtected(target.path)) {
29
+ logger.debug(`Skipping protected file: ${target.path}`);
30
+ return false;
31
+ }
32
+ return true;
33
+ });
34
+
35
+ // Step 2: Handle empty state
36
+ if (safeTargets.length === 0) {
37
+ spinner.succeed('Nothing to clean! Your project is already tidy.');
38
+ return;
39
+ }
40
+
41
+ // Step 3: Calculate total size
42
+ const totalSize = await cacheManager.calculateSize(safeTargets);
43
+ const formattedSize = cacheManager.formatBytes(totalSize);
44
+
45
+ // Step 4: Handle dry-run mode
46
+ if (options.dryRun || process.env.ATOMIX_DRY_RUN === 'true') {
47
+ spinner.stop();
48
+ cacheManager.displayDryRun(safeTargets);
49
+ logger.info(
50
+ `\nℹ️ Total size: ${formattedSize}\n` +
51
+ `⚠️ Run without --dry-run to actually delete these files.`
52
+ );
53
+ return;
54
+ }
55
+
56
+ // Step 5: Perform cleanup
57
+ spinner.text = `Cleaning ${safeTargets.length} item(s)...`;
58
+
59
+ let successCount = 0;
60
+ let errorCount = 0;
61
+ const errors = [];
62
+
63
+ for (const target of safeTargets) {
64
+ try {
65
+ if (options.verbose) {
66
+ logger.info(`Removing: ${target.relativePath}`);
67
+ }
68
+
69
+ await cacheManager.deletePath(target.path, options);
70
+ successCount++;
71
+ } catch (error) {
72
+ errorCount++;
73
+ errors.push({
74
+ path: target.relativePath,
75
+ error: error.message
76
+ });
77
+ logger.debug(`Failed to delete ${target.path}: ${error.message}`);
78
+ }
79
+ }
80
+
81
+ // Step 6: Report results
82
+ if (errorCount > 0) {
83
+ spinner.warn(`Cleanup completed with ${errorCount} warning(s)`);
84
+
85
+ if (options.verbose && errors.length > 0) {
86
+ logger.warn('\nWarnings:');
87
+ errors.forEach(err => {
88
+ logger.warn(` • ${err.path}: ${err.error}`);
89
+ });
90
+ }
91
+ } else {
92
+ spinner.succeed(`Cleanup completed! Removed ${successCount} item(s) (${formattedSize})`);
93
+ }
94
+
95
+ } catch (error) {
96
+ spinner.fail('Cleanup failed');
97
+
98
+ // Re-throw as AtomixCLIError with helpful suggestions
99
+ throw new AtomixCLIError(
100
+ error.message,
101
+ 'FILESYSTEM_ERROR',
102
+ [
103
+ 'Check if you have write permissions for the target directories',
104
+ 'Ensure no files are locked by another process',
105
+ 'Try running with --verbose to see which file caused the issue'
106
+ ]
107
+ );
108
+ }
109
+ }