@shohojdhara/atomix 0.4.6 → 0.4.8

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 (61) hide show
  1. package/dist/atomix.css +61 -56
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +4 -4
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.d.ts +93 -109
  6. package/dist/charts.js +141 -233
  7. package/dist/charts.js.map +1 -1
  8. package/dist/core.js +51 -46
  9. package/dist/core.js.map +1 -1
  10. package/dist/forms.js +51 -46
  11. package/dist/forms.js.map +1 -1
  12. package/dist/heavy.js +51 -46
  13. package/dist/heavy.js.map +1 -1
  14. package/dist/index.d.ts +6 -22
  15. package/dist/index.esm.js +141 -234
  16. package/dist/index.esm.js.map +1 -1
  17. package/dist/index.js +144 -237
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.min.js +1 -1
  20. package/dist/index.min.js.map +1 -1
  21. package/package.json +1 -1
  22. package/scripts/atomix-cli.js +40 -1875
  23. package/scripts/cli/commands/build-theme.js +112 -0
  24. package/scripts/cli/commands/generate.js +97 -0
  25. package/scripts/cli/commands/init.js +46 -0
  26. package/scripts/cli/internal/compiler.js +114 -0
  27. package/scripts/cli/internal/filesystem.js +58 -0
  28. package/scripts/cli/internal/generator.js +110 -0
  29. package/scripts/cli/internal/wizard.js +61 -0
  30. package/scripts/cli/utils/error.js +47 -0
  31. package/scripts/cli/utils/helpers.js +43 -0
  32. package/scripts/cli/utils/logger.js +75 -0
  33. package/scripts/cli/utils/validation.js +71 -0
  34. package/src/components/AtomixGlass/AtomixGlass.test.tsx +37 -3
  35. package/src/components/AtomixGlass/AtomixGlass.tsx +41 -29
  36. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +4 -19
  37. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +216 -0
  38. package/src/components/Chart/BubbleChart.tsx +6 -2
  39. package/src/components/Chart/Chart.stories.tsx +108 -96
  40. package/src/components/Chart/ChartToolbar.tsx +6 -4
  41. package/src/components/Chart/ChartTooltip.tsx +5 -4
  42. package/src/components/Chart/GaugeChart.tsx +20 -12
  43. package/src/components/Chart/HeatmapChart.tsx +53 -23
  44. package/src/components/Chart/TreemapChart.tsx +44 -15
  45. package/src/components/Chart/index.ts +0 -2
  46. package/src/components/Chart/types.ts +4 -4
  47. package/src/components/index.ts +0 -1
  48. package/src/lib/composables/useAtomixGlass.ts +4 -1
  49. package/src/lib/composables/useAtomixGlassStyles.ts +9 -7
  50. package/src/lib/constants/components.ts +7 -7
  51. package/src/styles/01-settings/_settings.chart.scss +13 -13
  52. package/src/styles/06-components/_components.atomix-glass.scss +17 -21
  53. package/src/styles/06-components/_components.chart.scss +23 -5
  54. package/src/styles/06-components/_components.edge-panel.scss +1 -5
  55. package/src/styles/06-components/_components.modal.scss +1 -4
  56. package/src/styles/06-components/_components.navbar.scss +1 -1
  57. package/src/styles/06-components/_components.tooltip.scss +9 -5
  58. package/scripts/cli/component-generator.js +0 -564
  59. package/scripts/cli/interactive-init.js +0 -357
  60. package/scripts/cli/utils.js +0 -359
  61. package/src/components/Chart/AnimatedChart.tsx +0 -230
