@shohojdhara/atomix 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/atomix.config.ts +58 -1
  2. package/dist/atomix.css +172 -157
  3. package/dist/atomix.css.map +1 -1
  4. package/dist/atomix.min.css +4 -4
  5. package/dist/atomix.min.css.map +1 -1
  6. package/dist/charts.d.ts +33 -0
  7. package/dist/charts.js +1274 -164
  8. package/dist/charts.js.map +1 -1
  9. package/dist/core.d.ts +33 -10
  10. package/dist/core.js +1099 -83
  11. package/dist/core.js.map +1 -1
  12. package/dist/forms.d.ts +33 -0
  13. package/dist/forms.js +2106 -1050
  14. package/dist/forms.js.map +1 -1
  15. package/dist/heavy.d.ts +42 -1
  16. package/dist/heavy.js +1663 -638
  17. package/dist/heavy.js.map +1 -1
  18. package/dist/index.d.ts +442 -270
  19. package/dist/index.esm.js +1947 -680
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/index.js +1982 -712
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.min.js +1 -1
  24. package/dist/index.min.js.map +1 -1
  25. package/package.json +6 -3
  26. package/scripts/atomix-cli.js +136 -1827
  27. package/scripts/cli/__tests__/basic.test.js +3 -2
  28. package/scripts/cli/__tests__/clean.test.js +278 -0
  29. package/scripts/cli/__tests__/component-validator.test.js +433 -0
  30. package/scripts/cli/__tests__/generator.test.js +613 -0
  31. package/scripts/cli/__tests__/glass-motion.test.js +256 -0
  32. package/scripts/cli/__tests__/integration.test.js +719 -108
  33. package/scripts/cli/__tests__/migrate.test.js +74 -0
  34. package/scripts/cli/__tests__/security.test.js +206 -0
  35. package/scripts/cli/__tests__/test-setup.js +3 -1
  36. package/scripts/cli/__tests__/theme-bridge.test.js +507 -0
  37. package/scripts/cli/__tests__/token-provider.test.js +361 -0
  38. package/scripts/cli/__tests__/utils.test.js +5 -5
  39. package/scripts/cli/commands/benchmark.js +105 -0
  40. package/scripts/cli/commands/build-theme.js +115 -0
  41. package/scripts/cli/commands/clean.js +109 -0
  42. package/scripts/cli/commands/doctor.js +88 -0
  43. package/scripts/cli/commands/generate.js +218 -0
  44. package/scripts/cli/commands/init.js +73 -0
  45. package/scripts/cli/commands/migrate.js +106 -0
  46. package/scripts/cli/commands/sync-tokens.js +206 -0
  47. package/scripts/cli/commands/theme-bridge.js +248 -0
  48. package/scripts/cli/commands/tokens.js +157 -0
  49. package/scripts/cli/commands/validate.js +194 -0
  50. package/scripts/cli/internal/ai-engine.js +156 -0
  51. package/scripts/cli/internal/compiler.js +114 -0
  52. package/scripts/cli/internal/component-validator.js +443 -0
  53. package/scripts/cli/internal/config-loader.js +162 -0
  54. package/scripts/cli/internal/filesystem.js +158 -0
  55. package/scripts/cli/internal/generator.js +430 -0
  56. package/scripts/cli/internal/glass-generator.js +398 -0
  57. package/scripts/cli/internal/hook-generator.js +369 -0
  58. package/scripts/cli/internal/hooks.js +61 -0
  59. package/scripts/cli/internal/itcss-generator.js +565 -0
  60. package/scripts/cli/internal/motion-generator.js +679 -0
  61. package/scripts/cli/internal/template-engine.js +301 -0
  62. package/scripts/cli/internal/theme-bridge.js +664 -0
  63. package/scripts/cli/internal/tokens/engine.js +122 -0
  64. package/scripts/cli/internal/tokens/provider.js +34 -0
  65. package/scripts/cli/internal/tokens/providers/figma.js +50 -0
  66. package/scripts/cli/internal/tokens/providers/style-dictionary.js +48 -0
  67. package/scripts/cli/internal/tokens/providers/w3c.js +48 -0
  68. package/scripts/cli/internal/tokens/token-provider.js +443 -0
  69. package/scripts/cli/internal/tokens/token-validator.js +513 -0
  70. package/scripts/cli/internal/validator.js +276 -0
  71. package/scripts/cli/internal/wizard.js +115 -0
  72. package/scripts/cli/mappings.js +23 -0
  73. package/scripts/cli/migration-tools.js +164 -94
  74. package/scripts/cli/plugins/style-dictionary.js +46 -0
  75. package/scripts/cli/templates/README.md +525 -95
  76. package/scripts/cli/templates/common-templates.js +40 -14
  77. package/scripts/cli/templates/components/react-component.ts +282 -0
  78. package/scripts/cli/templates/config/project-config.ts +112 -0
  79. package/scripts/cli/templates/hooks/use-component.ts +477 -0
  80. package/scripts/cli/templates/index.js +19 -4
  81. package/scripts/cli/templates/index.ts +171 -0
  82. package/scripts/cli/templates/next-templates.js +72 -0
  83. package/scripts/cli/templates/react-templates.js +70 -126
  84. package/scripts/cli/templates/scss-templates.js +35 -35
  85. package/scripts/cli/templates/stories/storybook-story.ts +241 -0
  86. package/scripts/cli/templates/styles/scss-component.ts +255 -0
  87. package/scripts/cli/templates/tests/vitest-test.ts +229 -0
  88. package/scripts/cli/templates/token-templates.js +337 -1
  89. package/scripts/cli/templates/tokens/token-generators.ts +1088 -0
  90. package/scripts/cli/templates/types/component-types.ts +145 -0
  91. package/scripts/cli/templates/utils/testing-utils.ts +144 -0
  92. package/scripts/cli/templates/vanilla-templates.js +39 -0
  93. package/scripts/cli/token-manager.js +8 -2
  94. package/scripts/cli/utils/cache-manager.js +240 -0
  95. package/scripts/cli/utils/detector.js +46 -0
  96. package/scripts/cli/utils/diagnostics.js +289 -0
  97. package/scripts/cli/utils/error.js +89 -0
  98. package/scripts/cli/utils/helpers.js +67 -0
  99. package/scripts/cli/utils/logger.js +75 -0
  100. package/scripts/cli/utils/security.js +302 -0
  101. package/scripts/cli/utils/telemetry.js +115 -0
  102. package/scripts/cli/utils/validation.js +37 -0
  103. package/scripts/cli/utils.js +28 -341
  104. package/src/components/Accordion/Accordion.stories.tsx +0 -18
  105. package/src/components/Accordion/Accordion.test.tsx +0 -17
  106. package/src/components/Accordion/Accordion.tsx +0 -4
  107. package/src/components/AtomixGlass/AtomixGlass.test.tsx +37 -3
  108. package/src/components/AtomixGlass/AtomixGlass.tsx +143 -31
  109. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +129 -31
  110. package/src/components/AtomixGlass/PerformanceDashboard.tsx +219 -0
  111. package/src/components/AtomixGlass/README.md +25 -10
  112. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +216 -0
  113. package/src/components/AtomixGlass/animation-system.ts +578 -0
  114. package/src/components/AtomixGlass/shader-utils.ts +4 -1
  115. package/src/components/AtomixGlass/stories/Overview.stories.tsx +157 -6
  116. package/src/components/AtomixGlass/stories/Phase1-Animation.stories.tsx +653 -0
  117. package/src/components/AtomixGlass/stories/Phase1-Test.stories.tsx +95 -0
  118. package/src/components/AtomixGlass/stories/Playground.stories.tsx +51 -51
  119. package/src/components/AtomixGlass/stories/shared-components.tsx +6 -0
  120. package/src/components/Avatar/Avatar.tsx +1 -1
  121. package/src/components/Button/Button.stories.disabled-link.tsx +10 -0
  122. package/src/components/Button/Button.stories.tsx +10 -0
  123. package/src/components/Button/Button.test.tsx +16 -11
  124. package/src/components/Button/Button.tsx +4 -4
  125. package/src/components/Card/Card.tsx +1 -1
  126. package/src/components/Dropdown/Dropdown.tsx +12 -12
  127. package/src/components/Form/Select.tsx +62 -3
  128. package/src/components/Modal/Modal.tsx +14 -3
  129. package/src/components/Navigation/Navbar/Navbar.tsx +44 -0
  130. package/src/components/Slider/Slider.stories.tsx +3 -3
  131. package/src/components/Slider/Slider.tsx +38 -0
  132. package/src/components/Steps/Steps.tsx +3 -3
  133. package/src/components/Tabs/Tabs.tsx +77 -8
  134. package/src/components/Testimonial/Testimonial.tsx +1 -1
  135. package/src/components/TypedButton/TypedButton.stories.tsx +59 -0
  136. package/src/components/TypedButton/TypedButton.tsx +39 -0
  137. package/src/components/TypedButton/index.ts +2 -0
  138. package/src/components/VideoPlayer/VideoPlayer.tsx +11 -4
  139. package/src/lib/composables/index.ts +4 -7
  140. package/src/lib/composables/types.ts +45 -0
  141. package/src/lib/composables/useAccordion.ts +0 -7
  142. package/src/lib/composables/useAtomixGlass.ts +148 -6
  143. package/src/lib/composables/useAtomixGlassStyles.ts +9 -7
  144. package/src/lib/composables/useChartExport.ts +3 -13
  145. package/src/lib/composables/useDropdown.ts +66 -0
  146. package/src/lib/composables/useFocusTrap.ts +80 -0
  147. package/src/lib/composables/usePerformanceMonitor.ts +448 -0
  148. package/src/lib/composables/useResponsiveGlass.presets.ts +192 -0
  149. package/src/lib/composables/useResponsiveGlass.ts +441 -0
  150. package/src/lib/composables/useTooltip.ts +16 -0
  151. package/src/lib/composables/useTypedButton.ts +66 -0
  152. package/src/lib/config/index.ts +62 -5
  153. package/src/lib/constants/components.ts +62 -7
  154. package/src/lib/theme/devtools/__tests__/useHistory.test.tsx +150 -0
  155. package/src/lib/theme/tokens/centralized-tokens.ts +120 -0
  156. package/src/lib/theme/utils/__tests__/domUtils.test.ts +101 -0
  157. package/src/lib/types/components.ts +37 -11
  158. package/src/lib/types/glass.ts +35 -0
  159. package/src/lib/types/index.ts +1 -0
  160. package/src/lib/utils/displacement-generator.ts +1 -1
  161. package/src/styles/01-settings/_settings.testtypecheck.scss +53 -0
  162. package/src/styles/01-settings/_settings.typedbutton.scss +53 -0
  163. package/src/styles/06-components/_components.atomix-glass.scss +17 -21
  164. package/src/styles/06-components/_components.edge-panel.scss +1 -5
  165. package/src/styles/06-components/_components.modal.scss +1 -4
  166. package/src/styles/06-components/_components.navbar.scss +1 -1
  167. package/src/styles/06-components/_components.testbutton.scss +212 -0
  168. package/src/styles/06-components/_components.testtypecheck.scss +212 -0
  169. package/src/styles/06-components/_components.tooltip.scss +9 -5
  170. package/src/styles/06-components/_components.typedbutton.scss +212 -0
  171. package/src/styles/99-utilities/_index.scss +1 -0
  172. package/src/styles/99-utilities/_utilities.text.scss +1 -1
  173. package/src/styles/99-utilities/_utilities.touch-target.scss +36 -0
  174. package/scripts/cli/component-generator.js +0 -564
  175. package/scripts/cli/interactive-init.js +0 -357
  176. package/src/styles/06-components/old.chart.styles.scss +0 -2788
