@deneb-ui/cli 2.0.85 → 2.0.87

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
 
package/src/arc/index.cjs CHANGED
@@ -15,6 +15,7 @@ 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');
@@ -47,6 +48,8 @@ const { classifyComponents } = require('./component-registry.cjs');
47
48
  const { checkAiReady, adaptComponent, generateDocsPage, loadEnv } = require('./ai-agent.cjs');
48
49
  const { checkGithubReady, createComponentPR } = require('./pr-agent.cjs');
49
50
  const { runAiEvaluatorPipeline } = require('./ai-evaluator.cjs');
51
+ const { explainFile } = require('./explain.cjs');
52
+ const { generateArcDiff } = require('./diff.cjs');
50
53
 
51
54
  function parseArcOptions(raw = {}) {
52
55
  return {
@@ -60,6 +63,8 @@ function parseArcOptions(raw = {}) {
60
63
  aiEnabled: Boolean(raw.aiEnabled),
61
64
  aiDryRun: Boolean(raw.aiDryRun),
62
65
  strict: Boolean(raw.strict),
66
+ verifyRuntime: Boolean(raw.verifyRuntime || raw['verify-runtime']),
67
+ previewUrl: raw.previewUrl || raw['preview-url'] || null,
63
68
  };
64
69
  }
65
70
 
@@ -220,7 +225,7 @@ function auditFivoraContract({ profile, siteData, manifest, inventory }) {
220
225
  };
221
226
  }
222
227
 
223
- function analyzeProjectFiles(profile, graph) {
228
+ function analyzeProjectFiles(profile, graph, ir) {
224
229
  const analyses = [];
225
230
  for (const relativeFile of profile.jsxFiles) {
226
231
  const abs = path.join(profile.root, relativeFile);
@@ -245,6 +250,7 @@ function analyzeProjectFiles(profile, graph) {
245
250
  graph,
246
251
  ownerScope,
247
252
  componentMeta,
253
+ ir,
248
254
  });
249
255
  analyses.push({
250
256
  ...result,
@@ -283,7 +289,8 @@ async function runDenebArcAsync(projectDir, projectName, options = {}) {
283
289
  mergeDetectedPages(profile, opts.detectedPages);
284
290
  printer.printProfile(profile);
285
291
  const graph = buildDependencyGraph(profile);
286
- const analyses = analyzeProjectFiles(profile, graph);
292
+ const ir = buildProjectIR(profile);
293
+ const analyses = analyzeProjectFiles(profile, graph, ir);
287
294
 
288
295
  const candidateCount = analyses.reduce(
289
296
  (n, a) => n + (a.candidates || []).filter((c) => c.kind !== 'decoration' && c.kind !== 'already-editable' && !c.skip).length,
@@ -376,7 +383,7 @@ async function runDenebArcAsync(projectDir, projectName, options = {}) {
376
383
  }
377
384
  }
378
385
 
379
- const result = runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt);
386
+ const result = runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt, ir);
380
387
 
381
388
  if (!opts.dryRun && result && result.outcome === 'success') {
382
389
  printer.printAiEvaluatorStart();
@@ -417,7 +424,8 @@ function runDenebArcSync(projectDir, projectName, options = {}) {
417
424
 
418
425
  printer.printProfile(profile);
419
426
  const graph = buildDependencyGraph(profile);
420
- const analyses = analyzeProjectFiles(profile, graph);
427
+ const ir = buildProjectIR(profile);
428
+ const analyses = analyzeProjectFiles(profile, graph, ir);
421
429
 
422
430
  const candidateCount = analyses.reduce(
423
431
  (n, a) => n + (a.candidates || []).filter((c) => c.kind !== 'decoration' && c.kind !== 'already-editable' && !c.skip).length,
@@ -429,7 +437,7 @@ function runDenebArcSync(projectDir, projectName, options = {}) {
429
437
  );
430
438
  printer.printScan(profile, graph, candidateCount, actionCount);
431
439
 
432
- return runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt);
440
+ return runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt, ir);
433
441
  }
434
442
 
435
443
  function runDenebArc(projectDir, projectName, options = {}) {
@@ -440,7 +448,7 @@ function runDenebArc(projectDir, projectName, options = {}) {
440
448
  return runDenebArcSync(projectDir, projectName, options);
441
449
  }
442
450
 
443
- function runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt) {
451
+ function runArcTransformations(projectDir, projectName, opts, profile, graph, analyses, runId, startedAt, ir) {
444
452
  const sourceAbs = profile.jsxFiles.map((f) => path.join(projectDir, f));
445
453
  const recipeMatch = matchRecipeV2(projectDir, profile, sourceAbs, opts.recipeName);
446
454
  if (recipeMatch.recipe) {
@@ -451,6 +459,7 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
451
459
  profile,
452
460
  analyses,
453
461
  recipe: recipeMatch.recipe,
462
+ ir,
454
463
  });
455
464
  printer.printPlan(plan);
456
465
 
@@ -586,6 +595,7 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
586
595
  usedPaths,
587
596
  componentName: analysis?.componentMeta?.name,
588
597
  role: analysis?.componentMeta?.role,
598
+ ir,
589
599
  });
590
600
  if (!residual.changed) continue;
591
601
  try {
@@ -745,6 +755,18 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
745
755
  uncoveredVisibleText: fivoraAudit.uncoveredVisibleText,
746
756
  });
747
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
+
748
770
  const validation = {
749
771
  syntaxPassed,
750
772
  fivoraContractPassed: fivoraAudit.passed,
@@ -759,6 +781,7 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
759
781
  astFailures: astResults.filter((r) => !r.passed),
760
782
  transformFailures,
761
783
  designPreservation: design.score,
784
+ runtimeVerification,
762
785
  };
763
786
 
764
787
  const criticalFailure = !syntaxPassed || (contracts.actionCollisions > 0 && appliedCount === 0);
@@ -892,6 +915,63 @@ function runArcTransformations(projectDir, projectName, opts, profile, graph, an
892
915
  writeJson(path.join(journalDir, 'validation.json'), validation);
893
916
  writeJson(path.join(projectDir, '.deneb', 'report.json'), report);
894
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
+
895
975
  if (outcome === 'success') {
896
976
  printer.printSuccess();
897
977
  printer.printDeveloperNextSteps();
@@ -956,8 +1036,40 @@ function buildReport(args) {
956
1036
  };
957
1037
  }
958
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
+
959
1068
  module.exports = {
960
1069
  runDenebArc,
1070
+ runTransactionalPipeline,
1071
+ explainFile,
1072
+ generateArcDiff,
961
1073
  parseArcOptions,
962
1074
  scanProject,
963
1075
  auditFivoraContract,