wxt 0.11.1 → 0.12.0
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/bin/wxt.mjs +2 -0
- package/dist/{chunk-65FJ5ESC.js → chunk-UY7NE73P.js} +145 -111
- package/dist/cli.js +3119 -0
- package/dist/client.d.ts +21 -13
- package/dist/client.js +1 -15
- package/dist/{external-PmmO6xnl.d.ts → external-TYmXqKVq.d.ts} +24 -11
- package/dist/{external-irU6kFSB.d.cts → external-tVP-84Pg.d.cts} +24 -11
- package/dist/{external-irU6kFSB.d.ts → external-tVP-84Pg.d.ts} +24 -11
- package/dist/index.cjs +190 -155
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/sandbox.d.ts +7 -2
- package/dist/sandbox.js +14 -0
- package/dist/testing.cjs +24 -15
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/virtual/{content-script-entrypoint.js → content-script-isolated-world-entrypoint.js} +3 -3
- package/dist/virtual/content-script-main-world-entrypoint.js +33 -0
- package/dist/virtual/mock-browser.js +3 -5
- package/package.json +2 -2
- package/bin/wxt.cjs +0 -2
- package/dist/cli.cjs +0 -5499
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3119 @@
|
|
|
1
|
+
import "./chunk-VBXJIVYU.js";
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import cac from "cac";
|
|
5
|
+
|
|
6
|
+
// package.json
|
|
7
|
+
var version = "0.12.0";
|
|
8
|
+
|
|
9
|
+
// src/core/utils/fs.ts
|
|
10
|
+
import fs from "fs-extra";
|
|
11
|
+
import glob from "fast-glob";
|
|
12
|
+
|
|
13
|
+
// src/core/utils/paths.ts
|
|
14
|
+
import systemPath from "node:path";
|
|
15
|
+
import normalize from "normalize-path";
|
|
16
|
+
function normalizePath(path7) {
|
|
17
|
+
return normalize(path7);
|
|
18
|
+
}
|
|
19
|
+
function unnormalizePath(path7) {
|
|
20
|
+
return systemPath.normalize(path7);
|
|
21
|
+
}
|
|
22
|
+
var CSS_EXTENSIONS = ["css", "scss", "sass", "less", "styl", "stylus"];
|
|
23
|
+
var CSS_EXTENSIONS_PATTERN = `+(${CSS_EXTENSIONS.join("|")})`;
|
|
24
|
+
|
|
25
|
+
// src/core/utils/fs.ts
|
|
26
|
+
async function writeFileIfDifferent(file, newContents) {
|
|
27
|
+
const existingContents = await fs.readFile(file, "utf-8").catch(() => void 0);
|
|
28
|
+
if (existingContents !== newContents) {
|
|
29
|
+
await fs.writeFile(file, newContents);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function getPublicFiles(config) {
|
|
33
|
+
if (!await fs.exists(config.publicDir))
|
|
34
|
+
return [];
|
|
35
|
+
const files = await glob("**/*", { cwd: config.publicDir });
|
|
36
|
+
return files.map(unnormalizePath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/core/utils/building/build-entrypoints.ts
|
|
40
|
+
import fs2 from "fs-extra";
|
|
41
|
+
import { dirname, resolve } from "path";
|
|
42
|
+
import pc from "picocolors";
|
|
43
|
+
async function buildEntrypoints(groups, config, spinner) {
|
|
44
|
+
const steps = [];
|
|
45
|
+
for (let i = 0; i < groups.length; i++) {
|
|
46
|
+
const group = groups[i];
|
|
47
|
+
const groupNames = [group].flat().map((e) => e.name).join(pc.dim(", "));
|
|
48
|
+
spinner.text = pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNames}`;
|
|
49
|
+
steps.push(await config.builder.build(group));
|
|
50
|
+
}
|
|
51
|
+
const publicAssets = await copyPublicDirectory(config);
|
|
52
|
+
return { publicAssets, steps };
|
|
53
|
+
}
|
|
54
|
+
async function copyPublicDirectory(config) {
|
|
55
|
+
const files = await getPublicFiles(config);
|
|
56
|
+
if (files.length === 0)
|
|
57
|
+
return [];
|
|
58
|
+
const publicAssets = [];
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
const srcPath = resolve(config.publicDir, file);
|
|
61
|
+
const outPath = resolve(config.outDir, file);
|
|
62
|
+
await fs2.ensureDir(dirname(outPath));
|
|
63
|
+
await fs2.copyFile(srcPath, outPath);
|
|
64
|
+
publicAssets.push({
|
|
65
|
+
type: "asset",
|
|
66
|
+
fileName: file
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return publicAssets;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/core/utils/arrays.ts
|
|
73
|
+
function every(array, predicate) {
|
|
74
|
+
for (let i = 0; i < array.length; i++)
|
|
75
|
+
if (!predicate(array[i], i))
|
|
76
|
+
return false;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/core/utils/building/detect-dev-changes.ts
|
|
81
|
+
function detectDevChanges(changedFiles, currentOutput) {
|
|
82
|
+
if (currentOutput == null)
|
|
83
|
+
return { type: "no-change" };
|
|
84
|
+
const changedSteps = new Set(
|
|
85
|
+
changedFiles.flatMap(
|
|
86
|
+
(changedFile) => findEffectedSteps(changedFile, currentOutput)
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
if (changedSteps.size === 0)
|
|
90
|
+
return { type: "no-change" };
|
|
91
|
+
const unchangedOutput = {
|
|
92
|
+
manifest: currentOutput.manifest,
|
|
93
|
+
steps: [],
|
|
94
|
+
publicAssets: []
|
|
95
|
+
};
|
|
96
|
+
const changedOutput = {
|
|
97
|
+
manifest: currentOutput.manifest,
|
|
98
|
+
steps: [],
|
|
99
|
+
publicAssets: []
|
|
100
|
+
};
|
|
101
|
+
for (const step of currentOutput.steps) {
|
|
102
|
+
if (changedSteps.has(step)) {
|
|
103
|
+
changedOutput.steps.push(step);
|
|
104
|
+
} else {
|
|
105
|
+
unchangedOutput.steps.push(step);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const asset of currentOutput.publicAssets) {
|
|
109
|
+
if (changedSteps.has(asset)) {
|
|
110
|
+
changedOutput.publicAssets.push(asset);
|
|
111
|
+
} else {
|
|
112
|
+
unchangedOutput.publicAssets.push(asset);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, ([_, file]) => file.endsWith(".html"));
|
|
116
|
+
if (isOnlyHtmlChanges) {
|
|
117
|
+
return {
|
|
118
|
+
type: "html-reload",
|
|
119
|
+
cachedOutput: unchangedOutput,
|
|
120
|
+
rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
|
|
124
|
+
changedOutput.steps.flatMap((step) => step.entrypoints),
|
|
125
|
+
(entry) => entry.type === "content-script"
|
|
126
|
+
);
|
|
127
|
+
if (isOnlyContentScripts) {
|
|
128
|
+
return {
|
|
129
|
+
type: "content-script-reload",
|
|
130
|
+
cachedOutput: unchangedOutput,
|
|
131
|
+
changedSteps: changedOutput.steps,
|
|
132
|
+
rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
type: "extension-reload",
|
|
137
|
+
cachedOutput: unchangedOutput,
|
|
138
|
+
rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function findEffectedSteps(changedFile, currentOutput) {
|
|
142
|
+
const changes = [];
|
|
143
|
+
const changedPath = normalizePath(changedFile[1]);
|
|
144
|
+
const isChunkEffected = (chunk) => (
|
|
145
|
+
// If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
|
|
146
|
+
// fileName is normalized, relative bundle path
|
|
147
|
+
chunk.type === "asset" && changedPath.endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
|
|
148
|
+
// moduleIds are absolute, normalized paths
|
|
149
|
+
chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
|
|
150
|
+
);
|
|
151
|
+
for (const step of currentOutput.steps) {
|
|
152
|
+
const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
|
|
153
|
+
if (effectedChunk)
|
|
154
|
+
changes.push(step);
|
|
155
|
+
}
|
|
156
|
+
const effectedAsset = currentOutput.publicAssets.find(
|
|
157
|
+
(chunk) => isChunkEffected(chunk)
|
|
158
|
+
);
|
|
159
|
+
if (effectedAsset)
|
|
160
|
+
changes.push(effectedAsset);
|
|
161
|
+
return changes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/core/utils/building/find-entrypoints.ts
|
|
165
|
+
import { relative as relative2, resolve as resolve3 } from "path";
|
|
166
|
+
import fs3 from "fs-extra";
|
|
167
|
+
import { minimatch } from "minimatch";
|
|
168
|
+
import { parseHTML } from "linkedom";
|
|
169
|
+
import JSON5 from "json5";
|
|
170
|
+
import glob2 from "fast-glob";
|
|
171
|
+
|
|
172
|
+
// src/core/utils/entrypoints.ts
|
|
173
|
+
import path, { relative, resolve as resolve2 } from "node:path";
|
|
174
|
+
function getEntrypointName(entrypointsDir, inputPath) {
|
|
175
|
+
const relativePath = path.relative(entrypointsDir, inputPath);
|
|
176
|
+
const name = relativePath.split(/[\.\/\\]/, 2)[0];
|
|
177
|
+
return name;
|
|
178
|
+
}
|
|
179
|
+
function getEntrypointOutputFile(entrypoint, ext) {
|
|
180
|
+
return resolve2(entrypoint.outputDir, `${entrypoint.name}${ext}`);
|
|
181
|
+
}
|
|
182
|
+
function getEntrypointBundlePath(entrypoint, outDir, ext) {
|
|
183
|
+
return normalizePath(
|
|
184
|
+
relative(outDir, getEntrypointOutputFile(entrypoint, ext))
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
function resolvePerBrowserOption(option, browser) {
|
|
188
|
+
if (typeof option === "object" && !Array.isArray(option))
|
|
189
|
+
return option[browser];
|
|
190
|
+
return option;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/core/utils/constants.ts
|
|
194
|
+
var VIRTUAL_NOOP_BACKGROUND_MODULE_ID = "virtual:user-background";
|
|
195
|
+
|
|
196
|
+
// src/core/utils/building/find-entrypoints.ts
|
|
197
|
+
async function findEntrypoints(config) {
|
|
198
|
+
const relativePaths = await glob2(Object.keys(PATH_GLOB_TO_TYPE_MAP), {
|
|
199
|
+
cwd: config.entrypointsDir
|
|
200
|
+
});
|
|
201
|
+
relativePaths.sort();
|
|
202
|
+
const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
|
|
203
|
+
const entrypointInfos = relativePaths.reduce((results, relativePath) => {
|
|
204
|
+
const inputPath = resolve3(config.entrypointsDir, relativePath);
|
|
205
|
+
const name = getEntrypointName(config.entrypointsDir, inputPath);
|
|
206
|
+
const matchingGlob = pathGlobs.find(
|
|
207
|
+
(glob5) => minimatch(relativePath, glob5)
|
|
208
|
+
);
|
|
209
|
+
if (matchingGlob) {
|
|
210
|
+
const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
|
|
211
|
+
results.push({ name, inputPath, type });
|
|
212
|
+
}
|
|
213
|
+
return results;
|
|
214
|
+
}, []);
|
|
215
|
+
preventNoEntrypoints(config, entrypointInfos);
|
|
216
|
+
preventDuplicateEntrypointNames(config, entrypointInfos);
|
|
217
|
+
let hasBackground = false;
|
|
218
|
+
const entrypoints = await Promise.all(
|
|
219
|
+
entrypointInfos.map(async (info) => {
|
|
220
|
+
const { type } = info;
|
|
221
|
+
switch (type) {
|
|
222
|
+
case "popup":
|
|
223
|
+
return await getPopupEntrypoint(config, info);
|
|
224
|
+
case "options":
|
|
225
|
+
return await getOptionsEntrypoint(config, info);
|
|
226
|
+
case "background":
|
|
227
|
+
hasBackground = true;
|
|
228
|
+
return await getBackgroundEntrypoint(config, info);
|
|
229
|
+
case "content-script":
|
|
230
|
+
return await getContentScriptEntrypoint(config, info);
|
|
231
|
+
case "unlisted-page":
|
|
232
|
+
return await getUnlistedPageEntrypoint(config, info);
|
|
233
|
+
case "unlisted-script":
|
|
234
|
+
return await getUnlistedScriptEntrypoint(config, info);
|
|
235
|
+
case "content-script-style":
|
|
236
|
+
return {
|
|
237
|
+
...info,
|
|
238
|
+
type,
|
|
239
|
+
outputDir: resolve3(config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
|
240
|
+
options: {
|
|
241
|
+
include: void 0,
|
|
242
|
+
exclude: void 0
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
default:
|
|
246
|
+
return {
|
|
247
|
+
...info,
|
|
248
|
+
type,
|
|
249
|
+
outputDir: config.outDir,
|
|
250
|
+
options: {
|
|
251
|
+
include: void 0,
|
|
252
|
+
exclude: void 0
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
if (config.command === "serve" && !hasBackground) {
|
|
259
|
+
entrypoints.push(
|
|
260
|
+
await getBackgroundEntrypoint(config, {
|
|
261
|
+
inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID,
|
|
262
|
+
name: "background",
|
|
263
|
+
type: "background"
|
|
264
|
+
})
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
config.logger.debug("All entrypoints:", entrypoints);
|
|
268
|
+
const targetEntrypoints = entrypoints.filter((entry) => {
|
|
269
|
+
const { include, exclude } = entry.options;
|
|
270
|
+
if (include?.length && exclude?.length) {
|
|
271
|
+
config.logger.warn(
|
|
272
|
+
`The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`
|
|
273
|
+
);
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
if (exclude?.length && !include?.length) {
|
|
277
|
+
return !exclude.includes(config.browser);
|
|
278
|
+
}
|
|
279
|
+
if (include?.length && !exclude?.length) {
|
|
280
|
+
return include.includes(config.browser);
|
|
281
|
+
}
|
|
282
|
+
return true;
|
|
283
|
+
});
|
|
284
|
+
config.logger.debug(`${config.browser} entrypoints:`, targetEntrypoints);
|
|
285
|
+
return targetEntrypoints;
|
|
286
|
+
}
|
|
287
|
+
function preventDuplicateEntrypointNames(config, files) {
|
|
288
|
+
const namesToPaths = files.reduce(
|
|
289
|
+
(map, { name, inputPath }) => {
|
|
290
|
+
map[name] ??= [];
|
|
291
|
+
map[name].push(inputPath);
|
|
292
|
+
return map;
|
|
293
|
+
},
|
|
294
|
+
{}
|
|
295
|
+
);
|
|
296
|
+
const errorLines = Object.entries(namesToPaths).reduce(
|
|
297
|
+
(lines, [name, absolutePaths]) => {
|
|
298
|
+
if (absolutePaths.length > 1) {
|
|
299
|
+
lines.push(`- ${name}`);
|
|
300
|
+
absolutePaths.forEach((absolutePath) => {
|
|
301
|
+
lines.push(` - ${relative2(config.root, absolutePath)}`);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return lines;
|
|
305
|
+
},
|
|
306
|
+
[]
|
|
307
|
+
);
|
|
308
|
+
if (errorLines.length > 0) {
|
|
309
|
+
const errorContent = errorLines.join("\n");
|
|
310
|
+
throw Error(
|
|
311
|
+
`Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.
|
|
312
|
+
|
|
313
|
+
${errorContent}`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function preventNoEntrypoints(config, files) {
|
|
318
|
+
if (files.length === 0) {
|
|
319
|
+
throw Error(`No entrypoints found in ${config.entrypointsDir}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function getHtmlBaseOptions(document) {
|
|
323
|
+
const options = {};
|
|
324
|
+
const includeContent = document.querySelector("meta[name='manifest.include']")?.getAttribute("content");
|
|
325
|
+
if (includeContent) {
|
|
326
|
+
options.include = JSON5.parse(includeContent);
|
|
327
|
+
}
|
|
328
|
+
const excludeContent = document.querySelector("meta[name='manifest.exclude']")?.getAttribute("content");
|
|
329
|
+
if (excludeContent) {
|
|
330
|
+
options.exclude = JSON5.parse(excludeContent);
|
|
331
|
+
}
|
|
332
|
+
return options;
|
|
333
|
+
}
|
|
334
|
+
async function getPopupEntrypoint(config, { inputPath, name }) {
|
|
335
|
+
const content = await fs3.readFile(inputPath, "utf-8");
|
|
336
|
+
const { document } = parseHTML(content);
|
|
337
|
+
const options = getHtmlBaseOptions(document);
|
|
338
|
+
const title = document.querySelector("title");
|
|
339
|
+
if (title != null)
|
|
340
|
+
options.defaultTitle = title.textContent ?? void 0;
|
|
341
|
+
const defaultIconContent = document.querySelector("meta[name='manifest.default_icon']")?.getAttribute("content");
|
|
342
|
+
if (defaultIconContent) {
|
|
343
|
+
try {
|
|
344
|
+
options.defaultIcon = JSON5.parse(defaultIconContent);
|
|
345
|
+
} catch (err) {
|
|
346
|
+
config.logger.fatal(
|
|
347
|
+
`Failed to parse default_icon meta tag content as JSON5. content=${defaultIconContent}`,
|
|
348
|
+
err
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const mv2TypeContent = document.querySelector("meta[name='manifest.type']")?.getAttribute("content");
|
|
353
|
+
if (mv2TypeContent) {
|
|
354
|
+
options.mv2Key = mv2TypeContent === "page_action" ? "page_action" : "browser_action";
|
|
355
|
+
}
|
|
356
|
+
const browserStyleContent = document.querySelector("meta[name='manifest.browser_style']")?.getAttribute("content");
|
|
357
|
+
if (browserStyleContent) {
|
|
358
|
+
options.browserStyle = browserStyleContent === "true";
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
type: "popup",
|
|
362
|
+
name: "popup",
|
|
363
|
+
options,
|
|
364
|
+
inputPath,
|
|
365
|
+
outputDir: config.outDir
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async function getOptionsEntrypoint(config, { inputPath, name }) {
|
|
369
|
+
const content = await fs3.readFile(inputPath, "utf-8");
|
|
370
|
+
const { document } = parseHTML(content);
|
|
371
|
+
const options = getHtmlBaseOptions(document);
|
|
372
|
+
const openInTabContent = document.querySelector("meta[name='manifest.open_in_tab']")?.getAttribute("content");
|
|
373
|
+
if (openInTabContent) {
|
|
374
|
+
options.openInTab = openInTabContent === "true";
|
|
375
|
+
}
|
|
376
|
+
const chromeStyleContent = document.querySelector("meta[name='manifest.chrome_style']")?.getAttribute("content");
|
|
377
|
+
if (chromeStyleContent) {
|
|
378
|
+
options.chromeStyle = chromeStyleContent === "true";
|
|
379
|
+
}
|
|
380
|
+
const browserStyleContent = document.querySelector("meta[name='manifest.browser_style']")?.getAttribute("content");
|
|
381
|
+
if (browserStyleContent) {
|
|
382
|
+
options.browserStyle = browserStyleContent === "true";
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
type: "options",
|
|
386
|
+
name: "options",
|
|
387
|
+
options,
|
|
388
|
+
inputPath,
|
|
389
|
+
outputDir: config.outDir
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
async function getUnlistedPageEntrypoint(config, { inputPath, name }) {
|
|
393
|
+
const content = await fs3.readFile(inputPath, "utf-8");
|
|
394
|
+
const { document } = parseHTML(content);
|
|
395
|
+
return {
|
|
396
|
+
type: "unlisted-page",
|
|
397
|
+
name: getEntrypointName(config.entrypointsDir, inputPath),
|
|
398
|
+
inputPath,
|
|
399
|
+
outputDir: config.outDir,
|
|
400
|
+
options: getHtmlBaseOptions(document)
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
async function getUnlistedScriptEntrypoint(config, { inputPath, name }) {
|
|
404
|
+
const defaultExport = await importEntrypointFile(
|
|
405
|
+
inputPath,
|
|
406
|
+
config
|
|
407
|
+
);
|
|
408
|
+
if (defaultExport == null) {
|
|
409
|
+
throw Error(
|
|
410
|
+
`${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
const { main: _, ...moduleOptions } = defaultExport;
|
|
414
|
+
const options = moduleOptions;
|
|
415
|
+
return {
|
|
416
|
+
type: "unlisted-script",
|
|
417
|
+
name,
|
|
418
|
+
inputPath,
|
|
419
|
+
outputDir: config.outDir,
|
|
420
|
+
options
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
async function getBackgroundEntrypoint(config, { inputPath, name }) {
|
|
424
|
+
let options = {};
|
|
425
|
+
if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
|
|
426
|
+
const defaultExport = await importEntrypointFile(
|
|
427
|
+
inputPath,
|
|
428
|
+
config
|
|
429
|
+
);
|
|
430
|
+
if (defaultExport == null) {
|
|
431
|
+
throw Error(
|
|
432
|
+
`${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const { main: _, ...moduleOptions } = defaultExport;
|
|
436
|
+
options = moduleOptions;
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
type: "background",
|
|
440
|
+
name,
|
|
441
|
+
inputPath,
|
|
442
|
+
outputDir: config.outDir,
|
|
443
|
+
options: {
|
|
444
|
+
...options,
|
|
445
|
+
type: resolvePerBrowserOption(options.type, config.browser),
|
|
446
|
+
persistent: resolvePerBrowserOption(options.persistent, config.browser)
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
async function getContentScriptEntrypoint(config, { inputPath, name }) {
|
|
451
|
+
const { main: _, ...options } = await importEntrypointFile(inputPath, config);
|
|
452
|
+
if (options == null) {
|
|
453
|
+
throw Error(
|
|
454
|
+
`${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
type: "content-script",
|
|
459
|
+
name,
|
|
460
|
+
inputPath,
|
|
461
|
+
outputDir: resolve3(config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
|
462
|
+
options
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
var PATH_GLOB_TO_TYPE_MAP = {
|
|
466
|
+
"sandbox.html": "sandbox",
|
|
467
|
+
"sandbox/index.html": "sandbox",
|
|
468
|
+
"*.sandbox.html": "sandbox",
|
|
469
|
+
"*.sandbox/index.html": "sandbox",
|
|
470
|
+
"bookmarks.html": "bookmarks",
|
|
471
|
+
"bookmarks/index.html": "bookmarks",
|
|
472
|
+
"history.html": "history",
|
|
473
|
+
"history/index.html": "history",
|
|
474
|
+
"newtab.html": "newtab",
|
|
475
|
+
"newtab/index.html": "newtab",
|
|
476
|
+
"sidepanel.html": "sidepanel",
|
|
477
|
+
"sidepanel/index.html": "sidepanel",
|
|
478
|
+
"*.sidepanel.html": "sidepanel",
|
|
479
|
+
"*.sidepanel/index.html": "sidepanel",
|
|
480
|
+
"devtools.html": "devtools",
|
|
481
|
+
"devtools/index.html": "devtools",
|
|
482
|
+
"background.[jt]s": "background",
|
|
483
|
+
"background/index.[jt]s": "background",
|
|
484
|
+
[VIRTUAL_NOOP_BACKGROUND_MODULE_ID]: "background",
|
|
485
|
+
"content.[jt]s?(x)": "content-script",
|
|
486
|
+
"content/index.[jt]s?(x)": "content-script",
|
|
487
|
+
"*.content.[jt]s?(x)": "content-script",
|
|
488
|
+
"*.content/index.[jt]s?(x)": "content-script",
|
|
489
|
+
[`content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
|
|
490
|
+
[`*.content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
|
|
491
|
+
[`content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
|
|
492
|
+
[`*.content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
|
|
493
|
+
"popup.html": "popup",
|
|
494
|
+
"popup/index.html": "popup",
|
|
495
|
+
"options.html": "options",
|
|
496
|
+
"options/index.html": "options",
|
|
497
|
+
"*.html": "unlisted-page",
|
|
498
|
+
"*/index.html": "unlisted-page",
|
|
499
|
+
"*.[jt]s?(x)": "unlisted-script",
|
|
500
|
+
"*/index.[jt]s?(x)": "unlisted-script",
|
|
501
|
+
[`*.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style",
|
|
502
|
+
[`*/index.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style"
|
|
503
|
+
};
|
|
504
|
+
var CONTENT_SCRIPT_OUT_DIR = "content-scripts";
|
|
505
|
+
|
|
506
|
+
// src/core/utils/building/generate-wxt-dir.ts
|
|
507
|
+
import { createUnimport } from "unimport";
|
|
508
|
+
import fs4 from "fs-extra";
|
|
509
|
+
import { relative as relative3, resolve as resolve4 } from "path";
|
|
510
|
+
|
|
511
|
+
// src/core/utils/unimport.ts
|
|
512
|
+
import { defu } from "defu";
|
|
513
|
+
function getUnimportOptions(config) {
|
|
514
|
+
if (config.imports === false)
|
|
515
|
+
return false;
|
|
516
|
+
const defaultOptions = {
|
|
517
|
+
debugLog: config.logger.debug,
|
|
518
|
+
imports: [
|
|
519
|
+
{ name: "defineConfig", from: "wxt" },
|
|
520
|
+
{ name: "fakeBrowser", from: "wxt/testing" }
|
|
521
|
+
],
|
|
522
|
+
presets: [
|
|
523
|
+
{ package: "wxt/client" },
|
|
524
|
+
{ package: "wxt/browser" },
|
|
525
|
+
{ package: "wxt/sandbox" },
|
|
526
|
+
{ package: "wxt/storage" }
|
|
527
|
+
],
|
|
528
|
+
warn: config.logger.warn,
|
|
529
|
+
dirs: ["components", "composables", "hooks", "utils"]
|
|
530
|
+
};
|
|
531
|
+
return defu(config.imports, defaultOptions);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/core/utils/globals.ts
|
|
535
|
+
function getGlobals(config) {
|
|
536
|
+
return [
|
|
537
|
+
{
|
|
538
|
+
name: surroundInUnderscore("MANIFEST_VERSION"),
|
|
539
|
+
value: config.manifestVersion,
|
|
540
|
+
type: `2 | 3`
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: surroundInUnderscore("BROWSER"),
|
|
544
|
+
value: config.browser,
|
|
545
|
+
type: `string`
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: surroundInUnderscore("IS_CHROME"),
|
|
549
|
+
value: config.browser === "chrome",
|
|
550
|
+
type: `boolean`
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: surroundInUnderscore("IS_FIREFOX"),
|
|
554
|
+
value: config.browser === "firefox",
|
|
555
|
+
type: `boolean`
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
name: surroundInUnderscore("IS_SAFARI"),
|
|
559
|
+
value: config.browser === "safari",
|
|
560
|
+
type: `boolean`
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: surroundInUnderscore("IS_EDGE"),
|
|
564
|
+
value: config.browser === "edge",
|
|
565
|
+
type: `boolean`
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: surroundInUnderscore("IS_OPERA"),
|
|
569
|
+
value: config.browser === "opera",
|
|
570
|
+
type: `boolean`
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: surroundInUnderscore("COMMAND"),
|
|
574
|
+
value: config.command,
|
|
575
|
+
type: `"build" | "serve"`
|
|
576
|
+
}
|
|
577
|
+
];
|
|
578
|
+
}
|
|
579
|
+
function getEntrypointGlobals(entrypointName) {
|
|
580
|
+
return [
|
|
581
|
+
{
|
|
582
|
+
name: surroundInUnderscore("ENTRYPOINT"),
|
|
583
|
+
value: entrypointName,
|
|
584
|
+
type: `string`
|
|
585
|
+
}
|
|
586
|
+
];
|
|
587
|
+
}
|
|
588
|
+
function surroundInUnderscore(name) {
|
|
589
|
+
return `__${name}__`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/core/utils/building/generate-wxt-dir.ts
|
|
593
|
+
import path2 from "node:path";
|
|
594
|
+
|
|
595
|
+
// src/core/utils/i18n.ts
|
|
596
|
+
var predefinedMessages = {
|
|
597
|
+
"@@extension_id": {
|
|
598
|
+
message: "<browser.runtime.id>",
|
|
599
|
+
description: "The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message.\nNote: You can't use this message in a manifest file."
|
|
600
|
+
},
|
|
601
|
+
"@@ui_locale": {
|
|
602
|
+
message: "<browser.i18n.getUiLocale()>",
|
|
603
|
+
description: ""
|
|
604
|
+
},
|
|
605
|
+
"@@bidi_dir": {
|
|
606
|
+
message: "<ltr|rtl>",
|
|
607
|
+
description: 'The text direction for the current locale, either "ltr" for left-to-right languages such as English or "rtl" for right-to-left languages such as Japanese.'
|
|
608
|
+
},
|
|
609
|
+
"@@bidi_reversed_dir": {
|
|
610
|
+
message: "<rtl|ltr>",
|
|
611
|
+
description: `If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".`
|
|
612
|
+
},
|
|
613
|
+
"@@bidi_start_edge": {
|
|
614
|
+
message: "<left|right>",
|
|
615
|
+
description: `If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".`
|
|
616
|
+
},
|
|
617
|
+
"@@bidi_end_edge": {
|
|
618
|
+
message: "<right|left>",
|
|
619
|
+
description: `If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".`
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
function parseI18nMessages(messagesJson) {
|
|
623
|
+
return Object.entries({
|
|
624
|
+
...predefinedMessages,
|
|
625
|
+
...messagesJson
|
|
626
|
+
}).map(([name, details]) => ({
|
|
627
|
+
name,
|
|
628
|
+
...details
|
|
629
|
+
}));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/core/utils/building/generate-wxt-dir.ts
|
|
633
|
+
async function generateTypesDir(entrypoints, config) {
|
|
634
|
+
await fs4.ensureDir(config.typesDir);
|
|
635
|
+
const references = [];
|
|
636
|
+
const imports = getUnimportOptions(config);
|
|
637
|
+
if (imports !== false) {
|
|
638
|
+
references.push(await writeImportsDeclarationFile(config, imports));
|
|
639
|
+
}
|
|
640
|
+
references.push(await writePathsDeclarationFile(entrypoints, config));
|
|
641
|
+
references.push(await writeI18nDeclarationFile(config));
|
|
642
|
+
references.push(await writeGlobalsDeclarationFile(config));
|
|
643
|
+
const mainReference = await writeMainDeclarationFile(references, config);
|
|
644
|
+
await writeTsConfigFile(mainReference, config);
|
|
645
|
+
}
|
|
646
|
+
async function writeImportsDeclarationFile(config, unimportOptions) {
|
|
647
|
+
const filePath = resolve4(config.typesDir, "imports.d.ts");
|
|
648
|
+
const unimport2 = createUnimport(unimportOptions);
|
|
649
|
+
await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
|
|
650
|
+
await writeFileIfDifferent(
|
|
651
|
+
filePath,
|
|
652
|
+
["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
|
|
653
|
+
"\n"
|
|
654
|
+
) + "\n"
|
|
655
|
+
);
|
|
656
|
+
return filePath;
|
|
657
|
+
}
|
|
658
|
+
async function writePathsDeclarationFile(entrypoints, config) {
|
|
659
|
+
const filePath = resolve4(config.typesDir, "paths.d.ts");
|
|
660
|
+
const unions = entrypoints.map(
|
|
661
|
+
(entry) => getEntrypointBundlePath(
|
|
662
|
+
entry,
|
|
663
|
+
config.outDir,
|
|
664
|
+
entry.inputPath.endsWith(".html") ? ".html" : ".js"
|
|
665
|
+
)
|
|
666
|
+
).concat(await getPublicFiles(config)).map(normalizePath).map((path7) => ` | "/${path7}"`).sort().join("\n");
|
|
667
|
+
const template = `// Generated by wxt
|
|
668
|
+
import "wxt/browser";
|
|
669
|
+
|
|
670
|
+
declare module "wxt/browser" {
|
|
671
|
+
export type PublicPath =
|
|
672
|
+
{{ union }}
|
|
673
|
+
export interface WxtRuntime extends Runtime.Static {
|
|
674
|
+
getURL(path: PublicPath): string;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
`;
|
|
678
|
+
await writeFileIfDifferent(
|
|
679
|
+
filePath,
|
|
680
|
+
template.replace("{{ union }}", unions || " | never")
|
|
681
|
+
);
|
|
682
|
+
return filePath;
|
|
683
|
+
}
|
|
684
|
+
async function writeI18nDeclarationFile(config) {
|
|
685
|
+
const filePath = resolve4(config.typesDir, "i18n.d.ts");
|
|
686
|
+
const defaultLocale = config.manifest.default_locale;
|
|
687
|
+
const template = `// Generated by wxt
|
|
688
|
+
import "wxt/browser";
|
|
689
|
+
|
|
690
|
+
declare module "wxt/browser" {
|
|
691
|
+
/**
|
|
692
|
+
* See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
|
|
693
|
+
*/
|
|
694
|
+
interface GetMessageOptions {
|
|
695
|
+
/**
|
|
696
|
+
* See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
|
|
697
|
+
*/
|
|
698
|
+
escapeLt?: boolean
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export interface WxtI18n extends I18n.Static {
|
|
702
|
+
{{ overrides }}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
`;
|
|
706
|
+
let messages;
|
|
707
|
+
if (defaultLocale) {
|
|
708
|
+
const defaultLocalePath = path2.resolve(
|
|
709
|
+
config.publicDir,
|
|
710
|
+
"_locales",
|
|
711
|
+
defaultLocale,
|
|
712
|
+
"messages.json"
|
|
713
|
+
);
|
|
714
|
+
const content = JSON.parse(await fs4.readFile(defaultLocalePath, "utf-8"));
|
|
715
|
+
messages = parseI18nMessages(content);
|
|
716
|
+
} else {
|
|
717
|
+
messages = parseI18nMessages({});
|
|
718
|
+
}
|
|
719
|
+
const overrides = messages.map((message) => {
|
|
720
|
+
return ` /**
|
|
721
|
+
* ${message.description ?? "No message description."}
|
|
722
|
+
*
|
|
723
|
+
* "${message.message}"
|
|
724
|
+
*/
|
|
725
|
+
getMessage(
|
|
726
|
+
messageName: "${message.name}",
|
|
727
|
+
substitutions?: string | string[],
|
|
728
|
+
options?: GetMessageOptions,
|
|
729
|
+
): string;`;
|
|
730
|
+
});
|
|
731
|
+
await writeFileIfDifferent(
|
|
732
|
+
filePath,
|
|
733
|
+
template.replace("{{ overrides }}", overrides.join("\n"))
|
|
734
|
+
);
|
|
735
|
+
return filePath;
|
|
736
|
+
}
|
|
737
|
+
async function writeGlobalsDeclarationFile(config) {
|
|
738
|
+
const filePath = resolve4(config.typesDir, "globals.d.ts");
|
|
739
|
+
const globals2 = [...getGlobals(config), ...getEntrypointGlobals("")];
|
|
740
|
+
await writeFileIfDifferent(
|
|
741
|
+
filePath,
|
|
742
|
+
[
|
|
743
|
+
"// Generated by wxt",
|
|
744
|
+
"export {}",
|
|
745
|
+
"declare global {",
|
|
746
|
+
...globals2.map((global) => ` const ${global.name}: ${global.type};`),
|
|
747
|
+
"}"
|
|
748
|
+
].join("\n") + "\n"
|
|
749
|
+
);
|
|
750
|
+
return filePath;
|
|
751
|
+
}
|
|
752
|
+
async function writeMainDeclarationFile(references, config) {
|
|
753
|
+
const dir = config.wxtDir;
|
|
754
|
+
const filePath = resolve4(dir, "wxt.d.ts");
|
|
755
|
+
await writeFileIfDifferent(
|
|
756
|
+
filePath,
|
|
757
|
+
[
|
|
758
|
+
"// Generated by wxt",
|
|
759
|
+
`/// <reference types="vite/client" />`,
|
|
760
|
+
...references.map(
|
|
761
|
+
(ref) => `/// <reference types="./${normalizePath(relative3(dir, ref))}" />`
|
|
762
|
+
)
|
|
763
|
+
].join("\n") + "\n"
|
|
764
|
+
);
|
|
765
|
+
return filePath;
|
|
766
|
+
}
|
|
767
|
+
async function writeTsConfigFile(mainReference, config) {
|
|
768
|
+
const dir = config.wxtDir;
|
|
769
|
+
const getTsconfigPath = (path7) => normalizePath(relative3(dir, path7));
|
|
770
|
+
const paths = Object.entries(config.alias).flatMap(([alias, absolutePath]) => {
|
|
771
|
+
const aliasPath = getTsconfigPath(absolutePath);
|
|
772
|
+
return [
|
|
773
|
+
` "${alias}": ["${aliasPath}"]`,
|
|
774
|
+
` "${alias}/*": ["${aliasPath}/*"]`
|
|
775
|
+
];
|
|
776
|
+
}).join(",\n");
|
|
777
|
+
await writeFileIfDifferent(
|
|
778
|
+
resolve4(dir, "tsconfig.json"),
|
|
779
|
+
`{
|
|
780
|
+
"compilerOptions": {
|
|
781
|
+
"target": "ESNext",
|
|
782
|
+
"module": "ESNext",
|
|
783
|
+
"moduleResolution": "Bundler",
|
|
784
|
+
"noEmit": true,
|
|
785
|
+
"esModuleInterop": true,
|
|
786
|
+
"forceConsistentCasingInFileNames": true,
|
|
787
|
+
"resolveJsonModule": true,
|
|
788
|
+
"strict": true,
|
|
789
|
+
"skipLibCheck": true,
|
|
790
|
+
"paths": {
|
|
791
|
+
${paths}
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
"include": [
|
|
795
|
+
"${getTsconfigPath(config.root)}/**/*",
|
|
796
|
+
"./${getTsconfigPath(mainReference)}"
|
|
797
|
+
],
|
|
798
|
+
"exclude": ["${getTsconfigPath(config.outBaseDir)}"]
|
|
799
|
+
}`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/core/utils/building/get-internal-config.ts
|
|
804
|
+
import { loadConfig } from "c12";
|
|
805
|
+
import path3 from "node:path";
|
|
806
|
+
|
|
807
|
+
// src/core/utils/cache.ts
|
|
808
|
+
import fs5, { ensureDir } from "fs-extra";
|
|
809
|
+
import { dirname as dirname2, resolve as resolve5 } from "path";
|
|
810
|
+
function createFsCache(wxtDir) {
|
|
811
|
+
const getPath = (key) => resolve5(wxtDir, "cache", encodeURIComponent(key));
|
|
812
|
+
return {
|
|
813
|
+
async set(key, value) {
|
|
814
|
+
const path7 = getPath(key);
|
|
815
|
+
await ensureDir(dirname2(path7));
|
|
816
|
+
await writeFileIfDifferent(path7, value);
|
|
817
|
+
},
|
|
818
|
+
async get(key) {
|
|
819
|
+
const path7 = getPath(key);
|
|
820
|
+
try {
|
|
821
|
+
return await fs5.readFile(path7, "utf-8");
|
|
822
|
+
} catch {
|
|
823
|
+
return void 0;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/core/utils/building/get-internal-config.ts
|
|
830
|
+
import consola, { LogLevels } from "consola";
|
|
831
|
+
|
|
832
|
+
// src/core/builders/vite/plugins/devHtmlPrerender.ts
|
|
833
|
+
import { parseHTML as parseHTML2 } from "linkedom";
|
|
834
|
+
import { dirname as dirname3, isAbsolute, relative as relative4, resolve as resolve6 } from "node:path";
|
|
835
|
+
var reactRefreshPreamble = "";
|
|
836
|
+
function devHtmlPrerender(config) {
|
|
837
|
+
const htmlReloadId = "@wxt/reload-html";
|
|
838
|
+
const resolvedHtmlReloadId = resolve6(
|
|
839
|
+
config.root,
|
|
840
|
+
"node_modules/wxt/dist/virtual/reload-html.js"
|
|
841
|
+
);
|
|
842
|
+
const virtualReactRefreshId = "@wxt/virtual-react-refresh";
|
|
843
|
+
const resolvedVirtualReactRefreshId = "\0" + virtualReactRefreshId;
|
|
844
|
+
return [
|
|
845
|
+
{
|
|
846
|
+
apply: "build",
|
|
847
|
+
name: "wxt:dev-html-prerender",
|
|
848
|
+
config() {
|
|
849
|
+
return {
|
|
850
|
+
resolve: {
|
|
851
|
+
alias: {
|
|
852
|
+
[htmlReloadId]: resolvedHtmlReloadId
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
},
|
|
857
|
+
// Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
|
|
858
|
+
// before the paths are replaced with their bundled path
|
|
859
|
+
transform(code, id) {
|
|
860
|
+
const server = config.server;
|
|
861
|
+
if (config.command !== "serve" || server == null || !id.endsWith(".html"))
|
|
862
|
+
return;
|
|
863
|
+
const { document } = parseHTML2(code);
|
|
864
|
+
const pointToDevServer = (querySelector, attr) => {
|
|
865
|
+
document.querySelectorAll(querySelector).forEach((element) => {
|
|
866
|
+
const src = element.getAttribute(attr);
|
|
867
|
+
if (!src)
|
|
868
|
+
return;
|
|
869
|
+
if (isAbsolute(src)) {
|
|
870
|
+
element.setAttribute(attr, server.origin + src);
|
|
871
|
+
} else if (src.startsWith(".")) {
|
|
872
|
+
const abs = resolve6(dirname3(id), src);
|
|
873
|
+
const pathname = relative4(config.root, abs);
|
|
874
|
+
element.setAttribute(attr, `${server.origin}/${pathname}`);
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
};
|
|
878
|
+
pointToDevServer("script[type=module]", "src");
|
|
879
|
+
pointToDevServer("link[rel=stylesheet]", "href");
|
|
880
|
+
const reloader = document.createElement("script");
|
|
881
|
+
reloader.src = htmlReloadId;
|
|
882
|
+
reloader.type = "module";
|
|
883
|
+
document.head.appendChild(reloader);
|
|
884
|
+
const newHtml = document.toString();
|
|
885
|
+
config.logger.debug("transform " + id);
|
|
886
|
+
config.logger.debug("Old HTML:\n" + code);
|
|
887
|
+
config.logger.debug("New HTML:\n" + newHtml);
|
|
888
|
+
return newHtml;
|
|
889
|
+
},
|
|
890
|
+
// Pass the HTML through the dev server to add dev-mode specific code
|
|
891
|
+
async transformIndexHtml(html, ctx) {
|
|
892
|
+
const server = config.server;
|
|
893
|
+
if (config.command !== "serve" || server == null)
|
|
894
|
+
return;
|
|
895
|
+
const originalUrl = `${server.origin}${ctx.path}`;
|
|
896
|
+
const name = getEntrypointName(config.entrypointsDir, ctx.filename);
|
|
897
|
+
const url = `${server.origin}/${name}.html`;
|
|
898
|
+
const serverHtml = await server.transformHtml(url, html, originalUrl);
|
|
899
|
+
const { document } = parseHTML2(serverHtml);
|
|
900
|
+
const reactRefreshScript = Array.from(
|
|
901
|
+
document.querySelectorAll("script[type=module]")
|
|
902
|
+
).find((script) => script.innerHTML.includes("@react-refresh"));
|
|
903
|
+
if (reactRefreshScript) {
|
|
904
|
+
reactRefreshPreamble = reactRefreshScript.innerHTML;
|
|
905
|
+
const virtualScript = document.createElement("script");
|
|
906
|
+
virtualScript.type = "module";
|
|
907
|
+
virtualScript.src = `${server.origin}/${virtualReactRefreshId}`;
|
|
908
|
+
reactRefreshScript.replaceWith(virtualScript);
|
|
909
|
+
}
|
|
910
|
+
const viteClientScript = document.querySelector(
|
|
911
|
+
"script[src='/@vite/client']"
|
|
912
|
+
);
|
|
913
|
+
if (viteClientScript) {
|
|
914
|
+
viteClientScript.src = `${server.origin}${viteClientScript.src}`;
|
|
915
|
+
}
|
|
916
|
+
const newHtml = document.toString();
|
|
917
|
+
config.logger.debug("transformIndexHtml " + ctx.filename);
|
|
918
|
+
config.logger.debug("Old HTML:\n" + html);
|
|
919
|
+
config.logger.debug("New HTML:\n" + newHtml);
|
|
920
|
+
return newHtml;
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
name: "wxt:virtualize-react-refresh",
|
|
925
|
+
apply: "serve",
|
|
926
|
+
resolveId(id) {
|
|
927
|
+
if (id === `/${virtualReactRefreshId}`) {
|
|
928
|
+
return resolvedVirtualReactRefreshId;
|
|
929
|
+
}
|
|
930
|
+
if (id.startsWith("/chunks/")) {
|
|
931
|
+
return "\0noop";
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
load(id) {
|
|
935
|
+
if (id === resolvedVirtualReactRefreshId) {
|
|
936
|
+
return reactRefreshPreamble;
|
|
937
|
+
}
|
|
938
|
+
if (id === "\0noop") {
|
|
939
|
+
return "";
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
];
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/core/builders/vite/plugins/devServerGlobals.ts
|
|
947
|
+
function devServerGlobals(config) {
|
|
948
|
+
return {
|
|
949
|
+
name: "wxt:dev-server-globals",
|
|
950
|
+
config() {
|
|
951
|
+
if (config.server == null || config.command == "build")
|
|
952
|
+
return;
|
|
953
|
+
return {
|
|
954
|
+
define: {
|
|
955
|
+
__DEV_SERVER_PROTOCOL__: JSON.stringify("ws:"),
|
|
956
|
+
__DEV_SERVER_HOSTNAME__: JSON.stringify(config.server.hostname),
|
|
957
|
+
__DEV_SERVER_PORT__: JSON.stringify(config.server.port)
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/core/utils/network.ts
|
|
965
|
+
import dns from "node:dns";
|
|
966
|
+
|
|
967
|
+
// src/core/utils/time.ts
|
|
968
|
+
function formatDuration(duration) {
|
|
969
|
+
if (duration < 1e3)
|
|
970
|
+
return `${duration} ms`;
|
|
971
|
+
if (duration < 1e4)
|
|
972
|
+
return `${(duration / 1e3).toFixed(3)} s`;
|
|
973
|
+
if (duration < 6e4)
|
|
974
|
+
return `${(duration / 1e3).toFixed(1)} s`;
|
|
975
|
+
return `${(duration / 1e3).toFixed(0)} s`;
|
|
976
|
+
}
|
|
977
|
+
function withTimeout(promise, duration) {
|
|
978
|
+
return new Promise((res, rej) => {
|
|
979
|
+
const timeout = setTimeout(() => {
|
|
980
|
+
rej(`Promise timed out after ${duration}ms`);
|
|
981
|
+
}, duration);
|
|
982
|
+
promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/core/utils/network.ts
|
|
987
|
+
function isOffline() {
|
|
988
|
+
const isOffline2 = new Promise((res) => {
|
|
989
|
+
dns.resolve("google.com", (err) => {
|
|
990
|
+
if (err == null) {
|
|
991
|
+
res(false);
|
|
992
|
+
} else {
|
|
993
|
+
res(true);
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
return withTimeout(isOffline2, 1e3).catch(() => true);
|
|
998
|
+
}
|
|
999
|
+
async function isOnline() {
|
|
1000
|
+
const offline = await isOffline();
|
|
1001
|
+
return !offline;
|
|
1002
|
+
}
|
|
1003
|
+
async function fetchCached(url, config) {
|
|
1004
|
+
let content = "";
|
|
1005
|
+
if (await isOnline()) {
|
|
1006
|
+
const res = await fetch(url);
|
|
1007
|
+
if (res.status < 300) {
|
|
1008
|
+
content = await res.text();
|
|
1009
|
+
await config.fsCache.set(url, content);
|
|
1010
|
+
} else {
|
|
1011
|
+
config.logger.debug(
|
|
1012
|
+
`Failed to download "${url}", falling back to cache...`
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (!content)
|
|
1017
|
+
content = await config.fsCache.get(url) ?? "";
|
|
1018
|
+
if (!content)
|
|
1019
|
+
throw Error(
|
|
1020
|
+
`Offline and "${url}" has not been cached. Try again when online.`
|
|
1021
|
+
);
|
|
1022
|
+
return content;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// src/core/builders/vite/plugins/download.ts
|
|
1026
|
+
function download(config) {
|
|
1027
|
+
return {
|
|
1028
|
+
name: "wxt:download",
|
|
1029
|
+
resolveId(id) {
|
|
1030
|
+
if (id.startsWith("url:"))
|
|
1031
|
+
return "\0" + id;
|
|
1032
|
+
},
|
|
1033
|
+
async load(id) {
|
|
1034
|
+
if (!id.startsWith("\0url:"))
|
|
1035
|
+
return;
|
|
1036
|
+
const url = id.replace("\0url:", "");
|
|
1037
|
+
return await fetchCached(url, config);
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/core/builders/vite/plugins/multipageMove.ts
|
|
1043
|
+
import { dirname as dirname4, extname, resolve as resolve7, join } from "node:path";
|
|
1044
|
+
import fs6, { ensureDir as ensureDir2 } from "fs-extra";
|
|
1045
|
+
function multipageMove(entrypoints, config) {
|
|
1046
|
+
return {
|
|
1047
|
+
name: "wxt:multipage-move",
|
|
1048
|
+
async writeBundle(_, bundle) {
|
|
1049
|
+
for (const oldBundlePath in bundle) {
|
|
1050
|
+
const entrypoint = entrypoints.find(
|
|
1051
|
+
(entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath)
|
|
1052
|
+
);
|
|
1053
|
+
if (entrypoint == null) {
|
|
1054
|
+
config.logger.debug(
|
|
1055
|
+
`No entrypoint found for ${oldBundlePath}, leaving in chunks directory`
|
|
1056
|
+
);
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
const newBundlePath = getEntrypointBundlePath(
|
|
1060
|
+
entrypoint,
|
|
1061
|
+
config.outDir,
|
|
1062
|
+
extname(oldBundlePath)
|
|
1063
|
+
);
|
|
1064
|
+
if (newBundlePath === oldBundlePath) {
|
|
1065
|
+
config.logger.debug(
|
|
1066
|
+
"HTML file is already in the correct location",
|
|
1067
|
+
oldBundlePath
|
|
1068
|
+
);
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
const oldAbsPath = resolve7(config.outDir, oldBundlePath);
|
|
1072
|
+
const newAbsPath = resolve7(config.outDir, newBundlePath);
|
|
1073
|
+
await ensureDir2(dirname4(newAbsPath));
|
|
1074
|
+
await fs6.move(oldAbsPath, newAbsPath, { overwrite: true });
|
|
1075
|
+
const renamedChunk = {
|
|
1076
|
+
...bundle[oldBundlePath],
|
|
1077
|
+
fileName: newBundlePath
|
|
1078
|
+
};
|
|
1079
|
+
delete bundle[oldBundlePath];
|
|
1080
|
+
bundle[newBundlePath] = renamedChunk;
|
|
1081
|
+
}
|
|
1082
|
+
removeEmptyDirs(config.outDir);
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
async function removeEmptyDirs(dir) {
|
|
1087
|
+
const files = await fs6.readdir(dir);
|
|
1088
|
+
for (const file of files) {
|
|
1089
|
+
const filePath = join(dir, file);
|
|
1090
|
+
const stats = await fs6.stat(filePath);
|
|
1091
|
+
if (stats.isDirectory()) {
|
|
1092
|
+
await removeEmptyDirs(filePath);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
try {
|
|
1096
|
+
await fs6.rmdir(dir);
|
|
1097
|
+
} catch {
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// src/core/builders/vite/plugins/unimport.ts
|
|
1102
|
+
import { createUnimport as createUnimport2 } from "unimport";
|
|
1103
|
+
import { extname as extname2 } from "path";
|
|
1104
|
+
var ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1105
|
+
".js",
|
|
1106
|
+
".jsx",
|
|
1107
|
+
".ts",
|
|
1108
|
+
".tsx",
|
|
1109
|
+
".vue",
|
|
1110
|
+
".svelte"
|
|
1111
|
+
]);
|
|
1112
|
+
function unimport(config) {
|
|
1113
|
+
const options = getUnimportOptions(config);
|
|
1114
|
+
if (options === false)
|
|
1115
|
+
return [];
|
|
1116
|
+
const unimport2 = createUnimport2(options);
|
|
1117
|
+
return {
|
|
1118
|
+
name: "wxt:unimport",
|
|
1119
|
+
async config() {
|
|
1120
|
+
await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
|
|
1121
|
+
},
|
|
1122
|
+
async transform(code, id) {
|
|
1123
|
+
if (id.includes("node_modules"))
|
|
1124
|
+
return;
|
|
1125
|
+
if (!ENABLED_EXTENSIONS.has(extname2(id)))
|
|
1126
|
+
return;
|
|
1127
|
+
return unimport2.injectImports(code, id);
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/core/builders/vite/plugins/virtualEntrypoint.ts
|
|
1133
|
+
import fs7 from "fs-extra";
|
|
1134
|
+
import { resolve as resolve8 } from "path";
|
|
1135
|
+
function virtualEntrypoint(type, config) {
|
|
1136
|
+
const virtualId = `virtual:wxt-${type}?`;
|
|
1137
|
+
const resolvedVirtualId = `\0${virtualId}`;
|
|
1138
|
+
return {
|
|
1139
|
+
name: `wxt:virtual-entrypoint`,
|
|
1140
|
+
resolveId(id) {
|
|
1141
|
+
const index = id.indexOf(virtualId);
|
|
1142
|
+
if (index === -1)
|
|
1143
|
+
return;
|
|
1144
|
+
const inputPath = normalizePath(id.substring(index + virtualId.length));
|
|
1145
|
+
return resolvedVirtualId + inputPath;
|
|
1146
|
+
},
|
|
1147
|
+
async load(id) {
|
|
1148
|
+
if (!id.startsWith(resolvedVirtualId))
|
|
1149
|
+
return;
|
|
1150
|
+
const inputPath = id.replace(resolvedVirtualId, "");
|
|
1151
|
+
const template = await fs7.readFile(
|
|
1152
|
+
resolve8(
|
|
1153
|
+
config.root,
|
|
1154
|
+
`node_modules/wxt/dist/virtual/${type}-entrypoint.js`
|
|
1155
|
+
),
|
|
1156
|
+
"utf-8"
|
|
1157
|
+
);
|
|
1158
|
+
return template.replace(`virtual:user-${type}`, inputPath);
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/core/builders/vite/plugins/tsconfigPaths.ts
|
|
1164
|
+
function tsconfigPaths(config) {
|
|
1165
|
+
return {
|
|
1166
|
+
name: "wxt:aliases",
|
|
1167
|
+
async config() {
|
|
1168
|
+
return {
|
|
1169
|
+
resolve: {
|
|
1170
|
+
alias: config.alias
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/core/builders/vite/plugins/noopBackground.ts
|
|
1178
|
+
function noopBackground() {
|
|
1179
|
+
const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID;
|
|
1180
|
+
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
1181
|
+
return {
|
|
1182
|
+
name: "wxt:noop-background",
|
|
1183
|
+
resolveId(id) {
|
|
1184
|
+
if (id === virtualModuleId)
|
|
1185
|
+
return resolvedVirtualModuleId;
|
|
1186
|
+
},
|
|
1187
|
+
load(id) {
|
|
1188
|
+
if (id === resolvedVirtualModuleId) {
|
|
1189
|
+
return `import { defineBackground } from 'wxt/client';
|
|
1190
|
+
export default defineBackground(() => void 0)`;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// src/core/builders/vite/plugins/cssEntrypoints.ts
|
|
1197
|
+
function cssEntrypoints(entrypoint, config) {
|
|
1198
|
+
return {
|
|
1199
|
+
name: "wxt:css-entrypoint",
|
|
1200
|
+
config() {
|
|
1201
|
+
return {
|
|
1202
|
+
build: {
|
|
1203
|
+
rollupOptions: {
|
|
1204
|
+
output: {
|
|
1205
|
+
assetFileNames: () => getEntrypointBundlePath(entrypoint, config.outDir, ".css")
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
},
|
|
1211
|
+
generateBundle(_, bundle) {
|
|
1212
|
+
Object.keys(bundle).forEach((file) => {
|
|
1213
|
+
if (file.endsWith(".js"))
|
|
1214
|
+
delete bundle[file];
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// src/core/builders/vite/plugins/bundleAnalysis.ts
|
|
1221
|
+
import { visualizer } from "rollup-plugin-visualizer";
|
|
1222
|
+
var increment = 0;
|
|
1223
|
+
function bundleAnalysis() {
|
|
1224
|
+
return visualizer({
|
|
1225
|
+
emitFile: true,
|
|
1226
|
+
template: "raw-data",
|
|
1227
|
+
filename: `stats-${increment++}.json`
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// src/core/builders/vite/plugins/globals.ts
|
|
1232
|
+
function globals(config) {
|
|
1233
|
+
return {
|
|
1234
|
+
name: "wxt:globals",
|
|
1235
|
+
config() {
|
|
1236
|
+
const define = {};
|
|
1237
|
+
for (const global of getGlobals(config)) {
|
|
1238
|
+
define[global.name] = JSON.stringify(global.value);
|
|
1239
|
+
}
|
|
1240
|
+
return {
|
|
1241
|
+
define
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// src/core/builders/vite/plugins/excludeBrowserPolyfill.ts
|
|
1248
|
+
function excludeBrowserPolyfill(config) {
|
|
1249
|
+
const virtualId = "virtual:wxt-webextension-polyfill-disabled";
|
|
1250
|
+
return {
|
|
1251
|
+
name: "wxt:exclude-browser-polyfill",
|
|
1252
|
+
config() {
|
|
1253
|
+
if (config.experimental.includeBrowserPolyfill)
|
|
1254
|
+
return;
|
|
1255
|
+
return {
|
|
1256
|
+
resolve: {
|
|
1257
|
+
alias: {
|
|
1258
|
+
"webextension-polyfill": virtualId
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
},
|
|
1263
|
+
load(id) {
|
|
1264
|
+
if (id === virtualId) {
|
|
1265
|
+
return "export default chrome";
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// src/core/builders/vite/plugins/entrypointGroupGlobals.ts
|
|
1272
|
+
function entrypointGroupGlobals(entrypointGroup) {
|
|
1273
|
+
return {
|
|
1274
|
+
name: "wxt:entrypoint-group-globals",
|
|
1275
|
+
config() {
|
|
1276
|
+
const define = {};
|
|
1277
|
+
let name = Array.isArray(entrypointGroup) ? "html" : entrypointGroup.name;
|
|
1278
|
+
for (const global of getEntrypointGlobals(name)) {
|
|
1279
|
+
define[global.name] = JSON.stringify(global.value);
|
|
1280
|
+
}
|
|
1281
|
+
return {
|
|
1282
|
+
define
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/core/builders/vite/index.ts
|
|
1289
|
+
async function craeteViteBuilder(inlineConfig, userConfig, wxtConfig) {
|
|
1290
|
+
const vite = await import("vite");
|
|
1291
|
+
const getBaseConfig = async () => {
|
|
1292
|
+
const resolvedInlineConfig = await inlineConfig.vite?.(wxtConfig.env) ?? {};
|
|
1293
|
+
const resolvedUserConfig = await userConfig.vite?.(wxtConfig.env) ?? {};
|
|
1294
|
+
const config = vite.mergeConfig(
|
|
1295
|
+
resolvedUserConfig,
|
|
1296
|
+
resolvedInlineConfig
|
|
1297
|
+
);
|
|
1298
|
+
config.root = wxtConfig.root;
|
|
1299
|
+
config.configFile = false;
|
|
1300
|
+
config.logLevel = "warn";
|
|
1301
|
+
config.mode = wxtConfig.mode;
|
|
1302
|
+
config.build ??= {};
|
|
1303
|
+
config.build.outDir = wxtConfig.outDir;
|
|
1304
|
+
config.build.emptyOutDir = false;
|
|
1305
|
+
config.plugins ??= [];
|
|
1306
|
+
config.plugins.push(
|
|
1307
|
+
download(wxtConfig),
|
|
1308
|
+
devHtmlPrerender(wxtConfig),
|
|
1309
|
+
unimport(wxtConfig),
|
|
1310
|
+
virtualEntrypoint("background", wxtConfig),
|
|
1311
|
+
virtualEntrypoint("content-script-isolated-world", wxtConfig),
|
|
1312
|
+
virtualEntrypoint("content-script-main-world", wxtConfig),
|
|
1313
|
+
virtualEntrypoint("unlisted-script", wxtConfig),
|
|
1314
|
+
devServerGlobals(wxtConfig),
|
|
1315
|
+
tsconfigPaths(wxtConfig),
|
|
1316
|
+
noopBackground(),
|
|
1317
|
+
globals(wxtConfig),
|
|
1318
|
+
excludeBrowserPolyfill(wxtConfig)
|
|
1319
|
+
);
|
|
1320
|
+
if (wxtConfig.analysis.enabled) {
|
|
1321
|
+
config.plugins.push(bundleAnalysis());
|
|
1322
|
+
}
|
|
1323
|
+
return config;
|
|
1324
|
+
};
|
|
1325
|
+
const getLibModeConfig = (entrypoint) => {
|
|
1326
|
+
let virtualEntrypointType;
|
|
1327
|
+
switch (entrypoint.type) {
|
|
1328
|
+
case "background":
|
|
1329
|
+
case "unlisted-script":
|
|
1330
|
+
virtualEntrypointType = entrypoint.type;
|
|
1331
|
+
break;
|
|
1332
|
+
case "content-script":
|
|
1333
|
+
virtualEntrypointType = entrypoint.options.world === "MAIN" ? "content-script-main-world" : "content-script-isolated-world";
|
|
1334
|
+
break;
|
|
1335
|
+
}
|
|
1336
|
+
const entry = virtualEntrypointType ? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}` : entrypoint.inputPath;
|
|
1337
|
+
const plugins = [
|
|
1338
|
+
entrypointGroupGlobals(entrypoint)
|
|
1339
|
+
];
|
|
1340
|
+
if (entrypoint.type === "content-script-style" || entrypoint.type === "unlisted-style") {
|
|
1341
|
+
plugins.push(cssEntrypoints(entrypoint, wxtConfig));
|
|
1342
|
+
}
|
|
1343
|
+
const libMode = {
|
|
1344
|
+
mode: wxtConfig.mode,
|
|
1345
|
+
plugins,
|
|
1346
|
+
build: {
|
|
1347
|
+
lib: {
|
|
1348
|
+
entry,
|
|
1349
|
+
formats: ["iife"],
|
|
1350
|
+
name: "_",
|
|
1351
|
+
fileName: entrypoint.name
|
|
1352
|
+
},
|
|
1353
|
+
rollupOptions: {
|
|
1354
|
+
output: {
|
|
1355
|
+
// There's only a single output for this build, so we use the desired bundle path for the
|
|
1356
|
+
// entry output (like "content-scripts/overlay.js")
|
|
1357
|
+
entryFileNames: getEntrypointBundlePath(
|
|
1358
|
+
entrypoint,
|
|
1359
|
+
wxtConfig.outDir,
|
|
1360
|
+
".js"
|
|
1361
|
+
),
|
|
1362
|
+
// Output content script CSS to `content-scripts/`, but all other scripts are written to
|
|
1363
|
+
// `assets/`.
|
|
1364
|
+
assetFileNames: ({ name }) => {
|
|
1365
|
+
if (entrypoint.type === "content-script" && name?.endsWith("css")) {
|
|
1366
|
+
return `content-scripts/${entrypoint.name}.[ext]`;
|
|
1367
|
+
} else {
|
|
1368
|
+
return `assets/${entrypoint.name}.[ext]`;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
},
|
|
1374
|
+
define: {
|
|
1375
|
+
// See https://github.com/aklinker1/vite-plugin-web-extension/issues/96
|
|
1376
|
+
"process.env.NODE_ENV": JSON.stringify(wxtConfig.mode)
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
return libMode;
|
|
1380
|
+
};
|
|
1381
|
+
const getMultiPageConfig = (entrypoints) => {
|
|
1382
|
+
return {
|
|
1383
|
+
mode: wxtConfig.mode,
|
|
1384
|
+
plugins: [
|
|
1385
|
+
multipageMove(entrypoints, wxtConfig),
|
|
1386
|
+
entrypointGroupGlobals(entrypoints)
|
|
1387
|
+
],
|
|
1388
|
+
build: {
|
|
1389
|
+
rollupOptions: {
|
|
1390
|
+
input: entrypoints.reduce((input, entry) => {
|
|
1391
|
+
input[entry.name] = entry.inputPath;
|
|
1392
|
+
return input;
|
|
1393
|
+
}, {}),
|
|
1394
|
+
output: {
|
|
1395
|
+
// Include a hash to prevent conflicts
|
|
1396
|
+
chunkFileNames: "chunks/[name]-[hash].js",
|
|
1397
|
+
// Include a hash to prevent conflicts
|
|
1398
|
+
entryFileNames: "chunks/[name]-[hash].js",
|
|
1399
|
+
// We can't control the "name", so we need a hash to prevent conflicts
|
|
1400
|
+
assetFileNames: "assets/[name]-[hash].[ext]"
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
};
|
|
1406
|
+
const getCssConfig = (entrypoint) => {
|
|
1407
|
+
return {
|
|
1408
|
+
mode: wxtConfig.mode,
|
|
1409
|
+
plugins: [entrypointGroupGlobals(entrypoint)],
|
|
1410
|
+
build: {
|
|
1411
|
+
rollupOptions: {
|
|
1412
|
+
input: {
|
|
1413
|
+
[entrypoint.name]: entrypoint.inputPath
|
|
1414
|
+
},
|
|
1415
|
+
output: {
|
|
1416
|
+
assetFileNames: () => {
|
|
1417
|
+
if (entrypoint.type === "content-script-style") {
|
|
1418
|
+
return `content-scripts/${entrypoint.name}.[ext]`;
|
|
1419
|
+
} else {
|
|
1420
|
+
return `assets/${entrypoint.name}.[ext]`;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
};
|
|
1428
|
+
return {
|
|
1429
|
+
name: "Vite",
|
|
1430
|
+
version: vite.version,
|
|
1431
|
+
async build(group) {
|
|
1432
|
+
let entryConfig;
|
|
1433
|
+
if (Array.isArray(group))
|
|
1434
|
+
entryConfig = getMultiPageConfig(group);
|
|
1435
|
+
else if (group.inputPath.endsWith(".css"))
|
|
1436
|
+
entryConfig = getCssConfig(group);
|
|
1437
|
+
else
|
|
1438
|
+
entryConfig = getLibModeConfig(group);
|
|
1439
|
+
const buildConfig = vite.mergeConfig(await getBaseConfig(), entryConfig);
|
|
1440
|
+
const result = await vite.build(buildConfig);
|
|
1441
|
+
return {
|
|
1442
|
+
entrypoints: group,
|
|
1443
|
+
chunks: getBuildOutputChunks(result)
|
|
1444
|
+
};
|
|
1445
|
+
},
|
|
1446
|
+
async createServer(info) {
|
|
1447
|
+
const serverConfig = {
|
|
1448
|
+
server: {
|
|
1449
|
+
port: info.port,
|
|
1450
|
+
strictPort: true,
|
|
1451
|
+
host: info.hostname,
|
|
1452
|
+
origin: info.origin
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
const baseConfig = await getBaseConfig();
|
|
1456
|
+
const viteServer = await vite.createServer(
|
|
1457
|
+
vite.mergeConfig(baseConfig, serverConfig)
|
|
1458
|
+
);
|
|
1459
|
+
const server = {
|
|
1460
|
+
async listen() {
|
|
1461
|
+
await viteServer.listen(info.port);
|
|
1462
|
+
},
|
|
1463
|
+
transformHtml(...args) {
|
|
1464
|
+
return viteServer.transformIndexHtml(...args);
|
|
1465
|
+
},
|
|
1466
|
+
ws: {
|
|
1467
|
+
send(message, payload) {
|
|
1468
|
+
return viteServer.ws.send(message, payload);
|
|
1469
|
+
},
|
|
1470
|
+
on(message, cb) {
|
|
1471
|
+
viteServer.ws.on(message, cb);
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
watcher: viteServer.watcher
|
|
1475
|
+
};
|
|
1476
|
+
return server;
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
function getBuildOutputChunks(result) {
|
|
1481
|
+
if ("on" in result)
|
|
1482
|
+
throw Error("wxt does not support vite watch mode.");
|
|
1483
|
+
if (Array.isArray(result))
|
|
1484
|
+
return result.flatMap(({ output }) => output);
|
|
1485
|
+
return result.output;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// src/core/utils/building/get-internal-config.ts
|
|
1489
|
+
import defu2 from "defu";
|
|
1490
|
+
async function getInternalConfig(inlineConfig, command, server) {
|
|
1491
|
+
let userConfig = {};
|
|
1492
|
+
let userConfigMetadata;
|
|
1493
|
+
if (inlineConfig.configFile !== false) {
|
|
1494
|
+
const { config: loadedConfig, ...metadata } = await loadConfig({
|
|
1495
|
+
name: "wxt",
|
|
1496
|
+
cwd: inlineConfig.root ?? process.cwd(),
|
|
1497
|
+
rcFile: false,
|
|
1498
|
+
jitiOptions: {
|
|
1499
|
+
esmResolve: true
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
userConfig = loadedConfig ?? {};
|
|
1503
|
+
userConfigMetadata = metadata;
|
|
1504
|
+
}
|
|
1505
|
+
const mergedConfig = mergeInlineConfig(inlineConfig, userConfig);
|
|
1506
|
+
const debug = mergedConfig.debug ?? false;
|
|
1507
|
+
const logger = mergedConfig.logger ?? consola;
|
|
1508
|
+
if (debug)
|
|
1509
|
+
logger.level = LogLevels.debug;
|
|
1510
|
+
const browser = mergedConfig.browser ?? "chrome";
|
|
1511
|
+
const manifestVersion = mergedConfig.manifestVersion ?? (browser === "firefox" || browser === "safari" ? 2 : 3);
|
|
1512
|
+
const mode = mergedConfig.mode ?? (command === "build" ? "production" : "development");
|
|
1513
|
+
const env = { browser, command, manifestVersion, mode };
|
|
1514
|
+
const root = path3.resolve(
|
|
1515
|
+
inlineConfig.root ?? userConfig.root ?? process.cwd()
|
|
1516
|
+
);
|
|
1517
|
+
const wxtDir = path3.resolve(root, ".wxt");
|
|
1518
|
+
const srcDir = path3.resolve(root, mergedConfig.srcDir ?? root);
|
|
1519
|
+
const entrypointsDir = path3.resolve(
|
|
1520
|
+
srcDir,
|
|
1521
|
+
mergedConfig.entrypointsDir ?? "entrypoints"
|
|
1522
|
+
);
|
|
1523
|
+
const publicDir = path3.resolve(srcDir, mergedConfig.publicDir ?? "public");
|
|
1524
|
+
const typesDir = path3.resolve(wxtDir, "types");
|
|
1525
|
+
const outBaseDir = path3.resolve(root, mergedConfig.outDir ?? ".output");
|
|
1526
|
+
const outDir = path3.resolve(outBaseDir, `${browser}-mv${manifestVersion}`);
|
|
1527
|
+
const runnerConfig = await loadConfig({
|
|
1528
|
+
name: "web-ext",
|
|
1529
|
+
cwd: root,
|
|
1530
|
+
globalRc: true,
|
|
1531
|
+
rcFile: ".webextrc",
|
|
1532
|
+
overrides: inlineConfig.runner,
|
|
1533
|
+
defaults: userConfig.runner
|
|
1534
|
+
});
|
|
1535
|
+
const alias = Object.fromEntries(
|
|
1536
|
+
Object.entries({
|
|
1537
|
+
...mergedConfig.alias,
|
|
1538
|
+
"@": srcDir,
|
|
1539
|
+
"~": srcDir,
|
|
1540
|
+
"@@": root,
|
|
1541
|
+
"~~": root
|
|
1542
|
+
}).map(([key, value]) => [key, path3.resolve(root, value)])
|
|
1543
|
+
);
|
|
1544
|
+
const finalConfig = {
|
|
1545
|
+
browser,
|
|
1546
|
+
command,
|
|
1547
|
+
debug,
|
|
1548
|
+
entrypointsDir,
|
|
1549
|
+
env,
|
|
1550
|
+
fsCache: createFsCache(wxtDir),
|
|
1551
|
+
imports: mergedConfig.imports ?? {},
|
|
1552
|
+
logger,
|
|
1553
|
+
manifest: await resolveManifestConfig(env, mergedConfig.manifest),
|
|
1554
|
+
manifestVersion,
|
|
1555
|
+
mode,
|
|
1556
|
+
outBaseDir,
|
|
1557
|
+
outDir,
|
|
1558
|
+
publicDir,
|
|
1559
|
+
root,
|
|
1560
|
+
runnerConfig,
|
|
1561
|
+
srcDir,
|
|
1562
|
+
typesDir,
|
|
1563
|
+
wxtDir,
|
|
1564
|
+
zip: resolveInternalZipConfig(root, mergedConfig),
|
|
1565
|
+
transformManifest(manifest) {
|
|
1566
|
+
userConfig.transformManifest?.(manifest);
|
|
1567
|
+
inlineConfig.transformManifest?.(manifest);
|
|
1568
|
+
},
|
|
1569
|
+
analysis: {
|
|
1570
|
+
enabled: mergedConfig.analysis?.enabled ?? false,
|
|
1571
|
+
template: mergedConfig.analysis?.template ?? "treemap"
|
|
1572
|
+
},
|
|
1573
|
+
userConfigMetadata: userConfigMetadata ?? {},
|
|
1574
|
+
alias,
|
|
1575
|
+
experimental: {
|
|
1576
|
+
includeBrowserPolyfill: mergedConfig.experimental?.includeBrowserPolyfill ?? true
|
|
1577
|
+
},
|
|
1578
|
+
server
|
|
1579
|
+
};
|
|
1580
|
+
const builder = await craeteViteBuilder(
|
|
1581
|
+
inlineConfig,
|
|
1582
|
+
userConfig,
|
|
1583
|
+
finalConfig
|
|
1584
|
+
);
|
|
1585
|
+
return {
|
|
1586
|
+
...finalConfig,
|
|
1587
|
+
builder
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
async function resolveManifestConfig(env, manifest) {
|
|
1591
|
+
return await (typeof manifest === "function" ? manifest(env) : manifest ?? {});
|
|
1592
|
+
}
|
|
1593
|
+
function mergeInlineConfig(inlineConfig, userConfig) {
|
|
1594
|
+
let imports;
|
|
1595
|
+
if (inlineConfig.imports === false || userConfig.imports === false) {
|
|
1596
|
+
imports = false;
|
|
1597
|
+
} else if (userConfig.imports == null && inlineConfig.imports == null) {
|
|
1598
|
+
imports = void 0;
|
|
1599
|
+
} else {
|
|
1600
|
+
imports = defu2(inlineConfig.imports ?? {}, userConfig.imports ?? {});
|
|
1601
|
+
}
|
|
1602
|
+
const manifest = async (env) => {
|
|
1603
|
+
const user = await resolveManifestConfig(env, userConfig.manifest);
|
|
1604
|
+
const inline = await resolveManifestConfig(env, inlineConfig.manifest);
|
|
1605
|
+
return defu2(inline, user);
|
|
1606
|
+
};
|
|
1607
|
+
const runner = defu2(
|
|
1608
|
+
inlineConfig.runner ?? {},
|
|
1609
|
+
userConfig.runner ?? {}
|
|
1610
|
+
);
|
|
1611
|
+
const zip2 = defu2(
|
|
1612
|
+
inlineConfig.zip ?? {},
|
|
1613
|
+
userConfig.zip ?? {}
|
|
1614
|
+
);
|
|
1615
|
+
return {
|
|
1616
|
+
root: inlineConfig.root ?? userConfig.root,
|
|
1617
|
+
browser: inlineConfig.browser ?? userConfig.browser,
|
|
1618
|
+
manifestVersion: inlineConfig.manifestVersion ?? userConfig.manifestVersion,
|
|
1619
|
+
configFile: inlineConfig.configFile,
|
|
1620
|
+
debug: inlineConfig.debug ?? userConfig.debug,
|
|
1621
|
+
entrypointsDir: inlineConfig.entrypointsDir ?? userConfig.entrypointsDir,
|
|
1622
|
+
imports,
|
|
1623
|
+
logger: inlineConfig.logger ?? userConfig.logger,
|
|
1624
|
+
manifest,
|
|
1625
|
+
mode: inlineConfig.mode ?? userConfig.mode,
|
|
1626
|
+
publicDir: inlineConfig.publicDir ?? userConfig.publicDir,
|
|
1627
|
+
runner,
|
|
1628
|
+
srcDir: inlineConfig.srcDir ?? userConfig.srcDir,
|
|
1629
|
+
outDir: inlineConfig.outDir ?? userConfig.outDir,
|
|
1630
|
+
zip: zip2,
|
|
1631
|
+
analysis: {
|
|
1632
|
+
enabled: inlineConfig.analysis?.enabled ?? userConfig.analysis?.enabled,
|
|
1633
|
+
template: inlineConfig.analysis?.template ?? userConfig.analysis?.template
|
|
1634
|
+
},
|
|
1635
|
+
alias: {
|
|
1636
|
+
...userConfig.alias,
|
|
1637
|
+
...inlineConfig.alias
|
|
1638
|
+
},
|
|
1639
|
+
experimental: {
|
|
1640
|
+
...userConfig.experimental,
|
|
1641
|
+
...inlineConfig.experimental
|
|
1642
|
+
},
|
|
1643
|
+
vite: void 0,
|
|
1644
|
+
transformManifest: void 0
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
function resolveInternalZipConfig(root, mergedConfig) {
|
|
1648
|
+
return {
|
|
1649
|
+
name: void 0,
|
|
1650
|
+
sourcesTemplate: "{{name}}-{{version}}-sources.zip",
|
|
1651
|
+
artifactTemplate: "{{name}}-{{version}}-{{browser}}.zip",
|
|
1652
|
+
sourcesRoot: root,
|
|
1653
|
+
...mergedConfig.zip,
|
|
1654
|
+
ignoredSources: [
|
|
1655
|
+
"**/node_modules",
|
|
1656
|
+
// WXT files
|
|
1657
|
+
"**/web-ext.config.ts",
|
|
1658
|
+
// Hidden files
|
|
1659
|
+
"**/.*",
|
|
1660
|
+
// Tests
|
|
1661
|
+
"**/__tests__/**",
|
|
1662
|
+
"**/*.+(test|spec).?(c|m)+(j|t)s?(x)",
|
|
1663
|
+
// From user
|
|
1664
|
+
...mergedConfig.zip?.ignoredSources ?? []
|
|
1665
|
+
]
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// src/core/utils/building/group-entrypoints.ts
|
|
1670
|
+
function groupEntrypoints(entrypoints) {
|
|
1671
|
+
const groupIndexMap = {};
|
|
1672
|
+
const groups = [];
|
|
1673
|
+
for (const entry of entrypoints) {
|
|
1674
|
+
const group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
|
|
1675
|
+
if (group === "no-group") {
|
|
1676
|
+
groups.push(entry);
|
|
1677
|
+
} else {
|
|
1678
|
+
let groupIndex = groupIndexMap[group];
|
|
1679
|
+
if (groupIndex == null) {
|
|
1680
|
+
groupIndex = groups.push([]) - 1;
|
|
1681
|
+
groupIndexMap[group] = groupIndex;
|
|
1682
|
+
}
|
|
1683
|
+
groups[groupIndex].push(entry);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
return groups;
|
|
1687
|
+
}
|
|
1688
|
+
var ENTRY_TYPE_TO_GROUP_MAP = {
|
|
1689
|
+
sandbox: "sandbox-page",
|
|
1690
|
+
popup: "extension-page",
|
|
1691
|
+
newtab: "extension-page",
|
|
1692
|
+
history: "extension-page",
|
|
1693
|
+
options: "extension-page",
|
|
1694
|
+
devtools: "extension-page",
|
|
1695
|
+
bookmarks: "extension-page",
|
|
1696
|
+
sidepanel: "extension-page",
|
|
1697
|
+
"unlisted-page": "extension-page",
|
|
1698
|
+
background: "no-group",
|
|
1699
|
+
"content-script": "no-group",
|
|
1700
|
+
"unlisted-script": "no-group",
|
|
1701
|
+
"unlisted-style": "no-group",
|
|
1702
|
+
"content-script-style": "no-group"
|
|
1703
|
+
};
|
|
1704
|
+
|
|
1705
|
+
// src/core/utils/building/import-entrypoint.ts
|
|
1706
|
+
import createJITI from "jiti";
|
|
1707
|
+
import { createUnimport as createUnimport3 } from "unimport";
|
|
1708
|
+
import fs8 from "fs-extra";
|
|
1709
|
+
import { resolve as resolve9 } from "node:path";
|
|
1710
|
+
|
|
1711
|
+
// src/core/utils/strings.ts
|
|
1712
|
+
function kebabCaseAlphanumeric(str) {
|
|
1713
|
+
return str.toLowerCase().replace(/[^a-z0-9-\s]/g, "").replace(/\s+/g, "-");
|
|
1714
|
+
}
|
|
1715
|
+
function removeImportStatements(text) {
|
|
1716
|
+
return text.replace(
|
|
1717
|
+
/(import\s?[{\w][\s\S]*?from\s?["'][\s\S]*?["'];?|import\s?["'][\s\S]*?["'];?)/gm,
|
|
1718
|
+
""
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
function removeProjectImportStatements(text) {
|
|
1722
|
+
const noImports = removeImportStatements(text);
|
|
1723
|
+
return `import { defineUnlistedScript, defineContentScript, defineBackground } from 'wxt/sandbox';
|
|
1724
|
+
|
|
1725
|
+
${noImports}`;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// src/core/utils/building/import-entrypoint.ts
|
|
1729
|
+
import { transformSync } from "esbuild";
|
|
1730
|
+
import { fileURLToPath } from "node:url";
|
|
1731
|
+
async function importEntrypointFile(path7, config) {
|
|
1732
|
+
config.logger.debug("Loading file metadata:", path7);
|
|
1733
|
+
const normalPath = normalizePath(path7);
|
|
1734
|
+
const unimport2 = createUnimport3({
|
|
1735
|
+
...getUnimportOptions(config),
|
|
1736
|
+
// Only allow specific imports, not all from the project
|
|
1737
|
+
dirs: []
|
|
1738
|
+
});
|
|
1739
|
+
await unimport2.init();
|
|
1740
|
+
const text = await fs8.readFile(path7, "utf-8");
|
|
1741
|
+
const textNoImports = removeProjectImportStatements(text);
|
|
1742
|
+
const { code } = await unimport2.injectImports(textNoImports);
|
|
1743
|
+
config.logger.debug(
|
|
1744
|
+
["Text:", text, "No imports:", textNoImports, "Code:", code].join("\n")
|
|
1745
|
+
);
|
|
1746
|
+
const jiti = createJITI(
|
|
1747
|
+
typeof __filename !== "undefined" ? __filename : fileURLToPath(import.meta.url),
|
|
1748
|
+
{
|
|
1749
|
+
cache: false,
|
|
1750
|
+
debug: config.debug,
|
|
1751
|
+
esmResolve: true,
|
|
1752
|
+
alias: {
|
|
1753
|
+
"webextension-polyfill": resolve9(
|
|
1754
|
+
config.root,
|
|
1755
|
+
"node_modules/wxt/dist/virtual/mock-browser.js"
|
|
1756
|
+
)
|
|
1757
|
+
},
|
|
1758
|
+
// Continue using node to load TS files even if `bun run --bun` is detected. Jiti does not
|
|
1759
|
+
// respect the custom transform function when using it's native bun option.
|
|
1760
|
+
experimentalBun: false,
|
|
1761
|
+
// List of extensions to transform with esbuild
|
|
1762
|
+
extensions: [
|
|
1763
|
+
".ts",
|
|
1764
|
+
".cts",
|
|
1765
|
+
".mts",
|
|
1766
|
+
".tsx",
|
|
1767
|
+
".js",
|
|
1768
|
+
".cjs",
|
|
1769
|
+
".mjs",
|
|
1770
|
+
".jsx"
|
|
1771
|
+
],
|
|
1772
|
+
transform(opts) {
|
|
1773
|
+
const isEntrypoint = opts.filename === normalPath;
|
|
1774
|
+
return transformSync(
|
|
1775
|
+
// Use modified source code for entrypoints
|
|
1776
|
+
isEntrypoint ? code : opts.source,
|
|
1777
|
+
getEsbuildOptions(opts)
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
);
|
|
1782
|
+
try {
|
|
1783
|
+
const res = await jiti(path7);
|
|
1784
|
+
return res.default;
|
|
1785
|
+
} catch (err) {
|
|
1786
|
+
config.logger.error(err);
|
|
1787
|
+
throw err;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function getEsbuildOptions(opts) {
|
|
1791
|
+
const isJsx = opts.filename?.endsWith("x");
|
|
1792
|
+
return {
|
|
1793
|
+
format: "cjs",
|
|
1794
|
+
loader: isJsx ? "tsx" : "ts",
|
|
1795
|
+
jsx: isJsx ? "automatic" : void 0
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// src/core/utils/building/internal-build.ts
|
|
1800
|
+
import pc4 from "picocolors";
|
|
1801
|
+
import fs12 from "fs-extra";
|
|
1802
|
+
|
|
1803
|
+
// src/core/utils/log/printBuildSummary.ts
|
|
1804
|
+
import { resolve as resolve10 } from "path";
|
|
1805
|
+
|
|
1806
|
+
// src/core/utils/log/printFileList.ts
|
|
1807
|
+
import path4 from "node:path";
|
|
1808
|
+
import pc2 from "picocolors";
|
|
1809
|
+
import fs9 from "fs-extra";
|
|
1810
|
+
import { filesize } from "filesize";
|
|
1811
|
+
|
|
1812
|
+
// src/core/utils/log/printTable.ts
|
|
1813
|
+
function printTable(log, header, rows, gap = 2) {
|
|
1814
|
+
if (rows.length === 0)
|
|
1815
|
+
return;
|
|
1816
|
+
const columnWidths = rows.reduce(
|
|
1817
|
+
(widths, row) => {
|
|
1818
|
+
for (let i = 0; i < Math.max(widths.length, row.length); i++) {
|
|
1819
|
+
widths[i] = Math.max(row[i]?.length ?? 0, widths[i] ?? 0);
|
|
1820
|
+
}
|
|
1821
|
+
return widths;
|
|
1822
|
+
},
|
|
1823
|
+
rows[0].map((column) => column.length)
|
|
1824
|
+
);
|
|
1825
|
+
let str = "";
|
|
1826
|
+
rows.forEach((row, i) => {
|
|
1827
|
+
row.forEach((col, j) => {
|
|
1828
|
+
str += col.padEnd(columnWidths[j], " ");
|
|
1829
|
+
if (j !== row.length - 1)
|
|
1830
|
+
str += "".padEnd(gap, " ");
|
|
1831
|
+
});
|
|
1832
|
+
if (i !== rows.length - 1)
|
|
1833
|
+
str += "\n";
|
|
1834
|
+
});
|
|
1835
|
+
log(`${header}
|
|
1836
|
+
${str}`);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// src/core/utils/log/printFileList.ts
|
|
1840
|
+
async function printFileList(log, header, baseDir, files) {
|
|
1841
|
+
let totalSize = 0;
|
|
1842
|
+
const fileRows = await Promise.all(
|
|
1843
|
+
files.map(async (file, i) => {
|
|
1844
|
+
const parts = [
|
|
1845
|
+
path4.relative(process.cwd(), baseDir) + path4.sep,
|
|
1846
|
+
path4.relative(baseDir, file)
|
|
1847
|
+
];
|
|
1848
|
+
const prefix = i === files.length - 1 ? " \u2514\u2500" : " \u251C\u2500";
|
|
1849
|
+
const color = getChunkColor(file);
|
|
1850
|
+
const stats = await fs9.lstat(file);
|
|
1851
|
+
totalSize += stats.size;
|
|
1852
|
+
const size = String(filesize(stats.size));
|
|
1853
|
+
return [
|
|
1854
|
+
`${pc2.gray(prefix)} ${pc2.dim(parts[0])}${color(parts[1])}`,
|
|
1855
|
+
pc2.dim(size)
|
|
1856
|
+
];
|
|
1857
|
+
})
|
|
1858
|
+
);
|
|
1859
|
+
fileRows.push([`${pc2.cyan("\u03A3 Total size:")} ${String(filesize(totalSize))}`]);
|
|
1860
|
+
printTable(log, header, fileRows);
|
|
1861
|
+
}
|
|
1862
|
+
var DEFAULT_COLOR = pc2.blue;
|
|
1863
|
+
var CHUNK_COLORS = {
|
|
1864
|
+
".js.map": pc2.gray,
|
|
1865
|
+
".cjs.map": pc2.gray,
|
|
1866
|
+
".mjs.map": pc2.gray,
|
|
1867
|
+
".html": pc2.green,
|
|
1868
|
+
".css": pc2.magenta,
|
|
1869
|
+
".js": pc2.cyan,
|
|
1870
|
+
".cjs": pc2.cyan,
|
|
1871
|
+
".mjs": pc2.cyan,
|
|
1872
|
+
".zip": pc2.yellow
|
|
1873
|
+
};
|
|
1874
|
+
function getChunkColor(filename) {
|
|
1875
|
+
return Object.entries(CHUNK_COLORS).find(([key]) => filename.endsWith(key))?.[1] ?? DEFAULT_COLOR;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// src/core/utils/log/printBuildSummary.ts
|
|
1879
|
+
async function printBuildSummary(log, header, output, config) {
|
|
1880
|
+
const chunks = [
|
|
1881
|
+
...output.steps.flatMap((step) => step.chunks),
|
|
1882
|
+
...output.publicAssets
|
|
1883
|
+
].sort((l, r) => {
|
|
1884
|
+
const lWeight = getChunkSortWeight(l.fileName);
|
|
1885
|
+
const rWeight = getChunkSortWeight(r.fileName);
|
|
1886
|
+
const diff = lWeight - rWeight;
|
|
1887
|
+
if (diff !== 0)
|
|
1888
|
+
return diff;
|
|
1889
|
+
return l.fileName.localeCompare(r.fileName);
|
|
1890
|
+
});
|
|
1891
|
+
const files = chunks.map((chunk) => resolve10(config.outDir, chunk.fileName));
|
|
1892
|
+
await printFileList(log, header, config.outDir, files);
|
|
1893
|
+
}
|
|
1894
|
+
var DEFAULT_SORT_WEIGHT = 100;
|
|
1895
|
+
var CHUNK_SORT_WEIGHTS = {
|
|
1896
|
+
"manifest.json": 0,
|
|
1897
|
+
".html": 1,
|
|
1898
|
+
".js.map": 2,
|
|
1899
|
+
".js": 2,
|
|
1900
|
+
".css": 3
|
|
1901
|
+
};
|
|
1902
|
+
function getChunkSortWeight(filename) {
|
|
1903
|
+
return Object.entries(CHUNK_SORT_WEIGHTS).find(
|
|
1904
|
+
([key]) => filename.endsWith(key)
|
|
1905
|
+
)?.[1] ?? DEFAULT_SORT_WEIGHT;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// src/core/utils/log/printHeader.ts
|
|
1909
|
+
import pc3 from "picocolors";
|
|
1910
|
+
import { consola as consola2 } from "consola";
|
|
1911
|
+
function printHeader() {
|
|
1912
|
+
console.log();
|
|
1913
|
+
consola2.log(`${pc3.gray("WXT")} ${pc3.gray(pc3.bold(version))}`);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// src/core/utils/building/internal-build.ts
|
|
1917
|
+
import glob3 from "fast-glob";
|
|
1918
|
+
|
|
1919
|
+
// src/core/utils/manifest.ts
|
|
1920
|
+
import fs11 from "fs-extra";
|
|
1921
|
+
import { resolve as resolve12 } from "path";
|
|
1922
|
+
|
|
1923
|
+
// src/core/utils/content-security-policy.ts
|
|
1924
|
+
var ContentSecurityPolicy = class _ContentSecurityPolicy {
|
|
1925
|
+
static DIRECTIVE_ORDER = {
|
|
1926
|
+
"default-src": 0,
|
|
1927
|
+
"script-src": 1,
|
|
1928
|
+
"object-src": 2
|
|
1929
|
+
};
|
|
1930
|
+
data;
|
|
1931
|
+
constructor(csp) {
|
|
1932
|
+
if (csp) {
|
|
1933
|
+
const sections = csp.split(";").map((section) => section.trim());
|
|
1934
|
+
this.data = sections.reduce((data, section) => {
|
|
1935
|
+
const [key, ...values] = section.split(" ").map((item) => item.trim());
|
|
1936
|
+
if (key)
|
|
1937
|
+
data[key] = values;
|
|
1938
|
+
return data;
|
|
1939
|
+
}, {});
|
|
1940
|
+
} else {
|
|
1941
|
+
this.data = {};
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Ensure a set of values are listed under a directive.
|
|
1946
|
+
*/
|
|
1947
|
+
add(directive, ...newValues) {
|
|
1948
|
+
const values = this.data[directive] ?? [];
|
|
1949
|
+
newValues.forEach((newValue) => {
|
|
1950
|
+
if (!values.includes(newValue))
|
|
1951
|
+
values.push(newValue);
|
|
1952
|
+
});
|
|
1953
|
+
this.data[directive] = values;
|
|
1954
|
+
return this;
|
|
1955
|
+
}
|
|
1956
|
+
toString() {
|
|
1957
|
+
const directives = Object.entries(this.data).sort(([l], [r]) => {
|
|
1958
|
+
const lo = _ContentSecurityPolicy.DIRECTIVE_ORDER[l] ?? 2;
|
|
1959
|
+
const ro = _ContentSecurityPolicy.DIRECTIVE_ORDER[r] ?? 2;
|
|
1960
|
+
return lo - ro;
|
|
1961
|
+
});
|
|
1962
|
+
return directives.map((entry) => entry.flat().join(" ")).join("; ") + ";";
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
|
|
1966
|
+
// src/core/utils/content-scripts.ts
|
|
1967
|
+
function hashContentScriptOptions(options, config) {
|
|
1968
|
+
const simplifiedOptions = mapWxtOptionsToContentScript(options, config);
|
|
1969
|
+
Object.keys(simplifiedOptions).forEach((key) => {
|
|
1970
|
+
if (simplifiedOptions[key] == null)
|
|
1971
|
+
delete simplifiedOptions[key];
|
|
1972
|
+
});
|
|
1973
|
+
const withDefaults = {
|
|
1974
|
+
exclude_globs: [],
|
|
1975
|
+
exclude_matches: [],
|
|
1976
|
+
include_globs: [],
|
|
1977
|
+
match_about_blank: false,
|
|
1978
|
+
run_at: "document_idle",
|
|
1979
|
+
all_frames: false,
|
|
1980
|
+
// @ts-expect-error - not in type
|
|
1981
|
+
match_origin_as_fallback: false,
|
|
1982
|
+
world: "ISOLATED",
|
|
1983
|
+
...simplifiedOptions
|
|
1984
|
+
};
|
|
1985
|
+
return JSON.stringify(
|
|
1986
|
+
Object.entries(withDefaults).map(([key, value]) => {
|
|
1987
|
+
if (Array.isArray(value))
|
|
1988
|
+
return [key, value.sort()];
|
|
1989
|
+
else
|
|
1990
|
+
return [key, value];
|
|
1991
|
+
}).sort((l, r) => l[0].localeCompare(r[0]))
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
function mapWxtOptionsToContentScript(options, config) {
|
|
1995
|
+
return {
|
|
1996
|
+
matches: resolvePerBrowserOption(options.matches, config.browser),
|
|
1997
|
+
all_frames: resolvePerBrowserOption(options.allFrames, config.browser),
|
|
1998
|
+
match_about_blank: resolvePerBrowserOption(
|
|
1999
|
+
options.matchAboutBlank,
|
|
2000
|
+
config.browser
|
|
2001
|
+
),
|
|
2002
|
+
exclude_globs: resolvePerBrowserOption(
|
|
2003
|
+
options.excludeGlobs,
|
|
2004
|
+
config.browser
|
|
2005
|
+
),
|
|
2006
|
+
exclude_matches: resolvePerBrowserOption(
|
|
2007
|
+
options.excludeMatches,
|
|
2008
|
+
config.browser
|
|
2009
|
+
),
|
|
2010
|
+
include_globs: resolvePerBrowserOption(
|
|
2011
|
+
options.includeGlobs,
|
|
2012
|
+
config.browser
|
|
2013
|
+
),
|
|
2014
|
+
run_at: resolvePerBrowserOption(options.runAt, config.browser),
|
|
2015
|
+
// @ts-expect-error: untyped chrome options
|
|
2016
|
+
match_origin_as_fallback: resolvePerBrowserOption(
|
|
2017
|
+
options.matchOriginAsFallback,
|
|
2018
|
+
config.browser
|
|
2019
|
+
),
|
|
2020
|
+
world: options.world
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/core/utils/package.ts
|
|
2025
|
+
import { resolve as resolve11 } from "node:path";
|
|
2026
|
+
import fs10 from "fs-extra";
|
|
2027
|
+
async function getPackageJson(config) {
|
|
2028
|
+
const file = resolve11(config.root, "package.json");
|
|
2029
|
+
try {
|
|
2030
|
+
return await fs10.readJson(file);
|
|
2031
|
+
} catch (err) {
|
|
2032
|
+
config.logger.debug(
|
|
2033
|
+
`Failed to read package.json at: ${file}. Returning undefined.`
|
|
2034
|
+
);
|
|
2035
|
+
return {};
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// src/core/utils/manifest.ts
|
|
2040
|
+
import { produce } from "immer";
|
|
2041
|
+
import defu3 from "defu";
|
|
2042
|
+
async function writeManifest(manifest, output, config) {
|
|
2043
|
+
const str = config.mode === "production" ? JSON.stringify(manifest) : JSON.stringify(manifest, null, 2);
|
|
2044
|
+
await fs11.ensureDir(config.outDir);
|
|
2045
|
+
await writeFileIfDifferent(resolve12(config.outDir, "manifest.json"), str);
|
|
2046
|
+
output.publicAssets.unshift({
|
|
2047
|
+
type: "asset",
|
|
2048
|
+
fileName: "manifest.json"
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
async function generateMainfest(entrypoints, buildOutput, config) {
|
|
2052
|
+
const pkg = await getPackageJson(config);
|
|
2053
|
+
const versionName = config.manifest.version_name ?? pkg?.version;
|
|
2054
|
+
const version2 = config.manifest.version ?? simplifyVersion(pkg?.version);
|
|
2055
|
+
const baseManifest = {
|
|
2056
|
+
manifest_version: config.manifestVersion,
|
|
2057
|
+
name: pkg?.name,
|
|
2058
|
+
description: pkg?.description,
|
|
2059
|
+
version: version2,
|
|
2060
|
+
version_name: (
|
|
2061
|
+
// Firefox doesn't support version_name
|
|
2062
|
+
config.browser === "firefox" || versionName === version2 ? void 0 : versionName
|
|
2063
|
+
),
|
|
2064
|
+
short_name: pkg?.shortName,
|
|
2065
|
+
icons: discoverIcons(buildOutput)
|
|
2066
|
+
};
|
|
2067
|
+
const userManifest = config.manifest;
|
|
2068
|
+
const manifest = defu3(
|
|
2069
|
+
userManifest,
|
|
2070
|
+
baseManifest
|
|
2071
|
+
);
|
|
2072
|
+
addEntrypoints(manifest, entrypoints, buildOutput, config);
|
|
2073
|
+
if (config.command === "serve")
|
|
2074
|
+
addDevModeCsp(manifest, config);
|
|
2075
|
+
if (config.command === "serve")
|
|
2076
|
+
addDevModePermissions(manifest, config);
|
|
2077
|
+
const finalManifest = produce(manifest, config.transformManifest);
|
|
2078
|
+
if (finalManifest.name == null)
|
|
2079
|
+
throw Error(
|
|
2080
|
+
"Manifest 'name' is missing. Either:\n1. Set the name in your <rootDir>/package.json\n2. Set a name via the manifest option in your wxt.config.ts"
|
|
2081
|
+
);
|
|
2082
|
+
if (finalManifest.version == null) {
|
|
2083
|
+
throw Error(
|
|
2084
|
+
"Manifest 'version' is missing. Either:\n1. Add a version in your <rootDir>/package.json\n2. Pass the version via the manifest option in your wxt.config.ts"
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
return finalManifest;
|
|
2088
|
+
}
|
|
2089
|
+
function simplifyVersion(versionName) {
|
|
2090
|
+
const version2 = /^((0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}).*$/.exec(
|
|
2091
|
+
versionName
|
|
2092
|
+
)?.[1];
|
|
2093
|
+
if (version2 == null)
|
|
2094
|
+
throw Error(
|
|
2095
|
+
`Cannot simplify package.json version "${versionName}" to a valid extension version, "X.Y.Z"`
|
|
2096
|
+
);
|
|
2097
|
+
return version2;
|
|
2098
|
+
}
|
|
2099
|
+
function addEntrypoints(manifest, entrypoints, buildOutput, config) {
|
|
2100
|
+
const entriesByType = entrypoints.reduce((map, entrypoint) => {
|
|
2101
|
+
map[entrypoint.type] ??= [];
|
|
2102
|
+
map[entrypoint.type]?.push(entrypoint);
|
|
2103
|
+
return map;
|
|
2104
|
+
}, {});
|
|
2105
|
+
const background = entriesByType["background"]?.[0];
|
|
2106
|
+
const bookmarks = entriesByType["bookmarks"]?.[0];
|
|
2107
|
+
const contentScripts = entriesByType["content-script"];
|
|
2108
|
+
const devtools = entriesByType["devtools"]?.[0];
|
|
2109
|
+
const history = entriesByType["history"]?.[0];
|
|
2110
|
+
const newtab = entriesByType["newtab"]?.[0];
|
|
2111
|
+
const options = entriesByType["options"]?.[0];
|
|
2112
|
+
const popup = entriesByType["popup"]?.[0];
|
|
2113
|
+
const sandboxes = entriesByType["sandbox"];
|
|
2114
|
+
const sidepanels = entriesByType["sidepanel"];
|
|
2115
|
+
if (background) {
|
|
2116
|
+
const script = getEntrypointBundlePath(background, config.outDir, ".js");
|
|
2117
|
+
if (config.browser === "firefox" && config.manifestVersion === 3) {
|
|
2118
|
+
manifest.background = {
|
|
2119
|
+
type: background.options.type,
|
|
2120
|
+
scripts: [script]
|
|
2121
|
+
};
|
|
2122
|
+
} else if (config.manifestVersion === 3) {
|
|
2123
|
+
manifest.background = {
|
|
2124
|
+
type: background.options.type,
|
|
2125
|
+
service_worker: script
|
|
2126
|
+
};
|
|
2127
|
+
} else {
|
|
2128
|
+
manifest.background = {
|
|
2129
|
+
persistent: background.options.persistent,
|
|
2130
|
+
scripts: [script]
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
if (bookmarks) {
|
|
2135
|
+
if (config.browser === "firefox") {
|
|
2136
|
+
config.logger.warn(
|
|
2137
|
+
"Bookmarks are not supported by Firefox. chrome_url_overrides.bookmarks was not added to the manifest"
|
|
2138
|
+
);
|
|
2139
|
+
} else {
|
|
2140
|
+
manifest.chrome_url_overrides ??= {};
|
|
2141
|
+
manifest.chrome_url_overrides.bookmarks = getEntrypointBundlePath(
|
|
2142
|
+
bookmarks,
|
|
2143
|
+
config.outDir,
|
|
2144
|
+
".html"
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
if (history) {
|
|
2149
|
+
if (config.browser === "firefox") {
|
|
2150
|
+
config.logger.warn(
|
|
2151
|
+
"Bookmarks are not supported by Firefox. chrome_url_overrides.history was not added to the manifest"
|
|
2152
|
+
);
|
|
2153
|
+
} else {
|
|
2154
|
+
manifest.chrome_url_overrides ??= {};
|
|
2155
|
+
manifest.chrome_url_overrides.history = getEntrypointBundlePath(
|
|
2156
|
+
history,
|
|
2157
|
+
config.outDir,
|
|
2158
|
+
".html"
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
if (newtab) {
|
|
2163
|
+
manifest.chrome_url_overrides ??= {};
|
|
2164
|
+
manifest.chrome_url_overrides.newtab = getEntrypointBundlePath(
|
|
2165
|
+
newtab,
|
|
2166
|
+
config.outDir,
|
|
2167
|
+
".html"
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
if (popup) {
|
|
2171
|
+
const default_popup = getEntrypointBundlePath(
|
|
2172
|
+
popup,
|
|
2173
|
+
config.outDir,
|
|
2174
|
+
".html"
|
|
2175
|
+
);
|
|
2176
|
+
const options2 = {};
|
|
2177
|
+
if (popup.options.defaultIcon)
|
|
2178
|
+
options2.default_icon = popup.options.defaultIcon;
|
|
2179
|
+
if (popup.options.defaultTitle)
|
|
2180
|
+
options2.default_title = popup.options.defaultTitle;
|
|
2181
|
+
if (popup.options.browserStyle)
|
|
2182
|
+
options2.browser_style = popup.options.browserStyle;
|
|
2183
|
+
if (manifest.manifest_version === 3) {
|
|
2184
|
+
manifest.action = {
|
|
2185
|
+
...manifest.action ?? {},
|
|
2186
|
+
...options2,
|
|
2187
|
+
default_popup
|
|
2188
|
+
};
|
|
2189
|
+
} else {
|
|
2190
|
+
const key = popup.options.mv2Key ?? "browser_action";
|
|
2191
|
+
manifest[key] = {
|
|
2192
|
+
...manifest[key] ?? {},
|
|
2193
|
+
...options2,
|
|
2194
|
+
default_popup
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
if (devtools) {
|
|
2199
|
+
manifest.devtools_page = getEntrypointBundlePath(
|
|
2200
|
+
devtools,
|
|
2201
|
+
config.outDir,
|
|
2202
|
+
".html"
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
if (options) {
|
|
2206
|
+
const page = getEntrypointBundlePath(options, config.outDir, ".html");
|
|
2207
|
+
manifest.options_ui = {
|
|
2208
|
+
open_in_tab: options.options.openInTab,
|
|
2209
|
+
browser_style: config.browser === "firefox" ? options.options.browserStyle : void 0,
|
|
2210
|
+
chrome_style: config.browser !== "firefox" ? options.options.chromeStyle : void 0,
|
|
2211
|
+
page
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
if (sandboxes?.length) {
|
|
2215
|
+
if (config.browser === "firefox") {
|
|
2216
|
+
config.logger.warn(
|
|
2217
|
+
"Sandboxed pages not supported by Firefox. sandbox.pages was not added to the manifest"
|
|
2218
|
+
);
|
|
2219
|
+
} else {
|
|
2220
|
+
manifest.sandbox = {
|
|
2221
|
+
pages: sandboxes.map(
|
|
2222
|
+
(entry) => getEntrypointBundlePath(entry, config.outDir, ".html")
|
|
2223
|
+
)
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
if (sidepanels?.length) {
|
|
2228
|
+
const defaultSidepanel = sidepanels.find((entry) => entry.name === "sidepanel") ?? sidepanels[0];
|
|
2229
|
+
const page = getEntrypointBundlePath(
|
|
2230
|
+
defaultSidepanel,
|
|
2231
|
+
config.outDir,
|
|
2232
|
+
".html"
|
|
2233
|
+
);
|
|
2234
|
+
if (config.browser === "firefox") {
|
|
2235
|
+
manifest.sidebar_action = {
|
|
2236
|
+
// TODO: Add options to side panel
|
|
2237
|
+
// ...defaultSidepanel.options,
|
|
2238
|
+
default_panel: page
|
|
2239
|
+
};
|
|
2240
|
+
} else if (config.manifestVersion === 3) {
|
|
2241
|
+
manifest.side_panel = {
|
|
2242
|
+
default_path: page
|
|
2243
|
+
};
|
|
2244
|
+
} else {
|
|
2245
|
+
config.logger.warn(
|
|
2246
|
+
"Side panel not supported by Chromium using MV2. side_panel.default_path was not added to the manifest"
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
if (contentScripts?.length) {
|
|
2251
|
+
const cssMap = getContentScriptsCssMap(buildOutput, contentScripts);
|
|
2252
|
+
if (config.command === "serve" && config.manifestVersion === 3) {
|
|
2253
|
+
const hostPermissions = new Set(manifest.host_permissions ?? []);
|
|
2254
|
+
contentScripts.forEach((script) => {
|
|
2255
|
+
const matches = resolvePerBrowserOption(
|
|
2256
|
+
script.options.matches,
|
|
2257
|
+
config.browser
|
|
2258
|
+
);
|
|
2259
|
+
matches.forEach((matchPattern) => {
|
|
2260
|
+
hostPermissions.add(matchPattern);
|
|
2261
|
+
});
|
|
2262
|
+
});
|
|
2263
|
+
hostPermissions.forEach(
|
|
2264
|
+
(permission) => addHostPermission(manifest, permission)
|
|
2265
|
+
);
|
|
2266
|
+
} else {
|
|
2267
|
+
const hashToEntrypointsMap = contentScripts.reduce((map, script) => {
|
|
2268
|
+
const hash = hashContentScriptOptions(script.options, config);
|
|
2269
|
+
if (map.has(hash))
|
|
2270
|
+
map.get(hash)?.push(script);
|
|
2271
|
+
else
|
|
2272
|
+
map.set(hash, [script]);
|
|
2273
|
+
return map;
|
|
2274
|
+
}, /* @__PURE__ */ new Map());
|
|
2275
|
+
const newContentScripts = Array.from(hashToEntrypointsMap.entries()).map(
|
|
2276
|
+
([, scripts]) => ({
|
|
2277
|
+
...mapWxtOptionsToContentScript(scripts[0].options, config),
|
|
2278
|
+
css: getContentScriptCssFiles(scripts, cssMap),
|
|
2279
|
+
js: scripts.map(
|
|
2280
|
+
(entry) => getEntrypointBundlePath(entry, config.outDir, ".js")
|
|
2281
|
+
)
|
|
2282
|
+
})
|
|
2283
|
+
);
|
|
2284
|
+
if (newContentScripts.length >= 0) {
|
|
2285
|
+
manifest.content_scripts ??= [];
|
|
2286
|
+
manifest.content_scripts.push(...newContentScripts);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
const contentScriptCssResources = getContentScriptCssWebAccessibleResources(
|
|
2290
|
+
config,
|
|
2291
|
+
contentScripts,
|
|
2292
|
+
cssMap
|
|
2293
|
+
);
|
|
2294
|
+
if (contentScriptCssResources.length > 0) {
|
|
2295
|
+
manifest.web_accessible_resources ??= [];
|
|
2296
|
+
manifest.web_accessible_resources.push(...contentScriptCssResources);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
function discoverIcons(buildOutput) {
|
|
2301
|
+
const icons = [];
|
|
2302
|
+
const iconRegex = [
|
|
2303
|
+
/^icon-([0-9]+)\.png$/,
|
|
2304
|
+
// icon-16.png
|
|
2305
|
+
/^icon-([0-9]+)x[0-9]+\.png$/,
|
|
2306
|
+
// icon-16x16.png
|
|
2307
|
+
/^icon@([0-9]+)w\.png$/,
|
|
2308
|
+
// icon@16w.png
|
|
2309
|
+
/^icon@([0-9]+)h\.png$/,
|
|
2310
|
+
// icon@16h.png
|
|
2311
|
+
/^icon@([0-9]+)\.png$/,
|
|
2312
|
+
// icon@16.png
|
|
2313
|
+
/^icons?[\/\\]([0-9]+)\.png$/,
|
|
2314
|
+
// icon/16.png | icons/16.png
|
|
2315
|
+
/^icons?[\/\\]([0-9]+)x[0-9]+\.png$/
|
|
2316
|
+
// icon/16x16.png | icons/16x16.png
|
|
2317
|
+
];
|
|
2318
|
+
buildOutput.publicAssets.forEach((asset) => {
|
|
2319
|
+
let size;
|
|
2320
|
+
for (const regex of iconRegex) {
|
|
2321
|
+
const match = asset.fileName.match(regex);
|
|
2322
|
+
if (match?.[1] != null) {
|
|
2323
|
+
size = match[1];
|
|
2324
|
+
break;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
if (size == null)
|
|
2328
|
+
return;
|
|
2329
|
+
icons.push([size, normalizePath(asset.fileName)]);
|
|
2330
|
+
});
|
|
2331
|
+
return icons.length > 0 ? Object.fromEntries(icons) : void 0;
|
|
2332
|
+
}
|
|
2333
|
+
function addDevModeCsp(manifest, config) {
|
|
2334
|
+
const permission = `http://${config.server?.hostname ?? ""}/*`;
|
|
2335
|
+
const allowedCsp = config.server?.origin ?? "http://localhost:*";
|
|
2336
|
+
if (manifest.manifest_version === 3) {
|
|
2337
|
+
addHostPermission(manifest, permission);
|
|
2338
|
+
} else {
|
|
2339
|
+
addPermission(manifest, permission);
|
|
2340
|
+
}
|
|
2341
|
+
const csp = new ContentSecurityPolicy(
|
|
2342
|
+
manifest.manifest_version === 3 ? (
|
|
2343
|
+
// @ts-expect-error: extension_pages is not typed
|
|
2344
|
+
manifest.content_security_policy?.extension_pages ?? "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
|
|
2345
|
+
) : manifest.content_security_policy ?? "script-src 'self'; object-src 'self';"
|
|
2346
|
+
// default CSP for MV2
|
|
2347
|
+
);
|
|
2348
|
+
if (config.server)
|
|
2349
|
+
csp.add("script-src", allowedCsp);
|
|
2350
|
+
if (manifest.manifest_version === 3) {
|
|
2351
|
+
manifest.content_security_policy ??= {};
|
|
2352
|
+
manifest.content_security_policy.extension_pages = csp.toString();
|
|
2353
|
+
} else {
|
|
2354
|
+
manifest.content_security_policy = csp.toString();
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
function addDevModePermissions(manifest, config) {
|
|
2358
|
+
addPermission(manifest, "tabs");
|
|
2359
|
+
if (config.manifestVersion === 3)
|
|
2360
|
+
addPermission(manifest, "scripting");
|
|
2361
|
+
}
|
|
2362
|
+
function getContentScriptCssFiles(contentScripts, contentScriptCssMap) {
|
|
2363
|
+
const css = [];
|
|
2364
|
+
contentScripts.forEach((script) => {
|
|
2365
|
+
if (script.options.cssInjectionMode === "manual" || script.options.cssInjectionMode === "ui")
|
|
2366
|
+
return;
|
|
2367
|
+
const cssFile = contentScriptCssMap[script.name];
|
|
2368
|
+
if (cssFile == null)
|
|
2369
|
+
return;
|
|
2370
|
+
if (cssFile)
|
|
2371
|
+
css.push(cssFile);
|
|
2372
|
+
});
|
|
2373
|
+
if (css.length > 0)
|
|
2374
|
+
return css;
|
|
2375
|
+
return void 0;
|
|
2376
|
+
}
|
|
2377
|
+
function getContentScriptCssWebAccessibleResources(config, contentScripts, contentScriptCssMap) {
|
|
2378
|
+
const resources = [];
|
|
2379
|
+
contentScripts.forEach((script) => {
|
|
2380
|
+
if (script.options.cssInjectionMode !== "ui")
|
|
2381
|
+
return;
|
|
2382
|
+
const cssFile = contentScriptCssMap[script.name];
|
|
2383
|
+
if (cssFile == null)
|
|
2384
|
+
return;
|
|
2385
|
+
if (config.manifestVersion === 2) {
|
|
2386
|
+
resources.push(cssFile);
|
|
2387
|
+
} else {
|
|
2388
|
+
resources.push({
|
|
2389
|
+
resources: [cssFile],
|
|
2390
|
+
matches: script.options.matches
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
});
|
|
2394
|
+
return resources;
|
|
2395
|
+
}
|
|
2396
|
+
function getContentScriptsCssMap(buildOutput, scripts) {
|
|
2397
|
+
const map = {};
|
|
2398
|
+
const allChunks = buildOutput.steps.flatMap((step) => step.chunks);
|
|
2399
|
+
scripts.forEach((script) => {
|
|
2400
|
+
const relatedCss = allChunks.find(
|
|
2401
|
+
(chunk) => chunk.fileName === `content-scripts/${script.name}.css`
|
|
2402
|
+
);
|
|
2403
|
+
if (relatedCss != null)
|
|
2404
|
+
map[script.name] = relatedCss.fileName;
|
|
2405
|
+
});
|
|
2406
|
+
return map;
|
|
2407
|
+
}
|
|
2408
|
+
function addPermission(manifest, permission) {
|
|
2409
|
+
manifest.permissions ??= [];
|
|
2410
|
+
if (manifest.permissions.includes(permission))
|
|
2411
|
+
return;
|
|
2412
|
+
manifest.permissions.push(permission);
|
|
2413
|
+
}
|
|
2414
|
+
function addHostPermission(manifest, hostPermission) {
|
|
2415
|
+
manifest.host_permissions ??= [];
|
|
2416
|
+
if (manifest.host_permissions.includes(hostPermission))
|
|
2417
|
+
return;
|
|
2418
|
+
manifest.host_permissions.push(hostPermission);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// src/core/utils/building/rebuild.ts
|
|
2422
|
+
async function rebuild(config, entrypointGroups, existingOutput = {
|
|
2423
|
+
steps: [],
|
|
2424
|
+
publicAssets: []
|
|
2425
|
+
}) {
|
|
2426
|
+
const { default: ora } = await import("ora");
|
|
2427
|
+
const spinner = ora(`Preparing...`).start();
|
|
2428
|
+
const allEntrypoints = await findEntrypoints(config);
|
|
2429
|
+
await generateTypesDir(allEntrypoints, config).catch((err) => {
|
|
2430
|
+
config.logger.warn("Failed to update .wxt directory:", err);
|
|
2431
|
+
if (config.command === "build")
|
|
2432
|
+
throw err;
|
|
2433
|
+
});
|
|
2434
|
+
const newOutput = await buildEntrypoints(entrypointGroups, config, spinner);
|
|
2435
|
+
const mergedOutput = {
|
|
2436
|
+
steps: [...existingOutput.steps, ...newOutput.steps],
|
|
2437
|
+
publicAssets: [...existingOutput.publicAssets, ...newOutput.publicAssets]
|
|
2438
|
+
};
|
|
2439
|
+
const newManifest = await generateMainfest(
|
|
2440
|
+
allEntrypoints,
|
|
2441
|
+
mergedOutput,
|
|
2442
|
+
config
|
|
2443
|
+
);
|
|
2444
|
+
const finalOutput = {
|
|
2445
|
+
manifest: newManifest,
|
|
2446
|
+
...newOutput
|
|
2447
|
+
};
|
|
2448
|
+
await writeManifest(newManifest, finalOutput, config);
|
|
2449
|
+
spinner.clear().stop();
|
|
2450
|
+
return {
|
|
2451
|
+
output: {
|
|
2452
|
+
manifest: newManifest,
|
|
2453
|
+
steps: [...existingOutput.steps, ...finalOutput.steps],
|
|
2454
|
+
publicAssets: [
|
|
2455
|
+
...existingOutput.publicAssets,
|
|
2456
|
+
...finalOutput.publicAssets
|
|
2457
|
+
]
|
|
2458
|
+
},
|
|
2459
|
+
manifest: newManifest
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// src/core/utils/building/internal-build.ts
|
|
2464
|
+
async function internalBuild(config) {
|
|
2465
|
+
const verb = config.command === "serve" ? "Pre-rendering" : "Building";
|
|
2466
|
+
const target = `${config.browser}-mv${config.manifestVersion}`;
|
|
2467
|
+
config.logger.info(
|
|
2468
|
+
`${verb} ${pc4.cyan(target)} for ${pc4.cyan(config.mode)} with ${pc4.green(
|
|
2469
|
+
`${config.builder.name} ${config.builder.version}`
|
|
2470
|
+
)}`
|
|
2471
|
+
);
|
|
2472
|
+
const startTime = Date.now();
|
|
2473
|
+
await fs12.rm(config.outDir, { recursive: true, force: true });
|
|
2474
|
+
await fs12.ensureDir(config.outDir);
|
|
2475
|
+
const entrypoints = await findEntrypoints(config);
|
|
2476
|
+
config.logger.debug("Detected entrypoints:", entrypoints);
|
|
2477
|
+
const groups = groupEntrypoints(entrypoints);
|
|
2478
|
+
const { output } = await rebuild(config, groups, void 0);
|
|
2479
|
+
await printBuildSummary(
|
|
2480
|
+
config.logger.success,
|
|
2481
|
+
`Built extension in ${formatDuration(Date.now() - startTime)}`,
|
|
2482
|
+
output,
|
|
2483
|
+
config
|
|
2484
|
+
);
|
|
2485
|
+
if (config.analysis.enabled) {
|
|
2486
|
+
await combineAnalysisStats(config);
|
|
2487
|
+
config.logger.info(
|
|
2488
|
+
`Analysis complete:
|
|
2489
|
+
${pc4.gray("\u2514\u2500")} ${pc4.yellow("stats.html")}`
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
return output;
|
|
2493
|
+
}
|
|
2494
|
+
async function combineAnalysisStats(config) {
|
|
2495
|
+
const { execaCommand } = await import("./execa-4F7CCWCA.js");
|
|
2496
|
+
const unixFiles = await glob3(`stats-*.json`, {
|
|
2497
|
+
cwd: config.outDir,
|
|
2498
|
+
absolute: true
|
|
2499
|
+
});
|
|
2500
|
+
const absolutePaths = unixFiles.map(unnormalizePath);
|
|
2501
|
+
await execaCommand(
|
|
2502
|
+
`rollup-plugin-visualizer ${absolutePaths.join(" ")} --template ${config.analysis.template}`,
|
|
2503
|
+
{ cwd: config.root, stdio: "inherit" }
|
|
2504
|
+
);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
// src/core/build.ts
|
|
2508
|
+
async function build(config) {
|
|
2509
|
+
const internalConfig = await getInternalConfig(config ?? {}, "build");
|
|
2510
|
+
return await internalBuild(internalConfig);
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
// src/core/clean.ts
|
|
2514
|
+
import path5 from "node:path";
|
|
2515
|
+
import glob4 from "fast-glob";
|
|
2516
|
+
import fs13 from "fs-extra";
|
|
2517
|
+
import { consola as consola3 } from "consola";
|
|
2518
|
+
import pc5 from "picocolors";
|
|
2519
|
+
async function clean(root = process.cwd()) {
|
|
2520
|
+
consola3.info("Cleaning Project");
|
|
2521
|
+
const tempDirs = [
|
|
2522
|
+
"node_modules/.vite",
|
|
2523
|
+
"node_modules/.cache",
|
|
2524
|
+
"**/.wxt",
|
|
2525
|
+
".output/*"
|
|
2526
|
+
];
|
|
2527
|
+
consola3.debug("Looking for:", tempDirs.map(pc5.cyan).join(", "));
|
|
2528
|
+
const directories = await glob4(tempDirs, {
|
|
2529
|
+
cwd: path5.resolve(root),
|
|
2530
|
+
absolute: true,
|
|
2531
|
+
onlyDirectories: true,
|
|
2532
|
+
deep: 2
|
|
2533
|
+
});
|
|
2534
|
+
if (directories.length === 0) {
|
|
2535
|
+
consola3.debug("No generated files found.");
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
consola3.debug(
|
|
2539
|
+
"Found:",
|
|
2540
|
+
directories.map((dir) => pc5.cyan(path5.relative(root, dir))).join(", ")
|
|
2541
|
+
);
|
|
2542
|
+
for (const directory of directories) {
|
|
2543
|
+
await fs13.rm(directory, { force: true, recursive: true });
|
|
2544
|
+
consola3.debug("Deleted " + pc5.cyan(path5.relative(root, directory)));
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// src/core/runners/wsl.ts
|
|
2549
|
+
import { relative as relative5 } from "node:path";
|
|
2550
|
+
function createWslRunner() {
|
|
2551
|
+
return {
|
|
2552
|
+
async openBrowser(config) {
|
|
2553
|
+
config.logger.warn(
|
|
2554
|
+
`Cannot open browser when using WSL. Load "${relative5(
|
|
2555
|
+
process.cwd(),
|
|
2556
|
+
config.outDir
|
|
2557
|
+
)}" as an unpacked extension manually`
|
|
2558
|
+
);
|
|
2559
|
+
},
|
|
2560
|
+
async closeBrowser() {
|
|
2561
|
+
}
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// src/core/runners/web-ext.ts
|
|
2566
|
+
function createWebExtRunner() {
|
|
2567
|
+
let runner;
|
|
2568
|
+
return {
|
|
2569
|
+
async openBrowser(config) {
|
|
2570
|
+
config.logger.info("Opening browser...");
|
|
2571
|
+
if (config.browser === "firefox" && config.manifestVersion === 3) {
|
|
2572
|
+
throw Error(
|
|
2573
|
+
"Dev mode does not support Firefox MV3. For alternatives, see https://github.com/wxt-dev/wxt/issues/230#issuecomment-1806881653"
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2576
|
+
const webExtLogger = await import("web-ext-run/util/logger");
|
|
2577
|
+
webExtLogger.consoleStream.write = ({ level, msg, name }) => {
|
|
2578
|
+
if (level >= ERROR_LOG_LEVEL)
|
|
2579
|
+
config.logger.error(name, msg);
|
|
2580
|
+
if (level >= WARN_LOG_LEVEL)
|
|
2581
|
+
config.logger.warn(msg);
|
|
2582
|
+
};
|
|
2583
|
+
const wxtUserConfig = config.runnerConfig.config;
|
|
2584
|
+
const userConfig = {
|
|
2585
|
+
console: wxtUserConfig?.openConsole,
|
|
2586
|
+
devtools: wxtUserConfig?.openDevtools,
|
|
2587
|
+
startUrl: wxtUserConfig?.startUrls,
|
|
2588
|
+
...config.browser === "firefox" ? {
|
|
2589
|
+
firefox: wxtUserConfig?.binaries?.firefox,
|
|
2590
|
+
firefoxProfile: wxtUserConfig?.firefoxProfile,
|
|
2591
|
+
prefs: wxtUserConfig?.firefoxPrefs,
|
|
2592
|
+
args: wxtUserConfig?.firefoxArgs
|
|
2593
|
+
} : {
|
|
2594
|
+
chromiumBinary: wxtUserConfig?.binaries?.[config.browser],
|
|
2595
|
+
chromiumProfile: wxtUserConfig?.chromiumProfile,
|
|
2596
|
+
args: wxtUserConfig?.chromiumArgs
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
const finalConfig = {
|
|
2600
|
+
...userConfig,
|
|
2601
|
+
target: config.browser === "firefox" ? "firefox-desktop" : "chromium",
|
|
2602
|
+
sourceDir: config.outDir,
|
|
2603
|
+
// WXT handles reloads, so disable auto-reload behaviors in web-ext
|
|
2604
|
+
noReload: true,
|
|
2605
|
+
noInput: true
|
|
2606
|
+
};
|
|
2607
|
+
const options = {
|
|
2608
|
+
// Don't call `process.exit(0)` after starting web-ext
|
|
2609
|
+
shouldExitProgram: false
|
|
2610
|
+
};
|
|
2611
|
+
config.logger.debug("web-ext config:", finalConfig);
|
|
2612
|
+
config.logger.debug("web-ext options:", options);
|
|
2613
|
+
const webExt = await import("web-ext-run");
|
|
2614
|
+
runner = await webExt.default.cmd.run(finalConfig, options);
|
|
2615
|
+
config.logger.success("Opened!");
|
|
2616
|
+
},
|
|
2617
|
+
async closeBrowser() {
|
|
2618
|
+
return await runner?.exit();
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
var WARN_LOG_LEVEL = 40;
|
|
2623
|
+
var ERROR_LOG_LEVEL = 50;
|
|
2624
|
+
|
|
2625
|
+
// src/core/runners/safari.ts
|
|
2626
|
+
import { relative as relative6 } from "node:path";
|
|
2627
|
+
function createSafariRunner() {
|
|
2628
|
+
return {
|
|
2629
|
+
async openBrowser(config) {
|
|
2630
|
+
config.logger.warn(
|
|
2631
|
+
`Cannot Safari using web-ext. Load "${relative6(
|
|
2632
|
+
process.cwd(),
|
|
2633
|
+
config.outDir
|
|
2634
|
+
)}" as an unpacked extension manually`
|
|
2635
|
+
);
|
|
2636
|
+
},
|
|
2637
|
+
async closeBrowser() {
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// src/core/runners/manual.ts
|
|
2643
|
+
import { relative as relative7 } from "node:path";
|
|
2644
|
+
function createManualRunner() {
|
|
2645
|
+
return {
|
|
2646
|
+
async openBrowser(config) {
|
|
2647
|
+
config.logger.info(
|
|
2648
|
+
`Load "${relative7(
|
|
2649
|
+
process.cwd(),
|
|
2650
|
+
config.outDir
|
|
2651
|
+
)}" as an unpacked extension manually`
|
|
2652
|
+
);
|
|
2653
|
+
},
|
|
2654
|
+
async closeBrowser() {
|
|
2655
|
+
}
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// src/core/utils/wsl.ts
|
|
2660
|
+
async function isWsl() {
|
|
2661
|
+
const { default: isWsl2 } = await import("is-wsl");
|
|
2662
|
+
return isWsl2;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
// src/core/runners/index.ts
|
|
2666
|
+
async function createExtensionRunner(config) {
|
|
2667
|
+
if (config.browser === "safari")
|
|
2668
|
+
return createSafariRunner();
|
|
2669
|
+
if (await isWsl())
|
|
2670
|
+
return createWslRunner();
|
|
2671
|
+
if (config.runnerConfig.config?.disabled)
|
|
2672
|
+
return createManualRunner();
|
|
2673
|
+
return createWebExtRunner();
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
// src/core/create-server.ts
|
|
2677
|
+
import { consola as consola4 } from "consola";
|
|
2678
|
+
import { Mutex } from "async-mutex";
|
|
2679
|
+
import pc6 from "picocolors";
|
|
2680
|
+
import { relative as relative8 } from "node:path";
|
|
2681
|
+
async function createServer(inlineConfig) {
|
|
2682
|
+
const port = await getPort();
|
|
2683
|
+
const hostname = "localhost";
|
|
2684
|
+
const origin = `http://${hostname}:${port}`;
|
|
2685
|
+
const serverInfo = {
|
|
2686
|
+
port,
|
|
2687
|
+
hostname,
|
|
2688
|
+
origin
|
|
2689
|
+
};
|
|
2690
|
+
const server = {
|
|
2691
|
+
...serverInfo,
|
|
2692
|
+
watcher: void 0,
|
|
2693
|
+
// Filled out later down below
|
|
2694
|
+
ws: void 0,
|
|
2695
|
+
// Filled out later down below
|
|
2696
|
+
currentOutput: void 0,
|
|
2697
|
+
// Filled out later down below
|
|
2698
|
+
async start() {
|
|
2699
|
+
await builderServer.listen();
|
|
2700
|
+
config.logger.success(`Started dev server @ ${serverInfo.origin}`);
|
|
2701
|
+
server.currentOutput = await internalBuild(config);
|
|
2702
|
+
await runner.openBrowser(config);
|
|
2703
|
+
},
|
|
2704
|
+
transformHtml(url, html, originalUrl) {
|
|
2705
|
+
return builderServer.transformHtml(url, html, originalUrl);
|
|
2706
|
+
},
|
|
2707
|
+
reloadContentScript(contentScript) {
|
|
2708
|
+
server.ws.send("wxt:reload-content-script", contentScript);
|
|
2709
|
+
},
|
|
2710
|
+
reloadPage(path7) {
|
|
2711
|
+
server.ws.send("wxt:reload-page", path7);
|
|
2712
|
+
},
|
|
2713
|
+
reloadExtension() {
|
|
2714
|
+
server.ws.send("wxt:reload-extension");
|
|
2715
|
+
}
|
|
2716
|
+
};
|
|
2717
|
+
const getLatestConfig = () => getInternalConfig(inlineConfig ?? {}, "serve", server);
|
|
2718
|
+
let config = await getLatestConfig();
|
|
2719
|
+
const [runner, builderServer] = await Promise.all([
|
|
2720
|
+
createExtensionRunner(config),
|
|
2721
|
+
config.builder.createServer(server)
|
|
2722
|
+
]);
|
|
2723
|
+
server.watcher = builderServer.watcher;
|
|
2724
|
+
server.ws = builderServer.ws;
|
|
2725
|
+
server.ws.on("wxt:background-initialized", () => {
|
|
2726
|
+
reloadContentScripts(server.currentOutput.steps, config, server);
|
|
2727
|
+
});
|
|
2728
|
+
const reloadOnChange = createFileReloader({
|
|
2729
|
+
server,
|
|
2730
|
+
getLatestConfig,
|
|
2731
|
+
updateConfig(newConfig) {
|
|
2732
|
+
config = newConfig;
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
server.watcher.on("all", reloadOnChange);
|
|
2736
|
+
return server;
|
|
2737
|
+
}
|
|
2738
|
+
async function getPort() {
|
|
2739
|
+
const { default: getPort2, portNumbers } = await import("get-port");
|
|
2740
|
+
return await getPort2({ port: portNumbers(3e3, 3010) });
|
|
2741
|
+
}
|
|
2742
|
+
function createFileReloader(options) {
|
|
2743
|
+
const { server, getLatestConfig, updateConfig } = options;
|
|
2744
|
+
const fileChangedMutex = new Mutex();
|
|
2745
|
+
const changeQueue = [];
|
|
2746
|
+
return async (event, path7) => {
|
|
2747
|
+
const config = await getLatestConfig();
|
|
2748
|
+
updateConfig(config);
|
|
2749
|
+
if (path7.startsWith(config.outBaseDir))
|
|
2750
|
+
return;
|
|
2751
|
+
changeQueue.push([event, path7]);
|
|
2752
|
+
await fileChangedMutex.runExclusive(async () => {
|
|
2753
|
+
const fileChanges = changeQueue.splice(0, changeQueue.length);
|
|
2754
|
+
if (fileChanges.length === 0)
|
|
2755
|
+
return;
|
|
2756
|
+
const changes = detectDevChanges(fileChanges, server.currentOutput);
|
|
2757
|
+
if (changes.type === "no-change")
|
|
2758
|
+
return;
|
|
2759
|
+
config.logger.info(
|
|
2760
|
+
`Changed: ${Array.from(new Set(fileChanges.map((change) => change[1]))).map((file) => pc6.dim(relative8(config.root, file))).join(", ")}`
|
|
2761
|
+
);
|
|
2762
|
+
const rebuiltNames = changes.rebuildGroups.flat().map((entry) => {
|
|
2763
|
+
return pc6.cyan(
|
|
2764
|
+
relative8(config.outDir, getEntrypointOutputFile(entry, ""))
|
|
2765
|
+
);
|
|
2766
|
+
}).join(pc6.dim(", "));
|
|
2767
|
+
const { output: newOutput } = await rebuild(
|
|
2768
|
+
config,
|
|
2769
|
+
// TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
|
|
2770
|
+
changes.rebuildGroups,
|
|
2771
|
+
changes.cachedOutput
|
|
2772
|
+
);
|
|
2773
|
+
server.currentOutput = newOutput;
|
|
2774
|
+
switch (changes.type) {
|
|
2775
|
+
case "extension-reload":
|
|
2776
|
+
server.reloadExtension();
|
|
2777
|
+
break;
|
|
2778
|
+
case "html-reload":
|
|
2779
|
+
reloadHtmlPages(changes.rebuildGroups, server, config);
|
|
2780
|
+
break;
|
|
2781
|
+
case "content-script-reload":
|
|
2782
|
+
reloadContentScripts(changes.changedSteps, config, server);
|
|
2783
|
+
break;
|
|
2784
|
+
}
|
|
2785
|
+
consola4.success(`Reloaded: ${rebuiltNames}`);
|
|
2786
|
+
});
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
function reloadContentScripts(steps, config, server) {
|
|
2790
|
+
if (config.manifestVersion === 3) {
|
|
2791
|
+
steps.forEach((step) => {
|
|
2792
|
+
const entry = step.entrypoints;
|
|
2793
|
+
if (Array.isArray(entry) || entry.type !== "content-script")
|
|
2794
|
+
return;
|
|
2795
|
+
const js = [getEntrypointBundlePath(entry, config.outDir, ".js")];
|
|
2796
|
+
const cssMap = getContentScriptsCssMap(server.currentOutput, [entry]);
|
|
2797
|
+
const css = getContentScriptCssFiles([entry], cssMap);
|
|
2798
|
+
server.reloadContentScript({
|
|
2799
|
+
allFrames: resolvePerBrowserOption(
|
|
2800
|
+
entry.options.allFrames,
|
|
2801
|
+
config.browser
|
|
2802
|
+
),
|
|
2803
|
+
excludeMatches: resolvePerBrowserOption(
|
|
2804
|
+
entry.options.excludeMatches,
|
|
2805
|
+
config.browser
|
|
2806
|
+
),
|
|
2807
|
+
matches: resolvePerBrowserOption(entry.options.matches, config.browser),
|
|
2808
|
+
runAt: resolvePerBrowserOption(entry.options.runAt, config.browser),
|
|
2809
|
+
// @ts-expect-error: Chrome accepts this, not typed in webextension-polyfill (https://developer.chrome.com/docs/extensions/reference/scripting/#type-RegisteredContentScript)
|
|
2810
|
+
world: resolvePerBrowserOption(entry.options.world, config.browser),
|
|
2811
|
+
js,
|
|
2812
|
+
css
|
|
2813
|
+
});
|
|
2814
|
+
});
|
|
2815
|
+
} else {
|
|
2816
|
+
server.reloadExtension();
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
function reloadHtmlPages(groups, server, config) {
|
|
2820
|
+
groups.flat().forEach((entry) => {
|
|
2821
|
+
const path7 = getEntrypointBundlePath(entry, config.outDir, ".html");
|
|
2822
|
+
server.reloadPage(path7);
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
// src/core/initialize.ts
|
|
2827
|
+
import prompts from "prompts";
|
|
2828
|
+
import { consola as consola5 } from "consola";
|
|
2829
|
+
import { downloadTemplate } from "giget";
|
|
2830
|
+
import fs14 from "fs-extra";
|
|
2831
|
+
import path6 from "node:path";
|
|
2832
|
+
import pc7 from "picocolors";
|
|
2833
|
+
async function initialize(options) {
|
|
2834
|
+
consola5.info("Initalizing new project");
|
|
2835
|
+
const templates = await listTemplates();
|
|
2836
|
+
const defaultTemplate = templates.find(
|
|
2837
|
+
(template) => template.name === options.template?.toLowerCase().trim()
|
|
2838
|
+
);
|
|
2839
|
+
const input = await prompts(
|
|
2840
|
+
[
|
|
2841
|
+
{
|
|
2842
|
+
name: "directory",
|
|
2843
|
+
type: () => options.directory == null ? "text" : void 0,
|
|
2844
|
+
message: "Project Directory",
|
|
2845
|
+
initial: options.directory
|
|
2846
|
+
},
|
|
2847
|
+
{
|
|
2848
|
+
name: "template",
|
|
2849
|
+
type: () => defaultTemplate == null ? "select" : void 0,
|
|
2850
|
+
message: "Choose a template",
|
|
2851
|
+
choices: templates.map((template) => ({
|
|
2852
|
+
title: TEMPLATE_COLORS[template.name]?.(template.name) ?? template.name,
|
|
2853
|
+
value: template
|
|
2854
|
+
}))
|
|
2855
|
+
},
|
|
2856
|
+
{
|
|
2857
|
+
name: "packageManager",
|
|
2858
|
+
type: () => options.packageManager == null ? "select" : void 0,
|
|
2859
|
+
message: "Package Manager",
|
|
2860
|
+
choices: [
|
|
2861
|
+
{ title: pc7.red("npm"), value: "npm" },
|
|
2862
|
+
{ title: pc7.yellow("pnpm"), value: "pnpm" },
|
|
2863
|
+
{ title: pc7.cyan("yarn"), value: "yarn" },
|
|
2864
|
+
{
|
|
2865
|
+
title: `${pc7.magenta("bun")}${pc7.gray(" (experimental)")}`,
|
|
2866
|
+
value: "bun"
|
|
2867
|
+
}
|
|
2868
|
+
]
|
|
2869
|
+
}
|
|
2870
|
+
],
|
|
2871
|
+
{
|
|
2872
|
+
onCancel: () => process.exit(1)
|
|
2873
|
+
}
|
|
2874
|
+
);
|
|
2875
|
+
input.directory ??= options.directory;
|
|
2876
|
+
input.template ??= defaultTemplate;
|
|
2877
|
+
input.packageManager ??= options.packageManager;
|
|
2878
|
+
await cloneProject(input);
|
|
2879
|
+
const cdPath = path6.relative(process.cwd(), path6.resolve(input.directory));
|
|
2880
|
+
console.log();
|
|
2881
|
+
consola5.log(
|
|
2882
|
+
`\u2728 WXT project created with the ${TEMPLATE_COLORS[input.template.name]?.(input.template.name) ?? input.template.name} template.`
|
|
2883
|
+
);
|
|
2884
|
+
console.log();
|
|
2885
|
+
consola5.log("Next steps:");
|
|
2886
|
+
let step = 0;
|
|
2887
|
+
if (cdPath !== "")
|
|
2888
|
+
consola5.log(` ${++step}.`, pc7.cyan(`cd ${cdPath}`));
|
|
2889
|
+
consola5.log(` ${++step}.`, pc7.cyan(`${input.packageManager} install`));
|
|
2890
|
+
console.log();
|
|
2891
|
+
}
|
|
2892
|
+
async function listTemplates() {
|
|
2893
|
+
try {
|
|
2894
|
+
const res = await fetch(
|
|
2895
|
+
"https://api.github.com/repos/wxt-dev/wxt/contents/templates",
|
|
2896
|
+
{
|
|
2897
|
+
headers: {
|
|
2898
|
+
Accept: "application/vnd.github+json",
|
|
2899
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
);
|
|
2903
|
+
if (res.status >= 300)
|
|
2904
|
+
throw Error(`Request failed with status ${res.status} ${res.statusText}`);
|
|
2905
|
+
const data = await res.json();
|
|
2906
|
+
return data.filter((item) => item.type === "dir").map((item) => ({ name: item.name, path: item.path })).sort((l, r) => {
|
|
2907
|
+
const lWeight = TEMPLATE_SORT_WEIGHT[l.name] ?? Number.MAX_SAFE_INTEGER;
|
|
2908
|
+
const rWeight = TEMPLATE_SORT_WEIGHT[r.name] ?? Number.MAX_SAFE_INTEGER;
|
|
2909
|
+
const diff = lWeight - rWeight;
|
|
2910
|
+
if (diff !== 0)
|
|
2911
|
+
return diff;
|
|
2912
|
+
return l.name.localeCompare(r.name);
|
|
2913
|
+
});
|
|
2914
|
+
} catch (err) {
|
|
2915
|
+
throw Error(`Cannot load templates: ${JSON.stringify(err, null, 2)}`);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
async function cloneProject({
|
|
2919
|
+
directory,
|
|
2920
|
+
template,
|
|
2921
|
+
packageManager
|
|
2922
|
+
}) {
|
|
2923
|
+
const { default: ora } = await import("ora");
|
|
2924
|
+
const spinner = ora("Downloading template").start();
|
|
2925
|
+
try {
|
|
2926
|
+
await downloadTemplate(`gh:wxt-dev/wxt/${template.path}`, {
|
|
2927
|
+
dir: directory,
|
|
2928
|
+
force: true
|
|
2929
|
+
});
|
|
2930
|
+
await fs14.move(
|
|
2931
|
+
path6.join(directory, "_gitignore"),
|
|
2932
|
+
path6.join(directory, ".gitignore")
|
|
2933
|
+
).catch(
|
|
2934
|
+
(err) => consola5.warn("Failed to move _gitignore to .gitignore:", err)
|
|
2935
|
+
);
|
|
2936
|
+
if (packageManager === "pnpm") {
|
|
2937
|
+
await fs14.writeFile(
|
|
2938
|
+
path6.join(directory, ".npmrc"),
|
|
2939
|
+
"shamefully-hoist=true\n"
|
|
2940
|
+
);
|
|
2941
|
+
}
|
|
2942
|
+
spinner.succeed();
|
|
2943
|
+
} catch (err) {
|
|
2944
|
+
spinner.fail();
|
|
2945
|
+
throw Error(`Failed to setup new project: ${JSON.stringify(err, null, 2)}`);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
var TEMPLATE_COLORS = {
|
|
2949
|
+
vanilla: pc7.blue,
|
|
2950
|
+
vue: pc7.green,
|
|
2951
|
+
react: pc7.cyan,
|
|
2952
|
+
svelte: pc7.red,
|
|
2953
|
+
solid: pc7.blue
|
|
2954
|
+
};
|
|
2955
|
+
var TEMPLATE_SORT_WEIGHT = {
|
|
2956
|
+
vanilla: 0,
|
|
2957
|
+
vue: 1,
|
|
2958
|
+
react: 2
|
|
2959
|
+
};
|
|
2960
|
+
|
|
2961
|
+
// src/core/prepare.ts
|
|
2962
|
+
async function prepare(config) {
|
|
2963
|
+
const internalConfig = await getInternalConfig(config, "build");
|
|
2964
|
+
internalConfig.logger.info("Generating types...");
|
|
2965
|
+
const entrypoints = await findEntrypoints(internalConfig);
|
|
2966
|
+
await generateTypesDir(entrypoints, internalConfig);
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
// src/core/zip.ts
|
|
2970
|
+
import zipdir from "zip-dir";
|
|
2971
|
+
import { dirname as dirname5, relative as relative9, resolve as resolve13 } from "node:path";
|
|
2972
|
+
import fs15 from "fs-extra";
|
|
2973
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
2974
|
+
async function zip(config) {
|
|
2975
|
+
const internalConfig = await getInternalConfig(config ?? {}, "build");
|
|
2976
|
+
const output = await internalBuild(internalConfig);
|
|
2977
|
+
const start = Date.now();
|
|
2978
|
+
internalConfig.logger.info("Zipping extension...");
|
|
2979
|
+
const zipFiles = [];
|
|
2980
|
+
const projectName = internalConfig.zip.name ?? kebabCaseAlphanumeric(
|
|
2981
|
+
(await getPackageJson(internalConfig))?.name || dirname5(process.cwd())
|
|
2982
|
+
);
|
|
2983
|
+
const applyTemplate = (template) => template.replaceAll("{{name}}", projectName).replaceAll("{{browser}}", internalConfig.browser).replaceAll(
|
|
2984
|
+
"{{version}}",
|
|
2985
|
+
output.manifest.version_name ?? output.manifest.version
|
|
2986
|
+
).replaceAll("{{manifestVersion}}", `mv${internalConfig.manifestVersion}`);
|
|
2987
|
+
await fs15.ensureDir(internalConfig.outBaseDir);
|
|
2988
|
+
const outZipFilename = applyTemplate(internalConfig.zip.artifactTemplate);
|
|
2989
|
+
const outZipPath = resolve13(internalConfig.outBaseDir, outZipFilename);
|
|
2990
|
+
await zipdir(internalConfig.outDir, {
|
|
2991
|
+
saveTo: outZipPath
|
|
2992
|
+
});
|
|
2993
|
+
zipFiles.push(outZipPath);
|
|
2994
|
+
if (internalConfig.browser === "firefox") {
|
|
2995
|
+
const sourcesZipFilename = applyTemplate(
|
|
2996
|
+
internalConfig.zip.sourcesTemplate
|
|
2997
|
+
);
|
|
2998
|
+
const sourcesZipPath = resolve13(
|
|
2999
|
+
internalConfig.outBaseDir,
|
|
3000
|
+
sourcesZipFilename
|
|
3001
|
+
);
|
|
3002
|
+
await zipdir(internalConfig.zip.sourcesRoot, {
|
|
3003
|
+
saveTo: sourcesZipPath,
|
|
3004
|
+
filter(path7) {
|
|
3005
|
+
const relativePath = relative9(internalConfig.zip.sourcesRoot, path7);
|
|
3006
|
+
const matchedPattern = internalConfig.zip.ignoredSources.find(
|
|
3007
|
+
(pattern) => minimatch2(relativePath, pattern)
|
|
3008
|
+
);
|
|
3009
|
+
return matchedPattern == null;
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
3012
|
+
zipFiles.push(sourcesZipPath);
|
|
3013
|
+
}
|
|
3014
|
+
await printFileList(
|
|
3015
|
+
internalConfig.logger.success,
|
|
3016
|
+
`Zipped extension in ${formatDuration(Date.now() - start)}`,
|
|
3017
|
+
internalConfig.outBaseDir,
|
|
3018
|
+
zipFiles
|
|
3019
|
+
);
|
|
3020
|
+
return zipFiles;
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
// src/cli.ts
|
|
3024
|
+
import consola6, { LogLevels as LogLevels2 } from "consola";
|
|
3025
|
+
var cli = cac("wxt");
|
|
3026
|
+
cli.help();
|
|
3027
|
+
cli.version(version);
|
|
3028
|
+
cli.option("--debug", "enable debug mode");
|
|
3029
|
+
cli.command("[root]", "start dev server").option("-c, --config <file>", "use specified config file").option("-m, --mode <mode>", "set env mode").option("-b, --browser <browser>", "specify a browser").option("--mv3", "target manifest v3").option("--mv2", "target manifest v2").action(
|
|
3030
|
+
wrapAction(async (root, flags) => {
|
|
3031
|
+
const server = await createServer({
|
|
3032
|
+
root,
|
|
3033
|
+
mode: flags.mode,
|
|
3034
|
+
browser: flags.browser,
|
|
3035
|
+
manifestVersion: flags.mv3 ? 3 : flags.mv2 ? 2 : void 0,
|
|
3036
|
+
configFile: flags.config,
|
|
3037
|
+
debug: flags.debug
|
|
3038
|
+
});
|
|
3039
|
+
await server.start();
|
|
3040
|
+
return { isOngoing: true };
|
|
3041
|
+
})
|
|
3042
|
+
);
|
|
3043
|
+
cli.command("build [root]", "build for production").option("-c, --config <file>", "use specified config file").option("-m, --mode <mode>", "set env mode").option("-b, --browser <browser>", "specify a browser").option("--mv3", "target manifest v3").option("--mv2", "target manifest v2").option("--analyze", "visualize extension bundle").action(
|
|
3044
|
+
wrapAction(async (root, flags) => {
|
|
3045
|
+
await build({
|
|
3046
|
+
root,
|
|
3047
|
+
mode: flags.mode,
|
|
3048
|
+
browser: flags.browser,
|
|
3049
|
+
manifestVersion: flags.mv3 ? 3 : flags.mv2 ? 2 : void 0,
|
|
3050
|
+
configFile: flags.config,
|
|
3051
|
+
debug: flags.debug,
|
|
3052
|
+
analysis: {
|
|
3053
|
+
enabled: flags.analyze
|
|
3054
|
+
}
|
|
3055
|
+
});
|
|
3056
|
+
})
|
|
3057
|
+
);
|
|
3058
|
+
cli.command("zip [root]", "build for production and zip output").option("-c, --config <file>", "use specified config file").option("-m, --mode <mode>", "set env mode").option("-b, --browser <browser>", "specify a browser").option("--mv3", "target manifest v3").option("--mv2", "target manifest v2").action(
|
|
3059
|
+
wrapAction(async (root, flags) => {
|
|
3060
|
+
await zip({
|
|
3061
|
+
root,
|
|
3062
|
+
mode: flags.mode,
|
|
3063
|
+
browser: flags.browser,
|
|
3064
|
+
manifestVersion: flags.mv3 ? 3 : flags.mv2 ? 2 : void 0,
|
|
3065
|
+
configFile: flags.config,
|
|
3066
|
+
debug: flags.debug
|
|
3067
|
+
});
|
|
3068
|
+
})
|
|
3069
|
+
);
|
|
3070
|
+
cli.command("prepare [root]", "prepare typescript project").option("-c, --config <file>", "use specified config file").action(
|
|
3071
|
+
wrapAction(async (root, flags) => {
|
|
3072
|
+
await prepare({
|
|
3073
|
+
root,
|
|
3074
|
+
configFile: flags.config,
|
|
3075
|
+
debug: flags.debug
|
|
3076
|
+
});
|
|
3077
|
+
})
|
|
3078
|
+
);
|
|
3079
|
+
cli.command("clean [root]", "clean generated files and caches").alias("cleanup").action(
|
|
3080
|
+
wrapAction(async (root, flags) => {
|
|
3081
|
+
await clean(root);
|
|
3082
|
+
})
|
|
3083
|
+
);
|
|
3084
|
+
cli.command("init [directory]", "initialize a new project").option("-t, --template <template>", "template to use").option("--pm <packageManager>", "which package manager to use").action(
|
|
3085
|
+
wrapAction(
|
|
3086
|
+
async (directory, flags) => {
|
|
3087
|
+
await initialize({
|
|
3088
|
+
directory,
|
|
3089
|
+
template: flags.template,
|
|
3090
|
+
packageManager: flags.pm
|
|
3091
|
+
});
|
|
3092
|
+
},
|
|
3093
|
+
{ disableFinishedLog: true }
|
|
3094
|
+
)
|
|
3095
|
+
);
|
|
3096
|
+
cli.parse();
|
|
3097
|
+
function wrapAction(cb, options) {
|
|
3098
|
+
return async (...args) => {
|
|
3099
|
+
const isDebug = !!args.find((arg) => arg?.debug);
|
|
3100
|
+
if (isDebug) {
|
|
3101
|
+
consola6.level = LogLevels2.debug;
|
|
3102
|
+
}
|
|
3103
|
+
const startTime = Date.now();
|
|
3104
|
+
try {
|
|
3105
|
+
printHeader();
|
|
3106
|
+
const status = await cb(...args);
|
|
3107
|
+
if (!status?.isOngoing && !options?.disableFinishedLog)
|
|
3108
|
+
consola6.success(
|
|
3109
|
+
`Finished in ${formatDuration(Date.now() - startTime)}`
|
|
3110
|
+
);
|
|
3111
|
+
} catch (err) {
|
|
3112
|
+
consola6.fail(
|
|
3113
|
+
`Command failed after ${formatDuration(Date.now() - startTime)}`
|
|
3114
|
+
);
|
|
3115
|
+
consola6.error(err);
|
|
3116
|
+
process.exit(1);
|
|
3117
|
+
}
|
|
3118
|
+
};
|
|
3119
|
+
}
|