rizzo-css 0.0.61 → 0.0.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +9 -5
  2. package/bin/rizzo-css.js +568 -79
  3. package/dist/rizzo.min.css +5 -3
  4. package/package.json +14 -7
  5. package/scaffold/astro/Footer.astro +8 -0
  6. package/scaffold/astro/Settings.astro +8 -2
  7. package/scaffold/astro/Tabs.astro +2 -2
  8. package/scaffold/react/Accordion.tsx +143 -0
  9. package/scaffold/react/Alert.tsx +90 -0
  10. package/scaffold/react/AlertDialog.tsx +80 -0
  11. package/scaffold/react/AspectRatio.tsx +32 -0
  12. package/scaffold/react/Avatar.tsx +53 -0
  13. package/scaffold/react/BackToTop.tsx +62 -0
  14. package/scaffold/react/Badge.tsx +39 -0
  15. package/scaffold/react/Breadcrumb.tsx +81 -0
  16. package/scaffold/react/Button.tsx +40 -0
  17. package/scaffold/react/ButtonGroup.tsx +24 -0
  18. package/scaffold/react/Card.tsx +26 -0
  19. package/scaffold/react/Checkbox.tsx +40 -0
  20. package/scaffold/react/Collapsible.tsx +58 -0
  21. package/scaffold/react/ContextMenu.tsx +67 -0
  22. package/scaffold/react/CopyToClipboard.tsx +128 -0
  23. package/scaffold/react/Dashboard.tsx +23 -0
  24. package/scaffold/react/Divider.tsx +47 -0
  25. package/scaffold/react/DocsSidebar.tsx +48 -0
  26. package/scaffold/react/Dropdown.tsx +256 -0
  27. package/scaffold/react/Empty.tsx +29 -0
  28. package/scaffold/react/FontSwitcher.tsx +68 -0
  29. package/scaffold/react/Footer.tsx +55 -0
  30. package/scaffold/react/FormGroup.tsx +57 -0
  31. package/scaffold/react/HoverCard.tsx +61 -0
  32. package/scaffold/react/Icons.tsx +22 -0
  33. package/scaffold/react/Input.tsx +69 -0
  34. package/scaffold/react/Kbd.tsx +16 -0
  35. package/scaffold/react/Label.tsx +16 -0
  36. package/scaffold/react/Modal.tsx +149 -0
  37. package/scaffold/react/Navbar.tsx +72 -0
  38. package/scaffold/react/Pagination.tsx +155 -0
  39. package/scaffold/react/Popover.tsx +66 -0
  40. package/scaffold/react/ProgressBar.tsx +66 -0
  41. package/scaffold/react/Radio.tsx +38 -0
  42. package/scaffold/react/ResizableHandle.tsx +24 -0
  43. package/scaffold/react/ResizablePane.tsx +29 -0
  44. package/scaffold/react/ResizablePaneGroup.tsx +29 -0
  45. package/scaffold/react/ScrollArea.tsx +29 -0
  46. package/scaffold/react/Search.tsx +62 -0
  47. package/scaffold/react/Select.tsx +65 -0
  48. package/scaffold/react/Separator.tsx +33 -0
  49. package/scaffold/react/Settings.tsx +60 -0
  50. package/scaffold/react/Sheet.tsx +86 -0
  51. package/scaffold/react/Skeleton.tsx +32 -0
  52. package/scaffold/react/Slider.tsx +66 -0
  53. package/scaffold/react/SoundEffects.tsx +15 -0
  54. package/scaffold/react/Spinner.tsx +36 -0
  55. package/scaffold/react/Switch.tsx +52 -0
  56. package/scaffold/react/Table.tsx +178 -0
  57. package/scaffold/react/Tabs.tsx +143 -0
  58. package/scaffold/react/Textarea.tsx +69 -0
  59. package/scaffold/react/ThemeSwitcher.tsx +89 -0
  60. package/scaffold/react/Toast.tsx +43 -0
  61. package/scaffold/react/Toggle.tsx +45 -0
  62. package/scaffold/react/ToggleGroup.tsx +34 -0
  63. package/scaffold/react/Tooltip.tsx +40 -0
  64. package/scaffold/vanilla/README-RIZZO.md +1 -1
  65. package/scaffold/vanilla/components/accordion.html +40 -0
  66. package/scaffold/vanilla/components/alert-dialog.html +40 -0
  67. package/scaffold/vanilla/components/alert.html +40 -0
  68. package/scaffold/vanilla/components/aspect-ratio.html +40 -0
  69. package/scaffold/vanilla/components/avatar.html +40 -0
  70. package/scaffold/vanilla/components/back-to-top.html +40 -0
  71. package/scaffold/vanilla/components/badge.html +40 -0
  72. package/scaffold/vanilla/components/breadcrumb.html +40 -0
  73. package/scaffold/vanilla/components/button-group.html +40 -0
  74. package/scaffold/vanilla/components/button.html +40 -0
  75. package/scaffold/vanilla/components/cards.html +40 -0
  76. package/scaffold/vanilla/components/collapsible.html +40 -0
  77. package/scaffold/vanilla/components/context-menu.html +40 -0
  78. package/scaffold/vanilla/components/copy-to-clipboard.html +40 -0
  79. package/scaffold/vanilla/components/dashboard.html +40 -0
  80. package/scaffold/vanilla/components/divider.html +40 -0
  81. package/scaffold/vanilla/components/docs-sidebar.html +40 -0
  82. package/scaffold/vanilla/components/dropdown.html +40 -0
  83. package/scaffold/vanilla/components/empty.html +40 -0
  84. package/scaffold/vanilla/components/font-switcher.html +40 -0
  85. package/scaffold/vanilla/components/footer.html +40 -0
  86. package/scaffold/vanilla/components/forms.html +40 -0
  87. package/scaffold/vanilla/components/hover-card.html +40 -0
  88. package/scaffold/vanilla/components/icons.html +40 -0
  89. package/scaffold/vanilla/components/index.html +40 -0
  90. package/scaffold/vanilla/components/kbd.html +40 -0
  91. package/scaffold/vanilla/components/label.html +40 -0
  92. package/scaffold/vanilla/components/modal.html +40 -0
  93. package/scaffold/vanilla/components/navbar.html +40 -0
  94. package/scaffold/vanilla/components/pagination.html +40 -0
  95. package/scaffold/vanilla/components/popover.html +40 -0
  96. package/scaffold/vanilla/components/progress-bar.html +40 -0
  97. package/scaffold/vanilla/components/resizable.html +40 -0
  98. package/scaffold/vanilla/components/scroll-area.html +40 -0
  99. package/scaffold/vanilla/components/search.html +40 -0
  100. package/scaffold/vanilla/components/separator.html +40 -0
  101. package/scaffold/vanilla/components/settings.html +40 -0
  102. package/scaffold/vanilla/components/sheet.html +40 -0
  103. package/scaffold/vanilla/components/skeleton.html +40 -0
  104. package/scaffold/vanilla/components/slider.html +40 -0
  105. package/scaffold/vanilla/components/sound-effects.html +40 -0
  106. package/scaffold/vanilla/components/spinner.html +40 -0
  107. package/scaffold/vanilla/components/switch.html +40 -0
  108. package/scaffold/vanilla/components/table.html +40 -0
  109. package/scaffold/vanilla/components/tabs.html +40 -0
  110. package/scaffold/vanilla/components/theme-switcher.html +40 -0
  111. package/scaffold/vanilla/components/toast.html +40 -0
  112. package/scaffold/vanilla/components/toggle-group.html +40 -0
  113. package/scaffold/vanilla/components/toggle.html +40 -0
  114. package/scaffold/vanilla/components/tooltip.html +40 -0
  115. package/scaffold/vanilla/index.html +40 -0
  116. package/scaffold/vue/Accordion.vue +9 -0
  117. package/scaffold/vue/Alert.vue +9 -0
  118. package/scaffold/vue/AlertDialog.vue +9 -0
  119. package/scaffold/vue/AspectRatio.vue +9 -0
  120. package/scaffold/vue/Avatar.vue +9 -0
  121. package/scaffold/vue/BackToTop.vue +9 -0
  122. package/scaffold/vue/Badge.vue +28 -0
  123. package/scaffold/vue/Breadcrumb.vue +9 -0
  124. package/scaffold/vue/Button.vue +23 -0
  125. package/scaffold/vue/ButtonGroup.vue +9 -0
  126. package/scaffold/vue/Card.vue +21 -0
  127. package/scaffold/vue/Checkbox.vue +31 -0
  128. package/scaffold/vue/Collapsible.vue +9 -0
  129. package/scaffold/vue/ContextMenu.vue +9 -0
  130. package/scaffold/vue/CopyToClipboard.vue +9 -0
  131. package/scaffold/vue/Dashboard.vue +9 -0
  132. package/scaffold/vue/Divider.vue +23 -0
  133. package/scaffold/vue/DocsSidebar.vue +9 -0
  134. package/scaffold/vue/Dropdown.vue +9 -0
  135. package/scaffold/vue/Empty.vue +9 -0
  136. package/scaffold/vue/FontSwitcher.vue +9 -0
  137. package/scaffold/vue/Footer.vue +9 -0
  138. package/scaffold/vue/FormGroup.vue +45 -0
  139. package/scaffold/vue/HoverCard.vue +9 -0
  140. package/scaffold/vue/Icons.vue +9 -0
  141. package/scaffold/vue/Input.vue +59 -0
  142. package/scaffold/vue/Kbd.vue +9 -0
  143. package/scaffold/vue/Label.vue +23 -0
  144. package/scaffold/vue/Modal.vue +9 -0
  145. package/scaffold/vue/Navbar.vue +9 -0
  146. package/scaffold/vue/Pagination.vue +9 -0
  147. package/scaffold/vue/Popover.vue +9 -0
  148. package/scaffold/vue/ProgressBar.vue +9 -0
  149. package/scaffold/vue/Radio.vue +29 -0
  150. package/scaffold/vue/ResizableHandle.vue +9 -0
  151. package/scaffold/vue/ResizablePane.vue +9 -0
  152. package/scaffold/vue/ResizablePaneGroup.vue +9 -0
  153. package/scaffold/vue/ScrollArea.vue +9 -0
  154. package/scaffold/vue/Search.vue +9 -0
  155. package/scaffold/vue/Select.vue +52 -0
  156. package/scaffold/vue/Separator.vue +9 -0
  157. package/scaffold/vue/Settings.vue +9 -0
  158. package/scaffold/vue/Sheet.vue +9 -0
  159. package/scaffold/vue/Skeleton.vue +9 -0
  160. package/scaffold/vue/Slider.vue +9 -0
  161. package/scaffold/vue/SoundEffects.vue +9 -0
  162. package/scaffold/vue/Spinner.vue +21 -0
  163. package/scaffold/vue/Switch.vue +9 -0
  164. package/scaffold/vue/Table.vue +9 -0
  165. package/scaffold/vue/Tabs.vue +9 -0
  166. package/scaffold/vue/Textarea.vue +60 -0
  167. package/scaffold/vue/ThemeSwitcher.vue +9 -0
  168. package/scaffold/vue/Toast.vue +9 -0
  169. package/scaffold/vue/Toggle.vue +9 -0
  170. package/scaffold/vue/ToggleGroup.vue +9 -0
  171. package/scaffold/vue/Tooltip.vue +9 -0
package/bin/rizzo-css.js CHANGED
@@ -18,7 +18,7 @@ const CLI_BANNER = ` /\\___/\\
18
18
  |_| \\_\\___/____/____\\___/ \\____|____/____/
19
19
 
20
20
 
21
- Design system · Vanilla · Astro · Svelte`;
21
+ Design system · Vanilla · Astro · Svelte · React · Vue`;
22
22
 
23
23
  /** Rainbow theme colors for "RIZZOCSS" block (from our themes: red → orange → yellow → green → blue → purple → pink). */
24
24
  const BANNER_RAINBOW = [
@@ -79,7 +79,7 @@ const RIZZO_SNIPPET_FILE = 'RIZZO-SNIPPET.txt';
79
79
  const RIZZO_SETUP_FILE = 'RIZZO-SETUP.md';
80
80
 
81
81
  const COMMANDS = ['init', 'add', 'theme', 'doctor', 'help'];
82
- const FRAMEWORKS = ['vanilla', 'astro', 'svelte'];
82
+ const FRAMEWORKS = ['vanilla', 'astro', 'svelte', 'react', 'vue'];
83
83
  /** Supported package managers: detection, install/add commands, and --package-manager override. */
84
84
  const VALID_PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
85
85
 
@@ -95,7 +95,7 @@ const TEMPLATES = [
95
95
  /** Build RIZZO-SETUP.md content: instructions + snippets. Never overwrites; use for landing or when files were skipped. */
96
96
  function buildRizzoSetupMd(framework, opts) {
97
97
  const { linkTag, linkHref, theme, defaultDark, defaultLight, skippedFiles = [], exampleMinimalPage } = opts;
98
- const fw = framework === 'vanilla' ? 'Vanilla' : framework === 'astro' ? 'Astro' : 'Svelte';
98
+ const fw = framework === 'vanilla' ? 'Vanilla' : framework === 'astro' ? 'Astro' : framework === 'svelte' ? 'Svelte' : framework === 'react' ? 'React' : 'Vue';
99
99
  let md = `# Rizzo CSS setup
