@deneb-ui/cli 2.0.23 → 2.0.25
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/bin/index.js +18 -0
- package/package.json +6 -1
- package/src/arc/__tests__/arc.test.cjs +46 -6
- package/src/arc/ast.cjs +17 -1
- package/src/arc/font-plan.cjs +65 -0
- package/src/arc/index.cjs +5 -6
- package/src/arc/learning.cjs +26 -0
- package/src/arc/manifest.cjs +17 -0
- package/src/arc/planner.cjs +26 -1
- package/src/arc/printer.cjs +32 -2
- package/src/arc/style-candidates.cjs +72 -0
- package/src/arc/transformer.cjs +57 -1
- package/src/arc/version.cjs +1 -1
- package/src/common/style-validation.ts +30 -0
- package/src/tools/deneb-fonts.cjs +114 -0
- package/src/tools/template-preview-focus-bridge.cjs +3 -3
package/src/arc/transformer.cjs
CHANGED
|
@@ -17,6 +17,8 @@ const {
|
|
|
17
17
|
jsxPreviewAttr,
|
|
18
18
|
jsxTemplatePathAttr,
|
|
19
19
|
wrapTextInEditableSpan,
|
|
20
|
+
jsxStyleAttrs,
|
|
21
|
+
ensureStyleAttrs,
|
|
20
22
|
b,
|
|
21
23
|
} = require('./ast.cjs');
|
|
22
24
|
const { toPosix } = require('./fs-utils.cjs');
|
|
@@ -85,6 +87,7 @@ function splitActionChildren(node, labelField, labelFallback) {
|
|
|
85
87
|
if (!child) continue;
|
|
86
88
|
if (child.type === 'JSXElement' && getJsxName(child) === 'span' && !hasJsxAttribute(child, 'data-preview-field-path')) {
|
|
87
89
|
ensurePreviewPath(child, labelField);
|
|
90
|
+
ensureStyleAttrs(child, labelField, 'text');
|
|
88
91
|
replaceTextChildren(child, labelField, labelFallback, 'text');
|
|
89
92
|
nextChildren.push(child);
|
|
90
93
|
wrapped = true;
|
|
@@ -146,10 +149,21 @@ function applyTransformToElement(pathNode, transform) {
|
|
|
146
149
|
}
|
|
147
150
|
if (transform.operation === 'extract-text') {
|
|
148
151
|
ensurePreviewPath(node, transform.field);
|
|
152
|
+
ensureStyleAttrs(node, transform.field, inferButtonKind(transform.tag));
|
|
149
153
|
replaceTextChildren(node, transform.field, transform.fallback, transform.fieldType || 'text');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (transform.operation === 'style-bind') {
|
|
157
|
+
if (transform.styleKind === 'grid' || transform.styleKind === 'card') return;
|
|
158
|
+
ensureStyleAttrs(node, transform.stylePath, transform.styleKind || 'text');
|
|
150
159
|
}
|
|
151
160
|
}
|
|
152
161
|
|
|
162
|
+
function inferButtonKind(tag) {
|
|
163
|
+
if (tag === 'button' || tag === 'Button') return 'button';
|
|
164
|
+
return 'text';
|
|
165
|
+
}
|
|
166
|
+
|
|
153
167
|
/**
|
|
154
168
|
* Replaces literal text children with an editable <span>, leaving the container
|
|
155
169
|
* element and every one of its classes untouched.
|
|
@@ -210,6 +224,14 @@ function applyCollectionTransform(ast, transform) {
|
|
|
210
224
|
jsxTemplatePathAttr('data-preview-item-path', listPath, indexName)
|
|
211
225
|
);
|
|
212
226
|
}
|
|
227
|
+
if (!hasJsxAttribute(itemRoot.node, 'data-preview-style-target')) {
|
|
228
|
+
itemRoot.node.openingElement.attributes.push(
|
|
229
|
+
jsxTemplatePathAttr('data-preview-style-target', listPath, indexName, '.card')
|
|
230
|
+
);
|
|
231
|
+
itemRoot.node.openingElement.attributes.push(
|
|
232
|
+
b.jsxAttribute(b.jsxIdentifier('data-preview-style-type'), b.stringLiteral('card'))
|
|
233
|
+
);
|
|
234
|
+
}
|
|
213
235
|
|
|
214
236
|
markItemFields(callback, { listPath, binding, indexName });
|
|
215
237
|
|
|
@@ -219,6 +241,14 @@ function applyCollectionTransform(ast, transform) {
|
|
|
219
241
|
b.jsxAttribute(b.jsxIdentifier('data-preview-list-path'), b.stringLiteral(listPath))
|
|
220
242
|
);
|
|
221
243
|
}
|
|
244
|
+
if (container && !hasJsxAttribute(container, 'data-preview-style-target')) {
|
|
245
|
+
container.openingElement.attributes.push(
|
|
246
|
+
b.jsxAttribute(b.jsxIdentifier('data-preview-style-target'), b.stringLiteral(`${listPath}.grid`))
|
|
247
|
+
);
|
|
248
|
+
container.openingElement.attributes.push(
|
|
249
|
+
b.jsxAttribute(b.jsxIdentifier('data-preview-style-type'), b.stringLiteral('grid'))
|
|
250
|
+
);
|
|
251
|
+
}
|
|
222
252
|
|
|
223
253
|
return bindArrayDeclaration(ast, mapCall, listPath);
|
|
224
254
|
}
|
|
@@ -425,6 +455,7 @@ function applyFilePlan(filePlan, profile) {
|
|
|
425
455
|
'extract-text',
|
|
426
456
|
'wrap-text-span',
|
|
427
457
|
'collection-conversion',
|
|
458
|
+
'style-bind',
|
|
428
459
|
]);
|
|
429
460
|
|
|
430
461
|
// Collections run first: they rewrite the array declaration and add an index
|
|
@@ -549,11 +580,36 @@ function inferPageKey(relativeFile) {
|
|
|
549
580
|
return 'home';
|
|
550
581
|
}
|
|
551
582
|
|
|
583
|
+
function ensureHtmlBodyHydration(ast) {
|
|
584
|
+
recast.types.visit(ast, {
|
|
585
|
+
visitJSXOpeningElement(pathNode) {
|
|
586
|
+
const name = pathNode.node.name;
|
|
587
|
+
const tag = name && name.type === 'JSXIdentifier' ? name.name : '';
|
|
588
|
+
if (tag === 'html' || tag === 'body') {
|
|
589
|
+
const has = (pathNode.node.attributes || []).some(
|
|
590
|
+
(attr) => attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'suppressHydrationWarning',
|
|
591
|
+
);
|
|
592
|
+
if (!has) {
|
|
593
|
+
pathNode.node.attributes.push(b.jsxAttribute(b.jsxIdentifier('suppressHydrationWarning')));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
this.traverse(pathNode);
|
|
597
|
+
},
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
552
601
|
function instrumentLayoutSource(code, siteDataImport) {
|
|
553
602
|
if (/SiteDataProvider|DenebDataProvider/.test(code)) {
|
|
554
|
-
|
|
603
|
+
if (/suppressHydrationWarning/.test(code)) {
|
|
604
|
+
return { code, updated: false };
|
|
605
|
+
}
|
|
606
|
+
const ast = parseSource(code, 'layout.tsx');
|
|
607
|
+
ensureHtmlBodyHydration(ast);
|
|
608
|
+
return { code: printSource(ast, code), updated: true };
|
|
555
609
|
}
|
|
610
|
+
|
|
556
611
|
const ast = parseSource(code, 'layout.tsx');
|
|
612
|
+
ensureHtmlBodyHydration(ast);
|
|
557
613
|
ensureImport(ast, '@deneb-ui/ui', ['SiteDataProvider']);
|
|
558
614
|
ensureDefaultImport(ast, siteDataImport, 'initialSiteData');
|
|
559
615
|
|
package/src/arc/version.cjs
CHANGED
|
@@ -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 };
|