@fairfox/polly 0.2.0 → 0.3.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.
@@ -13,13 +13,448 @@ var __export = (target, all) => {
13
13
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
14
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
15
15
 
16
+ // vendor/analysis/src/extract/project-detector.ts
17
+ var exports_project_detector = {};
18
+ __export(exports_project_detector, {
19
+ detectProjectConfig: () => detectProjectConfig,
20
+ ProjectDetector: () => ProjectDetector
21
+ });
22
+ import * as fs3 from "node:fs";
23
+ import * as path3 from "node:path";
24
+
25
+ class ProjectDetector {
26
+ projectRoot;
27
+ constructor(projectRoot) {
28
+ this.projectRoot = projectRoot;
29
+ }
30
+ detect() {
31
+ const manifestPath = path3.join(this.projectRoot, "manifest.json");
32
+ if (fs3.existsSync(manifestPath)) {
33
+ return this.detectChromeExtension(manifestPath);
34
+ }
35
+ const pwaManifestPath = path3.join(this.projectRoot, "public", "manifest.json");
36
+ if (fs3.existsSync(pwaManifestPath)) {
37
+ return this.detectPWA(pwaManifestPath);
38
+ }
39
+ const packageJsonPath = path3.join(this.projectRoot, "package.json");
40
+ if (fs3.existsSync(packageJsonPath)) {
41
+ const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
42
+ if (packageJson.dependencies?.electron || packageJson.devDependencies?.electron) {
43
+ return this.detectElectron(packageJson);
44
+ }
45
+ const deps = {
46
+ ...packageJson.dependencies,
47
+ ...packageJson.devDependencies
48
+ };
49
+ if (deps?.ws || deps?.["socket.io"] || deps?.elysia || deps?.express || deps?.fastify || deps?.hono || deps?.koa) {
50
+ return this.detectWebSocketApp(packageJson);
51
+ }
52
+ const serverResult = this.detectWebSocketApp(packageJson);
53
+ if (Object.keys(serverResult.entryPoints).length > 0) {
54
+ return serverResult;
55
+ }
56
+ } else {
57
+ const serverResult = this.detectWebSocketApp({});
58
+ if (Object.keys(serverResult.entryPoints).length > 0) {
59
+ return serverResult;
60
+ }
61
+ }
62
+ return this.detectGenericProject();
63
+ }
64
+ detectChromeExtension(manifestPath) {
65
+ const manifest = JSON.parse(fs3.readFileSync(manifestPath, "utf-8"));
66
+ const entryPoints = {};
67
+ const background = manifest.background;
68
+ if (background) {
69
+ const file = background.service_worker || background.scripts?.[0] || background.page;
70
+ if (file) {
71
+ entryPoints.background = this.findSourceFile(file);
72
+ }
73
+ }
74
+ const contentScripts = manifest.content_scripts;
75
+ if (contentScripts && contentScripts.length > 0) {
76
+ const firstScript = contentScripts[0].js?.[0];
77
+ if (firstScript) {
78
+ entryPoints.content = this.findSourceFile(firstScript);
79
+ }
80
+ }
81
+ const popup = manifest.action?.default_popup || manifest.browser_action?.default_popup;
82
+ if (popup) {
83
+ const jsFile = this.findAssociatedJS(path3.join(this.projectRoot, popup));
84
+ if (jsFile)
85
+ entryPoints.popup = jsFile;
86
+ }
87
+ const options = manifest.options_ui?.page || manifest.options_page;
88
+ if (options) {
89
+ const jsFile = this.findAssociatedJS(path3.join(this.projectRoot, options));
90
+ if (jsFile)
91
+ entryPoints.options = jsFile;
92
+ }
93
+ return {
94
+ type: "chrome-extension",
95
+ entryPoints,
96
+ metadata: {
97
+ name: manifest.name,
98
+ version: manifest.version,
99
+ description: manifest.description
100
+ }
101
+ };
102
+ }
103
+ detectPWA(manifestPath) {
104
+ const manifest = JSON.parse(fs3.readFileSync(manifestPath, "utf-8"));
105
+ const entryPoints = {};
106
+ const swCandidates = [
107
+ "src/service-worker.ts",
108
+ "src/sw.ts",
109
+ "public/service-worker.js",
110
+ "public/sw.js"
111
+ ];
112
+ for (const candidate of swCandidates) {
113
+ const fullPath = path3.join(this.projectRoot, candidate);
114
+ if (fs3.existsSync(fullPath)) {
115
+ entryPoints.worker = fullPath;
116
+ break;
117
+ }
118
+ }
119
+ const clientCandidates = [
120
+ "src/main.ts",
121
+ "src/index.ts",
122
+ "src/app.ts",
123
+ "src/main.tsx",
124
+ "src/index.tsx"
125
+ ];
126
+ for (const candidate of clientCandidates) {
127
+ const fullPath = path3.join(this.projectRoot, candidate);
128
+ if (fs3.existsSync(fullPath)) {
129
+ entryPoints.client = fullPath;
130
+ break;
131
+ }
132
+ }
133
+ return {
134
+ type: "pwa",
135
+ entryPoints,
136
+ contextMapping: {
137
+ worker: "Service Worker",
138
+ client: "Client App"
139
+ },
140
+ metadata: {
141
+ name: manifest.name || manifest.short_name,
142
+ version: "1.0.0",
143
+ description: manifest.description
144
+ }
145
+ };
146
+ }
147
+ detectElectron(packageJson) {
148
+ const entryPoints = {};
149
+ const mainCandidates = [
150
+ packageJson.main,
151
+ "src/main/index.ts",
152
+ "src/electron/main.ts",
153
+ "electron/main.ts",
154
+ "main.ts"
155
+ ].filter(Boolean);
156
+ for (const candidate of mainCandidates) {
157
+ const fullPath = path3.join(this.projectRoot, candidate);
158
+ if (fs3.existsSync(fullPath) || fs3.existsSync(fullPath.replace(/\.js$/, ".ts"))) {
159
+ entryPoints.main = fs3.existsSync(fullPath) ? fullPath : fullPath.replace(/\.js$/, ".ts");
160
+ break;
161
+ }
162
+ }
163
+ const rendererCandidates = [
164
+ "src/renderer/index.tsx",
165
+ "src/renderer/index.ts",
166
+ "src/index.tsx",
167
+ "src/index.ts"
168
+ ];
169
+ for (const candidate of rendererCandidates) {
170
+ const fullPath = path3.join(this.projectRoot, candidate);
171
+ if (fs3.existsSync(fullPath)) {
172
+ entryPoints.renderer = fullPath;
173
+ break;
174
+ }
175
+ }
176
+ return {
177
+ type: "electron",
178
+ entryPoints,
179
+ contextMapping: {
180
+ main: "Main Process",
181
+ renderer: "Renderer Process"
182
+ },
183
+ metadata: {
184
+ name: packageJson.name,
185
+ version: packageJson.version,
186
+ description: packageJson.description
187
+ }
188
+ };
189
+ }
190
+ detectWebSocketApp(packageJson) {
191
+ const entryPoints = {};
192
+ const contextMapping = {};
193
+ const serverCandidates = [
194
+ "src/server.ts",
195
+ "src/server.js",
196
+ "src/index.ts",
197
+ "src/index.js",
198
+ "src/main.ts",
199
+ "src/main.js",
200
+ "src/app.ts",
201
+ "src/app.js",
202
+ "server.ts",
203
+ "server.js",
204
+ "index.ts",
205
+ "index.js"
206
+ ];
207
+ const scoredServers = this.scoreServerCandidates(serverCandidates);
208
+ if (scoredServers.length > 0) {
209
+ const best = scoredServers[0];
210
+ entryPoints.server = best.path;
211
+ if (best.hasWebSocket) {
212
+ contextMapping.server = "WebSocket Server";
213
+ } else if (best.hasHTTP) {
214
+ contextMapping.server = "HTTP Server";
215
+ } else {
216
+ contextMapping.server = "Server";
217
+ }
218
+ }
219
+ const clientCandidates = [
220
+ "src/client/index.ts",
221
+ "src/client/index.js",
222
+ "src/client.ts",
223
+ "src/client.js",
224
+ "client/index.ts",
225
+ "client/index.js"
226
+ ];
227
+ for (const candidate of clientCandidates) {
228
+ const fullPath = path3.join(this.projectRoot, candidate);
229
+ if (fs3.existsSync(fullPath)) {
230
+ entryPoints.client = fullPath;
231
+ contextMapping.client = "Client";
232
+ break;
233
+ }
234
+ }
235
+ return {
236
+ type: "websocket-app",
237
+ entryPoints,
238
+ contextMapping: Object.keys(contextMapping).length > 0 ? contextMapping : {
239
+ server: "Server",
240
+ client: "Client"
241
+ },
242
+ metadata: {
243
+ name: packageJson.name,
244
+ version: packageJson.version,
245
+ description: packageJson.description
246
+ }
247
+ };
248
+ }
249
+ scoreServerCandidates(candidates) {
250
+ const scored = [];
251
+ for (const candidate of candidates) {
252
+ const fullPath = path3.join(this.projectRoot, candidate);
253
+ if (!fs3.existsSync(fullPath))
254
+ continue;
255
+ try {
256
+ const content = fs3.readFileSync(fullPath, "utf-8");
257
+ let score = 0;
258
+ let hasWebSocket = false;
259
+ let hasHTTP = false;
260
+ let framework = null;
261
+ const patterns = {
262
+ bunWebSocket: /Bun\.serve\s*\([^)]*websocket\s*:/i,
263
+ bunHTTP: /Bun\.serve\s*\(/i,
264
+ wsServer: /new\s+WebSocket\.Server/i,
265
+ wsImport: /from\s+['"]ws['"]/,
266
+ socketIO: /io\s*\(|require\s*\(\s*['"]socket\.io['"]\s*\)/i,
267
+ elysia: /new\s+Elysia\s*\(|\.ws\s*\(/i,
268
+ elysiaImport: /from\s+['"]elysia['"]/,
269
+ express: /express\s*\(\)|app\.listen/i,
270
+ expressWs: /expressWs\s*\(/i,
271
+ httpServer: /createServer|app\.listen|server\.listen/i,
272
+ webSocket: /WebSocket|websocket|\.ws\s*\(|wss\s*:/i
273
+ };
274
+ if (patterns.bunWebSocket.test(content)) {
275
+ score += 15;
276
+ hasWebSocket = true;
277
+ hasHTTP = true;
278
+ framework = "Bun";
279
+ } else if (patterns.bunHTTP.test(content)) {
280
+ score += 10;
281
+ hasHTTP = true;
282
+ framework = "Bun";
283
+ }
284
+ if (patterns.wsServer.test(content) || patterns.wsImport.test(content)) {
285
+ score += 12;
286
+ hasWebSocket = true;
287
+ framework = framework || "ws";
288
+ }
289
+ if (patterns.socketIO.test(content)) {
290
+ score += 12;
291
+ hasWebSocket = true;
292
+ framework = framework || "socket.io";
293
+ }
294
+ if (patterns.elysia.test(content) || patterns.elysiaImport.test(content)) {
295
+ score += 10;
296
+ hasHTTP = true;
297
+ framework = framework || "Elysia";
298
+ }
299
+ if (patterns.elysiaImport.test(content) && patterns.webSocket.test(content)) {
300
+ score += 8;
301
+ hasWebSocket = true;
302
+ }
303
+ if (patterns.express.test(content)) {
304
+ score += 8;
305
+ hasHTTP = true;
306
+ framework = framework || "Express";
307
+ }
308
+ if (patterns.expressWs.test(content)) {
309
+ score += 5;
310
+ hasWebSocket = true;
311
+ }
312
+ if (patterns.httpServer.test(content) && !hasHTTP) {
313
+ score += 5;
314
+ hasHTTP = true;
315
+ }
316
+ if (patterns.webSocket.test(content) && !hasWebSocket) {
317
+ score += 3;
318
+ hasWebSocket = true;
319
+ }
320
+ if (/\.listen\s*\(/.test(content)) {
321
+ score += 5;
322
+ }
323
+ if (/export\s+default/.test(content)) {
324
+ score += 3;
325
+ }
326
+ if (candidate.includes("server")) {
327
+ score += 3;
328
+ }
329
+ if (candidate === "src/index.ts" || candidate === "src/index.js") {
330
+ score += 2;
331
+ }
332
+ if (score > 0) {
333
+ scored.push({ path: fullPath, score, hasWebSocket, hasHTTP, framework });
334
+ }
335
+ } catch (error) {
336
+ continue;
337
+ }
338
+ }
339
+ return scored.sort((a, b) => b.score - a.score);
340
+ }
341
+ detectGenericProject() {
342
+ const entryPoints = {};
343
+ const tsConfigPath = path3.join(this.projectRoot, "tsconfig.json");
344
+ if (fs3.existsSync(tsConfigPath)) {
345
+ try {
346
+ const tsConfig = JSON.parse(fs3.readFileSync(tsConfigPath, "utf-8"));
347
+ if (tsConfig.files && Array.isArray(tsConfig.files)) {
348
+ tsConfig.files.forEach((file, idx) => {
349
+ const fullPath = path3.join(this.projectRoot, file);
350
+ if (fs3.existsSync(fullPath)) {
351
+ entryPoints[`module${idx + 1}`] = fullPath;
352
+ }
353
+ });
354
+ }
355
+ } catch (error) {
356
+ console.warn(`Failed to parse tsconfig.json: ${error}`);
357
+ }
358
+ }
359
+ if (Object.keys(entryPoints).length === 0) {
360
+ const commonEntries = [
361
+ "src/index.ts",
362
+ "src/main.ts",
363
+ "src/app.ts",
364
+ "index.ts",
365
+ "main.ts"
366
+ ];
367
+ for (const candidate of commonEntries) {
368
+ const fullPath = path3.join(this.projectRoot, candidate);
369
+ if (fs3.existsSync(fullPath)) {
370
+ entryPoints.main = fullPath;
371
+ break;
372
+ }
373
+ }
374
+ }
375
+ const packageJsonPath = path3.join(this.projectRoot, "package.json");
376
+ let metadata = { name: "Unknown Project" };
377
+ if (fs3.existsSync(packageJsonPath)) {
378
+ try {
379
+ const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
380
+ metadata = {
381
+ name: packageJson.name,
382
+ version: packageJson.version,
383
+ description: packageJson.description
384
+ };
385
+ } catch (error) {
386
+ console.warn(`Failed to parse package.json: ${error}`);
387
+ }
388
+ }
389
+ return {
390
+ type: "generic",
391
+ entryPoints,
392
+ metadata
393
+ };
394
+ }
395
+ findSourceFile(manifestPath) {
396
+ const candidates = [
397
+ path3.join(this.projectRoot, manifestPath),
398
+ path3.join(this.projectRoot, manifestPath.replace(/\.js$/, ".ts")),
399
+ path3.join(this.projectRoot, "src", manifestPath),
400
+ path3.join(this.projectRoot, "src", manifestPath.replace(/\.js$/, ".ts")),
401
+ path3.join(this.projectRoot, "src", manifestPath.replace(/\.js$/, ".tsx"))
402
+ ];
403
+ for (const candidate of candidates) {
404
+ if (fs3.existsSync(candidate)) {
405
+ return candidate;
406
+ }
407
+ }
408
+ return path3.join(this.projectRoot, manifestPath);
409
+ }
410
+ findAssociatedJS(htmlPath) {
411
+ if (!fs3.existsSync(htmlPath)) {
412
+ return null;
413
+ }
414
+ const html = fs3.readFileSync(htmlPath, "utf-8");
415
+ const scriptMatch = html.match(/<script[^>]+src=["']([^"']+)["']/i);
416
+ if (scriptMatch && scriptMatch[1]) {
417
+ const scriptPath = scriptMatch[1];
418
+ const fullPath = path3.resolve(path3.dirname(htmlPath), scriptPath);
419
+ if (fs3.existsSync(fullPath))
420
+ return fullPath;
421
+ if (fs3.existsSync(fullPath.replace(/\.js$/, ".ts"))) {
422
+ return fullPath.replace(/\.js$/, ".ts");
423
+ }
424
+ if (fs3.existsSync(fullPath.replace(/\.js$/, ".tsx"))) {
425
+ return fullPath.replace(/\.js$/, ".tsx");
426
+ }
427
+ }
428
+ const baseName = path3.basename(htmlPath, ".html");
429
+ const dir = path3.dirname(htmlPath);
430
+ const candidates = [
431
+ path3.join(dir, `${baseName}.ts`),
432
+ path3.join(dir, `${baseName}.tsx`),
433
+ path3.join(dir, `${baseName}.js`),
434
+ path3.join(dir, "index.ts"),
435
+ path3.join(dir, "index.tsx")
436
+ ];
437
+ for (const candidate of candidates) {
438
+ if (fs3.existsSync(candidate)) {
439
+ return candidate;
440
+ }
441
+ }
442
+ return null;
443
+ }
444
+ }
445
+ function detectProjectConfig(projectRoot) {
446
+ const detector = new ProjectDetector(projectRoot);
447
+ return detector.detect();
448
+ }
449
+ var init_project_detector = () => {};
450
+
16
451
  // vendor/visualize/src/cli.ts
17
- import * as fs5 from "node:fs";
18
- import * as path5 from "node:path";
452
+ import * as fs6 from "node:fs";
453
+ import * as path6 from "node:path";
19
454
 
20
455
  // vendor/analysis/src/extract/architecture.ts
21
- import * as fs3 from "node:fs";
22
- import * as path3 from "node:path";
456
+ import * as fs4 from "node:fs";
457
+ import * as path4 from "node:path";
23
458
 
24
459
  // vendor/analysis/src/extract/manifest.ts
25
460
  import * as fs from "node:fs";
@@ -29,10 +464,14 @@ class ManifestParser {
29
464
  manifestPath;
30
465
  manifestData;
31
466
  baseDir;
32
- constructor(projectRoot) {
467
+ constructor(projectRoot, optional = false) {
33
468
  this.baseDir = projectRoot;
34
469
  this.manifestPath = path.join(projectRoot, "manifest.json");
35
470
  if (!fs.existsSync(this.manifestPath)) {
471
+ if (optional) {
472
+ this.manifestData = null;
473
+ return;
474
+ }
36
475
  throw new Error(`manifest.json not found at ${this.manifestPath}`);
37
476
  }
38
477
  try {
@@ -42,7 +481,13 @@ class ManifestParser {
42
481
  throw new Error(`Failed to parse manifest.json: ${error}`);
43
482
  }
44
483
  }
484
+ hasManifest() {
485
+ return this.manifestData !== null;
486
+ }
45
487
  parse() {
488
+ if (!this.manifestData) {
489
+ throw new Error("Cannot parse manifest: manifest.json not loaded. Use hasManifest() to check availability.");
490
+ }
46
491
  const manifest = this.manifestData;
47
492
  return {
48
493
  name: manifest.name || "Unknown Extension",
@@ -59,6 +504,9 @@ class ManifestParser {
59
504
  };
60
505
  }
61
506
  getContextEntryPoints() {
507
+ if (!this.manifestData) {
508
+ return {};
509
+ }
62
510
  const entryPoints = {};
63
511
  const background = this.parseBackground();
64
512
  if (background) {
@@ -101,6 +549,8 @@ class ManifestParser {
101
549
  return entryPoints;
102
550
  }
103
551
  parseBackground() {
552
+ if (!this.manifestData)
553
+ return;
104
554
  const bg = this.manifestData.background;
105
555
  if (!bg)
106
556
  return;
@@ -125,6 +575,8 @@ class ManifestParser {
125
575
  return;
126
576
  }
127
577
  parseContentScripts() {
578
+ if (!this.manifestData)
579
+ return;
128
580
  const cs = this.manifestData.content_scripts;
129
581
  if (!cs || !Array.isArray(cs))
130
582
  return;
@@ -135,6 +587,8 @@ class ManifestParser {
135
587
  }));
136
588
  }
137
589
  parsePopup() {
590
+ if (!this.manifestData)
591
+ return;
138
592
  const action = this.manifestData.action || this.manifestData.browser_action;
139
593
  if (!action)
140
594
  return;
@@ -147,6 +601,8 @@ class ManifestParser {
147
601
  return;
148
602
  }
149
603
  parseOptions() {
604
+ if (!this.manifestData)
605
+ return;
150
606
  const options = this.manifestData.options_ui || this.manifestData.options_page;
151
607
  if (!options)
152
608
  return;
@@ -162,6 +618,8 @@ class ManifestParser {
162
618
  };
163
619
  }
164
620
  parseDevtools() {
621
+ if (!this.manifestData)
622
+ return;
165
623
  const devtools = this.manifestData.devtools_page;
166
624
  if (!devtools)
167
625
  return;
@@ -466,7 +924,34 @@ class FlowAnalyzer {
466
924
  const expression = node.getExpression();
467
925
  if (Node2.isPropertyAccessExpression(expression)) {
468
926
  const methodName = expression.getName();
469
- if (methodName === "send" || methodName === "emit") {
927
+ if (methodName === "send" || methodName === "emit" || methodName === "postMessage" || methodName === "broadcast") {
928
+ const args = node.getArguments();
929
+ if (args.length > 0) {
930
+ const firstArg = args[0];
931
+ let msgType;
932
+ if (Node2.isStringLiteral(firstArg)) {
933
+ msgType = firstArg.getLiteralValue();
934
+ } else if (Node2.isObjectLiteralExpression(firstArg)) {
935
+ const typeProperty = firstArg.getProperty("type");
936
+ if (typeProperty && Node2.isPropertyAssignment(typeProperty)) {
937
+ const initializer = typeProperty.getInitializer();
938
+ if (initializer && Node2.isStringLiteral(initializer)) {
939
+ msgType = initializer.getLiteralValue();
940
+ }
941
+ }
942
+ }
943
+ if (msgType === messageType) {
944
+ senders.push({
945
+ context,
946
+ file: filePath,
947
+ line: node.getStartLineNumber()
948
+ });
949
+ }
950
+ }
951
+ }
952
+ }
953
+ if (Node2.isIdentifier(expression)) {
954
+ if (expression.getText() === "postMessage") {
470
955
  const args = node.getArguments();
471
956
  if (args.length > 0) {
472
957
  const firstArg = args[0];
@@ -618,6 +1103,15 @@ class FlowAnalyzer {
618
1103
  if (path2.includes("/offscreen/") || path2.includes("\\offscreen\\")) {
619
1104
  return "offscreen";
620
1105
  }
1106
+ if (path2.includes("/server/") || path2.includes("\\server\\") || path2.includes("/server.")) {
1107
+ return "server";
1108
+ }
1109
+ if (path2.includes("/client/") || path2.includes("\\client\\") || path2.includes("/client.")) {
1110
+ return "client";
1111
+ }
1112
+ if (path2.includes("/worker/") || path2.includes("\\worker\\") || path2.includes("service-worker")) {
1113
+ return "worker";
1114
+ }
621
1115
  return "unknown";
622
1116
  }
623
1117
  }
@@ -854,7 +1348,7 @@ class HandlerExtractor {
854
1348
  const expression = node.getExpression();
855
1349
  if (Node4.isPropertyAccessExpression(expression)) {
856
1350
  const methodName = expression.getName();
857
- if (methodName === "on") {
1351
+ if (methodName === "on" || methodName === "addEventListener") {
858
1352
  const handler = this.extractHandler(node, context, filePath);
859
1353
  if (handler) {
860
1354
  handlers.push(handler);
@@ -862,6 +1356,14 @@ class HandlerExtractor {
862
1356
  }
863
1357
  }
864
1358
  }
1359
+ if (Node4.isSwitchStatement(node)) {
1360
+ const switchHandlers = this.extractSwitchCaseHandlers(node, context, filePath);
1361
+ handlers.push(...switchHandlers);
1362
+ }
1363
+ if (Node4.isVariableDeclaration(node)) {
1364
+ const mapHandlers = this.extractHandlerMapPattern(node, context, filePath);
1365
+ handlers.push(...mapHandlers);
1366
+ }
865
1367
  });
866
1368
  return handlers;
867
1369
  }
@@ -1003,6 +1505,75 @@ class HandlerExtractor {
1003
1505
  }
1004
1506
  return;
1005
1507
  }
1508
+ extractSwitchCaseHandlers(switchNode, context, filePath) {
1509
+ const handlers = [];
1510
+ try {
1511
+ const expression = switchNode.getExpression();
1512
+ const expressionText = expression.getText();
1513
+ if (!/\.(type|kind|event|action)/.test(expressionText)) {
1514
+ return handlers;
1515
+ }
1516
+ const caseClauses = switchNode.getClauses();
1517
+ for (const clause of caseClauses) {
1518
+ if (Node4.isCaseClause(clause)) {
1519
+ const caseExpr = clause.getExpression();
1520
+ let messageType = null;
1521
+ if (Node4.isStringLiteral(caseExpr)) {
1522
+ messageType = caseExpr.getLiteralValue();
1523
+ }
1524
+ if (messageType) {
1525
+ const line = clause.getStartLineNumber();
1526
+ handlers.push({
1527
+ messageType,
1528
+ node: context,
1529
+ assignments: [],
1530
+ preconditions: [],
1531
+ postconditions: [],
1532
+ location: { file: filePath, line }
1533
+ });
1534
+ }
1535
+ }
1536
+ }
1537
+ } catch (error) {}
1538
+ return handlers;
1539
+ }
1540
+ extractHandlerMapPattern(varDecl, context, filePath) {
1541
+ const handlers = [];
1542
+ try {
1543
+ const initializer = varDecl.getInitializer();
1544
+ if (!initializer || !Node4.isObjectLiteralExpression(initializer)) {
1545
+ return handlers;
1546
+ }
1547
+ const varName = varDecl.getName().toLowerCase();
1548
+ if (!/(handler|listener|callback|event)s?/.test(varName)) {
1549
+ return handlers;
1550
+ }
1551
+ const properties = initializer.getProperties();
1552
+ for (const prop of properties) {
1553
+ if (Node4.isPropertyAssignment(prop)) {
1554
+ const nameNode = prop.getNameNode();
1555
+ let messageType = null;
1556
+ if (Node4.isStringLiteral(nameNode)) {
1557
+ messageType = nameNode.getLiteralValue();
1558
+ } else if (Node4.isIdentifier(nameNode)) {
1559
+ messageType = nameNode.getText();
1560
+ }
1561
+ if (messageType) {
1562
+ const line = prop.getStartLineNumber();
1563
+ handlers.push({
1564
+ messageType,
1565
+ node: context,
1566
+ assignments: [],
1567
+ preconditions: [],
1568
+ postconditions: [],
1569
+ location: { file: filePath, line }
1570
+ });
1571
+ }
1572
+ }
1573
+ }
1574
+ } catch (error) {}
1575
+ return handlers;
1576
+ }
1006
1577
  inferContext(filePath) {
1007
1578
  const path2 = filePath.toLowerCase();
1008
1579
  if (path2.includes("/background/") || path2.includes("\\background\\")) {
@@ -1023,6 +1594,15 @@ class HandlerExtractor {
1023
1594
  if (path2.includes("/offscreen/") || path2.includes("\\offscreen\\")) {
1024
1595
  return "offscreen";
1025
1596
  }
1597
+ if (path2.includes("/server/") || path2.includes("\\server\\") || path2.includes("/server.")) {
1598
+ return "server";
1599
+ }
1600
+ if (path2.includes("/client/") || path2.includes("\\client\\") || path2.includes("/client.")) {
1601
+ return "client";
1602
+ }
1603
+ if (path2.includes("/worker/") || path2.includes("\\worker\\") || path2.includes("service-worker")) {
1604
+ return "worker";
1605
+ }
1026
1606
  return "unknown";
1027
1607
  }
1028
1608
  }
@@ -1168,9 +1748,29 @@ class ArchitectureAnalyzer {
1168
1748
  this.options = options;
1169
1749
  }
1170
1750
  async analyze() {
1171
- const manifestParser = new ManifestParser(this.options.projectRoot);
1172
- const manifest = manifestParser.parse();
1173
- const entryPoints = manifestParser.getContextEntryPoints();
1751
+ let manifest;
1752
+ let projectConfig;
1753
+ let entryPoints = {};
1754
+ let systemInfo;
1755
+ const manifestParser = new ManifestParser(this.options.projectRoot, true);
1756
+ if (manifestParser.hasManifest() && !this.options.useProjectDetector) {
1757
+ manifest = manifestParser.parse();
1758
+ entryPoints = manifestParser.getContextEntryPoints();
1759
+ systemInfo = {
1760
+ name: manifest.name,
1761
+ version: manifest.version,
1762
+ description: manifest.description
1763
+ };
1764
+ } else {
1765
+ const { detectProjectConfig: detectProjectConfig2 } = await Promise.resolve().then(() => (init_project_detector(), exports_project_detector));
1766
+ projectConfig = detectProjectConfig2(this.options.projectRoot);
1767
+ entryPoints = projectConfig.entryPoints;
1768
+ systemInfo = {
1769
+ name: projectConfig.metadata?.name || "Unknown Project",
1770
+ version: projectConfig.metadata?.version || "0.0.0",
1771
+ description: projectConfig.metadata?.description
1772
+ };
1773
+ }
1174
1774
  const handlerExtractor = new HandlerExtractor(this.options.tsConfigPath);
1175
1775
  const { handlers } = handlerExtractor.extractHandlers();
1176
1776
  const contextAnalyzer = new ContextAnalyzer(this.options.tsConfigPath);
@@ -1191,12 +1791,9 @@ class ArchitectureAnalyzer {
1191
1791
  const adrs = extractADRs(this.options.projectRoot);
1192
1792
  const repository = this.extractRepositoryInfo();
1193
1793
  return {
1194
- system: {
1195
- name: manifest.name,
1196
- version: manifest.version,
1197
- description: manifest.description
1198
- },
1794
+ system: systemInfo,
1199
1795
  manifest,
1796
+ projectConfig,
1200
1797
  contexts,
1201
1798
  messageFlows,
1202
1799
  integrations,
@@ -1219,12 +1816,12 @@ class ArchitectureAnalyzer {
1219
1816
  }
1220
1817
  }
1221
1818
  extractRepositoryInfo() {
1222
- const packageJsonPath = path3.join(this.options.projectRoot, "package.json");
1223
- if (!fs3.existsSync(packageJsonPath)) {
1819
+ const packageJsonPath = path4.join(this.options.projectRoot, "package.json");
1820
+ if (!fs4.existsSync(packageJsonPath)) {
1224
1821
  return;
1225
1822
  }
1226
1823
  try {
1227
- const content = fs3.readFileSync(packageJsonPath, "utf-8");
1824
+ const content = fs4.readFileSync(packageJsonPath, "utf-8");
1228
1825
  const packageJson = JSON.parse(content);
1229
1826
  if (packageJson.repository) {
1230
1827
  if (typeof packageJson.repository === "string") {
@@ -1724,8 +2321,8 @@ function generateStructurizrDSL(analysis, options) {
1724
2321
  }
1725
2322
 
1726
2323
  // vendor/visualize/src/runner/export.ts
1727
- import * as fs4 from "node:fs";
1728
- import * as path4 from "node:path";
2324
+ import * as fs5 from "node:fs";
2325
+ import * as path5 from "node:path";
1729
2326
  import { spawn } from "node:child_process";
1730
2327
 
1731
2328
  class DiagramExporter {
@@ -1733,15 +2330,15 @@ class DiagramExporter {
1733
2330
  static DEFAULT_TIMEOUT = 120000;
1734
2331
  async export(options) {
1735
2332
  const { dslPath, outputDir, timeout = DiagramExporter.DEFAULT_TIMEOUT } = options;
1736
- if (!fs4.existsSync(dslPath)) {
2333
+ if (!fs5.existsSync(dslPath)) {
1737
2334
  return {
1738
2335
  success: false,
1739
2336
  siteDir: "",
1740
2337
  error: `DSL file not found: ${dslPath}`
1741
2338
  };
1742
2339
  }
1743
- if (!fs4.existsSync(outputDir)) {
1744
- fs4.mkdirSync(outputDir, { recursive: true });
2340
+ if (!fs5.existsSync(outputDir)) {
2341
+ fs5.mkdirSync(outputDir, { recursive: true });
1745
2342
  }
1746
2343
  const dockerAvailable = await this.isDockerAvailable();
1747
2344
  if (!dockerAvailable) {
@@ -1774,8 +2371,8 @@ class DiagramExporter {
1774
2371
  }
1775
2372
  }
1776
2373
  async exportStaticSite(dslPath, outputDir, timeout) {
1777
- const dslDir = path4.dirname(dslPath);
1778
- const dslFileName = path4.basename(dslPath);
2374
+ const dslDir = path5.dirname(dslPath);
2375
+ const dslFileName = path5.basename(dslPath);
1779
2376
  const workspaceMount = `${dslDir}:/usr/local/structurizr`;
1780
2377
  const outputMount = `${outputDir}:/output`;
1781
2378
  const args = [
@@ -1813,7 +2410,7 @@ class DiagramExporter {
1813
2410
  }
1814
2411
  }
1815
2412
  async pullImage(onProgress) {
1816
- return new Promise((resolve2, reject) => {
2413
+ return new Promise((resolve3, reject) => {
1817
2414
  const proc = spawn("docker", ["pull", DiagramExporter.DOCKER_IMAGE]);
1818
2415
  proc.stdout.on("data", (data) => {
1819
2416
  onProgress?.(data.toString().trim());
@@ -1823,7 +2420,7 @@ class DiagramExporter {
1823
2420
  });
1824
2421
  proc.on("close", (code) => {
1825
2422
  if (code === 0) {
1826
- resolve2();
2423
+ resolve3();
1827
2424
  } else {
1828
2425
  reject(new Error(`Failed to pull image, exit code: ${code}`));
1829
2426
  }
@@ -1837,7 +2434,7 @@ class DiagramExporter {
1837
2434
  return this.runCommand("docker", args, timeout);
1838
2435
  }
1839
2436
  async runCommand(command, args, timeout) {
1840
- return new Promise((resolve2, reject) => {
2437
+ return new Promise((resolve3, reject) => {
1841
2438
  const proc = spawn(command, args);
1842
2439
  let stdout = "";
1843
2440
  let stderr = "";
@@ -1854,7 +2451,7 @@ class DiagramExporter {
1854
2451
  proc.on("close", (code) => {
1855
2452
  clearTimeout(timer);
1856
2453
  if (code === 0) {
1857
- resolve2(stdout);
2454
+ resolve3(stdout);
1858
2455
  } else {
1859
2456
  reject(new Error(`Command failed with exit code ${code}: ${stderr}`));
1860
2457
  }
@@ -1923,11 +2520,17 @@ async function generateCommand() {
1923
2520
  console.log(color(` Using: ${tsConfigPath}`, COLORS.gray));
1924
2521
  const projectRoot = findProjectRoot();
1925
2522
  if (!projectRoot) {
1926
- console.error(color("❌ Could not find manifest.json", COLORS.red));
1927
- console.error(" Run this command from your extension project root");
2523
+ console.error(color("❌ Could not find project root", COLORS.red));
2524
+ console.error(" Run this command from a directory with manifest.json, package.json, or tsconfig.json");
1928
2525
  process.exit(1);
1929
2526
  }
1930
2527
  console.log(color(` Project: ${projectRoot}`, COLORS.gray));
2528
+ const hasManifest = fs6.existsSync(path6.join(projectRoot, "manifest.json"));
2529
+ if (hasManifest) {
2530
+ console.log(color(` Type: Chrome Extension`, COLORS.gray));
2531
+ } else {
2532
+ console.log(color(` Type: Detecting from project structure...`, COLORS.gray));
2533
+ }
1931
2534
  const analysis = await analyzeArchitecture({
1932
2535
  tsConfigPath,
1933
2536
  projectRoot
@@ -1963,12 +2566,12 @@ async function generateCommand() {
1963
2566
  includeComponentDiagrams: true,
1964
2567
  componentDiagramContexts: ["background"]
1965
2568
  });
1966
- const outputDir = path5.join(process.cwd(), "docs");
1967
- if (!fs5.existsSync(outputDir)) {
1968
- fs5.mkdirSync(outputDir, { recursive: true });
2569
+ const outputDir = path6.join(process.cwd(), "docs");
2570
+ if (!fs6.existsSync(outputDir)) {
2571
+ fs6.mkdirSync(outputDir, { recursive: true });
1969
2572
  }
1970
- const dslPath = path5.join(outputDir, "architecture.dsl");
1971
- fs5.writeFileSync(dslPath, dsl, "utf-8");
2573
+ const dslPath = path6.join(outputDir, "architecture.dsl");
2574
+ fs6.writeFileSync(dslPath, dsl, "utf-8");
1972
2575
  console.log(color(`✅ Architecture documentation generated!
1973
2576
  `, COLORS.green));
1974
2577
  console.log(` File: ${color(dslPath, COLORS.blue)}`);
@@ -2010,14 +2613,14 @@ async function exportCommand(args) {
2010
2613
  \uD83D\uDCE4 Generating static site...
2011
2614
  `, COLORS.blue));
2012
2615
  try {
2013
- const dslPath = path5.join(process.cwd(), "docs", "architecture.dsl");
2014
- if (!fs5.existsSync(dslPath)) {
2616
+ const dslPath = path6.join(process.cwd(), "docs", "architecture.dsl");
2617
+ if (!fs6.existsSync(dslPath)) {
2015
2618
  console.error(color("❌ DSL file not found", COLORS.red));
2016
2619
  console.error(" Expected: docs/architecture.dsl");
2017
2620
  console.error(" Run 'bun visualize' first to generate the DSL");
2018
2621
  process.exit(1);
2019
2622
  }
2020
- const outputDir = path5.join(process.cwd(), "docs", "site");
2623
+ const outputDir = path6.join(process.cwd(), "docs", "site");
2021
2624
  console.log(color(` DSL: ${dslPath}`, COLORS.gray));
2022
2625
  console.log(color(` Output: ${outputDir}`, COLORS.gray));
2023
2626
  console.log();
@@ -2061,9 +2664,9 @@ async function serveCommand(args) {
2061
2664
  \uD83C\uDF10 Starting static site server...
2062
2665
  `, COLORS.blue));
2063
2666
  try {
2064
- const siteDir = path5.join(process.cwd(), "docs", "site");
2065
- const indexPath = path5.join(siteDir, "index.html");
2066
- if (!fs5.existsSync(indexPath)) {
2667
+ const siteDir = path6.join(process.cwd(), "docs", "site");
2668
+ const indexPath = path6.join(siteDir, "index.html");
2669
+ if (!fs6.existsSync(indexPath)) {
2067
2670
  console.error(color("❌ Static site not found", COLORS.red));
2068
2671
  console.error(" Expected: docs/site/index.html");
2069
2672
  console.error(" Run 'bun visualize --export' first to generate the site");
@@ -2082,8 +2685,8 @@ async function serveCommand(args) {
2082
2685
  port,
2083
2686
  fetch(req) {
2084
2687
  const url = new URL(req.url);
2085
- let filePath = path5.join(siteDir, url.pathname === "/" ? "index.html" : url.pathname);
2086
- if (fs5.existsSync(filePath) && fs5.statSync(filePath).isFile()) {
2688
+ let filePath = path6.join(siteDir, url.pathname === "/" ? "index.html" : url.pathname);
2689
+ if (fs6.existsSync(filePath) && fs6.statSync(filePath).isFile()) {
2087
2690
  const file = BunGlobal.file(filePath);
2088
2691
  return new Response(file);
2089
2692
  }
@@ -2114,7 +2717,15 @@ async function serveCommand(args) {
2114
2717
  }
2115
2718
  function showHelp() {
2116
2719
  console.log(`
2117
- ${color("bun visualize", COLORS.blue)} - Architecture visualization for web extensions
2720
+ ${color("bun visualize", COLORS.blue)} - Architecture visualization tool
2721
+
2722
+ ${color("Supports:", COLORS.blue)}
2723
+
2724
+ • Chrome Extensions (manifest.json)
2725
+ • PWAs (public/manifest.json)
2726
+ • WebSocket/Server Apps (ws, socket.io, elysia)
2727
+ • Electron Apps
2728
+ • Generic TypeScript Projects
2118
2729
 
2119
2730
  ${color("Commands:", COLORS.blue)}
2120
2731
 
@@ -2134,14 +2745,14 @@ ${color("Commands:", COLORS.blue)}
2134
2745
 
2135
2746
  ${color("Getting Started:", COLORS.blue)}
2136
2747
 
2137
- 1. Run ${color("bun visualize", COLORS.green)} from your extension project root
2748
+ 1. Run ${color("bun visualize", COLORS.green)} from your project root
2138
2749
  2. Find generated ${color("docs/architecture.dsl", COLORS.blue)}
2139
2750
  3. View with Structurizr Lite (see instructions after generation)
2140
2751
 
2141
2752
  ${color("What gets generated:", COLORS.blue)}
2142
2753
 
2143
- • System Context diagram - Extension + external systems
2144
- • Container diagram - Extension contexts (background, content, popup, etc.)
2754
+ • System Context diagram - Your app + external systems
2755
+ • Container diagram - App contexts (background, content, server, client, etc.)
2145
2756
  • Component diagrams - Internal components within contexts
2146
2757
  • Dynamic diagrams - Message flows between contexts
2147
2758
 
@@ -2154,21 +2765,20 @@ ${color("Learn More:", COLORS.blue)}
2154
2765
  }
2155
2766
  function findTsConfig() {
2156
2767
  const locations = [
2157
- path5.join(process.cwd(), "tsconfig.json"),
2158
- path5.join(process.cwd(), "..", "tsconfig.json")
2768
+ path6.join(process.cwd(), "tsconfig.json"),
2769
+ path6.join(process.cwd(), "..", "tsconfig.json")
2159
2770
  ];
2160
2771
  for (const loc of locations) {
2161
- if (fs5.existsSync(loc)) {
2772
+ if (fs6.existsSync(loc)) {
2162
2773
  return loc;
2163
2774
  }
2164
2775
  }
2165
2776
  return null;
2166
2777
  }
2167
2778
  function findProjectRoot() {
2168
- const locations = [process.cwd(), path5.join(process.cwd(), "..")];
2779
+ const locations = [process.cwd(), path6.join(process.cwd(), "..")];
2169
2780
  for (const loc of locations) {
2170
- const manifestPath = path5.join(loc, "manifest.json");
2171
- if (fs5.existsSync(manifestPath)) {
2781
+ if (fs6.existsSync(path6.join(loc, "manifest.json")) || fs6.existsSync(path6.join(loc, "package.json")) || fs6.existsSync(path6.join(loc, "tsconfig.json"))) {
2172
2782
  return loc;
2173
2783
  }
2174
2784
  }
@@ -2186,4 +2796,4 @@ Stack trace:`, COLORS.gray));
2186
2796
  process.exit(1);
2187
2797
  });
2188
2798
 
2189
- //# debugId=3B56BF342A9A6DCB64756E2164756E21
2799
+ //# debugId=37FBF573C9C57A4C64756E2164756E21