@nqlib/nqui 0.1.1 → 0.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.
@@ -1,580 +1,125 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * nqui CSS initialization script
4
+ * nqui CSS initialization CLI
5
5
  *
6
- * Copies the nqui design system CSS (variables, colors, tokens) to the user's project.
7
- * Usage: npx nqui init-css [target-path]
6
+ * Usage:
7
+ * npx nqui init-css [output.css] [options]
8
8
  */
9
9
 
10
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
11
- import { join, dirname, resolve } from 'path';
12
- import { fileURLToPath } from 'url';
13
- import { createInterface } from 'readline';
14
-
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = dirname(__filename);
17
-
18
- // Get the project root (where package.json is)
19
- const projectRoot = resolve(__dirname, '..');
20
- const indexCssPath = join(projectRoot, 'src', 'index.css');
21
- const colorsCssPath = join(projectRoot, 'src', 'styles', 'colors.css');
22
- const distStylesPath = join(projectRoot, 'dist', 'styles.css');
23
- const examplesDir = join(projectRoot, 'scripts', 'examples');
24
-
25
- // Check if we're in a published package (source files don't exist, but dist/styles.css does)
26
- const isPublishedPackage = !existsSync(indexCssPath) && existsSync(distStylesPath);
27
-
28
- // Function to find actual CSS file location for Next.js
29
- function findNextJsCssFile() {
30
- const possiblePaths = [
31
- 'src/app/globals.css',
32
- 'app/globals.css',
33
- 'src/app/layout.css',
34
- 'app/layout.css',
35
- ];
36
-
37
- for (const path of possiblePaths) {
38
- const fullPath = join(process.cwd(), path);
39
- if (existsSync(fullPath)) {
40
- return path;
41
- }
42
- }
43
-
44
- // Default fallback based on directory structure
45
- if (existsSync(join(process.cwd(), 'src', 'app'))) {
46
- return 'src/app/globals.css';
47
- }
48
- return 'app/globals.css';
49
- }
50
-
51
- // Function to find actual CSS file location for Vite
52
- function findViteCssFile() {
53
- const possiblePaths = [
54
- 'src/index.css',
55
- 'src/main.css',
56
- 'src/App.css',
57
- ];
58
-
59
- for (const path of possiblePaths) {
60
- const fullPath = join(process.cwd(), path);
61
- if (existsSync(fullPath)) {
62
- return path;
63
- }
64
- }
65
-
66
- return 'src/index.css';
67
- }
10
+ import minimist from 'minimist';
11
+ import { resolve, dirname } from 'path';
12
+ import { readFileSync, existsSync } from 'fs';
13
+ import { getPackageRoot } from './getPackageRoot.js';
14
+ import { printHelp } from './help.js';
15
+ import { runPipeline } from './pipeline/index.js';
16
+ import { wizard, askAboutExamples } from './wizard.js';
17
+ import { detectFramework, findMainCssFile } from './framework.js';
18
+ import { generateSetupContent } from './setup-helper.js';
19
+ import { copyNextJsExamples } from './examples.js';
20
+ import { emit } from './pipeline/emit.js';
21
+
22
+ // Filter out command names from argv
23
+ const commandNames = ['init-css', 'nqui', 'nqui-init-css'];
24
+ const filteredArgs = process.argv.slice(2).filter(arg => !commandNames.includes(arg));
25
+
26
+ const args = minimist(filteredArgs, {
27
+ boolean: ['js', 'tokens-only', 'force', 'dry-run', 'wizard', 'setup', 'help', 'version', 'local-copy'],
28
+ alias: { h: 'help', v: 'version' },
29
+ });
68
30
 