100
100
 
101
101
  This file was generated by \`npx rizzo-css init\` or \`npx rizzo-css add\`. Use it to integrate Rizzo CSS into your project. **We never overwrite your existing files** — add the snippets below where needed.
@@ -193,6 +193,7 @@ const LIGHT_THEMES = [
193
193
  'semi-light-purple',
194
194
  ];
195
195
  const THEMES = [...DARK_THEMES, ...LIGHT_THEMES];
196
+ const VALID_THEME_VALUES = ['system', ...THEMES];
196
197
  // Components available for scaffold (must match scaffold/svelte and scaffold/astro; missing files are skipped on copy)
197
198
  const SVELTE_COMPONENTS = [
198
199
  'Button', 'Badge', 'Card', 'Dashboard', 'Divider', 'DocsSidebar', 'Footer', 'Spinner', 'ProgressBar', 'Avatar', 'Alert',
@@ -212,6 +213,8 @@ const ASTRO_COMPONENTS = [
212
213
  'Label', 'Kbd', 'Collapsible', 'AlertDialog', 'AspectRatio', 'ButtonGroup', 'Empty', 'Separator',
213
214
  'Slider', 'Sheet', 'Popover', 'Toggle', 'ToggleGroup', 'ScrollArea', 'HoverCard', 'ContextMenu', 'ResizablePaneGroup', 'ResizablePane', 'ResizableHandle',
214
215
  ];
216
+ const REACT_COMPONENTS = [...ASTRO_COMPONENTS];
217
+ const VUE_COMPONENTS = [...ASTRO_COMPONENTS];
215
218
 
216
219
  // Base set for Manual: all interactive components we ship (so manual has a full working set). Full includes everything (same list).
217
220
  const RECOMMENDED_COMPONENTS = [
@@ -232,6 +235,8 @@ const VANILLA_JS_COMPONENTS = ['Modal', 'Dropdown', 'Tabs', 'Toast', 'ThemeSwitc
232
235
  const COMPONENT_DEPS = {
233
236
  astro: { Settings: ['ThemeSwitcher', 'FontSwitcher', 'SoundEffects'], Toast: ['Alert'], Navbar: ['Search', 'Settings'] },
234
237
  svelte: { Settings: ['ThemeSwitcher', 'FontSwitcher', 'SoundEffects'], Toast: ['Alert'], Navbar: ['Search', 'Settings'] },
238
+ react: { Settings: ['ThemeSwitcher', 'FontSwitcher', 'SoundEffects'], Toast: ['Alert'], Navbar: ['Search', 'Settings'] },
239
+ vue: { Settings: ['ThemeSwitcher', 'FontSwitcher', 'SoundEffects'], Toast: ['Alert'], Navbar: ['Search', 'Settings'] },
235
240
  };
236
241
 
237
242
  function getComponentDeps(framework, componentName) {
@@ -302,6 +307,8 @@ const C = {
302
307
  vanilla: '\u001b[38;5;226m', // Vanilla JS yellow
303
308
  astro: '\u001b[38;5;208m', // Astro orange #ff5d01
304
309
  svelte: '\u001b[38;5;202m', // Svelte orange #ff3e00
310
+ react: '\u001b[38;5;39m', // React blue
311
+ vue: '\u001b[38;5;42m', // Vue green
305
312
  };
306
313
 
307
314
  const CIRCLE_EMPTY = '\u25CB '; // ○
@@ -316,11 +323,57 @@ function getCssPath() {
316
323
  return join(getPackageRoot(), 'dist', 'rizzo.min.css');
317
324
  }
318
325
 
319
- /** Copy package dist/fonts into <cssTargetDir>/fonts so CSS url(./fonts/...) resolves. cssTargetDir is framework-specific (static/css | css). Not used for Astro; use copyRizzoCssAndFontsForAstro instead. */
320
- function copyRizzoFonts(cssTargetDir) {
326
+ /** Simple semver comparison: true if a > b (e.g. "1.2.3" > "1.2.2"). */
327
+ function semverGt(a, b) {
328
+ const pa = a.split('.').map((n) => parseInt(n, 10) || 0);
329
+ const pb = b.split('.').map((n) => parseInt(n, 10) || 0);
330
+ for (let i = 0; i < 3; i++) {
331
+ const na = pa[i] || 0, nb = pb[i] || 0;
332
+ if (na > nb) return true;
333
+ if (na < nb) return false;
334
+ }
335
+ return false;
336
+ }
337
+
338
+ /** Optional version check: if not --offline, fetch latest from registry and print one line if newer. Non-blocking (2s timeout). */
339
+ async function checkNewerVersion(argv) {
340
+ if (hasFlag(argv, '--offline')) return;
341
+ let current;
342
+ try {
343
+ const pkgPath = join(getPackageRoot(), 'package.json');
344
+ current = JSON.parse(readFileSync(pkgPath, 'utf8')).version;
345
+ } catch (_) { return; }
346
+ try {
347
+ const ac = new AbortController();
348
+ const timeout = setTimeout(() => ac.abort(), 2500);
349
+ const res = await fetch('https://registry.npmjs.org/rizzo-css/latest', { signal: ac.signal }).finally(() => clearTimeout(timeout));
350
+ if (!res || !res.ok) return;
351
+ const data = await res.json();
352
+ const latest = data.version;
353
+ if (latest && semverGt(latest, current)) {
354
+ console.log('\n \u2139 A newer version of rizzo-css is available: ' + latest + ' (you have ' + current + '). Run npm update rizzo-css or re-run npx rizzo-css add to use it.');
355
+ }
356
+ } catch (_) { /* ignore network or parse errors */ }
357
+ }
358
+
359
+ /** Copy package dist/fonts into <cssTargetDir>/fonts so CSS url(./fonts/...) resolves. cssTargetDir is framework-specific (static/css | css). Not used for Astro; use copyRizzoCssAndFontsForAstro instead. opts: optional { dryRun, plan }; when dryRun push relative paths to plan.wouldWrite (projectDir must be cwd). */
360
+ function copyRizzoFonts(cssTargetDir, opts) {
321
361
  const fontsSrc = join(getPackageRoot(), 'dist', 'fonts');
322
362
  if (!existsSync(fontsSrc)) return;
323
363
  const dest = join(cssTargetDir, 'fonts');
364
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
365
+ if (plan) {
366
+ function collectPaths(src, d) {
367
+ const entries = readdirSync(src, { withFileTypes: true });
368
+ for (const e of entries) {
369
+ const destPath = join(d, e.name);
370
+ if (e.isDirectory()) collectPaths(join(src, e.name), destPath);
371
+ else plan.wouldWrite.push(pathRelative(process.cwd(), destPath));
372
+ }
373
+ }
374
+ collectPaths(fontsSrc, dest);
375
+ return;
376
+ }
324
377
  mkdirSync(dest, { recursive: true });
325
378
  const entries = readdirSync(fontsSrc, { withFileTypes: true });
326
379
  for (const e of entries) {
@@ -334,11 +387,19 @@ function copyRizzoFonts(cssTargetDir) {
334
387
  }
335
388
  }
336
389
 
337
- /** Copy package dist/sfx (click.mp3 or click.wav) into projectDir/assets/sfx so sound-effects-inline.js can play the click sound. Used for Vanilla init and add. */
338
- function copyRizzoSfx(projectDir) {
390
+ /** Copy package dist/sfx (click.mp3 or click.wav) into projectDir/assets/sfx so sound-effects-inline.js can play the click sound. Used for Vanilla init and add. opts: optional { dryRun, plan }; when dryRun push relative paths to plan.wouldWrite. */
391
+ function copyRizzoSfx(projectDir, opts) {
339
392
  const sfxSrc = join(getPackageRoot(), 'dist', 'sfx');
340
393
  if (!existsSync(sfxSrc)) return;
341
394
  const dest = join(projectDir, 'assets', 'sfx');
395
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
396
+ if (plan) {
397
+ readdirSync(sfxSrc, { withFileTypes: true }).forEach((e) => {
398
+ if (!e.isDirectory() && (/\.mp3$/i.test(e.name) || /\.wav$/i.test(e.name)))
399
+ plan.wouldWrite.push(pathRelative(projectDir, join(dest, e.name)));
400
+ });
401
+ return;
402
+ }
342
403
  mkdirSync(dest, { recursive: true });
343
404
  const entries = readdirSync(sfxSrc, { withFileTypes: true });
344
405
  for (const e of entries) {
@@ -348,12 +409,34 @@ function copyRizzoSfx(projectDir) {
348
409
  }
349
410
  }
350
411
 
351
- /** Astro only: copy rizzo.min.css to public/css with font URLs rewritten to ../assets/fonts/ (relative so base path works), and copy fonts to public/assets/fonts. */
352
- function copyRizzoCssAndFontsForAstro(projectDir, cssSource) {
412
+ /** Astro only: copy rizzo.min.css to public/css with font URLs rewritten to ../assets/fonts/ (relative so base path works), and copy fonts to public/assets/fonts. opts: optional { dryRun, plan }; when dryRun push relative paths to plan.wouldWrite. */
413
+ function copyRizzoCssAndFontsForAstro(projectDir, cssSource, opts) {
353
414
  const cssDir = join(projectDir, 'public', 'css');
354
415
  const cssTarget = join(cssDir, 'rizzo.min.css');
355
416
  const fontsDest = join(projectDir, 'public', 'assets', 'fonts');
356
417
  const sfxDest = join(projectDir, 'public', 'assets', 'sfx');
418
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
419
+ if (plan) {
420
+ plan.wouldWrite.push(pathRelative(projectDir, cssTarget));
421
+ function collectPaths(src, dest) {
422
+ const entries = readdirSync(src, { withFileTypes: true });
423
+ for (const e of entries) {
424
+ const destPath = join(dest, e.name);
425
+ if (e.isDirectory()) collectPaths(join(src, e.name), destPath);
426
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
427
+ }
428
+ }
429
+ const fontsSrc = join(getPackageRoot(), 'dist', 'fonts');
430
+ if (existsSync(fontsSrc)) collectPaths(fontsSrc, fontsDest);
431
+ const sfxSrc = join(getPackageRoot(), 'dist', 'sfx');
432
+ if (existsSync(sfxSrc)) {
433
+ readdirSync(sfxSrc, { withFileTypes: true }).forEach((e) => {
434
+ if (!e.isDirectory() && (/\.mp3$/i.test(e.name) || /\.wav$/i.test(e.name)))
435
+ plan.wouldWrite.push(pathRelative(projectDir, join(sfxDest, e.name)));
436
+ });
437
+ }
438
+ return;
439
+ }
357
440
  mkdirSync(cssDir, { recursive: true });
358
441
  mkdirSync(fontsDest, { recursive: true });
359
442
  mkdirSync(sfxDest, { recursive: true });
@@ -382,12 +465,34 @@ function copyRizzoCssAndFontsForAstro(projectDir, cssSource) {
382
465
  }
383
466
  }
384
467
 
385
- /** SvelteKit only: copy rizzo.min.css to static/css with font URLs rewritten to ../assets/fonts/ (relative so base path works), and copy fonts to static/assets/fonts. */
386
- function copyRizzoCssAndFontsForSvelte(projectDir, cssSource) {
468
+ /** SvelteKit only: copy rizzo.min.css to static/css with font URLs rewritten to ../assets/fonts/ (relative so base path works), and copy fonts to static/assets/fonts. opts: optional { dryRun, plan }; when dryRun push relative paths to plan.wouldWrite. */
469
+ function copyRizzoCssAndFontsForSvelte(projectDir, cssSource, opts) {
387
470
  const cssDir = join(projectDir, 'static', 'css');
388
471
  const cssTarget = join(cssDir, 'rizzo.min.css');
389
472
  const fontsDest = join(projectDir, 'static', 'assets', 'fonts');
390
473
  const sfxDest = join(projectDir, 'static', 'assets', 'sfx');
474
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
475
+ if (plan) {
476
+ plan.wouldWrite.push(pathRelative(projectDir, cssTarget));
477
+ function collectPaths(src, dest) {
478
+ const entries = readdirSync(src, { withFileTypes: true });
479
+ for (const e of entries) {
480
+ const destPath = join(dest, e.name);
481
+ if (e.isDirectory()) collectPaths(join(src, e.name), destPath);
482
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
483
+ }
484
+ }
485
+ const fontsSrc = join(getPackageRoot(), 'dist', 'fonts');
486
+ if (existsSync(fontsSrc)) collectPaths(fontsSrc, fontsDest);
487
+ const sfxSrc = join(getPackageRoot(), 'dist', 'sfx');
488
+ if (existsSync(sfxSrc)) {
489
+ readdirSync(sfxSrc, { withFileTypes: true }).forEach((e) => {
490
+ if (!e.isDirectory() && (/\.mp3$/i.test(e.name) || /\.wav$/i.test(e.name)))
491
+ plan.wouldWrite.push(pathRelative(projectDir, join(sfxDest, e.name)));
492
+ });
493
+ }
494
+ return;
495
+ }
391
496
  mkdirSync(cssDir, { recursive: true });
392
497
  mkdirSync(fontsDest, { recursive: true });
393
498
  mkdirSync(sfxDest, { recursive: true });
@@ -416,12 +521,16 @@ function copyRizzoCssAndFontsForSvelte(projectDir, cssSource) {
416
521
  }
417
522
  }
418
523
 
419
- /** Copy the package LICENSE into the project dir as LICENSE-RIZZO so we do not overwrite an existing LICENSE. */
420
- function copyPackageLicense(projectDir) {
524
+ /** Copy the package LICENSE into the project dir as LICENSE-RIZZO so we do not overwrite an existing LICENSE. opts: optional { dryRun, plan }; when dryRun push relative path to plan.wouldWrite. */
525
+ function copyPackageLicense(projectDir, opts) {
421
526
  const licensePath = join(getPackageRoot(), 'LICENSE');
422
- if (existsSync(licensePath)) {
423
- copyFileSync(licensePath, join(projectDir, SCAFFOLD_LICENSE_FILENAME));
527
+ if (!existsSync(licensePath)) return;
528
+ const dest = join(projectDir, SCAFFOLD_LICENSE_FILENAME);
529
+ if (opts && opts.dryRun && opts.plan) {
530
+ opts.plan.wouldWrite.push(pathRelative(projectDir, dest));
531
+ return;
424
532
  }
533
+ copyFileSync(licensePath, dest);
425
534
  }
426
535
 
427
536
  /** Name of the scaffold gitignore file (no leading dot so npm pack includes it). Copied to project as .gitignore. */
@@ -615,11 +724,11 @@ function copyVariantOverlay(projectDir, framework, variation, replacements) {
615
724
  copyDirRecursiveWithReplacements(variantDir, projectDir, replacements);
616
725
  }
617
726
 
618
- /** Copy variant overlay with no-overwrite (for add to existing). Returns { skipped } like copyDirRecursiveWithReplacementsNoOverwrite. */
619
- function copyVariantOverlayNoOverwrite(projectDir, framework, variation, replacements) {
727
+ /** Copy variant overlay with no-overwrite (for add to existing). Returns { skipped } like copyDirRecursiveWithReplacementsNoOverwrite. opts: optional { dryRun, plan }. */
728
+ function copyVariantOverlayNoOverwrite(projectDir, framework, variation, replacements, opts) {
620
729
  const variantDir = getVariantDir(framework, variation);
621
730
  if (!variantDir) return { skipped: [] };
622
- return copyDirRecursiveWithReplacementsNoOverwrite(variantDir, projectDir, replacements, projectDir);
731
+ return copyDirRecursiveWithReplacementsNoOverwrite(variantDir, projectDir, replacements, projectDir, opts);
623
732
  }
624
733
 
625
734
  function question(prompt) {
@@ -969,13 +1078,13 @@ function multiSelectMenu(options, title, initialSelected) {
969
1078
  function printHelp() {
970
1079
  console.log(getBanner());
971
1080
  console.log(`
