@fairfox/polly 0.1.0 → 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.
Files changed (40) hide show
  1. package/README.md +9 -9
  2. package/cli/polly.ts +9 -3
  3. package/package.json +2 -2
  4. package/vendor/analysis/src/extract/adr.ts +212 -0
  5. package/vendor/analysis/src/extract/architecture.ts +160 -0
  6. package/vendor/analysis/src/extract/contexts.ts +298 -0
  7. package/vendor/analysis/src/extract/flows.ts +309 -0
  8. package/vendor/analysis/src/extract/handlers.ts +321 -0
  9. package/vendor/analysis/src/extract/index.ts +9 -0
  10. package/vendor/analysis/src/extract/integrations.ts +329 -0
  11. package/vendor/analysis/src/extract/manifest.ts +298 -0
  12. package/vendor/analysis/src/extract/types.ts +389 -0
  13. package/vendor/analysis/src/index.ts +7 -0
  14. package/vendor/analysis/src/types/adr.ts +53 -0
  15. package/vendor/analysis/src/types/architecture.ts +245 -0
  16. package/vendor/analysis/src/types/core.ts +210 -0
  17. package/vendor/analysis/src/types/index.ts +18 -0
  18. package/vendor/verify/src/adapters/base.ts +164 -0
  19. package/vendor/verify/src/adapters/detection.ts +281 -0
  20. package/vendor/verify/src/adapters/event-bus/index.ts +480 -0
  21. package/vendor/verify/src/adapters/web-extension/index.ts +508 -0
  22. package/vendor/verify/src/adapters/websocket/index.ts +486 -0
  23. package/vendor/verify/src/cli.ts +430 -0
  24. package/vendor/verify/src/codegen/config.ts +354 -0
  25. package/vendor/verify/src/codegen/tla.ts +719 -0
  26. package/vendor/verify/src/config/parser.ts +303 -0
  27. package/vendor/verify/src/config/types.ts +113 -0
  28. package/vendor/verify/src/core/model.ts +267 -0
  29. package/vendor/verify/src/core/primitives.ts +106 -0
  30. package/vendor/verify/src/extract/handlers.ts +2 -0
  31. package/vendor/verify/src/extract/types.ts +2 -0
  32. package/vendor/verify/src/index.ts +150 -0
  33. package/vendor/verify/src/primitives/index.ts +102 -0
  34. package/vendor/verify/src/runner/docker.ts +283 -0
  35. package/vendor/verify/src/types.ts +51 -0
  36. package/vendor/visualize/src/cli.ts +365 -0
  37. package/vendor/visualize/src/codegen/structurizr.ts +770 -0
  38. package/vendor/visualize/src/index.ts +13 -0
  39. package/vendor/visualize/src/runner/export.ts +235 -0
  40. package/vendor/visualize/src/viewer/server.ts +485 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @fairfox/web-ext
1
+ # @fairfox/polly
2
2
 
3
3
  **Build Chrome extensions with reactive state and zero boilerplate.**
4
4
 
@@ -36,16 +36,16 @@ This framework fixes all of that:
36
36
 
37
37
  ```bash
38
38
  # Using Bun (recommended)
39
- bun add @fairfox/web-ext
39
+ bun add @fairfox/polly
40
40
 
41
41
  # Using npm
42
- npm install @fairfox/web-ext
42
+ npm install @fairfox/polly
43
43
 
44
44
  # Using pnpm
45
- pnpm add @fairfox/web-ext
45
+ pnpm add @fairfox/polly
46
46
 
47
47
  # Using yarn
48
- yarn add @fairfox/web-ext
48
+ yarn add @fairfox/polly
49
49
  ```
50
50
 
51
51
  ### Create Extension
@@ -54,7 +54,7 @@ yarn add @fairfox/web-ext
54
54
 
55
55
  ```typescript
56
56
  // src/shared/state.ts
57
- import { $sharedState } from '@fairfox/web-ext/state'
57
+ import { $sharedState } from '@fairfox/polly/state'
58
58
 
59
59
  export const counter = $sharedState('counter', 0)
60
60
  export const settings = $sharedState('settings', { theme: 'dark' })
@@ -83,7 +83,7 @@ render(<Popup />, document.getElementById('root')!)
83
83
 
84
84
  ```typescript
