@appilots/cli 0.11.3 → 0.13.0
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/README.md +23 -11
- package/dist/cli/index.js +1015 -390
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +549 -71
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +547 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,19 +1,356 @@
|
|
|
1
1
|
import fs, { readFile, mkdir, writeFile } from 'fs/promises';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import traverse5 from '@babel/traverse';
|
|
4
|
+
import * as BabelTypes from '@babel/types';
|
|
2
5
|
import * as path2 from 'path';
|
|
3
6
|
import path2__default, { join, resolve } from 'path';
|
|
4
|
-
import traverse4 from '@babel/traverse';
|
|
5
|
-
import * as BabelTypes from '@babel/types';
|
|
6
7
|
import fastGlob from 'fast-glob';
|
|
7
8
|
import * as parser from '@babel/parser';
|
|
8
9
|
import { parse } from '@babel/parser';
|
|
9
10
|
import { promises, readFileSync, writeFileSync, existsSync, statSync, readdirSync } from 'fs';
|
|
10
|
-
import { createHash } from 'crypto';
|
|
11
11
|
|
|
12
12
|
var __defProp = Object.defineProperty;
|
|
13
13
|
var __export = (target, all) => {
|
|
14
14
|
for (var name in all)
|
|
15
15
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
16
|
};
|
|
17
|
+
var MAX_HOPS = 8;
|
|
18
|
+
function unwrap(path11) {
|
|
19
|
+
while (path11.isTSAsExpression() || path11.isTSTypeAssertion() || path11.isTSNonNullExpression() || path11.isTSSatisfiesExpression() || path11.isParenthesizedExpression())
|
|
20
|
+
path11 = path11.get("expression");
|
|
21
|
+
return path11;
|
|
22
|
+
}
|
|
23
|
+
function constantValue(path11, depth = 0) {
|
|
24
|
+
if (depth > MAX_HOPS) return void 0;
|
|
25
|
+
path11 = unwrap(path11);
|
|
26
|
+
if (!path11.isIdentifier()) return path11;
|
|
27
|
+
const binding = path11.scope.getBinding(path11.node.name);
|
|
28
|
+
if (!binding?.constant || !binding.path.isVariableDeclarator()) return path11;
|
|
29
|
+
const init = binding.path.get("init");
|
|
30
|
+
const resolved = init.node ? constantValue(init, depth + 1) : void 0;
|
|
31
|
+
if (resolved?.isObjectExpression() && binding.referencePaths.some(
|
|
32
|
+
(reference) => !reference.parentPath?.isJSXSpreadAttribute() && !reference.parentPath?.isSpreadElement()
|
|
33
|
+
))
|
|
34
|
+
return void 0;
|
|
35
|
+
return resolved;
|
|
36
|
+
}
|
|
37
|
+
function propValue(path11, name) {
|
|
38
|
+
const readObject = (path12) => {
|
|
39
|
+
const resolved = constantValue(path12);
|
|
40
|
+
if (!resolved?.isObjectExpression()) return { blocked: true };
|
|
41
|
+
for (const property of [...resolved.get("properties")].reverse()) {
|
|
42
|
+
if (property.isSpreadElement()) {
|
|
43
|
+
const found = readObjectBounded(property.get("argument"));
|
|
44
|
+
if (found.value || found.blocked) return found;
|
|
45
|
+
} else if (property.isObjectProperty() || property.isObjectMethod()) {
|
|
46
|
+
if (property.node.computed) return { blocked: true };
|
|
47
|
+
const key = property.node.key;
|
|
48
|
+
if ((BabelTypes.isIdentifier(key) ? key.name : BabelTypes.isStringLiteral(key) ? key.value : "") === name)
|
|
49
|
+
return property.isObjectProperty() ? { value: property.get("value") } : { value: property };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {};
|
|
53
|
+
};
|
|
54
|
+
let objectBudget = MAX_HOPS;
|
|
55
|
+
const readObjectBounded = (path12) => objectBudget-- > 0 ? readObject(path12) : { blocked: true };
|
|
56
|
+
for (const attr of [...path11.get("attributes")].reverse()) {
|
|
57
|
+
if (attr.isJSXAttribute() && BabelTypes.isJSXIdentifier(attr.node.name, { name })) {
|
|
58
|
+
const value = attr.get("value");
|
|
59
|
+
return value.isJSXExpressionContainer() ? unwrap(value.get("expression")) : value.node ? value : void 0;
|
|
60
|
+
}
|
|
61
|
+
if (attr.isJSXSpreadAttribute()) {
|
|
62
|
+
const found = readObjectBounded(attr.get("argument"));
|
|
63
|
+
if (found.value || found.blocked) return found.value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
function importIdentity(path11, depth = 0) {
|
|
69
|
+
if (depth > MAX_HOPS) return void 0;
|
|
70
|
+
path11 = unwrap(path11);
|
|
71
|
+
if (path11.isIdentifier() || path11.isJSXIdentifier()) {
|
|
72
|
+
const binding = path11.scope.getBinding(path11.node.name);
|
|
73
|
+
if (!binding?.constant) return void 0;
|
|
74
|
+
const declaration = binding.path;
|
|
75
|
+
if (declaration.parentPath?.isImportDeclaration()) {
|
|
76
|
+
const module = declaration.parentPath.node.source.value;
|
|
77
|
+
if (declaration.isImportSpecifier()) {
|
|
78
|
+
const imported = declaration.node.imported;
|
|
79
|
+
return { module, imported: BabelTypes.isIdentifier(imported) ? imported.name : imported.value };
|
|
80
|
+
}
|
|
81
|
+
if (declaration.isImportDefaultSpecifier()) return { module, imported: "default" };
|
|
82
|
+
if (declaration.isImportNamespaceSpecifier()) return { module, imported: "*" };
|
|
83
|
+
}
|
|
84
|
+
if (declaration.isVariableDeclarator() && declaration.get("init").node)
|
|
85
|
+
return importIdentity(declaration.get("init"), depth + 1);
|
|
86
|
+
}
|
|
87
|
+
if (path11.isJSXMemberExpression() || path11.isMemberExpression() && !path11.node.computed) {
|
|
88
|
+
const origin = importIdentity(path11.get("object"), depth + 1);
|
|
89
|
+
const key = path11.node.property;
|
|
90
|
+
if (origin && (BabelTypes.isIdentifier(key) || BabelTypes.isJSXIdentifier(key)) && (origin.imported === "*" || origin.module === "react" && origin.imported === "default"))
|
|
91
|
+
return { module: origin.module, imported: key.name };
|
|
92
|
+
}
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
function handlerFunction(path11, depth = 0) {
|
|
96
|
+
if (depth > MAX_HOPS) return void 0;
|
|
97
|
+
path11 = unwrap(path11);
|
|
98
|
+
if (path11.isFunction()) return path11;
|
|
99
|
+
if (path11.isIdentifier()) {
|
|
100
|
+
const binding = path11.scope.getBinding(path11.node.name);
|
|
101
|
+
if (!binding?.constant) return void 0;
|
|
102
|
+
if (binding.path.isFunctionDeclaration()) return binding.path;
|
|
103
|
+
if (binding.path.isVariableDeclarator() && binding.path.get("init").node)
|
|
104
|
+
return handlerFunction(binding.path.get("init"), depth + 1);
|
|
105
|
+
}
|
|
106
|
+
if (path11.isCallExpression()) {
|
|
107
|
+
const origin = importIdentity(path11.get("callee"));
|
|
108
|
+
if (origin?.module === "react" && origin.imported === "useCallback") {
|
|
109
|
+
const callback = path11.get("arguments")[0];
|
|
110
|
+
if (callback) return handlerFunction(callback, depth + 1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (path11.isMemberExpression() && !path11.node.computed && BabelTypes.isThisExpression(path11.node.object)) {
|
|
114
|
+
const key = path11.node.property;
|
|
115
|
+
if (!BabelTypes.isIdentifier(key)) return void 0;
|
|
116
|
+
const owner = path11.findParent((p) => p.isClassDeclaration() || p.isClassExpression());
|
|
117
|
+
if (!owner || !(owner.isClassDeclaration() || owner.isClassExpression())) return void 0;
|
|
118
|
+
for (const member of owner.get("body").get("body")) {
|
|
119
|
+
if (!(member.isClassMethod() || member.isClassProperty()) || member.node.computed || member.node.static)
|
|
120
|
+
continue;
|
|
121
|
+
if (!BabelTypes.isIdentifier(member.node.key, { name: key.name })) continue;
|
|
122
|
+
if (member.isClassMethod() && member.node.kind === "method") return member;
|
|
123
|
+
if (member.isClassProperty() && member.get("value").node)
|
|
124
|
+
return handlerFunction(member.get("value"), depth + 1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/extractors/control-evidence.ts
|
|
131
|
+
function symbol(node) {
|
|
132
|
+
if (BabelTypes.isTSAsExpression(node) || BabelTypes.isTSTypeAssertion(node) || BabelTypes.isTSNonNullExpression(node) || BabelTypes.isTSSatisfiesExpression(node))
|
|
133
|
+
return symbol(node.expression);
|
|
134
|
+
if (BabelTypes.isIdentifier(node) || BabelTypes.isJSXIdentifier(node)) return node.name;
|
|
135
|
+
if (BabelTypes.isThisExpression(node)) return "this";
|
|
136
|
+
if (BabelTypes.isMemberExpression(node) && !node.computed || BabelTypes.isJSXMemberExpression(node)) {
|
|
137
|
+
const object = symbol(node.object), property = symbol(node.property);
|
|
138
|
+
return object && property ? `${object}.${property}` : void 0;
|
|
139
|
+
}
|
|
140
|
+
if (BabelTypes.isUnaryExpression(node) && node.operator === "!") {
|
|
141
|
+
const value = symbol(node.argument);
|
|
142
|
+
return value ? `!${value}` : void 0;
|
|
143
|
+
}
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
function attribute(node, name) {
|
|
147
|
+
return node.attributes.find(
|
|
148
|
+
(a) => BabelTypes.isJSXAttribute(a) && BabelTypes.isJSXIdentifier(a.name, { name })
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
var ICON_LIBRARIES = [
|
|
152
|
+
"lucide-react-native",
|
|
153
|
+
"lucide-react",
|
|
154
|
+
"@tamagui/lucide-icons",
|
|
155
|
+
"@expo/vector-icons",
|
|
156
|
+
"@react-native-vector-icons/",
|
|
157
|
+
"react-native-vector-icons/"
|
|
158
|
+
];
|
|
159
|
+
function iconLibrary(module) {
|
|
160
|
+
return ICON_LIBRARIES.some(
|
|
161
|
+
(name) => name.endsWith("/") ? module.startsWith(name) : module === name || module.startsWith(name + "/")
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
function add(values, value, max = 12) {
|
|
165
|
+
if (value && value.length <= 240 && values.length < max && !values.includes(value))
|
|
166
|
+
values.push(value);
|
|
167
|
+
}
|
|
168
|
+
function iconName(path11, explicitIcon = false) {
|
|
169
|
+
const origin = importIdentity(path11);
|
|
170
|
+
if (origin && iconLibrary(origin.module) && origin.imported !== "*")
|
|
171
|
+
return origin.imported === "default" ? origin.module : `${origin.module}:${origin.imported}`;
|
|
172
|
+
const name = symbol(path11.node);
|
|
173
|
+
return name && (explicitIcon || /icon/i.test(name)) ? name : void 0;
|
|
174
|
+
}
|
|
175
|
+
function iconsInElement(path11, explicitIcon = false) {
|
|
176
|
+
const name = iconName(path11.get("name"), explicitIcon);
|
|
177
|
+
if (!name) return void 0;
|
|
178
|
+
const glyph = propValue(path11, "name");
|
|
179
|
+
const value = glyph && constantValue(glyph);
|
|
180
|
+
return value?.isStringLiteral() ? `${name}:${value.node.value}` : name;
|
|
181
|
+
}
|
|
182
|
+
function hasInteraction(path11) {
|
|
183
|
+
return ["onPress", "onLongPress", "onClick"].some(
|
|
184
|
+
(name) => attribute(path11.node, name) || propValue(path11, name)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
function collectPresentationIcons(path11, icons) {
|
|
188
|
+
const namedIconProps = [
|
|
189
|
+
"icon",
|
|
190
|
+
"prefix",
|
|
191
|
+
"suffix",
|
|
192
|
+
"left",
|
|
193
|
+
"right",
|
|
194
|
+
"leadingIcon",
|
|
195
|
+
"trailingIcon",
|
|
196
|
+
"startIcon",
|
|
197
|
+
"endIcon",
|
|
198
|
+
"renderIcon"
|
|
199
|
+
];
|
|
200
|
+
const props = new Set(namedIconProps);
|
|
201
|
+
for (const attr of path11.node.attributes)
|
|
202
|
+
if (BabelTypes.isJSXAttribute(attr) && BabelTypes.isJSXIdentifier(attr.name) && !/^on[A-Z]/.test(attr.name.name))
|
|
203
|
+
props.add(attr.name.name);
|
|
204
|
+
for (const prop of props) {
|
|
205
|
+
const icon = propValue(path11, prop);
|
|
206
|
+
if (icon) {
|
|
207
|
+
const value = constantValue(icon);
|
|
208
|
+
if (value?.isStringLiteral()) {
|
|
209
|
+
if (prop.toLowerCase().includes("icon")) add(icons, value.node.value, 8);
|
|
210
|
+
} else {
|
|
211
|
+
const explicitIcon = prop.toLowerCase() === "icon";
|
|
212
|
+
if (icon.isJSXElement()) {
|
|
213
|
+
if (hasInteraction(icon.get("openingElement"))) continue;
|
|
214
|
+
add(icons, iconsInElement(icon.get("openingElement"), explicitIcon), 8);
|
|
215
|
+
}
|
|
216
|
+
icon.traverse({
|
|
217
|
+
JSXAttribute(attr) {
|
|
218
|
+
if (BabelTypes.isJSXIdentifier(attr.node.name) && /^on[A-Z]/.test(attr.node.name.name))
|
|
219
|
+
attr.skip();
|
|
220
|
+
},
|
|
221
|
+
JSXElement(child) {
|
|
222
|
+
const opening = child.get("openingElement");
|
|
223
|
+
if (hasInteraction(opening)) {
|
|
224
|
+
child.skip();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
add(icons, iconsInElement(opening, explicitIcon), 8);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
if (!icons.length && (namedIconProps.includes(prop) || explicitIcon))
|
|
231
|
+
add(icons, iconName(icon, explicitIcon), 8);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function collectIcons(path11) {
|
|
237
|
+
const icons = [];
|
|
238
|
+
collectPresentationIcons(path11, icons);
|
|
239
|
+
const jsx = path11.parentPath;
|
|
240
|
+
if (jsx.isJSXElement())
|
|
241
|
+
jsx.traverse({
|
|
242
|
+
// JSX mentioned inside an event callback is not a rendered child icon.
|
|
243
|
+
JSXAttribute(attr) {
|
|
244
|
+
attr.skip();
|
|
245
|
+
},
|
|
246
|
+
JSXElement(child) {
|
|
247
|
+
const opening = child.get("openingElement");
|
|
248
|
+
if (hasInteraction(opening)) {
|
|
249
|
+
child.skip();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
add(icons, iconsInElement(opening), 8);
|
|
253
|
+
collectPresentationIcons(opening, icons);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return icons;
|
|
257
|
+
}
|
|
258
|
+
function conditionsAt(path11) {
|
|
259
|
+
const conditions = [];
|
|
260
|
+
let child = path11;
|
|
261
|
+
for (let parent = child.parentPath; parent && !parent.isFunction(); child = parent, parent = parent.parentPath) {
|
|
262
|
+
let test;
|
|
263
|
+
let negated = false;
|
|
264
|
+
if (parent.isLogicalExpression() && parent.node.right === child.node) {
|
|
265
|
+
if (parent.node.operator === "&&") test = parent.node.left;
|
|
266
|
+
if (parent.node.operator === "||") {
|
|
267
|
+
test = parent.node.left;
|
|
268
|
+
negated = true;
|
|
269
|
+
}
|
|
270
|
+
} else if (parent.isConditionalExpression() && parent.node.test !== child.node) {
|
|
271
|
+
test = parent.node.test;
|
|
272
|
+
negated = parent.node.alternate === child.node;
|
|
273
|
+
} else if (parent.isIfStatement() && parent.node.test !== child.node) {
|
|
274
|
+
test = parent.node.test;
|
|
275
|
+
negated = parent.node.alternate === child.node;
|
|
276
|
+
}
|
|
277
|
+
const name = symbol(test);
|
|
278
|
+
if (name) add(conditions, negated ? name.startsWith("!") ? name.slice(1) : `!${name}` : name);
|
|
279
|
+
}
|
|
280
|
+
return conditions;
|
|
281
|
+
}
|
|
282
|
+
function collectHandlerEvidence(expression, evidence) {
|
|
283
|
+
const seen = /* @__PURE__ */ new Set();
|
|
284
|
+
let budget = 100;
|
|
285
|
+
const visit = (path11, depth) => {
|
|
286
|
+
if (depth > 4 || budget-- <= 0) return;
|
|
287
|
+
const fn = handlerFunction(path11);
|
|
288
|
+
if (!fn || seen.has(fn.node)) return;
|
|
289
|
+
seen.add(fn.node);
|
|
290
|
+
fn.traverse({
|
|
291
|
+
// Ignore uncalled helper definitions; inline callbacks remain source evidence.
|
|
292
|
+
Function(nested) {
|
|
293
|
+
if (!nested.parentPath.isCallExpression() && !nested.parentPath.isObjectProperty())
|
|
294
|
+
nested.skip();
|
|
295
|
+
},
|
|
296
|
+
CallExpression(call) {
|
|
297
|
+
if (budget-- <= 0) {
|
|
298
|
+
call.skip();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
add(evidence.calls, symbol(call.node.callee));
|
|
302
|
+
for (const arg of call.node.arguments) add(evidence.argumentBindings, symbol(arg));
|
|
303
|
+
visit(call.get("callee"), depth + 1);
|
|
304
|
+
if (!BabelTypes.isMemberExpression(call.node.callee) || call.node.callee.computed || !BabelTypes.isIdentifier(call.node.callee.property, { name: "alert" }))
|
|
305
|
+
return;
|
|
306
|
+
const callee = call.get("callee");
|
|
307
|
+
if (!callee.isMemberExpression()) return;
|
|
308
|
+
const origin = importIdentity(callee.get("object"));
|
|
309
|
+
if (origin?.module !== "react-native" || origin.imported !== "Alert") return;
|
|
310
|
+
const options = call.node.arguments[2];
|
|
311
|
+
const destructiveOption = BabelTypes.isArrayExpression(options) && options.elements.some(
|
|
312
|
+
(option) => BabelTypes.isObjectExpression(option) && option.properties.some(
|
|
313
|
+
(p) => BabelTypes.isObjectProperty(p) && !p.computed && symbol(p.key) === "style" && BabelTypes.isStringLiteral(p.value, { value: "destructive" })
|
|
314
|
+
)
|
|
315
|
+
);
|
|
316
|
+
if (destructiveOption)
|
|
317
|
+
evidence.nativeConfirmation = {
|
|
318
|
+
title: symbol(call.node.arguments[0]),
|
|
319
|
+
destructiveOption
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
visit(unwrap(expression), 0);
|
|
325
|
+
}
|
|
326
|
+
function extractControlEvidence(ast, source, file) {
|
|
327
|
+
const candidates = [];
|
|
328
|
+
const sourceHash = createHash("sha256").update(source).digest("hex");
|
|
329
|
+
traverse5(ast, {
|
|
330
|
+
JSXOpeningElement(path11) {
|
|
331
|
+
const node = path11.node;
|
|
332
|
+
if (attribute(node, "__appilotsControl")) return;
|
|
333
|
+
const value = propValue(path11, "onPress");
|
|
334
|
+
if (!value && !attribute(node, "onPress") || value?.isNullLiteral() || value?.isJSXEmptyExpression())
|
|
335
|
+
return;
|
|
336
|
+
const icons = collectIcons(path11);
|
|
337
|
+
if (!icons.length || node.end == null) return;
|
|
338
|
+
const evidence = {
|
|
339
|
+
version: 1,
|
|
340
|
+
siteId: createHash("sha256").update(`${file}:${sourceHash}:${node.start}`).digest("hex").slice(0, 20),
|
|
341
|
+
component: symbol(node.name) ?? "unknown",
|
|
342
|
+
icons,
|
|
343
|
+
...value && symbol(value.node) ? { handler: symbol(value.node) } : {},
|
|
344
|
+
calls: [],
|
|
345
|
+
argumentBindings: [],
|
|
346
|
+
conditions: conditionsAt(path11)
|
|
347
|
+
};
|
|
348
|
+
if (value) collectHandlerEvidence(value, evidence);
|
|
349
|
+
candidates.push({ evidence, sourceHash, offset: node.end - (node.selfClosing ? 2 : 1) });
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
return candidates;
|
|
353
|
+
}
|
|
17
354
|
function byCodeUnit(a, b) {
|
|
18
355
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
19
356
|
}
|
|
@@ -135,7 +472,7 @@ function classifyJsxComponent(name, element) {
|
|
|
135
472
|
}
|
|
136
473
|
function collectFunctions(ast) {
|
|
137
474
|
const handlers = /* @__PURE__ */ new Map();
|
|
138
|
-
|
|
475
|
+
traverse5(ast, {
|
|
139
476
|
FunctionDeclaration: (nodePath) => {
|
|
140
477
|
if (nodePath.node.id?.name) handlers.set(nodePath.node.id.name, nodePath.node);
|
|
141
478
|
},
|
|
@@ -238,7 +575,7 @@ function analyzeFunctionBehavior(name, fn, handlers, seen = /* @__PURE__ */ new
|
|
|
238
575
|
}
|
|
239
576
|
};
|
|
240
577
|
if (fn.body) {
|
|
241
|
-
|
|
578
|
+
traverse5(fn.body, {
|
|
242
579
|
noScope: true,
|
|
243
580
|
enter: (nodePath) => inspectNode(nodePath.node)
|
|
244
581
|
});
|
|
@@ -266,7 +603,7 @@ function setterToStateName(setterName) {
|
|
|
266
603
|
}
|
|
267
604
|
function extractNavigationCalls(ast) {
|
|
268
605
|
const calls = [];
|
|
269
|
-
|
|
606
|
+
traverse5(ast, {
|
|
270
607
|
noScope: !BabelTypes.isFile(ast),
|
|
271
608
|
CallExpression: (nodePath) => {
|
|
272
609
|
const node = nodePath.node;
|
|
@@ -417,6 +754,7 @@ var ScreenAnalyzer = class {
|
|
|
417
754
|
routeTargetFiles;
|
|
418
755
|
/** §D: Count of screens filtered out in strict mode (available after analyze()) */
|
|
419
756
|
screensFilteredOut = 0;
|
|
757
|
+
controlEvidenceFiles = {};
|
|
420
758
|
constructor(config, options) {
|
|
421
759
|
this.config = config;
|
|
422
760
|
this.routeTargetFiles = options?.routeTargetFiles ?? /* @__PURE__ */ new Set();
|
|
@@ -517,6 +855,11 @@ var ScreenAnalyzer = class {
|
|
|
517
855
|
title: registerScreenMeta?.title,
|
|
518
856
|
description: registerScreenMeta?.description,
|
|
519
857
|
components,
|
|
858
|
+
controlCandidates: extractControlEvidence(
|
|
859
|
+
ast,
|
|
860
|
+
source,
|
|
861
|
+
path2__default.relative(this.config.rootDir, filePath)
|
|
862
|
+
),
|
|
520
863
|
forms,
|
|
521
864
|
actions,
|
|
522
865
|
navigationTargets,
|
|
@@ -527,6 +870,8 @@ var ScreenAnalyzer = class {
|
|
|
527
870
|
...permissionsFromJsDoc.isPii ? { isPii: true } : {}
|
|
528
871
|
} : {}
|
|
529
872
|
};
|
|
873
|
+
if (descriptor.controlCandidates?.length)
|
|
874
|
+
this.controlEvidenceFiles[path2__default.relative(this.config.rootDir, filePath).split(path2__default.sep).join("/")] = descriptor.controlCandidates;
|
|
530
875
|
descriptor.__hasRegisterScreen = hasRegisterScreenCall;
|
|
531
876
|
return descriptor;
|
|
532
877
|
}
|
|
@@ -536,7 +881,7 @@ var ScreenAnalyzer = class {
|
|
|
536
881
|
*/
|
|
537
882
|
detectRegisterScreenCall(ast) {
|
|
538
883
|
let found = false;
|
|
539
|
-
|
|
884
|
+
traverse5(ast, {
|
|
540
885
|
CallExpression: (nodePath) => {
|
|
541
886
|
if (found) return;
|
|
542
887
|
const callee = nodePath.node.callee;
|
|
@@ -553,7 +898,7 @@ var ScreenAnalyzer = class {
|
|
|
553
898
|
*/
|
|
554
899
|
extractRegisterScreenMetadata(ast) {
|
|
555
900
|
let metadata = null;
|
|
556
|
-
|
|
901
|
+
traverse5(ast, {
|
|
557
902
|
CallExpression: (nodePath) => {
|
|
558
903
|
const callee = nodePath.node.callee;
|
|
559
904
|
if (BabelTypes.isIdentifier(callee) && callee.name === "registerScreen" || BabelTypes.isMemberExpression(callee) && BabelTypes.isIdentifier(callee.property) && callee.property.name === "registerScreen") {
|
|
@@ -850,7 +1195,7 @@ var ScreenAnalyzer = class {
|
|
|
850
1195
|
*/
|
|
851
1196
|
extractDefaultComponentName(ast) {
|
|
852
1197
|
let componentName = "";
|
|
853
|
-
|
|
1198
|
+
traverse5(ast, {
|
|
854
1199
|
ExportDefaultDeclaration: (nodePath) => {
|
|
855
1200
|
const declaration = nodePath.node.declaration;
|
|
856
1201
|
if (BabelTypes.isFunctionDeclaration(declaration) && declaration.id?.name) {
|
|
@@ -875,7 +1220,7 @@ var ScreenAnalyzer = class {
|
|
|
875
1220
|
*/
|
|
876
1221
|
extractNavigationTargets(ast) {
|
|
877
1222
|
const targets = /* @__PURE__ */ new Set();
|
|
878
|
-
|
|
1223
|
+
traverse5(ast, {
|
|
879
1224
|
CallExpression: (nodePath) => {
|
|
880
1225
|
const callee = nodePath.node.callee;
|
|
881
1226
|
if (BabelTypes.isMemberExpression(callee) && BabelTypes.isIdentifier(callee.object) && callee.object.name === "navigation" && BabelTypes.isIdentifier(callee.property) && callee.property.name === "navigate") {
|
|
@@ -894,7 +1239,7 @@ var ScreenAnalyzer = class {
|
|
|
894
1239
|
extractForms(ast) {
|
|
895
1240
|
const forms = [];
|
|
896
1241
|
const fields = /* @__PURE__ */ new Map();
|
|
897
|
-
|
|
1242
|
+
traverse5(ast, {
|
|
898
1243
|
JSXOpeningElement: (nodePath) => {
|
|
899
1244
|
const element = nodePath.node;
|
|
900
1245
|
if (BabelTypes.isJSXIdentifier(element.name)) {
|
|
@@ -1015,7 +1360,7 @@ var ScreenAnalyzer = class {
|
|
|
1015
1360
|
extractComponents(ast) {
|
|
1016
1361
|
const components = [];
|
|
1017
1362
|
const seen = /* @__PURE__ */ new Set();
|
|
1018
|
-
|
|
1363
|
+
traverse5(ast, {
|
|
1019
1364
|
JSXOpeningElement: (nodePath) => {
|
|
1020
1365
|
const element = nodePath.node;
|
|
1021
1366
|
if (BabelTypes.isJSXIdentifier(element.name)) {
|
|
@@ -1079,7 +1424,7 @@ var ScreenAnalyzer = class {
|
|
|
1079
1424
|
const actionLabels = new Map(
|
|
1080
1425
|
actions.filter((a) => a.label).map((a) => [this.normalizeLabel(a.label), a])
|
|
1081
1426
|
);
|
|
1082
|
-
|
|
1427
|
+
traverse5(ast, {
|
|
1083
1428
|
JSXOpeningElement: (nodePath) => {
|
|
1084
1429
|
const element = nodePath.node;
|
|
1085
1430
|
if (BabelTypes.isJSXIdentifier(element.name)) {
|
|
@@ -1148,7 +1493,7 @@ var ScreenAnalyzer = class {
|
|
|
1148
1493
|
}
|
|
1149
1494
|
collectButtonHandlersByLabel(ast) {
|
|
1150
1495
|
const out = /* @__PURE__ */ new Map();
|
|
1151
|
-
|
|
1496
|
+
traverse5(ast, {
|
|
1152
1497
|
JSXOpeningElement: (nodePath) => {
|
|
1153
1498
|
const element = nodePath.node;
|
|
1154
1499
|
if (!BabelTypes.isJSXIdentifier(element.name)) return;
|
|
@@ -1230,7 +1575,7 @@ var ScreenAnalyzer = class {
|
|
|
1230
1575
|
}
|
|
1231
1576
|
};
|
|
1232
1577
|
if (fn.body) {
|
|
1233
|
-
|
|
1578
|
+
traverse5(
|
|
1234
1579
|
fn.body,
|
|
1235
1580
|
{
|
|
1236
1581
|
noScope: true,
|
|
@@ -1427,7 +1772,7 @@ var ScreenAnalyzer = class {
|
|
|
1427
1772
|
extractCollections(ast) {
|
|
1428
1773
|
const renderItemFns = this.collectRenderItemFunctions(ast);
|
|
1429
1774
|
const collections = [];
|
|
1430
|
-
|
|
1775
|
+
traverse5(ast, {
|
|
1431
1776
|
JSXOpeningElement: (nodePath) => {
|
|
1432
1777
|
const element = nodePath.node;
|
|
1433
1778
|
if (!BabelTypes.isJSXIdentifier(element.name)) return;
|
|
@@ -1464,7 +1809,7 @@ var ScreenAnalyzer = class {
|
|
|
1464
1809
|
}
|
|
1465
1810
|
collectRenderItemFunctions(ast) {
|
|
1466
1811
|
const out = /* @__PURE__ */ new Map();
|
|
1467
|
-
|
|
1812
|
+
traverse5(ast, {
|
|
1468
1813
|
VariableDeclarator: (nodePath) => {
|
|
1469
1814
|
if (!BabelTypes.isIdentifier(nodePath.node.id)) return;
|
|
1470
1815
|
const init = nodePath.node.init;
|
|
@@ -1507,7 +1852,7 @@ var ScreenAnalyzer = class {
|
|
|
1507
1852
|
}
|
|
1508
1853
|
extractRowAction(fn) {
|
|
1509
1854
|
let action;
|
|
1510
|
-
|
|
1855
|
+
traverse5(
|
|
1511
1856
|
fn.body,
|
|
1512
1857
|
{
|
|
1513
1858
|
noScope: true,
|
|
@@ -1560,7 +1905,7 @@ var ScreenAnalyzer = class {
|
|
|
1560
1905
|
} else if (BabelTypes.isIdentifier(firstParam)) {
|
|
1561
1906
|
itemNames.add(firstParam.name);
|
|
1562
1907
|
}
|
|
1563
|
-
|
|
1908
|
+
traverse5(
|
|
1564
1909
|
fn.body,
|
|
1565
1910
|
{
|
|
1566
1911
|
noScope: true,
|
|
@@ -1602,7 +1947,7 @@ var ScreenAnalyzer = class {
|
|
|
1602
1947
|
inferSearchField(ast, dataSource) {
|
|
1603
1948
|
if (!dataSource) return void 0;
|
|
1604
1949
|
let queryBinding;
|
|
1605
|
-
|
|
1950
|
+
traverse5(ast, {
|
|
1606
1951
|
CallExpression: (nodePath) => {
|
|
1607
1952
|
const node = nodePath.node;
|
|
1608
1953
|
if (!BabelTypes.isMemberExpression(node.callee)) return;
|
|
@@ -1613,7 +1958,7 @@ var ScreenAnalyzer = class {
|
|
|
1613
1958
|
const fn = node.arguments[0];
|
|
1614
1959
|
if (!BabelTypes.isArrowFunctionExpression(fn) && !BabelTypes.isFunctionExpression(fn))
|
|
1615
1960
|
return;
|
|
1616
|
-
|
|
1961
|
+
traverse5(
|
|
1617
1962
|
fn.body,
|
|
1618
1963
|
{
|
|
1619
1964
|
noScope: true,
|
|
@@ -1655,7 +2000,7 @@ var ScreenAnalyzer = class {
|
|
|
1655
2000
|
}
|
|
1656
2001
|
};
|
|
1657
2002
|
var EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
|
|
1658
|
-
var
|
|
2003
|
+
var MAX_HOPS2 = 8;
|
|
1659
2004
|
var ModuleGraph = class {
|
|
1660
2005
|
asts = /* @__PURE__ */ new Map();
|
|
1661
2006
|
resolved = /* @__PURE__ */ new Map();
|
|
@@ -1774,7 +2119,7 @@ var ModuleGraph = class {
|
|
|
1774
2119
|
* Devolve o arquivo e o nome sob o qual ele é definido lá.
|
|
1775
2120
|
*/
|
|
1776
2121
|
resolveBinding(file, name, hops = 0) {
|
|
1777
|
-
if (hops >
|
|
2122
|
+
if (hops > MAX_HOPS2) return null;
|
|
1778
2123
|
if (this.topLevelInit(file, name) !== null) return { file, name };
|
|
1779
2124
|
const binding = this.imports(file).get(name);
|
|
1780
2125
|
if (binding) {
|
|
@@ -1828,7 +2173,7 @@ var ModuleGraph = class {
|
|
|
1828
2173
|
* com interpolação nem valor calculado, de propósito.
|
|
1829
2174
|
*/
|
|
1830
2175
|
stringConstant(file, node, hops = 0) {
|
|
1831
|
-
if (!node || hops >
|
|
2176
|
+
if (!node || hops > MAX_HOPS2) return null;
|
|
1832
2177
|
if (BabelTypes.isStringLiteral(node)) return node.value;
|
|
1833
2178
|
if (BabelTypes.isTemplateLiteral(node)) {
|
|
1834
2179
|
return node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? null : null;
|
|
@@ -1857,7 +2202,7 @@ var ModuleGraph = class {
|
|
|
1857
2202
|
* que é como o `pocketpal` monta todas as telas do Drawer).
|
|
1858
2203
|
*/
|
|
1859
2204
|
componentFile(file, node, hops = 0) {
|
|
1860
|
-
if (!node || hops >
|
|
2205
|
+
if (!node || hops > MAX_HOPS2) return null;
|
|
1861
2206
|
if (BabelTypes.isCallExpression(node)) {
|
|
1862
2207
|
for (const arg of node.arguments) {
|
|
1863
2208
|
if (BabelTypes.isIdentifier(arg) || BabelTypes.isMemberExpression(arg)) {
|
|
@@ -2053,7 +2398,7 @@ function workspaceGlobs(dir) {
|
|
|
2053
2398
|
}
|
|
2054
2399
|
function expandWorkspaceGlobs(root, globs) {
|
|
2055
2400
|
const out = [];
|
|
2056
|
-
const
|
|
2401
|
+
const add2 = (dir) => {
|
|
2057
2402
|
const pkgPath = path2__default.join(dir, "package.json");
|
|
2058
2403
|
if (!existsSync(pkgPath)) return;
|
|
2059
2404
|
try {
|
|
@@ -2074,12 +2419,12 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
2074
2419
|
for (const entry of entries) {
|
|
2075
2420
|
const dir = path2__default.join(parent, entry);
|
|
2076
2421
|
try {
|
|
2077
|
-
if (statSync(dir).isDirectory())
|
|
2422
|
+
if (statSync(dir).isDirectory()) add2(dir);
|
|
2078
2423
|
} catch {
|
|
2079
2424
|
}
|
|
2080
2425
|
}
|
|
2081
2426
|
} else if (!glob.includes("*")) {
|
|
2082
|
-
|
|
2427
|
+
add2(path2__default.join(root, glob));
|
|
2083
2428
|
}
|
|
2084
2429
|
}
|
|
2085
2430
|
return out.sort((a, b) => b.name.length - a.name.length);
|
|
@@ -2399,7 +2744,7 @@ var NavigationAnalyzer = class {
|
|
|
2399
2744
|
const ast = this.graph.parse(filePath);
|
|
2400
2745
|
if (!ast) return [];
|
|
2401
2746
|
const out = /* @__PURE__ */ new Map();
|
|
2402
|
-
|
|
2747
|
+
traverse5(ast, {
|
|
2403
2748
|
CallExpression: (nodePath) => {
|
|
2404
2749
|
const callee = nodePath.node.callee;
|
|
2405
2750
|
if (!BabelTypes.isIdentifier(callee)) return;
|
|
@@ -2560,7 +2905,7 @@ var NavigationAnalyzer = class {
|
|
|
2560
2905
|
const ast = this.graph.parse(filePath);
|
|
2561
2906
|
if (!ast) return [];
|
|
2562
2907
|
const found = /* @__PURE__ */ new Map();
|
|
2563
|
-
|
|
2908
|
+
traverse5(ast, {
|
|
2564
2909
|
JSXElement: (nodePath) => {
|
|
2565
2910
|
const { node } = nodePath;
|
|
2566
2911
|
const openingElement = node.openingElement;
|
|
@@ -2632,7 +2977,7 @@ var NavigationAnalyzer = class {
|
|
|
2632
2977
|
if (!ast) return [];
|
|
2633
2978
|
const byNavigator = /* @__PURE__ */ new Map();
|
|
2634
2979
|
const resolved = /* @__PURE__ */ new Map();
|
|
2635
|
-
|
|
2980
|
+
traverse5(ast, {
|
|
2636
2981
|
JSXElement: (nodePath) => {
|
|
2637
2982
|
const name = nodePath.node.openingElement.name;
|
|
2638
2983
|
if (!BabelTypes.isJSXMemberExpression(name) || !BabelTypes.isJSXIdentifier(name.object)) return;
|
|
@@ -2865,7 +3210,7 @@ var NavigationAnalyzer = class {
|
|
|
2865
3210
|
...this.config.parserPlugins || []
|
|
2866
3211
|
]
|
|
2867
3212
|
});
|
|
2868
|
-
|
|
3213
|
+
traverse5(ast, {
|
|
2869
3214
|
TSTypeAliasDeclaration: (nodePath) => {
|
|
2870
3215
|
const { node } = nodePath;
|
|
2871
3216
|
const typeName = node.id.name;
|
|
@@ -2938,7 +3283,7 @@ var NavigationAnalyzer = class {
|
|
|
2938
3283
|
if (type.type === "TSUndefinedKeyword") return "undefined";
|
|
2939
3284
|
if (type.type === "TSNullKeyword") return "null";
|
|
2940
3285
|
if (type.type === "TSUnionType") {
|
|
2941
|
-
return type.types.map((
|
|
3286
|
+
return type.types.map((t15) => this.typeToString(t15)).join(" | ");
|
|
2942
3287
|
}
|
|
2943
3288
|
if (type.type === "TSTypeLiteral") {
|
|
2944
3289
|
return "object";
|
|
@@ -2959,7 +3304,7 @@ var NavigationAnalyzer = class {
|
|
|
2959
3304
|
/** Attach parsed type params to navigator screens */
|
|
2960
3305
|
attachParamsToNavigators(navigators, types) {
|
|
2961
3306
|
for (const navigator of navigators) {
|
|
2962
|
-
const matchingType = types.find((
|
|
3307
|
+
const matchingType = types.find((t15) => t15.type === navigator.type);
|
|
2963
3308
|
if (matchingType) {
|
|
2964
3309
|
for (const screen of navigator.screens) {
|
|
2965
3310
|
const screenParams = matchingType.paramEntries.get(screen.name);
|
|
@@ -3048,7 +3393,7 @@ var ComponentAnalyzer = class {
|
|
|
3048
3393
|
plugins: ["jsx", "typescript", ["decorators", { decoratorsBeforeExport: true }]]
|
|
3049
3394
|
});
|
|
3050
3395
|
const components = [];
|
|
3051
|
-
|
|
3396
|
+
traverse5(ast, {
|
|
3052
3397
|
JSXElement: (path11) => {
|
|
3053
3398
|
const component = this.extractComponentFromJSXElement(path11.node);
|
|
3054
3399
|
if (component) {
|
|
@@ -3176,12 +3521,12 @@ var FormAnalyzer = class {
|
|
|
3176
3521
|
this.stateVariables.clear();
|
|
3177
3522
|
this.inputElements = [];
|
|
3178
3523
|
this.submitButtons = [];
|
|
3179
|
-
|
|
3524
|
+
traverse5(ast, {
|
|
3180
3525
|
CallExpression: (path11) => {
|
|
3181
3526
|
this.extractStateVariables(path11.node);
|
|
3182
3527
|
}
|
|
3183
3528
|
});
|
|
3184
|
-
|
|
3529
|
+
traverse5(ast, {
|
|
3185
3530
|
JSXElement: (path11) => {
|
|
3186
3531
|
this.extractFormElements(path11.node);
|
|
3187
3532
|
}
|
|
@@ -3217,23 +3562,23 @@ var FormAnalyzer = class {
|
|
|
3217
3562
|
for (const attr of openingElement.attributes) {
|
|
3218
3563
|
if (BabelTypes.isJSXAttribute(attr) && BabelTypes.isJSXIdentifier(attr.name)) {
|
|
3219
3564
|
const propName = attr.name.name;
|
|
3220
|
-
const
|
|
3565
|
+
const propValue2 = this.extractAttributeValue(attr.value);
|
|
3221
3566
|
switch (propName) {
|
|
3222
3567
|
case "label":
|
|
3223
|
-
info.label =
|
|
3568
|
+
info.label = propValue2;
|
|
3224
3569
|
break;
|
|
3225
3570
|
case "placeholder":
|
|
3226
|
-
info.placeholder =
|
|
3571
|
+
info.placeholder = propValue2;
|
|
3227
3572
|
break;
|
|
3228
3573
|
case "keyboardType":
|
|
3229
|
-
info.keyboardType =
|
|
3574
|
+
info.keyboardType = propValue2;
|
|
3230
3575
|
break;
|
|
3231
3576
|
case "testID":
|
|
3232
|
-
info.testID =
|
|
3577
|
+
info.testID = propValue2;
|
|
3233
3578
|
break;
|
|
3234
3579
|
case "appilotsId":
|
|
3235
|
-
info.appilotsId =
|
|
3236
|
-
if (
|
|
3580
|
+
info.appilotsId = propValue2;
|
|
3581
|
+
if (propValue2) info.varName = propValue2;
|
|
3237
3582
|
break;
|
|
3238
3583
|
case "value":
|
|
3239
3584
|
if (attr.value && BabelTypes.isJSXExpressionContainer(attr.value) && BabelTypes.isIdentifier(attr.value.expression)) {
|
|
@@ -3282,7 +3627,7 @@ var FormAnalyzer = class {
|
|
|
3282
3627
|
}
|
|
3283
3628
|
extractValidationRules(ast) {
|
|
3284
3629
|
const rules = {};
|
|
3285
|
-
|
|
3630
|
+
traverse5(ast, {
|
|
3286
3631
|
IfStatement: (path11) => {
|
|
3287
3632
|
const test = path11.node.test;
|
|
3288
3633
|
const rule = this.extractRuleFromCondition(test);
|
|
@@ -3436,7 +3781,7 @@ async function composeAffordances(options) {
|
|
|
3436
3781
|
const exclusive = children.filter((c) => (fanIn.get(c) ?? 0) <= MAX_FAN_IN);
|
|
3437
3782
|
if (exclusive.length === 0) continue;
|
|
3438
3783
|
const actionIds = new Set(screen.actions.map((a) => a.id));
|
|
3439
|
-
const targetIds = new Set((screen.targets ?? []).map((
|
|
3784
|
+
const targetIds = new Set((screen.targets ?? []).map((t15) => t15.id));
|
|
3440
3785
|
const formIds = new Set(screen.forms.map((f) => f.id));
|
|
3441
3786
|
let gained = false;
|
|
3442
3787
|
for (const file of exclusive) {
|
|
@@ -3578,6 +3923,7 @@ var ReactNativePlatformAnalyzer = class {
|
|
|
3578
3923
|
});
|
|
3579
3924
|
return {
|
|
3580
3925
|
screens: enrichedScreens,
|
|
3926
|
+
controlEvidenceFiles: screenAnalyzer.controlEvidenceFiles,
|
|
3581
3927
|
navigation,
|
|
3582
3928
|
analyzedFiles: screenFiles.length,
|
|
3583
3929
|
...screenAnalyzer.screensFilteredOut > 0 ? { screensFilteredOut: screenAnalyzer.screensFilteredOut } : {},
|
|
@@ -3842,7 +4188,7 @@ function extractWebNavigationCalls(ast) {
|
|
|
3842
4188
|
calls.push({ method: "navigate", targetPath: node.right.value });
|
|
3843
4189
|
}
|
|
3844
4190
|
};
|
|
3845
|
-
|
|
4191
|
+
traverse5(ast, {
|
|
3846
4192
|
noScope: !BabelTypes.isFile(ast),
|
|
3847
4193
|
enter: (nodePath) => inspect(nodePath.node)
|
|
3848
4194
|
});
|
|
@@ -3964,7 +4310,7 @@ var WebScreenAnalyzer = class {
|
|
|
3964
4310
|
// ── registerScreen ────────────────────────────────────────────────
|
|
3965
4311
|
detectRegisterScreenCall(ast) {
|
|
3966
4312
|
let found = false;
|
|
3967
|
-
|
|
4313
|
+
traverse5(ast, {
|
|
3968
4314
|
CallExpression: (nodePath) => {
|
|
3969
4315
|
if (found) return;
|
|
3970
4316
|
if (isRegisterScreenCallee(nodePath.node.callee)) {
|
|
@@ -3977,7 +4323,7 @@ var WebScreenAnalyzer = class {
|
|
|
3977
4323
|
}
|
|
3978
4324
|
extractRegisterScreenMetadata(ast) {
|
|
3979
4325
|
let plain = null;
|
|
3980
|
-
|
|
4326
|
+
traverse5(ast, {
|
|
3981
4327
|
CallExpression: (nodePath) => {
|
|
3982
4328
|
if (!isRegisterScreenCallee(nodePath.node.callee)) return;
|
|
3983
4329
|
const arg = nodePath.node.arguments[0];
|
|
@@ -4025,7 +4371,7 @@ var WebScreenAnalyzer = class {
|
|
|
4025
4371
|
extractComponentName(ast) {
|
|
4026
4372
|
let defaultName = "";
|
|
4027
4373
|
let firstExported = "";
|
|
4028
|
-
|
|
4374
|
+
traverse5(ast, {
|
|
4029
4375
|
ExportDefaultDeclaration: (nodePath) => {
|
|
4030
4376
|
const declaration = nodePath.node.declaration;
|
|
4031
4377
|
if (BabelTypes.isFunctionDeclaration(declaration) && declaration.id?.name) {
|
|
@@ -4054,7 +4400,7 @@ var WebScreenAnalyzer = class {
|
|
|
4054
4400
|
extractComponents(ast) {
|
|
4055
4401
|
const components = [];
|
|
4056
4402
|
const seen = /* @__PURE__ */ new Set();
|
|
4057
|
-
|
|
4403
|
+
traverse5(ast, {
|
|
4058
4404
|
JSXOpeningElement: (nodePath) => {
|
|
4059
4405
|
const element = nodePath.node;
|
|
4060
4406
|
const name = getJsxElementName(element);
|
|
@@ -4075,7 +4421,7 @@ var WebScreenAnalyzer = class {
|
|
|
4075
4421
|
// ── <label htmlFor> association ───────────────────────────────────
|
|
4076
4422
|
collectHtmlForLabels(ast) {
|
|
4077
4423
|
const labels = /* @__PURE__ */ new Map();
|
|
4078
|
-
|
|
4424
|
+
traverse5(ast, {
|
|
4079
4425
|
JSXElement: (nodePath) => {
|
|
4080
4426
|
const element = nodePath.node;
|
|
4081
4427
|
if (getJsxElementName(element.openingElement) !== "label") return;
|
|
@@ -4118,7 +4464,7 @@ var WebScreenAnalyzer = class {
|
|
|
4118
4464
|
formBuckets.set(formElement, bucket);
|
|
4119
4465
|
return bucket;
|
|
4120
4466
|
};
|
|
4121
|
-
|
|
4467
|
+
traverse5(ast, {
|
|
4122
4468
|
JSXElement: (nodePath) => {
|
|
4123
4469
|
const element = nodePath.node;
|
|
4124
4470
|
const name = getJsxElementName(element.openingElement);
|
|
@@ -4135,7 +4481,7 @@ var WebScreenAnalyzer = class {
|
|
|
4135
4481
|
if (!bucket.fields.has(field.name)) bucket.fields.set(field.name, field);
|
|
4136
4482
|
}
|
|
4137
4483
|
});
|
|
4138
|
-
|
|
4484
|
+
traverse5(ast, {
|
|
4139
4485
|
JSXElement: (nodePath) => {
|
|
4140
4486
|
const element = nodePath.node;
|
|
4141
4487
|
const name = getJsxElementName(element.openingElement);
|
|
@@ -4313,7 +4659,7 @@ var WebScreenAnalyzer = class {
|
|
|
4313
4659
|
const actionLabels = new Map(
|
|
4314
4660
|
actions.filter((a) => a.label).map((a) => [normalizeLabel(a.label), a])
|
|
4315
4661
|
);
|
|
4316
|
-
|
|
4662
|
+
traverse5(ast, {
|
|
4317
4663
|
JSXElement: (nodePath) => {
|
|
4318
4664
|
const element = nodePath.node;
|
|
4319
4665
|
const name = getJsxElementName(element.openingElement);
|
|
@@ -4544,7 +4890,7 @@ var WebScreenAnalyzer = class {
|
|
|
4544
4890
|
for (const call of extractWebNavigationCalls(ast)) {
|
|
4545
4891
|
if (call.targetPath && call.targetPath.startsWith("/")) targets.add(call.targetPath);
|
|
4546
4892
|
}
|
|
4547
|
-
|
|
4893
|
+
traverse5(ast, {
|
|
4548
4894
|
JSXOpeningElement: (nodePath) => {
|
|
4549
4895
|
const element = nodePath.node;
|
|
4550
4896
|
const name = getJsxElementName(element);
|
|
@@ -4564,7 +4910,7 @@ var WebScreenAnalyzer = class {
|
|
|
4564
4910
|
extractCollections(ast) {
|
|
4565
4911
|
const collections = [];
|
|
4566
4912
|
const seen = /* @__PURE__ */ new Set();
|
|
4567
|
-
|
|
4913
|
+
traverse5(ast, {
|
|
4568
4914
|
JSXExpressionContainer: (nodePath) => {
|
|
4569
4915
|
const expr = nodePath.node.expression;
|
|
4570
4916
|
if (!BabelTypes.isCallExpression(expr) || !BabelTypes.isMemberExpression(expr.callee) || !BabelTypes.isIdentifier(expr.callee.object) || !BabelTypes.isIdentifier(expr.callee.property) || expr.callee.property.name !== "map") {
|
|
@@ -4676,7 +5022,7 @@ function firstCalledFunctionName(node) {
|
|
|
4676
5022
|
}
|
|
4677
5023
|
function containsWindowConfirm(body) {
|
|
4678
5024
|
let found = false;
|
|
4679
|
-
|
|
5025
|
+
traverse5(
|
|
4680
5026
|
body,
|
|
4681
5027
|
{
|
|
4682
5028
|
noScope: true,
|
|
@@ -4728,7 +5074,7 @@ function collectionItemNames(callback) {
|
|
|
4728
5074
|
function collectionDisplayFields(callback, itemNames) {
|
|
4729
5075
|
const fields = /* @__PURE__ */ new Set();
|
|
4730
5076
|
if (!callback.body) return [];
|
|
4731
|
-
|
|
5077
|
+
traverse5(
|
|
4732
5078
|
callback.body,
|
|
4733
5079
|
{
|
|
4734
5080
|
noScope: true,
|
|
@@ -4745,7 +5091,7 @@ function collectionDisplayFields(callback, itemNames) {
|
|
|
4745
5091
|
function collectionKeyField(callback, itemNames) {
|
|
4746
5092
|
let keyField;
|
|
4747
5093
|
if (!callback.body) return void 0;
|
|
4748
|
-
|
|
5094
|
+
traverse5(
|
|
4749
5095
|
callback.body,
|
|
4750
5096
|
{
|
|
4751
5097
|
noScope: true,
|
|
@@ -4888,7 +5234,7 @@ var WebNavigationAnalyzer = class {
|
|
|
4888
5234
|
if (BabelTypes.isJSXElement(child)) visitRoute(child, fullPath);
|
|
4889
5235
|
}
|
|
4890
5236
|
};
|
|
4891
|
-
|
|
5237
|
+
traverse5(ast, {
|
|
4892
5238
|
JSXElement: (nodePath) => {
|
|
4893
5239
|
const name = getJsxElementName(nodePath.node.openingElement);
|
|
4894
5240
|
if (name !== "Routes" && name !== "Route") return;
|
|
@@ -4927,7 +5273,7 @@ var WebNavigationAnalyzer = class {
|
|
|
4927
5273
|
"createMemoryRouter",
|
|
4928
5274
|
"useRoutes"
|
|
4929
5275
|
]);
|
|
4930
|
-
|
|
5276
|
+
traverse5(ast, {
|
|
4931
5277
|
CallExpression: (nodePath) => {
|
|
4932
5278
|
const callee = nodePath.node.callee;
|
|
4933
5279
|
if (!BabelTypes.isIdentifier(callee) || !ROUTER_FACTORIES.has(callee.name)) return;
|
|
@@ -5360,8 +5706,8 @@ var ZodParsedType = util.arrayToEnum([
|
|
|
5360
5706
|
"set"
|
|
5361
5707
|
]);
|
|
5362
5708
|
var getParsedType = (data) => {
|
|
5363
|
-
const
|
|
5364
|
-
switch (
|
|
5709
|
+
const t15 = typeof data;
|
|
5710
|
+
switch (t15) {
|
|
5365
5711
|
case "undefined":
|
|
5366
5712
|
return ZodParsedType.undefined;
|
|
5367
5713
|
case "string":
|
|
@@ -9206,7 +9552,25 @@ function isSafeImageUrl(value) {
|
|
|
9206
9552
|
return SAFE_IMAGE_URL_RE.test(trimmed);
|
|
9207
9553
|
}
|
|
9208
9554
|
|
|
9209
|
-
// ../shared/dist/chunk-
|
|
9555
|
+
// ../shared/dist/chunk-KUMPS6RH.mjs
|
|
9556
|
+
var SUPPORTED_APPILOTS_LOCALES = ["pt-BR", "en", "es", "fr"];
|
|
9557
|
+
|
|
9558
|
+
// ../shared/dist/chunk-2GTRKNPJ.mjs
|
|
9559
|
+
var symbols = external_exports.array(external_exports.string().max(240)).max(12);
|
|
9560
|
+
var controlEvidenceSchema = external_exports.object({
|
|
9561
|
+
version: external_exports.literal(1),
|
|
9562
|
+
siteId: external_exports.string().regex(/^[a-f0-9]{20}$/),
|
|
9563
|
+
component: external_exports.string().max(240),
|
|
9564
|
+
icons: symbols,
|
|
9565
|
+
handler: external_exports.string().max(240).optional(),
|
|
9566
|
+
calls: symbols,
|
|
9567
|
+
argumentBindings: symbols,
|
|
9568
|
+
conditions: symbols,
|
|
9569
|
+
nativeConfirmation: external_exports.object({
|
|
9570
|
+
title: external_exports.string().max(240).optional(),
|
|
9571
|
+
destructiveOption: external_exports.boolean()
|
|
9572
|
+
}).optional()
|
|
9573
|
+
});
|
|
9210
9574
|
var locatorSourceSchema = external_exports.enum([
|
|
9211
9575
|
"appilotsId",
|
|
9212
9576
|
"testID",
|
|
@@ -9633,7 +9997,9 @@ var snapshotInputSchema = external_exports.object({
|
|
|
9633
9997
|
inModal: external_exports.boolean().optional()
|
|
9634
9998
|
}).passthrough();
|
|
9635
9999
|
var snapshotButtonSchema = external_exports.object({
|
|
10000
|
+
controlEvidence: controlEvidenceSchema.optional(),
|
|
9636
10001
|
id: boundedString(160).optional(),
|
|
10002
|
+
dispatchable: external_exports.boolean().optional(),
|
|
9637
10003
|
provenance: identityProvenanceSchema.optional(),
|
|
9638
10004
|
/**
|
|
9639
10005
|
* False when the control is mounted but currently OUTSIDE the window —
|
|
@@ -9694,7 +10060,36 @@ var snapshotListSchema = external_exports.object({
|
|
|
9694
10060
|
*/
|
|
9695
10061
|
source: boundedString(40).optional(),
|
|
9696
10062
|
itemCount: external_exports.number().int().optional(),
|
|
10063
|
+
/**
|
|
10064
|
+
* Size of the whole collection when the app declared it, for a list
|
|
10065
|
+
* that is a WINDOW onto more data than it holds.
|
|
10066
|
+
*
|
|
10067
|
+
* `itemCount` is how many rows the list is rendering from and
|
|
10068
|
+
* `visibleItemCount` how many of those are mounted; neither can
|
|
10069
|
+
* express "there are 36 and you are looking at the first 20",
|
|
10070
|
+
* because a paginated list's `data` is the page. Absent means
|
|
10071
|
+
* unknown — never "same as itemCount".
|
|
10072
|
+
*/
|
|
10073
|
+
totalItemCount: external_exports.number().int().optional(),
|
|
9697
10074
|
visibleItemCount: external_exports.number().int().optional(),
|
|
10075
|
+
viewportItemCount: external_exports.number().int().nonnegative().optional(),
|
|
10076
|
+
exploration: external_exports.object({
|
|
10077
|
+
revision: external_exports.number().int().nonnegative(),
|
|
10078
|
+
observedItemCount: external_exports.number().int().min(0).max(5e3),
|
|
10079
|
+
observedRanges: external_exports.array(
|
|
10080
|
+
external_exports.object({
|
|
10081
|
+
start: external_exports.number().int().nonnegative(),
|
|
10082
|
+
end: external_exports.number().int().nonnegative()
|
|
10083
|
+
})
|
|
10084
|
+
).max(32),
|
|
10085
|
+
rangesTruncated: external_exports.boolean(),
|
|
10086
|
+
coverage: external_exports.enum(["partial", "all-loaded"]),
|
|
10087
|
+
pagination: external_exports.enum(["possible", "not-declared"]),
|
|
10088
|
+
scrollSteps: external_exports.number().int().nonnegative(),
|
|
10089
|
+
remainingScrollSteps: external_exports.number().int().nonnegative(),
|
|
10090
|
+
consecutiveNoProgress: external_exports.number().int().nonnegative(),
|
|
10091
|
+
lastScroll: external_exports.enum(["moved", "no-progress", "boundary", "unverified"]).optional()
|
|
10092
|
+
}).optional(),
|
|
9698
10093
|
refreshing: external_exports.boolean().optional(),
|
|
9699
10094
|
empty: external_exports.boolean().optional(),
|
|
9700
10095
|
label: boundedString(300).optional(),
|
|
@@ -9742,6 +10137,7 @@ var snapshotChoiceGroupSchema = external_exports.object({
|
|
|
9742
10137
|
}).passthrough();
|
|
9743
10138
|
var snapshotElementSchema = external_exports.object({
|
|
9744
10139
|
id: boundedString(160).optional(),
|
|
10140
|
+
dispatchable: external_exports.boolean().optional(),
|
|
9745
10141
|
role: boundedString(40).optional(),
|
|
9746
10142
|
label: boundedString(300).optional(),
|
|
9747
10143
|
texts: external_exports.array(boundedString(500)).max(50).optional(),
|
|
@@ -9804,6 +10200,8 @@ var agentSnapshotSchema = external_exports.object({
|
|
|
9804
10200
|
loadingFinished: external_exports.boolean().optional(),
|
|
9805
10201
|
textsAdded: external_exports.number().int().nonnegative().optional(),
|
|
9806
10202
|
textsRemoved: external_exports.number().int().nonnegative().optional(),
|
|
10203
|
+
buttonsAddedIndices: external_exports.array(external_exports.number().int().nonnegative()).max(12).optional(),
|
|
10204
|
+
buttonsRemoved: external_exports.number().int().nonnegative().optional(),
|
|
9807
10205
|
visibleRowsDelta: external_exports.number().int().optional(),
|
|
9808
10206
|
totalRowsDelta: external_exports.number().int().optional(),
|
|
9809
10207
|
fieldsNewlyFilled: external_exports.array(boundedString(160)).max(12).optional(),
|
|
@@ -9829,6 +10227,10 @@ var agentSnapshotSchema = external_exports.object({
|
|
|
9829
10227
|
}).passthrough().optional()
|
|
9830
10228
|
}).passthrough();
|
|
9831
10229
|
var agentContextSchema = external_exports.object({
|
|
10230
|
+
missionProtocol: external_exports.literal(1).optional(),
|
|
10231
|
+
missionId: boundedString(160).optional(),
|
|
10232
|
+
/** Preferred supported device language, reported by the SDK independently of map/UI labels. */
|
|
10233
|
+
deviceLocale: external_exports.enum(SUPPORTED_APPILOTS_LOCALES).optional(),
|
|
9832
10234
|
/**
|
|
9833
10235
|
* Client platform this observation was captured on. Optional and
|
|
9834
10236
|
* additive (see `clientPlatformSchema`) — absent means
|
|
@@ -10079,6 +10481,12 @@ var actionDiagnoseSchema = external_exports.object({
|
|
|
10079
10481
|
requiresUserInput: external_exports.boolean().optional()
|
|
10080
10482
|
});
|
|
10081
10483
|
var actionResultSchema = external_exports.object({
|
|
10484
|
+
nativeConfirmation: external_exports.object({
|
|
10485
|
+
title: external_exports.string().max(300),
|
|
10486
|
+
message: external_exports.string().max(1e3).optional(),
|
|
10487
|
+
buttonLabel: external_exports.string().max(160).optional(),
|
|
10488
|
+
handlerCompleted: external_exports.boolean()
|
|
10489
|
+
}).optional(),
|
|
10082
10490
|
actionId: external_exports.string(),
|
|
10083
10491
|
type: external_exports.string(),
|
|
10084
10492
|
success: external_exports.boolean(),
|
|
@@ -10206,7 +10614,7 @@ external_exports.object({
|
|
|
10206
10614
|
/** null = remove webhook, undefined = leave unchanged. */
|
|
10207
10615
|
budgetWebhookUrl: external_exports.string().url().nullable().optional()
|
|
10208
10616
|
}).strict();
|
|
10209
|
-
var localeSchema = external_exports.enum(
|
|
10617
|
+
var localeSchema = external_exports.enum(SUPPORTED_APPILOTS_LOCALES);
|
|
10210
10618
|
var hexColorSchema = external_exports.string().regex(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
|
|
10211
10619
|
message: "Must be a hex color like #6366f1"
|
|
10212
10620
|
});
|
|
@@ -11217,9 +11625,22 @@ var MCPGenerator = class _MCPGenerator {
|
|
|
11217
11625
|
const filePath = path2__default.resolve(outputDir, `mcp-document.${this.options.format}`);
|
|
11218
11626
|
await writeFile(filePath, serialized, "utf-8");
|
|
11219
11627
|
console.log(`[MCPGenerator] Document written to: ${filePath}`);
|
|
11628
|
+
const controlFiles = analyzed.controlEvidenceFiles ?? {};
|
|
11629
|
+
await writeFile(
|
|
11630
|
+
path2__default.resolve(outputDir, "control-evidence.json"),
|
|
11631
|
+
JSON.stringify({ version: 1, files: controlFiles }, null, 2),
|
|
11632
|
+
"utf-8"
|
|
11633
|
+
);
|
|
11220
11634
|
const checksumFilePath = path2__default.resolve(outputDir, ".appilots-checksum");
|
|
11221
11635
|
await writeFile(checksumFilePath, checksum, "utf-8");
|
|
11222
11636
|
console.log(`[MCPGenerator] Checksum written to: ${checksumFilePath}`);
|
|
11637
|
+
const evidenceCount = Object.values(controlFiles).reduce(
|
|
11638
|
+
(sum, entries) => sum + entries.length,
|
|
11639
|
+
0
|
|
11640
|
+
);
|
|
11641
|
+
console.log(
|
|
11642
|
+
`[MCPGenerator] Source evidence: ${evidenceCount} icon controls across ${Object.keys(controlFiles).length} files (runtime binding required)`
|
|
11643
|
+
);
|
|
11223
11644
|
console.log("[MCPGenerator] Generation complete!");
|
|
11224
11645
|
return {
|
|
11225
11646
|
document,
|
|
@@ -11285,7 +11706,8 @@ var KNOWN_CONFIG_KEYS = [
|
|
|
11285
11706
|
"navigationExclude",
|
|
11286
11707
|
"platform",
|
|
11287
11708
|
"manifestPath",
|
|
11288
|
-
"eval"
|
|
11709
|
+
"eval",
|
|
11710
|
+
"knowledge"
|
|
11289
11711
|
];
|
|
11290
11712
|
var KEY_ALIASES = {
|
|
11291
11713
|
apiUrl: "serverUrl",
|
|
@@ -11433,7 +11855,8 @@ function saveConfig(dir, config) {
|
|
|
11433
11855
|
navigationExclude: config.navigationExclude || existingConfig?.navigationExclude,
|
|
11434
11856
|
platform: config.platform || existingConfig?.platform,
|
|
11435
11857
|
manifestPath: config.manifestPath || existingConfig?.manifestPath,
|
|
11436
|
-
eval: config.eval || existingConfig?.eval
|
|
11858
|
+
eval: config.eval || existingConfig?.eval,
|
|
11859
|
+
knowledge: config.knowledge || existingConfig?.knowledge
|
|
11437
11860
|
};
|
|
11438
11861
|
try {
|
|
11439
11862
|
writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
|
|
@@ -11526,6 +11949,19 @@ function validateConfig(config) {
|
|
|
11526
11949
|
if (config.manifestPath !== void 0 && typeof config.manifestPath !== "string") {
|
|
11527
11950
|
errors.push("manifestPath must be a string");
|
|
11528
11951
|
}
|
|
11952
|
+
if (config.knowledge !== void 0) {
|
|
11953
|
+
if (typeof config.knowledge !== "object" || config.knowledge === null || Array.isArray(config.knowledge)) {
|
|
11954
|
+
errors.push("knowledge must be an object");
|
|
11955
|
+
} else {
|
|
11956
|
+
const kc = config.knowledge;
|
|
11957
|
+
for (const field of ["sources", "exclude"]) {
|
|
11958
|
+
const value = kc[field];
|
|
11959
|
+
if (value !== void 0 && (!Array.isArray(value) || !value.every((item) => typeof item === "string"))) {
|
|
11960
|
+
errors.push(`knowledge.${field} must be an array of strings`);
|
|
11961
|
+
}
|
|
11962
|
+
}
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11529
11965
|
if (config.eval !== void 0) {
|
|
11530
11966
|
if (typeof config.eval !== "object" || config.eval === null || Array.isArray(config.eval)) {
|
|
11531
11967
|
errors.push("eval must be an object");
|
|
@@ -11802,6 +12238,48 @@ var AppilotsAPIClient = class {
|
|
|
11802
12238
|
};
|
|
11803
12239
|
}
|
|
11804
12240
|
}
|
|
12241
|
+
/**
|
|
12242
|
+
* Syncs knowledge documents with the Appilots backend.
|
|
12243
|
+
*
|
|
12244
|
+
* @param documents Array of documents to sync (filename, base64 content, mimeType, checksum)
|
|
12245
|
+
* @returns KnowledgeSyncResult with counts of uploaded, skipped, and errored docs
|
|
12246
|
+
*/
|
|
12247
|
+
async knowledgeSync(documents) {
|
|
12248
|
+
try {
|
|
12249
|
+
const response = await this.request(`${this.baseUrl}/cli/knowledge/sync`, {
|
|
12250
|
+
method: "POST",
|
|
12251
|
+
headers: {
|
|
12252
|
+
"Content-Type": "application/json",
|
|
12253
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
12254
|
+
},
|
|
12255
|
+
body: JSON.stringify({ documents })
|
|
12256
|
+
});
|
|
12257
|
+
if (!response.ok) {
|
|
12258
|
+
const errorData = await response.json().catch(() => ({}));
|
|
12259
|
+
return {
|
|
12260
|
+
success: false,
|
|
12261
|
+
uploaded: 0,
|
|
12262
|
+
skipped: 0,
|
|
12263
|
+
errors: 0,
|
|
12264
|
+
error: describeApiError(errorData, `HTTP ${response.status}: ${response.statusText}`)
|
|
12265
|
+
};
|
|
12266
|
+
}
|
|
12267
|
+
const json = await response.json();
|
|
12268
|
+
const inner = json.data ?? json;
|
|
12269
|
+
return {
|
|
12270
|
+
success: true,
|
|
12271
|
+
...inner
|
|
12272
|
+
};
|
|
12273
|
+
} catch (error) {
|
|
12274
|
+
return {
|
|
12275
|
+
success: false,
|
|
12276
|
+
uploaded: 0,
|
|
12277
|
+
skipped: 0,
|
|
12278
|
+
errors: 0,
|
|
12279
|
+
error: error instanceof Error ? error.message : "Failed to sync knowledge with Appilots API"
|
|
12280
|
+
};
|
|
12281
|
+
}
|
|
12282
|
+
}
|
|
11805
12283
|
/**
|
|
11806
12284
|
* Checks if the Appilots API server is healthy
|
|
11807
12285
|
*
|
|
@@ -11820,7 +12298,7 @@ var AppilotsAPIClient = class {
|
|
|
11820
12298
|
};
|
|
11821
12299
|
|
|
11822
12300
|
// src/version.ts
|
|
11823
|
-
var CLI_VERSION = "0.
|
|
12301
|
+
var CLI_VERSION = "0.13.0";
|
|
11824
12302
|
|
|
11825
12303
|
export { AppilotsAPIClient, CLI_VERSION, ComponentAnalyzer, DEFAULT_MANIFEST_FILENAME, DEFAULT_WEB_SCREEN_PATTERNS, FormAnalyzer, GenericPlatformAnalyzer, MCPGenerator, NavigationAnalyzer, ReactNativePlatformAnalyzer, ReactWebPlatformAnalyzer, ScreenAnalyzer, WebNavigationAnalyzer, WebScreenAnalyzer, formatMetadataWarnings, getConfigPath, getEnvOverrides, lintActionMetadata, loadConfig, loadManifest, mergeManifestNavigation, mergeManifestScreens, resolvePathToScreen, saveConfig, screenNameFromPath, validateConfig };
|
|
11826
12304
|
//# sourceMappingURL=index.mjs.map
|