@deneb-ui/cli 2.0.84 → 2.0.86

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.
@@ -44,16 +44,20 @@ function inferSection(context) {
44
44
  ['hero', /hero|banner|jumbotron/],
45
45
  ['navigation', /nav|menu|links/],
46
46
  ['feedback', /feedback|google-review/],
47
- ['testimonials', /testimonial/],
47
+ ['testimonials', /testimonial|review/],
48
+ ['featuredProducts', /featured|product-grid|catalog|goods|store/],
49
+ ['services', /service|offering/],
50
+ ['gallery', /gallery|portfolio|showcase/],
51
+ ['team', /team|member|staff|author/],
52
+ ['stats', /stat|counter|metric/],
48
53
  ['map', /map\b|location-map|google-map/],
49
54
  ['faq', /faq|accordion/],
50
55
  ['form', /form|booking|inquiry|registration/],
51
56
  ['contact', /contact|whatsapp|mailto/],
52
- ['featuredProducts', /featured|product-grid|collection/],
53
57
  ['newsletter', /newsletter|subscribe/],
54
58
  ['pricing', /pricing|plan/],
55
59
  ['features', /feature/],
56
- ['about', /about|story|brand/],
60
+ ['about', /about|story|brand|history/],
57
61
  ];
58
62
  for (const [name, re] of rules) {
59
63
  if (re.test(haystack)) return name;
@@ -163,16 +167,24 @@ function listActionCtaItemFieldNames() {
163
167
  return ['buttonLabel', 'buttonUrl'];
164
168
  }
165
169
 
166
- function classifyFieldType(kind, value) {
167
- if (kind === 'image') return 'image';
168
- if (kind === 'url') return 'url';
169
- if (kind === 'email' || (typeof value === 'string' && /^mailto:/i.test(value))) return 'email';
170
- if (kind === 'phone' || (typeof value === 'string' && /^(tel:|\+)/i.test(value))) return 'tel';
171
- if (kind === 'color') return 'text';
172
- if (kind === 'rating' || kind === 'number' || typeof value === 'number') return 'number';
173
- if (typeof value === 'boolean') return 'boolean';
174
- if (kind === 'textarea' || (typeof value === 'string' && value.length > 80)) return 'textarea';
175
- if (typeof value === 'string' && /\$|lkr|usd|rs\.?\s*\d/i.test(value)) return 'text';
170
+ function classifyFieldType(kind, value, fieldName = '') {
171
+ const name = String(fieldName || '').toLowerCase();
172
+ if (kind === 'image' || /image|thumbnail|avatar|picture|cover|photo/i.test(name)) return 'image';
173
+ if (kind === 'url' || /url|href|link|path$/i.test(name)) return 'url';
174
+ if (kind === 'email' || /email/i.test(name) || (typeof value === 'string' && /^mailto:/i.test(value))) return 'email';
175
+ if (kind === 'phone' || /phone|tel|whatsapp/i.test(name) || (typeof value === 'string' && /^(tel:|\+)/i.test(value))) return 'tel';
176
+ if (kind === 'color' || /color/i.test(name)) return 'text';
177
+ if (
178
+ kind === 'rating' ||
179
+ kind === 'number' ||
180
+ typeof value === 'number' ||
181
+ /price|amount|rating|count|score|stars|qty|quantity|discount|fee|cost/i.test(name)
182
+ ) {
183
+ if (typeof value === 'string' && /\$|lkr|usd|rs\.?\s*\d/i.test(value)) return 'text';
184
+ return typeof value === 'number' ? 'number' : 'text';
185
+ }
186
+ if (typeof value === 'boolean' || /enabled|visible|show|active/i.test(name)) return 'boolean';
187
+ if (kind === 'textarea' || (typeof value === 'string' && (value.length > 70 || value.includes('\n')))) return 'textarea';
176
188
  return 'text';
177
189
  }
178
190
 
@@ -119,6 +119,9 @@ function rel(projectDir, filePath) {
119
119
  }
120
120
 
121
121
  function homeDenebDir() {
122
+ if (process.env.DENEB_HOME) {
123
+ return path.resolve(process.env.DENEB_HOME);
124
+ }
122
125
  return path.join(os.homedir(), '.deneb');
123
126
  }
124
127
 
package/src/arc/index.cjs CHANGED
@@ -15,12 +15,14 @@ const path = require('path');
15
15
  const { ARC_NAME, ARC_VERSION, SCHEMA_VERSION, ENGINE_ID } = require('./version.cjs');
16
16
  const { walkFiles, isJsxFile, rel, copyFilePreserve, writeJson, readJsonSafe, findFirstExisting } = require('./fs-utils.cjs');
17
17
  const { scanProject, buildDependencyGraph, inferOwnerScope, resolvePageFileOnDisk } = require('./scanner.cjs');
18
+ const { buildProjectIR } = require('./ir-builder.cjs');
18
19
  const { analyzeFile, collectDesignSnapshot } = require('./semantic.cjs');
19
20
  const { planTransformations } = require('./planner.cjs');
20
21
  const { applyFilePlan, instrumentLayoutSource, instrumentPageKey, resolveSiteDataSpecifier, resolveSiteDataRuntimeSpecifier, rewriteRecursiveSiteDataContext, ensureJsonModule, sanitizeContradictoryMarkersInSource } = require('./transformer.cjs');
21
22
  const { applyResidualPass } = require('./residual.cjs');
22
23
  const { parseSource } = require('./ast.cjs');
23
24
  const { buildSiteDataAndManifest, writeDataBank, loadExistingData, countSchemaFields } = require('./manifest.cjs');
25
+ const { collectFontIdsFromSiteData } = require('./font-plan.cjs');
24
26
  const { validateAstFiles, validateContracts, designPreservationScore, coverageMetrics } = require('./validator.cjs');
25
27
  const { recordExperience, registryArchitecture } = require('./learning.cjs');
26
28
  const { matchRecipeV2 } = require('./recipes-v2.cjs');
@@ -46,6 +48,8 @@ const { classifyComponents } = require('./component-registry.cjs');
46
48
  const { checkAiReady, adaptComponent, generateDocsPage, loadEnv } = require('./ai-agent.cjs');
47
49
  const { checkGithubReady, createComponentPR } = require('./pr-agent.cjs');
48
50
  const { runAiEvaluatorPipeline } = require('./ai-evaluator.cjs');
51
+ const { explainFile } = require('./explain.cjs');
52
+ const { generateArcDiff } = require('./diff.cjs');
49
53
 
50
54
  function parseArcOptions(raw = {}) {
51
55
  return {
@@ -59,6 +63,8 @@ function parseArcOptions(raw = {}) {
59
63
  aiEnabled: Boolean(raw.aiEnabled),
60
64
  aiDryRun: Boolean(raw.aiDryRun),
61
65
  strict: Boolean(raw.strict),
66
+ verifyRuntime: Boolean(raw.verifyRuntime || raw['verify-runtime']),
67
+ previewUrl: raw.previewUrl || raw['preview-url'] || null,
62
68
  };
63
69
  }
64
70
 
@@ -219,7 +225,7 @@ function auditFivoraContract({ profile, siteData, manifest, inventory }) {
219
225
  };
220
226
  }
221
227
 
222
- function analyzeProjectFiles(profile, graph) {
228
+ function analyzeProjectFiles(profile, graph, ir) {
223
229
  const analyses = [];
224
230
  for (const relativeFile of profile.jsxFiles) {
225
231
  const abs = path.join(profile.root, relativeFile);
@@ -244,6 +250,7 @@ function analyzeProjectFiles(profile, graph) {
244
250
  graph,
245
251
  ownerScope,
246
252
  componentMeta,
253
+ ir,
247
254
  });
248
255
  analyses.push({
249
256
  ...result,
@@ -282,7 +289,8 @@ async function runDenebArcAsync(projectDir, projectName, options = {}) {
282
289
  mergeDetectedPages(profile, opts.detectedPages);
283
290
  printer.printProfile(profile);
284
291
  const graph = buildDependencyGraph(profile);
285
- const analyses = analyzeProjectFiles(profile, graph);
292
+ const ir = buildProjectIR(profile);
293
+ const analyses = analyzeProjectFiles(profile, graph, ir);
286
294
 
287
295
  const candidateCount = analyses.reduce(
288
296
  (n, a) => n + (a.candidates || []).filter((c) => c.kind !== 'decoration' && c.kind !== 'already-editable' && !c.skip).length,
@@ -375,7 +383,7 @@ async function runDenebArcAsync(projectDir, projectName, options = {}) {
375
383
  }
376
384
  }
377
385
 
378
- const result = runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt);
386
+ const result = runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt, ir);
379
387
 
380
388
  if (!opts.dryRun && result && result.outcome === 'success') {
381
389
  printer.printAiEvaluatorStart();
@@ -416,7 +424,8 @@ function runDenebArcSync(projectDir, projectName, options = {}) {
416
424
 
417
425
  printer.printProfile(profile);
418
426
  const graph = buildDependencyGraph(profile);
419
- const analyses = analyzeProjectFiles(profile, graph);
427
+ const ir = buildProjectIR(profile);
428
+ const analyses = analyzeProjectFiles(profile, graph, ir);
420
429
 
421
430
  const candidateCount = analyses.reduce(
422
431
  (n, a) => n + (a.candidates || []).filter((c) => c.kind !== 'decoration' && c.kind !== 'already-editable' && !c.skip).length,
@@ -428,7 +437,7 @@ function runDenebArcSync(projectDir, projectName, options = {}) {
428
437
  );
429
438
  printer.printScan(profile, graph, candidateCount, actionCount);
430
439
 
431
- return runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt);
440
+ return runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt, ir);
432
441
  }
433
442
 
434
443
  function runDenebArc(projectDir, projectName, options = {}) {
@@ -439,7 +448,7 @@ function runDenebArc(projectDir, projectName, options = {}) {
439
448
  return runDenebArcSync(projectDir, projectName, options);
440
449
  }
441
450
 
442
- function runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt) {
451
+ function runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt, ir) {
443
452
  const sourceAbs = profile.jsxFiles.map((f) => path.join(projectDir, f));
444
453
  const recipeMatch = matchRecipeV2(projectDir, profile, sourceAbs, opts.recipeName);
445
454
  if (recipeMatch.recipe) {
@@ -450,6 +459,7 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
450
459
  profile,
451
460
  analyses,
452
461
  recipe: recipeMatch.recipe,
462
+ ir,
453
463
  });
454
464
  printer.printPlan(plan);
455
465
 
@@ -585,6 +595,7 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
585
595
  usedPaths,
586
596
  componentName: analysis?.componentMeta?.name,
587
597
  role: analysis?.componentMeta?.role,
598
+ ir,
588
599
  });
589
600
  if (!residual.changed) continue;
590
601
  try {
@@ -744,6 +755,18 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
744
755
  uncoveredVisibleText: fivoraAudit.uncoveredVisibleText,
745
756
  });
746
757
 
758
+ const { validateRuntimeEditabilitySync } = require('./runtime-validator.cjs');
759
+ let runtimeVerification = null;
760
+ try {
761
+ runtimeVerification = validateRuntimeEditabilitySync({
762
+ projectDir,
763
+ siteData: dataBundle.siteData,
764
+ manifest: dataBundle.manifest,
765
+ });
766
+ } catch (err) {
767
+ runtimeVerification = { passed: false, error: err.message, runtimeEditabilityScore: 0 };
768
+ }
769
+
747
770
  const validation = {
748
771
  syntaxPassed,
749
772
  fivoraContractPassed: fivoraAudit.passed,
@@ -758,6 +781,7 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
758
781
  astFailures: astResults.filter((r) => !r.passed),
759
782
  transformFailures,
760
783
  designPreservation: design.score,
784
+ runtimeVerification,
761
785
  };
762
786
 
763
787
  const criticalFailure = !syntaxPassed || (contracts.actionCollisions > 0 && appliedCount === 0);
@@ -785,7 +809,11 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
785
809
  }
786
810
  }
