@fairfox/polly 0.2.1 → 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.
|
@@ -42,9 +42,22 @@ class ProjectDetector {
|
|
|
42
42
|
if (packageJson.dependencies?.electron || packageJson.devDependencies?.electron) {
|
|
43
43
|
return this.detectElectron(packageJson);
|
|
44
44
|
}
|
|
45
|
-
|
|
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) {
|
|
46
50
|
return this.detectWebSocketApp(packageJson);
|
|
47
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
|
+
}
|
|
48
61
|
}
|
|
49
62
|
return this.detectGenericProject();
|
|
50
63
|
}
|
|
@@ -176,37 +189,53 @@ class ProjectDetector {
|
|
|
176
189
|
}
|
|
177
190
|
detectWebSocketApp(packageJson) {
|
|
178
191
|
const entryPoints = {};
|
|
192
|
+
const contextMapping = {};
|
|
179
193
|
const serverCandidates = [
|
|
180
194
|
"src/server.ts",
|
|
195
|
+
"src/server.js",
|
|
181
196
|
"src/index.ts",
|
|
197
|
+
"src/index.js",
|
|
182
198
|
"src/main.ts",
|
|
199
|
+
"src/main.js",
|
|
183
200
|
"src/app.ts",
|
|
201
|
+
"src/app.js",
|
|
184
202
|
"server.ts",
|
|
185
|
-
"
|
|
203
|
+
"server.js",
|
|
204
|
+
"index.ts",
|
|
205
|
+
"index.js"
|
|
186
206
|
];
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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";
|
|
192
217
|
}
|
|
193
218
|
}
|
|
194
219
|
const clientCandidates = [
|
|
195
220
|
"src/client/index.ts",
|
|
221
|
+
"src/client/index.js",
|
|
196
222
|
"src/client.ts",
|
|
197
|
-
"client
|
|
223
|
+
"src/client.js",
|
|
224
|
+
"client/index.ts",
|
|
225
|
+
"client/index.js"
|
|
198
226
|
];
|
|
199
227
|
for (const candidate of clientCandidates) {
|
|
200
228
|
const fullPath = path3.join(this.projectRoot, candidate);
|
|
201
229
|
if (fs3.existsSync(fullPath)) {
|
|
202
230
|
entryPoints.client = fullPath;
|
|
231
|
+
contextMapping.client = "Client";
|
|
203
232
|
break;
|
|
204
233
|
}
|
|
205
234
|
}
|
|
206
235
|
return {
|
|
207
236
|
type: "websocket-app",
|
|
208
237
|
entryPoints,
|
|
209
|
-
contextMapping: {
|
|
238
|
+
contextMapping: Object.keys(contextMapping).length > 0 ? contextMapping : {
|
|
210
239
|
server: "Server",
|
|
211
240
|
client: "Client"
|
|
212
241
|
},
|
|
@@ -217,6 +246,98 @@ class ProjectDetector {
|
|
|
217
246
|
}
|
|
218
247
|
};
|
|
219
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
|
+
}
|
|
220
341
|
detectGenericProject() {
|
|
221
342
|
const entryPoints = {};
|
|
222
343
|
const tsConfigPath = path3.join(this.projectRoot, "tsconfig.json");
|
|
@@ -343,10 +464,14 @@ class ManifestParser {
|
|
|
343
464
|
manifestPath;
|
|
344
465
|
manifestData;
|
|
345
466
|
baseDir;
|
|
346
|
-
constructor(projectRoot) {
|
|
467
|
+
constructor(projectRoot, optional = false) {
|
|
347
468
|
this.baseDir = projectRoot;
|
|
348
469
|
this.manifestPath = path.join(projectRoot, "manifest.json");
|
|
349
470
|
if (!fs.existsSync(this.manifestPath)) {
|
|
471
|
+
if (optional) {
|
|
472
|
+
this.manifestData = null;
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
350
475
|
throw new Error(`manifest.json not found at ${this.manifestPath}`);
|
|
351
476
|
}
|
|
352
477
|
try {
|
|
@@ -356,7 +481,13 @@ class ManifestParser {
|
|
|
356
481
|
throw new Error(`Failed to parse manifest.json: ${error}`);
|
|
357
482
|
}
|
|
358
483
|
}
|
|
484
|
+
hasManifest() {
|
|
485
|
+
return this.manifestData !== null;
|
|
486
|
+
}
|
|
359
487
|
parse() {
|
|
488
|
+
if (!this.manifestData) {
|
|
489
|
+
throw new Error("Cannot parse manifest: manifest.json not loaded. Use hasManifest() to check availability.");
|
|
490
|
+
}
|
|
360
491
|
const manifest = this.manifestData;
|
|
361
492
|
return {
|
|
362
493
|
name: manifest.name || "Unknown Extension",
|
|
@@ -373,6 +504,9 @@ class ManifestParser {
|
|
|
373
504
|
};
|
|
374
505
|
}
|
|
375
506
|
getContextEntryPoints() {
|
|
507
|
+
if (!this.manifestData) {
|
|
508
|
+
return {};
|
|
509
|
+
}
|
|
376
510
|
const entryPoints = {};
|
|
377
511
|
const background = this.parseBackground();
|
|
378
512
|
if (background) {
|
|
@@ -415,6 +549,8 @@ class ManifestParser {
|
|
|
415
549
|
return entryPoints;
|
|
416
550
|
}
|
|
417
551
|
parseBackground() {
|
|
552
|
+
if (!this.manifestData)
|
|
553
|
+
return;
|
|
418
554
|
const bg = this.manifestData.background;
|
|
419
555
|
if (!bg)
|
|
420
556
|
return;
|
|
@@ -439,6 +575,8 @@ class ManifestParser {
|
|
|
439
575
|
return;
|
|
440
576
|
}
|
|
441
577
|
parseContentScripts() {
|
|
578
|
+
if (!this.manifestData)
|
|
579
|
+
return;
|
|
442
580
|
const cs = this.manifestData.content_scripts;
|
|
443
581
|
if (!cs || !Array.isArray(cs))
|
|
444
582
|
return;
|
|
@@ -449,6 +587,8 @@ class ManifestParser {
|
|
|
449
587
|
}));
|
|
450
588
|
}
|
|
451
589
|
parsePopup() {
|
|
590
|
+
if (!this.manifestData)
|
|
591
|
+
return;
|
|
452
592
|
const action = this.manifestData.action || this.manifestData.browser_action;
|
|
453
593
|
if (!action)
|
|
454
594
|
return;
|
|
@@ -461,6 +601,8 @@ class ManifestParser {
|
|
|
461
601
|
return;
|
|
462
602
|
}
|
|
463
603
|
parseOptions() {
|
|
604
|
+
if (!this.manifestData)
|
|
605
|
+
return;
|
|
464
606
|
const options = this.manifestData.options_ui || this.manifestData.options_page;
|
|
465
607
|
if (!options)
|
|
466
608
|
return;
|
|
@@ -476,6 +618,8 @@ class ManifestParser {
|
|
|
476
618
|
};
|
|
477
619
|
}
|
|
478
620
|
parseDevtools() {
|
|
621
|
+
if (!this.manifestData)
|
|
622
|
+
return;
|
|
479
623
|
const devtools = this.manifestData.devtools_page;
|
|
480
624
|
if (!devtools)
|
|
481
625
|
return;
|
|
@@ -780,7 +924,34 @@ class FlowAnalyzer {
|
|
|
780
924
|
const expression = node.getExpression();
|
|
781
925
|
if (Node2.isPropertyAccessExpression(expression)) {
|
|
782
926
|
const methodName = expression.getName();
|
|
783
|
-
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") {
|
|
784
955
|
const args = node.getArguments();
|
|
785
956
|
if (args.length > 0) {
|
|
786
957
|
const firstArg = args[0];
|
|
@@ -932,6 +1103,15 @@ class FlowAnalyzer {
|
|
|
932
1103
|
if (path2.includes("/offscreen/") || path2.includes("\\offscreen\\")) {
|
|
933
1104
|
return "offscreen";
|
|
934
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
|
+
}
|
|
935
1115
|
return "unknown";
|
|
936
1116
|
}
|
|
937
1117
|
}
|
|
@@ -1168,7 +1348,7 @@ class HandlerExtractor {
|
|
|
1168
1348
|
const expression = node.getExpression();
|
|
1169
1349
|
if (Node4.isPropertyAccessExpression(expression)) {
|
|
1170
1350
|
const methodName = expression.getName();
|
|
1171
|
-
if (methodName === "on") {
|
|
1351
|
+
if (methodName === "on" || methodName === "addEventListener") {
|
|
1172
1352
|
const handler = this.extractHandler(node, context, filePath);
|
|
1173
1353
|
if (handler) {
|
|
1174
1354
|
handlers.push(handler);
|
|
@@ -1176,6 +1356,14 @@ class HandlerExtractor {
|
|
|
1176
1356
|
}
|
|
1177
1357
|
}
|
|
1178
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
|
+
}
|
|
1179
1367
|
});
|
|
1180
1368
|
return handlers;
|
|
1181
1369
|
}
|
|
@@ -1317,6 +1505,75 @@ class HandlerExtractor {
|
|
|
1317
1505
|
}
|
|
1318
1506
|
return;
|
|
1319
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
|
+
}
|
|
1320
1577
|
inferContext(filePath) {
|
|
1321
1578
|
const path2 = filePath.toLowerCase();
|
|
1322
1579
|
if (path2.includes("/background/") || path2.includes("\\background\\")) {
|
|
@@ -1337,6 +1594,15 @@ class HandlerExtractor {
|
|
|
1337
1594
|
if (path2.includes("/offscreen/") || path2.includes("\\offscreen\\")) {
|
|
1338
1595
|
return "offscreen";
|
|
1339
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
|
+
}
|
|
1340
1606
|
return "unknown";
|
|
1341
1607
|
}
|
|
1342
1608
|
}
|
|
@@ -1486,10 +1752,8 @@ class ArchitectureAnalyzer {
|
|
|
1486
1752
|
let projectConfig;
|
|
1487
1753
|
let entryPoints = {};
|
|
1488
1754
|
let systemInfo;
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
if (hasManifest && !this.options.useProjectDetector) {
|
|
1492
|
-
const manifestParser = new ManifestParser(this.options.projectRoot);
|
|
1755
|
+
const manifestParser = new ManifestParser(this.options.projectRoot, true);
|
|
1756
|
+
if (manifestParser.hasManifest() && !this.options.useProjectDetector) {
|
|
1493
1757
|
manifest = manifestParser.parse();
|
|
1494
1758
|
entryPoints = manifestParser.getContextEntryPoints();
|
|
1495
1759
|
systemInfo = {
|
|
@@ -2256,11 +2520,17 @@ async function generateCommand() {
|
|
|
2256
2520
|
console.log(color(` Using: ${tsConfigPath}`, COLORS.gray));
|
|
2257
2521
|
const projectRoot = findProjectRoot();
|
|
2258
2522
|
if (!projectRoot) {
|
|
2259
|
-
console.error(color("❌ Could not find
|
|
2260
|
-
console.error(" Run this command from
|
|
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");
|
|
2261
2525
|
process.exit(1);
|
|
2262
2526
|
}
|
|
2263
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
|
+
}
|
|
2264
2534
|
const analysis = await analyzeArchitecture({
|
|
2265
2535
|
tsConfigPath,
|
|
2266
2536
|
projectRoot
|
|
@@ -2447,7 +2717,15 @@ async function serveCommand(args) {
|
|
|
2447
2717
|
}
|
|
2448
2718
|
function showHelp() {
|
|
2449
2719
|
console.log(`
|
|
2450
|
-
${color("bun visualize", COLORS.blue)} - Architecture visualization
|
|
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
|
|
2451
2729
|
|
|
2452
2730
|
${color("Commands:", COLORS.blue)}
|
|
2453
2731
|
|
|
@@ -2467,14 +2745,14 @@ ${color("Commands:", COLORS.blue)}
|
|
|
2467
2745
|
|
|
2468
2746
|
${color("Getting Started:", COLORS.blue)}
|
|
2469
2747
|
|
|
2470
|
-
1. Run ${color("bun visualize", COLORS.green)} from your
|
|
2748
|
+
1. Run ${color("bun visualize", COLORS.green)} from your project root
|
|
2471
2749
|
2. Find generated ${color("docs/architecture.dsl", COLORS.blue)}
|
|
2472
2750
|
3. View with Structurizr Lite (see instructions after generation)
|
|
2473
2751
|
|
|
2474
2752
|
${color("What gets generated:", COLORS.blue)}
|
|
2475
2753
|
|
|
2476
|
-
• System Context diagram -
|
|
2477
|
-
• Container diagram -
|
|
2754
|
+
• System Context diagram - Your app + external systems
|
|
2755
|
+
• Container diagram - App contexts (background, content, server, client, etc.)
|
|
2478
2756
|
• Component diagrams - Internal components within contexts
|
|
2479
2757
|
• Dynamic diagrams - Message flows between contexts
|
|
2480
2758
|
|
|
@@ -2500,8 +2778,7 @@ function findTsConfig() {
|
|
|
2500
2778
|
function findProjectRoot() {
|
|
2501
2779
|
const locations = [process.cwd(), path6.join(process.cwd(), "..")];
|
|
2502
2780
|
for (const loc of locations) {
|
|
2503
|
-
|
|
2504
|
-
if (fs6.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"))) {
|
|
2505
2782
|
return loc;
|
|
2506
2783
|
}
|
|
2507
2784
|
}
|
|
@@ -2519,4 +2796,4 @@ Stack trace:`, COLORS.gray));
|
|
|
2519
2796
|
process.exit(1);
|
|
2520
2797
|
});
|
|
2521
2798
|
|
|
2522
|
-
//# debugId=
|
|
2799
|
+
//# debugId=37FBF573C9C57A4C64756E2164756E21
|