rizzo-css 0.0.61 → 0.0.62

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 (55) hide show
  1. package/README.md +1 -1
  2. package/bin/rizzo-css.js +323 -54
  3. package/package.json +1 -1
  4. package/scaffold/vanilla/README-RIZZO.md +1 -1
  5. package/scaffold/vanilla/components/accordion.html +10 -0
  6. package/scaffold/vanilla/components/alert-dialog.html +10 -0
  7. package/scaffold/vanilla/components/alert.html +10 -0
  8. package/scaffold/vanilla/components/aspect-ratio.html +10 -0
  9. package/scaffold/vanilla/components/avatar.html +10 -0
  10. package/scaffold/vanilla/components/back-to-top.html +10 -0
  11. package/scaffold/vanilla/components/badge.html +10 -0
  12. package/scaffold/vanilla/components/breadcrumb.html +10 -0
  13. package/scaffold/vanilla/components/button-group.html +10 -0
  14. package/scaffold/vanilla/components/button.html +10 -0
  15. package/scaffold/vanilla/components/cards.html +10 -0
  16. package/scaffold/vanilla/components/collapsible.html +10 -0
  17. package/scaffold/vanilla/components/context-menu.html +10 -0
  18. package/scaffold/vanilla/components/copy-to-clipboard.html +10 -0
  19. package/scaffold/vanilla/components/dashboard.html +10 -0
  20. package/scaffold/vanilla/components/divider.html +10 -0
  21. package/scaffold/vanilla/components/docs-sidebar.html +10 -0
  22. package/scaffold/vanilla/components/dropdown.html +10 -0
  23. package/scaffold/vanilla/components/empty.html +10 -0
  24. package/scaffold/vanilla/components/font-switcher.html +10 -0
  25. package/scaffold/vanilla/components/footer.html +10 -0
  26. package/scaffold/vanilla/components/forms.html +10 -0
  27. package/scaffold/vanilla/components/hover-card.html +10 -0
  28. package/scaffold/vanilla/components/icons.html +10 -0
  29. package/scaffold/vanilla/components/index.html +10 -0
  30. package/scaffold/vanilla/components/kbd.html +10 -0
  31. package/scaffold/vanilla/components/label.html +10 -0
  32. package/scaffold/vanilla/components/modal.html +10 -0
  33. package/scaffold/vanilla/components/navbar.html +10 -0
  34. package/scaffold/vanilla/components/pagination.html +10 -0
  35. package/scaffold/vanilla/components/popover.html +10 -0
  36. package/scaffold/vanilla/components/progress-bar.html +10 -0
  37. package/scaffold/vanilla/components/resizable.html +10 -0
  38. package/scaffold/vanilla/components/scroll-area.html +10 -0
  39. package/scaffold/vanilla/components/search.html +10 -0
  40. package/scaffold/vanilla/components/separator.html +10 -0
  41. package/scaffold/vanilla/components/settings.html +10 -0
  42. package/scaffold/vanilla/components/sheet.html +10 -0
  43. package/scaffold/vanilla/components/skeleton.html +10 -0
  44. package/scaffold/vanilla/components/slider.html +10 -0
  45. package/scaffold/vanilla/components/sound-effects.html +10 -0
  46. package/scaffold/vanilla/components/spinner.html +10 -0
  47. package/scaffold/vanilla/components/switch.html +10 -0
  48. package/scaffold/vanilla/components/table.html +10 -0
  49. package/scaffold/vanilla/components/tabs.html +10 -0
  50. package/scaffold/vanilla/components/theme-switcher.html +10 -0
  51. package/scaffold/vanilla/components/toast.html +10 -0
  52. package/scaffold/vanilla/components/toggle-group.html +10 -0
  53. package/scaffold/vanilla/components/toggle.html +10 -0
  54. package/scaffold/vanilla/components/tooltip.html +10 -0
  55. package/scaffold/vanilla/index.html +10 -0
package/bin/rizzo-css.js CHANGED
@@ -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',
@@ -316,11 +317,57 @@ function getCssPath() {
316
317
  return join(getPackageRoot(), 'dist', 'rizzo.min.css');
317
318
  }