@@ -1,71 +1,31 @@
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';
12
+ import { logger } from './cli/utils/logger.js';
13
+ import { handleCLIError } from './cli/utils/error.js';
14
+ import { telemetry } from './cli/utils/telemetry.js';
17
15
  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';
16
+
17
+ // Action Modules
18
+ import { initAction } from './cli/commands/init.js';
19
+ import { generateAction } from './cli/commands/generate.js';
20
+ import { buildThemeAction } from './cli/commands/build-theme.js';
21
+ import { doctorAction } from './cli/commands/doctor.js';
22
+ import { validateAction } from './cli/commands/validate.js';
23
+ import { tokensAction } from './cli/commands/tokens.js';
24
+ import { migrateAction } from './cli/commands/migrate.js';
25
+ import { benchmarkAction } from './cli/commands/benchmark.js';
26
+ import { cleanAction } from './cli/commands/clean.js';
27
+ import { themeBridgeAction } from './cli/commands/theme-bridge.js';
28
+ import { configLoader } from './cli/internal/config-loader.js';
69
29
 
70
30
  const __filename = fileURLToPath(import.meta.url);
71
31
  const __dirname = dirname(__filename);
@@ -75,1856 +35,205 @@ const packageJson = JSON.parse(
75
35
  await readFile(join(__dirname, '../package.json'), 'utf8')
76
36
  );
