@deneb-ui/cli 2.0.11 → 2.0.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/cli",
3
- "version": "2.0.11",
3
+ "version": "2.0.13",
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",
@@ -280,6 +280,11 @@
280
280
  "key": "contactHeading",
281
281
  "type": "text",
282
282
  "label": "Contact Column Heading"
283
+ },
284
+ {
285
+ "key": "instagramUrl",
286
+ "type": "text",
287
+ "label": "Instagram Profile URL"
283
288
  }
284
289
  ]
285
290
  }
@@ -589,6 +594,16 @@
589
594
  "type": "textarea",
590
595
  "label": "WhatsApp Section Description"
591
596
  },
597
+ {
598
+ "key": "whatsappCtaLabel",
599
+ "type": "text",
600
+ "label": "WhatsApp CTA Button Label"
601
+ },
602
+ {
603
+ "key": "whatsappCtaUrl",
604
+ "type": "text",
605
+ "label": "WhatsApp CTA Link URL"
606
+ },
592
607
  {
593
608
  "key": "categoryLabel1",
594
609
  "type": "text",
@@ -1019,7 +1034,8 @@
1019
1034
  "supportLink1": "Shipping & Returns",
1020
1035
  "supportLink2": "Size Guide",
1021
1036
  "supportLink3": "WhatsApp Orders",
1022
- "contactHeading": "Contact"
1037
+ "contactHeading": "Contact",
1038
+ "instagramUrl": "https://instagram.com"
1023
1039
  }
1024
1040
  },
1025
1041
  "home": {
@@ -1080,6 +1096,8 @@
1080
1096
  "whatsappLabel": "Easy Ordering",
1081
1097
  "whatsappTitle": "Order Directly via WhatsApp",
1082
1098
  "whatsappDesc": "Select your size, add to your bag, and send your order in one tap. Our team confirms availability within hours.",
1099
+ "whatsappCtaLabel": "Start a Conversation",
1100
+ "whatsappCtaUrl": "https://wa.me/94771234567?text=Hi%2C%20I'd%20like%20to%20order%3A%0A%0AI'm%20interested%20in%20VANTA%20footwear.%20Can%20you%20help%20me%20choose%20a%20pair%3F",
1083
1101
  "categoryLabel1": "Performance",
1084
1102
  "categoryLabel2": "Sneakers",
1085
1103
  "categoryLabel3": "Lifestyle",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "vanta-shoes-store",
3
- "label": "vanta-shoes-store",
4
- "description": "Learned and saved recipe extracted from shoes_shop.",
5
- "savedAt": "2026-09-08T15:46:21.569Z",
3
+ "label": "Vanta Shoes Storefront",
4
+ "description": "Calibrated Vanta shoes store recipe with WhatsApp CTA, Instagram social link, product grid, and hero slides.",
5
+ "savedAt": "2026-09-08T17:20:49.206Z",
6
6
  "signatures": {
7
7
  "keywords": [
8
8
  "vanta-shoes-store",
@@ -55,6 +55,40 @@
55
55
  "autoIndex": true,
56
56
  "defaultCardPrefix": "product"
57
57
  },
58
+ "actionRules": {
59
+ "splitActionAndLabel": true,
60
+ "whatsappKeywords": [
61
+ "whatsapp",
62
+ "wa.me",
63
+ "order via whatsapp",
64
+ "chat on whatsapp"
65
+ ],
66
+ "actionFieldMapping": {
67
+ "whatsapp": {
68
+ "urlField": "whatsappCtaUrl",
69
+ "labelField": "whatsappCtaLabel"
70
+ },
71
+ "order": {
72
+ "urlField": "orderCtaUrl",
73
+ "labelField": "orderCtaLabel"
74
+ },
75
+ "promo": {
76
+ "urlField": "promoCtaUrl",
77
+ "labelField": "promoCtaLabel"
78
+ }
79
+ }
80
+ },
81
+ "socialRules": {
82
+ "platforms": [
83
+ "instagram",
84
+ "facebook",
85
+ "twitter",
86
+ "tiktok",
87
+ "youtube",
88
+ "linkedin"
89
+ ],
90
+ "targetPath": "common.footer"
91
+ },
58
92
  "sections": [
59
93
  {
60
94
  "id": "common",
@@ -255,6 +289,11 @@
255
289
  "key": "contactHeading",
256
290
  "type": "text",
257
291
  "label": "Contact Column Heading"
292
+ },
293
+ {
294
+ "key": "instagramUrl",
295
+ "type": "text",
296
+ "label": "Instagram Profile URL"
258
297
  }
259
298
  ]
260
299
  }
@@ -564,6 +603,16 @@
564
603
  "type": "textarea",
565
604
  "label": "WhatsApp Section Description"
566
605
  },
