@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,302 @@
1
+ /**
2
+ * Atomix CLI Security Utilities
3
+ * Input sanitization and security-focused validation functions
4
+ */
5
+
6
+ import { normalize, resolve, relative, isAbsolute } from 'path';
7
+ import { logger } from './logger.js';
8
+ import chalk from 'chalk';
9
+
10
+ export const SecurityError = {
11
+ PATH_TRAVERSAL: 'PATH_TRAVERSAL',
12
+ INVALID_INPUT: 'INVALID_INPUT',
13
+ MALICIOUS_CONTENT: 'MALICIOUS_CONTENT',
14
+ RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED'
15
+ };
16
+
17
+ /**
18
+ * Sanitizes user input to prevent injection attacks
19
+ * @param {string} input - The user input to sanitize
20
+ * @param {string} type - The type of input (filename, componentName, path, etc.)
21
+ * @returns {string} - Sanitized input
22
+ */
23
+ export function sanitizeInput(input, type = 'generic') {
24
+ if (typeof input !== 'string') {
25
+ throw new Error(`Input must be a string, received: ${typeof input}`);
26
+ }
27
+
28
+ // Remove null bytes and control characters
29
+ let sanitized = input.replace(/[\x00-\x1F\x7F]/g, '');
30
+
31
+ switch (type) {
32
+ case 'filename':
33
+ // Remove path traversal attempts and special characters
34
+ sanitized = sanitized.replace(/\.\.\//g, '')
35
+ .replace(/[<>:"|?*]/g, '')
36
+ .replace(/\/+/g, '')
37
+ .trim();
38
+ break;
39
+
40
+ case 'componentName':
41
+ // Enforce PascalCase and remove non-alphanumeric characters
42
+ sanitized = sanitized.replace(/[^a-zA-Z0-9]/g, '');
43
+ if (sanitized.length > 0) {
44
+ sanitized = sanitized.charAt(0).toUpperCase() + sanitized.slice(1);
45
+ }
46
+ break;
47
+
48
+ case 'path':
49
+ // Basic path sanitization - more comprehensive validation happens in validatePath
50
+ sanitized = sanitized.replace(/[\x00-\x1F\x7F]/g, '')
51
+ .replace(/\/\//g, '/')
52
+ .trim();
53
+ break;
54
+
55
+ case 'prompt':
56
+ // For AI prompts, remove potentially malicious content but preserve most characters
57
+ sanitized = sanitized.replace(/[\x00-\x1F\x7F]/g, '')
58
+ .replace(/<script[^>]*>.*?<\/script>/gi, '')
59
+ .replace(/javascript:/gi, '')
60
+ .replace(/data:/gi, '')
61
+ .trim();
62
+ break;
63
+
64
+ default:
65
+ // Generic sanitization
66
+ sanitized = sanitized.trim();
67
+ }
68
+
69
+ if (sanitized.length === 0) {
70
+ throw new Error(`Sanitized ${type} input is empty`);
71
+ }
72
+
73
+ return sanitized;
74
+ }
75
+
76
+ /**
77
+ * Validates and secures file paths to prevent directory traversal attacks
78
+ * @param {string} inputPath - The path to validate
79
+ * @param {string} basePath - Base directory to resolve against
80
+ * @returns {Object} { isValid: boolean, safePath: string, error?: string }
81
+ */
82
+ export function validateSecurePath(inputPath, basePath = process.cwd()) {
83
+ try {
84
+ const normalizedBase = normalize(resolve(basePath));
85
+ const normalizedInput = normalize(isAbsolute(inputPath)
86
+ ? inputPath
87
+ : resolve(basePath, inputPath));
88
+
89
+ const relativePath = relative(normalizedBase, normalizedInput);
90
+
91
+ // Check for path traversal attempts
92
+ if (relativePath.startsWith('..') ||
93
+ /\/\.\.\//.test(relativePath) ||
94
+ relativePath.includes('..\\')) {
95
+ return {
96
+ isValid: false,
97
+ safePath: '',
98
+ error: 'Path traversal attempt detected'
99
+ };
100
+ }
101
+
102
+ // Check for dangerous patterns
103
+ const dangerousPatterns = [
104
+ /\/etc\//,
105
+ /\/proc\//,
106
+ /\/dev\//,
107
+ /\/sys\//,
108
+ /\/root\//,
109
+ /\/bin\//,
110
+ /\/sbin\//,
111
+ /\/usr\/bin\//,
112
+ /\/usr\/sbin\//
113
+ ];
114
+
115
+ for (const pattern of dangerousPatterns) {
116
+ if (pattern.test(normalizedInput)) {
117
+ return {
118
+ isValid: false,
119
+ safePath: '',
120
+ error: 'Access to system directories is not allowed'
121
+ };
122
+ }
123
+ }
124
+
125
+ return {
126
+ isValid: true,
127
+ safePath: normalizedInput
128
+ };
129
+
130
+ } catch (error) {
131
+ return {
132
+ isValid: false,
133
+ safePath: '',
134
+ error: `Path validation failed: ${error.message}`
135
+ };
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Validates component names with enhanced security checks
141
+ * @param {string} name - The component name to validate
142
+ * @returns {Object} { isValid: boolean, error?: string }
143
+ */
144
+ export function validateComponentNameSecure(name) {
145
+ try {
146
+ const sanitized = sanitizeInput(name, 'componentName');
147
+
148
+ if (!sanitized || sanitized.length < 2) {
149
+ return {
150
+ isValid: false,
151
+ error: 'Component name must be at least 2 characters long'
152
+ };
153
+ }
154
+
155
+ // Check PascalCase: starts with uppercase, only contains letters and numbers
156
+ if (!/^[A-Z][a-zA-Z0-9]*$/.test(sanitized)) {
157
+ return {
158
+ isValid: false,
159
+ error: 'Component name must be in PascalCase (e.g., Button, CardHeader)'
160
+ };
161
+ }
162
+
163
+ // Check for reserved words and potentially dangerous names
164
+ const reservedWords = [
165
+ 'Component', 'React', 'Fragment', 'Suspense', 'StrictMode',
166
+ 'Error', 'Loading', 'App', 'Root', 'Document', 'Html',
167
+ 'Window', 'Document', 'Global', 'Process', 'Console',
168
+ 'Eval', 'Function', 'Script', 'Import', 'Require', 'Module'
169
+ ];
170
+
171
+ if (reservedWords.includes(sanitized)) {
172
+ return {
173
+ isValid: false,
174
+ error: `"${sanitized}" is a reserved word. Please choose a different name.`
175
+ };
176
+ }
177
+
178
+ // Check for potentially malicious patterns
179
+ const maliciousPatterns = [
180
+ /eval/i,
181
+ /script/i,
182
+ /javascript/i,
183
+ /alert/i,
184
+ /prompt/i,
185
+ /confirm/i,
186
+ /onload/i,
187
+ /onerror/i,
188
+ /onclick/i
189
+ ];
190
+
191
+ for (const pattern of maliciousPatterns) {
192
+ if (pattern.test(sanitized)) {
193
+ return {
194
+ isValid: false,
195
+ error: 'Component name contains potentially malicious patterns'
196
+ };
197
+ }
198
+ }
199
+
200
+ return { isValid: true };
201
+
202
+ } catch (error) {
203
+ return {
204
+ isValid: false,
205
+ error: `Component name validation failed: ${error.message}`
206
+ };
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Rate limiter for AI-based generation features
212
+ */
213
+ export class RateLimiter {
214
+ constructor(maxRequests = 10, timeWindow = 60000) { // 10 requests per minute
215
+ this.maxRequests = maxRequests;
216
+ this.timeWindow = timeWindow;
217
+ this.requests = new Map();
218
+ }
219
+
220
+ checkLimit(identifier) {
221
+ const now = Date.now();
222
+ const userRequests = this.requests.get(identifier) || [];
223
+
224
+ // Remove old requests
225
+ const recentRequests = userRequests.filter(time => now - time < this.timeWindow);
226
+
227
+ if (recentRequests.length >= this.maxRequests) {
228
+ return false;
229
+ }
230
+
231
+ recentRequests.push(now);
232
+ this.requests.set(identifier, recentRequests);
233
+ return true;
234
+ }
235
+
236
+ getRemaining(identifier) {
237
+ const now = Date.now();
238
+ const userRequests = this.requests.get(identifier) || [];
239
+ const recentRequests = userRequests.filter(time => now - time < this.timeWindow);
240
+ return Math.max(0, this.maxRequests - recentRequests.length);
241
+ }
242
+
243
+ reset() {
244
+ this.requests.clear();
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Creates a backup of a file before overwriting
250
+ * @param {string} filePath - Path to the file to backup
251
+ * @param {string} backupDir - Directory to store backups
252
+ * @returns {Promise<string>} - Path to the backup file
253
+ */
254
+ export async function createBackup(filePath, backupDir = '.atomix/backups') {
255
+ const { readFile, writeFile, mkdir } = await import('fs/promises');
256
+ const { join } = await import('path');
257
+
258
+ try {
259
+ const content = await readFile(filePath, 'utf8');
260
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
261
+ const backupFileName = `${filePath.split('/').pop()}.backup.${timestamp}`;
262
+ const backupPath = join(backupDir, backupFileName);
263
+
264
+ await mkdir(backupDir, { recursive: true });
265
+ await writeFile(backupPath, content, 'utf8');
266
+
267
+ logger.debug(`Created backup: ${backupPath}`);
268
+ return backupPath;
269
+
270
+ } catch (error) {
271
+ logger.warn(`Failed to create backup for ${filePath}: ${error.message}`);
272
+ throw error;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Retry mechanism with exponential backoff
278
+ * @param {Function} operation - The async operation to retry
279
+ * @param {number} maxRetries - Maximum number of retries
280
+ * @param {number} initialDelay - Initial delay in ms
281
+ * @returns {Promise<any>} - Result of the operation
282
+ */
283
+ export async function retryWithBackoff(operation, maxRetries = 3, initialDelay = 100) {
284
+ let retries = 0;
285
+ let delay = initialDelay;
286
+
287
+ while (true) {
288
+ try {
289
+ return await operation();
290
+ } catch (error) {
291
+ retries++;
292
+
293
+ if (retries > maxRetries) {
294
+ throw error;
295
+ }
296
+
297
+ logger.debug(`Retry ${retries}/${maxRetries} after ${delay}ms: ${error.message}`);
298
+ await new Promise(resolve => setTimeout(resolve, delay));
299
+ delay *= 2; // Exponential backoff
300
+ }
301
+ }
302
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Atomix CLI - Local Telemetry system
3
+ * Tracks command execution times and success/failure rates
4
+ */
5
+
6
+ import { writeFile, readFile, mkdir } from 'fs/promises';
7
+ import { join, dirname } from 'path';
8
+ import { existsSync } from 'fs';
9
+ import { configLoader } from '../internal/config-loader.js';
10
+ import { logger } from './logger.js';
11
+
12
+ class Telemetry {
13
+ constructor() {
14
+ this.logs = [];
15
+ this.startTime = null;
16
+ this.currentCommand = null;
17
+ }
18
+
19
+ /**
20
+ * Start tracking a command
21
+ * @param {string} commandName - Name of the command being run
22
+ */
23
+ start(commandName) {
24
+ const config = configLoader.get('telemetry') || {};
25
+ if (!config.enabled) return;
26
+
27
+ this.startTime = performance.now();
28
+ this.currentCommand = commandName;
29
+ }
30
+
31
+ /**
32
+ * Stop tracking and log the result
33
+ * @param {boolean} success - Whether the command succeeded
34
+ * @param {Object} extra - Extra data to log
35
+ */
36
+ async stop(success = true, extra = {}) {
37
+ const config = configLoader.get('telemetry') || {};
38
+ if (!config.enabled || !this.startTime) return;
39
+
40
+ const duration = performance.now() - this.startTime;
41
+ const logEntry = {
42
+ command: this.currentCommand,
43
+ duration: parseFloat(duration.toFixed(2)),
44
+ timestamp: new Date().toISOString(),
45
+ success,
46
+ ...extra
47
+ };
48
+
49
+ // Anonymize if needed
50
+ if (config.anonymize !== false) {
51
+ delete logEntry.path;
52
+ delete logEntry.source;
53
+ }
54
+
55
+ await this._saveLog(logEntry, config.path || '.atomix/telemetry.json');
56
+ this.startTime = null;
57
+ }
58
+
59
+ /**
60
+ * Save the log entry to the local file
61
+ * @private
62
+ */
63
+ async _saveLog(entry, logPath) {
64
+ try {
65
+ const fullPath = join(process.cwd(), logPath);
66
+ const dir = dirname(fullPath);
67
+
68
+ if (!existsSync(dir)) {
69
+ await mkdir(dir, { recursive: true });
70
+ }
71
+
72
+ let logs = [];
73
+ if (existsSync(fullPath)) {
74
+ const content = await readFile(fullPath, 'utf8');
75
+ try {
76
+ logs = JSON.parse(content);
77
+ if (!Array.isArray(logs)) logs = [];
78
+ } catch (e) {
79
+ logs = [];
80
+ }
81
+ }
82
+
83
+ logs.push(entry);
84
+
85
+ // Keep only last 100 logs to prevent file bloat
86
+ if (logs.length > 100) {
87
+ logs = logs.slice(-100);
88
+ }
89
+
90
+ await writeFile(fullPath, JSON.stringify(logs, null, 2), 'utf8');
91
+ } catch (error) {
92
+ logger.debug(`Telemetry failed to save: ${error.message}`);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Get all local telemetry logs
98
+ */
99
+ async getLogs() {
100
+ const config = configLoader.get('telemetry') || {};
101
+ const logPath = config.path || '.atomix/telemetry.json';
102
+ const fullPath = join(process.cwd(), logPath);
103
+
104
+ if (!existsSync(fullPath)) return [];
105
+
106
+ try {
107
+ const content = await readFile(fullPath, 'utf8');
108
+ return JSON.parse(content);
109
+ } catch (e) {
110
+ return [];
111
+ }
112
+ }
113
+ }
114
+
115
+ export const telemetry = new Telemetry();
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Atomix CLI Validation Utilities
3
+ */
4
+
5
+ /**
6
+ * Validates component names according to PascalCase convention
7
+ * @param {string} name - The component name to validate
8
+ * @returns {Object} { isValid: boolean, error?: string }
9
+ */
10
+ export async function validateComponentName(name) {
11
+ // Use the enhanced security validation
12
+ const { validateComponentNameSecure } = await import('./security.js');
13
+ return validateComponentNameSecure(name);
14
+ }
15
+
16
+ /**
17
+ * Validates theme names according to kebab-case convention
18
+ */
19
+ export function validateThemeName(name) {
20
+ if (!name || typeof name !== 'string') return { isValid: false, error: 'Theme name must be a string' };
21
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) return { isValid: false, error: 'Theme name must be kebab-case' };
22
+ if (/--/.test(name)) return { isValid: false, error: 'Cannot contain consecutive hyphens' };
23
+ if (name.endsWith('-')) return { isValid: false, error: 'Cannot end with a hyphen' };
24
+ return { isValid: true };
25
+ }
26
+
27
+ /**
28
+ * Validates SCSS/CSS color values
29
+ */
30
+ export function isValidColor(color) {
31
+ const patterns = [
32
+ /^#[0-9A-F]{3,8}$/i,
33
+ /^(rgb|rgba|hsl|hsla)\(/i,
34
+ /^var\(--/
35
+ ];
36
+ return patterns.some(pattern => pattern.test(color));
37
+ }