77
37
 
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
38
  program
162
39
  .name('atomix')
163
- .description('Atomix Design System CLI - Enhanced Edition')
40
+ .description('Atomix Design System CLI - Modular Edition')
164
41
  .version(packageJson.version)
165
42
  .option('-d, --debug', 'Enable debug mode', false)
166
- .hook('preAction', (thisCommand) => {
43
+ .option('--dry-run', 'Preview changes without modifying files', false)
44
+ .hook('preAction', async (thisCommand) => {
45
+ // Load config
46
+ await configLoader.load();
47
+
167
48
  if (thisCommand.opts().debug) {
168
49
  process.env.ATOMIX_DEBUG = 'true';
50
+ logger.debug('Debug mode enabled');
169
51
  }
52
+ if (thisCommand.opts().dryRun) {
53
+ process.env.ATOMIX_DRY_RUN = 'true';
54
+ logger.info(chalk.yellow('⚠️ Dry-run mode enabled. No files will be modified.'));
55
+ }
56
+
57
+ // Start telemetry
58
+ const fullCommand = thisCommand.name() === 'atomix'
59
+ ? thisCommand.args[0] || 'atomix'
60
+ : thisCommand.name();
61
+ telemetry.start(fullCommand);
62
+ })
63
+ .hook('postAction', async (thisCommand) => {
64
+ // Stop telemetry
65
+ await telemetry.stop(true);
170
66
  });
171
67
 
172
68
  /**
173
- * Enhanced Build Theme Command with Watch Mode
69
+ * Environment Diagnostics
174
70
  */
175
71
  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
-
72
+ .command('doctor')
73
+ .description('Verify the environment and project health. Use --explain to list what each check does.')
74
+ .option('--explain', 'Print short descriptions for each doctor check and exit')
75
+ .action(async (options) => {
186
76
  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
-
77
+ await doctorAction(options);
365
78
  } catch (error) {
366
- handleError(error, spinner);
79
+ await handleCLIError(error);
367
80
  }
368
81
  });
369
82
 
370
83
  /**
371
- * Generate Component Command - NEW
84
+ * Code & Config Validation
372
85
  */
373
86
  program
374
- .command('generate <type> <name>')
375
- .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)
386
- .action(async (type, name, options) => {
387
- const spinner = ora(`Generating ${type}: ${name}...`).start();
388
-
87
+ .command('validate [subcommand] [name]')
88
+ .description('Audit project quality (A11y, Tokens, Performance). Use "validate component <Name>" for component-scoped audit. Run build first for performance analysis (requires dist/).')
89
+ .action(async (subcommand, name, options) => {
389
90
  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
-
91
+ await validateAction(options, subcommand, name);
862
92
  } catch (error) {
863
- handleError(error, spinner);
93
+ await handleCLIError(error);
864
94
  }
865
95
  });
866
96
 
867
97
  /**
868
- * Validate Command - NEW
98
+ * Design Token Management
869
99
  */
870
100
  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
-
101
+ .command('tokens <subcommand>')
102
+ .description('Manage design tokens (list, export, pull, push). For pull/push, --provider is required (configure tokenEngine.providers in atomix.config; types: figma, style-dictionary, w3c).')
103
+ .option('-f, --format <format>', 'Export format (css|scss|json)', 'css')
104
+ .option('-o, --output <path>', 'Output directory', './tokens')
105
+ .option('-p, --provider <provider>', 'Token provider name (required for pull and push)')
106
+ .action(async (subcommand, options) => {
881
107
  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
- }
108
+ await tokensAction(subcommand, options);
1073
109
  } catch (error) {
1074
- handleError(error, spinner);
110
+ await handleCLIError(error);
1075
111
  }
1076
112
  });
