@deneb-ui/cli 2.0.54 → 2.0.55
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 +7 -0
- package/package.json +5 -2
- package/src/arc/fivora-contract.cjs +10 -0
- package/src/arc/manifest.cjs +78 -1
- package/src/arc/transformer.cjs +67 -0
- package/src/platform/platform-contract.json +39 -0
- package/src/recipes/mobile-shop.json +2651 -0
- package/src/tools/deneb-doctor.cjs +210 -63
- package/src/tools/template-preview-focus-bridge.cjs +1 -1
package/bin/index.js
CHANGED
|
@@ -720,6 +720,13 @@ async function initProject(targetInput, options = {}) {
|
|
|
720
720
|
addedCount++;
|
|
721
721
|
}
|
|
722
722
|
}
|
|
723
|
+
// Ensure Node 20 LTS platform engine compatibility
|
|
724
|
+
pkg.overrides = pkg.overrides || {};
|
|
725
|
+
if (!pkg.overrides['content-type']) {
|
|
726
|
+
pkg.overrides['content-type'] = '2.1.0';
|
|
727
|
+
pkg.overrides['@octokit/request'] = { 'content-type': '2.1.0' };
|
|
728
|
+
}
|
|
729
|
+
|
|
723
730
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
724
731
|
if (addedCount > 0) {
|
|
725
732
|
console.log(`\x1b[32m✔ Configured\x1b[0m DENEB scripts in package.json (lab, validate, zip, validate-and-zip, package:template, update:deneb)`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deneb-ui/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.55",
|
|
4
4
|
"description": "Official DENEB CLI — scaffold, convert, validate, and package Fivora-ready storefront templates.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"deneb": "bin/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"license": "MIT",
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@babel/parser": "^7.28.0",
|
|
52
|
-
"@deneb-ui/core": "^2.0.
|
|
52
|
+
"@deneb-ui/core": "^2.0.55",
|
|
53
53
|
"@octokit/rest": "^22.0.1",
|
|
54
54
|
"adm-zip": "^0.6.0",
|
|
55
55
|
"dotenv": "^17.4.2",
|
|
@@ -58,5 +58,8 @@
|
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"esbuild": "^0.28.2",
|
|
60
60
|
"typescript": "^5.9.3"
|
|
61
|
+
},
|
|
62
|
+
"overrides": {
|
|
63
|
+
"content-type": "2.1.0"
|
|
61
64
|
}
|
|
62
65
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const PLATFORM_CONTRACT = require('../platform/platform-contract.json');
|
|
4
|
+
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* Faithful port of the Fivora strict visual-editing contract rules that the
|
|
5
8
|
* platform ingest pipeline applies (backend/src/common/template-visual-edit-contract.ts).
|
|
@@ -64,7 +67,14 @@ function pathsOverlap(left, right) {
|
|
|
64
67
|
return wildcardPath(left) === wildcardPath(right);
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
function isPlatformControlled(path) {
|
|
71
|
+
return (PLATFORM_CONTRACT.platformControlledPaths || []).some((platformPath) =>
|
|
72
|
+
platformPath.includes('[*]') ? wildcardPath(path) === platformPath : path === platformPath
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
function isControlOnly(path, controlOnlyPaths) {
|
|
77
|
+
if (isPlatformControlled(path)) return true;
|
|
68
78
|
return (controlOnlyPaths || []).some((controlPath) =>
|
|
69
79
|
controlPath.includes('[*]') ? wildcardPath(path) === controlPath : path === controlPath
|
|
70
80
|
);
|
package/src/arc/manifest.cjs
CHANGED
|
@@ -5,6 +5,8 @@ const { readJsonSafe, writeJson, deepMerge, isPlainObject } = require('./fs-util
|
|
|
5
5
|
const { ARC_VERSION, SCHEMA_VERSION } = require('./version.cjs');
|
|
6
6
|
const { collectFontIdsFromSiteData, applyFontTheme } = require('./font-plan.cjs');
|
|
7
7
|
const { humanLabel, classifyFieldType } = require('./field-paths.cjs');
|
|
8
|
+
const PLATFORM_CONTRACT = require('../platform/platform-contract.json');
|
|
9
|
+
|
|
8
10
|
const {
|
|
9
11
|
enumerateContentPaths,
|
|
10
12
|
canonicalizeMarkerPath,
|
|
@@ -294,9 +296,16 @@ function baseContent(projectName, routes) {
|
|
|
294
296
|
* data-preview-field-path marker or be declared control-only. Control-only is
|
|
295
297
|
* reserved for ids, internal flags, and the unrendered merchant baseline — not
|
|
296
298
|
* as a dump for fields ARC planned but failed to bind.
|
|
299
|
+
*
|
|
300
|
+
* PLATFORM_CONTROLLED_PATHS: paths the Fivora AI/platform writes that templates
|
|
301
|
+
* must never render as inline editable HTML. Loaded from platform-contract.json
|
|
302
|
+
* so all templates benefit automatically without any template-side declaration.
|
|
297
303
|
*/
|
|
298
304
|
const BASELINE_CONTROL_ONLY = /^(common\.(websiteTitle|shortDescription|logoUrl|headerCtaLabel|copyright|navLabels(\.[^.]+)?|business(\.[^.]+)*))$/;
|
|
299
305
|
const SYSTEM_FIELD = /(^|\.)(id|key|slug|internalId|sku|_id)$/i;
|
|
306
|
+
const PLATFORM_CONTROLLED_PATHS = new Set(
|
|
307
|
+
(PLATFORM_CONTRACT.platformControlledPaths || []).map(wildcardPath)
|
|
308
|
+
);
|
|
300
309
|
|
|
301
310
|
function slimListItems(items, itemFields) {
|
|
302
311
|
const keys = (itemFields || []).map((field) => field.key).filter(Boolean);
|
|
@@ -314,7 +323,56 @@ function slimListItems(items, itemFields) {
|
|
|
314
323
|
}
|
|
315
324
|
|
|
316
325
|
function isAllowedControlOnly(path) {
|
|
317
|
-
return
|
|
326
|
+
return (
|
|
327
|
+
BASELINE_CONTROL_ONLY.test(path) ||
|
|
328
|
+
SYSTEM_FIELD.test(path) ||
|
|
329
|
+
PLATFORM_CONTROLLED_PATHS.has(wildcardPath(path))
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Ensures platform-managed editorSchema sections exist. These sections are required
|
|
335
|
+
* by the strict contract validator when their sub-paths appear in controlOnlyPaths.
|
|
336
|
+
* Template developers must never need to add these manually.
|
|
337
|
+
*/
|
|
338
|
+
function ensurePlatformSections(sections) {
|
|
339
|
+
// additionalPages: rendered visually by PlatformAdditionalPages component.
|
|
340
|
+
// label is editable; id and route are platform-controlled.
|
|
341
|
+
if (!sections.find((s) => s.id === 'additionalPages')) {
|
|
342
|
+
sections.push({
|
|
343
|
+
id: 'additionalPages',
|
|
344
|
+
path: 'additionalPages',
|
|
345
|
+
type: 'list',
|
|
346
|
+
label: 'Additional Pages',
|
|
347
|
+
minItems: 0,
|
|
348
|
+
maxItems: 20,
|
|
349
|
+
fields: [
|
|
350
|
+
{ key: 'label', type: 'text', label: 'Page label', required: true },
|
|
351
|
+
{ key: 'id', type: 'text', label: 'Page ID' },
|
|
352
|
+
{ key: 'route', type: 'text', label: 'Page route' },
|
|
353
|
+
],
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// __fivoraIntake: AI intake data written by the platform. Never rendered in
|
|
358
|
+
// template HTML — always control-only. Section required so sub-paths can be
|
|
359
|
+
// declared in controlOnlyPaths without "unknown path" errors.
|
|
360
|
+
if (!sections.find((s) => s.id === '__fivoraIntake')) {
|
|
361
|
+
sections.push({
|
|
362
|
+
id: '__fivoraIntake',
|
|
363
|
+
path: '__fivoraIntake',
|
|
364
|
+
type: 'object',
|
|
365
|
+
label: 'AI Intake (Platform Managed)',
|
|
366
|
+
fields: [
|
|
367
|
+
{ key: 'tone', type: 'text', label: 'Brand tone' },
|
|
368
|
+
{ key: 'outputLanguage', type: 'text', label: 'Output language' },
|
|
369
|
+
{ key: 'businessSummary', type: 'textarea', label: 'Business summary' },
|
|
370
|
+
{ key: 'additionalBusinessDetails', type: 'textarea', label: 'Additional business details' },
|
|
371
|
+
{ key: 'referenceWebsiteUrl', type: 'url', label: 'Reference website URL' },
|
|
372
|
+
{ key: 'guidanceNotes', type: 'object', label: 'AI Guidance Notes' },
|
|
373
|
+
],
|
|
374
|
+
});
|
|
375
|
+
}
|
|
318
376
|
}
|
|
319
377
|
|
|
320
378
|
function computeControlOnlyPaths(content, boundPaths, declared = []) {
|
|
@@ -322,6 +380,17 @@ function computeControlOnlyPaths(content, boundPaths, declared = []) {
|
|
|
322
380
|
const bound = new Set([...(boundPaths || [])].map(wildcardPath));
|
|
323
381
|
const controlOnly = new Set();
|
|
324
382
|
|
|
383
|
+
// Auto-inject all platform-contract paths that exist in site data content —
|
|
384
|
+
// templates never need to declare these manually.
|
|
385
|
+
for (const platformPath of PLATFORM_CONTRACT.platformControlledPaths || []) {
|
|
386
|
+
const canonical = canonicalizeMarkerPath(platformPath);
|
|
387
|
+
if (!canonical) continue;
|
|
388
|
+
const known = [...inventory.fieldPatterns, ...inventory.concreteFields].some(
|
|
389
|
+
(path) => wildcardPath(path) === wildcardPath(canonical)
|
|
390
|
+
);
|
|
391
|
+
if (known) controlOnly.add(canonical);
|
|
392
|
+
}
|
|
393
|
+
|
|
325
394
|
for (const declaredPath of declared) {
|
|
326
395
|
const canonical = canonicalizeMarkerPath(declaredPath);
|
|
327
396
|
if (!canonical || !isAllowedControlOnly(canonical)) continue;
|
|
@@ -506,6 +575,14 @@ function buildSiteDataAndManifest({
|
|
|
506
575
|
assignSectionPageKeys(editorSections, routes, markerRoutes);
|
|
507
576
|
enrichSchemasFromContent(content, editorSections);
|
|
508
577
|
|
|
578
|
+
// Auto-inject platform-managed editorSchema sections if not already present.
|
|
579
|
+
// These sections are required so the validator accepts their controlOnlyPaths,
|
|
580
|
+
// but template developers should never need to add these manually.
|
|
581
|
+
ensurePlatformSections(editorSections);
|
|
582
|
+
|
|
583
|
+
// controlOnlyPaths: platform paths are auto-merged by platform-contract.json at
|
|
584
|
+
// validate/build time. We only emit template-specific paths here (currently none
|
|
585
|
+
// by default — templates add their own via controlOnlyPaths in fivora-template.json).
|
|
509
586
|
const controlOnlyPaths = computeControlOnlyPaths(
|
|
510
587
|
content,
|
|
511
588
|
boundFieldPaths,
|
package/src/arc/transformer.cjs
CHANGED
|
@@ -1046,11 +1046,78 @@ function instrumentLayoutSource(code, siteDataImport, providerImport = '@deneb-u
|
|
|
1046
1046
|
}
|
|
1047
1047
|
|
|
1048
1048
|
ensureProviderInitialData(ast, jsonIdent);
|
|
1049
|
+
injectPlatformAdditionalPages(ast, providerImport);
|
|
1049
1050
|
sanitizeDuplicateBindings(ast);
|
|
1050
1051
|
const next = printSource(ast, code);
|
|
1051
1052
|
return { code: next, updated: next !== code };
|
|
1052
1053
|
}
|
|
1053
1054
|
|
|
1055
|
+
/**
|
|
1056
|
+
* Injects <PlatformAdditionalPages /> into the layout JSX tree after <main>.
|
|
1057
|
+
* This component handles all additionalPages visual-editing markers correctly
|
|
1058
|
+
* so template developers never need to know the complex nesting rules.
|
|
1059
|
+
*
|
|
1060
|
+
* The injected pattern looks like:
|
|
1061
|
+
* <main>{children}</main>
|
|
1062
|
+
* <PlatformAdditionalPages /> ← injected (inside the SiteDataProvider/body)
|
|
1063
|
+
*
|
|
1064
|
+
* If PlatformAdditionalPages is already in the source, it's a no-op.
|
|
1065
|
+
*/
|
|
1066
|
+
function injectPlatformAdditionalPages(ast, providerImport) {
|
|
1067
|
+
if (!ast) return;
|
|
1068
|
+
// Already injected - skip
|
|
1069
|
+
let alreadyPresent = false;
|
|
1070
|
+
recast.types.visit(ast, {
|
|
1071
|
+
visitJSXIdentifier(pathNode) {
|
|
1072
|
+
if (pathNode.node.name === 'PlatformAdditionalPages') {
|
|
1073
|
+
alreadyPresent = true;
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
this.traverse(pathNode);
|
|
1077
|
+
},
|
|
1078
|
+
});
|
|
1079
|
+
if (alreadyPresent) return;
|
|
1080
|
+
|
|
1081
|
+
// Inject after the first <main> element in any JSX tree
|
|
1082
|
+
let injected = false;
|
|
1083
|
+
recast.types.visit(ast, {
|
|
1084
|
+
visitJSXElement(pathNode) {
|
|
1085
|
+
if (injected) return false;
|
|
1086
|
+
const name = getJsxName(pathNode.node);
|
|
1087
|
+
if (name === 'main') {
|
|
1088
|
+
const parent = pathNode.parent;
|
|
1089
|
+
if (!parent || !Array.isArray(parent.node.children)) {
|
|
1090
|
+
this.traverse(pathNode);
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const siblings = parent.node.children;
|
|
1094
|
+
const idx = siblings.indexOf(pathNode.node);
|
|
1095
|
+
if (idx === -1) {
|
|
1096
|
+
this.traverse(pathNode);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
// Build <PlatformAdditionalPages />
|
|
1100
|
+
const platformElement = b.jsxElement(
|
|
1101
|
+
b.jsxOpeningElement(
|
|
1102
|
+
b.jsxIdentifier('PlatformAdditionalPages'),
|
|
1103
|
+
[],
|
|
1104
|
+
true // self-closing
|
|
1105
|
+
),
|
|
1106
|
+
null,
|
|
1107
|
+
[],
|
|
1108
|
+
true
|
|
1109
|
+
);
|
|
1110
|
+
const newline = b.jsxText('\n ');
|
|
1111
|
+
siblings.splice(idx + 1, 0, newline, platformElement);
|
|
1112
|
+
ensureImport(ast, providerImport || '@deneb-ui/ui', ['PlatformAdditionalPages']);
|
|
1113
|
+
injected = true;
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
this.traverse(pathNode);
|
|
1117
|
+
},
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1054
1121
|
function ensureJsonModule(tsconfig) {
|
|
1055
1122
|
if (!tsconfig || !tsconfig.compilerOptions) return { config: tsconfig, changed: false };
|
|
1056
1123
|
if (tsconfig.compilerOptions.resolveJsonModule) return { config: tsconfig, changed: false };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Platform-managed field paths for Fivora templates. These are written by the Fivora platform (AI agent, site builder) and must NEVER require data-preview-field-path HTML markers. The CLI merges these into every template's controlOnlyPaths automatically — templates do not need to declare them.",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"platformControlledPaths": [
|
|
5
|
+
"__fivoraIntake.tone",
|
|
6
|
+
"__fivoraIntake.outputLanguage",
|
|
7
|
+
"__fivoraIntake.businessSummary",
|
|
8
|
+
"__fivoraIntake.additionalBusinessDetails",
|
|
9
|
+
"__fivoraIntake.referenceWebsiteUrl",
|
|
10
|
+
"additionalPages[*].id",
|
|
11
|
+
"additionalPages[*].route",
|
|
12
|
+
"common.currency",
|
|
13
|
+
"common.whatsappNumber",
|
|
14
|
+
"common.whatsapp",
|
|
15
|
+
"common.email",
|
|
16
|
+
"common.address",
|
|
17
|
+
"common.contactNumber",
|
|
18
|
+
"common.openingHours",
|
|
19
|
+
"common.newsletterTitle",
|
|
20
|
+
"common.newsletterDescription",
|
|
21
|
+
"contact.contactNumber",
|
|
22
|
+
"contact.googleMapLink",
|
|
23
|
+
"contact.whatsapp",
|
|
24
|
+
"products[*].id",
|
|
25
|
+
"products[*].isAvailable",
|
|
26
|
+
"products[*].measurement",
|
|
27
|
+
"products[*].unit",
|
|
28
|
+
"testimonials[*].id",
|
|
29
|
+
"categories[*].slug"
|
|
30
|
+
],
|
|
31
|
+
"platformSiteDataKeys": [
|
|
32
|
+
"__fivoraIntake",
|
|
33
|
+
"additionalPages"
|
|
34
|
+
],
|
|
35
|
+
"forbiddenNextConfigKeys": [
|
|
36
|
+
"outputFileTracingRoot",
|
|
37
|
+
"experimental.outputFileTracingRoot"
|
|
38
|
+
]
|
|
39
|
+
}
|