@fairfox/polly 0.1.3 → 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.
- package/{cli/polly.ts → dist/cli/polly.js} +100 -206
- package/dist/cli/polly.js.map +10 -0
- package/dist/scripts/build-extension.js +137 -0
- package/dist/scripts/build-extension.js.map +10 -0
- package/dist/vendor/verify/src/cli.js +2089 -0
- package/dist/vendor/verify/src/cli.js.map +16 -0
- package/dist/vendor/visualize/src/cli.js +2204 -0
- package/dist/vendor/visualize/src/cli.js.map +19 -0
- package/package.json +12 -12
- package/vendor/analysis/src/extract/adr.ts +0 -212
- package/vendor/analysis/src/extract/architecture.ts +0 -160
- package/vendor/analysis/src/extract/contexts.ts +0 -298
- package/vendor/analysis/src/extract/flows.ts +0 -309
- package/vendor/analysis/src/extract/handlers.ts +0 -321
- package/vendor/analysis/src/extract/index.ts +0 -9
- package/vendor/analysis/src/extract/integrations.ts +0 -329
- package/vendor/analysis/src/extract/manifest.ts +0 -298
- package/vendor/analysis/src/extract/types.ts +0 -389
- package/vendor/analysis/src/index.ts +0 -7
- package/vendor/analysis/src/types/adr.ts +0 -53
- package/vendor/analysis/src/types/architecture.ts +0 -245
- package/vendor/analysis/src/types/core.ts +0 -210
- package/vendor/analysis/src/types/index.ts +0 -18
- package/vendor/verify/src/adapters/base.ts +0 -164
- package/vendor/verify/src/adapters/detection.ts +0 -281
- package/vendor/verify/src/adapters/event-bus/index.ts +0 -480
- package/vendor/verify/src/adapters/web-extension/index.ts +0 -508
- package/vendor/verify/src/adapters/websocket/index.ts +0 -486
- package/vendor/verify/src/cli.ts +0 -430
- package/vendor/verify/src/codegen/config.ts +0 -354
- package/vendor/verify/src/codegen/tla.ts +0 -719
- package/vendor/verify/src/config/parser.ts +0 -303
- package/vendor/verify/src/config/types.ts +0 -113
- package/vendor/verify/src/core/model.ts +0 -267
- package/vendor/verify/src/core/primitives.ts +0 -106
- package/vendor/verify/src/extract/handlers.ts +0 -2
- package/vendor/verify/src/extract/types.ts +0 -2
- package/vendor/verify/src/index.ts +0 -150
- package/vendor/verify/src/primitives/index.ts +0 -102
- package/vendor/verify/src/runner/docker.ts +0 -283
- package/vendor/verify/src/types.ts +0 -51
- package/vendor/visualize/src/cli.ts +0 -365
- package/vendor/visualize/src/codegen/structurizr.ts +0 -770
- package/vendor/visualize/src/index.ts +0 -13
- package/vendor/visualize/src/runner/export.ts +0 -235
- package/vendor/visualize/src/viewer/server.ts +0 -485
- /package/dist/{background → src/background}/index.js +0 -0
- /package/dist/{background → src/background}/index.js.map +0 -0
- /package/dist/{background → src/background}/message-router.js +0 -0
- /package/dist/{background → src/background}/message-router.js.map +0 -0
- /package/dist/{index.js → src/index.js} +0 -0
- /package/dist/{index.js.map → src/index.js.map} +0 -0
- /package/dist/{shared → src/shared}/adapters/index.js +0 -0
- /package/dist/{shared → src/shared}/adapters/index.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/context-helpers.js +0 -0
- /package/dist/{shared → src/shared}/lib/context-helpers.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/errors.js +0 -0
- /package/dist/{shared → src/shared}/lib/errors.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/message-bus.js +0 -0
- /package/dist/{shared → src/shared}/lib/message-bus.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/state.js +0 -0
- /package/dist/{shared → src/shared}/lib/state.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/test-helpers.js +0 -0
- /package/dist/{shared → src/shared}/lib/test-helpers.js.map +0 -0
- /package/dist/{shared → src/shared}/state/app-state.js +0 -0
- /package/dist/{shared → src/shared}/state/app-state.js.map +0 -0
- /package/dist/{shared → src/shared}/types/messages.js +0 -0
- /package/dist/{shared → src/shared}/types/messages.js.map +0 -0
|
@@ -1,212 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
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";
|
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
// Context analysis - analyze individual execution contexts
|
|
2
|
-
|
|
3
|
-
import { Project, Node, SyntaxKind } from "ts-morph";
|
|
4
|
-
import type { ContextInfo, ComponentInfo } from "../types/architecture";
|
|
5
|
-
import type { MessageHandler } from "../types/core";
|
|
6
|
-
|
|
7
|
-
export class ContextAnalyzer {
|
|
8
|
-
private project: Project;
|
|
9
|
-
|
|
10
|
-
constructor(tsConfigPath: string) {
|
|
11
|
-
this.project = new Project({
|
|
12
|
-
tsConfigFilePath: tsConfigPath,
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Analyze a specific context given its entry point
|
|
18
|
-
*/
|
|
19
|
-
analyzeContext(contextType: string, entryPoint: string, handlers: MessageHandler[]): ContextInfo {
|
|
20
|
-
const sourceFile = this.project.getSourceFile(entryPoint);
|
|
21
|
-
|
|
22
|
-
if (!sourceFile) {
|
|
23
|
-
throw new Error(`Could not find source file: ${entryPoint}`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Extract Chrome API usage
|
|
27
|
-
const chromeAPIs = this.extractChromeAPIs(sourceFile);
|
|
28
|
-
|
|
29
|
-
// Extract dependencies
|
|
30
|
-
const dependencies = this.extractDependencies(sourceFile);
|
|
31
|
-
|
|
32
|
-
// Extract JSDoc description
|
|
33
|
-
const description = this.extractDescription(sourceFile);
|
|
34
|
-
|
|
35
|
-
// Extract components (for UI contexts)
|
|
36
|
-
const components = this.isUIContext(contextType)
|
|
37
|
-
? this.extractComponents(sourceFile)
|
|
38
|
-
: undefined;
|
|
39
|
-
|
|
40
|
-
// Filter handlers for this context
|
|
41
|
-
const contextHandlers = handlers.filter((h) => h.node === contextType);
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
type: contextType,
|
|
45
|
-
entryPoint,
|
|
46
|
-
handlers: contextHandlers,
|
|
47
|
-
chromeAPIs,
|
|
48
|
-
externalAPIs: [], // Will be filled by integration analyzer
|
|
49
|
-
components,
|
|
50
|
-
dependencies,
|
|
51
|
-
description,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Extract Chrome API usage from source file
|
|
57
|
-
*/
|
|
58
|
-
private extractChromeAPIs(sourceFile: any): string[] {
|
|
59
|
-
const apis = new Set<string>();
|
|
60
|
-
|
|
61
|
-
sourceFile.forEachDescendant((node: any) => {
|
|
62
|
-
if (Node.isPropertyAccessExpression(node)) {
|
|
63
|
-
const text = node.getText();
|
|
64
|
-
|
|
65
|
-
// Match chrome.* API calls
|
|
66
|
-
if (text.startsWith("chrome.")) {
|
|
67
|
-
// Extract API namespace (e.g., "chrome.tabs", "chrome.storage.local")
|
|
68
|
-
const match = text.match(/^chrome\.([^.(]+(?:\.[^.(]+)?)/);
|
|
69
|
-
if (match) {
|
|
70
|
-
apis.add(match[1]);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Also match browser.* for Firefox compatibility
|
|
75
|
-
if (text.startsWith("browser.")) {
|
|
76
|
-
const match = text.match(/^browser\.([^.(]+(?:\.[^.(]+)?)/);
|
|
77
|
-
if (match) {
|
|
78
|
-
apis.add(match[1]);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Match bus.adapters.* pattern (framework abstraction over Chrome APIs)
|
|
83
|
-
// e.g., bus.adapters.storage.get() -> storage
|
|
84
|
-
// bus.adapters.tabs.query() -> tabs
|
|
85
|
-
if (text.includes("bus.adapters.")) {
|
|
86
|
-
const match = text.match(/bus\.adapters\.([^.(]+)/);
|
|
87
|
-
if (match) {
|
|
88
|
-
apis.add(match[1]);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
return Array.from(apis).sort();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Extract import dependencies
|
|
99
|
-
*/
|
|
100
|
-
private extractDependencies(sourceFile: any): string[] {
|
|
101
|
-
const deps: string[] = [];
|
|
102
|
-
|
|
103
|
-
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
104
|
-
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
105
|
-
deps.push(moduleSpecifier);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return deps;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Extract JSDoc description from file
|
|
113
|
-
*/
|
|
114
|
-
private extractDescription(sourceFile: any): string | undefined {
|
|
115
|
-
// Look for file-level JSDoc comment
|
|
116
|
-
const firstStatement = sourceFile.getStatements()[0];
|
|
117
|
-
if (!firstStatement) return undefined;
|
|
118
|
-
|
|
119
|
-
const leadingComments = firstStatement.getLeadingCommentRanges();
|
|
120
|
-
if (leadingComments.length === 0) return undefined;
|
|
121
|
-
|
|
122
|
-
const comment = leadingComments[0].getText();
|
|
123
|
-
|
|
124
|
-
// Extract description from JSDoc
|
|
125
|
-
const descMatch = comment.match(/@description\s+(.+?)(?:\n|$)/s);
|
|
126
|
-
if (descMatch) {
|
|
127
|
-
return descMatch[1].trim();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Or just take the first line of the comment
|
|
131
|
-
const lines = comment
|
|
132
|
-
.split("\n")
|
|
133
|
-
.map((l: string) => l.replace(/^[\s*]+/, "").trim())
|
|
134
|
-
.filter((l: string) => l && !l.startsWith("@"));
|
|
135
|
-
|
|
136
|
-
return lines[0] || undefined;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Extract React/Preact components from source file
|
|
141
|
-
*/
|
|
142
|
-
private extractComponents(sourceFile: any): ComponentInfo[] {
|
|
143
|
-
const components: ComponentInfo[] = [];
|
|
144
|
-
|
|
145
|
-
sourceFile.forEachDescendant((node: any) => {
|
|
146
|
-
// Function components
|
|
147
|
-
if (Node.isFunctionDeclaration(node)) {
|
|
148
|
-
const name = node.getName();
|
|
149
|
-
if (name && this.looksLikeComponent(name, node)) {
|
|
150
|
-
components.push({
|
|
151
|
-
name,
|
|
152
|
-
type: "function",
|
|
153
|
-
filePath: sourceFile.getFilePath(),
|
|
154
|
-
line: node.getStartLineNumber(),
|
|
155
|
-
props: this.extractProps(node),
|
|
156
|
-
description: this.extractJSDocDescription(node),
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Arrow function components (const Foo = () => ...)
|
|
162
|
-
if (Node.isVariableDeclaration(node)) {
|
|
163
|
-
const name = node.getName();
|
|
164
|
-
const initializer = node.getInitializer();
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
name &&
|
|
168
|
-
initializer &&
|
|
169
|
-
(Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer))
|
|
170
|
-
) {
|
|
171
|
-
if (this.looksLikeComponent(name, initializer)) {
|
|
172
|
-
components.push({
|
|
173
|
-
name,
|
|
174
|
-
type: "function",
|
|
175
|
-
filePath: sourceFile.getFilePath(),
|
|
176
|
-
line: node.getStartLineNumber(),
|
|
177
|
-
props: this.extractProps(initializer),
|
|
178
|
-
description: this.extractJSDocDescription(node),
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Class components
|
|
185
|
-
if (Node.isClassDeclaration(node)) {
|
|
186
|
-
const name = node.getName();
|
|
187
|
-
if (name && this.looksLikeClassComponent(node)) {
|
|
188
|
-
components.push({
|
|
189
|
-
name,
|
|
190
|
-
type: "class",
|
|
191
|
-
filePath: sourceFile.getFilePath(),
|
|
192
|
-
line: node.getStartLineNumber(),
|
|
193
|
-
props: this.extractPropsFromClass(node),
|
|
194
|
-
description: this.extractJSDocDescription(node),
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
return components;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Check if a function looks like a React/Preact component
|
|
205
|
-
*/
|
|
206
|
-
private looksLikeComponent(name: string, node: any): boolean {
|
|
207
|
-
// Component names should start with uppercase
|
|
208
|
-
if (!/^[A-Z]/.test(name)) return false;
|
|
209
|
-
|
|
210
|
-
// Check if it returns JSX
|
|
211
|
-
const body = node.getBody();
|
|
212
|
-
if (!body) return false;
|
|
213
|
-
|
|
214
|
-
let hasJSX = false;
|
|
215
|
-
|
|
216
|
-
if (Node.isBlock(body)) {
|
|
217
|
-
body.forEachDescendant((child: any) => {
|
|
218
|
-
if (Node.isJsxElement(child) || Node.isJsxSelfClosingElement(child)) {
|
|
219
|
-
hasJSX = true;
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
} else {
|
|
223
|
-
// Arrow function with implicit return
|
|
224
|
-
if (Node.isJsxElement(body) || Node.isJsxSelfClosingElement(body)) {
|
|
225
|
-
hasJSX = true;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return hasJSX;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Check if a class looks like a React component
|
|
234
|
-
*/
|
|
235
|
-
private looksLikeClassComponent(node: any): boolean {
|
|
236
|
-
const extendedTypes = node.getExtends();
|
|
237
|
-
if (!extendedTypes) return false;
|
|
238
|
-
|
|
239
|
-
const extendsText = extendedTypes.getText();
|
|
240
|
-
return /Component|PureComponent/.test(extendsText);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Extract props from function component
|
|
245
|
-
*/
|
|
246
|
-
private extractProps(node: any): string[] {
|
|
247
|
-
const params = node.getParameters();
|
|
248
|
-
if (params.length === 0) return [];
|
|
249
|
-
|
|
250
|
-
const propsParam = params[0];
|
|
251
|
-
const type = propsParam.getType();
|
|
252
|
-
|
|
253
|
-
const props: string[] = [];
|
|
254
|
-
for (const prop of type.getProperties()) {
|
|
255
|
-
props.push(prop.getName());
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return props;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Extract props from class component
|
|
263
|
-
*/
|
|
264
|
-
private extractPropsFromClass(node: any): string[] {
|
|
265
|
-
const extendedTypes = node.getExtends();
|
|
266
|
-
if (!extendedTypes) return [];
|
|
267
|
-
|
|
268
|
-
const typeArgs = extendedTypes.getType().getTypeArguments();
|
|
269
|
-
if (typeArgs.length === 0) return [];
|
|
270
|
-
|
|
271
|
-
const propsType = typeArgs[0];
|
|
272
|
-
const props: string[] = [];
|
|
273
|
-
|
|
274
|
-
for (const prop of propsType.getProperties()) {
|
|
275
|
-
props.push(prop.getName());
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return props;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Extract JSDoc description from node
|
|
283
|
-
*/
|
|
284
|
-
private extractJSDocDescription(node: any): string | undefined {
|
|
285
|
-
const jsDocs = node.getJsDocs();
|
|
286
|
-
if (jsDocs.length === 0) return undefined;
|
|
287
|
-
|
|
288
|
-
const description = jsDocs[0].getDescription().trim();
|
|
289
|
-
return description || undefined;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Check if context is a UI context
|
|
294
|
-
*/
|
|
295
|
-
private isUIContext(contextType: string): boolean {
|
|
296
|
-
return ["popup", "options", "devtools"].includes(contextType);
|
|
297
|
-
}
|
|
298
|
-
}
|