606
+ {
607
+ "key": "whatsappCtaLabel",
608
+ "type": "text",
609
+ "label": "WhatsApp CTA Button Label"
610
+ },
611
+ {
612
+ "key": "whatsappCtaUrl",
613
+ "type": "text",
614
+ "label": "WhatsApp CTA Link URL"
615
+ },
567
616
  {
568
617
  "key": "categoryLabel1",
569
618
  "type": "text",
@@ -994,7 +1043,8 @@
994
1043
  "supportLink1": "Shipping & Returns",
995
1044
  "supportLink2": "Size Guide",
996
1045
  "supportLink3": "WhatsApp Orders",
997
- "contactHeading": "Contact"
1046
+ "contactHeading": "Contact",
1047
+ "instagramUrl": "https://instagram.com"
998
1048
  }
999
1049
  },
1000
1050
  "home": {
@@ -1055,6 +1105,8 @@
1055
1105
  "whatsappLabel": "Easy Ordering",
1056
1106
  "whatsappTitle": "Order Directly via WhatsApp",
1057
1107
  "whatsappDesc": "Select your size, add to your bag, and send your order in one tap. Our team confirms availability within hours.",
1108
+ "whatsappCtaLabel": "Start a Conversation",
1109
+ "whatsappCtaUrl": "https://wa.me/94771234567?text=Hi%2C%20I'd%20like%20to%20order%3A%0A%0AI'm%20interested%20in%20VANTA%20footwear.%20Can%20you%20help%20me%20choose%20a%20pair%3F",
1058
1110
  "categoryLabel1": "Performance",
1059
1111
  "categoryLabel2": "Sneakers",
1060
1112
  "categoryLabel3": "Lifestyle",
@@ -133,6 +133,19 @@ function saveRecipeFromProject(projectDir, recipeName = 'custom-storefront', opt
133
133
  autoIndex: true,
134
134
  defaultCardPrefix: 'product',
135
135
  },
136
+ actionRules: {
137
+ splitActionAndLabel: true,
138
+ whatsappKeywords: ['whatsapp', 'wa.me', 'order via whatsapp', 'chat on whatsapp'],
139
+ actionFieldMapping: {
140
+ whatsapp: { urlField: 'whatsappCtaUrl', labelField: 'whatsappCtaLabel' },
141
+ order: { urlField: 'orderCtaUrl', labelField: 'orderCtaLabel' },
142
+ promo: { urlField: 'promoCtaUrl', labelField: 'promoCtaLabel' },
143
+ },
144
+ },
145
+ socialRules: {
146
+ platforms: ['instagram', 'facebook', 'twitter', 'tiktok', 'youtube', 'linkedin'],
147
+ targetPath: 'common.footer',
148
+ },
136
149
  sections: manifest.editorSchema?.sections || [],
137
150
  defaults: siteData.content || {},
138
151
  };
@@ -406,7 +406,94 @@ function transformFileContent(filePath, pageKey, extractedData, backupDir, proje
406
406
  return `<span data-preview-field-path="${pageKey}.${fieldKey}"${attrs}>{siteData?.content?.${pageKey}?.${fieldKey} || ${JSON.stringify(trimmed)}}</span>`;
407
407
  });
408
408
 