972
- rizzo-css CLI — Add Rizzo CSS to your project (Vanilla, Astro, Svelte)
1081
+ rizzo-css CLI — Add Rizzo CSS to your project (Vanilla, Astro, Svelte, React, Vue)
973
1082
 
974
1083
  Available commands: init, add, theme, doctor, help
975
1084
 
976
1085
  Flags summary:
977
1086
  init --yes --path <dir> --framework <fw> --template <t> --package-manager <pm> --install --no-install --no-git
978
- add --path <dir> --framework <fw> --template css-only|landing|docs|dashboard|full --no-snippet --readme --force --vanilla-js
1087
+ add --path <dir> --framework <fw> --template css-only|landing|docs|dashboard|full --dry-run --no-snippet --readme --force --vanilla-js
979
1088
  theme (no flags)
980
1089
  doctor Check config, CSS file, and optional layout link
981
1090
  help (no flags)
@@ -995,21 +1104,24 @@ Commands:
995
1104
  Options (init):
996
1105
  --yes Non-interactive: scaffold new in cwd with defaults (framework: astro, template: landing)
997
1106
  --path <dir> Project directory (relative to cwd or absolute). Scaffold and run install there. With --yes; interactive: "Enter path" option.
998
- --framework <fw> vanilla | astro | svelte (with --yes; otherwise first prompt)
1107
+ --framework <fw> vanilla | astro | svelte | react | vue (same 56 components each)
999
1108
  --template <t> create new: css-only | landing | docs | dashboard | full (default: landing). add: same options.
1000
1109
  --package-manager <pm> npm | pnpm | yarn | bun (with --yes, or skip PM prompt when interactive)
1001
1110
  --install After scaffolding, run package manager install in project directory (no prompt)
1002
1111
  --no-install Do not run install; do not prompt (skip "Run <pm> install? (Y/n)")
1112
+ --offline Use package manager cache only (no network). Passed to install/add when running PM.
1003
1113
  --no-git With --yes, skip initializing a git repository (default with --yes is to run git init)
1004
1114
  (Git: only init offers or runs git init. Interactive init: "Initialize git? (Y/n)". With --yes, git init runs unless --no-git. Add does not prompt for git. Init (create new): "Run <pm> install? (Y/n)" uses the package manager you selected (npm, pnpm, yarn, bun). Add: "Run <pm> add rizzo-css? (Y/n)" same. Vanilla has no package manager. rizzo-css.json is written only when the project does not already have one.)
1005
1115
 
1006
1116
  Options (add) — run from your existing project root; you will choose a template then select components (or CSS only):
1007
1117
  --template <t> css-only | landing | docs | dashboard | full (CSS only = stylesheet only; others = component picker)
1008
1118
  --path <dir> Target directory for rizzo.min.css (overrides config and framework default)
1009
- --framework <fw> vanilla | astro | svelte (overrides config and detection)
1119
+ --framework <fw> vanilla | astro | svelte | react | vue (overrides config and detection)
1120
+ --dry-run Preview which files would be written without writing; show RIZZO-SETUP.md snippet for skipped files
1010
1121
  --package-manager <pm> npm | pnpm | yarn | bun (override detection for install/print commands)
1011
1122
  --install-package After copying CSS, run package manager add rizzo-css
1012
1123
  --no-install Do not run install or add (overrides --install-package)
1124
+ --offline Use package manager cache only (no network). Passed to add when running PM.
1013
1125
  --no-snippet (Full only) Do not write RIZZO-SNIPPET.txt (link + theme copy-paste)
1014
1126
  --readme Write README-RIZZO.md into the project
1015
1127
  --force Overwrite existing rizzo.min.css without prompting
@@ -1056,6 +1168,9 @@ Component dependencies (Full template):
1056
1168
  Some components require others to work. The component picker adds required ones automatically.
1057
1169
  Full list of available components and what relies on what: npx rizzo-css help components
1058
1170
 
1171
+ Frameworks:
1172
+ Scaffolds (init/add): Vanilla, Astro, Svelte, React, Vue. Same 56 components in each framework’s syntax; same CSS and BEM. Docs: https://rizzo-css.vercel.app/docs/react and Vue.
1173
+
1059
1174
  Docs: https://rizzo-css.vercel.app
1060
1175
  `);
1061
1176
  }
@@ -1071,7 +1186,7 @@ function printHelpComponents() {
1071
1186
  console.log(`
1072
1187
  Components — full list and what relies on what
1073
1188
 
1074
- Available to pick (Astro & Svelte; same list):
1189
+ Available to pick (Astro, Svelte, React, Vue; same list):
1075
1190
  ` + line1 + (line2 ? ',\n ' + line2 : '') + (line3 ? ',\n ' + line3 : '') + `
1076
1191
 
1077
1192
  Dependencies (when you pick the component on the left, the right is added automatically):
@@ -1085,6 +1200,8 @@ Icons: copied whenever you add any component.
1085
1200
  Where components are copied:
1086
1201
  Astro → src/components/rizzo/ (import from there)
1087
1202
  Svelte → src/lib/rizzo/ (import from '$lib/rizzo')
1203
+ React → src/components/rizzo/ (import from there or @/components/rizzo)
1204
+ Vue → src/components/rizzo/ (import from there or @/components/rizzo)
1088
1205
  Vanilla → components/ (HTML) (for interactivity add js/main.js; use --vanilla-js on add)
1089
1206
 
1090
1207
  Core = all components above; dependencies are included so everything works.
@@ -1102,12 +1219,12 @@ function cmdTheme() {
1102
1219
  process.stdout.write('\nExample: <html lang="en" data-theme="github-dark-classic">\n\n');
1103
1220
  }
1104
1221
 
