@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,71 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Atomix CLI
5
- * Design System Development Tools
4
+ * Atomix CLI Orchestrator
5
+ * Design System Development Tools - Modular Edition
6
6
  */
7
7
 
8
8
  import { program } from 'commander';
9
- import { readFile, writeFile, mkdir, access, stat, rm } from 'fs/promises';
10
- import { join, dirname, basename, relative, resolve } from 'path';
9
+ import { readFile } from 'fs/promises';
10
+ import { join, dirname } from 'path';
11
11
  import { fileURLToPath } from 'url';
12
- import { existsSync, cpSync } from 'fs';
13
- import * as sass from 'sass';
14
- import postcss from 'postcss';
15
- import autoprefixer from 'autoprefixer';
16
- import cssnano from 'cssnano';
17
- import chalk from 'chalk';
18
- import ora from 'ora';
19
- import chokidar from 'chokidar';
20
- import inquirer from 'inquirer';
21
- import boxen from 'boxen';
22
- import { runInitWizard } from './cli/interactive-init.js';
23
- import {
24
- checkDependencies,
25
- validateProjectStructure,
26
- validateFrameworkConfig
27
- } from './cli/dependency-checker.js';
28
- import {
29
- migrateTailwind,
30
- migrateBootstrap,
31
- migrateSCSSVariables,
32
- displayMigrationReport
33
- } from './cli/migration-tools.js';
34
- import {
35
- listTokens,
36
- validateTokens,
37
- exportTokens,
38
- importTokens,
39
- fixTokens
40
- } from './cli/token-manager.js';
41
- import { createThemeCLIBridge } from './cli/theme-bridge.js';
42
- import {
43
- validatePath,
44
- validateComponentName,
45
- validateThemeName,
46
- sanitizeInput,
47
- checkNodeVersion,
48
- AtomixCLIError
49
- } from './cli/utils.js';
50
- import {
51
- componentTemplates,
52
- generateColorTokens,
53
- generateSpacingTokens,
54
- generateTypographyTokens,
55
- generateShadowTokens,
56
- generateRadiusTokens,
57
- generateAnimationTokens
58
- } from './cli/templates.js';
59
- import {
60
- interactiveComponentGeneration,
61
- validateGeneratedComponent,
62
- displayValidationReport
63
- } from './cli/component-generator.js';
64
- import {
65
- syncDocumentation,
66
- validateDocumentation,
67
- generateCLIDocumentation
68
- } from './cli/documentation-sync.js';
12
+ import { logger } from './cli/utils/logger.js';
13
+ import { handleCLIError } from './cli/utils/error.js';
14
+
15
+ // Action Modules
16
+ import { initAction } from './cli/commands/init.js';
17
+ import { generateAction } from './cli/commands/generate.js';
18
+ import { buildThemeAction } from './cli/commands/build-theme.js';
69
19
 
70
20
  const __filename = fileURLToPath(import.meta.url);
71
21
  const __dirname = dirname(__filename);
@@ -75,1856 +25,71 @@ const packageJson = JSON.parse(
75
25
  await readFile(join(__dirname, '../package.json'), 'utf8')
76
26
  );
77
27
 
78
- // CLI Configuration
79
- const DEBUG = process.env.ATOMIX_DEBUG === 'true' || process.argv.includes('--debug');
80
-
81
- const SENSITIVE_KEYS = /password|secret|token|api[-_]?key|access[-_]?key|auth[-_]?token|authorization|credential/i;
82
-
83
- function sensitiveDataReplacer(key, value) {
84
- if (key && SENSITIVE_KEYS.test(key)) {
85
- return '***REDACTED***';
86
- }
87
- return value;
88
- }
89
-
90
- /**
91
- * Replacer function for JSON.stringify to sanitize sensitive data
92
- */
93
- function sanitizeReplacer(key, value) {
94
- // If no key (root object), return value
95
- if (!key) return value;
96
-
97
- const lowerKey = key.toLowerCase();
98
-
99
- // Heuristic for sensitive keys
100
- const isSensitive =
101
- lowerKey.includes('password') ||
102
- lowerKey.includes('secret') ||
103
- lowerKey.includes('credential') ||
104
- lowerKey.includes('bearer') ||
105
- // Ends with 'token' (e.g. accessToken, authToken) or is 'token', avoid 'tokenizer'
106
- /token$/i.test(key) ||
107
- lowerKey === 'token' ||
108
- // Ends with 'key' (e.g. apiKey, accessKey) or is 'key', avoid 'keyboard', 'keyword', exclude 'publickey'
109
- ((/key$/i.test(key) || lowerKey === 'key') && !lowerKey.includes('public')) ||
110
- // Auth
111
- lowerKey === 'auth' ||
112
- lowerKey.startsWith('auth_') ||
113
- /^auth[A-Z]/.test(key) ||
114
- lowerKey.includes('authorization');
115
-
116
- if (isSensitive) {
117
- return '[REDACTED]';
118
- }
119
-
120
- return value;
121
- }
122
-
123
- /**
124
- * Debug logger
125
- */
126
- function debug(message, data = null) {
127
- if (DEBUG) {
128
- console.log(chalk.gray(`[DEBUG] ${message}`));
129
- if (data) {
130
- console.log(chalk.gray(JSON.stringify(data, sanitizeReplacer, 2)));
131
- }
132
- }
133
- }
134
-
135
- /**
136
- * Error handler with suggestions
137
- */
138
- function handleError(error, spinner = null) {
139
- if (spinner) spinner.fail(chalk.red('Operation failed'));
140
-
141
- console.error(chalk.bold.red(`\n❌ ${error.message}`));
142
-
143
- if (error instanceof AtomixCLIError && error.suggestions.length > 0) {
144
- console.log(chalk.yellow('\n💡 Suggestions:'));
145
- error.suggestions.forEach((suggestion, index) => {
146
- console.log(chalk.gray(` ${index + 1}. ${suggestion}`));
147
- });
148
- }
149
-
150
- if (DEBUG && error.stack) {
151
- console.error(chalk.gray('\nStack trace:'));
152
- console.error(chalk.gray(error.stack));
153
- }
154
-
155
- process.exit(1);
156
- }
157
-
158
-
159
-
160
- // Initialize program
161
28
  program
162
29
  .name('atomix')
163
- .description('Atomix Design System CLI - Enhanced Edition')
30
+ .description('Atomix Design System CLI - Modular Edition')
164
31
  .version(packageJson.version)
165
32
  .option('-d, --debug', 'Enable debug mode', false)
166
33
  .hook('preAction', (thisCommand) => {
167
34
  if (thisCommand.opts().debug) {
168
35
  process.env.ATOMIX_DEBUG = 'true';
36
+ logger.debug('Debug mode enabled');
169
37
  }
170
38
  });
171
39
 
172
40
  /**
173
- * Enhanced Build Theme Command with Watch Mode
41
+ * Project Initialization
174
42
  */
175
43
  program
