@deneb-ui/cli 2.0.6 → 2.0.8

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 (3) hide show
  1. package/README.md +57 -23
  2. package/bin/index.js +582 -230
  3. package/package.json +10 -3
package/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  > **DENEB UI — Build beautiful interfaces, effortlessly.**
6
6
  > The premier visual-first React component ecosystem.
7
- > Created by **Chamika Gayashan & Induranga Kawishwara**.
7
+ > Proudly presented by **DENEB-UI Collaborate with FIVORA**.
8
8
 
9
- [![Framework: DENEB UI](https://img.shields.io/badge/Framework-DENEB_UI_v2.0-blue.svg)](https://github.com/chamikathereal)
9
+ [![Framework: DENEB UI](https://img.shields.io/badge/Framework-DENEB_UI_v2.0-blue.svg)](https://github.com/deneb-ui/ui)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT)
11
11
 
12
12
  ---
@@ -50,50 +50,84 @@ npx @deneb-ui/cli add dialog
50
50
  npx @deneb-ui/cli add all
51
51
  ```
52
52
 
53
- ### 2. `deneb init` (Configure an Existing Project)
54
- Converts any **existing Next.js project** into a compliant DENEB storefront template:
53
+ ### 1. `deneb init` (Initialize Existing Project)
54
+ Converts and configures any **ongoing Next.js project** for the Fivora platform:
55
+ - Generates `fivora-template.json` (Version 2 contract with strict visual editing)
56
+ - Creates `src/data/site-data.json` with merchant defaults
57
+ - Injects npm scripts (`lab`, `validate`, `zip`, `validate-and-zip`, `update:deneb`)
58
+ - Installs `@deneb-ui/ui` and `@deneb-ui/cli`
55
59
 
56
60
  ```bash
57
61
  npx @deneb-ui/cli init
62
+ # Or: deneb init
58
63
  ```
59
64
 
60
- ### 3. `deneb create <project-name>` (Start from Scratch)
61
- Scaffolds a brand-new Next.js App Router storefront with Tailwind CSS and `@deneb-ui/ui`:
65
+ ### 2. `deneb update` (Update Packages & DENEB Components)
66
+ Keeps your project up to date by updating both npm packages and installed DENEB components:
67
+ - Updates `@deneb-ui/ui` and `@deneb-ui/cli` to the latest releases
68
+ - Automatically scans `src/components/ui/` and synchronizes existing DENEB UI components with the latest definitions
62
69
 
63
70
  ```bash
64
- npx @deneb-ui/cli create my-store
65
- ```
66
-
67
- ### 4. `deneb lab` (Local Visual Editing Lab)
68
- Launches the interactive visual editing test lab:
69
-
70
- ```bash
71
- npm run lab
72
- # Or: deneb lab .
71
+ npm run update:deneb
72
+ # Or: deneb update
73
73
  ```
74
74
 
75
- ### 5. `deneb validate` (Preflight Contract Validator)
76
- Runs preflight checks to guarantee 100% compliance with visual editing and platform rules:
75
+ ### 3. `deneb validate` (Preflight Contract Validator)
76
+ Runs Fivora preflight verification against manifest contracts, static export fixtures, and visual editing markers (`data-fivora-path`, `data-fivora-page`):
77
77
 
78
78
  ```bash
79
79
  npm run validate
80
80
  # Or: deneb validate .
81
+ # Or validate and immediately bundle on success:
82
+ deneb validate --zip
81
83
  ```
82
84
 
83
- ### 6. `deneb zip` / `deneb pack` (Clean Package Generator)
84
- Creates a clean, production-ready template ZIP excluding `node_modules`, `.next`, `.git`, `.env*`:
85
+ ### 4. `deneb zip` / `deneb pack` (Clean Package Generator)
86
+ Creates a clean, upload-ready `fivora-template.zip` excluding unnecessary directories and secret files (`node_modules`, `.next`, `.git`, `.env*`, `.cache`, `.turbo`, logs):
85
87
 
86
88
  ```bash
87
89
  npm run zip
88
90
  # Or: deneb zip .
89
91
  ```
90
92
 
91
- ### 7. `deneb update` (Dependency Updater)
92
- Updates `@deneb-ui/ui` and `@deneb-ui/cli` to the latest versions:
93
+ ### 5. `deneb validate-and-zip` (One-Step Preflight Check & Clean ZIP)
94
+ The safest, recommended command for releasing templates. Runs full preflight validation; if and only if all platform checks pass 100%, it bundles a clean `fivora-template.zip`:
93
95
 
94
96
  ```bash
95
- npm run update:deneb
96
- # Or: deneb update
97
+ npm run validate-and-zip
98
+ # Or: deneb validate-and-zip .
99
+ # Or: deneb validate and zip
100
+ ```
101
+
102
+ ### 6. `deneb add <component>` (Component Registry)
103
+ Add or update production-ready DENEB UI components into `src/components/ui/`:
104
+
105
+ ```bash
106
+ # List available components:
107
+ npx @deneb-ui/cli add list
108
+
109
+ # Add specific components:
110
+ npx @deneb-ui/cli add product-card
111
+ npx @deneb-ui/cli add contact-actions
112
+ npx @deneb-ui/cli add location-card
113
+ npx @deneb-ui/cli add whatsapp-button
114
+ npx @deneb-ui/cli add dialog
115
+ npx @deneb-ui/cli add all
116
+ ```
117
+
118
+ ### 7. `deneb create <project-name>` (Start from Scratch)
119
+ Scaffolds a brand-new Next.js App Router storefront pre-configured with Tailwind CSS and `@deneb-ui/ui`:
120
+
121
+ ```bash
122
+ npx @deneb-ui/cli create my-store
123
+ ```
124
+
125
+ ### 8. `deneb lab` (Local Visual Editing Lab)
126
+ Launches the interactive visual editing test lab simulating Fivora editor messages:
127
+
128
+ ```bash
129
+ npm run lab
130
+ # Or: deneb lab .
97
131
  ```
98
132
 
99
133
  ---
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
 
@@ -36,6 +35,27 @@ function isForbiddenFile(filename) {
36
35
  );
37
36
  }
38
37
 
38
+ function stripAnsi(str) {
39
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
40
+ }
41
+
42
+ function createBox(lines, width = 55) {
43
+ const cyan = '\x1b[36m';
44
+ const reset = '\x1b[0m';
45
+ const top = ` ${cyan}╔${'═'.repeat(width)}╗${reset}`;
46
+ const bottom = ` ${cyan}╚${'═'.repeat(width)}╝${reset}`;
47
+
48
+ const rows = lines.map((line) => {
49
+ const rawLen = stripAnsi(line).length;
50
+ const padTotal = Math.max(0, width - rawLen);
51
+ const padLeft = Math.floor(padTotal / 2);
52
+ const padRight = padTotal - padLeft;
53
+ return ` ${cyan}║${reset}${' '.repeat(padLeft)}${line}${' '.repeat(padRight)}${cyan}║${reset}`;
54
+ });
55
+
56
+ return [top, ...rows, bottom].join('\n');
57
+ }
58
+
39
59
  function packageCleanZip(sourceDir, outputPath) {
40
60
  let AdmZip;
41
61
  try {
@@ -133,8 +153,8 @@ function createTemplate(targetName) {
133
153
  console.log(` npm run validate # Run Fivora preflight checks`);
134
154
  console.log(` npm run zip # Create clean upload-ready ZIP\n`);
135
155
  } else {
136
- console.log(`\n🚀 Scaffolding new Fivora Template via @fivora/create-template...\n`);
137
- 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], {
138
158
  stdio: 'inherit',
139
159
  shell: process.platform === 'win32',
140
160
  });
@@ -336,19 +356,170 @@ function getDefaultSiteData(projectName, pages) {
336
356
  };
337
357
  }
338
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
+
339
509
  function initProject(targetInput) {
340
510
  const targetDir = path.resolve(process.cwd(), targetInput || '.');
341
511
  const pkgPath = path.join(targetDir, 'package.json');
342
512
 
343
- console.log(`\n\x1b[36m╔═══════════════════════════════════════════════════════╗\x1b[0m`);
344
- console.log(`\x1b[36m║ \x1b[1mFIVORA TEMPLATE INITIALIZER\x1b[0m\x1b[36m ║\x1b[0m`);
345
- console.log(`\x1b[36m║ \x1b[90mConfigure existing Next.js app for Fivora\x1b[0m\x1b[36m ║\x1b[0m`);
346
- console.log(`\x1b[36m╚═══════════════════════════════════════════════════════╝\x1b[0m\n`);
513
+ console.log('\n' + createBox([
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');
347
518
 
348
519
  if (!fs.existsSync(pkgPath)) {
349
520
  console.error(`\x1b[31mError:\x1b[0m No package.json found in "${targetDir}".`);
350
- console.error(`\nPlease run 'fivora init' inside your Next.js project root, or scaffold a new project with:`);
351
- 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`);
352
523
  process.exit(1);
353
524
  }
354
525
 
@@ -375,19 +546,14 @@ function initProject(targetInput) {
375
546
  // 1. Scan / Detect Pages
376
547
  const detectedPages = detectPages(targetDir);
377
548
 
378
- // 2. Generate fivora-template.json (or keep fivora-template.json if present)
379
- const fivoraManifestPath = path.join(targetDir, 'fivora-template.json');
380
- const legacyManifestPath = path.join(targetDir, 'fivora-template.json');
381
- const manifestPath = fs.existsSync(legacyManifestPath) && !fs.existsSync(fivoraManifestPath)
382
- ? legacyManifestPath
383
- : fivoraManifestPath;
384
-
549
+ // 2. Generate fivora-template.json (version 2 contract)
550
+ const manifestPath = path.join(targetDir, 'fivora-template.json');
385
551
  if (!fs.existsSync(manifestPath)) {
386
552
  const manifest = getDefaultManifest(projectName, detectedPages);
387
553
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
388
554
  console.log(`\x1b[32m✔ Created\x1b[0m fivora-template.json (version 2, strict visual editing contract)`);
389
555
  } else {
390
- console.log(`\x1b[90m⏩ Kept existing\x1b[0m ${path.basename(manifestPath)}`);
556
+ console.log(`\x1b[90m⏩ Kept existing\x1b[0m fivora-template.json`);
391
557
  }
392
558
 
393
559
  // 3. Generate siteDataFile
@@ -412,24 +578,25 @@ function initProject(targetInput) {
412
578
  // 4. Update package.json scripts
413
579
  pkg.scripts = pkg.scripts || {};
414
580
  const scriptsToAdd = {
415
- 'lab': 'fivora lab .',
416
- 'validate': 'fivora validate .',
417
- 'zip': 'fivora zip .',
418
- 'package:template': 'fivora package .',
419
- 'update:fivora': 'fivora update',
581
+ 'lab': 'deneb lab .',
582
+ 'validate': 'deneb validate .',
583
+ 'zip': 'deneb zip .',
584
+ 'validate-and-zip': 'deneb validate-and-zip .',
585
+ 'package:template': 'deneb package .',
586
+ 'update:deneb': 'deneb update',
420
587
  };
421
588
  let addedCount = 0;
422
589
  for (const [key, val] of Object.entries(scriptsToAdd)) {
423
- if (!pkg.scripts[key]) {
590
+ if (!pkg.scripts[key] || pkg.scripts[key].startsWith('fivora ')) {
424
591
  pkg.scripts[key] = val;
425
592
  addedCount++;
426
593
  }
427
594
  }
428
595
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
429
596
  if (addedCount > 0) {
430
- console.log(`\x1b[32m✔ Injected\x1b[0m Fivora scripts into package.json (lab, validate, zip, package:template, update:fivora)`);
597
+ console.log(`\x1b[32m✔ Configured\x1b[0m DENEB scripts in package.json (lab, validate, zip, validate-and-zip, package:template, update:deneb)`);
431
598
  } else {
432
- console.log(`\x1b[90m⏩ Fivora scripts already present\x1b[0m in package.json`);
599
+ console.log(`\x1b[90m⏩ DENEB scripts already present\x1b[0m in package.json`);
433
600
  }
434
601
 
435
602
  // 5. Check next.config for static export
@@ -453,47 +620,53 @@ function initProject(targetInput) {
453
620
  console.log(` \x1b[90mconst nextConfig = { output: 'export' };\x1b[0m`);
454
621
  }
455
622
 
456
- // 6. Install Fivora packages if missing
623
+ // 6. Install DENEB packages if missing
457
624
  const skipInstall = process.argv.includes('--skip-install');
458
- const hasEditable = Boolean(
459
- (pkg.dependencies && pkg.dependencies['@fivora/editable-components']) ||
460
- (pkg.devDependencies && pkg.devDependencies['@fivora/editable-components'])
625
+ const hasUi = Boolean(
626
+ (pkg.dependencies && (pkg.dependencies['@deneb-ui/ui'] || pkg.dependencies['@deneb/ui'] || pkg.dependencies['@fivora/editable-components'])) ||
627
+ (pkg.devDependencies && (pkg.devDependencies['@deneb-ui/ui'] || pkg.devDependencies['@deneb/ui'] || pkg.devDependencies['@fivora/editable-components']))
461
628
  );
462
629
  const hasCli = Boolean(
463
- (pkg.devDependencies && pkg.devDependencies['@fivora/cli']) ||
464
- (pkg.dependencies && pkg.dependencies['@fivora/cli'])
630
+ (pkg.devDependencies && (pkg.devDependencies['@deneb-ui/cli'] || pkg.devDependencies['@fivora/cli'])) ||
631
+ (pkg.dependencies && (pkg.dependencies['@deneb-ui/cli'] || pkg.dependencies['@fivora/cli']))
465
632
  );
466
633
 
467
- if (!skipInstall && (!hasEditable || !hasCli)) {
468
- const toInstall = [];
469
- if (!hasEditable) toInstall.push('@fivora/editable-components');
470
- if (!hasCli) toInstall.push('@fivora/cli');
634
+ if (!skipInstall && (!hasUi || !hasCli)) {
635
+ const depsToInstall = [];
636
+ const devDepsToInstall = [];
637
+ if (!hasUi) depsToInstall.push('@deneb-ui/ui@latest');
638
+ if (!hasCli) devDepsToInstall.push('@deneb-ui/cli@latest');
471
639
 
472
- console.log(`\n📦 Installing ${toInstall.join(' and ')}...`);
473
- const installRes = spawnSync('npm', ['install', ...toInstall], {
474
- cwd: targetDir,
475
- stdio: 'inherit',
476
- shell: process.platform === 'win32',
477
- });
478
- if (installRes.status === 0) {
479
- console.log(`\x1b[32m✔ Packages installed successfully!\x1b[0m`);
480
- } else {
481
- console.log(`\x1b[33mNote: npm install exited with code ${installRes.status}. You can run 'npm i ${toInstall.join(' ')}' manually.\x1b[0m`);
640
+ if (depsToInstall.length > 0) {
641
+ console.log(`\n📦 Installing ${depsToInstall.join(' ')}...`);
642
+ spawnSync('npm', ['install', ...depsToInstall], {
643
+ cwd: targetDir,
644
+ stdio: 'inherit',
645
+ shell: process.platform === 'win32',
646
+ });
647
+ }
648
+ if (devDepsToInstall.length > 0) {
649
+ console.log(`\n📦 Installing (dev) ${devDepsToInstall.join(' ')}...`);
650
+ spawnSync('npm', ['install', '-D', ...devDepsToInstall], {
651
+ cwd: targetDir,
652
+ stdio: 'inherit',
653
+ shell: process.platform === 'win32',
654
+ });
482
655
  }
483
656
  }
484
657
 
485
- console.log(`\n\x1b[32m✔ Template initialization complete!\x1b[0m`);
658
+ console.log(`\n\x1b[32m✔ Project initialization complete!\x1b[0m`);
486
659
  console.log(`\nYou can now run:`);
487
- console.log(` \x1b[36mnpm run lab\x1b[0m \x1b[90m# Launch Local Visual Editing Lab\x1b[0m`);
488
- console.log(` \x1b[36mnpm run validate\x1b[0m \x1b[90m# Check compliance with Fivora contract\x1b[0m`);
489
- console.log(` \x1b[36mnpm run zip\x1b[0m \x1b[90m# Package clean ZIP for 1-click upload\x1b[0m\n`);
660
+ console.log(` \x1b[36mnpm run lab\x1b[0m \x1b[90m# Launch Local Visual Editing Lab\x1b[0m`);
661
+ console.log(` \x1b[36mnpm run validate\x1b[0m \x1b[90m# Check compliance with Fivora contract\x1b[0m`);
662
+ console.log(` \x1b[36mnpm run zip\x1b[0m \x1b[90m# Package clean ZIP for 1-click upload\x1b[0m`);
663
+ console.log(` \x1b[36mnpm run validate-and-zip\x1b[0m \x1b[90m# Validate preflight and bundle clean ZIP in 1 step\x1b[0m\n`);
490
664
  }
491
665
 
492
-
493
666
  function updateDependencies(cmdArgs = []) {
494
667
  let targetDir = '.';
495
668
  let isLocal = false;
496
- let localPath = '/mnt/GAMES/GitHub/fivora_package';
669
+ let localPath = path.resolve(__dirname, '..', '..', '..');
497
670
 
498
671
  for (let i = 0; i < cmdArgs.length; i++) {
499
672
  const arg = cmdArgs[i];
@@ -526,53 +699,80 @@ function updateDependencies(cmdArgs = []) {
526
699
  process.exit(1);
527
700
  }
528
701
 
529
- console.log('\n\x1b[36m╔═══════════════════════════════════════════════════════╗\x1b[0m');
530
- console.log('\x1b[36m║ \x1b[1mFIVORA PACKAGE UPDATER\x1b[0m\x1b[36m ║\x1b[0m');
531
- console.log('\x1b[36m╚═══════════════════════════════════════════════════════╝\x1b[0m\n');
702
+ console.log('\n' + createBox([
703
+ '\x1b[1m\x1b[37mDENEB PACKAGE & COMPONENT UPDATER\x1b[0m',
704
+ '\x1b[90mUpdate DENEB packages and UI components to latest\x1b[0m',
705
+ '\x1b[90mPowered by DENEB-UI Collaborate with FIVORA\x1b[0m'
706
+ ], 58) + '\n');
532
707
 
533
- const currentEditable = String(pkg.dependencies?.['@fivora/editable-components'] || pkg.devDependencies?.['@fivora/editable-components'] || '');
534
- const currentCli = String(pkg.dependencies?.['@fivora/cli'] || pkg.devDependencies?.['@fivora/cli'] || '');
535
- if (!isLocal && (currentEditable.startsWith('file:') || currentCli.startsWith('file:'))) {
536
- isLocal = true;
708
+ let importPkg = '@deneb-ui/ui';
709
+ if (pkg.dependencies?.['@deneb-ui/ui'] || pkg.devDependencies?.['@deneb-ui/ui']) {
710
+ importPkg = '@deneb-ui/ui';
711
+ } else if (pkg.dependencies?.['@deneb/ui'] || pkg.devDependencies?.['@deneb/ui']) {
712
+ importPkg = '@deneb/ui';
537
713
  }
538
714
 
715
+ // 1. Update npm packages
539
716
  if (isLocal) {
540
- const editableDir = path.join(localPath, 'packages', 'editable-components');
717
+ const uiDir = path.join(localPath, 'packages', 'deneb-ui');
541
718
  const cliDir = path.join(localPath, 'cli', 'fivora-cli');
542
719
 
543
- if (fs.existsSync(editableDir) && fs.existsSync(cliDir)) {
544
- console.log('🔄 Updating from local Fivora workspace:');
545
- console.log(' - ' + editableDir);
720
+ if (fs.existsSync(uiDir) && fs.existsSync(cliDir)) {
721
+ console.log('🔄 Updating from local workspace:');
722
+ console.log(' - ' + uiDir);
546
723
  console.log(' - ' + cliDir + '\n');
547
724
 
548
- const installRes = spawnSync('npm', ['install', editableDir, cliDir], {
725
+ const installRes = spawnSync('npm', ['install', uiDir, cliDir], {
549
726
  cwd: targetDir,
550
727
  stdio: 'inherit',
551
728
  shell: process.platform === 'win32',
552
729
  });
553
730
 
554
731
  if (installRes.status === 0) {
555
- console.log('\n\x1b[32m✔ Local Fivora packages re-linked & updated successfully!\x1b[0m\n');
732
+ console.log('\n\x1b[32m✔ Local DENEB packages re-linked & updated successfully!\x1b[0m\n');
556
733
  } else {
557
734
  console.log('\n\x1b[31m✖ Failed to link local packages (exit code: ' + installRes.status + ')\x1b[0m\n');
558
735
  }
559
- return;
560
736
  }
561
- }
737
+ } else {
738
+ console.log('📦 Updating @deneb-ui/ui and @deneb-ui/cli to latest from npm...');
739
+ const packagesToInstall = ['@deneb-ui/ui@latest', '@deneb-ui/cli@latest'];
562
740
 
563
- console.log('📦 Updating @fivora/editable-components and @fivora/cli to latest from npm...');
564
- const packagesToInstall = ['@fivora/editable-components@latest', '@fivora/cli@latest'];
741
+ const installRes = spawnSync('npm', ['install', ...packagesToInstall], {
742
+ cwd: targetDir,
743
+ stdio: 'inherit',
744
+ shell: process.platform === 'win32',
745
+ });
565
746
 
566
- const installRes = spawnSync('npm', ['install', ...packagesToInstall], {
567
- cwd: targetDir,
568
- stdio: 'inherit',
569
- shell: process.platform === 'win32',
570
- });
747
+ if (installRes.status === 0) {
748
+ console.log('\n\x1b[32m✔ DENEB packages updated to latest versions successfully!\x1b[0m\n');
749
+ } else {
750
+ console.log('\n\x1b[31m✖ npm install failed with exit code ' + installRes.status + '\x1b[0m\n');
751
+ }
752
+ }
571
753
 
572
- if (installRes.status === 0) {
573
- console.log('\n\x1b[32m✔ Fivora packages updated to latest versions successfully!\x1b[0m\n');
574
- } else {
575
- console.log('\n\x1b[31m✖ npm install failed with exit code ' + installRes.status + '\x1b[0m\n');
754
+ // 2. Update DENEB components in src/components/ui/
755
+ const uiDir = path.join(targetDir, 'src', 'components', 'ui');
756
+ if (fs.existsSync(uiDir)) {
757
+ console.log('🧩 Inspecting and refreshing installed DENEB UI components in src/components/ui/...\n');
758
+ const registry = getComponentRegistry(importPkg);
759
+ const existingFiles = fs.readdirSync(uiDir);
760
+ let updatedComponentsCount = 0;
761
+
762
+ for (const [key, item] of Object.entries(registry)) {
763
+ if (existingFiles.includes(item.file)) {
764
+ const filePath = path.join(uiDir, item.file);
765
+ fs.writeFileSync(filePath, item.code);
766
+ console.log(` \x1b[32m✔ Updated\x1b[0m src/components/ui/${item.file} (${key})`);
767
+ updatedComponentsCount++;
768
+ }
769
+ }
770
+
771
+ if (updatedComponentsCount > 0) {
772
+ console.log(`\n\x1b[32m✔ Successfully updated ${updatedComponentsCount} DENEB component(s) to the latest definitions!\x1b[0m\n`);
773
+ } else {
774
+ console.log(` \x1b[90mNo existing DENEB UI components found in src/components/ui to update.\x1b[0m\n`);
775
+ }
576
776
  }
577
777
  }
578
778
 
@@ -598,138 +798,12 @@ function addComponent(componentName, targetDirInput) {
598
798
  }
599
799
  }
600
800
 
601
- const REGISTRY = {
602
- 'button': {
603
- file: 'Button.tsx',
604
- component: 'Button',
605
- code: `'use client';\n\nimport { Button, type EditableButtonProps } from '${importPkg}';\n\nexport { Button, type EditableButtonProps };\n`,
606
- },
607
- 'dialog': {
608
- file: 'Dialog.tsx',
609
- component: 'Dialog',
610
- code: `'use client';\n\nimport { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, type DialogProps } from '${importPkg}';\n\nexport { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, type DialogProps };\n`,
611
- },
612
- 'card': {
613
- file: 'Card.tsx',
614
- component: 'Card',
615
- code: `'use client';\n\nimport { Card, type EditableCardProps } from '${importPkg}';\n\nexport { Card, type EditableCardProps };\n`,
616
- },
617
- 'product-card': {
618
- file: 'ProductCard.tsx',
619
- component: 'EditableProductCard',
620
- code: `'use client';\n\nimport { EditableProductCard, type EditableProductCardProps } from '${importPkg}';\n\nexport function ProductCard(props: EditableProductCardProps) {\n return <EditableProductCard {...props} />;\n}\n`,
621
- },
622
- 'pricing-card': {
623
- file: 'PricingCard.tsx',
624
- component: 'EditablePricingCard',
625
- code: `'use client';\n\nimport { EditablePricingCard, type EditablePricingCardProps } from '${importPkg}';\n\nexport function PricingCard(props: EditablePricingCardProps) {\n return <EditablePricingCard {...props} />;\n}\n`,
626
- },
627
- 'testimonial-card': {
628
- file: 'TestimonialCard.tsx',
629
- component: 'EditableTestimonialCard',
630
- code: `'use client';\n\nimport { EditableTestimonialCard, type EditableTestimonialCardProps } from '${importPkg}';\n\nexport function TestimonialCard(props: EditableTestimonialCardProps) {\n return <EditableTestimonialCard {...props} />;\n}\n`,
631
- },
632
- 'contact-form': {
633
- file: 'ContactForm.tsx',
634
- component: 'EditableContactForm',
635
- code: `'use client';\n\nimport { EditableContactForm, type EditableContactFormProps } from '${importPkg}';\n\nexport function ContactForm(props: EditableContactFormProps) {\n return <EditableContactForm {...props} />;\n}\n`,
636
- },
637
- 'faq': {
638
- file: 'FAQAccordion.tsx',
639
- component: 'EditableFAQAccordion',
640
- 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`,
641
- },
642
- 'navbar': {
643
- file: 'Navbar.tsx',
644
- component: 'EditableNavbar',
645
- code: `'use client';\n\nimport { EditableNavbar, type EditableNavbarProps } from '${importPkg}';\n\nexport function Navbar(props: EditableNavbarProps) {\n return <EditableNavbar {...props} />;\n}\n`,
646
- },
647
- 'footer': {
648
- file: 'Footer.tsx',
649
- component: 'EditableFooter',
650
- code: `'use client';\n\nimport { EditableFooter, type EditableFooterProps } from '${importPkg}';\n\nexport function Footer(props: EditableFooterProps) {\n return <EditableFooter {...props} />;\n}\n`,
651
- },
652
- 'hero': {
653
- file: 'Hero.tsx',
654
- component: 'EditableHeroCentered',
655
- 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`,
656
- },
657
- 'whatsapp-button': {
658
- file: 'WhatsAppButton.tsx',
659
- component: 'WhatsAppButton',
660
- code: `'use client';\n\nimport { WhatsAppButton, type WhatsAppButtonProps } from '${importPkg}';\n\nexport { WhatsAppButton, type WhatsAppButtonProps };\n`,
661
- },
662
- 'phone-button': {
663
- file: 'PhoneButton.tsx',
664
- component: 'PhoneButton',
665
- code: `'use client';\n\nimport { PhoneButton, type PhoneButtonProps } from '${importPkg}';\n\nexport { PhoneButton, type PhoneButtonProps };\n`,
666
- },
667
- 'email-button': {
668
- file: 'EmailButton.tsx',
669
- component: 'EmailButton',
670
- code: `'use client';\n\nimport { EmailButton, type EmailButtonProps } from '${importPkg}';\n\nexport { EmailButton, type EmailButtonProps };\n`,
671
- },
672
- 'contact-actions': {
673
- file: 'ContactActions.tsx',
674
- component: 'ContactActions',
675
- code: `'use client';\n\nimport { ContactActions, type ContactActionsProps } from '${importPkg}';\n\nexport { ContactActions, type ContactActionsProps };\n`,
676
- },
677
- 'location-card': {
678
- file: 'LocationCard.tsx',
679
- component: 'LocationCard',
680
- code: `'use client';\n\nimport { LocationCard, type LocationCardProps } from '${importPkg}';\n\nexport { LocationCard, type LocationCardProps };\n`,
681
- },
682
- 'location-link': {
683
- file: 'LocationLink.tsx',
684
- component: 'LocationLink',
685
- code: `'use client';\n\nimport { LocationLink, type LocationLinkProps } from '${importPkg}';\n\nexport { LocationLink, type LocationLinkProps };\n`,
686
- },
687
- 'map-embed': {
688
- file: 'MapEmbed.tsx',
689
- component: 'MapEmbed',
690
- code: `'use client';\n\nimport { MapEmbed, type MapEmbedProps } from '${importPkg}';\n\nexport { MapEmbed, type MapEmbedProps };\n`,
691
- },
692
- 'social-links': {
693
- file: 'SocialLinks.tsx',
694
- component: 'SocialLinks',
695
- code: `'use client';\n\nimport { SocialLinks, type SocialLinksProps } from '${importPkg}';\n\nexport { SocialLinks, type SocialLinksProps };\n`,
696
- },
697
- 'social-button': {
698
- file: 'SocialButton.tsx',
699
- component: 'SocialButton',
700
- code: `'use client';\n\nimport { SocialButton, type SocialButtonProps } from '${importPkg}';\n\nexport { SocialButton, type SocialButtonProps };\n`,
701
- },
702
- 'business-hours': {
703
- file: 'BusinessHours.tsx',
704
- component: 'BusinessHours',
705
- code: `'use client';\n\nimport { BusinessHours, type BusinessHoursProps } from '${importPkg}';\n\nexport { BusinessHours, type BusinessHoursProps };\n`,
706
- },
707
- 'announcement-bar': {
708
- file: 'AnnouncementBar.tsx',
709
- component: 'EditableAnnouncementBar',
710
- code: `'use client';\n\nimport { EditableAnnouncementBar, AnnouncementBar, type EditableAnnouncementBarProps } from '${importPkg}';\n\nexport { EditableAnnouncementBar, AnnouncementBar, type EditableAnnouncementBarProps };\n`,
711
- },
712
- 'category-pills': {
713
- file: 'CategoryPills.tsx',
714
- component: 'EditableCategoryPills',
715
- code: `'use client';\n\nimport { EditableCategoryPills, CategoryPills, type EditableCategoryPillsProps } from '${importPkg}';\n\nexport { EditableCategoryPills, CategoryPills, type EditableCategoryPillsProps };\n`,
716
- },
717
- 'floating-contact-widget': {
718
- file: 'FloatingContactWidget.tsx',
719
- component: 'FloatingContactWidget',
720
- code: `'use client';\n\nimport { FloatingContactWidget, type FloatingContactWidgetProps } from '${importPkg}';\n\nexport { FloatingContactWidget, type FloatingContactWidgetProps };\n`,
721
- },
722
- 'deneb-action': {
723
- file: 'DenebAction.tsx',
724
- component: 'DenebAction',
725
- code: `'use client';\n\nimport { DenebAction, type DenebActionProps } from '${importPkg}';\n\nexport { DenebAction, type DenebActionProps };\n`,
726
- },
727
- };
801
+ const REGISTRY = getComponentRegistry(importPkg);
728
802
 
729
803
  if (!componentName || componentName === 'list') {
730
- console.log('\n\x1b[36m╔═══════════════════════════════════════════════════════╗\x1b[0m');
731
- console.log('\x1b[36m║ \x1b[1mDENEB UI COMPONENT REGISTRY\x1b[0m\x1b[36m ║\x1b[0m');
732
- console.log('\x1b[36m╚═══════════════════════════════════════════════════════╝\x1b[0m\n');
804
+ console.log('\n' + createBox([
805
+ '\x1b[1m\x1b[37mDENEB UI COMPONENT REGISTRY\x1b[0m'
806
+ ], 55) + '\n');
733
807
  console.log('Available components to add (run "deneb add <name>"):');
734
808
  for (const [key, val] of Object.entries(REGISTRY)) {
735
809
  console.log(` - \x1b[32m${key.padEnd(18)}\x1b[0m -> src/components/ui/${val.file}`);
@@ -757,43 +831,321 @@ function addComponent(componentName, targetDirInput) {
757
831
  console.log('Import them in your pages:\n \x1b[90mimport { ... } from "@/components/ui/...";\x1b[0m\n');
758
832
  }
759
833
 
834
+ function runValidateAndZip(targetDirInput, extraArgs = []) {
835
+ if (targetDirInput === '--help' || targetDirInput === '-h' || extraArgs.includes('--help') || extraArgs.includes('-h')) {
836
+ console.log(`\nUsage: deneb validate-and-zip [template-directory] [options]
837
+ (or deneb validate --zip [template-directory])
838
+ (or deneb validate and zip [template-directory])
839
+
840
+ Runs Fivora preflight verification against manifest contracts, static export fixtures,
841
+ and visual editing markers. If and only if all checks pass, packages a clean upload-ready
842
+ fivora-template.zip (excluding node_modules, .next, .git, .env*).
843
+
844
+ Options:
845
+ --skip-install Reuse existing dependencies in working directory (diagnostic only)
846
+ --skip-build Inspect existing build output directory in place (diagnostic only)
847
+ --json Output validation report as JSON\n`);
848
+ process.exit(0);
849
+ }
850
+
851
+ const targetDir = path.resolve(targetDirInput || '.');
852
+ const outputZip = path.resolve(targetDir, 'fivora-template.zip');
853
+
854
+ console.log('\n' + createBox([
855
+ '\x1b[1m\x1b[37mDENEB VALIDATE & ZIP PREFLIGHT\x1b[0m',
856
+ '\x1b[90m1. Validate website configuration with Fivora platform\x1b[0m',
857
+ '\x1b[90m2. Package clean upload-ready ZIP if 100% compliant\x1b[0m'
858
+ ], 58) + '\n');
859
+
860
+ console.log(`[Step 1/2] Running Fivora preflight validation in ${targetDir}...\n`);
861
+
862
+ const validatorScript = path.join(toolsDir, 'fivora-template-validator.cjs');
863
+ const valRes = spawnSync(process.execPath, [validatorScript, 'validate', targetDir, ...extraArgs], {
864
+ stdio: 'inherit',
865
+ });
866
+
867
+ if (valRes.status !== 0) {
868
+ console.error(`\n\x1b[31m✖ Validation failed with exit code ${valRes.status}.\x1b[0m`);
869
+ console.error(`\x1b[33mRefusing to package ZIP: template does not meet Fivora platform requirements.\x1b[0m`);
870
+ console.error(`Fix the validation errors above and re-run "deneb validate-and-zip".\n`);
871
+ process.exit(valRes.status ?? 1);
872
+ }
873
+
874
+ console.log(`\n[Step 2/2] Validation PASSED! Creating clean upload-ready ZIP...\n`);
875
+ packageCleanZip(targetDir, outputZip);
876
+
877
+ console.log('\n' + createBox([
878
+ '\x1b[1m\x1b[32m✔ PREFLIGHT VALIDATION & PACKAGING SUCCESSFUL!\x1b[0m',
879
+ `\x1b[37mOutput:\x1b[0m ${path.basename(outputZip)}`,
880
+ '\x1b[90mReady for 1-click upload at Fivora Developer Portal\x1b[0m'
881
+ ], 58) + '\n');
882
+ }
883
+
884
+ function runDoctor(targetDirInput) {
885
+ const targetDir = path.resolve(targetDirInput || '.');
886
+
887
+ console.log('\n' + createBox([
888
+ '\x1b[1m\x1b[36m🩺 DENEB SYSTEM & TEMPLATE DOCTOR\x1b[0m',
889
+ '\x1b[90mComprehensive diagnostic analysis for Fivora & Next.js\x1b[0m',
890
+ `\x1b[37mTarget:\x1b[0m ${targetDir}`
891
+ ], 60) + '\n');
892
+
893
+ let passed = 0;
894
+ let warnings = 0;
895
+ let errors = 0;
896
+
897
+ function report(type, title, detail) {
898
+ if (type === 'pass') {
899
+ passed++;
900
+ console.log(` \x1b[32m✔\x1b[0m \x1b[1m${title}\x1b[0m${detail ? ` \x1b[90m(${detail})\x1b[0m` : ''}`);
901
+ } else if (type === 'warn') {
902
+ warnings++;
903
+ console.log(` \x1b[33m⚠\x1b[0m \x1b[33m${title}\x1b[0m${detail ? ` \x1b[90m- ${detail}\x1b[0m` : ''}`);
904
+ } else {
905
+ errors++;
906
+ console.log(` \x1b[31m✖\x1b[0m \x1b[31m${title}\x1b[0m${detail ? ` \x1b[90m- ${detail}\x1b[0m` : ''}`);
907
+ }
908
+ }
909
+
910
+ console.log('\x1b[1m[1/6] System & Runtime Environment:\x1b[0m');
911
+ const nodeVersion = process.version;
912
+ const majorNode = parseInt(nodeVersion.replace(/^v/, '').split('.')[0], 10);
913
+ if (majorNode >= 18) {
914
+ report('pass', 'Node.js Runtime', `${nodeVersion} (Supported)`);
915
+ } else {
916
+ report('err', 'Node.js Runtime', `${nodeVersion} (Requires Node.js >= 18.0.0)`);
917
+ }
918
+
919
+ const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
920
+ const npmCheck = spawnSync(npmBin, ['--version'], { encoding: 'utf-8', shell: process.platform === 'win32' });
921
+ if (!npmCheck.error && npmCheck.status === 0) {
922
+ report('pass', 'Package Manager', `npm v${npmCheck.stdout.trim()}`);
923
+ } else {
924
+ report('warn', 'Package Manager', 'npm not found in system PATH');
925
+ }
926
+
927
+ console.log('\n\x1b[1m[2/6] Project Package Configuration:\x1b[0m');
928
+ const pkgPath = path.join(targetDir, 'package.json');
929
+ let pkg = null;
930
+ if (fs.existsSync(pkgPath)) {
931
+ try {
932
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
933
+ report('pass', 'package.json', `Found "${pkg.name || 'unnamed'}"`);
934
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
935
+
936
+ if (allDeps['next']) {
937
+ report('pass', 'Next.js Framework', allDeps['next']);
938
+ } else {
939
+ report('err', 'Next.js Framework', 'next dependency missing in package.json');
940
+ }
941
+
942
+ if (allDeps['@deneb-ui/ui']) {
943
+ report('pass', '@deneb-ui/ui Library', allDeps['@deneb-ui/ui']);
944
+ } else {
945
+ report('warn', '@deneb-ui/ui Library', 'Not installed (run "npm i @deneb-ui/ui")');
946
+ }
947
+
948
+ if (allDeps['@deneb-ui/cli']) {
949
+ report('pass', '@deneb-ui/cli Tooling', allDeps['@deneb-ui/cli']);
950
+ } else {
951
+ report('warn', '@deneb-ui/cli Tooling', 'Recommended for local CLI scripts');
952
+ }
953
+ } catch (e) {
954
+ report('err', 'package.json Syntax', e.message);
955
+ }
956
+ } else {
957
+ report('err', 'package.json', `Not found at ${pkgPath}`);
958
+ }
959
+
960
+ console.log('\n\x1b[1m[3/6] Static Export Configuration:\x1b[0m');
961
+ const nextConfigTs = path.join(targetDir, 'next.config.ts');
962
+ const nextConfigMjs = path.join(targetDir, 'next.config.mjs');
963
+ const nextConfigJs = path.join(targetDir, 'next.config.js');
964
+ let nextConfigFile = [nextConfigTs, nextConfigMjs, nextConfigJs].find((p) => fs.existsSync(p));
965
+
966
+ if (nextConfigFile) {
967
+ const content = fs.readFileSync(nextConfigFile, 'utf-8');
968
+ if (content.includes("output: 'export'") || content.includes('output: "export"')) {
969
+ report('pass', 'Next.js Static Export', `output: 'export' verified in ${path.basename(nextConfigFile)}`);
970
+ } else {
971
+ report('err', 'Next.js Static Export', `Missing output: 'export' in ${path.basename(nextConfigFile)} (Required by Fivora)`);
972
+ }
973
+ } else {
974
+ report('err', 'Next.js Config', 'No next.config.ts, next.config.mjs, or next.config.js found');
975
+ }
976
+
977
+ console.log('\n\x1b[1m[4/6] Fivora Manifest v2 Contract:\x1b[0m');
978
+ const manifestPath = path.join(targetDir, 'fivora-template.json');
979
+ let manifestData = null;
980
+ if (fs.existsSync(manifestPath)) {
981
+ try {
982
+ manifestData = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
983
+ report('pass', 'fivora-template.json', `Valid JSON (strict=${manifestData.strict !== false})`);
984
+
985
+ if (manifestData.version === 2 || manifestData.version === '2') {
986
+ report('pass', 'Manifest Version', 'Version 2 (Current standard)');
987
+ } else {
988
+ report('warn', 'Manifest Version', `Version ${manifestData.version} detected (Recommend version 2)`);
989
+ }
990
+
991
+ const hasHome = Array.isArray(manifestData.pages) && manifestData.pages.some((p) =>
992
+ p.route === '/' || p.slug === '/' || p.path === '/' || p.id === 'home'
993
+ );
994
+ if (hasHome) {
995
+ report('pass', 'Home Page Entry', 'Home page ("/") declared in manifest');
996
+ } else {
997
+ report('err', 'Home Page Entry', 'Manifest pages array missing root slug or route: "/"');
998
+ }
999
+
1000
+ if (manifestData.theme && (manifestData.theme.primary || manifestData.theme.accent)) {
1001
+ report('pass', 'Theme Configuration', 'Primary and accent color tokens declared');
1002
+ } else {
1003
+ // Will check site-data.json below as alternative
1004
+ }
1005
+ } catch (e) {
1006
+ report('err', 'fivora-template.json Syntax', e.message);
1007
+ }
1008
+ } else {
1009
+ report('err', 'fivora-template.json', 'File not found. Run "deneb init" to generate it');
1010
+ }
1011
+
1012
+ console.log('\n\x1b[1m[5/6] Reactive Site Data & Visual Editing:\x1b[0m');
1013
+ const siteDataPath = path.join(targetDir, 'src', 'data', 'site-data.json');
1014
+ if (fs.existsSync(siteDataPath)) {
1015
+ try {
1016
+ const siteData = JSON.parse(fs.readFileSync(siteDataPath, 'utf-8'));
1017
+ report('pass', 'site-data.json', 'src/data/site-data.json exists & valid');
1018
+ const merchantName = (siteData.merchant && (siteData.merchant.businessName || siteData.merchant.name)) || null;
1019
+ if (merchantName) {
1020
+ report('pass', 'Merchant Metadata', `Name: "${merchantName}"`);
1021
+ } else {
1022
+ report('warn', 'Merchant Metadata', 'Missing merchant name in site-data.json');
1023
+ }
1024
+
1025
+ const themeTokens = manifestData?.theme || siteData?.template?.structure?.theme;
1026
+ if (themeTokens && (themeTokens.primaryColor || themeTokens.primary || themeTokens.accentColor || themeTokens.accent)) {
1027
+ report('pass', 'Theme Design Tokens', 'Theme color tokens declared');
1028
+ } else {
1029
+ report('warn', 'Theme Design Tokens', 'No theme color tokens found in manifest or site-data.json');
1030
+ }
1031
+
1032
+ if (siteData.content) {
1033
+ report('pass', 'Visual Content Bindings', 'Content section ready for live sync');
1034
+ } else {
1035
+ report('warn', 'Visual Content Bindings', 'Missing content section in site-data.json');
1036
+ }
1037
+ } catch (e) {
1038
+ report('err', 'site-data.json Syntax', e.message);
1039
+ }
1040
+ } else {
1041
+ report('warn', 'site-data.json', 'src/data/site-data.json not found. Recommended for Fivora live editor');
1042
+ }
1043
+
1044
+ console.log('\n\x1b[1m[6/6] Cleanliness & Security Check:\x1b[0m');
1045
+ const envFiles = ['.env', '.env.local', '.env.production', '.env.development'];
1046
+ const foundEnv = envFiles.filter((f) => fs.existsSync(path.join(targetDir, f)));
1047
+ if (foundEnv.length === 0) {
1048
+ report('pass', 'Secrets Isolation', 'No raw .env files detected in root directory');
1049
+ } else {
1050
+ report('warn', 'Secrets Isolation', `Active env files: ${foundEnv.join(', ')} (Excluded during packaging)`);
1051
+ }
1052
+
1053
+ const previewExists = fs.existsSync(path.join(targetDir, 'preview.png')) ||
1054
+ fs.existsSync(path.join(targetDir, 'thumbnail.png')) ||
1055
+ fs.existsSync(path.join(targetDir, 'public', 'fivora-logo.png'));
1056
+ if (previewExists) {
1057
+ report('pass', 'Storefront Assets', 'Brand/preview graphics verified');
1058
+ } else {
1059
+ report('warn', 'Storefront Assets', 'preview.png not found in template root');
1060
+ }
1061
+
1062
+ // Summary
1063
+ console.log('\n' + createBox([
1064
+ '\x1b[1mDOCTOR DIAGNOSTIC SUMMARY\x1b[0m',
1065
+ `\x1b[32m✔ Passed:\x1b[0m ${passed}`,
1066
+ `\x1b[33m⚠ Warnings:\x1b[0m ${warnings}`,
1067
+ `\x1b[31m✖ Errors:\x1b[0m ${errors}`,
1068
+ errors === 0
1069
+ ? '\x1b[32mStatus: HEALTHY — Ready for Fivora packaging & build!\x1b[0m'
1070
+ : '\x1b[31mStatus: ATTENTION REQUIRED — Fix errors before deployment\x1b[0m'
1071
+ ], 58) + '\n');
1072
+
1073
+ if (errors > 0) {
1074
+ process.exit(1);
1075
+ }
1076
+ }
1077
+
1078
+ // Normalize multi-word "validate and zip" or "validate & zip"
1079
+ let command = args[0];
1080
+ let commandArgs = args.slice(1);
1081
+
1082
+ if (command === 'validate' && (commandArgs[0] === 'and' || commandArgs[0] === '&') && commandArgs[1] === 'zip') {
1083
+ command = 'validate-and-zip';
1084
+ commandArgs = commandArgs.slice(2);
1085
+ }
1086
+
760
1087
  if (command === 'init') {
761
- initProject(args[1]);
1088
+ initProject(commandArgs[0]);
762
1089
  } else if (command === 'create') {
763
- createTemplate(args[1]);
1090
+ createTemplate(commandArgs[0]);
764
1091
  } else if (command === 'add') {
765
- addComponent(args[1], args[2]);
1092
+ addComponent(commandArgs[0], commandArgs[1]);
1093
+ } else if (command === 'doctor' || command === 'check') {
1094
+ runDoctor(commandArgs[0]);
766
1095
  } else if (command === 'lab') {
767
1096
  const script = path.join(toolsDir, 'local-template-lab.cjs');
768
- const res = spawnSync(process.execPath, [script, ...args.slice(1)], { stdio: 'inherit' });
1097
+ const res = spawnSync(process.execPath, [script, ...commandArgs], { stdio: 'inherit' });
769
1098
  process.exit(res.status ?? 0);
770
1099
  } else if (command === 'validate') {
771
- const script = path.join(toolsDir, 'fivora-template-validator.cjs');
772
- const res = spawnSync(process.execPath, [script, 'validate', ...args.slice(1)], { stdio: 'inherit' });
773
- process.exit(res.status ?? 0);
1100
+ if (commandArgs.includes('--zip') || commandArgs.includes('-z')) {
1101
+ const cleanArgs = commandArgs.filter((a) => a !== '--zip' && a !== '-z');
1102
+ runValidateAndZip(cleanArgs[0] || '.', cleanArgs.slice(1));
1103
+ } else {
1104
+ const script = path.join(toolsDir, 'fivora-template-validator.cjs');
1105
+ const res = spawnSync(process.execPath, [script, 'validate', ...commandArgs], { stdio: 'inherit' });
1106
+ process.exit(res.status ?? 0);
1107
+ }
774
1108
  } else if (command === 'pack' || command === 'zip') {
775
- const targetDir = path.resolve(args[1] || '.');
1109
+ const targetDir = path.resolve(commandArgs[0] || '.');
776
1110
  const outputZip = path.resolve(targetDir, 'fivora-template.zip');
777
1111
  packageCleanZip(targetDir, outputZip);
1112
+ } else if (command === 'validate-and-zip' || command === 'validate-zip') {
1113
+ runValidateAndZip(commandArgs[0] || '.', commandArgs.slice(1));
778
1114
  } else if (command === 'update' || command === 'upgrade') {
779
- updateDependencies(args.slice(1));
1115
+ updateDependencies(commandArgs);
780
1116
  } else if (command === 'package') {
781
1117
  const script = path.join(toolsDir, 'fivora-template-validator.cjs');
782
- const res = spawnSync(process.execPath, [script, 'package', ...args.slice(1)], { stdio: 'inherit' });
1118
+ const res = spawnSync(process.execPath, [script, 'package', ...commandArgs], { stdio: 'inherit' });
783
1119
  process.exit(res.status ?? 0);
784
1120
  } else {
785
- console.log(`Usage: deneb <command> [options] (or fivora <command> [options])
786
- DENEB UI Framework — Created by Chamika Gayashan & Induranga Kawishwara
787
-
788
- Commands:
789
- add Add a DENEB UI component to src/components/ui/ (e.g. deneb add product-card)
790
- init Configure an existing Next.js project with missing Fivora files & scripts
791
- create Scaffold a new storefront template (e.g. deneb create my-store)
792
- lab Start the local visual editing lab
793
- validate Validate template visual editing contracts and empty states
794
- update Update DENEB & Fivora packages to latest
795
- pack Quick-package a clean, upload-ready ZIP without junk files (node_modules, .next, .git, .env)
796
- zip Alias for pack
797
- package Run full preflight verification and generate upload ZIP`);
1121
+ console.log(`Usage: deneb <command> [options]
1122
+ DENEB UI Framework — Powered by DENEB-UI Collaborate with FIVORA
1123
+
1124
+ Core Commands:
1125
+ init Configure an existing Next.js project with missing Fivora files & scripts
1126
+ update Update DENEB packages (@deneb-ui/ui, @deneb-ui/cli) and UI components
1127
+ validate Validate website configuration and visual editing contracts with Fivora platform
1128
+ doctor Run comprehensive environment, manifest & asset diagnostic checks
1129
+ zip Zip the project without unnecessary folders or files (node_modules, .next, .git, .env)
1130
+ validate-and-zip Validate website configuration and immediately package clean upload-ready ZIP
1131
+
1132
+ Development & Scaffolding:
1133
+ add <component> Add or update a DENEB UI component in src/components/ui/ (e.g. deneb add product-card)
1134
+ create <name> Scaffold a new storefront template (e.g. deneb create my-store)
1135
+ lab Start the local visual editing lab simulation
1136
+ pack Alias for zip
1137
+ package Run strict sandbox preflight verification and generate upload ZIP
1138
+
1139
+ Examples:
1140
+ deneb doctor
1141
+ deneb init
1142
+ deneb update
1143
+ deneb validate .
1144
+ deneb validate --zip
1145
+ deneb validate-and-zip
1146
+ deneb zip .
1147
+ deneb add product-card
1148
+ deneb add all`);
798
1149
  process.exit(1);
799
1150
  }
1151
+
package/package.json CHANGED
@@ -1,18 +1,26 @@
1
1
  {
2
2
  "name": "@deneb-ui/cli",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "description": "Official developer CLI for DENEB UI — Validating, scaffolding, testing, and packaging storefront templates. Created by Chamika Gayashan & Induranga Kawishwara.",
5
5
  "bin": {
6
6
  "deneb": "bin/index.js",
7
7
  "denebui": "bin/index.js",
8
8
  "deneb-cli": "bin/index.js",
9
- "fivora": "bin/index.js",
10
9
  "ceeg": "bin/index.js",
11
10
  "ceegui": "bin/index.js"
12
11
  },
13
12
  "publishConfig": {
14
13
  "access": "public"
15
14
  },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/deneb-ui/core.git",
18
+ "directory": "cli/deneb-cli"
19
+ },
20
+ "homepage": "https://github.com/deneb-ui/core#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/deneb-ui/core/issues"
23
+ },
16
24
  "files": [
17
25
  "bin",
18
26
  "src",
@@ -21,7 +29,6 @@
21
29
  "keywords": [
22
30
  "deneb",
23
31
  "deneb-ui",
24
- "fivora",
25
32
  "cli",
26
33
  "validator",
27
34
  "packager"