@deneb-ui/cli 2.0.7 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js CHANGED
@@ -5,7 +5,6 @@ const fs = require('node:fs');
5
5
  const { spawnSync } = require('node:child_process');
6
6
 
7
7
  const args = process.argv.slice(2);
8
- const command = args[0];
9
8
 
10
9
  const toolsDir = path.join(__dirname, '..', 'src', 'tools');
11
10
 
@@ -154,8 +153,8 @@ function createTemplate(targetName) {
154
153
  console.log(` npm run validate # Run Fivora preflight checks`);
155
154
  console.log(` npm run zip # Create clean upload-ready ZIP\n`);
156
155
  } else {
157
- console.log(`\n🚀 Scaffolding new Fivora Template via @fivora/create-template...\n`);
158
- const res = spawnSync('npx', ['--yes', '@fivora/create-template', projectName], {
156
+ console.log(`\n🚀 Scaffolding new DENEB Storefront Template via @deneb-ui/create-template...\n`);
157
+ const res = spawnSync('npx', ['--yes', '@deneb-ui/create-template', projectName], {
159
158
  stdio: 'inherit',
160
159
  shell: process.platform === 'win32',
161
160
  });
@@ -357,19 +356,170 @@ function getDefaultSiteData(projectName, pages) {
357
356
  };
358
357
  }
359
358
 
359
+ function getComponentRegistry(importPkg) {
360
+ return {
361
+ 'button': {
362
+ file: 'Button.tsx',
363
+ component: 'Button',
364
+ code: `'use client';\n\nimport { Button, type EditableButtonProps } from '${importPkg}';\n\nexport { Button, type EditableButtonProps };\n`,
365
+ },
366
+ 'dialog': {
367
+ file: 'Dialog.tsx',
368
+ component: 'Dialog',
369
+ code: `'use client';\n\nimport { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, type DialogProps } from '${importPkg}';\n\nexport { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, type DialogProps };\n`,
370
+ },
371
+ 'card': {
372
+ file: 'Card.tsx',
373
+ component: 'Card',
374
+ code: `'use client';\n\nimport { Card, type EditableCardProps } from '${importPkg}';\n\nexport { Card, type EditableCardProps };\n`,
375
+ },
376
+ 'product-card': {
377
+ file: 'ProductCard.tsx',
378
+ component: 'EditableProductCard',
379
+ code: `'use client';\n\nimport { EditableProductCard, type EditableProductCardProps } from '${importPkg}';\n\nexport function ProductCard(props: EditableProductCardProps) {\n return <EditableProductCard {...props} />;\n}\n`,
380
+ },
381
+ 'pricing-card': {
382
+ file: 'PricingCard.tsx',
383
+ component: 'EditablePricingCard',
384
+ code: `'use client';\n\nimport { EditablePricingCard, type EditablePricingCardProps } from '${importPkg}';\n\nexport function PricingCard(props: EditablePricingCardProps) {\n return <EditablePricingCard {...props} />;\n}\n`,
385
+ },
386
+ 'testimonial-card': {
387
+ file: 'TestimonialCard.tsx',
388
+ component: 'EditableTestimonialCard',
389
+ code: `'use client';\n\nimport { EditableTestimonialCard, type EditableTestimonialCardProps } from '${importPkg}';\n\nexport function TestimonialCard(props: EditableTestimonialCardProps) {\n return <EditableTestimonialCard {...props} />;\n}\n`,
390
+ },
391
+ 'contact-form': {
392
+ file: 'ContactForm.tsx',
393
+ component: 'EditableContactForm',
394
+ code: `'use client';\n\nimport { EditableContactForm, type EditableContactFormProps } from '${importPkg}';\n\nexport function ContactForm(props: EditableContactFormProps) {\n return <EditableContactForm {...props} />;\n}\n`,
395
+ },
396
+ 'faq': {
397
+ file: 'FAQAccordion.tsx',
398
+ component: 'EditableFAQAccordion',
399
+ code: `'use client';\n\nimport { EditableFAQAccordion, EditableFAQItem, type EditableFAQAccordionProps } from '${importPkg}';\n\nexport function FAQAccordion(props: EditableFAQAccordionProps) {\n return <EditableFAQAccordion {...props} />;\n}\n\nexport { EditableFAQItem };\n`,
400
+ },
401
+ 'navbar': {
402
+ file: 'Navbar.tsx',
403
+ component: 'EditableNavbar',
404
+ code: `'use client';\n\nimport { EditableNavbar, type EditableNavbarProps } from '${importPkg}';\n\nexport function Navbar(props: EditableNavbarProps) {\n return <EditableNavbar {...props} />;\n}\n`,
405
+ },
406
+ 'footer': {
407
+ file: 'Footer.tsx',
408
+ component: 'EditableFooter',
409
+ code: `'use client';\n\nimport { EditableFooter, type EditableFooterProps } from '${importPkg}';\n\nexport function Footer(props: EditableFooterProps) {\n return <EditableFooter {...props} />;\n}\n`,
410
+ },
411
+ 'hero': {
412
+ file: 'Hero.tsx',
413
+ component: 'EditableHeroCentered',
414
+ code: `'use client';\n\nimport { EditableHeroCentered, EditableHeroSplit, type EditableHeroCenteredProps, type EditableHeroSplitProps } from '${importPkg}';\n\nexport function HeroCentered(props: EditableHeroCenteredProps) {\n return <EditableHeroCentered {...props} />;\n}\n\nexport function HeroSplit(props: EditableHeroSplitProps) {\n return <EditableHeroSplit {...props} />;\n}\n`,
415
+ },
416
+ 'whatsapp-button': {
417
+ file: 'WhatsAppButton.tsx',
418
+ component: 'WhatsAppButton',
419
+ code: `'use client';\n\nimport { WhatsAppButton, type WhatsAppButtonProps } from '${importPkg}';\n\nexport { WhatsAppButton, type WhatsAppButtonProps };\n`,
420
+ },
421
+ 'phone-button': {
422
+ file: 'PhoneButton.tsx',
423
+ component: 'PhoneButton',
424
+ code: `'use client';\n\nimport { PhoneButton, type PhoneButtonProps } from '${importPkg}';\n\nexport { PhoneButton, type PhoneButtonProps };\n`,
425
+ },
426
+ 'email-button': {
427
+ file: 'EmailButton.tsx',
428
+ component: 'EmailButton',
429
+ code: `'use client';\n\nimport { EmailButton, type EmailButtonProps } from '${importPkg}';\n\nexport { EmailButton, type EmailButtonProps };\n`,
430
+ },
431
+ 'contact-actions': {
432
+ file: 'ContactActions.tsx',
433
+ component: 'ContactActions',
434
+ code: `'use client';\n\nimport { ContactActions, type ContactActionsProps } from '${importPkg}';\n\nexport { ContactActions, type ContactActionsProps };\n`,
435
+ },
436
+ 'location-card': {
437
+ file: 'LocationCard.tsx',
438
+ component: 'LocationCard',
439
+ code: `'use client';\n\nimport { LocationCard, type LocationCardProps } from '${importPkg}';\n\nexport { LocationCard, type LocationCardProps };\n`,
440
+ },
441
+ 'location-link': {
442
+ file: 'LocationLink.tsx',
443
+ component: 'LocationLink',
444
+ code: `'use client';\n\nimport { LocationLink, type LocationLinkProps } from '${importPkg}';\n\nexport { LocationLink, type LocationLinkProps };\n`,
445
+ },
446
+ 'map-embed': {
447
+ file: 'MapEmbed.tsx',
448
+ component: 'MapEmbed',
449
+ code: `'use client';\n\nimport { MapEmbed, type MapEmbedProps } from '${importPkg}';\n\nexport { MapEmbed, type MapEmbedProps };\n`,
450
+ },
451
+ 'social-links': {
452
+ file: 'SocialLinks.tsx',
453
+ component: 'SocialLinks',
454
+ code: `'use client';\n\nimport { SocialLinks, type SocialLinksProps } from '${importPkg}';\n\nexport { SocialLinks, type SocialLinksProps };\n`,
455
+ },
456
+ 'social-button': {
457
+ file: 'SocialButton.tsx',
458
+ component: 'SocialButton',
459
+ code: `'use client';\n\nimport { SocialButton, type SocialButtonProps } from '${importPkg}';\n\nexport { SocialButton, type SocialButtonProps };\n`,
460
+ },
461
+ 'business-hours': {
462
+ file: 'BusinessHours.tsx',
463
+ component: 'BusinessHours',
464
+ code: `'use client';\n\nimport { BusinessHours, type BusinessHoursProps } from '${importPkg}';\n\nexport { BusinessHours, type BusinessHoursProps };\n`,
465
+ },
466
+ 'announcement-bar': {
467
+ file: 'AnnouncementBar.tsx',
468
+ component: 'EditableAnnouncementBar',
469
+ code: `'use client';\n\nimport { EditableAnnouncementBar, AnnouncementBar, type EditableAnnouncementBarProps } from '${importPkg}';\n\nexport { EditableAnnouncementBar, AnnouncementBar, type EditableAnnouncementBarProps };\n`,
470
+ },
471
+ 'category-pills': {
472
+ file: 'CategoryPills.tsx',
473
+ component: 'EditableCategoryPills',
474
+ code: `'use client';\n\nimport { EditableCategoryPills, CategoryPills, type EditableCategoryPillsProps } from '${importPkg}';\n\nexport { EditableCategoryPills, CategoryPills, type EditableCategoryPillsProps };\n`,
475
+ },
476
+ 'floating-contact-widget': {
477
+ file: 'FloatingContactWidget.tsx',
478
+ component: 'FloatingContactWidget',
479
+ code: `'use client';\n\nimport { FloatingContactWidget, type FloatingContactWidgetProps } from '${importPkg}';\n\nexport { FloatingContactWidget, type FloatingContactWidgetProps };\n`,
480
+ },
481
+ 'sticky-mobile-bar': {
482
+ file: 'StickyMobileBar.tsx',
483
+ component: 'StickyMobileBar',
484
+ code: `'use client';\n\nimport { StickyMobileBar, type StickyMobileBarProps, type StickyMobileBarAction } from '${importPkg}';\n\nexport { StickyMobileBar, type StickyMobileBarProps, type StickyMobileBarAction };\n`,
485
+ },
486
+ 'trust-badges': {
487
+ file: 'TrustBadges.tsx',
488
+ component: 'TrustBadges',
489
+ code: `'use client';\n\nimport { TrustBadges, type TrustBadgesProps, type TrustBadgeItem } from '${importPkg}';\n\nexport { TrustBadges, type TrustBadgesProps, type TrustBadgeItem };\n`,
490
+ },
491
+ 'product-quickview': {
492
+ file: 'ProductQuickView.tsx',
493
+ component: 'ProductQuickView',
494
+ code: `'use client';\n\nimport { ProductQuickView, type ProductQuickViewProps, type ProductQuickViewItem } from '${importPkg}';\n\nexport { ProductQuickView, type ProductQuickViewProps, type ProductQuickViewItem };\n`,
495
+ },
496
+ 'cookie-consent': {
497
+ file: 'CookieConsentBanner.tsx',
498
+ component: 'CookieConsentBanner',
499
+ code: `'use client';\n\nimport { CookieConsentBanner, type CookieConsentBannerProps } from '${importPkg}';\n\nexport { CookieConsentBanner, type CookieConsentBannerProps };\n`,
500
+ },
501
+ 'deneb-action': {
502
+ file: 'DenebAction.tsx',
503
+ component: 'DenebAction',
504
+ code: `'use client';\n\nimport { DenebAction, type DenebActionProps } from '${importPkg}';\n\nexport { DenebAction, type DenebActionProps };\n`,
505
+ },
506
+ };
507
+ }
508
+
360
509
  function initProject(targetInput) {
361
510
  const targetDir = path.resolve(process.cwd(), targetInput || '.');
362
511
  const pkgPath = path.join(targetDir, 'package.json');
363
512
 
364
513
  console.log('\n' + createBox([
365
- '\x1b[1m\x1b[37mFIVORA TEMPLATE INITIALIZER\x1b[0m',
366
- '\x1b[90mConfigure existing Next.js app for Fivora\x1b[0m'
367
- ], 55) + '\n');
514
+ '\x1b[1m\x1b[37mDENEB TEMPLATE INITIALIZER\x1b[0m',
515
+ '\x1b[90mConfigure existing Next.js project for Fivora Platform\x1b[0m',
516
+ '\x1b[90mPowered by DENEB-UI Collaborate with FIVORA\x1b[0m'
517
+ ], 58) + '\n');
368
518
 
