weloop-kosign 1.1.4 → 1.1.5

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 (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/cli-remote.js +217 -851
@@ -8,19 +8,16 @@
8
8
  * - If running in external projects: uses remote GitLab/GitHub URL
9
9
  *
10
10
  * Usage (via npx - recommended):
11
- * npx weloop-kosign@latest init
12
11
  * npx weloop-kosign@latest add <component-name>
13
12
  * npx weloop-kosign@latest list
14
13
  * npx weloop-kosign@latest css [--overwrite]
15
14
  *
16
15
  * Usage (local development):
17
- * node scripts/cli-remote.js init [--registry <url|path>]
18
16
  * node scripts/cli-remote.js add <component-name> [--registry <url|path>]
19
17
  * node scripts/cli-remote.js list [--registry <url|path>]
20
18
  * node scripts/cli-remote.js css [--registry <url|path>] [--overwrite]
21
19
  *
22
20
  * Examples:
23
- * npx weloop-kosign@latest init
24
21
  * npx weloop-kosign@latest add button
25
22
  * npx weloop-kosign@latest add button --registry ./registry
26
23
  * npx weloop-kosign@latest css
@@ -48,81 +45,22 @@ const { execSync } = require('child_process');
48
45
  */
49
46
  function getDefaultRegistryUrl() {
50
47
  const localRegistryPath = path.join(__dirname, '../registry');
51
-
48
+
52
49
  // Check if we're running in the component library project itself
53
50
  if (fs.existsSync(localRegistryPath) && fs.existsSync(path.join(localRegistryPath, 'index.json'))) {
54
51
  return localRegistryPath;
55
52
  }
56
-
53
+
57
54
  // Fall back to environment variable or default remote URL
58
- return process.env.WELOOP_REGISTRY_URL ||
55
+ return process.env.WELOOP_REGISTRY_URL ||
59
56
  'https://gitlab.com/Sophanithchrek/weloop-shadcn-next-app/-/raw/main/registry';
60
57
  }
61
58
 
62
59
  const DEFAULT_REGISTRY_URL = getDefaultRegistryUrl();
63
60
 
64
- // ============================================================================
65
- // CONSTANTS
66
- // ============================================================================
67
-
68
61
  // Network timeout constants (in milliseconds)
69
62
  const REQUEST_TIMEOUT = 15000;
70
63
  const RETRY_DELAY = 1000;
71
- const MAX_RETRIES = 3;
72
-
73
- // Package names
74
- const PACKAGE_NAMES = {
75
- CLSX: 'clsx',
76
- TAILWIND_MERGE: 'tailwind-merge',
77
- TW_ANIMATE_CSS: 'tw-animate-css',
78
- };
79
-
80
- // Base dependencies required for all components
81
- const BASE_DEPENDENCIES = [PACKAGE_NAMES.CLSX, PACKAGE_NAMES.TAILWIND_MERGE];
82
-
83
- // File paths
84
- const PATHS = {
85
- COMPONENTS_JSON: 'components.json',
86
- REGISTRY_INDEX: 'index.json',
87
- APP_GLOBALS_CSS: 'app/globals.css',
88
- STYLES_GLOBALS_CSS: 'styles/globals.css',
89
- VITE_INDEX_CSS: 'src/index.css',
90
- PACKAGE_JSON: 'package.json',
91
- };
92
-
93
- // Default configuration values
94
- const DEFAULTS = {
95
- STYLE: 'blue',
96
- BASE_COLOR: 'neutral',
97
- CSS_PATH: PATHS.APP_GLOBALS_CSS,
98
- SCHEMA_URL: 'https://weloop.kosign.dev/schema.json',
99
- ICON_LIBRARY: 'lucide',
100
- };
101
-
102
- // Registry URLs
103
- const REGISTRY_URLS = {
104
- DEFAULT: 'https://gitlab.com/Sophanithchrek/weloop-shadcn-next-app/-/raw/main/registry',
105
- INDEX: 'https://gitlab.com/Sophanithchrek/weloop-shadcn-next-app/-/raw/main/registry/index.json',
106
- CSS: 'https://gitlab.com/Sophanithchrek/weloop-shadcn-next-app/-/raw/main/app/globals.css',
107
- };
108
-
109
- // CSS patterns for regex matching
110
- const CSS_PATTERNS = {
111
- TAILWIND_IMPORT: /@import\s+["']tailwindcss["'];?\s*\n?/g,
112
- TW_ANIMATE_IMPORT: /@import\s+["']tw-animate-css["'];?\s*\n?/g,
113
- CUSTOM_VARIANT: /@custom-variant\s+dark\s+\(&:is\(\.dark \*\)\);\s*\n?/g,
114
- IMPORT_STATEMENT: /@import\s+["'][^"']+["'];?\s*\n?/g,
115
- WELOOP_START: /(@theme\s+inline|:root\s*\{)/,
116
- };
117
-
118
- // Weloop style markers
119
- const WELOOP_MARKERS = [
120
- '--WLDS-PRM-',
121
- '--WLDS-RED-',
122
- '--WLDS-NTL-',
123
- '--system-100',
124
- '--system-200',
125
- ];
126
64
 
127
65
  // ============================================================================
128
66
  // UTILITIES - Logging and File System Helpers
@@ -189,38 +127,6 @@ function ensureDirectoryExists(dirPath) {
189
127
  }
190
128
  }
191
129
 
192
- /**
193
- * Normalizes and cleans CSS content by removing duplicates and ensuring proper format
194
- * @param {string} cssContent - CSS content to normalize
195
- * @param {boolean} hasTwAnimate - Whether tw-animate-css package is installed
196
- * @returns {string} Normalized CSS content
197
- */
198
- function normalizeAndCleanCSS(cssContent, hasTwAnimate) {
199
- let normalized = normalizeCSSFormat(cssContent);
200
- normalized = removeDuplicateTwAnimateImports(normalized);
201
- normalized = removeDuplicateCustomVariant(normalized);
202
- // Ensure tw-animate import is handled correctly
203
- normalized = processTwAnimateImport(normalized, hasTwAnimate, false);
204
- return normalized;
205
- }
206
-
207
- /**
208
- * Creates error message for CSS installation failures with helpful guidance
209
- * @param {string} registryUrl - Registry URL that was attempted
210
- * @param {string} cssPath - Target CSS file path
211
- * @returns {string} Formatted error message with instructions
212
- */
213
- function getCSSInstallErrorMessage(registryUrl, cssPath) {
214
- const cssUrl = isLocalPath(registryUrl)
215
- ? 'your local registry/app/globals.css'
216
- : registryUrl.replace('/registry', '/app/globals.css');
217
-
218
- return `\n To add styles manually:\n` +
219
- ` 1. Download: ${cssUrl}\n` +
220
- ` 2. Add the CSS variables to your ${cssPath} file\n` +
221
- ` 3. Or copy from: ${REGISTRY_URLS.CSS}\n`;
222
- }
223
-
224
130
  /**
225
131
  * Checks if a path is a local file path or a remote URL
226
132
  * @param {string} pathOrUrl - Path or URL to check
@@ -249,12 +155,12 @@ function detectPackageManager() {
249
155
  if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) return 'yarn';
250
156
  if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) return 'pnpm';
251
157
  if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) return 'npm';
252
-
158
+
253
159
  // Fallback: check npm user agent (set by npm/yarn/pnpm when running)
254
160
  const userAgent = process.env.npm_config_user_agent || '';
255
161
  if (userAgent.includes('yarn')) return 'yarn';
256
162
  if (userAgent.includes('pnpm')) return 'pnpm';
257
-
163
+
258
164
  // Default to npm if we can't detect anything
259
165
  return 'npm';
260
166
  }
@@ -282,9 +188,9 @@ function getInstallCommand(packageManager, packages) {
282
188
  * @returns {boolean} True if package is installed, false otherwise
283
189
  */
284
190
  function checkPackageInstalled(packageName) {
285
- const packageJsonPath = path.join(process.cwd(), PATHS.PACKAGE_JSON);
191
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
286
192
  if (!fs.existsSync(packageJsonPath)) return false;
287
-
193
+
288
194
  try {
289
195
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
290
196
  // Check both dependencies and devDependencies
@@ -303,16 +209,16 @@ function checkPackageInstalled(packageName) {
303
209
  * @returns {string[]} List of packages that need to be installed
304
210
  */
305
211
  function getMissingDependencies(requiredDeps) {
306
- const packageJsonPath = path.join(process.cwd(), PATHS.PACKAGE_JSON);
212
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
307
213
  if (!fs.existsSync(packageJsonPath)) {
308
214
  // No package.json means we need to install everything
309
215
  return requiredDeps;
310
216
  }
311
-
217
+
312
218
  try {
313
219
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
314
220
  const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
315
-
221
+
316
222
  return requiredDeps.filter(dep => {
317
223
  // For scoped packages like @radix-ui/react-button, also check @radix-ui
318
224
  const depName = dep.split('/').slice(0, 2).join('/');
@@ -334,44 +240,44 @@ function getMissingDependencies(requiredDeps) {
334
240
  */
335
241
  async function installPackages(packages) {
336
242
  if (packages.length === 0) return;
337
-
243
+
338
244
  const packageManager = detectPackageManager();
339
245
  info(`\nInstalling dependencies: ${packages.join(', ')}`);
340
-
246
+
341
247
  try {
342
248
  const installCmd = getInstallCommand(packageManager, packages);
343
-
249
+
344
250
  // npm has noisy ERESOLVE warnings that aren't real errors
345
251
  // We filter these out while keeping actual error messages
346
252
  if (packageManager === 'npm') {
347
253
  const { spawn } = require('child_process');
348
254
  const [cmd, ...args] = installCmd.split(' ');
349
-
255
+
350
256
  await new Promise((resolve, reject) => {
351
257
  const proc = spawn(cmd, args, {
352
258
  cwd: process.cwd(),
353
259
  stdio: ['inherit', 'inherit', 'pipe'],
354
260
  shell: true
355
261
  });
356
-
262
+
357
263
  let stderr = '';
358
264
  proc.stderr.on('data', (data) => {
359
265
  const output = data.toString();
360
266
  // Filter out ERESOLVE peer dependency warnings (these are usually safe to ignore)
361
267
  // but keep actual error messages visible to the user
362
- if (!output.includes('ERESOLVE overriding peer dependency') &&
363
- !output.includes('npm warn ERESOLVE')) {
268
+ if (!output.includes('ERESOLVE overriding peer dependency') &&
269
+ !output.includes('npm warn ERESOLVE')) {
364
270
  process.stderr.write(data);
365
271
  }
366
272
  stderr += output;
367
273
  });
368
-
274
+
369
275
  proc.on('close', (code) => {
370
276
  if (code !== 0) {
371
277
  // npm sometimes exits with non-zero code due to warnings, not real errors
372
278
  // Check if there's an actual error message (not just ERESOLVE warnings)
373
- const hasRealError = stderr.includes('npm error') &&
374
- !stderr.match(/npm error.*ERESOLVE/);
279
+ const hasRealError = stderr.includes('npm error') &&
280
+ !stderr.match(/npm error.*ERESOLVE/);
375
281
  if (hasRealError) {
376
282
  reject(new Error(`Installation failed with code ${code}`));
377
283
  } else {
@@ -382,13 +288,13 @@ async function installPackages(packages) {
382
288
  resolve();
383
289
  }
384
290
  });
385
-
291
+
386
292
  proc.on('error', reject);
387
293
  });
388
294
  } else {
389
295
  execSync(installCmd, { stdio: 'inherit', cwd: process.cwd() });
390
296
  }
391
-
297
+
392
298
  success(`Dependencies installed successfully`);
393
299
  } catch (error) {
394
300
  warn(`Failed to install dependencies automatically`);
@@ -410,25 +316,25 @@ async function installPackages(packages) {
410
316
  * @returns {object} The default configuration object
411
317
  */
412
318
  function createDefaultComponentsConfig() {
413
- const configPath = path.join(process.cwd(), PATHS.COMPONENTS_JSON);
414
-
319
+ const configPath = path.join(process.cwd(), 'components.json');
320
+
415
321
  // Detect Next.js project structure
416
322
  // App Router uses 'app/globals.css', Pages Router uses 'styles/globals.css'
417
323
  const hasAppDir = fs.existsSync(path.join(process.cwd(), 'app'));
418
- const cssPath = hasAppDir ? PATHS.APP_GLOBALS_CSS : PATHS.STYLES_GLOBALS_CSS;
419
-
324
+ const cssPath = hasAppDir ? 'app/globals.css' : 'styles/globals.css';
325
+
420
326
  const defaultConfig = {
421
- style: DEFAULTS.STYLE,
327
+ style: 'blue',
422
328
  rsc: true,
423
329
  tsx: true,
424
330
  tailwind: {
425
331
  config: 'tailwind.config.ts',
426
332
  css: cssPath,
427
- baseColor: DEFAULTS.BASE_COLOR,
333
+ baseColor: 'neutral',
428
334
  cssVariables: true,
429
335
  prefix: ''
430
336
  },
431
- iconLibrary: DEFAULTS.ICON_LIBRARY,
337
+ iconLibrary: 'lucide',
432
338
  aliases: {
433
339
  components: '@/components',
434
340
  utils: '@/lib/utils',
@@ -436,13 +342,13 @@ function createDefaultComponentsConfig() {
436
342
  lib: '@/lib',
437
343
  hooks: '@/hooks'
438
344
  },
439
- registry: REGISTRY_URLS.INDEX
345
+ registry: 'https://gitlab.com/Sophanithchrek/weloop-shadcn-next-app/-/raw/main/registry/index.json'
440
346
  };
441
-
347
+
442
348
  fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
443
- success(`Created ${PATHS.COMPONENTS_JSON}`);
349
+ success(`Created components.json`);
444
350
  info(` You can customize this file to match your project setup`);
445
-
351
+
446
352
  return defaultConfig;
447
353
  }
448
354
 
@@ -453,20 +359,15 @@ function createDefaultComponentsConfig() {
453
359
  * @returns {object} The configuration object
454
360
  */
455
361
  function loadComponentsConfig() {
456
- const configPath = path.join(process.cwd(), PATHS.COMPONENTS_JSON);
457
-
362
+ const configPath = path.join(process.cwd(), 'components.json');
363
+
458
364
  if (!fs.existsSync(configPath)) {
459
- info(`${PATHS.COMPONENTS_JSON} not found. Creating default configuration...`);
365
+ info('components.json not found. Creating default configuration...');
460
366
  return createDefaultComponentsConfig();
461
367
  }
462
-
463
- try {
464
- const content = fs.readFileSync(configPath, 'utf-8');
465
- return JSON.parse(content);
466
- } catch (err) {
467
- error(`Failed to parse ${PATHS.COMPONENTS_JSON}: ${err.message}`);
468
- throw err;
469
- }
368
+
369
+ const content = fs.readFileSync(configPath, 'utf-8');
370
+ return JSON.parse(content);
470
371
  }
471
372
 
472
373
  /**
@@ -482,21 +383,21 @@ function loadComponentsConfig() {
482
383
  * @param {number} retries - Number of retry attempts (default: 3)
483
384
  * @returns {Promise<object>} Parsed JSON object
484
385
  */
485
- async function fetchJSON(urlOrPath, retries = MAX_RETRIES) {
386
+ async function fetchJSON(urlOrPath, retries = 3) {
486
387
  // Handle local file paths
487
388
  if (isLocalPath(urlOrPath)) {
488
389
  return new Promise((resolve, reject) => {
489
390
  try {
490
391
  // Resolve relative paths to absolute
491
- const fullPath = path.isAbsolute(urlOrPath)
492
- ? urlOrPath
392
+ const fullPath = path.isAbsolute(urlOrPath)
393
+ ? urlOrPath
493
394
  : path.join(process.cwd(), urlOrPath);
494
-
395
+
495
396
  if (!fs.existsSync(fullPath)) {
496
397
  reject(new Error(`File not found: ${fullPath}`));
497
398
  return;
498
399
  }
499
-
400
+
500
401
  const content = fs.readFileSync(fullPath, 'utf-8');
501
402
  resolve(JSON.parse(content));
502
403
  } catch (e) {
@@ -504,38 +405,38 @@ async function fetchJSON(urlOrPath, retries = MAX_RETRIES) {
504
405
  }
505
406
  });
506
407
  }
507
-
408
+
508
409
  // Handle remote URLs
509
410
  return new Promise((resolve, reject) => {
510
411
  const client = urlOrPath.startsWith('https') ? https : http;
511
412
  let timeout;
512
413
  let request;
513
-
414
+
514
415
  const makeRequest = () => {
515
416
  request = client.get(urlOrPath, (res) => {
516
417
  clearTimeout(timeout);
517
-
418
+
518
419
  // Handle HTTP redirects (301, 302)
519
420
  if (res.statusCode === 302 || res.statusCode === 301) {
520
421
  return fetchJSON(res.headers.location, retries).then(resolve).catch(reject);
521
422
  }
522
-
423
+
523
424
  // Provide helpful error messages for common HTTP status codes
524
425
  if (res.statusCode === 403) {
525
426
  reject(new Error(`Access forbidden (403). Repository may be private. Make it public in GitLab/GitHub settings, or use --registry with a public URL.`));
526
427
  return;
527
428
  }
528
-
429
+
529
430
  if (res.statusCode === 404) {
530
431
  reject(new Error(`Not found (404). Check that the registry files are pushed to the repository and the URL is correct.`));
531
432
  return;
532
433
  }
533
-
434
+
534
435
  if (res.statusCode !== 200) {
535
436
  reject(new Error(`Failed to fetch: HTTP ${res.statusCode} - ${res.statusMessage || 'Unknown error'}`));
536
437
  return;
537
438
  }
538
-
439
+
539
440
  // Collect response data
540
441
  let data = '';
541
442
  res.on('data', (chunk) => { data += chunk; });
@@ -552,7 +453,7 @@ async function fetchJSON(urlOrPath, retries = MAX_RETRIES) {
552
453
  }
553
454
  });
554
455
  });
555
-
456
+
556
457
  // Handle network errors with automatic retry
557
458
  request.on('error', (err) => {
558
459
  clearTimeout(timeout);
@@ -565,7 +466,7 @@ async function fetchJSON(urlOrPath, retries = MAX_RETRIES) {
565
466
  reject(new Error(`Network error: ${err.message} (${err.code || 'UNKNOWN'})`));
566
467
  }
567
468
  });
568
-
469
+
569
470
  // Set request timeout with retry logic
570
471
  timeout = setTimeout(() => {
571
472
  request.destroy();
@@ -579,7 +480,7 @@ async function fetchJSON(urlOrPath, retries = MAX_RETRIES) {
579
480
  }
580
481
  }, REQUEST_TIMEOUT);
581
482
  };
582
-
483
+
583
484
  makeRequest();
584
485
  });
585
486
  }
@@ -599,34 +500,34 @@ async function fetchText(urlOrPath) {
599
500
  }
600
501
  return fs.readFileSync(urlOrPath, 'utf-8');
601
502
  }
602
-
503
+
603
504
  // Handle remote URLs
604
505
  return new Promise((resolve, reject) => {
605
506
  const client = urlOrPath.startsWith('https') ? https : http;
606
507
  let data = '';
607
-
508
+
608
509
  const req = client.get(urlOrPath, (res) => {
609
510
  // Follow redirects
610
511
  if (res.statusCode === 302 || res.statusCode === 301) {
611
512
  return fetchText(res.headers.location).then(resolve).catch(reject);
612
513
  }
613
-
514
+
614
515
  // Handle common HTTP errors
615
516
  if (res.statusCode === 403) {
616
517
  reject(new Error('Access forbidden - repository may be private'));
617
518
  return;
618
519
  }
619
-
520
+
620
521
  if (res.statusCode === 404) {
621
522
  reject(new Error('CSS file not found in repository'));
622
523
  return;
623
524
  }
624
-
525
+
625
526
  if (res.statusCode !== 200) {
626
527
  reject(new Error(`HTTP ${res.statusCode}`));
627
528
  return;
628
529
  }
629
-
530
+
630
531
  // Collect response data
631
532
  res.on('data', (chunk) => { data += chunk; });
632
533
  res.on('end', () => {
@@ -638,7 +539,7 @@ async function fetchText(urlOrPath) {
638
539
  resolve(data);
639
540
  });
640
541
  });
641
-
542
+
642
543
  // Handle network errors with automatic retry
643
544
  req.on('error', (err) => {
644
545
  if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') {
@@ -649,7 +550,7 @@ async function fetchText(urlOrPath) {
649
550
  reject(err);
650
551
  }
651
552
  });
652
-
553
+
653
554
  // Set request timeout
654
555
  req.setTimeout(REQUEST_TIMEOUT, () => {
655
556
  req.destroy();
@@ -669,14 +570,13 @@ async function fetchText(urlOrPath) {
669
570
  function resolveRegistryPath(baseUrl, fileName) {
670
571
  if (isLocalPath(baseUrl)) {
671
572
  // Resolve local paths (absolute or relative)
672
- const basePath = path.isAbsolute(baseUrl)
673
- ? baseUrl
573
+ const basePath = path.isAbsolute(baseUrl)
574
+ ? baseUrl
674
575
  : path.join(process.cwd(), baseUrl);
675
576
  return path.join(basePath, fileName);
676
577
  }
677
- // For remote URLs, append filename with proper separator
678
- const separator = baseUrl.endsWith('/') ? '' : '/';
679
- return `${baseUrl}${separator}${fileName}`;
578
+ // For remote URLs, just append the filename
579
+ return `${baseUrl}/${fileName}`;
680
580
  }
681
581
 
682
582
  /**
@@ -693,7 +593,6 @@ async function loadRegistry(componentName, registryBaseUrl) {
693
593
  return await fetchJSON(registryPath);
694
594
  } catch (err) {
695
595
  // Component not found - return null instead of throwing
696
- // This allows the caller to handle missing components gracefully
697
596
  return null;
698
597
  }
699
598
  }
@@ -706,7 +605,7 @@ async function loadRegistry(componentName, registryBaseUrl) {
706
605
  * @returns {Promise<object>} Index object containing list of components
707
606
  */
708
607
  async function loadIndex(registryBaseUrl) {
709
- const indexPath = resolveRegistryPath(registryBaseUrl, PATHS.REGISTRY_INDEX);
608
+ const indexPath = resolveRegistryPath(registryBaseUrl, 'index.json');
710
609
  try {
711
610
  return await fetchJSON(indexPath);
712
611
  } catch (err) {
@@ -727,18 +626,12 @@ async function loadIndex(registryBaseUrl) {
727
626
  * @returns {boolean} True if Weloop styles are present
728
627
  */
729
628
  function hasWeloopStyles(content) {
730
- return WELOOP_MARKERS.some(marker => content.includes(marker));
731
- }
732
-
733
- /**
734
- * Checks if CSS content has a Tailwind import (either format)
735
- * @param {string} content - CSS content to check
736
- * @returns {boolean} True if Tailwind import exists
737
- */
738
- function hasTailwindImport(content) {
739
- return content.includes('@import "tailwindcss"') ||
740
- content.includes("@import 'tailwindcss'") ||
741
- content.includes('@tailwind base');
629
+ // Check for Weloop-specific CSS variable prefixes
630
+ return content.includes('--WLDS-PRM-') || // Primary colors
631
+ content.includes('--WLDS-RED-') || // Red colors
632
+ content.includes('--WLDS-NTL-') || // Neutral colors
633
+ content.includes('--system-100') || // System colors
634
+ content.includes('--system-200');
742
635
  }
743
636
 
744
637
  /**
@@ -749,15 +642,16 @@ function hasTailwindImport(content) {
749
642
  * @returns {string} CSS content with duplicates removed
750
643
  */
751
644
  function removeDuplicateTwAnimateImports(cssContent) {
752
- const matches = cssContent.match(CSS_PATTERNS.TW_ANIMATE_IMPORT);
753
-
645
+ const twAnimatePattern = /@import\s+["']tw-animate-css["'];?\s*\n?/g;
646
+ const matches = cssContent.match(twAnimatePattern);
647
+
754
648
  if (matches && matches.length > 1) {
755
649
  // Remove all occurrences first
756
- let cleaned = cssContent.replace(CSS_PATTERNS.TW_ANIMATE_IMPORT, '');
650
+ let cleaned = cssContent.replace(twAnimatePattern, '');
757
651
  // Add it back once, right after tailwindcss import if it exists
758
- if (hasTailwindImport(cleaned)) {
652
+ if (cleaned.includes('@import "tailwindcss"') || cleaned.includes("@import 'tailwindcss'")) {
759
653
  cleaned = cleaned.replace(
760
- CSS_PATTERNS.TAILWIND_IMPORT,
654
+ /(@import\s+["']tailwindcss["'];?\s*\n?)/,
761
655
  '$1@import "tw-animate-css";\n'
762
656
  );
763
657
  } else {
@@ -766,7 +660,7 @@ function removeDuplicateTwAnimateImports(cssContent) {
766
660
  }
767
661
  return cleaned;
768
662
  }
769
-
663
+
770
664
  return cssContent;
771
665
  }
772
666
 
@@ -778,11 +672,12 @@ function removeDuplicateTwAnimateImports(cssContent) {
778
672
  * @returns {string} CSS content with duplicates removed
779
673
  */
780
674
  function removeDuplicateCustomVariant(cssContent) {
781
- const matches = cssContent.match(CSS_PATTERNS.CUSTOM_VARIANT);
675
+ const variantPattern = /@custom-variant\s+dark\s+\(&:is\(\.dark \*\)\);\s*\n?/g;
676
+ const matches = cssContent.match(variantPattern);
782
677
 
783
678
  if (matches && matches.length > 1) {
784
679
  // Remove all occurrences and add back one at the top
785
- const withoutVariants = cssContent.replace(CSS_PATTERNS.CUSTOM_VARIANT, '');
680
+ const withoutVariants = cssContent.replace(variantPattern, '');
786
681
  return `@custom-variant dark (&:is(.dark *));\n\n${withoutVariants.trimStart()}`;
787
682
  }
788
683
 
@@ -805,28 +700,30 @@ function removeDuplicateCustomVariant(cssContent) {
805
700
  */
806
701
  function normalizeCSSFormat(cssContent) {
807
702
  // Extract @custom-variant declaration
808
- const variantMatch = cssContent.match(CSS_PATTERNS.CUSTOM_VARIANT);
703
+ const variantPattern = /@custom-variant\s+dark\s+\(&:is\(\.dark \*\)\);\s*\n?/g;
704
+ const variantMatch = cssContent.match(variantPattern);
809
705
  const hasVariant = variantMatch && variantMatch.length > 0;
810
-
706
+
811
707
  // Extract all @import statements
812
- const imports = cssContent.match(CSS_PATTERNS.IMPORT_STATEMENT) || [];
813
-
708
+ const importPattern = /@import\s+["'][^"']+["'];?\s*\n?/g;
709
+ const imports = cssContent.match(importPattern) || [];
710
+
814
711
  // Separate imports by type for proper ordering
815
712
  const tailwindImport = imports.find(imp => imp.includes('tailwindcss'));
816
713
  const twAnimateImport = imports.find(imp => imp.includes('tw-animate-css'));
817
- const otherImports = imports.filter(imp =>
714
+ const otherImports = imports.filter(imp =>
818
715
  !imp.includes('tailwindcss') && !imp.includes('tw-animate-css')
819
716
  );
820
-
717
+
821
718
  // Remove all imports and variant from content to get the rest
822
719
  let content = cssContent
823
- .replace(CSS_PATTERNS.CUSTOM_VARIANT, '')
824
- .replace(CSS_PATTERNS.IMPORT_STATEMENT, '')
720
+ .replace(variantPattern, '')
721
+ .replace(importPattern, '')
825
722
  .trim();
826
-
723
+
827
724
  // Build normalized format in the correct order
828
725
  let normalized = '';
829
-
726
+
830
727
  // Step 1: Imports first (tailwindcss, then tw-animate-css, then others)
831
728
  if (tailwindImport) {
832
729
  normalized += tailwindImport.trim() + '\n';
@@ -837,19 +734,23 @@ function normalizeCSSFormat(cssContent) {
837
734
  if (otherImports.length > 0) {
838
735
  normalized += otherImports.join('') + '\n';
839
736
  }
840
-
737
+
841
738
  // Step 2: @custom-variant second (if it exists)
842
739
  if (hasVariant) {
843
- normalized += normalized ? '\n@custom-variant dark (&:is(.dark *));\n' : '@custom-variant dark (&:is(.dark *));\n';
740
+ if (normalized) {
741
+ normalized += '\n@custom-variant dark (&:is(.dark *));\n';
742
+ } else {
743
+ normalized += '@custom-variant dark (&:is(.dark *));\n';
744
+ }
844
745
  }
845
-
746
+
846
747
  // Step 3: Rest of content (theme variables, etc.)
847
748
  if (normalized && content) {
848
749
  normalized += '\n' + content;
849
750
  } else if (content) {
850
751
  normalized = content;
851
752
  }
852
-
753
+
853
754
  return normalized.trim() + (normalized ? '\n' : '');
854
755
  }
855
756
 
@@ -865,32 +766,32 @@ function normalizeCSSFormat(cssContent) {
865
766
  * @returns {string} Processed CSS content
866
767
  */
867
768
  function processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate = false) {
769
+ const twAnimatePattern = /@import\s+["']tw-animate-css["'];?\s*\n?/g;
868
770
  let processed = cssContent;
869
-
771
+
870
772
  if (!hasTwAnimate) {
871
773
  // Package not installed - remove import to prevent build errors
872
- processed = cssContent.replace(CSS_PATTERNS.TW_ANIMATE_IMPORT, '');
774
+ processed = cssContent.replace(twAnimatePattern, '');
873
775
  if (cssContent.includes('tw-animate-css') && !processed.includes('tw-animate-css')) {
874
- const installCmd = `npm install ${PACKAGE_NAMES.TW_ANIMATE_CSS}`;
875
776
  if (forceUpdate) {
876
- warn(`${PACKAGE_NAMES.TW_ANIMATE_CSS} package not found - removed from CSS to prevent build errors`);
877
- info(` Install with: ${installCmd}`);
878
- info(' Then run: node scripts/cli-remote.js css --overwrite to include it');
777
+ warn('tw-animate-css package not found - removed from CSS to prevent build errors');
778
+ info(' Install with: npm install tw-animate-css');
779
+ info(' Then run: node scripts/weloop-cli.js css --overwrite to include it');
879
780
  } else {
880
- warn(`${PACKAGE_NAMES.TW_ANIMATE_CSS} package not found - removed from CSS`);
881
- info(` Install with: ${installCmd}`);
781
+ warn('tw-animate-css package not found - removed from CSS');
782
+ info(' Install with: npm install tw-animate-css');
882
783
  info(' Or add the import manually after installing the package');
883
784
  }
884
785
  }
885
786
  } else {
886
787
  // Package is installed - ensure import exists and remove duplicates
887
788
  processed = removeDuplicateTwAnimateImports(processed);
888
-
789
+
889
790
  // Add import if it doesn't exist (right after tailwindcss if present)
890
- if (!processed.match(CSS_PATTERNS.TW_ANIMATE_IMPORT)) {
891
- if (hasTailwindImport(processed)) {
791
+ if (!processed.match(/@import\s+["']tw-animate-css["'];?\s*\n?/)) {
792
+ if (processed.includes('@import "tailwindcss"') || processed.includes("@import 'tailwindcss'")) {
892
793
  processed = processed.replace(
893
- CSS_PATTERNS.TAILWIND_IMPORT,
794
+ /(@import\s+["']tailwindcss["'];?\s*\n?)/,
894
795
  '$1@import "tw-animate-css";\n'
895
796
  );
896
797
  } else {
@@ -898,7 +799,7 @@ function processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate = false) {
898
799
  }
899
800
  }
900
801
  }
901
-
802
+
902
803
  return processed;
903
804
  }
904
805
 
@@ -910,7 +811,7 @@ function processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate = false) {
910
811
  * @returns {string} CSS content without tailwindcss imports
911
812
  */
912
813
  function removeTailwindImport(cssContent) {
913
- return cssContent.replace(CSS_PATTERNS.TAILWIND_IMPORT, '');
814
+ return cssContent.replace(/@import\s+["']tailwindcss["'];?\s*\n?/g, '');
914
815
  }
915
816
 
916
817
  /**
@@ -925,23 +826,23 @@ function ensureTwAnimateImport(cssContent, hasTwAnimate) {
925
826
  if (!hasTwAnimate) {
926
827
  return cssContent;
927
828
  }
928
-
829
+
929
830
  // Remove duplicates first
930
831
  let cleaned = removeDuplicateTwAnimateImports(cssContent);
931
-
832
+
932
833
  // Check if import already exists
933
- if (cleaned.match(CSS_PATTERNS.TW_ANIMATE_IMPORT)) {
834
+ if (cleaned.match(/@import\s+["']tw-animate-css["'];?\s*\n?/)) {
934
835
  return cleaned;
935
836
  }
936
-
837
+
937
838
  // Add import after tailwindcss if it exists, otherwise at the beginning
938
- if (hasTailwindImport(cleaned)) {
839
+ if (cleaned.includes('@import "tailwindcss"') || cleaned.includes("@import 'tailwindcss'")) {
939
840
  return cleaned.replace(
940
- CSS_PATTERNS.TAILWIND_IMPORT,
841
+ /(@import\s+["']tailwindcss["'];?\s*\n?)/,
941
842
  '$1@import "tw-animate-css";\n'
942
843
  );
943
844
  }
944
-
845
+
945
846
  return '@import "tw-animate-css";\n' + cleaned;
946
847
  }
947
848
 
@@ -959,32 +860,35 @@ function ensureTwAnimateImport(cssContent, hasTwAnimate) {
959
860
  * @returns {string} Merged CSS content
960
861
  */
961
862
  function mergeCSSWithTailwind(existing, weloopStyles, hasTwAnimate) {
962
- const tailwindMatch = existing.match(CSS_PATTERNS.TAILWIND_IMPORT);
863
+ const tailwindMatch = existing.match(/(@import\s+["']tailwindcss["'];?\s*\n?)/);
963
864
  if (!tailwindMatch) {
964
865
  // No tailwindcss import found - just prepend Weloop styles
965
866
  return weloopStyles + '\n\n' + existing;
966
867
  }
967
-
868
+
968
869
  // Split existing CSS around the tailwindcss import
969
- const tailwindIndex = existing.indexOf(tailwindMatch[0]);
970
- const beforeTailwind = existing.substring(0, tailwindIndex);
971
- const afterTailwind = existing.substring(tailwindIndex + tailwindMatch[0].length);
972
-
870
+ const beforeTailwind = existing.substring(0, existing.indexOf(tailwindMatch[0]));
871
+ const afterTailwind = existing.substring(existing.indexOf(tailwindMatch[0]) + tailwindMatch[0].length);
872
+
973
873
  // Check if tw-animate-css import already exists
974
- const hasTwAnimateInExisting = existing.match(CSS_PATTERNS.TW_ANIMATE_IMPORT);
975
- const hasTwAnimateInWeloop = weloopStyles.match(CSS_PATTERNS.TW_ANIMATE_IMPORT);
976
-
874
+ const hasTwAnimateInExisting = existing.match(/@import\s+["']tw-animate-css["'];?\s*\n?/);
875
+ const hasTwAnimateInWeloop = weloopStyles.match(/@import\s+["']tw-animate-css["'];?\s*\n?/);
876
+
977
877
  // If existing file already has tw-animate import, remove it from Weloop styles
978
878
  // to avoid duplicating it in the merge output
979
879
  let weloopStylesCleaned = weloopStyles;
980
880
  if (hasTwAnimateInExisting && hasTwAnimateInWeloop) {
981
- weloopStylesCleaned = weloopStyles.replace(CSS_PATTERNS.TW_ANIMATE_IMPORT, '');
881
+ weloopStylesCleaned = weloopStyles.replace(
882
+ /@import\s+["']tw-animate-css["'];?\s*\n?/g,
883
+ ''
884
+ );
982
885
  }
983
-
886
+
984
887
  // Add tw-animate import if needed (package installed but import missing)
985
- const importsToAdd = (hasTwAnimate && !hasTwAnimateInExisting && !hasTwAnimateInWeloop)
986
- ? '@import "tw-animate-css";\n'
987
- : '';
888
+ let importsToAdd = '';
889
+ if (hasTwAnimate && !hasTwAnimateInExisting && !hasTwAnimateInWeloop) {
890
+ importsToAdd = '@import "tw-animate-css";\n';
891
+ }
988
892
 
989
893
  // Merge: before tailwind + tailwind import + tw-animate (if needed) + Weloop styles + after tailwind
990
894
  return beforeTailwind + tailwindMatch[0] + importsToAdd + weloopStylesCleaned + '\n' + afterTailwind;
@@ -1003,26 +907,28 @@ function mergeCSSWithTailwind(existing, weloopStyles, hasTwAnimate) {
1003
907
  * @returns {string} CSS content with Weloop styles replaced
1004
908
  */
1005
909
  function replaceWeloopStyles(existing, newStyles, hasTwAnimate) {
1006
- const imports = existing.match(CSS_PATTERNS.IMPORT_STATEMENT) || [];
910
+ const importPattern = /(@import\s+["'][^"']+["'];?\s*\n?)/g;
911
+ const imports = existing.match(importPattern) || [];
1007
912
  const importsText = imports.join('');
1008
-
913
+
1009
914
  // Find where Weloop styles start (look for @theme inline or :root)
1010
- const weloopStartMatch = existing.search(CSS_PATTERNS.WELOOP_START);
1011
-
915
+ const weloopStartPattern = /(@theme\s+inline|:root\s*\{)/;
916
+ const weloopStartMatch = existing.search(weloopStartPattern);
917
+
1012
918
  if (weloopStartMatch === -1) {
1013
919
  // No existing Weloop styles found - just append
1014
920
  return existing + '\n\n' + newStyles;
1015
921
  }
1016
-
922
+
1017
923
  // Extract content before Weloop styles
1018
924
  let contentBeforeWeloop = existing.substring(0, weloopStartMatch);
1019
925
  // Keep non-Weloop imports (filter out tw-animate if package not installed)
1020
- const nonWeloopImports = importsText.split('\n').filter(imp =>
926
+ const nonWeloopImports = importsText.split('\n').filter(imp =>
1021
927
  !imp.includes('tw-animate-css') || hasTwAnimate
1022
928
  ).join('\n');
1023
929
  // Remove all imports from contentBeforeWeloop (we'll add them back)
1024
- contentBeforeWeloop = nonWeloopImports + '\n' + contentBeforeWeloop.replace(CSS_PATTERNS.IMPORT_STATEMENT, '');
1025
-
930
+ contentBeforeWeloop = nonWeloopImports + '\n' + contentBeforeWeloop.replace(importPattern, '');
931
+
1026
932
  // Return: preserved imports + content before Weloop + new Weloop styles
1027
933
  return contentBeforeWeloop.trim() + '\n\n' + newStyles.trim();
1028
934
  }
@@ -1038,11 +944,11 @@ function replaceWeloopStyles(existing, newStyles, hasTwAnimate) {
1038
944
  */
1039
945
  async function fetchCSSFromRegistry(registryUrl) {
1040
946
  let sourceCssPath;
1041
-
947
+
1042
948
  if (isLocalPath(registryUrl)) {
1043
949
  // Local path: find app/globals.css relative to registry directory
1044
- const basePath = path.isAbsolute(registryUrl)
1045
- ? path.dirname(registryUrl)
950
+ const basePath = path.isAbsolute(registryUrl)
951
+ ? path.dirname(registryUrl)
1046
952
  : path.join(process.cwd(), path.dirname(registryUrl));
1047
953
  sourceCssPath = path.join(basePath, 'app', 'globals.css');
1048
954
  } else {
@@ -1050,7 +956,7 @@ async function fetchCSSFromRegistry(registryUrl) {
1050
956
  const baseUrl = registryUrl.replace('/registry', '');
1051
957
  sourceCssPath = `${baseUrl}/app/globals.css`;
1052
958
  }
1053
-
959
+
1054
960
  return await fetchText(sourceCssPath);
1055
961
  }
1056
962
 
@@ -1068,49 +974,52 @@ async function fetchCSSFromRegistry(registryUrl) {
1068
974
  * @param {boolean} silent - Whether to suppress output messages
1069
975
  */
1070
976
  async function installCSSStyles(config, registryUrl, forceUpdate = false, silent = false) {
1071
- const cssPath = config.tailwind?.css || DEFAULTS.CSS_PATH;
977
+ const cssPath = config.tailwind?.css || 'app/globals.css';
1072
978
  const fullCssPath = path.join(process.cwd(), cssPath);
1073
-
979
+
1074
980
  // Check if Weloop styles already exist in the file
1075
981
  let hasWeloopStylesInFile = false;
1076
982
  if (fs.existsSync(fullCssPath)) {
1077
983
  const existingContent = fs.readFileSync(fullCssPath, 'utf-8');
1078
984
  hasWeloopStylesInFile = hasWeloopStyles(existingContent);
1079
-
985
+
1080
986
  // If styles already exist and we're not forcing update, skip silently
1081
987
  // This prevents unnecessary updates when installing components
1082
988
  if (hasWeloopStylesInFile && !forceUpdate && silent) {
1083
989
  return;
1084
990
  }
1085
991
  }
1086
-
992
+
1087
993
  try {
1088
994
  if (!silent) {
1089
995
  info('Installing CSS styles...');
1090
996
  }
1091
-
997
+
1092
998
  const cssContent = await fetchCSSFromRegistry(registryUrl);
1093
999
  ensureDirectoryExists(path.dirname(fullCssPath));
1094
-
1095
- const hasTwAnimate = checkPackageInstalled(PACKAGE_NAMES.TW_ANIMATE_CSS);
1000
+
1001
+ const hasTwAnimate = checkPackageInstalled('tw-animate-css');
1096
1002
  let processedCssContent = processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate);
1097
-
1003
+
1098
1004
  // Handle file installation based on mode and existing file state
1099
1005
  if (forceUpdate && fs.existsSync(fullCssPath)) {
1100
1006
  // Mode 1: --overwrite flag - Replace entire file with fresh styles
1101
- const normalized = normalizeAndCleanCSS(processedCssContent, hasTwAnimate);
1007
+ let normalized = normalizeCSSFormat(processedCssContent);
1008
+ normalized = removeDuplicateTwAnimateImports(normalized);
1009
+ normalized = removeDuplicateCustomVariant(normalized);
1102
1010
  fs.writeFileSync(fullCssPath, normalized);
1103
1011
  if (!silent) {
1104
1012
  success(`Overwritten ${cssPath} with Weloop styles`);
1105
1013
  if (hasTwAnimate) {
1106
- info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
1014
+ info(` tw-animate-css import included`);
1107
1015
  }
1108
1016
  }
1109
1017
  } else if (fs.existsSync(fullCssPath)) {
1110
1018
  // Mode 2: Normal update - Intelligently merge with existing styles
1111
1019
  const existing = fs.readFileSync(fullCssPath, 'utf-8');
1112
- const existingHasTailwind = hasTailwindImport(existing);
1113
-
1020
+ const hasTailwindImport = existing.includes('@import "tailwindcss"') ||
1021
+ existing.includes('@tailwind base');
1022
+
1114
1023
  if (hasWeloopStylesInFile) {
1115
1024
  // Case 2a: Weloop styles already exist - replace them with updated versions
1116
1025
  let weloopStyles = removeTailwindImport(processedCssContent);
@@ -1119,7 +1028,7 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
1119
1028
  finalContent = removeDuplicateTwAnimateImports(finalContent);
1120
1029
  finalContent = removeDuplicateCustomVariant(finalContent);
1121
1030
  finalContent = normalizeCSSFormat(finalContent);
1122
-
1031
+
1123
1032
  // Only write if content actually changed (prevents unnecessary file updates)
1124
1033
  if (finalContent !== existing) {
1125
1034
  fs.writeFileSync(fullCssPath, finalContent);
@@ -1128,29 +1037,31 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
1128
1037
  }
1129
1038
  }
1130
1039
  // If no changes, silently skip (file is already up to date)
1131
- } else if (existingHasTailwind) {
1040
+ } else if (hasTailwindImport) {
1132
1041
  // Case 2b: No Weloop styles but Tailwind exists - merge after Tailwind imports
1133
1042
  let weloopStyles = removeTailwindImport(processedCssContent);
1134
1043
  weloopStyles = ensureTwAnimateImport(weloopStyles, hasTwAnimate);
1135
1044
  let merged = mergeCSSWithTailwind(existing, weloopStyles, hasTwAnimate);
1136
- merged = normalizeAndCleanCSS(merged, hasTwAnimate);
1045
+ merged = removeDuplicateTwAnimateImports(merged);
1046
+ merged = removeDuplicateCustomVariant(merged);
1047
+ merged = normalizeCSSFormat(merged);
1137
1048
  fs.writeFileSync(fullCssPath, merged);
1138
1049
  if (!silent) {
1139
1050
  success(`Updated ${cssPath} with Weloop styles`);
1140
1051
  if (hasTwAnimate) {
1141
- info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
1052
+ info(` tw-animate-css import included`);
1142
1053
  }
1143
1054
  info(` Your existing styles are preserved`);
1144
1055
  }
1145
1056
  } else {
1146
1057
  // Case 2c: No Tailwind imports - prepend Weloop styles to existing content
1147
1058
  let finalCssContent = removeDuplicateTwAnimateImports(processedCssContent);
1148
-
1059
+
1149
1060
  // Ensure tw-animate import exists if package is installed
1150
- if (hasTwAnimate && !finalCssContent.match(CSS_PATTERNS.TW_ANIMATE_IMPORT)) {
1151
- if (hasTailwindImport(finalCssContent)) {
1061
+ if (hasTwAnimate && !finalCssContent.match(/@import\s+["']tw-animate-css["'];?\s*\n?/)) {
1062
+ if (finalCssContent.includes('@import "tailwindcss"')) {
1152
1063
  finalCssContent = finalCssContent.replace(
1153
- CSS_PATTERNS.TAILWIND_IMPORT,
1064
+ /(@import\s+["']tailwindcss["'];?\s*\n?)/,
1154
1065
  '$1@import "tw-animate-css";\n'
1155
1066
  );
1156
1067
  } else {
@@ -1158,34 +1069,35 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
1158
1069
  }
1159
1070
  }
1160
1071
  let combined = finalCssContent + '\n\n' + existing;
1161
- combined = normalizeAndCleanCSS(combined, hasTwAnimate);
1072
+ combined = normalizeCSSFormat(combined);
1162
1073
  fs.writeFileSync(fullCssPath, combined);
1163
1074
  if (!silent) {
1164
1075
  success(`Updated ${cssPath} with Weloop styles`);
1165
1076
  if (hasTwAnimate) {
1166
- info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
1077
+ info(` tw-animate-css import included`);
1167
1078
  }
1168
1079
  }
1169
1080
  }
1170
1081
  } else {
1171
1082
  // Mode 3: File doesn't exist - create new file with Weloop styles
1172
- const normalized = normalizeAndCleanCSS(processedCssContent, hasTwAnimate);
1083
+ let normalized = normalizeCSSFormat(processedCssContent);
1084
+ normalized = removeDuplicateTwAnimateImports(normalized);
1085
+ normalized = removeDuplicateCustomVariant(normalized);
1173
1086
  fs.writeFileSync(fullCssPath, normalized);
1174
1087
  if (!silent) {
1175
1088
  success(`Created ${cssPath} with Weloop styles`);
1176
1089
  if (hasTwAnimate) {
1177
- info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
1090
+ info(` tw-animate-css import included`);
1178
1091
  }
1179
1092
  }
1180
1093
  }
1181
1094
  } catch (err) {
1182
1095
  if (!silent) {
1183
1096
  warn(`Could not automatically install CSS styles: ${err.message}`);
1184
- info(getCSSInstallErrorMessage(registryUrl, cssPath));
1185
- }
1186
- // Re-throw in silent mode so caller can handle it
1187
- if (silent) {
1188
- throw err;
1097
+ info(`\n To add styles manually:`);
1098
+ info(` 1. Download: ${registryUrl.replace('/registry', '/app/globals.css')}`);
1099
+ info(` 2. Add the CSS variables to your ${cssPath} file`);
1100
+ info(` 3. Or copy from: https://gitlab.com/Sophanithchrek/weloop-shadcn-next-app/-/raw/main/app/globals.css\n`);
1189
1101
  }
1190
1102
  }
1191
1103
  }
@@ -1204,11 +1116,11 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
1204
1116
  */
1205
1117
  function createUtilsFile(utilsPath) {
1206
1118
  if (fs.existsSync(utilsPath)) return;
1207
-
1119
+
1208
1120
  // Silently create utils file (matching shadcn/ui behavior)
1209
1121
  // Don't overwrite if user has customized it
1210
1122
  ensureDirectoryExists(path.dirname(utilsPath));
1211
-
1123
+
1212
1124
  const utilsContent = `import { clsx, type ClassValue } from "clsx"
1213
1125
  import { twMerge } from "tailwind-merge"
1214
1126
 
@@ -1238,41 +1150,47 @@ export function cn(...inputs: ClassValue[]) {
1238
1150
  */
1239
1151
  async function installComponent(componentName, options = {}) {
1240
1152
  const { overwrite = false, registryUrl = DEFAULT_REGISTRY_URL } = options;
1241
-
1153
+
1242
1154
  // Step 1: Install base dependencies (required for utils.ts to work)
1243
- const baseDeps = BASE_DEPENDENCIES.filter(dep => !checkPackageInstalled(dep));
1155
+ const baseDeps = [];
1156
+ if (!checkPackageInstalled('clsx')) {
1157
+ baseDeps.push('clsx');
1158
+ }
1159
+ if (!checkPackageInstalled('tailwind-merge')) {
1160
+ baseDeps.push('tailwind-merge');
1161
+ }
1244
1162
  if (baseDeps.length > 0) {
1245
1163
  info(`Installing base dependencies: ${baseDeps.join(', ')}...`);
1246
1164
  await installPackages(baseDeps);
1247
1165
  }
1248
-
1166
+
1249
1167
  // Step 2: Load or create components.json configuration
1250
1168
  const config = loadComponentsConfig();
1251
-
1169
+
1252
1170
  // Step 3: Install tw-animate-css automatically (required for component animations)
1253
- if (!checkPackageInstalled(PACKAGE_NAMES.TW_ANIMATE_CSS)) {
1254
- info(`Installing ${PACKAGE_NAMES.TW_ANIMATE_CSS} for animations...`);
1255
- await installPackages([PACKAGE_NAMES.TW_ANIMATE_CSS]);
1171
+ if (!checkPackageInstalled('tw-animate-css')) {
1172
+ info('Installing tw-animate-css for animations...');
1173
+ await installPackages(['tw-animate-css']);
1256
1174
  }
1257
-
1175
+
1258
1176
  // Step 4: Install CSS styles early (before component installation)
1259
1177
  // Silent mode prevents output when styles are already installed
1260
1178
  await installCSSStyles(config, registryUrl, false, true);
1261
-
1179
+
1262
1180
  // Step 5: Resolve paths from components.json configuration
1263
1181
  const uiAlias = config.aliases?.ui || '@/components/ui';
1264
1182
  const utilsAlias = config.aliases?.utils || '@/lib/utils';
1265
-
1183
+
1266
1184
  const componentsDir = path.join(process.cwd(), uiAlias.replace('@/', '').replace(/^\/+/, ''));
1267
1185
  let utilsPath = utilsAlias.replace('@/', '').replace(/^\/+/, '');
1268
1186
  if (!utilsPath.endsWith('.ts') && !utilsPath.endsWith('.tsx')) {
1269
1187
  utilsPath = utilsPath + '.ts';
1270
1188
  }
1271
1189
  utilsPath = path.join(process.cwd(), utilsPath);
1272
-
1190
+
1273
1191
  // Step 6: Load component registry from remote or local source
1274
1192
  const registry = await loadRegistry(componentName, registryUrl);
1275
-
1193
+
1276
1194
  if (!registry) {
1277
1195
  error(`Component "${componentName}" not found in registry.`);
1278
1196
  info('Available components:');
@@ -1331,552 +1249,6 @@ async function installComponent(componentName, options = {}) {
1331
1249
  success(`\nSuccessfully installed "${componentName}"`);
1332
1250
  }
1333
1251
 
1334
- // ============================================================================
1335
- // INIT COMMAND - Project Initialization
1336
- // ============================================================================
1337
-
1338
- /**
1339
- * Detects the project type (Next.js or Vite)
1340
- * @returns {string} 'nextjs' or 'vite' or null
1341
- */
1342
- function detectProjectType() {
1343
- // Check for Next.js
1344
- if (fs.existsSync(path.join(process.cwd(), 'next.config.js')) ||
1345
- fs.existsSync(path.join(process.cwd(), 'next.config.mjs')) ||
1346
- fs.existsSync(path.join(process.cwd(), 'next.config.ts')) ||
1347
- fs.existsSync(path.join(process.cwd(), 'app')) ||
1348
- fs.existsSync(path.join(process.cwd(), 'pages'))) {
1349
- return 'nextjs';
1350
- }
1351
-
1352
- // Check for Vite
1353
- if (fs.existsSync(path.join(process.cwd(), 'vite.config.js')) ||
1354
- fs.existsSync(path.join(process.cwd(), 'vite.config.ts')) ||
1355
- fs.existsSync(path.join(process.cwd(), 'vite.config.mjs'))) {
1356
- return 'vite';
1357
- }
1358
-
1359
- return null;
1360
- }
1361
-
1362
- /**
1363
- * Prompts user for configuration using readline
1364
- * @param {string} question - Question to ask
1365
- * @param {string} defaultValue - Default value
1366
- * @returns {Promise<string>} User input
1367
- */
1368
- function prompt(question, defaultValue = '') {
1369
- return new Promise((resolve) => {
1370
- const readline = require('readline');
1371
- const rl = readline.createInterface({
1372
- input: process.stdin,
1373
- output: process.stdout
1374
- });
1375
-
1376
- const promptText = defaultValue
1377
- ? `${question} › ${defaultValue} `
1378
- : `${question} › `;
1379
-
1380
- rl.question(promptText, (answer) => {
1381
- rl.close();
1382
- resolve(answer.trim() || defaultValue);
1383
- });
1384
- });
1385
- }
1386
-
1387
- /**
1388
- * Initializes the project with interactive prompts
1389
- * Similar to shadcn/ui init command but with custom theme selection
1390
- */
1391
- async function initProject() {
1392
- info('Initializing project...\n');
1393
-
1394
- // Check if components.json already exists
1395
- const configPath = path.join(process.cwd(), PATHS.COMPONENTS_JSON);
1396
- if (fs.existsSync(configPath)) {
1397
- warn(`${PATHS.COMPONENTS_JSON} already exists.`);
1398
- const overwrite = await prompt('Do you want to overwrite it? (y/N)', 'N');
1399
- if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
1400
- info('Skipping initialization.');
1401
- return;
1402
- }
1403
- }
1404
-
1405
- // Step 1: Ask for project type
1406
- console.log('Project ? Next.js | Vite');
1407
- const projectTypeAnswer = await prompt('Project ?', 'Next.js');
1408
- const isNextjs = projectTypeAnswer.toLowerCase().includes('next');
1409
- const isVite = projectTypeAnswer.toLowerCase().includes('vite');
1410
-
1411
- // Step 2: Ask for project name
1412
- console.log('\nProject Name ? name-app | (default: my-app)');
1413
- const projectName = await prompt('Project Name ?', 'my-app');
1414
-
1415
- // Step 3: Ask for theme color with options
1416
- console.log('\nTheme Color ? (Blue: Default, Tech-Blue and Brew) for user to choose');
1417
- console.log('Available themes:');
1418
- console.log(' 1. Blue (Default)');
1419
- console.log(' 2. Tech-Blue');
1420
- console.log(' 3. Brew');
1421
- const themeAnswer = await prompt('Theme Color ?', '1');
1422
-
1423
- let selectedTheme;
1424
- switch (themeAnswer) {
1425
- case '2':
1426
- case 'tech-blue':
1427
- case 'Tech-Blue':
1428
- selectedTheme = 'tech-blue';
1429
- break;
1430
- case '3':
1431
- case 'brew':
1432
- case 'Brew':
1433
- selectedTheme = 'brew';
1434
- break;
1435
- case '1':
1436
- case 'blue':
1437
- case 'Blue':
1438
- default:
1439
- selectedTheme = 'blue';
1440
- break;
1441
- }
1442
-
1443
- // Step 4: Ask for border radius
1444
- console.log('\nBorder Radius ? Choose the border radius for components');
1445
- console.log('Available options:');
1446
- console.log(' 1. 0px (Sharp)');
1447
- console.log(' 2. 4px (Small)');
1448
- console.log(' 3. 8px (Default)');
1449
- console.log(' 4. 10px (Medium)');
1450
- console.log(' 5. 16px (Large)');
1451
- const radiusAnswer = await prompt('Border Radius ?', '3');
1452
-
1453
- let selectedRadius;
1454
- switch (radiusAnswer) {
1455
- case '1':
1456
- case '0':
1457
- case '0px':
1458
- selectedRadius = '0px';
1459
- break;
1460
- case '2':
1461
- case '4':
1462
- case '4px':
1463
- selectedRadius = '4px';
1464
- break;
1465
- case '4':
1466
- case '10':
1467
- case '10px':
1468
- selectedRadius = '10px';
1469
- break;
1470
- case '5':
1471
- case '16':
1472
- case '16px':
1473
- selectedRadius = '16px';
1474
- break;
1475
- case '3':
1476
- case '8':
1477
- case '8px':
1478
- default:
1479
- selectedRadius = '8px';
1480
- break;
1481
- }
1482
-
1483
- // Show configuration summary
1484
- info('\nšŸ“‹ Configuration Summary:');
1485
- info(` Project Type: ${isNextjs ? 'Next.js' : 'Vite'}`);
1486
- info(` Project Name: ${projectName}`);
1487
- info(` Theme Color: ${selectedTheme}`);
1488
- info(` Border Radius: ${selectedRadius}`);
1489
-
1490
- const confirm = await prompt('\nProceed with this configuration? (Y/n)', 'Y');
1491
- if (confirm.toLowerCase() === 'n' || confirm.toLowerCase() === 'no') {
1492
- info('Initialization cancelled.');
1493
- return;
1494
- }
1495
-
1496
- // Step 3: Setup project structure based on shadcn/ui + Next.js
1497
- info('\nšŸ—ļø Setting up project structure...');
1498
-
1499
- // Create basic Next.js project structure
1500
- const directories = [
1501
- 'app',
1502
- 'components',
1503
- 'components/ui',
1504
- 'lib',
1505
- 'hooks',
1506
- 'types',
1507
- 'public',
1508
- 'public/icons'
1509
- ];
1510
-
1511
- directories.forEach(dir => {
1512
- const dirPath = path.join(process.cwd(), dir);
1513
- if (!fs.existsSync(dirPath)) {
1514
- fs.mkdirSync(dirPath, { recursive: true });
1515
- info(` → Created ${dir}/ directory`);
1516
- }
1517
- });
1518
-
1519
- // Create Next.js app structure files
1520
- const appFiles = {
1521
- 'app/layout.tsx': `import type { Metadata } from "next";
1522
- import { Inter } from "next/font/google";
1523
- import "./globals.css";
1524
-
1525
- const inter = Inter({ subsets: ["latin"] });
1526
-
1527
- export const metadata: Metadata = {
1528
- title: "${projectName}",
1529
- description: "Built with Weloop Design System",
1530
- };
1531
-
1532
- export default function RootLayout({
1533
- children,
1534
- }: {
1535
- children: React.ReactNode;
1536
- }) {
1537
- return (
1538
- <html lang="en">
1539
- <body className={inter.className}>{children}</body>
1540
- </html>
1541
- );
1542
- }`,
1543
- 'app/page.tsx': `export default function Home() {
1544
- return (
1545
- <main className="flex min-h-screen flex-col items-center justify-between p-24">
1546
- <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
1547
- <h1 className="text-4xl font-bold">
1548
- Welcome to ${projectName}
1549
- </h1>
1550
- </div>
1551
- </main>
1552
- );
1553
- }`,
1554
- 'app/globals.css': `@tailwind base;
1555
- @tailwind components;
1556
- @tailwind utilities;
1557
-
1558
- @layer base {
1559
- :root {
1560
- --background: 0 0% 100%;
1561
- --foreground: 222.2 84% 4.9%;
1562
- --card: 0 0% 100%;
1563
- --card-foreground: 222.2 84% 4.9%;
1564
- --popover: 0 0% 100%;
1565
- --popover-foreground: 222.2 84% 4.9%;
1566
- --primary: 222.2 47.4% 11.2%;
1567
- --primary-foreground: 210 40% 98%;
1568
- --secondary: 210 40% 96%;
1569
- --secondary-foreground: 222.2 84% 4.9%;
1570
- --muted: 210 40% 96%;
1571
- --muted-foreground: 215.4 16.3% 46.9%;
1572
- --accent: 210 40% 96%;
1573
- --accent-foreground: 222.2 84% 4.9%;
1574
- --destructive: 0 84.2% 60.2%;
1575
- --destructive-foreground: 210 40% 98%;
1576
- --border: 214.3 31.8% 91.4%;
1577
- --input: 214.3 31.8% 91.4%;
1578
- --ring: 222.2 84% 4.9%;
1579
- --radius: ${selectedRadius.replace('px', '')};
1580
- --chart-1: 12 76% 61%;
1581
- --chart-2: 173 58% 39%;
1582
- --chart-3: 197 37% 24%;
1583
- --chart-4: 43 74% 66%;
1584
- --chart-5: 27 87% 67%;
1585
- }
1586
-
1587
- .dark {
1588
- --background: 222.2 84% 4.9%;
1589
- --foreground: 210 40% 98%;
1590
- --card: 222.2 84% 4.9%;
1591
- --card-foreground: 210 40% 98%;
1592
- --popover: 222.2 84% 4.9%;
1593
- --popover-foreground: 210 40% 98%;
1594
- --primary: 210 40% 98%;
1595
- --primary-foreground: 222.2 47.4% 11.2%;
1596
- --secondary: 217.2 32.6% 17.5%;
1597
- --secondary-foreground: 210 40% 98%;
1598
- --muted: 217.2 32.6% 17.5%;
1599
- --muted-foreground: 215 20.2% 65.1%;
1600
- --accent: 217.2 32.6% 17.5%;
1601
- --accent-foreground: 210 40% 98%;
1602
- --destructive: 0 62.8% 30.6%;
1603
- --destructive-foreground: 210 40% 98%;
1604
- --border: 217.2 32.6% 17.5%;
1605
- --input: 217.2 32.6% 17.5%;
1606
- --ring: 212.7 26.8% 83.9%;
1607
- --chart-1: 220 70% 50%;
1608
- --chart-2: 160 60% 45%;
1609
- --chart-3: 30 80% 55%;
1610
- --chart-4: 280 65% 60%;
1611
- --chart-5: 340 75% 55%;
1612
- }
1613
- }
1614
-
1615
- @layer base {
1616
- * {
1617
- @apply border-border;
1618
- }
1619
- body {
1620
- @apply bg-background text-foreground;
1621
- }
1622
- }`,
1623
- 'next.config.mjs': `/** @type {import('next').NextConfig} */
1624
- const nextConfig = {
1625
- experimental: {
1626
- appDir: true,
1627
- },
1628
- };
1629
-
1630
- export default nextConfig;`,
1631
- 'tailwind.config.ts': `import type { Config } from "tailwindcss";
1632
-
1633
- const config: Config = {
1634
- darkMode: ["class"],
1635
- content: [
1636
- "./pages/**/*.{ts,tsx}",
1637
- "./components/**/*.{ts,tsx}",
1638
- "./app/**/*.{ts,tsx}",
1639
- "./src/**/*.{ts,tsx}",
1640
- ],
1641
- prefix: "",
1642
- theme: {
1643
- container: {
1644
- center: true,
1645
- padding: "2rem",
1646
- screens: {
1647
- "2xl": "1400px",
1648
- },
1649
- },
1650
- extend: {
1651
- colors: {
1652
- border: "hsl(var(--border))",
1653
- input: "hsl(var(--input))",
1654
- ring: "hsl(var(--ring))",
1655
- background: "hsl(var(--background))",
1656
- foreground: "hsl(var(--foreground))",
1657
- primary: {
1658
- DEFAULT: "hsl(var(--primary))",
1659
- foreground: "hsl(var(--primary-foreground))",
1660
- },
1661
- secondary: {
1662
- DEFAULT: "hsl(var(--secondary))",
1663
- foreground: "hsl(var(--secondary-foreground))",
1664
- },
1665
- destructive: {
1666
- DEFAULT: "hsl(var(--destructive))",
1667
- foreground: "hsl(var(--destructive-foreground))",
1668
- },
1669
- muted: {
1670
- DEFAULT: "hsl(var(--muted))",
1671
- foreground: "hsl(var(--muted-foreground))",
1672
- },
1673
- accent: {
1674
- DEFAULT: "hsl(var(--accent))",
1675
- foreground: "hsl(var(--accent-foreground))",
1676
- },
1677
- popover: {
1678
- DEFAULT: "hsl(var(--popover))",
1679
- foreground: "hsl(var(--popover-foreground))",
1680
- },
1681
- card: {
1682
- DEFAULT: "hsl(var(--card))",
1683
- foreground: "hsl(var(--card-foreground))",
1684
- },
1685
- },
1686
- borderRadius: {
1687
- lg: "var(--radius)",
1688
- md: "calc(var(--radius) - 2px)",
1689
- sm: "calc(var(--radius) - 4px)",
1690
- },
1691
- keyframes: {
1692
- "accordion-down": {
1693
- from: { height: "0" },
1694
- to: { height: "var(--radix-accordion-content-height)" },
1695
- },
1696
- "accordion-up": {
1697
- from: { height: "var(--radix-accordion-content-height)" },
1698
- to: { height: "0" },
1699
- },
1700
- },
1701
- animation: {
1702
- "accordion-down": "accordion-down 0.2s ease-out",
1703
- "accordion-up": "accordion-up 0.2s ease-out",
1704
- },
1705
- },
1706
- },
1707
- plugins: [require("tailwindcss-animate")],
1708
- } satisfies Config;
1709
-
1710
- export default config;`,
1711
- 'tsconfig.json': `{
1712
- "compilerOptions": {
1713
- "target": "es5",
1714
- "lib": ["dom", "dom.iterable", "es6"],
1715
- "allowJs": true,
1716
- "skipLibCheck": true,
1717
- "strict": true,
1718
- "noEmit": true,
1719
- "esModuleInterop": true,
1720
- "module": "esnext",
1721
- "moduleResolution": "bundler",
1722
- "resolveJsonModule": true,
1723
- "isolatedModules": true,
1724
- "jsx": "preserve",
1725
- "incremental": true,
1726
- "plugins": [
1727
- {
1728
- "name": "next"
1729
- }
1730
- ],
1731
- "baseUrl": ".",
1732
- "paths": {
1733
- "@/*": ["./*"]
1734
- }
1735
- },
1736
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1737
- "exclude": ["node_modules"]
1738
- }`,
1739
- '.eslintrc.json': `{
1740
- "extends": ["next/core-web-vitals", "next/typescript"]
1741
- }`
1742
- };
1743
-
1744
- // Create files
1745
- Object.entries(appFiles).forEach(([filePath, content]) => {
1746
- const fullPath = path.join(process.cwd(), filePath);
1747
- if (!fs.existsSync(fullPath)) {
1748
- fs.writeFileSync(fullPath, content);
1749
- info(` → Created ${filePath}`);
1750
- }
1751
- });
1752
-
1753
- // Create or update package.json for Next.js project
1754
- const packageJsonPath = path.join(process.cwd(), 'package.json');
1755
- let packageJson = { dependencies: {}, devDependencies: {} };
1756
-
1757
- if (fs.existsSync(packageJsonPath)) {
1758
- packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
1759
- }
1760
-
1761
- // Update package.json with Next.js dependencies
1762
- const nextDependencies = {
1763
- "next": "^14.0.0",
1764
- "react": "^18.0.0",
1765
- "react-dom": "^18.0.0",
1766
- "typescript": "^5.0.0",
1767
- "@types/node": "^20.0.0",
1768
- "@types/react": "^18.0.0",
1769
- "@types/react-dom": "^18.0.0",
1770
- "tailwindcss": "^3.3.0",
1771
- "autoprefixer": "^10.0.0",
1772
- "postcss": "^8.0.0",
1773
- "tailwindcss-animate": "^1.0.7",
1774
- "eslint": "^8.0.0",
1775
- "eslint-config-next": "^14.0.0"
1776
- };
1777
-
1778
- packageJson.name = projectName;
1779
- packageJson.version = "0.1.0";
1780
- packageJson.private = true;
1781
- packageJson.scripts = {
1782
- "dev": "next dev",
1783
- "build": "next build",
1784
- "start": "next start",
1785
- "lint": "next lint"
1786
- };
1787
-
1788
- // Merge dependencies
1789
- Object.assign(packageJson.dependencies || {}, nextDependencies);
1790
- Object.assign(packageJson.devDependencies || {}, {});
1791
-
1792
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
1793
- info(` → Updated package.json`);
1794
-
1795
- // Detect CSS path based on project type
1796
- let cssPath = PATHS.APP_GLOBALS_CSS;
1797
- if (isVite) {
1798
- cssPath = PATHS.VITE_INDEX_CSS;
1799
- } else if (fs.existsSync(path.join(process.cwd(), 'app', 'globals.css'))) {
1800
- cssPath = PATHS.APP_GLOBALS_CSS;
1801
- } else if (fs.existsSync(path.join(process.cwd(), 'styles', 'globals.css'))) {
1802
- cssPath = PATHS.STYLES_GLOBALS_CSS;
1803
- } else {
1804
- cssPath = await prompt('Where is your global CSS file?', cssPath);
1805
- }
1806
-
1807
- // Create components.json with selected theme
1808
- const config = {
1809
- $schema: DEFAULTS.SCHEMA_URL,
1810
- style: selectedTheme,
1811
- rsc: isNextjs,
1812
- tsx: true,
1813
- tailwind: {
1814
- config: 'tailwind.config.ts',
1815
- css: cssPath,
1816
- baseColor: 'neutral',
1817
- cssVariables: true,
1818
- prefix: ''
1819
- },
1820
- iconLibrary: DEFAULTS.ICON_LIBRARY,
1821
- aliases: {
1822
- components: '@/components',
1823
- utils: '@/lib/utils',
1824
- ui: '@/components/ui',
1825
- lib: '@/lib',
1826
- hooks: '@/hooks'
1827
- },
1828
- registry: REGISTRY_URLS.INDEX,
1829
- projectName: projectName,
1830
- radius: selectedRadius
1831
- };
1832
-
1833
- // Show setup progress
1834
- info('\nšŸš€ Setting up your project...');
1835
-
1836
- // Step 1: Create components.json
1837
- info(' → [:root]/components.json');
1838
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
1839
-
1840
- // Step 2: Install dependencies
1841
- info(' → Installing dependencies');
1842
- const missingBaseDeps = BASE_DEPENDENCIES.filter(dep => !checkPackageInstalled(dep));
1843
-
1844
- if (missingBaseDeps.length > 0) {
1845
- await installPackages(missingBaseDeps);
1846
- }
1847
-
1848
- // Install Next.js dependencies if not present
1849
- const nextDeps = [
1850
- "next", "react", "react-dom", "typescript",
1851
- "@types/node", "@types/react", "@types/react-dom",
1852
- "tailwindcss", "autoprefixer", "postcss",
1853
- "tailwindcss-animate", "eslint", "eslint-config-next"
1854
- ];
1855
-
1856
- const missingNextDeps = nextDeps.filter(dep => !checkPackageInstalled(dep));
1857
- if (missingNextDeps.length > 0) {
1858
- await installPackages(missingNextDeps);
1859
- }
1860
-
1861
- // Install tw-animate-css for animations
1862
- if (!checkPackageInstalled(PACKAGE_NAMES.TW_ANIMATE_CSS)) {
1863
- await installPackages([PACKAGE_NAMES.TW_ANIMATE_CSS]);
1864
- }
1865
-
1866
- // Step 3: Create utils.ts file
1867
- info(' → Create 1 files:');
1868
- const utilsPath = path.join(process.cwd(), 'lib', 'utils.ts');
1869
- createUtilsFile(utilsPath);
1870
- info(' → libs/utils.ts');
1871
-
1872
- success('\nāœ… Success! Successfully initialization.');
1873
- info('\nYou could add the components now.');
1874
- info('\nAvailable commands:');
1875
- info(' npx weloop-kosign@latest add <component-name>');
1876
- info(' pnpm dlx weloop-kosign@latest add <component-name>');
1877
- info(' yarn weloop-kosign@latest add <component-name>\n');
1878
- }
1879
-
1880
1252
  // ============================================================================
1881
1253
  // COMMANDS - CLI Command Handlers
1882
1254
  // ============================================================================
@@ -1890,7 +1262,7 @@ export default config;`,
1890
1262
  async function listComponents(registryUrl = DEFAULT_REGISTRY_URL) {
1891
1263
  try {
1892
1264
  const index = await loadIndex(registryUrl);
1893
-
1265
+
1894
1266
  console.log('\nAvailable components:\n');
1895
1267
  index.registry.forEach(comp => {
1896
1268
  // Show dependencies if component has any
@@ -1926,13 +1298,13 @@ async function main() {
1926
1298
  const args = process.argv.slice(2);
1927
1299
  const command = args[0];
1928
1300
  const componentName = args[1];
1929
-
1301
+
1930
1302
  // Parse --registry option if provided
1931
1303
  const registryIndex = args.indexOf('--registry');
1932
1304
  const registryUrl = registryIndex !== -1 && args[registryIndex + 1]
1933
1305
  ? args[registryIndex + 1]
1934
1306
  : DEFAULT_REGISTRY_URL;
1935
-
1307
+
1936
1308
  // Build options object
1937
1309
  const options = {
1938
1310
  overwrite: args.includes('--overwrite') || args.includes('-f'),
@@ -1943,7 +1315,6 @@ async function main() {
1943
1315
  if (!command) {
1944
1316
  console.log(`
1945
1317
  Usage:
1946
- node scripts/cli-remote.js init [--registry <url>] Initialize project
1947
1318
  node scripts/cli-remote.js add <component-name> [--registry <url>] Install a component
1948
1319
  node scripts/cli-remote.js list [--registry <url>] List all available components
1949
1320
  node scripts/cli-remote.js css [--registry <url>] [--overwrite] Install/update CSS styles
@@ -1953,7 +1324,6 @@ Options:
1953
1324
  --overwrite, -f Overwrite existing files
1954
1325
 
1955
1326
  Examples:
1956
- node scripts/cli-remote.js init
1957
1327
  node scripts/cli-remote.js add button
1958
1328
  node scripts/cli-remote.js add button --registry https://raw.githubusercontent.com/user/repo/main/registry
1959
1329
  node scripts/cli-remote.js list
@@ -1965,10 +1335,6 @@ Examples:
1965
1335
 
1966
1336
  // Route to appropriate command handler
1967
1337
  switch (command) {
1968
- case 'init':
1969
- await initProject();
1970
- break;
1971
-
1972
1338
  case 'add':
1973
1339
  if (!componentName) {
1974
1340
  error('Please provide a component name');
@@ -1977,21 +1343,21 @@ Examples:
1977
1343
  }
1978
1344
  await installComponent(componentName, options);
1979
1345
  break;
1980
-
1346
+
1981
1347
  case 'list':
1982
1348
  await listComponents(registryUrl);
1983
1349
  break;
1984
-
1350
+
1985
1351
  case 'css':
1986
1352
  case 'styles':
1987
1353
  // Both 'css' and 'styles' commands do the same thing
1988
1354
  const config = loadComponentsConfig();
1989
1355
  await installCSSStyles(config, registryUrl, options.overwrite, false);
1990
1356
  break;
1991
-
1357
+
1992
1358
  default:
1993
1359
  error(`Unknown command: ${command}`);
1994
- console.log('Available commands: init, add, list, css');
1360
+ console.log('Available commands: add, list, css');
1995
1361
  process.exit(1);
1996
1362
  }
1997
1363
  }