787
811
  outcome = 'rolled-back';
788
- printer.printRollback('Strict Fivora contract failed');
812
+ const reasons = [];
813
+ if (!fivoraAudit.passed) reasons.push(`${fivoraAudit.errors.length} Fivora contract error(s)`);
814
+ if (fivoraAudit.uncoveredVisibleText.length > 0) reasons.push(`${fivoraAudit.uncoveredVisibleText.length} uncovered visible text node(s)`);
815
+ if (designFailure) reasons.push(`Design preservation score below threshold (${design.score} < 98)`);
816
+ printer.printRollback(`Strict Fivora contract failed: ${reasons.join(', ')}`);
789
817
  } else if (contractFailure) {
790
818
  outcome = 'contract-failed';
791
819
  } else if (designFailure) {
@@ -862,6 +890,7 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
862
890
  designPreservation: design.score,
863
891
  validation,
864
892
  outcome,
893
+ fontIds: collectFontIdsFromSiteData(dataBundle.siteData),
865
894
  };
866
895
 
867
896
  const report = buildReport({
@@ -886,6 +915,63 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
886
915
  writeJson(path.join(journalDir, 'validation.json'), validation);
887
916
  writeJson(path.join(projectDir, '.deneb', 'report.json'), report);
888
917
 
918
+ function classifyDiagnosticSeverity(item) {
919
+ if (item.blocking || /syntax|ast-invalid|fatal/i.test(item.reason || '')) {
920
+ return 'BLOCKING';
921
+ }
922
+ if (/contract|collision|unresolved-binding|failed/i.test(item.reason || '') || item.type === 'error') {
923
+ return 'ERROR';
924
+ }
925
+ if (/dynamic|uncovered|compound|complex|skip/i.test(item.reason || '') && !/icon|decorative|hidden|picture-source/i.test(item.reason || '')) {
926
+ return 'WARNING';
927
+ }
928
+ return 'INFO';
929
+ }
930
+
931
+ const unresolvedList = (plan.skipped || []).map((s) => ({
932
+ file: s.file,
933
+ loc: s.loc,
934
+ type: s.kind || 'unknown',
935
+ reason: s.reason || 'skipped',
936
+ confidence: s.confidence,
937
+ severity: classifyDiagnosticSeverity(s),
938
+ }));
939
+
940
+ const diagnosticsBySeverity = {
941
+ info: unresolvedList.filter((u) => u.severity === 'INFO').length,
942
+ warning: unresolvedList.filter((u) => u.severity === 'WARNING').length,
943
+ error: unresolvedList.filter((u) => u.severity === 'ERROR').length,
944
+ blocking: unresolvedList.filter((u) => u.severity === 'BLOCKING').length,
945
+ };
946
+
947
+ const conversionReport = {
948
+ runId,
949
+ timestamp: startedAt,
950
+ projectName: projectName || profile.packageName,
951
+ outcome,
952
+ scorecard: {
953
+ contractValidity: validation.fivoraContractPassed ? '100%' : 'Failed',
954
+ editabilityCoverage: `${coverage.visualCoverage != null ? coverage.visualCoverage : coverage.editableCoverage}%`,
955
+ visualBound: coverage.visualCovered != null ? `${coverage.visualCovered}/${coverage.visualRequired}` : null,
956
+ designPreservation: `${design.score}%`,
957
+ runtimeEditability: runtimeVerification ? `${runtimeVerification.runtimeEditabilityScore}%` : '100%',
958
+ },
959
+ runtimeVerification,
960
+ diagnosticsBySeverity,
961
+ unresolvedCount: unresolvedList.length,
962
+ unresolved: unresolvedList,
963
+ categories: {
964
+ text: { planned: plan.files.reduce((n, f) => n + f.transformations.filter((t) => /text|heading|label/.test(t.fieldType || '')).length, 0) },
965
+ images: { planned: plan.files.reduce((n, f) => n + f.transformations.filter((t) => t.fieldType === 'image').length, 0) },
966
+ links: { planned: plan.files.reduce((n, f) => n + f.transformations.filter((t) => t.fieldType === 'url').length, 0) },
967
+ collections: { planned: plan.files.reduce((n, f) => n + f.transformations.filter((t) => t.operation === 'collection-conversion').length, 0) },
968
+ backgrounds: { planned: plan.files.reduce((n, f) => n + f.transformations.filter((t) => t.operation === 'extract-tailwind-bg').length, 0) },
969
+ componentProps: { planned: plan.files.reduce((n, f) => n + f.transformations.filter((t) => t.operation === 'extract-prop' || t.operation === 'prop-flow-callsite').length, 0) },
970
+ },
971
+ };
972
+ writeJson(path.join(projectDir, 'deneb-conversion-report.json'), conversionReport);
973
+ writeJson(path.join(journalDir, 'conversion-report.json'), conversionReport);
974
+
889
975
  if (outcome === 'success') {
890
976
  printer.printSuccess();
891
977
  printer.printDeveloperNextSteps();
@@ -950,10 +1036,43 @@ function buildReport(args) {
950
1036
  };
951
1037
  }
952
1038
 
1039
+ function runTransactionalPipeline(targetDirInput = '.', options = {}) {
1040
+ const opts = { ...options, strict: options.strict ?? true };
1041
+ const projectDir = path.resolve(targetDirInput);
1042
+ try {
1043
+ const result = runArc(targetDirInput, opts);
1044
+ return {
1045
+ success: result.outcome === 'success',
1046
+ rolledBack: result.outcome === 'rolled-back',
1047
+ outcome: result.outcome,
1048
+ result,
1049
+ };
1050
+ } catch (err) {
1051
+ const runsDir = path.join(projectDir, '.deneb', 'backups');
1052
+ if (fs.existsSync(runsDir)) {
1053
+ const backups = fs.readdirSync(runsDir).sort();
1054
+ const latest = backups[backups.length - 1];
1055
+ if (latest) {
1056
+ restoreBackup(projectDir, path.join(runsDir, latest));
1057
+ }
1058
+ }
1059
+ return {
1060
+ success: false,
1061
+ rolledBack: true,
1062
+ outcome: 'rolled-back',
1063
+ error: err.message,
1064
+ };
1065
+ }
1066
+ }
1067
+
953
1068
  module.exports = {
954
1069
  runDenebArc,
1070
+ runTransactionalPipeline,
1071
+ explainFile,
1072
+ generateArcDiff,
955
1073
  parseArcOptions,
956
1074
  scanProject,
1075
+ auditFivoraContract,
957
1076
  ENGINE_ID,
958
1077
  ARC_VERSION,
959
1078
  };