@deneb-ui/cli 2.0.29 → 2.0.31
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 +4 -4
- package/package.json +2 -2
- package/src/arc/__tests__/arc.test.cjs +23 -0
- package/src/arc/ast.cjs +18 -10
- package/src/arc/fivora-contract.cjs +1 -0
- package/src/arc/index.cjs +14 -1
- package/src/arc/transformer.cjs +57 -0
package/bin/index.js
CHANGED
|
@@ -732,12 +732,12 @@ function initProject(targetInput, options = {}) {
|
|
|
732
732
|
// 6. Install DENEB packages if missing
|
|
733
733
|
const skipInstall = process.argv.includes('--skip-install');
|
|
734
734
|
const hasUi = Boolean(
|
|
735
|
-
(pkg.dependencies && (pkg.dependencies['@deneb-ui/ui'] || pkg.dependencies['@deneb/ui']
|
|
736
|
-
(pkg.devDependencies && (pkg.devDependencies['@deneb-ui/ui'] || pkg.devDependencies['@deneb/ui']
|
|
735
|
+
(pkg.dependencies && (pkg.dependencies['@deneb-ui/ui'] || pkg.dependencies['@deneb/ui'])) ||
|
|
736
|
+
(pkg.devDependencies && (pkg.devDependencies['@deneb-ui/ui'] || pkg.devDependencies['@deneb/ui']))
|
|
737
737
|
);
|
|
738
738
|
const hasCli = Boolean(
|
|
739
|
-
(pkg.devDependencies && (pkg.devDependencies['@deneb-ui/cli'] || pkg.devDependencies['@
|
|
740
|
-
(pkg.dependencies && (pkg.dependencies['@deneb-ui/cli'] || pkg.dependencies['@
|
|
739
|
+
(pkg.devDependencies && (pkg.devDependencies['@deneb-ui/cli'] || pkg.devDependencies['@deneb/cli'])) ||
|
|
740
|
+
(pkg.dependencies && (pkg.dependencies['@deneb-ui/cli'] || pkg.dependencies['@deneb/cli']))
|
|
741
741
|
);
|
|
742
742
|
|
|
743
743
|
if (!skipInstall && (!hasUi || !hasCli)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deneb-ui/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.31",
|
|
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.31",
|
|
53
53
|
"adm-zip": "^0.6.0",
|
|
54
54
|
"recast": "^0.23.11"
|
|
55
55
|
},
|
|
@@ -496,3 +496,26 @@ test('schema sections only claim a pageKey when reachability proves it', () => {
|
|
|
496
496
|
assert.equal(sections.find((s) => s.id === 'home').pageKey, 'home');
|
|
497
497
|
assert.equal(sections.find((s) => s.id === 'about').pageKey, 'about');
|
|
498
498
|
});
|
|
499
|
+
|
|
500
|
+
test('conflicting data-preview-static is stripped when element has editable markers', () => {
|
|
501
|
+
const dir = copyOf(STOREFRONT_FIXTURE);
|
|
502
|
+
// Introduce a conflicting static marker on an element with an editable marker
|
|
503
|
+
const heroPath = path.join(dir, 'src', 'components', 'Hero.tsx');
|
|
504
|
+
let heroCode = fs.readFileSync(heroPath, 'utf8');
|
|
505
|
+
heroCode = heroCode.replace(
|
|
506
|
+
'<p className="mt-4',
|
|
507
|
+
'<p data-preview-static="legacy static description" className="mt-4'
|
|
508
|
+
);
|
|
509
|
+
fs.writeFileSync(heroPath, heroCode, 'utf8');
|
|
510
|
+
|
|
511
|
+
silence(() => runDenebArc(dir, 'acme-store', { telemetry: 'off' }));
|
|
512
|
+
const updatedHero = fs.readFileSync(heroPath, 'utf8');
|
|
513
|
+
|
|
514
|
+
// data-preview-static must be stripped since the paragraph has data-preview-field-path
|
|
515
|
+
assert.ok(!updatedHero.includes('data-preview-static="legacy static description"'));
|
|
516
|
+
assert.ok(updatedHero.includes('data-preview-field-path'));
|
|
517
|
+
|
|
518
|
+
const { errors } = auditFivora(dir);
|
|
519
|
+
const placementErrors = errors.filter((e) => e.includes('cannot share an element with data-preview-static'));
|
|
520
|
+
assert.equal(placementErrors.length, 0);
|
|
521
|
+
});
|
package/src/arc/ast.cjs
CHANGED
|
@@ -267,22 +267,30 @@ function hasDirective(ast, value) {
|
|
|
267
267
|
function ensureImport(ast, source, names) {
|
|
268
268
|
const program = ast.program || ast;
|
|
269
269
|
const body = program.body || [];
|
|
270
|
+
|
|
271
|
+
// Deduplicate against any import in the entire module so we never import the same identifier twice
|
|
272
|
+
const alreadyImported = new Set();
|
|
273
|
+
for (const node of body) {
|
|
274
|
+
if (node.type === 'ImportDeclaration' && Array.isArray(node.specifiers)) {
|
|
275
|
+
for (const spec of node.specifiers) {
|
|
276
|
+
const local = spec.local?.name || spec.imported?.name;
|
|
277
|
+
if (local) alreadyImported.add(local);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const namesToImport = names.filter((name) => !alreadyImported.has(name));
|
|
283
|
+
if (namesToImport.length === 0) return;
|
|
284
|
+
|
|
270
285
|
const existing = body.find((node) => node.type === 'ImportDeclaration' && node.source && node.source.value === source);
|
|
271
286
|
if (existing) {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
.filter((s) => s.type === 'ImportSpecifier')
|
|
275
|
-
.map((s) => s.imported?.name || s.local?.name)
|
|
276
|
-
);
|
|
277
|
-
for (const name of names) {
|
|
278
|
-
if (!already.has(name)) {
|
|
279
|
-
existing.specifiers.push(b.importSpecifier(b.identifier(name), b.identifier(name)));
|
|
280
|
-
}
|
|
287
|
+
for (const name of namesToImport) {
|
|
288
|
+
existing.specifiers.push(b.importSpecifier(b.identifier(name), b.identifier(name)));
|
|
281
289
|
}
|
|
282
290
|
return;
|
|
283
291
|
}
|
|
284
292
|
|
|
285
|
-
const specifiers =
|
|
293
|
+
const specifiers = namesToImport.map((name) => b.importSpecifier(b.identifier(name), b.identifier(name)));
|
|
286
294
|
const decl = b.importDeclaration(specifiers, b.stringLiteral(source));
|
|
287
295
|
let insertAt = 0;
|
|
288
296
|
if (body[0] && body[0].type === 'ExpressionStatement') insertAt = 1;
|
|
@@ -490,6 +490,7 @@ function findUncoveredVisibleText(code, filePath) {
|
|
|
490
490
|
// A `<` preceded by an identifier character is a TypeScript generic
|
|
491
491
|
// (forwardRef<HTMLButtonElement, Props>), never a JSX element.
|
|
492
492
|
if (/[\w$)\]]/.test(source[offset - 1] || '')) continue;
|
|
493
|
+
if (tag.toLowerCase() === 'option') continue;
|
|
493
494
|
if (!text || text.length <= 2 || !/\p{L}/u.test(text)) continue;
|
|
494
495
|
if (!/^[\p{L}\p{N}"'(¡¿#$€£]/u.test(text)) continue;
|
|
495
496
|
if (/^(?:true|false|null|undefined)$/i.test(text)) continue;
|
package/src/arc/index.cjs
CHANGED
|
@@ -17,7 +17,7 @@ const { walkFiles, isJsxFile, rel, copyFilePreserve, writeJson, readJsonSafe, fi
|
|
|
17
17
|
const { scanProject, buildDependencyGraph, inferOwnerScope } = require('./scanner.cjs');
|
|
18
18
|
const { analyzeFile, collectDesignSnapshot } = require('./semantic.cjs');
|
|
19
19
|
const { planTransformations } = require('./planner.cjs');
|
|
20
|
-
const { applyFilePlan, instrumentLayoutSource, instrumentPageKey, resolveSiteDataSpecifier, ensureJsonModule } = require('./transformer.cjs');
|
|
20
|
+
const { applyFilePlan, instrumentLayoutSource, instrumentPageKey, resolveSiteDataSpecifier, ensureJsonModule, sanitizeContradictoryMarkersInSource } = require('./transformer.cjs');
|
|
21
21
|
const { parseSource } = require('./ast.cjs');
|
|
22
22
|
const { buildSiteDataAndManifest, writeDataBank, loadExistingData, countSchemaFields } = require('./manifest.cjs');
|
|
23
23
|
const { validateAstFiles, validateContracts, designPreservationScore, coverageMetrics } = require('./validator.cjs');
|
|
@@ -355,6 +355,19 @@ function runDenebArc(projectDir, projectName, options = {}) {
|
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
// Remove any conflicting data-preview-static from elements carrying editable markers
|
|
359
|
+
for (const relativeFile of profile.jsxFiles || []) {
|
|
360
|
+
const abs = path.join(projectDir, relativeFile);
|
|
361
|
+
if (!fs.existsSync(abs)) continue;
|
|
362
|
+
const original = fs.readFileSync(abs, 'utf8');
|
|
363
|
+
const sanitized = sanitizeContradictoryMarkersInSource(original, relativeFile);
|
|
364
|
+
if (sanitized.updated && sanitized.code !== original) {
|
|
365
|
+
backupFile(projectDir, backupDir, abs);
|
|
366
|
+
fs.writeFileSync(abs, sanitized.code, 'utf8');
|
|
367
|
+
if (!changedFiles.includes(relativeFile)) changedFiles.push(relativeFile);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
358
371
|
if (profile.framework === 'nextjs') {
|
|
359
372
|
const before = findNextConfig(projectDir);
|
|
360
373
|
if (before) backupFile(projectDir, backupDir, before.abs);
|
package/src/arc/transformer.cjs
CHANGED
|
@@ -48,7 +48,16 @@ function replaceAttrValue(node, attrName, expression) {
|
|
|
48
48
|
attr.value = b.jsxExpressionContainer(expression);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function stripStaticAttribute(node) {
|
|
52
|
+
if (node && node.openingElement && Array.isArray(node.openingElement.attributes)) {
|
|
53
|
+
node.openingElement.attributes = node.openingElement.attributes.filter(
|
|
54
|
+
(attr) => !(attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'data-preview-static')
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
function ensurePreviewPath(node, fieldPath) {
|
|
60
|
+
stripStaticAttribute(node);
|
|
52
61
|
if (hasJsxAttribute(node, 'data-preview-field-path')) return;
|
|
53
62
|
node.openingElement.attributes.push(jsxPreviewAttr(fieldPath));
|
|
54
63
|
}
|
|
@@ -505,6 +514,9 @@ function applyFilePlan(filePlan, profile) {
|
|
|
505
514
|
ensureDefaultImport(ast, siteDataImport, 'siteData');
|
|
506
515
|
}
|
|
507
516
|
|
|
517
|
+
// Sanitize any conflicting data-preview-static on elements with editable markers
|
|
518
|
+
sanitizeContradictoryMarkers(ast);
|
|
519
|
+
|
|
508
520
|
// Page keys are stamped in a separate route-driven pass so App Router and
|
|
509
521
|
// Pages Router projects are handled by the same logic.
|
|
510
522
|
const code = printSource(ast, filePlan.originalCode);
|
|
@@ -692,6 +704,49 @@ function ensureJsonModule(tsconfig) {
|
|
|
692
704
|
};
|
|
693
705
|
}
|
|
694
706
|
|
|
707
|
+
function sanitizeContradictoryMarkers(ast) {
|
|
708
|
+
let cleaned = 0;
|
|
709
|
+
recast.types.visit(ast, {
|
|
710
|
+
visitJSXOpeningElement(pathNode) {
|
|
711
|
+
const attrs = pathNode.node.attributes || [];
|
|
712
|
+
const hasEditable = attrs.some(
|
|
713
|
+
(a) => a.type === 'JSXAttribute' && a.name && (
|
|
714
|
+
a.name.name === 'data-preview-field-path' ||
|
|
715
|
+
a.name.name === 'data-preview-list-path' ||
|
|
716
|
+
a.name.name === 'data-preview-item-path'
|
|
717
|
+
)
|
|
718
|
+
);
|
|
719
|
+
const hasStatic = attrs.some(
|
|
720
|
+
(a) => a.type === 'JSXAttribute' && a.name && a.name.name === 'data-preview-static'
|
|
721
|
+
);
|
|
722
|
+
if (hasEditable && hasStatic) {
|
|
723
|
+
pathNode.node.attributes = attrs.filter(
|
|
724
|
+
(a) => !(a.type === 'JSXAttribute' && a.name && a.name.name === 'data-preview-static')
|
|
725
|
+
);
|
|
726
|
+
cleaned++;
|
|
727
|
+
}
|
|
728
|
+
this.traverse(pathNode);
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
return cleaned;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function sanitizeContradictoryMarkersInSource(code, relativeFile) {
|
|
735
|
+
if (!code.includes('data-preview-static')) return { code, updated: false };
|
|
736
|
+
if (!code.includes('data-preview-field-path') && !code.includes('data-preview-list-path') && !code.includes('data-preview-item-path')) {
|
|
737
|
+
return { code, updated: false };
|
|
738
|
+
}
|
|
739
|
+
let ast;
|
|
740
|
+
try {
|
|
741
|
+
ast = parseSource(code, relativeFile);
|
|
742
|
+
} catch {
|
|
743
|
+
return { code, updated: false };
|
|
744
|
+
}
|
|
745
|
+
const count = sanitizeContradictoryMarkers(ast);
|
|
746
|
+
if (count === 0) return { code, updated: false };
|
|
747
|
+
return { code: printSource(ast, code), updated: true, count };
|
|
748
|
+
}
|
|
749
|
+
|
|
695
750
|
module.exports = {
|
|
696
751
|
applyFilePlan,
|
|
697
752
|
instrumentLayoutSource,
|
|
@@ -699,4 +754,6 @@ module.exports = {
|
|
|
699
754
|
resolveSiteDataSpecifier,
|
|
700
755
|
ensureJsonModule,
|
|
701
756
|
inferPageKey,
|
|
757
|
+
sanitizeContradictoryMarkers,
|
|
758
|
+
sanitizeContradictoryMarkersInSource,
|
|
702
759
|
};
|