69
- // Get default path based on project type (uses detection functions)
70
- function getDefaultPath(projectType) {
71
- switch (projectType) {
72
- case 'nextjs':
73
- return findNextJsCssFile();
74
- case 'vite':
75
- return findViteCssFile();
76
- case 'remix':
77
- return 'app/root.css';
78
- case 'create-react-app':
79
- return 'src/index.css';
80
- default:
81
- return 'nqui.css';
82
- }
31
+ if (args.help) {
32
+ printHelp();
33
+ process.exit(0);
83
34
  }
84
35
 
85
- function findProjectType() {
86
- // Check for framework-specific files
87
- if (existsSync(join(process.cwd(), 'next.config.js')) ||
88
- existsSync(join(process.cwd(), 'next.config.ts'))) {
89
- return 'nextjs';
90
- }
91
- if (existsSync(join(process.cwd(), 'vite.config.js')) ||
92
- existsSync(join(process.cwd(), 'vite.config.ts'))) {
93
- return 'vite';
94
- }
95
- if (existsSync(join(process.cwd(), 'remix.config.js'))) {
96
- return 'remix';
97
- }
98
- if (existsSync(join(process.cwd(), 'src', 'index.css'))) {
99
- return 'create-react-app';
100
- }
101
- return null;
36
+ if (args.version) {
37
+ const root = getPackageRoot();
38
+ const pkg = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'));
39
+ console.log(pkg.version);
40
+ process.exit(0);
102
41
  }
103
42
 
104
- function extractStandaloneCSS() {
105
- // If we're in a published package, use dist/styles.css directly
106
- if (isPublishedPackage) {
107
- if (!existsSync(distStylesPath)) {
108
- throw new Error(`Published package CSS file not found: ${distStylesPath}`);
109
- }
110
- return readFileSync(distStylesPath, 'utf-8');
111
- }
112
-
113
- // Read the source CSS files (for local development)
114
- if (!existsSync(indexCssPath)) {
115
- throw new Error(`Source CSS file not found: ${indexCssPath}`);
116
- }
117
- if (!existsSync(colorsCssPath)) {
118
- throw new Error(`Colors CSS file not found: ${colorsCssPath}`);
119
- }
120
-
121
- let indexCss = readFileSync(indexCssPath, 'utf-8');
122
- let colorsCss = readFileSync(colorsCssPath, 'utf-8');
123
-
124
- // Extract :root and .dark blocks from colors.css (they're wrapped in @layer base)
125
- // We need to unwrap them and merge with index.css's :root and .dark
126
- const colorsRootMatch = colorsCss.match(/@layer base\s*\{\s*:root\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}\s*\.dark\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}\s*\}/s);
127
-
128
- let colorsRootContent = '';
129
- let colorsDarkContent = '';
130
-
131
- if (colorsRootMatch) {
132
- colorsRootContent = colorsRootMatch[1].trim();
133
- colorsDarkContent = colorsRootMatch[2].trim();
134
- } else {
135
- // Fallback: try to extract without @layer base wrapper
136
- const rootMatch = colorsCss.match(/:root\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
137
- const darkMatch = colorsCss.match(/\.dark\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
138
- if (rootMatch) colorsRootContent = rootMatch[1].trim();
139
- if (darkMatch) colorsDarkContent = darkMatch[1].trim();
140
- }
43
+ const output = args._[0] || 'nqui/index.css';
141
44
 
142
- // Remove imports that require build tools (users should handle these in their own setup)
143
- // Keep only the design tokens and variables
144
- indexCss = indexCss
145
- .replace(/@import\s+["']tailwindcss["'];?\s*\n/g, '')
146
- .replace(/@import\s+["']tw-animate-css["'];?\s*\n/g, '')
147
- .replace(/@import\s+["']shadcn\/tailwind\.css["'];?\s*\n/g, '')
148
- .replace(/@import\s+["']@fontsource-variable\/inter["'];?\s*\n/g, '')
149
- .replace(/@import\s+["']\.\/styles\/colors\.css["'];?\s*\n/g, '')
150
- .replace(/\/\*\s*Import enhanced color system\s*\*\//g, '');
151
-
152
- // Extract :root and .dark blocks from index.css
153
- const indexRootMatch = indexCss.match(/:root\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
154
- const indexDarkMatch = indexCss.match(/\.dark\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
155
-
156
- let indexRootContent = '';
157
- let indexDarkContent = '';
158
-
159
- if (indexRootMatch) indexRootContent = indexRootMatch[1].trim();
160
- if (indexDarkMatch) indexDarkContent = indexDarkMatch[1].trim();
161
-
162
- // Merge :root blocks (colors.css first, then index.css)
163
- const mergedRootContent = colorsRootContent
164
- ? colorsRootContent + '\n\n /* Additional variables from index.css */\n ' + indexRootContent
165
- : indexRootContent;
166
-
167
- // Merge .dark blocks (colors.css first, then index.css)
168
- const mergedDarkContent = colorsDarkContent
169
- ? colorsDarkContent + '\n\n /* Additional variables from index.css */\n ' + indexDarkContent
170
- : indexDarkContent;
171
-
172
- // Replace :root and .dark in index.css with merged versions
173
- if (indexRootMatch) {
174
- indexCss = indexCss.replace(/:root\s*\{[^}]+(?:\{[^}]*\}[^}]*)*\}/s, `:root {\n ${mergedRootContent}\n}`);
175
- }
176
-
177
- if (indexDarkMatch) {
178
- indexCss = indexCss.replace(/\.dark\s*\{[^}]+(?:\{[^}]*\}[^}]*)*\}/s, `.dark {\n ${mergedDarkContent}\n}`);
179
- }
180
-
181
- // Remove duplicate @layer base blocks (there are two identical ones in index.css)
182
- // Match @layer base blocks with proper brace counting
183
- const lines = indexCss.split('\n');
184
- let inLayerBase = false;
185
- let layerBaseStart = -1;
186
- let braceCount = 0;
187
- const layerBaseBlocks = [];
188
-
189
- for (let i = 0; i < lines.length; i++) {
190
- const line = lines[i];
191
- if (line.includes('@layer base')) {
192
- if (!inLayerBase) {
193
- inLayerBase = true;
194
- layerBaseStart = i;
195
- braceCount = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
196
- } else {
197
- braceCount += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
198
- }
199
- } else if (inLayerBase) {
200
- braceCount += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
201
- if (braceCount === 0) {
202
- layerBaseBlocks.push({ start: layerBaseStart, end: i });
203
- inLayerBase = false;
204
- }
205
- }
206
- }
207
-
208
- // If we found duplicate @layer base blocks, remove all but the first
209
- if (layerBaseBlocks.length > 1) {
210
- const firstBlock = layerBaseBlocks[0];
211
- const firstBlockContent = lines.slice(firstBlock.start, firstBlock.end + 1).join('\n');
212
-
213
- // Check if all blocks are identical
214
- const allIdentical = layerBaseBlocks.every(block => {
215
- const blockContent = lines.slice(block.start, block.end + 1).join('\n');
216
- return blockContent === firstBlockContent;
217
- });
218
-
219
- if (allIdentical) {
220
- // Remove all but the first block (working backwards to preserve indices)
221
- for (let i = layerBaseBlocks.length - 1; i > 0; i--) {
222
- const block = layerBaseBlocks[i];
223
- lines.splice(block.start, block.end - block.start + 1);
224
- }
225
- indexCss = lines.join('\n');
226
- }
227
- }
228
-
229
- // The :root and .dark blocks are already merged into indexCss
230
- // Now we just need to add a comment header
231
- let combinedCss = indexCss;
232
-
233
- // Add comment before :root if it doesn't already have one
234
- if (combinedCss.includes(':root') && !combinedCss.includes('nqui Color System')) {
235
- const rootIndex = combinedCss.indexOf(':root');
236
- combinedCss = combinedCss.slice(0, rootIndex) +
237
- '/* ============================================\n' +
238
- ' nqui Color System & Design Tokens\n' +
239
- ' ============================================ */\n\n' +
240
- combinedCss.slice(rootIndex);
241
- }
242
-
243
- // Clean up extra blank lines
244
- combinedCss = combinedCss.replace(/\n{4,}/g, '\n\n\n');
245
-
246
- // Add header comment
247
- const header = `/* nqui Design System CSS
248
- *
249
- * This file contains ALL design tokens, CSS variables, and color scales from nqui.
250
- * It includes everything the library needs to style components correctly.
251
- *
252
- * IMPORTANT: This file does NOT include Tailwind CSS imports.
253
- * You must have Tailwind CSS configured in your project.
254
- *
255
- * For Tailwind CSS v4, add this at the TOP of your CSS file:
256
- * @import "tailwindcss";
257
- *
258
- * For Tailwind CSS v3, configure it in your tailwind.config.js
45
+ /**
46
+ * Generate index.css that imports from library package
47
+ */
48
+ function generateIndexCssContent() {
49
+ return `/* nqui Design System
259
50
  *
260
- * This file includes:
261
- * - All CSS variables (--primary, --background, --foreground, etc.)
262
- * - Complete color scales (100-600) for all semantic colors
263
- * - Light and dark mode support
264
- * - Base layer styles
265
- * - Utility animations
51
+ * This file imports nqui styles from the library package.
52
+ * Import this file in your main CSS file (e.g. globals.css)
266
53
  *
267
- * Generated by: npx nqui init-css
268
- * Do not edit manually - regenerate if nqui is updated
54
+ * Usage in your globals.css:
55
+ * @import "./nqui/index.css";
269
56
  */
270
57
 
58
+ @import "@nqlib/nqui/styles";
271
59
  `;
272
-
273
- return header + combinedCss;
274
- }
275
-
276
- function getFrameworkDirectives(projectType) {
277
- switch (projectType) {
278
- case 'nextjs':
279
- return `@import "tailwindcss";
280
- @source "./**/*.{js,ts,jsx,tsx,mdx}";
281
- @source "../components/**/*.{js,ts,jsx,tsx,mdx}";
282
- @source "../node_modules/@nqlib/nqui/dist/**/*.js";
283
- @import "../node_modules/tw-animate-css/dist/tw-animate.css";
284
- @custom-variant dark (&:is(.dark *));
285
-
286
- `;
287
- case 'vite':
288
- case 'create-react-app':
289
- return `@import "tailwindcss";
290
- @import "../node_modules/tw-animate-css/dist/tw-animate.css";
291
-
292
- `;
293
- case 'remix':
294
- return `@import "tailwindcss";
295
- @import "../node_modules/tw-animate-css/dist/tw-animate.css";
296
-
297
- `;
298
- default:
299
- return '';
300
- }
301
- }
302
-
303
- // Function to ask user for input
304
- function askQuestion(question) {
305
- const rl = createInterface({
306
- input: process.stdin,
307
- output: process.stdout,
308
- });
309
-
310
- return new Promise((resolve) => {
311
- rl.question(question, (answer) => {
312
- rl.close();
313
- resolve(answer.trim().toLowerCase());
314
- });
315
- });
316
- }
317
-
318
- // Function to find Next.js page.tsx file
319
- function findNextJsPageFile() {
320
- const possiblePaths = [
321
- 'src/app/page.tsx',
322
- 'app/page.tsx',
323
- ];
324
-
325
- for (const path of possiblePaths) {
326
- const fullPath = join(process.cwd(), path);
327
- if (existsSync(fullPath)) {
328
- return path;
329
- }
330
- }
331
-
332
- // Default fallback based on directory structure
333
- if (existsSync(join(process.cwd(), 'src', 'app'))) {
334
- return 'src/app/page.tsx';
335
- }
336
- return 'app/page.tsx';
337
- }
338
-
339
- // Function to find Next.js layout.tsx file
340
- function findNextJsLayoutFile() {
341
- const possiblePaths = [
342
- 'src/app/layout.tsx',
343
- 'app/layout.tsx',
344
- ];
345
-
346
- for (const path of possiblePaths) {
347
- const fullPath = join(process.cwd(), path);
348
- if (existsSync(fullPath)) {
349
- return path;
350
- }
351
- }
352
-
353
- // Default fallback based on directory structure
354
- if (existsSync(join(process.cwd(), 'src', 'app'))) {
355
- return 'src/app/layout.tsx';
356
- }
357
- return 'app/layout.tsx';
358
60
  }
359
61
 
360
- // Function to copy example files for Next.js
361
- async function copyNextJsExamples() {
362
- const pageExamplePath = join(examplesDir, 'nextjs-page.tsx');
363
- const layoutExamplePath = join(examplesDir, 'nextjs-layout.tsx');
364
-
365
- if (!existsSync(pageExamplePath) || !existsSync(layoutExamplePath)) {
366
- console.log('⚠️ Example files not found. Skipping example copy.');
367
- return;
368
- }
369
-
370
- const targetPagePath = findNextJsPageFile();
371
- const targetLayoutPath = findNextJsLayoutFile();
372
- const fullPagePath = resolve(process.cwd(), targetPagePath);
373
- const fullLayoutPath = resolve(process.cwd(), targetLayoutPath);
374
-
375
- // Check if files exist
376
- const pageExists = existsSync(fullPagePath);
377
- const layoutExists = existsSync(fullLayoutPath);
378
-
379
- if (pageExists || layoutExists) {
380
- console.log('\n📄 Found existing Next.js files:');
381
- if (pageExists) console.log(` - ${targetPagePath}`);
382
- if (layoutExists) console.log(` - ${targetLayoutPath}`);
383
-
384
- const overwrite = await askQuestion('\n Do you want to overwrite them with nqui examples? (y/n): ');
385
- if (overwrite !== 'y' && overwrite !== 'yes') {
386
- console.log(' Skipping example files...');
387
- return;
388
- }
389
- }
390
-
391
- // Create directories if needed
392
- const pageDir = dirname(fullPagePath);
393
- const layoutDir = dirname(fullLayoutPath);
394
- if (!existsSync(pageDir)) {
395
- mkdirSync(pageDir, { recursive: true });
396
- }
397
- if (!existsSync(layoutDir)) {
398
- mkdirSync(layoutDir, { recursive: true });
399
- }
400
-
401
- // Copy example files
62
+ (async () => {
402
63
  try {
403
- const pageContent = readFileSync(pageExamplePath, 'utf-8');
404
- const layoutContent = readFileSync(layoutExamplePath, 'utf-8');
64
+ const root = getPackageRoot();
65
+ const framework = detectFramework();
66
+ const wiz = args.wizard ? await wizard() : {};
405
67
 
406
- writeFileSync(fullPagePath, pageContent, 'utf-8');
407
- writeFileSync(fullLayoutPath, layoutContent, 'utf-8');
68
+ const outCssPath = resolve(process.cwd(), output);
69
+ const useLibraryImport = !args['local-copy']; // Default to library import
408
70
 
409
- console.log(`✅ Copied example page: ${targetPagePath}`);
410
- console.log(`✅ Copied example layout: ${targetLayoutPath}`);
411
- console.log('\n📝 Note: Make sure to install next-themes if not already installed:');
412
- console.log(' npm install next-themes');
413
- } catch (error) {
414
- console.error('❌ Error copying example files:', error.message);
415
- }
416
- }
417
-
418
- async function main() {
419
- const args = process.argv.slice(2);
420
- const targetPath = args[0];
421
-
422
- try {
423
- // Extract standalone CSS
424
- const standaloneCss = extractStandaloneCSS();
425
-
426
- let finalTargetPath;
427
- let projectType = null;
428
-
429
- if (targetPath) {
430
- // User provided a custom path
431
- finalTargetPath = resolve(process.cwd(), targetPath);
432
- projectType = findProjectType();
71
+ if (useLibraryImport) {
72
+ // Create index.css that imports from library
73
+ const indexCssContent = generateIndexCssContent();
74
+ emit(outCssPath, indexCssContent, { force: args.force, dryRun: args['dry-run'] });
433
75
  } else {
434
- // Auto-detect project type
435
- projectType = findProjectType();
436
-
437
- if (projectType) {
438
- const defaultPath = getDefaultPath(projectType);
439
- finalTargetPath = resolve(process.cwd(), defaultPath);
440
- console.log(`📦 Detected ${projectType} project`);
441
- console.log(` Found CSS file: ${defaultPath}`);
442
- } else {
443
- // Fallback: create a new CSS file
444
- finalTargetPath = resolve(process.cwd(), 'nqui.css');
445
- console.log('⚠️ Could not detect project type.');
446
- console.log(` Creating standalone file: nqui.css`);
447
- console.log(' You can import this file manually in your app.');
448
- }
449
- }
450
-
451
- // Create target directory if it doesn't exist
452
- const targetDir = dirname(finalTargetPath);
453
- if (!existsSync(targetDir)) {
454
- mkdirSync(targetDir, { recursive: true });
76
+ // Original behavior: generate local copy
77
+ await runPipeline({
78
+ root,
79
+ outCss: outCssPath,
80
+ outJs: args.js || wiz.js
81
+ ? resolve(process.cwd(), output.replace('.css', '.tokens.js'))
82
+ : null,
83
+ tokensOnly: args['tokens-only'] || wiz.tokensOnly,
84
+ force: args.force,
85
+ dryRun: args['dry-run'],
86
+ });
455
87
  }
456
88
 
457
- // Get framework-specific directives
458
- const frameworkDirectives = projectType ? getFrameworkDirectives(projectType) : '';
459
- const fullCss = frameworkDirectives + standaloneCss;
460
-
461
- // Check if target file exists
462
- if (existsSync(finalTargetPath)) {
463
- console.log(`\n⚠️ File already exists: ${finalTargetPath}`);
464
-
465
- // Read existing content
466
- const existingContent = readFileSync(finalTargetPath, 'utf-8');
467
-
468
- // Check if nqui CSS is already present
469
- if (existingContent.includes('nqui Design System CSS') ||
470
- existingContent.includes('nqui Color System')) {
471
- console.log(' ⚠️ nqui CSS already present in file.');
472
- const update = await askQuestion(' Do you want to update it? (y/n): ');
473
- if (update !== 'y' && update !== 'yes') {
474
- console.log(' Skipping...');
475
- return;
476
- }
477
- // Remove old nqui CSS and add new
478
- const cleaned = existingContent.replace(
479
- /\/\* nqui Design System CSS[\s\S]*?(?=\n\n|\n$|$)/,
480
- ''
481
- ).replace(
482
- /\/\* ============================================[\s\S]*?============================================ \*\/[\s\S]*?}/g,
483
- ''
484
- );
485
- const hasTailwindImport = cleaned.includes('@import "tailwindcss"');
486
- if (hasTailwindImport && frameworkDirectives) {
487
- writeFileSync(finalTargetPath, cleaned.trim() + '\n\n' + standaloneCss, 'utf-8');
488
- } else {
489
- writeFileSync(finalTargetPath, fullCss + '\n\n' + cleaned.trim(), 'utf-8');
490
- }
491
- console.log(`✅ Updated nqui CSS in: ${finalTargetPath}`);
492
- console.log('\n✨ Done!');
493
- return;
494
- }
495
-
496
- // Ask user what to do
497
- console.log('\n What would you like to do?');
498
- console.log(' 1. Overwrite the file (replace all content)');
499
- console.log(' 2. Append nqui CSS to the file');
500
- console.log(' 3. Create a separate file (nqui.css) and import it');
89
+ // Generate setup helper file (always, unless dry-run)
90
+ if (!args['dry-run'] && (args.setup || !args['tokens-only'])) {
91
+ const setupContent = generateSetupContent(framework, output, useLibraryImport);
92
+ const setupPath = resolve(process.cwd(), dirname(output), 'nqui-setup.css');
93
+ emit(setupPath, setupContent, { force: args.force, dryRun: false });
501
94
 
502
- const choice = await askQuestion('\n Enter your choice (1/2/3): ');
95
+ const mainCssFile = findMainCssFile(framework);
503
96
 
504
- if (choice === '1') {
505
- // Overwrite
506
- const hasTailwindImport = existingContent.includes('@import "tailwindcss"');
507
- if (hasTailwindImport && frameworkDirectives) {
508
- // Keep existing Tailwind setup, add nqui CSS
509
- writeFileSync(finalTargetPath, existingContent + '\n\n' + standaloneCss, 'utf-8');
510
- } else {
511
- // Replace with framework directives + nqui CSS
512
- writeFileSync(finalTargetPath, fullCss, 'utf-8');
513
- }
514
- console.log(`✅ Updated: ${finalTargetPath}`);
515
- } else if (choice === '2') {
516
- // Append
517
- const hasTailwindImport = existingContent.includes('@import "tailwindcss"');
518
- if (hasTailwindImport && frameworkDirectives) {
519
- writeFileSync(finalTargetPath, existingContent + '\n\n' + standaloneCss, 'utf-8');
520
- } else {
521
- writeFileSync(finalTargetPath, fullCss + '\n\n' + existingContent, 'utf-8');
522
- }
523
- console.log(`✅ Appended nqui CSS to: ${finalTargetPath}`);
524
- } else if (choice === '3') {
525
- // Create separate file
526
- const separatePath = join(dirname(finalTargetPath), 'nqui.css');
527
- writeFileSync(separatePath, standaloneCss, 'utf-8');
528
- console.log(`✅ Created: ${separatePath}`);
529
- console.log(`\n📝 Next step: Import this file in ${finalTargetPath}:`);
530
- const relativeSeparatePath = separatePath.replace(process.cwd() + '/', '');
531
- const relativeTargetPath = finalTargetPath.replace(process.cwd() + '/', '');
532
- const importPath = relativeSeparatePath.replace(dirname(relativeTargetPath) + '/', './');
533
- console.log(` @import "${importPath}";`);
534
- console.log('\n✨ Done!');
535
- return;
536
- } else {
537
- console.log(' Invalid choice. Skipping...');
538
- return;
539
- }
540
- } else {
541
- // Create new file with framework directives + nqui CSS
542
- writeFileSync(finalTargetPath, fullCss, 'utf-8');
543
- console.log(`✅ Created: ${finalTargetPath}`);
544
- if (frameworkDirectives) {
545
- console.log(` Added ${projectType}-specific Tailwind setup`);
546
- }
97
+ console.log(`\n📝 Next steps:`);
98
+ console.log(` 1. Copy the contents of ${dirname(output)}/nqui-setup.css`);
99
+ console.log(` 2. Paste them at the VERY TOP of your main CSS file`);
100
+ console.log(` (e.g. ${mainCssFile})\n`);
547
101
  }
548
102
 
549
- // Provide next steps
550
- console.log('\n📝 Next steps:');
551
- if (!frameworkDirectives) {
552
- console.log(' 1. Ensure Tailwind CSS is configured in your project');
553
- console.log(' 2. Add Tailwind imports at the top of this CSS file if needed');
103
+ // Ask about copying examples (if not in wizard mode, ask for Next.js projects)
104
+ let shouldCopyExamples = wiz.copyExamples;
105
+ if (!args['dry-run'] && !shouldCopyExamples && framework === 'nextjs' && !args.wizard) {
106
+ shouldCopyExamples = await askAboutExamples(framework);
554
107
  }
555
- console.log(' 3. The CSS file is ready to use!');
556
108
 
557
- // Offer to copy example files for Next.js
558
- if (projectType === 'nextjs') {
559
- const copyExamples = await askQuestion('\n Would you like to copy example Next.js files (page.tsx, layout.tsx)? (y/n): ');
560
- if (copyExamples === 'y' || copyExamples === 'yes') {
561
- await copyNextJsExamples();
109
+ // Copy example files if requested
110
+ if (!args['dry-run'] && shouldCopyExamples) {
111
+ const copied = await copyNextJsExamples(framework, { force: args.force });
112
+ if (copied && copied.length === 0) {
113
+ // Files were skipped (user said no to overwrite)
114
+ console.log('\n⏭️ Example files skipped. Run with --force to overwrite existing files.\n');
562
115
  }
563
116
  }
564
117
 
565
- console.log('\n✨ Done!');
566
- } catch (error) {
567
- console.error('❌ Error:', error.message);
568
- if (error.message.includes('not found')) {
569
- console.error(' Please ensure you are running this from the nqui package directory');
570
- console.error(' or that the source CSS files exist.');
118
+ if (!args['dry-run']) {
119
+ console.log(`\n✅ nqui installed successfully`);
571
120
  }
121
+ } catch (err) {
122
+ console.error('❌ Error:', err.message);
572
123
  process.exit(1);
573
124
  }
574
- }
575
-
576
- main().catch((error) => {
577
- console.error('❌ Unexpected error:', error);
578
- process.exit(1);
579
- });
580
-
125
+ })();