chaincss 2.0.6 → 2.1.0

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 (159) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/CODE_OF_CONDUCT.md +21 -0
  3. package/CONTRIBUTING.md +28 -0
  4. package/README.md +454 -231
  5. package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +1 -0
  6. package/demo/index.html +16 -0
  7. package/demo/package.json +20 -0
  8. package/demo/src/App.tsx +117 -0
  9. package/demo/src/chaincss-barrel.ts +9 -0
  10. package/demo/src/main.tsx +8 -0
  11. package/demo/src/styles.chain.ts +300 -0
  12. package/demo/vite.config.ts +46 -0
  13. package/dist/cli/commands/build.d.ts +0 -1
  14. package/dist/cli/commands/cache.d.ts +1 -0
  15. package/dist/cli/commands/init.d.ts +6 -3
  16. package/dist/cli/commands/timeline.d.ts +0 -1
  17. package/dist/cli/commands/watch.d.ts +0 -1
  18. package/dist/cli/index.d.ts +0 -1
  19. package/dist/cli/index.js +3213 -5296
  20. package/dist/cli/types.d.ts +51 -20
  21. package/dist/cli/utils/config-loader.d.ts +0 -1
  22. package/dist/cli/utils/file-utils.d.ts +27 -3
  23. package/dist/cli/utils/logger.d.ts +0 -1
  24. package/dist/compiler/Chain.d.ts +215 -0
  25. package/dist/compiler/animations.d.ts +76 -0
  26. package/dist/compiler/atomic-optimizer.d.ts +47 -12
  27. package/dist/compiler/breakpoints.d.ts +46 -0
  28. package/dist/compiler/btt.d.ts +36 -60
  29. package/dist/compiler/cache-manager.d.ts +58 -4
  30. package/dist/compiler/commonProps.d.ts +0 -1
  31. package/dist/compiler/content-addressable-cache.d.ts +78 -0
  32. package/dist/compiler/helpers.d.ts +54 -0
  33. package/dist/compiler/index.d.ts +16 -9
  34. package/dist/compiler/index.js +4450 -4316
  35. package/dist/compiler/prefixer.d.ts +17 -1
  36. package/dist/compiler/shorthands.d.ts +28 -0
  37. package/dist/compiler/suggestions.d.ts +43 -0
  38. package/dist/compiler/theme-contract.d.ts +16 -27
  39. package/dist/compiler/token-resolver.d.ts +69 -0
  40. package/dist/compiler/tokens.d.ts +33 -8
  41. package/dist/core/auto-detector.d.ts +34 -0
  42. package/dist/core/common-utils.d.ts +97 -0
  43. package/dist/core/compiler.d.ts +63 -23
  44. package/dist/core/constants.d.ts +137 -36
  45. package/dist/core/smart-chain.d.ts +3 -0
  46. package/dist/core/types.d.ts +122 -15
  47. package/dist/core/utils.d.ts +134 -17
  48. package/dist/index.d.ts +52 -8
  49. package/dist/index.js +7090 -5578
  50. package/dist/plugins/vite.d.ts +7 -5
  51. package/dist/plugins/vite.js +2964 -25641
  52. package/dist/plugins/webpack.d.ts +24 -1
  53. package/dist/plugins/webpack.js +209 -72
  54. package/dist/runtime/Chain.d.ts +32 -0
  55. package/dist/runtime/auto-hooks.d.ts +11 -0
  56. package/dist/runtime/hmr.d.ts +22 -2
  57. package/dist/runtime/index.d.ts +3 -2
  58. package/dist/runtime/index.js +3649 -301
  59. package/dist/runtime/injector.d.ts +39 -71
  60. package/dist/runtime/react.d.ts +17 -12
  61. package/dist/runtime/svelte.d.ts +15 -0
  62. package/dist/runtime/types.d.ts +126 -4
  63. package/dist/runtime/utils.d.ts +0 -1
  64. package/dist/runtime/vue.d.ts +34 -14
  65. package/package.json +59 -66
  66. package/src/cli/commands/build.ts +133 -0
  67. package/src/cli/commands/cache.ts +371 -0
  68. package/src/cli/commands/init.ts +230 -0
  69. package/src/cli/commands/timeline.ts +435 -0
  70. package/src/cli/commands/watch.ts +211 -0
  71. package/src/cli/index.ts +226 -0
  72. package/src/cli/types.ts +100 -0
  73. package/src/cli/utils/config-loader.ts +174 -0
  74. package/src/cli/utils/file-utils.ts +139 -0
  75. package/src/cli/utils/logger.ts +74 -0
  76. package/src/compiler/Chain.ts +831 -0
  77. package/src/compiler/animations.ts +517 -0
  78. package/src/compiler/atomic-optimizer.ts +786 -0
  79. package/src/compiler/breakpoints.ts +347 -0
  80. package/src/compiler/btt.ts +1147 -0
  81. package/src/compiler/cache-manager.ts +446 -0
  82. package/src/compiler/commonProps.ts +18 -0
  83. package/src/compiler/content-addressable-cache.ts +478 -0
  84. package/src/compiler/helpers.ts +407 -0
  85. package/src/compiler/index.ts +72 -0
  86. package/src/compiler/prefixer.ts +724 -0
  87. package/src/compiler/shorthands.ts +558 -0
  88. package/src/compiler/suggestions.ts +436 -0
  89. package/src/compiler/theme-contract.ts +197 -0
  90. package/src/compiler/token-resolver.ts +241 -0
  91. package/src/compiler/tokens.ts +612 -0
  92. package/src/core/auto-detector.ts +187 -0
  93. package/src/core/common-utils.ts +423 -0
  94. package/src/core/compiler.ts +835 -0
  95. package/src/core/constants.ts +424 -0
  96. package/src/core/index.ts +107 -0
  97. package/src/core/smart-chain.ts +163 -0
  98. package/src/core/types.ts +257 -0
  99. package/src/core/utils.ts +598 -0
  100. package/src/index.ts +208 -0
  101. package/src/plugins/vite.d.ts +316 -0
  102. package/src/plugins/vite.ts +424 -0
  103. package/src/plugins/webpack.d.ts +289 -0
  104. package/src/plugins/webpack.ts +416 -0
  105. package/src/runtime/Chain.ts +242 -0
  106. package/src/runtime/auto-hooks.tsx +127 -0
  107. package/src/runtime/auto-vue.ts +72 -0
  108. package/src/runtime/hmr.ts +212 -0
  109. package/src/runtime/index.ts +82 -0
  110. package/src/runtime/injector.ts +273 -0
  111. package/src/runtime/react.tsx +269 -0
  112. package/src/runtime/svelte.ts +15 -0
  113. package/src/runtime/types.ts +256 -0
  114. package/src/runtime/utils.ts +128 -0
  115. package/src/runtime/vite-env.d.ts +120 -0
  116. package/src/runtime/vue.ts +231 -0
  117. package/tsconfig.build.json +41 -0
  118. package/tsconfig.json +25 -0
  119. package/tsconfig.runtimes.json +18 -0
  120. package/dist/cli/cli.cjs +0 -7
  121. package/dist/cli/commands/build.d.ts.map +0 -1
  122. package/dist/cli/commands/compile.d.ts +0 -3
  123. package/dist/cli/commands/compile.d.ts.map +0 -1
  124. package/dist/cli/commands/init.d.ts.map +0 -1
  125. package/dist/cli/commands/timeline.d.ts.map +0 -1
  126. package/dist/cli/commands/watch.d.ts.map +0 -1
  127. package/dist/cli/index.d.ts.map +0 -1
  128. package/dist/cli/types.d.ts.map +0 -1
  129. package/dist/cli/utils/config-loader.d.ts.map +0 -1
  130. package/dist/cli/utils/file-utils.d.ts.map +0 -1
  131. package/dist/cli/utils/logger.d.ts.map +0 -1
  132. package/dist/compiler/atomic-optimizer.d.ts.map +0 -1
  133. package/dist/compiler/btt.d.ts.map +0 -1
  134. package/dist/compiler/cache-manager.d.ts.map +0 -1
  135. package/dist/compiler/commonProps.d.ts.map +0 -1
  136. package/dist/compiler/index.d.ts.map +0 -1
  137. package/dist/compiler/prefixer.d.ts.map +0 -1
  138. package/dist/compiler/theme-contract.d.ts.map +0 -1
  139. package/dist/compiler/tokens.d.ts.map +0 -1
  140. package/dist/compiler/types.d.ts +0 -57
  141. package/dist/compiler/types.d.ts.map +0 -1
  142. package/dist/core/compiler.d.ts.map +0 -1
  143. package/dist/core/constants.d.ts.map +0 -1
  144. package/dist/core/index.d.ts +0 -4
  145. package/dist/core/index.d.ts.map +0 -1
  146. package/dist/core/types.d.ts.map +0 -1
  147. package/dist/core/utils.d.ts.map +0 -1
  148. package/dist/index.d.ts.map +0 -1
  149. package/dist/plugins/vite.d.ts.map +0 -1
  150. package/dist/plugins/webpack.d.ts.map +0 -1
  151. package/dist/runtime/hmr.d.ts.map +0 -1
  152. package/dist/runtime/index.d.ts.map +0 -1
  153. package/dist/runtime/injector.d.ts.map +0 -1
  154. package/dist/runtime/react.d.ts.map +0 -1
  155. package/dist/runtime/react.js +0 -270
  156. package/dist/runtime/types.d.ts.map +0 -1
  157. package/dist/runtime/utils.d.ts.map +0 -1
  158. package/dist/runtime/vue.d.ts.map +0 -1
  159. package/dist/runtime/vue.js +0 -232