@@ -1,357 +0,0 @@
1
- /**
2
- * Interactive Init Wizard for Atomix Design System
3
- */
4
-
5
- import inquirer from 'inquirer';
6
- import chalk from 'chalk';
7
- import { readFile, writeFile, mkdir } from 'fs/promises';
8
- import { join, dirname, basename } from 'path';
9
- import { existsSync } from 'fs';
10
- import boxen from 'boxen';
11
- import ora from 'ora';
12
-
13
- import { projectTemplates, configTemplates } from './templates.js';
14
- import { commonTemplates } from './templates/common-templates.js';
15
-
16
- /**
17
- * Run the interactive init wizard
18
- */
19
- export async function runInitWizard() {
20
- console.log(boxen(
21
- chalk.bold.cyan('🎨 Atomix Design System Setup Wizard\n\n') +
22
- chalk.gray('Let\'s set up your design system project!'),
23
- {
24
- padding: 1,
25
- margin: 1,
26
- borderStyle: 'round',
27
- borderColor: 'cyan'
28
- }
29
- ));
30
-
31
- try {
32
- // Step 1: Project type
33
- const { projectType } = await inquirer.prompt([
34
- {
35
- type: 'list',
36
- name: 'projectType',
37
- message: 'What type of project are you building?',
38
- choices: [
39
- { name: 'React Application', value: 'react' },
40
- { name: 'Next.js Application', value: 'nextjs' },
41
- { name: 'Vanilla JavaScript/HTML', value: 'vanilla' },
42
- { name: 'Custom Setup', value: 'custom' }
43
- ]
44
- }
45
- ]);
46
-
47
- // Step 2: Theme selection
48
- const { themeChoice } = await inquirer.prompt([
49
- {
50
- type: 'list',
51
- name: 'themeChoice',
52
- message: 'How would you like to handle theming?',
53
- choices: [
54
- { name: 'Use a pre-built theme', value: 'prebuilt' },
55
- { name: 'Create a custom theme', value: 'custom' },
56
- { name: 'Start with default styles', value: 'default' },
57
- { name: 'I\'ll configure this later', value: 'skip' }
58
- ]
59
- }
60
- ]);
61
-
62
- let prebuiltThemeName = null;
63
- if (themeChoice === 'prebuilt') {
64
- const { theme } = await inquirer.prompt([
65
- {
66
- type: 'input',
67
- name: 'theme',
68
- message: 'Enter the name of the pre-built theme:',
69
- default: 'default'
70
- }
71
- ]);
72
- prebuiltThemeName = (theme && String(theme).trim()) || 'default';
73
- }
74
-
75
- // Step 3: Features
76
- const { features } = await inquirer.prompt([
77
- {
78
- type: 'checkbox',
79
- name: 'features',
80
- message: 'Select additional features:',
81
- choices: [
82
- { name: 'TypeScript support', value: 'typescript', checked: true },
83
- { name: 'Storybook integration', value: 'storybook' },
84
- { name: 'Testing setup (Vitest)', value: 'testing' },
85
- { name: 'ESLint & Prettier', value: 'linting' },
86
- { name: 'Git hooks (Husky)', value: 'githooks' },
87
- { name: 'CI/CD workflows', value: 'cicd' }
88
- ]
89
- }
90
- ]);
91
-
92
- // Step 4: Configuration
93
- const { configType } = await inquirer.prompt([
94
- {
95
- type: 'list',
96
- name: 'configType',
97
- message: 'Configuration file format:',
98
- choices: [
99
- { name: 'JSON (.atomixrc.json)', value: 'json' },
100
- { name: 'JavaScript (atomix.config.js)', value: 'js' },
101
- { name: 'No configuration file', value: 'none' }
102
- ]
103
- }
104
- ]);
105
-
106
- // Step 5: Installation
107
- const { shouldInstall } = await inquirer.prompt([
108
- {
109
- type: 'confirm',
110
- name: 'shouldInstall',
111
- message: 'Install dependencies now?',
112
- default: true
113
- }
114
- ]);
115
-
116
- // Create project structure
117
- if (projectType !== 'custom') {
118
- const template = projectTemplates[projectType];
119
-
120
- // Step 6: Create/Update package.json
121
- const packageJsonPath = join(process.cwd(), 'package.json');
122
- let packageJson = {};
123
-
124
- if (existsSync(packageJsonPath)) {
125
- packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
126
- } else {
127
- // Create basic package.json
128
- packageJson = {
129
- name: basename(process.cwd()),
130
- version: '0.1.0',
131
- private: true,
132
- scripts: {},
133
- dependencies: {},
134
- devDependencies: {}
135
- };
136
- }
137
-
138
- // Merge dependencies
139
- const packageJsonVersion = packageJson.version || '0.1.0';
140
-
141
- template.dependencies.forEach(dep => {
142
- if (!packageJson.dependencies[dep]) {
143
- // Use a default compatible version if not specified
144
- const defaultVersions = {
145
- 'react': '^18.0.0',
146
- 'react-dom': '^18.0.0',
147
- 'next': '^14.0.0',
148
- 'sass': '^1.70.0'
149
- };
150
- packageJson.dependencies[dep] = defaultVersions[dep] || 'latest';
151
- }
152
- });
153
-
154
- template.devDependencies.forEach(dep => {
155
- if (!packageJson.devDependencies[dep]) {
156
- const defaultDevVersions = {
157
- 'typescript': '^5.0.0',
158
- 'vite': '^5.0.0',
159
- '@shohojdhara/atomix': `^${packageJsonVersion}` // Self-reference for templates
160
- };
161
- packageJson.devDependencies[dep] = defaultDevVersions[dep] || 'latest';
162
- }
163
- });
164
-
165
- // Merge scripts carefully
166
- const themePath = themeChoice === 'prebuilt' && prebuiltThemeName
167
- ? `themes/${prebuiltThemeName}`
168
- : 'themes/custom';
169
- const newScripts = {
170
- 'dev': projectType === 'nextjs' ? 'next dev' : 'vite',
171
- 'build': projectType === 'nextjs' ? 'next build' : 'vite build',
172
- 'build:theme': `atomix build-theme ${themePath}`,
173
- 'generate:component': 'atomix generate component',
174
- 'validate': 'atomix validate --tokens --theme'
175
- };
176
-
177
- if (features.includes('storybook')) {
178
- newScripts['storybook'] = 'storybook dev -p 6006';
179
- newScripts['build:storybook'] = 'storybook build';
180
- }
181
-
182
- if (features.includes('testing')) {
183
- newScripts['test'] = 'vitest';
184
- newScripts['test:watch'] = 'vitest --watch';
185
- }
186
-
187
- // Add new scripts without overwriting user's existing scripts
188
- for (const [key, value] of Object.entries(newScripts)) {
189
- if (!packageJson.scripts[key]) {
190
- packageJson.scripts[key] = value;
191
- } else if (packageJson.scripts[key] !== value) {
192
- // Suggest renamed script if conflict exists
193
- const suggestedKey = `atomix:${key}`;
194
- if (!packageJson.scripts[suggestedKey]) {
195
- packageJson.scripts[suggestedKey] = value;
196
- console.log(chalk.yellow(` ⚠️ Script conflict for "${key}". Added as "${suggestedKey}" instead.`));
197
- }
198
- }
199
- }
200
-
201
- await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
202
- console.log(chalk.green(' ✓ Updated package.json'));
203
-
204
- // Create directories
205
- await mkdir('src', { recursive: true });
206
- if (projectType === 'nextjs') {
207
- await mkdir('src/pages', { recursive: true });
208
- } else if (projectType === 'react') {
209
- await mkdir('src/components', { recursive: true });
210
- } else if (projectType === 'vanilla') {
211
- await mkdir('src/styles', { recursive: true });
212
- }
213
-
214
- // Write template files
215
- const spinner = ora('Creating project files...').start();
216
- let filesCreated = 0;
217
- const totalFiles = Object.keys(template.files).length;
218
-
219
- for (const [path, content] of Object.entries(template.files)) {
220
- const filePath = join(process.cwd(), path);
221
- const dir = dirname(filePath);
222
-
223
- if (!existsSync(dir)) {
224
- await mkdir(dir, { recursive: true });
225
- }
226
-
227
- await writeFile(filePath, content, 'utf8');
228
- filesCreated++;
229
- spinner.text = `Creating project files... (${filesCreated}/${totalFiles})`;
230
- }
231
-
232
- spinner.succeed(chalk.green(`✓ Created ${filesCreated} files`));
233
-
234
- // Generate README
235
- const readmeSpinner = ora('Generating README...').start();
236
- const projectName = basename(process.cwd());
237
- const readmeTemplate = projectType === 'react'
238
- ? commonTemplates.readme.react(projectName)
239
- : projectType === 'nextjs'
240
- ? commonTemplates.readme.nextjs(projectName)
241
- : commonTemplates.readme.vanilla(projectName);
242
-
243
- await writeFile(
244
- join(process.cwd(), 'README.md'),
245
- readmeTemplate,
246
- 'utf8'
247
- );
248
- readmeSpinner.succeed(chalk.green('✓ Generated README.md'));
249
- }
250
-
251
- // Create configuration file
252
- if (configType !== 'none') {
253
- const configTemplate = configType === 'json'
254
- ? configTemplates.basic
255
- : configTemplates.advanced;
256
- const themePathForConfig = themeChoice === 'prebuilt' && prebuiltThemeName
257
- ? `themes/${prebuiltThemeName}`
258
- : 'themes/custom';
259
-
260
- for (const [filename, content] of Object.entries(configTemplate)) {
261
- let configContent = typeof content === 'object'
262
- ? JSON.stringify(content, null, 2)
263
- : content;
264
-
265
- if (themeChoice === 'prebuilt' && prebuiltThemeName) {
266
- if (typeof content === 'object' && content.theme) {
267
- const merged = { ...content, theme: { ...content.theme, path: themePathForConfig } };
268
- configContent = JSON.stringify(merged, null, 2);
269
- } else if (typeof content === 'string') {
270
- configContent = content.replace(/path:\s*['"]themes\/custom['"]/g, `path: '${themePathForConfig}'`);
271
- }
272
- }
273
-
274
- await writeFile(
275
- join(process.cwd(), filename),
276
- configContent,
277
- 'utf8'
278
- );
279
- console.log(chalk.green(` ✓ Created ${filename}`));
280
- }
281
- }
282
-
283
- // Create custom theme if selected
284
- if (themeChoice === 'custom') {
285
- await mkdir('themes/custom', { recursive: true });
286
-
287
- const themeContent = `// Custom Theme
288
- // Generated by Atomix CLI
289
-
290
- @use '@shohojdhara/atomix/scss/settings' as * with (
291
- // Your custom token overrides
292
- $primary-500: #7AFFD7,
293
- $secondary-500: #FF5733,
294
-
295
- // Add more overrides as needed
296
- );
297
-
298
- // Import Atomix components
299
- @use '@shohojdhara/atomix/scss/components';
300
-
301
- // Your custom styles
302
- .custom-component {
303
- // Custom component styles
304
- }`;
305
-
306
- await writeFile(
307
- join(process.cwd(), 'themes/custom/index.scss'),
308
- themeContent,
309
- 'utf8'
310
- );
311
- console.log(chalk.green(' ✓ Created custom theme'));
312
- }
313
-
314
- // Success message
315
- console.log(boxen(
316
- chalk.bold.green('✨ Setup Complete!\n\n') +
317
- chalk.cyan('Your Atomix project is ready.\n\n') +
318
- chalk.gray('Next steps:\n') +
319
- (shouldInstall ? '' : chalk.white('1. Install dependencies: npm install\n')) +
320
- chalk.white(`${shouldInstall ? '1' : '2'}. Start development: npm run dev\n`) +
321
- chalk.white(`${shouldInstall ? '2' : '3'}. Build your theme: npm run build:theme\n`) +
322
- chalk.white(`${shouldInstall ? '3' : '4'}. Generate components: npm run generate:component\n\n`) +
323
- chalk.gray('Documentation: https://github.com/shohojdhara/atomix'),
324
- {
325
- padding: 1,
326
- margin: 1,
327
- borderStyle: 'round',
328
- borderColor: 'green'
329
- }
330
- ));
331
-
332
- // Install dependencies if requested
333
- if (shouldInstall) {
334
- console.log(chalk.cyan('\n📥 Installing dependencies...\n'));
335
- const { execSync } = await import('child_process');
336
-
337
- try {
338
- execSync('npm install', { stdio: 'inherit' });
339
- console.log(chalk.green('\n✅ Dependencies installed successfully!'));
340
- } catch (error) {
341
- console.error(chalk.red('\n❌ Failed to install dependencies'));
342
- console.log(chalk.yellow('Please run: npm install'));
343
- }
344
- }
345
-
346
- } catch (error) {
347
- if (error.isTTYError) {
348
- console.error(chalk.red('This environment doesn\'t support interactive prompts'));
349
- console.log(chalk.yellow('Please use manual setup commands instead'));
350
- } else {
351
- console.error(chalk.red('Setup failed:'), error.message);
352
- }
353
- process.exit(1);
354
- }
355
- }
356
-
357
- export default runInitWizard;
@@ -1,359 +0,0 @@
1
- /**
2
- * CLI Utility Functions
3
- * Provides common utilities for the Atomix CLI including security, validation, and helpers
4
- */
5
-
6
- import { resolve, relative, isAbsolute, normalize } from 'path';
7
- import { access } from 'fs/promises';
8
-
9
- /**
10
- * Enhanced Error Class for CLI
11
- */
12
- export class AtomixCLIError extends Error {
13
- constructor(message, code, suggestions = []) {
14
- super(message);
15
- this.name = 'AtomixCLIError';
16
- this.code = code;
17
- this.suggestions = suggestions;
18
- }
19
- }
20
-
21
- /**
22
- * Validates and sanitizes file paths to prevent directory traversal attacks
23
- * @param {string} inputPath - The path to validate
24
- * @param {string} basePath - The base directory (defaults to process.cwd())
25
- * @returns {Object} { isValid: boolean, safePath: string, error?: string }
26
- */
27
- export function validatePath(inputPath, basePath = process.cwd()) {
28
- try {
29
- // Normalize the paths to remove any '..' or '.' segments
30
- const normalizedBase = normalize(resolve(basePath));
31
- const normalizedInput = normalize(isAbsolute(inputPath)
32
- ? inputPath
33
- : resolve(basePath, inputPath));
34
-
35
- // Check if the resolved path is within the base directory
36
- const relativePath = relative(normalizedBase, normalizedInput);
37
-
38
- // If the relative path starts with '..', it's outside the base directory
39
- if (relativePath.startsWith('..')) {
40
- return {
41
- isValid: false,
42
- safePath: null,
43
- error: 'Path is outside the project directory'
44
- };
45
- }
46
-
47
- // Additional checks for sensitive paths
48
- const sensitivePatterns = [
49
- /^\.git/,
50
- /node_modules/,
51
- /^\.env/,
52
- /\.pem$/,
53
- /\.key$/,
54
- /private/i,
55
- /secret/i
56
- ];
57
-
58
- for (const pattern of sensitivePatterns) {
59
- if (pattern.test(relativePath)) {
60
- return {
61
- isValid: false,
62
- safePath: null,
63
- error: `Access to sensitive path is restricted: ${pattern}`
64
- };
65
- }
66
- }
67
-
68
- return {
69
- isValid: true,
70
- safePath: normalizedInput,
71
- error: null
72
- };
73
- } catch (error) {
74
- return {
75
- isValid: false,
76
- safePath: null,
77
- error: `Invalid path: ${error.message}`
78
- };
79
- }
80
- }
81
-
82
- /**
83
- * Validates component names according to PascalCase convention
84
- * @param {string} name - The component name to validate
85
- * @returns {Object} { isValid: boolean, error?: string }
86
- */
87
- export function validateComponentName(name) {
88
- if (!name || typeof name !== 'string') {
89
- return {
90
- isValid: false,
91
- error: 'Component name must be a non-empty string'
92
- };
93
- }
94
-
95
- // Check PascalCase: starts with uppercase, only contains letters and numbers
96
- if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
97
- return {
98
- isValid: false,
99
- error: 'Component name must be in PascalCase (e.g., Button, CardHeader)'
100
- };
101
- }
102
-
103
- // Check for reserved words
104
- const reservedWords = [
105
- 'Component', 'React', 'Fragment', 'Suspense', 'StrictMode',
106
- 'Error', 'Loading', 'App', 'Root', 'Document', 'Html'
107
- ];
108
-
109
- if (reservedWords.includes(name)) {
110
- return {
111
- isValid: false,
112
- error: `"${name}" is a reserved word. Please choose a different name.`
113
- };
114
- }
115
-
116
- // Check minimum length
117
- if (name.length < 2) {
118
- return {
119
- isValid: false,
120
- error: 'Component name must be at least 2 characters long'
121
- };
122
- }
123
-
124
- return { isValid: true };
125
- }
126
-
127
- /**
128
- * Validates theme names according to kebab-case convention
129
- * @param {string} name - The theme name to validate
130
- * @returns {Object} { isValid: boolean, error?: string }
131
- */
132
- export function validateThemeName(name) {
133
- if (!name || typeof name !== 'string') {
134
- return {
135
- isValid: false,
136
- error: 'Theme name must be a non-empty string'
137
- };
138
- }
139
-
140
- // Check kebab-case: lowercase letters, numbers, and hyphens
141
- if (!/^[a-z][a-z0-9-]*$/.test(name)) {
142
- return {
143
- isValid: false,
144
- error: 'Theme name must be lowercase and use hyphens (e.g., dark-theme)'
145
- };
146
- }
147
-
148
- // Check for consecutive hyphens
149
- if (/--/.test(name)) {
150
- return {
151
- isValid: false,
152
- error: 'Theme name cannot contain consecutive hyphens'
153
- };
154
- }
155
-
156
- // Check for trailing hyphen
157
- if (name.endsWith('-')) {
158
- return {
159
- isValid: false,
160
- error: 'Theme name cannot end with a hyphen'
161
- };
162
- }
163
-
164
- return { isValid: true };
165
- }
166
-
167
- /**
168
- * Sanitizes user input to prevent injection attacks
169
- * @param {string} input - The user input to sanitize
170
- * @returns {string} Sanitized input
171
- */
172
- export function sanitizeInput(input) {
173
- if (typeof input !== 'string') {
174
- return String(input);
175
- }
176
-
177
- // Remove any shell metacharacters that could be dangerous
178
- // Added single and double quotes to the blacklist to prevent shell injection
179
- return input
180
- .replace(/[;&|`$<>\\"']/g, '')
181
- .replace(/\0/g, '') // Remove null bytes
182
- .trim();
183
- }
184
-
185
- /**
186
- * Checks if a file exists and is accessible
187
- * @param {string} filePath - Path to check
188
- * @returns {Promise<boolean>}
189
- */
190
- export async function fileExists(filePath) {
191
- try {
192
- await access(filePath);
193
- return true;
194
- } catch {
195
- return false;
196
- }
197
- }
198
-
199
- /**
200
- * Checks if running in CI environment
201
- * @returns {boolean}
202
- */
203
- export function isCI() {
204
- return !!(
205
- process.env.CI ||
206
- process.env.CONTINUOUS_INTEGRATION ||
207
- process.env.GITHUB_ACTIONS ||
208
- process.env.GITLAB_CI ||
209
- process.env.CIRCLECI ||
210
- process.env.TRAVIS ||
211
- process.env.JENKINS_URL
212
- );
213
- }
214
-
215
- /**
216
- * Checks if running in debug mode
217
- * @returns {boolean}
218
- */
219
- export function isDebug() {
220
- return process.env.ATOMIX_DEBUG === 'true' ||
221
- process.argv.includes('--debug') ||
222
- process.argv.includes('-d');
223
- }
224
-
225
- /**
226
- * Formats file size in human readable format
227
- * @param {number} bytes - File size in bytes
228
- * @returns {string} Formatted size
229
- */
230
- export function formatFileSize(bytes) {
231
- const sizes = ['B', 'KB', 'MB', 'GB'];
232
- if (bytes === 0) return '0 B';
233
- const i = Math.floor(Math.log(bytes) / Math.log(1024));
234
- return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
235
- }
236
-
237
- /**
238
- * Debounce function for watch mode
239
- * @param {Function} func - Function to debounce
240
- * @param {number} wait - Wait time in milliseconds
241
- * @returns {Function} Debounced function
242
- */
243
- export function debounce(func, wait) {
244
- let timeout;
245
- return function executedFunction(...args) {
246
- const later = () => {
247
- clearTimeout(timeout);
248
- func(...args);
249
- };
250
- clearTimeout(timeout);
251
- timeout = setTimeout(later, wait);
252
- };
253
- }
254
-
255
- /**
256
- * Creates a safe file path for cross-platform compatibility
257
- * @param {...string} segments - Path segments
258
- * @returns {string} Safe file path
259
- */
260
- export function safePath(...segments) {
261
- // Filter out empty segments and join with proper separator
262
- return segments
263
- .filter(Boolean)
264
- .join('/')
265
- .replace(/\/+/g, '/') // Remove duplicate slashes
266
- .replace(/\\/g, '/'); // Convert Windows backslashes
267
- }
268
-
269
- /**
270
- * Validates SCSS/CSS color values
271
- * @param {string} color - Color value to validate
272
- * @returns {boolean}
273
- */
274
- export function isValidColor(color) {
275
- const patterns = [
276
- /^#[0-9A-F]{3}$/i, // #RGB
277
- /^#[0-9A-F]{4}$/i, // #RGBA
278
- /^#[0-9A-F]{6}$/i, // #RRGGBB
279
- /^#[0-9A-F]{8}$/i, // #RRGGBBAA
280
- /^rgb\(/i, // rgb()
281
- /^rgba\(/i, // rgba()
282
- /^hsl\(/i, // hsl()
283
- /^hsla\(/i, // hsla()
284
- /^var\(--/ // CSS custom property
285
- ];
286
-
287
- return patterns.some(pattern => pattern.test(color));
288
- }
289
-
290
- /**
291
- * Extracts and validates npm scripts from package.json
292
- * @param {Object} packageJson - Parsed package.json content
293
- * @param {Array<string>} requiredScripts - List of required script names
294
- * @returns {Object} { valid: boolean, missing: Array<string> }
295
- */
296
- export function validateNpmScripts(packageJson, requiredScripts = []) {
297
- const scripts = packageJson.scripts || {};
298
- const missing = requiredScripts.filter(script => !scripts[script]);
299
-
300
- return {
301
- valid: missing.length === 0,
302
- missing
303
- };
304
- }
305
-
306
- /**
307
- * Generates a unique ID for components/themes
308
- * @param {string} prefix - Prefix for the ID
309
- * @returns {string} Unique ID
310
- */
311
- export function generateId(prefix = 'atomix') {
312
- const timestamp = Date.now().toString(36);
313
- const random = Math.random().toString(36).substring(2, 7);
314
- return `${prefix}-${timestamp}-${random}`;
315
- }
316
-
317
- /**
318
- * Checks Node.js version compatibility
319
- * @param {string} requiredVersion - Minimum required version (e.g., '18.0.0')
320
- * @returns {Object} { compatible: boolean, current: string, required: string }
321
- */
322
- export function checkNodeVersion(requiredVersion = '18.0.0') {
323
- const currentVersion = process.version.substring(1); // Remove 'v' prefix
324
- const current = currentVersion.split('.').map(Number);
325
- const required = requiredVersion.split('.').map(Number);
326
-
327
- let compatible = true;
328
- for (let i = 0; i < required.length; i++) {
329
- if (current[i] < required[i]) {
330
- compatible = false;
331
- break;
332
- } else if (current[i] > required[i]) {
333
- break;
334
- }
335
- }
336
-
337
- return {
338
- compatible,
339
- current: currentVersion,
340
- required: requiredVersion
341
- };
342
- }
343
-
344
- export default {
345
- validatePath,
346
- validateComponentName,
347
- validateThemeName,
348
- sanitizeInput,
349
- fileExists,
350
- isCI,
351
- isDebug,
352
- formatFileSize,
353
- debounce,
354
- safePath,
355
- isValidColor,
356
- validateNpmScripts,
357
- generateId,
358
- checkNodeVersion
359
- };