409
- // 9. Anchor Links: <a> (sensitive attribute handler)
409
+ // 9. Adaptive Action-Link Splitter & Social Media Handler (100% Fivora Contract Safe)
410
+ // 9a. WhatsApp & Direct Communication Links
411
+ const waRegex = /<a(\s+[^>]*?)href="([^"]*(?:wa\.me|whatsapp)[^"]*)"([^>]*)>([\s\S]*?)<\/a>/gi;
412
+ code = code.replace(waRegex, (match, preHref = '', href, postHref = '', innerContent) => {
413
+ if (preHref.includes('data-preview-field-path') || postHref.includes('data-preview-field-path')) {
414
+ return match;
415
+ }
416
+ const urlKey = 'whatsappCtaUrl';
417
+ const labelKey = 'whatsappCtaLabel';
418
+ extractedData[urlKey] = href;
419
+
420
+ // Extract text from innerContent
421
+ const textMatch = innerContent.match(/>([^<>{}]+)</) || [null, innerContent.replace(/<[^>]+>/g, '').trim()];
422
+ const text = (textMatch[1] || 'Order via WhatsApp').trim();
423
+ extractedData[labelKey] = text;
424
+
425
+ elementCount += 2;
426
+ fileModified = true;
427
+
428
+ // Wrap inner label cleanly in span if not already wrapped
429
+ let updatedInner = innerContent;
430
+ if (/<span(\s+[^>]*)?>([^<>{}]+)<\/span>/i.test(innerContent)) {
431
+ updatedInner = innerContent.replace(
432
+ /<span(\s+[^>]*)?>([^<>{}]+)<\/span>/i,
433
+ `<span data-preview-field-path="${pageKey}.${labelKey}"$1>{siteData?.content?.${pageKey}?.${labelKey} || ${JSON.stringify(text)}}</span>`
434
+ );
435
+ } else if (text && innerContent.includes(text)) {
436
+ updatedInner = innerContent.replace(
437
+ text,
438
+ `<span data-preview-field-path="${pageKey}.${labelKey}">{siteData?.content?.${pageKey}?.${labelKey} || ${JSON.stringify(text)}}</span>`
439
+ );
440
+ }
441
+
442
+ return `<a${preHref}href={siteData?.content?.${pageKey}?.${urlKey} || ${JSON.stringify(href)}} data-preview-field-path="${pageKey}.${urlKey}"${postHref}>${updatedInner}</a>`;
443
+ });
444
+
445
+ // 9b. Social Media Icon Links (Instagram, Facebook, TikTok, Twitter/X, YouTube, LinkedIn)
446
+ const socialRegex = /<a(\s+[^>]*?)href="([^"]*(?:instagram|facebook|tiktok|twitter|youtube|linkedin)\.com[^"]*)"([^>]*)>([\s\S]*?)<\/a>/gi;
447
+ code = code.replace(socialRegex, (match, preHref = '', href, postHref = '', innerContent) => {
448
+ if (preHref.includes('data-preview-field-path') || postHref.includes('data-preview-field-path')) {
449
+ return match;
450
+ }
451
+ const platMatch = href.match(/(instagram|facebook|tiktok|twitter|youtube|linkedin)\.com/i);
452
+ const platform = platMatch ? platMatch[1].toLowerCase() : 'social';
453
+ const fieldKey = `${platform}Url`;
454
+
455
+ extractedData[fieldKey] = href;
456
+ elementCount++;
457
+ fileModified = true;
458
+
459
+ return `<a${preHref}href={siteData?.content?.common?.footer?.${fieldKey} || siteData?.content?.${pageKey}?.${fieldKey} || ${JSON.stringify(href)}} data-preview-field-path="common.footer.${fieldKey}"${postHref}>${innerContent}</a>`;
460
+ });
461
+
462
+ // 9c. Button / CTA Action Links with Text (Split URL on <a> and Label on <span>)
463
+ const btnLinkRegex = /<a(\s+[^>]*?class(?:Name)?="[^"]*(?:btn|button|cta|action|rounded|bg-)[^"]*"[^>]*?)href="([^"]+)"([^>]*)>([\s\S]*?)<\/a>/gi;
464
+ code = code.replace(btnLinkRegex, (match, preHref = '', href, postHref = '', innerContent) => {
465
+ if (preHref.includes('data-preview-field-path') || postHref.includes('data-preview-field-path') || href.startsWith('#') || href.includes('javascript:')) {
466
+ return match;
467
+ }
468
+ const textOnly = innerContent.replace(/<[^>]+>/g, '').trim();
469
+ if (!textOnly || textOnly.length < 2) return match;
470
+
471
+ const rawUrlKey = toFieldKey(textOnly, 'ctaUrl', elementCount + 1);
472
+ const urlKey = getUniqueKey(rawUrlKey);
473
+ const labelKey = urlKey.replace(/Url$/, 'Label') || getUniqueKey('ctaLabel');
474
+
475
+ extractedData[urlKey] = href;
476
+ extractedData[labelKey] = textOnly;
477
+ elementCount += 2;
478
+ fileModified = true;
479
+
480
+ let updatedInner = innerContent;
481
+ if (/<span(\s+[^>]*)?>([^<>{}]+)<\/span>/i.test(innerContent)) {
482
+ updatedInner = innerContent.replace(
483
+ /<span(\s+[^>]*)?>([^<>{}]+)<\/span>/i,
484
+ `<span data-preview-field-path="${pageKey}.${labelKey}"$1>{siteData?.content?.${pageKey}?.${labelKey} || ${JSON.stringify(textOnly)}}</span>`
485
+ );
486
+ } else {
487
+ updatedInner = innerContent.replace(
488
+ textOnly,
489
+ `<span data-preview-field-path="${pageKey}.${labelKey}">{siteData?.content?.${pageKey}?.${labelKey} || ${JSON.stringify(textOnly)}}</span>`
490
+ );
491
+ }
492
+
493
+ return `<a${preHref}href={siteData?.content?.${pageKey}?.${urlKey} || ${JSON.stringify(href)}} data-preview-field-path="${pageKey}.${urlKey}"${postHref}>${updatedInner}</a>`;
494
+ });
495
+
496
+ // 9d. Standard Anchor Links with Plain Text
410
497
  const linkRegex = /<a(\s+[^>]*)?>([^<>{}]+)<\/a>/g;