@@ -0,0 +1,211 @@
1
+ // src/cli/commands/watch.ts (with fixes for logger)
2
+
3
+ import path from 'path';
4
+ import chalk from 'chalk';
5
+ import fs from 'fs';
6
+ import { ChainCSSCompiler } from '../../core/compiler.js';
7
+ import { createLogger } from '../utils/logger.js';
8
+ import { loadConfig } from '../utils/config-loader.js';
9
+ import { findInputFiles, ensureDirectory, getOutputPath } from '../utils/file-utils.js';
10
+ import type { BuildOptions } from '../types.js';
11
+
12
+ export interface WatchOptions extends BuildOptions {
13
+ debounce?: number;
14
+ }
15
+
16
+ export async function watchCommand(options: WatchOptions): Promise<void> {
17
+ const logger = createLogger(options.verbose);
18
+
19
+ logger.header('ChainCSS Watch Mode');
20
+
21
+ // Load configuration
22
+ const config = await loadConfig(options.config);
23
+ const inputs = config.inputs || ['src/**/*.chain.{js,ts}', 'src/**/*.chain.{jsx,tsx}'];
24
+
25
+ // Determine output directory
26
+ let outputDir = 'dist/styles';
27
+ if (typeof config.output === 'object' && config.output.cssFile) {
28
+ outputDir = path.dirname(config.output.cssFile);
29
+ } else if (typeof config.output === 'string') {
30
+ outputDir = config.output;
31
+ }
32
+
33
+ if (inputs.length === 0) {
34
+ logger.error('No input patterns found in configuration');
35
+ process.exit(1);
36
+ }
37
+
38
+ logger.info(`Watching files matching: ${inputs.join(', ')}`);
39
+
40
+ // Find initial files
41
+ const initialFiles = findInputFiles(inputs);
42
+ logger.success(`Found ${initialFiles.length} file(s) to watch`);
43
+
44
+ // Ensure output directory exists
45
+ ensureDirectory(outputDir);
46
+
47
+ // Initialize compiler
48
+ const compiler = new ChainCSSCompiler({
49
+ tokens: config.tokens,
50
+ atomic: {
51
+ enabled: config.atomic?.enabled !== false,
52
+ threshold: config.atomic?.threshold || 2,
53
+ naming: config.atomic?.naming || (process.env.NODE_ENV === 'production' ? 'hash' : 'readable'),
54
+ minify: config.atomic?.minify !== false,
55
+ mode: config.atomic?.mode || 'hybrid',
56
+ verbose: options.verbose || config.verbose || false
57
+ },
58
+ prefixer: {
59
+ enabled: config.prefixer?.enabled !== false,
60
+ browsers: config.prefixer?.browsers
61
+ },
62
+ output: {
63
+ minify: config.output?.minify !== false
64
+ },
65
+ verbose: options.verbose || config.verbose || false,
66
+ breakpoints: config.breakpoints,
67
+ debug: config.debug || false,
68
+ timeline: config.timeline || false
69
+ });
70
+
71
+ // Initial compilation
72
+ logger.step('Initial compilation...');
73
+ let compiledCount = 0;
74
+
75
+ for (let i = 0; i < initialFiles.length; i++) {
76
+ const file = initialFiles[i];
77
+ const relativePath = path.relative(process.cwd(), file);
78
+ logger.progress(i + 1, initialFiles.length, `Compiling ${relativePath}...`);
79
+
80
+ try {
81
+ await compiler.compile(file, outputDir);
82
+ compiledCount++;
83
+ } catch (error) {
84
+ logger.error(`Failed to compile ${relativePath}: ${(error as Error).message}`);
85
+ }
86
+ }
87
+
88
+ logger.progress(initialFiles.length, initialFiles.length, 'Initial build complete!');
89
+ logger.success(`Compiled ${compiledCount} file(s)`);
90
+
91
+ // Setup file watcher
92
+ logger.divider();
93
+ logger.info(`👀 Watching for changes... (press Ctrl+C to stop)\n`);
94
+
95
+ const chokidar = await import('chokidar');
96
+ const debounceDelay = options.debounce || 100;
97
+
98
+ // Create watcher
99
+ const watcher = chokidar.watch(inputs, {
100
+ ignored: ['**/node_modules/**', '**/dist/**', '**/.chaincss-cache/**', '**/*.css', '**/*.d.ts'],
101
+ persistent: true,
102
+ ignoreInitial: true
103
+ });
104
+
105
+ // Debounce function to avoid multiple rapid recompilations
106
+ let debounceTimer: NodeJS.Timeout | null = null;
107
+ let pendingFiles = new Set<string>();
108
+
109
+ function scheduleRecompile() {
110
+ if (debounceTimer) {
111
+ clearTimeout(debounceTimer);
112
+ }
113
+
114
+ debounceTimer = setTimeout(async () => {
115
+ const filesToCompile = Array.from(pendingFiles);
116
+ pendingFiles.clear();
117
+
118
+ logger.divider();
119
+ logger.info(`📦 Recompiling ${filesToCompile.length} changed file(s)...`);
120
+
121
+ let successCount = 0;
122
+ let failCount = 0;
123
+
124
+ for (const file of filesToCompile) {
125
+ const relativePath = path.relative(process.cwd(), file);
126
+ try {
127
+ await compiler.compile(file, outputDir);
128
+ logger.success(` ✓ ${relativePath}`);
129
+ successCount++;
130
+ } catch (error) {
131
+ logger.error(` ✗ ${relativePath}: ${(error as Error).message}`);
132
+ failCount++;
133
+ }
134
+ }
135
+
136
+ if (successCount > 0) {
137
+ logger.success(`✅ Recompiled ${successCount} file(s) successfully`);
138
+ }
139
+ if (failCount > 0) {
140
+ logger.error(`❌ Failed to recompile ${failCount} file(s)`);
141
+ }
142
+
143
+ debounceTimer = null;
144
+ }, debounceDelay);
145
+ }
146
+
147
+ // Handle file changes
148
+ watcher.on('change', (filePath: string) => {
149
+ const ext = path.extname(filePath);
150
+ if (ext === '.js' || ext === '.ts' || ext === '.jsx' || ext === '.tsx') {
151
+ const relativePath = path.relative(process.cwd(), filePath);
152
+ if (options.verbose) {
153
+ logger.info(`File changed: ${relativePath}`);
154
+ }
155
+ pendingFiles.add(filePath);
156
+ scheduleRecompile();
157
+ }
158
+ });
159
+
160
+ // Handle new files
161
+ watcher.on('add', (filePath: string) => {
162
+ const ext = path.extname(filePath);
163
+ if (ext === '.js' || ext === '.ts' || ext === '.jsx' || ext === '.tsx') {
164
+ const relativePath = path.relative(process.cwd(), filePath);
165
+ logger.info(`📄 New file detected: ${relativePath}`);
166
+ pendingFiles.add(filePath);
167
+ scheduleRecompile();
168
+ }
169
+ });
170
+
171
+ // Handle deleted files
172
+ watcher.on('unlink', (filePath: string) => {
173
+ const ext = path.extname(filePath);
174
+ if (ext === '.js' || ext === '.ts' || ext === '.jsx' || ext === '.tsx') {
175
+ const relativePath = path.relative(process.cwd(), filePath);
176
+ logger.warn(`🗑️ File deleted: ${relativePath}`);
177
+
178
+ // Remove corresponding output files
179
+ const baseName = path.basename(filePath, ext);
180
+ const outputBase = path.join(outputDir, baseName);
181
+ const cssFile = `${outputBase}.css`;
182
+ const classFile = `${outputBase}.class.js`;
183
+
184
+ if (fs.existsSync(cssFile)) fs.unlinkSync(cssFile);
185
+ if (fs.existsSync(classFile)) fs.unlinkSync(classFile);
186
+
187
+ logger.info(` Removed output files for ${relativePath}`);
188
+ }
189
+ });
190
+
191
+ // Handle watcher errors
192
+ watcher.on('error', (error: unknown) => {
193
+ logger.error(`Watcher error: ${error instanceof Error ? error.message : String(error)}`);
194
+ });
195
+
196
+ // Handle process termination
197
+ const cleanup = () => {
198
+ logger.info('\n👋 Shutting down watcher...');
199
+ watcher.close();
200
+ if (debounceTimer) {
201
+ clearTimeout(debounceTimer);
202
+ }
203
+ process.exit(0);
204
+ };
205
+
206
+ process.on('SIGINT', cleanup);
207
+ process.on('SIGTERM', cleanup);
208
+
209
+ // Keep the process alive
210
+ await new Promise(() => {});
211
+ }
@@ -0,0 +1,226 @@
1
+ // src/cli/index.ts
2
+ import { Command } from 'commander';
3
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import path from 'path';
6
+ import { glob } from 'glob';
7
+ import chalk from 'chalk';
8
+ import { loadConfig } from './utils/config-loader.js';
9
+
10
+ // ============================================================================
11
+ // Path Resolution
12
+ // ============================================================================
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+
17
+ const findPackageJson = (startDir: string): string => {
18
+ let currentDir = startDir;
19
+ while (currentDir !== path.parse(currentDir).root) {
20
+ const pkgPath = path.join(currentDir, 'package.json');
21
+ if (existsSync(pkgPath)) return pkgPath;
22
+ currentDir = path.dirname(currentDir);
23
+ }
24
+ throw new Error('Could not find package.json');
25
+ };
26
+
27
+ const packageJsonPath = findPackageJson(__dirname);
28
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
29
+
30
+ // ============================================================================
31
+ // CLI Setup
32
+ // ============================================================================
33
+
34
+ const program = new Command();
35
+
36
+ program
37
+ .name('chaincss')
38
+ .description('ChainCSS - Zero-runtime CSS-in-JS Compiler')
39
+ .version(packageJson.version, '-V, --version')
40
+ .usage('[command] [options]')
41
+ .helpOption('-h, --help', 'Display help for command');
42
+
43
+ // ============================================================================
44
+ // Dynamic Import for Compiler (avoids bundling top-level await)
45
+ // ============================================================================
46
+
47
+ const getCompiler = async (config?: any) => {
48
+ const { ChainCSSCompiler } = await import('../core/compiler.js');
49
+ return new ChainCSSCompiler(config);
50
+ };
51
+
52
+ // ============================================================================
53
+ // Error Handling
54
+ // ============================================================================
55
+
56
+ const handleError = (error: unknown, command: string): void => {
57
+ console.error(chalk.red(`\n❌ Error running "${command}":`));
58
+ if (error instanceof Error) {
59
+ console.error(chalk.red(` ${error.message}`));
60
+ if (process.env.DEBUG) console.error(error.stack);
61
+ } else {
62
+ console.error(chalk.red(` ${String(error)}`));
63
+ }
64
+ process.exit(1);
65
+ };
66
+
67
+ // ============================================================================
68
+ // Init Command
69
+ // ============================================================================
70
+
71
+ // ============================================================================
72
+ // Init Command (Generates the NEW Object-based config)
73
+ // ============================================================================
74
+ program
75
+ .command('init')
76
+ .description('Initialize ChainCSS configuration file')
77
+ .option('-f, --force', 'Overwrite existing config file')
78
+ .action(async (options) => {
79
+ try {
80
+ const configPath = 'chaincss.config.js';
81
+ if (existsSync(configPath) && !options.force) {
82
+ console.log(chalk.yellow('Config file already exists. Use --force to overwrite.'));
83
+ return;
84
+ }
85
+
86
+ const config = `export default {
87
+ inputs: ['src/**/*.chain.{js,ts}', 'src/**/*.tsx'],
88
+ output: {
89
+ cssFile: 'global.css',
90
+ classMapFile: 'style',
91
+ minify: false,
92
+ generateGlobalCSS: true
93
+ },
94
+ atomic: {
95
+ enabled: true,
96
+ naming: 'hash',
97
+ mode: 'hybrid'
98
+ },
99
+ verbose: true
100
+ };`;
101
+ writeFileSync(configPath, config);
102
+ console.log(chalk.green('✓ Created chaincss.config.js with Object-based output.'));
103
+ } catch (error) {
104
+ handleError(error, 'init');
105
+ }
106
+ });
107
+
108
+ // ============================================================================
109
+ // Build Command
110
+ // ============================================================================
111
+ program
112
+ .command('build')
113
+ .option('-v, --verbose', 'Verbose output')
114
+ .action(async (opts) => {
115
+ try {
116
+ const config = await loadConfig();
117
+ const compiler = await getCompiler(config);
118
+
119
+ console.log(chalk.blue('🚀 Starting ChainCSS Build...'));
120
+
121
+ const patterns = config.inputs || ['src/**/*.chain.{js,ts}', 'src/**/*.tsx'];
122
+ const files = await glob(patterns);
123
+
124
+ // The compiler handles the logic we wrote earlier:
125
+ // 1. Component CSS in src/components/<Name>/style/
126
+ // 2. Global CSS in public/
127
+ // 3. Manifest in src/manifest/
128
+ await compiler.compileComponents(files);
129
+
130
+ const stats = compiler.getStats();
131
+ console.log(chalk.green(`\n✅ Build Complete!`));
132
+ console.log(chalk.cyan(`📊 Atomic Rules: ${stats.atomicStyles}`));
133
+ } catch (error) {
134
+ handleError(error, 'build');
135
+ }
136
+ });
137
+
138
+ // ============================================================================
139
+ // Watch Command
140
+ // ============================================================================
141
+ program
142
+ .command('watch')
143
+ .description('Watch and automatically recompile styles')
144
+ .action(async () => {
145
+ try {
146
+ const config = await loadConfig();
147
+ const compiler = await getCompiler(config);
148
+ const chokidar = await import('chokidar');
149
+
150
+ const patterns = config.inputs || ['src/**/*.chain.{js,ts}', 'src/**/*.tsx'];
151
+ const watcher = chokidar.watch(patterns, { ignored: '**/node_modules/**' });
152
+
153
+ console.log(chalk.blue('📡 Watching for changes...'));
154
+
155
+ watcher.on('change', async (filePath) => {
156
+ console.log(chalk.yellow(`\r🔄 Change detected: ${path.basename(filePath)}`));
157
+ const files = await glob(patterns);
158
+ await compiler.compileComponents(files);
159
+ });
160
+ } catch (error) {
161
+ handleError(error, 'watch');
162
+ }
163
+ });
164
+
165
+ // ============================================================================
166
+ // Timeline
167
+ // ============================================================================
168
+
169
+ program
170
+ .command('timeline')
171
+ .description('Manage style timeline')
172
+ .argument('<action>', 'Action: list, diff, export, clear')
173
+ .option('-s, --snapshot1 <id>', 'First snapshot ID or selector for diff')
174
+ .option('--snapshot2 <id>', 'Second snapshot ID or selector for diff')
175
+ .option('-o, --output <path>', 'Output file for export')
176
+ .action(async (action, options) => {
177
+ const { timelineCommand } = await import('./commands/timeline.js');
178
+ await timelineCommand(action, options);
179
+ });
180
+
181
+ // ============================================================================
182
+ // Help and Examples
183
+ // ============================================================================
184
+
185
+ program.on('--help', () => {
186
+ console.log('');
187
+ console.log(chalk.cyan('Examples:'));
188
+ console.log(chalk.gray(' # Initialize a new project'));
189
+ console.log(' $ chaincss init');
190
+ console.log('');
191
+ console.log(chalk.gray(' # Build all styles'));
192
+ console.log(' $ chaincss build -c "src/**/*.chain.js"');
193
+ console.log('');
194
+ console.log(chalk.gray(' # Watch for changes'));
195
+ console.log(' $ chaincss watch -c "src/**/*.chain.js"');
196
+ console.log('');
197
+ console.log(chalk.cyan('Documentation:'));
198
+ console.log(' https://github.com/melcanz08/chaincss');
199
+ console.log('');
200
+ });
201
+
202
+ // ============================================================================
203
+ // Cache
204
+ // ============================================================================
205
+
206
+ program
207
+ .command('cache')
208
+ .description('Manage persistent cache')
209
+ .argument('<action>', 'Action: clear, stats, prune')
210
+ .option('-v, --verbose', 'Verbose output')
211
+ .action(async (action, options) => {
212
+ const { cacheCommand } = await import('./commands/cache.js');
213
+ await cacheCommand(action, options);
214
+ });
215
+
216
+ // ============================================================================
217
+ // Parse Arguments
218
+ // ============================================================================
219
+
220
+ if (process.argv.length === 2) {
221
+ program.outputHelp();
222
+ process.exit(0);
223
+ }
224
+
225
+ program.parse(process.argv);
226
+
@@ -0,0 +1,100 @@
1
+ // src/cli/types.ts
2
+
3
+ import type { ChainCSSConfig as CoreChainCSSConfig } from '../core/types.js';
4
+
5
+ // Re-export core config
6
+ export type ChainCSSConfig = CoreChainCSSConfig;
7
+
8
+ // CLI-specific options
9
+ export interface CLIOptions {
10
+ input?: string;
11
+ output?: string;
12
+ watch?: boolean;
13
+ atomic?: boolean;
14
+ minify?: boolean;
15
+ prefix?: boolean;
16
+ sourceMap?: boolean;
17
+ verbose?: boolean;
18
+ config?: string;
19
+ help?: boolean;
20
+ version?: boolean;
21
+ }
22
+
23
+ export interface CompileOptions {
24
+ input: string;
25
+ output: string;
26
+ watch?: boolean;
27
+ atomic?: boolean;
28
+ minify?: boolean;
29
+ prefix?: boolean;
30
+ sourceMap?: boolean;
31
+ verbose?: boolean;
32
+ }
33
+
34
+ export interface BuildOptions {
35
+ config?: string;
36
+ watch?: boolean;
37
+ verbose?: boolean;
38
+ atomic?: boolean;
39
+ minify?: boolean;
40
+ }
41
+
42
+ export interface WatchOptions {
43
+ config?: string;
44
+ verbose?: boolean;
45
+ atomic?: boolean;
46
+ }
47
+
48
+ export interface CacheOptions {
49
+ action: 'clear' | 'stats' | 'prune' | 'list' | 'inspect' | 'delete' | 'validate' | 'backup';
50
+ key?: string;
51
+ force?: boolean;
52
+ maxAge?: number;
53
+ maxSize?: number;
54
+ output?: string;
55
+ verbose?: boolean;
56
+ }
57
+
58
+ export interface TimelineOptions {
59
+ action: 'list' | 'diff' | 'changes' | 'stats' | 'export' | 'clear' | 'watch';
60
+ snapshot1?: string;
61
+ snapshot2?: string;
62
+ output?: string;
63
+ verbose?: boolean;
64
+ }
65
+
66
+ export interface InitOptions {
67
+ force?: boolean;
68
+ template?: 'full' | 'minimal';
69
+ typescript?: boolean;
70
+ framework?: 'react' | 'vue' | 'svelte' | 'solid';
71
+ }
72
+
73
+ // Utility type for command handlers
74
+ export type CommandHandler<T = any> = (options: T) => Promise<void> | void;
75
+
76
+ // CLI command definition
77
+ export interface CLICommand {
78
+ name: string;
79
+ description: string;
80
+ options?: Array<{
81
+ flags: string;
82
+ description: string;
83
+ defaultValue?: any;
84
+ }>;
85
+ handler: CommandHandler;
86
+ }
87
+
88
+ // Build result type
89
+ export interface BuildResult {
90
+ success: boolean;
91
+ compiledFiles: number;
92
+ duration: number;
93
+ errors: Error[];
94
+ warnings: string[];
95
+ stats?: {
96
+ totalStyles: number;
97
+ atomicStyles: number;
98
+ cssSize: number;
99
+ };
100
+ }
@@ -0,0 +1,174 @@
1
+ // src/cli/utils/config-loader.ts
2
+ /**
3
+ * ChainCSS Configuration Loader
4
+ * @module config-loader
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { createLogger } from './logger.js';
10
+ import type { ChainCSSConfig } from '../types.js';
11
+ import { DEFAULT_CONFIG as CORE_DEFAULTS } from '../../core/constants.js';
12
+
13
+ export async function loadConfig(configPath?: string): Promise<ChainCSSConfig> {
14
+ const logger = createLogger(false);
15
+
16
+ const possiblePaths = configPath
17
+ ? [configPath]
18
+ : [
19
+ path.join(process.cwd(), 'chaincss.config.js'),
20
+ path.join(process.cwd(), 'chaincss.config.ts'),
21
+ path.join(process.cwd(), '.chaincssrc.js'),
22
+ path.join(process.cwd(), 'chaincss.json')
23
+ ];
24
+
25
+ for (const configFile of possiblePaths) {
26
+ if (fs.existsSync(configFile)) {
27
+ try {
28
+ logger.debug(`Loading config from ${configFile}`);
29
+
30
+ const configModule = await import(`file://${configFile}`);
31
+ const userConfig = configModule.default || configModule;
32
+
33
+ // 2. MERGE WITH CORE DEFAULTS INSTEAD OF LOCAL DEFAULTS
34
+ return mergeConfig(CORE_DEFAULTS, userConfig);
35
+ } catch (error) {
36
+ logger.warn(`Failed to load config from ${configFile}:`, error);
37
+ }
38
+ }
39
+ }
40
+
41
+ return CORE_DEFAULTS;
42
+ }
43
+
44
+ function mergeConfig(defaults: any, user: any): ChainCSSConfig {
45
+ return {
46
+ ...defaults,
47
+ ...user,
48
+ tokens: {
49
+ ...defaults.tokens,
50
+ ...user.tokens
51
+ },
52
+ atomic: {
53
+ ...defaults.atomic,
54
+ ...user.atomic
55
+ },
56
+ prefixer: {
57
+ ...defaults.prefixer,
58
+ ...user.prefixer
59
+ },
60
+ output: {
61
+ ...defaults.output,
62
+ ...user.output
63
+ },
64
+ breakpoints: {
65
+ ...defaults.breakpoints,
66
+ ...user.breakpoints
67
+ }
68
+ };
69
+ }
70
+
71
+ export function saveConfigTemplate(outputPath: string = 'chaincss.config.js', full: boolean = false): void {
72
+ let template = '';
73
+
74
+ if (full) {
75
+ template = `/**
76
+ * ChainCSS Configuration
77
+ * @type {import('chaincss').ChainCSSConfig}
78
+ *
79
+ * Documentation: https://chaincss.dev/docs/configuration
80
+ */
81
+ export default {
82
+ // ========== INPUT/OUTPUT ==========
83
+ inputs: ['src/**/*.chain.js', 'src/**/*.chain.ts'],
84
+ output: 'dist/styles',
85
+
86
+ // ========== TOKENS ==========
87
+ tokens: {
88
+ enabled: true,
89
+ prefix: 'chain'
90
+ },
91
+
92
+ // ========== ATOMIC CSS ==========
93
+ atomic: {
94
+ enabled: true,
95
+ threshold: 3,
96
+ naming: 'readable',
97
+ minify: true,
98
+ mode: 'hybrid',
99
+ outputStrategy: 'component-first',
100
+ verbose: false
101
+ },
102
+
103
+ // ========== AUTOPREFIXER ==========
104
+ prefixer: {
105
+ enabled: true,
106
+ mode: 'auto',
107
+ browsers: ['> 0.5%', 'last 2 versions', 'not dead'],
108
+ sourceMap: true
109
+ },
110
+
111
+ // ========== OUTPUT OPTIONS ==========
112
+ output: {
113
+ cssFile: 'styles.css',
114
+ classMapFile: 'class-map.json',
115
+ typesFile: 'classes.d.ts',
116
+ minify: true,
117
+ generateGlobalCSS: true
118
+ },
119
+
120
+ // ========== RESPONSIVE BREAKPOINTS ==========
121
+ breakpoints: {
122
+ sm: '(max-width: 640px)',
123
+ md: '(min-width: 641px) and (max-width: 768px)',
124
+ lg: '(min-width: 769px) and (max-width: 1024px)',
125
+ xl: '(min-width: 1025px)',
126
+ '2xl': '(min-width: 1280px)',
127
+ mobile: '(max-width: 768px)',
128
+ tablet: '(min-width: 769px) and (max-width: 1024px)',
129
+ desktop: '(min-width: 1025px)'
130
+ },
131
+
132
+ // ========== DEBUG & TIMELINE ==========
133
+ debug: false,
134
+ sourceComments: true,
135
+ timeline: false,
136
+
137
+ // ========== FRAMEWORK ==========
138
+ framework: 'auto',
139
+
140
+ // ========== GENERAL ==========
141
+ namespace: 'chain',
142
+ watch: false,
143
+ verbose: false
144
+ };
145
+ `;
146
+ } else {
147
+ template = `/**
148
+ * ChainCSS Configuration
149
+ * @type {import('chaincss').ChainCSSConfig}
150
+ */
151
+ export default {
152
+ inputs: ['src/**/*.chain.js', 'src/**/*.chain.ts'],
153
+ output: 'dist/styles',
154
+ atomic: {
155
+ enabled: true,
156
+ naming: 'readable',
157
+ minify: true
158
+ },
159
+ prefixer: {
160
+ enabled: true
161
+ },
162
+ breakpoints: {
163
+ mobile: '(max-width: 768px)',
164
+ tablet: '(min-width: 769px) and (max-width: 1024px)',
165
+ desktop: '(min-width: 1025px)'
166
+ },
167
+ debug: false,
168
+ verbose: false
169
+ };
170
+ `;
171
+ }
172
+
173
+ fs.writeFileSync(outputPath, template, 'utf8');
174
+ }