@deneb-ui/cli 2.0.83 → 2.0.84

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.83",
3
+ "version": "2.0.84",
4
4
  "description": "Official DENEB CLI — scaffold, convert, validate, and package Fivora-ready storefront templates.",
5
5
  "bin": {
6
6
  "deneb": "bin/index.js",
@@ -49,7 +49,7 @@
49
49
  "license": "MIT",
50
50
  "dependencies": {
51
51
  "@babel/parser": "^7.28.0",
52
- "@deneb-ui/core": "^2.0.83",
52
+ "@deneb-ui/core": "^2.0.84",
53
53
  "@octokit/rest": "^22.0.1",
54
54
  "adm-zip": "^0.6.0",
55
55
  "dotenv": "^17.4.2",
package/src/arc/index.cjs CHANGED
@@ -613,6 +613,23 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
613
613
  }
614
614
  }
615
615
 
616
+ // Mandatory Post-Pass: Guarantee all files referencing siteData have useSiteData hook injected
617
+ const { healMissingSiteDataHooks } = require('./transformer.cjs');
618
+ for (const relativeFile of profile.jsxFiles || []) {
619
+ const abs = path.join(projectDir, relativeFile);
620
+ if (!fs.existsSync(abs)) continue;
621
+ const currentCode = fs.readFileSync(abs, 'utf8');
622
+ if (currentCode.includes('siteData')) {
623
+ const healed = healMissingSiteDataHooks(currentCode, relativeFile);
624
+ if (healed && healed !== currentCode) {
625
+ backupFile(projectDir, backupDir, abs);
626
+ fs.writeFileSync(abs, healed, 'utf8');
627
+ afterFiles[relativeFile] = healed;
628
+ if (!changedFiles.includes(relativeFile)) changedFiles.push(relativeFile);
629
+ }
630
+ }
631
+ }
632
+
616
633
  if (profile.framework === 'nextjs') {
617
634
  const before = findNextConfig(projectDir);
618
635
  if (before) backupFile(projectDir, backupDir, before.abs);
@@ -682,13 +699,39 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
682
699
  const alreadyEditable = analyses.filter((a) => a.alreadyEditable).length;
683
700
  const skippedDynamic = plan.skipped.filter((s) => /dynamic|api/.test(s.reason || '')).length;
684
701
 
685
- const fivoraAudit = auditFivoraContract({
702
+ let fivoraAudit = auditFivoraContract({
686
703
  profile,
687
704
  siteData: dataBundle.siteData,
688
705
  manifest: dataBundle.manifest,
689
706
  inventory: collectMarkerInventory(projectDir, profile, graph),
690
707
  });
691
708
 
709
+ // Auto-reconciliation: If fivoraAudit flagged unknown paths, auto-register them to controlOnlyPaths
710
+ if (!fivoraAudit.passed && fivoraAudit.errors?.length > 0) {
711
+ let manifestModified = false;
712
+ for (const err of fivoraAudit.errors) {
713
+ const unknownMatch = err.match(/references unknown path "([^"]+)"/);
714
+ if (unknownMatch) {
715
+ const unknownPath = unknownMatch[1];
716
+ dataBundle.manifest.visualEditing = dataBundle.manifest.visualEditing || {};
717
+ dataBundle.manifest.visualEditing.controlOnlyPaths = dataBundle.manifest.visualEditing.controlOnlyPaths || [];
718
+ if (!dataBundle.manifest.visualEditing.controlOnlyPaths.includes(unknownPath)) {
719
+ dataBundle.manifest.visualEditing.controlOnlyPaths.push(unknownPath);
720
+ manifestModified = true;
721
+ }
722
+ }
723
+ }
724
+ if (manifestModified) {
725
+ writeDataBank(projectDir, dataBundle.siteData, dataBundle.manifest);
726
+ fivoraAudit = auditFivoraContract({
727
+ profile,
728
+ siteData: dataBundle.siteData,
729
+ manifest: dataBundle.manifest,
730
+ inventory: collectMarkerInventory(projectDir, profile, graph),
731
+ });
732
+ }
733
+ }
734
+
692
735
  const coverage = coverageMetrics({
693
736
  analyses,
694
737
  plan,
@@ -108,6 +108,10 @@ const BASELINE_VERIFIED_FINGERPRINTS = {
108
108
  'empty-state-array-guard': { id: 'empty-state-array-guard', state: 'verified', successfulApplications: 50, failedApplications: 0 },
109
109
  'action-label-split': { id: 'action-label-split', state: 'verified', successfulApplications: 50, failedApplications: 0 },
110
110
  'section-overflow-clip': { id: 'section-overflow-clip', state: 'verified', successfulApplications: 50, failedApplications: 0 },
111
+ 'auto-injected-site-data-hook': { id: 'auto-injected-site-data-hook', state: 'verified', successfulApplications: 50, failedApplications: 0 },
112
+ 'hidden-preview-marker-strip': { id: 'hidden-preview-marker-strip', state: 'verified', successfulApplications: 50, failedApplications: 0 },
113
+ 'unknown-marker-auto-prune': { id: 'unknown-marker-auto-prune', state: 'verified', successfulApplications: 50, failedApplications: 0 },
114
+ 'hidden-element-strict-contract': { id: 'hidden-element-strict-contract', state: 'deprecated', successfulApplications: 0, failedApplications: 50 },
111
115
  };
112
116
 
113
117
  function loadFingerprintBoost(fingerprint) {
@@ -427,8 +427,16 @@ function applyResidualPass({ code, file, ownerScope, usedPaths, componentName, r
427
427
  return { code, changed: false, fields: [], applied: 0 };
428
428
  }
429
429
 
430
+ let finalCode = printSource(ast, code);
431
+ if (finalCode.includes('siteData')) {
432
+ try {
433
+ const { healMissingSiteDataHooks } = require('./transformer.cjs');
434
+ finalCode = healMissingSiteDataHooks(finalCode, file);
435
+ } catch {}
436
+ }
437
+
430
438
  return {
431
- code: printSource(ast, code),
439
+ code: finalCode,
432
440
  changed: true,
433
441
  fields,
434
442
  applied,
@@ -286,6 +286,34 @@ function hasStaticMarker(node) {
286
286
  return hasJsxAttribute(node, 'data-preview-static');
287
287
  }
288
288
 
289
+ function isElementOrParentHidden(pathNode) {
290
+ let current = pathNode;
291
+ while (current) {
292
+ const node = current.node || current.value || current;
293
+ if (node && (node.type === 'JSXElement' || node.type === 'JSXOpeningElement')) {
294
+ const attrs = (node.type === 'JSXElement' ? node.openingElement?.attributes : node.attributes) || [];
295
+ if (attrs.some((a) => a.type === 'JSXAttribute' && a.name && (
296
+ a.name.name === 'hidden' ||
297
+ (a.name.name === 'aria-hidden' && /true/.test(recast.print(a).code))
298
+ ))) {
299
+ return true;
300
+ }
301
+ const classAttr = attrs.find((a) => a.type === 'JSXAttribute' && a.name && (a.name.name === 'className' || a.name.name === 'class'));
302
+ if (classAttr) {
303
+ const rawClass = recast.print(classAttr).code;
304
+ if (/(?<![\w-])hidden(?![a-zA-Z0-9_-])/.test(rawClass)) return true;
305
+ }
306
+ const styleAttr = attrs.find((a) => a.type === 'JSXAttribute' && a.name && a.name.name === 'style');
307
+ if (styleAttr) {
308
+ const rawStyle = recast.print(styleAttr).code;
309
+ if (/(?:display\s*:\s*['"]none['"]|visibility\s*:\s*['"]hidden['"])/.test(rawStyle)) return true;
310
+ }
311
+ }
312
+ current = current.parentPath || current.parent;
313
+ }
314
+ return false;
315
+ }
316
+
289
317
  function parentNames(pathNode) {
290
318
  const names = [];
291
319
  let current = pathNode.parent;
@@ -523,6 +551,21 @@ function analyzeFile({ code, relativeFile, profile, graph, ownerScope, component
523
551
  return;
524
552
  }
525
553
 
554
+ if (isElementOrParentHidden(pathNode)) {
555
+ candidates.push({
556
+ loc,
557
+ tag: name,
558
+ kind: 'decoration',
559
+ confidence: 0.1,
560
+ skip: true,
561
+ reason: 'hidden-element-strict-contract',
562
+ file: relativeFile,
563
+ ownerScope,
564
+ });
565
+ this.traverse(pathNode);
566
+ return;
567
+ }
568
+
526
569
  const importSource = imports[name.split('.')[0]];
527
570
  const recognition = recognizeWithAdapters(node, { profile, imports }, adapters);
528
571
  const mapInfo = inMapCallback(pathNode);
@@ -1537,7 +1537,7 @@ function sanitizeContradictoryMarkers(ast) {
1537
1537
  }
1538
1538
 
1539
1539
  function sanitizeContradictoryMarkersInSource(code, relativeFile) {
1540
- if (!code.includes('data-preview-static') && !code.includes('data-preview-field-path') && !code.includes('overflow-hidden')) {
1540
+ if (!code.includes('data-preview-static') && !code.includes('data-preview-field-path') && !code.includes('data-preview-list-path') && !code.includes('data-preview-item-path') && !code.includes('overflow-hidden') && !code.includes('hidden')) {
1541
1541
  return { code, updated: false };
1542
1542
  }
1543
1543
  let ast;
@@ -1720,10 +1720,15 @@ function healHiddenPreviewMarkers(ast) {
1720
1720
  if (expr.type === 'TemplateLiteral') classText = (expr.quasis || []).map((q) => q.value.cooked || q.value.raw || '').join(' ');
1721
1721
  }
1722
1722
  }
1723
- const hiddenAttr = attrs.some(
1724
- (attr) => attr.type === 'JSXAttribute' && attr.name && (attr.name.name === 'hidden' || (attr.name.name === 'aria-hidden' && recast.print(attr).code.includes('true')))
1725
- );
1726
- if (hiddenAttr || /(^|\s)hidden(\s|$)/.test(classText)) {
1723
+ const isHidden =
1724
+ attrs.some((attr) => attr.type === 'JSXAttribute' && attr.name && (
1725
+ attr.name.name === 'hidden' ||
1726
+ (attr.name.name === 'aria-hidden' && /true/.test(recast.print(attr).code))
1727
+ )) ||
1728
+ /(?<![\w-])hidden(?![a-zA-Z0-9_-])/.test(classText) ||
1729
+ attrs.some((attr) => attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'style' && /(?:display\s*:\s*['"]none['"]|visibility\s*:\s*['"]hidden['"])/.test(recast.print(attr).code));
1730
+
1731
+ if (isHidden) {
1727
1732
  pathNode.node.attributes = attrs.filter(
1728
1733
  (attr) =>
1729
1734
  !(
@@ -201,6 +201,7 @@ function runDoctor(targetDirInput = '.', options = {}) {
201
201
  // =========================================================================
202
202
  if (!isJson) console.log('\n\x1b[1m[2/7] Project Package Configuration:\x1b[0m');
203
203
  const suite2 = 'Package Configuration';
204
+ const sourceFiles = findSourceFiles(path.join(targetDir, 'src'));
204
205
 
205
206
  const pkgPath = path.join(targetDir, 'package.json');
206
207
  let pkg = null;
@@ -288,6 +289,20 @@ function runDoctor(targetDirInput = '.', options = {}) {
288
289
  addCheck(suite2, 'err', 'Platform Engine Compatibility', 'Detected packages requiring Node >=22. Run "deneb doctor --fix" to inject Node 20 overrides.', { code: 'DNB-ENG-001' }) //, 'Detected packages requiring Node >=22. Run "deneb doctor --fix" to inject Node 20 overrides.');
289
290
  }
290
291
 
292
+ // Pre-heal missing useSiteData hooks before tsc runs when --fix is enabled
293
+ if (shouldFix) {
294
+ for (const file of sourceFiles) {
295
+ if (!/\.(tsx|jsx|ts|js)$/.test(file)) continue;
296
+ let c = fs.readFileSync(file, 'utf-8');
297
+ if (c.includes('siteData')) {
298
+ const healed = healMissingSiteDataHooks(c, file);
299
+ if (healed && healed !== c) {
300
+ fs.writeFileSync(file, healed, 'utf8');
301
+ }
302
+ }
303
+ }
304
+ }
305
+
291
306
  // Check TypeScript compilation preflight
292
307
  const tsconfigPath = path.join(targetDir, 'tsconfig.json');
293
308
  if (fs.existsSync(tsconfigPath)) {
@@ -480,7 +495,7 @@ function runDoctor(targetDirInput = '.', options = {}) {
480
495
  }
481
496
 
482
497
  // Scan all source files for visual editing contract compliance
483
- const sourceFiles = findSourceFiles(path.join(targetDir, 'src'));
498
+ // (sourceFiles already loaded)
484
499
  const foundFieldPaths = new Set();
485
500
  const orphanPaths = [];
486
501
  const missingInSchema = [];
@@ -869,7 +884,7 @@ function runDoctor(targetDirInput = '.', options = {}) {
869
884
  for (const file of sourceFiles) {
870
885
  if (!/\.(tsx|jsx|ts|js)$/.test(file)) continue;
871
886
  let c = fs.readFileSync(file, 'utf-8');
872
- if (c.includes('siteData') && (c.includes('useSiteData') || c.includes('@deneb-ui/ui'))) {
887
+ if (c.includes('siteData')) {
873
888
  const healed = healMissingSiteDataHooks(c, file);
874
889
  const norm = (s) => s.replace(/\r\n/g, '\n');
875
890
  if (healed && norm(healed) !== norm(c)) {