@fairfox/polly 0.16.0 → 0.18.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.
@@ -39,6 +39,12 @@ class ProjectDetector {
39
39
  const packageJsonPath = path3.join(this.projectRoot, "package.json");
40
40
  if (fs3.existsSync(packageJsonPath)) {
41
41
  const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
42
+ if (packageJson.workspaces) {
43
+ const monorepoConfig = this.detectMonorepo(packageJson);
44
+ if (monorepoConfig) {
45
+ return monorepoConfig;
46
+ }
47
+ }
42
48
  if (packageJson.dependencies?.electron || packageJson.devDependencies?.electron) {
43
49
  return this.detectElectron(packageJson);
44
50
  }
@@ -268,10 +274,11 @@ class ProjectDetector {
268
274
  }
269
275
  };
270
276
  }
271
- scoreServerCandidates(candidates) {
277
+ scoreServerCandidates(candidates, baseDir) {
278
+ const root = baseDir || this.projectRoot;
272
279
  const scored = [];
273
280
  for (const candidate of candidates) {
274
- const fullPath = path3.join(this.projectRoot, candidate);
281
+ const fullPath = path3.join(root, candidate);
275
282
  if (!fs3.existsSync(fullPath))
276
283
  continue;
277
284
  try {
@@ -426,6 +433,135 @@ class ProjectDetector {
426
433
  }
427
434
  return score;
428
435
  }
436
+ detectMonorepo(packageJson) {
437
+ const workspacesRaw = packageJson.workspaces;
438
+ const patterns = Array.isArray(workspacesRaw) ? workspacesRaw : Array.isArray(workspacesRaw?.packages) ? workspacesRaw.packages : [];
439
+ if (patterns.length === 0)
440
+ return null;
441
+ const workspaceDirs = this.resolveWorkspaceGlobs(patterns);
442
+ if (workspaceDirs.length === 0)
443
+ return null;
444
+ const entryPoints = {};
445
+ const contextMapping = {};
446
+ const workspacePackages = [];
447
+ for (const wsDir of workspaceDirs) {
448
+ const wsPkgPath = path3.join(wsDir, "package.json");
449
+ if (!fs3.existsSync(wsPkgPath))
450
+ continue;
451
+ let wsPkg;
452
+ try {
453
+ wsPkg = JSON.parse(fs3.readFileSync(wsPkgPath, "utf-8"));
454
+ } catch {
455
+ continue;
456
+ }
457
+ const pkgName = wsPkg.name || path3.basename(wsDir);
458
+ const context = this.inferContextFromPackageName(pkgName);
459
+ const serverCandidates = [
460
+ "src/server.ts",
461
+ "src/server.js",
462
+ "src/index.ts",
463
+ "src/index.js",
464
+ "src/main.ts",
465
+ "src/main.js",
466
+ "src/app.ts",
467
+ "src/app.js",
468
+ "server.ts",
469
+ "server.js",
470
+ "index.ts",
471
+ "index.js"
472
+ ];
473
+ const scoredServers = this.scoreServerCandidates(serverCandidates, wsDir);
474
+ if (scoredServers.length > 0) {
475
+ const best = scoredServers[0];
476
+ if (best) {
477
+ entryPoints[context] = best.path;
478
+ if (best.hasWebSocket) {
479
+ contextMapping[context] = "WebSocket Server";
480
+ } else if (best.hasHTTP) {
481
+ contextMapping[context] = "HTTP Server";
482
+ } else {
483
+ contextMapping[context] = this.capitalize(context);
484
+ }
485
+ }
486
+ } else {
487
+ const commonEntries = [
488
+ "src/index.ts",
489
+ "src/index.tsx",
490
+ "src/main.ts",
491
+ "src/main.tsx",
492
+ "src/app.ts",
493
+ "src/app.tsx",
494
+ "index.ts",
495
+ "index.tsx"
496
+ ];
497
+ for (const candidate of commonEntries) {
498
+ const fullPath = path3.join(wsDir, candidate);
499
+ if (fs3.existsSync(fullPath)) {
500
+ entryPoints[context] = fullPath;
501
+ contextMapping[context] = this.capitalize(context);
502
+ break;
503
+ }
504
+ }
505
+ }
506
+ workspacePackages.push({ name: pkgName, path: wsDir, context });
507
+ }
508
+ if (Object.keys(entryPoints).length === 0)
509
+ return null;
510
+ return {
511
+ type: "monorepo",
512
+ entryPoints,
513
+ contextMapping,
514
+ workspacePackages,
515
+ metadata: {
516
+ name: packageJson.name,
517
+ version: packageJson.version,
518
+ description: packageJson.description
519
+ }
520
+ };
521
+ }
522
+ resolveWorkspaceGlobs(patterns) {
523
+ const dirs = [];
524
+ for (const pattern of patterns) {
525
+ if (pattern.includes("*")) {
526
+ const base = pattern.replace(/\/?\*.*$/, "");
527
+ const baseDir = path3.join(this.projectRoot, base);
528
+ if (fs3.existsSync(baseDir) && fs3.statSync(baseDir).isDirectory()) {
529
+ try {
530
+ const entries = fs3.readdirSync(baseDir, { withFileTypes: true });
531
+ for (const entry of entries) {
532
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
533
+ dirs.push(path3.join(baseDir, entry.name));
534
+ }
535
+ }
536
+ } catch {}
537
+ }
538
+ } else {
539
+ const fullPath = path3.join(this.projectRoot, pattern);
540
+ if (fs3.existsSync(fullPath) && fs3.statSync(fullPath).isDirectory()) {
541
+ dirs.push(fullPath);
542
+ }
543
+ }
544
+ }
545
+ return dirs;
546
+ }
547
+ inferContextFromPackageName(name) {
548
+ const parts = name.split("/");
549
+ const baseName = name.includes("/") ? parts[parts.length - 1] || name : name;
550
+ const lower = baseName.toLowerCase();
551
+ const serverAliases = ["api", "server", "backend"];
552
+ if (serverAliases.includes(lower))
553
+ return "server";
554
+ const clientAliases = ["web", "app", "frontend", "client", "ui"];
555
+ if (clientAliases.includes(lower))
556
+ return "client";
557
+ const sharedAliases = ["shared", "common", "lib", "core", "utils"];
558
+ if (sharedAliases.includes(lower))
559
+ return "shared";
560
+ return lower;
561
+ }
562
+ capitalize(str) {
563
+ return str.charAt(0).toUpperCase() + str.slice(1);
564
+ }
429
565
  detectGenericProject() {
430
566
  const entryPoints = {};
431
567
  const tsConfigPath = path3.join(this.projectRoot, "tsconfig.json");
@@ -915,11 +1051,13 @@ import { Node as Node2, Project as Project2 } from "ts-morph";
915
1051
  class FlowAnalyzer {
916
1052
  project;
917
1053
  handlers;
918
- constructor(tsConfigPath, handlers) {
1054
+ contextOverrides;
1055
+ constructor(tsConfigPath, handlers, contextOverrides) {
919
1056
  this.project = new Project2({
920
1057
  tsConfigFilePath: tsConfigPath
921
1058
  });
922
1059
  this.handlers = handlers;
1060
+ this.contextOverrides = contextOverrides || new Map;
923
1061
  }
924
1062
  analyzeFlows() {
925
1063
  const flows = [];
@@ -1155,6 +1293,10 @@ class FlowAnalyzer {
1155
1293
  return { trigger, flowName, description };
1156
1294
  }
1157
1295
  inferContext(filePath) {
1296
+ for (const [pathPrefix, context] of this.contextOverrides.entries()) {
1297
+ if (filePath.startsWith(pathPrefix))
1298
+ return context;
1299
+ }
1158
1300
  const path2 = filePath.toLowerCase();
1159
1301
  const contextPatterns = [
1160
1302
  { context: "background", patterns: ["/background/", "\\background\\"] },
@@ -1585,7 +1727,8 @@ class HandlerExtractor {
1585
1727
  packageRoot;
1586
1728
  warnings;
1587
1729
  currentFunctionParams = [];
1588
- constructor(tsConfigPath) {
1730
+ contextOverrides;
1731
+ constructor(tsConfigPath, contextOverrides) {
1589
1732
  this.project = new Project3({
1590
1733
  tsConfigFilePath: tsConfigPath
1591
1734
  });
@@ -1593,6 +1736,7 @@ class HandlerExtractor {
1593
1736
  this.relationshipExtractor = new RelationshipExtractor;
1594
1737
  this.analyzedFiles = new Set;
1595
1738
  this.warnings = [];
1739
+ this.contextOverrides = contextOverrides || new Map;
1596
1740
  this.packageRoot = this.findPackageRoot(tsConfigPath);
1597
1741
  }
1598
1742
  warnUnsupportedPattern(pattern, location, suggestion) {
@@ -1638,13 +1782,14 @@ class HandlerExtractor {
1638
1782
  const messageTypes = new Set;
1639
1783
  const invalidMessageTypes = new Set;
1640
1784
  const stateConstraints = [];
1785
+ const globalStateConstraints = [];
1641
1786
  const verifiedStates = [];
1642
1787
  this.warnings = [];
1643
1788
  const allSourceFiles = this.project.getSourceFiles();
1644
1789
  const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
1645
1790
  this.debugLogSourceFiles(allSourceFiles, entryPoints);
1646
1791
  for (const entryPoint of entryPoints) {
1647
- this.analyzeFileAndImports(entryPoint, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates);
1792
+ this.analyzeFileAndImports(entryPoint, handlers, messageTypes, invalidMessageTypes, stateConstraints, globalStateConstraints, verifiedStates);
1648
1793
  }
1649
1794
  if (verifiedStates.length > 0) {
1650
1795
  if (process.env["POLLY_DEBUG"]) {
@@ -1674,11 +1819,12 @@ class HandlerExtractor {
1674
1819
  handlers,
1675
1820
  messageTypes,
1676
1821
  stateConstraints,
1822
+ globalStateConstraints,
1677
1823
  verifiedStates,
1678
1824
  warnings: this.warnings
1679
1825
  };
1680
1826
  }
1681
- analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates) {
1827
+ analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, globalStateConstraints, verifiedStates) {
1682
1828
  const filePath = sourceFile.getFilePath();
1683
1829
  if (this.analyzedFiles.has(filePath)) {
1684
1830
  return;
@@ -1692,6 +1838,8 @@ class HandlerExtractor {
1692
1838
  this.categorizeHandlerMessageTypes(fileHandlers, messageTypes, invalidMessageTypes);
1693
1839
  const fileConstraints = this.extractStateConstraintsFromFile(sourceFile);
1694
1840
  stateConstraints.push(...fileConstraints);
1841
+ const fileGlobalConstraints = this.extractGlobalStateConstraintsFromFile(sourceFile);
1842
+ globalStateConstraints.push(...fileGlobalConstraints);
1695
1843
  const fileVerifiedStates = this.extractVerifiedStatesFromFile(sourceFile);
1696
1844
  verifiedStates.push(...fileVerifiedStates);
1697
1845
  const importDeclarations = sourceFile.getImportDeclarations();
@@ -1705,7 +1853,7 @@ class HandlerExtractor {
1705
1853
  }
1706
1854
  continue;
1707
1855
  }
1708
- this.analyzeFileAndImports(importedFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates);
1856
+ this.analyzeFileAndImports(importedFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, globalStateConstraints, verifiedStates);
1709
1857
  } else if (process.env["POLLY_DEBUG"]) {
1710
1858
  const specifier = importDecl.getModuleSpecifierValue();
1711
1859
  if (!specifier.startsWith("node:") && !this.isNodeModuleImport(specifier)) {
@@ -1806,8 +1954,20 @@ class HandlerExtractor {
1806
1954
  handlers.push(handler);
1807
1955
  }
1808
1956
  }
1957
+ if (methodName === "ws") {
1958
+ this.extractElysiaWsHandlers(node, context, filePath, handlers);
1959
+ }
1960
+ if (this.isRestMethod(methodName) && this.isWebFrameworkFile(node.getSourceFile())) {
1961
+ const restHandler = this.extractRestHandler(node, methodName, context, filePath);
1962
+ if (restHandler) {
1963
+ handlers.push(restHandler);
1964
+ }
1965
+ }
1809
1966
  }
1810
1967
  }
1968
+ isRestMethod(name) {
1969
+ return ["get", "post", "put", "delete", "patch"].includes(name);
1970
+ }
1811
1971
  isElseIfStatement(node) {
1812
1972
  const parent = node.getParent();
1813
1973
  return parent !== undefined && Node4.isIfStatement(parent);
@@ -1873,6 +2033,135 @@ class HandlerExtractor {
1873
2033
  parameters
1874
2034
  };
1875
2035
  }
2036
+ extractElysiaWsHandlers(node, context, filePath, handlers) {
2037
+ const args = node.getArguments();
2038
+ if (args.length < 2)
2039
+ return;
2040
+ const routeArg = args[0];
2041
+ if (!routeArg || !Node4.isStringLiteral(routeArg))
2042
+ return;
2043
+ const routePath = routeArg.getLiteralValue();
2044
+ const configArg = args[1];
2045
+ if (!configArg || !Node4.isObjectLiteralExpression(configArg))
2046
+ return;
2047
+ const callbacks = ["message", "open", "close"];
2048
+ for (const cbName of callbacks) {
2049
+ const prop = configArg.getProperty(cbName);
2050
+ if (!prop)
2051
+ continue;
2052
+ let funcBody = null;
2053
+ if (Node4.isMethodDeclaration(prop)) {
2054
+ funcBody = prop;
2055
+ } else if (Node4.isPropertyAssignment(prop)) {
2056
+ const init = prop.getInitializer();
2057
+ if (init && (Node4.isArrowFunction(init) || Node4.isFunctionExpression(init))) {
2058
+ funcBody = init;
2059
+ }
2060
+ }
2061
+ if (!funcBody)
2062
+ continue;
2063
+ if (cbName === "message") {
2064
+ const body = funcBody.getBody();
2065
+ if (!body)
2066
+ continue;
2067
+ const subHandlers = this.extractSubHandlersFromBody(body, context, filePath);
2068
+ if (subHandlers.length > 0) {
2069
+ handlers.push(...subHandlers);
2070
+ } else {
2071
+ handlers.push(this.buildWsHandler(`ws_message`, routePath, context, filePath, funcBody, node.getStartLineNumber()));
2072
+ }
2073
+ } else {
2074
+ handlers.push(this.buildWsHandler(`ws_${cbName}`, routePath, context, filePath, funcBody, node.getStartLineNumber()));
2075
+ }
2076
+ }
2077
+ }
2078
+ extractSubHandlersFromBody(body, context, filePath) {
2079
+ const subHandlers = [];
2080
+ body.forEachDescendant((child) => {
2081
+ if (Node4.isIfStatement(child) && !this.isElseIfStatement(child)) {
2082
+ const typeGuardHandlers = this.extractTypeGuardHandlers(child, context, filePath);
2083
+ subHandlers.push(...typeGuardHandlers);
2084
+ }
2085
+ if (Node4.isSwitchStatement(child)) {
2086
+ const switchHandlers = this.extractSwitchCaseHandlers(child, context, filePath);
2087
+ subHandlers.push(...switchHandlers);
2088
+ }
2089
+ });
2090
+ return subHandlers;
2091
+ }
2092
+ buildWsHandler(messageType, _routePath, context, filePath, funcBody, line) {
2093
+ const assignments = [];
2094
+ const preconditions = [];
2095
+ const postconditions = [];
2096
+ this.currentFunctionParams = this.extractParameterNames(funcBody);
2097
+ this.extractAssignments(funcBody, assignments);
2098
+ this.extractVerificationConditions(funcBody, preconditions, postconditions);
2099
+ this.currentFunctionParams = [];
2100
+ return {
2101
+ messageType,
2102
+ node: context,
2103
+ assignments,
2104
+ preconditions,
2105
+ postconditions,
2106
+ location: { file: filePath, line },
2107
+ origin: "event"
2108
+ };
2109
+ }
2110
+ extractRestHandler(node, methodName, context, filePath) {
2111
+ const args = node.getArguments();
2112
+ if (args.length < 2)
2113
+ return null;
2114
+ const routeArg = args[0];
2115
+ if (!routeArg || !Node4.isStringLiteral(routeArg))
2116
+ return null;
2117
+ const routePath = routeArg.getLiteralValue();
2118
+ const httpMethod = methodName.toUpperCase();
2119
+ const messageType = `${httpMethod} ${routePath}`;
2120
+ const handlerArg = args[1];
2121
+ const assignments = [];
2122
+ const preconditions = [];
2123
+ const postconditions = [];
2124
+ let actualHandler = null;
2125
+ if (Node4.isArrowFunction(handlerArg) || Node4.isFunctionExpression(handlerArg)) {
2126
+ actualHandler = handlerArg;
2127
+ } else if (Node4.isIdentifier(handlerArg)) {
2128
+ actualHandler = this.resolveFunctionReference(handlerArg);
2129
+ }
2130
+ let parameters;
2131
+ if (actualHandler) {
2132
+ this.currentFunctionParams = this.extractParameterNames(actualHandler);
2133
+ parameters = this.currentFunctionParams.length > 0 ? [...this.currentFunctionParams] : undefined;
2134
+ this.extractAssignments(actualHandler, assignments);
2135
+ this.extractVerificationConditions(actualHandler, preconditions, postconditions);
2136
+ this.currentFunctionParams = [];
2137
+ }
2138
+ return {
2139
+ messageType,
2140
+ node: context,
2141
+ assignments,
2142
+ preconditions,
2143
+ postconditions,
2144
+ location: {
2145
+ file: filePath,
2146
+ line: node.getStartLineNumber()
2147
+ },
2148
+ origin: "event",
2149
+ parameters,
2150
+ handlerKind: "rest",
2151
+ httpMethod,
2152
+ routePath
2153
+ };
2154
+ }
2155
+ isWebFrameworkFile(sourceFile) {
2156
+ const frameworks = ["elysia", "express", "hono", "fastify", "koa", "@elysiajs/eden"];
2157
+ for (const importDecl of sourceFile.getImportDeclarations()) {
2158
+ const specifier = importDecl.getModuleSpecifierValue();
2159
+ if (frameworks.some((fw) => specifier === fw || specifier.startsWith(`${fw}/`))) {
2160
+ return true;
2161
+ }
2162
+ }
2163
+ return false;
2164
+ }
1876
2165
  extractAssignments(funcNode, assignments) {
1877
2166
  funcNode.forEachDescendant((node) => {
1878
2167
  if (Node4.isBinaryExpression(node)) {
@@ -2707,32 +2996,64 @@ class HandlerExtractor {
2707
2996
  try {
2708
2997
  const ifStmt = ifNode;
2709
2998
  const condition = ifStmt.getExpression();
2710
- if (!Node4.isCallExpression(condition)) {
2711
- return null;
2999
+ if (Node4.isCallExpression(condition)) {
3000
+ const funcExpr = condition.getExpression();
3001
+ const funcName = Node4.isIdentifier(funcExpr) ? funcExpr.getText() : undefined;
3002
+ this.debugLogProcessingFunction(funcName);
3003
+ const messageType = this.resolveMessageType(funcExpr, funcName, typeGuards);
3004
+ if (!messageType) {
3005
+ this.debugLogUnresolvedMessageType(funcName);
3006
+ return null;
3007
+ }
3008
+ const line = ifStmt.getStartLineNumber();
3009
+ const relationships = this.extractRelationshipsFromIfBlock(ifStmt, messageType);
3010
+ return {
3011
+ messageType,
3012
+ node: context,
3013
+ assignments: [],
3014
+ preconditions: [],
3015
+ postconditions: [],
3016
+ location: { file: filePath, line },
3017
+ relationships
3018
+ };
2712
3019
  }
2713
- const funcExpr = condition.getExpression();
2714
- const funcName = Node4.isIdentifier(funcExpr) ? funcExpr.getText() : undefined;
2715
- this.debugLogProcessingFunction(funcName);
2716
- const messageType = this.resolveMessageType(funcExpr, funcName, typeGuards);
2717
- if (!messageType) {
2718
- this.debugLogUnresolvedMessageType(funcName);
2719
- return null;
3020
+ if (Node4.isBinaryExpression(condition)) {
3021
+ const messageType = this.extractMessageTypeFromEqualityCheck(condition);
3022
+ if (messageType) {
3023
+ const line = ifStmt.getStartLineNumber();
3024
+ const relationships = this.extractRelationshipsFromIfBlock(ifStmt, messageType);
3025
+ return {
3026
+ messageType,
3027
+ node: context,
3028
+ assignments: [],
3029
+ preconditions: [],
3030
+ postconditions: [],
3031
+ location: { file: filePath, line },
3032
+ relationships
3033
+ };
3034
+ }
2720
3035
  }
2721
- const line = ifStmt.getStartLineNumber();
2722
- const relationships = this.extractRelationshipsFromIfBlock(ifStmt, messageType);
2723
- return {
2724
- messageType,
2725
- node: context,
2726
- assignments: [],
2727
- preconditions: [],
2728
- postconditions: [],
2729
- location: { file: filePath, line },
2730
- relationships
2731
- };
3036
+ return null;
2732
3037
  } catch (_error) {
2733
3038
  return null;
2734
3039
  }
2735
3040
  }
3041
+ extractMessageTypeFromEqualityCheck(expr) {
3042
+ const operator = expr.getOperatorToken().getText();
3043
+ if (operator !== "===" && operator !== "==")
3044
+ return null;
3045
+ const left = expr.getLeft();
3046
+ const right = expr.getRight();
3047
+ const stringLiteral = Node4.isStringLiteral(right) ? right : Node4.isStringLiteral(left) ? left : null;
3048
+ const propAccess = Node4.isPropertyAccessExpression(left) ? left : Node4.isPropertyAccessExpression(right) ? right : null;
3049
+ if (!stringLiteral || !propAccess)
3050
+ return null;
3051
+ const propName = propAccess.getName();
3052
+ if (propName !== "type" && propName !== "kind" && propName !== "action") {
3053
+ return null;
3054
+ }
3055
+ return stringLiteral.getLiteralValue();
3056
+ }
2736
3057
  debugLogProcessingFunction(funcName) {
2737
3058
  if (process.env["POLLY_DEBUG"] && funcName) {
2738
3059
  console.log(`[DEBUG] Processing if condition with function: ${funcName}`);
@@ -2942,6 +3263,10 @@ class HandlerExtractor {
2942
3263
  return messageType;
2943
3264
  }
2944
3265
  inferContext(filePath) {
3266
+ for (const [pathPrefix, context] of this.contextOverrides.entries()) {
3267
+ if (filePath.startsWith(pathPrefix))
3268
+ return context;
3269
+ }
2945
3270
  const path2 = filePath.toLowerCase();
2946
3271
  return this.inferElectronContext(path2) || this.inferWorkerContext(path2) || this.inferServerAppContext(path2) || this.inferChromeExtensionContext(path2) || "unknown";
2947
3272
  }
@@ -3040,6 +3365,81 @@ class HandlerExtractor {
3040
3365
  });
3041
3366
  return constraints;
3042
3367
  }
3368
+ extractGlobalStateConstraintsFromFile(sourceFile) {
3369
+ const constraints = [];
3370
+ const filePath = sourceFile.getFilePath();
3371
+ sourceFile.forEachDescendant((node) => {
3372
+ const constraint = this.recognizeGlobalStateConstraint(node, filePath);
3373
+ if (constraint) {
3374
+ constraints.push(constraint);
3375
+ }
3376
+ });
3377
+ return constraints;
3378
+ }
3379
+ recognizeGlobalStateConstraint(node, filePath) {
3380
+ if (!Node4.isCallExpression(node)) {
3381
+ return null;
3382
+ }
3383
+ const expression = node.getExpression();
3384
+ if (!Node4.isIdentifier(expression)) {
3385
+ return null;
3386
+ }
3387
+ const functionName = expression.getText();
3388
+ if (functionName !== "stateConstraint") {
3389
+ return null;
3390
+ }
3391
+ const args = node.getArguments();
3392
+ if (args.length < 2) {
3393
+ return null;
3394
+ }
3395
+ const nameArg = args[0];
3396
+ if (!Node4.isStringLiteral(nameArg)) {
3397
+ return null;
3398
+ }
3399
+ const name = nameArg.getLiteralValue();
3400
+ const predicateArg = args[1];
3401
+ if (!Node4.isArrowFunction(predicateArg)) {
3402
+ return null;
3403
+ }
3404
+ const body = predicateArg.getBody();
3405
+ let expressionText;
3406
+ if (Node4.isBlock(body)) {
3407
+ const returnStatement = body.getStatements().find((s) => Node4.isReturnStatement(s));
3408
+ if (!returnStatement || !Node4.isReturnStatement(returnStatement)) {
3409
+ return null;
3410
+ }
3411
+ const returnExpr = returnStatement.getExpression();
3412
+ if (!returnExpr) {
3413
+ return null;
3414
+ }
3415
+ expressionText = returnExpr.getText();
3416
+ } else {
3417
+ expressionText = body.getText();
3418
+ }
3419
+ let message;
3420
+ if (args.length >= 3) {
3421
+ const optionsArg = args[2];
3422
+ if (Node4.isObjectLiteralExpression(optionsArg)) {
3423
+ for (const prop of optionsArg.getProperties()) {
3424
+ if (Node4.isPropertyAssignment(prop) && prop.getName() === "message") {
3425
+ const value = prop.getInitializer();
3426
+ if (value && Node4.isStringLiteral(value)) {
3427
+ message = value.getLiteralValue();
3428
+ }
3429
+ }
3430
+ }
3431
+ }
3432
+ }
3433
+ return {
3434
+ name,
3435
+ expression: expressionText,
3436
+ message,
3437
+ location: {
3438
+ file: filePath,
3439
+ line: node.getStartLineNumber()
3440
+ }
3441
+ };
3442
+ }
3043
3443
  recognizeStateConstraint(node, filePath) {
3044
3444
  if (!Node4.isCallExpression(node)) {
3045
3445
  return [];
@@ -3815,10 +4215,16 @@ class ArchitectureAnalyzer {
3815
4215
  }
3816
4216
  async analyze() {
3817
4217
  const { manifest, projectConfig, entryPoints, systemInfo } = await this.initializeSystemInfo();
3818
- const handlerExtractor = new HandlerExtractor(this.options.tsConfigPath);
4218
+ const contextOverrides = new Map;
4219
+ if (projectConfig?.workspacePackages) {
4220
+ for (const pkg of projectConfig.workspacePackages) {
4221
+ contextOverrides.set(pkg.path, pkg.context);
4222
+ }
4223
+ }
4224
+ const handlerExtractor = new HandlerExtractor(this.options.tsConfigPath, contextOverrides.size > 0 ? contextOverrides : undefined);
3819
4225
  const { handlers } = handlerExtractor.extractHandlers();
3820
4226
  const contexts = this.analyzeContexts(entryPoints, handlers);
3821
- const flowAnalyzer = new FlowAnalyzer(this.options.tsConfigPath, handlers);
4227
+ const flowAnalyzer = new FlowAnalyzer(this.options.tsConfigPath, handlers, contextOverrides.size > 0 ? contextOverrides : undefined);
3822
4228
  const messageFlows = flowAnalyzer.analyzeFlows();
3823
4229
  const integrationAnalyzer = new IntegrationAnalyzer(this.options.tsConfigPath);
3824
4230
  const integrations = integrationAnalyzer.analyzeIntegrations();
@@ -4150,12 +4556,16 @@ class StructurizrDSLGenerator {
4150
4556
  handlersByType.get(handler.messageType)?.push(handler);
4151
4557
  }
4152
4558
  for (const [messageType, handlers] of handlersByType) {
4153
- const componentName = this.toComponentName(messageType);
4154
4559
  const firstHandler = handlers[0];
4155
4560
  if (!firstHandler)
4156
4561
  continue;
4562
+ const isRest = firstHandler.handlerKind === "rest";
4563
+ const componentName = isRest ? messageType : this.toComponentName(messageType);
4157
4564
  const description = this.generateComponentDescription(messageType, firstHandler);
4158
4565
  const tags = this.getComponentTags(messageType, firstHandler);
4566
+ if (isRest) {
4567
+ tags.push("REST Endpoint");
4568
+ }
4159
4569
  const properties = this.getComponentProperties(messageType, firstHandler, contextType);
4160
4570
  componentDefs.push({
4161
4571
  id: this.toId(componentName),
@@ -4987,9 +5397,12 @@ class StructurizrDSLGenerator {
4987
5397
  popup: "Browser Action Popup",
4988
5398
  devtools: "DevTools Panel",
4989
5399
  options: "Options Page",
4990
- offscreen: "Offscreen Document"
5400
+ offscreen: "Offscreen Document",
5401
+ server: "Server Application",
5402
+ client: "Client Application",
5403
+ shared: "Shared Library"
4991
5404
  };
4992
- return technologies[contextType] || "Extension Context";
5405
+ return technologies[contextType] || "Application Context";
4993
5406
  }
4994
5407
  toComponentName(messageType) {
4995
5408
  return `${messageType.split("_").map((part) => this.capitalize(part.toLowerCase())).join(" ")} Handler`;
@@ -5639,4 +6052,4 @@ main().catch((_error) => {
5639
6052
  process.exit(1);
5640
6053
  });
5641
6054
 
5642
- //# debugId=E7AAE0901ACF898964756E2164756E21
6055
+ //# debugId=345DE519CB00B03D64756E2164756E21