1077
113
 
1078
114
  /**
1079
- * Dev Command - NEW (Alias for build --watch)
1080
- */
1081
- program
1082
- .command('dev <theme>')
1083
- .description('Start development mode with hot reload')
1084
- .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)
115
+ * Project Initialization
1114
116
  */
1115
117
  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
-
118
+ .command('init')
119
+ .description('Initialize a new Atomix design system project. For CI/scripts use --type <react|nextjs|vanilla> or --yes.')
120
+ .option('-y, --yes', 'Use default choices (non-interactive)')
121
+ .option('-t, --type <type>', 'Project type: react, nextjs, or vanilla (skips prompt when set)')
122
+ .action(async (options) => {
1124
123
  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
-
124
+ await initAction(options);
1244
125
  } catch (error) {
1245
- handleError(error, spinner);
126
+ await handleCLIError(error);
1246
127
  }
1247
128
  });
1248
129
 
1249
130
  /**
1250
- * Init Command - NEW (Interactive Setup Wizard)
131
+ * Resource Generation
1251
132
  */
1252
133
  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) => {
134
+ .command('generate <type> <name>')
135
+ .alias('g')
136
+ .description('Generate components, tokens, or themes')
137
+ .option('-i, --interactive', 'Interactive mode', false)
138
+ .option('-p, --path <path>', 'Output path', './src/components')
139
+ .option('--prompt <prompt>', 'AI prompt for generating component')
140
+ .option('--complexity <level>', 'Complexity (simple|medium|complex)', 'medium')
141
+ .option('--validate', 'Validate after generation', true)
142
+ .action(async (type, name, options) => {
1257
143
  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();
144
+ await generateAction(type, name, options);
1265
145
  } catch (error) {
1266
- handleError(error);
146
+ await handleCLIError(error);
1267
147
  }
1268
148
  });
