@deneb-ui/cli 2.0.90 → 2.0.92
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/package.json +2 -2
- package/src/arc/__tests__/composite-fragments.test.cjs +48 -0
- package/src/arc/__tests__/fifteen-gates.test.cjs +47 -0
- package/src/arc/__tests__/fivora-schema-authority.test.cjs +75 -0
- package/src/arc/__tests__/recovery-and-inventory.test.cjs +65 -0
- package/src/arc/__tests__/routing-and-assets.test.cjs +54 -0
- package/src/arc/__tests__/site-data-scope.test.cjs +94 -0
- package/src/arc/__tests__/transform-proof.test.cjs +43 -0
- package/src/arc/__tests__/vanta-regressions.test.cjs +110 -0
- package/src/arc/acceptance-gates.cjs +21 -3
- package/src/arc/asset-auditor.cjs +115 -0
- package/src/arc/confidence-recovery.cjs +87 -0
- package/src/arc/editability-inventory.cjs +84 -0
- package/src/arc/fivora-schema-authority.cjs +203 -0
- package/src/arc/index.cjs +90 -4
- package/src/arc/manifest.cjs +16 -4
- package/src/arc/planner.cjs +13 -1
- package/src/arc/transform-proof.cjs +111 -0
- package/src/arc/transforms/healing/sanitizer.cjs +29 -2
- package/src/arc/transforms/primitives/text.cjs +18 -2
- package/src/arc/transforms/routing/page-key.cjs +43 -24
- package/src/arc/transforms/runtime/provider.cjs +51 -21
- package/src/arc/transforms/runtime/site-data-scope.cjs +227 -0
- package/src/tools/deneb-template-validator.cjs +10 -10
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const recast = require('recast');
|
|
4
|
+
|
|
5
|
+
const SCOPE_STATUS = {
|
|
6
|
+
ALREADY_BOUND: 'ALREADY_BOUND',
|
|
7
|
+
PROP_BOUND: 'PROP_BOUND',
|
|
8
|
+
HOOK_SAFE: 'HOOK_SAFE',
|
|
9
|
+
SERVER_BOUNDARY: 'SERVER_BOUNDARY',
|
|
10
|
+
UTILITY_FUNCTION: 'UTILITY_FUNCTION',
|
|
11
|
+
PROVIDER_OWNER: 'PROVIDER_OWNER',
|
|
12
|
+
INVALID_SCOPE: 'INVALID_SCOPE',
|
|
13
|
+
UNKNOWN: 'UNKNOWN',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Checks if an AST node or its return statements return JSX elements or fragments.
|
|
18
|
+
*/
|
|
19
|
+
function functionReturnsJsx(fn) {
|
|
20
|
+
if (!fn || !fn.body) return false;
|
|
21
|
+
let returnsJsx = false;
|
|
22
|
+
|
|
23
|
+
// Direct expression body: () => <div />
|
|
24
|
+
if (fn.body.type === 'JSXElement' || fn.body.type === 'JSXFragment') {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
recast.types.visit(fn.body, {
|
|
29
|
+
visitReturnStatement(pathNode) {
|
|
30
|
+
const arg = pathNode.node.argument;
|
|
31
|
+
if (arg && (arg.type === 'JSXElement' || arg.type === 'JSXFragment')) {
|
|
32
|
+
returnsJsx = true;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
this.traverse(pathNode);
|
|
36
|
+
},
|
|
37
|
+
// Don't traverse into nested functions to avoid attributing nested JSX to outer function
|
|
38
|
+
visitFunctionDeclaration() {
|
|
39
|
+
return false;
|
|
40
|
+
},
|
|
41
|
+
visitFunctionExpression() {
|
|
42
|
+
return false;
|
|
43
|
+
},
|
|
44
|
+
visitArrowFunctionExpression() {
|
|
45
|
+
return false;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return returnsJsx;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if a function mounts or references <SiteDataProvider> directly in its JSX.
|
|
54
|
+
*/
|
|
55
|
+
function functionMountsSiteDataProvider(fn) {
|
|
56
|
+
if (!fn || !fn.body) return false;
|
|
57
|
+
let mounts = false;
|
|
58
|
+
|
|
59
|
+
recast.types.visit(fn.body, {
|
|
60
|
+
visitJSXElement(pathNode) {
|
|
61
|
+
const opening = pathNode.node.openingElement;
|
|
62
|
+
if (opening && opening.name) {
|
|
63
|
+
const name = opening.name.name;
|
|
64
|
+
if (name === 'SiteDataProvider' || name === 'BaseSiteDataProvider' || name === 'DenebDataProvider') {
|
|
65
|
+
mounts = true;
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.traverse(pathNode);
|
|
70
|
+
},
|
|
71
|
+
visitFunctionDeclaration() {
|
|
72
|
+
return false;
|
|
73
|
+
},
|
|
74
|
+
visitFunctionExpression() {
|
|
75
|
+
return false;
|
|
76
|
+
},
|
|
77
|
+
visitArrowFunctionExpression() {
|
|
78
|
+
return false;
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return mounts;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Checks if a function has a parameter named siteData (direct or destructured).
|
|
87
|
+
*/
|
|
88
|
+
function functionHasSiteDataParam(fn) {
|
|
89
|
+
if (!fn || !Array.isArray(fn.params)) return false;
|
|
90
|
+
for (const param of fn.params) {
|
|
91
|
+
if (param.type === 'Identifier' && param.name === 'siteData') {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (param.type === 'AssignmentPattern' && param.left && param.left.name === 'siteData') {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (param.type === 'ObjectPattern' && Array.isArray(param.properties)) {
|
|
98
|
+
for (const prop of param.properties) {
|
|
99
|
+
if (prop.value && prop.value.type === 'Identifier' && prop.value.name === 'siteData') {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (prop.key && prop.key.type === 'Identifier' && prop.key.name === 'siteData') {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Checks if the function body already binds or declares `siteData`.
|
|
113
|
+
*/
|
|
114
|
+
function functionLocallyDeclaresSiteData(fn) {
|
|
115
|
+
if (!fn || !fn.body || fn.body.type !== 'BlockStatement') return false;
|
|
116
|
+
let declares = false;
|
|
117
|
+
|
|
118
|
+
for (const stmt of fn.body.body) {
|
|
119
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
120
|
+
for (const decl of stmt.declarations) {
|
|
121
|
+
if (decl.id && decl.id.type === 'Identifier' && decl.id.name === 'siteData') {
|
|
122
|
+
declares = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (declares) break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return declares;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Checks if function calls useSiteData() already.
|
|
135
|
+
*/
|
|
136
|
+
function functionCallsUseSiteData(fn) {
|
|
137
|
+
if (!fn || !fn.body) return false;
|
|
138
|
+
let calls = false;
|
|
139
|
+
|
|
140
|
+
recast.types.visit(fn.body, {
|
|
141
|
+
visitCallExpression(pathNode) {
|
|
142
|
+
const callee = pathNode.node.callee;
|
|
143
|
+
if (callee && callee.type === 'Identifier' && callee.name === 'useSiteData') {
|
|
144
|
+
calls = true;
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
this.traverse(pathNode);
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return calls;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Checks if a function is a React component or custom hook.
|
|
156
|
+
*/
|
|
157
|
+
function isReactComponentOrHook(fn, fnName) {
|
|
158
|
+
// Custom hooks: use*
|
|
159
|
+
if (fnName && /^use[A-Z]/.test(fnName)) return true;
|
|
160
|
+
|
|
161
|
+
// Capitalized function name
|
|
162
|
+
const isCapitalized = Boolean(fnName && /^[A-Z]/.test(fnName));
|
|
163
|
+
|
|
164
|
+
// Must return JSX to qualify as a component if not a hook
|
|
165
|
+
const returnsJsx = functionReturnsJsx(fn);
|
|
166
|
+
|
|
167
|
+
if (isCapitalized && returnsJsx) return true;
|
|
168
|
+
if (returnsJsx) return true; // Anonymous or exported default component returning JSX
|
|
169
|
+
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Classifies whether a function within an AST can safely have `useSiteData()` injected.
|
|
175
|
+
*
|
|
176
|
+
* @param {object} fn AST Function node
|
|
177
|
+
* @param {string|null} fnName Identifier name of function if available
|
|
178
|
+
* @param {object} fileContext Context about the file (isServerComponent, mountsProvider, filePath)
|
|
179
|
+
* @returns {string} Status code from SCOPE_STATUS
|
|
180
|
+
*/
|
|
181
|
+
function classifySiteDataRequirement(fn, fnName, fileContext = {}) {
|
|
182
|
+
if (!fn) return SCOPE_STATUS.INVALID_SCOPE;
|
|
183
|
+
|
|
184
|
+
// 1. Check if function already declares or receives siteData
|
|
185
|
+
if (functionHasSiteDataParam(fn)) {
|
|
186
|
+
return SCOPE_STATUS.PROP_BOUND;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (functionLocallyDeclaresSiteData(fn) || functionCallsUseSiteData(fn)) {
|
|
190
|
+
return SCOPE_STATUS.ALREADY_BOUND;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 2. Check if async function -> Server Component / async handler
|
|
194
|
+
if (fn.async) {
|
|
195
|
+
return SCOPE_STATUS.SERVER_BOUNDARY;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 3. Check if function mounts <SiteDataProvider>
|
|
199
|
+
if (functionMountsSiteDataProvider(fn) || fileContext.mountsProvider) {
|
|
200
|
+
return SCOPE_STATUS.PROVIDER_OWNER;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 4. Check if function is RootLayout or Server Layout without 'use client'
|
|
204
|
+
const isLayout = /(?:^|[/\\])layout\.(?:tsx|jsx|js|ts)$/.test(fileContext.filePath || '');
|
|
205
|
+
if (fnName === 'RootLayout' || (isLayout && !fileContext.isClientComponent)) {
|
|
206
|
+
return SCOPE_STATUS.SERVER_BOUNDARY;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 5. Check if React component or custom hook
|
|
210
|
+
if (!isReactComponentOrHook(fn, fnName)) {
|
|
211
|
+
return SCOPE_STATUS.UTILITY_FUNCTION;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 6. Safe to inject hook
|
|
215
|
+
return SCOPE_STATUS.HOOK_SAFE;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
SCOPE_STATUS,
|
|
220
|
+
functionReturnsJsx,
|
|
221
|
+
functionMountsSiteDataProvider,
|
|
222
|
+
functionHasSiteDataParam,
|
|
223
|
+
functionLocallyDeclaresSiteData,
|
|
224
|
+
functionCallsUseSiteData,
|
|
225
|
+
isReactComponentOrHook,
|
|
226
|
+
classifySiteDataRequirement,
|
|
227
|
+
};
|