1105
- /** Check project for Rizzo CSS: config, CSS file, optional link in layout. */
1222
+ /** Check project for Rizzo CSS: config, CSS file, layout link, theme, fonts/sfx paths, and optional outdated-CSS hint. */
1106
1223
  function cmdDoctor() {
1107
1224
  const cwd = process.cwd();
1108
1225
  const config = readRizzoConfig(cwd);
1109
1226
  console.log(getBanner());
1110
- console.log(' Doctor — check config, CSS path, and layout link\n');
1227
+ console.log(' Doctor — check config, CSS path, layout link, theme, and assets\n');
1111
1228
  let ok = true;
1112
1229
  if (!config) {
1113
1230
  console.log(' ✗ No ' + RIZZO_CONFIG_FILE + '. Run: npx rizzo-css add or init');
@@ -1117,14 +1234,56 @@ function cmdDoctor() {
1117
1234
  const fw = config.framework || 'vanilla';
1118
1235
  const paths = getFrameworkCssPaths(fw);
1119
1236
  const targetDir = (config.targetDir || paths.targetDir);
1120
- const cssPath = fw === 'astro' ? join(cwd, 'public', 'css', 'rizzo.min.css') : fw === 'svelte' ? join(cwd, 'static', 'css', 'rizzo.min.css') : join(cwd, targetDir, 'rizzo.min.css');
1237
+ const cssPath = (fw === 'astro' || fw === 'react' || fw === 'vue') ? join(cwd, 'public', 'css', 'rizzo.min.css') : fw === 'svelte' ? join(cwd, 'static', 'css', 'rizzo.min.css') : join(cwd, targetDir, 'rizzo.min.css');
1121
1238
  if (!existsSync(cssPath)) {
1122
1239
  console.log(' ✗ CSS not found at ' + cssPath);
1123
1240
  ok = false;
1124
1241
  } else {
1125
1242
  console.log(' ✓ CSS at ' + cssPath);
1243
+ const cssSize = statSync(cssPath).size;
1244
+ if (cssSize < 5000) {
1245
+ console.log(' ? CSS file is very small (' + cssSize + ' B). Run pnpm build:css in the repo or re-run npx rizzo-css add to refresh.');
1246
+ ok = false;
1247
+ }
1126
1248
  }
1127
- const layoutPaths = fw === 'svelte' ? ['src/app.html'] : fw === 'astro' ? ['src/layouts/Layout.astro', 'src/layouts/BaseLayout.astro'] : [];
1249
+ if (config.theme != null && config.theme !== '') {
1250
+ if (VALID_THEME_VALUES.includes(config.theme)) {
1251
+ console.log(' ✓ Theme (from config): ' + config.theme);
1252
+ } else {
1253
+ console.log(' ✗ Invalid theme in config: "' + config.theme + '". Use one of: system, ' + THEMES.slice(0, 5).join(', ') + ', ... (run npx rizzo-css theme for full list)');
1254
+ ok = false;
1255
+ }
1256
+ }
1257
+ const fontsDir = (fw === 'astro' || fw === 'react' || fw === 'vue') ? join(cwd, 'public', 'assets', 'fonts') : fw === 'svelte' ? join(cwd, 'static', 'assets', 'fonts') : join(cwd, targetDir, 'fonts');
1258
+ if (!existsSync(fontsDir)) {
1259
+ console.log(' ? Fonts path not found: ' + pathRelative(cwd, fontsDir) + ' (CSS may reference ../assets/fonts/ or ./fonts/; add fonts if you use theme typography)');
1260
+ } else {
1261
+ try {
1262
+ const entries = readdirSync(fontsDir, { withFileTypes: true });
1263
+ const hasFiles = entries.some((e) => e.isFile() && /\.(woff2?|ttf|otf)$/i.test(e.name));
1264
+ if (!hasFiles) {
1265
+ console.log(' ? Fonts dir exists but is empty or has no font files: ' + pathRelative(cwd, fontsDir));
1266
+ } else {
1267
+ console.log(' ✓ Fonts at ' + pathRelative(cwd, fontsDir));
1268
+ }
1269
+ } catch (_) {
1270
+ console.log(' ? Could not read fonts dir: ' + pathRelative(cwd, fontsDir));
1271
+ }
1272
+ }
1273
+ const sfxDir = (fw === 'astro' || fw === 'react' || fw === 'vue') ? join(cwd, 'public', 'assets', 'sfx') : fw === 'svelte' ? join(cwd, 'static', 'assets', 'sfx') : join(cwd, 'assets', 'sfx');
1274
+ if (!existsSync(sfxDir)) {
1275
+ console.log(' ? Sound effects path not found: ' + pathRelative(cwd, sfxDir) + ' (optional; needed if you use Settings or SoundEffects with click sounds)');
1276
+ } else {
1277
+ try {
1278
+ const entries = readdirSync(sfxDir, { withFileTypes: true });
1279
+ const hasSfx = entries.some((e) => e.isFile() && (/\.(mp3|wav)$/i.test(e.name)));
1280
+ if (hasSfx) console.log(' ✓ Sound effects at ' + pathRelative(cwd, sfxDir));
1281
+ else console.log(' ? Sfx dir exists but has no .mp3/.wav: ' + pathRelative(cwd, sfxDir));
1282
+ } catch (_) {
1283
+ console.log(' ? Could not read sfx dir: ' + pathRelative(cwd, sfxDir));
1284
+ }
1285
+ }
1286
+ const layoutPaths = fw === 'svelte' ? ['src/app.html'] : fw === 'astro' ? ['src/layouts/Layout.astro', 'src/layouts/BaseLayout.astro'] : fw === 'react' ? ['index.html', 'src/main.tsx', 'src/main.jsx', 'src/App.tsx', 'src/App.jsx'] : fw === 'vue' ? ['index.html', 'src/main.ts', 'src/main.js', 'src/App.vue'] : [];
1128
1287
  for (const lp of layoutPaths) {
1129
1288
  const full = join(cwd, lp);
1130
1289
  if (existsSync(full)) {
@@ -1134,11 +1293,25 @@ function cmdDoctor() {
1134
1293
  } else {
1135
1294
  console.log(' ✓ Layout ' + lp + ' includes Rizzo link');
1136
1295
  }
1296
+ if (content.includes('data-theme=')) {
1297
+ const match = content.match(/data-theme=["']([^"']+)["']/);
1298
+ if (match && !VALID_THEME_VALUES.includes(match[1])) {
1299
+ console.log(' ? Layout has data-theme="' + match[1] + '" which is not a known theme. Use: npx rizzo-css theme');
1300
+ ok = false;
1301
+ }
1302
+ }
1137
1303
  break;
1138
1304
  }
1139
1305
  }
1306
+ const pkgPath = join(cwd, 'node_modules', 'rizzo-css', 'package.json');
1307
+ if (existsSync(pkgPath)) {
1308
+ try {
1309
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
1310
+ const ver = pkg.version;
1311
+ if (ver) console.log(' ℹ rizzo-css in node_modules: v' + ver + '. Re-run npx rizzo-css add to refresh CSS if needed.');
1312
+ } catch (_) { /* ignore */ }
1313
+ }
1140
1314
  }
1141
- if (config && config.theme) console.log(' Theme (from config): ' + config.theme);
1142
1315
  console.log(ok ? '\nAll checks passed.\n' : '\nFix the items above, then run your dev server.\n');
1143
1316
  }
1144
1317
 
@@ -1205,16 +1378,27 @@ async function promptComponentChoice(componentList, framework, initialSelection)
1205
1378
  );
1206
1379
  }
1207
1380
 
1208
- /** Detect framework from cwd: "svelte" | "astro" | null. */
1381
+ /** Detect framework from cwd: "svelte" | "astro" | "react" | "vue" | null. */
1209
1382
  function detectFramework(cwd) {
1210
1383
  if (existsSync(join(cwd, 'svelte.config.js')) || existsSync(join(cwd, 'svelte.config.ts'))) return 'svelte';
1211
1384
  if (existsSync(join(cwd, 'astro.config.mjs')) || existsSync(join(cwd, 'astro.config.mts')) || existsSync(join(cwd, 'astro.config.js'))) return 'astro';
1385
+ if (existsSync(join(cwd, 'vite.config.js')) || existsSync(join(cwd, 'vite.config.ts'))) {
1386
+ try {
1387
+ const pkg = readFileSync(join(cwd, 'package.json'), 'utf8');
1388
+ const json = JSON.parse(pkg);
1389
+ const deps = { ...json.dependencies, ...(json.devDependencies || {}) };
1390
+ if (deps['vue']) return 'vue';
1391
+ if (deps['react'] || deps['react-dom']) return 'react';
1392
+ } catch (_) { /* ignore */ }
1393
+ }
1212
1394
  try {
1213
1395
  const pkg = readFileSync(join(cwd, 'package.json'), 'utf8');
1214
1396
  const json = JSON.parse(pkg);
1215
1397
  const deps = { ...json.dependencies, ...(json.devDependencies || {}) };
1216
1398
  if (deps['@sveltejs/kit'] || deps['svelte']) return 'svelte';
1217
1399
  if (deps['astro']) return 'astro';
1400
+ if (deps['vue']) return 'vue';
1401
+ if (deps['react'] || deps['react-dom']) return 'react';
1218
1402
  } catch (_) { /* ignore */ }
1219
1403
  return null;
1220
1404
  }
@@ -1232,6 +1416,12 @@ function getFrameworkCssPaths(framework) {
1232
1416
  if (framework === 'astro') {
1233
1417
  return { targetDir: 'public/css', linkHref: '/css/rizzo.min.css', fontsDir: 'public/assets/fonts', assetsRoot: 'public' };
1234
1418
  }
1419
+ if (framework === 'react') {
1420
+ return { targetDir: 'public/css', linkHref: '/css/rizzo.min.css', fontsDir: 'public/assets/fonts', assetsRoot: 'public' };
1421
+ }
1422
+ if (framework === 'vue') {
1423
+ return { targetDir: 'public/css', linkHref: '/css/rizzo.min.css', fontsDir: 'public/assets/fonts', assetsRoot: 'public' };
1424
+ }
1235
1425
  return { targetDir: 'css', linkHref: 'css/rizzo.min.css', fontsDir: 'css/fonts', assetsRoot: '' };
1236
1426
  }
1237
1427
 