1269
149
 
1270
150
  /**
1271
- * Tokens Command - NEW (Design Token Management)
151
+ * Migration Tools
1272
152
  */
1273
153
  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) => {
154
+ .command('migrate <type> <source>')
155
+ .description('Migrate from other frameworks (tailwind|bootstrap). <source> is the project root directory (e.g. . or ./my-tailwind-app).')
156
+ .option('-p, --preview', 'Preview side-by-side diff before applying', false)
157
+ .action(async (type, source, options) => {
1281
158
  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
-
159
+ await migrateAction(type, source, options);
1368
160
  } catch (error) {
1369
- handleError(error);
161
+ await handleCLIError(error);
1370
162
  }
1371
163
  });
1372
164
 
1373
165
  /**
1374
- * Theme Command Group - NEW (Integrated with Theme Devtools)
166
+ * Performance & Benchmarking
1375
167
  */
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')
168
+ program
169
+ .command('benchmark')
170
+ .description('Profile CLI performance and show metrics. Benchmark collects metrics from previous CLI runs.')
1386
171
  .action(async (options) => {
1387
172
  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);
173
+ await benchmarkAction(options);
1431
174
  } 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) => {
1442
- try {
1443
- const themeCLI = createThemeCLIBridge();
1444
- await themeCLI.export(name, options);
1445
- } catch (error) {
1446
- handleError(error);
1447
- }
1448
- });
1449
-
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
- },
1624
- });
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);
175
+ await handleCLIError(error);
1699
176
  }
