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