@fairfox/polly 0.1.2 → 0.1.4

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.
Files changed (68) hide show
  1. package/{cli/polly.ts → dist/cli/polly.js} +100 -206
  2. package/dist/cli/polly.js.map +10 -0
  3. package/dist/scripts/build-extension.js +137 -0
  4. package/dist/scripts/build-extension.js.map +10 -0
  5. package/dist/vendor/verify/src/cli.js +2089 -0
  6. package/dist/vendor/verify/src/cli.js.map +16 -0
  7. package/dist/vendor/visualize/src/cli.js +2204 -0
  8. package/dist/vendor/visualize/src/cli.js.map +19 -0
  9. package/package.json +12 -12
  10. package/vendor/analysis/src/extract/adr.ts +0 -212
  11. package/vendor/analysis/src/extract/architecture.ts +0 -160
  12. package/vendor/analysis/src/extract/contexts.ts +0 -298
  13. package/vendor/analysis/src/extract/flows.ts +0 -309
  14. package/vendor/analysis/src/extract/handlers.ts +0 -321
  15. package/vendor/analysis/src/extract/index.ts +0 -9
  16. package/vendor/analysis/src/extract/integrations.ts +0 -329
  17. package/vendor/analysis/src/extract/manifest.ts +0 -298
  18. package/vendor/analysis/src/extract/types.ts +0 -389
  19. package/vendor/analysis/src/index.ts +0 -7
  20. package/vendor/analysis/src/types/adr.ts +0 -53
  21. package/vendor/analysis/src/types/architecture.ts +0 -245
  22. package/vendor/analysis/src/types/core.ts +0 -210
  23. package/vendor/analysis/src/types/index.ts +0 -18
  24. package/vendor/verify/src/adapters/base.ts +0 -164
  25. package/vendor/verify/src/adapters/detection.ts +0 -281
  26. package/vendor/verify/src/adapters/event-bus/index.ts +0 -480
  27. package/vendor/verify/src/adapters/web-extension/index.ts +0 -508
  28. package/vendor/verify/src/adapters/websocket/index.ts +0 -486
  29. package/vendor/verify/src/cli.ts +0 -430
  30. package/vendor/verify/src/codegen/config.ts +0 -354
  31. package/vendor/verify/src/codegen/tla.ts +0 -719
  32. package/vendor/verify/src/config/parser.ts +0 -303
  33. package/vendor/verify/src/config/types.ts +0 -113
  34. package/vendor/verify/src/core/model.ts +0 -267
  35. package/vendor/verify/src/core/primitives.ts +0 -106
  36. package/vendor/verify/src/extract/handlers.ts +0 -2
  37. package/vendor/verify/src/extract/types.ts +0 -2
  38. package/vendor/verify/src/index.ts +0 -150
  39. package/vendor/verify/src/primitives/index.ts +0 -102
  40. package/vendor/verify/src/runner/docker.ts +0 -283
  41. package/vendor/verify/src/types.ts +0 -51
  42. package/vendor/visualize/src/cli.ts +0 -365
  43. package/vendor/visualize/src/codegen/structurizr.ts +0 -770
  44. package/vendor/visualize/src/index.ts +0 -13
  45. package/vendor/visualize/src/runner/export.ts +0 -235
  46. package/vendor/visualize/src/viewer/server.ts +0 -485
  47. /package/dist/{background → src/background}/index.js +0 -0
  48. /package/dist/{background → src/background}/index.js.map +0 -0
  49. /package/dist/{background → src/background}/message-router.js +0 -0
  50. /package/dist/{background → src/background}/message-router.js.map +0 -0
  51. /package/dist/{index.js → src/index.js} +0 -0
  52. /package/dist/{index.js.map → src/index.js.map} +0 -0
  53. /package/dist/{shared → src/shared}/adapters/index.js +0 -0
  54. /package/dist/{shared → src/shared}/adapters/index.js.map +0 -0
  55. /package/dist/{shared → src/shared}/lib/context-helpers.js +0 -0
  56. /package/dist/{shared → src/shared}/lib/context-helpers.js.map +0 -0
  57. /package/dist/{shared → src/shared}/lib/errors.js +0 -0
  58. /package/dist/{shared → src/shared}/lib/errors.js.map +0 -0
  59. /package/dist/{shared → src/shared}/lib/message-bus.js +0 -0
  60. /package/dist/{shared → src/shared}/lib/message-bus.js.map +0 -0
  61. /package/dist/{shared → src/shared}/lib/state.js +0 -0
  62. /package/dist/{shared → src/shared}/lib/state.js.map +0 -0
  63. /package/dist/{shared → src/shared}/lib/test-helpers.js +0 -0
  64. /package/dist/{shared → src/shared}/lib/test-helpers.js.map +0 -0
  65. /package/dist/{shared → src/shared}/state/app-state.js +0 -0
  66. /package/dist/{shared → src/shared}/state/app-state.js.map +0 -0
  67. /package/dist/{shared → src/shared}/types/messages.js +0 -0
  68. /package/dist/{shared → src/shared}/types/messages.js.map +0 -0
