@fairfox/polly 0.1.1 ā 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/polly.ts +9 -3
- package/package.json +2 -2
- package/vendor/analysis/src/extract/adr.ts +212 -0
- package/vendor/analysis/src/extract/architecture.ts +160 -0
- package/vendor/analysis/src/extract/contexts.ts +298 -0
- package/vendor/analysis/src/extract/flows.ts +309 -0
- package/vendor/analysis/src/extract/handlers.ts +321 -0
- package/vendor/analysis/src/extract/index.ts +9 -0
- package/vendor/analysis/src/extract/integrations.ts +329 -0
- package/vendor/analysis/src/extract/manifest.ts +298 -0
- package/vendor/analysis/src/extract/types.ts +389 -0
- package/vendor/analysis/src/index.ts +7 -0
- package/vendor/analysis/src/types/adr.ts +53 -0
- package/vendor/analysis/src/types/architecture.ts +245 -0
- package/vendor/analysis/src/types/core.ts +210 -0
- package/vendor/analysis/src/types/index.ts +18 -0
- package/vendor/verify/src/adapters/base.ts +164 -0
- package/vendor/verify/src/adapters/detection.ts +281 -0
- package/vendor/verify/src/adapters/event-bus/index.ts +480 -0
- package/vendor/verify/src/adapters/web-extension/index.ts +508 -0
- package/vendor/verify/src/adapters/websocket/index.ts +486 -0
- package/vendor/verify/src/cli.ts +430 -0
- package/vendor/verify/src/codegen/config.ts +354 -0
- package/vendor/verify/src/codegen/tla.ts +719 -0
- package/vendor/verify/src/config/parser.ts +303 -0
- package/vendor/verify/src/config/types.ts +113 -0
- package/vendor/verify/src/core/model.ts +267 -0
- package/vendor/verify/src/core/primitives.ts +106 -0
- package/vendor/verify/src/extract/handlers.ts +2 -0
- package/vendor/verify/src/extract/types.ts +2 -0
- package/vendor/verify/src/index.ts +150 -0
- package/vendor/verify/src/primitives/index.ts +102 -0
- package/vendor/verify/src/runner/docker.ts +283 -0
- package/vendor/verify/src/types.ts +51 -0
- package/vendor/visualize/src/cli.ts +365 -0
- package/vendor/visualize/src/codegen/structurizr.ts +770 -0
- package/vendor/visualize/src/index.ts +13 -0
- package/vendor/visualize/src/runner/export.ts +235 -0
- package/vendor/visualize/src/viewer/server.ts +485 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// CLI for visualization system
|
|
3
|
+
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { analyzeArchitecture } from "@fairfox/polly-analysis";
|
|
7
|
+
import { generateStructurizrDSL } from "./codegen/structurizr";
|
|
8
|
+
import { exportDiagrams } from "./runner/export";
|
|
9
|
+
|
|
10
|
+
const COLORS = {
|
|
11
|
+
reset: "\x1b[0m",
|
|
12
|
+
red: "\x1b[31m",
|
|
13
|
+
green: "\x1b[32m",
|
|
14
|
+
yellow: "\x1b[33m",
|
|
15
|
+
blue: "\x1b[34m",
|
|
16
|
+
gray: "\x1b[90m",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function color(text: string, colorCode: string): string {
|
|
20
|
+
return `${colorCode}${text}${COLORS.reset}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const command = args[0];
|
|
26
|
+
|
|
27
|
+
switch (command) {
|
|
28
|
+
case "--generate":
|
|
29
|
+
case "generate":
|
|
30
|
+
await generateCommand();
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case "--export":
|
|
34
|
+
case "export":
|
|
35
|
+
await exportCommand(args.slice(1));
|
|
36
|
+
break;
|
|
37
|
+
|
|
38
|
+
case "--serve":
|
|
39
|
+
case "serve":
|
|
40
|
+
case "--view":
|
|
41
|
+
case "view":
|
|
42
|
+
await serveCommand(args.slice(1));
|
|
43
|
+
break;
|
|
44
|
+
|
|
45
|
+
case "--help":
|
|
46
|
+
case "help":
|
|
47
|
+
showHelp();
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
default:
|
|
51
|
+
await generateCommand();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function generateCommand() {
|
|
56
|
+
console.log(color("\nš Analyzing architecture...\n", COLORS.blue));
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Find tsconfig
|
|
60
|
+
const tsConfigPath = findTsConfig();
|
|
61
|
+
if (!tsConfigPath) {
|
|
62
|
+
console.error(color("ā Could not find tsconfig.json", COLORS.red));
|
|
63
|
+
console.error(" Run this command from your project root");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(color(` Using: ${tsConfigPath}`, COLORS.gray));
|
|
68
|
+
|
|
69
|
+
// Find project root (where manifest.json is)
|
|
70
|
+
const projectRoot = findProjectRoot();
|
|
71
|
+
if (!projectRoot) {
|
|
72
|
+
console.error(color("ā Could not find manifest.json", COLORS.red));
|
|
73
|
+
console.error(" Run this command from your extension project root");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(color(` Project: ${projectRoot}`, COLORS.gray));
|
|
78
|
+
|
|
79
|
+
// Analyze architecture
|
|
80
|
+
const analysis = await analyzeArchitecture({
|
|
81
|
+
tsConfigPath,
|
|
82
|
+
projectRoot,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
console.log(
|
|
86
|
+
color(`\nā Found ${Object.keys(analysis.contexts).length} context(s)`, COLORS.green)
|
|
87
|
+
);
|
|
88
|
+
console.log(color(`ā Found ${analysis.messageFlows.length} message flow(s)`, COLORS.green));
|
|
89
|
+
console.log(color(`ā Found ${analysis.integrations.length} integration(s)`, COLORS.green));
|
|
90
|
+
|
|
91
|
+
// Show summary
|
|
92
|
+
console.log(color("\nš Architecture Summary:\n", COLORS.blue));
|
|
93
|
+
|
|
94
|
+
console.log(color(" Contexts:", COLORS.blue));
|
|
95
|
+
for (const [contextType, contextInfo] of Object.entries(analysis.contexts)) {
|
|
96
|
+
console.log(color(` ⢠${contextType}`, COLORS.gray));
|
|
97
|
+
console.log(color(` - ${contextInfo.handlers.length} handler(s)`, COLORS.gray));
|
|
98
|
+
console.log(color(` - ${contextInfo.chromeAPIs.length} Chrome API(s)`, COLORS.gray));
|
|
99
|
+
if (contextInfo.components) {
|
|
100
|
+
console.log(color(` - ${contextInfo.components.length} component(s)`, COLORS.gray));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (analysis.integrations.length > 0) {
|
|
105
|
+
console.log(color("\n External Integrations:", COLORS.blue));
|
|
106
|
+
for (const integration of analysis.integrations) {
|
|
107
|
+
console.log(color(` ⢠${integration.name} (${integration.type})`, COLORS.gray));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Generate Structurizr DSL
|
|
112
|
+
console.log(color("\nš Generating Structurizr DSL...\n", COLORS.blue));
|
|
113
|
+
|
|
114
|
+
const dsl = generateStructurizrDSL(analysis, {
|
|
115
|
+
includeDynamicDiagrams: true,
|
|
116
|
+
includeComponentDiagrams: true,
|
|
117
|
+
componentDiagramContexts: ["background"],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Write to file
|
|
121
|
+
const outputDir = path.join(process.cwd(), "docs");
|
|
122
|
+
if (!fs.existsSync(outputDir)) {
|
|
123
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const dslPath = path.join(outputDir, "architecture.dsl");
|
|
127
|
+
fs.writeFileSync(dslPath, dsl, "utf-8");
|
|
128
|
+
|
|
129
|
+
console.log(color("ā
Architecture documentation generated!\n", COLORS.green));
|
|
130
|
+
console.log(` File: ${color(dslPath, COLORS.blue)}`);
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(color("š Next steps:", COLORS.blue));
|
|
133
|
+
console.log();
|
|
134
|
+
console.log(" 1. Export diagrams:");
|
|
135
|
+
console.log(" bun visualize --export");
|
|
136
|
+
console.log();
|
|
137
|
+
console.log(" 2. View in browser:");
|
|
138
|
+
console.log(" bun visualize --serve");
|
|
139
|
+
console.log();
|
|
140
|
+
console.log(color("š” Alternative: Structurizr Lite", COLORS.gray));
|
|
141
|
+
console.log(color(" docker run -it --rm -p 8080:8080 \\", COLORS.gray));
|
|
142
|
+
console.log(color(` -v ${outputDir}:/usr/local/structurizr \\`, COLORS.gray));
|
|
143
|
+
console.log(color(" structurizr/lite", COLORS.gray));
|
|
144
|
+
console.log();
|
|
145
|
+
console.log(color("š” Upload to Structurizr Cloud:", COLORS.gray));
|
|
146
|
+
console.log(color(" 1. Sign up at https://structurizr.com", COLORS.gray));
|
|
147
|
+
console.log(color(" 2. Create a workspace and get API credentials", COLORS.gray));
|
|
148
|
+
console.log(
|
|
149
|
+
color(" 3. docker run -it --rm -v $(pwd)/docs:/usr/local/structurizr \\", COLORS.gray)
|
|
150
|
+
);
|
|
151
|
+
console.log(
|
|
152
|
+
color(" structurizr/cli push -id WORKSPACE_ID -key KEY -secret SECRET \\", COLORS.gray)
|
|
153
|
+
);
|
|
154
|
+
console.log(color(" -workspace /usr/local/structurizr/architecture.dsl", COLORS.gray));
|
|
155
|
+
console.log();
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error(color("\nā Generation failed:", COLORS.red));
|
|
158
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
159
|
+
if (error instanceof Error && error.stack) {
|
|
160
|
+
console.error(color("\nStack trace:", COLORS.gray));
|
|
161
|
+
console.error(color(error.stack, COLORS.gray));
|
|
162
|
+
}
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function exportCommand(args: string[]) {
|
|
168
|
+
console.log(color("\nš¤ Generating static site...\n", COLORS.blue));
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
// Find DSL file
|
|
172
|
+
const dslPath = path.join(process.cwd(), "docs", "architecture.dsl");
|
|
173
|
+
if (!fs.existsSync(dslPath)) {
|
|
174
|
+
console.error(color("ā DSL file not found", COLORS.red));
|
|
175
|
+
console.error(" Expected: docs/architecture.dsl");
|
|
176
|
+
console.error(" Run 'bun visualize' first to generate the DSL");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const outputDir = path.join(process.cwd(), "docs", "site");
|
|
181
|
+
|
|
182
|
+
console.log(color(` DSL: ${dslPath}`, COLORS.gray));
|
|
183
|
+
console.log(color(` Output: ${outputDir}`, COLORS.gray));
|
|
184
|
+
console.log();
|
|
185
|
+
|
|
186
|
+
// Export static site
|
|
187
|
+
const result = await exportDiagrams({
|
|
188
|
+
dslPath,
|
|
189
|
+
outputDir,
|
|
190
|
+
onProgress: (message) => {
|
|
191
|
+
console.log(color(` ${message}`, COLORS.gray));
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!result.success) {
|
|
196
|
+
console.error(color("\nā Export failed:", COLORS.red));
|
|
197
|
+
console.error(` ${result.error}`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Show results
|
|
202
|
+
console.log(color("\nā
Static site generated!\n", COLORS.green));
|
|
203
|
+
console.log(color("š Location:", COLORS.blue));
|
|
204
|
+
console.log(` ${outputDir}`);
|
|
205
|
+
console.log();
|
|
206
|
+
console.log(color("š” Next steps:", COLORS.gray));
|
|
207
|
+
console.log(color(" ⢠View: bun visualize --serve", COLORS.gray));
|
|
208
|
+
console.log(color(" ⢠Or open: docs/site/index.html", COLORS.gray));
|
|
209
|
+
console.log();
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error(color("\nā Export failed:", COLORS.red));
|
|
212
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
213
|
+
if (error instanceof Error && error.stack) {
|
|
214
|
+
console.error(color("\nStack trace:", COLORS.gray));
|
|
215
|
+
console.error(color(error.stack, COLORS.gray));
|
|
216
|
+
}
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function serveCommand(args: string[]) {
|
|
222
|
+
console.log(color("\nš Starting static site server...\n", COLORS.blue));
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const siteDir = path.join(process.cwd(), "docs", "site");
|
|
226
|
+
const indexPath = path.join(siteDir, "index.html");
|
|
227
|
+
|
|
228
|
+
if (!fs.existsSync(indexPath)) {
|
|
229
|
+
console.error(color("ā Static site not found", COLORS.red));
|
|
230
|
+
console.error(" Expected: docs/site/index.html");
|
|
231
|
+
console.error(" Run 'bun visualize --export' first to generate the site");
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Parse port argument
|
|
236
|
+
const portArg = args.find((arg) => arg.startsWith("--port="));
|
|
237
|
+
const port = portArg ? parseInt(portArg.replace("--port=", "")) : 3000;
|
|
238
|
+
|
|
239
|
+
console.log(color(` Site: ${siteDir}`, COLORS.gray));
|
|
240
|
+
console.log(color(` Port: ${port}`, COLORS.gray));
|
|
241
|
+
console.log();
|
|
242
|
+
|
|
243
|
+
// Start Bun's static file server
|
|
244
|
+
const BunGlobal = (globalThis as any).Bun;
|
|
245
|
+
if (!BunGlobal) {
|
|
246
|
+
throw new Error("Bun runtime is required to run the server");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const server = BunGlobal.serve({
|
|
250
|
+
port,
|
|
251
|
+
fetch(req: Request) {
|
|
252
|
+
const url = new URL(req.url);
|
|
253
|
+
let filePath = path.join(siteDir, url.pathname === "/" ? "index.html" : url.pathname);
|
|
254
|
+
|
|
255
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
256
|
+
const file = BunGlobal.file(filePath);
|
|
257
|
+
return new Response(file);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return new Response("Not found", { status: 404 });
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
console.log(color(`\nā
Server started!\n`, COLORS.green));
|
|
265
|
+
console.log(` ${color(`http://localhost:${port}`, COLORS.blue)}`);
|
|
266
|
+
console.log();
|
|
267
|
+
console.log(color("Press Ctrl+C to stop the server", COLORS.gray));
|
|
268
|
+
console.log();
|
|
269
|
+
|
|
270
|
+
// Auto-open browser
|
|
271
|
+
if (process.platform === "darwin") {
|
|
272
|
+
await BunGlobal.spawn(["open", `http://localhost:${port}`]);
|
|
273
|
+
} else if (process.platform === "linux") {
|
|
274
|
+
await BunGlobal.spawn(["xdg-open", `http://localhost:${port}`]);
|
|
275
|
+
} else if (process.platform === "win32") {
|
|
276
|
+
await BunGlobal.spawn(["cmd", "/c", "start", `http://localhost:${port}`]);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Keep process alive
|
|
280
|
+
await new Promise(() => {});
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error(color("\nā Failed to start server:", COLORS.red));
|
|
283
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function showHelp() {
|
|
289
|
+
console.log(`
|
|
290
|
+
${color("bun visualize", COLORS.blue)} - Architecture visualization for web extensions
|
|
291
|
+
|
|
292
|
+
${color("Commands:", COLORS.blue)}
|
|
293
|
+
|
|
294
|
+
${color("bun visualize", COLORS.green)}
|
|
295
|
+
${color("bun visualize --generate", COLORS.green)}
|
|
296
|
+
Analyze codebase and generate Structurizr DSL
|
|
297
|
+
|
|
298
|
+
${color("bun visualize --export", COLORS.green)}
|
|
299
|
+
Generate static HTML site with interactive diagrams (requires Docker)
|
|
300
|
+
|
|
301
|
+
${color("bun visualize --serve", COLORS.green)}
|
|
302
|
+
${color("bun visualize --serve --port=3000", COLORS.green)}
|
|
303
|
+
Serve the static site in browser
|
|
304
|
+
|
|
305
|
+
${color("bun visualize --help", COLORS.green)}
|
|
306
|
+
Show this help message
|
|
307
|
+
|
|
308
|
+
${color("Getting Started:", COLORS.blue)}
|
|
309
|
+
|
|
310
|
+
1. Run ${color("bun visualize", COLORS.green)} from your extension project root
|
|
311
|
+
2. Find generated ${color("docs/architecture.dsl", COLORS.blue)}
|
|
312
|
+
3. View with Structurizr Lite (see instructions after generation)
|
|
313
|
+
|
|
314
|
+
${color("What gets generated:", COLORS.blue)}
|
|
315
|
+
|
|
316
|
+
⢠System Context diagram - Extension + external systems
|
|
317
|
+
⢠Container diagram - Extension contexts (background, content, popup, etc.)
|
|
318
|
+
⢠Component diagrams - Internal components within contexts
|
|
319
|
+
⢠Dynamic diagrams - Message flows between contexts
|
|
320
|
+
|
|
321
|
+
${color("Learn More:", COLORS.blue)}
|
|
322
|
+
|
|
323
|
+
Documentation: https://github.com/fairfox/web-ext
|
|
324
|
+
Structurizr: https://structurizr.com
|
|
325
|
+
C4 Model: https://c4model.com
|
|
326
|
+
`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function findTsConfig(): string | null {
|
|
330
|
+
const locations = [
|
|
331
|
+
path.join(process.cwd(), "tsconfig.json"),
|
|
332
|
+
path.join(process.cwd(), "..", "tsconfig.json"),
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
for (const loc of locations) {
|
|
336
|
+
if (fs.existsSync(loc)) {
|
|
337
|
+
return loc;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function findProjectRoot(): string | null {
|
|
345
|
+
const locations = [process.cwd(), path.join(process.cwd(), "..")];
|
|
346
|
+
|
|
347
|
+
for (const loc of locations) {
|
|
348
|
+
const manifestPath = path.join(loc, "manifest.json");
|
|
349
|
+
if (fs.existsSync(manifestPath)) {
|
|
350
|
+
return loc;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
main().catch((error) => {
|
|
358
|
+
console.error(color("\nā Fatal error:", COLORS.red));
|
|
359
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
360
|
+
if (error instanceof Error && error.stack) {
|
|
361
|
+
console.error(color("\nStack trace:", COLORS.gray));
|
|
362
|
+
console.error(color(error.stack, COLORS.gray));
|
|
363
|
+
}
|
|
364
|
+
process.exit(1);
|
|
365
|
+
});
|