@fairfox/polly 0.4.1 → 0.5.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.
|
@@ -1314,16 +1314,250 @@ class IntegrationAnalyzer {
|
|
|
1314
1314
|
}
|
|
1315
1315
|
|
|
1316
1316
|
// vendor/analysis/src/extract/handlers.ts
|
|
1317
|
-
import { Project as Project4, SyntaxKind as
|
|
1317
|
+
import { Project as Project4, SyntaxKind as SyntaxKind3, Node as Node5 } from "ts-morph";
|
|
1318
1318
|
|
|
1319
|
+
// vendor/analysis/src/extract/relationships.ts
|
|
1320
|
+
import { Node as Node4 } from "ts-morph";
|
|
1321
|
+
|
|
1322
|
+
class RelationshipExtractor {
|
|
1323
|
+
extractFromHandler(handlerNode, sourceFile, handlerName) {
|
|
1324
|
+
const relationships = [];
|
|
1325
|
+
const visited = new Set;
|
|
1326
|
+
this.extractFromNode(handlerNode, sourceFile, handlerName, relationships, visited);
|
|
1327
|
+
return this.deduplicateRelationships(relationships);
|
|
1328
|
+
}
|
|
1329
|
+
extractFromNode(node, sourceFile, handlerName, relationships, visited) {
|
|
1330
|
+
node.forEachDescendant((descendant) => {
|
|
1331
|
+
if (Node4.isCallExpression(descendant)) {
|
|
1332
|
+
const expr = descendant.getExpression();
|
|
1333
|
+
if (Node4.isIdentifier(expr)) {
|
|
1334
|
+
const functionName = expr.getText();
|
|
1335
|
+
const functionDecl = sourceFile.getFunction(functionName);
|
|
1336
|
+
if (functionDecl && !visited.has(functionName)) {
|
|
1337
|
+
visited.add(functionName);
|
|
1338
|
+
const body = functionDecl.getBody();
|
|
1339
|
+
if (body) {
|
|
1340
|
+
this.extractFromNode(body, sourceFile, handlerName, relationships, visited);
|
|
1341
|
+
}
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
if (Node4.isPropertyAccessExpression(expr)) {
|
|
1346
|
+
const rel2 = this.extractFromPropertyAccess(expr, handlerName);
|
|
1347
|
+
if (rel2) {
|
|
1348
|
+
relationships.push(rel2);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
const rel = this.extractFromFunctionCall(descendant, handlerName, sourceFile);
|
|
1353
|
+
if (rel) {
|
|
1354
|
+
relationships.push(rel);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (Node4.isAwaitExpression(descendant)) {
|
|
1358
|
+
const rel = this.extractFromDatabaseCall(descendant, handlerName);
|
|
1359
|
+
if (rel) {
|
|
1360
|
+
relationships.push(rel);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
if (Node4.isCallExpression(descendant) && descendant.getExpression().getText() === "fetch") {
|
|
1364
|
+
const rel = this.extractFromFetchCall(descendant, handlerName);
|
|
1365
|
+
if (rel) {
|
|
1366
|
+
relationships.push(rel);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
extractFromFunctionCall(callExpr, handlerName, sourceFile) {
|
|
1372
|
+
const expr = callExpr.getExpression();
|
|
1373
|
+
const exprText = expr.getText();
|
|
1374
|
+
const skipList = [
|
|
1375
|
+
"console.",
|
|
1376
|
+
"JSON.",
|
|
1377
|
+
"Math.",
|
|
1378
|
+
"Object.",
|
|
1379
|
+
"Array.",
|
|
1380
|
+
"String.",
|
|
1381
|
+
"Number.",
|
|
1382
|
+
"Date.",
|
|
1383
|
+
"Promise.",
|
|
1384
|
+
"setTimeout",
|
|
1385
|
+
"setInterval",
|
|
1386
|
+
"clearTimeout",
|
|
1387
|
+
"clearInterval"
|
|
1388
|
+
];
|
|
1389
|
+
if (skipList.some((skip) => exprText.startsWith(skip))) {
|
|
1390
|
+
return null;
|
|
1391
|
+
}
|
|
1392
|
+
let functionName = exprText;
|
|
1393
|
+
let targetComponent = null;
|
|
1394
|
+
if (Node4.isPropertyAccessExpression(expr)) {
|
|
1395
|
+
const objectExpr = expr.getExpression();
|
|
1396
|
+
const objectName = objectExpr.getText();
|
|
1397
|
+
const methodName = expr.getName();
|
|
1398
|
+
targetComponent = this.inferComponentFromCall(objectName, methodName);
|
|
1399
|
+
if (!targetComponent) {
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
functionName = methodName;
|
|
1403
|
+
} else {
|
|
1404
|
+
targetComponent = this.resolveComponentFromImport(exprText, sourceFile);
|
|
1405
|
+
if (!targetComponent) {
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
return {
|
|
1410
|
+
from: this.toComponentId(handlerName),
|
|
1411
|
+
to: targetComponent,
|
|
1412
|
+
description: `Calls ${functionName}()`,
|
|
1413
|
+
technology: "Function Call",
|
|
1414
|
+
confidence: "high",
|
|
1415
|
+
evidence: [`Function call: ${exprText}`]
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
extractFromPropertyAccess(propAccess, handlerName) {
|
|
1419
|
+
if (!Node4.isPropertyAccessExpression(propAccess)) {
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
const fullChain = propAccess.getText();
|
|
1423
|
+
const objectExpr = propAccess.getExpression();
|
|
1424
|
+
const objectName = objectExpr.getText();
|
|
1425
|
+
const methodName = propAccess.getName();
|
|
1426
|
+
const targetComponent = this.inferComponentFromCall(objectName, methodName);
|
|
1427
|
+
if (!targetComponent) {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
return {
|
|
1431
|
+
from: this.toComponentId(handlerName),
|
|
1432
|
+
to: targetComponent,
|
|
1433
|
+
description: `Calls ${methodName}()`,
|
|
1434
|
+
technology: "Function Call",
|
|
1435
|
+
confidence: "high",
|
|
1436
|
+
evidence: [`Property access: ${fullChain}`]
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
extractFromDatabaseCall(awaitExpr, handlerName) {
|
|
1440
|
+
if (!Node4.isAwaitExpression(awaitExpr)) {
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
const innerExpr = awaitExpr.getExpression();
|
|
1444
|
+
if (!Node4.isCallExpression(innerExpr)) {
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
const callExpr = innerExpr.getExpression().getText();
|
|
1448
|
+
if (callExpr.includes("db.query") || callExpr.includes("db.execute") || callExpr.includes("db.select") || callExpr.includes("db.insert") || callExpr.includes("db.update") || callExpr.includes("db.delete")) {
|
|
1449
|
+
const operation = this.inferDatabaseOperation(callExpr);
|
|
1450
|
+
return {
|
|
1451
|
+
from: this.toComponentId(handlerName),
|
|
1452
|
+
to: "database",
|
|
1453
|
+
description: operation,
|
|
1454
|
+
technology: "SQL",
|
|
1455
|
+
confidence: "high",
|
|
1456
|
+
evidence: [`Database call: ${callExpr}`]
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
return null;
|
|
1460
|
+
}
|
|
1461
|
+
extractFromFetchCall(callExpr, handlerName) {
|
|
1462
|
+
const args = callExpr.getArguments();
|
|
1463
|
+
if (args.length === 0) {
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
const urlArg = args[0].getText();
|
|
1467
|
+
let apiName = "external_api";
|
|
1468
|
+
if (urlArg.includes("openai")) {
|
|
1469
|
+
apiName = "openai_api";
|
|
1470
|
+
} else if (urlArg.includes("anthropic")) {
|
|
1471
|
+
apiName = "anthropic_api";
|
|
1472
|
+
}
|
|
1473
|
+
return {
|
|
1474
|
+
from: this.toComponentId(handlerName),
|
|
1475
|
+
to: apiName,
|
|
1476
|
+
description: "Calls external API",
|
|
1477
|
+
technology: "HTTP/REST",
|
|
1478
|
+
confidence: "high",
|
|
1479
|
+
evidence: [`fetch() call to: ${urlArg}`]
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
inferComponentFromCall(objectName, methodName) {
|
|
1483
|
+
const mappings = {
|
|
1484
|
+
db: "db_client",
|
|
1485
|
+
database: "database",
|
|
1486
|
+
repos: "repositories",
|
|
1487
|
+
repository: "repositories",
|
|
1488
|
+
cache: "cache",
|
|
1489
|
+
storage: "storage",
|
|
1490
|
+
ai: "ai_service",
|
|
1491
|
+
auth: "auth_service",
|
|
1492
|
+
authservice: "auth_service",
|
|
1493
|
+
user: "user_service",
|
|
1494
|
+
userservice: "user_service",
|
|
1495
|
+
logger: "logger",
|
|
1496
|
+
queue: "queue_service"
|
|
1497
|
+
};
|
|
1498
|
+
const normalized = objectName.toLowerCase();
|
|
1499
|
+
return mappings[normalized] || null;
|
|
1500
|
+
}
|
|
1501
|
+
resolveComponentFromImport(functionName, sourceFile) {
|
|
1502
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
1503
|
+
const namedImports = importDecl.getNamedImports();
|
|
1504
|
+
for (const namedImport of namedImports) {
|
|
1505
|
+
if (namedImport.getName() === functionName) {
|
|
1506
|
+
const modulePath = importDecl.getModuleSpecifierValue();
|
|
1507
|
+
if (modulePath.includes("/db/") || modulePath.includes("/database/")) {
|
|
1508
|
+
return "db_client";
|
|
1509
|
+
}
|
|
1510
|
+
if (modulePath.includes("/repos") || modulePath.includes("/repositories")) {
|
|
1511
|
+
return "repositories";
|
|
1512
|
+
}
|
|
1513
|
+
if (modulePath.includes("/service") || modulePath.includes("/services")) {
|
|
1514
|
+
const match = modulePath.match(/\/([^/]+)\.ts$/);
|
|
1515
|
+
if (match) {
|
|
1516
|
+
return this.toComponentId(match[1]);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return null;
|
|
1523
|
+
}
|
|
1524
|
+
inferDatabaseOperation(callExpr) {
|
|
1525
|
+
if (callExpr.includes("query") || callExpr.includes("select")) {
|
|
1526
|
+
return "Reads from database";
|
|
1527
|
+
}
|
|
1528
|
+
if (callExpr.includes("execute") || callExpr.includes("insert") || callExpr.includes("update") || callExpr.includes("delete")) {
|
|
1529
|
+
return "Writes to database";
|
|
1530
|
+
}
|
|
1531
|
+
return "Accesses database";
|
|
1532
|
+
}
|
|
1533
|
+
toComponentId(name) {
|
|
1534
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
1535
|
+
}
|
|
1536
|
+
deduplicateRelationships(relationships) {
|
|
1537
|
+
const seen = new Set;
|
|
1538
|
+
const unique = [];
|
|
1539
|
+
for (const rel of relationships) {
|
|
1540
|
+
const key = `${rel.from}->${rel.to}`;
|
|
1541
|
+
if (!seen.has(key)) {
|
|
1542
|
+
seen.add(key);
|
|
1543
|
+
unique.push(rel);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
return unique;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// vendor/analysis/src/extract/handlers.ts
|
|
1319
1551
|
class HandlerExtractor {
|
|
1320
1552
|
project;
|
|
1321
1553
|
typeGuardCache;
|
|
1554
|
+
relationshipExtractor;
|
|
1322
1555
|
constructor(tsConfigPath) {
|
|
1323
1556
|
this.project = new Project4({
|
|
1324
1557
|
tsConfigFilePath: tsConfigPath
|
|
1325
1558
|
});
|
|
1326
1559
|
this.typeGuardCache = new WeakMap;
|
|
1560
|
+
this.relationshipExtractor = new RelationshipExtractor;
|
|
1327
1561
|
}
|
|
1328
1562
|
extractHandlers() {
|
|
1329
1563
|
const handlers = [];
|
|
@@ -1357,9 +1591,9 @@ class HandlerExtractor {
|
|
|
1357
1591
|
const filePath = sourceFile.getFilePath();
|
|
1358
1592
|
const context = this.inferContext(filePath);
|
|
1359
1593
|
sourceFile.forEachDescendant((node) => {
|
|
1360
|
-
if (
|
|
1594
|
+
if (Node5.isCallExpression(node)) {
|
|
1361
1595
|
const expression = node.getExpression();
|
|
1362
|
-
if (
|
|
1596
|
+
if (Node5.isPropertyAccessExpression(expression)) {
|
|
1363
1597
|
const methodName = expression.getName();
|
|
1364
1598
|
if (methodName === "on" || methodName === "addEventListener") {
|
|
1365
1599
|
const handler = this.extractHandler(node, context, filePath);
|
|
@@ -1369,17 +1603,17 @@ class HandlerExtractor {
|
|
|
1369
1603
|
}
|
|
1370
1604
|
}
|
|
1371
1605
|
}
|
|
1372
|
-
if (
|
|
1606
|
+
if (Node5.isSwitchStatement(node)) {
|
|
1373
1607
|
const switchHandlers = this.extractSwitchCaseHandlers(node, context, filePath);
|
|
1374
1608
|
handlers.push(...switchHandlers);
|
|
1375
1609
|
}
|
|
1376
|
-
if (
|
|
1610
|
+
if (Node5.isVariableDeclaration(node)) {
|
|
1377
1611
|
const mapHandlers = this.extractHandlerMapPattern(node, context, filePath);
|
|
1378
1612
|
handlers.push(...mapHandlers);
|
|
1379
1613
|
}
|
|
1380
|
-
if (
|
|
1614
|
+
if (Node5.isIfStatement(node)) {
|
|
1381
1615
|
const parent = node.getParent();
|
|
1382
|
-
const isElseIf = parent &&
|
|
1616
|
+
const isElseIf = parent && Node5.isIfStatement(parent);
|
|
1383
1617
|
if (!isElseIf) {
|
|
1384
1618
|
const typeGuardHandlers = this.extractTypeGuardHandlers(node, context, filePath);
|
|
1385
1619
|
handlers.push(...typeGuardHandlers);
|
|
@@ -1395,9 +1629,9 @@ class HandlerExtractor {
|
|
|
1395
1629
|
}
|
|
1396
1630
|
const messageTypeArg = args[0];
|
|
1397
1631
|
let messageType = null;
|
|
1398
|
-
if (
|
|
1632
|
+
if (Node5.isStringLiteral(messageTypeArg)) {
|
|
1399
1633
|
messageType = messageTypeArg.getLiteralValue();
|
|
1400
|
-
} else if (
|
|
1634
|
+
} else if (Node5.isTemplateExpression(messageTypeArg)) {
|
|
1401
1635
|
messageType = messageTypeArg.getText().replace(/[`'"]/g, "");
|
|
1402
1636
|
}
|
|
1403
1637
|
if (!messageType) {
|
|
@@ -1407,11 +1641,20 @@ class HandlerExtractor {
|
|
|
1407
1641
|
const assignments = [];
|
|
1408
1642
|
const preconditions = [];
|
|
1409
1643
|
const postconditions = [];
|
|
1410
|
-
if (
|
|
1644
|
+
if (Node5.isArrowFunction(handlerArg) || Node5.isFunctionExpression(handlerArg)) {
|
|
1411
1645
|
this.extractAssignments(handlerArg, assignments);
|
|
1412
1646
|
this.extractVerificationConditions(handlerArg, preconditions, postconditions);
|
|
1413
1647
|
}
|
|
1414
1648
|
const line = callExpr.getStartLineNumber();
|
|
1649
|
+
const sourceFile = callExpr.getSourceFile();
|
|
1650
|
+
const handlerName = `${messageType}_handler`;
|
|
1651
|
+
let relationships = undefined;
|
|
1652
|
+
if (Node5.isArrowFunction(handlerArg) || Node5.isFunctionExpression(handlerArg)) {
|
|
1653
|
+
const detectedRelationships = this.relationshipExtractor.extractFromHandler(handlerArg, sourceFile, handlerName);
|
|
1654
|
+
if (detectedRelationships.length > 0) {
|
|
1655
|
+
relationships = detectedRelationships;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1415
1658
|
return {
|
|
1416
1659
|
messageType,
|
|
1417
1660
|
node: context,
|
|
@@ -1421,17 +1664,18 @@ class HandlerExtractor {
|
|
|
1421
1664
|
location: {
|
|
1422
1665
|
file: filePath,
|
|
1423
1666
|
line
|
|
1424
|
-
}
|
|
1667
|
+
},
|
|
1668
|
+
relationships
|
|
1425
1669
|
};
|
|
1426
1670
|
}
|
|
1427
1671
|
extractAssignments(funcNode, assignments) {
|
|
1428
1672
|
funcNode.forEachDescendant((node) => {
|
|
1429
|
-
if (
|
|
1673
|
+
if (Node5.isBinaryExpression(node)) {
|
|
1430
1674
|
const operator = node.getOperatorToken().getText();
|
|
1431
1675
|
if (operator === "=") {
|
|
1432
1676
|
const left = node.getLeft();
|
|
1433
1677
|
const right = node.getRight();
|
|
1434
|
-
if (
|
|
1678
|
+
if (Node5.isPropertyAccessExpression(left)) {
|
|
1435
1679
|
const fieldPath = this.getPropertyPath(left);
|
|
1436
1680
|
if (fieldPath.startsWith("state.")) {
|
|
1437
1681
|
const field = fieldPath.substring(6);
|
|
@@ -1450,13 +1694,13 @@ class HandlerExtractor {
|
|
|
1450
1694
|
}
|
|
1451
1695
|
extractVerificationConditions(funcNode, preconditions, postconditions) {
|
|
1452
1696
|
const body = funcNode.getBody();
|
|
1453
|
-
const statements =
|
|
1697
|
+
const statements = Node5.isBlock(body) ? body.getStatements() : [body];
|
|
1454
1698
|
statements.forEach((statement, index) => {
|
|
1455
|
-
if (
|
|
1699
|
+
if (Node5.isExpressionStatement(statement)) {
|
|
1456
1700
|
const expr = statement.getExpression();
|
|
1457
|
-
if (
|
|
1701
|
+
if (Node5.isCallExpression(expr)) {
|
|
1458
1702
|
const callee = expr.getExpression();
|
|
1459
|
-
if (
|
|
1703
|
+
if (Node5.isIdentifier(callee)) {
|
|
1460
1704
|
const functionName = callee.getText();
|
|
1461
1705
|
if (functionName === "requires") {
|
|
1462
1706
|
const condition = this.extractCondition(expr);
|
|
@@ -1482,7 +1726,7 @@ class HandlerExtractor {
|
|
|
1482
1726
|
const conditionArg = args[0];
|
|
1483
1727
|
const expression = conditionArg.getText();
|
|
1484
1728
|
let message;
|
|
1485
|
-
if (args.length >= 2 &&
|
|
1729
|
+
if (args.length >= 2 && Node5.isStringLiteral(args[1])) {
|
|
1486
1730
|
message = args[1].getLiteralValue();
|
|
1487
1731
|
}
|
|
1488
1732
|
const line = callExpr.getStartLineNumber();
|
|
@@ -1499,29 +1743,29 @@ class HandlerExtractor {
|
|
|
1499
1743
|
getPropertyPath(node) {
|
|
1500
1744
|
const parts = [];
|
|
1501
1745
|
let current = node;
|
|
1502
|
-
while (
|
|
1746
|
+
while (Node5.isPropertyAccessExpression(current)) {
|
|
1503
1747
|
parts.unshift(current.getName());
|
|
1504
1748
|
current = current.getExpression();
|
|
1505
1749
|
}
|
|
1506
|
-
if (
|
|
1750
|
+
if (Node5.isIdentifier(current)) {
|
|
1507
1751
|
parts.unshift(current.getText());
|
|
1508
1752
|
}
|
|
1509
1753
|
return parts.join(".");
|
|
1510
1754
|
}
|
|
1511
1755
|
extractValue(node) {
|
|
1512
|
-
if (
|
|
1756
|
+
if (Node5.isStringLiteral(node)) {
|
|
1513
1757
|
return node.getLiteralValue();
|
|
1514
1758
|
}
|
|
1515
|
-
if (
|
|
1759
|
+
if (Node5.isNumericLiteral(node)) {
|
|
1516
1760
|
return node.getLiteralValue();
|
|
1517
1761
|
}
|
|
1518
|
-
if (node.getKind() ===
|
|
1762
|
+
if (node.getKind() === SyntaxKind3.TrueKeyword) {
|
|
1519
1763
|
return true;
|
|
1520
1764
|
}
|
|
1521
|
-
if (node.getKind() ===
|
|
1765
|
+
if (node.getKind() === SyntaxKind3.FalseKeyword) {
|
|
1522
1766
|
return false;
|
|
1523
1767
|
}
|
|
1524
|
-
if (node.getKind() ===
|
|
1768
|
+
if (node.getKind() === SyntaxKind3.NullKeyword) {
|
|
1525
1769
|
return null;
|
|
1526
1770
|
}
|
|
1527
1771
|
return;
|
|
@@ -1536,10 +1780,10 @@ class HandlerExtractor {
|
|
|
1536
1780
|
}
|
|
1537
1781
|
const caseClauses = switchNode.getClauses();
|
|
1538
1782
|
for (const clause of caseClauses) {
|
|
1539
|
-
if (
|
|
1783
|
+
if (Node5.isCaseClause(clause)) {
|
|
1540
1784
|
const caseExpr = clause.getExpression();
|
|
1541
1785
|
let messageType = null;
|
|
1542
|
-
if (
|
|
1786
|
+
if (Node5.isStringLiteral(caseExpr)) {
|
|
1543
1787
|
messageType = caseExpr.getLiteralValue();
|
|
1544
1788
|
}
|
|
1545
1789
|
if (messageType) {
|
|
@@ -1562,7 +1806,7 @@ class HandlerExtractor {
|
|
|
1562
1806
|
const handlers = [];
|
|
1563
1807
|
try {
|
|
1564
1808
|
const initializer = varDecl.getInitializer();
|
|
1565
|
-
if (!initializer || !
|
|
1809
|
+
if (!initializer || !Node5.isObjectLiteralExpression(initializer)) {
|
|
1566
1810
|
return handlers;
|
|
1567
1811
|
}
|
|
1568
1812
|
const varName = varDecl.getName().toLowerCase();
|
|
@@ -1571,12 +1815,12 @@ class HandlerExtractor {
|
|
|
1571
1815
|
}
|
|
1572
1816
|
const properties = initializer.getProperties();
|
|
1573
1817
|
for (const prop of properties) {
|
|
1574
|
-
if (
|
|
1818
|
+
if (Node5.isPropertyAssignment(prop)) {
|
|
1575
1819
|
const nameNode = prop.getNameNode();
|
|
1576
1820
|
let messageType = null;
|
|
1577
|
-
if (
|
|
1821
|
+
if (Node5.isStringLiteral(nameNode)) {
|
|
1578
1822
|
messageType = nameNode.getLiteralValue();
|
|
1579
|
-
} else if (
|
|
1823
|
+
} else if (Node5.isIdentifier(nameNode)) {
|
|
1580
1824
|
messageType = nameNode.getText();
|
|
1581
1825
|
}
|
|
1582
1826
|
if (messageType) {
|
|
@@ -1623,7 +1867,7 @@ class HandlerExtractor {
|
|
|
1623
1867
|
}
|
|
1624
1868
|
}
|
|
1625
1869
|
const elseStatement = currentIf.getElseStatement();
|
|
1626
|
-
if (elseStatement &&
|
|
1870
|
+
if (elseStatement && Node5.isIfStatement(elseStatement)) {
|
|
1627
1871
|
currentIf = elseStatement;
|
|
1628
1872
|
} else {
|
|
1629
1873
|
break;
|
|
@@ -1639,12 +1883,12 @@ class HandlerExtractor {
|
|
|
1639
1883
|
extractHandlerFromIfClause(ifNode, typeGuards, context, filePath) {
|
|
1640
1884
|
try {
|
|
1641
1885
|
const condition = ifNode.getExpression();
|
|
1642
|
-
if (!
|
|
1886
|
+
if (!Node5.isCallExpression(condition)) {
|
|
1643
1887
|
return null;
|
|
1644
1888
|
}
|
|
1645
1889
|
const funcExpr = condition.getExpression();
|
|
1646
1890
|
let funcName;
|
|
1647
|
-
if (
|
|
1891
|
+
if (Node5.isIdentifier(funcExpr)) {
|
|
1648
1892
|
funcName = funcExpr.getText();
|
|
1649
1893
|
}
|
|
1650
1894
|
if (process.env.POLLY_DEBUG && funcName) {
|
|
@@ -1656,7 +1900,7 @@ class HandlerExtractor {
|
|
|
1656
1900
|
if (process.env.POLLY_DEBUG) {
|
|
1657
1901
|
console.log(`[DEBUG] Found in local type guards: ${funcName} → ${messageType}`);
|
|
1658
1902
|
}
|
|
1659
|
-
} else if (
|
|
1903
|
+
} else if (Node5.isIdentifier(funcExpr)) {
|
|
1660
1904
|
if (process.env.POLLY_DEBUG) {
|
|
1661
1905
|
console.log(`[DEBUG] Not found locally, trying import resolution for: ${funcName}`);
|
|
1662
1906
|
}
|
|
@@ -1669,13 +1913,24 @@ class HandlerExtractor {
|
|
|
1669
1913
|
return null;
|
|
1670
1914
|
}
|
|
1671
1915
|
const line = ifNode.getStartLineNumber();
|
|
1916
|
+
const sourceFile = ifNode.getSourceFile();
|
|
1917
|
+
const handlerName = `${messageType}_handler`;
|
|
1918
|
+
let relationships = undefined;
|
|
1919
|
+
const thenStatement = ifNode.getThenStatement();
|
|
1920
|
+
if (thenStatement) {
|
|
1921
|
+
const detectedRelationships = this.relationshipExtractor.extractFromHandler(thenStatement, sourceFile, handlerName);
|
|
1922
|
+
if (detectedRelationships.length > 0) {
|
|
1923
|
+
relationships = detectedRelationships;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1672
1926
|
return {
|
|
1673
1927
|
messageType,
|
|
1674
1928
|
node: context,
|
|
1675
1929
|
assignments: [],
|
|
1676
1930
|
preconditions: [],
|
|
1677
1931
|
postconditions: [],
|
|
1678
|
-
location: { file: filePath, line }
|
|
1932
|
+
location: { file: filePath, line },
|
|
1933
|
+
relationships
|
|
1679
1934
|
};
|
|
1680
1935
|
} catch (error) {
|
|
1681
1936
|
return null;
|
|
@@ -1684,20 +1939,20 @@ class HandlerExtractor {
|
|
|
1684
1939
|
findTypePredicateFunctions(sourceFile) {
|
|
1685
1940
|
const typeGuards = new Map;
|
|
1686
1941
|
sourceFile.forEachDescendant((node) => {
|
|
1687
|
-
if (
|
|
1942
|
+
if (Node5.isFunctionDeclaration(node) || Node5.isFunctionExpression(node) || Node5.isArrowFunction(node)) {
|
|
1688
1943
|
const returnTypeNode = node.getReturnTypeNode();
|
|
1689
|
-
if (returnTypeNode &&
|
|
1944
|
+
if (returnTypeNode && Node5.isTypePredicate(returnTypeNode)) {
|
|
1690
1945
|
let functionName;
|
|
1691
|
-
if (
|
|
1946
|
+
if (Node5.isFunctionDeclaration(node)) {
|
|
1692
1947
|
functionName = node.getName();
|
|
1693
|
-
} else if (
|
|
1948
|
+
} else if (Node5.isFunctionExpression(node)) {
|
|
1694
1949
|
const parent = node.getParent();
|
|
1695
|
-
if (
|
|
1950
|
+
if (Node5.isVariableDeclaration(parent)) {
|
|
1696
1951
|
functionName = parent.getName();
|
|
1697
1952
|
}
|
|
1698
|
-
} else if (
|
|
1953
|
+
} else if (Node5.isArrowFunction(node)) {
|
|
1699
1954
|
const parent = node.getParent();
|
|
1700
|
-
if (
|
|
1955
|
+
if (Node5.isVariableDeclaration(parent)) {
|
|
1701
1956
|
functionName = parent.getName();
|
|
1702
1957
|
}
|
|
1703
1958
|
}
|
|
@@ -1738,15 +1993,15 @@ class HandlerExtractor {
|
|
|
1738
1993
|
return null;
|
|
1739
1994
|
}
|
|
1740
1995
|
for (const def of definitions) {
|
|
1741
|
-
if (
|
|
1996
|
+
if (Node5.isFunctionDeclaration(def) || Node5.isFunctionExpression(def) || Node5.isArrowFunction(def)) {
|
|
1742
1997
|
const returnTypeNode = def.getReturnTypeNode();
|
|
1743
1998
|
if (process.env.POLLY_DEBUG) {
|
|
1744
1999
|
const returnType = def.getReturnType().getText();
|
|
1745
2000
|
console.log(`[DEBUG] Function ${funcName} return type (resolved): ${returnType}`);
|
|
1746
2001
|
console.log(`[DEBUG] Has return type node: ${!!returnTypeNode}`);
|
|
1747
|
-
console.log(`[DEBUG] Is type predicate node: ${returnTypeNode &&
|
|
2002
|
+
console.log(`[DEBUG] Is type predicate node: ${returnTypeNode && Node5.isTypePredicate(returnTypeNode)}`);
|
|
1748
2003
|
}
|
|
1749
|
-
if (returnTypeNode &&
|
|
2004
|
+
if (returnTypeNode && Node5.isTypePredicate(returnTypeNode)) {
|
|
1750
2005
|
const typeNode = returnTypeNode.getTypeNode();
|
|
1751
2006
|
if (typeNode) {
|
|
1752
2007
|
const typeName = typeNode.getText();
|
|
@@ -2288,10 +2543,33 @@ class StructurizrDSLGenerator {
|
|
|
2288
2543
|
if (this.options.groups && this.options.groups.length > 0) {
|
|
2289
2544
|
parts.push(...this.generateGroupedComponents(componentDefs, this.options.groups));
|
|
2290
2545
|
} else {
|
|
2291
|
-
|
|
2292
|
-
|
|
2546
|
+
const autoGroups = this.generateAutomaticGroups(componentDefs);
|
|
2547
|
+
if (autoGroups.length > 0) {
|
|
2548
|
+
parts.push(...this.generateGroupedComponents(componentDefs, autoGroups));
|
|
2549
|
+
} else {
|
|
2550
|
+
for (const comp of componentDefs) {
|
|
2551
|
+
parts.push(this.generateComponentDefinition(comp, " "));
|
|
2552
|
+
}
|
|
2293
2553
|
}
|
|
2294
2554
|
}
|
|
2555
|
+
const serviceComponents = new Set;
|
|
2556
|
+
for (const handler of contextInfo.handlers) {
|
|
2557
|
+
if (handler.relationships) {
|
|
2558
|
+
for (const rel of handler.relationships) {
|
|
2559
|
+
const targetId = this.toId(rel.to);
|
|
2560
|
+
const isHandler = componentDefs.some((c) => c.id === targetId);
|
|
2561
|
+
if (!isHandler) {
|
|
2562
|
+
serviceComponents.add(rel.to);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
for (const serviceId of serviceComponents) {
|
|
2568
|
+
const serviceName = serviceId.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
2569
|
+
parts.push(` ${this.toId(serviceId)} = component "${serviceName}" "Business logic service" {`);
|
|
2570
|
+
parts.push(` tags "Service" "Auto-detected"`);
|
|
2571
|
+
parts.push(` }`);
|
|
2572
|
+
}
|
|
2295
2573
|
if (contextInfo.components) {
|
|
2296
2574
|
for (const comp of contextInfo.components) {
|
|
2297
2575
|
parts.push(` ${this.toId(comp.name)} = component "${comp.name}" "${this.escape(comp.description || "UI component")}" {`);
|
|
@@ -2444,6 +2722,21 @@ class StructurizrDSLGenerator {
|
|
|
2444
2722
|
parts.push(` }`);
|
|
2445
2723
|
}
|
|
2446
2724
|
}
|
|
2725
|
+
for (const handler of contextInfo.handlers) {
|
|
2726
|
+
if (handler.relationships && handler.relationships.length > 0) {
|
|
2727
|
+
for (const rel of handler.relationships) {
|
|
2728
|
+
const fromId = this.toId(rel.from);
|
|
2729
|
+
const toId = this.toId(rel.to);
|
|
2730
|
+
const description = this.escape(rel.description);
|
|
2731
|
+
parts.push(` ${fromId} -> ${toId} "${description}" {`);
|
|
2732
|
+
if (rel.technology) {
|
|
2733
|
+
parts.push(` technology "${this.escape(rel.technology)}"`);
|
|
2734
|
+
}
|
|
2735
|
+
parts.push(` tags "Auto-detected"`);
|
|
2736
|
+
parts.push(` }`);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2447
2740
|
const stateHandlers = [];
|
|
2448
2741
|
const queryHandlers = [];
|
|
2449
2742
|
for (const [messageType, handlers] of handlersByType) {
|
|
@@ -2568,6 +2861,10 @@ class StructurizrDSLGenerator {
|
|
|
2568
2861
|
parts.push(this.generateUserDynamicDiagram(diagram));
|
|
2569
2862
|
}
|
|
2570
2863
|
}
|
|
2864
|
+
const autoDiagrams = this.generateAutomaticDynamicDiagrams();
|
|
2865
|
+
if (autoDiagrams.length > 0) {
|
|
2866
|
+
parts.push(...autoDiagrams);
|
|
2867
|
+
}
|
|
2571
2868
|
const flowsByDomain = new Map;
|
|
2572
2869
|
for (const flow of this.analysis.messageFlows) {
|
|
2573
2870
|
const messageType = flow.messageType.toLowerCase();
|
|
@@ -2596,6 +2893,111 @@ class StructurizrDSLGenerator {
|
|
|
2596
2893
|
|
|
2597
2894
|
`);
|
|
2598
2895
|
}
|
|
2896
|
+
generateAutomaticDynamicDiagrams() {
|
|
2897
|
+
const diagrams = [];
|
|
2898
|
+
const processedHandlers = new Set;
|
|
2899
|
+
const handlersWithRelationships = [];
|
|
2900
|
+
for (const [contextType, contextInfo] of Object.entries(this.analysis.contexts)) {
|
|
2901
|
+
for (const handler of contextInfo.handlers) {
|
|
2902
|
+
if (handler.relationships && handler.relationships.length > 0) {
|
|
2903
|
+
handlersWithRelationships.push({
|
|
2904
|
+
handler,
|
|
2905
|
+
contextType,
|
|
2906
|
+
contextName: this.toId(contextType)
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
const handlerGroups = new Map;
|
|
2912
|
+
for (const hwc of handlersWithRelationships) {
|
|
2913
|
+
const messageType = hwc.handler.messageType.toLowerCase();
|
|
2914
|
+
let category = "general";
|
|
2915
|
+
if (messageType.includes("auth") || messageType.includes("login") || messageType.includes("logout") || messageType.includes("verify") || messageType.includes("register")) {
|
|
2916
|
+
category = "authentication";
|
|
2917
|
+
} else if (messageType.includes("user")) {
|
|
2918
|
+
category = "user";
|
|
2919
|
+
} else if (messageType.includes("todo")) {
|
|
2920
|
+
category = "todo";
|
|
2921
|
+
} else if (messageType.includes("query") || messageType.includes("get") || messageType.includes("fetch") || messageType.includes("list")) {
|
|
2922
|
+
category = "query";
|
|
2923
|
+
} else if (messageType.includes("command") || messageType.includes("create") || messageType.includes("update") || messageType.includes("delete") || messageType.includes("add") || messageType.includes("remove")) {
|
|
2924
|
+
category = "command";
|
|
2925
|
+
}
|
|
2926
|
+
if (!handlerGroups.has(category)) {
|
|
2927
|
+
handlerGroups.set(category, []);
|
|
2928
|
+
}
|
|
2929
|
+
handlerGroups.get(category).push(hwc);
|
|
2930
|
+
}
|
|
2931
|
+
let diagramCount = 0;
|
|
2932
|
+
const maxDiagrams = 5;
|
|
2933
|
+
const totalHandlers = handlersWithRelationships.length;
|
|
2934
|
+
const categoriesWithHandlers = handlerGroups.size;
|
|
2935
|
+
if (totalHandlers <= 5 && categoriesWithHandlers <= 3) {
|
|
2936
|
+
const allHandlers = Array.from(handlerGroups.values()).flat();
|
|
2937
|
+
const diagram = this.generateHandlerFlowDiagram("general", allHandlers);
|
|
2938
|
+
if (diagram) {
|
|
2939
|
+
diagrams.push(diagram);
|
|
2940
|
+
}
|
|
2941
|
+
} else {
|
|
2942
|
+
for (const [category, handlers] of handlerGroups) {
|
|
2943
|
+
if (diagramCount >= maxDiagrams)
|
|
2944
|
+
break;
|
|
2945
|
+
if (handlers.length === 0)
|
|
2946
|
+
continue;
|
|
2947
|
+
const diagram = this.generateHandlerFlowDiagram(category, handlers);
|
|
2948
|
+
if (diagram) {
|
|
2949
|
+
diagrams.push(diagram);
|
|
2950
|
+
diagramCount++;
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
return diagrams;
|
|
2955
|
+
}
|
|
2956
|
+
generateHandlerFlowDiagram(category, handlers) {
|
|
2957
|
+
const parts = [];
|
|
2958
|
+
const title = this.getCategoryTitle(category);
|
|
2959
|
+
const description = this.getCategoryDescription(category);
|
|
2960
|
+
const scope = handlers[0]?.contextName ? `extension.${handlers[0].contextName}` : "extension";
|
|
2961
|
+
parts.push(` dynamic ${scope} "${title}" "${description}" {`);
|
|
2962
|
+
let stepCount = 0;
|
|
2963
|
+
for (const { handler, contextName } of handlers) {
|
|
2964
|
+
const handlerComponentId = this.toId(`${handler.messageType}_handler`);
|
|
2965
|
+
for (const rel of handler.relationships) {
|
|
2966
|
+
const toComponent = this.toId(rel.to);
|
|
2967
|
+
parts.push(` ${handlerComponentId} -> ${toComponent} "${rel.description}"`);
|
|
2968
|
+
stepCount++;
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
if (stepCount === 0) {
|
|
2972
|
+
return null;
|
|
2973
|
+
}
|
|
2974
|
+
parts.push(" autoLayout lr");
|
|
2975
|
+
parts.push(" }");
|
|
2976
|
+
return parts.join(`
|
|
2977
|
+
`);
|
|
2978
|
+
}
|
|
2979
|
+
getCategoryTitle(category) {
|
|
2980
|
+
const titles = {
|
|
2981
|
+
authentication: "Authentication Flow",
|
|
2982
|
+
user: "User Management Flow",
|
|
2983
|
+
todo: "Todo Management Flow",
|
|
2984
|
+
query: "Query Processing Flow",
|
|
2985
|
+
command: "Command Processing Flow",
|
|
2986
|
+
general: "Message Processing Flow"
|
|
2987
|
+
};
|
|
2988
|
+
return titles[category] || `${this.capitalize(category)} Flow`;
|
|
2989
|
+
}
|
|
2990
|
+
getCategoryDescription(category) {
|
|
2991
|
+
const descriptions = {
|
|
2992
|
+
authentication: "Shows authentication request processing and service interactions",
|
|
2993
|
+
user: "Shows user management operations and service dependencies",
|
|
2994
|
+
todo: "Shows todo item operations and service dependencies",
|
|
2995
|
+
query: "Shows read operations flow through query handlers and services",
|
|
2996
|
+
command: "Shows write operations flow through command handlers and services",
|
|
2997
|
+
general: "Shows message processing flow through handlers and services"
|
|
2998
|
+
};
|
|
2999
|
+
return descriptions[category] || "Shows message flow through system components";
|
|
3000
|
+
}
|
|
2599
3001
|
generateUserDynamicDiagram(diagram) {
|
|
2600
3002
|
const parts = [];
|
|
2601
3003
|
parts.push(` dynamic ${diagram.scope || "extension"} "${this.escape(diagram.title)}" "${this.escape(diagram.description || "")}" {`);
|
|
@@ -2892,6 +3294,105 @@ class StructurizrDSLGenerator {
|
|
|
2892
3294
|
return parts.join(`
|
|
2893
3295
|
`);
|
|
2894
3296
|
}
|
|
3297
|
+
generateAutomaticGroups(componentDefs) {
|
|
3298
|
+
const groups = [];
|
|
3299
|
+
const assigned = new Set;
|
|
3300
|
+
const authHandlers = componentDefs.filter((comp) => {
|
|
3301
|
+
const type = comp.messageType.toLowerCase();
|
|
3302
|
+
return type.includes("login") || type.includes("logout") || type.includes("auth") || type.includes("verify") || type.includes("register") || type.includes("signup");
|
|
3303
|
+
});
|
|
3304
|
+
if (authHandlers.length > 0) {
|
|
3305
|
+
groups.push({
|
|
3306
|
+
name: "Authentication",
|
|
3307
|
+
components: authHandlers.map((c) => c.id)
|
|
3308
|
+
});
|
|
3309
|
+
authHandlers.forEach((h) => assigned.add(h.id));
|
|
3310
|
+
}
|
|
3311
|
+
const subscriptionHandlers = componentDefs.filter((comp) => {
|
|
3312
|
+
const type = comp.messageType.toLowerCase();
|
|
3313
|
+
return type.includes("subscribe") || type.includes("unsubscribe");
|
|
3314
|
+
});
|
|
3315
|
+
if (subscriptionHandlers.length > 0) {
|
|
3316
|
+
groups.push({
|
|
3317
|
+
name: "Subscriptions",
|
|
3318
|
+
components: subscriptionHandlers.map((c) => c.id)
|
|
3319
|
+
});
|
|
3320
|
+
subscriptionHandlers.forEach((h) => assigned.add(h.id));
|
|
3321
|
+
}
|
|
3322
|
+
const entityGroups = new Map;
|
|
3323
|
+
for (const comp of componentDefs) {
|
|
3324
|
+
if (assigned.has(comp.id))
|
|
3325
|
+
continue;
|
|
3326
|
+
const type = comp.messageType.toLowerCase();
|
|
3327
|
+
if (type === "connection" || type === "message" || type === "close" || type === "error") {
|
|
3328
|
+
continue;
|
|
3329
|
+
}
|
|
3330
|
+
let entity = null;
|
|
3331
|
+
const underscoreMatch = type.match(/^([a-z]+)_(add|create|update|delete|remove|get|fetch|load|list|query)/);
|
|
3332
|
+
if (underscoreMatch) {
|
|
3333
|
+
entity = underscoreMatch[1];
|
|
3334
|
+
}
|
|
3335
|
+
if (!entity) {
|
|
3336
|
+
const camelMatch = type.match(/(add|create|update|delete|remove|get|fetch|load|list|query)([a-z]+)/i);
|
|
3337
|
+
if (camelMatch) {
|
|
3338
|
+
entity = camelMatch[2].toLowerCase();
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
if (!entity && type.match(/^[a-z]+$/)) {
|
|
3342
|
+
entity = type;
|
|
3343
|
+
}
|
|
3344
|
+
if (entity) {
|
|
3345
|
+
if (!entityGroups.has(entity)) {
|
|
3346
|
+
entityGroups.set(entity, []);
|
|
3347
|
+
}
|
|
3348
|
+
entityGroups.get(entity).push(comp.id);
|
|
3349
|
+
assigned.add(comp.id);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
for (const [entity, componentIds] of entityGroups) {
|
|
3353
|
+
if (componentIds.length >= 2) {
|
|
3354
|
+
const entityName = entity.charAt(0).toUpperCase() + entity.slice(1);
|
|
3355
|
+
groups.push({
|
|
3356
|
+
name: `${entityName} Management`,
|
|
3357
|
+
components: componentIds
|
|
3358
|
+
});
|
|
3359
|
+
} else {
|
|
3360
|
+
componentIds.forEach((id) => assigned.delete(id));
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
const queryHandlers = componentDefs.filter((comp) => {
|
|
3364
|
+
if (assigned.has(comp.id))
|
|
3365
|
+
return false;
|
|
3366
|
+
const type = comp.messageType.toLowerCase();
|
|
3367
|
+
return type.includes("query") || type.includes("get") || type.includes("fetch") || type.includes("load") || type.includes("list") || type.includes("read");
|
|
3368
|
+
});
|
|
3369
|
+
const commandHandlers = componentDefs.filter((comp) => {
|
|
3370
|
+
if (assigned.has(comp.id))
|
|
3371
|
+
return false;
|
|
3372
|
+
const type = comp.messageType.toLowerCase();
|
|
3373
|
+
return type.includes("command") || type.includes("add") || type.includes("create") || type.includes("update") || type.includes("delete") || type.includes("remove") || type.includes("set") || type.includes("clear");
|
|
3374
|
+
});
|
|
3375
|
+
if (queryHandlers.length >= 2) {
|
|
3376
|
+
groups.push({
|
|
3377
|
+
name: "Query Handlers",
|
|
3378
|
+
components: queryHandlers.map((c) => c.id)
|
|
3379
|
+
});
|
|
3380
|
+
queryHandlers.forEach((h) => assigned.add(h.id));
|
|
3381
|
+
}
|
|
3382
|
+
if (commandHandlers.length >= 2) {
|
|
3383
|
+
groups.push({
|
|
3384
|
+
name: "Command Handlers",
|
|
3385
|
+
components: commandHandlers.map((c) => c.id)
|
|
3386
|
+
});
|
|
3387
|
+
commandHandlers.forEach((h) => assigned.add(h.id));
|
|
3388
|
+
}
|
|
3389
|
+
const groupedCount = assigned.size;
|
|
3390
|
+
const totalCount = componentDefs.length;
|
|
3391
|
+
if (groupedCount >= totalCount * 0.5 || groups.length >= 2) {
|
|
3392
|
+
return groups;
|
|
3393
|
+
}
|
|
3394
|
+
return [];
|
|
3395
|
+
}
|
|
2895
3396
|
generateGroupedComponents(componentDefs, groups) {
|
|
2896
3397
|
const parts = [];
|
|
2897
3398
|
const assignedComponents = new Set;
|
|
@@ -3399,4 +3900,4 @@ Stack trace:`, COLORS.gray));
|
|
|
3399
3900
|
process.exit(1);
|
|
3400
3901
|
});
|
|
3401
3902
|
|
|
3402
|
-
//# debugId=
|
|
3903
|
+
//# debugId=59C09397E2AB6BFF64756E2164756E21
|