@addfox/core 0.1.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/cjs/index.cjs +1729 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/pipeline/index.cjs +67 -0
- package/dist/cjs/pipeline/index.cjs.map +1 -0
- package/dist/esm/434.js +33 -0
- package/dist/esm/434.js.map +1 -0
- package/dist/esm/config/defineConfig.d.ts +2 -0
- package/dist/esm/config/index.d.ts +8 -0
- package/dist/esm/config/loader.d.ts +21 -0
- package/dist/esm/constants.d.ts +77 -0
- package/dist/esm/entry/discoverer.d.ts +13 -0
- package/dist/esm/entry/html.d.ts +35 -0
- package/dist/esm/entry/index.d.ts +12 -0
- package/dist/esm/entry/manifestParser.d.ts +27 -0
- package/dist/esm/entry/resolver.d.ts +30 -0
- package/dist/esm/frameworkDetect.d.ts +11 -0
- package/dist/esm/index.d.ts +32 -0
- package/dist/esm/index.js +1521 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/manifest/builder.d.ts +16 -0
- package/dist/esm/manifest/index.d.ts +9 -0
- package/dist/esm/manifest/loader.d.ts +26 -0
- package/dist/esm/pipeline/Pipeline.d.ts +18 -0
- package/dist/esm/pipeline/index.d.ts +5 -0
- package/dist/esm/pipeline/index.js +1 -0
- package/dist/esm/pipeline/types.d.ts +26 -0
- package/dist/esm/reloadManager.d.ts +9 -0
- package/dist/esm/types.d.ts +186 -0
- package/dist/esm/version.d.ts +4 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1521 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { basename as external_path_basename, dirname, extname, resolve } from "path";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
4
|
+
import { ADDFOX_ERROR_CODES, AddfoxError, logDoneTimed, logEntriesTable } from "@addfox/common";
|
|
5
|
+
import { createRequire as external_node_module_createRequire } from "node:module";
|
|
6
|
+
import { existsSync as external_node_fs_existsSync, readFileSync as external_node_fs_readFileSync } from "node:fs";
|
|
7
|
+
import { dirname as external_node_path_dirname, join, resolve as external_node_path_resolve } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
function defineConfig(config) {
|
|
10
|
+
return config;
|
|
11
|
+
}
|
|
12
|
+
const CONFIG_FILES = [
|
|
13
|
+
"addfox.config.ts",
|
|
14
|
+
"addfox.config.js",
|
|
15
|
+
"addfox.config.mjs"
|
|
16
|
+
];
|
|
17
|
+
const SCRIPT_EXTS = [
|
|
18
|
+
".js",
|
|
19
|
+
".jsx",
|
|
20
|
+
".ts",
|
|
21
|
+
".tsx"
|
|
22
|
+
];
|
|
23
|
+
const RESERVED_ENTRY_NAMES = [
|
|
24
|
+
"popup",
|
|
25
|
+
"options",
|
|
26
|
+
"sidepanel",
|
|
27
|
+
"offscreen",
|
|
28
|
+
"sandbox",
|
|
29
|
+
"newtab",
|
|
30
|
+
"bookmarks",
|
|
31
|
+
"history",
|
|
32
|
+
"background",
|
|
33
|
+
"devtools",
|
|
34
|
+
"content"
|
|
35
|
+
];
|
|
36
|
+
const HTML_ENTRY_NAMES = [
|
|
37
|
+
"popup",
|
|
38
|
+
"options",
|
|
39
|
+
"sidepanel",
|
|
40
|
+
"devtools",
|
|
41
|
+
"offscreen",
|
|
42
|
+
"sandbox",
|
|
43
|
+
"newtab",
|
|
44
|
+
"bookmarks",
|
|
45
|
+
"history"
|
|
46
|
+
];
|
|
47
|
+
const SCRIPT_ONLY_ENTRY_NAMES = [
|
|
48
|
+
"background",
|
|
49
|
+
"content"
|
|
50
|
+
];
|
|
51
|
+
const RELOAD_MANAGER_ENTRY_NAMES = new Set(SCRIPT_ONLY_ENTRY_NAMES);
|
|
52
|
+
const ADDFOX_OUTPUT_ROOT = ".addfox";
|
|
53
|
+
const DEFAULT_OUT_DIR = "extension";
|
|
54
|
+
const DEFAULT_ENV_PREFIXES = [
|
|
55
|
+
"ADDFOX_PUBLIC_"
|
|
56
|
+
];
|
|
57
|
+
const DEFAULT_BROWSER = "chromium";
|
|
58
|
+
const SUPPORTED_BROWSERS = [
|
|
59
|
+
"chromium",
|
|
60
|
+
"firefox"
|
|
61
|
+
];
|
|
62
|
+
const BROWSER_OUTPUT_PREFIX = "extension";
|
|
63
|
+
function getBrowserOutputDir(browser) {
|
|
64
|
+
return `${BROWSER_OUTPUT_PREFIX}-${browser}`;
|
|
65
|
+
}
|
|
66
|
+
const SUPPORTED_LAUNCH_TARGETS = [
|
|
67
|
+
"chrome",
|
|
68
|
+
"chromium",
|
|
69
|
+
"edge",
|
|
70
|
+
"brave",
|
|
71
|
+
"vivaldi",
|
|
72
|
+
"opera",
|
|
73
|
+
"santa",
|
|
74
|
+
"arc",
|
|
75
|
+
"yandex",
|
|
76
|
+
"browseros",
|
|
77
|
+
"custom",
|
|
78
|
+
"firefox"
|
|
79
|
+
];
|
|
80
|
+
const CLI_COMMANDS = [
|
|
81
|
+
"dev",
|
|
82
|
+
"build",
|
|
83
|
+
"test"
|
|
84
|
+
];
|
|
85
|
+
const RSTEST_CONFIG_FILES = [
|
|
86
|
+
"rstest.config.cts",
|
|
87
|
+
"rstest.config.mts",
|
|
88
|
+
"rstest.config.cjs",
|
|
89
|
+
"rstest.config.js",
|
|
90
|
+
"rstest.config.ts",
|
|
91
|
+
"rstest.config.mjs"
|
|
92
|
+
];
|
|
93
|
+
const MANIFEST_DIR = "manifest";
|
|
94
|
+
const MANIFEST_FILE_NAMES = {
|
|
95
|
+
base: "manifest.json",
|
|
96
|
+
chromium: "manifest.chromium.json",
|
|
97
|
+
firefox: "manifest.firefox.json"
|
|
98
|
+
};
|
|
99
|
+
const MANIFEST_ENTRY_PATHS = {
|
|
100
|
+
background: "background/index.js",
|
|
101
|
+
content: "content/index.js",
|
|
102
|
+
popup: "popup/index.html",
|
|
103
|
+
options: "options/index.html",
|
|
104
|
+
sidepanel: "sidepanel/index.html",
|
|
105
|
+
devtools: "devtools/index.html",
|
|
106
|
+
offscreen: "offscreen/index.html",
|
|
107
|
+
sandbox: "sandbox/index.html",
|
|
108
|
+
newtab: "newtab/index.html",
|
|
109
|
+
bookmarks: "bookmarks/index.html",
|
|
110
|
+
history: "history/index.html"
|
|
111
|
+
};
|
|
112
|
+
const MANIFEST_ENTRY_KEYS = Object.keys(MANIFEST_ENTRY_PATHS);
|
|
113
|
+
function isScriptSrcRelative(src) {
|
|
114
|
+
const t = src.trim();
|
|
115
|
+
if (t.startsWith("/") || t.startsWith("\\")) return false;
|
|
116
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(t)) return false;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
const ENTRY_SCRIPT_REGEX = /<script\b[^>]*data-addfox-entry[^>]*>[\s\S]*?<\/script>/gi;
|
|
120
|
+
const HEAD_CLOSE_REGEX = /<\/head\s*>/i;
|
|
121
|
+
const SCRIPT_WITH_SRC_REGEX = /<script\b[^>]*\ssrc\s*=\s*["']([^"']+)["'][^>]*>[\s\S]*?<\/script>/gi;
|
|
122
|
+
function findFirstScriptWithRelativeSrc(html) {
|
|
123
|
+
const reg = new RegExp(SCRIPT_WITH_SRC_REGEX.source, "gi");
|
|
124
|
+
let match;
|
|
125
|
+
while(null !== (match = reg.exec(html))){
|
|
126
|
+
const src = match[1];
|
|
127
|
+
if (!isScriptSrcRelative(src)) continue;
|
|
128
|
+
return {
|
|
129
|
+
src,
|
|
130
|
+
fullTag: match[0],
|
|
131
|
+
tagStartIndex: match.index
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function parseAddfoxEntryFromHtml(htmlPath) {
|
|
136
|
+
const raw = readFileSync(htmlPath, "utf-8");
|
|
137
|
+
const reg = new RegExp(ENTRY_SCRIPT_REGEX.source, "gi");
|
|
138
|
+
const match = reg.exec(raw);
|
|
139
|
+
if (match) {
|
|
140
|
+
const tag = match[0];
|
|
141
|
+
const tagStartIndex = match.index;
|
|
142
|
+
const srcMatch = tag.match(/src\s*=\s*["']([^"']+)["']/i);
|
|
143
|
+
if (srcMatch?.[1] && isScriptSrcRelative(srcMatch[1])) {
|
|
144
|
+
const src = srcMatch[1];
|
|
145
|
+
const headCloseMatch = raw.match(HEAD_CLOSE_REGEX);
|
|
146
|
+
const headCloseIndex = headCloseMatch?.index ?? -1;
|
|
147
|
+
const inject = headCloseIndex >= 0 && tagStartIndex < headCloseIndex ? "head" : "body";
|
|
148
|
+
const strippedHtml = raw.replace(ENTRY_SCRIPT_REGEX, "").trimEnd();
|
|
149
|
+
return {
|
|
150
|
+
src,
|
|
151
|
+
inject,
|
|
152
|
+
strippedHtml
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const fallback = findFirstScriptWithRelativeSrc(raw);
|
|
157
|
+
if (!fallback) return;
|
|
158
|
+
const headCloseMatch = raw.match(HEAD_CLOSE_REGEX);
|
|
159
|
+
const headCloseIndex = headCloseMatch?.index ?? -1;
|
|
160
|
+
const inject = headCloseIndex >= 0 && fallback.tagStartIndex < headCloseIndex ? "head" : "body";
|
|
161
|
+
const strippedHtml = raw.replace(fallback.fullTag, "").trimEnd();
|
|
162
|
+
return {
|
|
163
|
+
src: fallback.src,
|
|
164
|
+
inject,
|
|
165
|
+
strippedHtml
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function getScriptInjectIfMatches(htmlPath, scriptPath) {
|
|
169
|
+
try {
|
|
170
|
+
const parsed = parseAddfoxEntryFromHtml(htmlPath);
|
|
171
|
+
if (!parsed) return;
|
|
172
|
+
const resolvedFromHtml = resolve(dirname(htmlPath), parsed.src);
|
|
173
|
+
return resolvedFromHtml === scriptPath ? parsed.inject : void 0;
|
|
174
|
+
} catch {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function resolveScriptFromHtmlStrict(htmlPath) {
|
|
179
|
+
const parsed = parseAddfoxEntryFromHtml(htmlPath);
|
|
180
|
+
if (!parsed) throw new Error("data-addfox-entry script must have a src attribute (relative path)");
|
|
181
|
+
if (!isScriptSrcRelative(parsed.src)) throw new Error(`data-addfox-entry src must be relative, got: ${JSON.stringify(parsed.src)}`);
|
|
182
|
+
const scriptPath = resolve(dirname(htmlPath), parsed.src);
|
|
183
|
+
if (!existsSync(scriptPath)) throw new Error(`Entry script not found: ${scriptPath}`);
|
|
184
|
+
return {
|
|
185
|
+
scriptPath,
|
|
186
|
+
inject: parsed.inject
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function readDirContents(dir) {
|
|
190
|
+
try {
|
|
191
|
+
const entries = readdirSync(dir, {
|
|
192
|
+
withFileTypes: true
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
files: new Set(entries.filter((e)=>e.isFile()).map((e)=>e.name)),
|
|
196
|
+
dirs: new Set(entries.filter((e)=>e.isDirectory()).map((e)=>e.name))
|
|
197
|
+
};
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function findScriptInDir(dir, scriptExts) {
|
|
203
|
+
const contents = readDirContents(dir);
|
|
204
|
+
if (!contents) return;
|
|
205
|
+
for (const ext of scriptExts)if (contents.files.has(`index${ext}`)) return resolve(dir, `index${ext}`);
|
|
206
|
+
}
|
|
207
|
+
function findScriptInDirFromContents(dirPath, contents, scriptExts) {
|
|
208
|
+
if (!contents) return findScriptInDir(dirPath, scriptExts);
|
|
209
|
+
for (const ext of scriptExts)if (contents.files.has(`index${ext}`)) return resolve(dirPath, `index${ext}`);
|
|
210
|
+
}
|
|
211
|
+
function findNamedScript(baseDir, name, scriptExts, baseContents) {
|
|
212
|
+
if (baseContents) {
|
|
213
|
+
for (const ext of scriptExts)if (baseContents.files.has(`${name}${ext}`)) return resolve(baseDir, `${name}${ext}`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
for (const ext of scriptExts){
|
|
217
|
+
const p = resolve(baseDir, `${name}${ext}`);
|
|
218
|
+
if (existsSync(p)) return p;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function findNamedHtml(baseDir, name, baseContents) {
|
|
222
|
+
if (baseContents) return baseContents.files.has(`${name}.html`) ? resolve(baseDir, `${name}.html`) : void 0;
|
|
223
|
+
const p = resolve(baseDir, `${name}.html`);
|
|
224
|
+
return existsSync(p) ? p : void 0;
|
|
225
|
+
}
|
|
226
|
+
function isValidScriptExt(pathStr, scriptExts) {
|
|
227
|
+
const lower = pathStr.trim().toLowerCase();
|
|
228
|
+
return scriptExts.some((ext)=>lower.endsWith(ext));
|
|
229
|
+
}
|
|
230
|
+
function resolveScriptFromHtmlWithInjectStrict(htmlPath, scriptExts) {
|
|
231
|
+
const resolved = resolveScriptFromHtmlStrict(htmlPath);
|
|
232
|
+
if (!isValidScriptExt(resolved.scriptPath, scriptExts)) throw new AddfoxError({
|
|
233
|
+
code: ADDFOX_ERROR_CODES.ENTRY_SCRIPT_FROM_HTML,
|
|
234
|
+
message: "Invalid data-addfox-entry script in HTML",
|
|
235
|
+
details: `HTML: ${htmlPath}. Resolved path is not a supported script: ${resolved.scriptPath}`,
|
|
236
|
+
hint: "data-addfox-entry script must have a relative src and the file must exist"
|
|
237
|
+
});
|
|
238
|
+
return resolved;
|
|
239
|
+
}
|
|
240
|
+
function tryResolveEntryFromHtml(htmlPath, scriptExts) {
|
|
241
|
+
try {
|
|
242
|
+
const parsed = parseAddfoxEntryFromHtml(htmlPath);
|
|
243
|
+
if (!parsed) return;
|
|
244
|
+
return resolveScriptFromHtmlWithInjectStrict(htmlPath, scriptExts);
|
|
245
|
+
} catch (e) {
|
|
246
|
+
throw new AddfoxError({
|
|
247
|
+
code: ADDFOX_ERROR_CODES.ENTRY_SCRIPT_FROM_HTML,
|
|
248
|
+
message: "Invalid data-addfox-entry script in HTML",
|
|
249
|
+
details: `HTML: ${htmlPath}. ${e instanceof Error ? e.message : String(e)}`,
|
|
250
|
+
hint: "data-addfox-entry script must have a relative src and the file must exist"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function buildHtmlEntryInfo(name, htmlPath, conventionalScriptPath, scriptExts) {
|
|
255
|
+
const fromHtml = tryResolveEntryFromHtml(htmlPath, scriptExts);
|
|
256
|
+
if (fromHtml) return {
|
|
257
|
+
name,
|
|
258
|
+
scriptPath: fromHtml.scriptPath,
|
|
259
|
+
htmlPath,
|
|
260
|
+
html: true,
|
|
261
|
+
scriptInject: fromHtml.inject,
|
|
262
|
+
outputFollowsScriptPath: true
|
|
263
|
+
};
|
|
264
|
+
if (!conventionalScriptPath) return null;
|
|
265
|
+
return {
|
|
266
|
+
name,
|
|
267
|
+
scriptPath: conventionalScriptPath,
|
|
268
|
+
htmlPath,
|
|
269
|
+
html: true,
|
|
270
|
+
scriptInject: getScriptInjectIfMatches(htmlPath, conventionalScriptPath)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function discoverScriptOnlyEntry(baseDir, name, contents, options) {
|
|
274
|
+
const singleScript = findNamedScript(baseDir, name, options.scriptExts, contents);
|
|
275
|
+
if (singleScript) return {
|
|
276
|
+
name,
|
|
277
|
+
scriptPath: singleScript,
|
|
278
|
+
html: false
|
|
279
|
+
};
|
|
280
|
+
if (!contents?.dirs.has(name)) return null;
|
|
281
|
+
const scriptPath = findScriptInDir(resolve(baseDir, name), options.scriptExts);
|
|
282
|
+
return scriptPath ? {
|
|
283
|
+
name,
|
|
284
|
+
scriptPath,
|
|
285
|
+
html: false
|
|
286
|
+
} : null;
|
|
287
|
+
}
|
|
288
|
+
function discoverHtmlEntryFromFlat(baseDir, name, contents, options) {
|
|
289
|
+
const singleScript = findNamedScript(baseDir, name, options.scriptExts, contents);
|
|
290
|
+
const singleHtml = findNamedHtml(baseDir, name, contents);
|
|
291
|
+
if (singleScript && singleHtml) return buildHtmlEntryInfo(name, singleHtml, singleScript, options.scriptExts);
|
|
292
|
+
if (singleHtml) {
|
|
293
|
+
const conventional = findNamedScript(baseDir, name, options.scriptExts, contents);
|
|
294
|
+
return buildHtmlEntryInfo(name, singleHtml, conventional, options.scriptExts);
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
function discoverHtmlEntryFromDir(baseDir, name, contents, options) {
|
|
299
|
+
if (!contents?.dirs.has(name)) return null;
|
|
300
|
+
const dirPath = resolve(baseDir, name);
|
|
301
|
+
const subContents = readDirContents(dirPath);
|
|
302
|
+
const htmlPath = subContents?.files.has("index.html") ? resolve(dirPath, "index.html") : void 0;
|
|
303
|
+
const scriptPath = findScriptInDirFromContents(dirPath, subContents, options.scriptExts);
|
|
304
|
+
if (scriptPath && htmlPath) return buildHtmlEntryInfo(name, htmlPath, scriptPath, options.scriptExts);
|
|
305
|
+
if (scriptPath) return {
|
|
306
|
+
name,
|
|
307
|
+
scriptPath,
|
|
308
|
+
htmlPath,
|
|
309
|
+
html: true,
|
|
310
|
+
scriptInject: htmlPath ? getScriptInjectIfMatches(htmlPath, scriptPath) : void 0
|
|
311
|
+
};
|
|
312
|
+
if (!htmlPath) return null;
|
|
313
|
+
return buildHtmlEntryInfo(name, htmlPath, void 0, options.scriptExts);
|
|
314
|
+
}
|
|
315
|
+
function discoverHtmlEntry(baseDir, name, contents, options) {
|
|
316
|
+
return discoverHtmlEntryFromFlat(baseDir, name, contents, options) ?? discoverHtmlEntryFromDir(baseDir, name, contents, options);
|
|
317
|
+
}
|
|
318
|
+
function discoverEntriesInternal(baseDir, options) {
|
|
319
|
+
const contents = readDirContents(baseDir);
|
|
320
|
+
const entries = [];
|
|
321
|
+
for (const name of options.scriptOnlyNames){
|
|
322
|
+
const entry = discoverScriptOnlyEntry(baseDir, name, contents, options);
|
|
323
|
+
if (entry) entries.push(entry);
|
|
324
|
+
}
|
|
325
|
+
for (const name of options.htmlEntryNames){
|
|
326
|
+
const entry = discoverHtmlEntry(baseDir, name, contents, options);
|
|
327
|
+
if (entry) entries.push(entry);
|
|
328
|
+
}
|
|
329
|
+
return entries;
|
|
330
|
+
}
|
|
331
|
+
function createOptions(options = {}) {
|
|
332
|
+
return {
|
|
333
|
+
scriptExts: options.scriptExts ?? SCRIPT_EXTS,
|
|
334
|
+
scriptOnlyNames: options.scriptOnlyNames ?? SCRIPT_ONLY_ENTRY_NAMES,
|
|
335
|
+
htmlEntryNames: options.htmlEntryNames ?? HTML_ENTRY_NAMES
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function discoverEntries(baseDir, options) {
|
|
339
|
+
return discoverEntriesInternal(baseDir, createOptions(options));
|
|
340
|
+
}
|
|
341
|
+
function getHtmlEntryNames(options) {
|
|
342
|
+
return [
|
|
343
|
+
...options?.htmlEntryNames ?? HTML_ENTRY_NAMES
|
|
344
|
+
];
|
|
345
|
+
}
|
|
346
|
+
function getScriptOnlyEntryNames(options) {
|
|
347
|
+
return [
|
|
348
|
+
...options?.scriptOnlyNames ?? SCRIPT_ONLY_ENTRY_NAMES
|
|
349
|
+
];
|
|
350
|
+
}
|
|
351
|
+
function getAllEntryNames(options) {
|
|
352
|
+
return [
|
|
353
|
+
...getHtmlEntryNames(options),
|
|
354
|
+
...getScriptOnlyEntryNames(options)
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
function isSourceFilePath(path) {
|
|
358
|
+
const lower = path.trim().toLowerCase();
|
|
359
|
+
return /\.(ts|tsx|js|jsx)$/.test(lower) && !lower.includes("/dist/") && !lower.includes("\\dist\\");
|
|
360
|
+
}
|
|
361
|
+
function inferEntryNameFromPath(path) {
|
|
362
|
+
const cleanPath = path.replace(/^\.\/|^\//, "");
|
|
363
|
+
const firstSegment = cleanPath.split(/[\/\\]/)[0];
|
|
364
|
+
if (MANIFEST_ENTRY_KEYS.includes(firstSegment)) return firstSegment;
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
function extractServiceWorker(manifest, browser) {
|
|
368
|
+
const bg = manifest.background;
|
|
369
|
+
if (!bg) return null;
|
|
370
|
+
const serviceWorker = bg.service_worker;
|
|
371
|
+
if ("string" == typeof serviceWorker && isSourceFilePath(serviceWorker)) {
|
|
372
|
+
const entryName = inferEntryNameFromPath(serviceWorker) ?? "background";
|
|
373
|
+
return {
|
|
374
|
+
name: entryName,
|
|
375
|
+
value: serviceWorker.replace(/^\.\//, ""),
|
|
376
|
+
manifestPath: "background.service_worker"
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const scripts = bg.scripts;
|
|
380
|
+
if (Array.isArray(scripts) && scripts.length > 0) {
|
|
381
|
+
const firstScript = scripts[0];
|
|
382
|
+
if ("string" == typeof firstScript && isSourceFilePath(firstScript)) {
|
|
383
|
+
const entryName = inferEntryNameFromPath(firstScript) ?? "background";
|
|
384
|
+
return {
|
|
385
|
+
name: entryName,
|
|
386
|
+
value: firstScript.replace(/^\.\//, ""),
|
|
387
|
+
manifestPath: "background.scripts[0]"
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const page = bg.page;
|
|
392
|
+
if ("string" == typeof page && isSourceFilePath(page)) {
|
|
393
|
+
const entryName = inferEntryNameFromPath(page) ?? "background";
|
|
394
|
+
return {
|
|
395
|
+
name: entryName,
|
|
396
|
+
value: page.replace(/^\.\//, ""),
|
|
397
|
+
manifestPath: "background.page"
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
function extractPopup(manifest, browser) {
|
|
403
|
+
const mv = 2 === manifest.manifest_version ? 2 : 3;
|
|
404
|
+
let popupPath;
|
|
405
|
+
let manifestPath;
|
|
406
|
+
if (3 === mv) {
|
|
407
|
+
const action = manifest.action;
|
|
408
|
+
const defaultPopup = action?.default_popup;
|
|
409
|
+
if ("string" == typeof defaultPopup) {
|
|
410
|
+
popupPath = defaultPopup;
|
|
411
|
+
manifestPath = "action.default_popup";
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
const browserAction = manifest.browser_action;
|
|
415
|
+
const defaultPopup = browserAction?.default_popup;
|
|
416
|
+
if ("string" == typeof defaultPopup) {
|
|
417
|
+
popupPath = defaultPopup;
|
|
418
|
+
manifestPath = "browser_action.default_popup";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (popupPath && isSourceFilePath(popupPath)) {
|
|
422
|
+
const entryName = inferEntryNameFromPath(popupPath) ?? "popup";
|
|
423
|
+
return {
|
|
424
|
+
name: entryName,
|
|
425
|
+
value: popupPath.replace(/^\.\//, ""),
|
|
426
|
+
manifestPath: manifestPath
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
function extractOptions(manifest) {
|
|
432
|
+
const mv = 2 === manifest.manifest_version ? 2 : 3;
|
|
433
|
+
let optionsPath;
|
|
434
|
+
let manifestPath;
|
|
435
|
+
if (3 === mv) {
|
|
436
|
+
const optionsUi = manifest.options_ui;
|
|
437
|
+
const page = optionsUi?.page;
|
|
438
|
+
if ("string" == typeof page) {
|
|
439
|
+
optionsPath = page;
|
|
440
|
+
manifestPath = "options_ui.page";
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
const optionsPage = manifest.options_page;
|
|
444
|
+
if ("string" == typeof optionsPage) {
|
|
445
|
+
optionsPath = optionsPage;
|
|
446
|
+
manifestPath = "options_page";
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (optionsPath && isSourceFilePath(optionsPath)) {
|
|
450
|
+
const entryName = inferEntryNameFromPath(optionsPath) ?? "options";
|
|
451
|
+
return {
|
|
452
|
+
name: entryName,
|
|
453
|
+
value: optionsPath.replace(/^\.\//, ""),
|
|
454
|
+
manifestPath: manifestPath
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
function extractDevtools(manifest) {
|
|
460
|
+
const devtoolsPage = manifest.devtools_page;
|
|
461
|
+
if ("string" == typeof devtoolsPage && isSourceFilePath(devtoolsPage)) {
|
|
462
|
+
const entryName = inferEntryNameFromPath(devtoolsPage) ?? "devtools";
|
|
463
|
+
return {
|
|
464
|
+
name: entryName,
|
|
465
|
+
value: devtoolsPage.replace(/^\.\//, ""),
|
|
466
|
+
manifestPath: "devtools_page"
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
function extractSidepanel(manifest) {
|
|
472
|
+
const sidePanel = manifest.side_panel;
|
|
473
|
+
const defaultPath = sidePanel?.default_path;
|
|
474
|
+
if ("string" == typeof defaultPath && isSourceFilePath(defaultPath)) {
|
|
475
|
+
const entryName = inferEntryNameFromPath(defaultPath) ?? "sidepanel";
|
|
476
|
+
return {
|
|
477
|
+
name: entryName,
|
|
478
|
+
value: defaultPath.replace(/^\.\//, ""),
|
|
479
|
+
manifestPath: "side_panel.default_path"
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
function extractSandbox(manifest) {
|
|
485
|
+
const sandbox = manifest.sandbox;
|
|
486
|
+
const pages = sandbox?.pages;
|
|
487
|
+
if (Array.isArray(pages) && pages.length > 0) {
|
|
488
|
+
const firstPage = pages[0];
|
|
489
|
+
if ("string" == typeof firstPage && isSourceFilePath(firstPage)) {
|
|
490
|
+
const entryName = inferEntryNameFromPath(firstPage) ?? "sandbox";
|
|
491
|
+
return {
|
|
492
|
+
name: entryName,
|
|
493
|
+
value: firstPage.replace(/^\.\//, ""),
|
|
494
|
+
manifestPath: "sandbox.pages[0]"
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function extractOverrides(manifest) {
|
|
501
|
+
const entries = [];
|
|
502
|
+
const overrides = manifest.chrome_url_overrides;
|
|
503
|
+
if (!overrides) return entries;
|
|
504
|
+
const overrideKeys = [
|
|
505
|
+
"newtab",
|
|
506
|
+
"bookmarks",
|
|
507
|
+
"history"
|
|
508
|
+
];
|
|
509
|
+
for (const key of overrideKeys){
|
|
510
|
+
const path = overrides[key];
|
|
511
|
+
if ("string" == typeof path && isSourceFilePath(path)) {
|
|
512
|
+
const entryName = inferEntryNameFromPath(path) ?? key;
|
|
513
|
+
entries.push({
|
|
514
|
+
name: entryName,
|
|
515
|
+
value: path.replace(/^\.\//, ""),
|
|
516
|
+
manifestPath: `chrome_url_overrides.${key}`
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return entries;
|
|
521
|
+
}
|
|
522
|
+
function extractContentScripts(manifest) {
|
|
523
|
+
const contentScripts = manifest.content_scripts;
|
|
524
|
+
if (!Array.isArray(contentScripts) || 0 === contentScripts.length) return null;
|
|
525
|
+
for(let i = 0; i < contentScripts.length; i++){
|
|
526
|
+
const cs = contentScripts[i];
|
|
527
|
+
const js = cs.js;
|
|
528
|
+
if (Array.isArray(js) && js.length > 0) for(let j = 0; j < js.length; j++){
|
|
529
|
+
const path = js[j];
|
|
530
|
+
if ("string" == typeof path && isSourceFilePath(path)) {
|
|
531
|
+
const entryName = inferEntryNameFromPath(path) ?? "content";
|
|
532
|
+
return {
|
|
533
|
+
name: entryName,
|
|
534
|
+
value: path.replace(/^\.\//, ""),
|
|
535
|
+
manifestPath: `content_scripts[${i}].js[${j}]`
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
function extractEntriesFromManifest(manifest, browser) {
|
|
543
|
+
const entries = {};
|
|
544
|
+
const replacementMap = new Map();
|
|
545
|
+
const extractors = [
|
|
546
|
+
()=>extractServiceWorker(manifest, browser),
|
|
547
|
+
()=>extractPopup(manifest, browser),
|
|
548
|
+
()=>extractOptions(manifest),
|
|
549
|
+
()=>extractDevtools(manifest),
|
|
550
|
+
()=>extractSidepanel(manifest),
|
|
551
|
+
()=>extractSandbox(manifest),
|
|
552
|
+
()=>extractContentScripts(manifest),
|
|
553
|
+
()=>extractOverrides(manifest)
|
|
554
|
+
];
|
|
555
|
+
for (const extractor of extractors){
|
|
556
|
+
const result = extractor();
|
|
557
|
+
if (result) if (Array.isArray(result)) for (const entry of result){
|
|
558
|
+
entries[entry.name] = entry.value;
|
|
559
|
+
replacementMap.set(entry.manifestPath, entry.name);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
entries[result.name] = result.value;
|
|
563
|
+
replacementMap.set(result.manifestPath, result.name);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
entries,
|
|
568
|
+
replacementMap
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
const HTML_ENTRY_SET = new Set(HTML_ENTRY_NAMES);
|
|
572
|
+
function isHtmlPath(pathStr) {
|
|
573
|
+
return pathStr.trim().toLowerCase().endsWith(".html");
|
|
574
|
+
}
|
|
575
|
+
function isScriptPath(pathStr) {
|
|
576
|
+
const lower = pathStr.trim().toLowerCase();
|
|
577
|
+
return SCRIPT_EXTS.some((ext)=>lower.endsWith(ext));
|
|
578
|
+
}
|
|
579
|
+
function resolver_findScriptInDir(dir) {
|
|
580
|
+
for (const ext of SCRIPT_EXTS){
|
|
581
|
+
const p = resolve(dir, `index${ext}`);
|
|
582
|
+
if (existsSync(p)) return p;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function findScriptForHtmlDir(dir, htmlFilename) {
|
|
586
|
+
const stem = external_path_basename(htmlFilename, ".html");
|
|
587
|
+
for (const ext of SCRIPT_EXTS){
|
|
588
|
+
const p = resolve(dir, `${stem}${ext}`);
|
|
589
|
+
if (existsSync(p)) return p;
|
|
590
|
+
}
|
|
591
|
+
return resolver_findScriptInDir(dir);
|
|
592
|
+
}
|
|
593
|
+
function isEntryConfigObject(value) {
|
|
594
|
+
return "object" == typeof value && null !== value && "src" in value;
|
|
595
|
+
}
|
|
596
|
+
function resolveHtmlPath(baseDir, htmlValue) {
|
|
597
|
+
if (!htmlValue) return;
|
|
598
|
+
const resolved = resolve(baseDir, htmlValue);
|
|
599
|
+
if (!isHtmlPath(resolved)) return;
|
|
600
|
+
return existsSync(resolved) ? resolved : void 0;
|
|
601
|
+
}
|
|
602
|
+
function resolveHtmlFlag(entryName, htmlValue, hasTemplate) {
|
|
603
|
+
if (false === htmlValue) return false;
|
|
604
|
+
if (true === htmlValue) return true;
|
|
605
|
+
if ("string" == typeof htmlValue) return hasTemplate;
|
|
606
|
+
return isHtmlEntryName(entryName);
|
|
607
|
+
}
|
|
608
|
+
function isHtmlEntryName(entryName) {
|
|
609
|
+
return HTML_ENTRY_SET.has(entryName);
|
|
610
|
+
}
|
|
611
|
+
function enrichEntryWithScriptInject(entry) {
|
|
612
|
+
if (!entry.htmlPath || !entry.scriptPath) return entry;
|
|
613
|
+
const scriptInject = getScriptInjectIfMatches(entry.htmlPath, entry.scriptPath);
|
|
614
|
+
return scriptInject ? {
|
|
615
|
+
...entry,
|
|
616
|
+
scriptInject
|
|
617
|
+
} : entry;
|
|
618
|
+
}
|
|
619
|
+
function inferHtmlPathForReservedName(entryName, scriptPath) {
|
|
620
|
+
if (!HTML_ENTRY_SET.has(entryName)) return;
|
|
621
|
+
const dir = dirname(scriptPath);
|
|
622
|
+
const htmlPath = resolve(dir, "index.html");
|
|
623
|
+
return existsSync(htmlPath) ? htmlPath : void 0;
|
|
624
|
+
}
|
|
625
|
+
function resolveEntryFromObject(baseDir, name, value) {
|
|
626
|
+
const scriptPath = resolve(baseDir, value.src);
|
|
627
|
+
if (!existsSync(scriptPath) || !isScriptPath(scriptPath)) return null;
|
|
628
|
+
const htmlPath = resolveHtmlPath(baseDir, "string" == typeof value.html ? value.html : void 0);
|
|
629
|
+
if ("string" == typeof value.html && !htmlPath) return null;
|
|
630
|
+
const inferredHtml = htmlPath ?? inferHtmlPathForReservedName(name, scriptPath);
|
|
631
|
+
const html = resolveHtmlFlag(name, value.html, Boolean(htmlPath));
|
|
632
|
+
return enrichEntryWithScriptInject({
|
|
633
|
+
name,
|
|
634
|
+
scriptPath,
|
|
635
|
+
htmlPath: inferredHtml,
|
|
636
|
+
html
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
function resolveEntryFromHtml(baseDir, name, pathStr) {
|
|
640
|
+
const htmlPath = resolve(baseDir, pathStr);
|
|
641
|
+
const dir = dirname(htmlPath);
|
|
642
|
+
let parsed;
|
|
643
|
+
try {
|
|
644
|
+
parsed = parseAddfoxEntryFromHtml(htmlPath);
|
|
645
|
+
} catch {
|
|
646
|
+
parsed = void 0;
|
|
647
|
+
}
|
|
648
|
+
let scriptPath;
|
|
649
|
+
if (parsed) try {
|
|
650
|
+
scriptPath = resolveScriptFromHtmlStrict(htmlPath).scriptPath;
|
|
651
|
+
} catch (e) {
|
|
652
|
+
throw new AddfoxError({
|
|
653
|
+
code: ADDFOX_ERROR_CODES.ENTRY_SCRIPT_FROM_HTML,
|
|
654
|
+
message: "Invalid data-addfox-entry script in HTML",
|
|
655
|
+
details: `HTML: ${htmlPath}. ${e instanceof Error ? e.message : String(e)}`,
|
|
656
|
+
hint: "data-addfox-entry script must have a relative src and the file must exist"
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
else scriptPath = findScriptForHtmlDir(dir, external_path_basename(htmlPath));
|
|
660
|
+
if (!scriptPath) return null;
|
|
661
|
+
return enrichEntryWithScriptInject({
|
|
662
|
+
name,
|
|
663
|
+
scriptPath,
|
|
664
|
+
htmlPath,
|
|
665
|
+
html: true,
|
|
666
|
+
outputFollowsScriptPath: Boolean(parsed)
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
function resolveEntryFromScript(baseDir, name, pathStr) {
|
|
670
|
+
const scriptPath = resolve(baseDir, pathStr);
|
|
671
|
+
if (!existsSync(scriptPath)) return null;
|
|
672
|
+
const htmlPath = inferHtmlPathForReservedName(name, scriptPath);
|
|
673
|
+
const html = isHtmlEntryName(name);
|
|
674
|
+
return enrichEntryWithScriptInject({
|
|
675
|
+
name,
|
|
676
|
+
scriptPath,
|
|
677
|
+
htmlPath,
|
|
678
|
+
html
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
function resolveSingleEntry(baseDir, name, value) {
|
|
682
|
+
if (isEntryConfigObject(value)) return resolveEntryFromObject(baseDir, name, value);
|
|
683
|
+
const resolved = resolve(baseDir, value);
|
|
684
|
+
if (!existsSync(resolved)) return null;
|
|
685
|
+
if (isHtmlPath(value)) return resolveEntryFromHtml(baseDir, name, value);
|
|
686
|
+
if (isScriptPath(value)) return resolveEntryFromScript(baseDir, name, value);
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
function resolveEntries(config, _root, baseDir, manifest, options) {
|
|
690
|
+
const browser = options?.browser ?? "chromium";
|
|
691
|
+
const defaultEntries = discoverEntries(baseDir, options?.entryDiscovererOptions);
|
|
692
|
+
const entryMap = new Map(defaultEntries.map((e)=>[
|
|
693
|
+
e.name,
|
|
694
|
+
e
|
|
695
|
+
]));
|
|
696
|
+
let manifestReplacementMap;
|
|
697
|
+
const entryConfig = config.entry;
|
|
698
|
+
const hasExplicitEntryConfig = entryConfig && Object.keys(entryConfig).length > 0;
|
|
699
|
+
if (manifest && !hasExplicitEntryConfig) {
|
|
700
|
+
const manifestResult = extractEntriesFromManifest(manifest, browser);
|
|
701
|
+
manifestReplacementMap = manifestResult.replacementMap;
|
|
702
|
+
for (const [name, pathStr] of Object.entries(manifestResult.entries)){
|
|
703
|
+
const resolved = resolveSingleEntry(baseDir, name, pathStr);
|
|
704
|
+
if (resolved) entryMap.set(name, resolved);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (hasExplicitEntryConfig) for (const [name, pathStr] of Object.entries(entryConfig)){
|
|
708
|
+
const resolved = resolveSingleEntry(baseDir, name, pathStr);
|
|
709
|
+
if (resolved) entryMap.set(name, resolved);
|
|
710
|
+
else entryMap.delete(name);
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
entries: Array.from(entryMap.values()),
|
|
714
|
+
manifestReplacementMap
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
class ManifestLoader {
|
|
718
|
+
readManifestJson(filePath) {
|
|
719
|
+
if (!existsSync(filePath)) return null;
|
|
720
|
+
try {
|
|
721
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
722
|
+
const data = JSON.parse(raw);
|
|
723
|
+
return "object" == typeof data && null !== data ? data : null;
|
|
724
|
+
} catch {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
isManifestPathConfig(m) {
|
|
729
|
+
if (!m || "object" != typeof m) return false;
|
|
730
|
+
const c = m;
|
|
731
|
+
return ("string" == typeof c.chromium || "string" == typeof c.firefox) && !("manifest_version" in c) && !("name" in c && "version" in c);
|
|
732
|
+
}
|
|
733
|
+
isChromiumFirefoxObject(m) {
|
|
734
|
+
return isChromiumFirefoxObjectValue(m);
|
|
735
|
+
}
|
|
736
|
+
deepMerge(base, override) {
|
|
737
|
+
const out = {
|
|
738
|
+
...base
|
|
739
|
+
};
|
|
740
|
+
for (const key of Object.keys(override)){
|
|
741
|
+
const b = base[key];
|
|
742
|
+
const o = override[key];
|
|
743
|
+
if (null === o || "object" != typeof o || Array.isArray(o) || null === b || "object" != typeof b || Array.isArray(b)) out[key] = o;
|
|
744
|
+
else out[key] = this.deepMerge(b, o);
|
|
745
|
+
}
|
|
746
|
+
return out;
|
|
747
|
+
}
|
|
748
|
+
loadFromDir(dir) {
|
|
749
|
+
const basePath = resolve(dir, MANIFEST_FILE_NAMES.base);
|
|
750
|
+
const chromPath = resolve(dir, MANIFEST_FILE_NAMES.chromium);
|
|
751
|
+
const firefoxPath = resolve(dir, MANIFEST_FILE_NAMES.firefox);
|
|
752
|
+
return {
|
|
753
|
+
base: this.readManifestJson(basePath),
|
|
754
|
+
chromium: this.readManifestJson(chromPath),
|
|
755
|
+
firefox: this.readManifestJson(firefoxPath)
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
hasAnyManifest(files) {
|
|
759
|
+
return null !== files.base || null !== files.chromium || null !== files.firefox;
|
|
760
|
+
}
|
|
761
|
+
buildConfigFromFiles(files) {
|
|
762
|
+
const base = files.base ?? files.chromium ?? files.firefox ?? null;
|
|
763
|
+
if (!base) return null;
|
|
764
|
+
const chromium = files.chromium ? this.deepMerge(base, files.chromium) : base;
|
|
765
|
+
const firefox = files.firefox ? this.deepMerge(base, files.firefox) : base;
|
|
766
|
+
return {
|
|
767
|
+
chromium,
|
|
768
|
+
firefox
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
resolve(input, root, appDir) {
|
|
772
|
+
if (null == input) {
|
|
773
|
+
const manifestSubdir = resolve(appDir, MANIFEST_DIR);
|
|
774
|
+
const baseInApp = resolve(appDir, MANIFEST_FILE_NAMES.base);
|
|
775
|
+
const baseInSub = resolve(manifestSubdir, MANIFEST_FILE_NAMES.base);
|
|
776
|
+
const dir = existsSync(baseInApp) ? appDir : existsSync(baseInSub) ? manifestSubdir : null;
|
|
777
|
+
if (null !== dir) {
|
|
778
|
+
const files = this.loadFromDir(dir);
|
|
779
|
+
if (this.hasAnyManifest(files)) return validateAndReturn(this.buildConfigFromFiles(files));
|
|
780
|
+
}
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
if (this.isManifestPathConfig(input)) {
|
|
784
|
+
const result = {};
|
|
785
|
+
if (input.chromium) {
|
|
786
|
+
const obj = this.readManifestJson(resolve(appDir, input.chromium));
|
|
787
|
+
if (obj) result.chromium = obj;
|
|
788
|
+
}
|
|
789
|
+
if (input.firefox) {
|
|
790
|
+
const obj = this.readManifestJson(resolve(appDir, input.firefox));
|
|
791
|
+
if (obj) result.firefox = obj;
|
|
792
|
+
}
|
|
793
|
+
const chrom = result.chromium ?? result.firefox;
|
|
794
|
+
const ff = result.firefox ?? result.chromium;
|
|
795
|
+
if (!chrom && !ff) return null;
|
|
796
|
+
return validateAndReturn({
|
|
797
|
+
chromium: chrom ?? {},
|
|
798
|
+
firefox: ff ?? {}
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
this.isChromiumFirefoxObject(input);
|
|
802
|
+
return validateAndReturn(input);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
const defaultLoader = new ManifestLoader();
|
|
806
|
+
function resolveManifestInput(input, root, appDir) {
|
|
807
|
+
return defaultLoader.resolve(input, root, appDir);
|
|
808
|
+
}
|
|
809
|
+
function validateAndReturn(config) {
|
|
810
|
+
if (!config) return null;
|
|
811
|
+
validateManifestSchema(config);
|
|
812
|
+
return config;
|
|
813
|
+
}
|
|
814
|
+
function validateManifestSchema(config) {
|
|
815
|
+
if (isChromiumFirefoxObjectValue(config)) {
|
|
816
|
+
if (config.chromium) validateManifestRecord(config.chromium, "chromium");
|
|
817
|
+
if (config.firefox) validateManifestRecord(config.firefox, "firefox");
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
validateManifestRecord(config);
|
|
821
|
+
}
|
|
822
|
+
function isChromiumFirefoxObjectValue(m) {
|
|
823
|
+
return "object" == typeof m && null !== m && ("chromium" in m || "firefox" in m);
|
|
824
|
+
}
|
|
825
|
+
function validateManifestRecord(manifest, target) {
|
|
826
|
+
const mv = getManifestVersion(manifest);
|
|
827
|
+
if (null == mv) throw new Error("manifest_version must be 2 or 3");
|
|
828
|
+
const errors = 2 === mv ? validateMv2(manifest) : validateMv3(manifest, target);
|
|
829
|
+
if (errors.length > 0) throw new Error(errors.join("; "));
|
|
830
|
+
}
|
|
831
|
+
function getManifestVersion(manifest) {
|
|
832
|
+
const mv = manifest.manifest_version;
|
|
833
|
+
if (2 === mv || 3 === mv) return mv;
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
function validateMv2(manifest) {
|
|
837
|
+
const errors = [];
|
|
838
|
+
if (null != manifest.action) errors.push('MV2 does not support "action"; use "browser_action" or "page_action"');
|
|
839
|
+
if (null != manifest.host_permissions) errors.push('MV2 does not support "host_permissions"; use "permissions"');
|
|
840
|
+
errors.push(...validateBackgroundMv2(manifest.background));
|
|
841
|
+
errors.push(...validatePermissionsList(manifest.permissions, "permissions"));
|
|
842
|
+
return errors;
|
|
843
|
+
}
|
|
844
|
+
function validateMv3(manifest, target) {
|
|
845
|
+
const errors = [];
|
|
846
|
+
if (null != manifest.browser_action || null != manifest.page_action) errors.push('MV3 does not support "browser_action" or "page_action"; use "action"');
|
|
847
|
+
const bgErrors = "firefox" === target ? validateBackgroundMv3Firefox(manifest.background) : validateBackgroundMv3Chrome(manifest.background);
|
|
848
|
+
errors.push(...bgErrors);
|
|
849
|
+
errors.push(...validatePermissionsList(manifest.permissions, "permissions"));
|
|
850
|
+
errors.push(...validatePermissionsList(manifest.host_permissions, "host_permissions"));
|
|
851
|
+
errors.push(...validateAction(manifest.action));
|
|
852
|
+
return errors;
|
|
853
|
+
}
|
|
854
|
+
function validateBackgroundMv2(background) {
|
|
855
|
+
const errors = [];
|
|
856
|
+
if (!background) return errors;
|
|
857
|
+
if ("object" != typeof background) {
|
|
858
|
+
errors.push('MV2 "background" must be an object');
|
|
859
|
+
return errors;
|
|
860
|
+
}
|
|
861
|
+
const bg = background;
|
|
862
|
+
if (null != bg.service_worker) errors.push('MV2 "background.service_worker" is not allowed');
|
|
863
|
+
if (null != bg.scripts && !isStringArray(bg.scripts)) errors.push('MV2 "background.scripts" must be string[]');
|
|
864
|
+
if (null != bg.page && "string" != typeof bg.page) errors.push('MV2 "background.page" must be a string');
|
|
865
|
+
return errors;
|
|
866
|
+
}
|
|
867
|
+
function validateBackgroundMv3Chrome(background) {
|
|
868
|
+
const errors = [];
|
|
869
|
+
if (!background) return errors;
|
|
870
|
+
if ("object" != typeof background) {
|
|
871
|
+
errors.push('MV3 (Chrome) "background" must be an object');
|
|
872
|
+
return errors;
|
|
873
|
+
}
|
|
874
|
+
const bg = background;
|
|
875
|
+
if (null != bg.scripts) errors.push('MV3 (Chrome) "background.scripts" is not allowed; use "service_worker"');
|
|
876
|
+
if (null != bg.page) errors.push('MV3 (Chrome) "background.page" is not allowed; use "service_worker"');
|
|
877
|
+
if (null != bg.service_worker && "string" != typeof bg.service_worker) errors.push('MV3 (Chrome) "background.service_worker" must be a string');
|
|
878
|
+
return errors;
|
|
879
|
+
}
|
|
880
|
+
function validateBackgroundMv3Firefox(background) {
|
|
881
|
+
const errors = [];
|
|
882
|
+
if (!background) return errors;
|
|
883
|
+
if ("object" != typeof background) {
|
|
884
|
+
errors.push('MV3 (Firefox) "background" must be an object');
|
|
885
|
+
return errors;
|
|
886
|
+
}
|
|
887
|
+
const bg = background;
|
|
888
|
+
const hasScripts = null != bg.scripts;
|
|
889
|
+
const hasPage = null != bg.page;
|
|
890
|
+
const hasServiceWorker = null != bg.service_worker;
|
|
891
|
+
if (!hasScripts && !hasPage && !hasServiceWorker) return errors;
|
|
892
|
+
if (hasScripts && !isStringArray(bg.scripts)) errors.push('MV3 (Firefox) "background.scripts" must be string[]');
|
|
893
|
+
if (hasPage && "string" != typeof bg.page) errors.push('MV3 (Firefox) "background.page" must be a string');
|
|
894
|
+
if (hasServiceWorker && "string" != typeof bg.service_worker) errors.push('MV3 (Firefox) "background.service_worker" must be a string when present');
|
|
895
|
+
if (hasPage && hasScripts) errors.push('MV3 (Firefox) "background" cannot have both "page" and "scripts"');
|
|
896
|
+
return errors;
|
|
897
|
+
}
|
|
898
|
+
function validateAction(action) {
|
|
899
|
+
if (null == action) return [];
|
|
900
|
+
if ("object" != typeof action) return [
|
|
901
|
+
'"action" must be an object'
|
|
902
|
+
];
|
|
903
|
+
const act = action;
|
|
904
|
+
if (null != act.default_popup && "string" != typeof act.default_popup) return [
|
|
905
|
+
'"action.default_popup" must be a string'
|
|
906
|
+
];
|
|
907
|
+
return [];
|
|
908
|
+
}
|
|
909
|
+
function validatePermissionsList(value, key) {
|
|
910
|
+
if (null == value) return [];
|
|
911
|
+
return isStringArray(value) ? [] : [
|
|
912
|
+
`"${key}" must be string[]`
|
|
913
|
+
];
|
|
914
|
+
}
|
|
915
|
+
function isStringArray(value) {
|
|
916
|
+
return Array.isArray(value) && value.every((item)=>"string" == typeof item);
|
|
917
|
+
}
|
|
918
|
+
const loader_require = createRequire("u" > typeof __filename ? __filename : import.meta.url);
|
|
919
|
+
function loadWithJiti(root, configPath) {
|
|
920
|
+
const isConfigRestart = "1" === process.env.ADDFOX_CONFIG_RESTART;
|
|
921
|
+
const jitiOptions = {
|
|
922
|
+
esmResolve: true
|
|
923
|
+
};
|
|
924
|
+
if (isConfigRestart) {
|
|
925
|
+
jitiOptions.moduleCache = false;
|
|
926
|
+
jitiOptions.requireCache = false;
|
|
927
|
+
}
|
|
928
|
+
const jitiFn = loader_require("jiti")(root, jitiOptions);
|
|
929
|
+
return jitiFn(configPath);
|
|
930
|
+
}
|
|
931
|
+
function loadNativeJs(configPath) {
|
|
932
|
+
const mod = loader_require(configPath);
|
|
933
|
+
return mod?.default ?? mod;
|
|
934
|
+
}
|
|
935
|
+
function unwrapConfig(mod) {
|
|
936
|
+
if (null == mod) return {};
|
|
937
|
+
if ("object" == typeof mod && "default" in mod && void 0 !== mod.default) return mod.default ?? {};
|
|
938
|
+
return mod ?? {};
|
|
939
|
+
}
|
|
940
|
+
function loadConfigModule(root, configPath, file) {
|
|
941
|
+
const isNativeJs = file.endsWith(".js") || file.endsWith(".mjs");
|
|
942
|
+
if (isNativeJs) try {
|
|
943
|
+
return unwrapConfig(loadNativeJs(configPath));
|
|
944
|
+
} catch {}
|
|
945
|
+
return unwrapConfig(loadWithJiti(root, configPath));
|
|
946
|
+
}
|
|
947
|
+
function findConfigFile(root, configFiles) {
|
|
948
|
+
for (const file of configFiles){
|
|
949
|
+
const p = resolve(root, file);
|
|
950
|
+
if (existsSync(p)) return p;
|
|
951
|
+
}
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
function loadConfigAtPath(root, configPath, file) {
|
|
955
|
+
try {
|
|
956
|
+
return loadConfigModule(root, configPath, file);
|
|
957
|
+
} catch (e) {
|
|
958
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
959
|
+
throw new AddfoxError({
|
|
960
|
+
code: ADDFOX_ERROR_CODES.CONFIG_LOAD_FAILED,
|
|
961
|
+
message: "Failed to load config file",
|
|
962
|
+
details: `File: ${configPath}, error: ${message}`,
|
|
963
|
+
hint: "Check addfox.config syntax and dependencies",
|
|
964
|
+
cause: e instanceof Error ? e : void 0
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function loadConfigFile(root, configFiles = CONFIG_FILES) {
|
|
969
|
+
for (const file of configFiles){
|
|
970
|
+
const p = resolve(root, file);
|
|
971
|
+
if (existsSync(p)) return loadConfigAtPath(root, p, file);
|
|
972
|
+
}
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
function getResolvedConfigFilePath(root, configFiles = CONFIG_FILES) {
|
|
976
|
+
return findConfigFile(root, configFiles);
|
|
977
|
+
}
|
|
978
|
+
function resolveAppDir(root, userConfig) {
|
|
979
|
+
return resolve(root, userConfig.appDir ?? "app");
|
|
980
|
+
}
|
|
981
|
+
function validateAppDir(appDir, entryDisabled) {
|
|
982
|
+
if (entryDisabled) return;
|
|
983
|
+
if (!existsSync(appDir) || !statSync(appDir).isDirectory()) throw new AddfoxError({
|
|
984
|
+
code: ADDFOX_ERROR_CODES.APP_DIR_MISSING,
|
|
985
|
+
message: "App directory not found",
|
|
986
|
+
details: `Missing appDir: ${appDir}`,
|
|
987
|
+
hint: "Create the directory or set appDir to an existing folder (default is app/)"
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
function createResolvedConfig(userConfig, root, appDir, manifest) {
|
|
991
|
+
return {
|
|
992
|
+
...userConfig,
|
|
993
|
+
manifest,
|
|
994
|
+
appDir,
|
|
995
|
+
outDir: userConfig.outDir ?? DEFAULT_OUT_DIR,
|
|
996
|
+
envPrefix: userConfig.envPrefix ?? [
|
|
997
|
+
...DEFAULT_ENV_PREFIXES
|
|
998
|
+
],
|
|
999
|
+
outputRoot: ADDFOX_OUTPUT_ROOT,
|
|
1000
|
+
root
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function discoverAndResolveEntries(userConfig, root, appDir, manifest, entryDiscovererOptions, entryResolverOptions) {
|
|
1004
|
+
const baseEntries = discoverEntries(appDir, entryDiscovererOptions);
|
|
1005
|
+
const result = resolveEntries(userConfig, root, appDir, manifest ?? void 0, entryResolverOptions);
|
|
1006
|
+
return {
|
|
1007
|
+
baseEntries,
|
|
1008
|
+
entries: result.entries
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function resolveAddfoxConfig(root, options = {}) {
|
|
1012
|
+
const t0 = performance.now();
|
|
1013
|
+
const userConfig = loadConfigFile(root, options.configFiles);
|
|
1014
|
+
if (!userConfig) throw new AddfoxError({
|
|
1015
|
+
code: ADDFOX_ERROR_CODES.CONFIG_NOT_FOUND,
|
|
1016
|
+
message: "Addfox config file not found",
|
|
1017
|
+
details: `No addfox.config.ts, addfox.config.js or addfox.config.mjs found under ${root}`,
|
|
1018
|
+
hint: "Run the command from project root or create addfox.config.ts / addfox.config.js"
|
|
1019
|
+
});
|
|
1020
|
+
const appDir = resolveAppDir(root, userConfig);
|
|
1021
|
+
const entryDisabled = false === userConfig.entry;
|
|
1022
|
+
validateAppDir(appDir, entryDisabled);
|
|
1023
|
+
const manifest = resolveManifestInput(userConfig.manifest, root, appDir);
|
|
1024
|
+
if (!manifest) throw new AddfoxError({
|
|
1025
|
+
code: ADDFOX_ERROR_CODES.MANIFEST_MISSING,
|
|
1026
|
+
message: "Manifest config or file not found",
|
|
1027
|
+
details: "Configure manifest in addfox.config, or place manifest.json / manifest.chromium.json / manifest.firefox.json under appDir or appDir/manifest",
|
|
1028
|
+
hint: "Option 1: manifest: { name, version, ... } in addfox.config; Option 2: manifest.json under appDir or appDir/manifest; Option 3: manifest: { chromium: 'path/to/manifest.json' } (path relative to appDir)"
|
|
1029
|
+
});
|
|
1030
|
+
const config = createResolvedConfig(userConfig, root, appDir, manifest);
|
|
1031
|
+
const manifestForEntry = "object" == typeof manifest && null !== manifest ? manifest.chromium ?? manifest.firefox ?? manifest : null;
|
|
1032
|
+
const { baseEntries, entries } = entryDisabled ? {
|
|
1033
|
+
baseEntries: [],
|
|
1034
|
+
entries: []
|
|
1035
|
+
} : discoverAndResolveEntries(userConfig, root, appDir, manifestForEntry, options.entryDiscovererOptions, options.entryResolverOptions);
|
|
1036
|
+
if (!entryDisabled && 0 === entries.length) throw new AddfoxError({
|
|
1037
|
+
code: ADDFOX_ERROR_CODES.NO_ENTRIES,
|
|
1038
|
+
message: "No entries discovered",
|
|
1039
|
+
details: `No background, content, popup, options or sidepanel entry found under ${appDir}`,
|
|
1040
|
+
hint: "At least one such directory with index.ts / index.js etc. is required"
|
|
1041
|
+
});
|
|
1042
|
+
logDoneTimed("Parse config", Math.round(performance.now() - t0));
|
|
1043
|
+
logEntriesTable(entries, {
|
|
1044
|
+
root
|
|
1045
|
+
});
|
|
1046
|
+
return {
|
|
1047
|
+
config,
|
|
1048
|
+
baseEntries,
|
|
1049
|
+
entries
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function clearConfigCache(configFilePath) {
|
|
1053
|
+
try {
|
|
1054
|
+
delete loader_require.cache[configFilePath];
|
|
1055
|
+
} catch {}
|
|
1056
|
+
for (const key of Object.keys(loader_require.cache)){
|
|
1057
|
+
const mod = loader_require.cache[key];
|
|
1058
|
+
if (mod?.filename === configFilePath) try {
|
|
1059
|
+
delete loader_require.cache[key];
|
|
1060
|
+
} catch {}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function getResolvedRstestConfigFilePath(root) {
|
|
1064
|
+
for (const file of RSTEST_CONFIG_FILES){
|
|
1065
|
+
const p = resolve(root, file);
|
|
1066
|
+
if (existsSync(p)) return p;
|
|
1067
|
+
}
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
const ADDFOX_PLACEHOLDER_REGEX = /\[addfox\.([a-z]+)\]/g;
|
|
1071
|
+
const CONTENT_PLACEHOLDER = "[addfox.content]";
|
|
1072
|
+
const CONTENT_PLACEHOLDER_LEGACY = "[addfox.content]";
|
|
1073
|
+
const SCRIPT_KEYS_SET = new Set(SCRIPT_ONLY_ENTRY_NAMES);
|
|
1074
|
+
function builder_isSourceFilePath(path) {
|
|
1075
|
+
const lower = path.trim().toLowerCase();
|
|
1076
|
+
return /\.(ts|tsx|js|jsx)$/.test(lower);
|
|
1077
|
+
}
|
|
1078
|
+
function isChromiumFirefoxManifest(m) {
|
|
1079
|
+
return "object" == typeof m && null !== m && ("chromium" in m || "firefox" in m);
|
|
1080
|
+
}
|
|
1081
|
+
function isEntryFieldEmpty(value) {
|
|
1082
|
+
if (null == value) return true;
|
|
1083
|
+
if ("string" == typeof value) return "" === value.trim();
|
|
1084
|
+
if (Array.isArray(value)) return 0 === value.length;
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
function hasNonEmptyStringArray(value) {
|
|
1088
|
+
return Array.isArray(value) && value.length > 0;
|
|
1089
|
+
}
|
|
1090
|
+
function isManifestRecord(value) {
|
|
1091
|
+
return null !== value && "object" == typeof value && !Array.isArray(value);
|
|
1092
|
+
}
|
|
1093
|
+
function getManifestRecordForTarget(config, target) {
|
|
1094
|
+
return pickManifestForTarget(config, target).manifest;
|
|
1095
|
+
}
|
|
1096
|
+
function pickManifestForTarget(config, target) {
|
|
1097
|
+
if (!isChromiumFirefoxManifest(config)) return {
|
|
1098
|
+
manifest: config
|
|
1099
|
+
};
|
|
1100
|
+
const hasChromium = null != config.chromium && "object" == typeof config.chromium;
|
|
1101
|
+
const hasFirefox = null != config.firefox && "object" == typeof config.firefox;
|
|
1102
|
+
if (hasChromium && hasFirefox) {
|
|
1103
|
+
const match = config[target];
|
|
1104
|
+
if (null != match && "object" == typeof match) return {
|
|
1105
|
+
manifest: match
|
|
1106
|
+
};
|
|
1107
|
+
const fallback = "firefox" === target ? config.chromium : config.firefox;
|
|
1108
|
+
return {
|
|
1109
|
+
manifest: fallback,
|
|
1110
|
+
warnMessage: `Build target is ${target} but manifest.${target} is missing; using ${"firefox" === target ? "chromium" : "firefox"} manifest.`
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
if (hasChromium) return {
|
|
1114
|
+
manifest: config.chromium,
|
|
1115
|
+
warnMessage: "firefox" === target ? "Build target is firefox but manifest only has chromium; using chromium manifest." : void 0
|
|
1116
|
+
};
|
|
1117
|
+
if (hasFirefox) return {
|
|
1118
|
+
manifest: config.firefox,
|
|
1119
|
+
warnMessage: "chromium" === target ? "Build target is chromium but manifest only has firefox; using firefox manifest." : void 0
|
|
1120
|
+
};
|
|
1121
|
+
return {
|
|
1122
|
+
manifest: config.chromium ?? config.firefox ?? {},
|
|
1123
|
+
warnMessage: "Manifest has no chromium or firefox object; using fallback."
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function buildScriptOutputPath(entry) {
|
|
1127
|
+
const scriptStem = external_path_basename(entry.scriptPath, extname(entry.scriptPath));
|
|
1128
|
+
return scriptStem === entry.name ? `${entry.name}.js` : `${entry.name}/index.js`;
|
|
1129
|
+
}
|
|
1130
|
+
function buildHtmlOutputPath(entry, fallback) {
|
|
1131
|
+
if (!entry.htmlPath) return fallback;
|
|
1132
|
+
const htmlFile = external_path_basename(entry.htmlPath).toLowerCase();
|
|
1133
|
+
return htmlFile === `${entry.name}.html` ? `${entry.name}.html` : fallback;
|
|
1134
|
+
}
|
|
1135
|
+
function buildPlaceholderMap(entries) {
|
|
1136
|
+
const map = {};
|
|
1137
|
+
for (const key of MANIFEST_ENTRY_KEYS){
|
|
1138
|
+
const entry = entries.find((e)=>e.name === key);
|
|
1139
|
+
if (entry) map[key] = SCRIPT_KEYS_SET.has(key) ? buildScriptOutputPath(entry) : buildHtmlOutputPath(entry, MANIFEST_ENTRY_PATHS[key]);
|
|
1140
|
+
}
|
|
1141
|
+
return map;
|
|
1142
|
+
}
|
|
1143
|
+
function buildSourceToOutputMap(entries) {
|
|
1144
|
+
const map = new Map();
|
|
1145
|
+
const placeholderMap = buildPlaceholderMap(entries);
|
|
1146
|
+
for (const key of MANIFEST_ENTRY_KEYS){
|
|
1147
|
+
const entry = entries.find((e)=>e.name === key);
|
|
1148
|
+
if (!entry) continue;
|
|
1149
|
+
const sourcePath = entry.scriptPath;
|
|
1150
|
+
const outputPath = placeholderMap[key];
|
|
1151
|
+
if (sourcePath && outputPath) {
|
|
1152
|
+
map.set(sourcePath, outputPath);
|
|
1153
|
+
const basename = sourcePath.split(/[\\/]/).pop();
|
|
1154
|
+
if (basename) {
|
|
1155
|
+
map.set(basename, outputPath);
|
|
1156
|
+
map.set(`./${basename}`, outputPath);
|
|
1157
|
+
}
|
|
1158
|
+
const entryDir = key;
|
|
1159
|
+
const entryIndexPath = `${entryDir}/${basename}`;
|
|
1160
|
+
map.set(entryIndexPath, outputPath);
|
|
1161
|
+
map.set(`./${entryIndexPath}`, outputPath);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return map;
|
|
1165
|
+
}
|
|
1166
|
+
function shouldUseServiceWorker(mv, browser) {
|
|
1167
|
+
return 3 === mv && "chromium" === browser;
|
|
1168
|
+
}
|
|
1169
|
+
function isChromiumOnlyAutofillKey(key) {
|
|
1170
|
+
return "sidepanel" === key || "newtab" === key || "bookmarks" === key || "history" === key;
|
|
1171
|
+
}
|
|
1172
|
+
const fillers = {
|
|
1173
|
+
background: (mv, browser)=>(out, path)=>{
|
|
1174
|
+
const bg = out.background ?? {};
|
|
1175
|
+
if (shouldUseServiceWorker(mv, browser)) {
|
|
1176
|
+
if (isEntryFieldEmpty(bg.service_worker)) out.background = {
|
|
1177
|
+
...bg,
|
|
1178
|
+
service_worker: path
|
|
1179
|
+
};
|
|
1180
|
+
} else {
|
|
1181
|
+
const scripts = bg.scripts;
|
|
1182
|
+
if (!Array.isArray(scripts) || 0 === scripts.length) out.background = {
|
|
1183
|
+
...bg,
|
|
1184
|
+
scripts: [
|
|
1185
|
+
path
|
|
1186
|
+
]
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
popup: (mv)=>(out, path)=>{
|
|
1191
|
+
if (3 === mv) {
|
|
1192
|
+
const action = out.action ?? {};
|
|
1193
|
+
if (isEntryFieldEmpty(action.default_popup)) out.action = {
|
|
1194
|
+
...action,
|
|
1195
|
+
default_popup: path
|
|
1196
|
+
};
|
|
1197
|
+
} else {
|
|
1198
|
+
const ba = out.browser_action ?? {};
|
|
1199
|
+
if (isEntryFieldEmpty(ba.default_popup)) out.browser_action = {
|
|
1200
|
+
...ba,
|
|
1201
|
+
default_popup: path
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
},
|
|
1205
|
+
options: (mv)=>(out, path)=>{
|
|
1206
|
+
if (3 === mv) {
|
|
1207
|
+
const opt = out.options_ui ?? {};
|
|
1208
|
+
if (isEntryFieldEmpty(opt.page)) out.options_ui = {
|
|
1209
|
+
...opt,
|
|
1210
|
+
page: path
|
|
1211
|
+
};
|
|
1212
|
+
} else if (isEntryFieldEmpty(out.options_page)) out.options_page = path;
|
|
1213
|
+
},
|
|
1214
|
+
devtools: ()=>(out, path)=>{
|
|
1215
|
+
if (isEntryFieldEmpty(out.devtools_page)) out.devtools_page = path;
|
|
1216
|
+
},
|
|
1217
|
+
sandbox: ()=>(out, path)=>{
|
|
1218
|
+
const sandbox = out.sandbox;
|
|
1219
|
+
if (null === sandbox || "object" != typeof sandbox || Array.isArray(sandbox)) {
|
|
1220
|
+
out.sandbox = {
|
|
1221
|
+
pages: [
|
|
1222
|
+
path
|
|
1223
|
+
]
|
|
1224
|
+
};
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const pages = sandbox.pages;
|
|
1228
|
+
if (!Array.isArray(pages) || 0 === pages.length) out.sandbox = {
|
|
1229
|
+
...sandbox,
|
|
1230
|
+
pages: [
|
|
1231
|
+
path
|
|
1232
|
+
]
|
|
1233
|
+
};
|
|
1234
|
+
},
|
|
1235
|
+
content: ()=>(out, path)=>{
|
|
1236
|
+
const cs = out.content_scripts;
|
|
1237
|
+
if (!Array.isArray(cs) || 0 === cs.length) out.content_scripts = [
|
|
1238
|
+
{
|
|
1239
|
+
matches: [
|
|
1240
|
+
"<all_urls>"
|
|
1241
|
+
],
|
|
1242
|
+
js: [
|
|
1243
|
+
path
|
|
1244
|
+
],
|
|
1245
|
+
run_at: "document_idle"
|
|
1246
|
+
}
|
|
1247
|
+
];
|
|
1248
|
+
},
|
|
1249
|
+
newtab: ()=>(out, path)=>{
|
|
1250
|
+
const overrides = out.chrome_url_overrides ?? {};
|
|
1251
|
+
if (isEntryFieldEmpty(overrides.newtab)) out.chrome_url_overrides = {
|
|
1252
|
+
...overrides,
|
|
1253
|
+
newtab: path
|
|
1254
|
+
};
|
|
1255
|
+
},
|
|
1256
|
+
bookmarks: ()=>(out, path)=>{
|
|
1257
|
+
const overrides = out.chrome_url_overrides ?? {};
|
|
1258
|
+
if (isEntryFieldEmpty(overrides.bookmarks)) out.chrome_url_overrides = {
|
|
1259
|
+
...overrides,
|
|
1260
|
+
bookmarks: path
|
|
1261
|
+
};
|
|
1262
|
+
ensurePermission(out, "bookmarks");
|
|
1263
|
+
},
|
|
1264
|
+
history: ()=>(out, path)=>{
|
|
1265
|
+
const overrides = out.chrome_url_overrides ?? {};
|
|
1266
|
+
if (isEntryFieldEmpty(overrides.history)) out.chrome_url_overrides = {
|
|
1267
|
+
...overrides,
|
|
1268
|
+
history: path
|
|
1269
|
+
};
|
|
1270
|
+
ensurePermission(out, "history");
|
|
1271
|
+
},
|
|
1272
|
+
sidepanel: ()=>(out, path)=>{
|
|
1273
|
+
const sp = out.side_panel ?? {};
|
|
1274
|
+
if (isEntryFieldEmpty(sp.default_path)) out.side_panel = {
|
|
1275
|
+
...sp,
|
|
1276
|
+
default_path: path
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
function ensurePermission(out, permission) {
|
|
1281
|
+
const current = out.permissions;
|
|
1282
|
+
if (!Array.isArray(current)) {
|
|
1283
|
+
out.permissions = [
|
|
1284
|
+
permission
|
|
1285
|
+
];
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
if (!current.includes(permission)) out.permissions = [
|
|
1289
|
+
...current,
|
|
1290
|
+
permission
|
|
1291
|
+
];
|
|
1292
|
+
}
|
|
1293
|
+
function buildAutoFillers(mv, browser) {
|
|
1294
|
+
const result = {};
|
|
1295
|
+
for (const [key, factory] of Object.entries(fillers))if ("sidepanel" !== key || 3 === mv) {
|
|
1296
|
+
if (!("firefox" === browser && isChromiumOnlyAutofillKey(key))) result[key] = factory(mv, browser);
|
|
1297
|
+
}
|
|
1298
|
+
return result;
|
|
1299
|
+
}
|
|
1300
|
+
function needsSidePanelPermission(out, placeholderMap, browser) {
|
|
1301
|
+
if ("chromium" !== browser) return false;
|
|
1302
|
+
if (null != placeholderMap.sidepanel) return true;
|
|
1303
|
+
const sp = out.side_panel;
|
|
1304
|
+
if (null == sp || "object" != typeof sp || Array.isArray(sp)) return false;
|
|
1305
|
+
const path = sp.default_path;
|
|
1306
|
+
return "string" == typeof path && "" !== path.trim();
|
|
1307
|
+
}
|
|
1308
|
+
function autoFillEntryFields(manifest, placeholderMap, browser) {
|
|
1309
|
+
const mv = 2 === manifest.manifest_version ? 2 : 3;
|
|
1310
|
+
const out = {
|
|
1311
|
+
...manifest
|
|
1312
|
+
};
|
|
1313
|
+
const autoFillers = buildAutoFillers(mv, browser);
|
|
1314
|
+
for (const [key, path] of Object.entries(placeholderMap))autoFillers[key]?.(out, path);
|
|
1315
|
+
if (3 === mv && needsSidePanelPermission(out, placeholderMap, browser)) ensurePermission(out, "sidePanel");
|
|
1316
|
+
return out;
|
|
1317
|
+
}
|
|
1318
|
+
function replacePlaceholderInString(str, placeholderMap) {
|
|
1319
|
+
return str.replace(ADDFOX_PLACEHOLDER_REGEX, (_, key)=>key in placeholderMap ? placeholderMap[key] : `[addfox.${key}]`);
|
|
1320
|
+
}
|
|
1321
|
+
function replacePlaceholdersInValue(value, placeholderMap) {
|
|
1322
|
+
if ("string" == typeof value) return replacePlaceholderInString(value, placeholderMap);
|
|
1323
|
+
if (Array.isArray(value)) return value.map((item)=>replacePlaceholdersInValue(item, placeholderMap));
|
|
1324
|
+
if (null !== value && "object" == typeof value) {
|
|
1325
|
+
const out = {};
|
|
1326
|
+
for (const [k, v] of Object.entries(value))out[k] = replacePlaceholdersInValue(v, placeholderMap);
|
|
1327
|
+
return out;
|
|
1328
|
+
}
|
|
1329
|
+
return value;
|
|
1330
|
+
}
|
|
1331
|
+
function replaceSourcePathsWithOutput(value, sourceToOutputMap) {
|
|
1332
|
+
if ("string" == typeof value) {
|
|
1333
|
+
if (builder_isSourceFilePath(value)) {
|
|
1334
|
+
const outputPath = sourceToOutputMap.get(value);
|
|
1335
|
+
if (outputPath) return outputPath;
|
|
1336
|
+
const basename = value.split(/[\\/]/).pop();
|
|
1337
|
+
if (basename) {
|
|
1338
|
+
const fromBasename = sourceToOutputMap.get(basename);
|
|
1339
|
+
if (fromBasename) return fromBasename;
|
|
1340
|
+
const fromDotSlash = sourceToOutputMap.get(`./${basename}`);
|
|
1341
|
+
if (fromDotSlash) return fromDotSlash;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
return value;
|
|
1345
|
+
}
|
|
1346
|
+
if (Array.isArray(value)) return value.map((item)=>replaceSourcePathsWithOutput(item, sourceToOutputMap));
|
|
1347
|
+
if (null !== value && "object" == typeof value) {
|
|
1348
|
+
const out = {};
|
|
1349
|
+
for (const [k, v] of Object.entries(value))out[k] = replaceSourcePathsWithOutput(v, sourceToOutputMap);
|
|
1350
|
+
return out;
|
|
1351
|
+
}
|
|
1352
|
+
return value;
|
|
1353
|
+
}
|
|
1354
|
+
function resolveContentScriptsPlaceholders(manifest, placeholderMap, contentScriptOutput) {
|
|
1355
|
+
const contentScripts = manifest.content_scripts;
|
|
1356
|
+
if (!Array.isArray(contentScripts)) return manifest;
|
|
1357
|
+
const defaultContentJs = placeholderMap.content ? [
|
|
1358
|
+
placeholderMap.content
|
|
1359
|
+
] : [];
|
|
1360
|
+
const shouldAutoFillCss = contentScriptOutput?.autoFillCssInManifest !== false;
|
|
1361
|
+
const resolved = contentScripts.map((item)=>{
|
|
1362
|
+
if (null === item || "object" != typeof item || Array.isArray(item)) return item;
|
|
1363
|
+
const obj = item;
|
|
1364
|
+
const out = {
|
|
1365
|
+
...obj
|
|
1366
|
+
};
|
|
1367
|
+
if (Array.isArray(obj.js)) {
|
|
1368
|
+
const jsReplacement = contentScriptOutput?.js ?? defaultContentJs;
|
|
1369
|
+
out.js = obj.js.flatMap((el)=>el === CONTENT_PLACEHOLDER || el === CONTENT_PLACEHOLDER_LEGACY ? jsReplacement : [
|
|
1370
|
+
el
|
|
1371
|
+
]);
|
|
1372
|
+
}
|
|
1373
|
+
if (Array.isArray(obj.css)) {
|
|
1374
|
+
const cssReplacement = contentScriptOutput?.css ?? [];
|
|
1375
|
+
const resolvedCss = obj.css.flatMap((el)=>el === CONTENT_PLACEHOLDER || el === CONTENT_PLACEHOLDER_LEGACY ? cssReplacement : [
|
|
1376
|
+
el
|
|
1377
|
+
]);
|
|
1378
|
+
if (resolvedCss.length > 0) out.css = resolvedCss;
|
|
1379
|
+
else delete out.css;
|
|
1380
|
+
}
|
|
1381
|
+
const hadJs = hasNonEmptyStringArray(out.js);
|
|
1382
|
+
const hadCss = hasNonEmptyStringArray(out.css);
|
|
1383
|
+
if (!hadJs && !hadCss && placeholderMap.content) out.js = contentScriptOutput?.js ?? defaultContentJs;
|
|
1384
|
+
const hasJs = hasNonEmptyStringArray(out.js);
|
|
1385
|
+
const isContentItem = hasJs && null != placeholderMap.content && out.js.includes(placeholderMap.content);
|
|
1386
|
+
if (shouldAutoFillCss && isContentItem && contentScriptOutput?.css?.length && !hasNonEmptyStringArray(out.css)) out.css = contentScriptOutput.css;
|
|
1387
|
+
return out;
|
|
1388
|
+
});
|
|
1389
|
+
return {
|
|
1390
|
+
...manifest,
|
|
1391
|
+
content_scripts: resolved
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
function replaceContentScriptSourcePaths(manifest, sourceToOutputMap) {
|
|
1395
|
+
const contentScripts = manifest.content_scripts;
|
|
1396
|
+
if (!Array.isArray(contentScripts)) return manifest;
|
|
1397
|
+
const resolved = contentScripts.map((item)=>{
|
|
1398
|
+
if (null === item || "object" != typeof item || Array.isArray(item)) return item;
|
|
1399
|
+
const obj = item;
|
|
1400
|
+
const out = {
|
|
1401
|
+
...obj
|
|
1402
|
+
};
|
|
1403
|
+
if (Array.isArray(obj.js)) out.js = obj.js.map((path)=>{
|
|
1404
|
+
if ("string" == typeof path && builder_isSourceFilePath(path)) {
|
|
1405
|
+
const outputPath = sourceToOutputMap.get(path);
|
|
1406
|
+
if (outputPath) return outputPath;
|
|
1407
|
+
const basename = path.split(/[\\/]/).pop();
|
|
1408
|
+
if (basename) {
|
|
1409
|
+
const fromBasename = sourceToOutputMap.get(basename);
|
|
1410
|
+
if (fromBasename) return fromBasename;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return path;
|
|
1414
|
+
});
|
|
1415
|
+
return out;
|
|
1416
|
+
});
|
|
1417
|
+
return {
|
|
1418
|
+
...manifest,
|
|
1419
|
+
content_scripts: resolved
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
function buildManifest(manifest, entries, browser, contentScriptOutput) {
|
|
1423
|
+
const placeholderMap = buildPlaceholderMap(entries);
|
|
1424
|
+
const sourceToOutputMap = buildSourceToOutputMap(entries);
|
|
1425
|
+
const pipeline = [
|
|
1426
|
+
(m)=>autoFillEntryFields(m, placeholderMap, browser),
|
|
1427
|
+
(m)=>resolveContentScriptsPlaceholders(m, placeholderMap, contentScriptOutput),
|
|
1428
|
+
(m)=>replaceSourcePathsWithOutput(m, sourceToOutputMap),
|
|
1429
|
+
(m)=>replaceContentScriptSourcePaths(m, sourceToOutputMap),
|
|
1430
|
+
(m)=>replacePlaceholdersInValue(m, placeholderMap)
|
|
1431
|
+
];
|
|
1432
|
+
const result = pipeline.reduce((acc, fn)=>fn(acc), {
|
|
1433
|
+
...manifest
|
|
1434
|
+
});
|
|
1435
|
+
return isManifestRecord(result) ? result : manifest;
|
|
1436
|
+
}
|
|
1437
|
+
function buildForBrowser(config, entries, browser, onWarn, contentScriptOutput) {
|
|
1438
|
+
const { manifest, warnMessage } = pickManifestForTarget(config, browser);
|
|
1439
|
+
if (warnMessage) onWarn?.(warnMessage);
|
|
1440
|
+
return buildManifest(manifest, entries, browser, contentScriptOutput);
|
|
1441
|
+
}
|
|
1442
|
+
function resolveManifestChromium(config, entries) {
|
|
1443
|
+
return buildForBrowser(config, entries, "chromium");
|
|
1444
|
+
}
|
|
1445
|
+
function resolveManifestFirefox(config, entries) {
|
|
1446
|
+
return buildForBrowser(config, entries, "firefox");
|
|
1447
|
+
}
|
|
1448
|
+
function resolveManifestForTarget(config, entries, target, onWarn, contentScriptOutput) {
|
|
1449
|
+
return buildForBrowser(config, entries, target, onWarn, contentScriptOutput);
|
|
1450
|
+
}
|
|
1451
|
+
function toReloadManagerEntries(entries, root) {
|
|
1452
|
+
return entries.filter((e)=>RELOAD_MANAGER_ENTRY_NAMES.has(e.name)).map((e)=>({
|
|
1453
|
+
name: e.name,
|
|
1454
|
+
path: resolve(root, e.scriptPath)
|
|
1455
|
+
}));
|
|
1456
|
+
}
|
|
1457
|
+
const version_require = external_node_module_createRequire(import.meta.url);
|
|
1458
|
+
function findPackageJson() {
|
|
1459
|
+
const __dirname = external_node_path_dirname(fileURLToPath(import.meta.url));
|
|
1460
|
+
const distPath = external_node_path_resolve(__dirname, "../../package.json");
|
|
1461
|
+
if (external_node_fs_existsSync(distPath)) return version_require(distPath);
|
|
1462
|
+
const srcPath = external_node_path_resolve(__dirname, "../package.json");
|
|
1463
|
+
if (external_node_fs_existsSync(srcPath)) return version_require(srcPath);
|
|
1464
|
+
return {};
|
|
1465
|
+
}
|
|
1466
|
+
const version_pkg = findPackageJson();
|
|
1467
|
+
function getAddfoxVersion() {
|
|
1468
|
+
return version_pkg?.version ?? "0.0.0";
|
|
1469
|
+
}
|
|
1470
|
+
const FRAMEWORK_KEYS = [
|
|
1471
|
+
{
|
|
1472
|
+
pkg: "solid-js",
|
|
1473
|
+
name: "Solid"
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
pkg: "svelte",
|
|
1477
|
+
name: "Svelte"
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
pkg: "vue",
|
|
1481
|
+
name: "Vue"
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
pkg: "preact",
|
|
1485
|
+
name: "Preact"
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
pkg: "react",
|
|
1489
|
+
name: "React"
|
|
1490
|
+
}
|
|
1491
|
+
];
|
|
1492
|
+
function readPackageJson(root) {
|
|
1493
|
+
const path = join(root, "package.json");
|
|
1494
|
+
if (!external_node_fs_existsSync(path)) return null;
|
|
1495
|
+
try {
|
|
1496
|
+
const raw = external_node_fs_readFileSync(path, "utf-8");
|
|
1497
|
+
return JSON.parse(raw);
|
|
1498
|
+
} catch {
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function hasDep(pkg, key) {
|
|
1503
|
+
const deps = pkg.dependencies;
|
|
1504
|
+
const devDeps = pkg.devDependencies;
|
|
1505
|
+
if (deps && "string" == typeof deps[key]) return true;
|
|
1506
|
+
if (devDeps && "string" == typeof devDeps[key]) return true;
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
function detectFrontendFramework(root) {
|
|
1510
|
+
if (!root || "string" != typeof root) return "Vanilla";
|
|
1511
|
+
const pkg = readPackageJson(root);
|
|
1512
|
+
if (!pkg) return "Vanilla";
|
|
1513
|
+
for (const { pkg: key, name } of FRAMEWORK_KEYS)if (hasDep(pkg, key)) return name;
|
|
1514
|
+
return "Vanilla";
|
|
1515
|
+
}
|
|
1516
|
+
var src_DEFAULT_APP_DIR = "app";
|
|
1517
|
+
var src_HMR_WS_PORT = 23333;
|
|
1518
|
+
export { Pipeline } from "./434.js";
|
|
1519
|
+
export { ADDFOX_OUTPUT_ROOT, BROWSER_OUTPUT_PREFIX, CLI_COMMANDS, CONFIG_FILES, DEFAULT_BROWSER, DEFAULT_ENV_PREFIXES, DEFAULT_OUT_DIR, HTML_ENTRY_NAMES, MANIFEST_DIR, MANIFEST_ENTRY_KEYS, MANIFEST_ENTRY_PATHS, MANIFEST_FILE_NAMES, ManifestLoader, RELOAD_MANAGER_ENTRY_NAMES, RESERVED_ENTRY_NAMES, RSTEST_CONFIG_FILES, SCRIPT_EXTS, SCRIPT_ONLY_ENTRY_NAMES, SUPPORTED_BROWSERS, SUPPORTED_LAUNCH_TARGETS, buildForBrowser, clearConfigCache, defineConfig, detectFrontendFramework, discoverEntries, extractEntriesFromManifest, getAddfoxVersion, getAllEntryNames, getBrowserOutputDir, getHtmlEntryNames, getManifestRecordForTarget, getResolvedConfigFilePath, getResolvedRstestConfigFilePath, getScriptInjectIfMatches, getScriptOnlyEntryNames, isScriptSrcRelative, loadConfigFile, parseAddfoxEntryFromHtml, resolveAddfoxConfig, resolveEntries, resolveManifestChromium, resolveManifestFirefox, resolveManifestForTarget, resolveManifestInput, resolveScriptFromHtmlStrict, src_DEFAULT_APP_DIR as DEFAULT_APP_DIR, src_HMR_WS_PORT as HMR_WS_PORT, toReloadManagerEntries };
|
|
1520
|
+
|
|
1521
|
+
//# sourceMappingURL=index.js.map
|