@shohojdhara/atomix 0.4.7 → 0.4.9

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 (176) hide show
  1. package/atomix.config.ts +58 -1
  2. package/dist/atomix.css +172 -157
  3. package/dist/atomix.css.map +1 -1
  4. package/dist/atomix.min.css +4 -4
  5. package/dist/atomix.min.css.map +1 -1
  6. package/dist/charts.d.ts +33 -0
  7. package/dist/charts.js +1274 -164
  8. package/dist/charts.js.map +1 -1
  9. package/dist/core.d.ts +33 -10
  10. package/dist/core.js +1099 -83
  11. package/dist/core.js.map +1 -1
  12. package/dist/forms.d.ts +33 -0
  13. package/dist/forms.js +2106 -1050
  14. package/dist/forms.js.map +1 -1
  15. package/dist/heavy.d.ts +42 -1
  16. package/dist/heavy.js +1663 -638
  17. package/dist/heavy.js.map +1 -1
  18. package/dist/index.d.ts +442 -270
  19. package/dist/index.esm.js +1947 -680
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/index.js +1982 -712
  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 +136 -1827
  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 +115 -0
  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 +218 -0
  44. package/scripts/cli/commands/init.js +73 -0
  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/compiler.js +114 -0
  52. package/scripts/cli/internal/component-validator.js +443 -0
  53. package/scripts/cli/internal/config-loader.js +162 -0
  54. package/scripts/cli/internal/filesystem.js +158 -0
  55. package/scripts/cli/internal/generator.js +430 -0
  56. package/scripts/cli/internal/glass-generator.js +398 -0
  57. package/scripts/cli/internal/hook-generator.js +369 -0
  58. package/scripts/cli/internal/hooks.js +61 -0
  59. package/scripts/cli/internal/itcss-generator.js +565 -0
  60. package/scripts/cli/internal/motion-generator.js +679 -0
  61. package/scripts/cli/internal/template-engine.js +301 -0
  62. package/scripts/cli/internal/theme-bridge.js +664 -0
  63. package/scripts/cli/internal/tokens/engine.js +122 -0
  64. package/scripts/cli/internal/tokens/provider.js +34 -0
  65. package/scripts/cli/internal/tokens/providers/figma.js +50 -0
  66. package/scripts/cli/internal/tokens/providers/style-dictionary.js +48 -0
  67. package/scripts/cli/internal/tokens/providers/w3c.js +48 -0
  68. package/scripts/cli/internal/tokens/token-provider.js +443 -0
  69. package/scripts/cli/internal/tokens/token-validator.js +513 -0
  70. package/scripts/cli/internal/validator.js +276 -0
  71. package/scripts/cli/internal/wizard.js +115 -0
  72. package/scripts/cli/mappings.js +23 -0
  73. package/scripts/cli/migration-tools.js +164 -94
  74. package/scripts/cli/plugins/style-dictionary.js +46 -0
  75. package/scripts/cli/templates/README.md +525 -95
  76. package/scripts/cli/templates/common-templates.js +40 -14
  77. package/scripts/cli/templates/components/react-component.ts +282 -0
  78. package/scripts/cli/templates/config/project-config.ts +112 -0
  79. package/scripts/cli/templates/hooks/use-component.ts +477 -0
  80. package/scripts/cli/templates/index.js +19 -4
  81. package/scripts/cli/templates/index.ts +171 -0
  82. package/scripts/cli/templates/next-templates.js +72 -0
  83. package/scripts/cli/templates/react-templates.js +70 -126
  84. package/scripts/cli/templates/scss-templates.js +35 -35
  85. package/scripts/cli/templates/stories/storybook-story.ts +241 -0
  86. package/scripts/cli/templates/styles/scss-component.ts +255 -0
  87. package/scripts/cli/templates/tests/vitest-test.ts +229 -0
  88. package/scripts/cli/templates/token-templates.js +337 -1
  89. package/scripts/cli/templates/tokens/token-generators.ts +1088 -0
  90. package/scripts/cli/templates/types/component-types.ts +145 -0
  91. package/scripts/cli/templates/utils/testing-utils.ts +144 -0
  92. package/scripts/cli/templates/vanilla-templates.js +39 -0
  93. package/scripts/cli/token-manager.js +8 -2
  94. package/scripts/cli/utils/cache-manager.js +240 -0
  95. package/scripts/cli/utils/detector.js +46 -0
  96. package/scripts/cli/utils/diagnostics.js +289 -0
  97. package/scripts/cli/utils/error.js +89 -0
  98. package/scripts/cli/utils/helpers.js +67 -0
  99. package/scripts/cli/utils/logger.js +75 -0
  100. package/scripts/cli/utils/security.js +302 -0
  101. package/scripts/cli/utils/telemetry.js +115 -0
  102. package/scripts/cli/utils/validation.js +37 -0
  103. package/scripts/cli/utils.js +28 -341
  104. package/src/components/Accordion/Accordion.stories.tsx +0 -18
  105. package/src/components/Accordion/Accordion.test.tsx +0 -17
  106. package/src/components/Accordion/Accordion.tsx +0 -4
  107. package/src/components/AtomixGlass/AtomixGlass.test.tsx +37 -3
  108. package/src/components/AtomixGlass/AtomixGlass.tsx +143 -31
  109. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +129 -31
  110. package/src/components/AtomixGlass/PerformanceDashboard.tsx +219 -0
  111. package/src/components/AtomixGlass/README.md +25 -10
  112. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +216 -0
  113. package/src/components/AtomixGlass/animation-system.ts +578 -0
  114. package/src/components/AtomixGlass/shader-utils.ts +4 -1
  115. package/src/components/AtomixGlass/stories/Overview.stories.tsx +157 -6
  116. package/src/components/AtomixGlass/stories/Phase1-Animation.stories.tsx +653 -0
  117. package/src/components/AtomixGlass/stories/Phase1-Test.stories.tsx +95 -0
  118. package/src/components/AtomixGlass/stories/Playground.stories.tsx +51 -51
  119. package/src/components/AtomixGlass/stories/shared-components.tsx +6 -0
  120. package/src/components/Avatar/Avatar.tsx +1 -1
  121. package/src/components/Button/Button.stories.disabled-link.tsx +10 -0
  122. package/src/components/Button/Button.stories.tsx +10 -0
  123. package/src/components/Button/Button.test.tsx +16 -11
  124. package/src/components/Button/Button.tsx +4 -4
  125. package/src/components/Card/Card.tsx +1 -1
  126. package/src/components/Dropdown/Dropdown.tsx +12 -12
  127. package/src/components/Form/Select.tsx +62 -3
  128. package/src/components/Modal/Modal.tsx +14 -3
  129. package/src/components/Navigation/Navbar/Navbar.tsx +44 -0
  130. package/src/components/Slider/Slider.stories.tsx +3 -3
  131. package/src/components/Slider/Slider.tsx +38 -0
  132. package/src/components/Steps/Steps.tsx +3 -3
  133. package/src/components/Tabs/Tabs.tsx +77 -8
  134. package/src/components/Testimonial/Testimonial.tsx +1 -1
  135. package/src/components/TypedButton/TypedButton.stories.tsx +59 -0
  136. package/src/components/TypedButton/TypedButton.tsx +39 -0
  137. package/src/components/TypedButton/index.ts +2 -0
  138. package/src/components/VideoPlayer/VideoPlayer.tsx +11 -4
  139. package/src/lib/composables/index.ts +4 -7
  140. package/src/lib/composables/types.ts +45 -0
  141. package/src/lib/composables/useAccordion.ts +0 -7
  142. package/src/lib/composables/useAtomixGlass.ts +148 -6
  143. package/src/lib/composables/useAtomixGlassStyles.ts +9 -7
  144. package/src/lib/composables/useChartExport.ts +3 -13
  145. package/src/lib/composables/useDropdown.ts +66 -0
  146. package/src/lib/composables/useFocusTrap.ts +80 -0
  147. package/src/lib/composables/usePerformanceMonitor.ts +448 -0
  148. package/src/lib/composables/useResponsiveGlass.presets.ts +192 -0
  149. package/src/lib/composables/useResponsiveGlass.ts +441 -0
  150. package/src/lib/composables/useTooltip.ts +16 -0
  151. package/src/lib/composables/useTypedButton.ts +66 -0
  152. package/src/lib/config/index.ts +62 -5
  153. package/src/lib/constants/components.ts +62 -7
  154. package/src/lib/theme/devtools/__tests__/useHistory.test.tsx +150 -0
  155. package/src/lib/theme/tokens/centralized-tokens.ts +120 -0
  156. package/src/lib/theme/utils/__tests__/domUtils.test.ts +101 -0
  157. package/src/lib/types/components.ts +37 -11
  158. package/src/lib/types/glass.ts +35 -0
  159. package/src/lib/types/index.ts +1 -0
  160. package/src/lib/utils/displacement-generator.ts +1 -1
  161. package/src/styles/01-settings/_settings.testtypecheck.scss +53 -0
  162. package/src/styles/01-settings/_settings.typedbutton.scss +53 -0
  163. package/src/styles/06-components/_components.atomix-glass.scss +17 -21
  164. package/src/styles/06-components/_components.edge-panel.scss +1 -5
  165. package/src/styles/06-components/_components.modal.scss +1 -4
  166. package/src/styles/06-components/_components.navbar.scss +1 -1
  167. package/src/styles/06-components/_components.testbutton.scss +212 -0
  168. package/src/styles/06-components/_components.testtypecheck.scss +212 -0
  169. package/src/styles/06-components/_components.tooltip.scss +9 -5
  170. package/src/styles/06-components/_components.typedbutton.scss +212 -0
  171. package/src/styles/99-utilities/_index.scss +1 -0
  172. package/src/styles/99-utilities/_utilities.text.scss +1 -1
  173. package/src/styles/99-utilities/_utilities.touch-target.scss +36 -0
  174. package/scripts/cli/component-generator.js +0 -564
  175. package/scripts/cli/interactive-init.js +0 -357
  176. 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
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Atomix CLI Build Theme Command
3
+ */
4
+
5
+ import { resolve, basename, dirname } from 'path';
6
+ import chokidar from 'chokidar';
7
+ import { logger } from '../utils/logger.js';
8
+ import { AtomixCLIError } from '../utils/error.js';
9
+ import { filesystem } from '../internal/filesystem.js';
10
+ import { themeCompiler } from '../internal/compiler.js';
11
+ import { sanitizeInput } from '../utils/helpers.js';
12
+
13
+ /**
14
+ * Action logic for building a theme
15
+ * @param {string} themePath - Input path to theme SCSS
16
+ * @param {object} options - Command options
17
+ */
18
+ export async function buildThemeAction(themePath, options) {
19
+ const spinner = logger.spinner('Initializing theme build...').start();
20
+
21
+ try {
22
+ const sanitizedThemePath = sanitizeInput(themePath);
23
+ const themeValidation = filesystem.validatePath(sanitizedThemePath);
24
+
25
+ if (!themeValidation.isValid) {
26
+ throw new AtomixCLIError(
27
+ themeValidation.error,
28
+ 'INVALID_PATH',
29
+ [
30
+ 'Ensure theme path is within the project directory',
31
+ 'Avoid sensitive or absolute system paths'
32
+ ]
33
+ );
34
+ }
35
+
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);
39
+ const outputValidation = filesystem.validatePath(sanitizedOutput);
40
+
41
+ if (!outputValidation.isValid) {
42
+ throw new AtomixCLIError(
43
+ outputValidation.error,
44
+ 'INVALID_PATH',
45
+ ['Use a project-relative directory for output']
46
+ );
47
+ }
48
+
49
+ // Resolve index.scss
50
+ const indexPath = sanitizedThemePath.endsWith('.scss')
51
+ ? resolve(themeValidation.safePath)
52
+ : resolve(themeValidation.safePath, 'index.scss');
53
+
54
+ if (!(await filesystem.exists(indexPath))) {
55
+ throw new AtomixCLIError(
56
+ `Theme file not found: ${indexPath}`,
57
+ 'THEME_NOT_FOUND',
58
+ ['Check if the file path is correct', 'Ensure the file has a .scss extension']
59
+ );
60
+ }
61
+
62
+ const themeName = basename(dirname(indexPath));
63
+
64
+ const performBuild = async () => {
65
+ try {
66
+ await themeCompiler.compile(indexPath, outputValidation.safePath, {
67
+ minify: options.minify,
68
+ sourcemap: options.sourcemap,
69
+ analyze: options.analyze,
70
+ themeName,
71
+ logger: {
72
+ info: (msg) => { spinner.text = msg; },
73
+ debug: (msg) => logger.debug(msg)
74
+ }
75
+ });
76
+
77
+ spinner.succeed(`Theme '${themeName}' built successfully`);
78
+ if (options.watch) {
79
+ logger.info('\n👁️ Watch mode enabled. Rebuilding on changes...');
80
+ }
81
+ } catch (error) {
82
+ spinner.fail(`Build failed: ${error.message}`);
83
+ if (!options.watch) throw error;
84
+ }
85
+ };
86
+
87
+ // Initial build
88
+ await performBuild();
89
+
90
+ // Watch mode
91
+ if (options.watch) {
92
+ const watcher = chokidar.watch([dirname(indexPath)], {
93
+ ignored: /node_modules/,
94
+ persistent: true,
95
+ ignoreInitial: true
96
+ });
97
+
98
+ watcher.on('all', async (event, path) => {
99
+ logger.debug(`File ${event}: ${path}`);
100
+ spinner.start('Rebuilding theme...');
101
+ await performBuild();
102
+ });
103
+
104
+ // Only allowed direct success exit: clean shutdown in watch mode.
105
+ process.on('SIGINT', () => {
106
+ watcher.close();
107
+ process.exit(0);
108
+ });
109
+ }
110
+
111
+ } catch (error) {
112
+ spinner.fail('Operation failed');
113
+ throw error;
114
+ }
115
+ }