85
85
  // src/background/index.ts
86
- import { createBackground } from '@fairfox/web-ext/background'
86
+ import { createBackground } from '@fairfox/polly/background'
87
87
 
88
88
  const bus = createBackground()
89
89
  ```
@@ -95,7 +95,7 @@ const bus = createBackground()
95
95
  **4. Build and load**:
96
96
 
97
97
  ```bash
98
- bunx web-ext build
98
+ polly build
99
99
  ```
100
100
 
101
101
  Load `dist/` folder in Chrome → **Done!** 🎉
@@ -319,4 +319,4 @@ MIT © 2024
319
319
 
320
320
  ---
321
321
 
322
- **[View Examples](./examples/)** · **[Read Docs](./docs/)** · **[Report Issue](https://github.com/fairfox/web-ext/issues)**
322
+ **[View Examples](https://github.com/AlexJeffcott/polly/tree/main/packages/examples)** · **[Read Docs](https://github.com/AlexJeffcott/polly/tree/main/packages/polly/docs)** · **[Report Issue](https://github.com/AlexJeffcott/polly/issues)**
package/cli/polly.ts CHANGED
@@ -111,7 +111,10 @@ async function dev() {
111
111
  * Verify command - delegate to @fairfox/web-ext-verify
112
112
  */
113
113
  async function verify() {
114
- const verifyCli = `${__dirname}/../../verify/src/cli.ts`;
114
+ // Try vendor directory first (published package), then monorepo
115
+ const vendorCli = `${__dirname}/../vendor/verify/src/cli.ts`;
116
+ const monorepoCli = `${__dirname}/../../verify/src/cli.ts`;
117
+ const verifyCli = (await Bun.file(vendorCli).exists()) ? vendorCli : monorepoCli;
115
118
 
116
119
  const proc = Bun.spawn(["bun", verifyCli, ...commandArgs], {
117
120
  cwd,
@@ -127,10 +130,13 @@ async function verify() {
127
130
  }
128
131
 
129
132
  /**
130
- * Visualize command - delegate to @fairfox/web-ext-visualize
133
+ * Visualize command - delegate to @fairfox/polly-visualize
131
134
  */
132
135
  async function visualize() {
133
- const visualizeCli = `${__dirname}/../../visualize/src/cli.ts`;
136
+ // Try vendor directory first (published package), then monorepo
137
+ const vendorCli = `${__dirname}/../vendor/visualize/src/cli.ts`;
138
+ const monorepoCli = `${__dirname}/../../visualize/src/cli.ts`;
139
+ const visualizeCli = (await Bun.file(vendorCli).exists()) ? vendorCli : monorepoCli;
134
140
 
135
141
  const proc = Bun.spawn(["bun", visualizeCli, ...commandArgs], {
136
142
  cwd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fairfox/polly",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Multi-execution-context framework with reactive state and cross-context messaging for Chrome extensions, PWAs, and worker-based applications",
@@ -44,7 +44,7 @@
44
44
  "types": "./dist/shared/types/messages.d.ts"
45
45
  }
46
46
  },
47
- "files": ["dist", "README.md", "LICENSE"],
47
+ "files": ["dist", "cli", "vendor", "README.md", "LICENSE"],
48
48
  "scripts": {
49
49
  "dev": "bun run build.ts --watch",
50
50
  "build": "bun run build.ts",
@@ -0,0 +1,212 @@
1
+ // ADR (Architecture Decision Record) extractor
2
+ // Parses ADRs from markdown files following Michael Nygard's format
3
+
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import type { ADR, ADRStatus, ADRLink, ADRCollection } from "../types/adr";
7
+
8
+ export class ADRExtractor {
9
+ private projectRoot: string;
10
+
11
+ constructor(projectRoot: string) {
12
+ this.projectRoot = projectRoot;
13
+ }
14
+
15
+ /**
16
+ * Extract ADRs from docs/adr directory
17
+ */
18
+ extract(): ADRCollection {
19
+ const adrDir = this.findADRDirectory();
20
+
21
+ if (!adrDir || !fs.existsSync(adrDir)) {
22
+ return {
23
+ adrs: [],
24
+ directory: adrDir || path.join(this.projectRoot, "docs", "adr"),
25
+ };
26
+ }
27
+
28
+ const files = fs
29
+ .readdirSync(adrDir)
30
+ .filter((file) => file.endsWith(".md"))
31
+ .map((file) => path.join(adrDir, file));
32
+
33
+ const adrs: ADR[] = [];
34
+
35
+ for (const file of files) {
36
+ try {
37
+ const adr = this.parseADR(file);
38
+ if (adr) {
39
+ adrs.push(adr);
40
+ }
41
+ } catch (error) {
42
+ console.warn(`Failed to parse ADR: ${file}`, error);
43
+ }
44
+ }
45
+
46
+ // Sort by ID
47
+ adrs.sort((a, b) => a.id.localeCompare(b.id));
48
+
49
+ return {
50
+ adrs,
51
+ directory: adrDir,
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Find ADR directory
57
+ */
58
+ private findADRDirectory(): string | null {
59
+ const candidates = [
60
+ path.join(this.projectRoot, "docs", "adr"),
61
+ path.join(this.projectRoot, "docs", "architecture", "decisions"),
62
+ path.join(this.projectRoot, "adr"),
63
+ path.join(this.projectRoot, "architecture", "decisions"),
64
+ ];
65
+
66
+ for (const candidate of candidates) {
67
+ if (fs.existsSync(candidate)) {
68
+ return candidate;
69
+ }
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * Parse ADR from markdown file
77
+ */
78
+ private parseADR(filePath: string): ADR | null {
79
+ const content = fs.readFileSync(filePath, "utf-8");
80
+ const fileName = path.basename(filePath, ".md");
81
+
82
+ // Extract ID from filename (e.g., "0001-use-preact.md" → "0001")
83
+ const idMatch = fileName.match(/^(\d+)/);
84
+ const id = idMatch ? idMatch[1] : fileName;
85
+
86
+ // Extract title from first heading
87
+ const titleMatch = content.match(/^#\s+(.+)$/m);
88
+ const title = titleMatch ? titleMatch[1].trim() : fileName;
89
+
90
+ // Extract status
91
+ const status = this.extractStatus(content);
92
+
93
+ // Extract date
94
+ const date = this.extractDate(content);
95
+
96
+ // Extract sections
97
+ const context = this.extractSection(content, "Context");
98
+ const decision = this.extractSection(content, "Decision");
99
+ const consequences = this.extractSection(content, "Consequences");
100
+
101
+ // Extract alternatives (if present)
102
+ const alternativesSection = this.extractSection(content, "Alternatives");
103
+ const alternatives = alternativesSection
104
+ ? alternativesSection
105
+ .split("\n")
106
+ .filter((line) => line.trim().startsWith("-"))
107
+ .map((line) => line.replace(/^-\s*/, "").trim())
108
+ : undefined;
109
+
110
+ // Extract links
111
+ const links = this.extractLinks(content);
112
+
113
+ if (!context || !decision || !consequences) {
114
+ // Not a valid ADR format
115
+ return null;
116
+ }
117
+
118
+ return {
119
+ id,
120
+ title,
121
+ status,
122
+ date,
123
+ context,
124
+ decision,
125
+ consequences,
126
+ alternatives,
127
+ links,
128
+ source: filePath,
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Extract status from content
134
+ */
135
+ private extractStatus(content: string): ADRStatus {
136
+ const statusMatch = content.match(/Status:\s*(\w+)/i);
137
+ if (!statusMatch) return "accepted";
138
+
139
+ const status = statusMatch[1].toLowerCase();
140
+ if (["proposed", "accepted", "deprecated", "superseded"].includes(status)) {
141
+ return status as ADRStatus;
142
+ }
143
+
144
+ return "accepted";
145
+ }
146
+
147
+ /**
148
+ * Extract date from content
149
+ */
150
+ private extractDate(content: string): string {
151
+ // Try to find date in various formats
152
+ const dateMatch =
153
+ content.match(/Date:\s*(\d{4}-\d{2}-\d{2})/i) || content.match(/(\d{4}-\d{2}-\d{2})/i);
154
+
155
+ return dateMatch ? dateMatch[1] : new Date().toISOString().split("T")[0];
156
+ }
157
+
158
+ /**
159
+ * Extract section content
160
+ */
161
+ private extractSection(content: string, sectionName: string): string {
162
+ // Match section heading and capture content until next heading or end
163
+ const regex = new RegExp(`##\\s+${sectionName}\\s*\\n([\\s\\S]*?)(?=\\n##|$)`, "i");
164
+ const match = content.match(regex);
165
+
166
+ return match ? match[1].trim() : "";
167
+ }
168
+
169
+ /**
170
+ * Extract links to other ADRs
171
+ */
172
+ private extractLinks(content: string): ADRLink[] {
173
+ const links: ADRLink[] = [];
174
+
175
+ // Look for supersedes/superseded-by links
176
+ const supersedesMatch = content.match(/Supersedes:\s*ADR-(\d+)/gi);
177
+ if (supersedesMatch) {
178
+ for (const match of supersedesMatch) {
179
+ const idMatch = match.match(/ADR-(\d+)/);
180
+ if (idMatch) {
181
+ links.push({
182
+ type: "supersedes",
183
+ adrId: idMatch[1],
184
+ });
185
+ }
186
+ }
187
+ }
188
+
189
+ const supersededByMatch = content.match(/Superseded by:\s*ADR-(\d+)/gi);
190
+ if (supersededByMatch) {
191
+ for (const match of supersededByMatch) {
192
+ const idMatch = match.match(/ADR-(\d+)/);
193
+ if (idMatch) {
194
+ links.push({
195
+ type: "superseded-by",
196
+ adrId: idMatch[1],
197
+ });
198
+ }
199
+ }
200
+ }
201
+
202
+ return links;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Extract ADRs from project
208
+ */
209
+ export function extractADRs(projectRoot: string): ADRCollection {
210
+ const extractor = new ADRExtractor(projectRoot);
211
+ return extractor.extract();
212
+ }
@@ -0,0 +1,160 @@
1
+ // Architecture analysis - main orchestrator
2
+
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import type { ArchitectureAnalysis, ContextInfo } from "../types/architecture";
6
+ import { ManifestParser } from "./manifest";
7
+ import { ContextAnalyzer } from "./contexts";
8
+ import { FlowAnalyzer } from "./flows";
9
+ import { IntegrationAnalyzer } from "./integrations";
10
+ import { HandlerExtractor } from "./handlers";
11
+ import { extractADRs } from "./adr";
12
+
13
+ export interface ArchitectureAnalysisOptions {
14
+ /** Path to tsconfig.json */
15
+ tsConfigPath: string;
16
+
17
+ /** Project root directory (where manifest.json is) */
18
+ projectRoot: string;
19
+
20
+ /** Optional: Override detected contexts */
21
+ contexts?: string[];
22
+ }
23
+
24
+ /**
25
+ * Comprehensive architecture analyzer
26
+ */
27
+ export class ArchitectureAnalyzer {
28
+ private options: ArchitectureAnalysisOptions;
29
+
30
+ constructor(options: ArchitectureAnalysisOptions) {
31
+ this.options = options;
32
+ }
33
+
34
+ /**
35
+ * Perform complete architecture analysis
36
+ */
37
+ async analyze(): Promise<ArchitectureAnalysis> {
38
+ // 1. Parse manifest.json
39
+ const manifestParser = new ManifestParser(this.options.projectRoot);
40
+ const manifest = manifestParser.parse();
41
+ const entryPoints = manifestParser.getContextEntryPoints();
42
+
43
+ // 2. Extract message handlers
44
+ const handlerExtractor = new HandlerExtractor(this.options.tsConfigPath);
45
+ const { handlers } = handlerExtractor.extractHandlers();
46
+
47
+ // 3. Analyze each context
48
+ const contextAnalyzer = new ContextAnalyzer(this.options.tsConfigPath);
49
+ const contexts: Record<string, ContextInfo> = {};
50
+
51
+ for (const [contextType, entryPoint] of Object.entries(entryPoints)) {
52
+ try {
53
+ const contextInfo = contextAnalyzer.analyzeContext(contextType, entryPoint, handlers);
54
+ contexts[contextType] = contextInfo;
55
+ } catch (error) {
56
+ console.warn(`Failed to analyze context ${contextType}: ${error}`);
57
+ }
58
+ }
59
+
60
+ // 4. Analyze message flows
61
+ const flowAnalyzer = new FlowAnalyzer(this.options.tsConfigPath, handlers);
62
+ const messageFlows = flowAnalyzer.analyzeFlows();
63
+
64
+ // 5. Analyze external integrations
65
+ const integrationAnalyzer = new IntegrationAnalyzer(this.options.tsConfigPath);
66
+ const integrations = integrationAnalyzer.analyzeIntegrations();
67
+
68
+ // 6. Merge external API calls into context info
69
+ this.mergeExternalAPIsIntoContexts(contexts, integrations);
70
+
71
+ // 7. Extract ADRs (Architecture Decision Records)
72
+ const adrs = extractADRs(this.options.projectRoot);
73
+
74
+ // 8. Extract repository info
75
+ const repository = this.extractRepositoryInfo();
76
+
77
+ return {
78
+ system: {
79
+ name: manifest.name,
80
+ version: manifest.version,
81
+ description: manifest.description,
82
+ },
83
+ manifest,
84
+ contexts,
85
+ messageFlows,
86
+ integrations,
87
+ adrs: adrs.adrs.length > 0 ? adrs : undefined,
88
+ repository,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Merge external API calls into their respective contexts
94
+ */
95
+ private mergeExternalAPIsIntoContexts(
96
+ contexts: Record<string, ContextInfo>,
97
+ integrations: ExternalIntegration[]
98
+ ): void {
99
+ for (const integration of integrations) {
100
+ if (integration.calls) {
101
+ for (const call of integration.calls) {
102
+ // Find which context this call belongs to
103
+ for (const [contextType, contextInfo] of Object.entries(contexts)) {
104
+ if (call.location.file.includes(`/${contextType}/`)) {
105
+ contextInfo.externalAPIs.push(call);
106
+ break;
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Extract repository information from package.json
116
+ */
117
+ private extractRepositoryInfo(): ArchitectureAnalysis["repository"] {
118
+ const packageJsonPath = path.join(this.options.projectRoot, "package.json");
119
+
120
+ if (!fs.existsSync(packageJsonPath)) {
121
+ return undefined;
122
+ }
123
+
124
+ try {
125
+ const content = fs.readFileSync(packageJsonPath, "utf-8");
126
+ const packageJson = JSON.parse(content);
127
+
128
+ if (packageJson.repository) {
129
+ if (typeof packageJson.repository === "string") {
130
+ return {
131
+ url: packageJson.repository,
132
+ type: "git",
133
+ };
134
+ }
135
+
136
+ return {
137
+ url: packageJson.repository.url || packageJson.repository,
138
+ type: packageJson.repository.type || "git",
139
+ };
140
+ }
141
+ } catch (error) {
142
+ console.warn(`Failed to parse package.json: ${error}`);
143
+ }
144
+
145
+ return undefined;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Convenience function to analyze architecture
151
+ */
152
+ export async function analyzeArchitecture(
153
+ options: ArchitectureAnalysisOptions
154
+ ): Promise<ArchitectureAnalysis> {
155
+ const analyzer = new ArchitectureAnalyzer(options);
156
+ return analyzer.analyze();
157
+ }
158
+
159
+ // Fix: Import ExternalIntegration type
160
+ import type { ExternalIntegration } from "../types/architecture";