@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,329 +0,0 @@
|
|
|
1
|
-
// External integration detection - find external APIs, services, etc.
|
|
2
|
-
|
|
3
|
-
import { Project, Node } from "ts-morph";
|
|
4
|
-
import type { ExternalIntegration, ExternalAPICall } from "../types/architecture";
|
|
5
|
-
|
|
6
|
-
export class IntegrationAnalyzer {
|
|
7
|
-
private project: Project;
|
|
8
|
-
|
|
9
|
-
constructor(tsConfigPath: string) {
|
|
10
|
-
this.project = new Project({
|
|
11
|
-
tsConfigFilePath: tsConfigPath,
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Analyze external integrations in the codebase
|
|
17
|
-
*/
|
|
18
|
-
analyzeIntegrations(): ExternalIntegration[] {
|
|
19
|
-
const integrations = new Map<string, ExternalIntegration>();
|
|
20
|
-
|
|
21
|
-
// Find all fetch() calls
|
|
22
|
-
const fetchCalls = this.findFetchCalls();
|
|
23
|
-
for (const call of fetchCalls) {
|
|
24
|
-
this.addOrMergeIntegration(integrations, this.createAPIIntegration(call));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Find WebSocket connections
|
|
28
|
-
const websockets = this.findWebSockets();
|
|
29
|
-
for (const ws of websockets) {
|
|
30
|
-
this.addOrMergeIntegration(integrations, this.createWebSocketIntegration(ws));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Find external script imports
|
|
34
|
-
const externalScripts = this.findExternalScripts();
|
|
35
|
-
for (const script of externalScripts) {
|
|
36
|
-
this.addOrMergeIntegration(integrations, script);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return Array.from(integrations.values());
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Find all fetch() API calls
|
|
44
|
-
*/
|
|
45
|
-
private findFetchCalls(): Array<{
|
|
46
|
-
url: string;
|
|
47
|
-
method: string;
|
|
48
|
-
file: string;
|
|
49
|
-
line: number;
|
|
50
|
-
description?: string;
|
|
51
|
-
}> {
|
|
52
|
-
const calls: Array<{
|
|
53
|
-
url: string;
|
|
54
|
-
method: string;
|
|
55
|
-
file: string;
|
|
56
|
-
line: number;
|
|
57
|
-
description?: string;
|
|
58
|
-
}> = [];
|
|
59
|
-
|
|
60
|
-
for (const sourceFile of this.project.getSourceFiles()) {
|
|
61
|
-
sourceFile.forEachDescendant((node) => {
|
|
62
|
-
if (Node.isCallExpression(node)) {
|
|
63
|
-
const expression = node.getExpression();
|
|
64
|
-
|
|
65
|
-
// Check for fetch() calls
|
|
66
|
-
if (Node.isIdentifier(expression) && expression.getText() === "fetch") {
|
|
67
|
-
const args = node.getArguments();
|
|
68
|
-
if (args.length > 0) {
|
|
69
|
-
// Extract URL
|
|
70
|
-
const urlArg = args[0];
|
|
71
|
-
let url: string | null = null;
|
|
72
|
-
|
|
73
|
-
if (Node.isStringLiteral(urlArg)) {
|
|
74
|
-
url = urlArg.getLiteralValue();
|
|
75
|
-
} else if (Node.isTemplateExpression(urlArg)) {
|
|
76
|
-
// Try to extract base URL from template
|
|
77
|
-
url = this.extractBaseURL(urlArg.getText());
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (url) {
|
|
81
|
-
// Extract method
|
|
82
|
-
let method = "GET"; // Default
|
|
83
|
-
if (args.length > 1 && Node.isObjectLiteralExpression(args[1])) {
|
|
84
|
-
const options = args[1];
|
|
85
|
-
const methodProp = options.getProperty("method");
|
|
86
|
-
if (methodProp && Node.isPropertyAssignment(methodProp)) {
|
|
87
|
-
const initializer = methodProp.getInitializer();
|
|
88
|
-
if (initializer && Node.isStringLiteral(initializer)) {
|
|
89
|
-
method = initializer.getLiteralValue().toUpperCase();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Extract description from JSDoc
|
|
95
|
-
const description = this.extractJSDocDescription(node);
|
|
96
|
-
|
|
97
|
-
calls.push({
|
|
98
|
-
url,
|
|
99
|
-
method,
|
|
100
|
-
file: sourceFile.getFilePath(),
|
|
101
|
-
line: node.getStartLineNumber(),
|
|
102
|
-
description,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return calls;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Find WebSocket connections
|
|
116
|
-
*/
|
|
117
|
-
private findWebSockets(): Array<{
|
|
118
|
-
url: string;
|
|
119
|
-
file: string;
|
|
120
|
-
line: number;
|
|
121
|
-
description?: string;
|
|
122
|
-
}> {
|
|
123
|
-
const websockets: Array<{
|
|
124
|
-
url: string;
|
|
125
|
-
file: string;
|
|
126
|
-
line: number;
|
|
127
|
-
description?: string;
|
|
128
|
-
}> = [];
|
|
129
|
-
|
|
130
|
-
for (const sourceFile of this.project.getSourceFiles()) {
|
|
131
|
-
sourceFile.forEachDescendant((node) => {
|
|
132
|
-
if (Node.isNewExpression(node)) {
|
|
133
|
-
const expression = node.getExpression();
|
|
134
|
-
|
|
135
|
-
// Check for new WebSocket()
|
|
136
|
-
if (Node.isIdentifier(expression) && expression.getText() === "WebSocket") {
|
|
137
|
-
const args = node.getArguments();
|
|
138
|
-
if (args.length > 0 && Node.isStringLiteral(args[0])) {
|
|
139
|
-
const url = args[0].getLiteralValue();
|
|
140
|
-
const description = this.extractJSDocDescription(node);
|
|
141
|
-
|
|
142
|
-
websockets.push({
|
|
143
|
-
url,
|
|
144
|
-
file: sourceFile.getFilePath(),
|
|
145
|
-
line: node.getStartLineNumber(),
|
|
146
|
-
description,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return websockets;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Find external script dependencies (from imports)
|
|
159
|
-
*/
|
|
160
|
-
private findExternalScripts(): ExternalIntegration[] {
|
|
161
|
-
const scripts: ExternalIntegration[] = [];
|
|
162
|
-
const seen = new Set<string>();
|
|
163
|
-
|
|
164
|
-
for (const sourceFile of this.project.getSourceFiles()) {
|
|
165
|
-
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
166
|
-
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
167
|
-
|
|
168
|
-
// Only consider external packages (not relative imports)
|
|
169
|
-
if (!moduleSpecifier.startsWith(".") && !moduleSpecifier.startsWith("/")) {
|
|
170
|
-
// Extract package name (handle scoped packages)
|
|
171
|
-
const packageName = moduleSpecifier.startsWith("@")
|
|
172
|
-
? moduleSpecifier.split("/").slice(0, 2).join("/")
|
|
173
|
-
: moduleSpecifier.split("/")[0];
|
|
174
|
-
|
|
175
|
-
if (!seen.has(packageName)) {
|
|
176
|
-
seen.add(packageName);
|
|
177
|
-
|
|
178
|
-
scripts.push({
|
|
179
|
-
type: "external-script",
|
|
180
|
-
name: packageName,
|
|
181
|
-
technology: "npm package",
|
|
182
|
-
usedIn: [sourceFile.getFilePath()],
|
|
183
|
-
description: `External dependency: ${packageName}`,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return scripts;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Create API integration from fetch call
|
|
195
|
-
*/
|
|
196
|
-
private createAPIIntegration(call: {
|
|
197
|
-
url: string;
|
|
198
|
-
method: string;
|
|
199
|
-
file: string;
|
|
200
|
-
line: number;
|
|
201
|
-
description?: string;
|
|
202
|
-
}): ExternalIntegration {
|
|
203
|
-
// Extract base URL
|
|
204
|
-
const baseURL = this.extractBaseURL(call.url);
|
|
205
|
-
|
|
206
|
-
// Infer name from URL
|
|
207
|
-
const name = this.inferAPIName(baseURL);
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
type: "api",
|
|
211
|
-
name,
|
|
212
|
-
technology: "REST API",
|
|
213
|
-
url: baseURL,
|
|
214
|
-
usedIn: [call.file],
|
|
215
|
-
description: call.description || `External API: ${name}`,
|
|
216
|
-
calls: [
|
|
217
|
-
{
|
|
218
|
-
method: call.method,
|
|
219
|
-
endpoint: call.url,
|
|
220
|
-
location: {
|
|
221
|
-
file: call.file,
|
|
222
|
-
line: call.line,
|
|
223
|
-
},
|
|
224
|
-
description: call.description,
|
|
225
|
-
},
|
|
226
|
-
],
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Create WebSocket integration
|
|
232
|
-
*/
|
|
233
|
-
private createWebSocketIntegration(ws: {
|
|
234
|
-
url: string;
|
|
235
|
-
file: string;
|
|
236
|
-
line: number;
|
|
237
|
-
description?: string;
|
|
238
|
-
}): ExternalIntegration {
|
|
239
|
-
const name = this.inferAPIName(ws.url);
|
|
240
|
-
|
|
241
|
-
return {
|
|
242
|
-
type: "websocket",
|
|
243
|
-
name,
|
|
244
|
-
technology: "WebSocket",
|
|
245
|
-
url: ws.url,
|
|
246
|
-
usedIn: [ws.file],
|
|
247
|
-
description: ws.description || `WebSocket connection: ${name}`,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Extract base URL from full URL or template
|
|
253
|
-
*/
|
|
254
|
-
private extractBaseURL(url: string): string {
|
|
255
|
-
// Remove template parts
|
|
256
|
-
url = url.replace(/\$\{[^}]+\}/g, "");
|
|
257
|
-
url = url.replace(/`/g, "");
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
const parsed = new URL(url);
|
|
261
|
-
return `${parsed.protocol}//${parsed.host}`;
|
|
262
|
-
} catch {
|
|
263
|
-
// If parsing fails, try to extract domain
|
|
264
|
-
const match = url.match(/https?:\/\/([^/]+)/);
|
|
265
|
-
return match ? match[0] : url;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Infer API name from URL
|
|
271
|
-
*/
|
|
272
|
-
private inferAPIName(url: string): string {
|
|
273
|
-
try {
|
|
274
|
-
const parsed = new URL(url);
|
|
275
|
-
const hostname = parsed.hostname;
|
|
276
|
-
|
|
277
|
-
// Remove www. prefix
|
|
278
|
-
const cleanHost = hostname.replace(/^www\./, "");
|
|
279
|
-
|
|
280
|
-
// Take first part of domain
|
|
281
|
-
const parts = cleanHost.split(".");
|
|
282
|
-
if (parts.length > 0) {
|
|
283
|
-
// Capitalize first letter
|
|
284
|
-
return parts[0].charAt(0).toUpperCase() + parts[0].slice(1) + " API";
|
|
285
|
-
}
|
|
286
|
-
} catch {
|
|
287
|
-
// Fallback
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return "External API";
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Add or merge integration into map
|
|
295
|
-
*/
|
|
296
|
-
private addOrMergeIntegration(
|
|
297
|
-
map: Map<string, ExternalIntegration>,
|
|
298
|
-
integration: ExternalIntegration
|
|
299
|
-
): void {
|
|
300
|
-
const key = `${integration.type}:${integration.name}`;
|
|
301
|
-
|
|
302
|
-
if (map.has(key)) {
|
|
303
|
-
const existing = map.get(key)!;
|
|
304
|
-
|
|
305
|
-
// Merge usedIn
|
|
306
|
-
existing.usedIn = [...new Set([...existing.usedIn, ...integration.usedIn])];
|
|
307
|
-
|
|
308
|
-
// Merge calls
|
|
309
|
-
if (integration.calls && existing.calls) {
|
|
310
|
-
existing.calls.push(...integration.calls);
|
|
311
|
-
} else if (integration.calls) {
|
|
312
|
-
existing.calls = integration.calls;
|
|
313
|
-
}
|
|
314
|
-
} else {
|
|
315
|
-
map.set(key, integration);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Extract JSDoc description from node
|
|
321
|
-
*/
|
|
322
|
-
private extractJSDocDescription(node: any): string | undefined {
|
|
323
|
-
const jsDocs = node.getJsDocs?.() || [];
|
|
324
|
-
if (jsDocs.length === 0) return undefined;
|
|
325
|
-
|
|
326
|
-
const comment = jsDocs[0].getDescription().trim();
|
|
327
|
-
return comment || undefined;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
// Manifest.json parser for Chrome extensions
|
|
2
|
-
|
|
3
|
-
import * as fs from "node:fs";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import type { ManifestInfo, ContextInfo } from "../types/architecture";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Parse manifest.json and extract context information
|
|
9
|
-
*/
|
|
10
|
-
export class ManifestParser {
|
|
11
|
-
private manifestPath: string;
|
|
12
|
-
private manifestData: any;
|
|
13
|
-
private baseDir: string;
|
|
14
|
-
|
|
15
|
-
constructor(projectRoot: string) {
|
|
16
|
-
this.baseDir = projectRoot;
|
|
17
|
-
this.manifestPath = path.join(projectRoot, "manifest.json");
|
|
18
|
-
|
|
19
|
-
if (!fs.existsSync(this.manifestPath)) {
|
|
20
|
-
throw new Error(`manifest.json not found at ${this.manifestPath}`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const content = fs.readFileSync(this.manifestPath, "utf-8");
|
|
25
|
-
this.manifestData = JSON.parse(content);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
throw new Error(`Failed to parse manifest.json: ${error}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Parse manifest and extract all information
|
|
33
|
-
*/
|
|
34
|
-
parse(): ManifestInfo {
|
|
35
|
-
const manifest = this.manifestData;
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
name: manifest.name || "Unknown Extension",
|
|
39
|
-
version: manifest.version || "0.0.0",
|
|
40
|
-
description: manifest.description,
|
|
41
|
-
manifestVersion: manifest.manifest_version || 2,
|
|
42
|
-
background: this.parseBackground(),
|
|
43
|
-
contentScripts: this.parseContentScripts(),
|
|
44
|
-
popup: this.parsePopup(),
|
|
45
|
-
options: this.parseOptions(),
|
|
46
|
-
devtools: this.parseDevtools(),
|
|
47
|
-
permissions: manifest.permissions || [],
|
|
48
|
-
hostPermissions: manifest.host_permissions || [],
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get context entry points from manifest
|
|
54
|
-
*/
|
|
55
|
-
getContextEntryPoints(): Record<string, string> {
|
|
56
|
-
const entryPoints: Record<string, string> = {};
|
|
57
|
-
|
|
58
|
-
// Background
|
|
59
|
-
const background = this.parseBackground();
|
|
60
|
-
if (background) {
|
|
61
|
-
// Take first file as entry point
|
|
62
|
-
const entryFile = background.files[0];
|
|
63
|
-
if (entryFile) {
|
|
64
|
-
entryPoints.background = this.findSourceFile(entryFile);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Content scripts
|
|
69
|
-
const contentScripts = this.parseContentScripts();
|
|
70
|
-
if (contentScripts && contentScripts.length > 0) {
|
|
71
|
-
const firstScript = contentScripts[0].js[0];
|
|
72
|
-
if (firstScript) {
|
|
73
|
-
entryPoints.content = this.findSourceFile(firstScript);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Popup
|
|
78
|
-
const popup = this.parsePopup();
|
|
79
|
-
if (popup) {
|
|
80
|
-
// For HTML, we need to find the associated JS/TS file
|
|
81
|
-
const htmlPath = path.join(this.baseDir, popup.html);
|
|
82
|
-
const jsPath = this.findAssociatedJS(htmlPath);
|
|
83
|
-
if (jsPath) {
|
|
84
|
-
entryPoints.popup = jsPath;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Options
|
|
89
|
-
const options = this.parseOptions();
|
|
90
|
-
if (options) {
|
|
91
|
-
const htmlPath = path.join(this.baseDir, options.page);
|
|
92
|
-
const jsPath = this.findAssociatedJS(htmlPath);
|
|
93
|
-
if (jsPath) {
|
|
94
|
-
entryPoints.options = jsPath;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// DevTools
|
|
99
|
-
const devtools = this.parseDevtools();
|
|
100
|
-
if (devtools) {
|
|
101
|
-
const htmlPath = path.join(this.baseDir, devtools.page);
|
|
102
|
-
const jsPath = this.findAssociatedJS(htmlPath);
|
|
103
|
-
if (jsPath) {
|
|
104
|
-
entryPoints.devtools = jsPath;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return entryPoints;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Parse background configuration
|
|
113
|
-
*/
|
|
114
|
-
private parseBackground(): ManifestInfo["background"] {
|
|
115
|
-
const bg = this.manifestData.background;
|
|
116
|
-
if (!bg) return undefined;
|
|
117
|
-
|
|
118
|
-
// Manifest V3 - service worker
|
|
119
|
-
if (bg.service_worker) {
|
|
120
|
-
return {
|
|
121
|
-
type: "service_worker",
|
|
122
|
-
files: [bg.service_worker],
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Manifest V2 - scripts
|
|
127
|
-
if (bg.scripts) {
|
|
128
|
-
return {
|
|
129
|
-
type: "script",
|
|
130
|
-
files: bg.scripts,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Manifest V2 - page
|
|
135
|
-
if (bg.page) {
|
|
136
|
-
return {
|
|
137
|
-
type: "script",
|
|
138
|
-
files: [bg.page],
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return undefined;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Parse content scripts configuration
|
|
147
|
-
*/
|
|
148
|
-
private parseContentScripts(): ManifestInfo["contentScripts"] {
|
|
149
|
-
const cs = this.manifestData.content_scripts;
|
|
150
|
-
if (!cs || !Array.isArray(cs)) return undefined;
|
|
151
|
-
|
|
152
|
-
return cs.map((script) => ({
|
|
153
|
-
matches: script.matches || [],
|
|
154
|
-
js: script.js || [],
|
|
155
|
-
css: script.css,
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Parse popup configuration
|
|
161
|
-
*/
|
|
162
|
-
private parsePopup(): ManifestInfo["popup"] {
|
|
163
|
-
const action = this.manifestData.action || this.manifestData.browser_action;
|
|
164
|
-
if (!action) return undefined;
|
|
165
|
-
|
|
166
|
-
if (action.default_popup) {
|
|
167
|
-
return {
|
|
168
|
-
html: action.default_popup,
|
|
169
|
-
default: true,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Parse options configuration
|
|
178
|
-
*/
|
|
179
|
-
private parseOptions(): ManifestInfo["options"] {
|
|
180
|
-
const options = this.manifestData.options_ui || this.manifestData.options_page;
|
|
181
|
-
if (!options) return undefined;
|
|
182
|
-
|
|
183
|
-
if (typeof options === "string") {
|
|
184
|
-
return {
|
|
185
|
-
page: options,
|
|
186
|
-
openInTab: false,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
page: options.page,
|
|
192
|
-
openInTab: options.open_in_tab,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Parse devtools configuration
|
|
198
|
-
*/
|
|
199
|
-
private parseDevtools(): ManifestInfo["devtools"] {
|
|
200
|
-
const devtools = this.manifestData.devtools_page;
|
|
201
|
-
if (!devtools) return undefined;
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
page: devtools,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Find source file from manifest reference
|
|
210
|
-
* Tries multiple locations: exact path, src/ directory, .ts extension
|
|
211
|
-
*/
|
|
212
|
-
private findSourceFile(manifestPath: string): string {
|
|
213
|
-
const candidates = [
|
|
214
|
-
// Exact path from manifest
|
|
215
|
-
path.join(this.baseDir, manifestPath),
|
|
216
|
-
// Same path with .ts extension
|
|
217
|
-
path.join(this.baseDir, manifestPath.replace(/\.js$/, ".ts")),
|
|
218
|
-
// In src/ directory
|
|
219
|
-
path.join(this.baseDir, "src", manifestPath),
|
|
220
|
-
path.join(this.baseDir, "src", manifestPath.replace(/\.js$/, ".ts")),
|
|
221
|
-
// In src/ with .tsx extension
|
|
222
|
-
path.join(this.baseDir, "src", manifestPath.replace(/\.js$/, ".tsx")),
|
|
223
|
-
];
|
|
224
|
-
|
|
225
|
-
for (const candidate of candidates) {
|
|
226
|
-
if (fs.existsSync(candidate)) {
|
|
227
|
-
return candidate;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Fallback to manifest path (will error later if not found)
|
|
232
|
-
return path.join(this.baseDir, manifestPath);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Find associated JavaScript/TypeScript file for an HTML file
|
|
237
|
-
*/
|
|
238
|
-
private findAssociatedJS(htmlPath: string): string | null {
|
|
239
|
-
if (!fs.existsSync(htmlPath)) {
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Read HTML and look for script tags
|
|
244
|
-
const html = fs.readFileSync(htmlPath, "utf-8");
|
|
245
|
-
const scriptMatch = html.match(/<script[^>]+src=["']([^"']+)["']/i);
|
|
246
|
-
|
|
247
|
-
if (scriptMatch && scriptMatch[1]) {
|
|
248
|
-
const scriptPath = scriptMatch[1];
|
|
249
|
-
const fullPath = path.resolve(path.dirname(htmlPath), scriptPath);
|
|
250
|
-
|
|
251
|
-
// Try with and without .js/.ts extension
|
|
252
|
-
if (fs.existsSync(fullPath)) return fullPath;
|
|
253
|
-
if (fs.existsSync(fullPath.replace(/\.js$/, ".ts"))) {
|
|
254
|
-
return fullPath.replace(/\.js$/, ".ts");
|
|
255
|
-
}
|
|
256
|
-
if (fs.existsSync(fullPath.replace(/\.js$/, ".tsx"))) {
|
|
257
|
-
return fullPath.replace(/\.js$/, ".tsx");
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Fallback: look for convention-based files
|
|
262
|
-
const baseName = path.basename(htmlPath, ".html");
|
|
263
|
-
const dir = path.dirname(htmlPath);
|
|
264
|
-
|
|
265
|
-
const candidates = [
|
|
266
|
-
path.join(dir, `${baseName}.ts`),
|
|
267
|
-
path.join(dir, `${baseName}.tsx`),
|
|
268
|
-
path.join(dir, `${baseName}.js`),
|
|
269
|
-
path.join(dir, "index.ts"),
|
|
270
|
-
path.join(dir, "index.tsx"),
|
|
271
|
-
path.join(dir, "index.js"),
|
|
272
|
-
];
|
|
273
|
-
|
|
274
|
-
for (const candidate of candidates) {
|
|
275
|
-
if (fs.existsSync(candidate)) {
|
|
276
|
-
return candidate;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Parse manifest.json and extract information
|
|
286
|
-
*/
|
|
287
|
-
export function parseManifest(projectRoot: string): ManifestInfo {
|
|
288
|
-
const parser = new ManifestParser(projectRoot);
|
|
289
|
-
return parser.parse();
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Get context entry points from manifest.json
|
|
294
|
-
*/
|
|
295
|
-
export function getContextEntryPoints(projectRoot: string): Record<string, string> {
|
|
296
|
-
const parser = new ManifestParser(projectRoot);
|
|
297
|
-
return parser.getContextEntryPoints();
|
|
298
|
-
}
|