@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.
@@ -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
- return { code, updated: false };
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
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const ARC_NAME = 'Deneb ARC';
4
4
  const ARC_FULL_NAME = 'Deneb Adaptive Refactoring Compiler';
5
- const ARC_VERSION = '1.0.0';
5
+ const ARC_VERSION = '1.1.0';
6
6
  const SCHEMA_VERSION = 2;
7
7
  const ENGINE_ID = 'deneb-arc';
8
8
 
@@ -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 };