@deneb-ui/cli 2.0.7 → 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.
- package/README.md +56 -22
- package/bin/index.js +554 -223
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
> The premier visual-first React component ecosystem.
|
|
7
7
|
> Proudly presented by **DENEB-UI Collaborate with FIVORA**.
|
|
8
8
|
|
|
9
|
-
[](https://github.com/
|
|
9
|
+
[](https://github.com/deneb-ui/ui)
|
|
10
10
|
[](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
|
-
###
|
|
54
|
-
Converts any **
|
|
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
|
-
###
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
76
|
-
Runs preflight
|
|
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
|
-
###
|
|
84
|
-
Creates a clean,
|
|
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
|
-
###
|
|
92
|
-
|
|
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
|
|
96
|
-
# Or: deneb
|
|
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
|
|
|
@@ -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
|
|
158
|
-
const res = spawnSync('npx', ['--yes', '@
|
|
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[
|
|
366
|
-
'\x1b[90mConfigure existing Next.js
|
|
367
|
-
|
|
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 '
|
|
372
|
-
console.error(` \x1b[36mnpx @
|
|
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,19 +546,14 @@ function initProject(targetInput) {
|
|
|
396
546
|
// 1. Scan / Detect Pages
|
|
397
547
|
const detectedPages = detectPages(targetDir);
|
|
398
548
|
|
|
399
|
-
// 2. Generate fivora-template.json (
|
|
400
|
-
const
|
|
401
|
-
const legacyManifestPath = path.join(targetDir, 'fivora-template.json');
|
|
402
|
-
const manifestPath = fs.existsSync(legacyManifestPath) && !fs.existsSync(fivoraManifestPath)
|
|
403
|
-
? legacyManifestPath
|
|
404
|
-
: fivoraManifestPath;
|
|
405
|
-
|
|
549
|
+
// 2. Generate fivora-template.json (version 2 contract)
|
|
550
|
+
const manifestPath = path.join(targetDir, 'fivora-template.json');
|
|
406
551
|
if (!fs.existsSync(manifestPath)) {
|
|
407
552
|
const manifest = getDefaultManifest(projectName, detectedPages);
|
|
408
553
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
409
554
|
console.log(`\x1b[32m✔ Created\x1b[0m fivora-template.json (version 2, strict visual editing contract)`);
|
|
410
555
|
} else {
|
|
411
|
-
console.log(`\x1b[90m⏩ Kept existing\x1b[0m
|
|
556
|
+
console.log(`\x1b[90m⏩ Kept existing\x1b[0m fivora-template.json`);
|
|
412
557
|
}
|
|
413
558
|
|
|
414
559
|
// 3. Generate siteDataFile
|
|
@@ -433,24 +578,25 @@ function initProject(targetInput) {
|
|
|
433
578
|
// 4. Update package.json scripts
|
|
434
579
|
pkg.scripts = pkg.scripts || {};
|
|
435
580
|
const scriptsToAdd = {
|
|
436
|
-
'lab': '
|
|
437
|
-
'validate': '
|
|
438
|
-
'zip': '
|
|
439
|
-
'
|
|
440
|
-
'
|
|
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',
|
|
441
587
|
};
|
|
442
588
|
let addedCount = 0;
|
|
443
589
|
for (const [key, val] of Object.entries(scriptsToAdd)) {
|
|
444
|
-
if (!pkg.scripts[key]) {
|
|
590
|
+
if (!pkg.scripts[key] || pkg.scripts[key].startsWith('fivora ')) {
|
|
445
591
|
pkg.scripts[key] = val;
|
|
446
592
|
addedCount++;
|
|
447
593
|
}
|
|
448
594
|
}
|
|
449
595
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
450
596
|
if (addedCount > 0) {
|
|
451
|
-
console.log(`\x1b[32m✔
|
|
597
|
+
console.log(`\x1b[32m✔ Configured\x1b[0m DENEB scripts in package.json (lab, validate, zip, validate-and-zip, package:template, update:deneb)`);
|
|
452
598
|
} else {
|
|
453
|
-
console.log(`\x1b[90m⏩
|
|
599
|
+
console.log(`\x1b[90m⏩ DENEB scripts already present\x1b[0m in package.json`);
|
|
454
600
|
}
|
|
455
601
|
|
|
456
602
|
// 5. Check next.config for static export
|
|
@@ -474,47 +620,53 @@ function initProject(targetInput) {
|
|
|
474
620
|
console.log(` \x1b[90mconst nextConfig = { output: 'export' };\x1b[0m`);
|
|
475
621
|
}
|
|
476
622
|
|
|
477
|
-
// 6. Install
|
|
623
|
+
// 6. Install DENEB packages if missing
|
|
478
624
|
const skipInstall = process.argv.includes('--skip-install');
|
|
479
|
-
const
|
|
480
|
-
(pkg.dependencies && pkg.dependencies['@fivora/editable-components']) ||
|
|
481
|
-
(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']))
|
|
482
628
|
);
|
|
483
629
|
const hasCli = Boolean(
|
|
484
|
-
(pkg.devDependencies && pkg.devDependencies['@fivora/cli']) ||
|
|
485
|
-
(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']))
|
|
486
632
|
);
|
|
487
633
|
|
|
488
|
-
if (!skipInstall && (!
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
if (!
|
|
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');
|
|
492
639
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
console.log(`\
|
|
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
|
+
});
|
|
503
655
|
}
|
|
504
656
|
}
|
|
505
657
|
|
|
506
|
-
console.log(`\n\x1b[32m✔
|
|
658
|
+
console.log(`\n\x1b[32m✔ Project initialization complete!\x1b[0m`);
|
|
507
659
|
console.log(`\nYou can now run:`);
|
|
508
|
-
console.log(` \x1b[36mnpm run lab\x1b[0m
|
|
509
|
-
console.log(` \x1b[36mnpm run validate\x1b[0m
|
|
510
|
-
console.log(` \x1b[36mnpm run zip\x1b[0m
|
|
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`);
|
|
511
664
|
}
|
|
512
665
|
|
|
513
|
-
|
|
514
666
|
function updateDependencies(cmdArgs = []) {
|
|
515
667
|
let targetDir = '.';
|
|
516
668
|
let isLocal = false;
|
|
517
|
-
let localPath = '
|
|
669
|
+
let localPath = path.resolve(__dirname, '..', '..', '..');
|
|
518
670
|
|
|
519
671
|
for (let i = 0; i < cmdArgs.length; i++) {
|
|
520
672
|
const arg = cmdArgs[i];
|
|
@@ -548,52 +700,79 @@ function updateDependencies(cmdArgs = []) {
|
|
|
548
700
|
}
|
|
549
701
|
|
|
550
702
|
console.log('\n' + createBox([
|
|
551
|
-
'\x1b[1m\x1b[
|
|
552
|
-
|
|
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');
|
|
553
707
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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';
|
|
558
713
|
}
|
|
559
714
|
|
|
715
|
+
// 1. Update npm packages
|
|
560
716
|
if (isLocal) {
|
|
561
|
-
const
|
|
717
|
+
const uiDir = path.join(localPath, 'packages', 'deneb-ui');
|
|
562
718
|
const cliDir = path.join(localPath, 'cli', 'fivora-cli');
|
|
563
719
|
|
|
564
|
-
if (fs.existsSync(
|
|
565
|
-
console.log('🔄 Updating from local
|
|
566
|
-
console.log(' - ' +
|
|
720
|
+
if (fs.existsSync(uiDir) && fs.existsSync(cliDir)) {
|
|
721
|
+
console.log('🔄 Updating from local workspace:');
|
|
722
|
+
console.log(' - ' + uiDir);
|
|
567
723
|
console.log(' - ' + cliDir + '\n');
|
|
568
724
|
|
|
569
|
-
const installRes = spawnSync('npm', ['install',
|
|
725
|
+
const installRes = spawnSync('npm', ['install', uiDir, cliDir], {
|
|
570
726
|
cwd: targetDir,
|
|
571
727
|
stdio: 'inherit',
|
|
572
728
|
shell: process.platform === 'win32',
|
|
573
729
|
});
|
|
574
730
|
|
|
575
731
|
if (installRes.status === 0) {
|
|
576
|
-
console.log('\n\x1b[32m✔ Local
|
|
732
|
+
console.log('\n\x1b[32m✔ Local DENEB packages re-linked & updated successfully!\x1b[0m\n');
|
|
577
733
|
} else {
|
|
578
734
|
console.log('\n\x1b[31m✖ Failed to link local packages (exit code: ' + installRes.status + ')\x1b[0m\n');
|
|
579
735
|
}
|
|
580
|
-
return;
|
|
581
736
|
}
|
|
582
|
-
}
|
|
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'];
|
|
583
740
|
|
|
584
|
-
|
|
585
|
-
|
|
741
|
+
const installRes = spawnSync('npm', ['install', ...packagesToInstall], {
|
|
742
|
+
cwd: targetDir,
|
|
743
|
+
stdio: 'inherit',
|
|
744
|
+
shell: process.platform === 'win32',
|
|
745
|
+
});
|
|
586
746
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
+
}
|
|
592
753
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
console.log('
|
|
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
|
+
}
|
|
597
776
|
}
|
|
598
777
|
}
|
|
599
778
|
|
|
@@ -619,133 +798,7 @@ function addComponent(componentName, targetDirInput) {
|
|
|
619
798
|
}
|
|
620
799
|
}
|
|
621
800
|
|
|
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
|
-
};
|
|
801
|
+
const REGISTRY = getComponentRegistry(importPkg);
|
|
749
802
|
|
|
750
803
|
if (!componentName || componentName === 'list') {
|
|
751
804
|
console.log('\n' + createBox([
|
|
@@ -778,43 +831,321 @@ function addComponent(componentName, targetDirInput) {
|
|
|
778
831
|
console.log('Import them in your pages:\n \x1b[90mimport { ... } from "@/components/ui/...";\x1b[0m\n');
|
|
779
832
|
}
|
|
780
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
|
+
|
|
781
1087
|
if (command === 'init') {
|
|
782
|
-
initProject(
|
|
1088
|
+
initProject(commandArgs[0]);
|
|
783
1089
|
} else if (command === 'create') {
|
|
784
|
-
createTemplate(
|
|
1090
|
+
createTemplate(commandArgs[0]);
|
|
785
1091
|
} else if (command === 'add') {
|
|
786
|
-
addComponent(
|
|
1092
|
+
addComponent(commandArgs[0], commandArgs[1]);
|
|
1093
|
+
} else if (command === 'doctor' || command === 'check') {
|
|
1094
|
+
runDoctor(commandArgs[0]);
|
|
787
1095
|
} else if (command === 'lab') {
|
|
788
1096
|
const script = path.join(toolsDir, 'local-template-lab.cjs');
|
|
789
|
-
const res = spawnSync(process.execPath, [script, ...
|
|
1097
|
+
const res = spawnSync(process.execPath, [script, ...commandArgs], { stdio: 'inherit' });
|
|
790
1098
|
process.exit(res.status ?? 0);
|
|
791
1099
|
} else if (command === 'validate') {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
+
}
|
|
795
1108
|
} else if (command === 'pack' || command === 'zip') {
|
|
796
|
-
const targetDir = path.resolve(
|
|
1109
|
+
const targetDir = path.resolve(commandArgs[0] || '.');
|
|
797
1110
|
const outputZip = path.resolve(targetDir, 'fivora-template.zip');
|
|
798
1111
|
packageCleanZip(targetDir, outputZip);
|
|
1112
|
+
} else if (command === 'validate-and-zip' || command === 'validate-zip') {
|
|
1113
|
+
runValidateAndZip(commandArgs[0] || '.', commandArgs.slice(1));
|
|
799
1114
|
} else if (command === 'update' || command === 'upgrade') {
|
|
800
|
-
updateDependencies(
|
|
1115
|
+
updateDependencies(commandArgs);
|
|
801
1116
|
} else if (command === 'package') {
|
|
802
1117
|
const script = path.join(toolsDir, 'fivora-template-validator.cjs');
|
|
803
|
-
const res = spawnSync(process.execPath, [script, 'package', ...
|
|
1118
|
+
const res = spawnSync(process.execPath, [script, 'package', ...commandArgs], { stdio: 'inherit' });
|
|
804
1119
|
process.exit(res.status ?? 0);
|
|
805
1120
|
} else {
|
|
806
|
-
console.log(`Usage: deneb <command> [options]
|
|
1121
|
+
console.log(`Usage: deneb <command> [options]
|
|
807
1122
|
DENEB UI Framework — Powered by DENEB-UI Collaborate with FIVORA
|
|
808
1123
|
|
|
809
|
-
Commands:
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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`);
|
|
819
1149
|
process.exit(1);
|
|
820
1150
|
}
|
|
1151
|
+
|
package/package.json
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deneb-ui/cli",
|
|
3
|
-
"version": "2.0.
|
|
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"
|