@deneb-ui/cli 2.0.22 → 2.0.24

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.
Files changed (52) hide show
  1. package/README.md +62 -114
  2. package/bin/index.js +55 -12
  3. package/package.json +25 -5
  4. package/src/arc/__fixtures__/next-app-basic/package.json +10 -0
  5. package/src/arc/__fixtures__/next-app-basic/src/app/globals.css +3 -0
  6. package/src/arc/__fixtures__/next-app-basic/src/app/layout.tsx +9 -0
  7. package/src/arc/__fixtures__/next-app-basic/src/app/page.tsx +11 -0
  8. package/src/arc/__fixtures__/next-app-basic/src/components/Header.tsx +13 -0
  9. package/src/arc/__fixtures__/next-app-basic/src/components/Hero.tsx +12 -0
  10. package/src/arc/__fixtures__/next-app-basic/src/components/PromoBanner.tsx +10 -0
  11. package/src/arc/__fixtures__/next-app-basic/tsconfig.json +12 -0
  12. package/src/arc/__fixtures__/next-app-storefront/components.json +14 -0
  13. package/src/arc/__fixtures__/next-app-storefront/package.json +22 -0
  14. package/src/arc/__fixtures__/next-app-storefront/src/app/about/page.tsx +13 -0
  15. package/src/arc/__fixtures__/next-app-storefront/src/app/globals.css +5 -0
  16. package/src/arc/__fixtures__/next-app-storefront/src/app/layout.tsx +19 -0
  17. package/src/arc/__fixtures__/next-app-storefront/src/app/page.tsx +13 -0
  18. package/src/arc/__fixtures__/next-app-storefront/src/components/Features.tsx +31 -0
  19. package/src/arc/__fixtures__/next-app-storefront/src/components/Hero.tsx +45 -0
  20. package/src/arc/__fixtures__/next-app-storefront/src/components/ProductGrid.tsx +49 -0
  21. package/src/arc/__fixtures__/next-app-storefront/src/components/SiteFooter.tsx +22 -0
  22. package/src/arc/__fixtures__/next-app-storefront/src/components/SiteHeader.tsx +21 -0
  23. package/src/arc/__fixtures__/next-app-storefront/src/components/ui/button.tsx +36 -0
  24. package/src/arc/__fixtures__/next-app-storefront/tsconfig.json +15 -0
  25. package/src/arc/__fixtures__/next-pages-basic/package.json +10 -0
  26. package/src/arc/__fixtures__/next-pages-basic/pages/_app.jsx +5 -0
  27. package/src/arc/__fixtures__/next-pages-basic/pages/contact.jsx +9 -0
  28. package/src/arc/__fixtures__/next-pages-basic/pages/index.jsx +11 -0
  29. package/src/arc/__fixtures__/next-pages-basic/styles/globals.css +9 -0
  30. package/src/arc/__tests__/arc.test.cjs +498 -0
  31. package/src/arc/adapters.cjs +184 -0
  32. package/src/arc/ast.cjs +339 -0
  33. package/src/arc/field-paths.cjs +165 -0
  34. package/src/arc/fivora-contract.cjs +521 -0
  35. package/src/arc/font-plan.cjs +65 -0
  36. package/src/arc/fs-utils.cjs +170 -0
  37. package/src/arc/index.cjs +627 -0
  38. package/src/arc/learning.cjs +211 -0
  39. package/src/arc/manifest.cjs +438 -0
  40. package/src/arc/next-config.cjs +279 -0
  41. package/src/arc/planner.cjs +252 -0
  42. package/src/arc/printer.cjs +183 -0
  43. package/src/arc/recipes-v2.cjs +49 -0
  44. package/src/arc/scanner.cjs +613 -0
  45. package/src/arc/semantic.cjs +651 -0
  46. package/src/arc/style-candidates.cjs +72 -0
  47. package/src/arc/transformer.cjs +677 -0
  48. package/src/arc/validator.cjs +173 -0
  49. package/src/arc/version.cjs +22 -0
  50. package/src/common/style-validation.ts +30 -0
  51. package/src/tools/deneb-fonts.cjs +114 -0
  52. package/src/tools/template-preview-focus-bridge.cjs +3 -3