369
519
  if (!fs.existsSync(pkgPath)) {
370
520
  console.error(`\x1b[31mError:\x1b[0m No package.json found in "${targetDir}".`);
371
- console.error(`\nPlease run 'fivora init' inside your Next.js project root, or scaffold a new project with:`);
372
- console.error(` \x1b[36mnpx @fivora/create-template <app-name>\x1b[0m\n`);
521
+ console.error(`\nPlease run 'deneb init' inside your Next.js project root, or scaffold a new project with:`);
522
+ console.error(` \x1b[36mnpx @deneb-ui/create-template <app-name>\x1b[0m\n`);
373
523
  process.exit(1);
374
524
  }
375
525
 
@@ -396,22 +546,29 @@ function initProject(targetInput) {
396
546
  // 1. Scan / Detect Pages
397
547
  const detectedPages = detectPages(targetDir);
398
548
 
399
- // 2. Generate fivora-template.json (or keep fivora-template.json if present)
400
- const fivoraManifestPath = path.join(targetDir, 'fivora-template.json');
401
- const legacyManifestPath = path.join(targetDir, 'fivora-template.json');
402
- const manifestPath = fs.existsSync(legacyManifestPath) && !fs.existsSync(fivoraManifestPath)
403
- ? legacyManifestPath
404
- : fivoraManifestPath;
549
+ // 2. Run Universal Template Conversion Engine
550
+ // Automatically detects CSS/UI frameworks (shadcn/ui, HeroUI, Tailwind CSS),
551
+ // creates safe backup, instruments Root Layout with SiteDataProvider,
552
+ // extracts hardcoded text/images/buttons/placeholders,
553
+ // injects data-preview-field-path markers, harmonizes UI components,
554
+ // and builds synchronized site-data.json & fivora-template.json.
555
+ let conversionRes = null;
556
+ try {
557
+ const { runUniversalTemplateConversion } = require('../src/tools/template-converter.cjs');
558
+ conversionRes = runUniversalTemplateConversion(targetDir, projectName, detectedPages);
559
+ } catch (err) {
560
+ console.error(`\x1b[33m⚠ Note:\x1b[0m Automated conversion encountered an issue: ${err.message}. Falling back to default generation.`);
561
+ }
405
562
 
563
+ // 3. Fallback: Generate fivora-template.json if not yet present
564
+ const manifestPath = path.join(targetDir, 'fivora-template.json');
406
565
  if (!fs.existsSync(manifestPath)) {
407
566
  const manifest = getDefaultManifest(projectName, detectedPages);
408
567
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
409
568
  console.log(`\x1b[32m✔ Created\x1b[0m fivora-template.json (version 2, strict visual editing contract)`);
410
- } else {
411
- console.log(`\x1b[90m⏩ Kept existing\x1b[0m ${path.basename(manifestPath)}`);
412
569
  }
413
570
 
414
- // 3. Generate siteDataFile
571
+ // 4. Fallback: Generate siteDataFile if not yet present
415
572
  let manifestObj;
416
573
  try {
417
574
  manifestObj = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
@@ -426,31 +583,30 @@ function initProject(targetInput) {
426
583
  const siteData = getDefaultSiteData(projectName, detectedPages);
427
584
  fs.writeFileSync(siteDataPath, JSON.stringify(siteData, null, 2) + '\n');
428
585
  console.log(`\x1b[32m✔ Created\x1b[0m ${relSiteData} (merchant & editable site data)`);
429
- } else {
430
- console.log(`\x1b[90m⏩ Kept existing\x1b[0m ${relSiteData}`);
431
586
  }
432
587
 
433
588
  // 4. Update package.json scripts
434
589
  pkg.scripts = pkg.scripts || {};
435
590
  const scriptsToAdd = {
436
- 'lab': 'fivora lab .',
437
- 'validate': 'fivora validate .',
438
- 'zip': 'fivora zip .',
439
- 'package:template': 'fivora package .',
440
- 'update:fivora': 'fivora update',
591
+ 'lab': 'deneb lab .',
592
+ 'validate': 'deneb validate .',
593
+ 'zip': 'deneb zip .',
594
+ 'validate-and-zip': 'deneb validate-and-zip .',
595
+ 'package:template': 'deneb package .',
596
+ 'update:deneb': 'deneb update',
441
597
  };
442
598
  let addedCount = 0;
443
599
  for (const [key, val] of Object.entries(scriptsToAdd)) {
444
- if (!pkg.scripts[key]) {
600
+ if (!pkg.scripts[key] || pkg.scripts[key].startsWith('fivora ')) {
445
601
  pkg.scripts[key] = val;
446
602
  addedCount++;
447
603
  }
448
604
  }
449
605
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
450
606
  if (addedCount > 0) {
451
- console.log(`\x1b[32m✔ Injected\x1b[0m Fivora scripts into package.json (lab, validate, zip, package:template, update:fivora)`);
607
+ console.log(`\x1b[32m✔ Configured\x1b[0m DENEB scripts in package.json (lab, validate, zip, validate-and-zip, package:template, update:deneb)`);
452
608
  } else {
453
- console.log(`\x1b[90m⏩ Fivora scripts already present\x1b[0m in package.json`);
609
+ console.log(`\x1b[90m⏩ DENEB scripts already present\x1b[0m in package.json`);
454
610
  }
455
611
 
456
612
  // 5. Check next.config for static export
@@ -474,47 +630,53 @@ function initProject(targetInput) {
474
630
  console.log(` \x1b[90mconst nextConfig = { output: 'export' };\x1b[0m`);
475
631
  }
476
632
 
477
- // 6. Install Fivora packages if missing
633
+ // 6. Install DENEB packages if missing
478
634
  const skipInstall = process.argv.includes('--skip-install');
479
- const hasEditable = Boolean(
480
- (pkg.dependencies && pkg.dependencies['@fivora/editable-components']) ||
481
- (pkg.devDependencies && pkg.devDependencies['@fivora/editable-components'])
635
+ const hasUi = Boolean(
636
+ (pkg.dependencies && (pkg.dependencies['@deneb-ui/ui'] || pkg.dependencies['@deneb/ui'] || pkg.dependencies['@fivora/editable-components'])) ||
637
+ (pkg.devDependencies && (pkg.devDependencies['@deneb-ui/ui'] || pkg.devDependencies['@deneb/ui'] || pkg.devDependencies['@fivora/editable-components']))
482
638
  );
483
639
  const hasCli = Boolean(
484
- (pkg.devDependencies && pkg.devDependencies['@fivora/cli']) ||
485
- (pkg.dependencies && pkg.dependencies['@fivora/cli'])
640
+ (pkg.devDependencies && (pkg.devDependencies['@deneb-ui/cli'] || pkg.devDependencies['@fivora/cli'])) ||
641
+ (pkg.dependencies && (pkg.dependencies['@deneb-ui/cli'] || pkg.dependencies['@fivora/cli']))
486
642
  );
487
643
 
488
- if (!skipInstall && (!hasEditable || !hasCli)) {
489
- const toInstall = [];
490
- if (!hasEditable) toInstall.push('@fivora/editable-components');
491
- if (!hasCli) toInstall.push('@fivora/cli');
644
+ if (!skipInstall && (!hasUi || !hasCli)) {
645
+ const depsToInstall = [];
646
+ const devDepsToInstall = [];
647
+ if (!hasUi) depsToInstall.push('@deneb-ui/ui@latest');
648
+ if (!hasCli) devDepsToInstall.push('@deneb-ui/cli@latest');
492
649
 
493
- console.log(`\n📦 Installing ${toInstall.join(' and ')}...`);
494
- const installRes = spawnSync('npm', ['install', ...toInstall], {
495
- cwd: targetDir,
496
- stdio: 'inherit',
497
- shell: process.platform === 'win32',
498
- });
499
- if (installRes.status === 0) {
500
- console.log(`\x1b[32m✔ Packages installed successfully!\x1b[0m`);
501
- } else {
502
- console.log(`\x1b[33mNote: npm install exited with code ${installRes.status}. You can run 'npm i ${toInstall.join(' ')}' manually.\x1b[0m`);
650
+ if (depsToInstall.length > 0) {
651
+ console.log(`\n📦 Installing ${depsToInstall.join(' ')}...`);
652
+ spawnSync('npm', ['install', ...depsToInstall], {
653
+ cwd: targetDir,
654
+ stdio: 'inherit',
655
+ shell: process.platform === 'win32',
656
+ });
657
+ }
658
+ if (devDepsToInstall.length > 0) {
659
+ console.log(`\n📦 Installing (dev) ${devDepsToInstall.join(' ')}...`);
660
+ spawnSync('npm', ['install', '-D', ...devDepsToInstall], {
661
+ cwd: targetDir,
662
+ stdio: 'inherit',
663
+ shell: process.platform === 'win32',
664
+ });
503
665
  }
504
666
  }
505
667
 
506
- console.log(`\n\x1b[32m✔ Template initialization complete!\x1b[0m`);
668
+ console.log(`\n\x1b[32m✔ Project initialization complete!\x1b[0m`);
507
669
  console.log(`\nYou can now run:`);
508
- console.log(` \x1b[36mnpm run lab\x1b[0m \x1b[90m# Launch Local Visual Editing Lab\x1b[0m`);
509
- console.log(` \x1b[36mnpm run validate\x1b[0m \x1b[90m# Check compliance with Fivora contract\x1b[0m`);
510
- console.log(` \x1b[36mnpm run zip\x1b[0m \x1b[90m# Package clean ZIP for 1-click upload\x1b[0m\n`);
670
+ console.log(` \x1b[36mnpm run lab\x1b[0m \x1b[90m# Launch Local Visual Editing Lab\x1b[0m`);
671
+ console.log(` \x1b[36mnpm run validate\x1b[0m \x1b[90m# Check compliance with Fivora contract\x1b[0m`);
672
+ console.log(` \x1b[36mnpm run zip\x1b[0m \x1b[90m# Package clean ZIP for 1-click upload\x1b[0m`);
673
+ console.log(` \x1b[36mnpm run validate-and-zip\x1b[0m \x1b[90m# Validate preflight and bundle clean ZIP in 1 step\x1b[0m\n`);
511
674
  }
512
675
 
513
-
514
676
  function updateDependencies(cmdArgs = []) {
515
677
  let targetDir = '.';
516
678
  let isLocal = false;
517
- let localPath = '/mnt/GAMES/GitHub/fivora_package';
679
+ let localPath = path.resolve(__dirname, '..', '..', '..');
518
680
 
519
681
  for (let i = 0; i < cmdArgs.length; i++) {
520
682
  const arg = cmdArgs[i];
@@ -548,52 +710,79 @@ function updateDependencies(cmdArgs = []) {
548
710
  }
549
711
 
550
712
  console.log('\n' + createBox([
551
- '\x1b[1m\x1b[37mFIVORA PACKAGE UPDATER\x1b[0m'
552
- ], 55) + '\n');
713
+ '\x1b[1m\x1b[37mDENEB PACKAGE & COMPONENT UPDATER\x1b[0m',
714
+ '\x1b[90mUpdate DENEB packages and UI components to latest\x1b[0m',
715
+ '\x1b[90mPowered by DENEB-UI Collaborate with FIVORA\x1b[0m'
716
+ ], 58) + '\n');
553
717
 
554
- const currentEditable = String(pkg.dependencies?.['@fivora/editable-components'] || pkg.devDependencies?.['@fivora/editable-components'] || '');
555
- const currentCli = String(pkg.dependencies?.['@fivora/cli'] || pkg.devDependencies?.['@fivora/cli'] || '');
556
- if (!isLocal && (currentEditable.startsWith('file:') || currentCli.startsWith('file:'))) {
557
- isLocal = true;
718
+ let importPkg = '@deneb-ui/ui';
719
+ if (pkg.dependencies?.['@deneb-ui/ui'] || pkg.devDependencies?.['@deneb-ui/ui']) {
720
+ importPkg = '@deneb-ui/ui';
721
+ } else if (pkg.dependencies?.['@deneb/ui'] || pkg.devDependencies?.['@deneb/ui']) {
722
+ importPkg = '@deneb/ui';
558
723
  }
559
724
 
725
+ // 1. Update npm packages
560
726
  if (isLocal) {
561
- const editableDir = path.join(localPath, 'packages', 'editable-components');
727
+ const uiDir = path.join(localPath, 'packages', 'deneb-ui');
562
728
  const cliDir = path.join(localPath, 'cli', 'fivora-cli');
563
729
 
564
- if (fs.existsSync(editableDir) && fs.existsSync(cliDir)) {
565
- console.log('🔄 Updating from local Fivora workspace:');
566
- console.log(' - ' + editableDir);
730
+ if (fs.existsSync(uiDir) && fs.existsSync(cliDir)) {
731
+ console.log('🔄 Updating from local workspace:');
732
+ console.log(' - ' + uiDir);
567
733
  console.log(' - ' + cliDir + '\n');
568
734
 
569
- const installRes = spawnSync('npm', ['install', editableDir, cliDir], {
735
+ const installRes = spawnSync('npm', ['install', uiDir, cliDir], {
570
736
  cwd: targetDir,
571
737
  stdio: 'inherit',
572
738
  shell: process.platform === 'win32',
573
739
  });
574
740
 
575
741
  if (installRes.status === 0) {
576
- console.log('\n\x1b[32m✔ Local Fivora packages re-linked & updated successfully!\x1b[0m\n');
742
+ console.log('\n\x1b[32m✔ Local DENEB packages re-linked & updated successfully!\x1b[0m\n');
577
743
  } else {
578
744
  console.log('\n\x1b[31m✖ Failed to link local packages (exit code: ' + installRes.status + ')\x1b[0m\n');
579
745
  }
580
- return;
581
746
  }
582
- }
747
+ } else {
748
+ console.log('📦 Updating @deneb-ui/ui and @deneb-ui/cli to latest from npm...');
749
+ const packagesToInstall = ['@deneb-ui/ui@latest', '@deneb-ui/cli@latest'];
583
750
 
584
- console.log('📦 Updating @fivora/editable-components and @fivora/cli to latest from npm...');
585
- const packagesToInstall = ['@fivora/editable-components@latest', '@fivora/cli@latest'];
751
+ const installRes = spawnSync('npm', ['install', ...packagesToInstall], {
752
+ cwd: targetDir,
753
+ stdio: 'inherit',
754
+ shell: process.platform === 'win32',
755
+ });
586
756
 
587
- const installRes = spawnSync('npm', ['install', ...packagesToInstall], {
588
- cwd: targetDir,
589
- stdio: 'inherit',
590
- shell: process.platform === 'win32',
591
- });
757
+ if (installRes.status === 0) {
758
+ console.log('\n\x1b[32m✔ DENEB packages updated to latest versions successfully!\x1b[0m\n');
759
+ } else {
760
+ console.log('\n\x1b[31m✖ npm install failed with exit code ' + installRes.status + '\x1b[0m\n');
761
+ }
762
+ }
592
763
 
593
- if (installRes.status === 0) {
594
- console.log('\n\x1b[32m✔ Fivora packages updated to latest versions successfully!\x1b[0m\n');
595
- } else {
596
- console.log('\n\x1b[31m✖ npm install failed with exit code ' + installRes.status + '\x1b[0m\n');
764
+ // 2. Update DENEB components in src/components/ui/
765
+ const uiDir = path.join(targetDir, 'src', 'components', 'ui');
766
+ if (fs.existsSync(uiDir)) {
767
+ console.log('🧩 Inspecting and refreshing installed DENEB UI components in src/components/ui/...\n');
768
+ const registry = getComponentRegistry(importPkg);
769
+ const existingFiles = fs.readdirSync(uiDir);
770
+ let updatedComponentsCount = 0;
771
+
772
+ for (const [key, item] of Object.entries(registry)) {
773
+ if (existingFiles.includes(item.file)) {
774
+ const filePath = path.join(uiDir, item.file);
775
+ fs.writeFileSync(filePath, item.code);
776
+ console.log(` \x1b[32m✔ Updated\x1b[0m src/components/ui/${item.file} (${key})`);
777
+ updatedComponentsCount++;
778
+ }
779
+ }
780
+
781
+ if (updatedComponentsCount > 0) {
782
+ console.log(`\n\x1b[32m✔ Successfully updated ${updatedComponentsCount} DENEB component(s) to the latest definitions!\x1b[0m\n`);
783
+ } else {
784
+ console.log(` \x1b[90mNo existing DENEB UI components found in src/components/ui to update.\x1b[0m\n`);
785
+ }
597
786
  }
598
787
  }
599
788
 
@@ -619,133 +808,7 @@ function addComponent(componentName, targetDirInput) {
619
808
  }
620
809
  }
621
810
 
622
- const REGISTRY = {
623
- 'button': {
624
- file: 'Button.tsx',
625
- component: 'Button',
626
- code: `'use client';\n\nimport { Button, type EditableButtonProps } from '${importPkg}';\n\nexport { Button, type EditableButtonProps };\n`,
627
- },
628
- 'dialog': {
629
- file: 'Dialog.tsx',
630
- component: 'Dialog',
631
- code: `'use client';\n\nimport { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, type DialogProps } from '${importPkg}';\n\nexport { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, type DialogProps };\n`,
632
- },
633
- 'card': {
634
- file: 'Card.tsx',
635
- component: 'Card',
636
- code: `'use client';\n\nimport { Card, type EditableCardProps } from '${importPkg}';\n\nexport { Card, type EditableCardProps };\n`,
637
- },
638
- 'product-card': {
639
- file: 'ProductCard.tsx',
640
- component: 'EditableProductCard',
641
- code: `'use client';\n\nimport { EditableProductCard, type EditableProductCardProps } from '${importPkg}';\n\nexport function ProductCard(props: EditableProductCardProps) {\n return <EditableProductCard {...props} />;\n}\n`,
642
- },
643
- 'pricing-card': {
644
- file: 'PricingCard.tsx',
645
- component: 'EditablePricingCard',
646
- code: `'use client';\n\nimport { EditablePricingCard, type EditablePricingCardProps } from '${importPkg}';\n\nexport function PricingCard(props: EditablePricingCardProps) {\n return <EditablePricingCard {...props} />;\n}\n`,
647
- },
648
- 'testimonial-card': {
649
- file: 'TestimonialCard.tsx',
650
- component: 'EditableTestimonialCard',
651
- code: `'use client';\n\nimport { EditableTestimonialCard, type EditableTestimonialCardProps } from '${importPkg}';\n\nexport function TestimonialCard(props: EditableTestimonialCardProps) {\n return <EditableTestimonialCard {...props} />;\n}\n`,
652
- },
653
- 'contact-form': {
654
- file: 'ContactForm.tsx',
655
- component: 'EditableContactForm',
656
- code: `'use client';\n\nimport { EditableContactForm, type EditableContactFormProps } from '${importPkg}';\n\nexport function ContactForm(props: EditableContactFormProps) {\n return <EditableContactForm {...props} />;\n}\n`,
657
- },
658
- 'faq': {
659
- file: 'FAQAccordion.tsx',
660
- component: 'EditableFAQAccordion',
661
- code: `'use client';\n\nimport { EditableFAQAccordion, EditableFAQItem, type EditableFAQAccordionProps } from '${importPkg}';\n\nexport function FAQAccordion(props: EditableFAQAccordionProps) {\n return <EditableFAQAccordion {...props} />;\n}\n\nexport { EditableFAQItem };\n`,
662
- },
663
- 'navbar': {
664
- file: 'Navbar.tsx',
665
- component: 'EditableNavbar',
666
- code: `'use client';\n\nimport { EditableNavbar, type EditableNavbarProps } from '${importPkg}';\n\nexport function Navbar(props: EditableNavbarProps) {\n return <EditableNavbar {...props} />;\n}\n`,
667
- },
668
- 'footer': {
669
- file: 'Footer.tsx',
670
- component: 'EditableFooter',
671
- code: `'use client';\n\nimport { EditableFooter, type EditableFooterProps } from '${importPkg}';\n\nexport function Footer(props: EditableFooterProps) {\n return <EditableFooter {...props} />;\n}\n`,
672
- },
673
- 'hero': {
674
- file: 'Hero.tsx',
675
- component: 'EditableHeroCentered',
676
- code: `'use client';\n\nimport { EditableHeroCentered, EditableHeroSplit, type EditableHeroCenteredProps, type EditableHeroSplitProps } from '${importPkg}';\n\nexport function HeroCentered(props: EditableHeroCenteredProps) {\n return <EditableHeroCentered {...props} />;\n}\n\nexport function HeroSplit(props: EditableHeroSplitProps) {\n return <EditableHeroSplit {...props} />;\n}\n`,
677
- },
678
- 'whatsapp-button': {
679
- file: 'WhatsAppButton.tsx',
680
- component: 'WhatsAppButton',
681
- code: `'use client';\n\nimport { WhatsAppButton, type WhatsAppButtonProps } from '${importPkg}';\n\nexport { WhatsAppButton, type WhatsAppButtonProps };\n`,
682
- },
683
- 'phone-button': {
684
- file: 'PhoneButton.tsx',
685
- component: 'PhoneButton',
686
- code: `'use client';\n\nimport { PhoneButton, type PhoneButtonProps } from '${importPkg}';\n\nexport { PhoneButton, type PhoneButtonProps };\n`,
687
- },
688
- 'email-button': {
689
- file: 'EmailButton.tsx',
690
- component: 'EmailButton',
691
- code: `'use client';\n\nimport { EmailButton, type EmailButtonProps } from '${importPkg}';\n\nexport { EmailButton, type EmailButtonProps };\n`,
692
- },
693
- 'contact-actions': {
694
- file: 'ContactActions.tsx',
695
- component: 'ContactActions',
696
- code: `'use client';\n\nimport { ContactActions, type ContactActionsProps } from '${importPkg}';\n\nexport { ContactActions, type ContactActionsProps };\n`,
697
- },
698
- 'location-card': {
699
- file: 'LocationCard.tsx',
700
- component: 'LocationCard',
701
- code: `'use client';\n\nimport { LocationCard, type LocationCardProps } from '${importPkg}';\n\nexport { LocationCard, type LocationCardProps };\n`,
702
- },
703
- 'location-link': {
704
- file: 'LocationLink.tsx',
705
- component: 'LocationLink',
706
- code: `'use client';\n\nimport { LocationLink, type LocationLinkProps } from '${importPkg}';\n\nexport { LocationLink, type LocationLinkProps };\n`,
707
- },
708
- 'map-embed': {
709
- file: 'MapEmbed.tsx',
710
- component: 'MapEmbed',
711
- code: `'use client';\n\nimport { MapEmbed, type MapEmbedProps } from '${importPkg}';\n\nexport { MapEmbed, type MapEmbedProps };\n`,
712
- },
713
- 'social-links': {
714
- file: 'SocialLinks.tsx',
715
- component: 'SocialLinks',
716
- code: `'use client';\n\nimport { SocialLinks, type SocialLinksProps } from '${importPkg}';\n\nexport { SocialLinks, type SocialLinksProps };\n`,
717
- },
718
- 'social-button': {
719
- file: 'SocialButton.tsx',
720
- component: 'SocialButton',
721
- code: `'use client';\n\nimport { SocialButton, type SocialButtonProps } from '${importPkg}';\n\nexport { SocialButton, type SocialButtonProps };\n`,
722
- },
723
- 'business-hours': {
724
- file: 'BusinessHours.tsx',
725
- component: 'BusinessHours',
726
- code: `'use client';\n\nimport { BusinessHours, type BusinessHoursProps } from '${importPkg}';\n\nexport { BusinessHours, type BusinessHoursProps };\n`,
727
- },
728
- 'announcement-bar': {
729
- file: 'AnnouncementBar.tsx',
730
- component: 'EditableAnnouncementBar',
731
- code: `'use client';\n\nimport { EditableAnnouncementBar, AnnouncementBar, type EditableAnnouncementBarProps } from '${importPkg}';\n\nexport { EditableAnnouncementBar, AnnouncementBar, type EditableAnnouncementBarProps };\n`,
732
- },
733
- 'category-pills': {
734
- file: 'CategoryPills.tsx',
735
- component: 'EditableCategoryPills',
736
- code: `'use client';\n\nimport { EditableCategoryPills, CategoryPills, type EditableCategoryPillsProps } from '${importPkg}';\n\nexport { EditableCategoryPills, CategoryPills, type EditableCategoryPillsProps };\n`,
737
- },
738
- 'floating-contact-widget': {
739
- file: 'FloatingContactWidget.tsx',
740
- component: 'FloatingContactWidget',
741
- code: `'use client';\n\nimport { FloatingContactWidget, type FloatingContactWidgetProps } from '${importPkg}';\n\nexport { FloatingContactWidget, type FloatingContactWidgetProps };\n`,
742
- },
743
- 'deneb-action': {
744
- file: 'DenebAction.tsx',
745
- component: 'DenebAction',
746
- code: `'use client';\n\nimport { DenebAction, type DenebActionProps } from '${importPkg}';\n\nexport { DenebAction, type DenebActionProps };\n`,
747
- },
748
- };
811
+ const REGISTRY = getComponentRegistry(importPkg);
749
812
 
750
813
  if (!componentName || componentName === 'list') {
751
814
  console.log('\n' + createBox([
@@ -778,43 +841,321 @@ function addComponent(componentName, targetDirInput) {
778
841
  console.log('Import them in your pages:\n \x1b[90mimport { ... } from "@/components/ui/...";\x1b[0m\n');
779
842
  }
780
843
 
844
+ function runValidateAndZip(targetDirInput, extraArgs = []) {
845
+ if (targetDirInput === '--help' || targetDirInput === '-h' || extraArgs.includes('--help') || extraArgs.includes('-h')) {
846
+ console.log(`\nUsage: deneb validate-and-zip [template-directory] [options]
847
+ (or deneb validate --zip [template-directory])
848
+ (or deneb validate and zip [template-directory])
849
+
850
+ Runs Fivora preflight verification against manifest contracts, static export fixtures,
851
+ and visual editing markers. If and only if all checks pass, packages a clean upload-ready
852
+ fivora-template.zip (excluding node_modules, .next, .git, .env*).
853
+
854
+ Options:
855
+ --skip-install Reuse existing dependencies in working directory (diagnostic only)
856
+ --skip-build Inspect existing build output directory in place (diagnostic only)
857
+ --json Output validation report as JSON\n`);
858
+ process.exit(0);
859
+ }
860
+
861
+ const targetDir = path.resolve(targetDirInput || '.');
862
+ const outputZip = path.resolve(targetDir, 'fivora-template.zip');
863
+
864
+ console.log('\n' + createBox([
865
+ '\x1b[1m\x1b[37mDENEB VALIDATE & ZIP PREFLIGHT\x1b[0m',
866
+ '\x1b[90m1. Validate website configuration with Fivora platform\x1b[0m',
867
+ '\x1b[90m2. Package clean upload-ready ZIP if 100% compliant\x1b[0m'
868
+ ], 58) + '\n');
869
+
870
+ console.log(`[Step 1/2] Running Fivora preflight validation in ${targetDir}...\n`);
871
+
872
+ const validatorScript = path.join(toolsDir, 'fivora-template-validator.cjs');
873
+ const valRes = spawnSync(process.execPath, [validatorScript, 'validate', targetDir, ...extraArgs], {
874
+ stdio: 'inherit',
875
+ });
876
+
877
+ if (valRes.status !== 0) {
878
+ console.error(`\n\x1b[31m✖ Validation failed with exit code ${valRes.status}.\x1b[0m`);
879
+ console.error(`\x1b[33mRefusing to package ZIP: template does not meet Fivora platform requirements.\x1b[0m`);
880
+ console.error(`Fix the validation errors above and re-run "deneb validate-and-zip".\n`);
881
+ process.exit(valRes.status ?? 1);
882
+ }
883
+
884
+ console.log(`\n[Step 2/2] Validation PASSED! Creating clean upload-ready ZIP...\n`);
885
+ packageCleanZip(targetDir, outputZip);
886
+
887
+ console.log('\n' + createBox([
888
+ '\x1b[1m\x1b[32m✔ PREFLIGHT VALIDATION & PACKAGING SUCCESSFUL!\x1b[0m',
889
+ `\x1b[37mOutput:\x1b[0m ${path.basename(outputZip)}`,
890
+ '\x1b[90mReady for 1-click upload at Fivora Developer Portal\x1b[0m'
891
+ ], 58) + '\n');
892
+ }
893
+
894
+ function runDoctor(targetDirInput) {
895
+ const targetDir = path.resolve(targetDirInput || '.');
896
+
897
+ console.log('\n' + createBox([
898
+ '\x1b[1m\x1b[36m🩺 DENEB SYSTEM & TEMPLATE DOCTOR\x1b[0m',
899
+ '\x1b[90mComprehensive diagnostic analysis for Fivora & Next.js\x1b[0m',
900
+ `\x1b[37mTarget:\x1b[0m ${targetDir}`
901
+ ], 60) + '\n');
902
+
903
+ let passed = 0;
904
+ let warnings = 0;
905
+ let errors = 0;
906
+
907
+ function report(type, title, detail) {
908
+ if (type === 'pass') {
909
+ passed++;
910
+ console.log(` \x1b[32m✔\x1b[0m \x1b[1m${title}\x1b[0m${detail ? ` \x1b[90m(${detail})\x1b[0m` : ''}`);
911
+ } else if (type === 'warn') {
912
+ warnings++;
913
+ console.log(` \x1b[33m⚠\x1b[0m \x1b[33m${title}\x1b[0m${detail ? ` \x1b[90m- ${detail}\x1b[0m` : ''}`);
914
+ } else {
915
+ errors++;
916
+ console.log(` \x1b[31m✖\x1b[0m \x1b[31m${title}\x1b[0m${detail ? ` \x1b[90m- ${detail}\x1b[0m` : ''}`);
917
+ }
918
+ }
919
+
920
+ console.log('\x1b[1m[1/6] System & Runtime Environment:\x1b[0m');
921
+ const nodeVersion = process.version;
922
+ const majorNode = parseInt(nodeVersion.replace(/^v/, '').split('.')[0], 10);
923
+ if (majorNode >= 18) {
924
+ report('pass', 'Node.js Runtime', `${nodeVersion} (Supported)`);
925
+ } else {
926
+ report('err', 'Node.js Runtime', `${nodeVersion} (Requires Node.js >= 18.0.0)`);
927
+ }
928
+
929
+ const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
930
+ const npmCheck = spawnSync(npmBin, ['--version'], { encoding: 'utf-8', shell: process.platform === 'win32' });
931
+ if (!npmCheck.error && npmCheck.status === 0) {
932
+ report('pass', 'Package Manager', `npm v${npmCheck.stdout.trim()}`);
933
+ } else {
934
+ report('warn', 'Package Manager', 'npm not found in system PATH');
935
+ }
936
+
937
+ console.log('\n\x1b[1m[2/6] Project Package Configuration:\x1b[0m');
938
+ const pkgPath = path.join(targetDir, 'package.json');
939
+ let pkg = null;
940
+ if (fs.existsSync(pkgPath)) {
941
+ try {
942
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
943
+ report('pass', 'package.json', `Found "${pkg.name || 'unnamed'}"`);
944
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
945
+
946
+ if (allDeps['next']) {
947
+ report('pass', 'Next.js Framework', allDeps['next']);
948
+ } else {
949
+ report('err', 'Next.js Framework', 'next dependency missing in package.json');
950
+ }
951
+
952
+ if (allDeps['@deneb-ui/ui']) {
953
+ report('pass', '@deneb-ui/ui Library', allDeps['@deneb-ui/ui']);
954
+ } else {
955
+ report('warn', '@deneb-ui/ui Library', 'Not installed (run "npm i @deneb-ui/ui")');
956
+ }
957
+
958
+ if (allDeps['@deneb-ui/cli']) {
959
+ report('pass', '@deneb-ui/cli Tooling', allDeps['@deneb-ui/cli']);
960
+ } else {
961
+ report('warn', '@deneb-ui/cli Tooling', 'Recommended for local CLI scripts');
962
+ }
963
+ } catch (e) {
964
+ report('err', 'package.json Syntax', e.message);
965
+ }
966
+ } else {
967
+ report('err', 'package.json', `Not found at ${pkgPath}`);
968
+ }
969
+
970
+ console.log('\n\x1b[1m[3/6] Static Export Configuration:\x1b[0m');
971
+ const nextConfigTs = path.join(targetDir, 'next.config.ts');
972
+ const nextConfigMjs = path.join(targetDir, 'next.config.mjs');
973
+ const nextConfigJs = path.join(targetDir, 'next.config.js');
974
+ let nextConfigFile = [nextConfigTs, nextConfigMjs, nextConfigJs].find((p) => fs.existsSync(p));
975
+
976
+ if (nextConfigFile) {
977
+ const content = fs.readFileSync(nextConfigFile, 'utf-8');
978
+ if (content.includes("output: 'export'") || content.includes('output: "export"')) {
979
+ report('pass', 'Next.js Static Export', `output: 'export' verified in ${path.basename(nextConfigFile)}`);
980
+ } else {
981
+ report('err', 'Next.js Static Export', `Missing output: 'export' in ${path.basename(nextConfigFile)} (Required by Fivora)`);
982
+ }
983
+ } else {
984
+ report('err', 'Next.js Config', 'No next.config.ts, next.config.mjs, or next.config.js found');
985
+ }
986
+
987
+ console.log('\n\x1b[1m[4/6] Fivora Manifest v2 Contract:\x1b[0m');
988
+ const manifestPath = path.join(targetDir, 'fivora-template.json');
989
+ let manifestData = null;
990
+ if (fs.existsSync(manifestPath)) {
991
+ try {
992
+ manifestData = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
993
+ report('pass', 'fivora-template.json', `Valid JSON (strict=${manifestData.strict !== false})`);
994
+
995
+ if (manifestData.version === 2 || manifestData.version === '2') {
996
+ report('pass', 'Manifest Version', 'Version 2 (Current standard)');
997
+ } else {
998
+ report('warn', 'Manifest Version', `Version ${manifestData.version} detected (Recommend version 2)`);
999
+ }
1000
+
1001
+ const hasHome = Array.isArray(manifestData.pages) && manifestData.pages.some((p) =>
1002
+ p.route === '/' || p.slug === '/' || p.path === '/' || p.id === 'home'
1003
+ );
1004
+ if (hasHome) {
1005
+ report('pass', 'Home Page Entry', 'Home page ("/") declared in manifest');
1006
+ } else {
1007
+ report('err', 'Home Page Entry', 'Manifest pages array missing root slug or route: "/"');
1008
+ }
1009
+
1010
+ if (manifestData.theme && (manifestData.theme.primary || manifestData.theme.accent)) {
1011
+ report('pass', 'Theme Configuration', 'Primary and accent color tokens declared');
1012
+ } else {
1013
+ // Will check site-data.json below as alternative
1014
+ }
1015
+ } catch (e) {
1016
+ report('err', 'fivora-template.json Syntax', e.message);
1017
+ }
1018
+ } else {
1019
+ report('err', 'fivora-template.json', 'File not found. Run "deneb init" to generate it');
1020
+ }
1021
+
1022
+ console.log('\n\x1b[1m[5/6] Reactive Site Data & Visual Editing:\x1b[0m');
1023
+ const siteDataPath = path.join(targetDir, 'src', 'data', 'site-data.json');
1024
+ if (fs.existsSync(siteDataPath)) {
1025
+ try {
1026
+ const siteData = JSON.parse(fs.readFileSync(siteDataPath, 'utf-8'));
1027
+ report('pass', 'site-data.json', 'src/data/site-data.json exists & valid');
1028
+ const merchantName = (siteData.merchant && (siteData.merchant.businessName || siteData.merchant.name)) || null;
1029
+ if (merchantName) {
1030
+ report('pass', 'Merchant Metadata', `Name: "${merchantName}"`);
1031
+ } else {
1032
+ report('warn', 'Merchant Metadata', 'Missing merchant name in site-data.json');
1033
+ }
1034
+
1035
+ const themeTokens = manifestData?.theme || siteData?.template?.structure?.theme;
1036
+ if (themeTokens && (themeTokens.primaryColor || themeTokens.primary || themeTokens.accentColor || themeTokens.accent)) {
1037
+ report('pass', 'Theme Design Tokens', 'Theme color tokens declared');
1038
+ } else {
1039
+ report('warn', 'Theme Design Tokens', 'No theme color tokens found in manifest or site-data.json');
1040
+ }
1041
+
1042
+ if (siteData.content) {
1043
+ report('pass', 'Visual Content Bindings', 'Content section ready for live sync');
1044
+ } else {
1045
+ report('warn', 'Visual Content Bindings', 'Missing content section in site-data.json');
1046
+ }
1047
+ } catch (e) {
1048
+ report('err', 'site-data.json Syntax', e.message);
1049
+ }
1050
+ } else {
1051
+ report('warn', 'site-data.json', 'src/data/site-data.json not found. Recommended for Fivora live editor');
1052
+ }
1053
+
1054
+ console.log('\n\x1b[1m[6/6] Cleanliness & Security Check:\x1b[0m');
1055
+ const envFiles = ['.env', '.env.local', '.env.production', '.env.development'];
1056
+ const foundEnv = envFiles.filter((f) => fs.existsSync(path.join(targetDir, f)));
1057
+ if (foundEnv.length === 0) {
1058
+ report('pass', 'Secrets Isolation', 'No raw .env files detected in root directory');
1059
+ } else {
1060
+ report('warn', 'Secrets Isolation', `Active env files: ${foundEnv.join(', ')} (Excluded during packaging)`);
1061
+ }
1062
+
1063
+ const previewExists = fs.existsSync(path.join(targetDir, 'preview.png')) ||
1064
+ fs.existsSync(path.join(targetDir, 'thumbnail.png')) ||
1065
+ fs.existsSync(path.join(targetDir, 'public', 'fivora-logo.png'));
1066
+ if (previewExists) {
1067
+ report('pass', 'Storefront Assets', 'Brand/preview graphics verified');
1068
+ } else {
1069
+ report('warn', 'Storefront Assets', 'preview.png not found in template root');
1070
+ }
1071
+
1072
+ // Summary
1073
+ console.log('\n' + createBox([
1074
+ '\x1b[1mDOCTOR DIAGNOSTIC SUMMARY\x1b[0m',
1075
+ `\x1b[32m✔ Passed:\x1b[0m ${passed}`,
1076
+ `\x1b[33m⚠ Warnings:\x1b[0m ${warnings}`,
1077
+ `\x1b[31m✖ Errors:\x1b[0m ${errors}`,
1078
+ errors === 0
1079
+ ? '\x1b[32mStatus: HEALTHY — Ready for Fivora packaging & build!\x1b[0m'
1080
+ : '\x1b[31mStatus: ATTENTION REQUIRED — Fix errors before deployment\x1b[0m'
1081
+ ], 58) + '\n');
1082
+
1083
+ if (errors > 0) {
1084
+ process.exit(1);
1085
+ }
1086
+ }
1087
+
1088
+ // Normalize multi-word "validate and zip" or "validate & zip"
1089
+ let command = args[0];
1090
+ let commandArgs = args.slice(1);
1091
+
1092
+ if (command === 'validate' && (commandArgs[0] === 'and' || commandArgs[0] === '&') && commandArgs[1] === 'zip') {
1093
+ command = 'validate-and-zip';
1094
+ commandArgs = commandArgs.slice(2);
1095
+ }
1096
+
781
1097
  if (command === 'init') {
782
- initProject(args[1]);
1098
+ initProject(commandArgs[0]);
783
1099
  } else if (command === 'create') {
784
- createTemplate(args[1]);
1100
+ createTemplate(commandArgs[0]);
785
1101
  } else if (command === 'add') {
786
- addComponent(args[1], args[2]);
1102
+ addComponent(commandArgs[0], commandArgs[1]);
1103
+ } else if (command === 'doctor' || command === 'check') {
1104
+ runDoctor(commandArgs[0]);
787
1105
  } else if (command === 'lab') {
788
1106
  const script = path.join(toolsDir, 'local-template-lab.cjs');
789
- const res = spawnSync(process.execPath, [script, ...args.slice(1)], { stdio: 'inherit' });
1107
+ const res = spawnSync(process.execPath, [script, ...commandArgs], { stdio: 'inherit' });
790
1108
  process.exit(res.status ?? 0);
791
1109
  } else if (command === 'validate') {
792
- const script = path.join(toolsDir, 'fivora-template-validator.cjs');
793
- const res = spawnSync(process.execPath, [script, 'validate', ...args.slice(1)], { stdio: 'inherit' });
794
- process.exit(res.status ?? 0);
1110
+ if (commandArgs.includes('--zip') || commandArgs.includes('-z')) {
1111
+ const cleanArgs = commandArgs.filter((a) => a !== '--zip' && a !== '-z');
1112
+ runValidateAndZip(cleanArgs[0] || '.', cleanArgs.slice(1));
1113
+ } else {
1114
+ const script = path.join(toolsDir, 'fivora-template-validator.cjs');
1115
+ const res = spawnSync(process.execPath, [script, 'validate', ...commandArgs], { stdio: 'inherit' });
1116
+ process.exit(res.status ?? 0);
1117
+ }
795
1118
  } else if (command === 'pack' || command === 'zip') {
796
- const targetDir = path.resolve(args[1] || '.');
1119
+ const targetDir = path.resolve(commandArgs[0] || '.');
797
1120
  const outputZip = path.resolve(targetDir, 'fivora-template.zip');
798
1121
  packageCleanZip(targetDir, outputZip);
1122
+ } else if (command === 'validate-and-zip' || command === 'validate-zip') {
1123
+ runValidateAndZip(commandArgs[0] || '.', commandArgs.slice(1));
799
1124
  } else if (command === 'update' || command === 'upgrade') {
800
- updateDependencies(args.slice(1));
1125
+ updateDependencies(commandArgs);
801
1126
  } else if (command === 'package') {
802
1127
  const script = path.join(toolsDir, 'fivora-template-validator.cjs');
803
- const res = spawnSync(process.execPath, [script, 'package', ...args.slice(1)], { stdio: 'inherit' });
1128
+ const res = spawnSync(process.execPath, [script, 'package', ...commandArgs], { stdio: 'inherit' });
804
1129
  process.exit(res.status ?? 0);
805
1130
  } else {
806
- console.log(`Usage: deneb <command> [options] (or fivora <command> [options])
1131
+ console.log(`Usage: deneb <command> [options]
807
1132
  DENEB UI Framework — Powered by DENEB-UI Collaborate with FIVORA
808
1133
 
809
- Commands:
810
- add Add a DENEB UI component to src/components/ui/ (e.g. deneb add product-card)
811
- init Configure an existing Next.js project with missing Fivora files & scripts
812
- create Scaffold a new storefront template (e.g. deneb create my-store)
813
- lab Start the local visual editing lab
814
- validate Validate template visual editing contracts and empty states
815
- update Update DENEB & Fivora packages to latest
816
- pack Quick-package a clean, upload-ready ZIP without junk files (node_modules, .next, .git, .env)
817
- zip Alias for pack
818
- package Run full preflight verification and generate upload ZIP`);
1134
+ Core Commands:
1135
+ init Auto-convert & configure existing Next.js (shadcn/HeroUI/Tailwind) into editable Fivora template
1136
+ update Update DENEB packages (@deneb-ui/ui, @deneb-ui/cli) and UI components
1137
+ validate Validate website configuration and visual editing contracts with Fivora platform
1138
+ doctor Run comprehensive environment, manifest & asset diagnostic checks
1139
+ zip Zip the project without unnecessary folders or files (node_modules, .next, .git, .env)
1140
+ validate-and-zip Validate website configuration and immediately package clean upload-ready ZIP
1141
+
1142
+ Development & Scaffolding:
1143
+ add <component> Add or update a DENEB UI component in src/components/ui/ (e.g. deneb add product-card)
1144
+ create <name> Scaffold a new storefront template (e.g. deneb create my-store)
1145
+ lab Start the local visual editing lab simulation
1146
+ pack Alias for zip
1147
+ package Run strict sandbox preflight verification and generate upload ZIP
1148
+
1149
+ Examples:
1150
+ deneb doctor
1151
+ deneb init
1152
+ deneb update
1153
+ deneb validate .
1154
+ deneb validate --zip
1155
+ deneb validate-and-zip
1156
+ deneb zip .
1157
+ deneb add product-card
1158
+ deneb add all`);
819
1159
  process.exit(1);
820
1160
  }
1161
+