@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.
- package/atomix.config.ts +58 -1
- package/dist/atomix.css +148 -120
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +1 -1
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +33 -0
- package/dist/charts.js +1227 -122
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +33 -10
- package/dist/core.js +1052 -41
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +33 -0
- package/dist/forms.js +2086 -1035
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +42 -1
- package/dist/heavy.js +1620 -600
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +441 -270
- package/dist/index.esm.js +1900 -638
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1935 -670
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +6 -3
- package/scripts/atomix-cli.js +148 -4
- package/scripts/cli/__tests__/basic.test.js +3 -2
- package/scripts/cli/__tests__/clean.test.js +278 -0
- package/scripts/cli/__tests__/component-validator.test.js +433 -0
- package/scripts/cli/__tests__/generator.test.js +613 -0
- package/scripts/cli/__tests__/glass-motion.test.js +256 -0
- package/scripts/cli/__tests__/integration.test.js +719 -108
- package/scripts/cli/__tests__/migrate.test.js +74 -0
- package/scripts/cli/__tests__/security.test.js +206 -0
- package/scripts/cli/__tests__/test-setup.js +3 -1
- package/scripts/cli/__tests__/theme-bridge.test.js +507 -0
- package/scripts/cli/__tests__/token-provider.test.js +361 -0
- package/scripts/cli/__tests__/utils.test.js +5 -5
- package/scripts/cli/commands/benchmark.js +105 -0
- package/scripts/cli/commands/build-theme.js +4 -1
- package/scripts/cli/commands/clean.js +109 -0
- package/scripts/cli/commands/doctor.js +88 -0
- package/scripts/cli/commands/generate.js +135 -14
- package/scripts/cli/commands/init.js +45 -18
- package/scripts/cli/commands/migrate.js +106 -0
- package/scripts/cli/commands/sync-tokens.js +206 -0
- package/scripts/cli/commands/theme-bridge.js +248 -0
- package/scripts/cli/commands/tokens.js +157 -0
- package/scripts/cli/commands/validate.js +194 -0
- package/scripts/cli/internal/ai-engine.js +156 -0
- package/scripts/cli/internal/component-validator.js +443 -0
- package/scripts/cli/internal/config-loader.js +162 -0
- package/scripts/cli/internal/filesystem.js +102 -2
- package/scripts/cli/internal/generator.js +359 -39
- package/scripts/cli/internal/glass-generator.js +398 -0
- package/scripts/cli/internal/hook-generator.js +369 -0
- package/scripts/cli/internal/hooks.js +61 -0
- package/scripts/cli/internal/itcss-generator.js +565 -0
- package/scripts/cli/internal/motion-generator.js +679 -0
- package/scripts/cli/internal/template-engine.js +301 -0
- package/scripts/cli/internal/theme-bridge.js +664 -0
- package/scripts/cli/internal/tokens/engine.js +122 -0
- package/scripts/cli/internal/tokens/provider.js +34 -0
- package/scripts/cli/internal/tokens/providers/figma.js +50 -0
- package/scripts/cli/internal/tokens/providers/style-dictionary.js +48 -0
- package/scripts/cli/internal/tokens/providers/w3c.js +48 -0
- package/scripts/cli/internal/tokens/token-provider.js +443 -0
- package/scripts/cli/internal/tokens/token-validator.js +513 -0
- package/scripts/cli/internal/validator.js +276 -0
- package/scripts/cli/internal/wizard.js +60 -6
- package/scripts/cli/mappings.js +23 -0
- package/scripts/cli/migration-tools.js +164 -94
- package/scripts/cli/plugins/style-dictionary.js +46 -0
- package/scripts/cli/templates/README.md +525 -95
- package/scripts/cli/templates/common-templates.js +40 -14
- package/scripts/cli/templates/components/react-component.ts +282 -0
- package/scripts/cli/templates/config/project-config.ts +112 -0
- package/scripts/cli/templates/hooks/use-component.ts +477 -0
- package/scripts/cli/templates/index.js +19 -4
- package/scripts/cli/templates/index.ts +171 -0
- package/scripts/cli/templates/next-templates.js +72 -0
- package/scripts/cli/templates/react-templates.js +70 -126
- package/scripts/cli/templates/scss-templates.js +35 -35
- package/scripts/cli/templates/stories/storybook-story.ts +241 -0
- package/scripts/cli/templates/styles/scss-component.ts +255 -0
- package/scripts/cli/templates/tests/vitest-test.ts +229 -0
- package/scripts/cli/templates/token-templates.js +337 -1
- package/scripts/cli/templates/tokens/token-generators.ts +1088 -0
- package/scripts/cli/templates/types/component-types.ts +145 -0
- package/scripts/cli/templates/utils/testing-utils.ts +144 -0
- package/scripts/cli/templates/vanilla-templates.js +39 -0
- package/scripts/cli/token-manager.js +8 -2
- package/scripts/cli/utils/cache-manager.js +240 -0
- package/scripts/cli/utils/detector.js +46 -0
- package/scripts/cli/utils/diagnostics.js +289 -0
- package/scripts/cli/utils/error.js +45 -3
- package/scripts/cli/utils/helpers.js +24 -0
- package/scripts/cli/utils/logger.js +1 -1
- package/scripts/cli/utils/security.js +302 -0
- package/scripts/cli/utils/telemetry.js +115 -0
- package/scripts/cli/utils/validation.js +4 -38
- package/scripts/cli/utils.js +46 -0
- package/src/components/Accordion/Accordion.stories.tsx +0 -18
- package/src/components/Accordion/Accordion.test.tsx +0 -17
- package/src/components/Accordion/Accordion.tsx +0 -4
- package/src/components/AtomixGlass/AtomixGlass.tsx +102 -2
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +125 -12
- package/src/components/AtomixGlass/PerformanceDashboard.tsx +219 -0
- package/src/components/AtomixGlass/README.md +25 -10
- package/src/components/AtomixGlass/animation-system.ts +578 -0
- package/src/components/AtomixGlass/shader-utils.ts +3 -0
- package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +653 -0
- package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +95 -0
- package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +212 -0
- package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +348 -0
- package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +410 -0
- package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +436 -0
- package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +264 -0
- package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +247 -0
- package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +418 -0
- package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +402 -0
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +157 -6
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +658 -93
- package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +335 -0
- package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +441 -0
- package/src/components/AtomixGlass/stories/argTypes.ts +384 -0
- package/src/components/AtomixGlass/stories/shared-components.tsx +91 -1
- package/src/components/AtomixGlass/stories/types.ts +127 -0
- package/src/components/Avatar/Avatar.tsx +1 -1
- package/src/components/Button/Button.stories.disabled-link.tsx +10 -0
- package/src/components/Button/Button.stories.tsx +10 -0
- package/src/components/Button/Button.test.tsx +16 -11
- package/src/components/Button/Button.tsx +4 -4
- package/src/components/Card/Card.tsx +1 -1
- package/src/components/Dropdown/Dropdown.tsx +12 -12
- package/src/components/Form/Select.tsx +62 -3
- package/src/components/Modal/Modal.tsx +14 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +44 -0
- package/src/components/Slider/Slider.stories.tsx +3 -3
- package/src/components/Slider/Slider.tsx +38 -0
- package/src/components/Steps/Steps.tsx +3 -3
- package/src/components/Tabs/Tabs.tsx +77 -8
- package/src/components/Testimonial/Testimonial.tsx +1 -1
- package/src/components/TypedButton/TypedButton.stories.tsx +59 -0
- package/src/components/TypedButton/TypedButton.tsx +39 -0
- package/src/components/TypedButton/index.ts +2 -0
- package/src/components/VideoPlayer/VideoPlayer.tsx +11 -4
- package/src/lib/composables/index.ts +4 -7
- package/src/lib/composables/types.ts +45 -0
- package/src/lib/composables/useAccordion.ts +0 -7
- package/src/lib/composables/useAtomixGlass.ts +144 -5
- package/src/lib/composables/useChartExport.ts +3 -13
- package/src/lib/composables/useDropdown.ts +66 -0
- package/src/lib/composables/useFocusTrap.ts +80 -0
- package/src/lib/composables/usePerformanceMonitor.ts +448 -0
- package/src/lib/composables/useResponsiveGlass.presets.ts +192 -0
- package/src/lib/composables/useResponsiveGlass.ts +441 -0
- package/src/lib/composables/useTooltip.ts +16 -0
- package/src/lib/composables/useTypedButton.ts +66 -0
- package/src/lib/config/index.ts +62 -5
- package/src/lib/constants/components.ts +55 -0
- package/src/lib/theme/devtools/__tests__/useHistory.test.tsx +150 -0
- package/src/lib/theme/tokens/centralized-tokens.ts +120 -0
- package/src/lib/theme/utils/__tests__/domUtils.test.ts +101 -0
- package/src/lib/types/components.ts +37 -11
- package/src/lib/types/glass.ts +35 -0
- package/src/lib/types/index.ts +1 -0
- package/src/lib/utils/displacement-generator.ts +1 -1
- package/src/styles/01-settings/_settings.testtypecheck.scss +53 -0
- package/src/styles/01-settings/_settings.typedbutton.scss +53 -0
- package/src/styles/06-components/_components.testbutton.scss +212 -0
- package/src/styles/06-components/_components.testtypecheck.scss +212 -0
- package/src/styles/06-components/_components.typedbutton.scss +212 -0
- package/src/styles/99-utilities/_index.scss +1 -0
- package/src/styles/99-utilities/_utilities.text.scss +1 -1
- package/src/styles/99-utilities/_utilities.touch-target.scss +36 -0
- 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).
|
|
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).
|
|
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).
|
|
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 = ['
|
|
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
|
-
|
|
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
|
+
}
|