@@ -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.1.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
+ };
@@ -0,0 +1,30 @@
1
+ import {
2
+ collectStyleTargetsFromHtml,
3
+ validateStyleTree,
4
+ type StyleValidationIssue,
5
+ } from '@deneb-ui/core';
6
+
7
+ import type { TemplateVisualEditingArtifact } from './template-visual-edit-contract';
8
+
9
+ export function validateTemplateStyleContract(input: {
10
+ siteData: unknown;
11
+ artifacts: TemplateVisualEditingArtifact[];
12
+ }): StyleValidationIssue[] {
13
+ const issues: StyleValidationIssue[] = [];
14
+ const htmlTargets = new Set<string>();
15
+
16
+ for (const artifact of input.artifacts) {
17
+ if (artifact.kind !== 'html') continue;
18
+ for (const target of collectStyleTargetsFromHtml(artifact.content)) {
19
+ htmlTargets.add(target);
20
+ }
21
+ }
22
+
23
+ const siteData =
24
+ input.siteData && typeof input.siteData === 'object' && !Array.isArray(input.siteData)
25
+ ? (input.siteData as Record<string, unknown>)
26
+ : null;
27
+ const styles = siteData?.styles;
28
+ issues.push(...validateStyleTree(styles, htmlTargets.size > 0 ? htmlTargets : undefined));
29
+ return issues;
30
+ }
@@ -0,0 +1,114 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ DENEB_FONT_REGISTRY,
5
+ DENEB_GOOGLE_FONT_COUNT,
6
+ listFontsByCategory,
7
+ normalizeFontId,
8
+ installProjectFonts,
9
+ } = (() => {
10
+ try {
11
+ return {
12
+ ...require('@deneb-ui/core'),
13
+ ...require('@deneb-ui/core/install-fonts'),
14
+ };
15
+ } catch {
16
+ return {
17
+ ...require('../../../../packages/deneb-core/dist/index.js'),
18
+ ...require('../../../../packages/deneb-core/dist/fonts/installProject.js'),
19
+ };
20
+ }
21
+ })();
22
+
23
+ function runFontsList(options = {}) {
24
+ if (options.json) {
25
+ console.log(
26
+ JSON.stringify(
27
+ { count: DENEB_FONT_REGISTRY.length, google: DENEB_GOOGLE_FONT_COUNT, grouped: listFontsByCategory() },
28
+ null,
29
+ 2,
30
+ ),
31
+ );
32
+ return 0;
33
+ }
34
+
35
+ console.log(
36
+ `\n\x1b[1mDENEB Font Catalog\x1b[0m — ${DENEB_FONT_REGISTRY.length} presets (${DENEB_GOOGLE_FONT_COUNT} on Google Fonts)\n`,
37
+ );
38
+ const grouped = listFontsByCategory();
39
+ for (const [category, fonts] of Object.entries(grouped)) {
40
+ console.log(`\x1b[36m${category}\x1b[0m`);
41
+ for (const font of fonts) {
42
+ const badge = font.googleFonts ? 'google' : font.substituteId ? `→ ${font.substituteId}` : 'system';
43
+ console.log(` ${font.id.padEnd(22)} ${font.label.padEnd(24)} \x1b[90m${badge}\x1b[0m`);
44
+ }
45
+ console.log('');
46
+ }
47
+ return 0;
48
+ }
49
+
50
+ function parseInstallArgs(argv) {
51
+ const fontIds = [];
52
+ let all = false;
53
+ let target = '.';
54
+ for (let i = 0; i < argv.length; i++) {
55
+ const arg = argv[i];
56
+ if (arg === '--all') all = true;
57
+ else if (arg === '--font' || arg === '-f') fontIds.push(normalizeFontId(argv[++i] || ''));
58
+ else if (arg.startsWith('--font=')) fontIds.push(normalizeFontId(arg.split('=')[1]));
59
+ else if (arg === '--json') continue;
60
+ else if (!arg.startsWith('-')) target = arg;
61
+ }
62
+ return { target, fontIds: fontIds.filter(Boolean), all };
63
+ }
64
+
65
+ function runFontsInstall(projectDirInput, args = []) {
66
+ const parsed = Array.isArray(args)
67
+ ? { target: projectDirInput, fontIds: parseInstallArgs(args).fontIds, all: parseInstallArgs(args).all }
68
+ : parseInstallArgs([projectDirInput, ...(args || [])]);
69
+
70
+ const result = installProjectFonts({
71
+ projectDir: parsed.target || projectDirInput || '.',
72
+ fontIds: parsed.fontIds,
73
+ all: parsed.all,
74
+ });
75
+ return result.ok ? 0 : 1;
76
+ }
77
+
78
+ function runFontsCommand(argv = []) {
79
+ const sub = argv[0];
80
+ const rest = argv.slice(1);
81
+ let json = rest.includes('--json');
82
+
83
+ if (sub === 'list') return runFontsList({ json });
84
+ if (sub === 'install') {
85
+ const parsed = parseInstallArgs(rest);
86
+ const result = installProjectFonts({
87
+ projectDir: parsed.target,
88
+ fontIds: parsed.fontIds,
89
+ all: parsed.all,
90
+ });
91
+ return result.ok ? 0 : 1;
92
+ }
93
+
94
+ console.log(`Usage: deneb fonts <command> [directory] [options]
95
+
96
+ Commands:
97
+ list Show all ${DENEB_FONT_REGISTRY.length} curated DENEB font presets
98
+ install [dir] Download @fontsource packages, write CSS, patch layout
99
+
100
+ Options:
101
+ --font <id> Install a specific font (repeatable)
102
+ --all Install all Google Fonts presets (~${DENEB_GOOGLE_FONT_COUNT} packages)
103
+ --json JSON output for list
104
+
105
+ Examples:
106
+ deneb fonts list
107
+ deneb fonts install .
108
+ deneb fonts install . --font inter --font playfair-display
109
+ deneb fonts install . --all
110
+ `);
111
+ return 1;
112
+ }
113
+
114
+ module.exports = { runFontsCommand, runFontsInstall, runFontsList };