weloop-kosign 1.1.1 ā 1.1.3
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/package.json +1 -1
- package/scripts/cli-remote.js +543 -217
package/scripts/cli-remote.js
CHANGED
|
@@ -8,16 +8,19 @@
|
|
|
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
|
|
11
12
|
* npx weloop-kosign@latest add <component-name>
|
|
12
13
|
* npx weloop-kosign@latest list
|
|
13
14
|
* npx weloop-kosign@latest css [--overwrite]
|
|
14
15
|
*
|
|
15
16
|
* Usage (local development):
|
|
17
|
+
* node scripts/cli-remote.js init [--registry <url|path>]
|
|
16
18
|
* node scripts/cli-remote.js add <component-name> [--registry <url|path>]
|
|
17
19
|
* node scripts/cli-remote.js list [--registry <url|path>]
|
|
18
20
|
* node scripts/cli-remote.js css [--registry <url|path>] [--overwrite]
|
|
19
21
|
*
|
|
20
22
|
* Examples:
|
|
23
|
+
* npx weloop-kosign@latest init
|
|
21
24
|
* npx weloop-kosign@latest add button
|
|
22
25
|
* npx weloop-kosign@latest add button --registry ./registry
|
|
23
26
|
* npx weloop-kosign@latest css
|
|
@@ -45,22 +48,81 @@ const { execSync } = require('child_process');
|
|
|
45
48
|
*/
|
|
46
49
|
function getDefaultRegistryUrl() {
|
|
47
50
|
const localRegistryPath = path.join(__dirname, '../registry');
|
|
48
|
-
|
|
51
|
+
|
|
49
52
|
// Check if we're running in the component library project itself
|
|
50
53
|
if (fs.existsSync(localRegistryPath) && fs.existsSync(path.join(localRegistryPath, 'index.json'))) {
|
|
51
54
|
return localRegistryPath;
|
|
52
55
|
}
|
|
53
|
-
|
|
56
|
+
|
|
54
57
|
// Fall back to environment variable or default remote URL
|
|
55
|
-
return process.env.WELOOP_REGISTRY_URL ||
|
|
58
|
+
return process.env.WELOOP_REGISTRY_URL ||
|
|
56
59
|
'https://gitlab.com/Sophanithchrek/weloop-shadcn-next-app/-/raw/main/registry';
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
const DEFAULT_REGISTRY_URL = getDefaultRegistryUrl();
|
|
60
63
|
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// CONSTANTS
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
61
68
|
// Network timeout constants (in milliseconds)
|
|
62
69
|
const REQUEST_TIMEOUT = 15000;
|
|
63
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
|
+
];
|
|
64
126
|
|
|
65
127
|
// ============================================================================
|
|
66
128
|
// UTILITIES - Logging and File System Helpers
|
|
@@ -127,6 +189,38 @@ function ensureDirectoryExists(dirPath) {
|
|
|
127
189
|
}
|
|
128
190
|
}
|
|
129
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
|
+
|
|
130
224
|
/**
|
|
131
225
|
* Checks if a path is a local file path or a remote URL
|
|
132
226
|
* @param {string} pathOrUrl - Path or URL to check
|
|
@@ -155,12 +249,12 @@ function detectPackageManager() {
|
|
|
155
249
|
if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) return 'yarn';
|
|
156
250
|
if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) return 'pnpm';
|
|
157
251
|
if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) return 'npm';
|
|
158
|
-
|
|
252
|
+
|
|
159
253
|
// Fallback: check npm user agent (set by npm/yarn/pnpm when running)
|
|
160
254
|
const userAgent = process.env.npm_config_user_agent || '';
|
|
161
255
|
if (userAgent.includes('yarn')) return 'yarn';
|
|
162
256
|
if (userAgent.includes('pnpm')) return 'pnpm';
|
|
163
|
-
|
|
257
|
+
|
|
164
258
|
// Default to npm if we can't detect anything
|
|
165
259
|
return 'npm';
|
|
166
260
|
}
|
|
@@ -188,9 +282,9 @@ function getInstallCommand(packageManager, packages) {
|
|
|
188
282
|
* @returns {boolean} True if package is installed, false otherwise
|
|
189
283
|
*/
|
|
190
284
|
function checkPackageInstalled(packageName) {
|
|
191
|
-
const packageJsonPath = path.join(process.cwd(),
|
|
285
|
+
const packageJsonPath = path.join(process.cwd(), PATHS.PACKAGE_JSON);
|
|
192
286
|
if (!fs.existsSync(packageJsonPath)) return false;
|
|
193
|
-
|
|
287
|
+
|
|
194
288
|
try {
|
|
195
289
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
196
290
|
// Check both dependencies and devDependencies
|
|
@@ -209,16 +303,16 @@ function checkPackageInstalled(packageName) {
|
|
|
209
303
|
* @returns {string[]} List of packages that need to be installed
|
|
210
304
|
*/
|
|
211
305
|
function getMissingDependencies(requiredDeps) {
|
|
212
|
-
const packageJsonPath = path.join(process.cwd(),
|
|
306
|
+
const packageJsonPath = path.join(process.cwd(), PATHS.PACKAGE_JSON);
|
|
213
307
|
if (!fs.existsSync(packageJsonPath)) {
|
|
214
308
|
// No package.json means we need to install everything
|
|
215
309
|
return requiredDeps;
|
|
216
310
|
}
|
|
217
|
-
|
|
311
|
+
|
|
218
312
|
try {
|
|
219
313
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
220
314
|
const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
221
|
-
|
|
315
|
+
|
|
222
316
|
return requiredDeps.filter(dep => {
|
|
223
317
|
// For scoped packages like @radix-ui/react-button, also check @radix-ui
|
|
224
318
|
const depName = dep.split('/').slice(0, 2).join('/');
|
|
@@ -240,44 +334,44 @@ function getMissingDependencies(requiredDeps) {
|
|
|
240
334
|
*/
|
|
241
335
|
async function installPackages(packages) {
|
|
242
336
|
if (packages.length === 0) return;
|
|
243
|
-
|
|
337
|
+
|
|
244
338
|
const packageManager = detectPackageManager();
|
|
245
339
|
info(`\nInstalling dependencies: ${packages.join(', ')}`);
|
|
246
|
-
|
|
340
|
+
|
|
247
341
|
try {
|
|
248
342
|
const installCmd = getInstallCommand(packageManager, packages);
|
|
249
|
-
|
|
343
|
+
|
|
250
344
|
// npm has noisy ERESOLVE warnings that aren't real errors
|
|
251
345
|
// We filter these out while keeping actual error messages
|
|
252
346
|
if (packageManager === 'npm') {
|
|
253
347
|
const { spawn } = require('child_process');
|
|
254
348
|
const [cmd, ...args] = installCmd.split(' ');
|
|
255
|
-
|
|
349
|
+
|
|
256
350
|
await new Promise((resolve, reject) => {
|
|
257
351
|
const proc = spawn(cmd, args, {
|
|
258
352
|
cwd: process.cwd(),
|
|
259
353
|
stdio: ['inherit', 'inherit', 'pipe'],
|
|
260
354
|
shell: true
|
|
261
355
|
});
|
|
262
|
-
|
|
356
|
+
|
|
263
357
|
let stderr = '';
|
|
264
358
|
proc.stderr.on('data', (data) => {
|
|
265
359
|
const output = data.toString();
|
|
266
360
|
// Filter out ERESOLVE peer dependency warnings (these are usually safe to ignore)
|
|
267
361
|
// but keep actual error messages visible to the user
|
|
268
|
-
if (!output.includes('ERESOLVE overriding peer dependency') &&
|
|
269
|
-
|
|
362
|
+
if (!output.includes('ERESOLVE overriding peer dependency') &&
|
|
363
|
+
!output.includes('npm warn ERESOLVE')) {
|
|
270
364
|
process.stderr.write(data);
|
|
271
365
|
}
|
|
272
366
|
stderr += output;
|
|
273
367
|
});
|
|
274
|
-
|
|
368
|
+
|
|
275
369
|
proc.on('close', (code) => {
|
|
276
370
|
if (code !== 0) {
|
|
277
371
|
// npm sometimes exits with non-zero code due to warnings, not real errors
|
|
278
372
|
// Check if there's an actual error message (not just ERESOLVE warnings)
|
|
279
|
-
const hasRealError = stderr.includes('npm error') &&
|
|
280
|
-
|
|
373
|
+
const hasRealError = stderr.includes('npm error') &&
|
|
374
|
+
!stderr.match(/npm error.*ERESOLVE/);
|
|
281
375
|
if (hasRealError) {
|
|
282
376
|
reject(new Error(`Installation failed with code ${code}`));
|
|
283
377
|
} else {
|
|
@@ -288,13 +382,13 @@ async function installPackages(packages) {
|
|
|
288
382
|
resolve();
|
|
289
383
|
}
|
|
290
384
|
});
|
|
291
|
-
|
|
385
|
+
|
|
292
386
|
proc.on('error', reject);
|
|
293
387
|
});
|
|
294
388
|
} else {
|
|
295
389
|
execSync(installCmd, { stdio: 'inherit', cwd: process.cwd() });
|
|
296
390
|
}
|
|
297
|
-
|
|
391
|
+
|
|
298
392
|
success(`Dependencies installed successfully`);
|
|
299
393
|
} catch (error) {
|
|
300
394
|
warn(`Failed to install dependencies automatically`);
|
|
@@ -316,25 +410,25 @@ async function installPackages(packages) {
|
|
|
316
410
|
* @returns {object} The default configuration object
|
|
317
411
|
*/
|
|
318
412
|
function createDefaultComponentsConfig() {
|
|
319
|
-
const configPath = path.join(process.cwd(),
|
|
320
|
-
|
|
413
|
+
const configPath = path.join(process.cwd(), PATHS.COMPONENTS_JSON);
|
|
414
|
+
|
|
321
415
|
// Detect Next.js project structure
|
|
322
416
|
// App Router uses 'app/globals.css', Pages Router uses 'styles/globals.css'
|
|
323
417
|
const hasAppDir = fs.existsSync(path.join(process.cwd(), 'app'));
|
|
324
|
-
const cssPath = hasAppDir ?
|
|
325
|
-
|
|
418
|
+
const cssPath = hasAppDir ? PATHS.APP_GLOBALS_CSS : PATHS.STYLES_GLOBALS_CSS;
|
|
419
|
+
|
|
326
420
|
const defaultConfig = {
|
|
327
|
-
style:
|
|
421
|
+
style: DEFAULTS.STYLE,
|
|
328
422
|
rsc: true,
|
|
329
423
|
tsx: true,
|
|
330
424
|
tailwind: {
|
|
331
425
|
config: 'tailwind.config.ts',
|
|
332
426
|
css: cssPath,
|
|
333
|
-
baseColor:
|
|
427
|
+
baseColor: DEFAULTS.BASE_COLOR,
|
|
334
428
|
cssVariables: true,
|
|
335
429
|
prefix: ''
|
|
336
430
|
},
|
|
337
|
-
iconLibrary:
|
|
431
|
+
iconLibrary: DEFAULTS.ICON_LIBRARY,
|
|
338
432
|
aliases: {
|
|
339
433
|
components: '@/components',
|
|
340
434
|
utils: '@/lib/utils',
|
|
@@ -342,13 +436,13 @@ function createDefaultComponentsConfig() {
|
|
|
342
436
|
lib: '@/lib',
|
|
343
437
|
hooks: '@/hooks'
|
|
344
438
|
},
|
|
345
|
-
registry:
|
|
439
|
+
registry: REGISTRY_URLS.INDEX
|
|
346
440
|
};
|
|
347
|
-
|
|
441
|
+
|
|
348
442
|
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
|
|
349
|
-
success(`Created
|
|
443
|
+
success(`Created ${PATHS.COMPONENTS_JSON}`);
|
|
350
444
|
info(` You can customize this file to match your project setup`);
|
|
351
|
-
|
|
445
|
+
|
|
352
446
|
return defaultConfig;
|
|
353
447
|
}
|
|
354
448
|
|
|
@@ -359,15 +453,20 @@ function createDefaultComponentsConfig() {
|
|
|
359
453
|
* @returns {object} The configuration object
|
|
360
454
|
*/
|
|
361
455
|
function loadComponentsConfig() {
|
|
362
|
-
const configPath = path.join(process.cwd(),
|
|
363
|
-
|
|
456
|
+
const configPath = path.join(process.cwd(), PATHS.COMPONENTS_JSON);
|
|
457
|
+
|
|
364
458
|
if (!fs.existsSync(configPath)) {
|
|
365
|
-
info(
|
|
459
|
+
info(`${PATHS.COMPONENTS_JSON} not found. Creating default configuration...`);
|
|
366
460
|
return createDefaultComponentsConfig();
|
|
367
461
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
+
}
|
|
371
470
|
}
|
|
372
471
|
|
|
373
472
|
/**
|
|
@@ -383,21 +482,21 @@ function loadComponentsConfig() {
|
|
|
383
482
|
* @param {number} retries - Number of retry attempts (default: 3)
|
|
384
483
|
* @returns {Promise<object>} Parsed JSON object
|
|
385
484
|
*/
|
|
386
|
-
async function fetchJSON(urlOrPath, retries =
|
|
485
|
+
async function fetchJSON(urlOrPath, retries = MAX_RETRIES) {
|
|
387
486
|
// Handle local file paths
|
|
388
487
|
if (isLocalPath(urlOrPath)) {
|
|
389
488
|
return new Promise((resolve, reject) => {
|
|
390
489
|
try {
|
|
391
490
|
// Resolve relative paths to absolute
|
|
392
|
-
const fullPath = path.isAbsolute(urlOrPath)
|
|
393
|
-
? urlOrPath
|
|
491
|
+
const fullPath = path.isAbsolute(urlOrPath)
|
|
492
|
+
? urlOrPath
|
|
394
493
|
: path.join(process.cwd(), urlOrPath);
|
|
395
|
-
|
|
494
|
+
|
|
396
495
|
if (!fs.existsSync(fullPath)) {
|
|
397
496
|
reject(new Error(`File not found: ${fullPath}`));
|
|
398
497
|
return;
|
|
399
498
|
}
|
|
400
|
-
|
|
499
|
+
|
|
401
500
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
402
501
|
resolve(JSON.parse(content));
|
|
403
502
|
} catch (e) {
|
|
@@ -405,38 +504,38 @@ async function fetchJSON(urlOrPath, retries = 3) {
|
|
|
405
504
|
}
|
|
406
505
|
});
|
|
407
506
|
}
|
|
408
|
-
|
|
507
|
+
|
|
409
508
|
// Handle remote URLs
|
|
410
509
|
return new Promise((resolve, reject) => {
|
|
411
510
|
const client = urlOrPath.startsWith('https') ? https : http;
|
|
412
511
|
let timeout;
|
|
413
512
|
let request;
|
|
414
|
-
|
|
513
|
+
|
|
415
514
|
const makeRequest = () => {
|
|
416
515
|
request = client.get(urlOrPath, (res) => {
|
|
417
516
|
clearTimeout(timeout);
|
|
418
|
-
|
|
517
|
+
|
|
419
518
|
// Handle HTTP redirects (301, 302)
|
|
420
519
|
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
421
520
|
return fetchJSON(res.headers.location, retries).then(resolve).catch(reject);
|
|
422
521
|
}
|
|
423
|
-
|
|
522
|
+
|
|
424
523
|
// Provide helpful error messages for common HTTP status codes
|
|
425
524
|
if (res.statusCode === 403) {
|
|
426
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.`));
|
|
427
526
|
return;
|
|
428
527
|
}
|
|
429
|
-
|
|
528
|
+
|
|
430
529
|
if (res.statusCode === 404) {
|
|
431
530
|
reject(new Error(`Not found (404). Check that the registry files are pushed to the repository and the URL is correct.`));
|
|
432
531
|
return;
|
|
433
532
|
}
|
|
434
|
-
|
|
533
|
+
|
|
435
534
|
if (res.statusCode !== 200) {
|
|
436
535
|
reject(new Error(`Failed to fetch: HTTP ${res.statusCode} - ${res.statusMessage || 'Unknown error'}`));
|
|
437
536
|
return;
|
|
438
537
|
}
|
|
439
|
-
|
|
538
|
+
|
|
440
539
|
// Collect response data
|
|
441
540
|
let data = '';
|
|
442
541
|
res.on('data', (chunk) => { data += chunk; });
|
|
@@ -453,7 +552,7 @@ async function fetchJSON(urlOrPath, retries = 3) {
|
|
|
453
552
|
}
|
|
454
553
|
});
|
|
455
554
|
});
|
|
456
|
-
|
|
555
|
+
|
|
457
556
|
// Handle network errors with automatic retry
|
|
458
557
|
request.on('error', (err) => {
|
|
459
558
|
clearTimeout(timeout);
|
|
@@ -466,7 +565,7 @@ async function fetchJSON(urlOrPath, retries = 3) {
|
|
|
466
565
|
reject(new Error(`Network error: ${err.message} (${err.code || 'UNKNOWN'})`));
|
|
467
566
|
}
|
|
468
567
|
});
|
|
469
|
-
|
|
568
|
+
|
|
470
569
|
// Set request timeout with retry logic
|
|
471
570
|
timeout = setTimeout(() => {
|
|
472
571
|
request.destroy();
|
|
@@ -480,7 +579,7 @@ async function fetchJSON(urlOrPath, retries = 3) {
|
|
|
480
579
|
}
|
|
481
580
|
}, REQUEST_TIMEOUT);
|
|
482
581
|
};
|
|
483
|
-
|
|
582
|
+
|
|
484
583
|
makeRequest();
|
|
485
584
|
});
|
|
486
585
|
}
|
|
@@ -500,34 +599,34 @@ async function fetchText(urlOrPath) {
|
|
|
500
599
|
}
|
|
501
600
|
return fs.readFileSync(urlOrPath, 'utf-8');
|
|
502
601
|
}
|
|
503
|
-
|
|
602
|
+
|
|
504
603
|
// Handle remote URLs
|
|
505
604
|
return new Promise((resolve, reject) => {
|
|
506
605
|
const client = urlOrPath.startsWith('https') ? https : http;
|
|
507
606
|
let data = '';
|
|
508
|
-
|
|
607
|
+
|
|
509
608
|
const req = client.get(urlOrPath, (res) => {
|
|
510
609
|
// Follow redirects
|
|
511
610
|
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
512
611
|
return fetchText(res.headers.location).then(resolve).catch(reject);
|
|
513
612
|
}
|
|
514
|
-
|
|
613
|
+
|
|
515
614
|
// Handle common HTTP errors
|
|
516
615
|
if (res.statusCode === 403) {
|
|
517
616
|
reject(new Error('Access forbidden - repository may be private'));
|
|
518
617
|
return;
|
|
519
618
|
}
|
|
520
|
-
|
|
619
|
+
|
|
521
620
|
if (res.statusCode === 404) {
|
|
522
621
|
reject(new Error('CSS file not found in repository'));
|
|
523
622
|
return;
|
|
524
623
|
}
|
|
525
|
-
|
|
624
|
+
|
|
526
625
|
if (res.statusCode !== 200) {
|
|
527
626
|
reject(new Error(`HTTP ${res.statusCode}`));
|
|
528
627
|
return;
|
|
529
628
|
}
|
|
530
|
-
|
|
629
|
+
|
|
531
630
|
// Collect response data
|
|
532
631
|
res.on('data', (chunk) => { data += chunk; });
|
|
533
632
|
res.on('end', () => {
|
|
@@ -539,7 +638,7 @@ async function fetchText(urlOrPath) {
|
|
|
539
638
|
resolve(data);
|
|
540
639
|
});
|
|
541
640
|
});
|
|
542
|
-
|
|
641
|
+
|
|
543
642
|
// Handle network errors with automatic retry
|
|
544
643
|
req.on('error', (err) => {
|
|
545
644
|
if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') {
|
|
@@ -550,7 +649,7 @@ async function fetchText(urlOrPath) {
|
|
|
550
649
|
reject(err);
|
|
551
650
|
}
|
|
552
651
|
});
|
|
553
|
-
|
|
652
|
+
|
|
554
653
|
// Set request timeout
|
|
555
654
|
req.setTimeout(REQUEST_TIMEOUT, () => {
|
|
556
655
|
req.destroy();
|
|
@@ -570,13 +669,14 @@ async function fetchText(urlOrPath) {
|
|
|
570
669
|
function resolveRegistryPath(baseUrl, fileName) {
|
|
571
670
|
if (isLocalPath(baseUrl)) {
|
|
572
671
|
// Resolve local paths (absolute or relative)
|
|
573
|
-
const basePath = path.isAbsolute(baseUrl)
|
|
574
|
-
? baseUrl
|
|
672
|
+
const basePath = path.isAbsolute(baseUrl)
|
|
673
|
+
? baseUrl
|
|
575
674
|
: path.join(process.cwd(), baseUrl);
|
|
576
675
|
return path.join(basePath, fileName);
|
|
577
676
|
}
|
|
578
|
-
// For remote URLs,
|
|
579
|
-
|
|
677
|
+
// For remote URLs, append filename with proper separator
|
|
678
|
+
const separator = baseUrl.endsWith('/') ? '' : '/';
|
|
679
|
+
return `${baseUrl}${separator}${fileName}`;
|
|
580
680
|
}
|
|
581
681
|
|
|
582
682
|
/**
|
|
@@ -593,6 +693,7 @@ async function loadRegistry(componentName, registryBaseUrl) {
|
|
|
593
693
|
return await fetchJSON(registryPath);
|
|
594
694
|
} catch (err) {
|
|
595
695
|
// Component not found - return null instead of throwing
|
|
696
|
+
// This allows the caller to handle missing components gracefully
|
|
596
697
|
return null;
|
|
597
698
|
}
|
|
598
699
|
}
|
|
@@ -605,7 +706,7 @@ async function loadRegistry(componentName, registryBaseUrl) {
|
|
|
605
706
|
* @returns {Promise<object>} Index object containing list of components
|
|
606
707
|
*/
|
|
607
708
|
async function loadIndex(registryBaseUrl) {
|
|
608
|
-
const indexPath = resolveRegistryPath(registryBaseUrl,
|
|
709
|
+
const indexPath = resolveRegistryPath(registryBaseUrl, PATHS.REGISTRY_INDEX);
|
|
609
710
|
try {
|
|
610
711
|
return await fetchJSON(indexPath);
|
|
611
712
|
} catch (err) {
|
|
@@ -626,12 +727,18 @@ async function loadIndex(registryBaseUrl) {
|
|
|
626
727
|
* @returns {boolean} True if Weloop styles are present
|
|
627
728
|
*/
|
|
628
729
|
function hasWeloopStyles(content) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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');
|
|
635
742
|
}
|
|
636
743
|
|
|
637
744
|
/**
|
|
@@ -642,16 +749,15 @@ function hasWeloopStyles(content) {
|
|
|
642
749
|
* @returns {string} CSS content with duplicates removed
|
|
643
750
|
*/
|
|
644
751
|
function removeDuplicateTwAnimateImports(cssContent) {
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
752
|
+
const matches = cssContent.match(CSS_PATTERNS.TW_ANIMATE_IMPORT);
|
|
753
|
+
|
|
648
754
|
if (matches && matches.length > 1) {
|
|
649
755
|
// Remove all occurrences first
|
|
650
|
-
let cleaned = cssContent.replace(
|
|
756
|
+
let cleaned = cssContent.replace(CSS_PATTERNS.TW_ANIMATE_IMPORT, '');
|
|
651
757
|
// Add it back once, right after tailwindcss import if it exists
|
|
652
|
-
if (
|
|
758
|
+
if (hasTailwindImport(cleaned)) {
|
|
653
759
|
cleaned = cleaned.replace(
|
|
654
|
-
|
|
760
|
+
CSS_PATTERNS.TAILWIND_IMPORT,
|
|
655
761
|
'$1@import "tw-animate-css";\n'
|
|
656
762
|
);
|
|
657
763
|
} else {
|
|
@@ -660,7 +766,7 @@ function removeDuplicateTwAnimateImports(cssContent) {
|
|
|
660
766
|
}
|
|
661
767
|
return cleaned;
|
|
662
768
|
}
|
|
663
|
-
|
|
769
|
+
|
|
664
770
|
return cssContent;
|
|
665
771
|
}
|
|
666
772
|
|
|
@@ -672,12 +778,11 @@ function removeDuplicateTwAnimateImports(cssContent) {
|
|
|
672
778
|
* @returns {string} CSS content with duplicates removed
|
|
673
779
|
*/
|
|
674
780
|
function removeDuplicateCustomVariant(cssContent) {
|
|
675
|
-
const
|
|
676
|
-
const matches = cssContent.match(variantPattern);
|
|
781
|
+
const matches = cssContent.match(CSS_PATTERNS.CUSTOM_VARIANT);
|
|
677
782
|
|
|
678
783
|
if (matches && matches.length > 1) {
|
|
679
784
|
// Remove all occurrences and add back one at the top
|
|
680
|
-
const withoutVariants = cssContent.replace(
|
|
785
|
+
const withoutVariants = cssContent.replace(CSS_PATTERNS.CUSTOM_VARIANT, '');
|
|
681
786
|
return `@custom-variant dark (&:is(.dark *));\n\n${withoutVariants.trimStart()}`;
|
|
682
787
|
}
|
|
683
788
|
|
|
@@ -700,30 +805,28 @@ function removeDuplicateCustomVariant(cssContent) {
|
|
|
700
805
|
*/
|
|
701
806
|
function normalizeCSSFormat(cssContent) {
|
|
702
807
|
// Extract @custom-variant declaration
|
|
703
|
-
const
|
|
704
|
-
const variantMatch = cssContent.match(variantPattern);
|
|
808
|
+
const variantMatch = cssContent.match(CSS_PATTERNS.CUSTOM_VARIANT);
|
|
705
809
|
const hasVariant = variantMatch && variantMatch.length > 0;
|
|
706
|
-
|
|
810
|
+
|
|
707
811
|
// Extract all @import statements
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
812
|
+
const imports = cssContent.match(CSS_PATTERNS.IMPORT_STATEMENT) || [];
|
|
813
|
+
|
|
711
814
|
// Separate imports by type for proper ordering
|
|
712
815
|
const tailwindImport = imports.find(imp => imp.includes('tailwindcss'));
|
|
713
816
|
const twAnimateImport = imports.find(imp => imp.includes('tw-animate-css'));
|
|
714
|
-
const otherImports = imports.filter(imp =>
|
|
817
|
+
const otherImports = imports.filter(imp =>
|
|
715
818
|
!imp.includes('tailwindcss') && !imp.includes('tw-animate-css')
|
|
716
819
|
);
|
|
717
|
-
|
|
820
|
+
|
|
718
821
|
// Remove all imports and variant from content to get the rest
|
|
719
822
|
let content = cssContent
|
|
720
|
-
.replace(
|
|
721
|
-
.replace(
|
|
823
|
+
.replace(CSS_PATTERNS.CUSTOM_VARIANT, '')
|
|
824
|
+
.replace(CSS_PATTERNS.IMPORT_STATEMENT, '')
|
|
722
825
|
.trim();
|
|
723
|
-
|
|
826
|
+
|
|
724
827
|
// Build normalized format in the correct order
|
|
725
828
|
let normalized = '';
|
|
726
|
-
|
|
829
|
+
|
|
727
830
|
// Step 1: Imports first (tailwindcss, then tw-animate-css, then others)
|
|
728
831
|
if (tailwindImport) {
|
|
729
832
|
normalized += tailwindImport.trim() + '\n';
|
|
@@ -734,23 +837,19 @@ function normalizeCSSFormat(cssContent) {
|
|
|
734
837
|
if (otherImports.length > 0) {
|
|
735
838
|
normalized += otherImports.join('') + '\n';
|
|
736
839
|
}
|
|
737
|
-
|
|
840
|
+
|
|
738
841
|
// Step 2: @custom-variant second (if it exists)
|
|
739
842
|
if (hasVariant) {
|
|
740
|
-
|
|
741
|
-
normalized += '\n@custom-variant dark (&:is(.dark *));\n';
|
|
742
|
-
} else {
|
|
743
|
-
normalized += '@custom-variant dark (&:is(.dark *));\n';
|
|
744
|
-
}
|
|
843
|
+
normalized += normalized ? '\n@custom-variant dark (&:is(.dark *));\n' : '@custom-variant dark (&:is(.dark *));\n';
|
|
745
844
|
}
|
|
746
|
-
|
|
845
|
+
|
|
747
846
|
// Step 3: Rest of content (theme variables, etc.)
|
|
748
847
|
if (normalized && content) {
|
|
749
848
|
normalized += '\n' + content;
|
|
750
849
|
} else if (content) {
|
|
751
850
|
normalized = content;
|
|
752
851
|
}
|
|
753
|
-
|
|
852
|
+
|
|
754
853
|
return normalized.trim() + (normalized ? '\n' : '');
|
|
755
854
|
}
|
|
756
855
|
|
|
@@ -766,32 +865,32 @@ function normalizeCSSFormat(cssContent) {
|
|
|
766
865
|
* @returns {string} Processed CSS content
|
|
767
866
|
*/
|
|
768
867
|
function processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate = false) {
|
|
769
|
-
const twAnimatePattern = /@import\s+["']tw-animate-css["'];?\s*\n?/g;
|
|
770
868
|
let processed = cssContent;
|
|
771
|
-
|
|
869
|
+
|
|
772
870
|
if (!hasTwAnimate) {
|
|
773
871
|
// Package not installed - remove import to prevent build errors
|
|
774
|
-
processed = cssContent.replace(
|
|
872
|
+
processed = cssContent.replace(CSS_PATTERNS.TW_ANIMATE_IMPORT, '');
|
|
775
873
|
if (cssContent.includes('tw-animate-css') && !processed.includes('tw-animate-css')) {
|
|
874
|
+
const installCmd = `npm install ${PACKAGE_NAMES.TW_ANIMATE_CSS}`;
|
|
776
875
|
if (forceUpdate) {
|
|
777
|
-
warn(
|
|
778
|
-
info(
|
|
779
|
-
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');
|
|
780
879
|
} else {
|
|
781
|
-
warn(
|
|
782
|
-
info(
|
|
880
|
+
warn(`${PACKAGE_NAMES.TW_ANIMATE_CSS} package not found - removed from CSS`);
|
|
881
|
+
info(` Install with: ${installCmd}`);
|
|
783
882
|
info(' Or add the import manually after installing the package');
|
|
784
883
|
}
|
|
785
884
|
}
|
|
786
885
|
} else {
|
|
787
886
|
// Package is installed - ensure import exists and remove duplicates
|
|
788
887
|
processed = removeDuplicateTwAnimateImports(processed);
|
|
789
|
-
|
|
888
|
+
|
|
790
889
|
// Add import if it doesn't exist (right after tailwindcss if present)
|
|
791
|
-
if (!processed.match(
|
|
792
|
-
if (
|
|
890
|
+
if (!processed.match(CSS_PATTERNS.TW_ANIMATE_IMPORT)) {
|
|
891
|
+
if (hasTailwindImport(processed)) {
|
|
793
892
|
processed = processed.replace(
|
|
794
|
-
|
|
893
|
+
CSS_PATTERNS.TAILWIND_IMPORT,
|
|
795
894
|
'$1@import "tw-animate-css";\n'
|
|
796
895
|
);
|
|
797
896
|
} else {
|
|
@@ -799,7 +898,7 @@ function processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate = false) {
|
|
|
799
898
|
}
|
|
800
899
|
}
|
|
801
900
|
}
|
|
802
|
-
|
|
901
|
+
|
|
803
902
|
return processed;
|
|
804
903
|
}
|
|
805
904
|
|
|
@@ -811,7 +910,7 @@ function processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate = false) {
|
|
|
811
910
|
* @returns {string} CSS content without tailwindcss imports
|
|
812
911
|
*/
|
|
813
912
|
function removeTailwindImport(cssContent) {
|
|
814
|
-
return cssContent.replace(
|
|
913
|
+
return cssContent.replace(CSS_PATTERNS.TAILWIND_IMPORT, '');
|
|
815
914
|
}
|
|
816
915
|
|
|
817
916
|
/**
|
|
@@ -826,23 +925,23 @@ function ensureTwAnimateImport(cssContent, hasTwAnimate) {
|
|
|
826
925
|
if (!hasTwAnimate) {
|
|
827
926
|
return cssContent;
|
|
828
927
|
}
|
|
829
|
-
|
|
928
|
+
|
|
830
929
|
// Remove duplicates first
|
|
831
930
|
let cleaned = removeDuplicateTwAnimateImports(cssContent);
|
|
832
|
-
|
|
931
|
+
|
|
833
932
|
// Check if import already exists
|
|
834
|
-
if (cleaned.match(
|
|
933
|
+
if (cleaned.match(CSS_PATTERNS.TW_ANIMATE_IMPORT)) {
|
|
835
934
|
return cleaned;
|
|
836
935
|
}
|
|
837
|
-
|
|
936
|
+
|
|
838
937
|
// Add import after tailwindcss if it exists, otherwise at the beginning
|
|
839
|
-
if (
|
|
938
|
+
if (hasTailwindImport(cleaned)) {
|
|
840
939
|
return cleaned.replace(
|
|
841
|
-
|
|
940
|
+
CSS_PATTERNS.TAILWIND_IMPORT,
|
|
842
941
|
'$1@import "tw-animate-css";\n'
|
|
843
942
|
);
|
|
844
943
|
}
|
|
845
|
-
|
|
944
|
+
|
|
846
945
|
return '@import "tw-animate-css";\n' + cleaned;
|
|
847
946
|
}
|
|
848
947
|
|
|
@@ -860,35 +959,32 @@ function ensureTwAnimateImport(cssContent, hasTwAnimate) {
|
|
|
860
959
|
* @returns {string} Merged CSS content
|
|
861
960
|
*/
|
|
862
961
|
function mergeCSSWithTailwind(existing, weloopStyles, hasTwAnimate) {
|
|
863
|
-
const tailwindMatch = existing.match(
|
|
962
|
+
const tailwindMatch = existing.match(CSS_PATTERNS.TAILWIND_IMPORT);
|
|
864
963
|
if (!tailwindMatch) {
|
|
865
964
|
// No tailwindcss import found - just prepend Weloop styles
|
|
866
965
|
return weloopStyles + '\n\n' + existing;
|
|
867
966
|
}
|
|
868
|
-
|
|
967
|
+
|
|
869
968
|
// Split existing CSS around the tailwindcss import
|
|
870
|
-
const
|
|
871
|
-
const
|
|
872
|
-
|
|
969
|
+
const tailwindIndex = existing.indexOf(tailwindMatch[0]);
|
|
970
|
+
const beforeTailwind = existing.substring(0, tailwindIndex);
|
|
971
|
+
const afterTailwind = existing.substring(tailwindIndex + tailwindMatch[0].length);
|
|
972
|
+
|
|
873
973
|
// Check if tw-animate-css import already exists
|
|
874
|
-
const hasTwAnimateInExisting = existing.match(
|
|
875
|
-
const hasTwAnimateInWeloop = weloopStyles.match(
|
|
876
|
-
|
|
974
|
+
const hasTwAnimateInExisting = existing.match(CSS_PATTERNS.TW_ANIMATE_IMPORT);
|
|
975
|
+
const hasTwAnimateInWeloop = weloopStyles.match(CSS_PATTERNS.TW_ANIMATE_IMPORT);
|
|
976
|
+
|
|
877
977
|
// If existing file already has tw-animate import, remove it from Weloop styles
|
|
878
978
|
// to avoid duplicating it in the merge output
|
|
879
979
|
let weloopStylesCleaned = weloopStyles;
|
|
880
980
|
if (hasTwAnimateInExisting && hasTwAnimateInWeloop) {
|
|
881
|
-
weloopStylesCleaned = weloopStyles.replace(
|
|
882
|
-
/@import\s+["']tw-animate-css["'];?\s*\n?/g,
|
|
883
|
-
''
|
|
884
|
-
);
|
|
981
|
+
weloopStylesCleaned = weloopStyles.replace(CSS_PATTERNS.TW_ANIMATE_IMPORT, '');
|
|
885
982
|
}
|
|
886
|
-
|
|
983
|
+
|
|
887
984
|
// Add tw-animate import if needed (package installed but import missing)
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
}
|
|
985
|
+
const importsToAdd = (hasTwAnimate && !hasTwAnimateInExisting && !hasTwAnimateInWeloop)
|
|
986
|
+
? '@import "tw-animate-css";\n'
|
|
987
|
+
: '';
|
|
892
988
|
|
|
893
989
|
// Merge: before tailwind + tailwind import + tw-animate (if needed) + Weloop styles + after tailwind
|
|
894
990
|
return beforeTailwind + tailwindMatch[0] + importsToAdd + weloopStylesCleaned + '\n' + afterTailwind;
|
|
@@ -907,28 +1003,26 @@ function mergeCSSWithTailwind(existing, weloopStyles, hasTwAnimate) {
|
|
|
907
1003
|
* @returns {string} CSS content with Weloop styles replaced
|
|
908
1004
|
*/
|
|
909
1005
|
function replaceWeloopStyles(existing, newStyles, hasTwAnimate) {
|
|
910
|
-
const
|
|
911
|
-
const imports = existing.match(importPattern) || [];
|
|
1006
|
+
const imports = existing.match(CSS_PATTERNS.IMPORT_STATEMENT) || [];
|
|
912
1007
|
const importsText = imports.join('');
|
|
913
|
-
|
|
1008
|
+
|
|
914
1009
|
// Find where Weloop styles start (look for @theme inline or :root)
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
1010
|
+
const weloopStartMatch = existing.search(CSS_PATTERNS.WELOOP_START);
|
|
1011
|
+
|
|
918
1012
|
if (weloopStartMatch === -1) {
|
|
919
1013
|
// No existing Weloop styles found - just append
|
|
920
1014
|
return existing + '\n\n' + newStyles;
|
|
921
1015
|
}
|
|
922
|
-
|
|
1016
|
+
|
|
923
1017
|
// Extract content before Weloop styles
|
|
924
1018
|
let contentBeforeWeloop = existing.substring(0, weloopStartMatch);
|
|
925
1019
|
// Keep non-Weloop imports (filter out tw-animate if package not installed)
|
|
926
|
-
const nonWeloopImports = importsText.split('\n').filter(imp =>
|
|
1020
|
+
const nonWeloopImports = importsText.split('\n').filter(imp =>
|
|
927
1021
|
!imp.includes('tw-animate-css') || hasTwAnimate
|
|
928
1022
|
).join('\n');
|
|
929
1023
|
// Remove all imports from contentBeforeWeloop (we'll add them back)
|
|
930
|
-
contentBeforeWeloop = nonWeloopImports + '\n' + contentBeforeWeloop.replace(
|
|
931
|
-
|
|
1024
|
+
contentBeforeWeloop = nonWeloopImports + '\n' + contentBeforeWeloop.replace(CSS_PATTERNS.IMPORT_STATEMENT, '');
|
|
1025
|
+
|
|
932
1026
|
// Return: preserved imports + content before Weloop + new Weloop styles
|
|
933
1027
|
return contentBeforeWeloop.trim() + '\n\n' + newStyles.trim();
|
|
934
1028
|
}
|
|
@@ -944,11 +1038,11 @@ function replaceWeloopStyles(existing, newStyles, hasTwAnimate) {
|
|
|
944
1038
|
*/
|
|
945
1039
|
async function fetchCSSFromRegistry(registryUrl) {
|
|
946
1040
|
let sourceCssPath;
|
|
947
|
-
|
|
1041
|
+
|
|
948
1042
|
if (isLocalPath(registryUrl)) {
|
|
949
1043
|
// Local path: find app/globals.css relative to registry directory
|
|
950
|
-
const basePath = path.isAbsolute(registryUrl)
|
|
951
|
-
? path.dirname(registryUrl)
|
|
1044
|
+
const basePath = path.isAbsolute(registryUrl)
|
|
1045
|
+
? path.dirname(registryUrl)
|
|
952
1046
|
: path.join(process.cwd(), path.dirname(registryUrl));
|
|
953
1047
|
sourceCssPath = path.join(basePath, 'app', 'globals.css');
|
|
954
1048
|
} else {
|
|
@@ -956,7 +1050,7 @@ async function fetchCSSFromRegistry(registryUrl) {
|
|
|
956
1050
|
const baseUrl = registryUrl.replace('/registry', '');
|
|
957
1051
|
sourceCssPath = `${baseUrl}/app/globals.css`;
|
|
958
1052
|
}
|
|
959
|
-
|
|
1053
|
+
|
|
960
1054
|
return await fetchText(sourceCssPath);
|
|
961
1055
|
}
|
|
962
1056
|
|
|
@@ -974,52 +1068,49 @@ async function fetchCSSFromRegistry(registryUrl) {
|
|
|
974
1068
|
* @param {boolean} silent - Whether to suppress output messages
|
|
975
1069
|
*/
|
|
976
1070
|
async function installCSSStyles(config, registryUrl, forceUpdate = false, silent = false) {
|
|
977
|
-
const cssPath = config.tailwind?.css ||
|
|
1071
|
+
const cssPath = config.tailwind?.css || DEFAULTS.CSS_PATH;
|
|
978
1072
|
const fullCssPath = path.join(process.cwd(), cssPath);
|
|
979
|
-
|
|
1073
|
+
|
|
980
1074
|
// Check if Weloop styles already exist in the file
|
|
981
1075
|
let hasWeloopStylesInFile = false;
|
|
982
1076
|
if (fs.existsSync(fullCssPath)) {
|
|
983
1077
|
const existingContent = fs.readFileSync(fullCssPath, 'utf-8');
|
|
984
1078
|
hasWeloopStylesInFile = hasWeloopStyles(existingContent);
|
|
985
|
-
|
|
1079
|
+
|
|
986
1080
|
// If styles already exist and we're not forcing update, skip silently
|
|
987
1081
|
// This prevents unnecessary updates when installing components
|
|
988
1082
|
if (hasWeloopStylesInFile && !forceUpdate && silent) {
|
|
989
1083
|
return;
|
|
990
1084
|
}
|
|
991
1085
|
}
|
|
992
|
-
|
|
1086
|
+
|
|
993
1087
|
try {
|
|
994
1088
|
if (!silent) {
|
|
995
1089
|
info('Installing CSS styles...');
|
|
996
1090
|
}
|
|
997
|
-
|
|
1091
|
+
|
|
998
1092
|
const cssContent = await fetchCSSFromRegistry(registryUrl);
|
|
999
1093
|
ensureDirectoryExists(path.dirname(fullCssPath));
|
|
1000
|
-
|
|
1001
|
-
const hasTwAnimate = checkPackageInstalled(
|
|
1094
|
+
|
|
1095
|
+
const hasTwAnimate = checkPackageInstalled(PACKAGE_NAMES.TW_ANIMATE_CSS);
|
|
1002
1096
|
let processedCssContent = processTwAnimateImport(cssContent, hasTwAnimate, forceUpdate);
|
|
1003
|
-
|
|
1097
|
+
|
|
1004
1098
|
// Handle file installation based on mode and existing file state
|
|
1005
1099
|
if (forceUpdate && fs.existsSync(fullCssPath)) {
|
|
1006
1100
|
// Mode 1: --overwrite flag - Replace entire file with fresh styles
|
|
1007
|
-
|
|
1008
|
-
normalized = removeDuplicateTwAnimateImports(normalized);
|
|
1009
|
-
normalized = removeDuplicateCustomVariant(normalized);
|
|
1101
|
+
const normalized = normalizeAndCleanCSS(processedCssContent, hasTwAnimate);
|
|
1010
1102
|
fs.writeFileSync(fullCssPath, normalized);
|
|
1011
1103
|
if (!silent) {
|
|
1012
1104
|
success(`Overwritten ${cssPath} with Weloop styles`);
|
|
1013
1105
|
if (hasTwAnimate) {
|
|
1014
|
-
info(`
|
|
1106
|
+
info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
|
|
1015
1107
|
}
|
|
1016
1108
|
}
|
|
1017
1109
|
} else if (fs.existsSync(fullCssPath)) {
|
|
1018
1110
|
// Mode 2: Normal update - Intelligently merge with existing styles
|
|
1019
1111
|
const existing = fs.readFileSync(fullCssPath, 'utf-8');
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1112
|
+
const existingHasTailwind = hasTailwindImport(existing);
|
|
1113
|
+
|
|
1023
1114
|
if (hasWeloopStylesInFile) {
|
|
1024
1115
|
// Case 2a: Weloop styles already exist - replace them with updated versions
|
|
1025
1116
|
let weloopStyles = removeTailwindImport(processedCssContent);
|
|
@@ -1028,7 +1119,7 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
|
|
|
1028
1119
|
finalContent = removeDuplicateTwAnimateImports(finalContent);
|
|
1029
1120
|
finalContent = removeDuplicateCustomVariant(finalContent);
|
|
1030
1121
|
finalContent = normalizeCSSFormat(finalContent);
|
|
1031
|
-
|
|
1122
|
+
|
|
1032
1123
|
// Only write if content actually changed (prevents unnecessary file updates)
|
|
1033
1124
|
if (finalContent !== existing) {
|
|
1034
1125
|
fs.writeFileSync(fullCssPath, finalContent);
|
|
@@ -1037,31 +1128,29 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
|
|
|
1037
1128
|
}
|
|
1038
1129
|
}
|
|
1039
1130
|
// If no changes, silently skip (file is already up to date)
|
|
1040
|
-
} else if (
|
|
1131
|
+
} else if (existingHasTailwind) {
|
|
1041
1132
|
// Case 2b: No Weloop styles but Tailwind exists - merge after Tailwind imports
|
|
1042
1133
|
let weloopStyles = removeTailwindImport(processedCssContent);
|
|
1043
1134
|
weloopStyles = ensureTwAnimateImport(weloopStyles, hasTwAnimate);
|
|
1044
1135
|
let merged = mergeCSSWithTailwind(existing, weloopStyles, hasTwAnimate);
|
|
1045
|
-
merged =
|
|
1046
|
-
merged = removeDuplicateCustomVariant(merged);
|
|
1047
|
-
merged = normalizeCSSFormat(merged);
|
|
1136
|
+
merged = normalizeAndCleanCSS(merged, hasTwAnimate);
|
|
1048
1137
|
fs.writeFileSync(fullCssPath, merged);
|
|
1049
1138
|
if (!silent) {
|
|
1050
1139
|
success(`Updated ${cssPath} with Weloop styles`);
|
|
1051
1140
|
if (hasTwAnimate) {
|
|
1052
|
-
info(`
|
|
1141
|
+
info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
|
|
1053
1142
|
}
|
|
1054
1143
|
info(` Your existing styles are preserved`);
|
|
1055
1144
|
}
|
|
1056
1145
|
} else {
|
|
1057
1146
|
// Case 2c: No Tailwind imports - prepend Weloop styles to existing content
|
|
1058
1147
|
let finalCssContent = removeDuplicateTwAnimateImports(processedCssContent);
|
|
1059
|
-
|
|
1148
|
+
|
|
1060
1149
|
// Ensure tw-animate import exists if package is installed
|
|
1061
|
-
if (hasTwAnimate && !finalCssContent.match(
|
|
1062
|
-
if (finalCssContent
|
|
1150
|
+
if (hasTwAnimate && !finalCssContent.match(CSS_PATTERNS.TW_ANIMATE_IMPORT)) {
|
|
1151
|
+
if (hasTailwindImport(finalCssContent)) {
|
|
1063
1152
|
finalCssContent = finalCssContent.replace(
|
|
1064
|
-
|
|
1153
|
+
CSS_PATTERNS.TAILWIND_IMPORT,
|
|
1065
1154
|
'$1@import "tw-animate-css";\n'
|
|
1066
1155
|
);
|
|
1067
1156
|
} else {
|
|
@@ -1069,35 +1158,34 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
|
|
|
1069
1158
|
}
|
|
1070
1159
|
}
|
|
1071
1160
|
let combined = finalCssContent + '\n\n' + existing;
|
|
1072
|
-
combined =
|
|
1161
|
+
combined = normalizeAndCleanCSS(combined, hasTwAnimate);
|
|
1073
1162
|
fs.writeFileSync(fullCssPath, combined);
|
|
1074
1163
|
if (!silent) {
|
|
1075
1164
|
success(`Updated ${cssPath} with Weloop styles`);
|
|
1076
1165
|
if (hasTwAnimate) {
|
|
1077
|
-
info(`
|
|
1166
|
+
info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
|
|
1078
1167
|
}
|
|
1079
1168
|
}
|
|
1080
1169
|
}
|
|
1081
1170
|
} else {
|
|
1082
1171
|
// Mode 3: File doesn't exist - create new file with Weloop styles
|
|
1083
|
-
|
|
1084
|
-
normalized = removeDuplicateTwAnimateImports(normalized);
|
|
1085
|
-
normalized = removeDuplicateCustomVariant(normalized);
|
|
1172
|
+
const normalized = normalizeAndCleanCSS(processedCssContent, hasTwAnimate);
|
|
1086
1173
|
fs.writeFileSync(fullCssPath, normalized);
|
|
1087
1174
|
if (!silent) {
|
|
1088
1175
|
success(`Created ${cssPath} with Weloop styles`);
|
|
1089
1176
|
if (hasTwAnimate) {
|
|
1090
|
-
info(`
|
|
1177
|
+
info(` ${PACKAGE_NAMES.TW_ANIMATE_CSS} import included`);
|
|
1091
1178
|
}
|
|
1092
1179
|
}
|
|
1093
1180
|
}
|
|
1094
1181
|
} catch (err) {
|
|
1095
1182
|
if (!silent) {
|
|
1096
1183
|
warn(`Could not automatically install CSS styles: ${err.message}`);
|
|
1097
|
-
info(
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1184
|
+
info(getCSSInstallErrorMessage(registryUrl, cssPath));
|
|
1185
|
+
}
|
|
1186
|
+
// Re-throw in silent mode so caller can handle it
|
|
1187
|
+
if (silent) {
|
|
1188
|
+
throw err;
|
|
1101
1189
|
}
|
|
1102
1190
|
}
|
|
1103
1191
|
}
|
|
@@ -1116,11 +1204,11 @@ async function installCSSStyles(config, registryUrl, forceUpdate = false, silent
|
|
|
1116
1204
|
*/
|
|
1117
1205
|
function createUtilsFile(utilsPath) {
|
|
1118
1206
|
if (fs.existsSync(utilsPath)) return;
|
|
1119
|
-
|
|
1207
|
+
|
|
1120
1208
|
// Silently create utils file (matching shadcn/ui behavior)
|
|
1121
1209
|
// Don't overwrite if user has customized it
|
|
1122
1210
|
ensureDirectoryExists(path.dirname(utilsPath));
|
|
1123
|
-
|
|
1211
|
+
|
|
1124
1212
|
const utilsContent = `import { clsx, type ClassValue } from "clsx"
|
|
1125
1213
|
import { twMerge } from "tailwind-merge"
|
|
1126
1214
|
|
|
@@ -1150,47 +1238,41 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
1150
1238
|
*/
|
|
1151
1239
|
async function installComponent(componentName, options = {}) {
|
|
1152
1240
|
const { overwrite = false, registryUrl = DEFAULT_REGISTRY_URL } = options;
|
|
1153
|
-
|
|
1241
|
+
|
|
1154
1242
|
// Step 1: Install base dependencies (required for utils.ts to work)
|
|
1155
|
-
const baseDeps =
|
|
1156
|
-
if (!checkPackageInstalled('clsx')) {
|
|
1157
|
-
baseDeps.push('clsx');
|
|
1158
|
-
}
|
|
1159
|
-
if (!checkPackageInstalled('tailwind-merge')) {
|
|
1160
|
-
baseDeps.push('tailwind-merge');
|
|
1161
|
-
}
|
|
1243
|
+
const baseDeps = BASE_DEPENDENCIES.filter(dep => !checkPackageInstalled(dep));
|
|
1162
1244
|
if (baseDeps.length > 0) {
|
|
1163
1245
|
info(`Installing base dependencies: ${baseDeps.join(', ')}...`);
|
|
1164
1246
|
await installPackages(baseDeps);
|
|
1165
1247
|
}
|
|
1166
|
-
|
|
1248
|
+
|
|
1167
1249
|
// Step 2: Load or create components.json configuration
|
|
1168
1250
|
const config = loadComponentsConfig();
|
|
1169
|
-
|
|
1251
|
+
|
|
1170
1252
|
// Step 3: Install tw-animate-css automatically (required for component animations)
|
|
1171
|
-
if (!checkPackageInstalled(
|
|
1172
|
-
info(
|
|
1173
|
-
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]);
|
|
1174
1256
|
}
|
|
1175
|
-
|
|
1257
|
+
|
|
1176
1258
|
// Step 4: Install CSS styles early (before component installation)
|
|
1177
1259
|
// Silent mode prevents output when styles are already installed
|
|
1178
1260
|
await installCSSStyles(config, registryUrl, false, true);
|
|
1179
|
-
|
|
1261
|
+
|
|
1180
1262
|
// Step 5: Resolve paths from components.json configuration
|
|
1181
1263
|
const uiAlias = config.aliases?.ui || '@/components/ui';
|
|
1182
1264
|
const utilsAlias = config.aliases?.utils || '@/lib/utils';
|
|
1183
|
-
|
|
1265
|
+
|
|
1184
1266
|
const componentsDir = path.join(process.cwd(), uiAlias.replace('@/', '').replace(/^\/+/, ''));
|
|
1185
1267
|
let utilsPath = utilsAlias.replace('@/', '').replace(/^\/+/, '');
|
|
1186
1268
|
if (!utilsPath.endsWith('.ts') && !utilsPath.endsWith('.tsx')) {
|
|
1187
1269
|
utilsPath = utilsPath + '.ts';
|
|
1188
1270
|
}
|
|
1189
1271
|
utilsPath = path.join(process.cwd(), utilsPath);
|
|
1190
|
-
|
|
1272
|
+
|
|
1191
1273
|
// Step 6: Load component registry from remote or local source
|
|
1192
1274
|
const registry = await loadRegistry(componentName, registryUrl);
|
|
1193
|
-
|
|
1275
|
+
|
|
1194
1276
|
if (!registry) {
|
|
1195
1277
|
error(`Component "${componentName}" not found in registry.`);
|
|
1196
1278
|
info('Available components:');
|
|
@@ -1249,6 +1331,244 @@ async function installComponent(componentName, options = {}) {
|
|
|
1249
1331
|
success(`\nSuccessfully installed "${componentName}"`);
|
|
1250
1332
|
}
|
|
1251
1333
|
|
|
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
|
+
// Detect CSS path based on project type
|
|
1497
|
+
let cssPath = PATHS.APP_GLOBALS_CSS;
|
|
1498
|
+
if (isVite) {
|
|
1499
|
+
cssPath = PATHS.VITE_INDEX_CSS;
|
|
1500
|
+
} else if (fs.existsSync(path.join(process.cwd(), 'app', 'globals.css'))) {
|
|
1501
|
+
cssPath = PATHS.APP_GLOBALS_CSS;
|
|
1502
|
+
} else if (fs.existsSync(path.join(process.cwd(), 'styles', 'globals.css'))) {
|
|
1503
|
+
cssPath = PATHS.STYLES_GLOBALS_CSS;
|
|
1504
|
+
} else {
|
|
1505
|
+
cssPath = await prompt('Where is your global CSS file?', cssPath);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// Create components.json with selected theme
|
|
1509
|
+
const config = {
|
|
1510
|
+
$schema: DEFAULTS.SCHEMA_URL,
|
|
1511
|
+
style: selectedTheme,
|
|
1512
|
+
rsc: isNextjs,
|
|
1513
|
+
tsx: true,
|
|
1514
|
+
tailwind: {
|
|
1515
|
+
config: 'tailwind.config.ts',
|
|
1516
|
+
css: cssPath,
|
|
1517
|
+
baseColor: 'neutral',
|
|
1518
|
+
cssVariables: true,
|
|
1519
|
+
prefix: ''
|
|
1520
|
+
},
|
|
1521
|
+
iconLibrary: DEFAULTS.ICON_LIBRARY,
|
|
1522
|
+
aliases: {
|
|
1523
|
+
components: '@/components',
|
|
1524
|
+
utils: '@/lib/utils',
|
|
1525
|
+
ui: '@/components/ui',
|
|
1526
|
+
lib: '@/lib',
|
|
1527
|
+
hooks: '@/hooks'
|
|
1528
|
+
},
|
|
1529
|
+
registry: REGISTRY_URLS.INDEX,
|
|
1530
|
+
projectName: projectName,
|
|
1531
|
+
radius: selectedRadius
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
// Show setup progress
|
|
1535
|
+
info('\nš Setting up your project...');
|
|
1536
|
+
|
|
1537
|
+
// Step 1: Create components.json
|
|
1538
|
+
info(' ā [:root]/components.json');
|
|
1539
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1540
|
+
|
|
1541
|
+
// Step 2: Update CSS variables
|
|
1542
|
+
info(' ā [:root]/app/globals.css (Updating css variables)');
|
|
1543
|
+
await installCSSStyles(config, DEFAULT_REGISTRY_URL, false, false);
|
|
1544
|
+
|
|
1545
|
+
// Step 3: Install dependencies
|
|
1546
|
+
info(' ā Installing dependencies');
|
|
1547
|
+
const missingBaseDeps = BASE_DEPENDENCIES.filter(dep => !checkPackageInstalled(dep));
|
|
1548
|
+
|
|
1549
|
+
if (missingBaseDeps.length > 0) {
|
|
1550
|
+
await installPackages(missingBaseDeps);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// Install tw-animate-css for animations
|
|
1554
|
+
if (!checkPackageInstalled(PACKAGE_NAMES.TW_ANIMATE_CSS)) {
|
|
1555
|
+
await installPackages([PACKAGE_NAMES.TW_ANIMATE_CSS]);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// Step 4: Create utils.ts file
|
|
1559
|
+
info(' ā Create 1 files:');
|
|
1560
|
+
const utilsPath = path.join(process.cwd(), 'lib', 'utils.ts');
|
|
1561
|
+
createUtilsFile(utilsPath);
|
|
1562
|
+
info(' ā libs/utils.ts');
|
|
1563
|
+
|
|
1564
|
+
success('\nā
Success! Successfully initialization.');
|
|
1565
|
+
info('\nYou could add the components now.');
|
|
1566
|
+
info('\nAvailable commands:');
|
|
1567
|
+
info(' npx weloop-kosign@latest add <component-name>');
|
|
1568
|
+
info(' pnpm dlx weloop-kosign@latest add <component-name>');
|
|
1569
|
+
info(' yarn weloop-kosign@latest add <component-name>\n');
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1252
1572
|
// ============================================================================
|
|
1253
1573
|
// COMMANDS - CLI Command Handlers
|
|
1254
1574
|
// ============================================================================
|
|
@@ -1262,7 +1582,7 @@ async function installComponent(componentName, options = {}) {
|
|
|
1262
1582
|
async function listComponents(registryUrl = DEFAULT_REGISTRY_URL) {
|
|
1263
1583
|
try {
|
|
1264
1584
|
const index = await loadIndex(registryUrl);
|
|
1265
|
-
|
|
1585
|
+
|
|
1266
1586
|
console.log('\nAvailable components:\n');
|
|
1267
1587
|
index.registry.forEach(comp => {
|
|
1268
1588
|
// Show dependencies if component has any
|
|
@@ -1298,13 +1618,13 @@ async function main() {
|
|
|
1298
1618
|
const args = process.argv.slice(2);
|
|
1299
1619
|
const command = args[0];
|
|
1300
1620
|
const componentName = args[1];
|
|
1301
|
-
|
|
1621
|
+
|
|
1302
1622
|
// Parse --registry option if provided
|
|
1303
1623
|
const registryIndex = args.indexOf('--registry');
|
|
1304
1624
|
const registryUrl = registryIndex !== -1 && args[registryIndex + 1]
|
|
1305
1625
|
? args[registryIndex + 1]
|
|
1306
1626
|
: DEFAULT_REGISTRY_URL;
|
|
1307
|
-
|
|
1627
|
+
|
|
1308
1628
|
// Build options object
|
|
1309
1629
|
const options = {
|
|
1310
1630
|
overwrite: args.includes('--overwrite') || args.includes('-f'),
|
|
@@ -1315,6 +1635,7 @@ async function main() {
|
|
|
1315
1635
|
if (!command) {
|
|
1316
1636
|
console.log(`
|
|
1317
1637
|
Usage:
|
|
1638
|
+
node scripts/cli-remote.js init [--registry <url>] Initialize project
|
|
1318
1639
|
node scripts/cli-remote.js add <component-name> [--registry <url>] Install a component
|
|
1319
1640
|
node scripts/cli-remote.js list [--registry <url>] List all available components
|
|
1320
1641
|
node scripts/cli-remote.js css [--registry <url>] [--overwrite] Install/update CSS styles
|
|
@@ -1324,6 +1645,7 @@ Options:
|
|
|
1324
1645
|
--overwrite, -f Overwrite existing files
|
|
1325
1646
|
|
|
1326
1647
|
Examples:
|
|
1648
|
+
node scripts/cli-remote.js init
|
|
1327
1649
|
node scripts/cli-remote.js add button
|
|
1328
1650
|
node scripts/cli-remote.js add button --registry https://raw.githubusercontent.com/user/repo/main/registry
|
|
1329
1651
|
node scripts/cli-remote.js list
|
|
@@ -1335,6 +1657,10 @@ Examples:
|
|
|
1335
1657
|
|
|
1336
1658
|
// Route to appropriate command handler
|
|
1337
1659
|
switch (command) {
|
|
1660
|
+
case 'init':
|
|
1661
|
+
await initProject();
|
|
1662
|
+
break;
|
|
1663
|
+
|
|
1338
1664
|
case 'add':
|
|
1339
1665
|
if (!componentName) {
|
|
1340
1666
|
error('Please provide a component name');
|
|
@@ -1343,21 +1669,21 @@ Examples:
|
|
|
1343
1669
|
}
|
|
1344
1670
|
await installComponent(componentName, options);
|
|
1345
1671
|
break;
|
|
1346
|
-
|
|
1672
|
+
|
|
1347
1673
|
case 'list':
|
|
1348
1674
|
await listComponents(registryUrl);
|
|
1349
1675
|
break;
|
|
1350
|
-
|
|
1676
|
+
|
|
1351
1677
|
case 'css':
|
|
1352
1678
|
case 'styles':
|
|
1353
1679
|
// Both 'css' and 'styles' commands do the same thing
|
|
1354
1680
|
const config = loadComponentsConfig();
|
|
1355
1681
|
await installCSSStyles(config, registryUrl, options.overwrite, false);
|
|
1356
1682
|
break;
|
|
1357
|
-
|
|
1683
|
+
|
|
1358
1684
|
default:
|
|
1359
1685
|
error(`Unknown command: ${command}`);
|
|
1360
|
-
console.log('Available commands: add, list, css');
|
|
1686
|
+
console.log('Available commands: init, add, list, css');
|
|
1361
1687
|
process.exit(1);
|
|
1362
1688
|
}
|
|
1363
1689
|
}
|