weloop-kosign 1.1.2 ā 1.1.4
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.
- package/README.md +0 -11
- package/package.json +1 -1
- package/scripts/cli-remote.js +752 -289
package/scripts/cli-remote.js
CHANGED
|
@@ -48,22 +48,81 @@ const { execSync } = require('child_process');
|
|
|
48
48
|
*/
|
|
49
49
|
function getDefaultRegistryUrl() {
|
|
50
50
|
const localRegistryPath = path.join(__dirname, '../registry');
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
// Check if we're running in the component library project itself
|
|
53
53
|
if (fs.existsSync(localRegistryPath) && fs.existsSync(path.join(localRegistryPath, 'index.json'))) {
|
|
54
54
|
return localRegistryPath;
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
// Fall back to environment variable or default remote URL
|
|
58
|
-
return process.env.WELOOP_REGISTRY_URL ||
|
|
58
|
+
return process.env.WELOOP_REGISTRY_URL ||
|
|
59
59
|
'https://gitlab.com/Sophanithchrek/weloop-shadcn-next-app/-/raw/main/registry';
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const DEFAULT_REGISTRY_URL = getDefaultRegistryUrl();
|
|
63
63
|
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// CONSTANTS
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
64
68
|
// Network timeout constants (in milliseconds)
|
|
65
69
|
const REQUEST_TIMEOUT = 15000;
|
|
66
70
|
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
|
+
];
|
|
67
126
|
|
|
68
127
|
// ============================================================================
|
|
69
128
|
// UTILITIES - Logging and File System Helpers
|
|
@@ -130,6 +189,38 @@ function ensureDirectoryExists(dirPath) {
|
|
|
130
189
|
}
|
|
131
190
|
}
|
|
132
191
|
|
|
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
|
+
|
|
133
224
|
/**
|
|
134
225
|
* Checks if a path is a local file path or a remote URL
|
|
135
226
|
* @param {string} pathOrUrl - Path or URL to check
|
|
@@ -158,12 +249,12 @@ function detectPackageManager() {
|
|
|
158
249
|
if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) return 'yarn';
|
|
159
250
|
if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) return 'pnpm';
|
|
160
251
|
if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) return 'npm';
|
|
161
|
-
|
|
252
|
+
|
|
162
253
|
// Fallback: check npm user agent (set by npm/yarn/pnpm when running)
|
|
163
254
|
const userAgent = process.env.npm_config_user_agent || '';
|
|
164
255
|
if (userAgent.includes('yarn')) return 'yarn';
|
|
165
256
|
if (userAgent.includes('pnpm')) return 'pnpm';
|
|
166
|
-
|
|
257
|
+
|
|
167
258
|
// Default to npm if we can't detect anything
|
|
168
259
|
return 'npm';
|
|
169
260
|
}
|
|
@@ -191,9 +282,9 @@ function getInstallCommand(packageManager, packages) {
|
|
|
191
282
|
* @returns {boolean} True if package is installed, false otherwise
|
|
192
283
|
*/
|
|
193
284
|
function checkPackageInstalled(packageName) {
|
|
194
|
-
const packageJsonPath = path.join(process.cwd(),
|
|
285
|
+
const packageJsonPath = path.join(process.cwd(), PATHS.PACKAGE_JSON);
|
|
195
286
|
if (!fs.existsSync(packageJsonPath)) return false;
|
|
196
|
-
|
|
287
|
+
|
|
197
288
|
try {
|
|
198
289
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
199
290
|
// Check both dependencies and devDependencies
|
|
@@ -212,16 +303,16 @@ function checkPackageInstalled(packageName) {
|
|
|
212
303
|
* @returns {string[]} List of packages that need to be installed
|
|
213
304
|
*/
|
|
214
305
|
function getMissingDependencies(requiredDeps) {
|
|
215
|
-
const packageJsonPath = path.join(process.cwd(),
|
|
306
|
+
const packageJsonPath = path.join(process.cwd(), PATHS.PACKAGE_JSON);
|
|
216
307
|
if (!fs.existsSync(packageJsonPath)) {
|
|
217
308
|
// No package.json means we need to install everything
|
|
218
309
|
return requiredDeps;
|
|
219
310
|
}
|
|
220
|
-
|
|
311
|
+
|
|
221
312
|
try {
|
|
222
313
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
223
314
|
const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
224
|
-
|
|
315
|
+
|
|
225
316
|
return requiredDeps.filter(dep => {
|
|
226
317
|
// For scoped packages like @radix-ui/react-button, also check @radix-ui
|
|
227
318
|
const depName = dep.split('/').slice(0, 2).join('/');
|
|
@@ -243,44 +334,44 @@ function getMissingDependencies(requiredDeps) {
|
|
|
243
334
|
*/
|
|
244
335
|
async function installPackages(packages) {
|
|
245
336
|
if (packages.length === 0) return;
|
|
246
|
-
|
|
337
|
+
|
|
247
338
|
const packageManager = detectPackageManager();
|
|
248
339
|
info(`\nInstalling dependencies: ${packages.join(', ')}`);
|
|
249
|
-
|
|
340
|
+
|
|
250
341
|
try {
|
|
251
342
|
const installCmd = getInstallCommand(packageManager, packages);
|
|
252
|
-
|
|
343
|
+
|
|
253
344
|
// npm has noisy ERESOLVE warnings that aren't real errors
|
|
254
345
|
// We filter these out while keeping actual error messages
|
|
255
346
|
if (packageManager === 'npm') {
|
|
256
347
|
const { spawn } = require('child_process');
|
|
257
348
|
const [cmd, ...args] = installCmd.split(' ');
|
|
258
|
-
|
|
349
|
+
|
|
259
350
|
await new Promise((resolve, reject) => {
|
|
260
351
|
const proc = spawn(cmd, args, {
|
|
261
352
|
cwd: process.cwd(),
|
|
262
353
|
stdio: ['inherit', 'inherit', 'pipe'],
|
|
263
354
|
shell: true
|
|
264
355
|
});
|
|
265
|
-
|
|
356
|
+
|
|
266
357
|
let stderr = '';
|
|
267
358
|
proc.stderr.on('data', (data) => {
|
|
268
359
|
const output = data.toString();
|
|
269
360
|
// Filter out ERESOLVE peer dependency warnings (these are usually safe to ignore)
|
|
270
361
|
// but keep actual error messages visible to the user
|
|
271
|
-
if (!output.includes('ERESOLVE overriding peer dependency') &&
|
|
272
|
-
|
|
362
|
+
if (!output.includes('ERESOLVE overriding peer dependency') &&
|
|
363
|
+
!output.includes('npm warn ERESOLVE')) {
|
|
273
364
|
process.stderr.write(data);
|
|
274
365
|
}
|
|
275
366
|
stderr += output;
|
|
276
367
|
});
|
|
277
|
-
|
|
368
|
+
|
|
278
369
|
proc.on('close', (code) => {
|
|
279
370
|
if (code !== 0) {
|
|
280
371
|
// npm sometimes exits with non-zero code due to warnings, not real errors
|
|
281
372
|
// Check if there's an actual error message (not just ERESOLVE warnings)
|
|
282
|
-
const hasRealError = stderr.includes('npm error') &&
|
|
283
|
-
|
|
373
|
+
const hasRealError = stderr.includes('npm error') &&
|
|
374
|
+
!stderr.match(/npm error.*ERESOLVE/);
|
|
284
375
|
if (hasRealError) {
|
|
285
376
|
reject(new Error(`Installation failed with code ${code}`));
|
|
286
377
|
} else {
|
|
@@ -291,13 +382,13 @@ async function installPackages(packages) {
|
|
|
291
382
|
resolve();
|
|
292
383
|
}
|
|
293
384
|
});
|
|
294
|
-
|
|
385
|
+
|
|
295
386
|
proc.on('error', reject);
|
|
296
387
|
});
|
|
297
388
|
} else {
|
|
298
389
|
execSync(installCmd, { stdio: 'inherit', cwd: process.cwd() });
|
|
299
390
|
}
|
|
300
|
-
|
|
391
|
+
|
|
301
392
|
success(`Dependencies installed successfully`);
|
|
302
393
|
} catch (error) {
|
|
303
394
|
warn(`Failed to install dependencies automatically`);
|
|
@@ -319,25 +410,25 @@ async function installPackages(packages) {
|
|
|
319
410
|
* @returns {object} The default configuration object
|
|
320
411
|
*/
|
|
321
412
|
function createDefaultComponentsConfig() {
|
|
322
|
-
const configPath = path.join(process.cwd(),
|
|
323
|
-
|
|
413
|
+
const configPath = path.join(process.cwd(), PATHS.COMPONENTS_JSON);
|
|
414
|
+
|
|
324
415
|
// Detect Next.js project structure
|
|
325
416
|
// App Router uses 'app/globals.css', Pages Router uses 'styles/globals.css'
|
|
326
417
|
const hasAppDir = fs.existsSync(path.join(process.cwd(), 'app'));
|
|
327
|
-
const cssPath = hasAppDir ?
|
|
328
|
-
|
|
418
|
+
const cssPath = hasAppDir ? PATHS.APP_GLOBALS_CSS : PATHS.STYLES_GLOBALS_CSS;
|
|
419
|
+
|
|
329
420
|
const defaultConfig = {
|
|
330
|
-
style:
|
|
421
|
+
style: DEFAULTS.STYLE,
|
|
331
422
|
rsc: true,
|
|
332
423
|
tsx: true,
|
|
333
424
|
tailwind: {
|
|
334
425
|
config: 'tailwind.config.ts',
|
|
335
426
|
css: cssPath,
|
|
336
|
-
baseColor:
|
|
427
|
+
baseColor: DEFAULTS.BASE_COLOR,
|
|
337
428
|
cssVariables: true,
|
|
338
429
|
prefix: ''
|
|
339
430
|
},
|
|
340
|
-
iconLibrary:
|
|
431
|
+
iconLibrary: DEFAULTS.ICON_LIBRARY,
|
|
341
432
|
aliases: {
|
|
342
433
|
components: '@/components',
|
|
343
434
|
utils: '@/lib/utils',
|
|
@@ -345,13 +436,13 @@ function createDefaultComponentsConfig() {
|
|
|
345
436
|
lib: '@/lib',
|
|
346
437
|
hooks: '@/hooks'
|
|
347
438
|
},
|
|
348
|
-
registry:
|
|
439
|
+
registry: REGISTRY_URLS.INDEX
|
|
349
440
|
};
|
|
350
|
-
|
|
441
|
+
|
|
351
442
|
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
|
|
352
|
-
success(`Created
|
|
443
|
+
success(`Created ${PATHS.COMPONENTS_JSON}`);
|
|
353
444
|
info(` You can customize this file to match your project setup`);
|
|
354
|
-
|
|
445
|
+
|
|
355
446
|
return defaultConfig;
|
|
356
447
|
}
|
|
357
448
|
|
|
@@ -362,15 +453,20 @@ function createDefaultComponentsConfig() {
|
|
|
362
453
|
* @returns {object} The configuration object
|
|
363
454
|
*/
|
|
364
455
|
function loadComponentsConfig() {
|
|
365
|
-
const configPath = path.join(process.cwd(),
|
|
366
|
-
|
|
456
|
+
const configPath = path.join(process.cwd(), PATHS.COMPONENTS_JSON);
|
|
457
|
+
|
|
367
458
|
if (!fs.existsSync(configPath)) {
|
|
368
|
-
info(
|
|
459
|
+
info(`${PATHS.COMPONENTS_JSON} not found. Creating default configuration...`);
|
|
369
460
|
return createDefaultComponentsConfig();
|
|
370
461
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
+
}
|
|
374
470
|
}
|
|
375
471
|
|
|
376
472
|
/**
|
|
@@ -386,21 +482,21 @@ function loadComponentsConfig() {
|
|
|
386
482
|
* @param {number} retries - Number of retry attempts (default: 3)
|
|
387
483
|
* @returns {Promise<object>} Parsed JSON object
|
|
388
484
|
*/
|
|
389
|
-
async function fetchJSON(urlOrPath, retries =
|
|
485
|
+
async function fetchJSON(urlOrPath, retries = MAX_RETRIES) {
|
|
390
486
|
// Handle local file paths
|
|
391
487
|
if (isLocalPath(urlOrPath)) {
|
|
392
488
|
return new Promise((resolve, reject) => {
|
|
393
489
|
try {
|
|
394
490
|
// Resolve relative paths to absolute
|
|
395
|
-
const fullPath = path.isAbsolute(urlOrPath)
|
|
396
|
-
? urlOrPath
|
|
491
|
+
const fullPath = path.isAbsolute(urlOrPath)
|
|
492
|
+
? urlOrPath
|
|
397
493
|
: path.join(process.cwd(), urlOrPath);
|
|
398
|
-
|
|
494
|
+
|
|
399
495
|
if (!fs.existsSync(fullPath)) {
|
|
400
496
|
reject(new Error(`File not found: ${fullPath}`));
|
|
401
497
|
return;
|
|
402
498
|
}
|
|
403
|
-
|
|
499
|
+
|
|
404
500
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
405
501
|
resolve(JSON.parse(content));
|
|
406
502
|
} catch (e) {
|
|
@@ -408,38 +504,38 @@ async function fetchJSON(urlOrPath, retries = 3) {
|
|
|
408
504
|
}
|
|
409
505
|
});
|
|
410
506
|
}
|
|
411
|
-
|
|
507
|
+
|
|
412
508
|
// Handle remote URLs
|
|
413
509
|
return new Promise((resolve, reject) => {
|
|
414
510
|
const client = urlOrPath.startsWith('https') ? https : http;
|
|
415
511
|
let timeout;
|
|
416
512
|
let request;
|
|
417
|
-
|
|
513
|
+
|
|
418
514
|
const makeRequest = () => {
|
|
419
515
|
request = client.get(urlOrPath, (res) => {
|
|
420
516
|
clearTimeout(timeout);
|
|
421
|
-
|
|
517
|
+
|
|
422
518
|
// Handle HTTP redirects (301, 302)
|
|
423
519
|
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
424
520
|
return fetchJSON(res.headers.location, retries).then(resolve).catch(reject);
|
|
425
521
|
}
|
|
426
|
-
|
|
522
|
+
|
|
427
523
|
// Provide helpful error messages for common HTTP status codes
|
|
428
524
|
if (res.statusCode === 403) {
|
|
429
525
|
reject(new Error(`Access forbidden (403). Repository may be private. Make it public in GitLab/GitHub settings, or use --registry with a public URL.`));
|
|
430
526
|
return;
|
|
431
527
|
}
|
|
432
|
-
|
|
528
|
+
|
|
433
529
|
if (res.statusCode === 404) {
|
|
434
530
|
reject(new Error(`Not found (404). Check that the registry files are pushed to the repository and the URL is correct.`));
|
|
435
531
|
return;
|
|
436
532
|
}
|
|
437
|
-
|
|
533
|
+
|
|
438
534
|
if (res.statusCode !== 200) {
|
|
439
535
|
reject(new Error(`Failed to fetch: HTTP ${res.statusCode} - ${res.statusMessage || 'Unknown error'}`));
|
|
440
536
|
return;
|
|
441
537
|
}
|
|
442
|
-
|
|
538
|
+
|
|
443
539
|
// Collect response data
|
|
444
540
|
let data = '';
|
|
445
541
|
res.on('data', (chunk) => { data += chunk; });
|
|
@@ -456,7 +552,7 @@ async function fetchJSON(urlOrPath, retries = 3) {
|
|
|
456
552
|
}
|
|
457
553
|
});
|
|
458
554
|
});
|
|
459
|
-
|
|
555
|
+
|
|
460
556
|
// Handle network errors with automatic retry
|
|
461
557
|
request.on('error', (err) => {
|
|
462
558
|
clearTimeout(timeout);
|
|
@@ -469,7 +565,7 @@ async function fetchJSON(urlOrPath, retries = 3) {
|
|
|
469
565
|
reject(new Error(`Network error: ${err.message} (${err.code || 'UNKNOWN'})`));
|
|
470
566
|
}
|
|
471
567
|
});
|
|
472
|
-
|
|
568
|
+
|
|
473
569
|
// Set request timeout with retry logic
|
|
474
570
|
timeout = setTimeout(() => {
|
|
475
571
|
request.destroy();
|
|
@@ -483,7 +579,7 @@ async function fetchJSON(urlOrPath, retries = 3) {
|
|
|
483
579
|
}
|
|
484
580
|
}, REQUEST_TIMEOUT);
|
|
485
581
|
};
|
|
486
|
-
|
|
582
|
+
|
|
487
583
|
makeRequest();
|
|
488
584
|
});
|
|
489
585
|
}
|
|
@@ -503,34 +599,34 @@ async function fetchText(urlOrPath) {
|
|
|
503
599
|
}
|
|
504
600
|
return fs.readFileSync(urlOrPath, 'utf-8');
|
|
505
601
|
}
|
|
506
|
-
|
|
602
|
+
|
|
507
603
|
// Handle remote URLs
|
|
508
604
|
return new Promise((resolve, reject) => {
|
|
509
605
|
const client = urlOrPath.startsWith('https') ? https : http;
|
|
510
606
|
let data = '';
|
|
511
|
-
|
|
607
|
+
|
|
512
608
|
const req = client.get(urlOrPath, (res) => {
|
|
513
609
|
// Follow redirects
|
|
514
610
|
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
515
611
|
return fetchText(res.headers.location).then(resolve).catch(reject);
|
|
516
612
|
}
|
|
517
|
-
|
|
613
|
+
|
|
518
614
|
// Handle common HTTP errors
|
|
519
615
|
if (res.statusCode === 403) {
|
|
520
616
|
reject(new Error('Access forbidden - repository may be private'));
|
|
521
617
|
return;
|
|
522
618
|
}
|
|
523
|
-
|
|
619
|
+
|
|
524
620
|
if (res.statusCode === 404) {
|
|
525
621
|
reject(new Error('CSS file not found in repository'));
|
|
526
622
|
return;
|
|
527
623
|
}
|
|
528
|
-
|
|
624
|
+
|
|
529
625
|
if (res.statusCode !== 200) {
|
|
530
626
|
reject(new Error(`HTTP ${res.statusCode}`));
|
|
531
627
|
return;
|
|
532
628
|
}
|
|
533
|
-
|
|
629
|
+
|
|
534
630
|
// Collect response data
|
|
535
631
|
res.on('data', (chunk) => { data += chunk; });
|
|
536
632
|
res.on('end', () => {
|
|
@@ -542,7 +638,7 @@ async function fetchText(urlOrPath) {
|
|
|
542
638
|
resolve(data);
|
|
543
639
|
});
|
|
544
640
|
});
|
|
545
|
-
|
|
641
|
+
|
|
546
642
|
// Handle network errors with automatic retry
|
|
547
643
|
req.on('error', (err) => {
|
|
548
644
|
if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') {
|
|
@@ -553,7 +649,7 @@ async function fetchText(urlOrPath) {
|
|
|
553
649
|
reject(err);
|
|
554
650
|
}
|
|
555
651
|
});
|
|
556
|
-
|
|
652
|
+
|
|
557
653
|
// Set request timeout
|
|
558
654
|
req.setTimeout(REQUEST_TIMEOUT, () => {
|
|
559
655
|
req.destroy();
|
|
@@ -573,13 +669,14 @@ async function fetchText(urlOrPath) {
|
|
|
573
669
|
function resolveRegistryPath(baseUrl, fileName) {
|
|
574
670
|
if (isLocalPath(baseUrl)) {
|
|
575
671
|
// Resolve local paths (absolute or relative)
|
|
576
|
-
const basePath = path.isAbsolute(baseUrl)
|
|
577
|
-
? baseUrl
|
|
672
|
+
const basePath = path.isAbsolute(baseUrl)
|
|
673
|
+
? baseUrl
|
|
578
674
|
: path.join(process.cwd(), baseUrl);
|
|
579
675
|
return path.join(basePath, fileName);
|
|
580
676
|
}
|
|
581
|
-
// For remote URLs,
|
|
582
|
-
|
|
677
|
+
// For remote URLs, append filename with proper separator
|
|
678
|
+
const separator = baseUrl.endsWith('/') ? '' : '/';
|
|
679
|
+
return `${baseUrl}${separator}${fileName}`;
|
|
583
680
|
}
|
|
584
681
|
|
|
585
682
|
/**
|
|
@@ -596,6 +693,7 @@ async function loadRegistry(componentName, registryBaseUrl) {
|
|
|
596
693
|
return await fetchJSON(registryPath);
|
|
597
694
|
} catch (err) {
|
|
598
695
|
// Component not found - return null instead of throwing
|
|
696
|
+
// This allows the caller to handle missing components gracefully
|
|
599
697
|
return null;
|
|
600
698
|
}
|
|
601
699
|
}
|
|
@@ -608,7 +706,7 @@ async function loadRegistry(componentName, registryBaseUrl) {
|
|
|
608
706
|
* @returns {Promise<object>} Index object containing list of components
|
|
609
707
|
*/
|
|
610
708
|
async function loadIndex(registryBaseUrl) {
|
|
611
|
-
const indexPath = resolveRegistryPath(registryBaseUrl,
|
|
709
|
+
const indexPath = resolveRegistryPath(registryBaseUrl, PATHS.REGISTRY_INDEX);
|
|
612
710
|
try {
|
|
613
711
|
return await fetchJSON(indexPath);
|
|
614
712
|
} catch (err) {
|
|
@@ -629,12 +727,18 @@ async function loadIndex(registryBaseUrl) {
|
|
|
629
727
|
* @returns {boolean} True if Weloop styles are present
|
|
630
728
|
*/
|
|
631
729
|
function hasWeloopStyles(content) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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');
|
|
638
742
|
}
|
|
639
743
|
|
|
640
744
|
/**
|
|
@@ -645,16 +749,15 @@ function hasWeloopStyles(content) {
|
|
|
645
749
|
* @returns {string} CSS content with duplicates removed
|
|
646
750
|
*/
|
|
647
751
|
function removeDuplicateTwAnimateImports(cssContent) {
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
752
|
+
const matches = cssContent.match(CSS_PATTERNS.TW_ANIMATE_IMPORT);
|
|
753
|
+
|
|
651
754
|
if (matches && matches.length > 1) {
|
|
652
755
|
// Remove all occurrences first
|
|
653
|
-
let cleaned = cssContent.replace(
|
|
756
|
+
let cleaned = cssContent.replace(CSS_PATTERNS.TW_ANIMATE_IMPORT, '');
|
|
654
757
|
// Add it back once, right after tailwindcss import if it exists
|
|
655
|
-
if (
|
|
758
|
+
if (hasTailwindImport(cleaned)) {
|
|
656
759
|
cleaned = cleaned.replace(
|
|
657
|
-
|
|
760
|
+
CSS_PATTERNS.TAILWIND_IMPORT,
|
|
658
761
|
'$1@import "tw-animate-css";\n'
|
|
659
762
|
);
|
|
660
763
|
} else {
|
|
@@ -663,7 +766,7 @@ function removeDuplicateTwAnimateImports(cssContent) {
|
|
|
663
766
|
}
|
|
664
767
|
return cleaned;
|
|
665
768
|
}
|
|
666
|
-
|
|
769
|
+
|
|
667
770
|
return cssContent;
|
|
668
771
|
}
|
|
669
772
|
|
|
@@ -675,12 +778,11 @@ function removeDuplicateTwAnimateImports(cssContent) {
|
|
|
675
778
|
* @returns {string} CSS content with duplicates removed
|
|
676
779
|
*/
|
|
677
780
|
function removeDuplicateCustomVariant(cssContent) {
|
|
678
|
-
const
|
|
679
|
-
const matches = cssContent.match(variantPattern);
|
|
781
|
+
const matches = cssContent.match(CSS_PATTERNS.CUSTOM_VARIANT);
|
|
680
782
|
|
|
681
783
|
if (matches && matches.length > 1) {
|
|
682
784
|
// Remove all occurrences and add back one at the top
|
|
683
|
-
const withoutVariants = cssContent.replace(
|
|
785
|
+
const withoutVariants = cssContent.replace(CSS_PATTERNS.CUSTOM_VARIANT, '');
|
|
684
786
|
return `@custom-variant dark (&:is(.dark *));\n\n${withoutVariants.trimStart()}`;
|
|
685
787
|
}
|
|
686
788
|
|
|
@@ -703,30 +805,28 @@ function removeDuplicateCustomVariant(cssContent) {
|
|
|
703
805
|
*/
|
|
704
806
|
function normalizeCSSFormat(cssContent) {
|
|
705
807
|
// Extract @custom-variant declaration
|
|
706
|
-
const
|
|
707
|
-
const variantMatch = cssContent.match(variantPattern);
|
|
808
|
+
const variantMatch = cssContent.match(CSS_PATTERNS.CUSTOM_VARIANT);
|
|
708
809
|
const hasVariant = variantMatch && variantMatch.length > 0;
|
|
709
|
-
|
|
810
|
+
|
|
710
811
|
// Extract all @import statements
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
812
|
+
const imports = cssContent.match(CSS_PATTERNS.IMPORT_STATEMENT) || [];
|
|
813
|
+
|
|
714
814
|
// Separate imports by type for proper ordering
|
|
715
815
|
const tailwindImport = imports.find(imp => imp.includes('tailwindcss'));
|
|
716
816
|
const twAnimateImport = imports.find(imp => imp.includes('tw-animate-css'));
|
|
717
|
-
const otherImports = imports.filter(imp =>
|
|
817
|
+
const otherImports = imports.filter(imp =>
|
|
718
818
|
!imp.includes('tailwindcss') && !imp.includes('tw-animate-css')
|
|
719
819
|
);
|
|
720
|
-
|
|
820
|
+
|
|
721
821
|
// Remove all imports and variant from content to get the rest
|
|
722
822
|
let content = cssContent
|
|
723
|
-
.replace(
|
|
724
|
-
.replace(
|
|
823
|
+
.replace(CSS_PATTERNS.CUSTOM_VARIANT, '')
|
|
824
|
+
.replace(CSS_PATTERNS.IMPORT_STATEMENT, '')
|
|
725
825
|
.trim();
|
|
726
|
-
|
|
826
|
+
|
|
727
827
|
// Build normalized format in the correct order
|
|
728
828
|
let normalized = '';
|
|
729
|
-
|
|
829
|
+
|
|
730
830
|
// Step 1: Imports first (tailwindcss, then tw-animate-css, then others)
|
|
731
831
|
if (tailwindImport) {
|
|
732
832
|
normalized += tailwindImport.trim() + '\n';
|
|
@@ -737,23 +837,19 @@ function normalizeCSSFormat(cssContent) {
|
|
|
737
837
|
if (otherImports.length > 0) {
|
|
738
838
|
normalized += otherImports.join('') + '\n';
|
|
739
839
|
}
|
|
740
|
-
|
|
840
|
+
|
|
741
841
|
// Step 2: @custom-variant second (if it exists)
|
|
742
842
|
if (hasVariant) {
|
|
743
|
-
|
|
744
|
-
normalized += '\n@custom-variant dark (&:is(.dark *));\n';
|
|
745
|
-
} else {
|
|
746
|
-
normalized += '@custom-variant dark (&:is(.dark *));\n';
|
|
747
|
-
}
|
|
843
|
+
normalized += normalized ? '\n@custom-variant dark (&:is(.dark *));\n' : '@custom-variant dark (&:is(.dark *));\n';
|
|
748
844
|
}
|
|
749
|
-
|
|
845
|
+
|
|
750
846
|
// Step 3: Rest of content (theme variables, etc.)
|
|
751
847
|
if (normalized && content) {
|
|
752
848
|
normalized += '\n' + content;
|
|
753
849
|
} else if (content) {
|
|
754
850
|
normalized = content;
|
|
755
851
|
}
|
|
756
|
-
|
|
852
|
+
|
|
757
853
|
return normalized.trim() + (normalized ? '\n' : '');
|
|
758
854
|
}
|
|
759
855
|
|
|
@@ -769,32 +865,32 @@ function normalizeCSSFormat(cssContent) {
|
|
|
769
865
|
* @returns {string} Processed CSS content
|
|
770
866
|
*/
|
|
771
867
|
function processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate = false) {
|
|
772
|
-
const twAnimatePattern = /@import\s+["']tw-animate-css["'];?\s*\n?/g;
|
|
773
868
|
let processed = cssContent;
|
|
774
|
-
|
|
869
|
+
|
|
775
870
|
if (!hasTwAnimate) {
|
|
776
871
|
// Package not installed - remove import to prevent build errors
|
|
777
|
-
processed = cssContent.replace(
|
|
872
|
+
processed = cssContent.replace(CSS_PATTERNS.TW_ANIMATE_IMPORT, '');
|
|
778
873
|
if (cssContent.includes('tw-animate-css') && !processed.includes('tw-animate-css')) {
|
|
874
|
+
const installCmd = `npm install ${PACKAGE_NAMES.TW_ANIMATE_CSS}`;
|
|
779
875
|
if (forceUpdate) {
|
|
780
|
-
warn(
|
|
781
|
-
info(
|
|
782
|
-
info(' Then run: node scripts/
|
|
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');
|
|
783
879
|
} else {
|
|
784
|
-
warn(
|
|
785
|
-
info(
|
|
880
|
+
warn(`${PACKAGE_NAMES.TW_ANIMATE_CSS} package not found - removed from CSS`);
|
|
881
|
+
info(` Install with: ${installCmd}`);
|
|
786
882
|
info(' Or add the import manually after installing the package');
|
|
787
883
|
}
|
|
788
884
|
}
|
|
789
885
|
} else {
|
|
790
886
|
// Package is installed - ensure import exists and remove duplicates
|
|
791
887
|
processed = removeDuplicateTwAnimateImports(processed);
|
|
792
|
-
|
|
888
|
+
|
|
793
889
|
// Add import if it doesn't exist (right after tailwindcss if present)
|
|
794
|
-
if (!processed.match(
|
|
795
|
-
if (
|
|
890
|
+
if (!processed.match(CSS_PATTERNS.TW_ANIMATE_IMPORT)) {
|
|
891
|
+
if (hasTailwindImport(processed)) {
|
|
796
892
|
processed = processed.replace(
|
|
797
|
-
|
|
893
|
+
CSS_PATTERNS.TAILWIND_IMPORT,
|
|
798
894
|
'$1@import "tw-animate-css";\n'
|
|
799
895
|
);
|
|
800
896
|
} else {
|
|
@@ -802,7 +898,7 @@ function processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate = false) {
|
|
|
802
898
|
}
|
|
803
899
|
}
|
|
804
900
|
}
|
|
805
|
-
|
|
901
|
+
|
|
806
902
|
return processed;
|
|
807
903
|
}
|
|
808
904
|
|
|
@@ -814,7 +910,7 @@ function processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate = false) {
|
|
|
814
910
|
* @returns {string} CSS content without tailwindcss imports
|
|
815
911
|
*/
|
|
816
912
|
function removeTailwindImport(cssContent) {
|
|
817
|
-
return cssContent.replace(
|
|
913
|
+
return cssContent.replace(CSS_PATTERNS.TAILWIND_IMPORT, '');
|
|
818
914
|
}
|
|
819
915
|
|
|
820
916
|
/**
|
|
@@ -829,23 +925,23 @@ function ensureTwAnimateImport(cssContent, hasTwAnimate) {
|
|
|
829
925
|
if (!hasTwAnimate) {
|
|
830
926
|
return cssContent;
|
|
831
927
|
}
|
|
832
|
-
|
|
928
|
+
|
|
833
929
|
// Remove duplicates first
|
|
834
930
|
let cleaned = removeDuplicateTwAnimateImports(cssContent);
|
|
835
|
-
|
|
931
|
+
|
|
836
932
|
// Check if import already exists
|
|
837
|
-
if (cleaned.match(
|
|
933
|
+
if (cleaned.match(CSS_PATTERNS.TW_ANIMATE_IMPORT)) {
|
|
838
934
|
return cleaned;
|
|
839
935
|
}
|
|
840
|
-
|
|
936
|
+
|
|
841
937
|
// Add import after tailwindcss if it exists, otherwise at the beginning
|
|
842
|
-
if (
|
|
938
|
+
if (hasTailwindImport(cleaned)) {
|
|
843
939
|
return cleaned.replace(
|
|
844
|
-
|
|
940
|
+
CSS_PATTERNS.TAILWIND_IMPORT,
|
|
845
941
|
'$1@import "tw-animate-css";\n'
|
|
846
942
|
);
|
|
847
943
|
}
|
|
848
|
-
|
|
944
|
+
|
|
849
945
|
return '@import "tw-animate-css";\n' + cleaned;
|
|
850
946
|
}
|
|
851
947
|
|
|
@@ -863,35 +959,32 @@ function ensureTwAnimateImport(cssContent, hasTwAnimate) {
|
|
|
863
959
|
* @returns {string} Merged CSS content
|
|
864
960
|
*/
|
|
865
961
|
function mergeCSSWithTailwind(existing, weloopStyles, hasTwAnimate) {
|
|
866
|
-
const tailwindMatch = existing.match(
|
|
962
|
+
const tailwindMatch = existing.match(CSS_PATTERNS.TAILWIND_IMPORT);
|
|
867
963
|
if (!tailwindMatch) {
|
|
868
964
|
// No tailwindcss import found - just prepend Weloop styles
|
|
869
965
|
return weloopStyles + '\n\n' + existing;
|
|
870
966
|
}
|
|
871
|
-
|
|
967
|
+
|
|
872
968
|
// Split existing CSS around the tailwindcss import
|
|
873
|
-
const
|
|
874
|
-
const
|
|
875
|
-
|
|
969
|
+
const tailwindIndex = existing.indexOf(tailwindMatch[0]);
|
|
970
|
+
const beforeTailwind = existing.substring(0, tailwindIndex);
|
|
971
|
+
const afterTailwind = existing.substring(tailwindIndex + tailwindMatch[0].length);
|
|
972
|
+
|
|
876
973
|
// Check if tw-animate-css import already exists
|
|
877
|
-
const hasTwAnimateInExisting = existing.match(
|
|
878
|
-
const hasTwAnimateInWeloop = weloopStyles.match(
|
|
879
|
-
|
|
974
|
+
const hasTwAnimateInExisting = existing.match(CSS_PATTERNS.TW_ANIMATE_IMPORT);
|
|
975
|
+
const hasTwAnimateInWeloop = weloopStyles.match(CSS_PATTERNS.TW_ANIMATE_IMPORT);
|
|
976
|
+
|
|
880
977
|
// If existing file already has tw-animate import, remove it from Weloop styles
|
|
881
978
|
// to avoid duplicating it in the merge output
|
|
882
979
|
let weloopStylesCleaned = weloopStyles;
|
|
883
980
|
if (hasTwAnimateInExisting && hasTwAnimateInWeloop) {
|
|
884
|
-
weloopStylesCleaned = weloopStyles.replace(
|
|
885
|
-
/@import\s+["']tw-animate-css["'];?\s*\n?/g,
|
|
886
|
-
''
|
|
887
|
-
);
|
|
981
|
+
weloopStylesCleaned = weloopStyles.replace(CSS_PATTERNS.TW_ANIMATE_IMPORT, '');
|
|
888
982
|
}
|
|
889
|
-
|
|
983
|
+
|
|
890
984
|
// Add tw-animate import if needed (package installed but import missing)
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
985
|
+
const importsToAdd = (hasTwAnimate && !hasTwAnimateInExisting && !hasTwAnimateInWeloop)
|
|
986
|
+
? '@import "tw-animate-css";\n'
|
|
987
|
+
: '';
|
|
895
988
|
|
|
896
989
|
// Merge: before tailwind + tailwind import + tw-animate (if needed) + Weloop styles + after tailwind
|
|
897
990
|
return beforeTailwind + tailwindMatch[0] + importsToAdd + weloopStylesCleaned + '\n' + afterTailwind;
|
|
@@ -910,28 +1003,26 @@ function mergeCSSWithTailwind(existing, weloopStyles, hasTwAnimate) {
|
|
|
910
1003
|
* @returns {string} CSS content with Weloop styles replaced
|
|
911
1004
|
*/
|
|
912
1005
|
function replaceWeloopStyles(existing, newStyles, hasTwAnimate) {
|
|
913
|
-
const
|
|
914
|
-
const imports = existing.match(importPattern) || [];
|
|
1006
|
+
const imports = existing.match(CSS_PATTERNS.IMPORT_STATEMENT) || [];
|
|
915
1007
|
const importsText = imports.join('');
|
|
916
|
-
|
|
1008
|
+
|
|
917
1009
|
// Find where Weloop styles start (look for @theme inline or :root)
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
1010
|
+
const weloopStartMatch = existing.search(CSS_PATTERNS.WELOOP_START);
|
|
1011
|
+
|
|
921
1012
|
if (weloopStartMatch === -1) {
|
|
922
1013
|
// No existing Weloop styles found - just append
|
|
923
1014
|
return existing + '\n\n' + newStyles;
|
|
924
1015
|
}
|
|
925
|
-
|
|
1016
|
+
|
|
926
1017
|
// Extract content before Weloop styles
|
|
927
1018
|
let contentBeforeWeloop = existing.substring(0, weloopStartMatch);
|
|
928
1019
|
// Keep non-Weloop imports (filter out tw-animate if package not installed)
|
|
929
|
-
const nonWeloopImports = importsText.split('\n').filter(imp =>
|
|
1020
|
+
const nonWeloopImports = importsText.split('\n').filter(imp =>
|
|
930
1021
|
!imp.includes('tw-animate-css') || hasTwAnimate
|
|
931
1022
|
).join('\n');
|
|
932
1023
|
// Remove all imports from contentBeforeWeloop (we'll add them back)
|
|
933
|
-
contentBeforeWeloop = nonWeloopImports + '\n' + contentBeforeWeloop.replace(
|
|
934
|
-
|
|
1024
|
+
contentBeforeWeloop = nonWeloopImports + '\n' + contentBeforeWeloop.replace(CSS_PATTERNS.IMPORT_STATEMENT, '');
|
|
1025
|
+
|
|
935
1026
|
// Return: preserved imports + content before Weloop + new Weloop styles
|
|
936
1027
|
return contentBeforeWeloop.trim() + '\n\n' + newStyles.trim();
|
|
937
1028
|
}
|
|
@@ -947,11 +1038,11 @@ function replaceWeloopStyles(existing, newStyles, hasTwAnimate) {
|
|
|
947
1038
|
*/
|
|
948
1039
|
async function fetchCSSFromRegistry(registryUrl) {
|
|
949
1040
|
let sourceCssPath;
|
|
950
|
-
|
|
1041
|
+
|
|
951
1042
|
if (isLocalPath(registryUrl)) {
|
|
952
1043
|
// Local path: find app/globals.css relative to registry directory
|
|
953
|
-
const basePath = path.isAbsolute(registryUrl)
|
|
954
|
-
? path.dirname(registryUrl)
|
|
1044
|
+
const basePath = path.isAbsolute(registryUrl)
|
|
1045
|
+
? path.dirname(registryUrl)
|
|
955
1046
|
: path.join(process.cwd(), path.dirname(registryUrl));
|
|
956
1047
|
sourceCssPath = path.join(basePath, 'app', 'globals.css');
|
|
957
1048
|
} else {
|
|
@@ -959,7 +1050,7 @@ async function fetchCSSFromRegistry(registryUrl) {
|
|
|
959
1050
|
const baseUrl = registryUrl.replace('/registry', '');
|
|
960
1051
|
sourceCssPath = `${baseUrl}/app/globals.css`;
|
|
961
1052
|
}
|
|
962
|
-
|
|
1053
|
+
|
|
963
1054
|
return await fetchText(sourceCssPath);
|
|
964
1055
|
}
|
|
965
1056
|
|
|
@@ -977,52 +1068,49 @@ async function fetchCSSFromRegistry(registryUrl) {
|
|
|
977
1068
|
* @param {boolean} silent - Whether to suppress output messages
|
|
978
1069
|
*/
|
|
979
1070
|
async function installCSSStyles(config, registryUrl, forceUpdate = false, silent = false) {
|
|
980
|
-
const cssPath = config.tailwind?.css ||
|
|
1071
|
+
const cssPath = config.tailwind?.css || DEFAULTS.CSS_PATH;
|
|
981
1072
|
const fullCssPath = path.join(process.cwd(), cssPath);
|
|
982
|
-
|
|
1073
|
+
|
|
983
1074
|
// Check if Weloop styles already exist in the file
|
|
984
1075
|
let hasWeloopStylesInFile = false;
|
|
985
1076
|
if (fs.existsSync(fullCssPath)) {
|
|
986
1077
|
const existingContent = fs.readFileSync(fullCssPath, 'utf-8');
|
|
987
1078
|
hasWeloopStylesInFile = hasWeloopStyles(existingContent);
|
|
988
|
-
|
|
1079
|
+
|
|
989
1080
|
// If styles already exist and we're not forcing update, skip silently
|
|
990
1081
|
// This prevents unnecessary updates when installing components
|
|
991
1082
|
if (hasWeloopStylesInFile && !forceUpdate && silent) {
|
|
992
1083
|
return;
|
|
993
1084
|
}
|
|
994
1085
|
}
|
|
995
|
-
|
|
1086
|
+
|
|
996
1087
|
try {
|
|
997
1088
|
if (!silent) {
|
|
998
1089
|
info('Installing CSS styles...');
|
|
999
1090
|
}
|
|
1000
|
-
|
|
1091
|
+
|
|
1001
1092
|
const cssContent = await fetchCSSFromRegistry(registryUrl);
|
|
1002
1093
|
ensureDirectoryExists(path.dirname(fullCssPath));
|
|
1003
|
-
|
|
1004
|
-
const hasTwAnimate = checkPackageInstalled(
|
|
1094
|
+
|
|
1095
|
+
const hasTwAnimate = checkPackageInstalled(PACKAGE_NAMES.TW_ANIMATE_CSS);
|
|
1005
1096
|
let processedCssContent = processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate);
|
|
1006
|
-
|
|
1097
|
+
|
|
1007
1098
|
// Handle file installation based on mode and existing file state
|
|
1008
1099
|
if (forceUpdate && fs.existsSync(fullCssPath)) {
|
|
1009
1100
|
// Mode 1: --overwrite flag - Replace entire file with fresh styles
|
|
1010
|
-
|
|
1011
|
-
normalized = removeDuplicateTwAnimateImports(normalized);
|
|
1012
|
-
normalized = removeDuplicateCustomVariant(normalized);
|
|
1101
|
+
const normalized = normalizeAndCleanCSS(processedCssContent, hasTwAnimate);
|
|
1013
1102
|
fs.writeFileSync(fullCssPath, normalized);
|
|
1014
1103
|
if (!silent) {
|
|
1015
1104
|
success(`Overwritten ${cssPath} with Weloop styles`);
|
|
1016
1105
|
if (hasTwAnimate) {
|
|
1017
|
-
info(`
|
|
1106
|
+
info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
|
|
1018
1107
|
}
|
|
1019
1108
|
}
|
|
1020
1109
|
} else if (fs.existsSync(fullCssPath)) {
|
|
1021
1110
|
// Mode 2: Normal update - Intelligently merge with existing styles
|
|
1022
1111
|
const existing = fs.readFileSync(fullCssPath, 'utf-8');
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1112
|
+
const existingHasTailwind = hasTailwindImport(existing);
|
|
1113
|
+
|
|
1026
1114
|
if (hasWeloopStylesInFile) {
|
|
1027
1115
|
// Case 2a: Weloop styles already exist - replace them with updated versions
|
|
1028
1116
|
let weloopStyles = removeTailwindImport(processedCssContent);
|
|
@@ -1031,7 +1119,7 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
|
|
|
1031
1119
|
finalContent = removeDuplicateTwAnimateImports(finalContent);
|
|
1032
1120
|
finalContent = removeDuplicateCustomVariant(finalContent);
|
|
1033
1121
|
finalContent = normalizeCSSFormat(finalContent);
|
|
1034
|
-
|
|
1122
|
+
|
|
1035
1123
|
// Only write if content actually changed (prevents unnecessary file updates)
|
|
1036
1124
|
if (finalContent !== existing) {
|
|
1037
1125
|
fs.writeFileSync(fullCssPath, finalContent);
|
|
@@ -1040,31 +1128,29 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
|
|
|
1040
1128
|
}
|
|
1041
1129
|
}
|
|
1042
1130
|
// If no changes, silently skip (file is already up to date)
|
|
1043
|
-
} else if (
|
|
1131
|
+
} else if (existingHasTailwind) {
|
|
1044
1132
|
// Case 2b: No Weloop styles but Tailwind exists - merge after Tailwind imports
|
|
1045
1133
|
let weloopStyles = removeTailwindImport(processedCssContent);
|
|
1046
1134
|
weloopStyles = ensureTwAnimateImport(weloopStyles, hasTwAnimate);
|
|
1047
1135
|
let merged = mergeCSSWithTailwind(existing, weloopStyles, hasTwAnimate);
|
|
1048
|
-
merged =
|
|
1049
|
-
merged = removeDuplicateCustomVariant(merged);
|
|
1050
|
-
merged = normalizeCSSFormat(merged);
|
|
1136
|
+
merged = normalizeAndCleanCSS(merged, hasTwAnimate);
|
|
1051
1137
|
fs.writeFileSync(fullCssPath, merged);
|
|
1052
1138
|
if (!silent) {
|
|
1053
1139
|
success(`Updated ${cssPath} with Weloop styles`);
|
|
1054
1140
|
if (hasTwAnimate) {
|
|
1055
|
-
info(`
|
|
1141
|
+
info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
|
|
1056
1142
|
}
|
|
1057
1143
|
info(` Your existing styles are preserved`);
|
|
1058
1144
|
}
|
|
1059
1145
|
} else {
|
|
1060
1146
|
// Case 2c: No Tailwind imports - prepend Weloop styles to existing content
|
|
1061
1147
|
let finalCssContent = removeDuplicateTwAnimateImports(processedCssContent);
|
|
1062
|
-
|
|
1148
|
+
|
|
1063
1149
|
// Ensure tw-animate import exists if package is installed
|
|
1064
|
-
if (hasTwAnimate && !finalCssContent.match(
|
|
1065
|
-
if (finalCssContent
|
|
1150
|
+
if (hasTwAnimate && !finalCssContent.match(CSS_PATTERNS.TW_ANIMATE_IMPORT)) {
|
|
1151
|
+
if (hasTailwindImport(finalCssContent)) {
|
|
1066
1152
|
finalCssContent = finalCssContent.replace(
|
|
1067
|
-
|
|
1153
|
+
CSS_PATTERNS.TAILWIND_IMPORT,
|
|
1068
1154
|
'$1@import "tw-animate-css";\n'
|
|
1069
1155
|
);
|
|
1070
1156
|
} else {
|
|
@@ -1072,35 +1158,34 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
|
|
|
1072
1158
|
}
|
|
1073
1159
|
}
|
|
1074
1160
|
let combined = finalCssContent + '\n\n' + existing;
|
|
1075
|
-
combined =
|
|
1161
|
+
combined = normalizeAndCleanCSS(combined, hasTwAnimate);
|
|
1076
1162
|
fs.writeFileSync(fullCssPath, combined);
|
|
1077
1163
|
if (!silent) {
|
|
1078
1164
|
success(`Updated ${cssPath} with Weloop styles`);
|
|
1079
1165
|
if (hasTwAnimate) {
|
|
1080
|
-
info(`
|
|
1166
|
+
info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
|
|
1081
1167
|
}
|
|
1082
1168
|
}
|
|
1083
1169
|
}
|
|
1084
1170
|
} else {
|
|
1085
1171
|
// Mode 3: File doesn't exist - create new file with Weloop styles
|
|
1086
|
-
|
|
1087
|
-
normalized = removeDuplicateTwAnimateImports(normalized);
|
|
1088
|
-
normalized = removeDuplicateCustomVariant(normalized);
|
|
1172
|
+
const normalized = normalizeAndCleanCSS(processedCssContent, hasTwAnimate);
|
|
1089
1173
|
fs.writeFileSync(fullCssPath, normalized);
|
|
1090
1174
|
if (!silent) {
|
|
1091
1175
|
success(`Created ${cssPath} with Weloop styles`);
|
|
1092
1176
|
if (hasTwAnimate) {
|
|
1093
|
-
info(`
|
|
1177
|
+
info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
|
|
1094
1178
|
}
|
|
1095
1179
|
}
|
|
1096
1180
|
}
|
|
1097
1181
|
} catch (err) {
|
|
1098
1182
|
if (!silent) {
|
|
1099
1183
|
warn(`Could not automatically install CSS styles: ${err.message}`);
|
|
1100
|
-
info(
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1184
|
+
info(getCSSInstallErrorMessage(registryUrl, cssPath));
|
|
1185
|
+
}
|
|
1186
|
+
// Re-throw in silent mode so caller can handle it
|
|
1187
|
+
if (silent) {
|
|
1188
|
+
throw err;
|
|
1104
1189
|
}
|
|
1105
1190
|
}
|
|
1106
1191
|
}
|
|
@@ -1119,11 +1204,11 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
|
|
|
1119
1204
|
*/
|
|
1120
1205
|
function createUtilsFile(utilsPath) {
|
|
1121
1206
|
if (fs.existsSync(utilsPath)) return;
|
|
1122
|
-
|
|
1207
|
+
|
|
1123
1208
|
// Silently create utils file (matching shadcn/ui behavior)
|
|
1124
1209
|
// Don't overwrite if user has customized it
|
|
1125
1210
|
ensureDirectoryExists(path.dirname(utilsPath));
|
|
1126
|
-
|
|
1211
|
+
|
|
1127
1212
|
const utilsContent = `import { clsx, type ClassValue } from "clsx"
|
|
1128
1213
|
import { twMerge } from "tailwind-merge"
|
|
1129
1214
|
|
|
@@ -1153,47 +1238,41 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
1153
1238
|
*/
|
|
1154
1239
|
async function installComponent(componentName, options = {}) {
|
|
1155
1240
|
const { overwrite = false, registryUrl = DEFAULT_REGISTRY_URL } = options;
|
|
1156
|
-
|
|
1241
|
+
|
|
1157
1242
|
// Step 1: Install base dependencies (required for utils.ts to work)
|
|
1158
|
-
const baseDeps =
|
|
1159
|
-
if (!checkPackageInstalled('clsx')) {
|
|
1160
|
-
baseDeps.push('clsx');
|
|
1161
|
-
}
|
|
1162
|
-
if (!checkPackageInstalled('tailwind-merge')) {
|
|
1163
|
-
baseDeps.push('tailwind-merge');
|
|
1164
|
-
}
|
|
1243
|
+
const baseDeps = BASE_DEPENDENCIES.filter(dep => !checkPackageInstalled(dep));
|
|
1165
1244
|
if (baseDeps.length > 0) {
|
|
1166
1245
|
info(`Installing base dependencies: ${baseDeps.join(', ')}...`);
|
|
1167
1246
|
await installPackages(baseDeps);
|
|
1168
1247
|
}
|
|
1169
|
-
|
|
1248
|
+
|
|
1170
1249
|
// Step 2: Load or create components.json configuration
|
|
1171
1250
|
const config = loadComponentsConfig();
|
|
1172
|
-
|
|
1251
|
+
|
|
1173
1252
|
// Step 3: Install tw-animate-css automatically (required for component animations)
|
|
1174
|
-
if (!checkPackageInstalled(
|
|
1175
|
-
info(
|
|
1176
|
-
await installPackages([
|
|
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]);
|
|
1177
1256
|
}
|
|
1178
|
-
|
|
1257
|
+
|
|
1179
1258
|
// Step 4: Install CSS styles early (before component installation)
|
|
1180
1259
|
// Silent mode prevents output when styles are already installed
|
|
1181
1260
|
await installCSSStyles(config, registryUrl, false, true);
|
|
1182
|
-
|
|
1261
|
+
|
|
1183
1262
|
// Step 5: Resolve paths from components.json configuration
|
|
1184
1263
|
const uiAlias = config.aliases?.ui || '@/components/ui';
|
|
1185
1264
|
const utilsAlias = config.aliases?.utils || '@/lib/utils';
|
|
1186
|
-
|
|
1265
|
+
|
|
1187
1266
|
const componentsDir = path.join(process.cwd(), uiAlias.replace('@/', '').replace(/^\/+/, ''));
|
|
1188
1267
|
let utilsPath = utilsAlias.replace('@/', '').replace(/^\/+/, '');
|
|
1189
1268
|
if (!utilsPath.endsWith('.ts') && !utilsPath.endsWith('.tsx')) {
|
|
1190
1269
|
utilsPath = utilsPath + '.ts';
|
|
1191
1270
|
}
|
|
1192
1271
|
utilsPath = path.join(process.cwd(), utilsPath);
|
|
1193
|
-
|
|
1272
|
+
|
|
1194
1273
|
// Step 6: Load component registry from remote or local source
|
|
1195
1274
|
const registry = await loadRegistry(componentName, registryUrl);
|
|
1196
|
-
|
|
1275
|
+
|
|
1197
1276
|
if (!registry) {
|
|
1198
1277
|
error(`Component "${componentName}" not found in registry.`);
|
|
1199
1278
|
info('Available components:');
|
|
@@ -1263,20 +1342,20 @@ async function installComponent(componentName, options = {}) {
|
|
|
1263
1342
|
function detectProjectType() {
|
|
1264
1343
|
// Check for Next.js
|
|
1265
1344
|
if (fs.existsSync(path.join(process.cwd(), 'next.config.js')) ||
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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'))) {
|
|
1270
1349
|
return 'nextjs';
|
|
1271
1350
|
}
|
|
1272
|
-
|
|
1351
|
+
|
|
1273
1352
|
// Check for Vite
|
|
1274
1353
|
if (fs.existsSync(path.join(process.cwd(), 'vite.config.js')) ||
|
|
1275
|
-
|
|
1276
|
-
|
|
1354
|
+
fs.existsSync(path.join(process.cwd(), 'vite.config.ts')) ||
|
|
1355
|
+
fs.existsSync(path.join(process.cwd(), 'vite.config.mjs'))) {
|
|
1277
1356
|
return 'vite';
|
|
1278
1357
|
}
|
|
1279
|
-
|
|
1358
|
+
|
|
1280
1359
|
return null;
|
|
1281
1360
|
}
|
|
1282
1361
|
|
|
@@ -1293,11 +1372,11 @@ function prompt(question, defaultValue = '') {
|
|
|
1293
1372
|
input: process.stdin,
|
|
1294
1373
|
output: process.stdout
|
|
1295
1374
|
});
|
|
1296
|
-
|
|
1297
|
-
const promptText = defaultValue
|
|
1375
|
+
|
|
1376
|
+
const promptText = defaultValue
|
|
1298
1377
|
? `${question} āŗ ${defaultValue} `
|
|
1299
1378
|
: `${question} āŗ `;
|
|
1300
|
-
|
|
1379
|
+
|
|
1301
1380
|
rl.question(promptText, (answer) => {
|
|
1302
1381
|
rl.close();
|
|
1303
1382
|
resolve(answer.trim() || defaultValue);
|
|
@@ -1306,73 +1385,439 @@ function prompt(question, defaultValue = '') {
|
|
|
1306
1385
|
}
|
|
1307
1386
|
|
|
1308
1387
|
/**
|
|
1309
|
-
* Initializes the project with
|
|
1310
|
-
* Similar to shadcn/ui init command
|
|
1388
|
+
* Initializes the project with interactive prompts
|
|
1389
|
+
* Similar to shadcn/ui init command but with custom theme selection
|
|
1311
1390
|
*/
|
|
1312
1391
|
async function initProject() {
|
|
1313
1392
|
info('Initializing project...\n');
|
|
1314
|
-
|
|
1393
|
+
|
|
1315
1394
|
// Check if components.json already exists
|
|
1316
|
-
const configPath = path.join(process.cwd(),
|
|
1395
|
+
const configPath = path.join(process.cwd(), PATHS.COMPONENTS_JSON);
|
|
1317
1396
|
if (fs.existsSync(configPath)) {
|
|
1318
|
-
warn(
|
|
1397
|
+
warn(`${PATHS.COMPONENTS_JSON} already exists.`);
|
|
1319
1398
|
const overwrite = await prompt('Do you want to overwrite it? (y/N)', 'N');
|
|
1320
1399
|
if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
|
|
1321
1400
|
info('Skipping initialization.');
|
|
1322
1401
|
return;
|
|
1323
1402
|
}
|
|
1324
1403
|
}
|
|
1325
|
-
|
|
1326
|
-
//
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
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;
|
|
1334
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: {} };
|
|
1335
1756
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
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;
|
|
1346
1797
|
if (isVite) {
|
|
1347
|
-
cssPath =
|
|
1798
|
+
cssPath = PATHS.VITE_INDEX_CSS;
|
|
1348
1799
|
} else if (fs.existsSync(path.join(process.cwd(), 'app', 'globals.css'))) {
|
|
1349
|
-
cssPath =
|
|
1800
|
+
cssPath = PATHS.APP_GLOBALS_CSS;
|
|
1350
1801
|
} else if (fs.existsSync(path.join(process.cwd(), 'styles', 'globals.css'))) {
|
|
1351
|
-
cssPath =
|
|
1802
|
+
cssPath = PATHS.STYLES_GLOBALS_CSS;
|
|
1352
1803
|
} else {
|
|
1353
1804
|
cssPath = await prompt('Where is your global CSS file?', cssPath);
|
|
1354
1805
|
}
|
|
1355
|
-
|
|
1356
|
-
//
|
|
1357
|
-
const baseColor = await prompt('Which color would you like to use as base color?', 'neutral');
|
|
1358
|
-
|
|
1359
|
-
// Ask for style
|
|
1360
|
-
const style = await prompt('Which style would you like to use?', 'blue');
|
|
1361
|
-
|
|
1362
|
-
// Create components.json
|
|
1806
|
+
|
|
1807
|
+
// Create components.json with selected theme
|
|
1363
1808
|
const config = {
|
|
1364
|
-
$schema:
|
|
1365
|
-
style:
|
|
1809
|
+
$schema: DEFAULTS.SCHEMA_URL,
|
|
1810
|
+
style: selectedTheme,
|
|
1366
1811
|
rsc: isNextjs,
|
|
1367
1812
|
tsx: true,
|
|
1368
1813
|
tailwind: {
|
|
1369
1814
|
config: 'tailwind.config.ts',
|
|
1370
1815
|
css: cssPath,
|
|
1371
|
-
baseColor:
|
|
1816
|
+
baseColor: 'neutral',
|
|
1372
1817
|
cssVariables: true,
|
|
1373
1818
|
prefix: ''
|
|
1374
1819
|
},
|
|
1375
|
-
iconLibrary:
|
|
1820
|
+
iconLibrary: DEFAULTS.ICON_LIBRARY,
|
|
1376
1821
|
aliases: {
|
|
1377
1822
|
components: '@/components',
|
|
1378
1823
|
utils: '@/lib/utils',
|
|
@@ -1380,35 +1825,53 @@ async function initProject() {
|
|
|
1380
1825
|
lib: '@/lib',
|
|
1381
1826
|
hooks: '@/hooks'
|
|
1382
1827
|
},
|
|
1383
|
-
registry:
|
|
1828
|
+
registry: REGISTRY_URLS.INDEX,
|
|
1829
|
+
projectName: projectName,
|
|
1830
|
+
radius: selectedRadius
|
|
1384
1831
|
};
|
|
1385
|
-
|
|
1832
|
+
|
|
1833
|
+
// Show setup progress
|
|
1834
|
+
info('\nš Setting up your project...');
|
|
1835
|
+
|
|
1836
|
+
// Step 1: Create components.json
|
|
1837
|
+
info(' ā [:root]/components.json');
|
|
1386
1838
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
const baseDeps = ['clsx', 'tailwind-merge'];
|
|
1392
|
-
const missingBaseDeps = baseDeps.filter(dep => !checkPackageInstalled(dep));
|
|
1839
|
+
|
|
1840
|
+
// Step 2: Install dependencies
|
|
1841
|
+
info(' ā Installing dependencies');
|
|
1842
|
+
const missingBaseDeps = BASE_DEPENDENCIES.filter(dep => !checkPackageInstalled(dep));
|
|
1393
1843
|
|
|
1394
1844
|
if (missingBaseDeps.length > 0) {
|
|
1395
1845
|
await installPackages(missingBaseDeps);
|
|
1396
|
-
} else {
|
|
1397
|
-
info('Base dependencies already installed.');
|
|
1398
1846
|
}
|
|
1399
|
-
|
|
1400
|
-
// Install
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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);
|
|
1404
1859
|
}
|
|
1405
|
-
|
|
1406
|
-
// Install
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
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:');
|
|
1412
1875
|
info(' npx weloop-kosign@latest add <component-name>');
|
|
1413
1876
|
info(' pnpm dlx weloop-kosign@latest add <component-name>');
|
|
1414
1877
|
info(' yarn weloop-kosign@latest add <component-name>\n');
|
|
@@ -1427,7 +1890,7 @@ async function initProject() {
|
|
|
1427
1890
|
async function listComponents(registryUrl = DEFAULT_REGISTRY_URL) {
|
|
1428
1891
|
try {
|
|
1429
1892
|
const index = await loadIndex(registryUrl);
|
|
1430
|
-
|
|
1893
|
+
|
|
1431
1894
|
console.log('\nAvailable components:\n');
|
|
1432
1895
|
index.registry.forEach(comp => {
|
|
1433
1896
|
// Show dependencies if component has any
|
|
@@ -1463,13 +1926,13 @@ async function main() {
|
|
|
1463
1926
|
const args = process.argv.slice(2);
|
|
1464
1927
|
const command = args[0];
|
|
1465
1928
|
const componentName = args[1];
|
|
1466
|
-
|
|
1929
|
+
|
|
1467
1930
|
// Parse --registry option if provided
|
|
1468
1931
|
const registryIndex = args.indexOf('--registry');
|
|
1469
1932
|
const registryUrl = registryIndex !== -1 && args[registryIndex + 1]
|
|
1470
1933
|
? args[registryIndex + 1]
|
|
1471
1934
|
: DEFAULT_REGISTRY_URL;
|
|
1472
|
-
|
|
1935
|
+
|
|
1473
1936
|
// Build options object
|
|
1474
1937
|
const options = {
|
|
1475
1938
|
overwrite: args.includes('--overwrite') || args.includes('-f'),
|
|
@@ -1505,7 +1968,7 @@ Examples:
|
|
|
1505
1968
|
case 'init':
|
|
1506
1969
|
await initProject();
|
|
1507
1970
|
break;
|
|
1508
|
-
|
|
1971
|
+
|
|
1509
1972
|
case 'add':
|
|
1510
1973
|
if (!componentName) {
|
|
1511
1974
|
error('Please provide a component name');
|
|
@@ -1514,18 +1977,18 @@ Examples:
|
|
|
1514
1977
|
}
|
|
1515
1978
|
await installComponent(componentName, options);
|
|
1516
1979
|
break;
|
|
1517
|
-
|
|
1980
|
+
|
|
1518
1981
|
case 'list':
|
|
1519
1982
|
await listComponents(registryUrl);
|
|
1520
1983
|
break;
|
|
1521
|
-
|
|
1984
|
+
|
|
1522
1985
|
case 'css':
|
|
1523
1986
|
case 'styles':
|
|
1524
1987
|
// Both 'css' and 'styles' commands do the same thing
|
|
1525
1988
|
const config = loadComponentsConfig();
|
|
1526
1989
|
await installCSSStyles(config, registryUrl, options.overwrite, false);
|
|
1527
1990
|
break;
|
|
1528
|
-
|
|
1991
|
+
|
|
1529
1992
|
default:
|
|
1530
1993
|
error(`Unknown command: ${command}`);
|
|
1531
1994
|
console.log('Available commands: init, add, list, css');
|