411
498
  code = code.replace(linkRegex, (match, attrs = '', text) => {
412
499
  const trimmed = text.trim().replace(/\s+/g, ' ');
@@ -651,16 +738,32 @@ function generateTemplateData(projectDir, projectName, detectedPages, extractedB
651
738
  // If active recipe has defined sections, seed them
652
739
  if (activeRecipe && Array.isArray(activeRecipe.sections)) {
653
740
  for (const sec of activeRecipe.sections) {
654
- if (sec.id === 'common') continue;
741
+ if (sec.id === 'common') {
742
+ const commonSec = editorSections.find((s) => s.id === 'common');
743
+ if (commonSec && Array.isArray(sec.fields)) {
744
+ for (const rf of sec.fields) {
745
+ if (!commonSec.fields.some((f) => f.key === rf.key)) {
746
+ commonSec.fields.push(rf);
747
+ }
748
+ }
749
+ }
750
+ continue;
751
+ }
655
752
  editorSections.push(sec);
656
753
  }
657
754
  }
658
755
 
659
- // Seed recipe defaults if available
756
+ // Seed recipe defaults if available with deep merge for common/footer
660
757
  if (activeRecipe && activeRecipe.defaults) {
661
758
  for (const [secKey, secVal] of Object.entries(activeRecipe.defaults)) {
662
- if (!content[secKey]) content[secKey] = {};
663
- Object.assign(content[secKey], secVal);
759
+ if (!content[secKey]) {
760
+ content[secKey] = JSON.parse(JSON.stringify(secVal));
761
+ } else if (typeof secVal === 'object' && !Array.isArray(secVal)) {
762
+ content[secKey] = { ...secVal, ...content[secKey] };
763
+ if (secVal.footer && content[secKey].footer) {
764
+ content[secKey].footer = { ...secVal.footer, ...content[secKey].footer };
765
+ }
766
+ }
664
767
  }
665
768
  }
666
769