@shohojdhara/atomix 0.4.8 → 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 (165) 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 +4 -1
  112. package/src/components/AtomixGlass/stories/Overview.stories.tsx +157 -6
  113. package/src/components/AtomixGlass/stories/Phase1-Animation.stories.tsx +653 -0
  114. package/src/components/AtomixGlass/stories/Phase1-Test.stories.tsx +95 -0
  115. package/src/components/AtomixGlass/stories/Playground.stories.tsx +51 -51
  116. package/src/components/AtomixGlass/stories/shared-components.tsx +6 -0
  117. package/src/components/Avatar/Avatar.tsx +1 -1
  118. package/src/components/Button/Button.stories.disabled-link.tsx +10 -0
  119. package/src/components/Button/Button.stories.tsx +10 -0
  120. package/src/components/Button/Button.test.tsx +16 -11
  121. package/src/components/Button/Button.tsx +4 -4
  122. package/src/components/Card/Card.tsx +1 -1
  123. package/src/components/Dropdown/Dropdown.tsx +12 -12
  124. package/src/components/Form/Select.tsx +62 -3
  125. package/src/components/Modal/Modal.tsx +14 -3
  126. package/src/components/Navigation/Navbar/Navbar.tsx +44 -0
  127. package/src/components/Slider/Slider.stories.tsx +3 -3
  128. package/src/components/Slider/Slider.tsx +38 -0
  129. package/src/components/Steps/Steps.tsx +3 -3
  130. package/src/components/Tabs/Tabs.tsx +77 -8
  131. package/src/components/Testimonial/Testimonial.tsx +1 -1
  132. package/src/components/TypedButton/TypedButton.stories.tsx +59 -0
  133. package/src/components/TypedButton/TypedButton.tsx +39 -0
  134. package/src/components/TypedButton/index.ts +2 -0
  135. package/src/components/VideoPlayer/VideoPlayer.tsx +11 -4
  136. package/src/lib/composables/index.ts +4 -7
  137. package/src/lib/composables/types.ts +45 -0
  138. package/src/lib/composables/useAccordion.ts +0 -7
  139. package/src/lib/composables/useAtomixGlass.ts +144 -5
  140. package/src/lib/composables/useChartExport.ts +3 -13
  141. package/src/lib/composables/useDropdown.ts +66 -0
  142. package/src/lib/composables/useFocusTrap.ts +80 -0
  143. package/src/lib/composables/usePerformanceMonitor.ts +448 -0
  144. package/src/lib/composables/useResponsiveGlass.presets.ts +192 -0
  145. package/src/lib/composables/useResponsiveGlass.ts +441 -0
  146. package/src/lib/composables/useTooltip.ts +16 -0
  147. package/src/lib/composables/useTypedButton.ts +66 -0
  148. package/src/lib/config/index.ts +62 -5
  149. package/src/lib/constants/components.ts +55 -0
  150. package/src/lib/theme/devtools/__tests__/useHistory.test.tsx +150 -0
  151. package/src/lib/theme/tokens/centralized-tokens.ts +120 -0
  152. package/src/lib/theme/utils/__tests__/domUtils.test.ts +101 -0
  153. package/src/lib/types/components.ts +37 -11
  154. package/src/lib/types/glass.ts +35 -0
  155. package/src/lib/types/index.ts +1 -0
  156. package/src/lib/utils/displacement-generator.ts +1 -1
  157. package/src/styles/01-settings/_settings.testtypecheck.scss +53 -0
  158. package/src/styles/01-settings/_settings.typedbutton.scss +53 -0
  159. package/src/styles/06-components/_components.testbutton.scss +212 -0
  160. package/src/styles/06-components/_components.testtypecheck.scss +212 -0
  161. package/src/styles/06-components/_components.typedbutton.scss +212 -0
  162. package/src/styles/99-utilities/_index.scss +1 -0
  163. package/src/styles/99-utilities/_utilities.text.scss +1 -1
  164. package/src/styles/99-utilities/_utilities.touch-target.scss +36 -0
  165. package/src/styles/06-components/old.chart.styles.scss +0 -2788
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Component Structure Validator
3
+ * Enforces Atomix design system architecture patterns
4
+ */
5
+
6
+ import { readFile } from 'fs/promises';
7
+ import { existsSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { logger } from '../utils/logger.js';
10
+ import { AtomixCLIError } from '../utils/error.js';
11
+
12
+ /**
13
+ * Component validation rule severity levels
14
+ */
15
+ export const COMPONENT_SEVERITY = {
16
+ ERROR: 'error',
17
+ WARNING: 'warning',
18
+ INFO: 'info'
19
+ };
20
+
21
+ /**
22
+ * Required component structure rules
23
+ */
24
+ export const COMPONENT_RULES = {
25
+ /**
26
+ * forwardRef usage - All components must use forwardRef
27
+ */
28
+ FORWARD_REF_REQUIRED: {
29
+ name: 'forward-ref-required',
30
+ description: 'Components must use forwardRef for accessibility and ref forwarding',
31
+ severity: COMPONENT_SEVERITY.ERROR,
32
+ validate: (content) => {
33
+ const issues = [];
34
+
35
+ if (!content.includes('forwardRef')) {
36
+ issues.push({
37
+ rule: 'forward-ref-required',
38
+ message: 'Component missing forwardRef wrapper',
39
+ suggestion: 'Wrap component with forwardRef<HTMLDivElement, ComponentProps>',
40
+ severity: COMPONENT_SEVERITY.ERROR
41
+ });
42
+ }
43
+
44
+ return issues;
45
+ }
46
+ },
47
+
48
+ /**
49
+ * displayName assignment - Required for debugging
50
+ */
51
+ DISPLAY_NAME_REQUIRED: {
52
+ name: 'display-name-required',
53
+ description: 'Components must have displayName property',
54
+ severity: COMPONENT_SEVERITY.ERROR,
55
+ validate: (content, componentName) => {
56
+ const issues = [];
57
+ const displayNamePattern = new RegExp(`${componentName}\\.displayName\\s*=\\s*['"]${componentName}['"]`);
58
+
59
+ if (!displayNamePattern.test(content)) {
60
+ issues.push({
61
+ rule: 'display-name-required',
62
+ message: `Missing or incorrect displayName assignment`,
63
+ suggestion: `Add: ${componentName}.displayName = '${componentName}';`,
64
+ severity: COMPONENT_SEVERITY.ERROR
65
+ });
66
+ }
67
+
68
+ return issues;
69
+ }
70
+ },
71
+
72
+ /**
73
+ * JSDoc documentation - Required for all components
74
+ */
75
+ JSDOC_REQUIRED: {
76
+ name: 'jsdoc-required',
77
+ description: 'Components must have JSDoc documentation',
78
+ severity: COMPONENT_SEVERITY.WARNING,
79
+ validate: (content) => {
80
+ const issues = [];
81
+
82
+ // Check for JSDoc comment block
83
+ if (!/\/\*\*[\s\S]*?\*\//.test(content)) {
84
+ issues.push({
85
+ rule: 'jsdoc-required',
86
+ message: 'Component missing JSDoc documentation',
87
+ suggestion: 'Add JSDoc comment with @param and @returns tags',
88
+ severity: COMPONENT_SEVERITY.WARNING
89
+ });
90
+ }
91
+
92
+ return issues;
93
+ }
94
+ },
95
+
96
+ /**
97
+ * TypeScript Props interface/type - Type safety required
98
+ */
99
+ TYPESCRIPT_TYPES_REQUIRED: {
100
+ name: 'typescript-types-required',
101
+ description: 'Components must define Props interface or type',
102
+ severity: COMPONENT_SEVERITY.ERROR,
103
+ validate: (content, componentName) => {
104
+ const issues = [];
105
+ const propsPattern = new RegExp(`(interface|type)\\s+${componentName}Props`);
106
+
107
+ // Check if Props type is defined locally OR imported
108
+ const hasLocalDef = propsPattern.test(content);
109
+ const hasImport = new RegExp(`import.*{.*${componentName}Props.*}`).test(content);
110
+
111
+ if (!hasLocalDef && !hasImport) {
112
+ issues.push({
113
+ rule: 'typescript-types-required',
114
+ message: `Missing ${componentName}Props type/interface definition or import`,
115
+ suggestion: `Define interface ${componentName}Props { ... } or import it`,
116
+ severity: COMPONENT_SEVERITY.ERROR
117
+ });
118
+ }
119
+
120
+ return issues;
121
+ }
122
+ },
123
+
124
+ /**
125
+ * Accessibility attributes - ARIA support required
126
+ */
127
+ ACCESSIBILITY_ATTRIBUTES: {
128
+ name: 'accessibility-attributes',
129
+ description: 'Components should include accessibility attributes',
130
+ severity: COMPONENT_SEVERITY.WARNING,
131
+ validate: (content) => {
132
+ const issues = [];
133
+
134
+ // Check for aria-* attributes
135
+ const hasAriaAttributes = /aria-[a-z]+/.test(content);
136
+
137
+ // Check for role attribute
138
+ const hasRole = /role=["'][^"']+["']/.test(content);
139
+
140
+ // Check for tabIndex
141
+ const hasTabIndex = /tabIndex/.test(content);
142
+
143
+ if (!hasAriaAttributes && !hasRole && !hasTabIndex) {
144
+ issues.push({
145
+ rule: 'accessibility-attributes',
146
+ message: 'Component missing accessibility attributes',
147
+ suggestion: 'Add aria-label, aria-describedby, role, or tabIndex as appropriate',
148
+ severity: COMPONENT_SEVERITY.WARNING
149
+ });
150
+ }
151
+
152
+ return issues;
153
+ }
154
+ },
155
+
156
+ /**
157
+ * No hardcoded colors - Design token enforcement
158
+ */
159
+ NO_HARDCODED_COLORS: {
160
+ name: 'no-hardcoded-colors',
161
+ description: 'Components must use design tokens instead of hardcoded colors',
162
+ severity: COMPONENT_SEVERITY.ERROR,
163
+ validate: (content) => {
164
+ const issues = [];
165
+
166
+ // Check for hex colors
167
+ const hexColorRegex = /#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})\b/g;
168
+ const hexMatches = content.match(hexColorRegex) || [];
169
+
170
+ // Check for RGB/RGBA colors
171
+ const rgbRegex = /rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*[\d.]+\s*)?\)/gi;
172
+ const rgbMatches = content.match(rgbRegex) || [];
173
+
174
+ // Check for HSL/HSLA colors
175
+ const hslRegex = /hsla?\(\s*\d+\s*,\s*[\d.]+%?\s*,\s*[\d.]+%?\s*(,\s*[\d.]+\s*)?\)/gi;
176
+ const hslMatches = content.match(hslRegex) || [];
177
+
178
+ const allMatches = [...hexMatches, ...rgbMatches, ...hslMatches];
179
+
180
+ if (allMatches.length > 0) {
181
+ issues.push({
182
+ rule: 'no-hardcoded-colors',
183
+ message: `Found ${allMatches.length} hardcoded color(s): ${allMatches.slice(0, 3).join(', ')}${allMatches.length > 3 ? '...' : ''}`,
184
+ suggestion: 'Replace with design tokens (e.g., var(--color-primary) or theme.colors.primary)',
185
+ severity: COMPONENT_SEVERITY.ERROR,
186
+ matches: allMatches
187
+ });
188
+ }
189
+
190
+ return issues;
191
+ }
192
+ },
193
+
194
+ /**
195
+ * Memo usage - Performance optimization
196
+ */
197
+ MEMO_USAGE: {
198
+ name: 'memo-usage',
199
+ description: 'Components should use React.memo for performance',
200
+ severity: COMPONENT_SEVERITY.INFO,
201
+ validate: (content) => {
202
+ const issues = [];
203
+
204
+ if (!content.includes('memo') && !content.includes('React.memo')) {
205
+ issues.push({
206
+ rule: 'memo-usage',
207
+ message: 'Component not wrapped in React.memo',
208
+ suggestion: 'Consider using React.memo() for performance optimization',
209
+ severity: COMPONENT_SEVERITY.INFO
210
+ });
211
+ }
212
+
213
+ return issues;
214
+ }
215
+ },
216
+
217
+ /**
218
+ * Composable hook pattern - Atomix architecture
219
+ */
220
+ COMPOSABLE_HOOK_PATTERN: {
221
+ name: 'composable-hook-pattern',
222
+ description: 'Components should use composable hook pattern',
223
+ severity: COMPONENT_SEVERITY.INFO,
224
+ validate: (content, componentName) => {
225
+ const issues = [];
226
+
227
+ const hookPattern = new RegExp(`use${componentName}`);
228
+
229
+ if (!hookPattern.test(content)) {
230
+ issues.push({
231
+ rule: 'composable-hook-pattern',
232
+ message: `Component not using composable hook (use${componentName})`,
233
+ suggestion: `Create and use lib/composables/use${componentName} hook`,
234
+ severity: COMPONENT_SEVERITY.INFO
235
+ });
236
+ }
237
+
238
+ return issues;
239
+ }
240
+ },
241
+
242
+ /**
243
+ * Theme naming utility - Consistent class names
244
+ */
245
+ THEME_NAMING_USAGE: {
246
+ name: 'theme-naming-usage',
247
+ description: 'Components should use ThemeNaming utility for class names',
248
+ severity: COMPONENT_SEVERITY.INFO,
249
+ validate: (content) => {
250
+ const issues = [];
251
+
252
+ // Check for ThemeNaming import or usage
253
+ const hasThemeNaming = /ThemeNaming\./.test(content) || /themeNaming\./.test(content);
254
+
255
+ // Check for variantClass, sizeClass, stateClass patterns
256
+ const hasVariantPattern = /(variant|size|state)Class/.test(content);
257
+
258
+ if (!hasThemeNaming && !hasVariantPattern) {
259
+ issues.push({
260
+ rule: 'theme-naming-usage',
261
+ message: 'Component not using ThemeNaming utility for variants/sizes/states',
262
+ suggestion: 'Use ThemeNaming.variantClass(), sizeClass(), stateClass() for consistency',
263
+ severity: COMPONENT_SEVERITY.INFO
264
+ });
265
+ }
266
+
267
+ return issues;
268
+ }
269
+ }
270
+ };
271
+
272
+ /**
273
+ * ComponentValidator class for enforcing design system architecture
274
+ */
275
+ export class ComponentValidator {
276
+ constructor(options = {}) {
277
+ this.rules = new Map();
278
+ this.enabledRules = options.enabledRules || Object.keys(COMPONENT_RULES);
279
+
280
+ // Register built-in rules
281
+ for (const [key, rule] of Object.entries(COMPONENT_RULES)) {
282
+ this.registerRule(key, rule);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Register a custom validation rule
288
+ * @param {string} name - Rule name
289
+ * @param {Object} rule - Rule definition
290
+ */
291
+ registerRule(name, rule) {
292
+ if (!rule.name || !rule.validate || !rule.severity) {
293
+ throw new Error(`Invalid rule "${name}": must have name, validate, and severity`);
294
+ }
295
+ this.rules.set(name, rule);
296
+ }
297
+
298
+ /**
299
+ * Enable or disable a rule
300
+ * @param {string} ruleName - Rule to toggle
301
+ * @param {boolean} enabled - Whether to enable
302
+ */
303
+ toggleRule(ruleName, enabled) {
304
+ if (enabled) {
305
+ if (!this.enabledRules.includes(ruleName)) {
306
+ this.enabledRules.push(ruleName);
307
+ }
308
+ } else {
309
+ this.enabledRules = this.enabledRules.filter(r => r !== ruleName);
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Validate component code against all enabled rules
315
+ * @param {string} content - Component source code
316
+ * @param {string} componentName - Component name (PascalCase)
317
+ * @returns {Object} Validation results
318
+ */
319
+ validate(content, componentName) {
320
+ const results = {
321
+ valid: true,
322
+ issues: [],
323
+ summary: {
324
+ errors: 0,
325
+ warnings: 0,
326
+ info: 0
327
+ }
328
+ };
329
+
330
+ for (const ruleName of this.enabledRules) {
331
+ const rule = this.rules.get(ruleName);
332
+
333
+ if (!rule) {
334
+ logger.debug(`Rule "${ruleName}" not found, skipping`);
335
+ continue;
336
+ }
337
+
338
+ try {
339
+ const issues = rule.validate(content, componentName);
340
+
341
+ if (issues.length > 0) {
342
+ results.valid = false;
343
+ results.issues.push(...issues);
344
+
345
+ // Update summary
346
+ for (const issue of issues) {
347
+ if (issue.severity === COMPONENT_SEVERITY.ERROR) {
348
+ results.summary.errors++;
349
+ } else if (issue.severity === COMPONENT_SEVERITY.WARNING) {
350
+ results.summary.warnings++;
351
+ } else {
352
+ results.summary.info++;
353
+ }
354
+ }
355
+ }
356
+ } catch (error) {
357
+ logger.warn(`Rule "${ruleName}" failed: ${error.message}`);
358
+ results.issues.push({
359
+ rule: ruleName,
360
+ message: `Validation rule error: ${error.message}`,
361
+ severity: COMPONENT_SEVERITY.WARNING
362
+ });
363
+ }
364
+ }
365
+
366
+ return results;
367
+ }
368
+
369
+ /**
370
+ * Validate component from file path
371
+ * @param {string} filePath - Path to component file
372
+ * @param {string} componentName - Component name
373
+ * @returns {Promise<Object>} Validation results
374
+ */
375
+ async validateFile(filePath, componentName) {
376
+ if (!existsSync(filePath)) {
377
+ throw new AtomixCLIError(
378
+ `Component file not found: ${filePath}`,
379
+ 'FILE_NOT_FOUND',
380
+ ['Verify the component file exists']
381
+ );
382
+ }
383
+
384
+ const content = await readFile(filePath, 'utf8');
385
+ return this.validate(content, componentName);
386
+ }
387
+
388
+ /**
389
+ * Get detailed report of validation results
390
+ * @param {Object} results - Validation results
391
+ * @param {string} componentName - Component name
392
+ * @returns {string} Formatted report
393
+ */
394
+ getReport(results, componentName) {
395
+ const lines = [
396
+ `\n🔍 Component Validation Report: ${componentName}`,
397
+ '='.repeat(60),
398
+ `Status: ${results.valid ? '✅ PASSED' : '❌ FAILED'}`,
399
+ '',
400
+ 'Summary:',
401
+ ` Errors: ${results.summary.errors}`,
402
+ ` Warnings: ${results.summary.warnings}`,
403
+ ` Info: ${results.summary.info}`,
404
+ ''
405
+ ];
406
+
407
+ if (results.issues.length > 0) {
408
+ lines.push('Issues:');
409
+
410
+ // Sort by severity (errors first)
411
+ const sortedIssues = [...results.issues].sort((a, b) => {
412
+ const severityOrder = {
413
+ [COMPONENT_SEVERITY.ERROR]: 0,
414
+ [COMPONENT_SEVERITY.WARNING]: 1,
415
+ [COMPONENT_SEVERITY.INFO]: 2
416
+ };
417
+ return severityOrder[a.severity] - severityOrder[b.severity];
418
+ });
419
+
420
+ for (const issue of sortedIssues) {
421
+ const icon = issue.severity === COMPONENT_SEVERITY.ERROR ? '❌' :
422
+ issue.severity === COMPONENT_SEVERITY.WARNING ? '⚠️' : 'ℹ️';
423
+
424
+ lines.push(` ${icon} [${issue.rule}] ${issue.message}`);
425
+
426
+ if (issue.suggestion) {
427
+ lines.push(` 💡 ${issue.suggestion}`);
428
+ }
429
+ }
430
+ }
431
+
432
+ lines.push('='.repeat(60));
433
+
434
+ return lines.join('\n');
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Create a singleton validator instance
440
+ */
441
+ export const componentValidator = new ComponentValidator();
442
+
443
+ export default componentValidator;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Atomix CLI Configuration Loader
3
+ * Supports loading atomix.config.ts and atomix.config.js
4
+ */
5
+
6
+ import { existsSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { pathToFileURL } from 'url';
9
+ import { logger } from '../utils/logger.js';
10
+ import { hookManager } from './hooks.js';
11
+
12
+ export class ConfigLoader {
13
+ constructor() {
14
+ this.config = null;
15
+ this.configPath = null;
16
+ }
17
+
18
+ /**
19
+ * Loads the configuration from the project root
20
+ * @param {string} projectRoot - Root directory of the project
21
+ * @returns {Promise<Object>} - Loaded configuration
22
+ */
23
+ async load(projectRoot = process.cwd()) {
24
+ if (this.config) return this.config;
25
+
26
+ const configFiles = ['atomix.config.ts', 'atomix.config.js'];
27
+ let foundFile = null;
28
+
29
+ for (const file of configFiles) {
30
+ const fullPath = join(projectRoot, file);
31
+ if (existsSync(fullPath)) {
32
+ foundFile = fullPath;
33
+ break;
34
+ }
35
+ }
36
+
37
+ if (!foundFile) {
38
+ logger.debug('No configuration file found. Using defaults.');
39
+ this.config = { prefix: 'atomix' };
40
+ return this.config;
41
+ }
42
+
43
+ this.configPath = foundFile;
44
+
45
+ try {
46
+ // If it's a TypeScript file, we need to register ts-node
47
+ if (foundFile.endsWith('.ts')) {
48
+ // Dynamic import to avoid issues in pure JS environments
49
+ const { register } = await import('ts-node');
50
+ register({
51
+ transpileOnly: true,
52
+ esm: true,
53
+ compilerOptions: {
54
+ module: 'ESNext',
55
+ target: 'ESNext'
56
+ }
57
+ });
58
+ }
59
+
60
+ // Use dynamic import for ESM compatibility
61
+ const configModule = await import(pathToFileURL(foundFile).href);
62
+ this.config = configModule.default || configModule;
63
+
64
+ logger.debug(`Loaded configuration from ${foundFile}`);
65
+
66
+ // Initialize plugins if present
67
+ if (this.config.plugins) {
68
+ await this._initializePlugins();
69
+ }
70
+
71
+ return this.config;
72
+ } catch (error) {
73
+ logger.error(`Failed to load configuration from ${foundFile}: ${error.message}`);
74
+ if (process.env.ATOMIX_DEBUG) {
75
+ console.error(error);
76
+ }
77
+ this.config = { prefix: 'atomix' };
78
+ return this.config;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Initializes plugins from the configuration
84
+ * @private
85
+ */
86
+ async _initializePlugins() {
87
+ for (const pluginEntry of this.config.plugins) {
88
+ let pluginName = '';
89
+ let pluginOptions = {};
90
+
91
+ if (typeof pluginEntry === 'string') {
92
+ pluginName = pluginEntry;
93
+ } else {
94
+ pluginName = pluginEntry.name;
95
+ pluginOptions = pluginEntry.options || {};
96
+ }
97
+
98
+ try {
99
+ let pluginModule;
100
+
101
+ // Check if it's a local plugin (starts with ./ or ../)
102
+ if (pluginName.startsWith('.')) {
103
+ const pluginPath = join(process.cwd(), pluginName);
104
+ pluginModule = await import(pathToFileURL(pluginPath).href);
105
+ } else {
106
+ // Assume it's an npm package
107
+ pluginModule = await import(pluginName);
108
+ }
109
+
110
+ const plugin = pluginModule.default || pluginModule;
111
+
112
+ if (typeof plugin === 'function') {
113
+ // Initialize plugin with API and options
114
+ await plugin(this._getPluginAPI(), pluginOptions);
115
+ logger.debug(`Initialized plugin: ${pluginName}`);
116
+ } else if (plugin && typeof plugin.init === 'function') {
117
+ await plugin.init(this._getPluginAPI(), pluginOptions);
118
+ logger.debug(`Initialized plugin: ${pluginName}`);
119
+ } else {
120
+ logger.warn(`Plugin ${pluginName} does not export a valid initializer.`);
121
+ }
122
+ } catch (error) {
123
+ logger.error(`Failed to load plugin ${pluginName}: ${error.message}`);
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Returns the stable API provided to plugins
130
+ * @private
131
+ */
132
+ _getPluginAPI() {
133
+ return {
134
+ logger,
135
+ config: this.config,
136
+ hooks: {
137
+ register: (hook, cb) => hookManager.register(hook, cb)
138
+ },
139
+ // Plugins might need file system access
140
+ fs: {
141
+ // We'll expose a subset of filesystem utilities later if needed
142
+ }
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Returns the currently loaded configuration
148
+ */
149
+ getConfig() {
150
+ return this.config || { prefix: 'atomix' };
151
+ }
152
+
153
+ /**
154
+ * Returns a specific configuration value
155
+ */
156
+ get(key) {
157
+ if (!this.config) return null;
158
+ return this.config[key];
159
+ }
160
+ }
161
+
162
+ export const configLoader = new ConfigLoader();