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