@deneb-ui/cli 2.0.21 → 2.0.23
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 +62 -114
- package/bin/index.js +101 -207
- package/package.json +20 -5
- package/src/arc/__fixtures__/next-app-basic/package.json +10 -0
- package/src/arc/__fixtures__/next-app-basic/src/app/globals.css +3 -0
- package/src/arc/__fixtures__/next-app-basic/src/app/layout.tsx +9 -0
- package/src/arc/__fixtures__/next-app-basic/src/app/page.tsx +11 -0
- package/src/arc/__fixtures__/next-app-basic/src/components/Header.tsx +13 -0
- package/src/arc/__fixtures__/next-app-basic/src/components/Hero.tsx +12 -0
- package/src/arc/__fixtures__/next-app-basic/src/components/PromoBanner.tsx +10 -0
- package/src/arc/__fixtures__/next-app-basic/tsconfig.json +12 -0
- package/src/arc/__fixtures__/next-app-storefront/components.json +14 -0
- package/src/arc/__fixtures__/next-app-storefront/package.json +22 -0
- package/src/arc/__fixtures__/next-app-storefront/src/app/about/page.tsx +13 -0
- package/src/arc/__fixtures__/next-app-storefront/src/app/globals.css +5 -0
- package/src/arc/__fixtures__/next-app-storefront/src/app/layout.tsx +19 -0
- package/src/arc/__fixtures__/next-app-storefront/src/app/page.tsx +13 -0
- package/src/arc/__fixtures__/next-app-storefront/src/components/Features.tsx +31 -0
- package/src/arc/__fixtures__/next-app-storefront/src/components/Hero.tsx +45 -0
- package/src/arc/__fixtures__/next-app-storefront/src/components/ProductGrid.tsx +49 -0
- package/src/arc/__fixtures__/next-app-storefront/src/components/SiteFooter.tsx +22 -0
- package/src/arc/__fixtures__/next-app-storefront/src/components/SiteHeader.tsx +21 -0
- package/src/arc/__fixtures__/next-app-storefront/src/components/ui/button.tsx +36 -0
- package/src/arc/__fixtures__/next-app-storefront/tsconfig.json +15 -0
- package/src/arc/__fixtures__/next-pages-basic/package.json +10 -0
- package/src/arc/__fixtures__/next-pages-basic/pages/_app.jsx +5 -0
- package/src/arc/__fixtures__/next-pages-basic/pages/contact.jsx +9 -0
- package/src/arc/__fixtures__/next-pages-basic/pages/index.jsx +11 -0
- package/src/arc/__fixtures__/next-pages-basic/styles/globals.css +9 -0
- package/src/arc/__tests__/arc.test.cjs +458 -0
- package/src/arc/adapters.cjs +184 -0
- package/src/arc/ast.cjs +323 -0
- package/src/arc/field-paths.cjs +165 -0
- package/src/arc/fivora-contract.cjs +521 -0
- package/src/arc/fs-utils.cjs +170 -0
- package/src/arc/index.cjs +628 -0
- package/src/arc/learning.cjs +185 -0
- package/src/arc/manifest.cjs +421 -0
- package/src/arc/next-config.cjs +279 -0
- package/src/arc/planner.cjs +227 -0
- package/src/arc/printer.cjs +153 -0
- package/src/arc/recipes-v2.cjs +49 -0
- package/src/arc/scanner.cjs +613 -0
- package/src/arc/semantic.cjs +651 -0
- package/src/arc/transformer.cjs +646 -0
- package/src/arc/validator.cjs +173 -0
- package/src/arc/version.cjs +22 -0
- package/src/recipes/cosmetics-beauty-store.json +1097 -0
- package/src/recipes/electronics-gadgets-store.json +1080 -0
- package/src/recipes/fashion-apparel-store.json +1074 -0
- package/src/tools/deneb-doctor.cjs +646 -0
- package/src/tools/recipe-engine.cjs +30 -0
- package/src/tools/template-converter.cjs +19 -6
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { parseSource } = require('./ast.cjs');
|
|
6
|
+
const { collectDesignSnapshot } = require('./semantic.cjs');
|
|
7
|
+
const { getDeep } = require('./manifest.cjs');
|
|
8
|
+
const { walkFiles, isJsxFile, rel } = require('./fs-utils.cjs');
|
|
9
|
+
|
|
10
|
+
function validateAstFiles(files) {
|
|
11
|
+
const results = [];
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
try {
|
|
14
|
+
parseSource(fs.readFileSync(file.abs, 'utf8'), file.rel);
|
|
15
|
+
results.push({ file: file.rel, passed: true });
|
|
16
|
+
} catch (err) {
|
|
17
|
+
results.push({ file: file.rel, passed: false, error: err.message });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return results;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function extractFieldPathsFromCode(code) {
|
|
24
|
+
return [
|
|
25
|
+
...code.matchAll(/data-preview-field-path=["']([^"']+)["']/g),
|
|
26
|
+
].map((m) => m[1]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validateContracts(projectDir, siteData, manifest) {
|
|
30
|
+
const files = walkFiles(projectDir, { include: (_p, name) => isJsxFile(name) });
|
|
31
|
+
const fieldPaths = [];
|
|
32
|
+
let actionCollisions = 0;
|
|
33
|
+
let staticAncestorCollisions = 0;
|
|
34
|
+
let duplicateMarkers = 0;
|
|
35
|
+
const seen = new Map();
|
|
36
|
+
const orphans = [];
|
|
37
|
+
const missingSchema = [];
|
|
38
|
+
|
|
39
|
+
for (const abs of files) {
|
|
40
|
+
const code = fs.readFileSync(abs, 'utf8');
|
|
41
|
+
const relative = rel(projectDir, abs);
|
|
42
|
+
const paths = extractFieldPathsFromCode(code);
|
|
43
|
+
for (const fieldPath of paths) {
|
|
44
|
+
fieldPaths.push(fieldPath);
|
|
45
|
+
const prev = seen.get(fieldPath) || 0;
|
|
46
|
+
seen.set(fieldPath, prev + 1);
|
|
47
|
+
if (siteData?.content && getDeep(siteData.content, fieldPath) === undefined) {
|
|
48
|
+
orphans.push({ fieldPath, file: relative });
|
|
49
|
+
}
|
|
50
|
+
if (manifest && !isFieldInEditorSchema(manifest, fieldPath)) {
|
|
51
|
+
missingSchema.push({ fieldPath, file: relative });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const collisions = code.matchAll(/<a\s+[^>]*data-preview-field-path="[^"]*(?:Url|Link|Action)"[^>]*>([^<>{}\n]+)<\/a>/gi);
|
|
56
|
+
for (const ac of collisions) {
|
|
57
|
+
if (String(ac[1] || '').trim().length > 1) actionCollisions++;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (code.includes('data-preview-static') && code.includes('data-preview-field-path')) {
|
|
61
|
+
const blocks = code.matchAll(/<([a-zA-Z0-9_-]+)(\s+[^>]*data-preview-static[^>]*)>([\s\S]*?)<\/\1>/g);
|
|
62
|
+
for (const sb of blocks) {
|
|
63
|
+
if (sb[3].includes('data-preview-field-path')) staticAncestorCollisions++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const [, count] of seen) {
|
|
69
|
+
if (count > 40) duplicateMarkers++;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
fieldPaths: [...new Set(fieldPaths)],
|
|
74
|
+
actionCollisions,
|
|
75
|
+
staticAncestorCollisions,
|
|
76
|
+
duplicateMarkers,
|
|
77
|
+
orphans,
|
|
78
|
+
missingSchema,
|
|
79
|
+
contractPassed: actionCollisions === 0 && staticAncestorCollisions === 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isFieldInEditorSchema(manifest, fieldPath) {
|
|
84
|
+
const parts = String(fieldPath).split('.');
|
|
85
|
+
const sectionId = parts[0];
|
|
86
|
+
const fieldKey = parts.slice(1).join('.');
|
|
87
|
+
const section = (manifest.editorSchema?.sections || []).find((s) => s.id === sectionId || s.path === sectionId);
|
|
88
|
+
if (!section) return false;
|
|
89
|
+
|
|
90
|
+
function checkFields(fields, targetKey) {
|
|
91
|
+
if (!Array.isArray(fields)) return false;
|
|
92
|
+
for (const f of fields) {
|
|
93
|
+
if (f.key === targetKey) return true;
|
|
94
|
+
if (f.fields && targetKey.startsWith(f.key + '.')) {
|
|
95
|
+
const subKey = targetKey.substring(f.key.length + 1);
|
|
96
|
+
if (checkFields(f.fields, subKey)) return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return checkFields(section.fields, fieldKey);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function designPreservationScore(filePlans, afterFiles) {
|
|
105
|
+
let total = 0;
|
|
106
|
+
let matched = 0;
|
|
107
|
+
const details = [];
|
|
108
|
+
for (const plan of filePlans) {
|
|
109
|
+
if (!plan.designSnapshot || !afterFiles[plan.file]) continue;
|
|
110
|
+
let afterSnap;
|
|
111
|
+
try {
|
|
112
|
+
afterSnap = collectDesignSnapshot(afterFiles[plan.file]);
|
|
113
|
+
} catch {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const beforeC = plan.designSnapshot.classNames || [];
|
|
117
|
+
const afterC = afterSnap.classNames || [];
|
|
118
|
+
const beforeS = plan.designSnapshot.styles || [];
|
|
119
|
+
const afterS = afterSnap.styles || [];
|
|
120
|
+
total += beforeC.length + beforeS.length;
|
|
121
|
+
const classEqual = beforeC.length === afterC.length && beforeC.every((c, i) => c === afterC[i]);
|
|
122
|
+
const styleEqual = beforeS.length === afterS.length && beforeS.every((s, i) => s === afterS[i]);
|
|
123
|
+
matched += (classEqual ? beforeC.length : countOverlap(beforeC, afterC)) + (styleEqual ? beforeS.length : countOverlap(beforeS, afterS));
|
|
124
|
+
if (!classEqual || !styleEqual) {
|
|
125
|
+
details.push({ file: plan.file, classEqual, styleEqual });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (total === 0) return { score: 100, details, total: 0 };
|
|
129
|
+
return { score: Math.round((matched / total) * 1000) / 10, details, total, matched };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function countOverlap(a, b) {
|
|
133
|
+
const bag = new Map();
|
|
134
|
+
for (const item of b) bag.set(item, (bag.get(item) || 0) + 1);
|
|
135
|
+
let n = 0;
|
|
136
|
+
for (const item of a) {
|
|
137
|
+
const left = bag.get(item) || 0;
|
|
138
|
+
if (left > 0) {
|
|
139
|
+
n++;
|
|
140
|
+
bag.set(item, left - 1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return n;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function coverageMetrics({ analyses, plan, appliedCount, skippedDynamic, alreadyEditable }) {
|
|
147
|
+
const detected = analyses.reduce((n, a) => n + (a.candidates || []).filter((c) => c.kind !== 'decoration' && c.kind !== 'already-editable').length, 0);
|
|
148
|
+
const skippedLow = plan.skipped.filter((s) => s.reason === 'low-confidence' || (s.confidence || 0) < 0.6).length;
|
|
149
|
+
const transformed = appliedCount;
|
|
150
|
+
const coverage = detected === 0 ? 100 : Math.round((transformed / detected) * 1000) / 10;
|
|
151
|
+
return {
|
|
152
|
+
detectedEditableCandidates: detected,
|
|
153
|
+
safelyTransformed: transformed,
|
|
154
|
+
alreadyEditable,
|
|
155
|
+
skippedDynamic,
|
|
156
|
+
skippedLowConfidence: skippedLow,
|
|
157
|
+
editableCoverage: Math.min(100, coverage),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function idempotencyCheck(firstCode, secondCode) {
|
|
162
|
+
return firstCode === secondCode;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
validateAstFiles,
|
|
167
|
+
validateContracts,
|
|
168
|
+
designPreservationScore,
|
|
169
|
+
coverageMetrics,
|
|
170
|
+
isFieldInEditorSchema,
|
|
171
|
+
extractFieldPathsFromCode,
|
|
172
|
+
idempotencyCheck,
|
|
173
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ARC_NAME = 'Deneb ARC';
|
|
4
|
+
const ARC_FULL_NAME = 'Deneb Adaptive Refactoring Compiler';
|
|
5
|
+
const ARC_VERSION = '1.0.0';
|
|
6
|
+
const SCHEMA_VERSION = 2;
|
|
7
|
+
const ENGINE_ID = 'deneb-arc';
|
|
8
|
+
|
|
9
|
+
const CONFIDENCE = {
|
|
10
|
+
AUTO: 0.85,
|
|
11
|
+
VALIDATE: 0.6,
|
|
12
|
+
SKIP: 0.6,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
ARC_NAME,
|
|
17
|
+
ARC_FULL_NAME,
|
|
18
|
+
ARC_VERSION,
|
|
19
|
+
SCHEMA_VERSION,
|
|
20
|
+
ENGINE_ID,
|
|
21
|
+
CONFIDENCE,
|
|
22
|
+
};
|