jasmincss 1.0.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 (76) hide show
  1. package/README.md +524 -0
  2. package/bin/jasmin.js +45 -0
  3. package/dist/index.d.ts +62 -0
  4. package/dist/index.js +14568 -0
  5. package/dist/index.mjs +14524 -0
  6. package/dist/jasmin.css +63308 -0
  7. package/dist/jasmin.min.css +1 -0
  8. package/dist/plugins/nextjs.js +14777 -0
  9. package/dist/plugins/nextjs.mjs +14747 -0
  10. package/dist/plugins/vite.js +14889 -0
  11. package/dist/plugins/vite.mjs +14860 -0
  12. package/package.json +101 -0
  13. package/src/cli/add.js +83 -0
  14. package/src/cli/init.js +210 -0
  15. package/src/cli/run.js +142 -0
  16. package/src/components/accordion.js +309 -0
  17. package/src/components/alerts.js +357 -0
  18. package/src/components/avatars.js +331 -0
  19. package/src/components/badges.js +353 -0
  20. package/src/components/buttons.js +412 -0
  21. package/src/components/cards.js +342 -0
  22. package/src/components/carousel.js +495 -0
  23. package/src/components/chips.js +440 -0
  24. package/src/components/command-palette.js +434 -0
  25. package/src/components/datepicker.js +517 -0
  26. package/src/components/dropdown.js +411 -0
  27. package/src/components/forms.js +516 -0
  28. package/src/components/index.js +81 -0
  29. package/src/components/modals.js +349 -0
  30. package/src/components/navigation.js +463 -0
  31. package/src/components/offcanvas.js +390 -0
  32. package/src/components/popover.js +427 -0
  33. package/src/components/progress.js +396 -0
  34. package/src/components/rating.js +394 -0
  35. package/src/components/skeleton.js +408 -0
  36. package/src/components/spinner.js +453 -0
  37. package/src/components/stepper.js +389 -0
  38. package/src/components/tables.js +304 -0
  39. package/src/components/timeline.js +452 -0
  40. package/src/components/timepicker.js +529 -0
  41. package/src/components/tooltips.js +345 -0
  42. package/src/components/upload.js +490 -0
  43. package/src/config/defaults.js +263 -0
  44. package/src/config/loader.js +109 -0
  45. package/src/core/base.js +241 -0
  46. package/src/core/compiler.js +135 -0
  47. package/src/core/utilities/accessibility.js +290 -0
  48. package/src/core/utilities/animations.js +205 -0
  49. package/src/core/utilities/background.js +109 -0
  50. package/src/core/utilities/colors.js +234 -0
  51. package/src/core/utilities/columns.js +161 -0
  52. package/src/core/utilities/effects.js +261 -0
  53. package/src/core/utilities/filters.js +135 -0
  54. package/src/core/utilities/icons.js +806 -0
  55. package/src/core/utilities/index.js +239 -0
  56. package/src/core/utilities/layout.js +321 -0
  57. package/src/core/utilities/scroll.js +205 -0
  58. package/src/core/utilities/spacing.js +120 -0
  59. package/src/core/utilities/svg.js +191 -0
  60. package/src/core/utilities/transforms.js +116 -0
  61. package/src/core/utilities/typography.js +188 -0
  62. package/src/index.js +7 -0
  63. package/src/js/components/accordion.js +154 -0
  64. package/src/js/components/alert.js +198 -0
  65. package/src/js/components/carousel.js +226 -0
  66. package/src/js/components/dropdown.js +166 -0
  67. package/src/js/components/modal.js +169 -0
  68. package/src/js/components/offcanvas.js +175 -0
  69. package/src/js/components/popover.js +221 -0
  70. package/src/js/components/tabs.js +163 -0
  71. package/src/js/components/tooltip.js +200 -0
  72. package/src/js/index.js +79 -0
  73. package/src/js/types/config.d.ts +228 -0
  74. package/src/js/types/index.d.ts +439 -0
  75. package/src/plugins/nextjs.js +100 -0
  76. package/src/plugins/vite.js +133 -0