318
319
 
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) {
320
+ /** Simple semver comparison: true if a > b (e.g. "1.2.3" > "1.2.2"). */
321
+ function semverGt(a, b) {
322
+ const pa = a.split('.').map((n) => parseInt(n, 10) || 0);
323
+ const pb = b.split('.').map((n) => parseInt(n, 10) || 0);
324
+ for (let i = 0; i < 3; i++) {
325
+ const na = pa[i] || 0, nb = pb[i] || 0;
326
+ if (na > nb) return true;
327
+ if (na < nb) return false;
328
+ }
329
+ return false;
330
+ }
331
+
332
+ /** Optional version check: if not --offline, fetch latest from registry and print one line if newer. Non-blocking (2s timeout). */
333
+ async function checkNewerVersion(argv) {
334
+ if (hasFlag(argv, '--offline')) return;
335
+ let current;
336
+ try {
337
+ const pkgPath = join(getPackageRoot(), 'package.json');
338
+ current = JSON.parse(readFileSync(pkgPath, 'utf8')).version;
339
+ } catch (_) { return; }
340
+ try {
341
+ const ac = new AbortController();
342
+ const timeout = setTimeout(() => ac.abort(), 2500);
343
+ const res = await fetch('https://registry.npmjs.org/rizzo-css/latest', { signal: ac.signal }).finally(() => clearTimeout(timeout));
344
+ if (!res || !res.ok) return;
345
+ const data = await res.json();
346
+ const latest = data.version;
347
+ if (latest && semverGt(latest, current)) {
348
+ 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.');
349
+ }
350
+ } catch (_) { /* ignore network or parse errors */ }
351
+ }
352
+
353
+ /** 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). */
354
+ function copyRizzoFonts(cssTargetDir, opts) {
321
355
  const fontsSrc = join(getPackageRoot(), 'dist', 'fonts');
322
356
  if (!existsSync(fontsSrc)) return;
323
357
  const dest = join(cssTargetDir, 'fonts');
358
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
359
+ if (plan) {
360
+ function collectPaths(src, d) {
361
+ const entries = readdirSync(src, { withFileTypes: true });
362
+ for (const e of entries) {
363
+ const destPath = join(d, e.name);
364
+ if (e.isDirectory()) collectPaths(join(src, e.name), destPath);
365
+ else plan.wouldWrite.push(pathRelative(process.cwd(), destPath));
366
+ }
367
+ }
368
+ collectPaths(fontsSrc, dest);
369
+ return;
370
+ }
324
371
  mkdirSync(dest, { recursive: true });
325
372
  const entries = readdirSync(fontsSrc, { withFileTypes: true });
326
373
  for (const e of entries) {
@@ -334,11 +381,19 @@ function copyRizzoFonts(cssTargetDir) {
334
381
  }
335
382
  }
336
383
 
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) {
384
+ /** 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. */
385
+ function copyRizzoSfx(projectDir, opts) {
339
386
  const sfxSrc = join(getPackageRoot(), 'dist', 'sfx');
340
387
  if (!existsSync(sfxSrc)) return;
341
388
  const dest = join(projectDir, 'assets', 'sfx');
389
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
390
+ if (plan) {
391
+ readdirSync(sfxSrc, { withFileTypes: true }).forEach((e) => {
392
+ if (!e.isDirectory() && (/\.mp3$/i.test(e.name) || /\.wav$/i.test(e.name)))
393
+ plan.wouldWrite.push(pathRelative(projectDir, join(dest, e.name)));
394
+ });
395
+ return;
396
+ }
342
397
  mkdirSync(dest, { recursive: true });
343
398
  const entries = readdirSync(sfxSrc, { withFileTypes: true });
344
399
  for (const e of entries) {
@@ -348,12 +403,34 @@ function copyRizzoSfx(projectDir) {
348
403
  }
349
404
  }
350
405
 
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) {
406
+ /** 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. */
407
+ function copyRizzoCssAndFontsForAstro(projectDir, cssSource, opts) {
353
408
  const cssDir = join(projectDir, 'public', 'css');
354
409
  const cssTarget = join(cssDir, 'rizzo.min.css');
355
410
  const fontsDest = join(projectDir, 'public', 'assets', 'fonts');
356
411
  const sfxDest = join(projectDir, 'public', 'assets', 'sfx');
412
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
413
+ if (plan) {
414
+ plan.wouldWrite.push(pathRelative(projectDir, cssTarget));
415
+ function collectPaths(src, dest) {
416
+ const entries = readdirSync(src, { withFileTypes: true });
417
+ for (const e of entries) {
418
+ const destPath = join(dest, e.name);
419
+ if (e.isDirectory()) collectPaths(join(src, e.name), destPath);
420
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
421
+ }
422
+ }
423
+ const fontsSrc = join(getPackageRoot(), 'dist', 'fonts');
424
+ if (existsSync(fontsSrc)) collectPaths(fontsSrc, fontsDest);
425
+ const sfxSrc = join(getPackageRoot(), 'dist', 'sfx');
426
+ if (existsSync(sfxSrc)) {
427
+ readdirSync(sfxSrc, { withFileTypes: true }).forEach((e) => {
428
+ if (!e.isDirectory() && (/\.mp3$/i.test(e.name) || /\.wav$/i.test(e.name)))
429
+ plan.wouldWrite.push(pathRelative(projectDir, join(sfxDest, e.name)));
430
+ });
431
+ }
432
+ return;
433
+ }
357
434
  mkdirSync(cssDir, { recursive: true });
358
435
  mkdirSync(fontsDest, { recursive: true });
359
436
  mkdirSync(sfxDest, { recursive: true });
@@ -382,12 +459,34 @@ function copyRizzoCssAndFontsForAstro(projectDir, cssSource) {
382
459
  }
383
460
  }
384
461
 
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) {
462
+ /** 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. */
463
+ function copyRizzoCssAndFontsForSvelte(projectDir, cssSource, opts) {
387
464
  const cssDir = join(projectDir, 'static', 'css');
388
465
  const cssTarget = join(cssDir, 'rizzo.min.css');
389
466
  const fontsDest = join(projectDir, 'static', 'assets', 'fonts');
390
467
  const sfxDest = join(projectDir, 'static', 'assets', 'sfx');
468
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
469
+ if (plan) {
470
+ plan.wouldWrite.push(pathRelative(projectDir, cssTarget));
471
+ function collectPaths(src, dest) {
472
+ const entries = readdirSync(src, { withFileTypes: true });
473
+ for (const e of entries) {
474
+ const destPath = join(dest, e.name);
475
+ if (e.isDirectory()) collectPaths(join(src, e.name), destPath);
476
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
477
+ }
478
+ }
479
+ const fontsSrc = join(getPackageRoot(), 'dist', 'fonts');
480
+ if (existsSync(fontsSrc)) collectPaths(fontsSrc, fontsDest);
481
+ const sfxSrc = join(getPackageRoot(), 'dist', 'sfx');
482
+ if (existsSync(sfxSrc)) {
483
+ readdirSync(sfxSrc, { withFileTypes: true }).forEach((e) => {
484
+ if (!e.isDirectory() && (/\.mp3$/i.test(e.name) || /\.wav$/i.test(e.name)))
485
+ plan.wouldWrite.push(pathRelative(projectDir, join(sfxDest, e.name)));
486
+ });
487
+ }
488
+ return;
489
+ }
391
490
  mkdirSync(cssDir, { recursive: true });
392
491
  mkdirSync(fontsDest, { recursive: true });
393
492
  mkdirSync(sfxDest, { recursive: true });
@@ -416,12 +515,16 @@ function copyRizzoCssAndFontsForSvelte(projectDir, cssSource) {
416
515
  }
417
516
  }
418
517
 
419
- /** Copy the package LICENSE into the project dir as LICENSE-RIZZO so we do not overwrite an existing LICENSE. */
420
- function copyPackageLicense(projectDir) {
518
+ /** 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. */
519
+ function copyPackageLicense(projectDir, opts) {
421
520
  const licensePath = join(getPackageRoot(), 'LICENSE');
422
- if (existsSync(licensePath)) {
423
- copyFileSync(licensePath, join(projectDir, SCAFFOLD_LICENSE_FILENAME));
521
+ if (!existsSync(licensePath)) return;
522
+ const dest = join(projectDir, SCAFFOLD_LICENSE_FILENAME);
523
+ if (opts && opts.dryRun && opts.plan) {
524
+ opts.plan.wouldWrite.push(pathRelative(projectDir, dest));
525
+ return;
424
526
  }
527
+ copyFileSync(licensePath, dest);
425
528
  }
426
529
 
427
530
  /** Name of the scaffold gitignore file (no leading dot so npm pack includes it). Copied to project as .gitignore. */
@@ -615,11 +718,11 @@ function copyVariantOverlay(projectDir, framework, variation, replacements) {
615
718
  copyDirRecursiveWithReplacements(variantDir, projectDir, replacements);
616
719
  }
617
720
 
618
- /** Copy variant overlay with no-overwrite (for add to existing). Returns { skipped } like copyDirRecursiveWithReplacementsNoOverwrite. */
619
- function copyVariantOverlayNoOverwrite(projectDir, framework, variation, replacements) {
721
+ /** Copy variant overlay with no-overwrite (for add to existing). Returns { skipped } like copyDirRecursiveWithReplacementsNoOverwrite. opts: optional { dryRun, plan }. */
722
+ function copyVariantOverlayNoOverwrite(projectDir, framework, variation, replacements, opts) {
620
723
  const variantDir = getVariantDir(framework, variation);
621
724
  if (!variantDir) return { skipped: [] };
622
- return copyDirRecursiveWithReplacementsNoOverwrite(variantDir, projectDir, replacements, projectDir);
725
+ return copyDirRecursiveWithReplacementsNoOverwrite(variantDir, projectDir, replacements, projectDir, opts);
623
726
  }
624
727
 
625
728
  function question(prompt) {
@@ -975,7 +1078,7 @@ Available commands: init, add, theme, doctor, help
975
1078
 
976
1079
  Flags summary:
977
1080
  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
1081
+ add --path <dir> --framework <fw> --template css-only|landing|docs|dashboard|full --dry-run --no-snippet --readme --force --vanilla-js
979
1082
  theme (no flags)
980
1083
  doctor Check config, CSS file, and optional layout link
981
1084
  help (no flags)
@@ -1000,6 +1103,7 @@ Options (init):
1000
1103
  --package-manager <pm> npm | pnpm | yarn | bun (with --yes, or skip PM prompt when interactive)
1001
1104
  --install After scaffolding, run package manager install in project directory (no prompt)
1002
1105
  --no-install Do not run install; do not prompt (skip "Run <pm> install? (Y/n)")
1106
+ --offline Use package manager cache only (no network). Passed to install/add when running PM.
1003
1107
  --no-git With --yes, skip initializing a git repository (default with --yes is to run git init)
1004
1108
  (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
1109
 
@@ -1007,9 +1111,11 @@ Options (add) — run from your existing project root; you will choose a templat
1007
1111
  --template <t> css-only | landing | docs | dashboard | full (CSS only = stylesheet only; others = component picker)
1008
1112
  --path <dir> Target directory for rizzo.min.css (overrides config and framework default)
1009
1113
  --framework <fw> vanilla | astro | svelte (overrides config and detection)
1114
+ --dry-run Preview which files would be written without writing; show RIZZO-SETUP.md snippet for skipped files
1010
1115
  --package-manager <pm> npm | pnpm | yarn | bun (override detection for install/print commands)
1011
1116
  --install-package After copying CSS, run package manager add rizzo-css
1012
1117
  --no-install Do not run install or add (overrides --install-package)
1118
+ --offline Use package manager cache only (no network). Passed to add when running PM.
1013
1119
  --no-snippet (Full only) Do not write RIZZO-SNIPPET.txt (link + theme copy-paste)
1014
1120
  --readme Write README-RIZZO.md into the project
1015
1121
  --force Overwrite existing rizzo.min.css without prompting
@@ -1102,12 +1208,12 @@ function cmdTheme() {
1102
1208
  process.stdout.write('\nExample: <html lang="en" data-theme="github-dark-classic">\n\n');
1103
1209
  }
1104
1210
 
1105
- /** Check project for Rizzo CSS: config, CSS file, optional link in layout. */
1211
+ /** Check project for Rizzo CSS: config, CSS file, layout link, theme, fonts/sfx paths, and optional outdated-CSS hint. */
1106
1212
  function cmdDoctor() {
1107
1213
  const cwd = process.cwd();
1108
1214
  const config = readRizzoConfig(cwd);
1109
1215
  console.log(getBanner());
1110
- console.log(' Doctor — check config, CSS path, and layout link\n');
1216
+ console.log(' Doctor — check config, CSS path, layout link, theme, and assets\n');
1111
1217
  let ok = true;
1112
1218
  if (!config) {
1113
1219
  console.log(' ✗ No ' + RIZZO_CONFIG_FILE + '. Run: npx rizzo-css add or init');
@@ -1123,6 +1229,48 @@ function cmdDoctor() {
1123
1229
  ok = false;
1124
1230
  } else {
1125
1231
  console.log(' ✓ CSS at ' + cssPath);
1232
+ const cssSize = statSync(cssPath).size;
1233
+ if (cssSize < 5000) {
1234
+ 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.');
1235
+ ok = false;
1236
+ }
1237
+ }
1238
+ if (config.theme != null && config.theme !== '') {
1239
+ if (VALID_THEME_VALUES.includes(config.theme)) {
1240
+ console.log(' ✓ Theme (from config): ' + config.theme);
1241
+ } else {
1242
+ 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)');
1243
+ ok = false;
1244
+ }
1245
+ }
1246
+ const fontsDir = fw === 'astro' ? join(cwd, 'public', 'assets', 'fonts') : fw === 'svelte' ? join(cwd, 'static', 'assets', 'fonts') : join(cwd, targetDir, 'fonts');
1247
+ if (!existsSync(fontsDir)) {
1248
+ console.log(' ? Fonts path not found: ' + pathRelative(cwd, fontsDir) + ' (CSS may reference ../assets/fonts/ or ./fonts/; add fonts if you use theme typography)');
1249
+ } else {
1250
+ try {
1251
+ const entries = readdirSync(fontsDir, { withFileTypes: true });
1252
+ const hasFiles = entries.some((e) => e.isFile() && /\.(woff2?|ttf|otf)$/i.test(e.name));
1253
+ if (!hasFiles) {
1254
+ console.log(' ? Fonts dir exists but is empty or has no font files: ' + pathRelative(cwd, fontsDir));
1255
+ } else {
1256
+ console.log(' ✓ Fonts at ' + pathRelative(cwd, fontsDir));
1257
+ }
1258
+ } catch (_) {
1259
+ console.log(' ? Could not read fonts dir: ' + pathRelative(cwd, fontsDir));
1260
+ }
1261
+ }
1262
+ const sfxDir = fw === 'astro' ? join(cwd, 'public', 'assets', 'sfx') : fw === 'svelte' ? join(cwd, 'static', 'assets', 'sfx') : join(cwd, 'assets', 'sfx');
1263
+ if (!existsSync(sfxDir)) {
1264
+ console.log(' ? Sound effects path not found: ' + pathRelative(cwd, sfxDir) + ' (optional; needed if you use Settings or SoundEffects with click sounds)');
1265
+ } else {
1266
+ try {
1267
+ const entries = readdirSync(sfxDir, { withFileTypes: true });
1268
+ const hasSfx = entries.some((e) => e.isFile() && (/\.(mp3|wav)$/i.test(e.name)));
1269
+ if (hasSfx) console.log(' ✓ Sound effects at ' + pathRelative(cwd, sfxDir));
1270
+ else console.log(' ? Sfx dir exists but has no .mp3/.wav: ' + pathRelative(cwd, sfxDir));
1271
+ } catch (_) {
1272
+ console.log(' ? Could not read sfx dir: ' + pathRelative(cwd, sfxDir));
1273
+ }
1126
1274
  }
1127
1275
  const layoutPaths = fw === 'svelte' ? ['src/app.html'] : fw === 'astro' ? ['src/layouts/Layout.astro', 'src/layouts/BaseLayout.astro'] : [];
1128
1276
  for (const lp of layoutPaths) {
@@ -1134,11 +1282,25 @@ function cmdDoctor() {
1134
1282
  } else {
1135
1283
  console.log(' ✓ Layout ' + lp + ' includes Rizzo link');
1136
1284
  }
1285
+ if (content.includes('data-theme=')) {
1286
+ const match = content.match(/data-theme=["']([^"']+)["']/);
1287
+ if (match && !VALID_THEME_VALUES.includes(match[1])) {
1288
+ console.log(' ? Layout has data-theme="' + match[1] + '" which is not a known theme. Use: npx rizzo-css theme');
1289
+ ok = false;
1290
+ }
1291
+ }
1137
1292
  break;
1138
1293
  }
1139
1294
  }
1295
+ const pkgPath = join(cwd, 'node_modules', 'rizzo-css', 'package.json');
1296
+ if (existsSync(pkgPath)) {
1297
+ try {
1298
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
1299
+ const ver = pkg.version;
1300
+ if (ver) console.log(' ℹ rizzo-css in node_modules: v' + ver + '. Re-run npx rizzo-css add to refresh CSS if needed.');
1301
+ } catch (_) { /* ignore */ }
1302
+ }
1140
1303
  }
1141
- if (config && config.theme) console.log(' Theme (from config): ' + config.theme);
1142
1304
  console.log(ok ? '\nAll checks passed.\n' : '\nFix the items above, then run your dev server.\n');
1143
1305
  }
1144
1306
 
@@ -1266,6 +1428,7 @@ async function cmdAdd(argv) {
1266
1428
  const writeReadme = hasFlag(argv, '--readme');
1267
1429
  const force = hasFlag(argv, '--force');
1268
1430
  const copyVanillaJs = hasFlag(argv, '--vanilla-js');
1431
+ const dryRun = hasFlag(argv, '--dry-run');
1269
1432
  const positionals = getPositionalArgs(argv, 3);
1270
1433
 
1271
1434
  const cwd = process.cwd();
@@ -1283,21 +1446,25 @@ async function cmdAdd(argv) {
1283
1446
  copyVanillaJs,
1284
1447
  installPackage: installPackage || undefined,
1285
1448
  noInstall: noInstall || undefined,
1449
+ dryRun: dryRun || undefined,
1450
+ plan: dryRun ? { wouldWrite: [], skipped: [] } : undefined,
1286
1451
  };
1287
1452
  await runAddToExisting(explicitFramework, options);
1453
+ if (options.dryRun) return;
1288
1454
  if (installPackage && !noInstall) {
1289
1455
  const pm = (pmOverride
1290
1456
  ? getPackageManagerCommands({ agent: pmOverride, command: pmOverride })
1291
1457
  : (config && config.packageManager)
1292
1458
  ? getPackageManagerCommands({ agent: config.packageManager, command: config.packageManager })
1293
1459
  : resolvePackageManager(cwd));
1294
- const addPkg = pm.add('rizzo-css');
1460
+ const addPkg = pm.add('rizzo-css') + (hasFlag(argv, '--offline') ? ' --offline' : '');
1295
1461
  console.log('\nRunning: ' + addPkg);
1296
1462
  const code = runInDir(cwd, addPkg);
1297
1463
  if (code !== 0) {
1298
1464
  console.error('\nInstall failed (exit ' + code + '). You can run manually: ' + addPkg);
1299
1465
  }
1300
1466
  }
1467
+ await checkNewerVersion(argv).catch(() => {});
1301
1468
  }
1302
1469
 
1303
1470
  function getScaffoldSvelteDir() {
@@ -1372,8 +1539,8 @@ function getNavbarHtmlVanilla(title, brandHref) {
1372
1539
  return html.replace(/\{\{TITLE\}\}/g, title || 'App').replace(/\{\{NAVBAR_BRAND_HREF\}\}/g, brandHref || '#');
1373
1540
  }
1374
1541
 
1375
- /** Copy selected Vanilla component HTML files into projectDir/components/, with replacements. Writes a simple components/index.html. */
1376
- function copyVanillaComponents(projectDir, selectedNames, replacements) {
1542
+ /** 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. */
1543
+ function copyVanillaComponents(projectDir, selectedNames, replacements, opts) {
1377
1544
  const srcDir = getScaffoldVanillaComponentsDir();
1378
1545
  if (!existsSync(srcDir)) return;
1379
1546
  const linkHref = replacements['{{LINK_HREF}}'] || 'css/rizzo.min.css';
@@ -1392,6 +1559,12 @@ function copyVanillaComponents(projectDir, selectedNames, replacements) {
1392
1559
  }
1393
1560
  if (slugsToCopy.length === 0) return;
1394
1561
  const destDir = join(projectDir, 'components');
1562
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1563
+ if (plan) {
1564
+ slugsToCopy.forEach((slug) => plan.wouldWrite.push(pathRelative(projectDir, join(destDir, slug + '.html'))));
1565
+ plan.wouldWrite.push(pathRelative(projectDir, join(destDir, 'index.html')));
1566
+ return;
1567
+ }
1395
1568
  mkdirSync(destDir, { recursive: true });
1396
1569
  for (const slug of slugsToCopy) {
1397
1570
  const src = join(srcDir, slug + '.html');
@@ -1430,24 +1603,45 @@ ${indexLinks}
1430
1603
  writeFileSync(join(destDir, 'index.html'), indexHtml, 'utf8');
1431
1604
  }
1432
1605
 
1433
- /** Copy Rizzo icons into the project for the given framework. */
1434
- function copyRizzoIcons(projectDir, framework) {
1606
+ /** Copy Rizzo icons into the project for the given framework. opts: optional { dryRun, plan }; when dryRun push relative paths to plan.wouldWrite. */
1607
+ function copyRizzoIcons(projectDir, framework, opts) {
1608
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1609
+ function collectIconPaths(src, dest) {
1610
+ const entries = readdirSync(src, { withFileTypes: true });
1611
+ for (const e of entries) {
1612
+ const destPath = join(dest, e.name);
1613
+ if (e.isDirectory()) collectIconPaths(join(src, e.name), destPath);
1614
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
1615
+ }
1616
+ }
1435
1617
  if (framework === 'astro') {
1436
1618
  const iconsSrc = join(getScaffoldAstroDir(), 'icons');
1437
1619
  if (!existsSync(iconsSrc)) return;
1438
1620
  const targetDir = join(projectDir, 'src', 'components', 'rizzo', 'icons');
1621
+ if (plan) {
1622
+ collectIconPaths(iconsSrc, targetDir);
1623
+ return;
1624
+ }
1439
1625
  mkdirSync(targetDir, { recursive: true });
1440
1626
  copyDirRecursive(iconsSrc, targetDir);
1441
1627
  } else if (framework === 'svelte') {
1442
1628
  const iconsSrc = join(getScaffoldSvelteDir(), 'icons');
1443
1629
  if (!existsSync(iconsSrc)) return;
1444
1630
  const targetDir = join(projectDir, 'src', 'lib', 'rizzo', 'icons');
1631
+ if (plan) {
1632
+ collectIconPaths(iconsSrc, targetDir);
1633
+ return;
1634
+ }
1445
1635
  mkdirSync(targetDir, { recursive: true });
1446
1636
  copyDirRecursive(iconsSrc, targetDir);
1447
1637
  } else if (framework === 'vanilla') {
1448
1638
  const iconsSrc = getScaffoldVanillaIconsDir();
1449
1639
  if (!existsSync(iconsSrc)) return;
1450
1640
  const targetDir = join(projectDir, 'icons');
1641
+ if (plan) {
1642
+ collectIconPaths(iconsSrc, targetDir);
1643
+ return;
1644
+ }
1451
1645
  mkdirSync(targetDir, { recursive: true });
1452
1646
  copyDirRecursive(iconsSrc, targetDir);
1453
1647
  }
@@ -1504,13 +1698,15 @@ function copyDirRecursiveWithReplacements(src, dest, replacements) {
1504
1698
  * Like copyDirRecursiveWithReplacements but never overwrites existing files.
1505
1699
  * Returns { skipped: Array<{ relativePath, content }> } for files that already existed (so caller can write RIZZO-SETUP.md).
1506
1700
  * relativePath is from projectDir (dest).
1701
+ * 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
1702
  */
1508
- function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, projectDir) {
1703
+ function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, projectDir, opts) {
1509
1704
  const skipped = [];
1510
1705
  projectDir = projectDir || dest;
1706
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1511
1707
  const textExtensions = new Set(['.html', '.astro', '.svelte', '.ts', '.js', '.mjs', '.json', '.css', '.md']);
1512
1708
  function recurse(s, d) {
1513
- mkdirSync(d, { recursive: true });
1709
+ if (!plan) mkdirSync(d, { recursive: true });
1514
1710
  const entries = readdirSync(s, { withFileTypes: true });
1515
1711
  for (const e of entries) {
1516
1712
  const srcPath = join(s, e.name);
@@ -1518,8 +1714,8 @@ function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, pr
1518
1714
  if (e.isDirectory()) {
1519
1715
  recurse(srcPath, destPath);
1520
1716
  } else {
1717
+ const rel = pathRelative(projectDir, destPath);
1521
1718
  if (existsSync(destPath)) {
1522
- const rel = pathRelative(projectDir, destPath);
1523
1719
  let content = '';
1524
1720
  const ext = srcPath.slice(srcPath.lastIndexOf('.'));
1525
1721
  if (textExtensions.has(ext)) {
@@ -1531,6 +1727,10 @@ function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, pr
1531
1727
  skipped.push({ relativePath: rel, content });
1532
1728
  continue;
1533
1729
  }
1730
+ if (plan) {
1731
+ plan.wouldWrite.push(rel);
1732
+ continue;
1733
+ }
1534
1734
  const ext = srcPath.slice(srcPath.lastIndexOf('.'));
1535
1735
  if (textExtensions.has(ext)) {
1536
1736
  let content = readFileSync(srcPath, 'utf8');
@@ -1548,7 +1748,7 @@ function copyDirRecursiveWithReplacementsNoOverwrite(src, dest, replacements, pr
1548
1748
  return { skipped };
1549
1749
  }
1550
1750
 
1551
- function copySvelteComponents(projectDir, selectedNames) {
1751
+ function copySvelteComponents(projectDir, selectedNames, opts) {
1552
1752
  const scaffoldDir = getScaffoldSvelteDir();
1553
1753
  if (!existsSync(scaffoldDir)) {
1554
1754
  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 +1763,28 @@ function copySvelteComponents(projectDir, selectedNames) {
1563
1763
  return;
1564
1764
  }
1565
1765
  const targetDir = join(projectDir, 'src', 'lib', 'rizzo');
1766
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1767
+ if (plan) {
1768
+ toCopy.forEach((name) => { plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, name + '.svelte'))); });
1769
+ const iconsSrc = join(scaffoldDir, 'icons');
1770
+ if (existsSync(iconsSrc) && (toCopy.length > 0 || copyIconsOnly)) {
1771
+ (function collect(s, d) {
1772
+ readdirSync(s, { withFileTypes: true }).forEach((e) => {
1773
+ const destPath = join(d, e.name);
1774
+ if (e.isDirectory()) collect(join(s, e.name), destPath);
1775
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
1776
+ });
1777
+ })(iconsSrc, join(targetDir, 'icons'));
1778
+ }
1779
+ if (toCopy.includes('ThemeSwitcher') || toCopy.includes('ThemeIcon')) {
1780
+ if (existsSync(join(scaffoldDir, 'themes.ts'))) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'themes.ts')));
1781
+ if (existsSync(join(scaffoldDir, 'theme.ts'))) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'theme.ts')));
1782
+ }
1783
+ if (toCopy.includes('Settings') && existsSync(join(getScaffoldConfigDir(), 'fonts.ts')))
1784
+ plan.wouldWrite.push(pathRelative(projectDir, join(projectDir, 'src', 'lib', 'config', 'fonts.ts')));
1785
+ if (toCopy.length > 0) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'index.ts')));
1786
+ return;
1787
+ }
1566
1788
  mkdirSync(targetDir, { recursive: true });
1567
1789
  const exports = [];
1568
1790
  for (const name of toCopy) {
@@ -1602,7 +1824,7 @@ function copySvelteComponents(projectDir, selectedNames) {
1602
1824
  }
1603
1825
  }
1604
1826
 
1605
- function copyAstroComponents(projectDir, selectedNames) {
1827
+ function copyAstroComponents(projectDir, selectedNames, opts) {
1606
1828
  const scaffoldDir = getScaffoldAstroDir();
1607
1829
  if (!existsSync(scaffoldDir)) {
1608
1830
  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 +1839,27 @@ function copyAstroComponents(projectDir, selectedNames) {
1617
1839
  return;
1618
1840
  }
1619
1841
  const targetDir = join(projectDir, 'src', 'components', 'rizzo');
1842
+ const plan = opts && opts.dryRun && opts.plan ? opts.plan : null;
1843
+ if (plan) {
1844
+ toCopy.forEach((name) => { plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, name + '.astro'))); });
1845
+ const iconsSrc = join(scaffoldDir, 'icons');
1846
+ if (existsSync(iconsSrc) && (toCopy.length > 0 || copyIconsOnly)) {
1847
+ (function collect(s, d) {
1848
+ readdirSync(s, { withFileTypes: true }).forEach((e) => {
1849
+ const destPath = join(d, e.name);
1850
+ if (e.isDirectory()) collect(join(s, e.name), destPath);
1851
+ else plan.wouldWrite.push(pathRelative(projectDir, destPath));
1852
+ });
1853
+ })(iconsSrc, join(targetDir, 'icons'));
1854
+ }
1855
+ if (toCopy.includes('ThemeSwitcher') || toCopy.includes('ThemeIcon')) {
1856
+ if (existsSync(join(scaffoldDir, 'themes.ts'))) plan.wouldWrite.push(pathRelative(projectDir, join(targetDir, 'themes.ts')));
1857
+ if (existsSync(join(getScaffoldUtilsDir(), 'theme.ts'))) plan.wouldWrite.push(pathRelative(projectDir, join(projectDir, 'src', 'components', 'utils', 'theme.ts')));
1858
+ }
1859
+ if (toCopy.includes('Settings') && existsSync(join(getScaffoldConfigDir(), 'fonts.ts')))
1860
+ plan.wouldWrite.push(pathRelative(projectDir, join(projectDir, 'src', 'components', 'config', 'fonts.ts')));
1861
+ return;
1862
+ }
1620
1863
  mkdirSync(targetDir, { recursive: true });
1621
1864
  let count = 0;
1622
1865
  for (const name of toCopy) {
@@ -1758,7 +2001,9 @@ async function runAddToExisting(frameworkOverride, options) {
1758
2001
  cssTarget = join(targetDir, 'rizzo.min.css');
1759
2002
  }
1760
2003
  const cssExists = existsSync(cssTarget);
1761
- if (cssExists && !options.force) {
2004
+ if (options.dryRun) {
2005
+ options._overwriteCss = true;
2006
+ } else if (cssExists && !options.force) {
1762
2007
  const answer = await question('\nCSS already exists at ' + cssTarget + '. Overwrite? (y/N) ');
1763
2008
  if (answer !== '' && !/^y(es)?$/i.test(answer)) {
1764
2009
  console.log('Skipping CSS copy. Updating config and components only.');
@@ -1776,9 +2021,10 @@ async function runAddToExisting(frameworkOverride, options) {
1776
2021
  const astroCoreDir = getScaffoldAstroCoreDir();
1777
2022
  const svelteCoreDir = getScaffoldSvelteCoreDir();
1778
2023
  const themeCommentAdd = ' <!-- Initial: ' + theme + '; dark: ' + defaultDark + '; light: ' + defaultLight + ' (all 14 themes in CSS) -->';
2024
+ const copyOpts = options.dryRun && options.plan ? options : undefined;
1779
2025
  if (framework === 'vanilla' && getVariantDir('vanilla', selectedVariation)) {
1780
2026
  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);
2027
+ const variantResult = copyVariantOverlayNoOverwrite(cwd, 'vanilla', selectedVariation, vanillaRepl, copyOpts);
1782
2028
  if (variantResult && variantResult.skipped) addSkippedFiles = variantResult.skipped;
1783
2029
  } else if (selectedVariation !== 'css-only' && ((framework === 'astro' && existsSync(astroCoreDir)) || (framework === 'svelte' && existsSync(svelteCoreDir)))) {
1784
2030
  const themeComment = themeCommentAdd;
@@ -1818,48 +2064,54 @@ async function runAddToExisting(frameworkOverride, options) {
1818
2064
  }
1819
2065
  }
1820
2066
  }
1821
- mkdirSync(cwd, { recursive: true });
2067
+ if (!options.dryRun) mkdirSync(cwd, { recursive: true });
1822
2068
  if (framework === 'astro') {
1823
- const baseResult = copyDirRecursiveWithReplacementsNoOverwrite(astroCoreDir, cwd, replacements, cwd);
1824
- const variantResult = copyVariantOverlayNoOverwrite(cwd, 'astro', selectedVariation, replacements);
2069
+ const baseResult = copyDirRecursiveWithReplacementsNoOverwrite(astroCoreDir, cwd, replacements, cwd, copyOpts);
2070
+ const variantResult = copyVariantOverlayNoOverwrite(cwd, 'astro', selectedVariation, replacements, copyOpts);
1825
2071
  addSkippedFiles = baseResult.skipped.concat(variantResult.skipped || []);
1826
2072
  } else if (framework === 'svelte') {
1827
- const baseResult = copyDirRecursiveWithReplacementsNoOverwrite(svelteCoreDir, cwd, replacements, cwd);
1828
- const variantResult = copyVariantOverlayNoOverwrite(cwd, 'svelte', selectedVariation, replacements);
2073
+ const baseResult = copyDirRecursiveWithReplacementsNoOverwrite(svelteCoreDir, cwd, replacements, cwd, copyOpts);
2074
+ const variantResult = copyVariantOverlayNoOverwrite(cwd, 'svelte', selectedVariation, replacements, copyOpts);
1829
2075
  addSkippedFiles = baseResult.skipped.concat(variantResult.skipped || []);
1830
2076
  }
1831
2077
  }
1832
2078
 
1833
2079
  if (options._overwriteCss) {
1834
2080
  if (framework === 'astro') {
1835
- copyRizzoCssAndFontsForAstro(cwd, cssSource);
2081
+ copyRizzoCssAndFontsForAstro(cwd, cssSource, copyOpts);
1836
2082
  } else if (framework === 'svelte') {
1837
- copyRizzoCssAndFontsForSvelte(cwd, cssSource);
2083
+ copyRizzoCssAndFontsForSvelte(cwd, cssSource, copyOpts);
1838
2084
  } else {
1839
2085
  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);
2086
+ if (copyOpts && copyOpts.plan) {
2087
+ copyOpts.plan.wouldWrite.push(pathRelative(cwd, cssTarget));
2088
+ copyRizzoFonts(dirname(cssTarget), copyOpts);
2089
+ if (framework === 'vanilla' && selectedVariation !== 'css-only') copyRizzoSfx(cwd, copyOpts);
2090
+ } else {
2091
+ mkdirSync(targetDir, { recursive: true });
2092
+ copyFileSync(cssSource, cssTarget);
2093
+ copyRizzoFonts(dirname(cssTarget));
2094
+ if (framework === 'vanilla' && selectedVariation !== 'css-only') copyRizzoSfx(cwd);
2095
+ }
1844
2096
  }
1845
2097
  }
1846
2098
 
1847
- if (selectedVariation !== 'css-only') copyRizzoIcons(cwd, framework);
2099
+ if (selectedVariation !== 'css-only') copyRizzoIcons(cwd, framework, copyOpts);
1848
2100
  if (framework === 'svelte' && selectedComponents.length > 0) {
1849
2101
  const expanded = expandWithDeps('svelte', selectedComponents);
1850
2102
  logAddedDeps(selectedComponents, expanded, 'svelte');
1851
- copySvelteComponents(cwd, expanded);
2103
+ copySvelteComponents(cwd, expanded, copyOpts);
1852
2104
  } else if (framework === 'astro' && selectedComponents.length > 0) {
1853
2105
  const expanded = expandWithDeps('astro', selectedComponents);
1854
2106
  logAddedDeps(selectedComponents, expanded, 'astro');
1855
- copyAstroComponents(cwd, expanded);
2107
+ copyAstroComponents(cwd, expanded, copyOpts);
1856
2108
  } else if (framework === 'vanilla' && selectedComponents.length > 0) {
1857
2109
  const linkHrefForVanilla = (options && options.targetDir) ? getLinkHrefForTargetDir(framework, options.targetDir) : paths.linkHref;
1858
2110
  const vanillaRepl = { '{{LINK_HREF}}': linkHrefForVanilla, '{{DATA_THEME}}': theme };
1859
- copyVanillaComponents(cwd, selectedComponents, vanillaRepl);
2111
+ copyVanillaComponents(cwd, selectedComponents, vanillaRepl, copyOpts);
1860
2112
  const needsJs = selectedComponents.some((c) => VANILLA_JS_COMPONENTS.includes(c));
1861
2113
  const vanillaJsPath = join(cwd, 'js', 'main.js');
1862
- if (needsJs && !existsSync(vanillaJsPath) && (options.copyVanillaJs || (!preselected && (await confirmCopyVanillaJs())))) {
2114
+ if (!options.dryRun && needsJs && !existsSync(vanillaJsPath) && (options.copyVanillaJs || (!preselected && (await confirmCopyVanillaJs())))) {
1863
2115
  const vanillaJsSrc = join(getPackageRoot(), 'scaffold', 'vanilla', 'js', 'main.js');
1864
2116
  if (existsSync(vanillaJsSrc)) {
1865
2117
  mkdirSync(join(cwd, 'js'), { recursive: true });
@@ -1880,6 +2132,7 @@ async function runAddToExisting(frameworkOverride, options) {
1880
2132
  } else if (needsJs && !existsSync(vanillaJsPath)) {
1881
2133
  options._vanillaJsHint = true;
1882
2134
  }
2135
+ if (options.dryRun && needsJs && !existsSync(vanillaJsPath) && options.plan) options.plan.wouldWrite.push('js/main.js');
1883
2136
  }
1884
2137
 
1885
2138
  const linkHref = (framework === 'astro' || framework === 'svelte') ? paths.linkHref : ((options && options.targetDir) ? getLinkHrefForTargetDir(framework, options.targetDir) : paths.linkHref);
@@ -1893,6 +2146,20 @@ async function runAddToExisting(frameworkOverride, options) {
1893
2146
  const configTargetDir = framework === 'astro' ? 'public/css' : framework === 'svelte' ? 'static/css' : targetDirRaw;
1894
2147
  const configPath = join(cwd, RIZZO_CONFIG_FILE);
1895
2148
  const hadConfig = existsSync(configPath);
2149
+ if (options.dryRun) {
2150
+ if (!hadConfig) options.plan.wouldWrite.push(RIZZO_CONFIG_FILE);
2151
+ options.plan.wouldWrite.push(RIZZO_SETUP_FILE);
2152
+ if (options.writeSnippet !== false) options.plan.wouldWrite.push(RIZZO_SNIPPET_FILE);
2153
+ if (options.writeReadme) options.plan.wouldWrite.push(SCAFFOLD_README_FILENAME);
2154
+ copyPackageLicense(cwd, copyOpts);
2155
+ console.log('\n Dry run — no files written.');
2156
+ console.log(' Would write (' + options.plan.wouldWrite.length + ' paths):');
2157
+ options.plan.wouldWrite.sort().forEach((p) => console.log(' ' + p));
2158
+ const setupMdContent = buildRizzoSetupMd(framework, { linkHref, theme, defaultDark, defaultLight, skippedFiles: addSkippedFiles.length > 0 ? addSkippedFiles : undefined });
2159
+ console.log('\n --- RIZZO-SETUP.md snippet (for skipped files) ---\n');
2160
+ console.log(setupMdContent);
2161
+ return;
2162
+ }
1896
2163
  if (!hadConfig) {
1897
2164
  writeRizzoConfig(cwd, { targetDir: configTargetDir, framework, packageManager: pm.agent, theme });
1898
2165
  }
@@ -2569,12 +2836,13 @@ async function cmdInit(argv) {
2569
2836
  }
2570
2837
 
2571
2838
  // Package manager install: only for Astro/Svelte (Vanilla has no package.json)
2839
+ const installCmd = pm.install + (hasFlag(argv, '--offline') ? ' --offline' : '');
2572
2840
  if (runInstallAfterScaffold && !noInstall && hasPackageJson) {
2573
2841
  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);
2842
+ console.log('\n Running' + dirLabel + ': ' + installCmd);
2843
+ const code = runInDir(projectDir, installCmd);
2576
2844
  if (code !== 0) {
2577
- console.error('\n Install failed (exit ' + code + '). Run manually: ' + runPrefix + pm.install);
2845
+ console.error('\n Install failed (exit ' + code + '). Run manually: ' + runPrefix + installCmd);
2578
2846
  } else {
2579
2847
  console.log(' ✓ Dependencies installed.');
2580
2848
  }
@@ -2582,15 +2850,15 @@ async function cmdInit(argv) {
2582
2850
  const shouldRun = await confirmRunInstall(pm);
2583
2851
  if (shouldRun) {
2584
2852
  const dirLabel = projectDir !== cwd ? ' in ' + pathRelative(cwd, projectDir) : ' here';
2585
- console.log('\n Running' + dirLabel + ': ' + pm.install);
2586
- const code = runInDir(projectDir, pm.install);
2853
+ console.log('\n Running' + dirLabel + ': ' + installCmd);
2854
+ const code = runInDir(projectDir, installCmd);
2587
2855
  if (code !== 0) {
2588
- console.error('\n Install failed (exit ' + code + '). Run manually: ' + runPrefix + pm.install);
2856
+ console.error('\n Install failed (exit ' + code + '). Run manually: ' + runPrefix + installCmd);
2589
2857
  } else {
2590
2858
  console.log(' ✓ Dependencies installed.');
2591
2859
  }
2592
2860
  } else {
2593
- console.log('\n Skipped. When ready, run: ' + runPrefix + pm.install);
2861
+ console.log('\n Skipped. When ready, run: ' + runPrefix + installCmd);
2594
2862
  }
2595
2863
  }
2596
2864
 
@@ -2621,6 +2889,7 @@ async function cmdInit(argv) {
2621
2889
  : 'open index.html or serve the folder (e.g. npx serve .)';
2622
2890
  console.log('\n Next: ' + vanillaNext);
2623
2891
  }
2892
+ await checkNewerVersion(argv).catch(() => {});
2624
2893
  console.log('\n Docs: https://rizzo-css.vercel.app\n');
2625
2894
  }
2626
2895