@@ -1241,7 +1431,7 @@ function getFrameworkCssPaths(framework) {
1241
1431
  */
1242
1432
  function getLinkHrefForTargetDir(framework, targetDir) {
1243
1433
  const file = 'rizzo.min.css';
1244
- if (framework === 'astro' && targetDir) {
1434
+ if ((framework === 'astro' || framework === 'react' || framework === 'vue') && targetDir) {
1245
1435
  const path = targetDir.replace(/^public\/?/, '').replace(/\/+$/, '') || 'css';
1246
1436
  return '/' + (path ? path + '/' : '') + file;
1247
1437
  }
@@ -1266,6 +1456,7 @@ async function cmdAdd(argv) {
1266
1456
  const writeReadme = hasFlag(argv, '--readme');
1267
1457
  const force = hasFlag(argv, '--force');
1268
1458
  const copyVanillaJs = hasFlag(argv, '--vanilla-js');
1459
+ const dryRun = hasFlag(argv, '--dry-run');
1269
1460
  const positionals = getPositionalArgs(argv, 3);
1270
1461
 
1271
1462
  const cwd = process.cwd();
@@ -1283,21 +1474,25 @@ async function cmdAdd(argv) {
1283
1474
  copyVanillaJs,
1284
1475
  installPackage: installPackage || undefined,
1285
1476
  noInstall: noInstall || undefined,
1477
+ dryRun: dryRun || undefined,
1478
+ plan: dryRun ? { wouldWrite: [], skipped: [] } : undefined,
1286
1479
  };
1287
1480
  await runAddToExisting(explicitFramework, options);
1481
+ if (options.dryRun) return;
1288
1482
  if (installPackage && !noInstall) {
1289
1483
  const pm = (pmOverride
1290
1484
  ? getPackageManagerCommands({ agent: pmOverride, command: pmOverride })
1291
1485
  : (config && config.packageManager)
1292
1486
  ? getPackageManagerCommands({ agent: config.packageManager, command: config.packageManager })
1293
1487
  : resolvePackageManager(cwd));
1294
- const addPkg = pm.add('rizzo-css');
1488
+ const addPkg = pm.add('rizzo-css') + (hasFlag(argv, '--offline') ? ' --offline' : '');
1295
1489
  console.log('\nRunning: ' + addPkg);
1296
1490
  const code = runInDir(cwd, addPkg);
1297
1491
  if (code !== 0) {
1298
1492
  console.error('\nInstall failed (exit ' + code + '). You can run manually: ' + addPkg);
1299
1493
  }
1300
1494
  }
1495
+ await checkNewerVersion(argv).catch(() => {});
1301
1496
  }
1302
1497
 
1303
1498
  function getScaffoldSvelteDir() {
@@ -1308,6 +1503,14 @@ function getScaffoldAstroDir() {
1308
1503
  return join(getPackageRoot(), 'scaffold', 'astro');
1309
1504
  }
1310
1505
 
1506
+ function getScaffoldReactDir() {
1507
+ return join(getPackageRoot(), 'scaffold', 'react');
1508
+ }
1509
+
1510
+ function getScaffoldVueDir() {
1511
+ return join(getPackageRoot(), 'scaffold', 'vue');
1512
+ }
1513
+
1311
1514
  function getScaffoldUtilsDir() {
1312
1515
  return join(getPackageRoot(), 'scaffold', 'utils');
1313
1516
  }
@@ -1372,8 +1575,8 @@ function getNavbarHtmlVanilla(title, brandHref) {
1372
1575
  return html.replace(/\{\{TITLE\}\}/g, title || 'App').replace(/\{\{NAVBAR_BRAND_HREF\}\}/g, brandHref || '#');
1373
1576
  }
1374
1577
 
1375
- /** Copy selected Vanilla component HTML files into projectDir/components/, with replacements. Writes a simple components/index.html. */
1376
- function copyVanillaComponents(projectDir, selectedNames, replacements) {
1578
+ /** Copy selected Vanilla component HTML files into projectDir/components/, with replacements. Writes a simple components/index.html. opts: optional { dryRun, plan }; when dryRun push relative paths to plan.wouldWrite. */
1579
+ function copyVanillaComponents(projectDir, selectedNames, replacements, opts) {
1377
1580
  const srcDir = getScaffoldVanillaComponentsDir();
1378
1581
  if (!existsSync(srcDir)) return;
1379
1582
  const linkHref = replacements['{{LINK_HREF}}'] || 'css/rizzo.min.css';
@@ -1392,6 +1595,12 @@ function copyVanillaComponents(projectDir, selectedNames, replacements) {
1392
1595
  }
1393
1596
  if (slugsToCopy.length === 0) return;
1394
1597
  const destDir = join(projectDir, 'components');
1598
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1599
+ if (plan) {
1600
+ slugsToCopy.forEach((slug) => plan.wouldWrite.push(pathRelative(projectDir, join(destDir, slug + '.html'))));
1601
+ plan.wouldWrite.push(pathRelative(projectDir, join(destDir, 'index.html')));
1602
+ return;
1603
+ }
1395
1604
  mkdirSync(destDir, { recursive: true });
1396
1605
  for (const slug of slugsToCopy) {
1397
1606
  const src = join(srcDir, slug + '.html');
@@ -1430,24 +1639,56 @@ ${indexLinks}
1430
1639
  writeFileSync(join(destDir, 'index.html'), indexHtml, 'utf8');
1431
1640
  }
1432
1641
 
1433
- /** Copy Rizzo icons into the project for the given framework. */
1434
- function copyRizzoIcons(projectDir, framework) {
1642
+ /** Copy Rizzo icons into the project for the given framework. opts: optional { dryRun, plan }; when dryRun push relative paths to plan.wouldWrite. */
1643
+ function copyRizzoIcons(projectDir, framework, opts) {
1644
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1645
+ function collectIconPaths(src, dest) {
1646
+ const entries = readdirSync(src, { withFileTypes: true });
1647
+ for (const e of entries) {
1648
+ const destPath = join(dest, e.name);
1649
+ if (e.isDirectory()) collectIconPaths(join(src, e.name), destPath);
1650
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
1651
+ }
1652
+ }
1435
1653
  if (framework === 'astro') {
1436
1654
  const iconsSrc = join(getScaffoldAstroDir(), 'icons');
1437
1655
  if (!existsSync(iconsSrc)) return;
1438
1656
  const targetDir = join(projectDir, 'src', 'components', 'rizzo', 'icons');
1657
+ if (plan) {
1658
+ collectIconPaths(iconsSrc, targetDir);
1659
+ return;
1660
+ }
1439
1661
  mkdirSync(targetDir, { recursive: true });
1440
1662
  copyDirRecursive(iconsSrc, targetDir);
1441
1663
  } else if (framework === 'svelte') {
1442
1664
  const iconsSrc = join(getScaffoldSvelteDir(), 'icons');
1443
1665
  if (!existsSync(iconsSrc)) return;
1444
1666
  const targetDir = join(projectDir, 'src', 'lib', 'rizzo', 'icons');
1667
+ if (plan) {
1668
+ collectIconPaths(iconsSrc, targetDir);
1669
+ return;
1670
+ }
1671
+ mkdirSync(targetDir, { recursive: true });
1672
+ copyDirRecursive(iconsSrc, targetDir);
1673
+ } else if (framework === 'react' || framework === 'vue') {
1674
+ const scaffoldDir = framework === 'react' ? getScaffoldReactDir() : getScaffoldVueDir();
1675
+ const iconsSrc = join(scaffoldDir, 'icons');
1676
+ if (!existsSync(iconsSrc)) return;
1677
+ const targetDir = join(projectDir, 'src', 'components', 'rizzo', 'icons');
1678
+ if (plan) {
1679
+ collectIconPaths(iconsSrc, targetDir);
1680
+ return;
1681
+ }
1445
1682
  mkdirSync(targetDir, { recursive: true });
1446
1683
  copyDirRecursive(iconsSrc, targetDir);
1447
1684
  } else if (framework === 'vanilla') {
1448
1685
  const iconsSrc = getScaffoldVanillaIconsDir();
1449
1686
  if (!existsSync(iconsSrc)) return;
1450
1687
  const targetDir = join(projectDir, 'icons');
1688
+ if (plan) {
1689
+ collectIconPaths(iconsSrc, targetDir);
1690
+ return;
1691
+ }
1451
1692
  mkdirSync(targetDir, { recursive: true });
1452
1693
  copyDirRecursive(iconsSrc, targetDir);
1453
1694
  }
@@ -1504,13 +1745,15 @@ function copyDirRecursiveWithReplacements(src, dest, replacements) {
1504
1745
  * Like copyDirRecursiveWithReplacements but never overwrites existing files.
1505
1746
  * Returns { skipped: Array<{ relativePath, content }> } for files that already existed (so caller can write RIZZO-SETUP.md).
1506
1747
  * relativePath is from projectDir (dest).
1748
+ * opts: optional { dryRun, plan: { wouldWrite: string[], skipped: [] } }. When dryRun, no I/O; plan.wouldWrite gets new file paths, plan.skipped gets skipped entries (merged with returned skipped).
1507
1749
  */
1508
- function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, projectDir) {
1750
+ function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, projectDir, opts) {
1509
1751
  const skipped = [];
1510
1752
  projectDir = projectDir || dest;
1753
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1511
1754
  const textExtensions = new Set(['.html', '.astro', '.svelte', '.ts', '.js', '.mjs', '.json', '.css', '.md']);
1512
1755
  function recurse(s, d) {
1513
- mkdirSync(d, { recursive: true });
1756
+ if (!plan) mkdirSync(d, { recursive: true });
1514
1757
  const entries = readdirSync(s, { withFileTypes: true });
1515
1758
  for (const e of entries) {
1516
1759
  const srcPath = join(s, e.name);
@@ -1518,8 +1761,8 @@ function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, pr
1518
1761
  if (e.isDirectory()) {
1519
1762
  recurse(srcPath, destPath);
1520
1763
  } else {
1764
+ const rel = pathRelative(projectDir, destPath);
1521
1765
  if (existsSync(destPath)) {
1522
- const rel = pathRelative(projectDir, destPath);
1523
1766
  let content = '';
1524
1767
  const ext = srcPath.slice(srcPath.lastIndexOf('.'));
1525
1768
  if (textExtensions.has(ext)) {
@@ -1531,6 +1774,10 @@ function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, pr
1531
1774
  skipped.push({ relativePath: rel, content });
1532
1775
  continue;
1533
1776
  }
1777
+ if (plan) {
1778
+ plan.wouldWrite.push(rel);
1779
+ continue;
1780
+ }
1534
1781
  const ext = srcPath.slice(srcPath.lastIndexOf('.'));
1535
1782
  if (textExtensions.has(ext)) {
1536
1783
  let content = readFileSync(srcPath, 'utf8');
@@ -1548,7 +1795,7 @@ function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, pr
1548
1795
  return { skipped };
1549
1796
  }
1550
1797
 
1551
- function copySvelteComponents(projectDir, selectedNames) {
1798
+ function copySvelteComponents(projectDir, selectedNames, opts) {
1552
1799
  const scaffoldDir = getScaffoldSvelteDir();
1553
1800
  if (!existsSync(scaffoldDir)) {
1554
1801
  console.log('\n Component templates not in this package; use CSS only or copy from repo: https://github.com/mingleusa/rizzo-css/tree/main/src/components/svelte');
@@ -1563,6 +1810,28 @@ function copySvelteComponents(projectDir, selectedNames) {
1563
1810
  return;
1564
1811
  }
1565
1812
  const targetDir = join(projectDir, 'src', 'lib', 'rizzo');
1813
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1814
+ if (plan) {
1815
+ toCopy.forEach((name) => { plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, name + '.svelte'))); });
1816
+ const iconsSrc = join(scaffoldDir, 'icons');
1817
+ if (existsSync(iconsSrc) && (toCopy.length > 0 || copyIconsOnly)) {
1818
+ (function collect(s, d) {
1819
+ readdirSync(s, { withFileTypes: true }).forEach((e) => {
1820
+ const destPath = join(d, e.name);
1821
+ if (e.isDirectory()) collect(join(s, e.name), destPath);
1822
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
1823
+ });
1824
+ })(iconsSrc, join(targetDir, 'icons'));
1825
+ }
1826
+ if (toCopy.includes('ThemeSwitcher') || toCopy.includes('ThemeIcon')) {
1827
+ if (existsSync(join(scaffoldDir, 'themes.ts'))) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'themes.ts')));
1828
+ if (existsSync(join(scaffoldDir, 'theme.ts'))) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'theme.ts')));
1829
+ }
1830
+ if (toCopy.includes('Settings') && existsSync(join(getScaffoldConfigDir(), 'fonts.ts')))
1831
+ plan.wouldWrite.push(pathRelative(projectDir, join(projectDir, 'src', 'lib', 'config', 'fonts.ts')));
1832
+ if (toCopy.length > 0) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'index.ts')));
1833
+ return;
1834
+ }
1566
1835
  mkdirSync(targetDir, { recursive: true });
1567
1836
  const exports = [];
1568
1837
  for (const name of toCopy) {
@@ -1602,7 +1871,7 @@ function copySvelteComponents(projectDir, selectedNames) {
1602
1871
  }
1603
1872
  }
1604
1873
 
1605
- function copyAstroComponents(projectDir, selectedNames) {
1874
+ function copyAstroComponents(projectDir, selectedNames, opts) {
1606
1875
  const scaffoldDir = getScaffoldAstroDir();
1607
1876
  if (!existsSync(scaffoldDir)) {
1608
1877
  console.log('\n Astro component templates not in this package; use CSS only or copy from repo: https://github.com/mingleusa/rizzo-css/tree/main/src/components');
@@ -1617,6 +1886,27 @@ function copyAstroComponents(projectDir, selectedNames) {
1617
1886
  return;
1618
1887
  }
1619
1888
  const targetDir = join(projectDir, 'src', 'components', 'rizzo');
1889
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1890
+ if (plan) {
1891
+ toCopy.forEach((name) => { plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, name + '.astro'))); });
1892
+ const iconsSrc = join(scaffoldDir, 'icons');
1893
+ if (existsSync(iconsSrc) && (toCopy.length > 0 || copyIconsOnly)) {
1894
+ (function collect(s, d) {
1895
+ readdirSync(s, { withFileTypes: true }).forEach((e) => {
1896
+ const destPath = join(d, e.name);
1897
+ if (e.isDirectory()) collect(join(s, e.name), destPath);
1898
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
1899
+ });
1900
+ })(iconsSrc, join(targetDir, 'icons'));
1901
+ }
1902
+ if (toCopy.includes('ThemeSwitcher') || toCopy.includes('ThemeIcon')) {
1903
+ if (existsSync(join(scaffoldDir, 'themes.ts'))) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'themes.ts')));
1904
+ if (existsSync(join(getScaffoldUtilsDir(), 'theme.ts'))) plan.wouldWrite.push(pathRelative(projectDir, join(projectDir, 'src', 'components', 'utils', 'theme.ts')));
1905
+ }
1906
+ if (toCopy.includes('Settings') && existsSync(join(getScaffoldConfigDir(), 'fonts.ts')))
1907
+ plan.wouldWrite.push(pathRelative(projectDir, join(projectDir, 'src', 'components', 'config', 'fonts.ts')));
1908
+ return;
1909
+ }
1620
1910
  mkdirSync(targetDir, { recursive: true });
1621
1911
  let count = 0;
1622
1912
  for (const name of toCopy) {
@@ -1663,6 +1953,139 @@ function copyAstroComponents(projectDir, selectedNames) {
1663
1953
  }
1664
1954
  }
1665
1955
 
1956
+ function copyReactComponents(projectDir, selectedNames, opts) {
1957
+ const scaffoldDir = getScaffoldReactDir();
1958
+ if (!existsSync(scaffoldDir)) {
1959
+ console.log('\n React component templates not in this package; use CSS only or see docs.');
1960
+ return;
1961
+ }
1962
+ const files = readdirSync(scaffoldDir).filter((f) => f.endsWith('.tsx') || f.endsWith('.jsx'));
1963
+ const available = files.map((f) => f.replace(/\.(tsx|jsx)$/, ''));
1964
+ const toCopy = selectedNames.filter((n) => n !== 'Icons' && available.includes(n));
1965
+ const copyIconsOnly = selectedNames.includes('Icons') && toCopy.length === 0;
1966
+ if (toCopy.length === 0 && !copyIconsOnly) {
1967
+ console.log('\n No matching React components in scaffold; use CSS only or see docs.');
1968
+ return;
1969
+ }
1970
+ const targetDir = join(projectDir, 'src', 'components', 'rizzo');
1971
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1972
+ if (plan) {
1973
+ toCopy.forEach((name) => {
1974
+ const f = files.find((file) => file.replace(/\.(tsx|jsx)$/, '') === name);
1975
+ const ext = f ? (f.endsWith('.tsx') ? '.tsx' : '.jsx') : '.tsx';
1976
+ plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, name + ext)));
1977
+ });
1978
+ const iconsSrc = join(scaffoldDir, 'icons');
1979
+ if (existsSync(iconsSrc) && (toCopy.length > 0 || copyIconsOnly)) {
1980
+ (function collect(s, d) {
1981
+ readdirSync(s, { withFileTypes: true }).forEach((e) => {
1982
+ const destPath = join(d, e.name);
1983
+ if (e.isDirectory()) collect(join(s, e.name), destPath);
1984
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
1985
+ });
1986
+ })(iconsSrc, join(targetDir, 'icons'));
1987
+ }
1988
+ if (toCopy.length > 0) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'index.ts')));
1989
+ return;
1990
+ }
1991
+ mkdirSync(targetDir, { recursive: true });
1992
+ const exports = [];
1993
+ const extUsed = {};
1994
+ for (const name of toCopy) {
1995
+ const tsxPath = join(scaffoldDir, name + '.tsx');
1996
+ const jsxPath = join(scaffoldDir, name + '.jsx');
1997
+ const src = existsSync(tsxPath) ? tsxPath : existsSync(jsxPath) ? jsxPath : null;
1998
+ if (src) {
1999
+ const ext = src.endsWith('.tsx') ? '.tsx' : '.jsx';
2000
+ copyFileSync(src, join(targetDir, name + ext));
2001
+ exports.push(`export { default as ${name} } from './${name}${ext}';`);
2002
+ extUsed[name] = ext;
2003
+ }
2004
+ }
2005
+ const iconsSrc = join(scaffoldDir, 'icons');
2006
+ if (existsSync(iconsSrc) && (toCopy.length > 0 || copyIconsOnly)) copyDirRecursive(iconsSrc, join(targetDir, 'icons'));
2007
+ if (toCopy.includes('Settings')) {
2008
+ const configDir = getScaffoldConfigDir();
2009
+ const fontsSrc = join(configDir, 'fonts.ts');
2010
+ if (existsSync(fontsSrc)) {
2011
+ const projectConfigDir = join(projectDir, 'src', 'components', 'config');
2012
+ mkdirSync(projectConfigDir, { recursive: true });
2013
+ copyFileSync(fontsSrc, join(projectConfigDir, 'fonts.ts'));
2014
+ }
2015
+ }
2016
+ if (exports.length > 0 || copyIconsOnly) {
2017
+ if (exports.length > 0) {
2018
+ const indexContent = `/** Rizzo CSS React components — selected via npx rizzo-css add */\n${exports.join('\n')}\n`;
2019
+ writeFileSync(join(targetDir, 'index.ts'), indexContent, 'utf8');
2020
+ }
2021
+ const msg = copyIconsOnly ? 'Icons' : exports.length + ' React components' + (existsSync(iconsSrc) ? ' + icons' : '');
2022
+ console.log('\n ✓ ' + msg + ' copied to ' + targetDir);
2023
+ console.log(' Import in your app: import { Button, Badge, ... } from \'../components/rizzo\' or \'@/components/rizzo\';\n');
2024
+ }
2025
+ }
2026
+
2027
+ function copyVueComponents(projectDir, selectedNames, opts) {
2028
+ const scaffoldDir = getScaffoldVueDir();
2029
+ if (!existsSync(scaffoldDir)) {
2030
+ console.log('\n Vue component templates not in this package; use CSS only or see docs.');
2031
+ return;
2032
+ }
2033
+ const files = readdirSync(scaffoldDir).filter((f) => f.endsWith('.vue'));
2034
+ const available = files.map((f) => f.replace('.vue', ''));
2035
+ const toCopy = selectedNames.filter((n) => n !== 'Icons' && available.includes(n));
2036
+ const copyIconsOnly = selectedNames.includes('Icons') && toCopy.length === 0;
2037
+ if (toCopy.length === 0 && !copyIconsOnly) {
2038
+ console.log('\n No matching Vue components in scaffold; use CSS only or see docs.');
2039
+ return;
2040
+ }
2041
+ const targetDir = join(projectDir, 'src', 'components', 'rizzo');
2042
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
2043
+ if (plan) {
2044
+ toCopy.forEach((name) => { plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, name + '.vue'))); });
2045
+ const iconsSrc = join(scaffoldDir, 'icons');
2046
+ if (existsSync(iconsSrc) && (toCopy.length > 0 || copyIconsOnly)) {
2047
+ (function collect(s, d) {
2048
+ readdirSync(s, { withFileTypes: true }).forEach((e) => {
2049
+ const destPath = join(d, e.name);
2050
+ if (e.isDirectory()) collect(join(s, e.name), destPath);
2051
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
2052
+ });
2053
+ })(iconsSrc, join(targetDir, 'icons'));
2054
+ }
2055
+ if (toCopy.length > 0) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'index.ts')));
2056
+ return;
2057
+ }
2058
+ mkdirSync(targetDir, { recursive: true });
2059
+ const exports = [];
2060
+ for (const name of toCopy) {
2061
+ const src = join(scaffoldDir, name + '.vue');
2062
+ if (existsSync(src)) {
2063
+ copyFileSync(src, join(targetDir, name + '.vue'));
2064
+ exports.push(`export { default as ${name} } from './${name}.vue';`);
2065
+ }
2066
+ }
2067
+ const iconsSrc = join(scaffoldDir, 'icons');
2068
+ if (existsSync(iconsSrc) && (toCopy.length > 0 || copyIconsOnly)) copyDirRecursive(iconsSrc, join(targetDir, 'icons'));
2069
+ if (toCopy.includes('Settings')) {
2070
+ const configDir = getScaffoldConfigDir();
2071
+ const fontsSrc = join(configDir, 'fonts.ts');
2072
+ if (existsSync(fontsSrc)) {
2073
+ const projectConfigDir = join(projectDir, 'src', 'components', 'config');
2074
+ mkdirSync(projectConfigDir, { recursive: true });
2075
+ copyFileSync(fontsSrc, join(projectConfigDir, 'fonts.ts'));
2076
+ }
2077
+ }
2078
+ if (exports.length > 0 || copyIconsOnly) {
2079
+ if (exports.length > 0) {
2080
+ const indexContent = `/** Rizzo CSS Vue components — selected via npx rizzo-css add */\n${exports.join('\n')}\n`;
2081
+ writeFileSync(join(targetDir, 'index.ts'), indexContent, 'utf8');
2082
+ }
2083
+ const msg = copyIconsOnly ? 'Icons' : exports.length + ' Vue components' + (existsSync(iconsSrc) ? ' + icons' : '');
2084
+ console.log('\n ✓ ' + msg + ' copied to ' + targetDir);
2085
+ console.log(' Import in your app: import { Button, Badge, ... } from \'@/components/rizzo\' or relative path;\n');
2086
+ }
2087
+ }
2088
+
1666
2089
  /** Add Rizzo CSS to an existing project. Same variation as create new: Landing | Docs | Dashboard. We never overwrite existing files (including config files). frameworkOverride: when set (from init), skip framework prompt. options: { config?, targetDir?, template? } — template is variation: landing|docs|dashboard (or full→landing). */