package/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "jasmincss",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "A modern, future-proof CSS framework with design philosophy",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "bin": {
10
+ "jasmin": "./bin/jasmin.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ },
18
+ "./js": {
19
+ "import": "./src/js/index.js",
20
+ "types": "./src/js/types/index.d.ts"
21
+ },
22
+ "./config": {
23
+ "types": "./src/js/types/config.d.ts"
24
+ },
25
+ "./css": "./dist/jasmin.css",
26
+ "./css/min": "./dist/jasmin.min.css",
27
+ "./dist/jasmin.css": "./dist/jasmin.css",
28
+ "./dist/jasmin.min.css": "./dist/jasmin.min.css",
29
+ "./plugins/next": "./dist/plugin/nextjs.js",
30
+ "./plugins/vite": "./dist/plugin/vite.js",
31
+ "./plugin/nextjs": "./dist/plugin/nextjs.js",
32
+ "./plugin/vite": "./dist/plugin/vite.js"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "bin",
37
+ "src"
38
+ ],
39
+ "scripts": {
40
+ "build": "node scripts/build.js",
41
+ "build:css": "node scripts/build-css.js",
42
+ "dev": "node scripts/dev.js",
43
+ "test": "node --test tests/**/*.test.js",
44
+ "test:browser": "npx playwright test --config=tests/browser/playwright.config.js",
45
+ "test:a11y": "npx playwright test tests/accessibility/",
46
+ "test:perf": "node tests/performance/benchmark.js",
47
+ "test:all": "npm run test && npm run test:browser && npm run test:a11y",
48
+ "docs:dev": "vitepress dev docs",
49
+ "docs:build": "vitepress build docs",
50
+ "docs:preview": "vitepress preview docs",
51
+ "prepublishOnly": "npm run build"
52
+ },
53
+ "keywords": [
54
+ "css",
55
+ "framework",
56
+ "utility-first",
57
+ "components",
58
+ "tailwind",
59
+ "design-system",
60
+ "jasmin",
61
+ "nextjs",
62
+ "react",
63
+ "vue"
64
+ ],
65
+ "author": "",
66
+ "license": "MIT",
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "https://github.com/jasmaine/JasminCSS"
70
+ },
71
+ "engines": {
72
+ "node": ">=18.0.0"
73
+ },
74
+ "dependencies": {
75
+ "chokidar": "^3.5.3",
76
+ "commander": "^11.1.0",
77
+ "cssnano": "^6.0.2",
78
+ "fast-glob": "^3.3.2",
79
+ "inquirer": "^9.2.12",
80
+ "picocolors": "^1.0.0",
81
+ "postcss": "^8.4.32"
82
+ },
83
+ "devDependencies": {
84
+ "@playwright/test": "^1.40.0",
85
+ "esbuild": "^0.19.10",
86
+ "serve": "^14.2.0",
87
+ "vitepress": "^1.6.4"
88
+ },
89
+ "peerDependencies": {
90
+ "next": ">=13.0.0",
91
+ "vite": ">=5.0.0"
92
+ },
93
+ "peerDependenciesMeta": {
94
+ "next": {
95
+ "optional": true
96
+ },
97
+ "vite": {
98
+ "optional": true
99
+ }
100
+ }
101
+ }
package/src/cli/add.js ADDED
@@ -0,0 +1,83 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import pc from 'picocolors';
4
+ import { components } from '../components/index.js';
5
+
6
+ const availableComponents = {
7
+ buttons: 'Interactive button styles with variants',
8
+ cards: 'Card containers with various layouts',
9
+ forms: 'Form inputs, selects, checkboxes, radios',
10
+ navigation: 'Navbars, sidebars, breadcrumbs',
11
+ modals: 'Modal dialogs and overlays',
12
+ tables: 'Data tables with sorting indicators',
13
+ alerts: 'Alert and notification banners',
14
+ badges: 'Status badges and tags',
15
+ avatars: 'User avatar components',
16
+ tooltips: 'Hover tooltips and popovers',
17
+ progress: 'Progress bars and spinners',
18
+ skeleton: 'Loading skeleton placeholders',
19
+ accordion: 'Collapsible accordion panels',
20
+ tabs: 'Tab navigation components',
21
+ dropdown: 'Dropdown menus',
22
+ pagination: 'Page navigation',
23
+ breadcrumb: 'Breadcrumb navigation',
24
+ toast: 'Toast notifications',
25
+ drawer: 'Side drawer panels',
26
+ hero: 'Hero section layouts'
27
+ };
28
+
29
+ export async function addCommand(component, options) {
30
+ if (options.list || component === 'list') {
31
+ console.log(pc.cyan('\n🌸 Available JasminCSS Components\n'));
32
+
33
+ Object.entries(availableComponents).forEach(([name, description]) => {
34
+ console.log(pc.white(` ${name.padEnd(15)}`), pc.dim(description));
35
+ });
36
+
37
+ console.log(pc.dim('\nUsage: jasmin add <component>\n'));
38
+ return;
39
+ }
40
+
41
+ if (!availableComponents[component]) {
42
+ console.log(pc.red(`Unknown component: ${component}`));
43
+ console.log(pc.dim('Run "jasmin add --list" to see available components'));
44
+ return;
45
+ }
46
+
47
+ const cwd = process.cwd();
48
+ const configPath = path.join(cwd, 'jasmin.config.js');
49
+
50
+ if (!fs.existsSync(configPath)) {
51
+ console.log(pc.yellow('No jasmin.config.js found. Run "jasmin init" first.'));
52
+ return;
53
+ }
54
+
55
+ // Read and update config
56
+ const configContent = fs.readFileSync(configPath, 'utf-8');
57
+
58
+ // Simple regex to find and update components array
59
+ const componentRegex = /components:\s*\[([\s\S]*?)\]/;
60
+ const match = configContent.match(componentRegex);
61
+
62
+ if (match) {
63
+ const existingComponents = match[1]
64
+ .split(',')
65
+ .map(c => c.trim().replace(/['"]/g, ''))
66
+ .filter(Boolean);
67
+
68
+ if (existingComponents.includes(component)) {
69
+ console.log(pc.yellow(`Component "${component}" is already included.`));
70
+ return;
71
+ }
72
+
73
+ existingComponents.push(component);
74
+ const newComponentsArray = existingComponents.map(c => `'${c}'`).join(', ');
75
+ const newConfigContent = configContent.replace(componentRegex, `components: [${newComponentsArray}]`);
76
+
77
+ fs.writeFileSync(configPath, newConfigContent);
78
+ console.log(pc.green(`✓ Added "${component}" component to jasmin.config.js`));
79
+ console.log(pc.dim('Run "jasmin run build" to regenerate CSS'));
80
+ } else {
81
+ console.log(pc.yellow('Could not update config. Please add the component manually.'));
82
+ }
83
+ }
@@ -0,0 +1,210 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import pc from 'picocolors';
4
+ import inquirer from 'inquirer';
5
+ import { defaultConfig, templates } from '../config/defaults.js';
6
+
7
+ export async function initCommand(options) {
8
+ const cwd = process.cwd();
9
+ const configPath = path.join(cwd, 'jasmin.config.js');
10
+
11
+ console.log(pc.cyan('🌸 Welcome to JasminCSS Setup\n'));
12
+
13
+ if (fs.existsSync(configPath) && !options.yes) {
14
+ const { overwrite } = await inquirer.prompt([{
15
+ type: 'confirm',
16
+ name: 'overwrite',
17
+ message: 'jasmin.config.js already exists. Overwrite?',
18
+ default: false
19
+ }]);
20
+
21
+ if (!overwrite) {
22
+ console.log(pc.yellow('Setup cancelled.'));
23
+ return;
24
+ }
25
+ }
26
+
27
+ let config = { ...defaultConfig };
28
+
29
+ if (options.yes) {
30
+ // Use defaults
31
+ console.log(pc.dim('Using default configuration...'));
32
+ } else if (options.template && templates[options.template]) {
33
+ // Use template
34
+ config = { ...config, ...templates[options.template] };
35
+ console.log(pc.green(`Using ${options.template} template`));
36
+ } else {
37
+ // Interactive setup
38
+ const answers = await inquirer.prompt([
39
+ {
40
+ type: 'input',
41
+ name: 'projectName',
42
+ message: 'Project name:',
43
+ default: path.basename(cwd)
44
+ },
45
+ {
46
+ type: 'list',
47
+ name: 'template',
48
+ message: 'Choose a design template:',
49
+ choices: [
50
+ { name: 'Futuristic - Bold, modern, tech-forward', value: 'futuristic' },
51
+ { name: 'Minimal - Clean, elegant, understated', value: 'minimal' },
52
+ { name: 'Corporate - Professional, trustworthy', value: 'corporate' },
53
+ { name: 'Creative - Vibrant, expressive, artistic', value: 'creative' },
54
+ { name: 'Custom - Define your own', value: 'custom' }
55
+ ]
56
+ }
57
+ ]);
58
+
59
+ if (answers.template === 'custom') {
60
+ const brandingAnswers = await inquirer.prompt([
61
+ {
62
+ type: 'input',
63
+ name: 'primaryColor',
64
+ message: 'Primary brand color (hex):',
65
+ default: '#6366f1',
66
+ validate: (input) => /^#[0-9A-Fa-f]{6}$/.test(input) || 'Please enter a valid hex color'
67
+ },
68
+ {
69
+ type: 'input',
70
+ name: 'secondaryColor',
71
+ message: 'Secondary brand color (hex):',
72
+ default: '#ec4899',
73
+ validate: (input) => /^#[0-9A-Fa-f]{6}$/.test(input) || 'Please enter a valid hex color'
74
+ },
75
+ {
76
+ type: 'input',
77
+ name: 'accentColor',
78
+ message: 'Accent color (hex):',
79
+ default: '#14b8a6',
80
+ validate: (input) => /^#[0-9A-Fa-f]{6}$/.test(input) || 'Please enter a valid hex color'
81
+ },
82
+ {
83
+ type: 'list',
84
+ name: 'fontFamily',
85
+ message: 'Primary font family:',
86
+ choices: [
87
+ { name: 'Inter (Modern sans-serif)', value: 'Inter' },
88
+ { name: 'Space Grotesk (Futuristic)', value: 'Space Grotesk' },
89
+ { name: 'DM Sans (Friendly)', value: 'DM Sans' },
90
+ { name: 'Outfit (Geometric)', value: 'Outfit' },
91
+ { name: 'System fonts', value: 'system-ui' }
92
+ ]
93
+ },
94
+ {
95
+ type: 'list',
96
+ name: 'borderRadius',
97
+ message: 'Border radius style:',
98
+ choices: [
99
+ { name: 'Sharp (0px)', value: '0' },
100
+ { name: 'Subtle (4px)', value: '0.25rem' },
101
+ { name: 'Rounded (8px)', value: '0.5rem' },
102
+ { name: 'Pill (16px)', value: '1rem' },
103
+ { name: 'Full (9999px)', value: '9999px' }
104
+ ]
105
+ },
106
+ {
107
+ type: 'confirm',
108
+ name: 'darkMode',
109
+ message: 'Include dark mode support?',
110
+ default: true
111
+ }
112
+ ]);
113
+
114
+ config.branding = {
115
+ ...config.branding,
116
+ colors: {
117
+ primary: brandingAnswers.primaryColor,
118
+ secondary: brandingAnswers.secondaryColor,
119
+ accent: brandingAnswers.accentColor
120
+ },
121
+ typography: {
122
+ fontFamily: brandingAnswers.fontFamily
123
+ },
124
+ borderRadius: {
125
+ default: brandingAnswers.borderRadius
126
+ }
127
+ };
128
+ config.darkMode = brandingAnswers.darkMode;
129
+ } else {
130
+ config = { ...config, ...templates[answers.template] };
131
+ }
132
+
133
+ config.projectName = answers.projectName;
134
+
135
+ // Component selection
136
+ const componentAnswers = await inquirer.prompt([
137
+ {
138
+ type: 'checkbox',
139
+ name: 'components',
140
+ message: 'Select components to include:',
141
+ choices: [
142
+ { name: 'Buttons', value: 'buttons', checked: true },
143
+ { name: 'Cards', value: 'cards', checked: true },
144
+ { name: 'Forms', value: 'forms', checked: true },
145
+ { name: 'Navigation', value: 'navigation', checked: true },
146
+ { name: 'Modals', value: 'modals', checked: false },
147
+ { name: 'Tables', value: 'tables', checked: false },
148
+ { name: 'Alerts', value: 'alerts', checked: true },
149
+ { name: 'Badges', value: 'badges', checked: true },
150
+ { name: 'Avatars', value: 'avatars', checked: false },
151
+ { name: 'Tooltips', value: 'tooltips', checked: false },
152
+ { name: 'Progress', value: 'progress', checked: false },
153
+ { name: 'Skeleton', value: 'skeleton', checked: false }
154
+ ]
155
+ },
156
+ {
157
+ type: 'checkbox',
158
+ name: 'utilities',
159
+ message: 'Select utility modules:',
160
+ choices: [
161
+ { name: 'Layout (flex, grid, container)', value: 'layout', checked: true },
162
+ { name: 'Spacing (margin, padding)', value: 'spacing', checked: true },
163
+ { name: 'Typography', value: 'typography', checked: true },
164
+ { name: 'Colors', value: 'colors', checked: true },
165
+ { name: 'Effects (shadows, blur)', value: 'effects', checked: true },
166
+ { name: 'Animations', value: 'animations', checked: true },
167
+ { name: 'Transforms', value: 'transforms', checked: false },
168
+ { name: 'Filters', value: 'filters', checked: false }
169
+ ]
170
+ }
171
+ ]);
172
+
173
+ config.components = componentAnswers.components;
174
+ config.utilities = componentAnswers.utilities;
175
+ }
176
+
177
+ // Generate config file
178
+ const configContent = `/** @type {import('jasmincss').JasminConfig} */
179
+ export default ${JSON.stringify(config, null, 2)};
180
+ `;
181
+
182
+ fs.writeFileSync(configPath, configContent);
183
+ console.log(pc.green('\n✓ Created jasmin.config.js'));
184
+
185
+ // Create CSS entry point
186
+ const cssDir = path.join(cwd, 'styles');
187
+ if (!fs.existsSync(cssDir)) {
188
+ fs.mkdirSync(cssDir, { recursive: true });
189
+ }
190
+
191
+ const mainCssPath = path.join(cssDir, 'jasmin.css');
192
+ const mainCssContent = `/* JasminCSS - Main Entry Point */
193
+ @import 'jasmincss/css';
194
+
195
+ /* Your custom styles below */
196
+ `;
197
+
198
+ fs.writeFileSync(mainCssPath, mainCssContent);
199
+ console.log(pc.green('✓ Created styles/jasmin.css'));
200
+
201
+ // Print next steps
202
+ console.log(pc.cyan('\n📦 Next steps:\n'));
203
+ console.log(pc.white(' 1. Run'), pc.yellow('jasmin run dev'), pc.white('to compile the full library'));
204
+ console.log(pc.white(' 2. Run'), pc.yellow('jasmin run build'), pc.white('for production (tree-shaken)'));
205
+ console.log(pc.white(' 3. Run'), pc.yellow('jasmin run watch'), pc.white('for development with hot reload'));
206
+ console.log(pc.white('\n Import in your project:'));
207
+ console.log(pc.dim(' import \'./dist/jasmin.min.css\';'));
208
+ console.log(pc.dim(' // or'));
209
+ console.log(pc.dim(' import \'jasmincss/css\';\n'));
210
+ }
package/src/cli/run.js ADDED
@@ -0,0 +1,142 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import pc from 'picocolors';
4
+ import chokidar from 'chokidar';
5
+ import { compileCSS, scanForUsedClasses } from '../core/compiler.js';
6
+ import { loadConfig } from '../config/loader.js';
7
+
8
+ export async function runCommand(task, options) {
9
+ const cwd = process.cwd();
10
+ const configPath = path.resolve(cwd, options.config);
11
+ const outputDir = path.resolve(cwd, options.output);
12
+
13
+ // Load config
14
+ let config;
15
+ try {
16
+ config = await loadConfig(configPath);
17
+ } catch (err) {
18
+ console.log(pc.yellow('No jasmin.config.js found. Using defaults.'));
19
+ config = (await import('../config/defaults.js')).defaultConfig;
20
+ }
21
+
22
+ // Ensure output directory exists
23
+ if (!fs.existsSync(outputDir)) {
24
+ fs.mkdirSync(outputDir, { recursive: true });
25
+ }
26
+
27
+ switch (task) {
28
+ case 'build':
29
+ await buildTask(config, outputDir, cwd, true);
30
+ break;
31
+
32
+ case 'dev':
33
+ await buildTask(config, outputDir, cwd, false);
34
+ break;
35
+
36
+ case 'watch':
37
+ await watchTask(config, outputDir, cwd, options);
38
+ break;
39
+
40
+ default:
41
+ console.log(pc.red(`Unknown task: ${task}`));
42
+ console.log(pc.dim('Available tasks: build, dev, watch'));
43
+ process.exit(1);
44
+ }
45
+ }
46
+
47
+ async function buildTask(config, outputDir, cwd, isProduction) {
48
+ const startTime = Date.now();
49
+
50
+ console.log(pc.cyan(`\n🌸 JasminCSS ${isProduction ? 'Production' : 'Development'} Build\n`));
51
+
52
+ let usedClasses = null;
53
+
54
+ if (isProduction) {
55
+ // Scan project for used classes (tree-shaking)
56
+ console.log(pc.dim('Scanning project for used classes...'));
57
+ usedClasses = await scanForUsedClasses(cwd, config.content || [
58
+ './**/*.{html,js,jsx,ts,tsx,vue,svelte}',
59
+ '!./node_modules/**'
60
+ ]);
61
+ console.log(pc.dim(`Found ${usedClasses.size} unique class references`));
62
+ }
63
+
64
+ // Compile CSS
65
+ console.log(pc.dim('Compiling CSS...'));
66
+ const { css, minified, stats } = await compileCSS(config, {
67
+ usedClasses,
68
+ minify: isProduction,
69
+ includeAll: !isProduction
70
+ });
71
+
72
+ // Write output files
73
+ const outputPath = path.join(outputDir, 'jasmin.css');
74
+ const minOutputPath = path.join(outputDir, 'jasmin.min.css');
75
+
76
+ fs.writeFileSync(outputPath, css);
77
+ console.log(pc.green(`✓ ${outputPath}`), pc.dim(`(${formatSize(css.length)})`));
78
+
79
+ if (isProduction && minified) {
80
+ fs.writeFileSync(minOutputPath, minified);
81
+ console.log(pc.green(`✓ ${minOutputPath}`), pc.dim(`(${formatSize(minified.length)})`));
82
+ }
83
+
84
+ const elapsed = Date.now() - startTime;
85
+ console.log(pc.cyan(`\n✨ Built in ${elapsed}ms`));
86
+
87
+ if (stats) {
88
+ console.log(pc.dim(` ${stats.utilities} utilities, ${stats.components} components`));
89
+ }
90
+ }
91
+
92
+ async function watchTask(config, outputDir, cwd, options) {
93
+ console.log(pc.cyan('\n🌸 JasminCSS Watch Mode\n'));
94
+ console.log(pc.dim('Watching for changes...\n'));
95
+
96
+ // Initial build
97
+ await buildTask(config, outputDir, cwd, false);
98
+
99
+ // Watch for changes
100
+ const watchPaths = config.content || [
101
+ './**/*.{html,js,jsx,ts,tsx,vue,svelte}',
102
+ 'jasmin.config.js'
103
+ ];
104
+
105
+ const watcher = chokidar.watch(watchPaths, {
106
+ ignored: /node_modules/,
107
+ persistent: true,
108
+ cwd
109
+ });
110
+
111
+ let rebuildTimeout;
112
+ const debouncedRebuild = () => {
113
+ clearTimeout(rebuildTimeout);
114
+ rebuildTimeout = setTimeout(async () => {
115
+ console.log(pc.dim('\nChange detected, rebuilding...'));
116
+ try {
117
+ // Reload config
118
+ const newConfig = await loadConfig(path.resolve(cwd, options.config));
119
+ await buildTask(newConfig, outputDir, cwd, false);
120
+ } catch (err) {
121
+ console.log(pc.red('Build error:'), err.message);
122
+ }
123
+ }, 100);
124
+ };
125
+
126
+ watcher.on('change', debouncedRebuild);
127
+ watcher.on('add', debouncedRebuild);
128
+ watcher.on('unlink', debouncedRebuild);
129
+
130
+ // Handle exit
131
+ process.on('SIGINT', () => {
132
+ console.log(pc.dim('\n\nStopping watch mode...'));
133
+ watcher.close();
134
+ process.exit(0);
135
+ });
136
+ }
137
+
138
+ function formatSize(bytes) {
139
+ if (bytes < 1024) return `${bytes} B`;
140
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
141
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
142
+ }