@appilots/cli 0.4.0 → 0.10.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/dist/cli/index.js +1901 -300
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +77 -9
- package/dist/index.d.ts +77 -9
- package/dist/index.js +794 -149
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +794 -150
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -83,6 +83,45 @@ function hasJsxAttribute(element, name) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// src/ast/jsx/classify.ts
|
|
86
|
+
var NON_INTERACTION_HANDLERS = /* @__PURE__ */ new Set([
|
|
87
|
+
"onLayout",
|
|
88
|
+
"onScroll",
|
|
89
|
+
"onScrollBeginDrag",
|
|
90
|
+
"onScrollEndDrag",
|
|
91
|
+
"onMomentumScrollBegin",
|
|
92
|
+
"onMomentumScrollEnd",
|
|
93
|
+
"onContentSizeChange",
|
|
94
|
+
"onEndReached",
|
|
95
|
+
"onViewableItemsChanged",
|
|
96
|
+
"onLoad",
|
|
97
|
+
"onLoadStart",
|
|
98
|
+
"onLoadEnd",
|
|
99
|
+
"onError",
|
|
100
|
+
"onProgress",
|
|
101
|
+
"onFocus",
|
|
102
|
+
"onBlur"
|
|
103
|
+
]);
|
|
104
|
+
var VALUE_HANDLERS = /* @__PURE__ */ new Set([
|
|
105
|
+
"onChange",
|
|
106
|
+
"onChangeText",
|
|
107
|
+
"onValueChange",
|
|
108
|
+
"onSelect",
|
|
109
|
+
"onSelectionChange",
|
|
110
|
+
"onSubmitEditing"
|
|
111
|
+
]);
|
|
112
|
+
function pressHandlerProp(element) {
|
|
113
|
+
let fallback;
|
|
114
|
+
for (const attr of element.attributes) {
|
|
115
|
+
if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") continue;
|
|
116
|
+
const name = attr.name.name;
|
|
117
|
+
if (!/^on[A-Z]/.test(name)) continue;
|
|
118
|
+
if (attr.value?.type !== "JSXExpressionContainer") continue;
|
|
119
|
+
if (name === "onPress") return name;
|
|
120
|
+
if (NON_INTERACTION_HANDLERS.has(name) || VALUE_HANDLERS.has(name)) continue;
|
|
121
|
+
fallback ??= name;
|
|
122
|
+
}
|
|
123
|
+
return fallback;
|
|
124
|
+
}
|
|
86
125
|
var VIEW_COMPONENTS = /* @__PURE__ */ new Set(["View", "ScrollView", "SafeAreaView", "KeyboardAvoidingView"]);
|
|
87
126
|
var INPUT_COMPONENTS = /* @__PURE__ */ new Set(["TextInput", "Input", "HocInput"]);
|
|
88
127
|
var BUTTON_COMPONENTS = /* @__PURE__ */ new Set(["TouchableOpacity", "Pressable", "Button"]);
|
|
@@ -97,17 +136,16 @@ function classifyJsxComponent(name, element) {
|
|
|
97
136
|
if (INPUT_COMPONENTS.has(name)) return "input";
|
|
98
137
|
if (BUTTON_COMPONENTS.has(name)) return "button";
|
|
99
138
|
if (element) {
|
|
100
|
-
const hasOptions = hasJsxAttribute(element, "options");
|
|
139
|
+
const hasOptions = hasJsxAttribute(element, "options") || hasJsxAttribute(element, "segments") || hasJsxAttribute(element, "choices") || hasJsxAttribute(element, "tabs");
|
|
101
140
|
const hasValue = hasJsxAttribute(element, "value");
|
|
102
141
|
const hasChecked = hasJsxAttribute(element, "checked") || hasJsxAttribute(element, "selected");
|
|
103
142
|
const hasOnChange = hasJsxAttribute(element, "onChange") || hasJsxAttribute(element, "onValueChange") || hasJsxAttribute(element, "onChangeText");
|
|
104
|
-
const hasOnPress = hasJsxAttribute(element, "onPress");
|
|
105
143
|
if (hasOptions && (hasValue || hasOnChange)) return "select";
|
|
106
144
|
if (hasChecked && hasOnChange) return "toggle";
|
|
107
145
|
if (hasOnChange && (hasJsxAttribute(element, "label") || hasJsxAttribute(element, "placeholder"))) {
|
|
108
146
|
return "input";
|
|
109
147
|
}
|
|
110
|
-
if (
|
|
148
|
+
if (pressHandlerProp(element)) return "button";
|
|
111
149
|
if (getStringAttr(element, "visible") || getExpressionIdentifierAttr(element, "visible")) {
|
|
112
150
|
return "modal";
|
|
113
151
|
}
|
|
@@ -445,12 +483,17 @@ var ScreenAnalyzer = class {
|
|
|
445
483
|
}
|
|
446
484
|
} catch (error) {
|
|
447
485
|
if (this.verbose) {
|
|
448
|
-
console.warn(
|
|
486
|
+
console.warn(
|
|
487
|
+
`[ScreenAnalyzer] Failed to parse ${file}:`,
|
|
488
|
+
error instanceof Error ? error.message : error
|
|
489
|
+
);
|
|
449
490
|
}
|
|
450
491
|
}
|
|
451
492
|
}
|
|
452
493
|
if (this.verbose && this.strictScreens) {
|
|
453
|
-
console.log(
|
|
494
|
+
console.log(
|
|
495
|
+
`[ScreenAnalyzer] Strict mode: ${screens.length} included, ${this.screensFilteredOut} filtered out`
|
|
496
|
+
);
|
|
454
497
|
}
|
|
455
498
|
return screens;
|
|
456
499
|
}
|
|
@@ -738,7 +781,11 @@ var ScreenAnalyzer = class {
|
|
|
738
781
|
for (const form of secondary) {
|
|
739
782
|
const existing = out.find((candidate) => candidate.id === form.id) ?? this.findFormWithSharedFields(out, form);
|
|
740
783
|
if (!existing) {
|
|
741
|
-
out.push({
|
|
784
|
+
out.push({
|
|
785
|
+
...form,
|
|
786
|
+
fields: [...form.fields],
|
|
787
|
+
submitAction: this.namedSubmitAction(form.submitAction)
|
|
788
|
+
});
|
|
742
789
|
continue;
|
|
743
790
|
}
|
|
744
791
|
for (const field of form.fields) {
|
|
@@ -757,9 +804,12 @@ var ScreenAnalyzer = class {
|
|
|
757
804
|
findEquivalentField(fields, incoming) {
|
|
758
805
|
return fields.find((field) => {
|
|
759
806
|
if (field.name && incoming.name && field.name === incoming.name) return true;
|
|
760
|
-
if (field.valueBinding && incoming.valueBinding && field.valueBinding === incoming.valueBinding)
|
|
761
|
-
|
|
762
|
-
if (field.
|
|
807
|
+
if (field.valueBinding && incoming.valueBinding && field.valueBinding === incoming.valueBinding)
|
|
808
|
+
return true;
|
|
809
|
+
if (field.locator?.id && incoming.locator?.id && field.locator.id === incoming.locator.id)
|
|
810
|
+
return true;
|
|
811
|
+
if (field.placeholder && incoming.placeholder && field.placeholder === incoming.placeholder)
|
|
812
|
+
return true;
|
|
763
813
|
if (field.locator?.accessibilityLabel && incoming.locator?.accessibilityLabel && field.locator.accessibilityLabel === incoming.locator.accessibilityLabel) {
|
|
764
814
|
return true;
|
|
765
815
|
}
|
|
@@ -919,9 +969,11 @@ var ScreenAnalyzer = class {
|
|
|
919
969
|
source: field.locator?.source ?? "accessibilityLabel"
|
|
920
970
|
});
|
|
921
971
|
} else if (attrName === "keyboardType") {
|
|
922
|
-
if (attr.value.value === "email-address" || attr.value.value === "email")
|
|
972
|
+
if (attr.value.value === "email-address" || attr.value.value === "email")
|
|
973
|
+
field.type = "email";
|
|
923
974
|
if (attr.value.value === "phone-pad") field.type = "phone";
|
|
924
|
-
if (attr.value.value === "numeric" || attr.value.value === "number-pad")
|
|
975
|
+
if (attr.value.value === "numeric" || attr.value.value === "number-pad")
|
|
976
|
+
field.type = "number";
|
|
925
977
|
}
|
|
926
978
|
} else if (BabelTypes__namespace.isJSXExpressionContainer(attr.value)) {
|
|
927
979
|
if (attrName === "value" && BabelTypes__namespace.isIdentifier(attr.value.expression)) {
|
|
@@ -1087,8 +1139,10 @@ var ScreenAnalyzer = class {
|
|
|
1087
1139
|
};
|
|
1088
1140
|
if (handler.nativeConfirmationExpected) action.nativeConfirmationExpected = true;
|
|
1089
1141
|
if (handler.targetScreen && !action.targetScreen) action.targetScreen = handler.targetScreen;
|
|
1090
|
-
if (handler.successSignal && !action.successSignal)
|
|
1091
|
-
|
|
1142
|
+
if (handler.successSignal && !action.successSignal)
|
|
1143
|
+
action.successSignal = handler.successSignal;
|
|
1144
|
+
if (handler.failureSignal && !action.failureSignal)
|
|
1145
|
+
action.failureSignal = handler.failureSignal;
|
|
1092
1146
|
if (handler.opensModal && !action.opensModal) action.opensModal = handler.opensModal;
|
|
1093
1147
|
if (handler.destructive || action.destructive === true || action.requiresConfirmation === true || action.effect === "destructive" || action.riskLevel === "high") {
|
|
1094
1148
|
action.destructive = true;
|
|
@@ -1106,6 +1160,7 @@ var ScreenAnalyzer = class {
|
|
|
1106
1160
|
if (!BabelTypes__namespace.isJSXIdentifier(element.name)) return;
|
|
1107
1161
|
const componentName = element.name.name;
|
|
1108
1162
|
if (classifyJsxComponent(componentName, element) !== "button") return;
|
|
1163
|
+
const pressProp = pressHandlerProp(element);
|
|
1109
1164
|
let label;
|
|
1110
1165
|
let handler;
|
|
1111
1166
|
for (const attr of element.attributes) {
|
|
@@ -1115,7 +1170,7 @@ var ScreenAnalyzer = class {
|
|
|
1115
1170
|
if ((attrName === "title" || attrName === "accessibilityLabel" || attrName === "label") && BabelTypes__namespace.isStringLiteral(attr.value)) {
|
|
1116
1171
|
label = attr.value.value;
|
|
1117
1172
|
}
|
|
1118
|
-
if (attrName ===
|
|
1173
|
+
if (attrName === pressProp && BabelTypes__namespace.isJSXExpressionContainer(attr.value) && BabelTypes__namespace.isIdentifier(attr.value.expression)) {
|
|
1119
1174
|
handler = attr.value.expression.name;
|
|
1120
1175
|
}
|
|
1121
1176
|
}
|
|
@@ -1181,10 +1236,13 @@ var ScreenAnalyzer = class {
|
|
|
1181
1236
|
}
|
|
1182
1237
|
};
|
|
1183
1238
|
if (fn.body) {
|
|
1184
|
-
traverse4__default.default(
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1239
|
+
traverse4__default.default(
|
|
1240
|
+
fn.body,
|
|
1241
|
+
{
|
|
1242
|
+
noScope: true,
|
|
1243
|
+
enter: (nodePath) => inspectNode(nodePath.node)
|
|
1244
|
+
}
|
|
1245
|
+
);
|
|
1188
1246
|
}
|
|
1189
1247
|
const expectedOutcome = targetScreen ? "navigation" : hasStateSetter ? "inline-feedback" : hasAwait || hasThen ? "mixed" : "none";
|
|
1190
1248
|
return {
|
|
@@ -1242,6 +1300,7 @@ var ScreenAnalyzer = class {
|
|
|
1242
1300
|
};
|
|
1243
1301
|
const componentName = BabelTypes__namespace.isJSXIdentifier(element.name) ? element.name.name : void 0;
|
|
1244
1302
|
action.sourceComponent = componentName;
|
|
1303
|
+
const pressProp = pressHandlerProp(element) ?? "onPress";
|
|
1245
1304
|
let handlerName;
|
|
1246
1305
|
for (const attr of element.attributes) {
|
|
1247
1306
|
if (!BabelTypes__namespace.isJSXAttribute(attr)) continue;
|
|
@@ -1276,7 +1335,7 @@ var ScreenAnalyzer = class {
|
|
|
1276
1335
|
}
|
|
1277
1336
|
}
|
|
1278
1337
|
} else if (BabelTypes__namespace.isJSXExpressionContainer(attr.value)) {
|
|
1279
|
-
if (attrName ===
|
|
1338
|
+
if (attrName === pressProp && BabelTypes__namespace.isIdentifier(attr.value.expression)) {
|
|
1280
1339
|
handlerName = attr.value.expression.name.toLowerCase();
|
|
1281
1340
|
action.handler = attr.value.expression.name;
|
|
1282
1341
|
if (handlerName.includes("submit")) {
|
|
@@ -1284,7 +1343,7 @@ var ScreenAnalyzer = class {
|
|
|
1284
1343
|
} else if (handlerName.includes("navigate")) {
|
|
1285
1344
|
action.type = "navigation";
|
|
1286
1345
|
}
|
|
1287
|
-
} else if (attrName ===
|
|
1346
|
+
} else if (attrName === pressProp) {
|
|
1288
1347
|
const inline = this.extractInlineOnPressMetadata(attr.value.expression);
|
|
1289
1348
|
if (inline.handler) {
|
|
1290
1349
|
handlerName = inline.handler.toLowerCase();
|
|
@@ -1308,7 +1367,11 @@ var ScreenAnalyzer = class {
|
|
|
1308
1367
|
}
|
|
1309
1368
|
}
|
|
1310
1369
|
if (!action.locator && action.id) {
|
|
1311
|
-
action.locator = {
|
|
1370
|
+
action.locator = {
|
|
1371
|
+
id: action.id,
|
|
1372
|
+
label: action.label,
|
|
1373
|
+
source: action.label ? "label" : "inferred"
|
|
1374
|
+
};
|
|
1312
1375
|
}
|
|
1313
1376
|
if (this.isElementDestructive(element, handlerName, action.id)) {
|
|
1314
1377
|
action.destructive = true;
|
|
@@ -1450,24 +1513,27 @@ var ScreenAnalyzer = class {
|
|
|
1450
1513
|
}
|
|
1451
1514
|
extractRowAction(fn) {
|
|
1452
1515
|
let action;
|
|
1453
|
-
traverse4__default.default(
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1516
|
+
traverse4__default.default(
|
|
1517
|
+
fn.body,
|
|
1518
|
+
{
|
|
1519
|
+
noScope: true,
|
|
1520
|
+
CallExpression: (nodePath) => {
|
|
1521
|
+
const node = nodePath.node;
|
|
1522
|
+
if (!BabelTypes__namespace.isMemberExpression(node.callee) || !BabelTypes__namespace.isIdentifier(node.callee.object) || node.callee.object.name !== "navigation" || !BabelTypes__namespace.isIdentifier(node.callee.property) || !["navigate", "push", "replace"].includes(node.callee.property.name)) {
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
const firstArg = node.arguments[0];
|
|
1526
|
+
if (!BabelTypes__namespace.isStringLiteral(firstArg)) return;
|
|
1527
|
+
const params = this.extractNavigationParams(node.arguments[1]);
|
|
1528
|
+
action = {
|
|
1529
|
+
type: "navigation",
|
|
1530
|
+
targetScreen: firstArg.value,
|
|
1531
|
+
...Object.keys(params).length > 0 ? { params } : {},
|
|
1532
|
+
description: `Pressing a row opens ${firstArg.value}`
|
|
1533
|
+
};
|
|
1459
1534
|
}
|
|
1460
|
-
const firstArg = node.arguments[0];
|
|
1461
|
-
if (!BabelTypes__namespace.isStringLiteral(firstArg)) return;
|
|
1462
|
-
const params = this.extractNavigationParams(node.arguments[1]);
|
|
1463
|
-
action = {
|
|
1464
|
-
type: "navigation",
|
|
1465
|
-
targetScreen: firstArg.value,
|
|
1466
|
-
...Object.keys(params).length > 0 ? { params } : {},
|
|
1467
|
-
description: `Pressing a row opens ${firstArg.value}`
|
|
1468
|
-
};
|
|
1469
1535
|
}
|
|
1470
|
-
|
|
1536
|
+
);
|
|
1471
1537
|
return action;
|
|
1472
1538
|
}
|
|
1473
1539
|
extractNavigationParams(arg) {
|
|
@@ -1500,15 +1566,18 @@ var ScreenAnalyzer = class {
|
|
|
1500
1566
|
} else if (BabelTypes__namespace.isIdentifier(firstParam)) {
|
|
1501
1567
|
itemNames.add(firstParam.name);
|
|
1502
1568
|
}
|
|
1503
|
-
traverse4__default.default(
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1569
|
+
traverse4__default.default(
|
|
1570
|
+
fn.body,
|
|
1571
|
+
{
|
|
1572
|
+
noScope: true,
|
|
1573
|
+
MemberExpression: (nodePath) => {
|
|
1574
|
+
const node = nodePath.node;
|
|
1575
|
+
if (BabelTypes__namespace.isIdentifier(node.object) && itemNames.has(node.object.name) && BabelTypes__namespace.isIdentifier(node.property)) {
|
|
1576
|
+
fields.add(node.property.name);
|
|
1577
|
+
}
|
|
1509
1578
|
}
|
|
1510
1579
|
}
|
|
1511
|
-
|
|
1580
|
+
);
|
|
1512
1581
|
return Array.from(fields).sort();
|
|
1513
1582
|
}
|
|
1514
1583
|
inferItemType(dataSource, renderItem, displayFields) {
|
|
@@ -1520,11 +1589,19 @@ var ScreenAnalyzer = class {
|
|
|
1520
1589
|
if (displayFields.length > 0) return "Item";
|
|
1521
1590
|
return void 0;
|
|
1522
1591
|
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Identity comes from how a field is NAMED, not from what the app
|
|
1594
|
+
* sells: `id`/`uuid`/`key`/`slug` are conventions any codebase uses,
|
|
1595
|
+
* `name`/`title`/`email` are how any row introduces itself. `plate`
|
|
1596
|
+
* used to sit in this list and read like one of them — but it is the
|
|
1597
|
+
* example app's schema, and no other tenant ever got its equivalent
|
|
1598
|
+
* (`mrn`, `trackingNumber`, `sku`) added here.
|
|
1599
|
+
*/
|
|
1523
1600
|
inferIdentityFields(keyField, displayFields) {
|
|
1524
1601
|
const out = /* @__PURE__ */ new Set();
|
|
1525
1602
|
if (keyField) out.add(keyField);
|
|
1526
1603
|
for (const field of displayFields) {
|
|
1527
|
-
if (/^(id|uuid|key|name|title|
|
|
1604
|
+
if (/^(id|uuid|key|name|title|email|slug)$/i.test(field)) out.add(field);
|
|
1528
1605
|
}
|
|
1529
1606
|
return Array.from(out);
|
|
1530
1607
|
}
|
|
@@ -1535,17 +1612,23 @@ var ScreenAnalyzer = class {
|
|
|
1535
1612
|
CallExpression: (nodePath) => {
|
|
1536
1613
|
const node = nodePath.node;
|
|
1537
1614
|
if (!BabelTypes__namespace.isMemberExpression(node.callee)) return;
|
|
1538
|
-
if (!BabelTypes__namespace.isIdentifier(node.callee.property) || node.callee.property.name !== "filter")
|
|
1539
|
-
|
|
1615
|
+
if (!BabelTypes__namespace.isIdentifier(node.callee.property) || node.callee.property.name !== "filter")
|
|
1616
|
+
return;
|
|
1617
|
+
if (!BabelTypes__namespace.isIdentifier(node.callee.object) || node.callee.object.name !== dataSource)
|
|
1618
|
+
return;
|
|
1540
1619
|
const fn = node.arguments[0];
|
|
1541
|
-
if (!BabelTypes__namespace.isArrowFunctionExpression(fn) && !BabelTypes__namespace.isFunctionExpression(fn))
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1620
|
+
if (!BabelTypes__namespace.isArrowFunctionExpression(fn) && !BabelTypes__namespace.isFunctionExpression(fn))
|
|
1621
|
+
return;
|
|
1622
|
+
traverse4__default.default(
|
|
1623
|
+
fn.body,
|
|
1624
|
+
{
|
|
1625
|
+
noScope: true,
|
|
1626
|
+
Identifier: (innerPath) => {
|
|
1627
|
+
const name = innerPath.node.name;
|
|
1628
|
+
if (/query|search|filter/i.test(name)) queryBinding = queryBinding ?? name;
|
|
1629
|
+
}
|
|
1547
1630
|
}
|
|
1548
|
-
|
|
1631
|
+
);
|
|
1549
1632
|
}
|
|
1550
1633
|
});
|
|
1551
1634
|
return queryBinding;
|
|
@@ -1689,11 +1772,7 @@ var NavigationAnalyzer = class {
|
|
|
1689
1772
|
if (BabelTypes__namespace.isJSXAttribute(initialRouteAttr) && BabelTypes__namespace.isStringLiteral(initialRouteAttr.value)) {
|
|
1690
1773
|
navigator.initialRouteName = initialRouteAttr.value.value;
|
|
1691
1774
|
}
|
|
1692
|
-
const screens = this.extractScreensFromNavigator(
|
|
1693
|
-
node,
|
|
1694
|
-
objectName,
|
|
1695
|
-
navigator.type
|
|
1696
|
-
);
|
|
1775
|
+
const screens = this.extractScreensFromNavigator(node, objectName, navigator.type);
|
|
1697
1776
|
screensByNavigator.set(objectName, screens);
|
|
1698
1777
|
}
|
|
1699
1778
|
}
|
|
@@ -1710,27 +1789,112 @@ var NavigationAnalyzer = class {
|
|
|
1710
1789
|
}
|
|
1711
1790
|
return navigators;
|
|
1712
1791
|
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Is this JSX element `<navigatorVarName.MEMBER …>`?
|
|
1794
|
+
*/
|
|
1795
|
+
// Deliberately NOT a type predicate (`node is t.JSXElement`): a false
|
|
1796
|
+
// result would then narrow `node` to "not a JSXElement", and the very
|
|
1797
|
+
// next check — the same node against a different member — would see
|
|
1798
|
+
// `never`. It is a question about the member name, not about the node
|
|
1799
|
+
// kind.
|
|
1800
|
+
isNavigatorMember(node, navigatorVarName, member) {
|
|
1801
|
+
if (!BabelTypes__namespace.isJSXElement(node)) return false;
|
|
1802
|
+
const name = node.openingElement.name;
|
|
1803
|
+
return BabelTypes__namespace.isJSXMemberExpression(name) && BabelTypes__namespace.isJSXIdentifier(name.object) && name.object.name === navigatorVarName && BabelTypes__namespace.isJSXIdentifier(name.property) && name.property.name === member;
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Flatten a navigator's JSX children into the `<X.Screen>` elements they
|
|
1807
|
+
* contain, unwrapping every container a real app puts in between.
|
|
1808
|
+
*
|
|
1809
|
+
* The old version compared `t.isJSXElement(child)` against the direct
|
|
1810
|
+
* children only. A ternary is a `JSXExpressionContainer`, so an
|
|
1811
|
+
* auth-gated root — the modal shape of a commercial app, and the shape
|
|
1812
|
+
* of this repo's own `apps/example-app` — contributed ZERO screens to
|
|
1813
|
+
* the graph while `appilots sync` reported success (#396).
|
|
1814
|
+
*
|
|
1815
|
+
* Both branches of a conditional are collected on purpose. The graph is
|
|
1816
|
+
* a design-time map of what routes EXIST, not a prediction of which one
|
|
1817
|
+
* a given session will render; the agent needs the destination name to
|
|
1818
|
+
* be there whether or not the user happens to be logged in right now.
|
|
1819
|
+
*/
|
|
1820
|
+
collectScreenElements(children, navigatorVarName, depth = 0) {
|
|
1821
|
+
if (depth > 12) return [];
|
|
1822
|
+
const found = [];
|
|
1823
|
+
const visit = (node) => {
|
|
1824
|
+
if (!node) return;
|
|
1825
|
+
if (this.isNavigatorMember(node, navigatorVarName, "Screen")) {
|
|
1826
|
+
found.push(node);
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
if (this.isNavigatorMember(node, navigatorVarName, "Group")) {
|
|
1830
|
+
const group = node;
|
|
1831
|
+
found.push(...this.collectScreenElements(group.children, navigatorVarName, depth + 1));
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
if (BabelTypes__namespace.isJSXElement(node)) {
|
|
1835
|
+
const name = node.openingElement.name;
|
|
1836
|
+
const isForeignNavigator = BabelTypes__namespace.isJSXMemberExpression(name) && BabelTypes__namespace.isJSXIdentifier(name.property) && name.property.name === "Navigator";
|
|
1837
|
+
if (isForeignNavigator) return;
|
|
1838
|
+
found.push(...this.collectScreenElements(node.children, navigatorVarName, depth + 1));
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (BabelTypes__namespace.isJSXFragment(node)) {
|
|
1842
|
+
found.push(...this.collectScreenElements(node.children, navigatorVarName, depth + 1));
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
if (BabelTypes__namespace.isJSXExpressionContainer(node)) {
|
|
1846
|
+
visit(node.expression);
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
if (BabelTypes__namespace.isConditionalExpression(node)) {
|
|
1850
|
+
visit(node.consequent);
|
|
1851
|
+
visit(node.alternate);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (BabelTypes__namespace.isLogicalExpression(node)) {
|
|
1855
|
+
visit(node.left);
|
|
1856
|
+
visit(node.right);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
if (BabelTypes__namespace.isCallExpression(node)) {
|
|
1860
|
+
for (const arg of node.arguments) {
|
|
1861
|
+
if (BabelTypes__namespace.isArrowFunctionExpression(arg) || BabelTypes__namespace.isFunctionExpression(arg)) {
|
|
1862
|
+
if (BabelTypes__namespace.isBlockStatement(arg.body)) {
|
|
1863
|
+
for (const stmt of arg.body.body) {
|
|
1864
|
+
if (BabelTypes__namespace.isReturnStatement(stmt)) visit(stmt.argument);
|
|
1865
|
+
}
|
|
1866
|
+
} else {
|
|
1867
|
+
visit(arg.body);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
if (BabelTypes__namespace.isArrayExpression(node)) {
|
|
1874
|
+
for (const el of node.elements) visit(el);
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
if (BabelTypes__namespace.isTSAsExpression(node) || BabelTypes__namespace.isTSNonNullExpression(node)) {
|
|
1878
|
+
visit(node.expression);
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
for (const child of children) visit(child);
|
|
1882
|
+
return found;
|
|
1883
|
+
}
|
|
1713
1884
|
/** Extract screens from a navigator JSX element */
|
|
1714
1885
|
extractScreensFromNavigator(navigatorElement, navigatorVarName, navigatorType) {
|
|
1715
1886
|
const screens = [];
|
|
1716
1887
|
if (!navigatorElement.children) return screens;
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
name: screenName,
|
|
1728
|
-
navigatorName: navigatorVarName,
|
|
1729
|
-
navigatorType
|
|
1730
|
-
});
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1888
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1889
|
+
for (const element of this.collectScreenElements(navigatorElement.children, navigatorVarName)) {
|
|
1890
|
+
const screenName = this.extractAttributeValue(element.openingElement.attributes, "name");
|
|
1891
|
+
if (!screenName || seen.has(screenName)) continue;
|
|
1892
|
+
seen.add(screenName);
|
|
1893
|
+
screens.push({
|
|
1894
|
+
name: screenName,
|
|
1895
|
+
navigatorName: navigatorVarName,
|
|
1896
|
+
navigatorType
|
|
1897
|
+
});
|
|
1734
1898
|
}
|
|
1735
1899
|
return screens;
|
|
1736
1900
|
}
|
|
@@ -1789,9 +1953,7 @@ var NavigationAnalyzer = class {
|
|
|
1789
1953
|
if (member.type === "TSPropertySignature" && member.key) {
|
|
1790
1954
|
const keyName = member.key.type === "Identifier" ? member.key.name : null;
|
|
1791
1955
|
if (keyName && member.typeAnnotation) {
|
|
1792
|
-
const params = this.extractParamsFromType(
|
|
1793
|
-
member.typeAnnotation.typeAnnotation
|
|
1794
|
-
);
|
|
1956
|
+
const params = this.extractParamsFromType(member.typeAnnotation.typeAnnotation);
|
|
1795
1957
|
entries.set(keyName, params);
|
|
1796
1958
|
}
|
|
1797
1959
|
}
|
|
@@ -1870,6 +2032,11 @@ var NavigationAnalyzer = class {
|
|
|
1870
2032
|
let initialScreen = "";
|
|
1871
2033
|
for (const navigator of parsedNavigators) {
|
|
1872
2034
|
const screenNames = navigator.screens.map((s) => s.name);
|
|
2035
|
+
if (screenNames.length === 0) {
|
|
2036
|
+
console.warn(
|
|
2037
|
+
`[MCPGenerator] navigator "${navigator.name}" (${navigator.type}) declared no reachable screens. Its routes will be missing from the app map, so the agent will have no destination name for them. If the screens are there in the source, this is a parser gap \u2014 please report it with the file.`
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
1873
2040
|
navigators.push({
|
|
1874
2041
|
name: navigator.name,
|
|
1875
2042
|
type: navigator.type,
|
|
@@ -2657,7 +2824,9 @@ var WebScreenAnalyzer = class {
|
|
|
2657
2824
|
include = ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"],
|
|
2658
2825
|
exclude = ["**/node_modules/**", "**/dist/**", "**/build/**"]
|
|
2659
2826
|
} = this.config;
|
|
2660
|
-
const files = await glob__default.default(include, { cwd: this.config.rootDir, ignore: exclude })
|
|
2827
|
+
const files = (await glob__default.default(include, { cwd: this.config.rootDir, ignore: exclude })).filter(
|
|
2828
|
+
(file) => !file.endsWith(".d.ts")
|
|
2829
|
+
);
|
|
2661
2830
|
const patternMatches = await glob__default.default(this.screenPatterns, {
|
|
2662
2831
|
cwd: this.config.rootDir,
|
|
2663
2832
|
ignore: exclude
|
|
@@ -3028,7 +3197,8 @@ var WebScreenAnalyzer = class {
|
|
|
3028
3197
|
if (type === "email") return "email";
|
|
3029
3198
|
if (type === "tel") return "phone";
|
|
3030
3199
|
if (type === "number") return "number";
|
|
3031
|
-
if (type === "date" || type === "datetime-local" || type === "month" || type === "week")
|
|
3200
|
+
if (type === "date" || type === "datetime-local" || type === "month" || type === "week")
|
|
3201
|
+
return "date";
|
|
3032
3202
|
const inputMode = getStringAttr(opening, "inputMode") ?? getStringAttr(opening, "inputmode");
|
|
3033
3203
|
if (inputMode === "email") return "email";
|
|
3034
3204
|
if (inputMode === "tel") return "phone";
|
|
@@ -3342,7 +3512,9 @@ var WebScreenAnalyzer = class {
|
|
|
3342
3512
|
}
|
|
3343
3513
|
const callback = expr.arguments[0];
|
|
3344
3514
|
if (!BabelTypes__namespace.isArrowFunctionExpression(callback) && !BabelTypes__namespace.isFunctionExpression(callback)) return;
|
|
3345
|
-
const enclosing = nodePath.findParent(
|
|
3515
|
+
const enclosing = nodePath.findParent(
|
|
3516
|
+
(p) => p.isJSXElement()
|
|
3517
|
+
);
|
|
3346
3518
|
const enclosingName = enclosing ? getJsxElementName(enclosing.node.openingElement) : null;
|
|
3347
3519
|
const enclosingRole = enclosing && enclosingName ? classifyWebJsxComponent(enclosingName, enclosing.node.openingElement) : null;
|
|
3348
3520
|
const returnsListItem = callbackReturnsTag(callback, /* @__PURE__ */ new Set(["li", "tr"]));
|
|
@@ -3444,16 +3616,19 @@ function firstCalledFunctionName(node) {
|
|
|
3444
3616
|
}
|
|
3445
3617
|
function containsWindowConfirm(body) {
|
|
3446
3618
|
let found = false;
|
|
3447
|
-
traverse4__default.default(
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
found = true;
|
|
3619
|
+
traverse4__default.default(
|
|
3620
|
+
body,
|
|
3621
|
+
{
|
|
3622
|
+
noScope: true,
|
|
3623
|
+
CallExpression: (nodePath) => {
|
|
3624
|
+
const callee = nodePath.node.callee;
|
|
3625
|
+
if (BabelTypes__namespace.isIdentifier(callee) && callee.name === "confirm") found = true;
|
|
3626
|
+
if (BabelTypes__namespace.isMemberExpression(callee) && BabelTypes__namespace.isIdentifier(callee.object) && callee.object.name === "window" && BabelTypes__namespace.isIdentifier(callee.property) && callee.property.name === "confirm") {
|
|
3627
|
+
found = true;
|
|
3628
|
+
}
|
|
3454
3629
|
}
|
|
3455
3630
|
}
|
|
3456
|
-
|
|
3631
|
+
);
|
|
3457
3632
|
return found;
|
|
3458
3633
|
}
|
|
3459
3634
|
function mergeLocator(current, next) {
|
|
@@ -3493,32 +3668,38 @@ function collectionItemNames(callback) {
|
|
|
3493
3668
|
function collectionDisplayFields(callback, itemNames) {
|
|
3494
3669
|
const fields = /* @__PURE__ */ new Set();
|
|
3495
3670
|
if (!callback.body) return [];
|
|
3496
|
-
traverse4__default.default(
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3671
|
+
traverse4__default.default(
|
|
3672
|
+
callback.body,
|
|
3673
|
+
{
|
|
3674
|
+
noScope: true,
|
|
3675
|
+
MemberExpression: (nodePath) => {
|
|
3676
|
+
const node = nodePath.node;
|
|
3677
|
+
if (BabelTypes__namespace.isIdentifier(node.object) && itemNames.has(node.object.name) && BabelTypes__namespace.isIdentifier(node.property)) {
|
|
3678
|
+
fields.add(node.property.name);
|
|
3679
|
+
}
|
|
3502
3680
|
}
|
|
3503
3681
|
}
|
|
3504
|
-
|
|
3682
|
+
);
|
|
3505
3683
|
return Array.from(fields).sort();
|
|
3506
3684
|
}
|
|
3507
3685
|
function collectionKeyField(callback, itemNames) {
|
|
3508
3686
|
let keyField;
|
|
3509
3687
|
if (!callback.body) return void 0;
|
|
3510
|
-
traverse4__default.default(
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3688
|
+
traverse4__default.default(
|
|
3689
|
+
callback.body,
|
|
3690
|
+
{
|
|
3691
|
+
noScope: true,
|
|
3692
|
+
JSXAttribute: (nodePath) => {
|
|
3693
|
+
const attr = nodePath.node;
|
|
3694
|
+
if (!BabelTypes__namespace.isJSXIdentifier(attr.name) || attr.name.name !== "key") return;
|
|
3695
|
+
if (!attr.value || !BabelTypes__namespace.isJSXExpressionContainer(attr.value)) return;
|
|
3696
|
+
const expr = attr.value.expression;
|
|
3697
|
+
if (BabelTypes__namespace.isMemberExpression(expr) && BabelTypes__namespace.isIdentifier(expr.object) && itemNames.has(expr.object.name) && BabelTypes__namespace.isIdentifier(expr.property)) {
|
|
3698
|
+
keyField = keyField ?? expr.property.name;
|
|
3699
|
+
}
|
|
3519
3700
|
}
|
|
3520
3701
|
}
|
|
3521
|
-
|
|
3702
|
+
);
|
|
3522
3703
|
return keyField;
|
|
3523
3704
|
}
|
|
3524
3705
|
function collectionRowAction(callback) {
|
|
@@ -3566,7 +3747,7 @@ function inferIdentityFields(keyField, displayFields) {
|
|
|
3566
3747
|
const out = /* @__PURE__ */ new Set();
|
|
3567
3748
|
if (keyField) out.add(keyField);
|
|
3568
3749
|
for (const field of displayFields) {
|
|
3569
|
-
if (/^(id|uuid|key|name|title|
|
|
3750
|
+
if (/^(id|uuid|key|name|title|email|slug)$/i.test(field)) out.add(field);
|
|
3570
3751
|
}
|
|
3571
3752
|
return Array.from(out);
|
|
3572
3753
|
}
|
|
@@ -7950,7 +8131,7 @@ var coerce = {
|
|
|
7950
8131
|
};
|
|
7951
8132
|
var NEVER = INVALID;
|
|
7952
8133
|
|
|
7953
|
-
// ../shared/dist/chunk-
|
|
8134
|
+
// ../shared/dist/chunk-PKS3VDNB.mjs
|
|
7954
8135
|
var locatorSourceSchema = external_exports.enum([
|
|
7955
8136
|
"appilotsId",
|
|
7956
8137
|
"testID",
|
|
@@ -7976,7 +8157,16 @@ var locatorDescriptorSchema = external_exports.object({
|
|
|
7976
8157
|
source: locatorSourceSchema.optional()
|
|
7977
8158
|
}).passthrough();
|
|
7978
8159
|
var signalDescriptorSchema = external_exports.object({
|
|
7979
|
-
type: external_exports.enum([
|
|
8160
|
+
type: external_exports.enum([
|
|
8161
|
+
"navigation",
|
|
8162
|
+
"goBack",
|
|
8163
|
+
"toast",
|
|
8164
|
+
"modal",
|
|
8165
|
+
"inline-error",
|
|
8166
|
+
"data-arrival",
|
|
8167
|
+
"loading",
|
|
8168
|
+
"none"
|
|
8169
|
+
]).or(external_exports.string().min(1).max(60)),
|
|
7980
8170
|
target: external_exports.string().optional(),
|
|
7981
8171
|
description: external_exports.string().optional()
|
|
7982
8172
|
}).passthrough();
|
|
@@ -7990,7 +8180,20 @@ var waitPolicyDescriptorSchema = external_exports.object({
|
|
|
7990
8180
|
signals: external_exports.array(signalDescriptorSchema).optional(),
|
|
7991
8181
|
maxMs: external_exports.number().finite().optional()
|
|
7992
8182
|
}).passthrough();
|
|
7993
|
-
var targetDescriptorRoleSchema = external_exports.enum([
|
|
8183
|
+
var targetDescriptorRoleSchema = external_exports.enum([
|
|
8184
|
+
"button",
|
|
8185
|
+
"submit",
|
|
8186
|
+
"input",
|
|
8187
|
+
"toggle",
|
|
8188
|
+
"select",
|
|
8189
|
+
"date",
|
|
8190
|
+
"list",
|
|
8191
|
+
"row",
|
|
8192
|
+
"menuItem",
|
|
8193
|
+
"modal",
|
|
8194
|
+
"custom"
|
|
8195
|
+
]).or(external_exports.string().min(1).max(60));
|
|
8196
|
+
var targetOriginSchema = external_exports.enum(["analyzer", "manifest"]).or(external_exports.string().min(1).max(40));
|
|
7994
8197
|
var targetDescriptorSchema = external_exports.object({
|
|
7995
8198
|
id: external_exports.string(),
|
|
7996
8199
|
role: targetDescriptorRoleSchema,
|
|
@@ -8004,10 +8207,20 @@ var targetDescriptorSchema = external_exports.object({
|
|
|
8004
8207
|
requiresConfirmation: external_exports.boolean().optional(),
|
|
8005
8208
|
opensModal: external_exports.string().optional(),
|
|
8006
8209
|
opensBottomSheet: external_exports.string().optional(),
|
|
8007
|
-
sourceComponent: external_exports.string().optional()
|
|
8210
|
+
sourceComponent: external_exports.string().optional(),
|
|
8211
|
+
origin: targetOriginSchema.optional()
|
|
8008
8212
|
}).passthrough();
|
|
8009
8213
|
var flowStepDescriptorSchema = external_exports.object({
|
|
8010
|
-
type: external_exports.enum([
|
|
8214
|
+
type: external_exports.enum([
|
|
8215
|
+
"navigate",
|
|
8216
|
+
"fill",
|
|
8217
|
+
"press",
|
|
8218
|
+
"select",
|
|
8219
|
+
"toggle",
|
|
8220
|
+
"wait",
|
|
8221
|
+
"confirm",
|
|
8222
|
+
"choose-list-item"
|
|
8223
|
+
]).or(external_exports.string().min(1).max(60)),
|
|
8011
8224
|
target: external_exports.string().optional(),
|
|
8012
8225
|
label: external_exports.string().optional(),
|
|
8013
8226
|
description: external_exports.string().optional(),
|
|
@@ -8229,6 +8442,15 @@ external_exports.object({
|
|
|
8229
8442
|
password: external_exports.string().min(1, "Password is required"),
|
|
8230
8443
|
code: totpCodeSchema
|
|
8231
8444
|
});
|
|
8445
|
+
external_exports.object({
|
|
8446
|
+
password: external_exports.string().min(1, "Password is required"),
|
|
8447
|
+
code: totpCodeSchema.optional(),
|
|
8448
|
+
recoveryCode: recoveryCodeSchema.optional(),
|
|
8449
|
+
confirm: external_exports.literal("DELETE MY ACCOUNT")
|
|
8450
|
+
}).refine((v) => !(v.code && v.recoveryCode), {
|
|
8451
|
+
message: "Provide either a TOTP code or a recovery code, not both",
|
|
8452
|
+
path: ["code"]
|
|
8453
|
+
});
|
|
8232
8454
|
var createProjectSchema = external_exports.object({
|
|
8233
8455
|
name: external_exports.string().min(1).max(100),
|
|
8234
8456
|
description: external_exports.string().max(500).optional(),
|
|
@@ -8264,6 +8486,7 @@ external_exports.object({
|
|
|
8264
8486
|
content: external_exports.record(external_exports.unknown())
|
|
8265
8487
|
});
|
|
8266
8488
|
var apiKeyScopeSchema = external_exports.enum(["sdk", "operator"]);
|
|
8489
|
+
var apiKeyEnvironmentSchema = external_exports.enum(["test", "live"]);
|
|
8267
8490
|
external_exports.object({
|
|
8268
8491
|
name: external_exports.string().min(1).max(100),
|
|
8269
8492
|
// Required for `sdk` (default), forbidden/ignored for `operator` —
|
|
@@ -8271,6 +8494,7 @@ external_exports.object({
|
|
|
8271
8494
|
// missing scope still validates as the pre-existing SDK shape.
|
|
8272
8495
|
projectId: external_exports.string().min(1).optional(),
|
|
8273
8496
|
scope: apiKeyScopeSchema.default("sdk"),
|
|
8497
|
+
environment: apiKeyEnvironmentSchema.optional(),
|
|
8274
8498
|
expiresAt: external_exports.string().datetime().optional()
|
|
8275
8499
|
}).refine((v) => v.scope !== "sdk" || !!v.projectId, {
|
|
8276
8500
|
message: 'projectId is required for scope "sdk"',
|
|
@@ -8278,13 +8502,23 @@ external_exports.object({
|
|
|
8278
8502
|
});
|
|
8279
8503
|
var boundedString = (max) => external_exports.string().max(max);
|
|
8280
8504
|
var clientPlatformSchema = external_exports.enum(["react-native", "web", "android", "ios"]).or(external_exports.string().min(1).max(40));
|
|
8505
|
+
var identityProvenanceSchema = external_exports.enum(["declared", "derived", "positional"]).or(external_exports.string().min(1).max(40));
|
|
8281
8506
|
var snapshotInputSchema = external_exports.object({
|
|
8282
8507
|
id: boundedString(160).optional(),
|
|
8508
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8509
|
+
/**
|
|
8510
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8511
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8512
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8513
|
+
* before the field existed), which must never be read as `false`.
|
|
8514
|
+
*/
|
|
8515
|
+
onScreen: external_exports.boolean().optional(),
|
|
8283
8516
|
label: boundedString(300).optional(),
|
|
8284
8517
|
value: boundedString(4096).optional(),
|
|
8285
8518
|
placeholder: boundedString(300).optional(),
|
|
8286
8519
|
editable: external_exports.boolean().optional(),
|
|
8287
8520
|
secure: external_exports.boolean().optional(),
|
|
8521
|
+
submitsOnReturn: external_exports.boolean().optional(),
|
|
8288
8522
|
type: boundedString(40).optional(),
|
|
8289
8523
|
required: external_exports.boolean().optional(),
|
|
8290
8524
|
invalid: external_exports.boolean().optional(),
|
|
@@ -8292,6 +8526,14 @@ var snapshotInputSchema = external_exports.object({
|
|
|
8292
8526
|
}).passthrough();
|
|
8293
8527
|
var snapshotButtonSchema = external_exports.object({
|
|
8294
8528
|
id: boundedString(160).optional(),
|
|
8529
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8530
|
+
/**
|
|
8531
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8532
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8533
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8534
|
+
* before the field existed), which must never be read as `false`.
|
|
8535
|
+
*/
|
|
8536
|
+
onScreen: external_exports.boolean().optional(),
|
|
8295
8537
|
label: boundedString(300).optional(),
|
|
8296
8538
|
disabled: external_exports.boolean().optional(),
|
|
8297
8539
|
inModal: external_exports.boolean().optional(),
|
|
@@ -8301,12 +8543,28 @@ var snapshotButtonSchema = external_exports.object({
|
|
|
8301
8543
|
}).passthrough();
|
|
8302
8544
|
var snapshotToggleSchema = external_exports.object({
|
|
8303
8545
|
id: boundedString(160).optional(),
|
|
8546
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8547
|
+
/**
|
|
8548
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8549
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8550
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8551
|
+
* before the field existed), which must never be read as `false`.
|
|
8552
|
+
*/
|
|
8553
|
+
onScreen: external_exports.boolean().optional(),
|
|
8304
8554
|
label: boundedString(300).optional(),
|
|
8305
8555
|
value: external_exports.boolean().optional(),
|
|
8306
8556
|
inModal: external_exports.boolean().optional()
|
|
8307
8557
|
}).passthrough();
|
|
8308
8558
|
var snapshotSliderSchema = external_exports.object({
|
|
8309
8559
|
id: boundedString(160).optional(),
|
|
8560
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8561
|
+
/**
|
|
8562
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8563
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8564
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8565
|
+
* before the field existed), which must never be read as `false`.
|
|
8566
|
+
*/
|
|
8567
|
+
onScreen: external_exports.boolean().optional(),
|
|
8310
8568
|
label: boundedString(300).optional(),
|
|
8311
8569
|
value: external_exports.number().finite().optional(),
|
|
8312
8570
|
min: external_exports.number().finite().optional(),
|
|
@@ -8347,6 +8605,26 @@ var snapshotListSchema = external_exports.object({
|
|
|
8347
8605
|
// should chase, and the relay only reads a known subset.
|
|
8348
8606
|
items: external_exports.array(external_exports.record(external_exports.unknown())).max(500).optional()
|
|
8349
8607
|
}).passthrough();
|
|
8608
|
+
var snapshotNativeDialogSchema = external_exports.object({
|
|
8609
|
+
id: boundedString(160),
|
|
8610
|
+
title: boundedString(300).optional(),
|
|
8611
|
+
message: boundedString(2e3).optional(),
|
|
8612
|
+
buttons: external_exports.array(
|
|
8613
|
+
external_exports.object({
|
|
8614
|
+
label: boundedString(160),
|
|
8615
|
+
style: external_exports.enum(["default", "cancel", "destructive"]).optional()
|
|
8616
|
+
}).passthrough()
|
|
8617
|
+
).max(10)
|
|
8618
|
+
}).passthrough();
|
|
8619
|
+
var snapshotScrollableSchema = external_exports.object({
|
|
8620
|
+
id: boundedString(160),
|
|
8621
|
+
containerType: boundedString(60).optional(),
|
|
8622
|
+
label: boundedString(300).optional(),
|
|
8623
|
+
scrollOffsetY: external_exports.number().finite().optional(),
|
|
8624
|
+
canScrollUp: external_exports.boolean().optional(),
|
|
8625
|
+
canScrollDown: external_exports.boolean().optional(),
|
|
8626
|
+
horizontal: external_exports.boolean().optional()
|
|
8627
|
+
}).passthrough();
|
|
8350
8628
|
var snapshotChoiceGroupSchema = external_exports.object({
|
|
8351
8629
|
index: external_exports.number().int().optional(),
|
|
8352
8630
|
id: boundedString(160).optional(),
|
|
@@ -8363,6 +8641,14 @@ var snapshotElementSchema = external_exports.object({
|
|
|
8363
8641
|
disabled: external_exports.boolean().optional(),
|
|
8364
8642
|
selected: external_exports.boolean().optional(),
|
|
8365
8643
|
source: boundedString(40).optional(),
|
|
8644
|
+
provenance: identityProvenanceSchema.optional(),
|
|
8645
|
+
/**
|
|
8646
|
+
* False when the control is mounted but currently OUTSIDE the window —
|
|
8647
|
+
* below the fold, scrolled off, pushed sideways. Absent means the client
|
|
8648
|
+
* cannot tell (old React Native architecture, or any SDK published
|
|
8649
|
+
* before the field existed), which must never be read as `false`.
|
|
8650
|
+
*/
|
|
8651
|
+
onScreen: external_exports.boolean().optional(),
|
|
8366
8652
|
targetId: boundedString(160).optional(),
|
|
8367
8653
|
listContext: external_exports.record(external_exports.unknown()).optional(),
|
|
8368
8654
|
inModal: external_exports.boolean().optional()
|
|
@@ -8379,7 +8665,34 @@ var agentSnapshotSchema = external_exports.object({
|
|
|
8379
8665
|
modalOpen: external_exports.boolean().optional(),
|
|
8380
8666
|
lists: external_exports.array(snapshotListSchema).max(100).optional(),
|
|
8381
8667
|
choiceGroups: external_exports.array(snapshotChoiceGroupSchema).max(100).optional(),
|
|
8382
|
-
|
|
8668
|
+
scrollables: external_exports.array(snapshotScrollableSchema).max(20).optional(),
|
|
8669
|
+
nativeDialog: snapshotNativeDialogSchema.optional(),
|
|
8670
|
+
elements: external_exports.array(snapshotElementSchema).max(1e3).optional(),
|
|
8671
|
+
/**
|
|
8672
|
+
* The client clamped this observation to the caps above and lost
|
|
8673
|
+
* data doing it (`clampSnapshotToWireLimits` in
|
|
8674
|
+
* `@appilots/client-core`). Read by the prompt serializer so the
|
|
8675
|
+
* model is told the view is partial — a screen truncated at 500
|
|
8676
|
+
* texts must not be read as a screen that only has 500 things on
|
|
8677
|
+
* it. Optional: older SDKs never clamp and never send it.
|
|
8678
|
+
*/
|
|
8679
|
+
truncated: external_exports.boolean().optional(),
|
|
8680
|
+
/**
|
|
8681
|
+
* How many of this screen's controls the client could name, split by
|
|
8682
|
+
* `identityProvenance`. Diagnostic only — the relay never grounds an
|
|
8683
|
+
* action on it — but it is the number that says whether an app needs
|
|
8684
|
+
* annotating, and comparing it between a debug and a release build is
|
|
8685
|
+
* the first direct measurement of what minification costs the agent.
|
|
8686
|
+
*/
|
|
8687
|
+
stats: external_exports.object({
|
|
8688
|
+
visitedFibers: external_exports.number().int().nonnegative().optional(),
|
|
8689
|
+
skippedHidden: external_exports.number().int().nonnegative().optional(),
|
|
8690
|
+
identity: external_exports.object({
|
|
8691
|
+
declared: external_exports.number().int().nonnegative().optional(),
|
|
8692
|
+
derived: external_exports.number().int().nonnegative().optional(),
|
|
8693
|
+
positional: external_exports.number().int().nonnegative().optional()
|
|
8694
|
+
}).passthrough().optional()
|
|
8695
|
+
}).passthrough().optional()
|
|
8383
8696
|
}).passthrough();
|
|
8384
8697
|
var agentContextSchema = external_exports.object({
|
|
8385
8698
|
/**
|
|
@@ -8429,6 +8742,19 @@ var agentContextSchema = external_exports.object({
|
|
|
8429
8742
|
).max(200).optional(),
|
|
8430
8743
|
loadingPending: external_exports.boolean().optional()
|
|
8431
8744
|
}).passthrough();
|
|
8745
|
+
var introspectionFailureReasonSchema = external_exports.enum([
|
|
8746
|
+
"sentinel-missing-internals",
|
|
8747
|
+
"never-mounted",
|
|
8748
|
+
"render-crashed",
|
|
8749
|
+
"names-mangled",
|
|
8750
|
+
"walk-recognized-nothing"
|
|
8751
|
+
]);
|
|
8752
|
+
var introspectionDiagnosticsSchema = external_exports.object({
|
|
8753
|
+
captured: external_exports.boolean(),
|
|
8754
|
+
failureReason: introspectionFailureReasonSchema.nullish(),
|
|
8755
|
+
reactVersion: boundedString(32).nullish(),
|
|
8756
|
+
failureCount: external_exports.number().int().min(0).max(1e6).optional()
|
|
8757
|
+
});
|
|
8432
8758
|
external_exports.object({
|
|
8433
8759
|
content: external_exports.string().min(1).max(4096),
|
|
8434
8760
|
sessionId: external_exports.string().optional(),
|
|
@@ -8438,7 +8764,20 @@ external_exports.object({
|
|
|
8438
8764
|
* the active MCP doc and emits `mcp_version_mismatch` telemetry when
|
|
8439
8765
|
* they differ. Optional — older SDKs simply skip the check.
|
|
8440
8766
|
*/
|
|
8441
|
-
mcpVersion: external_exports.string().max(64).optional()
|
|
8767
|
+
mcpVersion: external_exports.string().max(64).optional(),
|
|
8768
|
+
/**
|
|
8769
|
+
* SDK-side introspection health. Only sent when it FAILED — a client
|
|
8770
|
+
* that reached the React tree omits the field entirely, so the happy
|
|
8771
|
+
* path costs nothing on the wire.
|
|
8772
|
+
*
|
|
8773
|
+
* Without this, an app whose introspection is dead sends a snapshot
|
|
8774
|
+
* that is empty but perfectly well-formed, indistinguishable from a
|
|
8775
|
+
* genuinely empty screen. The server emits
|
|
8776
|
+
* `introspection_unavailable` so the failure is visible to us instead
|
|
8777
|
+
* of waiting for a bug report. Optional — older SDKs simply never
|
|
8778
|
+
* send it.
|
|
8779
|
+
*/
|
|
8780
|
+
introspection: introspectionDiagnosticsSchema.optional()
|
|
8442
8781
|
});
|
|
8443
8782
|
var agentActionTypeSchema = external_exports.enum([
|
|
8444
8783
|
"navigate",
|
|
@@ -8449,11 +8788,22 @@ var agentActionTypeSchema = external_exports.enum([
|
|
|
8449
8788
|
"custom"
|
|
8450
8789
|
]);
|
|
8451
8790
|
var navigationPayloadSchema = external_exports.object({
|
|
8452
|
-
|
|
8791
|
+
// Optional ONLY so `goBack` can exist (#398): it returns to whatever
|
|
8792
|
+
// is beneath on the stack and has no destination to name. Every other
|
|
8793
|
+
// action is still required to carry one — enforced by the refine
|
|
8794
|
+
// below rather than by the field, so the error names the real problem
|
|
8795
|
+
// ("navigate needs a screenName") instead of a missing key.
|
|
8796
|
+
screenName: external_exports.string().min(1).optional(),
|
|
8453
8797
|
params: external_exports.record(external_exports.unknown()).optional(),
|
|
8454
8798
|
navigationAction: external_exports.enum(["push", "navigate", "replace", "goBack", "reset"]).default("navigate").optional(),
|
|
8455
8799
|
path: external_exports.array(external_exports.string().min(1)).optional()
|
|
8456
|
-
}).passthrough()
|
|
8800
|
+
}).passthrough().refine(
|
|
8801
|
+
(payload) => payload.navigationAction === "goBack" || typeof payload.screenName === "string" && payload.screenName.length > 0,
|
|
8802
|
+
{
|
|
8803
|
+
message: 'screenName is required for every navigationAction except "goBack"',
|
|
8804
|
+
path: ["screenName"]
|
|
8805
|
+
}
|
|
8806
|
+
);
|
|
8457
8807
|
var formFillFieldSchema = external_exports.object({
|
|
8458
8808
|
fieldId: external_exports.string().min(1),
|
|
8459
8809
|
fieldType: external_exports.enum(["text", "select", "toggle", "date", "number", "custom"]).default("text").optional(),
|
|
@@ -8787,6 +9137,8 @@ var sandboxObservationSchema = external_exports.object({
|
|
|
8787
9137
|
inputs: external_exports.array(
|
|
8788
9138
|
external_exports.object({
|
|
8789
9139
|
id: external_exports.string().max(120),
|
|
9140
|
+
provenance: identityProvenanceSchema.optional(),
|
|
9141
|
+
onScreen: external_exports.boolean().optional(),
|
|
8790
9142
|
label: external_exports.string().max(200).optional(),
|
|
8791
9143
|
value: external_exports.string().max(2e3).optional(),
|
|
8792
9144
|
disabled: external_exports.boolean().optional(),
|
|
@@ -8800,6 +9152,8 @@ var sandboxObservationSchema = external_exports.object({
|
|
|
8800
9152
|
buttons: external_exports.array(
|
|
8801
9153
|
external_exports.object({
|
|
8802
9154
|
id: external_exports.string().max(120),
|
|
9155
|
+
provenance: identityProvenanceSchema.optional(),
|
|
9156
|
+
onScreen: external_exports.boolean().optional(),
|
|
8803
9157
|
label: external_exports.string().max(200).optional(),
|
|
8804
9158
|
disabled: external_exports.boolean().optional(),
|
|
8805
9159
|
/** Risk metadata (e.g. "high") the SDK forwards for danger UI. */
|
|
@@ -8821,6 +9175,8 @@ var sandboxObservationSchema = external_exports.object({
|
|
|
8821
9175
|
sliders: external_exports.array(
|
|
8822
9176
|
external_exports.object({
|
|
8823
9177
|
id: external_exports.string().max(120),
|
|
9178
|
+
provenance: identityProvenanceSchema.optional(),
|
|
9179
|
+
onScreen: external_exports.boolean().optional(),
|
|
8824
9180
|
label: external_exports.string().max(200).optional(),
|
|
8825
9181
|
value: external_exports.number().finite().optional(),
|
|
8826
9182
|
min: external_exports.number().finite().optional(),
|
|
@@ -8850,6 +9206,44 @@ var sandboxObservationSchema = external_exports.object({
|
|
|
8850
9206
|
).max(200).optional()
|
|
8851
9207
|
}).passthrough()
|
|
8852
9208
|
).max(20).optional(),
|
|
9209
|
+
/**
|
|
9210
|
+
* Toggles exactly as the SDK snapshot reports them (#390).
|
|
9211
|
+
*
|
|
9212
|
+
* Their absence is why `generality-component-control-smarthome`
|
|
9213
|
+
* came back `errored` with a 422 instead of measuring anything:
|
|
9214
|
+
* this schema is `.strict()`, so a scenario that declares a switch
|
|
9215
|
+
* is rejected before the agent is ever asked. Which means the
|
|
9216
|
+
* corpus could not express the single control behind failure 02 of
|
|
9217
|
+
* the functional audit — "disable an agent; the switch never
|
|
9218
|
+
* moves". The one defect the eval most needed to catch was the one
|
|
9219
|
+
* shape it could not describe.
|
|
9220
|
+
*/
|
|
9221
|
+
toggles: external_exports.array(
|
|
9222
|
+
external_exports.object({
|
|
9223
|
+
id: external_exports.string().max(120),
|
|
9224
|
+
provenance: identityProvenanceSchema.optional(),
|
|
9225
|
+
onScreen: external_exports.boolean().optional(),
|
|
9226
|
+
label: external_exports.string().max(200).optional(),
|
|
9227
|
+
value: external_exports.boolean().optional(),
|
|
9228
|
+
disabled: external_exports.boolean().optional()
|
|
9229
|
+
})
|
|
9230
|
+
).max(100).optional(),
|
|
9231
|
+
/**
|
|
9232
|
+
* The interaction graph — the `el:*` ids the prompt tells the model
|
|
9233
|
+
* to prefer over ordinal handles. Passthrough per entry for the
|
|
9234
|
+
* same reason `lists` is: the SDK adds fields faster than this
|
|
9235
|
+
* schema should chase them.
|
|
9236
|
+
*/
|
|
9237
|
+
elements: external_exports.array(
|
|
9238
|
+
external_exports.object({
|
|
9239
|
+
id: external_exports.string().max(160),
|
|
9240
|
+
role: external_exports.string().max(40).optional(),
|
|
9241
|
+
label: external_exports.string().max(300).optional()
|
|
9242
|
+
}).passthrough()
|
|
9243
|
+
).max(500).optional(),
|
|
9244
|
+
/** Screen-level state the relay renders and branches on. */
|
|
9245
|
+
loading: external_exports.boolean().optional(),
|
|
9246
|
+
modalOpen: external_exports.boolean().optional(),
|
|
8853
9247
|
extra: external_exports.record(external_exports.unknown()).optional()
|
|
8854
9248
|
}).strict();
|
|
8855
9249
|
external_exports.object({
|
|
@@ -8890,13 +9284,29 @@ external_exports.object({
|
|
|
8890
9284
|
* heuristics, forces tool choice when the last user message carries a
|
|
8891
9285
|
* failure marker). Only meaningful alongside `history`.
|
|
8892
9286
|
*/
|
|
8893
|
-
isRecoveryHop: external_exports.boolean().optional()
|
|
9287
|
+
isRecoveryHop: external_exports.boolean().optional(),
|
|
9288
|
+
/**
|
|
9289
|
+
* Run this call against the application structure supplied here
|
|
9290
|
+
* instead of the project's active MCP document.
|
|
9291
|
+
*
|
|
9292
|
+
* Sandbox-only, and the relay honours it only on a `dryRun` call —
|
|
9293
|
+
* see `resolveMcpOverride` in apps/api. It exists because the eval
|
|
9294
|
+
* corpus could otherwise only ever describe ONE app: every scenario
|
|
9295
|
+
* resolves to the same seeded project, so every navigate destination,
|
|
9296
|
+
* every screen title and every bit of domain priming in the prompt
|
|
9297
|
+
* came from the same vehicle-management demo. A suite that can only
|
|
9298
|
+
* pose questions about one app cannot measure whether the agent works
|
|
9299
|
+
* on another one.
|
|
9300
|
+
*
|
|
9301
|
+
* Deliberately loose (`z.record`): the MCP document's shape is owned
|
|
9302
|
+
* by the generator, and pinning it here would turn every additive
|
|
9303
|
+
* generator change into a 422 in the sandbox.
|
|
9304
|
+
*/
|
|
9305
|
+
applicationStructure: external_exports.record(external_exports.unknown()).optional()
|
|
8894
9306
|
}).strict();
|
|
8895
9307
|
var relayDiagnosticsSchema = external_exports.object({
|
|
8896
9308
|
unverifiedTargets: external_exports.array(external_exports.string()),
|
|
8897
|
-
guardsFired: external_exports.array(
|
|
8898
|
-
external_exports.object({ guard: external_exports.string(), detail: external_exports.string().optional() })
|
|
8899
|
-
),
|
|
9309
|
+
guardsFired: external_exports.array(external_exports.object({ guard: external_exports.string(), detail: external_exports.string().optional() })),
|
|
8900
9310
|
repairMode: external_exports.enum(["repair", "strict", "disabled"]),
|
|
8901
9311
|
promptVersion: external_exports.string(),
|
|
8902
9312
|
// Defaulted for producers that predate the field: before it existed
|
|
@@ -9155,6 +9565,30 @@ external_exports.object({
|
|
|
9155
9565
|
name: external_exports.string().min(1).max(200).optional(),
|
|
9156
9566
|
identifiers: endUserIdentifiersSchema.optional()
|
|
9157
9567
|
});
|
|
9568
|
+
var MAX_EXTERNAL_USER_ID_LENGTH = 128;
|
|
9569
|
+
function normalizeExternalUserId(value) {
|
|
9570
|
+
let raw;
|
|
9571
|
+
if (typeof value === "string") raw = value;
|
|
9572
|
+
else if (typeof value === "number" && Number.isFinite(value)) raw = String(value);
|
|
9573
|
+
else return void 0;
|
|
9574
|
+
const trimmed = raw.trim();
|
|
9575
|
+
if (!trimmed) return void 0;
|
|
9576
|
+
if (trimmed.length > MAX_EXTERNAL_USER_ID_LENGTH) return void 0;
|
|
9577
|
+
return trimmed;
|
|
9578
|
+
}
|
|
9579
|
+
var MAX_DEVICE_INFO_BYTES = 4096;
|
|
9580
|
+
var sessionDeviceInfoSchema = external_exports.object({
|
|
9581
|
+
platform: external_exports.string().max(64).optional(),
|
|
9582
|
+
osVersion: external_exports.string().max(64).optional(),
|
|
9583
|
+
appVersion: external_exports.string().max(64).optional(),
|
|
9584
|
+
sdkVersion: external_exports.string().max(64).optional()
|
|
9585
|
+
}).passthrough().refine((info) => JSON.stringify(info).length <= MAX_DEVICE_INFO_BYTES, {
|
|
9586
|
+
message: `device_info exceeds ${MAX_DEVICE_INFO_BYTES} bytes`
|
|
9587
|
+
});
|
|
9588
|
+
external_exports.object({
|
|
9589
|
+
userId: external_exports.preprocess(normalizeExternalUserId, external_exports.string().optional()),
|
|
9590
|
+
deviceInfo: sessionDeviceInfoSchema.optional().catch(void 0)
|
|
9591
|
+
});
|
|
9158
9592
|
external_exports.object({
|
|
9159
9593
|
projectId: external_exports.string().min(1).max(64),
|
|
9160
9594
|
externalId: external_exports.string().min(1).max(128),
|
|
@@ -9162,6 +9596,27 @@ external_exports.object({
|
|
|
9162
9596
|
identifiers: endUserIdentifiersSchema.optional()
|
|
9163
9597
|
});
|
|
9164
9598
|
|
|
9599
|
+
// src/utils/stringify.ts
|
|
9600
|
+
function stringifyUnknown(value) {
|
|
9601
|
+
if (value instanceof Error) {
|
|
9602
|
+
const message = value.message || value.name;
|
|
9603
|
+
const cause = value.cause;
|
|
9604
|
+
if (cause instanceof Error && cause.message && cause.message !== message) {
|
|
9605
|
+
return `${message} (${cause.message})`;
|
|
9606
|
+
}
|
|
9607
|
+
return message;
|
|
9608
|
+
}
|
|
9609
|
+
if (typeof value === "string") return value;
|
|
9610
|
+
if (value === null || value === void 0) return String(value);
|
|
9611
|
+
if (typeof value !== "object") return String(value);
|
|
9612
|
+
try {
|
|
9613
|
+
const json = JSON.stringify(value);
|
|
9614
|
+
if (json && json !== "{}") return json;
|
|
9615
|
+
} catch {
|
|
9616
|
+
}
|
|
9617
|
+
return String(value);
|
|
9618
|
+
}
|
|
9619
|
+
|
|
9165
9620
|
// src/manifest/loadManifest.ts
|
|
9166
9621
|
var DEFAULT_MANIFEST_FILENAME = "appilots.manifest.json";
|
|
9167
9622
|
var manifestSchema = external_exports.object({
|
|
@@ -9178,14 +9633,16 @@ async function loadManifest(rootDir, manifestPath) {
|
|
|
9178
9633
|
if (error?.code === "ENOENT") {
|
|
9179
9634
|
return { manifest: null, resolvedPath };
|
|
9180
9635
|
}
|
|
9181
|
-
throw new Error(
|
|
9636
|
+
throw new Error(
|
|
9637
|
+
`Failed to read Appilots manifest at ${resolvedPath}: ${stringifyUnknown(error)}`
|
|
9638
|
+
);
|
|
9182
9639
|
}
|
|
9183
9640
|
let parsedJson;
|
|
9184
9641
|
try {
|
|
9185
9642
|
parsedJson = JSON.parse(raw);
|
|
9186
9643
|
} catch (error) {
|
|
9187
9644
|
throw new Error(
|
|
9188
|
-
`Appilots manifest at ${resolvedPath} is not valid JSON: ${
|
|
9645
|
+
`Appilots manifest at ${resolvedPath} is not valid JSON: ${stringifyUnknown(error)}`
|
|
9189
9646
|
);
|
|
9190
9647
|
}
|
|
9191
9648
|
const result = manifestSchema.safeParse(parsedJson);
|
|
@@ -9199,11 +9656,47 @@ Each entry in "screens" must match the ScreenDescriptor shape (see docs/platform
|
|
|
9199
9656
|
}
|
|
9200
9657
|
return { manifest: result.data, resolvedPath };
|
|
9201
9658
|
}
|
|
9659
|
+
function mergeScreen(base, declared) {
|
|
9660
|
+
const fields = { ...base };
|
|
9661
|
+
for (const [key, value] of Object.entries(declared)) {
|
|
9662
|
+
if (value === void 0) continue;
|
|
9663
|
+
if (Array.isArray(value) && value.length === 0) continue;
|
|
9664
|
+
fields[key] = value;
|
|
9665
|
+
}
|
|
9666
|
+
const merged = fields;
|
|
9667
|
+
merged.targets = mergeById(base.targets, markManifestOrigin(declared.targets));
|
|
9668
|
+
merged.actions = mergeById(base.actions, declared.actions) ?? [];
|
|
9669
|
+
if (base.agentHints || declared.agentHints) {
|
|
9670
|
+
merged.agentHints = { ...base.agentHints, ...declared.agentHints };
|
|
9671
|
+
}
|
|
9672
|
+
return merged;
|
|
9673
|
+
}
|
|
9674
|
+
function mergeById(base, overlay) {
|
|
9675
|
+
if (!overlay || overlay.length === 0) return base;
|
|
9676
|
+
if (!base || base.length === 0) return overlay;
|
|
9677
|
+
const byId = /* @__PURE__ */ new Map();
|
|
9678
|
+
for (const entry of base) byId.set(entry.id, entry);
|
|
9679
|
+
for (const entry of overlay) {
|
|
9680
|
+
const existing = byId.get(entry.id);
|
|
9681
|
+
byId.set(entry.id, existing ? { ...existing, ...entry } : entry);
|
|
9682
|
+
}
|
|
9683
|
+
return Array.from(byId.values());
|
|
9684
|
+
}
|
|
9685
|
+
function markManifestOrigin(targets) {
|
|
9686
|
+
if (!targets || targets.length === 0) return targets;
|
|
9687
|
+
return targets.map((target) => ({ origin: "manifest", ...target }));
|
|
9688
|
+
}
|
|
9202
9689
|
function mergeManifestScreens(analyzerScreens, manifestScreens) {
|
|
9203
9690
|
if (!manifestScreens || manifestScreens.length === 0) return analyzerScreens;
|
|
9204
9691
|
const byName = /* @__PURE__ */ new Map();
|
|
9205
9692
|
for (const screen of analyzerScreens) byName.set(screen.name, screen);
|
|
9206
|
-
for (const
|
|
9693
|
+
for (const declared of manifestScreens) {
|
|
9694
|
+
const base = byName.get(declared.name);
|
|
9695
|
+
byName.set(
|
|
9696
|
+
declared.name,
|
|
9697
|
+
base ? mergeScreen(base, declared) : { ...declared, targets: markManifestOrigin(declared.targets) }
|
|
9698
|
+
);
|
|
9699
|
+
}
|
|
9207
9700
|
return Array.from(byName.values());
|
|
9208
9701
|
}
|
|
9209
9702
|
function mergeManifestNavigation(analyzerNavigation, manifestNavigation) {
|
|
@@ -9493,7 +9986,12 @@ var MCPGenerator = class _MCPGenerator {
|
|
|
9493
9986
|
}
|
|
9494
9987
|
const mergedScreens = mergeManifestScreens(analyzed.screens, manifest?.screens);
|
|
9495
9988
|
const navigation = mergeManifestNavigation(analyzed.navigation, manifest?.navigation);
|
|
9496
|
-
const agentReadyScreens = mergedScreens.map(
|
|
9989
|
+
const agentReadyScreens = mergedScreens.map(
|
|
9990
|
+
(screen) => enrichScreenForAgent({
|
|
9991
|
+
...screen,
|
|
9992
|
+
filePath: screen.filePath ? path8__namespace.default.relative(this.generatorConfig.rootDir, screen.filePath) : screen.filePath
|
|
9993
|
+
})
|
|
9994
|
+
);
|
|
9497
9995
|
const projectInfo = await this.getProjectInfo();
|
|
9498
9996
|
const projectName = projectInfo.name;
|
|
9499
9997
|
console.log(`[MCPGenerator] Project name: ${projectName}`);
|
|
@@ -9516,10 +10014,7 @@ var MCPGenerator = class _MCPGenerator {
|
|
|
9516
10014
|
};
|
|
9517
10015
|
const serialized = JSON.stringify(document, null, 2);
|
|
9518
10016
|
const checksum = this.calculateChecksum(serialized);
|
|
9519
|
-
const filePath = path8__namespace.default.resolve(
|
|
9520
|
-
outputDir,
|
|
9521
|
-
`mcp-document.${this.options.format}`
|
|
9522
|
-
);
|
|
10017
|
+
const filePath = path8__namespace.default.resolve(outputDir, `mcp-document.${this.options.format}`);
|
|
9523
10018
|
await fs.writeFile(filePath, serialized, "utf-8");
|
|
9524
10019
|
console.log(`[MCPGenerator] Document written to: ${filePath}`);
|
|
9525
10020
|
const checksumFilePath = path8__namespace.default.resolve(outputDir, ".appilots-checksum");
|
|
@@ -9574,6 +10069,70 @@ var MCPGenerator = class _MCPGenerator {
|
|
|
9574
10069
|
}
|
|
9575
10070
|
}
|
|
9576
10071
|
};
|
|
10072
|
+
var DEFAULT_SERVER_URL = "http://localhost:4000";
|
|
10073
|
+
var KNOWN_CONFIG_KEYS = [
|
|
10074
|
+
"apiKey",
|
|
10075
|
+
"projectId",
|
|
10076
|
+
"serverUrl",
|
|
10077
|
+
"outputDir",
|
|
10078
|
+
"include",
|
|
10079
|
+
"exclude",
|
|
10080
|
+
"autoActivate",
|
|
10081
|
+
"strictScreens",
|
|
10082
|
+
"screenPatterns",
|
|
10083
|
+
"navigationInclude",
|
|
10084
|
+
"navigationExclude",
|
|
10085
|
+
"platform",
|
|
10086
|
+
"manifestPath",
|
|
10087
|
+
"eval"
|
|
10088
|
+
];
|
|
10089
|
+
var KEY_ALIASES = {
|
|
10090
|
+
apiUrl: "serverUrl",
|
|
10091
|
+
// `@appilots/sdk`'s own name for this. One `.appilotsrc` feeds both
|
|
10092
|
+
// packages, so an integrator who wrote the SDK's key should not be told
|
|
10093
|
+
// it is unrecognized.
|
|
10094
|
+
apiBaseUrl: "serverUrl",
|
|
10095
|
+
apiURL: "serverUrl",
|
|
10096
|
+
baseUrl: "serverUrl",
|
|
10097
|
+
url: "serverUrl",
|
|
10098
|
+
host: "serverUrl",
|
|
10099
|
+
endpoint: "serverUrl",
|
|
10100
|
+
server: "serverUrl",
|
|
10101
|
+
key: "apiKey",
|
|
10102
|
+
token: "apiKey",
|
|
10103
|
+
project: "projectId",
|
|
10104
|
+
sourceDir: "include",
|
|
10105
|
+
srcDir: "include",
|
|
10106
|
+
source: "include",
|
|
10107
|
+
outDir: "outputDir"
|
|
10108
|
+
};
|
|
10109
|
+
function editDistance(a, b) {
|
|
10110
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
10111
|
+
for (let i = 1; i <= a.length; i++) {
|
|
10112
|
+
const current = [i];
|
|
10113
|
+
for (let j = 1; j <= b.length; j++) {
|
|
10114
|
+
const deletion = (previous[j] ?? 0) + 1;
|
|
10115
|
+
const insertion = (current[j - 1] ?? 0) + 1;
|
|
10116
|
+
const substitution = (previous[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
10117
|
+
current.push(Math.min(deletion, insertion, substitution));
|
|
10118
|
+
}
|
|
10119
|
+
previous = current;
|
|
10120
|
+
}
|
|
10121
|
+
return previous[b.length] ?? 0;
|
|
10122
|
+
}
|
|
10123
|
+
function suggestConfigKey(key) {
|
|
10124
|
+
const alias = KEY_ALIASES[key];
|
|
10125
|
+
if (alias) return alias;
|
|
10126
|
+
const lower = key.toLowerCase();
|
|
10127
|
+
let best;
|
|
10128
|
+
for (const known of KNOWN_CONFIG_KEYS) {
|
|
10129
|
+
const distance = editDistance(lower, known.toLowerCase());
|
|
10130
|
+
if (distance <= 2 && (!best || distance < best.distance)) {
|
|
10131
|
+
best = { key: known, distance };
|
|
10132
|
+
}
|
|
10133
|
+
}
|
|
10134
|
+
return best?.key;
|
|
10135
|
+
}
|
|
9577
10136
|
function getEnvOverrides(env = process.env) {
|
|
9578
10137
|
const clean = (value) => {
|
|
9579
10138
|
const trimmed = value?.trim();
|
|
@@ -9585,7 +10144,7 @@ function getEnvOverrides(env = process.env) {
|
|
|
9585
10144
|
serverUrl: clean(env.APPILOTS_SERVER_URL)
|
|
9586
10145
|
};
|
|
9587
10146
|
}
|
|
9588
|
-
function loadConfig() {
|
|
10147
|
+
function loadConfig(onWarn) {
|
|
9589
10148
|
const configPath = getConfigPath();
|
|
9590
10149
|
const env = getEnvOverrides();
|
|
9591
10150
|
let fileConfig = {};
|
|
@@ -9593,13 +10152,15 @@ function loadConfig() {
|
|
|
9593
10152
|
try {
|
|
9594
10153
|
fileConfig = JSON.parse(fs$1.readFileSync(configPath, "utf-8"));
|
|
9595
10154
|
} catch (error) {
|
|
9596
|
-
throw new Error(
|
|
10155
|
+
throw new Error(
|
|
10156
|
+
`Failed to parse .appilotsrc at ${configPath}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
10157
|
+
);
|
|
9597
10158
|
}
|
|
9598
10159
|
} else if (!env.apiKey) {
|
|
9599
10160
|
return null;
|
|
9600
10161
|
}
|
|
9601
10162
|
const merged = {
|
|
9602
|
-
serverUrl:
|
|
10163
|
+
serverUrl: DEFAULT_SERVER_URL,
|
|
9603
10164
|
outputDir: ".appilots",
|
|
9604
10165
|
autoActivate: true,
|
|
9605
10166
|
strictScreens: true,
|
|
@@ -9609,6 +10170,12 @@ function loadConfig() {
|
|
|
9609
10170
|
...env.serverUrl ? { serverUrl: env.serverUrl } : {}
|
|
9610
10171
|
};
|
|
9611
10172
|
const validation = validateConfig(merged);
|
|
10173
|
+
if (onWarn) {
|
|
10174
|
+
const source = configPath ?? "environment variables";
|
|
10175
|
+
for (const warning of validation.warnings) {
|
|
10176
|
+
onWarn(`${source}: ${warning}`);
|
|
10177
|
+
}
|
|
10178
|
+
}
|
|
9612
10179
|
if (!validation.valid) {
|
|
9613
10180
|
const source = configPath ?? "environment variables";
|
|
9614
10181
|
throw new Error(
|
|
@@ -9624,7 +10191,7 @@ function saveConfig(dir, config) {
|
|
|
9624
10191
|
const mergedConfig = {
|
|
9625
10192
|
apiKey: config.apiKey || existingConfig?.apiKey || "",
|
|
9626
10193
|
projectId: config.projectId || existingConfig?.projectId || "",
|
|
9627
|
-
serverUrl: config.serverUrl || existingConfig?.serverUrl ||
|
|
10194
|
+
serverUrl: config.serverUrl || existingConfig?.serverUrl || DEFAULT_SERVER_URL,
|
|
9628
10195
|
outputDir: config.outputDir || existingConfig?.outputDir || ".appilots",
|
|
9629
10196
|
autoActivate: config.autoActivate !== void 0 ? config.autoActivate : existingConfig?.autoActivate ?? true,
|
|
9630
10197
|
include: config.include || existingConfig?.include,
|
|
@@ -9640,7 +10207,9 @@ function saveConfig(dir, config) {
|
|
|
9640
10207
|
try {
|
|
9641
10208
|
fs$1.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
|
|
9642
10209
|
} catch (error) {
|
|
9643
|
-
throw new Error(
|
|
10210
|
+
throw new Error(
|
|
10211
|
+
`Failed to write .appilotsrc to ${configPath}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
10212
|
+
);
|
|
9644
10213
|
}
|
|
9645
10214
|
}
|
|
9646
10215
|
function getConfigPath() {
|
|
@@ -9667,14 +10236,25 @@ function getConfigPath() {
|
|
|
9667
10236
|
}
|
|
9668
10237
|
function validateConfig(config) {
|
|
9669
10238
|
const errors = [];
|
|
10239
|
+
const warnings = [];
|
|
9670
10240
|
if (!config || typeof config !== "object") {
|
|
9671
10241
|
return {
|
|
9672
10242
|
valid: false,
|
|
9673
|
-
errors: ["Configuration must be an object"]
|
|
10243
|
+
errors: ["Configuration must be an object"],
|
|
10244
|
+
warnings
|
|
9674
10245
|
};
|
|
9675
10246
|
}
|
|
10247
|
+
for (const key of Object.keys(config)) {
|
|
10248
|
+
if (KNOWN_CONFIG_KEYS.includes(key)) continue;
|
|
10249
|
+
const suggestion = suggestConfigKey(key);
|
|
10250
|
+
warnings.push(
|
|
10251
|
+
`unknown key "${key}" \u2014 ignored` + (suggestion ? ` (did you mean "${suggestion}"?)` : "")
|
|
10252
|
+
);
|
|
10253
|
+
}
|
|
9676
10254
|
if (!config.apiKey || typeof config.apiKey !== "string") {
|
|
9677
|
-
errors.push(
|
|
10255
|
+
errors.push(
|
|
10256
|
+
"apiKey is required and must be a string (set it in .appilotsrc or via APPILOTS_API_KEY)"
|
|
10257
|
+
);
|
|
9678
10258
|
} else if (!config.apiKey.startsWith("ak_")) {
|
|
9679
10259
|
errors.push('apiKey must start with "ak_"');
|
|
9680
10260
|
}
|
|
@@ -9736,7 +10316,8 @@ function validateConfig(config) {
|
|
|
9736
10316
|
}
|
|
9737
10317
|
return {
|
|
9738
10318
|
valid: errors.length === 0,
|
|
9739
|
-
errors
|
|
10319
|
+
errors,
|
|
10320
|
+
warnings
|
|
9740
10321
|
};
|
|
9741
10322
|
}
|
|
9742
10323
|
|
|
@@ -9782,19 +10363,76 @@ function formatMetadataWarnings(warnings) {
|
|
|
9782
10363
|
);
|
|
9783
10364
|
}
|
|
9784
10365
|
|
|
10366
|
+
// ../shared/dist/chunk-UEMUBXFH.mjs
|
|
10367
|
+
var DEFAULT_API_BASE_URL = "https://api.appilots.com";
|
|
10368
|
+
var APPILOTS_API_PATH_PREFIX = "/api/v1";
|
|
10369
|
+
function normalizeApiBaseUrl(configured) {
|
|
10370
|
+
const raw = (configured ?? DEFAULT_API_BASE_URL).trim().replace(/\/+$/, "");
|
|
10371
|
+
if (!raw) return `${DEFAULT_API_BASE_URL}${APPILOTS_API_PATH_PREFIX}`;
|
|
10372
|
+
if (new RegExp(`${APPILOTS_API_PATH_PREFIX}(/|$)`).test(raw)) return raw;
|
|
10373
|
+
return `${raw}${APPILOTS_API_PATH_PREFIX}`;
|
|
10374
|
+
}
|
|
10375
|
+
|
|
9785
10376
|
// src/services/api-client.ts
|
|
9786
|
-
var
|
|
10377
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
9787
10378
|
var DEFAULT_MAX_RETRIES = 2;
|
|
9788
10379
|
var RETRY_BASE_DELAY_MS = 500;
|
|
10380
|
+
function describeNetworkError(url, err) {
|
|
10381
|
+
let origin = url;
|
|
10382
|
+
try {
|
|
10383
|
+
origin = new URL(url).origin;
|
|
10384
|
+
} catch {
|
|
10385
|
+
}
|
|
10386
|
+
const cause = err?.cause;
|
|
10387
|
+
const code = typeof cause?.code === "string" ? cause.code : void 0;
|
|
10388
|
+
const detail = code ?? stringifyUnknown(err);
|
|
10389
|
+
return new Error(`Cannot reach Appilots server at ${origin} (${detail})`);
|
|
10390
|
+
}
|
|
10391
|
+
function errorCodeOf(payload) {
|
|
10392
|
+
const error = payload?.error;
|
|
10393
|
+
if (error && typeof error === "object") {
|
|
10394
|
+
const { code } = error;
|
|
10395
|
+
if (typeof code === "string" && code.trim()) return code;
|
|
10396
|
+
}
|
|
10397
|
+
return void 0;
|
|
10398
|
+
}
|
|
10399
|
+
function describeApiError(payload, fallback) {
|
|
10400
|
+
const error = payload?.error;
|
|
10401
|
+
if (typeof error === "string" && error.trim()) return error;
|
|
10402
|
+
if (error && typeof error === "object") {
|
|
10403
|
+
const { code, message: message2 } = error;
|
|
10404
|
+
const text = typeof message2 === "string" && message2.trim() ? message2 : void 0;
|
|
10405
|
+
const tag = typeof code === "string" && code.trim() ? code : void 0;
|
|
10406
|
+
if (text && tag) return `${text} (${tag})`;
|
|
10407
|
+
if (text) return text;
|
|
10408
|
+
if (tag) return tag;
|
|
10409
|
+
}
|
|
10410
|
+
const message = payload?.message;
|
|
10411
|
+
if (typeof message === "string" && message.trim()) return message;
|
|
10412
|
+
return fallback;
|
|
10413
|
+
}
|
|
9789
10414
|
var AppilotsAPIClient = class {
|
|
9790
|
-
|
|
10415
|
+
/**
|
|
10416
|
+
* `serverUrl` with the API's mount prefix resolved — every path below
|
|
10417
|
+
* is relative to THIS, not to the configured origin.
|
|
10418
|
+
*
|
|
10419
|
+
* It used to be the raw `serverUrl` with `/api/v1` hardcoded into each
|
|
10420
|
+
* path, which made the field mean something different here than in the
|
|
10421
|
+
* SDK. Both read the same `.appilotsrc`: the SDK completes the prefix
|
|
10422
|
+
* when it is missing, so `https://api.appilots.com/api/v1` is correct
|
|
10423
|
+
* there — and here that same value produced
|
|
10424
|
+
* `/api/v1/api/v1/cli/sync`, a 404 whose body reads `Route not found`.
|
|
10425
|
+
* Sharing `normalizeApiBaseUrl` is what makes one file mean one thing:
|
|
10426
|
+
* with or without the prefix now works in both.
|
|
10427
|
+
*/
|
|
10428
|
+
baseUrl;
|
|
9791
10429
|
apiKey;
|
|
9792
10430
|
timeoutMs;
|
|
9793
10431
|
maxRetries;
|
|
9794
10432
|
constructor(config) {
|
|
9795
|
-
this.
|
|
10433
|
+
this.baseUrl = normalizeApiBaseUrl(config.serverUrl);
|
|
9796
10434
|
this.apiKey = config.apiKey;
|
|
9797
|
-
this.timeoutMs = config.timeoutMs ??
|
|
10435
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
9798
10436
|
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
9799
10437
|
}
|
|
9800
10438
|
/**
|
|
@@ -9818,7 +10456,7 @@ var AppilotsAPIClient = class {
|
|
|
9818
10456
|
}
|
|
9819
10457
|
return response;
|
|
9820
10458
|
} catch (err) {
|
|
9821
|
-
lastError = controller.signal.aborted ? new Error(`Request timed out after ${this.timeoutMs}ms: ${url}`) : err;
|
|
10459
|
+
lastError = controller.signal.aborted ? new Error(`Request timed out after ${this.timeoutMs}ms: ${url}`) : describeNetworkError(url, err);
|
|
9822
10460
|
} finally {
|
|
9823
10461
|
clearTimeout(timer);
|
|
9824
10462
|
}
|
|
@@ -9837,7 +10475,7 @@ var AppilotsAPIClient = class {
|
|
|
9837
10475
|
*/
|
|
9838
10476
|
async sync(content, version, appVersion) {
|
|
9839
10477
|
try {
|
|
9840
|
-
const response = await this.request(`${this.
|
|
10478
|
+
const response = await this.request(`${this.baseUrl}/cli/sync`, {
|
|
9841
10479
|
method: "POST",
|
|
9842
10480
|
headers: {
|
|
9843
10481
|
"Content-Type": "application/json",
|
|
@@ -9851,7 +10489,9 @@ var AppilotsAPIClient = class {
|
|
|
9851
10489
|
return {
|
|
9852
10490
|
success: false,
|
|
9853
10491
|
unchanged: false,
|
|
9854
|
-
error: errorData
|
|
10492
|
+
error: describeApiError(errorData, `HTTP ${response.status}: ${response.statusText}`),
|
|
10493
|
+
errorCode: errorCodeOf(errorData),
|
|
10494
|
+
errorStatus: response.status
|
|
9855
10495
|
};
|
|
9856
10496
|
}
|
|
9857
10497
|
const json = await response.json();
|
|
@@ -9864,7 +10504,8 @@ var AppilotsAPIClient = class {
|
|
|
9864
10504
|
return {
|
|
9865
10505
|
success: false,
|
|
9866
10506
|
unchanged: false,
|
|
9867
|
-
error: error instanceof Error ? error.message : "Failed to sync with Appilots API"
|
|
10507
|
+
error: error instanceof Error ? error.message : "Failed to sync with Appilots API",
|
|
10508
|
+
errorStatus: 0
|
|
9868
10509
|
};
|
|
9869
10510
|
}
|
|
9870
10511
|
}
|
|
@@ -9875,7 +10516,7 @@ var AppilotsAPIClient = class {
|
|
|
9875
10516
|
*/
|
|
9876
10517
|
async status() {
|
|
9877
10518
|
try {
|
|
9878
|
-
const response = await this.request(`${this.
|
|
10519
|
+
const response = await this.request(`${this.baseUrl}/cli/status`, {
|
|
9879
10520
|
method: "GET",
|
|
9880
10521
|
headers: {
|
|
9881
10522
|
Authorization: `Bearer ${this.apiKey}`
|
|
@@ -9884,7 +10525,7 @@ var AppilotsAPIClient = class {
|
|
|
9884
10525
|
if (!response.ok) {
|
|
9885
10526
|
const errorData = await response.json().catch(() => ({}));
|
|
9886
10527
|
return {
|
|
9887
|
-
error: errorData
|
|
10528
|
+
error: describeApiError(errorData, `HTTP ${response.status}: ${response.statusText}`)
|
|
9888
10529
|
};
|
|
9889
10530
|
}
|
|
9890
10531
|
const json = await response.json();
|
|
@@ -9906,7 +10547,7 @@ var AppilotsAPIClient = class {
|
|
|
9906
10547
|
*/
|
|
9907
10548
|
async evalRun(scenarios) {
|
|
9908
10549
|
try {
|
|
9909
|
-
const response = await this.request(`${this.
|
|
10550
|
+
const response = await this.request(`${this.baseUrl}/cli/eval/run`, {
|
|
9910
10551
|
method: "POST",
|
|
9911
10552
|
headers: {
|
|
9912
10553
|
"Content-Type": "application/json",
|
|
@@ -9937,7 +10578,7 @@ var AppilotsAPIClient = class {
|
|
|
9937
10578
|
*/
|
|
9938
10579
|
async health() {
|
|
9939
10580
|
try {
|
|
9940
|
-
const response = await this.request(`${this.
|
|
10581
|
+
const response = await this.request(`${this.baseUrl}/health`, {
|
|
9941
10582
|
method: "GET"
|
|
9942
10583
|
});
|
|
9943
10584
|
return response.ok;
|
|
@@ -9947,7 +10588,11 @@ var AppilotsAPIClient = class {
|
|
|
9947
10588
|
}
|
|
9948
10589
|
};
|
|
9949
10590
|
|
|
10591
|
+
// src/version.ts
|
|
10592
|
+
var CLI_VERSION = "0.10.0";
|
|
10593
|
+
|
|
9950
10594
|
exports.AppilotsAPIClient = AppilotsAPIClient;
|
|
10595
|
+
exports.CLI_VERSION = CLI_VERSION;
|
|
9951
10596
|
exports.ComponentAnalyzer = ComponentAnalyzer;
|
|
9952
10597
|
exports.DEFAULT_MANIFEST_FILENAME = DEFAULT_MANIFEST_FILENAME;
|
|
9953
10598
|
exports.DEFAULT_WEB_SCREEN_PATTERNS = DEFAULT_WEB_SCREEN_PATTERNS;
|