1667
2090
  async function runAddToExisting(frameworkOverride, options) {
1668
2091
  const cwd = process.cwd();
@@ -1675,6 +2098,8 @@ async function runAddToExisting(frameworkOverride, options) {
1675
2098
  { value: 'vanilla', label: 'Vanilla JS (HTML + CSS)', color: C.vanilla },
1676
2099
  { value: 'astro', label: 'Astro', color: C.astro },
1677
2100
  { value: 'svelte', label: 'Svelte', color: C.svelte },
2101
+ { value: 'react', label: 'React', color: C.react },
2102
+ { value: 'vue', label: 'Vue', color: C.vue },
1678
2103
  ];
1679
2104
  let frameworkPrompt = '? Framework';
1680
2105
  if (detected) {
@@ -1689,7 +2114,7 @@ async function runAddToExisting(frameworkOverride, options) {
1689
2114
  selectedVariation = await promptTemplate();
1690
2115
  }
1691
2116
 
1692
- const componentList = framework === 'svelte' ? SVELTE_COMPONENTS : framework === 'astro' ? ASTRO_COMPONENTS : framework === 'vanilla' ? Object.keys(VANILLA_COMPONENT_SLUGS) : [];
2117
+ const componentList = framework === 'svelte' ? SVELTE_COMPONENTS : framework === 'astro' ? ASTRO_COMPONENTS : framework === 'react' ? REACT_COMPONENTS : framework === 'vue' ? VUE_COMPONENTS : framework === 'vanilla' ? Object.keys(VANILLA_COMPONENT_SLUGS) : [];
1693
2118
  const preselected = options.preselectedComponents && options.preselectedComponents.length > 0 ? options.preselectedComponents : null;
1694
2119
  let selectedComponents;
1695
2120
  if (selectedVariation === 'css-only') {
@@ -1749,7 +2174,7 @@ async function runAddToExisting(frameworkOverride, options) {
1749
2174
  const paths = getFrameworkCssPaths(framework);
1750
2175
  const targetDirRaw = (options && options.targetDir) || (config && config.targetDir) || paths.targetDir;
1751
2176
  let cssTarget;
1752
- if (framework === 'astro') {
2177
+ if (framework === 'astro' || framework === 'react' || framework === 'vue') {
1753
2178
  cssTarget = join(cwd, 'public', 'css', 'rizzo.min.css');
1754
2179
  } else if (framework === 'svelte') {
1755
2180
  cssTarget = join(cwd, 'static', 'css', 'rizzo.min.css');
@@ -1758,7 +2183,9 @@ async function runAddToExisting(frameworkOverride, options) {
1758
2183
  cssTarget = join(targetDir, 'rizzo.min.css');
1759
2184
  }
1760
2185
  const cssExists = existsSync(cssTarget);
1761
- if (cssExists && !options.force) {
2186
+ if (options.dryRun) {
2187
+ options._overwriteCss = true;
2188
+ } else if (cssExists && !options.force) {
1762
2189
  const answer = await question('\nCSS already exists at ' + cssTarget + '. Overwrite? (y/N) ');
1763
2190
  if (answer !== '' && !/^y(es)?$/i.test(answer)) {
1764
2191
  console.log('Skipping CSS copy. Updating config and components only.');
@@ -1776,9 +2203,10 @@ async function runAddToExisting(frameworkOverride, options) {
1776
2203
  const astroCoreDir = getScaffoldAstroCoreDir();
1777
2204
  const svelteCoreDir = getScaffoldSvelteCoreDir();
1778
2205
  const themeCommentAdd = ' <!-- Initial: ' + theme + '; dark: ' + defaultDark + '; light: ' + defaultLight + ' (all 14 themes in CSS) -->';
2206
+ const copyOpts = options.dryRun && options.plan ? options : undefined;
1779
2207
  if (framework === 'vanilla' && getVariantDir('vanilla', selectedVariation)) {
1780
2208
  const vanillaRepl = { '{{LINK_HREF}}': 'css/rizzo.min.css', '{{TITLE}}': 'App', '{{DATA_THEME}}': theme, '{{THEME_LIST_COMMENT}}': themeCommentAdd };
1781
- const variantResult = copyVariantOverlayNoOverwrite(cwd, 'vanilla', selectedVariation, vanillaRepl);
2209
+ const variantResult = copyVariantOverlayNoOverwrite(cwd, 'vanilla', selectedVariation, vanillaRepl, copyOpts);
1782
2210
  if (variantResult && variantResult.skipped) addSkippedFiles = variantResult.skipped;
1783
2211
  } else if (selectedVariation !== 'css-only' && ((framework === 'astro' && existsSync(astroCoreDir)) || (framework === 'svelte' && existsSync(svelteCoreDir)))) {
1784
2212
  const themeComment = themeCommentAdd;
@@ -1818,48 +2246,62 @@ async function runAddToExisting(frameworkOverride, options) {
1818
2246
  }
1819
2247
  }
1820
2248
  }
1821
- mkdirSync(cwd, { recursive: true });
2249
+ if (!options.dryRun) mkdirSync(cwd, { recursive: true });
1822
2250
  if (framework === 'astro') {
1823
- const baseResult = copyDirRecursiveWithReplacementsNoOverwrite(astroCoreDir, cwd, replacements, cwd);
1824
- const variantResult = copyVariantOverlayNoOverwrite(cwd, 'astro', selectedVariation, replacements);
2251
+ const baseResult = copyDirRecursiveWithReplacementsNoOverwrite(astroCoreDir, cwd, replacements, cwd, copyOpts);
2252
+ const variantResult = copyVariantOverlayNoOverwrite(cwd, 'astro', selectedVariation, replacements, copyOpts);
1825
2253
  addSkippedFiles = baseResult.skipped.concat(variantResult.skipped || []);
1826
2254
  } else if (framework === 'svelte') {
1827
- const baseResult = copyDirRecursiveWithReplacementsNoOverwrite(svelteCoreDir, cwd, replacements, cwd);
1828
- const variantResult = copyVariantOverlayNoOverwrite(cwd, 'svelte', selectedVariation, replacements);
2255
+ const baseResult = copyDirRecursiveWithReplacementsNoOverwrite(svelteCoreDir, cwd, replacements, cwd, copyOpts);
2256
+ const variantResult = copyVariantOverlayNoOverwrite(cwd, 'svelte', selectedVariation, replacements, copyOpts);
1829
2257
  addSkippedFiles = baseResult.skipped.concat(variantResult.skipped || []);
1830
2258
  }
1831
2259
  }
1832
2260
 
1833
2261
  if (options._overwriteCss) {
1834
- if (framework === 'astro') {
1835
- copyRizzoCssAndFontsForAstro(cwd, cssSource);
2262
+ if (framework === 'astro' || framework === 'react' || framework === 'vue') {
2263
+ copyRizzoCssAndFontsForAstro(cwd, cssSource, copyOpts);
1836
2264
  } else if (framework === 'svelte') {
1837
- copyRizzoCssAndFontsForSvelte(cwd, cssSource);
2265
+ copyRizzoCssAndFontsForSvelte(cwd, cssSource, copyOpts);
1838
2266
  } else {
1839
2267
  const targetDir = join(cwd, targetDirRaw);
1840
- mkdirSync(targetDir, { recursive: true });
1841
- copyFileSync(cssSource, cssTarget);
1842
- copyRizzoFonts(dirname(cssTarget));
1843
- if (framework === 'vanilla' && selectedVariation !== 'css-only') copyRizzoSfx(cwd);
2268
+ if (copyOpts && copyOpts.plan) {
2269
+ copyOpts.plan.wouldWrite.push(pathRelative(cwd, cssTarget));
2270
+ copyRizzoFonts(dirname(cssTarget), copyOpts);
2271
+ if (framework === 'vanilla' && selectedVariation !== 'css-only') copyRizzoSfx(cwd, copyOpts);
2272
+ } else {
2273
+ mkdirSync(targetDir, { recursive: true });
2274
+ copyFileSync(cssSource, cssTarget);
2275
+ copyRizzoFonts(dirname(cssTarget));
2276
+ if (framework === 'vanilla' && selectedVariation !== 'css-only') copyRizzoSfx(cwd);
2277
+ }
1844
2278
  }
1845
2279
  }
1846
2280
 
1847
- if (selectedVariation !== 'css-only') copyRizzoIcons(cwd, framework);
2281
+ if (selectedVariation !== 'css-only') copyRizzoIcons(cwd, framework, copyOpts);
1848
2282
  if (framework === 'svelte' && selectedComponents.length > 0) {
1849
2283
  const expanded = expandWithDeps('svelte', selectedComponents);
1850
2284
  logAddedDeps(selectedComponents, expanded, 'svelte');
1851
- copySvelteComponents(cwd, expanded);
2285
+ copySvelteComponents(cwd, expanded, copyOpts);
1852
2286
  } else if (framework === 'astro' && selectedComponents.length > 0) {
1853
2287
  const expanded = expandWithDeps('astro', selectedComponents);
1854
2288
  logAddedDeps(selectedComponents, expanded, 'astro');
1855
- copyAstroComponents(cwd, expanded);
2289
+ copyAstroComponents(cwd, expanded, copyOpts);
2290
+ } else if (framework === 'react' && selectedComponents.length > 0) {
2291
+ const expanded = expandWithDeps('react', selectedComponents);
2292
+ logAddedDeps(selectedComponents, expanded, 'react');
2293
+ copyReactComponents(cwd, expanded, copyOpts);
2294
+ } else if (framework === 'vue' && selectedComponents.length > 0) {
2295
+ const expanded = expandWithDeps('vue', selectedComponents);
2296
+ logAddedDeps(selectedComponents, expanded, 'vue');
2297
+ copyVueComponents(cwd, expanded, copyOpts);
1856
2298
  } else if (framework === 'vanilla' && selectedComponents.length > 0) {
1857
2299
  const linkHrefForVanilla = (options && options.targetDir) ? getLinkHrefForTargetDir(framework, options.targetDir) : paths.linkHref;
1858
2300
  const vanillaRepl = { '{{LINK_HREF}}': linkHrefForVanilla, '{{DATA_THEME}}': theme };
1859
- copyVanillaComponents(cwd, selectedComponents, vanillaRepl);
2301
+ copyVanillaComponents(cwd, selectedComponents, vanillaRepl, copyOpts);
1860
2302
  const needsJs = selectedComponents.some((c) => VANILLA_JS_COMPONENTS.includes(c));
1861
2303
  const vanillaJsPath = join(cwd, 'js', 'main.js');
1862
- if (needsJs && !existsSync(vanillaJsPath) && (options.copyVanillaJs || (!preselected && (await confirmCopyVanillaJs())))) {
2304
+ if (!options.dryRun && needsJs && !existsSync(vanillaJsPath) && (options.copyVanillaJs || (!preselected && (await confirmCopyVanillaJs())))) {
1863
2305
  const vanillaJsSrc = join(getPackageRoot(), 'scaffold', 'vanilla', 'js', 'main.js');
1864
2306
  if (existsSync(vanillaJsSrc)) {
1865
2307
  mkdirSync(join(cwd, 'js'), { recursive: true });
@@ -1880,9 +2322,10 @@ async function runAddToExisting(frameworkOverride, options) {
1880
2322
  } else if (needsJs && !existsSync(vanillaJsPath)) {
1881
2323
  options._vanillaJsHint = true;
1882
2324
  }
2325
+ if (options.dryRun && needsJs && !existsSync(vanillaJsPath) && options.plan) options.plan.wouldWrite.push('js/main.js');
1883
2326
  }
1884
2327
 
1885
- const linkHref = (framework === 'astro' || framework === 'svelte') ? paths.linkHref : ((options && options.targetDir) ? getLinkHrefForTargetDir(framework, options.targetDir) : paths.linkHref);
2328
+ const linkHref = (framework === 'astro' || framework === 'svelte' || framework === 'react' || framework === 'vue') ? paths.linkHref : ((options && options.targetDir) ? getLinkHrefForTargetDir(framework, options.targetDir) : paths.linkHref);
1886
2329
  const pmFromOption = options && options.packageManager && VALID_PACKAGE_MANAGERS.includes(options.packageManager);
1887
2330
  const pm = pmFromOption
1888
2331
  ? getPackageManagerCommands({ agent: options.packageManager, command: options.packageManager })
@@ -1890,9 +2333,23 @@ async function runAddToExisting(frameworkOverride, options) {
1890
2333
  ? getPackageManagerCommands({ agent: config.packageManager, command: config.packageManager })
1891
2334
  : resolvePackageManager(cwd);
1892
2335
  const cliExample = pm.dlx('rizzo-css theme');
1893
- const configTargetDir = framework === 'astro' ? 'public/css' : framework === 'svelte' ? 'static/css' : targetDirRaw;
2336
+ const configTargetDir = (framework === 'astro' || framework === 'react' || framework === 'vue') ? 'public/css' : framework === 'svelte' ? 'static/css' : targetDirRaw;
1894
2337
  const configPath = join(cwd, RIZZO_CONFIG_FILE);
1895
2338
  const hadConfig = existsSync(configPath);
2339
+ if (options.dryRun) {
2340
+ if (!hadConfig) options.plan.wouldWrite.push(RIZZO_CONFIG_FILE);
2341
+ options.plan.wouldWrite.push(RIZZO_SETUP_FILE);
2342
+ if (options.writeSnippet !== false) options.plan.wouldWrite.push(RIZZO_SNIPPET_FILE);
2343
+ if (options.writeReadme) options.plan.wouldWrite.push(SCAFFOLD_README_FILENAME);
2344
+ copyPackageLicense(cwd, copyOpts);
2345
+ console.log('\n Dry run — no files written.');
2346
+ console.log(' Would write (' + options.plan.wouldWrite.length + ' paths):');
2347
+ options.plan.wouldWrite.sort().forEach((p) => console.log(' ' + p));
2348
+ const setupMdContent = buildRizzoSetupMd(framework, { linkHref, theme, defaultDark, defaultLight, skippedFiles: addSkippedFiles.length > 0 ? addSkippedFiles : undefined });
2349
+ console.log('\n --- RIZZO-SETUP.md snippet (for skipped files) ---\n');
2350
+ console.log(setupMdContent);
2351
+ return;
2352
+ }
1896
2353
  if (!hadConfig) {
1897
2354
  writeRizzoConfig(cwd, { targetDir: configTargetDir, framework, packageManager: pm.agent, theme });
1898
2355
  }
@@ -1902,7 +2359,7 @@ async function runAddToExisting(frameworkOverride, options) {
1902
2359
  copyPackageLicense(cwd);
1903
2360
  const writeSnippet = options.writeSnippet !== false;
1904
2361
  if (writeSnippet) {
1905
- const where = framework === 'svelte' ? 'Root layout (e.g. src/app.html)' : framework === 'astro' ? 'Layout (e.g. src/layouts/Layout.astro)' : 'HTML or layout';
2362
+ const where = framework === 'svelte' ? 'Root layout (e.g. src/app.html)' : framework === 'astro' ? 'Layout (e.g. src/layouts/Layout.astro)' : (framework === 'react' || framework === 'vue') ? 'index.html or root component' : 'HTML or layout';
1906
2363
  const snippetBody = [
1907
2364
  'Add to ' + where + ':',
1908
2365
  '',
@@ -1944,6 +2401,11 @@ async function runAddToExisting(frameworkOverride, options) {
1944
2401
  console.log(' <link rel="stylesheet" href="' + linkHref + '" />');
1945
2402
  console.log(' data-theme="' + theme + '" on <html> (themes: ' + cliExample + ')');
1946
2403
  console.log(' Components: src/components/rizzo — import from there.');
2404
+ } else if (framework === 'react' || framework === 'vue') {
2405
+ console.log('\nIn index.html or root component, add:');
2406
+ console.log(' <link rel="stylesheet" href="' + linkHref + '" />');
2407
+ console.log(' data-theme="' + theme + '" on <html> (themes: ' + cliExample + ')');
2408
+ console.log(' Components: src/components/rizzo — import from there or @/components/rizzo.');
1947
2409
  } else {
1948
2410
  console.log('\nIn your HTML or layout, add:');
1949
2411
  console.log(' <link rel="stylesheet" href="' + linkHref + '" />');
@@ -1955,7 +2417,7 @@ async function runAddToExisting(frameworkOverride, options) {
1955
2417
  }
1956
2418
  const addCmd = pm.add('rizzo-css');
1957
2419
  console.log('\n To add the rizzo-css package to this project (optional): ' + addCmd);
1958
- const shouldPromptAdd = (framework === 'astro' || framework === 'svelte') && options.noInstall !== true && options.installPackage !== true && process.stdin.isTTY;
2420
+ const shouldPromptAdd = (framework === 'astro' || framework === 'svelte' || framework === 'react' || framework === 'vue') && options.noInstall !== true && options.installPackage !== true && process.stdin.isTTY;
1959
2421
  if (shouldPromptAdd) {
1960
2422
  console.log('\n Add rizzo-css to your package.json so you can import components from the package.');
1961
2423
  const answer = await question(' Run ' + addCmd + '? (Y/n) ');
@@ -2012,6 +2474,8 @@ async function cmdInit(argv) {
2012
2474
  fullAllComponents = true;
2013
2475
  if (framework === 'svelte') selectedComponents = [...SVELTE_COMPONENTS];
2014
2476
  else if (framework === 'astro') selectedComponents = [...ASTRO_COMPONENTS];
2477
+ else if (framework === 'react') selectedComponents = [...REACT_COMPONENTS];
2478
+ else if (framework === 'vue') selectedComponents = [...VUE_COMPONENTS];
2015
2479
  else selectedComponents = Object.keys(VANILLA_COMPONENT_SLUGS);
2016
2480
  }
2017
2481
  const projectDir = customProjectPath ? pathResolve(cwd, customProjectPath) : cwd;
@@ -2029,6 +2493,8 @@ async function cmdInit(argv) {
2029
2493
  { value: 'vanilla', label: 'Vanilla JS (HTML + CSS + same styles & components)', color: C.vanilla },
2030
2494
  { value: 'astro', label: 'Astro', color: C.astro },
2031
2495
  { value: 'svelte', label: 'Svelte', color: C.svelte },
2496
+ { value: 'react', label: 'React', color: C.react },
2497
+ { value: 'vue', label: 'Vue', color: C.vue },
2032
2498
  ],
2033
2499
  '? Framework — all get the same CSS and component styles'
2034
2500
  );
@@ -2074,7 +2540,7 @@ async function cmdInit(argv) {
2074
2540
  fullAllComponents = false;
2075
2541
  } else if (selectedVariation === 'full' && hasFullVariant(framework)) {
2076
2542
  fullAllComponents = true;
2077
- selectedComponents = framework === 'svelte' ? [...SVELTE_COMPONENTS] : framework === 'astro' ? [...ASTRO_COMPONENTS] : Object.keys(VANILLA_COMPONENT_SLUGS);
2543
+ selectedComponents = framework === 'svelte' ? [...SVELTE_COMPONENTS] : framework === 'astro' ? [...ASTRO_COMPONENTS] : framework === 'react' ? [...REACT_COMPONENTS] : framework === 'vue' ? [...VUE_COMPONENTS] : Object.keys(VANILLA_COMPONENT_SLUGS);
2078
2544
  } else {
2079
2545
  const addChoice = await selectMenu(
2080
2546
  [
@@ -2085,9 +2551,9 @@ async function cmdInit(argv) {
2085
2551
  );
2086
2552
  fullAllComponents = (addChoice === 'all');
2087
2553
  if (fullAllComponents) {
2088
- selectedComponents = framework === 'svelte' ? [...SVELTE_COMPONENTS] : framework === 'astro' ? [...ASTRO_COMPONENTS] : Object.keys(VANILLA_COMPONENT_SLUGS);
2554
+ selectedComponents = framework === 'svelte' ? [...SVELTE_COMPONENTS] : framework === 'astro' ? [...ASTRO_COMPONENTS] : framework === 'react' ? [...REACT_COMPONENTS] : framework === 'vue' ? [...VUE_COMPONENTS] : Object.keys(VANILLA_COMPONENT_SLUGS);
2089
2555
  } else {
2090
- const componentList = framework === 'svelte' ? SVELTE_COMPONENTS : framework === 'astro' ? ASTRO_COMPONENTS : Object.keys(VANILLA_COMPONENT_SLUGS);
2556
+ const componentList = framework === 'svelte' ? SVELTE_COMPONENTS : framework === 'astro' ? ASTRO_COMPONENTS : framework === 'react' ? REACT_COMPONENTS : framework === 'vue' ? VUE_COMPONENTS : Object.keys(VANILLA_COMPONENT_SLUGS);
2091
2557
  const recommended = RECOMMENDED_COMPONENTS.filter((c) => componentList.includes(c));
2092
2558
  selectedComponents = await promptComponentChoice(componentList, framework, recommended);
2093
2559
  }
@@ -2152,7 +2618,7 @@ async function cmdInit(argv) {
2152
2618
 
2153
2619
  // Full gets all required dependencies so everything works; manual gets deps when user picks (see prompt labels).
2154
2620
  let componentsToCopy = selectedComponents;
2155
- if ((framework === 'astro' || framework === 'svelte') && selectedComponents.length > 0) {
2621
+ if ((framework === 'astro' || framework === 'svelte' || framework === 'react' || framework === 'vue') && selectedComponents.length > 0) {
2156
2622
  componentsToCopy = expandWithDeps(framework, selectedComponents);
2157
2623
  logAddedDeps(selectedComponents, componentsToCopy, framework);
2158
2624
  }
@@ -2431,8 +2897,8 @@ async function cmdInit(argv) {
2431
2897
  copyPackageLicense(projectDir);
2432
2898
  copyVanillaGitignore(projectDir);
2433
2899
  } else {
2434
- // Add-to-existing (landing) or Vanilla create new with picked components: CSS, fonts, icons, sfx (framework-appropriate)
2435
- if (framework === 'astro') {
2900
+ // Add-to-existing (landing) or Vanilla/React/Vue create new with picked components: CSS, fonts, icons, sfx (framework-appropriate)
2901
+ if (framework === 'astro' || framework === 'react' || framework === 'vue') {
2436
2902
  copyRizzoCssAndFontsForAstro(projectDir, cssSource);
2437
2903
  cssTarget = join(projectDir, 'public', 'css', 'rizzo.min.css');
2438
2904
  } else if (framework === 'svelte') {
@@ -2452,7 +2918,7 @@ async function cmdInit(argv) {
2452
2918
  if (statSync(cssTarget).size < 5000) {
2453
2919
  console.warn('\nWarning: rizzo.min.css is very small. From repo root run: pnpm build:css');
2454
2920
  }
2455
- const linkHrefForSetup = framework === 'vanilla' ? 'css/rizzo.min.css' : '/css/rizzo.min.css';
2921
+ const linkHrefForSetup = (framework === 'vanilla') ? 'css/rizzo.min.css' : '/css/rizzo.min.css';
2456
2922
  const setupMdContent = buildRizzoSetupMd(framework, {
2457
2923
  linkHref: linkHrefForSetup,
2458
2924
  theme,
@@ -2477,10 +2943,19 @@ async function cmdInit(argv) {
2477
2943
  mkdirSync(join(projectDir, 'static'), { recursive: true });
2478
2944
  indexPath = svelteIndex;
2479
2945
  writeFileSync(svelteIndex, landingHtmlForAdd, 'utf8');
2946
+ } else if ((framework === 'react' || framework === 'vue') && !existsSync(join(projectDir, 'public', 'index.html'))) {
2947
+ mkdirSync(join(projectDir, 'public'), { recursive: true });
2948
+ indexPath = join(projectDir, 'public', 'index.html');
2949
+ writeFileSync(indexPath, landingHtmlForAdd, 'utf8');
2480
2950
  }
2481
2951
  writeFileSync(join(projectDir, RIZZO_SETUP_FILE), setupMdContent, 'utf8');
2482
2952
  copyPackageLicense(projectDir);
2483
2953
  if (framework === 'vanilla') copyVanillaGitignore(projectDir);
2954
+ if ((framework === 'react' || framework === 'vue') && componentsToCopy.length > 0) {
2955
+ copyRizzoIcons(projectDir, framework);
2956
+ if (framework === 'react') copyReactComponents(projectDir, componentsToCopy);
2957
+ else copyVueComponents(projectDir, componentsToCopy);
2958
+ }
2484
2959
  } else if (framework === 'vanilla' && selectedTemplate === 'full' && !fullAllComponents) {
2485
2960
  indexPath = join(projectDir, 'index.html');
2486
2961
  if (!existsSync(indexPath)) {
@@ -2519,6 +2994,18 @@ async function cmdInit(argv) {
2519
2994
  else writeFileSync(join(projectDir, RIZZO_SETUP_FILE), buildRizzoSetupMd(framework, { linkHref: '/css/rizzo.min.css', theme, defaultDark, defaultLight, skippedFiles: [{ relativePath: 'static/index.html', content: landingHtml }] }), 'utf8');
2520
2995
  writeFileSync(join(projectDir, SCAFFOLD_README_FILENAME), FALLBACK_MINIMAL_README, 'utf8');
2521
2996
  copyPackageLicense(projectDir);
2997
+ } else if (framework === 'react' || framework === 'vue') {
2998
+ indexPath = join(projectDir, 'public', 'index.html');
2999
+ mkdirSync(join(projectDir, 'public'), { recursive: true });
3000
+ if (!existsSync(indexPath)) writeFileSync(indexPath, landingHtml, 'utf8');
3001
+ else writeFileSync(join(projectDir, RIZZO_SETUP_FILE), buildRizzoSetupMd(framework, { linkHref: '/css/rizzo.min.css', theme, defaultDark, defaultLight, skippedFiles: [{ relativePath: 'public/index.html', content: landingHtml }] }), 'utf8');
3002
+ writeFileSync(join(projectDir, SCAFFOLD_README_FILENAME), FALLBACK_MINIMAL_README, 'utf8');
3003
+ if (componentsToCopy.length > 0) {
3004
+ copyRizzoIcons(projectDir, framework);
3005
+ if (framework === 'react') copyReactComponents(projectDir, componentsToCopy);
3006
+ else copyVueComponents(projectDir, componentsToCopy);
3007
+ }
3008
+ copyPackageLicense(projectDir);
2522
3009
  } else {
2523
3010
  copyPackageLicense(projectDir);
2524
3011
  }
@@ -2569,12 +3056,13 @@ async function cmdInit(argv) {
2569
3056
  }
2570
3057
 
2571
3058
  // Package manager install: only for Astro/Svelte (Vanilla has no package.json)
3059
+ const installCmd = pm.install + (hasFlag(argv, '--offline') ? ' --offline' : '');
2572
3060
  if (runInstallAfterScaffold && !noInstall && hasPackageJson) {
2573
3061
  const dirLabel = projectDir !== cwd ? ' in ' + pathRelative(cwd, projectDir) : ' (current directory)';
2574
- console.log('\n Running' + dirLabel + ': ' + pm.install);
2575
- const code = runInDir(projectDir, pm.install);
3062
+ console.log('\n Running' + dirLabel + ': ' + installCmd);
3063
+ const code = runInDir(projectDir, installCmd);
2576
3064
  if (code !== 0) {
2577
- console.error('\n Install failed (exit ' + code + '). Run manually: ' + runPrefix + pm.install);
3065
+ console.error('\n Install failed (exit ' + code + '). Run manually: ' + runPrefix + installCmd);
2578
3066
  } else {
2579
3067
  console.log(' ✓ Dependencies installed.');
2580
3068
  }
@@ -2582,15 +3070,15 @@ async function cmdInit(argv) {
2582
3070
  const shouldRun = await confirmRunInstall(pm);
2583
3071
  if (shouldRun) {
2584
3072
  const dirLabel = projectDir !== cwd ? ' in ' + pathRelative(cwd, projectDir) : ' here';
2585
- console.log('\n Running' + dirLabel + ': ' + pm.install);
2586
- const code = runInDir(projectDir, pm.install);
3073
+ console.log('\n Running' + dirLabel + ': ' + installCmd);
3074
+ const code = runInDir(projectDir, installCmd);
2587
3075
  if (code !== 0) {
2588
- console.error('\n Install failed (exit ' + code + '). Run manually: ' + runPrefix + pm.install);
3076
+ console.error('\n Install failed (exit ' + code + '). Run manually: ' + runPrefix + installCmd);
2589
3077
  } else {
2590
3078
  console.log(' ✓ Dependencies installed.');
2591
3079
  }
2592
3080
  } else {
2593
- console.log('\n Skipped. When ready, run: ' + runPrefix + pm.install);
3081
+ console.log('\n Skipped. When ready, run: ' + runPrefix + installCmd);
2594
3082
  }
2595
3083
  }
2596
3084
 
@@ -2621,6 +3109,7 @@ async function cmdInit(argv) {
2621
3109
  : 'open index.html or serve the folder (e.g. npx serve .)';
2622
3110
  console.log('\n Next: ' + vanillaNext);
2623
3111
  }
3112
+ await checkNewerVersion(argv).catch(() => {});
2624
3113
  console.log('\n Docs: https://rizzo-css.vercel.app\n');
2625
3114
  }
2626
3115