@@ -1,770 +0,0 @@
1
- // Structurizr DSL generator
2
-
3
- import type { ArchitectureAnalysis } from "@fairfox/polly-analysis";
4
-
5
- export interface StructurizrDSLOptions {
6
- /** Include dynamic diagrams for message flows */
7
- includeDynamicDiagrams?: boolean;
8
-
9
- /** Include component diagrams for contexts */
10
- includeComponentDiagrams?: boolean;
11
-
12
- /** Which contexts to generate component diagrams for */
13
- componentDiagramContexts?: string[];
14
-
15
- /** Custom theme URL */
16
- theme?: string;
17
-
18
- /** Custom styles */
19
- styles?: Record<string, string>;
20
- }
21
-
22
- export class StructurizrDSLGenerator {
23
- private analysis: ArchitectureAnalysis;
24
- private options: StructurizrDSLOptions;
25
-
26
- constructor(analysis: ArchitectureAnalysis, options: StructurizrDSLOptions = {}) {
27
- this.analysis = analysis;
28
- this.options = {
29
- includeDynamicDiagrams: true,
30
- includeComponentDiagrams: true,
31
- componentDiagramContexts: ["background"],
32
- theme: "https://static.structurizr.com/themes/default",
33
- ...options,
34
- };
35
- }
36
-
37
- /**
38
- * Generate complete Structurizr DSL
39
- */
40
- generate(): string {
41
- const parts: string[] = [];
42
-
43
- parts.push(this.generateWorkspaceHeader());
44
- parts.push(this.generateModel());
45
- parts.push(this.generateViews());
46
- parts.push(this.generateWorkspaceFooter());
47
-
48
- return parts.join("\n\n");
49
- }
50
-
51
- /**
52
- * Generate workspace header
53
- */
54
- private generateWorkspaceHeader(): string {
55
- const { name, description } = this.analysis.system;
56
-
57
- const parts = [`workspace "${this.escape(name)}" "${this.escape(description || "")}" {`];
58
- parts.push("");
59
- parts.push(" !identifiers hierarchical");
60
-
61
- // Add ADRs if present
62
- if (this.analysis.adrs && this.analysis.adrs.adrs.length > 0) {
63
- parts.push(" !adrs " + this.analysis.adrs.directory);
64
- }
65
-
66
- return parts.join("\n");
67
- }
68
-
69
- /**
70
- * Generate workspace footer
71
- */
72
- private generateWorkspaceFooter(): string {
73
- return "}";
74
- }
75
-
76
- /**
77
- * Generate model section
78
- */
79
- private generateModel(): string {
80
- const parts: string[] = [];
81
-
82
- parts.push(" model {");
83
- parts.push(this.generatePeople());
84
- parts.push(this.generateExternalSystems());
85
- parts.push(this.generateMainSystem());
86
- parts.push(" }");
87
-
88
- return parts.join("\n\n");
89
- }
90
-
91
- /**
92
- * Generate people/actors
93
- */
94
- private generatePeople(): string {
95
- return ` user = person "User" "Extension user"`;
96
- }
97
-
98
- /**
99
- * Generate external systems
100
- */
101
- private generateExternalSystems(): string {
102
- const parts: string[] = [];
103
-
104
- for (const integration of this.analysis.integrations) {
105
- if (integration.type === "api" || integration.type === "websocket") {
106
- const tech =
107
- integration.technology || (integration.type === "websocket" ? "WebSocket" : "REST API");
108
- let desc = integration.description || "";
109
-
110
- // Generate better description from API calls if available
111
- if (!desc && integration.calls && integration.calls.length > 0) {
112
- const endpoints = integration.calls
113
- .slice(0, 3)
114
- .map((c) => c.endpoint)
115
- .join(", ");
116
- const methods = [...new Set(integration.calls.map((c) => c.method))].join(", ");
117
- desc = `External API with endpoints: ${endpoints}. Methods: ${methods}`;
118
- }
119
-
120
- parts.push(
121
- ` ${this.toId(integration.name)} = softwareSystem "${this.escape(integration.name)}" "${this.escape(desc)}" {`
122
- );
123
- parts.push(
124
- ` tags "External System" "${integration.type === "websocket" ? "WebSocket" : "REST API"}"`
125
- );
126
- parts.push(` }`);
127
- }
128
- }
129
-
130
- return parts.join("\n");
131
- }
132
-
133
- /**
134
- * Generate main system (the extension)
135
- */
136
- private generateMainSystem(): string {
137
- const parts: string[] = [];
138
-
139
- parts.push(` extension = softwareSystem "${this.escape(this.analysis.system.name)}" {`);
140
-
141
- // Generate containers (contexts)
142
- for (const [contextType, contextInfo] of Object.entries(this.analysis.contexts)) {
143
- parts.push(this.generateContainer(contextType, contextInfo));
144
- }
145
-
146
- // Generate relationships between containers
147
- parts.push(this.generateContainerRelationships());
148
-
149
- parts.push(" }");
150
-
151
- return parts.join("\n\n");
152
- }
153
-
154
- /**
155
- * Generate container (context)
156
- */
157
- private generateContainer(contextType: string, contextInfo: any): string {
158
- const parts: string[] = [];
159
-
160
- const technology = this.getContextTechnology(contextType);
161
- const description = contextInfo.description || `${this.capitalize(contextType)} context`;
162
-
163
- parts.push(
164
- ` ${contextType} = container "${this.capitalize(contextType)}" "${this.escape(description)}" "${technology}" {`
165
- );
166
-
167
- // Generate components if enabled
168
- if (
169
- this.options.includeComponentDiagrams &&
170
- this.options.componentDiagramContexts?.includes(contextType)
171
- ) {
172
- parts.push(this.generateComponents(contextType, contextInfo));
173
- parts.push("");
174
- parts.push(this.generateComponentRelationships(contextType, contextInfo));
175
- }
176
-
177
- parts.push(" }");
178
-
179
- return parts.join("\n");
180
- }
181
-
182
- /**
183
- * Generate components within a container
184
- */
185
- private generateComponents(contextType: string, contextInfo: any): string {
186
- const parts: string[] = [];
187
-
188
- // Generate components from handlers
189
- const handlersByType = new Map<string, any[]>();
190
- for (const handler of contextInfo.handlers) {
191
- if (!handlersByType.has(handler.messageType)) {
192
- handlersByType.set(handler.messageType, []);
193
- }
194
- handlersByType.get(handler.messageType)!.push(handler);
195
- }
196
-
197
- for (const [messageType, handlers] of handlersByType) {
198
- const componentName = this.toComponentName(messageType);
199
- const description = this.generateComponentDescription(messageType, handlers[0]);
200
- const tags = this.getComponentTags(messageType, handlers[0]);
201
-
202
- parts.push(
203
- ` ${this.toId(componentName)} = component "${componentName}" "${description}" {`
204
- );
205
- if (tags.length > 0) {
206
- parts.push(` tags "${tags.join('" "')}"`);
207
- }
208
- parts.push(` }`);
209
- }
210
-
211
- // Generate components from UI components
212
- if (contextInfo.components) {
213
- for (const comp of contextInfo.components) {
214
- parts.push(
215
- ` ${this.toId(comp.name)} = component "${comp.name}" "${this.escape(comp.description || "UI component")}" {`
216
- );
217
- parts.push(` tags "UI Component"`);
218
- parts.push(` }`);
219
- }
220
- }
221
-
222
- // Add Chrome API components if used
223
- if (contextInfo.chromeAPIs && contextInfo.chromeAPIs.length > 0) {
224
- for (const api of contextInfo.chromeAPIs) {
225
- const apiId = this.toId(`chrome_${api}`);
226
- parts.push(
227
- ` ${apiId} = component "Chrome ${this.capitalize(api)} API" "Browser API for ${api}" {`
228
- );
229
- parts.push(` tags "Chrome API" "External"`);
230
- parts.push(` }`);
231
- }
232
- }
233
-
234
- return parts.join("\n");
235
- }
236
-
237
- /**
238
- * Generate better component descriptions based on message type
239
- */
240
- private generateComponentDescription(messageType: string, handler: any): string {
241
- const type = messageType.toLowerCase();
242
-
243
- // Authentication related
244
- if (type.includes("login")) {
245
- return "Authenticates users and establishes sessions";
246
- }
247
- if (type.includes("logout")) {
248
- return "Terminates user sessions and clears credentials";
249
- }
250
-
251
- // CRUD operations
252
- if (type.includes("add") || type.includes("create")) {
253
- const entity = type.replace(/_(add|create)/, "").replace(/_/g, " ");
254
- return `Creates new ${entity} items and persists to storage`;
255
- }
256
- if (type.includes("remove") || type.includes("delete")) {
257
- const entity = type.replace(/_(remove|delete)/, "").replace(/_/g, " ");
258
- return `Removes ${entity} items from storage`;
259
- }
260
- if (type.includes("update") || type.includes("toggle")) {
261
- const entity = type.replace(/_(update|toggle)/, "").replace(/_/g, " ");
262
- return `Updates ${entity} item state and syncs with storage`;
263
- }
264
- if (type.includes("clear")) {
265
- const entity = type.replace(/_clear.*/, "").replace(/_/g, " ");
266
- return `Clears all ${entity} items matching criteria`;
267
- }
268
-
269
- // Query operations
270
- if (type.includes("get") || type.includes("fetch") || type.includes("load")) {
271
- const entity = type.replace(/_(get|fetch|load)/, "").replace(/_/g, " ");
272
- return `Retrieves ${entity} data from storage`;
273
- }
274
-
275
- // Default
276
- return `Processes ${messageType} messages and coordinates business logic`;
277
- }
278
-
279
- /**
280
- * Determine appropriate tags for a component
281
- */
282
- private getComponentTags(messageType: string, handler: any): string[] {
283
- const tags: string[] = ["Message Handler"];
284
- const type = messageType.toLowerCase();
285
-
286
- // Add functional tags
287
- if (type.includes("login") || type.includes("logout") || type.includes("auth")) {
288
- tags.push("Authentication");
289
- } else if (
290
- type.includes("add") ||
291
- type.includes("create") ||
292
- type.includes("update") ||
293
- type.includes("delete") ||
294
- type.includes("remove") ||
295
- type.includes("toggle")
296
- ) {
297
- tags.push("CRUD");
298
- } else if (type.includes("get") || type.includes("fetch") || type.includes("load")) {
299
- tags.push("Query");
300
- }
301
-
302
- // Add domain tags
303
- if (type.includes("user")) {
304
- tags.push("User Management");
305
- }
306
- if (type.includes("todo")) {
307
- tags.push("Todo Management");
308
- }
309
- if (type.includes("state")) {
310
- tags.push("State Management");
311
- }
312
-
313
- return tags;
314
- }
315
-
316
- /**
317
- * Generate relationships between components within a container
318
- */
319
- private generateComponentRelationships(contextType: string, contextInfo: any): string {
320
- const parts: string[] = [];
321
-
322
- // Build a map of handler components
323
- const handlersByType = new Map<string, any[]>();
324
- for (const handler of contextInfo.handlers) {
325
- if (!handlersByType.has(handler.messageType)) {
326
- handlersByType.set(handler.messageType, []);
327
- }
328
- handlersByType.get(handler.messageType)!.push(handler);
329
- }
330
-
331
- // Add relationships to Chrome APIs
332
- if (contextInfo.chromeAPIs && contextInfo.chromeAPIs.length > 0) {
333
- for (const api of contextInfo.chromeAPIs) {
334
- const apiId = this.toId(`chrome_${api}`);
335
-
336
- // Find handlers that use this API
337
- for (const [messageType, handlers] of handlersByType) {
338
- const componentId = this.toId(this.toComponentName(messageType));
339
-
340
- // Infer relationship based on API
341
- let description = `Uses ${api}`;
342
- if (api === "storage") {
343
- if (
344
- messageType.toLowerCase().includes("get") ||
345
- messageType.toLowerCase().includes("load")
346
- ) {
347
- description = "Reads from storage";
348
- } else {
349
- description = "Writes to storage";
350
- }
351
- } else if (api === "tabs") {
352
- description = "Manages browser tabs";
353
- } else if (api === "runtime") {
354
- description = "Sends messages";
355
- }
356
-
357
- parts.push(` ${componentId} -> ${apiId} "${description}"`);
358
- }
359
- }
360
- }
361
-
362
- // Add state management relationships (handlers that modify state)
363
- const stateHandlers: string[] = [];
364
- const queryHandlers: string[] = [];
365
-
366
- for (const [messageType, handlers] of handlersByType) {
367
- const type = messageType.toLowerCase();
368
- const componentId = this.toId(this.toComponentName(messageType));
369
-
370
- if (
371
- type.includes("add") ||
372
- type.includes("create") ||
373
- type.includes("update") ||
374
- type.includes("delete") ||
375
- type.includes("remove") ||
376
- type.includes("toggle") ||
377
- type.includes("clear") ||
378
- type.includes("login") ||
379
- type.includes("logout")
380
- ) {
381
- stateHandlers.push(componentId);
382
- } else if (type.includes("get") || type.includes("fetch") || type.includes("load")) {
383
- queryHandlers.push(componentId);
384
- }
385
- }
386
-
387
- // Create implicit state manager if we have state operations
388
- if (stateHandlers.length > 0 && queryHandlers.length > 0) {
389
- // Query handlers depend on state set by mutation handlers
390
- for (const queryHandler of queryHandlers) {
391
- for (const stateHandler of stateHandlers) {
392
- if (queryHandler !== stateHandler) {
393
- parts.push(` ${stateHandler} -> ${queryHandler} "Updates state" {`);
394
- parts.push(` tags "Implicit"`);
395
- parts.push(` }`);
396
- }
397
- }
398
- }
399
- }
400
-
401
- return parts.join("\n");
402
- }
403
-
404
- /**
405
- * Generate relationships between containers
406
- */
407
- private generateContainerRelationships(): string {
408
- const parts: string[] = [];
409
-
410
- // Add user relationships
411
- const uiContexts = ["popup", "options", "devtools"];
412
- for (const contextType of Object.keys(this.analysis.contexts)) {
413
- if (uiContexts.includes(contextType)) {
414
- parts.push(` user -> extension.${contextType} "Uses"`);
415
- }
416
- }
417
-
418
- // Add message flow relationships
419
- for (const flow of this.analysis.messageFlows) {
420
- const tech = `Sends ${flow.messageType}`;
421
- for (const to of flow.to) {
422
- parts.push(` extension.${flow.from} -> extension.${to} "${tech}"`);
423
- }
424
- }
425
-
426
- // Add external API relationships
427
- for (const integration of this.analysis.integrations) {
428
- if (integration.type === "api" || integration.type === "websocket") {
429
- // Find which contexts use this integration
430
- for (const [contextType, contextInfo] of Object.entries(this.analysis.contexts)) {
431
- const usesIntegration = contextInfo.externalAPIs.some((api: any) =>
432
- integration.calls?.some((call) => call.endpoint === api.endpoint)
433
- );
434
-
435
- if (usesIntegration) {
436
- const method = integration.type === "websocket" ? "Connects to" : "Calls";
437
- parts.push(
438
- ` extension.${contextType} -> ${this.toId(integration.name)} "${method}"`
439
- );
440
- }
441
- }
442
- }
443
- }
444
-
445
- return parts.join("\n");
446
- }
447
-
448
- /**
449
- * Generate views section
450
- */
451
- private generateViews(): string {
452
- const parts: string[] = [];
453
-
454
- parts.push(" views {");
455
- parts.push(this.generateSystemContextView());
456
- parts.push(this.generateContainerView());
457
-
458
- if (this.options.includeComponentDiagrams) {
459
- for (const contextType of this.options.componentDiagramContexts || []) {
460
- if (this.analysis.contexts[contextType]) {
461
- parts.push(this.generateComponentView(contextType));
462
- }
463
- }
464
- }
465
-
466
- if (this.options.includeDynamicDiagrams) {
467
- parts.push(this.generateDynamicViews());
468
- }
469
-
470
- parts.push(this.generateStyles());
471
- parts.push(" }");
472
-
473
- // Add documentation section if ADRs exist
474
- if (this.analysis.adrs && this.analysis.adrs.adrs.length > 0) {
475
- parts.push("");
476
- parts.push(this.generateDocumentation());
477
- }
478
-
479
- return parts.join("\n\n");
480
- }
481
-
482
- /**
483
- * Generate documentation section for ADRs
484
- */
485
- private generateDocumentation(): string {
486
- if (!this.analysis.adrs || this.analysis.adrs.adrs.length === 0) {
487
- return "";
488
- }
489
-
490
- const parts: string[] = [];
491
- parts.push(" documentation {");
492
-
493
- for (const adr of this.analysis.adrs.adrs) {
494
- parts.push(` decision "${adr.id}" {`);
495
- parts.push(` title "${this.escape(adr.title)}"`);
496
- parts.push(` status "${adr.status}"`);
497
- parts.push(` date "${adr.date}"`);
498
- parts.push(` content "${this.escape(adr.context)}"`);
499
- parts.push(" }");
500
- }
501
-
502
- parts.push(" }");
503
-
504
- return parts.join("\n");
505
- }
506
-
507
- /**
508
- * Generate system context view
509
- */
510
- private generateSystemContextView(): string {
511
- return ` systemContext extension "SystemContext" {
512
- include *
513
- autoLayout lr
514
- }`;
515
- }
516
-
517
- /**
518
- * Generate container view
519
- */
520
- private generateContainerView(): string {
521
- return ` container extension "Containers" {
522
- include *
523
- autoLayout lr
524
- }`;
525
- }
526
-
527
- /**
528
- * Generate component view
529
- */
530
- private generateComponentView(contextType: string): string {
531
- return ` component extension.${contextType} "Components_${this.capitalize(contextType)}" {
532
- include *
533
- autoLayout tb
534
- }`;
535
- }
536
-
537
- /**
538
- * Generate dynamic views for message flows
539
- */
540
- private generateDynamicViews(): string {
541
- const parts: string[] = [];
542
-
543
- // Group flows by domain/feature
544
- const flowsByDomain = new Map<string, any[]>();
545
-
546
- for (const flow of this.analysis.messageFlows) {
547
- // Extract domain from message type (e.g., USER_LOGIN -> user, TODO_ADD -> todo)
548
- const messageType = flow.messageType.toLowerCase();
549
- let domain = "general";
550
-
551
- if (
552
- messageType.includes("user") ||
553
- messageType.includes("login") ||
554
- messageType.includes("logout") ||
555
- messageType.includes("auth")
556
- ) {
557
- domain = "authentication";
558
- } else if (messageType.includes("todo")) {
559
- domain = "todo";
560
- } else if (messageType.includes("state")) {
561
- domain = "state";
562
- }
563
-
564
- if (!flowsByDomain.has(domain)) {
565
- flowsByDomain.set(domain, []);
566
- }
567
- flowsByDomain.get(domain)!.push(flow);
568
- }
569
-
570
- // Generate a dynamic view for each domain
571
- let count = 0;
572
- for (const [domain, flows] of flowsByDomain) {
573
- if (count >= 5) break; // Limit to avoid too many diagrams
574
-
575
- const viewName = this.capitalize(domain) + " Flow";
576
- parts.push(this.generateDynamicView(viewName, flows, domain));
577
- count++;
578
- }
579
-
580
- return parts.join("\n\n");
581
- }
582
-
583
- /**
584
- * Generate single dynamic view
585
- */
586
- private generateDynamicView(flowName: string, flows: any[], domain: string): string {
587
- const parts: string[] = [];
588
-
589
- // Create a user-centric description
590
- const description = this.getDynamicViewDescription(domain);
591
- parts.push(` dynamic extension "${flowName}" "${description}" {`);
592
-
593
- // Add user interaction if this is a UI flow
594
- const uiContexts = ["popup", "options", "devtools"];
595
- const hasUIFlow = flows.some((f) => uiContexts.includes(f.from));
596
-
597
- if (hasUIFlow) {
598
- // Start with user interaction
599
- const firstFlow = flows.find((f) => uiContexts.includes(f.from));
600
- if (firstFlow) {
601
- const action = this.getUserAction(domain);
602
- parts.push(` user -> extension.${firstFlow.from} "${action}"`);
603
- }
604
- }
605
-
606
- // Generate message flows
607
- for (const flow of flows) {
608
- const messageDesc = this.getMessageDescription(flow.messageType);
609
-
610
- for (const to of flow.to) {
611
- parts.push(` extension.${flow.from} -> extension.${to} "${messageDesc}"`);
612
- }
613
- }
614
-
615
- parts.push(" autoLayout lr");
616
- parts.push(" }");
617
-
618
- return parts.join("\n");
619
- }
620
-
621
- /**
622
- * Get description for dynamic view based on domain
623
- */
624
- private getDynamicViewDescription(domain: string): string {
625
- const descriptions: Record<string, string> = {
626
- authentication: "User authentication and session management",
627
- todo: "Todo item creation, updates, and retrieval",
628
- state: "Application state synchronization",
629
- general: "Message flow through the system",
630
- };
631
- return descriptions[domain] || descriptions.general;
632
- }
633
-
634
- /**
635
- * Get user action description for domain
636
- */
637
- private getUserAction(domain: string): string {
638
- const actions: Record<string, string> = {
639
- authentication: "Initiates login",
640
- todo: "Manages todo items",
641
- state: "Requests state",
642
- general: "Interacts",
643
- };
644
- return actions[domain] || actions.general;
645
- }
646
-
647
- /**
648
- * Get message description based on type
649
- */
650
- private getMessageDescription(messageType: string): string {
651
- const type = messageType.toLowerCase();
652
-
653
- if (type.includes("login")) return "Authenticate user";
654
- if (type.includes("logout")) return "End session";
655
- if (type.includes("add") || type.includes("create")) return "Create item";
656
- if (type.includes("remove") || type.includes("delete")) return "Delete item";
657
- if (type.includes("update") || type.includes("toggle")) return "Update item";
658
- if (type.includes("get") || type.includes("fetch")) return "Retrieve data";
659
- if (type.includes("clear")) return "Clear items";
660
-
661
- return messageType;
662
- }
663
-
664
- /**
665
- * Generate styles
666
- */
667
- private generateStyles(): string {
668
- const parts: string[] = [];
669
-
670
- // Skip theme directive - causes issues with Structurizr CLI export
671
- // The inline styles are sufficient for diagram generation
672
- parts.push(" styles {");
673
-
674
- // Default styles for containers (contexts)
675
- const contextStyles: Record<string, string> = {
676
- background: "#2E7D32",
677
- content: "#F57C00",
678
- popup: "#1976D2",
679
- devtools: "#7B1FA2",
680
- options: "#0288D1",
681
- ...this.options.styles,
682
- };
683
-
684
- for (const [context, color] of Object.entries(contextStyles)) {
685
- if (this.analysis.contexts[context]) {
686
- parts.push(` element "extension.${context}" {`);
687
- parts.push(` background ${color}`);
688
- parts.push(` color #ffffff`);
689
- parts.push(" }");
690
- }
691
- }
692
-
693
- parts.push(" }");
694
-
695
- return parts.join("\n");
696
- }
697
-
698
- /**
699
- * Get technology label for context
700
- */
701
- private getContextTechnology(contextType: string): string {
702
- const technologies: Record<string, string> = {
703
- background: "Service Worker / Background Script",
704
- content: "Content Script",
705
- popup: "Browser Action Popup",
706
- devtools: "DevTools Panel",
707
- options: "Options Page",
708
- offscreen: "Offscreen Document",
709
- };
710
-
711
- return technologies[contextType] || "Extension Context";
712
- }
713
-
714
- /**
715
- * Convert message type to component name
716
- */
717
- private toComponentName(messageType: string): string {
718
- return (
719
- messageType
720
- .split("_")
721
- .map((part) => this.capitalize(part.toLowerCase()))
722
- .join(" ") + " Handler"
723
- );
724
- }
725
-
726
- /**
727
- * Convert name to identifier
728
- */
729
- private toId(name: string): string {
730
- return name
731
- .toLowerCase()
732
- .replace(/[^a-z0-9]+/g, "_")
733
- .replace(/^_|_$/g, "");
734
- }
735
-
736
- /**
737
- * Convert flow name to view name
738
- */
739
- private toViewName(flowName: string): string {
740
- return flowName
741
- .split(/[_-]/)
742
- .map((part) => this.capitalize(part))
743
- .join(" ");
744
- }
745
-
746
- /**
747
- * Capitalize first letter
748
- */
749
- private capitalize(str: string): string {
750
- return str.charAt(0).toUpperCase() + str.slice(1);
751
- }
752
-
753
- /**
754
- * Escape string for DSL
755
- */
756
- private escape(str: string): string {
757
- return str.replace(/"/g, '\\"');
758
- }
759
- }
760
-
761
- /**
762
- * Generate Structurizr DSL from architecture analysis
763
- */
764
- export function generateStructurizrDSL(
765
- analysis: ArchitectureAnalysis,
766
- options?: StructurizrDSLOptions
767
- ): string {
768
- const generator = new StructurizrDSLGenerator(analysis, options);
769
- return generator.generate();
770
- }