176
- .command('build-theme <path>')
177
- .description('Build a custom theme from SCSS')
178
- .option('-o, --output <path>', 'Output directory', './dist')
179
- .option('-m, --minify', 'Generate minified version', true)
180
- .option('-s, --sourcemap', 'Generate source maps', false)
181
- .option('-w, --watch', 'Watch for changes and rebuild', false)
182
- .option('--analyze', 'Analyze bundle size', false)
183
- .action(async (themePath, options) => {
184
- let spinner = ora('Initializing theme build...').start();
185
-
44
+ .command('init')
45
+ .description('Initialize a new Atomix design system project')
46
+ .action(async (options) => {
186
47
  try {
187
- const sanitizedThemePath = sanitizeInput(themePath);
188
- const themePathValidation = validatePath(sanitizedThemePath);
189
- if (!themePathValidation.isValid) {
190
- throw new AtomixCLIError(
191
- themePathValidation.error,
192
- 'INVALID_PATH',
193
- [
194
- 'Ensure theme path is within the project directory',
195
- 'Avoid sensitive or absolute system paths',
196
- 'Example: atomix build-theme themes/my-theme'
197
- ]
198
- );
199
- }
200
- const sanitizedOutput = sanitizeInput(options.output);
201
- const outputValidation = validatePath(sanitizedOutput);
202
- if (!outputValidation.isValid) {
203
- throw new AtomixCLIError(
204
- outputValidation.error,
205
- 'INVALID_PATH',
206
- [
207
- 'Use a project-relative directory for output',
208
- 'Example: --output ./dist'
209
- ]
210
- );
211
- }
212
- // Resolve paths
213
- const indexPath = sanitizedThemePath.endsWith('.scss')
214
- ? resolve(themePathValidation.safePath)
215
- : resolve(themePathValidation.safePath, 'index.scss');
216
-
217
- debug(`Building theme from: ${indexPath}`);
218
-
219
- // Check if path exists
220
- try {
221
- await access(indexPath);
222
- } catch (error) {
223
- throw new AtomixCLIError(
224
- `Theme file not found: ${indexPath}`,
225
- 'THEME_NOT_FOUND',
226
- [
227
- 'Check if the file path is correct',
228
- 'Ensure the file has a .scss extension',
229
- 'Create a new theme with: atomix create-theme <name>'
230
- ]
231
- );
232
- }
233
-
234
- // Build function
235
- const buildTheme = async () => {
236
- const startTime = Date.now();
237
-
238
- try {
239
- // Compile SCSS
240
- spinner.text = 'Compiling SCSS...';
241
- debug('Starting SCSS compilation');
242
-
243
- const result = sass.compile(indexPath, {
244
- loadPaths: [
245
- process.cwd(),
246
- join(process.cwd(), 'node_modules'),
247
- join(__dirname, '../src'),
248
- join(__dirname, '../src/styles'),
249
- dirname(indexPath)
250
- ],
251
- sourceMap: options.sourcemap,
252
- style: 'expanded',
253
- });
254
-
255
- // Process with PostCSS
256
- spinner.text = 'Processing with PostCSS...';
257
- const processed = await postcss([
258
- autoprefixer({
259
- overrideBrowserslist: ['> 1%', 'last 2 versions', 'not dead'],
260
- }),
261
- ]).process(result.css, {
262
- from: indexPath,
263
- map: options.sourcemap,
264
- });
265
-
266
- // Ensure output directory exists
267
- await mkdir(outputValidation.safePath, { recursive: true });
268
-
269
- // Get theme name
270
- const themeName = basename(dirname(indexPath));
271
-
272
- // Write expanded CSS
273
- const outputPath = join(outputValidation.safePath, `${themeName}.css`);
274
- await writeFile(outputPath, processed.css, 'utf8');
275
-
276
- // Get file size
277
- const stats = await stat(outputPath);
278
- const sizeKB = (stats.size / 1024).toFixed(2);
279
-
280
- spinner.succeed(chalk.green(`✓ Built ${outputPath} (${sizeKB} KB)`));
281
-
282
- // Write minified if requested
283
- if (options.minify) {
284
- const minifySpinner = ora('Minifying CSS...').start();
285
- const minified = await postcss([
286
- autoprefixer(),
287
- cssnano({ preset: 'default' }),
288
- ]).process(result.css, {
289
- from: indexPath,
290
- });
291
-
292
- const minPath = join(outputValidation.safePath, `${themeName}.min.css`);
293
- await writeFile(minPath, minified.css, 'utf8');
294
-
295
- const minStats = await stat(minPath);
296
- const minSizeKB = (minStats.size / 1024).toFixed(2);
297
-
298
- minifySpinner.succeed(chalk.green(`✓ Built ${minPath} (${minSizeKB} KB)`));
299
- }
300
-
301
- // Analyze if requested
302
- if (options.analyze) {
303
- console.log(chalk.cyan('\n📊 Theme Analysis:'));
304
- console.log(chalk.gray(` Original size: ${sizeKB} KB`));
305
- if (options.minify) {
306
- const minPath = join(outputValidation.safePath, `${themeName}.min.css`);
307
- const minStats = await stat(minPath);
308
- const minSizeKB = (minStats.size / 1024).toFixed(2);
309
- const reduction = ((1 - minStats.size / stats.size) * 100).toFixed(1);
310
- console.log(chalk.gray(` Minified size: ${minSizeKB} KB (${reduction}% reduction)`));
311
- }
312
- console.log(chalk.gray(` Build time: ${Date.now() - startTime}ms`));
313
- }
314
-
315
- if (!options.watch) {
316
- console.log(chalk.bold.green('\n✨ Theme build complete!'));
317
- }
318
-
319
- } catch (error) {
320
- if (options.watch) {
321
- console.error(chalk.red(`Build error: ${error.message}`));
322
- console.log(chalk.yellow('Waiting for changes...'));
323
- } else {
324
- throw error;
325
- }
326
- }
327
- };
328
-
329
- // Initial build
330
- await buildTheme();
331
- spinner.stop();
332
-
333
- // Watch mode
334
- if (options.watch) {
335
- console.log(chalk.cyan('\n👁️ Watch mode enabled. Press Ctrl+C to exit.\n'));
336
-
337
- const watcher = chokidar.watch([themePathValidation.safePath], {
338
- ignored: /node_modules/,
339
- persistent: true,
340
- ignoreInitial: true
341
- });
342
-
343
- watcher.on('change', async (path) => {
344
- console.log(chalk.gray(`\n[${new Date().toLocaleTimeString()}] File changed: ${relative(process.cwd(), path)}`));
345
- spinner = ora('Rebuilding theme...').start();
346
- await buildTheme();
347
- spinner.stop();
348
- });
349
-
350
- watcher.on('add', async (path) => {
351
- console.log(chalk.gray(`\n[${new Date().toLocaleTimeString()}] File added: ${relative(process.cwd(), path)}`));
352
- spinner = ora('Rebuilding theme...').start();
353
- await buildTheme();
354
- spinner.stop();
355
- });
356
-
357
- // Handle graceful shutdown
358
- process.on('SIGINT', () => {
359
- console.log(chalk.yellow('\n\nShutting down watch mode...'));
360
- watcher.close();
361
- process.exit(0);
362
- });
363
- }
364
-
48
+ await initAction(options);
365
49
  } catch (error) {
366
- handleError(error, spinner);
50
+ await handleCLIError(error);
367
51
  }
368
52
  });
369
53
 
370
54
  /**
371
- * Generate Component Command - NEW
55
+ * Resource Generation
372
56
  */
373
57
  program
374
58
  .command('generate <type> <name>')
375
59
  .alias('g')
376
- .description('Generate design system components, tokens, or themes')
377
- .option('-t, --typescript', 'Use TypeScript (default)', true)
378
- .option('-s, --story', 'Include Storybook story', true)
379
- .option('--test', 'Include test file', false)
380
- .option('--scss-module', 'Use SCSS modules', false)
381
- .option('--path <path>', 'Custom output path', './src/components')
382
- .option('-f, --force', 'Overwrite existing files', false)
383
- .option('-i, --interactive', 'Interactive component generation', false)
384
- .option('--complexity <level>', 'Component complexity level (simple|medium|complex)', 'medium')
385
- .option('--validate', 'Validate component after generation', true)
60
+ .description('Generate components, tokens, or themes')
61
+ .option('-i, --interactive', 'Interactive mode', false)
62
+ .option('-p, --path <path>', 'Output path', './src/components')
63
+ .option('--complexity <level>', 'Complexity (simple|medium|complex)', 'medium')
64
+ .option('--validate', 'Validate after generation', true)
386
65
  .action(async (type, name, options) => {
387
- const spinner = ora(`Generating ${type}: ${name}...`).start();
388
-
389
- try {
390
- debug(`Generating ${type} with name: ${name}`, options);
391
- let safeName = sanitizeInput(name);
392
-
393
- if (type === 'component' || type === 'c') {
394
- // Handle interactive generation
395
- if (options.interactive) {
396
- const interactiveResult = await interactiveComponentGeneration();
397
- if (!interactiveResult) {
398
- spinner.stop();
399
- return; // User cancelled
400
- }
401
- // Update options with interactive results (name, complexity, features, outputPath)
402
- Object.assign(options, interactiveResult);
403
- safeName = sanitizeInput(options.name);
404
- }
405
-
406
- const nameValidation = validateComponentName(safeName);
407
- if (!nameValidation.isValid) {
408
- throw new AtomixCLIError(
409
- nameValidation.error,
410
- 'INVALID_NAME',
411
- [
412
- 'Use PascalCase naming (e.g., MyComponent)',
413
- 'Start with an uppercase letter',
414
- 'Use only letters and numbers',
415
- 'Avoid reserved words'
416
- ]
417
- );
418
- }
419
- // Validate output path
420
- const sanitizedPath = sanitizeInput(options.path);
421
- const pathValidation = validatePath(sanitizedPath);
422
- if (!pathValidation.isValid) {
423
- throw new AtomixCLIError(
424
- pathValidation.error,
425
- 'INVALID_PATH',
426
- [
427
- 'Ensure the path is within the project directory',
428
- 'Avoid using ".." to navigate outside the project',
429
- 'Check for typos in the path'
430
- ]
431
- );
432
- }
433
-
434
- const componentPath = join(pathValidation.safePath, safeName);
435
-
436
- // Check if component already exists
437
- if (existsSync(componentPath) && !options.force) {
438
- throw new AtomixCLIError(
439
- `Component ${safeName} already exists`,
440
- 'COMPONENT_EXISTS',
441
- [
442
- `Delete the existing component at ${componentPath}`,
443
- 'Use --force flag to overwrite',
444
- 'Choose a different component name'
445
- ]
446
- );
447
- }
448
-
449
- // Create component directory
450
- await mkdir(componentPath, { recursive: true });
451
-
452
- // Generate composable hook
453
- const hookPath = join(process.cwd(), 'src/lib/composables');
454
- if (existsSync(hookPath)) {
455
- const hookContent = componentTemplates.composable.useHook(safeName);
456
- const hookFilePath = join(hookPath, `use${safeName}.ts`);
457
-
458
- if (!existsSync(hookFilePath) || options.force) {
459
- await writeFile(hookFilePath, hookContent, 'utf8');
460
- console.log(chalk.green(` ✓ Created use${safeName}.ts in src/lib/composables`));
461
- } else {
462
- console.log(chalk.yellow(` ⚠️ Hook file already exists: use${safeName}.ts`));
463
- }
464
- } else {
465
- console.log(chalk.yellow(` ⚠️ Composables directory not found: ${hookPath}`));
466
- }
467
-
468
- // Generate types in lib/types/components.ts
469
- const typesPath = join(process.cwd(), 'src/lib/types');
470
- if (existsSync(typesPath)) {
471
- const componentsTypesPath = join(typesPath, 'components.ts');
472
- if (existsSync(componentsTypesPath)) {
473
- let typesContent = await readFile(componentsTypesPath, 'utf8');
474
- const newTypeContent = componentTemplates.react.types(safeName);
475
-
476
- // Check if type already exists
477
- if (!typesContent.includes(`interface ${safeName}Props`)) {
478
- // Insert before the last export
479
- const lastExportIndex = typesContent.lastIndexOf('export');
480
- const insertionPoint = typesContent.lastIndexOf('\n\n', lastExportIndex) + 2;
481
- typesContent =
482
- typesContent.slice(0, insertionPoint) +
483
- '\n' + newTypeContent + '\n' +
484
- typesContent.slice(insertionPoint);
485
-
486
- await writeFile(componentsTypesPath, typesContent, 'utf8');
487
- console.log(chalk.green(` ✓ Added ${safeName}Props to src/lib/types/components.ts`));
488
- } else {
489
- console.log(chalk.yellow(` ⚠️ ${safeName}Props already exists in types`));
490
- }
491
- }
492
- }
493
-
494
- // Generate constants in lib/constants/components.ts
495
- const constantsPath = join(process.cwd(), 'src/lib/constants');
496
- if (existsSync(constantsPath)) {
497
- const constantsFilePath = join(constantsPath, 'components.ts');
498
- if (existsSync(constantsFilePath)) {
499
- let constantsContent = await readFile(constantsFilePath, 'utf8');
500
- const newConstantsContent = componentTemplates.react.constants(safeName);
501
-
502
- // Check if constants already exist
503
- if (!constantsContent.includes(`export const ${safeName.toUpperCase()}`)) {
504
- // Insert before the last export
505
- const lastExportIndex = constantsContent.lastIndexOf('export');
506
- const insertionPoint = constantsContent.lastIndexOf('\n\n', lastExportIndex) + 2;
507
- constantsContent =
508
- constantsContent.slice(0, insertionPoint) +
509
- '\n' + newConstantsContent + '\n' +
510
- constantsContent.slice(insertionPoint);
511
-
512
- await writeFile(constantsFilePath, constantsContent, 'utf8');
513
- console.log(chalk.green(` ✓ Added ${safeName.toUpperCase()} constants to src/lib/constants/components.ts`));
514
- } else {
515
- console.log(chalk.yellow(` ⚠️ ${safeName.toUpperCase()} constants already exist`));
516
- }
517
- }
518
- }
519
-
520
- // Generate component file
521
- const componentContent = componentTemplates.react.component(safeName);
522
-
523
- await writeFile(
524
- join(componentPath, `${safeName}.tsx`),
525
- componentContent,
526
- 'utf8'
527
- );
528
- spinner.succeed(chalk.green(`✓ Created ${safeName}.tsx`));
529
-
530
- // Generate index file
531
- const indexContent = componentTemplates.react.index(safeName);
532
- await writeFile(
533
- join(componentPath, 'index.ts'),
534
- indexContent,
535
- 'utf8'
536
- );
537
- console.log(chalk.green(` ✓ Created index.ts`));
538
-
539
- // Generate SCSS files
540
- if (!options.scssModule) {
541
- // Generate settings file first
542
- const settingsPath = join(process.cwd(), 'src/styles/01-settings');
543
- if (existsSync(settingsPath)) {
544
- const settingsContent = componentTemplates.react.settings(safeName);
545
- const settingsFilename = `_settings.${safeName.toLowerCase()}.scss`;
546
- const settingsFilePath = join(settingsPath, settingsFilename);
547
-
548
- if (!existsSync(settingsFilePath) || options.force) {
549
- await writeFile(settingsFilePath, settingsContent, 'utf8');
550
- console.log(chalk.green(` ✓ Created ${settingsFilename} in src/styles/01-settings`));
551
-
552
- // Update _index.scss in settings
553
- const settingsIndexPath = join(settingsPath, '_index.scss');
554
- if (existsSync(settingsIndexPath)) {
555
- let indexContent = await readFile(settingsIndexPath, 'utf8');
556
- const forwardStatement = `@forward 'settings.${safeName.toLowerCase()}';`;
557
-
558
- if (!indexContent.includes(forwardStatement)) {
559
- if (!indexContent.endsWith('\n')) indexContent += '\n';
560
- indexContent += `${forwardStatement}\n`;
561
- await writeFile(settingsIndexPath, indexContent, 'utf8');
562
- console.log(chalk.green(` ✓ Updated settings _index.scss`));
563
- }
564
- }
565
- } else {
566
- console.log(chalk.yellow(` ⚠️ Settings file already exists: ${settingsFilename}`));
567
- }
568
- }
569
-
570
- // Generate component SCSS file
571
- const scssContent = componentTemplates.scss.component(safeName);
572
- const scssPath = join(process.cwd(), 'src/styles/06-components');
573
- const scssFilename = `_components.${safeName.toLowerCase()}.scss`;
574
- const scssFilePath = join(scssPath, scssFilename);
575
-
576
- // Ensure styles directory exists
577
- if (existsSync(scssPath)) {
578
- // Check if SCSS file already exists
579
- if (!existsSync(scssFilePath) || options.force) {
580
- await writeFile(scssFilePath, scssContent, 'utf8');
581
- console.log(chalk.green(` ✓ Created ${scssFilename} in src/styles/06-components`));
582
-
583
- // Update _index.scss
584
- const indexPath = join(scssPath, '_index.scss');
585
- if (existsSync(indexPath)) {
586
- let indexContent = await readFile(indexPath, 'utf8');
587
- const forwardStatement = `@forward 'components.${safeName.toLowerCase()}';`;
588
-
589
- if (!indexContent.includes(forwardStatement)) {
590
- // Append to end of file, ensuring newline
591
- if (!indexContent.endsWith('\n')) indexContent += '\n';
592
- indexContent += `${forwardStatement}\n`;
593
- await writeFile(indexPath, indexContent, 'utf8');
594
- console.log(chalk.green(` ✓ Updated _index.scss`));
595
- }
596
- }
597
- } else {
598
- console.log(chalk.yellow(` ⚠️ SCSS file already exists: ${scssFilename}`));
599
- }
600
- } else {
601
- console.log(chalk.yellow(` ⚠️ Styles directory not found: ${scssPath}`));
602
- }
603
- } else {
604
- // Fallback for modules if strictly requested (though we discourage it)
605
- const scssContent = componentTemplates.react.scssModule(safeName);
606
- await writeFile(
607
- join(componentPath, `${safeName}.module.scss`),
608
- scssContent,
609
- 'utf8'
610
- );
611
- }
612
-
613
- // Generate Storybook story
614
- if (options.story) {
615
- const storyContent = componentTemplates.storybook.story(safeName);
616
- await writeFile(
617
- join(componentPath, `${safeName}.stories.tsx`),
618
- storyContent,
619
- 'utf8'
620
- );
621
- console.log(chalk.green(` ✓ Created ${safeName}.stories.tsx`));
622
- }
623
-
624
- // Generate test file
625
- if (options.test) {
626
- const testContent = componentTemplates.react.test(safeName);
627
- await writeFile(
628
- join(componentPath, `${safeName}.test.tsx`),
629
- testContent,
630
- 'utf8'
631
- );
632
- console.log(chalk.green(` ✓ Created ${safeName}.test.tsx`));
633
- }
634
-
635
- // Post-generation validation if requested
636
- if (options.validate) {
637
- const validationResult = await validateGeneratedComponent(safeName, componentPath);
638
- const isValid = displayValidationReport(validationResult);
639
-
640
- if (!isValid) {
641
- console.log(chalk.yellow('\n💡 Some issues were found. Consider addressing them for better component quality.'));
642
- }
643
- }
644
-
645
- // Success message with next steps
646
- console.log(boxen(
647
- chalk.bold.green(`🎉 Component ${safeName} created successfully!\n\n`) +
648
- chalk.cyan('Next steps:\n') +
649
- chalk.gray(`1. Import in your app:\n`) +
650
- chalk.white(` import { ${safeName} } from '${options.path}/${safeName}';\n\n`) +
651
- chalk.gray(`2. Add to design system exports:\n`) +
652
- chalk.white(` export { ${safeName} } from './${safeName}';\n\n`) +
653
- chalk.gray(`3. Run Storybook to see your component:\n`) +
654
- chalk.white(` npm run storybook\n\n`) +
655
- chalk.gray(`4. Validate component quality:\n`) +
656
- chalk.white(` atomix validate component ${safeName}`),
657
- {
658
- padding: 1,
659
- margin: 1,
660
- borderStyle: 'round',
661
- borderColor: 'green'
662
- }
663
- ));
664
-
665
- } else if (type === 'hook' || type === 'h') {
666
- const hookPathValidation = validatePath(sanitizeInput(options.path));
667
- if (!hookPathValidation.isValid) {
668
- throw new AtomixCLIError(
669
- hookPathValidation.error,
670
- 'INVALID_PATH',
671
- [
672
- 'Ensure the path is within the project directory',
673
- 'Avoid using ".." to navigate outside the project',
674
- 'Example: --path ./src/components'
675
- ]
676
- );
677
- }
678
- const componentPath = join(hookPathValidation.safePath, `use${safeName}`);
679
- await mkdir(componentPath, { recursive: true });
680
-
681
- const hookContent = componentTemplates.hook.hook(safeName);
682
- await writeFile(join(componentPath, `use${safeName}.ts`), hookContent, 'utf8');
683
-
684
- if (options.test) {
685
- const testContent = componentTemplates.hook.test(safeName);
686
- await writeFile(join(componentPath, `use${safeName}.test.ts`), testContent, 'utf8');
687
- }
688
-
689
- spinner.succeed(chalk.green(`✓ Created hook use${safeName}`));
690
-
691
- } else if (type === 'layout' || type === 'l') {
692
- const layoutPathValidation = validatePath(sanitizeInput(options.path));
693
- if (!layoutPathValidation.isValid) {
694
- throw new AtomixCLIError(
695
- layoutPathValidation.error,
696
- 'INVALID_PATH',
697
- [
698
- 'Ensure the path is within the project directory',
699
- 'Avoid using ".." to navigate outside the project',
700
- 'Example: --path ./src/layouts'
701
- ]
702
- );
703
- }
704
- const layoutPath = join(layoutPathValidation.safePath, safeName);
705
- await mkdir(layoutPath, { recursive: true });
706
-
707
- const componentContent = componentTemplates.layout.component(safeName);
708
- await writeFile(join(layoutPath, `${safeName}.tsx`), componentContent, 'utf8');
709
-
710
- const scssContent = componentTemplates.layout.scss(safeName);
711
- const scssFilename = `_layouts.${safeName.toLowerCase()}.scss`;
712
- const scssPath = join(process.cwd(), 'src/styles/05-layouts');
713
-
714
- if (existsSync(scssPath)) {
715
- await writeFile(join(scssPath, scssFilename), scssContent, 'utf8');
716
-
717
- // Update layouts index
718
- const indexPath = join(scssPath, '_index.scss');
719
- if (existsSync(indexPath)) {
720
- let indexContent = await readFile(indexPath, 'utf8');
721
- const forwardStatement = `@forward 'layouts.${safeName.toLowerCase()}';`;
722
- if (!indexContent.includes(forwardStatement)) {
723
- indexContent += `\n${forwardStatement}`;
724
- await writeFile(indexPath, indexContent, 'utf8');
725
- }
726
- }
727
- }
728
-
729
- spinner.succeed(chalk.green(`✓ Created layout ${safeName}`));
730
-
731
- } else if (type === 'context' || type === 'ctx') {
732
- const contextPathValidation = validatePath(sanitizeInput(options.path));
733
- if (!contextPathValidation.isValid) {
734
- throw new AtomixCLIError(
735
- contextPathValidation.error,
736
- 'INVALID_PATH',
737
- [
738
- 'Ensure the path is within the project directory',
739
- 'Avoid using ".." to navigate outside the project',
740
- 'Example: --path ./src/contexts'
741
- ]
742
- );
743
- }
744
- const contextPath = join(contextPathValidation.safePath, `${safeName}Context`);
745
- await mkdir(contextPath, { recursive: true });
746
-
747
- const contextContent = componentTemplates.context.context(safeName);
748
- await writeFile(join(contextPath, `${safeName}Context.tsx`), contextContent, 'utf8');
749
-
750
- spinner.succeed(chalk.green(`✓ Created context ${safeName}Context`));
751
-
752
- } else if (type === 'token' || type === 't') {
753
- // Token generation
754
- const validCategories = ['colors', 'spacing', 'typography', 'shadows', 'radius', 'animations'];
755
-
756
- if (!validCategories.includes(name.toLowerCase())) {
757
- throw new AtomixCLIError(
758
- `Invalid token category: ${name}`,
759
- 'INVALID_TOKEN_CATEGORY',
760
- [
761
- `Valid categories: ${validCategories.join(', ')}`,
762
- 'Example: atomix generate token colors',
763
- 'Example: atomix g t spacing'
764
- ]
765
- );
766
- }
767
-
768
- const tokenPath = join(process.cwd(), 'src/styles/01-settings');
769
-
770
- // Check if settings directory exists
771
- if (!existsSync(tokenPath)) {
772
- throw new AtomixCLIError(
773
- 'Settings directory not found',
774
- 'MISSING_DIRECTORY',
775
- [
776
- 'Ensure you are in an Atomix project directory',
777
- 'Create the directory: mkdir -p src/styles/01-settings',
778
- 'Or initialize a new project: atomix init'
779
- ]
780
- );
781
- }
782
-
783
- // Generate token file based on category
784
- let tokenContent = '';
785
- let filename = '';
786
-
787
- switch (name.toLowerCase()) {
788
- case 'colors':
789
- filename = '_settings.colors.custom.scss';
790
- tokenContent = generateColorTokens();
791
- break;
792
- case 'spacing':
793
- filename = '_settings.spacing.custom.scss';
794
- tokenContent = generateSpacingTokens();
795
- break;
796
- case 'typography':
797
- filename = '_settings.typography.custom.scss';
798
- tokenContent = generateTypographyTokens();
799
- break;
800
- case 'shadows':
801
- filename = '_settings.box-shadow.custom.scss';
802
- tokenContent = generateShadowTokens();
803
- break;
804
- case 'radius':
805
- filename = '_settings.border-radius.custom.scss';
806
- tokenContent = generateRadiusTokens();
807
- break;
808
- case 'animations':
809
- filename = '_settings.animations.custom.scss';
810
- tokenContent = generateAnimationTokens();
811
- break;
812
- }
813
-
814
- const filePath = join(tokenPath, filename);
815
-
816
- // Check if file already exists
817
- if (existsSync(filePath) && !options.force) {
818
- throw new AtomixCLIError(
819
- `Token file already exists: ${filename}`,
820
- 'FILE_EXISTS',
821
- [
822
- 'Use --force flag to overwrite',
823
- `Or edit the existing file: ${filePath}`,
824
- 'Or choose a different category'
825
- ]
826
- );
827
- }
828
-
829
- // Write token file
830
- await writeFile(filePath, tokenContent, 'utf8');
831
- spinner.succeed(chalk.green(`✓ Created token file: ${filename}`));
832
-
833
- // Success message
834
- console.log(boxen(
835
- chalk.bold.green(`🎨 ${name} tokens generated successfully!\n\n`) +
836
- chalk.cyan('Next steps:\n') +
837
- chalk.gray(`1. Customize your tokens:\n`) +
838
- chalk.white(` Edit ${filePath}\n\n`) +
839
- chalk.gray(`2. Import in your theme:\n`) +
840
- chalk.white(` @use '${filename.replace('.scss', '')}' as *;\n\n`) +
841
- chalk.gray(`3. Build your styles:\n`) +
842
- chalk.white(` npm run build`),
843
- {
844
- padding: 1,
845
- margin: 1,
846
- borderStyle: 'round',
847
- borderColor: 'green'
848
- }
849
- ));
850
- } else {
851
- throw new AtomixCLIError(
852
- `Unknown generation type: ${type}`,
853
- 'UNKNOWN_TYPE',
854
- [
855
- 'Valid types are: component (or c), token (or t)',
856
- 'Example: atomix generate component Button',
857
- 'Example: atomix g c Button'
858
- ]
859
- );
860
- }
861
-
862
- } catch (error) {
863
- handleError(error, spinner);
864
- }
865
- });
866
-
867
- /**
868
- * Validate Command - NEW
869
- */
870
- program
871
- .command('validate [target]')
872
- .description('Validate themes, design tokens, components, or accessibility')
873
- .option('--tokens', 'Validate design tokens', false)
874
- .option('--theme <path>', 'Validate specific theme', '')
875
- .option('--component <name>', 'Validate specific component', '')
876
- .option('--a11y, --accessibility', 'Check accessibility compliance', false)
877
- .option('--fix', 'Attempt to fix issues automatically', false)
878
- .action(async (target, options) => {
879
- const spinner = ora('Running validation...').start();
880
-
881
66
  try {
882
- debug('Validation options:', options);
883
-
884
- const issues = [];
885
- const warnings = [];
886
-
887
- // Token validation
888
- if (options.tokens || target === 'tokens') {
889
- spinner.text = 'Validating design tokens...';
890
-
891
- const tokenFiles = [
892
- 'src/styles/01-settings/_settings.colors.scss',
893
- 'src/styles/01-settings/_settings.typography.scss',
894
- 'src/styles/01-settings/_settings.spacing.scss',
895
- 'src/styles/01-settings/_settings.radius.scss'
896
- ];
897
-
898
- for (const file of tokenFiles) {
899
- const filePath = join(process.cwd(), file);
900
- if (existsSync(filePath)) {
901
- const content = await readFile(filePath, 'utf8');
902
-
903
- // Check for hardcoded values
904
- const hardcodedColors = content.match(/#[0-9a-fA-F]{3,8}(?![0-9a-fA-F])/g);
905
- if (hardcodedColors && hardcodedColors.length > 0) {
906
- warnings.push({
907
- file: file,
908
- issue: `Found ${hardcodedColors.length} hardcoded color values`,
909
- suggestion: 'Use CSS custom properties or SCSS variables'
910
- });
911
- }
912
-
913
- // Check for missing default flags
914
- const variables = content.match(/\$[a-z-]+:/gi);
915
- const defaultFlags = content.match(/!default/g);
916
- if (variables && (!defaultFlags || defaultFlags.length < variables.length)) {
917
- warnings.push({
918
- file: file,
919
- issue: 'Some variables missing !default flag',
920
- suggestion: 'Add !default to all token variables for better theming'
921
- });
922
- }
923
- } else {
924
- issues.push({
925
- file: file,
926
- issue: 'Token file not found',
927
- suggestion: 'Ensure all token files are present in src/styles/01-settings/'
928
- });
929
- }
930
- }
931
- }
932
-
933
- // Component validation
934
- if (options.component) {
935
- spinner.text = `Validating component: ${options.component}...`;
936
-
937
- const componentPath = join('./src/components', options.component);
938
- const validationResult = await validateGeneratedComponent(options.component, componentPath);
939
-
940
- // Convert validation results to issue/warning format
941
- validationResult.issues.forEach(issue => {
942
- issues.push({
943
- file: options.component,
944
- issue: issue,
945
- suggestion: 'Check component structure and implementation'
946
- });
947
- });
948
-
949
- validationResult.warnings.forEach(warning => {
950
- warnings.push({
951
- file: options.component,
952
- issue: warning,
953
- suggestion: 'Improve component quality by addressing warnings'
954
- });
955
- });
956
- }
957
-
958
- // Theme validation
959
- if (options.theme) {
960
- spinner.text = `Validating theme: ${options.theme}...`;
961
-
962
- const themePath = resolve(options.theme);
963
- if (!existsSync(themePath)) {
964
- issues.push({
965
- file: options.theme,
966
- issue: 'Theme file not found',
967
- suggestion: 'Check the theme path is correct'
968
- });
969
- } else {
970
- const content = await readFile(themePath, 'utf8');
971
-
972
- // Check for required imports
973
- const requiredImports = [
974
- '@import.*settings',
975
- '@use.*settings',
976
- '@import.*tools',
977
- '@use.*tools'
978
- ];
979
-
980
- let hasSettings = false;
981
- for (const pattern of requiredImports) {
982
- if (new RegExp(pattern).test(content)) {
983
- hasSettings = true;
984
- break;
985
- }
986
- }
987
-
988
- if (!hasSettings) {
989
- issues.push({
990
- file: options.theme,
991
- issue: 'Missing design system imports',
992
- suggestion: 'Import settings and tools from the design system'
993
- });
994
- }
995
- }
996
- }
997
-
998
- // Accessibility validation
999
- if (options.a11y || options.accessibility) {
1000
- spinner.text = 'Checking accessibility compliance...';
1001
-
1002
- // Check for focus styles
1003
- const componentFiles = [
1004
- 'src/styles/06-components'
1005
- ];
1006
-
1007
- for (const dir of componentFiles) {
1008
- const dirPath = join(process.cwd(), dir);
1009
- if (existsSync(dirPath)) {
1010
- // This is a simplified check - in reality, we'd parse the CSS
1011
- warnings.push({
1012
- file: dir,
1013
- issue: 'Manual accessibility review recommended',
1014
- suggestion: 'Ensure all interactive components have :focus-visible styles'
1015
- });
1016
- }
1017
- }
1018
- }
1019
-
1020
- spinner.stop();
1021
-
1022
- // Display results
1023
- if (issues.length === 0 && warnings.length === 0) {
1024
- console.log(boxen(
1025
- chalk.bold.green('✅ All validations passed!\n\n') +
1026
- chalk.gray('Your design system is following best practices.'),
1027
- {
1028
- padding: 1,
1029
- margin: 1,
1030
- borderStyle: 'round',
1031
- borderColor: 'green'
1032
- }
1033
- ));
1034
- } else {
1035
- if (issues.length > 0) {
1036
- console.log(chalk.bold.red(`\n❌ Found ${issues.length} issue(s):\n`));
1037
- issues.forEach((issue, index) => {
1038
- console.log(chalk.red(` ${index + 1}. ${issue.file}`));
1039
- console.log(chalk.gray(` Issue: ${issue.issue}`));
1040
- console.log(chalk.yellow(` Fix: ${issue.suggestion}\n`));
1041
- });
1042
- }
1043
-
1044
- if (warnings.length > 0) {
1045
- console.log(chalk.bold.yellow(`\n⚠️ Found ${warnings.length} warning(s):\n`));
1046
- warnings.forEach((warning, index) => {
1047
- console.log(chalk.yellow(` ${index + 1}. ${warning.file}`));
1048
- console.log(chalk.gray(` Warning: ${warning.issue}`));
1049
- console.log(chalk.cyan(` Suggestion: ${warning.suggestion}\n`));
1050
- });
1051
- }
1052
-
1053
- if (options.fix && (issues.length > 0 || warnings.length > 0)) {
1054
- console.log(chalk.cyan('\n🔧 Attempting to fix issues...'));
1055
-
1056
- let fixedCount = 0;
1057
-
1058
- // Run token fixes
1059
- if (options.tokens || target === 'tokens' || !target) {
1060
- const tokenFixResult = await fixTokens(options);
1061
- fixedCount += tokenFixResult.totalFixed;
1062
- }
1063
-
1064
- if (fixedCount > 0) {
1065
- console.log(chalk.green(`\n✨ Fixed ${fixedCount} issue(s). Please run validate again to verify.`));
1066
- } else {
1067
- console.log(chalk.yellow('\nCould not automatically fix all reported issues. Manual intervention required.'));
1068
- }
1069
- } else {
1070
- console.log(chalk.yellow('\nCould not automatically fix reported issues. Manual intervention required.'));
1071
- }
1072
- }
67
+ await generateAction(type, name, options);
1073
68
  } catch (error) {
1074
- handleError(error, spinner);
69
+ await handleCLIError(error);
1075
70
  }
1076
71
  });
1077
72
 
1078
73
  /**
1079
- * Dev Command - NEW (Alias for build --watch)
74
+ * Theme Building
1080
75
  */
1081
76
  program
1082
- .command('dev <theme>')
1083
- .description('Start development mode with hot reload')
77
+ .command('build-theme <path>')
78
+ .description('Build a custom theme from SCSS')
1084
79
  .option('-o, --output <path>', 'Output directory', './dist')
1085
- .option('--open', 'Open in browser after build', false)
1086
- .action(async (theme, options) => {
1087
- console.log(boxen(
1088
- chalk.bold.cyan('🚀 Starting Atomix Dev Mode\n\n') +
1089
- chalk.gray('Watching for changes...\n') +
1090
- chalk.gray('Press Ctrl+C to exit'),
1091
- {
1092
- padding: 1,
1093
- margin: 1,
1094
- borderStyle: 'round',
1095
- borderColor: 'cyan'
1096
- }
1097
- ));
1098
-
1099
- // Reuse build command with watch flag
1100
- await program.parseAsync([
1101
- ...process.argv.slice(0, 2),
1102
- 'build-theme',
1103
- theme,
1104
- '--watch',
1105
- '--output', options.output
1106
- ]);
1107
- });
1108
-
1109
- // Keep existing commands (create-theme, list-themes, info)
1110
- // ... [Previous create-theme, list-themes, and info commands remain the same]
1111
-
1112
- /**
1113
- * Migrate Command - NEW (Migration from other frameworks)
1114
- */
1115
- program
1116
- .command('migrate <from>')
1117
- .description('Migrate from other CSS frameworks to Atomix design system')
1118
- .option('-s, --source <path>', 'Source directory to migrate', './src')
1119
- .option('--dry-run', 'Preview changes without modifying files', false)
1120
- .option('--create-backup', 'Create backup before migration', true)
1121
- .action(async (from, options) => {
1122
- const spinner = ora('Preparing migration...').start();
1123
-
1124
- try {
1125
- debug(`Migrating from ${from}`, options);
1126
-
1127
- // Validate migration type
1128
- const validMigrations = ['tailwind', 'bootstrap', 'scss-variables'];
1129
- if (!validMigrations.includes(from.toLowerCase())) {
1130
- throw new AtomixCLIError(
1131
- `Unknown migration source: ${from}`,
1132
- 'INVALID_MIGRATION',
1133
- [
1134
- 'Valid migration sources: tailwind, bootstrap, scss-variables',
1135
- 'Example: atomix migrate tailwind',
1136
- 'Example: atomix migrate bootstrap --source ./src'
1137
- ]
1138
- );
1139
- }
1140
-
1141
- const sanitizedSource = sanitizeInput(options.source);
1142
- const sourceValidation = validatePath(sanitizedSource);
1143
- if (!sourceValidation.isValid) {
1144
- throw new AtomixCLIError(
1145
- sourceValidation.error,
1146
- 'INVALID_PATH',
1147
- [
1148
- 'Ensure source path is within the project directory',
1149
- 'Avoid sensitive or absolute system paths',
1150
- 'Example: --source ./src'
1151
- ]
1152
- );
1153
- }
1154
- const sourcePath = resolve(sourceValidation.safePath);
1155
- if (!existsSync(sourcePath)) {
1156
- throw new AtomixCLIError(
1157
- `Source directory not found: ${sourcePath}`,
1158
- 'SOURCE_NOT_FOUND',
1159
- [
1160
- 'Check the source path is correct',
1161
- 'Use --source flag to specify a different directory',
1162
- 'Example: atomix migrate tailwind --source ./app'
1163
- ]
1164
- );
1165
- }
1166
-
1167
- spinner.stop();
1168
-
1169
- // Show migration preview
1170
- console.log(boxen(
1171
- chalk.bold.cyan(`🔄 Migration Preview\n\n`) +
1172
- chalk.gray(`From: ${chalk.white(from)}\n`) +
1173
- chalk.gray(`Source: ${chalk.white(sourcePath)}\n`) +
1174
- chalk.gray(`Mode: ${options.dryRun ? chalk.yellow('Dry Run') : chalk.green('Live')}\n`) +
1175
- chalk.gray(`Backup: ${options.createBackup ? chalk.green('Yes') : chalk.red('No')}`),
1176
- {
1177
- padding: 1,
1178
- margin: 1,
1179
- borderStyle: 'round',
1180
- borderColor: 'cyan'
1181
- }
1182
- ));
1183
-
1184
- // Confirm migration
1185
- if (!options.dryRun) {
1186
- const { confirmMigration } = await inquirer.prompt([
1187
- {
1188
- type: 'confirm',
1189
- name: 'confirmMigration',
1190
- message: chalk.yellow('This will modify your files. Continue?'),
1191
- default: false
1192
- }
1193
- ]);
1194
-
1195
- if (!confirmMigration) {
1196
- console.log(chalk.yellow('\n Migration cancelled.'));
1197
- return;
1198
- }
1199
- }
1200
-
1201
- // Create backup if requested (cross-platform: fs.cpSync works on Windows and Unix)
1202
- if (options.createBackup && !options.dryRun) {
1203
- const backupSpinner = ora('Creating backup...').start();
1204
- const backupDir = `${sourcePath}.backup.${Date.now()}`;
1205
-
1206
- try {
1207
- cpSync(sourcePath, backupDir, { recursive: true });
1208
- backupSpinner.succeed(chalk.green(`✓ Backup created: ${backupDir}`));
1209
- } catch (error) {
1210
- backupSpinner.warn(chalk.yellow('Could not create backup, continuing anyway...'));
1211
- }
1212
- }
1213
-
1214
- // Run migration
1215
- let report;
1216
-
1217
- switch (from.toLowerCase()) {
1218
- case 'tailwind':
1219
- report = await migrateTailwind(sourcePath, options);
1220
- break;
1221
-
1222
- case 'bootstrap':
1223
- report = await migrateBootstrap(sourcePath, options);
1224
- break;
1225
-
1226
- case 'scss-variables':
1227
- report = await migrateSCSSVariables(sourcePath, options);
1228
- break;
1229
- }
1230
-
1231
- // Display report
1232
- displayMigrationReport(report);
1233
-
1234
- // Next steps
1235
- if (!options.dryRun && report.filesProcessed > 0) {
1236
- console.log(chalk.cyan('\n📝 Next Steps:'));
1237
- console.log(chalk.gray(' 1. Review the changes in your code'));
1238
- console.log(chalk.gray(' 2. Install Atomix: npm install @shohojdhara/atomix'));
1239
- console.log(chalk.gray(' 3. Import Atomix styles: import "@shohojdhara/atomix/css"'));
1240
- console.log(chalk.gray(' 4. Test your application thoroughly'));
1241
- console.log(chalk.gray(' 5. Customize with your theme: atomix create-theme custom'));
1242
- }
1243
-
1244
- } catch (error) {
1245
- handleError(error, spinner);
1246
- }
1247
- });
1248
-
1249
- /**
1250
- * Init Command - NEW (Interactive Setup Wizard)
1251
- */
1252
- program
1253
- .command('init')
1254
- .description('Interactive setup wizard for Atomix design system')
1255
- .option('--skip-install', 'Skip dependency installation', false)
1256
- .action(async (options) => {
1257
- try {
1258
- // Set environment variable for skip install if needed
1259
- if (options.skipInstall) {
1260
- process.env.ATOMIX_SKIP_INSTALL = 'true';
1261
- }
1262
-
1263
- // Run the interactive wizard
1264
- await runInitWizard();
1265
- } catch (error) {
1266
- handleError(error);
1267
- }
1268
- });
1269
-
1270
- /**
1271
- * Tokens Command - NEW (Design Token Management)
1272
- */
1273
- program
1274
- .command('tokens <action>')
1275
- .description('Manage design tokens (list, validate, export, import)')
1276
- .option('-c, --category <category>', 'Token category (colors, typography, spacing, etc.)')
1277
- .option('-f, --format <format>', 'Export format (json, css, scss, js, ts)', 'json')
1278
- .option('-o, --output <path>', 'Output file path')
1279
- .option('--dry-run', 'Preview changes without modifying files', false)
1280
- .action(async (action, options) => {
1281
- try {
1282
- debug(`Token action: ${action}`, options);
1283
-
1284
- switch (action.toLowerCase()) {
1285
- case 'list':
1286
- case 'ls':
1287
- await listTokens(options.category);
1288
- break;
1289
-
1290
- case 'validate':
1291
- case 'check': {
1292
- const validationResult = await validateTokens(options);
1293
- if (validationResult.issues.length > 0) {
1294
- process.exit(1); // Exit with error if issues found
1295
- }
1296
- break;
1297
- }
1298
-
1299
- case 'export':
1300
- if (!options.format) {
1301
- throw new AtomixCLIError(
1302
- 'Export format is required',
1303
- 'MISSING_FORMAT',
1304
- [
1305
- 'Specify format with --format flag',
1306
- 'Valid formats: json, css, scss, js, ts',
1307
- 'Example: atomix tokens export --format json'
1308
- ]
1309
- );
1310
- }
1311
- if (options.output) {
1312
- const outValidation = validatePath(sanitizeInput(options.output));
1313
- if (!outValidation.isValid) {
1314
- throw new AtomixCLIError(
1315
- outValidation.error,
1316
- 'INVALID_PATH',
1317
- [
1318
- 'Use a project-relative output file path',
1319
- 'Example: --output ./tokens.json'
1320
- ]
1321
- );
1322
- }
1323
- await exportTokens(options.format, outValidation.safePath);
1324
- } else {
1325
- await exportTokens(options.format, options.output);
1326
- }
1327
- break;
1328
-
1329
- case 'import':
1330
- if (!options.output) {
1331
- throw new AtomixCLIError(
1332
- 'Import file path is required',
1333
- 'MISSING_PATH',
1334
- [
1335
- 'Specify file with --output flag',
1336
- 'Example: atomix tokens import --output tokens.json'
1337
- ]
1338
- );
1339
- }
1340
- {
1341
- const inValidation = validatePath(sanitizeInput(options.output));
1342
- if (!inValidation.isValid) {
1343
- throw new AtomixCLIError(
1344
- inValidation.error,
1345
- 'INVALID_PATH',
1346
- [
1347
- 'Use a project-relative input file path',
1348
- 'Example: --output ./tokens.json'
1349
- ]
1350
- );
1351
- }
1352
- await importTokens(inValidation.safePath, { dryRun: options.dryRun });
1353
- }
1354
- break;
1355
-
1356
- default:
1357
- throw new AtomixCLIError(
1358
- `Unknown token action: ${action}`,
1359
- 'UNKNOWN_ACTION',
1360
- [
1361
- 'Valid actions: list, validate, export, import',
1362
- 'Example: atomix tokens list',
1363
- 'Example: atomix tokens export --format json'
1364
- ]
1365
- );
1366
- }
1367
-
1368
- } catch (error) {
1369
- handleError(error);
1370
- }
1371
- });
1372
-
1373
- /**
1374
- * Theme Command Group - NEW (Integrated with Theme Devtools)
1375
- */
1376
- const themeCommand = program
1377
- .command('theme')
1378
- .description('Theme management commands');
1379
-
1380
- // Theme validate
1381
- themeCommand
1382
- .command('validate')
1383
- .description('Validate theme configuration')
1384
- .option('--config <path>', 'Path to theme config file')
1385
- .option('--strict', 'Enable strict validation')
1386
- .action(async (options) => {
1387
- try {
1388
- const themeCLI = createThemeCLIBridge();
1389
- await themeCLI.validate(options);
1390
- } catch (error) {
1391
- handleError(error);
1392
- }
1393
- });
1394
-
1395
- // Theme list
1396
- themeCommand
1397
- .command('list')
1398
- .alias('ls')
1399
- .description('List all available themes')
1400
- .action(async () => {
1401
- try {
1402
- const themeCLI = createThemeCLIBridge();
1403
- await themeCLI.list();
1404
- } catch (error) {
1405
- handleError(error);
1406
- }
1407
- });
1408
-
1409
- // Theme inspect
1410
- themeCommand
1411
- .command('inspect <name>')
1412
- .description('Inspect a specific theme')
1413
- .option('--json', 'Output as JSON')
1414
- .action(async (name, options) => {
1415
- try {
1416
- const themeCLI = createThemeCLIBridge();
1417
- await themeCLI.inspect(name, options);
1418
- } catch (error) {
1419
- handleError(error);
1420
- }
1421
- });
1422
-
1423
- // Theme compare
1424
- themeCommand
1425
- .command('compare <theme1> <theme2>')
1426
- .description('Compare two themes')
1427
- .action(async (theme1, theme2) => {
1428
- try {
1429
- const themeCLI = createThemeCLIBridge();
1430
- await themeCLI.compare(theme1, theme2);
1431
- } catch (error) {
1432
- handleError(error);
1433
- }
1434
- });
1435
-
1436
- // Theme export
1437
- themeCommand
1438
- .command('export <name>')
1439
- .description('Export theme to JSON')
1440
- .option('-o, --output <path>', 'Output file path')
1441
- .action(async (name, options) => {
80
+ .option('-m, --minify', 'Minify CSS', true)
81
+ .option('-s, --sourcemap', 'Generate source maps', false)
82
+ .option('-w, --watch', 'Watch mode', false)
83
+ .option('--analyze', 'Analyze bundle', false)
84
+ .action(async (themePath, options) => {
1442
85
  try {
1443
- const themeCLI = createThemeCLIBridge();
1444
- await themeCLI.export(name, options);
86
+ await buildThemeAction(themePath, options);
1445
87
  } catch (error) {
1446
- handleError(error);
88
+ await handleCLIError(error);
1447
89
  }
1448
90
  });
1449
91
 
1450
- // Theme create - NEW
1451
- themeCommand
1452
- .command('create <name>')
1453
- .description('Create a new theme')
1454
- .option('-t, --type <type>', 'Theme type (css|js)', 'css')
1455
- .option('--template <name>', 'Use template (dark|light|high-contrast)')
1456
- .option('--interactive', 'Interactive mode', false)
1457
- .option('-o, --output <path>', 'Output directory', './themes')
1458
- .option('-f, --force', 'Overwrite existing theme', false)
1459
- .action(async (name, options) => {
1460
- const spinner = ora('Creating theme...').start();
1461
-
1462
- try {
1463
- debug(`Creating theme: ${name}`, options);
1464
-
1465
- // Validate name
1466
- const nameValidation = validateThemeName(name);
1467
- if (!nameValidation.isValid) {
1468
- throw new AtomixCLIError(
1469
- nameValidation.error,
1470
- 'INVALID_NAME',
1471
- [
1472
- 'Use lowercase letters, numbers, and hyphens',
1473
- 'Start with a letter',
1474
- 'Example: dark-theme, light-mode, custom-theme',
1475
- 'Avoid consecutive or trailing hyphens'
1476
- ]
1477
- );
1478
- }
1479
-
1480
- const themePath = join(options.output, name);
1481
-
1482
- // Check if theme already exists
1483
- if (existsSync(themePath)) {
1484
- if (options.force) {
1485
- await rm(themePath, { recursive: true, force: true });
1486
- await mkdir(themePath, { recursive: true });
1487
- spinner.info(chalk.yellow(`Overwriting existing theme: ${name}`));
1488
- } else {
1489
- throw new AtomixCLIError(
1490
- `Theme ${name} already exists`,
1491
- 'THEME_EXISTS',
1492
- [
1493
- `Delete the existing theme at ${themePath}`,
1494
- 'Choose a different theme name',
1495
- 'Use --force flag to overwrite'
1496
- ]
1497
- );
1498
- }
1499
- } else {
1500
- // Create theme directory
1501
- await mkdir(themePath, { recursive: true });
1502
- }
1503
-
1504
- // Generate theme files based on type
1505
- if (options.type === 'css') {
1506
- // Create SCSS theme
1507
- const scssContent = `// Theme: ${name}
1508
- // =============================================================================
1509
-
1510
- @import '../../src/styles/01-settings';
1511
- @import '../../src/styles/02-tools';
1512
-
1513
- // Theme Variables
1514
- // =============================================================================
1515
- :root[data-theme="${name}"] {
1516
- // Colors
1517
- --atomix-color-primary: #7AFFD7;
1518
- --atomix-color-secondary: #FF5733;
1519
- --atomix-color-success: #4DFF9F;
1520
- --atomix-color-error: #FF1A1A;
1521
- --atomix-color-warning: #FFB84D;
1522
-
1523
- // Background
1524
- --atomix-color-background: #000000;
1525
- --atomix-color-surface: #212121;
1526
-
1527
- // Text
1528
- --atomix-color-text: #FFFFFF;
1529
- --atomix-color-text-secondary: rgba(255, 255, 255, 0.8);
1530
-
1531
- // Border
1532
- --atomix-color-border: rgba(255, 255, 255, 0.1);
1533
-
1534
- // Spacing (if needed)
1535
- // --atomix-space-base: 16px;
1536
-
1537
- // Typography (if needed)
1538
- // --atomix-font-family-base: 'Inter', sans-serif;
1539
- }
1540
-
1541
- // Theme-specific Component Overrides
1542
- // =============================================================================
1543
- [data-theme="${name}"] {
1544
- // Add component-specific overrides here
1545
-
1546
- .c-button {
1547
- // Button overrides
1548
- }
1549
-
1550
- .c-card {
1551
- // Card overrides
1552
- }
1553
- }
1554
- `;
1555
-
1556
- await writeFile(join(themePath, 'index.scss'), scssContent, 'utf8');
1557
- spinner.succeed(chalk.green(`✓ Created ${name}/index.scss`));
1558
-
1559
- } else if (options.type === 'js') {
1560
- // Create JavaScript theme
1561
- const jsContent = `/**
1562
- * Theme: ${name}
1563
- */
1564
-
1565
- import { createTheme } from '@shohojdhara/atomix/theme';
1566
-
1567
- export const ${name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}Theme = createTheme({
1568
- name: '${name}',
1569
- palette: {
1570
- primary: {
1571
- main: '#7AFFD7',
1572
- light: '#A0FFE6',
1573
- dark: '#00E6C3',
1574
- contrastText: '#000000',
1575
- },
1576
- secondary: {
1577
- main: '#FF5733',
1578
- light: '#FF8A65',
1579
- dark: '#E64A19',
1580
- contrastText: '#FFFFFF',
1581
- },
1582
- success: {
1583
- main: '#4DFF9F',
1584
- light: '#80FFB8',
1585
- dark: '#00E66B',
1586
- contrastText: '#000000',
1587
- },
1588
- error: {
1589
- main: '#FF1A1A',
1590
- light: '#FF5252',
1591
- dark: '#E60000',
1592
- contrastText: '#FFFFFF',
1593
- },
1594
- warning: {
1595
- main: '#FFB84D',
1596
- light: '#FFCC80',
1597
- dark: '#FF9800',
1598
- contrastText: '#000000',
1599
- },
1600
- background: {
1601
- default: '#000000',
1602
- paper: '#212121',
1603
- },
1604
- text: {
1605
- primary: '#FFFFFF',
1606
- secondary: 'rgba(255, 255, 255, 0.8)',
1607
- disabled: 'rgba(255, 255, 255, 0.5)',
1608
- },
1609
- },
1610
- typography: {
1611
- fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
1612
- fontSize: 16,
1613
- fontWeightLight: 300,
1614
- fontWeightRegular: 400,
1615
- fontWeightMedium: 500,
1616
- fontWeightBold: 700,
1617
- },
1618
- spacing: {
1619
- unit: 8,
1620
- },
1621
- shape: {
1622
- borderRadius: 6,
1623
- },
92
+ // Run program
93
+ program.parseAsync(process.argv).catch(async (error) => {
94
+ await handleCLIError(error);
1624
95
  });
1625
-
1626
- export default ${name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}Theme;
1627
- `;
1628
-
1629
- await writeFile(join(themePath, 'index.ts'), jsContent, 'utf8');
1630
- spinner.succeed(chalk.green(`✓ Created ${name}/index.ts`));
1631
- }
1632
-
1633
- // Create README
1634
- const readmeContent = `# ${name} Theme
1635
-
1636
- ## Description
1637
-
1638
- A custom theme for Atomix Design System.
1639
-
1640
- ## Usage
1641
-
1642
- ### CSS Theme
1643
-
1644
- \`\`\`scss
1645
- @import 'themes/${name}';
1646
- \`\`\`
1647
-
1648
- ### JavaScript Theme
1649
-
1650
- \`\`\`typescript
1651
- import { ${name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}Theme } from './themes/${name}';
1652
- import { ThemeProvider } from '@shohojdhara/atomix/theme';
1653
-
1654
- function App() {
1655
- return (
1656
- <ThemeProvider theme={${name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}Theme}>
1657
- {/* Your app */}
1658
- </ThemeProvider>
1659
- );
1660
- }
1661
- \`\`\`
1662
-
1663
- ## Customization
1664
-
1665
- Edit the theme variables in \`index.${options.type === 'css' ? 'scss' : 'ts'}\` to customize colors, typography, spacing, and more.
1666
-
1667
- ## Build
1668
-
1669
- \`\`\`bash
1670
- atomix build-theme themes/${name}
1671
- \`\`\`
1672
- `;
1673
-
1674
- await writeFile(join(themePath, 'README.md'), readmeContent, 'utf8');
1675
- console.log(chalk.green(` ✓ Created ${name}/README.md`));
1676
-
1677
- // Success message
1678
- console.log(boxen(
1679
- chalk.bold.green(`🎨 Theme "${name}" created successfully!\n\n`) +
1680
- chalk.cyan('Next steps:\n') +
1681
- chalk.gray(`1. Customize your theme:\n`) +
1682
- chalk.white(` Edit ${themePath}/index.${options.type === 'css' ? 'scss' : 'ts'}\n\n`) +
1683
- (options.type === 'css'
1684
- ? chalk.gray(`2. Build your theme:\n`) + chalk.white(` atomix build-theme ${themePath}\n\n`)
1685
- : chalk.gray(`2. Use in your app:\n`) + chalk.white(` import theme from './themes/${name}';\n\n`)
1686
- ) +
1687
- chalk.gray(`3. Apply your theme:\n`) +
1688
- chalk.white(` <ThemeProvider theme="${name}">...</ThemeProvider>`),
1689
- {
1690
- padding: 1,
1691
- margin: 1,
1692
- borderStyle: 'round',
1693
- borderColor: 'green'
1694
- }
1695
- ));
1696
-
1697
- } catch (error) {
1698
- handleError(error, spinner);
1699
- }
1700
- });
1701
-
1702
- /**
1703
- * Docs Command Group - NEW (Documentation Management)
1704
- */
1705
- const docsCommand = program
1706
- .command('docs')
1707
- .description('Documentation management commands');
1708
-
1709
- // Docs sync
1710
- docsCommand
1711
- .command('sync')
1712
- .description('Sync documentation with component guidelines')
1713
- .option('--validate', 'Validate documentation after sync', true)
1714
- .action(async (options) => {
1715
- const spinner = ora('Syncing documentation...').start();
1716
-
1717
- try {
1718
- await syncDocumentation();
1719
- spinner.stop();
1720
-
1721
- if (options.validate) {
1722
- await validateDocumentation();
1723
- }
1724
- } catch (error) {
1725
- handleError(error, spinner);
1726
- }
1727
- });
1728
-
1729
- // Docs validate
1730
- docsCommand
1731
- .command('validate')
1732
- .description('Validate documentation completeness')
1733
- .action(async () => {
1734
- try {
1735
- await validateDocumentation();
1736
- } catch (error) {
1737
- handleError(error);
1738
- }
1739
- });
1740
-
1741
- // Docs generate CLI
1742
- docsCommand
1743
- .command('generate-cli')
1744
- .description('Generate CLI documentation from commands')
1745
- .action(async () => {
1746
- const spinner = ora('Generating CLI documentation...').start();
1747
-
1748
- try {
1749
- const result = await generateCLIDocumentation();
1750
- spinner.succeed(chalk.green('CLI documentation generated'));
1751
- console.log(chalk.gray(` → ${result.file}`));
1752
- } catch (error) {
1753
- handleError(error, spinner);
1754
- }
1755
- });
1756
-
1757
- /**
1758
- * Doctor Command - NEW
1759
- */
1760
- program
1761
- .command('doctor')
1762
- .alias('audit')
1763
- .description('Diagnose common issues with your Atomix setup')
1764
- .action(async () => {
1765
- const spinner = ora('Running diagnostics...').start();
1766
-
1767
- try {
1768
- const results = {
1769
- system: [],
1770
- dependencies: [],
1771
- structure: [],
1772
- framework: [],
1773
- theme: []
1774
- };
1775
-
1776
- // 1. System Checks
1777
- const nodeVersion = checkNodeVersion('18.0.0');
1778
- results.system.push({
1779
- name: 'Node.js',
1780
- status: nodeVersion.compatible ? '✅' : '❌',
1781
- message: nodeVersion.compatible
1782
- ? `v${nodeVersion.current} (supported)`
1783
- : `v${nodeVersion.current} (requires Node ${nodeVersion.required}+)`,
1784
- });
1785
-
1786
- // 2. Dependency Checks
1787
- const atomixPath = join(process.cwd(), 'node_modules', '@shohojdhara', 'atomix');
1788
- results.dependencies.push({
1789
- name: 'Atomix Package',
1790
- status: existsSync(atomixPath) ? '✅' : '❌',
1791
- message: existsSync(atomixPath)
1792
- ? 'Installed correctly'
1793
- : 'Not found - run: npm install @shohojdhara/atomix',
1794
- });
1795
-
1796
- const peerDeps = [
1797
- { name: 'react', required: '>= 18.0.0' },
1798
- { name: 'react-dom', required: '>= 18.0.0' },
1799
- { name: 'sass', required: 'installed' }
1800
- ];
1801
-
1802
- for (const dep of peerDeps) {
1803
- const depPath = join(process.cwd(), 'node_modules', dep.name);
1804
- const exists = existsSync(depPath);
1805
- results.dependencies.push({
1806
- name: dep.name,
1807
- status: exists ? '✅' : '⚠️',
1808
- message: exists ? 'Installed' : `Missing (required: ${dep.required})`,
1809
- });
1810
- }
1811
-
1812
- // 3. Project Structure Checks
1813
- const structureChecks = validateProjectStructure();
1814
-
1815
- structureChecks.forEach(check => {
1816
- let statusIcon = '✅';
1817
- if (!check.valid && check.required) statusIcon = '❌';
1818
- else if (!check.message.includes('exists') && !check.required) statusIcon = '⚠️';
1819
-
1820
- results.structure.push({
1821
- name: check.name,
1822
- status: statusIcon,
1823
- message: check.message.replace(/✓ |✗ |⚠ /, '')
1824
- });
1825
- });
1826
-
1827
- // 4. Framework & Config Checks
1828
- const frameworkChecks = validateFrameworkConfig();
1829
-
1830
- frameworkChecks.forEach(check => {
1831
- results.framework.push({
1832
- name: check.name,
1833
- status: check.valid ? '✅' : '💡',
1834
- message: check.message.replace(/✓ |• /, '')
1835
- });
1836
- });
1837
-
1838
- const configFiles = ['.atomixrc', 'atomix.config.js', 'atomix.config.json', 'theme.config.ts', 'atomix.config.ts'];
1839
- let foundConfig = null;
1840
- for (const file of configFiles) {
1841
- if (existsSync(join(process.cwd(), file))) {
1842
- foundConfig = file;
1843
- break;
1844
- }
1845
- }
1846
- results.framework.push({
1847
- name: 'Atomix Config',
1848
- status: foundConfig ? '✅' : '💡',
1849
- message: foundConfig ? `Found (${foundConfig})` : 'No config file (using defaults)',
1850
- });
1851
-
1852
- // 5. Theme CLI Checks
1853
- const themeCLIAvailable = await import('./cli/theme-bridge.js')
1854
- .then(m => m.isThemeCLIAvailable())
1855
- .catch(() => false);
1856
-
1857
- results.theme.push({
1858
- name: 'Theme Devtools',
1859
- status: themeCLIAvailable ? '✅' : '⚠️',
1860
- message: themeCLIAvailable ? 'Available' : 'Theme devtools not found',
1861
- });
1862
-
1863
- spinner.stop();
1864
-
1865
- // Display results
1866
- console.log(chalk.bold('\n🏥 Atomix Doctor / Audit Report\n'));
1867
-
1868
- const categories = [
1869
- { key: 'system', label: '💻 System environment' },
1870
- { key: 'dependencies', label: '📦 Core dependencies' },
1871
- { key: 'structure', label: '🏗️ Project structure' },
1872
- { key: 'framework', label: '⚙️ Configuration & Framework' },
1873
- { key: 'theme', label: '🎨 Design System tools' }
1874
- ];
1875
-
1876
- categories.forEach(cat => {
1877
- console.log(chalk.cyan.bold(`\n${cat.label}`));
1878
- console.log(chalk.gray('-'.repeat(40)));
1879
- results[cat.key].forEach(check => {
1880
- console.log(` ${check.status} ${chalk.bold(check.name.padEnd(20))} ${chalk.gray(check.message)}`);
1881
- });
1882
- });
1883
-
1884
- const hasIssues = Object.values(results).flat().some(c => c.status === '❌');
1885
- const hasWarnings = Object.values(results).flat().some(c => c.status === '⚠️');
1886
-
1887
- console.log('\n' + chalk.gray('='.repeat(60)));
1888
- if (hasIssues) {
1889
- console.log(chalk.red.bold('\n❌ Some critical issues need attention.'));
1890
- console.log(chalk.gray('Check the report above and follow the suggested fixes.'));
1891
- } else if (hasWarnings) {
1892
- console.log(chalk.yellow.bold('\n⚠️ Your setup is functional but could be improved.'));
1893
- console.log(chalk.gray('Recommended directories or scripts are missing.'));
1894
- } else {
1895
- console.log(chalk.green.bold('\n✨ Everything looks great! Your project is Atomix-ready.'));
1896
- }
1897
- console.log(chalk.gray('='.repeat(60)) + '\n');
1898
-
1899
- } catch (error) {
1900
- handleError(error, spinner);
1901
- }
1902
- });
1903
-
1904
-
1905
-
1906
- // Check dependencies before execution (except for doctor command)
1907
- const args = process.argv.slice(2);
1908
- const command = args[0];
1909
-
1910
- // Skip dependency check for doctor command or help
1911
- if (command !== 'doctor' && command !== '--help' && command !== '-h' && !args.includes('--help') && !args.includes('-h') && !process.env.ATOMIX_SKIP_DEP_CHECK) {
1912
- try {
1913
- const depResult = await checkDependencies();
1914
- if (!depResult.success) {
1915
- console.log(chalk.yellow('\n⚠️ Some dependencies are missing. Run `atomix doctor` for detailed information.'));
1916
- // Don't exit - allow users to see help or run doctor
1917
- }
1918
- } catch (error) {
1919
- // Silently continue if dependency check fails
1920
- debug('Dependency check failed:', error.message);
1921
- }
1922
- }
1923
-
1924
- // Parse arguments
1925
- program.parse(process.argv);
1926
-
1927
- // Show help if no command provided
1928
- if (!process.argv.slice(2).length) {
1929
- program.outputHelp();
1930
- }