1700
177
  });
1701
178
 
1702
179
  /**
1703
- * Docs Command Group - NEW (Documentation Management)
180
+ * Build Artifact Cleanup
1704
181
  */
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)
182
+ program
183
+ .command('clean')
184
+ .description('Clean build artifacts and cache files. Use --dry-run to preview, --all to include node_modules.')
185
+ .option('--all', 'Include node_modules in cleanup', false)
186
+ .option('--cache', 'Only clean cache directories', false)
187
+ .option('--verbose', 'Show detailed output', false)
1714
188
  .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
189
  try {
1735
- await validateDocumentation();
190
+ await cleanAction(options);
1736
191
  } catch (error) {
1737
- handleError(error);
192
+ await handleCLIError(error);
1738
193
  }
1739
194
  });
1740
195
 
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
-
196
+ /**
197
+ * Theme Building
198
+ */
199
+ program
200
+ .command('build-theme <path>')
201
+ .description('Build a custom theme from SCSS. Default output is ./dist (may overwrite app build; use -o dist-theme for custom themes).')
202
+ .option('-o, --output <path>', 'Output directory (default: ./dist)', './dist')
203
+ .option('-m, --minify', 'Minify CSS', true)
204
+ .option('-s, --sourcemap', 'Generate source maps', false)
205
+ .option('-w, --watch', 'Watch mode', false)
206
+ .option('--analyze', 'Analyze bundle', false)
207
+ .action(async (themePath, options) => {
1748
208
  try {
1749
- const result = await generateCLIDocumentation();
1750
- spinner.succeed(chalk.green('CLI documentation generated'));
1751
- console.log(chalk.gray(` → ${result.file}`));
209
+ await buildThemeAction(themePath, options);
1752
210
  } catch (error) {
1753
- handleError(error, spinner);
211
+ await handleCLIError(error);
1754
212
  }
1755
213
  });
1756
214
 
1757
215
  /**
1758
- * Doctor Command - NEW
216
+ * Theme Bridge - Sync Design Tokens with Theme Providers
1759
217
  */
1760
218
  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
-
219
+ .command('theme-bridge [source]')
220
+ .description('Sync design tokens with theme providers (Tailwind, CSS-in-JS, CSS Variables)')
221
+ .option('-o, --output <dir>', 'Output directory for theme files', './src/theme')
222
+ .option('-f, --format <format>', 'Theme format (tailwind, emotion, styled-components, vanilla-extract, css-variables, all)', 'all')
223
+ .option('--prefix <prefix>', 'CSS variable prefix', 'atomix')
224
+ .option('--selector <selector>', 'CSS selector for variables', ':root')
225
+ .option('--no-typescript', 'Skip TypeScript type generation')
226
+ .option('--validate', 'Validate generated theme files')
227
+ .option('--dry-run', 'Show what would be generated without writing files')
228
+ .action(async (source, options) => {
1767
229
  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
-
230
+ await themeBridgeAction(source, options);
1899
231
  } catch (error) {
1900
- handleError(error, spinner);
232
+ await handleCLIError(error);
1901
233
  }
1902
234
  });
1903
235
 
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
- }
236
+ // Run program
237
+ program.parseAsync(process.argv).catch(async (error) => {
238
+ await handleCLIError(error);
239
+ });